Skip to content

templates

Generic base class for template brains.

Minimally, a template should have a name and label property. For mirroring, it also needs a boundingbox.

See flybrains for an example of how to use template brains.

Source code in navis/transforms/templates.py
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
class TemplateBrain:
    """Generic base class for template brains.

    Minimally, a template should have a `name` and `label` property. For
    mirroring, it also needs a `boundingbox`.

    See [flybrains](https://github.com/navis-org/navis-flybrains) for
    an example of how to use template brains.

    """
    def __init__(self, **properties):
        """Initialize class."""
        for k, v in properties.items():
            setattr(self, k, v)

    @property
    def mesh(self):
        """Mesh represenation of this brain."""
        if not hasattr(self, '_mesh'):
            name = getattr(self, 'regName', getattr(self, 'name', None))
            raise ValueError(f'{name} does not appear to have a mesh')
        return self._mesh

Mesh represenation of this brain.

Initialize class.

Source code in navis/transforms/templates.py
1436
1437
1438
1439
def __init__(self, **properties):
    """Initialize class."""
    for k, v in properties.items():
        setattr(self, k, v)

Tracks template brains, available transforms and produces bridging sequences.

PARAMETER DESCRIPTION
scan_paths
        If True will scan paths on initialization.

TYPE: bool DEFAULT: True

