Source code for ClearMap.Alignment.Annotation

# -*- coding: utf-8 -*-
"""
Annotation
==========

Module to handle label and annotation info from Allen Brain Atlas (v2).
 
Notes
-----      
  - The files containing annotation information is assumed to be in the path
    stored in :const:`atlas_path`, which defaults to the resource path 
    :const:`ClearMap.Settings.resources_path`.

References
----------
  - `Allen Brain Atlas <https://mouse.brain-map.org/static/atlas>`_
"""
__author__ = 'Christoph Kirst <christoph.kirst.ck@gmail.com>, Charly Rousseau <charly.rousseau@icm-institute.org>'
__license__ = 'GPLv3 - GNU General Public License v3 (see LICENSE)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__ = 'https://idisco.info'
__download__ = 'https://github.com/ClearAnatomics/ClearMap'


# TODO: inherit Label from dict
# TODO: integrate with atlas meshes / plotting / transforming data / region selection


import os
import collections

import json
import warnings
from pathlib import Path

import numpy as np
import pandas as pd
from scipy.ndimage import distance_transform_edt

import ClearMap.Settings
import ClearMap.Settings as settings

import ClearMap.IO.IO as clearmap_io
import ClearMap.IO.FileUtils as fu

from ClearMap.Alignment.Resampling import orientation_to_transposition, format_orientation
import ClearMap.Utils.HierarchicalDict as hdict

from ClearMap.Alignment.utils import create_label_table
from ClearMap.IO import IO as clearmap_io

from ClearMap.Visualization import Color as col
from ClearMap.config.atlas import STRUCTURE_TREE_NAMES_MAP


###############################################################################
# ## Atlas Structures
###############################################################################

