Source code for atlannot.merge.coarse

# Copyright 2021, Blue Brain Project, EPFL
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The coarse merging of the annotation atlases.

This is the refactored and optimized version of ``atlas_merge.coarse``. It
uses ``RegionMeta`` instead of ``JSONread`` and greatly speeds up the merging
by optimizing a number of steps. The original logic was designed by
Dimitri Rodarie.
"""
from __future__ import annotations

import logging

import numpy as np

from atlannot.merge.common import atlas_remap, descendants, replace
from atlannot.region_meta import RegionMeta

logger = logging.getLogger(__name__)


[docs]def manual_relabel(ids_v2: np.ndarray, ids_v3: np.ndarray) -> None: """Perform a manual re-labeling step on the CCFv2 and CCFv3 atlases. The replacements were compiled by Dimitri Rodarie. Parameters ---------- ids_v2 The (unique) region IDs of the CCFv2 atlas. ids_v3 The (unique) region IDs of the CCFv3 atlas. """ # Entorhinal area, lateral part replace(ids_v2, 60, 28) # L6b -> L6a replace(ids_v2, 999, 20) # L2/3 -> L2 # double check? replace(ids_v2, 715, 20) # L2a -> L2 replace(ids_v2, 764, 20) # L2b -> L2 replace(ids_v2, 92, 139) # L4 -> L5 replace(ids_v2, 312, 139) # L4/5 -> L5 # Entorhinal area, medial part, dorsal zone replace(ids_v2, 468, 543) # L2a -> L2 replace(ids_v2, 508, 543) # L2b -> L2 replace(ids_v2, 712, 727) # L4 -> L5 # double check? replace(ids_v2, 195, 304) # L2 -> L2/3 replace(ids_v2, 524, 582) # L2 -> L2/3 replace(ids_v2, 606, 430) # L2 -> L2/3 replace(ids_v2, 747, 556) # L2 -> L2/3 # subreg of Cochlear nuclei -> Cochlear nuclei replace(ids_v2, 96, 607) replace(ids_v2, 101, 607) replace(ids_v2, 112, 607) replace(ids_v2, 560, 607) replace(ids_v3, 96, 607) replace(ids_v3, 101, 607) # subreg of Nucleus ambiguus -> Nucleus ambiguus replace(ids_v2, 143, 135) replace(ids_v2, 939, 135) replace(ids_v3, 143, 135) replace(ids_v3, 939, 135) # subreg of Accessory olfactory bulb -> Accessory olfactory bulb replace(ids_v2, 188, 151) replace(ids_v2, 196, 151) replace(ids_v2, 204, 151) replace(ids_v3, 188, 151) replace(ids_v3, 196, 151) replace(ids_v3, 204, 151) # subreg of Medial mammillary nucleus -> Medial mammillary nucleus replace(ids_v2, 798, 491) replace(ids_v3, 798, 491) replace(ids_v3, 606826647, 491) replace(ids_v3, 606826651, 491) replace(ids_v3, 606826655, 491) replace(ids_v3, 606826659, 491) # Subreg to Dorsal part of the lateral geniculate complex replace(ids_v3, 496345664, 170) replace(ids_v3, 496345668, 170) replace(ids_v3, 496345672, 170) # Subreg to Lateral reticular nucleus replace(ids_v2, 955, 235) replace(ids_v2, 963, 235) replace(ids_v3, 955, 235) replace(ids_v3, 963, 235) # subreg of Posterior parietal association areas combined layer by layer replace(ids_v3, 312782550, 532) replace(ids_v3, 312782604, 532) replace(ids_v3, 312782554, 241) replace(ids_v3, 312782608, 241) replace(ids_v3, 312782558, 635) replace(ids_v3, 312782612, 635) replace(ids_v3, 312782562, 683) replace(ids_v3, 312782616, 683) replace(ids_v3, 312782566, 308) replace(ids_v3, 312782620, 308) replace(ids_v3, 312782570, 340) replace(ids_v3, 312782624, 340) # subreg to Parabrachial nucleus replace(ids_v2, 123, 867) replace(ids_v2, 860, 867) replace(ids_v2, 868, 867) replace(ids_v2, 875, 867) replace(ids_v2, 883, 867) replace(ids_v2, 891, 867) replace(ids_v2, 899, 867) replace(ids_v2, 915, 867) replace(ids_v3, 123, 867)
[docs]def merge( ccfv2: np.ndarray, ccfv3: np.ndarray, rm: RegionMeta, ) -> tuple[np.ndarray, np.ndarray]: """Perform the coarse atlas merging. Parameters ---------- ccfv2 The first atlas to merge, usually CCFv2. ccfv3 The second atlas to merge, usually CCFv3. rm The brain region metadata. Usually constructed as follows: ``RegionMeta.from_dict(brain_regions)``, where ``brain_regions`` can be obtained from the "msg" key of the `brain_regions.json` (`1.json`) file. Returns ------- ccfv2_new : np.ndarray The merged CCFv2 atlas. ccfv3_new : np.ndarray The merged CCFv3 atlas. """ logger.info("Preparing region ID maps") v2_from = np.unique(ccfv2) v2_to = v2_from.copy() v3_from = np.unique(ccfv3) v3_to = v3_from.copy() logger.info("First loop") unique_v2 = set(v2_to) unique_v3 = set(v3_to) ids_to_correct = unique_v2 - unique_v3 for id_ in ids_to_correct: if rm.is_leaf(id_) and rm.parent(id_) in unique_v3: replace(v2_to, id_, rm.parent(id_)) elif rm.is_leaf(id_) and ( rm.in_region_like("Medial amygdalar nucleus", id_) or rm.in_region_like("Subiculum", id_) or rm.in_region_like("Bed nuclei of the stria terminalis", id_) ): replace(v2_to, id_, rm.parent(rm.parent(id_))) elif rm.in_region_like("Paraventricular hypothalamic nucleus", id_): replace(v3_to, id_, 38) logger.info("Manual replacements #1") manual_relabel(v2_to, v3_to) logger.info("Second loop") for id_ in (unique_v2 | unique_v3) - {0}: if not rm.in_region_like("Visual areas", id_): continue if rm.in_region_like("ayer 1", id_): replace(v3_to, id_, 801) replace(v2_to, id_, 801) elif rm.in_region_like("ayer 2/3", id_): replace(v3_to, id_, 561) replace(v2_to, id_, 561) elif rm.in_region_like("ayer 4", id_): replace(v3_to, id_, 913) replace(v2_to, id_, 913) elif rm.in_region_like("ayer 5", id_): replace(v3_to, id_, 937) replace(v2_to, id_, 937) elif rm.in_region_like("ayer 6a", id_): replace(v3_to, id_, 457) replace(v2_to, id_, 457) elif rm.in_region_like("ayer 6b", id_): replace(v3_to, id_, 497) replace(v2_to, id_, 497) logger.info("Manual replacements #2") # subreg of Prosubiculum to subiculum replace(v3_to, 484682470, 502) # Orbital area, medial part, layer 6b -> 6a replace(v3_to, 527696977, 910) replace(v3_to, 355, 314) logger.info("Third loop") for id_ in unique_v3 - {0}: if ( ( rm.in_region_like("fiber tracts", id_) or rm.in_region_like("Interpeduncular nucleus", id_) ) and id_ not in unique_v2 and rm.parent(id_) in unique_v2 ): replace(v3_to, id_, rm.parent(id_)) if rm.in_region_like("Frontal pole, cerebral cortex", id_): replace(v3_to, id_, 184) replace(v2_to, id_, 184) def while_correct(ids_1, ids_2, allowed_1, allowed_2): unique_1 = set(ids_1) unique_2 = set(ids_2) ids_to_correct_ = unique_1 - unique_2 - {8, 997} while len(ids_to_correct_) > 0: id__ = ids_to_correct_.pop() while id__ not in allowed_v2: id__ = rm.parent(id__) for child in descendants(id__, allowed_1, rm): replace(ids_1, child, id__) for child in descendants(id__, allowed_2, rm): replace(ids_2, child, id__) unique_2 = set(ids_2) unique_1 = set(ids_1) ids_to_correct_ = unique_1 - unique_2 - {8, 997} logger.info("While loop corrections") allowed_v2 = rm.ancestors(v2_to) allowed_v3 = rm.ancestors(v3_to) while_correct(v3_to, v2_to, allowed_v3, allowed_v2) while_correct(v2_to, v3_to, allowed_v2, allowed_v3) logger.info("Applying replacements") ccfv2_new = atlas_remap(ccfv2, v2_from, v2_to) ccfv3_new = atlas_remap(ccfv3, v3_from, v3_to) return ccfv2_new, ccfv3_new