Source code in navis/transforms/templates.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
class TemplateRegistry:
    """Tracks template brains, available transforms and produces bridging sequences.

    Parameters
    ----------
    scan_paths :    bool
                    If True will scan paths on initialization.

    """
    def __init__(self, scan_paths: bool = True):
        # Paths to scan for transforms
        self._transpaths = _OS_TRANSPATHS.copy()
        # Transforms
        self._transforms = []
        # Template brains
        self._templates = []

        if scan_paths:
            self.scan_paths()

    def __contains__(self, other) -> bool:
        """Check if transform is in registry.

        Parameters
        ----------
        other :     transform, filepath, tuple
                    Either a transform (e.g. CMTKtransform), a filepath (e.g.
                    to a .list file) or a tuple of `(source, target, transform)`
                    where `transform` can be a transform or a filepath.

        """
        if isinstance(other, (tuple, list)):
            return any([t == other for t in self.transforms])
        else:
            return other in [t.transform for t in self.transforms]

    def __len__(self) -> int:
        return len(self.transforms)

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return f'TemplateRegistry with {len(self)} transforms'

    @property
    def transpaths(self) -> list:
        """Paths searched for transforms.

        Use `.scan_paths` to trigger a scan. Use `.register_path` to add
        more path(s).
        """
        return self._transpaths

    @property
    def templates(self) -> list:
        """Registered template (brains)."""
        return self._templates

    @property
    def transforms(self) -> list:
        """Registered transforms (bridging + mirror)."""
        return self._transforms

    @property
    def bridges(self) -> list:
        """Registered bridging transforms."""
        return [t for t in self.transforms if t.type == 'bridging']

    @property
    def mirrors(self) ->list:
        """Registered mirror transforms."""
        return [t for t in self.transforms if t.type == 'mirror']

    def clear_caches(self):
        """Clear caches of all cached functions."""
        self.bridging_graph.cache_clear()
        self.shortest_bridging_seq.cache_clear()

    def summary(self) -> pd.DataFrame:
        """Generate summary of available transforms."""
        return pd.DataFrame(self.transforms)

    def register_path(self, paths: str, trigger_scan: bool = True):
        """Register path(s) to scan for transforms.

        Parameters
        ----------
        paths :         str | list thereof
                        Paths (or list thereof) to scans for transforms. This
                        is not permanent. For permanent additions set path(s)
                        via the `NAVIS_TRANSFORMS` environment variable.
        trigger_scan :  bool
                        If True, a re-scan of all paths will be triggered.

        """
        paths = utils.make_iterable(paths)

        for p in paths:
            # Try not to duplicate paths
            if p not in self.transpaths:
                self._transpaths.append(p)

        if trigger_scan:
            self.scan_paths()

    def register_templatebrain(self, template: 'TemplateBrain',
                               skip_existing=True):
        """Register a template brain.

        This is used, for example, by navis.mirror_brain.

        Parameters
        ----------
        template :      TemplateBrain
                        TemplateBrain to register.
        skip_existing : bool
                        If True, will skip existing template brains.

        """
        utils.eval_param(template,
                         name='template',
                         allowed_types=(TemplateBrain, ))

        if template not in self._templates or not skip_existing:
            self._templates.append(template)

    def register_transform(self, transform: BaseTransform, source: str,
                           target: str, transform_type: str,
                           skip_existing: bool = True,
                           weight: int = 1):
        """Register a transform.

        Parameters
        ----------
        transform :         subclass of BaseTransform | TransformSequence
                            A transform (AffineTransform, CMTKtransform, etc.)
                            or a TransformSequence.
        source :            str
                            Source for forward transform.
        target :            str
                            Target for forward transform. Ignored for mirror
                            transforms.
        transform_type :    "bridging" | "mirror"
                            Type of transform.
        skip_existing :     bool
                            If True will skip if transform is already in registry.
        weight :            int
                            Giving a transform a lower weight will make it
                            preferable when plotting bridging sequences.

        See Also
        --------
        register_transformfile
                            If you want to register a file instead of an
                            already constructed transform.

        """
        assert transform_type in ('bridging', 'mirror')
        assert isinstance(transform, (BaseTransform, TransformSequence))

        # Translate into edge
        edge = transform_reg(source=source, target=target, transform=transform,
                             type=transform_type,
                             invertible=hasattr(transform, '__neg__'),
                             weight=weight)

        # Don't add if already exists
        if not skip_existing or edge not in self:
            self.transforms.append(edge)

        # Clear cached functions
        self.clear_caches()

    def register_transformfile(self, path: str, **kwargs):
        """Parse and register a transform file.

        File/Directory name must follow the a `{TARGET}_{SOURCE}.{ext}`
        convention (e.g. `JRC2013_FCWB.list`).

        Parameters
        ----------
        path :          str
                        Path to transform.
        **kwargs
                        Keyword arguments are passed to the constructor of the
                        Transform (e.g. CMTKtransform for `.list` directory).

        See Also
        --------
        register_transform
                        If you want to register an already constructed transform
                        instead of a transform file that still needs to be
                        parsed.

        """
        assert isinstance(path, (str, pathlib.Path))

        path = pathlib.Path(path).expanduser()

        if not path.is_dir() and not path.is_file():
            raise ValueError(f'File/directory "{path}" does not exist')

        # Parse properties
        try:
            if 'mirror' in path.name or 'imgflip' in path.name:
                transform_type = 'mirror'
                source = path.name.split('_')[0]
                target = None
            else:
                transform_type = 'bridging'
                target = path.name.split('_')[0]
                source = path.name.split('_')[1].split('.')[0]

            # Initialize the transform
            transform = factory.factory_methods[path.suffix](path, **kwargs)

            self.register_transform(transform=transform,
                                    source=source,
                                    target=target,
                                    transform_type=transform_type)
        except BaseException as e:
            logger.error(f'Error registering {path} as transform: {str(e)}')

    def scan_paths(self, extra_paths: List[str] = None):
        """Scan registered paths for transforms and add to registry.

        Will skip transforms that already exist in this registry.

        Parameters
        ----------
        extra_paths :   list of str
                        Any Extra paths to search.

        """
        search_paths = self.transpaths

        if isinstance(extra_paths, str):
            extra_paths = [i for i in extra_paths.split(';') if len(i) > 0]
            search_paths = np.append(search_paths, extra_paths)

        for path in search_paths:
            path = pathlib.Path(path).expanduser()
            # Skip if path does not exist
            if not path.is_dir():
                continue

            # Go over the file extensions we can work with (.h5, .list, .json)
            # These file extensions are registered in the
            # `navis.transforms.factory` module
            for ext in factory.factory_methods:
                for hit in path.rglob(f'*{ext}'):
                    if hit.is_dir() or hit.is_file():
                        # Register this file
                        self.register_transformfile(hit)

        # Clear cached functions
        self.clear_caches()

    @functools.lru_cache()
    def bridging_graph(self,
                       reciprocal: Union[Literal[False], int, float] = True) -> nx.DiGraph:
        """Generate networkx Graph describing the bridging paths.

        Parameters
        ----------
        reciprocal :    bool | float
                        If True or float, will add forward and inverse edges for
                        transforms that are invertible. If float, the inverse
                        edges' weights will be scaled by that factor.

        Returns
        -------
        networkx.MultiDiGraph

        """
        # Drop mirror transforms
        bridge = [t for t in self.transforms if t.type == 'bridging']
        bridge_inv = [t for t in bridge if t.invertible]

        # Generate graph
        # Note we are using MultiDi graph here because we might
        # have multiple edges between nodes. For example, there
        # is a JFRC2013DS_JFRC2013 and a JFRC2013_JFRC2013DS
        # bridging registration. If we include the inverse, there
        # will be two edges connecting JFRC2013DS and JFRC2013 in
        # both directions
        G = nx.MultiDiGraph()
        edges = [(t.source, t.target,
                  {'transform': t.transform,
                   'type': type(t.transform).__name__,
                   'weight': t.weight}) for t in bridge]

        if reciprocal:
            if isinstance(reciprocal, numbers.Number):
                rv_edges = [(t.target, t.source,
                             {'transform': -t.transform,  # note inverse transform!
                              'type': str(type(t.transform)).split('.')[-1],
                              'weight': t.weight * reciprocal}) for t in bridge_inv]
            else:
                rv_edges = [(t.target, t.source,
                             {'transform': -t.transform,  # note inverse transform!
                              'type': str(type(t.transform)).split('.')[-1],
                              'weight': t.weight}) for t in bridge_inv]
            edges += rv_edges

        G.add_edges_from(edges)

        return G

    def find_bridging_path(self, source: str,
                           target: str,
                           via: Optional[str] = None,
                           avoid: Optional[str] = None,
                           reciprocal=True) -> tuple:
        """Find bridging path from source to target.

        Parameters
        ----------
        source :        str
                        Source from which to transform to `target`.
        target :        str
                        Target to which to transform to.
        via :           str | list thereof, optional
                        Force specific intermediate template(s).
        avoid :         str | list thereof, optional
                        Avoid going through specific intermediate template(s).
        reciprocal :    bool | float
                        If True or float, will add forward and inverse edges for
                        transforms that are invertible. If float, the inverse
                        edges' weights will be scaled by that factor.

        Returns
        -------
        path :          list
                        Path from source to target: [source, ..., target]
        transforms :    list
                        Transforms as [[path_to_transform, inverse], ...]

        """
        # Generate (or get cached) bridging graph
        G = self.bridging_graph(reciprocal=reciprocal)

        if len(G) == 0:
            raise ValueError('No bridging registrations available')

        # Do not remove the conversion to list - fuzzy matching does act up
        # otherwise
        nodes = list(G.nodes)
        if source not in nodes:
            best_match = fw.process.extractOne(source, nodes,
                                               scorer=fw.fuzz.token_sort_ratio)
            raise ValueError(f'Source "{source}" has no known bridging '
                             f'registrations. Did you mean "{best_match[0]}" '
                             'instead?')
        if target not in G.nodes:
            best_match = fw.process.extractOne(target, nodes,
                                               scorer=fw.fuzz.token_sort_ratio)
            raise ValueError(f'Target "{target}" has no known bridging '
                             f'registrations. Did you mean "{best_match[0]}" '
                             'instead?')

        if via:
            via = list(utils.make_iterable(via))  # do not remove the list() here
            for v in via:
                if v not in G.nodes:
                    best_match = fw.process.extractOne(v, nodes,
                                                       scorer=fw.fuzz.token_sort_ratio)
                    raise ValueError(f'Via "{v}" has no known bridging '
                                    f'registrations. Did you mean "{best_match[0]}" '
                                    'instead?')

        if avoid:
            avoid = list(utils.make_iterable(avoid))

        # This will raise a error message if no path is found
        if not via and not avoid:
            try:
                path = nx.shortest_path(G, source, target, weight='weight')
            except nx.NetworkXNoPath:
                raise nx.NetworkXNoPath(f'No bridging path connecting {source} '
                                        f'and {target} found.')
        else:
            # Go through all possible paths and find one that...
            found_any = False  # track if we found any path
            found_good = False  # track if we found a path matching the criteria
            for path in nx.all_simple_paths(G, source, target):
                found_any = True
                # ... has all `via`s...
                if via and all([v in path for v in via]):
                    # ... and none of the `avoid`
                    if avoid:
                        if not any([v in path for v in avoid]):
                            found_good = True
                            break
                    else:
                        found_good = True
                        break
                # If we only have `avoid` but no `via`
                elif avoid and not any([v in path for v in avoid]):
                    found_good = True
                    break

            if not found_any:
                raise nx.NetworkXNoPath(f'No bridging path connecting {source} '
                                        f'and {target} found.')
            elif not found_good:
                if via and avoid:
                    raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                            f'and {target} via "{via}" and '
                                            f'avoiding "{avoid}" found')
                elif via:
                    raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                            f'and {target} via "{via}" found.')
                else:
                    raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                            f'and {target} avoiding "{avoid}" found.')

        # `path` holds the sequence of nodes we are traversing but not which
        # transforms (i.e. edges) to use
        transforms = []
        for n1, n2 in zip(path[:-1], path[1:]):
            this_edges = []
            i = 0
            # First collect all edges between those two nodes
            # - this is annoyingly complicated with MultiDiGraphs
            while True:
                try:
                    e = G.edges[(n1, n2, i)]
                except KeyError:
                    break
                this_edges.append([e['transform'], e['weight']])
                i += 1

            # Now find the edge with the highest weight
            # (inverse transforms might have a lower weight)
            this_edges = sorted(this_edges, key=lambda x: x[-1])
            transforms.append(this_edges[-1][0])

        return path, transforms

    def find_all_bridging_paths(self, source: str,
                                target: str,
                                via: Optional[str] = None,
                                avoid: Optional[str] = None,
                                reciprocal: bool = True,
                                cutoff: int = None) -> tuple:
        """Find all bridging paths from source to target.

        Parameters
        ----------
        source :        str
                        Source from which to transform to `target`.
        target :        str
                        Target to which to transform to.
        via :           str | list thereof, optional
                        Force specific intermediate template(s).
        avoid :         str | list thereof, optional
                        Avoid specific intermediate template(s).
        reciprocal :    bool | float
                        If True or float, will add forward and inverse edges for
                        transforms that are invertible. If float, the inverse
                        edges' weights will be scaled by that factor.
        cutoff :        int, optional
                        Depth to stop the search. Only paths of length
                        <= cutoff are returned.

        Returns
        -------

        path :          list
                        Path from source to target: [source, ..., target]
        transforms :    list
                        Transforms as [[path_to_transform, inverse], ...]

        """
        # Generate (or get cached) bridging graph
        G = self.bridging_graph(reciprocal=reciprocal)

        if len(G) == 0:
            raise ValueError('No bridging registrations available')

        # Do not remove the conversion to list - fuzzy matching does act up
        # otherwise
        nodes = list(G.nodes)
        if source not in nodes:
            best_match = fw.process.extractOne(source, nodes,
                                               scorer=fw.fuzz.token_sort_ratio)
            raise ValueError(f'Source "{source}" has no known bridging '
                             f'registrations. Did you mean "{best_match[0]}" '
                             'instead?')
        if target not in G.nodes:
            best_match = fw.process.extractOne(target, nodes,
                                               scorer=fw.fuzz.token_sort_ratio)
            raise ValueError(f'Target "{target}" has no known bridging '
                             f'registrations. Did you mean "{best_match[0]}" '
                             'instead?')

        if via and via not in G.nodes:
            best_match = fw.process.extractOne(via, nodes,
                                               scorer=fw.fuzz.token_sort_ratio)
            raise ValueError(f'Via "{via}" has no known bridging '
                             f'registrations. Did you mean "{best_match[0]}" '
                             'instead?')

        # This will raise a error message if no path is found
        for path in nx.all_simple_paths(G, source, target, cutoff=cutoff):
            # Skip paths that don't contain `via`
            if isinstance(via, str) and (via not in path):
                continue
            elif isinstance(via, (list, tuple, np.ndarray)) and not all([v in path for v in via]):
                continue

            # Skip paths that contain `avoid`
            if isinstance(avoid, str) and (avoid in path):
                continue
            elif isinstance(avoid, (list, tuple, np.ndarray)) and any([v in path for v in avoid]):
                continue

            # `path` holds the sequence of nodes we are traversing but not which
            # transforms (i.e. edges) to use
            transforms = []
            for n1, n2 in zip(path[:-1], path[1:]):
                this_edges = []
                i = 0
                # First collect all edges between those two nodes
                # - this is annoyingly complicated with MultiDiGraphs
                while True:
                    try:
                        e = G.edges[(n1, n2, i)]
                    except KeyError:
                        break
                    this_edges.append([e['transform'], e['weight']])
                    i += 1

                # Now find the edge with the highest weight
                # (inverse transforms might have a lower weight)
                this_edges = sorted(this_edges, key=lambda x: x[-1])
                transforms.append(this_edges[-1][0])

            yield path, transforms

    @functools.lru_cache()
    def shortest_bridging_seq(self, source: str, target: str,
                              via: Optional[str] = None,
                              inverse_weight: float = .5) -> tuple:
        """Find shortest bridging sequence to get from source to target.

        Parameters
        ----------
        source :            str
                            Source from which to transform to `target`.
        target :            str
                            Target to which to transform to.
        via :               str | list of str
                            Waystations to traverse on the way from source to
                            target.
        inverse_weight :    float
                            Weight for inverse transforms. If < 1 will prefer
                            forward transforms.

        Returns
        -------
        sequence :          (N, ) array
                            Sequence of registrations that will be traversed.
        transform_seq :     TransformSequence
                            Class that collates the required transforms to get
                            from source to target.

        """
        # Generate sequence of nodes we need to find a path for
        # Minimally it's just from source to target
        nodes = np.array([source, target])

        if via:
            nodes = np.insert(nodes, 1, via)

        seq = [nodes[0]]
        transforms = []
        for n1, n2 in zip(nodes[:-1], nodes[1:]):
            path, tr = self.find_bridging_path(n1, n2, reciprocal=inverse_weight)
            seq = np.append(seq, path[1:])
            transforms = np.append(transforms, tr)

        if any(np.unique(seq, return_counts=True)[1] > 1):
            logger.warning('Bridging sequence contains loop: '
                           f'{"->".join(seq)}')

        # Generate the transform sequence
        transform_seq = TransformSequence(*transforms)

        return seq, transform_seq

    def find_mirror_reg(self, template: str, non_found: str = 'raise') -> tuple:
        """Search for a mirror transformation for given template.

        Typically a mirror transformation specifies a non-rigid transformation
        to correct asymmetries in an image.

        Parameters
        ----------
        template :  str
                    Name of the template to find a mirror transformation for.
        non_found : "raise" | "ignore"
                    What to do if no mirror transformation is found. If "ignore"
                    and no mirror transformation found, will silently return
                    `None`.

        Returns
        -------
        tuple
                    Named tuple containing a mirror transformation. Will only
                    ever return one - even if multiple are available.

        """
        for tr in self.mirrors:
            if tr.source == template:
                return tr

        if non_found == 'raise':
            raise ValueError(f'No mirror transformation found for {template}')
        return None

    def find_closest_mirror_reg(self, template: str, non_found: str = 'raise') -> str:
        """Search for the closest mirror transformation for given template.

        Typically a mirror transformation specifies a non-rigid transformation
        to correct asymmetries in an image.

        Parameters
        ----------
        template :  str
                    Name of the template to find a mirror transformation for.
        non_found : "raise" | "ignore"
                    What to do if there is no path to a mirror transformation.
                    If "ignore" and no path is found, will silently return
                    `None`.

        Returns
        -------
        str
                    Name of the closest template with a mirror transform.

        """
        # Templates with mirror registrations
        temps_w_mirrors = [t.source for t in self.mirrors]

        # Add symmetrical template brains
        temps_w_mirrors += [t.label for t in self.templates if getattr(t, 'symmetrical', False) == True]

        if not temps_w_mirrors:
            raise ValueError('No mirror transformations registered')

        # If this template has a mirror registration:
        if template in temps_w_mirrors:
            return template

        # Get bridging graph
        G = self.bridging_graph()

        if template not in G.nodes:
            raise ValueError(f'"{template}" does not appear to be a registered '
                             'template')

        # Get path lengths from template to all other nodes
        pl = nx.single_source_dijkstra_path_length(G, template)

        # Subset to targets that have a mirror reg
        pl = {k: v for k, v in pl.items() if k in temps_w_mirrors}

        # Find the closest mirror
        cl = sorted(pl.keys(), key=lambda x: pl[x])

        # If any, return the closests
        if cl:
            return cl[0]

        if non_found == 'raise':
            raise ValueError(f'No path to a mirror transformation found for "{template}"')

        return None

    def find_template(self, name: str, non_found: str = 'raise') -> 'TemplateBrain':
        """Search for a given template (brain).

        Parameters
        ----------
        name :      str
                    Name of the template to find a mirror transformation for.
                    Searches against `name` and `label` (short name) properties
                    of registered templates.
        non_found : "raise" | "ignore"
                    What to do if no mirror transformation is found. If "ignore"
                    and no mirror transformation found, will silently return
                    `None`.

        Returns
        -------
        TemplateBrain

        """
        for tmp in self.templates:
            if getattr(tmp, 'label', None) == name:
                return tmp
            if getattr(tmp, 'name', None) == name:
                return tmp

        if non_found == 'raise':
            raise ValueError(f'No template brain registered that matches "{name}"')
        return None

    def plot_bridging_graph(self, **kwargs):
        """Draw bridging graph using networkX.

        Parameters
        ----------
        **kwargs
                    Keyword arguments are passed to `networkx.draw_networkx`.

        Returns
        -------
        None

        """
        # Get graph
        G = self.bridging_graph(reciprocal=False)

        # Draw nodes and edges
        node_labels = {n: n for n in G.nodes}
        pos = nx.kamada_kawai_layout(G)

        # Draw all nodes
        nx.draw_networkx_nodes(G, pos=pos, node_color='lightgrey',
                               node_shape='o', node_size=300)
        nx.draw_networkx_labels(G, pos=pos, labels=node_labels,
                                font_color='k', font_size=10)

        # Draw edges by type of transform
        edge_types = set([e[2]['type'] for e in G.edges(data=True)])

        lines = []
        labels = []
        for t, c in zip(edge_types,
                        sns.color_palette('muted', len(edge_types))):
            subset = [e for e in G.edges(data=True) if e[2]['type'] == t]
            nx.draw_networkx_edges(G, pos=pos, edgelist=subset,
                                   edge_color=mcl.to_hex(c), width=1.5)
            lines.append(Line2D([0], [0], color=c, linewidth=2, linestyle='-'))
            labels.append(t)

        plt.legend(lines, labels)

