Source code for soma.aims.graph_comparison

# -*- coding: utf-8 -*-
#  This software and supporting documentation are distributed by
#      Institut Federatif de Recherche 49
#      CEA/NeuroSpin, Batiment 145,
#      91191 Gif-sur-Yvette cedex
#      France
#
# This software is governed by the CeCILL-B license under
# French law and abiding by the rules of distribution of free software.
# You can  use, modify and/or redistribute the software under the
# terms of the CeCILL-B license as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL-B license and that you accept its terms.


from soma import aims
import six
from . import diff

rel_flx_max_diff = 1e-4
''' max accepted relative difference of float numbers
'''


[docs] def same_graphs(ref_graph, test_graph, verbose=False, max_label_diff=0): ''' Compare two graphs and return if they are identical. This function is useful for testing and validation purposes. Graphs structure are compared, and vertices / edges attributes. AIMS objects inside attributes are not compared (meshes, bucket, volumes in sulci graphs for instance) Parameters ---------- ref_graph: string or Graph object reference graph to be compared. A filename may be passed here: in this case the graph is read using aims.read() function. test_graph: string or Graph object test graph to be compared. A filename may be passed here: in this case the graph is read using aims.read() function. verbose: bool (optional, default: False) if True, messages are print on the standard output during comparison. max_label_diff: int (optional, default: 0) if graphs differ only for a limited of "label" properties, then this maximum number of mis-labelings are accepted. This is useful when stochastic or unreproducible lableing methods are used. Returns ------- True if ref and test graphs are identical, or False otherwise. ''' if isinstance(ref_graph, six.string_types): ref_graph = aims.read(ref_graph) if isinstance(test_graph, six.string_types): test_graph = aims.read(test_graph) ref_vertices = _build_vertice_dictionary(ref_graph) test_vertices = _build_vertice_dictionary(test_graph) if verbose: print("Compare vertices:") cmp_func = SulcusComparator() if max_label_diff != 0: cmp_func = FuzzySulcusComparator(max_label_diff) if not _same_dictionary(ref_vertices, test_vertices, cmp_func, verbose): if verbose: print(" differences in vertices") return False if verbose and max_label_diff != 0 and cmp_func.num_diff != 0: print('A few label differences:', cmp_func.num_diff) ref_edges = _build_edge_dictionary(ref_graph) test_edges = _build_edge_dictionary(test_graph) if verbose: print("Compare edges:") cmp_func = SulcusEdgeComparator() if not _same_dictionary(ref_edges, test_edges, cmp_func, verbose): if verbose: print(" differences in edges") return False return True
def _build_vertice_dictionary(graph): vertice_dict = {} for vertice in graph.vertices(): vertice_dict[vertice['index']] = vertice return vertice_dict def _build_edge_dictionary(graph): edge_dict = {} for edge in graph.edges(): index = (edge.vertices()[0]['index'], edge.vertices()[1]['index'], edge.getSyntax()) assert(index not in edge_dict) edge_dict[index] = edge return edge_dict def _same_dictionary(ref_dict, test_dict, same_element_function, verbose=False): if verbose: print(" first: " + str(len(ref_dict)) + " elements.") print(" second: " + str(len(test_dict)) + " elements.") if not len(ref_dict) == len(test_dict): if verbose: print(" different number of elements.") return False same_dict = True for index, ref in six.iteritems(ref_dict): same_item = True if index not in test_dict: same_item = False same_dict = False if verbose: print(" test dict has no element with index " + repr(index)) else: break if same_item: same_item = same_element_function(ref, test_dict[index], verbose) if not same_item: same_dict = False if verbose: print("difference in " + repr(index)) else: break return same_dict def _same_value(ref_value, test_value): if isinstance(ref_value, diff.TESTABLE_TYPES): return (diff.diff_objects(ref_value, test_value) == 0) elif isinstance(ref_value, float): if ref_value != test_value and \ abs(ref_value - test_value) / max(abs(ref_value), abs(test_value)) \ > rel_flx_max_diff: return False elif not isinstance(ref_value, str) and hasattr(ref_value, '__len__'): #print('comp lists', ref_value, '*/*', test_value) if len(ref_value) != len(test_value): return False return len([1 for x, y in zip(ref_value, test_value) if not _same_value(x, y)]) == 0 elif str(ref_value) != str(test_value): return False return True def _same_vertice(ref_vertice, test_vertice, verbose, ignored=None): if not len(ref_vertice) == len(test_vertice): if verbose: print(" different number of arguments " + repr(len(ref_vertice)) + " " + repr(len(test_vertice))) return False for key in ref_vertice.keys(): if ignored is not None and key in ignored: continue if key not in test_vertice: if verbose: print(repr(key) + " not in test_vertice") return False ref_value = ref_vertice[key] test_value = test_vertice[key] if not _same_value(ref_value, test_value): if verbose: print("vertex " + repr(key) + " " + str(ref_vertice[key]) + " != " + str(test_vertice[key])) return False return True class SulcusComparator: ignored = {'ss_label', 'bottom_label', 'other_label', 'Tmtktri_label'} def __call__(self, ref_vertice, test_vertice, verbose): return _same_vertice(ref_vertice, test_vertice, verbose, self.ignored) class FuzzySulcusComparator(SulcusComparator): def __init__(self, nmax): self.num_diff = 0 self.num_diff_max = nmax def __call__(self, ref_vertice, test_vertice, verbose): ref_keys = [k for k in ref_vertice.keys() if k != 'label'] test_keys = [k for k in test_vertice.keys() if k != 'label'] if not len(ref_keys) == len(test_keys): return False for key in ref_keys: if self.ignored is not None and key in self.ignored: continue if key not in test_vertice: return False ref_value = ref_vertice[key] test_value = test_vertice[key] if not _same_value(ref_value, test_value): return False ref_label = ref_vertice.get('label') test_label = test_vertice.get('label') if ref_label != test_label: self.num_diff += 1 return self.num_diff <= self.num_diff_max return True def _same_edge(ref_edge, test_edge, verbose, ignored=None): # TODO remove the duplication with same_vertice same = len(ref_edge) == len(test_edge) if not same: if verbose: print("different numbers of arguments " + repr(len(ref_edge)) + " " + repr(len(test_edge))) return False for key in ref_edge.keys(): if ignored is not None and key in ignored: continue if key not in test_edge: if verbose: print(repr(key) + " not in test_edge") return False ref_value = ref_edge[key] test_value = test_edge[key] if not _same_value(ref_value, test_value): if verbose: print("edge " + repr(key) + " " + str(ref_edge[key]) + " " + str(test_edge[key])) return False return True class SulcusEdgeComparator: ignored = {'cortical_label', 'junction_label', 'plidepassage_label'} def __call__(self, ref_vertice, test_vertice, verbose): return _same_edge(ref_vertice, test_vertice, verbose, self.ignored)