[docs] def decompress_atlases(atlas_base_name): # FIXME: add option to move to a different folder if we can't write to the current one paths = {} atlas_component_names = ('annotation', 'hemispheres', 'reference', 'distance_to_surface') for atlas_type in atlas_component_names: f_path = os.path.join(settings.atlas_folder, f'{atlas_base_name}_{atlas_type}.tif') fu.uncompress(f_path, extension='auto') paths[atlas_type] = f_path return paths
ATLAS_BASE_NAME = 'ABA_25um' # warning: change if different atlas default_annotation_file, default_hemispheres_file,\ default_reference_file, default_distance_to_surface_file = decompress_atlases(ATLAS_BASE_NAME).values() """ Note ---- This file is by default the Allen brain annotated mouse atlas with 25um isotropic resolution. Includes the - Default volumetric annotated image file. - Default hemispheres annotated image file - Default reference (grayscale) image file - Corresponding distance to surface file """ default_label_file = os.path.join(settings.atlas_folder, 'ABA_annotation.json') """Default list of labels and region names in the annotated image. Note ---- This file is by default the labels for the Allen brain annotated mouse atlas with 25um isotropic resolution. """ default_extra_label = [ (182305696, 453, 'No label', 'NoL'), (182305712, 453, 'No label', 'NoL'), (312782560, 315, 'No label', 'NoL'), (312782592, 453, 'No label', 'NoL'), (312782656, 315, 'No label', 'NoL'), (526157184, 993, 'No label', 'NoL'), (526322272, 500, 'No label', 'NoL'), (527696992, 315, 'No label', 'NoL') ] """Additional label not in the Allen Brain Atlas label but in the atlas image. Warning ------- This is required for the older version of the Allen Brain Atlas. There are no more labels missing in the current version (2017+ with 2022+ json file). Note ---- The form is a list of tuples, each tuple has the form (atlas id, parent id, name, acronym). """
[docs] class Label: """Class holding information of an individual Atlas label.""" def __init__(self, data, children=None, parent=None, level=0): self.data = data self.children = children self.parent = parent self.data['level'] = level @property def name(self): return self.data['name'] @property def id(self): return self.data['id'] @property def level(self): return self.data['level'] @property def order(self): return self.data['order']
[docs] def color(self, *args, **kwargs): return col.color(self.data['rgb'], **kwargs)
def __getitem__(self, key): return self.data[key] def __setitem__(self, key, value): self.data[key] = value
[docs] def parent_list(self, max_depth=None, min_level=None): if max_depth is None: max_depth = self.level + 1 if min_level is None: min_level = 1 if max_depth > 0 and self.level > min_level and self.parent is not None: return [self] + self.parent.parent_list(max_depth=max_depth-1, min_level=min_level) else: return []
[docs] def write(self, with_children=True, indent=None): return self.__str__(indent=indent, with_children=with_children)
[docs] def info(self, with_children=True, indent=None): print(self.write(indent=indent, with_children=with_children))
def __str__(self, indent=None, with_children=False): indent = indent or '' s = '' if with_children and isinstance(self.children, list): for c in self.children: s += '\n' + c.__str__(indent=indent + ' ') h = f'{indent}{self.name}\n{indent}{"=" * len(self.name)}\n' s = h + hdict.write(self.data, head=indent[:-1]) + '\n' + s return s def __repr__(self, indent=None, with_children=False): return self.__str__(indent=indent, with_children=False)
[docs] class Annotation: """Class that holds information of the annotated regions.""" def __init__(self, atlas_base_name, slicing, orientation, label_source, target_directory=None, extra_label=None): """Initialization Arguments --------- atlas_base_name: str The prefix of the atlas files. They will be searched for in the ClearMap resources folder. slicing : tuple The slicing specification after reorienting. orientation : tuple, or None. The orientation specification. Strings can be 'left' or 'right', for the two hemispheres. label_source : str File with label information in json format. It can also be the name as defined in the atlas config. target_directory : str The target directory where to store the resliced/reoriented atlas files. If None, use ClearMap resources folder. extra_label : list of tuples Additional labels for IDs present in the atlas but not in the label file. """ if not label_source.startswith('/'): label_file_path = Path(settings.atlas_folder) / STRUCTURE_TREE_NAMES_MAP[label_source] else: label_file_path = label_source atlas_source_files = decompress_atlases(atlas_base_name) # just return the paths if already decompressed atlas_files = Annotation.prepare_annotation_files(slicing, orientation, directory=target_directory, hemispheres_file=atlas_source_files['hemispheres'], annotation_file=atlas_source_files['annotation'], distance_to_surface_file=atlas_source_files['distance_to_surface'], reference_file=atlas_source_files['reference'], verbose=True) atlas_files = {k: v for k, v in zip(atlas_source_files.keys(), atlas_files) if v} atlas_files['hemispheres'] = atlas_files.get('hemispheres', None) # Save the input parameters self.atlas_base_name = atlas_base_name self.slicing = slicing self.orientation = orientation self.label_source = label_source self.target_directory = target_directory self.root = None self.structures = None self.ids = None self.names = None self.labels = None self.acronyms = None self.colors_rgb = None self.colors_hex = None self.df = None self.annotation_file = '' self.hemispheres_file = '' self.distance_to_surface_file = '' self.reference_file = '' self.label_file = None self.extra_label = None self.dict_id_to_acronym = {} self.dict_id_to_name = {} self.dict_id_to_color = {} # self.dict_id_to_path = {} self.dict_acronym_to_id = {} self.dict_name_to_id = {} self.initialize(annotation_file_path=atlas_files['annotation'], hemispheres_file_path=atlas_files['hemispheres'], distance_to_surface_file_path=atlas_files['distance_to_surface'], reference_file_path=atlas_files['reference'], label_file_path=label_file_path, extra_label=extra_label) def _initialize_dataframe(self): df = pd.DataFrame({ "id": self.ids, "name": self.names, "acronym": self.acronyms, "colors_hex": self.colors_hex, #"colors_rgb": self.colors_rgb, }) df["colors_rgb"] = df["colors_hex"].map(lambda x: col.hex_to_rgb(x)) return df
[docs] def get_atlas_paths(self): return { 'annotation': self.annotation_file, 'hemispheres': self.hemispheres_file, 'distance_to_surface': self.distance_to_surface_file, 'reference': self.reference_file }
[docs] def get_dict(self, from_='id', to='acronym'): return dict(zip(self.df[from_], self.df[to]))
[docs] def ids_to_acronyms(self, ids): return [self.dict_id_to_acronym[id_] for id_ in ids]
[docs] def ids_to_names(self, ids): return [self.dict_id_to_name[id_] for id_ in ids]
[docs] def get_colors_rgba(self, alpha=1): return self.df["colors_hex"].map(lambda x: col.hex_to_rgb(x, alpha=alpha))
[docs] def set_annotation_file(self, annotation_file_path): # FIXME: mark deprecated print(f"Setting annotation file to {annotation_file_path}.\n" f"The annotator will be reinitialized.") self.initialize(annotation_file_path=annotation_file_path)
[docs] def set_label_file(self, label_file_path): # FIXME: mark deprecated print(f"Setting label file to {label_file_path}.\n" f"The annotator will be reinitialized.") self.initialize(label_file_path=label_file_path)
[docs] def initialize(self, annotation_file_path=None, hemispheres_file_path=None, # TODO: make private distance_to_surface_file_path=None, reference_file_path=None, label_file_path=None, extra_label=None): if label_file_path is None: if self.label_file is None: warnings.warn(f'Label file not defined, using default: {default_label_file}.\n' f'Please note this is deprecated and will be removed in future versions.', DeprecationWarning) label_file_path = default_label_file else: label_file_path = self.label_file if annotation_file_path is None: if self.annotation_file is None: warnings.warn(f'Annotation file not defined, using default: {default_annotation_file}.\n' f'Please note this is deprecated and will be removed in future versions.', DeprecationWarning) annotation_file_path = default_annotation_file else: annotation_file_path = self.annotation_file if extra_label is None: extra_label = self.extra_label or default_extra_label if not extra_label: # add nodes for missing labels extra_label = [] self.label_file = label_file_path self.annotation_file = annotation_file_path self.hemispheres_file = hemispheres_file_path or self.hemispheres_file self.distance_to_surface_file = distance_to_surface_file_path or self.distance_to_surface_file self.reference_file = reference_file_path or self.reference_file self.extra_label = extra_label # initialize label tree with open(label_file_path, 'r') as file_in: aba = json.load(file_in) root = aba['msg'][0] self.root = self.initialize_tree(root) # maxgraph = max(self.get_list('graph_order')) for a in extra_label: i, p, n, l = a node = self.find(p, key='id') data = node.data.copy() data['id'] = i data['name'] = n data['acronym'] = l data['parent_structure_id'] = p data['graph_order'] = -1 data['atlas_id'] = -1 node.children.append(Label(data, parent=node, children=[], level=node.level+1)) # initialize generic id self.add_data('order', range(self.n_structures)) # initialize color label self.colors_hex = self.get_list('color_hex_triplet') self.colors_rgb = np.array([col.hex_to_rgb(c) for c in self.colors_hex]) self.add_data('rgb', self.colors_rgb) self.add_data('color_order', range(self.n_structures)) # initialize lookup tables self.structures = self.get_list(None) self.ids = self.get_list('id') self.labels = self.get_list('atlas_id') self.names = self.get_list('name') self.acronyms = self.get_list('acronym') # build dataframe self.df = self._initialize_dataframe() # build dictionaries self.dict_id_to_acronym = self.get_dict(from_='id', to='acronym') self.dict_id_to_name = self.get_dict(from_='id', to='name') self.dict_id_to_color = self.get_dict(from_='id', to='colors_hex') # self.dict_id_to_path = self.get_dict(from_='id', to='structure_acronym_path') #TODO to be implemented self.dict_acronym_to_id = self.get_dict(from_='acronym', to='id') self.dict_name_to_id = self.get_dict(from_='name', to='id') # import atlas self.atlas = clearmap_io.read(self.annotation_file) if self.atlas.dtype.kind == 'f': self.atlas = self.atlas.astype(int) self.children_df = create_label_table(self.label_file, save=False, from_cached=True)
[docs] def initialize_tree(self, root, parent=None, level=0): label = Label({k: v for k, v in root.items() if k != "children"}, parent=parent, level=level) label.children = [self.initialize_tree(c, parent=label, level=level + 1) for c in root['children']] return label
[docs] def get_list(self, key=None, node=None, level=None): if node is None: node = self.root l = [] if level is not None: n = node while n.level > level: n = n.parent else: n = node if key is not None: l.append(n[key]) else: l.append(n) for c in node.children: l.extend(self.get_list(node=c, key=key, level=level)) return l
@property def n_structures(self): return len(self.get_list()) @property def max_level(self): return np.max(self.get_list('level'))
[docs] def get_hierarchical_dictionary(self, node=None): if node is None: node = self.root d = node.data d['children'] = [self.get_hierarchical_dictionary(node=n) for n in node.children] return d
[docs] def get_dictionary(self, key, value, node=None, level=None, ordered=False, with_parents=False, max_depth=None, min_level=None): if node is None: node = self.root keys = self.get_list(key=key, node=node, level=None) values = self.get_list(key=value, node=node, level=level) if ordered: dictionary = collections.OrderedDict() else: dictionary = dict() for k, v in zip(keys, values): dictionary[k] = v if with_parents: for k, v in dictionary.items(): node = self.find(k, key=key) parent_list = node.parent_list(max_depth=max_depth, min_level=min_level) dictionary[k] = tuple(node[value] for node in parent_list) return dictionary
[docs] def get_map(self, key, value, node=None, level=None): d = self.get_dictionary(key=key, value=value, node=node, level=level) n = max(d.keys()) m = np.zeros(n+1, dtype=int) m[list(d.keys())] = list(d.values()) return m
[docs] def add_data(self, name, data): nodes = self.get_list() for n, d in zip(nodes, data): n.data[name] = d
[docs] def convert_label(self, label, key='id', value='order', level=None, method='map', node=None): if value.lower().startswith('rgb') and len(value) <= 4: alpha = value.lower().endswith('a') as_int = value.startswith('RGB') return self.label_to_color(label, key=key, alpha=alpha, as_int=as_int) # string keys or values, force dictionary mode if key in ('acronym', 'name', 'color_hex_triplet') or value in ('acronym', 'name', 'color_hex_triplet'): method = 'dictionary' if method == 'map': m = self.get_map(key=key, value=value, node=node, level=level) return m[label] else: d = self.get_dictionary(key=key, value=value, node=node, level=level) return np.vectorize(d.__getitem__, otypes=[type(list(d.values())[0])])(label)
[docs] def label_to_color(self, label, key='id', level=None, alpha=True, as_int=False, int_type='uint8'): cm = self.colors_rgb cm = col.color(cm, alpha=alpha, as_int=as_int, int_type=int_type) if key != 'order' or level is not None: label = self.convert_label(label, key=key, value='order', level=level) return cm[label]
[docs] def find(self, label, key='id', value=None, node=None, level=None): d = self.get_dictionary(key=key, value=value, node=node, level=level) if isinstance(label, list): # FIXME: iterable return [d[l] for l in label] else: return d[label]
[docs] def parents(self, label, key='id', value=None): d = self.get_dictionary(key=key, value=None, node=None, level=None) p = d[label] l = [p] while p.level > 0: p = p.parent l.append(p) if value is None: return l else: return [ll[value] for ll in l]
[docs] def common_parent(self, label, key='id', value=None): parents = [self.parents(l, key=key, value=key) for l in label] n = min([len(p) for p in parents]) p0 = parents[0] level = -1 while -level-1 < n and np.all([p[level] == p0[level] for p in parents]): level -= 1 return self.find(p0[level+1], key=key, value=value)
@property def map_volume(self): uniques, counts = np.unique(self.atlas, return_counts=True) return dict(zip(uniques, counts))
[docs] def get_lateralised_volume_map(self, atlas_scale, hemispheres_file_path=None): if hemispheres_file_path is None: hemispheres_file_path = self.hemispheres_file hemispheres_atlas = clearmap_io.read(hemispheres_file_path) scale = np.prod(atlas_scale) hem_ids = sorted(np.unique(hemispheres_atlas).astype(int).tolist()) volumes = {} for hem_id in hem_ids: unique_ids, counts = np.unique(self.atlas[hemispheres_atlas == hem_id], return_counts=True) for region_id, count in zip(unique_ids.astype(int).tolist(), counts.tolist()): volumes[(region_id, hem_id)] = int(count) * scale return volumes
[docs] def get_dict_parents_to_children(self, parents_ids=None, including_parents=False): map_children = {} for parent_id in parents_ids: map_children[parent_id] = self.children_df.set_index('id').loc[parent_id, 'all_children_structures_ids'].copy() if including_parents: for parent in parents_ids: map_children[parent].append(parent) return map_children
[docs] def get_dict_children_to_parents(self, parents_ids=None, including_parents=False): map_children = self.get_dict_parents_to_children(parents_ids=parents_ids, including_parents=including_parents).copy() map_parent = {} for parent in map_children: for child in map_children[parent].copy(): map_parent[child] = parent return map_parent
[docs] def get_map_to_parent(self, parent_ids): """ uses annotation graph to map all possible children structures to its parent in parent_ids """ return self.get_dict_children_to_parents(parent_ids, including_parents=True)
[docs] def get_children(self, structure_ids): if isinstance(structure_ids, int): structure_ids = [structure_ids] map_children = self.get_dict_parents_to_children(parents_ids=structure_ids, including_parents=False) children = [] for parent in map_children: children.extend(map_children[parent]) return children
[docs] def enrich_df(self, df): df = df.copy() df['name'] = df['id'].map(self.dict_id_to_name) df['acronym'] = df['id'].map(self.dict_id_to_acronym) return df
[docs] def label_points_hemispheres(self, points, key='id', level=None, invalid=-1): """ Label points using the hemispheres annotation file (atlas with hemisphere id as voxels). Parameters ---------- points: array-like The points to label. key: str The key (in the ontology file) to use for the label conversion. The default is 'id'. If key != 'id', the label will be converted to the specified key. See label_points for details level: None | int The level in the hierarchy to use for the label conversion. The default is None. (see `convert_label` for details) invalid The default value for points outside the atlas. The default is 0. Returns ------- point_labels: array-like """ return self.label_points(points, annotation_file_path=self.hemispheres_file, key=key, level=level, invalid=invalid, d_type=np.int32) # We store a bool but historically 0 or 255 so
# as int32_t to handle outside of brain as negative
[docs] def label_points(self, points, annotation_file_path=None, key='id', level=None, invalid=0, d_type=np.uint64): """ Label points using the annotation file (atlas with ids as voxels). Parameters ---------- points: array-like The points to label. annotation_file_path: str | Path | None The path to the annotation file. If None, use the atlas attribute. key: str The key (in the ontology file) to use for the label conversion. The default is 'id'. If key != 'id', the label will be converted to the specified key. level: None | int The level in the hierarchy to use for the label conversion. The default is None. (see `convert_label` for details) invalid: float | int The default value for points outside the atlas. The default is 0. d_type: np.dtype data type of the labels (typically ids). The default is np.uint64 because the default atlas is uint64. Returns ------- point_labels: array-like The labels for each point. (shape = len(points)) """ if annotation_file_path: atlas = clearmap_io.read(annotation_file_path) else: atlas = self.atlas # Create mask of coordinates within annotation file coordinates (of shape = len(coordinates)) # TODO: check if warn xs, ys, zs = points.astype(int).T xmax, ymax, zmax = self.atlas.shape within_atlas = (xs >= 0) & (xs < xmax) & (ys >= 0) & (ys < ymax) & (zs >= 0) & (zs < zmax) # Create a list of labels (shape = len(coordinates)) which defaults to 'invalid' and where # coordinates within the atlas have the atlas value point_labels = np.full(xs.shape, invalid, dtype=d_type) point_labels[within_atlas] = atlas[xs[within_atlas], ys[within_atlas], zs[within_atlas]] # convert cell labels to 'key' if key != 'id' or level is not None: if annotation_file_path: if 'hemisphere' in annotation_file_path.lower(): raise ValueError(f'Label conversion for {annotation_file_path} is not implemented') else: warnings.warn(f'Label conversion for {annotation_file_path} may not be implemented') point_labels[within_atlas] = self.convert_label(point_labels[within_atlas], key='id', value=key, level=level) return point_labels
[docs] def get_names_map(self): return dict(zip(self.ids, self.names))
def __str__(self): return f'Annotation({self.n_structures})[{self.max_level}]{{{self.label_file}}}' def __repr__(self): return self.__str__()
[docs] @staticmethod def prepare_annotation_files(slicing, orientation, directory=None, annotation_file=None, hemispheres_file=None, reference_file=None, distance_to_surface_file=None, overwrite=False, verbose=False): """ Crop the annotation, reference and distance files to match the data. Arguments --------- slicing : tuple or None The slice specification after reorienting. orientation : tuple, str or None. The orientation specification. Strings can be 'left' or 'right', for the two hemispheres. directory : str or None The target directory. If None, use ClearMap resources folder. annotation_file : str or None The annotation file to use. hemispheres_file : str or None The hemispheres annotation file to use. "hemispheres" must be set to True reference_file : str or None The reference file to use. distance_to_surface_file : str or None The distance file to use. overwrite : bool If True, overwrite existing files. verbose : bool Whether to print verbose output. Returns ------- annotation_file : str The cropped annotation file. reference_file : str The cropped reference file. distance_to_surface_file : str The distance cropped file. """ return_hemispheres = hemispheres_file is not None file_paths = { # must be ordered 'annotation': annotation_file, 'hemispheres': hemispheres_file, 'reference': reference_file, 'distance_to_surface': distance_to_surface_file } specified_paths = {k: v for k, v in file_paths.items() if v} if not specified_paths: file_paths['annotation'] = default_annotation_file file_paths['reference'] = default_reference_file file_paths['distance_to_surface'] = default_distance_to_surface_file if return_hemispheres: file_paths['hemispheres'] = default_hemispheres_file else: # infer the other paths existing_keys = list(specified_paths.keys()) # TODO: we should check that if > 1 key, they share pattern missing_keys = [k for k in file_paths if k not in existing_keys] for k in missing_keys: file_paths[k] = file_paths[existing_keys[0]].replace(existing_keys[0], k) if directory and not os.path.exists(directory): os.makedirs(directory) results = [] for f_path in file_paths.values(): if not f_path: continue new_file_path = get_atlas_filepath(f_path, directory=directory, orientation=orientation, slicing=slicing) if verbose: print(f'Preparing: {new_file_path}') if not overwrite and os.path.exists(new_file_path): if verbose: print('Atlas file exists, skipping') results.append(new_file_path) continue if not os.path.exists(f_path): raise FileNotFoundError(f'Cannot find annotation file: {f_path}') # Extract array if verbose: print(f'Preparing: from file: "{f_path}"') src = clearmap_io.read(f_path) data = np.array(src) if orientation is not None: # transpose data to match orientation per = orientation_to_transposition(orientation) data = data.transpose(per) # reverse axes if orientation[axis] is negative slices = [slice(None, None, -1) if ori < 0 else slice(None) for ori in orientation] if any([s != slice(None) for s in slices]): data = data[tuple(slices)] # Crop if slicing is not None: data = data[slicing] clearmap_io.write(new_file_path, data) results.append(new_file_path) return results
[docs] def get_columns(self, coordinates_transformed, atlas_resolution, ids=None): # TODO: rename if ids is None: ids = self.label_points(coordinates_transformed) out = pd.DataFrame({'id': ids}) out['xt'] = coordinates_transformed[:, 0] out['yt'] = coordinates_transformed[:, 1] out['zt'] = coordinates_transformed[:, 2] if self.hemispheres_file: out['hemisphere'] = self.label_points_hemispheres(coordinates_transformed) out['name'] = self.convert_label(out['id'], key='id', value='name') unique_ids = np.sort(out['id'].unique()) order_map = {id_: self.find(id_, key='id')['order'] for id_ in unique_ids} out['order'] = out['id'].map(order_map) color_map = {id_: self.find(id_, key='id')['rgb'] for id_ in unique_ids} # WARNING RGB upper case should give integer but does not work out['color'] = out['id'].map(color_map) volumes = self.get_lateralised_volume_map(atlas_resolution) if self.hemispheres_file: out['volume'] = out.set_index(['id', 'hemisphere']).index.map(volumes.get) else: out['volume'] = out['id'].map(volumes) return out
[docs] def color_map(self, color_ids=None, alpha=True, as_int=False, int_type='uint8'): """ Generates a color map from color ids to rgb Arguments --------- color_ids: list The list of ids to generate the color map for. e.g. a rgb tuple, color name from matplotlib or vispy, hex code alpha : bool If True return a color map with alpha values. as_int : bool If True return a color map with integer values in the range 0-255. int_type : str The integer type to use for the output, e.g. 'uint8', 'uint16'. Returns ------- color_map : array An array of rgb colors for each label. """ if color_ids is None: color_ids = self.colors_rgb return col.color(color_ids, alpha=alpha, as_int=as_int, int_type=int_type)
[docs] def write_color_palette(self, file_path=''): """ Creates a pal or lut file for Imaris or Imagej based on label colors of atlas. Arguments --------- file_path : str | Path The name of the color palette file. Returns ------- filename : str The name of the file to which the color palette was written. """ cm = self.color_map(alpha=False, as_int=True) extension = clearmap_io.file_extension(file_path) if extension == 'pal': col.write_PAL(file_path, cm) elif extension == 'lut': col.write_LUT(file_path, cm) else: raise RuntimeError(f'Color palette format: {extension} not lut or pal') return file_path
[docs] def create_color_annotation(self, annotation_file_path='', dest_path=''): # TEST: """Creates a rgb image from the atlas color data. Arguments --------- annotation_file_path : str File name of the atlas annotation. dest_path : str The path to the file where the color atlas should be written. If empty, the color atlas is only returned as an array. Returns ------- filename : str The name of the file to which the color atlas was written. """ if annotation_file_path: atlas = clearmap_io.read(annotation_file_path) else: atlas = self.atlas atlas = self.convert_label(atlas, key='id', value='order', method='map') # FIXME: why value='order'? # apply color map cm = self.color_map(alpha=False, as_int=True) atlas = cm[atlas] if dest_path: clearmap_io.write(dest_path, atlas) return atlas
[docs] def get_atlas_filepath(filename, directory=None, orientation=None, slicing=None): """Formats the annotation filename given orientation and slicing.""" def substitute_chars(in_str, chars_to_strip=' ', chars_to_substitute='(,)'): # TODO: check if we add '[]' to chars_to_substitute out_str = in_str for c in chars_to_strip: out_str = out_str.replace(c, '') for c in chars_to_substitute: out_str = out_str.replace(c, '_') return out_str x, y, z = format_orientation(orientation, default=(1, 2, 3)) postfix = substitute_chars(f'{x}_{y}_{z}_{slicing}') base, ext = os.path.splitext(filename) fn = base + f'_{postfix}{ext}' if directory is not None: fn = os.path.join(directory, os.path.basename(fn)) return fn
[docs] def annotation_to_distance_file(annotation_file_path): brain_mask = (clearmap_io.read(annotation_file_path) > 0).astype(int) distance_array = distance_transform_edt(brain_mask) return distance_array
############################################################################### # ## Tests ############################################################################### def _test1(): import numpy as np import ClearMap.Alignment.Annotation as ano from importlib import reload reload(ano) annotator = ano.Annotation(ATLAS_BASE_NAME, None, None, default_label_file) points = np.array([[162, 200, 138], [246, 486, 138], [246, 486, 138]]) label = ano.label_points(points) print(label) cnts = annotator.count_points(points) print(cnts) cnts = annotator.count_points(points, hierarchical=False) print(cnts) import ClearMap.IO.IO as io ano.write_color_annotation('test.tif') io.delete_file('test.tif') l = annotator.find(247, key='id') print(l) l.info(with_children=True) print(l.level) annotator.get_dictionary(key='id', value='acronym', with_parents=True, min_level=3, max_depth=3) def _test2(): import ClearMap.Alignment.Annotation as ano from importlib import reload reload(ano) annotation = ano.Annotation(ATLAS_BASE_NAME, None, None,default_label_file) assert annotation.df.shape == (1319, 5) assert annotation.dict_id_to_acronym[1] == "TMv" assert annotation.dict_name_to_id['Interpeduncular nucleus'] == 100 assert annotation.dict_id_to_name[1000] == 'extrapyramidal fiber systems' assert annotation.dict_acronym_to_id['MO'] == 500 assert annotation.dict_id_to_color[200] == '61E7B7' ### annotation_new contains last annotation atlas (2017) and last annotation graph (from Allen, October 2022 label_fpath = os.path.join(ClearMap.Settings.atlas_folder, "ABA_annotation_last.json") annotation_new = Annotation('ABA_25um_2017', None, None, label_source=label_fpath) # assert annotation_new.df.shape == (1336, 5) # assert annotation_new.dict_id_to_acronym[1] == "TMv" # assert annotation_new.dict_name_to_id['Interpeduncular nucleus'] == 100 # assert annotation_new.dict_id_to_name[1000] == 'extrapyramidal fiber systems' # assert annotation_new.dict_acronym_to_id['MO'] == 500 # assert annotation_new.dict_id_to_color[200] == '61E7B7' # # assert annotation_new.children_df.shape == (1327, 9) # assert annotation_new.children_df.set_index('id').loc[100, 'structure_path'] == [997, 8, 343, 313, 348, 165, 100] # assert annotation_new.children_df.set_index('id').loc[997, 'direct_children_structures_ids'] == [8, 1009, 73, 1024, 304325711] # assert annotation_new.children_df.set_index('id').loc[65, 'all_children_structures_ids'] == [] # # assert annotation_new.get_dict_children_to_parents([1032]) == {1055: 1032, 1063: 1032, 1071: 1032, 1078: 1032} # assert annotation_new.get_dict_parents_to_children([1032]) == {1032: [1055, 1063, 1071, 1078]} # assert (annotation_new.enrich_df(pd.DataFrame([{"id": 1111}])).equals( # pd.DataFrame([{'id': 1111, 'name': 'Primary somatosensory area, trunk, layer 5', # 'acronym': 'SSp-tr5'}])))