Registered bridging transforms.

Registered mirror transforms.

Registered template (brains).

Registered transforms (bridging + mirror).

Paths searched for transforms.

Use .scan_paths to trigger a scan. Use .register_path to add more path(s).

Generate networkx Graph describing the bridging paths.

PARAMETER DESCRIPTION
reciprocal
        If True or float, will add forward and inverse edges for
        transforms that are invertible. If float, the inverse
        edges' weights will be scaled by that factor.

TYPE: bool | float DEFAULT: True

RETURNS DESCRIPTION
networkx.MultiDiGraph
Source code in navis/transforms/templates.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
@functools.lru_cache()
def bridging_graph(self,
                   reciprocal: Union[Literal[False], int, float] = True) -> nx.DiGraph:
    """Generate networkx Graph describing the bridging paths.

    Parameters
    ----------
    reciprocal :    bool | float
                    If True or float, will add forward and inverse edges for
                    transforms that are invertible. If float, the inverse
                    edges' weights will be scaled by that factor.

    Returns
    -------
    networkx.MultiDiGraph

    """
    # Drop mirror transforms
    bridge = [t for t in self.transforms if t.type == 'bridging']
    bridge_inv = [t for t in bridge if t.invertible]

    # Generate graph
    # Note we are using MultiDi graph here because we might
    # have multiple edges between nodes. For example, there
    # is a JFRC2013DS_JFRC2013 and a JFRC2013_JFRC2013DS
    # bridging registration. If we include the inverse, there
    # will be two edges connecting JFRC2013DS and JFRC2013 in
    # both directions
    G = nx.MultiDiGraph()
    edges = [(t.source, t.target,
              {'transform': t.transform,
               'type': type(t.transform).__name__,
               'weight': t.weight}) for t in bridge]

    if reciprocal:
        if isinstance(reciprocal, numbers.Number):
            rv_edges = [(t.target, t.source,
                         {'transform': -t.transform,  # note inverse transform!
                          'type': str(type(t.transform)).split('.')[-1],
                          'weight': t.weight * reciprocal}) for t in bridge_inv]
        else:
            rv_edges = [(t.target, t.source,
                         {'transform': -t.transform,  # note inverse transform!
                          'type': str(type(t.transform)).split('.')[-1],
                          'weight': t.weight}) for t in bridge_inv]
        edges += rv_edges

    G.add_edges_from(edges)

    return G

Clear caches of all cached functions.

Source code in navis/transforms/templates.py
142
143
144
145
def clear_caches(self):
    """Clear caches of all cached functions."""
    self.bridging_graph.cache_clear()
    self.shortest_bridging_seq.cache_clear()

Find all bridging paths from source to target.

PARAMETER DESCRIPTION
source
        Source from which to transform to `target`.

TYPE: str

target
        Target to which to transform to.

TYPE: str

via
        Force specific intermediate template(s).

TYPE: str | list thereof DEFAULT: None

avoid
        Avoid specific intermediate template(s).

TYPE: str | list thereof DEFAULT: None

reciprocal
        If True or float, will add forward and inverse edges for
        transforms that are invertible. If float, the inverse
        edges' weights will be scaled by that factor.

TYPE: bool | float DEFAULT: True

cutoff
        Depth to stop the search. Only paths of length
        <= cutoff are returned.

TYPE: int DEFAULT: None

RETURNS DESCRIPTION
path

Path from source to target: [source, ..., target]

TYPE: list

transforms

Transforms as [[path_to_transform, inverse], ...]

TYPE: list

Source code in navis/transforms/templates.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
def find_all_bridging_paths(self, source: str,
                            target: str,
                            via: Optional[str] = None,
                            avoid: Optional[str] = None,
                            reciprocal: bool = True,
                            cutoff: int = None) -> tuple:
    """Find all bridging paths from source to target.

    Parameters
    ----------
    source :        str
                    Source from which to transform to `target`.
    target :        str
                    Target to which to transform to.
    via :           str | list thereof, optional
                    Force specific intermediate template(s).
    avoid :         str | list thereof, optional
                    Avoid specific intermediate template(s).
    reciprocal :    bool | float
                    If True or float, will add forward and inverse edges for
                    transforms that are invertible. If float, the inverse
                    edges' weights will be scaled by that factor.
    cutoff :        int, optional
                    Depth to stop the search. Only paths of length
                    <= cutoff are returned.

    Returns
    -------

    path :          list
                    Path from source to target: [source, ..., target]
    transforms :    list
                    Transforms as [[path_to_transform, inverse], ...]

    """
    # Generate (or get cached) bridging graph
    G = self.bridging_graph(reciprocal=reciprocal)

    if len(G) == 0:
        raise ValueError('No bridging registrations available')

    # Do not remove the conversion to list - fuzzy matching does act up
    # otherwise
    nodes = list(G.nodes)
    if source not in nodes:
        best_match = fw.process.extractOne(source, nodes,
                                           scorer=fw.fuzz.token_sort_ratio)
        raise ValueError(f'Source "{source}" has no known bridging '
                         f'registrations. Did you mean "{best_match[0]}" '
                         'instead?')
    if target not in G.nodes:
        best_match = fw.process.extractOne(target, nodes,
                                           scorer=fw.fuzz.token_sort_ratio)
        raise ValueError(f'Target "{target}" has no known bridging '
                         f'registrations. Did you mean "{best_match[0]}" '
                         'instead?')

    if via and via not in G.nodes:
        best_match = fw.process.extractOne(via, nodes,
                                           scorer=fw.fuzz.token_sort_ratio)
        raise ValueError(f'Via "{via}" has no known bridging '
                         f'registrations. Did you mean "{best_match[0]}" '
                         'instead?')

    # This will raise a error message if no path is found
    for path in nx.all_simple_paths(G, source, target, cutoff=cutoff):
        # Skip paths that don't contain `via`
        if isinstance(via, str) and (via not in path):
            continue
        elif isinstance(via, (list, tuple, np.ndarray)) and not all([v in path for v in via]):
            continue

        # Skip paths that contain `avoid`
        if isinstance(avoid, str) and (avoid in path):
            continue
        elif isinstance(avoid, (list, tuple, np.ndarray)) and any([v in path for v in avoid]):
            continue

        # `path` holds the sequence of nodes we are traversing but not which
        # transforms (i.e. edges) to use
        transforms = []
        for n1, n2 in zip(path[:-1], path[1:]):
            this_edges = []
            i = 0
            # First collect all edges between those two nodes
            # - this is annoyingly complicated with MultiDiGraphs
            while True:
                try:
                    e = G.edges[(n1, n2, i)]
                except KeyError:
                    break
                this_edges.append([e['transform'], e['weight']])
                i += 1

            # Now find the edge with the highest weight
            # (inverse transforms might have a lower weight)
            this_edges = sorted(this_edges, key=lambda x: x[-1])
            transforms.append(this_edges[-1][0])

        yield path, transforms

Find bridging path from source to target.

PARAMETER DESCRIPTION
source
        Source from which to transform to `target`.

TYPE: str

target
        Target to which to transform to.

TYPE: str

via
        Force specific intermediate template(s).

TYPE: str | list thereof DEFAULT: None

avoid
        Avoid going through specific intermediate template(s).

TYPE: str | list thereof DEFAULT: None

reciprocal
        If True or float, will add forward and inverse edges for
        transforms that are invertible. If float, the inverse
        edges' weights will be scaled by that factor.

TYPE: bool | float DEFAULT: True

RETURNS DESCRIPTION
path

Path from source to target: [source, ..., target]

TYPE: list

transforms

Transforms as [[path_to_transform, inverse], ...]

TYPE: list

Source code in navis/transforms/templates.py
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
def find_bridging_path(self, source: str,
                       target: str,
                       via: Optional[str] = None,
                       avoid: Optional[str] = None,
                       reciprocal=True) -> tuple:
    """Find bridging path from source to target.

    Parameters
    ----------
    source :        str
                    Source from which to transform to `target`.
    target :        str
                    Target to which to transform to.
    via :           str | list thereof, optional
                    Force specific intermediate template(s).
    avoid :         str | list thereof, optional
                    Avoid going through specific intermediate template(s).
    reciprocal :    bool | float
                    If True or float, will add forward and inverse edges for
                    transforms that are invertible. If float, the inverse
                    edges' weights will be scaled by that factor.

    Returns
    -------
    path :          list
                    Path from source to target: [source, ..., target]
    transforms :    list
                    Transforms as [[path_to_transform, inverse], ...]

    """
    # Generate (or get cached) bridging graph
    G = self.bridging_graph(reciprocal=reciprocal)

    if len(G) == 0:
        raise ValueError('No bridging registrations available')

    # Do not remove the conversion to list - fuzzy matching does act up
    # otherwise
    nodes = list(G.nodes)
    if source not in nodes:
        best_match = fw.process.extractOne(source, nodes,
                                           scorer=fw.fuzz.token_sort_ratio)
        raise ValueError(f'Source "{source}" has no known bridging '
                         f'registrations. Did you mean "{best_match[0]}" '
                         'instead?')
    if target not in G.nodes:
        best_match = fw.process.extractOne(target, nodes,
                                           scorer=fw.fuzz.token_sort_ratio)
        raise ValueError(f'Target "{target}" has no known bridging '
                         f'registrations. Did you mean "{best_match[0]}" '
                         'instead?')

    if via:
        via = list(utils.make_iterable(via))  # do not remove the list() here
        for v in via:
            if v not in G.nodes:
                best_match = fw.process.extractOne(v, nodes,
                                                   scorer=fw.fuzz.token_sort_ratio)
                raise ValueError(f'Via "{v}" has no known bridging '
                                f'registrations. Did you mean "{best_match[0]}" '
                                'instead?')

    if avoid:
        avoid = list(utils.make_iterable(avoid))

    # This will raise a error message if no path is found
    if not via and not avoid:
        try:
            path = nx.shortest_path(G, source, target, weight='weight')
        except nx.NetworkXNoPath:
            raise nx.NetworkXNoPath(f'No bridging path connecting {source} '
                                    f'and {target} found.')
    else:
        # Go through all possible paths and find one that...
        found_any = False  # track if we found any path
        found_good = False  # track if we found a path matching the criteria
        for path in nx.all_simple_paths(G, source, target):
            found_any = True
            # ... has all `via`s...
            if via and all([v in path for v in via]):
                # ... and none of the `avoid`
                if avoid:
                    if not any([v in path for v in avoid]):
                        found_good = True
                        break
                else:
                    found_good = True
                    break
            # If we only have `avoid` but no `via`
            elif avoid and not any([v in path for v in avoid]):
                found_good = True
                break

        if not found_any:
            raise nx.NetworkXNoPath(f'No bridging path connecting {source} '
                                    f'and {target} found.')
        elif not found_good:
            if via and avoid:
                raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                        f'and {target} via "{via}" and '
                                        f'avoiding "{avoid}" found')
            elif via:
                raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                        f'and {target} via "{via}" found.')
            else:
                raise nx.NetworkXNoPath(f'No bridging path connecting {source}'
                                        f'and {target} avoiding "{avoid}" found.')

    # `path` holds the sequence of nodes we are traversing but not which
    # transforms (i.e. edges) to use
    transforms = []
    for n1, n2 in zip(path[:-1], path[1:]):
        this_edges = []
        i = 0
        # First collect all edges between those two nodes
        # - this is annoyingly complicated with MultiDiGraphs
        while True:
            try:
                e = G.edges[(n1, n2, i)]
            except KeyError:
                break
            this_edges.append([e['transform'], e['weight']])
            i += 1

        # Now find the edge with the highest weight
        # (inverse transforms might have a lower weight)
        this_edges = sorted(this_edges, key=lambda x: x[-1])
        transforms.append(this_edges[-1][0])

    return path, transforms

Search for the closest mirror transformation for given template.

Typically a mirror transformation specifies a non-rigid transformation to correct asymmetries in an image.

PARAMETER DESCRIPTION
template
    Name of the template to find a mirror transformation for.

TYPE: str

non_found
    What to do if there is no path to a mirror transformation.
    If "ignore" and no path is found, will silently return
    `None`.

TYPE: 'raise' | ignore DEFAULT: 'raise'

RETURNS DESCRIPTION
str

Name of the closest template with a mirror transform.

Source code in navis/transforms/templates.py
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
def find_closest_mirror_reg(self, template: str, non_found: str = 'raise') -> str:
    """Search for the closest mirror transformation for given template.

    Typically a mirror transformation specifies a non-rigid transformation
    to correct asymmetries in an image.

    Parameters
    ----------
    template :  str
                Name of the template to find a mirror transformation for.
    non_found : "raise" | "ignore"
                What to do if there is no path to a mirror transformation.
                If "ignore" and no path is found, will silently return
                `None`.

    Returns
    -------
    str
                Name of the closest template with a mirror transform.

    """
    # Templates with mirror registrations
    temps_w_mirrors = [t.source for t in self.mirrors]

    # Add symmetrical template brains
    temps_w_mirrors += [t.label for t in self.templates if getattr(t, 'symmetrical', False) == True]

    if not temps_w_mirrors:
        raise ValueError('No mirror transformations registered')

    # If this template has a mirror registration:
    if template in temps_w_mirrors:
        return template

    # Get bridging graph
    G = self.bridging_graph()

    if template not in G.nodes:
        raise ValueError(f'"{template}" does not appear to be a registered '
                         'template')

    # Get path lengths from template to all other nodes
    pl = nx.single_source_dijkstra_path_length(G, template)

    # Subset to targets that have a mirror reg
    pl = {k: v for k, v in pl.items() if k in temps_w_mirrors}

    # Find the closest mirror
    cl = sorted(pl.keys(), key=lambda x: pl[x])

    # If any, return the closests
    if cl:
        return cl[0]

    if non_found == 'raise':
        raise ValueError(f'No path to a mirror transformation found for "{template}"')

    return None

Search for a mirror transformation for given template.

Typically a mirror transformation specifies a non-rigid transformation to correct asymmetries in an image.

PARAMETER DESCRIPTION
template
    Name of the template to find a mirror transformation for.

TYPE: str

non_found
    What to do if no mirror transformation is found. If "ignore"
    and no mirror transformation found, will silently return
    `None`.

TYPE: 'raise' | ignore DEFAULT: 'raise'

RETURNS DESCRIPTION
tuple

Named tuple containing a mirror transformation. Will only ever return one - even if multiple are available.

Source code in navis/transforms/templates.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
def find_mirror_reg(self, template: str, non_found: str = 'raise') -> tuple:
    """Search for a mirror transformation for given template.

    Typically a mirror transformation specifies a non-rigid transformation
    to correct asymmetries in an image.

    Parameters
    ----------
    template :  str
                Name of the template to find a mirror transformation for.
    non_found : "raise" | "ignore"
                What to do if no mirror transformation is found. If "ignore"
                and no mirror transformation found, will silently return
                `None`.

    Returns
    -------
    tuple
                Named tuple containing a mirror transformation. Will only
                ever return one - even if multiple are available.

    """
    for tr in self.mirrors:
        if tr.source == template:
            return tr

    if non_found == 'raise':
        raise ValueError(f'No mirror transformation found for {template}')
    return None

Search for a given template (brain).

PARAMETER DESCRIPTION
name
    Name of the template to find a mirror transformation for.
    Searches against `name` and `label` (short name) properties
    of registered templates.

TYPE: str

non_found
    What to do if no mirror transformation is found. If "ignore"
    and no mirror transformation found, will silently return
    `None`.

TYPE: 'raise' | ignore DEFAULT: 'raise'

RETURNS DESCRIPTION
TemplateBrain
Source code in navis/transforms/templates.py
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
def find_template(self, name: str, non_found: str = 'raise') -> 'TemplateBrain':
    """Search for a given template (brain).

    Parameters
    ----------
    name :      str
                Name of the template to find a mirror transformation for.
                Searches against `name` and `label` (short name) properties
                of registered templates.
    non_found : "raise" | "ignore"
                What to do if no mirror transformation is found. If "ignore"
                and no mirror transformation found, will silently return
                `None`.

    Returns
    -------
    TemplateBrain

    """
    for tmp in self.templates:
        if getattr(tmp, 'label', None) == name:
            return tmp
        if getattr(tmp, 'name', None) == name:
            return tmp

    if non_found == 'raise':
        raise ValueError(f'No template brain registered that matches "{name}"')
    return None

Draw bridging graph using networkX.

PARAMETER DESCRIPTION
**kwargs
    Keyword arguments are passed to `networkx.draw_networkx`.

DEFAULT: {}

RETURNS DESCRIPTION
None
Source code in navis/transforms/templates.py
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
def plot_bridging_graph(self, **kwargs):
    """Draw bridging graph using networkX.

    Parameters
    ----------
    **kwargs
                Keyword arguments are passed to `networkx.draw_networkx`.

    Returns
    -------
    None

    """
    # Get graph
    G = self.bridging_graph(reciprocal=False)

    # Draw nodes and edges
    node_labels = {n: n for n in G.nodes}
    pos = nx.kamada_kawai_layout(G)

    # Draw all nodes
    nx.draw_networkx_nodes(G, pos=pos, node_color='lightgrey',
                           node_shape='o', node_size=300)
    nx.draw_networkx_labels(G, pos=pos, labels=node_labels,
                            font_color='k', font_size=10)

    # Draw edges by type of transform
    edge_types = set([e[2]['type'] for e in G.edges(data=True)])

    lines = []
    labels = []
    for t, c in zip(edge_types,
                    sns.color_palette('muted', len(edge_types))):
        subset = [e for e in G.edges(data=True) if e[2]['type'] == t]
        nx.draw_networkx_edges(G, pos=pos, edgelist=subset,
                               edge_color=mcl.to_hex(c), width=1.5)
        lines.append(Line2D([0], [0], color=c, linewidth=2, linestyle='-'))
        labels.append(t)

    plt.legend(lines, labels)

Register path(s) to scan for transforms.

PARAMETER DESCRIPTION
paths
        Paths (or list thereof) to scans for transforms. This
        is not permanent. For permanent additions set path(s)
        via the `NAVIS_TRANSFORMS` environment variable.

TYPE: str | list thereof

trigger_scan
        If True, a re-scan of all paths will be triggered.

TYPE: bool DEFAULT: True

Source code in navis/transforms/templates.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def register_path(self, paths: str, trigger_scan: bool = True):
    """Register path(s) to scan for transforms.

    Parameters
    ----------
    paths :         str | list thereof
                    Paths (or list thereof) to scans for transforms. This
                    is not permanent. For permanent additions set path(s)
                    via the `NAVIS_TRANSFORMS` environment variable.
    trigger_scan :  bool
                    If True, a re-scan of all paths will be triggered.

    """
    paths = utils.make_iterable(paths)

    for p in paths:
        # Try not to duplicate paths
        if p not in self.transpaths:
            self._transpaths.append(p)

    if trigger_scan:
        self.scan_paths()

Register a template brain.

This is used, for example, by navis.mirror_brain.

PARAMETER DESCRIPTION
template
        TemplateBrain to register.

TYPE: TemplateBrain

skip_existing
        If True, will skip existing template brains.

TYPE: bool DEFAULT: True

Source code in navis/transforms/templates.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def register_templatebrain(self, template: 'TemplateBrain',
                           skip_existing=True):
    """Register a template brain.

    This is used, for example, by navis.mirror_brain.

    Parameters
    ----------
    template :      TemplateBrain
                    TemplateBrain to register.
    skip_existing : bool
                    If True, will skip existing template brains.

    """
    utils.eval_param(template,
                     name='template',
                     allowed_types=(TemplateBrain, ))

    if template not in self._templates or not skip_existing:
        self._templates.append(template)

Register a transform.

PARAMETER DESCRIPTION
transform
            A transform (AffineTransform, CMTKtransform, etc.)
            or a TransformSequence.

TYPE: subclass of BaseTransform | TransformSequence

source
            Source for forward transform.

TYPE: str

target
            Target for forward transform. Ignored for mirror
            transforms.

TYPE: str

transform_type
            Type of transform.

TYPE: "bridging" | "mirror"

skip_existing
            If True will skip if transform is already in registry.

TYPE: bool DEFAULT: True

weight
            Giving a transform a lower weight will make it
            preferable when plotting bridging sequences.

TYPE: int DEFAULT: 1

See Also

register_transformfile If you want to register a file instead of an already constructed transform.

Source code in navis/transforms/templates.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def register_transform(self, transform: BaseTransform, source: str,
                       target: str, transform_type: str,
                       skip_existing: bool = True,
                       weight: int = 1):
    """Register a transform.

    Parameters
    ----------
    transform :         subclass of BaseTransform | TransformSequence
                        A transform (AffineTransform, CMTKtransform, etc.)
                        or a TransformSequence.
    source :            str
                        Source for forward transform.
    target :            str
                        Target for forward transform. Ignored for mirror
                        transforms.
    transform_type :    "bridging" | "mirror"
                        Type of transform.
    skip_existing :     bool
                        If True will skip if transform is already in registry.
    weight :            int
                        Giving a transform a lower weight will make it
                        preferable when plotting bridging sequences.

    See Also
    --------
    register_transformfile
                        If you want to register a file instead of an
                        already constructed transform.

    """
    assert transform_type in ('bridging', 'mirror')
    assert isinstance(transform, (BaseTransform, TransformSequence))

    # Translate into edge
    edge = transform_reg(source=source, target=target, transform=transform,
                         type=transform_type,
                         invertible=hasattr(transform, '__neg__'),
                         weight=weight)

    # Don't add if already exists
    if not skip_existing or edge not in self:
        self.transforms.append(edge)

    # Clear cached functions
    self.clear_caches()

Parse and register a transform file.

File/Directory name must follow the a {TARGET}_{SOURCE}.{ext} convention (e.g. JRC2013_FCWB.list).

PARAMETER DESCRIPTION
path
        Path to transform.

TYPE: str

**kwargs
        Keyword arguments are passed to the constructor of the
        Transform (e.g. CMTKtransform for `.list` directory).

DEFAULT: {}

See Also

register_transform If you want to register an already constructed transform instead of a transform file that still needs to be parsed.

Source code in navis/transforms/templates.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def register_transformfile(self, path: str, **kwargs):
    """Parse and register a transform file.

    File/Directory name must follow the a `{TARGET}_{SOURCE}.{ext}`
    convention (e.g. `JRC2013_FCWB.list`).

    Parameters
    ----------
    path :          str
                    Path to transform.
    **kwargs
                    Keyword arguments are passed to the constructor of the
                    Transform (e.g. CMTKtransform for `.list` directory).

    See Also
    --------
    register_transform
                    If you want to register an already constructed transform
                    instead of a transform file that still needs to be
                    parsed.

    """
    assert isinstance(path, (str, pathlib.Path))

    path = pathlib.Path(path).expanduser()

    if not path.is_dir() and not path.is_file():
        raise ValueError(f'File/directory "{path}" does not exist')

    # Parse properties
    try:
        if 'mirror' in path.name or 'imgflip' in path.name:
            transform_type = 'mirror'
            source = path.name.split('_')[0]
            target = None
        else:
            transform_type = 'bridging'
            target = path.name.split('_')[0]
            source = path.name.split('_')[1].split('.')[0]

        # Initialize the transform
        transform = factory.factory_methods[path.suffix](path, **kwargs)

        self.register_transform(transform=transform,
                                source=source,
                                target=target,
                                transform_type=transform_type)
    except BaseException as e:
        logger.error(f'Error registering {path} as transform: {str(e)}')

Scan registered paths for transforms and add to registry.

Will skip transforms that already exist in this registry.

PARAMETER DESCRIPTION
extra_paths
        Any Extra paths to search.

TYPE: list of str DEFAULT: None

Source code in navis/transforms/templates.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
def scan_paths(self, extra_paths: List[str] = None):
    """Scan registered paths for transforms and add to registry.

    Will skip transforms that already exist in this registry.

    Parameters
    ----------
    extra_paths :   list of str
                    Any Extra paths to search.

    """
    search_paths = self.transpaths

    if isinstance(extra_paths, str):
        extra_paths = [i for i in extra_paths.split(';') if len(i) > 0]
        search_paths = np.append(search_paths, extra_paths)

    for path in search_paths:
        path = pathlib.Path(path).expanduser()
        # Skip if path does not exist
        if not path.is_dir():
            continue

        # Go over the file extensions we can work with (.h5, .list, .json)
        # These file extensions are registered in the
        # `navis.transforms.factory` module
        for ext in factory.factory_methods:
            for hit in path.rglob(f'*{ext}'):
                if hit.is_dir() or hit.is_file():
                    # Register this file
                    self.register_transformfile(hit)

    # Clear cached functions
    self.clear_caches()

Find shortest bridging sequence to get from source to target.

PARAMETER DESCRIPTION
source
            Source from which to transform to `target`.

TYPE: str

target
            Target to which to transform to.

TYPE: str

via
            Waystations to traverse on the way from source to
            target.

TYPE: str | list of str DEFAULT: None

inverse_weight
            Weight for inverse transforms. If < 1 will prefer
            forward transforms.

TYPE: float DEFAULT: 0.5

RETURNS DESCRIPTION
sequence

Sequence of registrations that will be traversed.

TYPE: (N, ) array

transform_seq

Class that collates the required transforms to get from source to target.

TYPE: TransformSequence

Source code in navis/transforms/templates.py
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
@functools.lru_cache()
def shortest_bridging_seq(self, source: str, target: str,
                          via: Optional[str] = None,
                          inverse_weight: float = .5) -> tuple:
    """Find shortest bridging sequence to get from source to target.

    Parameters
    ----------
    source :            str
                        Source from which to transform to `target`.
    target :            str
                        Target to which to transform to.
    via :               str | list of str
                        Waystations to traverse on the way from source to
                        target.
    inverse_weight :    float
                        Weight for inverse transforms. If < 1 will prefer
                        forward transforms.

    Returns
    -------
    sequence :          (N, ) array
                        Sequence of registrations that will be traversed.
    transform_seq :     TransformSequence
                        Class that collates the required transforms to get
                        from source to target.

    """
    # Generate sequence of nodes we need to find a path for
    # Minimally it's just from source to target
    nodes = np.array([source, target])

    if via:
        nodes = np.insert(nodes, 1, via)

    seq = [nodes[0]]
    transforms = []
    for n1, n2 in zip(nodes[:-1], nodes[1:]):
        path, tr = self.find_bridging_path(n1, n2, reciprocal=inverse_weight)
        seq = np.append(seq, path[1:])
        transforms = np.append(transforms, tr)

    if any(np.unique(seq, return_counts=True)[1] > 1):
        logger.warning('Bridging sequence contains loop: '
                       f'{"->".join(seq)}')

    # Generate the transform sequence
    transform_seq = TransformSequence(*transforms)

    return seq, transform_seq

Generate summary of available transforms.

Source code in navis/transforms/templates.py
147
148
149
def summary(self) -> pd.DataFrame:
    """Generate summary of available transforms."""
    return pd.DataFrame(self.transforms)