Source code for magmap.settings.atlas_prof

# Microscope profile settings
# Author: David Young, 2020
"""Profile settings for processing regions of interests.

These setting typically involve high-resolution such as microscopy images.
"""

from magmap.settings import config
from magmap.settings import profiles
from magmap.settings.profiles import RegKeys


[docs] class RegParamMap(dict): """Registration parameter map dictionary initialized with required keys.""" def __init__(self, *args, **kwargs): super().__init__(self) self["map_name"] = None self["metric_similarity"] = "AdvancedMattesMutualInformation" # fallback to alternate similarity metric if below DSC threshold as # given by (threshold, alternate_metric) self["max_iter"] = None self["grid_space_voxels"] = None self["num_resolutions"] = "4" self["grid_spacing_schedule"] = None # True to prevent artificial edges from entering ROI during smoothing, # but leave False to use area around mask for the registration # (see Elastix manual section 5.4) self["erode_mask"] = None # True to use point-based registration during b-spline reg self["point_based"] = False # update with args self.update(*args, **kwargs)
[docs] class AtlasProfile(profiles.SettingsDict): """Atlas profile dictionary. Attributes: PATH_PREFIX (str): Prefix for atlas profile files. """ PATH_PREFIX = "atlas" def __init__(self, *args, **kwargs): super().__init__(self) self[self.NAME_KEY] = self.DEFAULT_NAME # REGISTRATION SETTINGS # translation transform reg_translation = RegParamMap() reg_translation["map_name"] = "translation" reg_translation["max_iter"] = "2048" self["reg_translation"] = reg_translation # affine/scaling transform reg_affine = RegParamMap() reg_affine["map_name"] = "affine" reg_affine["max_iter"] = "1024" self["reg_affine"] = reg_affine # b-spline transform reg_bspline = RegParamMap() reg_bspline["map_name"] = "bspline" reg_bspline["max_iter"] = "512" reg_bspline["grid_space_voxels"] = "50" self["reg_bspline"] = reg_bspline # similarity metric fallback given as ``(threshold_dsc, metric)``, # where metric will be used if the DSC falls below the threshold self["metric_sim_fallback"] = None self["groupwise_iter_max"] = "1024" self["preprocess"] = False self["curate"] = True # carve image; in-paint if generating atlas # erase labels outside of ((x_start, x_end), (y_start, ...) ...) # (applied after transposition), where each val is given as fractions # of the full range or None to not truncate that at that position; # None for the entire setting turns off truncation self["truncate_labels"] = None # LABELS CURATION # ACTIVE (bool): True to apply the setting to the final image and # metrics; False to use only for metrics and cropping, etc # start (float): fractions of the total planes (0-1); use -1 to # set automatically, None to turn off the entire setting group # type of label smoothing self["smoothing_mode"] = config.SmoothingModes.opening # size of filter for label smoothing self["smooth"] = None # mirror labels onto the unlabeled hemisphere self["labels_mirror"] = { RegKeys.ACTIVE: False, "start": None, # reflect planes starting here "neg_labels": True, # invert values of mirrored labels "atlas_mirror": True, # also mirror intensity image } # extend edge labels self["labels_edge"] = { RegKeys.ACTIVE: False, RegKeys.SAVE_STEPS: False, "start": None, # start plane index "surr_size": 5, # dilation filter size for finding histology region # smoothing filter size to remove artifacts (None or 0 to ignore) "smoothing_size": 3, "in_paint": True, # True to fill pxs missing labels # erosion filter size for watershed markers (0 to ignore), which # are weighted by distance to border RegKeys.MARKER_EROSION: 10, # no erosion if weighted filter size is below min; None to use # default size (half of erosion filter size); 0 for no min RegKeys.MARKER_EROSION_MIN: None, # if filter size is below min, still erode at this size if True RegKeys.MARKER_EROSION_USE_MIN: False, # don't erode if reach min # preferentially weight erosion filter sizes in lateral planes # so that sizes are reduced linearly to this fraction in the most # medial plane; 0 = no weighting, 1 = full weighting "wt_lat": 0 } self["labels_dup"] = None # start duplicating planes til last labels # expand labels within bounds given by # (((x_pixels_start, x_pixels_end), ...), (next_region...)), or None # to avoid expansion self["expand_labels"] = None # crop atlas and intensity to fit a mask that excludes this sequence # of labels; note that labels within this cropped region will remain self["crop_out_labels"] = None # atlas and labels rotation by ((angle0, axis0), ...), or None to # avoid rotation, where an axis value of 0 is the z-axis, 1 is y, etc self["rotate"] = { "rotation": None, "resize": False, # True to keep full image rather than clipping # spline interpolation; 0 for labels (ignored for known labels imgs) "order": 1, } # atlas thresholds for microscopy images self["atlas_threshold"] = 10.0 # raise for finer segmentation self["atlas_threshold_all"] = 10.0 # keep low to include all signal self["target_size"] = None # x,y,z in exp orientation self["rescale"] = None # rescaling factor # carving and max size of small holes for removal, respectively self["carve_threshold"] = None self["holes_area"] = None # paste in region from first image during groupwise reg; # x,y,z, same format as truncate_labels except in pixels self["extend_borders"] = None # affine transformation as a dict of ``axis_along`` for the axis along # which to perform transformation (ie the planes that will be # affine transformed); ``axis_shift`` for the axis or # direction in which to shear; ``shift`` for a tuple of indices # of starting to ending shift while traveling from low to high # indices along ``axis_along``; ``bounds`` for a tuple of # ``((z_start z_end), (y_start, ...) ...)`` indices (note the # z,y,x ordering to use directly); and an optional ``axis_attach`` # for the axis along which to perform another affine to attach the # main affine back to the rest of the image self["affine"] = None # Laplacian of Gaussian self["log_sigma"] = 5 # Gaussian sigma; use None to skip # use atlas_threshold on atlas image to generate mask for finding # background rather than using labels and thresholded LoG image, # useful when ventricular spaces are labeled self["log_atlas_thresh"] = False # edge-aware reannotation: label erosion to generate watershed # seeds/markers for resegmentation; also used to demarcate the interior # of regions for metrics; can turn on/off with erode_labels self[RegKeys.EDGE_AWARE_REANNOTATION] = { # starting filter size for labels to markers, decreasing iteratively # if eroded size is not above target vol ratio to original RegKeys.MARKER_EROSION: 8, # minimum filter size, below which label reverts to no erosion: # - None: half of MARKER_EROSION # - 0: no minimum, instead using size of 1 even if below vol ratio # - n: min size of n if above vol ratio, otherwise reverts to orig RegKeys.MARKER_EROSION_MIN: 1, # erosion filter size before skeletonization: # - False: no skeletonization # - None: half of MARKER_EROSION # - n: filter kernel size RegKeys.SKELETON_EROSION: None, RegKeys.WATERSHED_MASK_FILTER: (config.SmoothingModes.opening, 2), } # target eroded size as frac of orig, used when generating interiors # of regions but not for watershed seeds; can be None self["erosion_frac"] = 0.5 self["erode_labels"] = {"markers": True, "interior": False} # crop labels and atlas to non-0 labels self["crop_to_labels"] = False # crop labels back to their original background after smoothing # (ignored during atlas import if no smoothing), given as the filter # size used to open up the background before cropping, 0 to use # the original background as-is, or False not to crop self["crop_to_orig"] = 1 # crop labels images to foreground of first labels image self["crop_to_first_image"] = False # combine values from opposite sides when measuring volume stats; # default to use raw values for each label and side to generate # a data frame that can be used for fast aggregation when # grouping into levels self["combine_sides"] = False # make the far hemisphere neg if it is not, for atlases (eg P56) with # bilateral pos labels where one half should be made neg for stats self["make_far_hem_neg"] = False # the atlas refiner assumes that the z-axis is sagittal planes; use # this setting to transpose an image to this orientation for # refinement, after which the image will be transposed back self["pre_plane"] = None # labels range given as ``((start0, end0), (start1, end1), ...)``, # where labels >= start and < end will be treated as foreground # when measuring overlap, eg labeled ventricles that would be # background in histology image self["overlap_meas_add_lbls"] = None # METRICS # measure smoothing quality metrics during label smoothing self["meas_smoothing"] = True # generate labels border/surface image and measure distances to # anatomical border/surface map self["meas_edge_dists"] = True # sequence of :class:`config.MetricGroups` enums to measure in # addition to basic metrics self["extra_metric_groups"] = None # cluster metrics self[RegKeys.METRICS_CLUSTER] = { RegKeys.KNN_N: 5, # num of neighbors for k-nearest-neighbors RegKeys.DBSCAN_EPS: 20, # epsilon for max dist in cluster RegKeys.DBSCAN_MINPTS: 6, # min points/samples per cluster } # the default unit is microns (um); use this factor to convert # atlases in other units to um (eg 1000 for atlases in mm) self["unit_factor"] = None # ATLAS EDITOR # downsample images loaded by these I/O packages in the Atlas Editor # to improve performance; defaults to images loaded by memmapping self["editor_downsample_io"] = [ config.LoadIO.NP, config.LoadIO.TIFFFILE, ] # downsample image planes with an edge size exceeding these values, # given as edge sizes of x,y,z-planes; decrease sizes to improve # performance, especially in x self["editor_max_sizes"] = (500, 1000, 2000) # update with args self.update(*args, **kwargs) self.profiles = { # turn off bspline registrations "nobspline": { "reg_bspline": None, }, # turn off affine and bspline registrations "noaffinebspline": { "reg_affine": None, "reg_bspline": None, }, # Normalized Correlation Coefficient similarity metric for # registration "ncc": { "reg_translation": { "metric_similarity": "AdvancedNormalizedCorrelation"}, "reg_affine": { "metric_similarity": "AdvancedNormalizedCorrelation"}, "reg_bspline": { "metric_similarity": "AdvancedNormalizedCorrelation", "grid_space_voxels": "60", }, # fallback to MMI since it has been rather reliable "metric_sim_fallback": (0.85, self.DEFAULT_NAME), }, # groupwise registration "groupwise": { # larger bspline voxels to avoid over deformation of internal # structures "reg_bspline": { "grid_space_voxels": "130", # increased num of resolutions with anisotropic size # (x0, y0, z0, x1, y1, z1, x2, ...) and overall increased # spacing since it appears to improve internal alignment "grid_spacing_schedule": [ "8", "8", "4", "4", "4", "2", "2", "2", "1", "1", "1", "1"], }, "bspline_grid_space_voxels": "130", # need to empirically determine "carve_threshold": 0.01, "holes_area": 10000, # empirically determined to add variable tissue area from # first image since this tissue may be necessary to register # to other images that contain this variable region "extend_borders": ((60, 180), (0, 200), (20, 110)), }, # test registration function with all registrations turned off "testreg": { "reg_translation": {"max_iter": "0"}, # need at least one reg "reg_affine": None, "reg_bspline": None, "curate": False, }, # test adding parameter maps for each type of registration # without actually performing any iterations "testnoiter": { "reg_translation": {"max_iter": "0"}, "reg_affine": {"max_iter": "0"}, "reg_bspline": {"max_iter": "0"}, "curate": False, }, # test a target size "testsize": { "target_size": (50, 50, 50), }, # new atlas generation: turn on preprocessing # TODO: likely remove since not using preprocessing currently "new": { "preprocess": True, }, # registration to new atlas assumes images are roughly same # orientation (ie transposed) and already have mirrored labels aligned # with the fixed image toward the bottom of the z-dimension "generated": { "truncate_labels": (None, (0.18, 1.0), (0.2, 1.0)), "labels_mirror": {RegKeys.ACTIVE: False}, "labels_edge": None, }, # atlas that uses groupwise image as the atlas itself should # determine atlas threshold dynamically "grouped": { "atlas_threshold": None, }, # ABA E11pt5 specific settings "abae11pt5": { "target_size": (345, 371, 158), "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.52}, "labels_edge": {RegKeys.ACTIVE: False, "start": None}, "log_atlas_thresh": True, "atlas_threshold": 75, # avoid over-extension into ventricles "atlas_threshold_all": 5, # include ventricles since labeled # rotate axis 0 to open vertical gap for affines (esp 2nd) "rotate": { "rotation": ((-5, 1), (-1, 2), (-30, 0)), "resize": False, }, "affine": ({ # shear cord opposite the brain back toward midline "axis_along": 1, "axis_shift": 0, "shift": (25, 0), "bounds": ((None, None), (70, 250), (0, 150)) }, { # shear distal cord where the tail wraps back on itself "axis_along": 2, "axis_shift": 0, "shift": (0, 50), "bounds": ((None, None), (0, 200), (50, 150)) }, { # counter shearing at far distal end, using attachment for # a more gradual shearing along the y-axis to preserve the # cord along that axis "axis_along": 2, "axis_shift": 0, "shift": (45, 0), "bounds": ((None, None), (160, 200), (90, 150)), "axis_attach": 1 }), "crop_to_labels": True, # req because of 2nd affine "smooth": 2, "overlap_meas_add_lbls": ((126651558, 126652059),), }, # ABA E13pt5 specific settings "abae13pt5": { "target_size": (552, 673, 340), "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.48}, # small, default surr size to avoid capturing 3rd labeled area # that becomes an artifact "labels_edge": { RegKeys.ACTIVE: True, "start": -1, }, "atlas_threshold": 55, # avoid edge over-extension into skull "rotate": { "rotation": ((-4, 1), (-2, 2)), "resize": False, }, RegKeys.EDGE_AWARE_REANNOTATION: { # turn off watershed mask filter and increase minimum # marker erosion filter size to avoid label loss RegKeys.WATERSHED_MASK_FILTER: (None, 0), RegKeys.MARKER_EROSION_MIN: 2, }, "crop_to_labels": True, "smooth": 2, }, # ABA E15pt5 specific settings "abae15pt5": { "target_size": (704, 982, 386), "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.49}, "labels_edge": { RegKeys.ACTIVE: True, "start": -1, "surr_size": 12, # increase template smoothing to prevent over-extension of # intermediate stratum of Str "smoothing_size": 5, # larger to allow superficial stratum of DPall to take over RegKeys.MARKER_EROSION: 19, }, "atlas_threshold": 45, # avoid edge over-extension into skull "rotate": { "rotation": ((-4, 1),), "resize": False, }, RegKeys.EDGE_AWARE_REANNOTATION: { # turn off filtering to avoid label loss RegKeys.WATERSHED_MASK_FILTER: (None, 0), }, "crop_to_labels": True, "smooth": 2, }, # ABA E18pt5 specific settings "abae18pt5": { "target_size": (278, 581, 370), "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.525}, # start from smallest BG; remove spurious label pxs around # medial pallium by smoothing "labels_edge": { RegKeys.ACTIVE: True, "start": 0.137, "surr_size": 12, RegKeys.MARKER_EROSION: 12, RegKeys.MARKER_EROSION_USE_MIN: True, }, "expand_labels": (((None,), (0, 279), (103, 108)),), "rotate": { "rotation": ((1.5, 1), (2, 2)), "resize": False, }, "smooth": 3, RegKeys.EDGE_AWARE_REANNOTATION: { RegKeys.MARKER_EROSION_MIN: 4, } }, # ABA P4 specific settings "abap4": { "target_size": (724, 403, 398), "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.487}, "labels_edge": { RegKeys.ACTIVE: True, "start": -1, "surr_size": 12, # balance eroding medial pallium and allowing dorsal # pallium to take over RegKeys.MARKER_EROSION: 8, }, # open caudal labels to allow smallest mirror plane index, # though still cross midline as some regions only have # labels past midline "rotate": { "rotation": ((0.22, 1),), "resize": False, }, "smooth": 4, RegKeys.EDGE_AWARE_REANNOTATION: { RegKeys.MARKER_EROSION_MIN: 4, } }, # ABA P14 specific settings "abap14": { "target_size": (390, 794, 469), # will still cross midline since some regions only have labels # past midline "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.5}, "labels_edge": { RegKeys.ACTIVE: True, "start": 0.078, # avoid alar part size jitter "surr_size": 12, RegKeys.MARKER_EROSION: 40, RegKeys.MARKER_EROSION_MIN: 10, }, # rotate conservatively for symmetry without losing labels "rotate": { "rotation": ((-0.4, 1),), "resize": False, }, "smooth": 5, RegKeys.EDGE_AWARE_REANNOTATION: { RegKeys.MARKER_EROSION_MIN: 4, } }, # ABA P28 specific settings "abap28": { "target_size": (863, 480, 418), # will still cross midline since some regions only have labels # past midline "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.48}, "labels_edge": { RegKeys.ACTIVE: True, "start": 0.11, # some lat labels only partially complete "surr_size": 12, "smoothing_size": 0, # no smoothing to avoid loss of detail # large erosion in outer planes (weighted ~off medially) RegKeys.MARKER_EROSION: 50, RegKeys.MARKER_EROSION_MIN: 20, # most erosion in lateral planes; minimal in medial planes "wt_lat": 0.9, }, # "labels_dup": 0.48, # rotate for symmetry, which also reduces label loss "rotate": { "rotation": ((1, 2),), "resize": False, }, "smooth": 2, RegKeys.EDGE_AWARE_REANNOTATION: { RegKeys.MARKER_EROSION_MIN: 2, } }, # ABA P56 (developing mouse) specific settings "abap56": { "target_size": (528, 320, 456), # stained sections and labels almost but not symmetric "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.5}, "labels_edge": { RegKeys.ACTIVE: True, "start": 0.138, # some lat labels only partially complete "surr_size": 12, "smoothing_size": 0, # no smoothing to avoid loss of detail # only mild erosion to minimize layer loss since histology # contrast is low RegKeys.MARKER_EROSION: 5, }, "smooth": 2, "make_far_hem_neg": True, RegKeys.EDGE_AWARE_REANNOTATION: { RegKeys.MARKER_EROSION_MIN: 2, } }, # ABA P56 (adult) specific settings "abap56adult": { # same atlas image as ABA P56dev "target_size": (528, 320, 456), # same stained sections as for P56dev; # labels are already mirrored starting at z=228, but atlas is # not here, so mirror starting at the same z-plane to make both # sections and labels symmetric and aligned with one another; # no need to extend lateral edges "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.5}, "smooth": 2, "make_far_hem_neg": True, }, # ABA CCFv3 specific settings "abaccfv3": { # for "25" image, which has same shape as ABA P56dev, P56adult "target_size": (456, 528, 320), # atlas is almost (though not perfectly) symmetric, so turn # off mirroring but specify midline (z=228) to make those # labels negative; no need to extend lateral edges "labels_mirror": {RegKeys.ACTIVE: False, "start": 0.5}, "make_far_hem_neg": True, "smooth": 0, }, # Waxholm rat atlas specific settings "whsrat": { "target_size": (441, 1017, 383), "pre_plane": config.PLANE[2], # mirror, but no need to extend lateral edges "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.48}, "crop_to_labels": True, # much extraneous, unlabeled tissue "smooth": 4, "unit_factor": 1000, }, # Allen Human Reference Atlas (3D, v1.0.0) "ahra": { "target_size": (193, 229, 193), "pre_plane": config.PLANE[2], # mirror, but no need to extend lateral edges "labels_mirror": {RegKeys.ACTIVE: True, "start": 0.5}, "crop_to_labels": True, # strip skull "smooth": 0, "unit_factor": 1000, # convert from mm to um "log_sigma": 4, # capture more detail }, # Profile modifiers to turn off settings. These "no..." profiles # can be applied on top of atlas-specific profiles to turn off # specific settings. Where possible, the ACTIVE flags will be turned # off to retain the rest of the settings within the given group # so that they can be used for metrics, cropping, etc. # turn off most image manipulations to show original atlas and labels # while allowing transformations set as command-line arguments "raw": { "labels_edge": {RegKeys.ACTIVE: False}, "labels_mirror": {RegKeys.ACTIVE: False}, "expand_labels": None, "rotate": None, "affine": None, "smooth": None, "crop_to_labels": False, }, # turn off atlas rotation "norotate": { "rotate": None, }, # turn off edge extension along with smoothing "noedge": { "labels_edge": {RegKeys.ACTIVE: False}, "labels_mirror": {RegKeys.ACTIVE: True}, "smooth": None, }, # turn off mirroring along with smoothing "nomirror": { "labels_edge": {RegKeys.ACTIVE: True}, "labels_mirror": {RegKeys.ACTIVE: False}, "smooth": None, }, # turn off both mirroring and edge extension along with smoothing # while preserving their settings for measurements and cropping "noext": { "labels_edge": {RegKeys.ACTIVE: False}, "labels_mirror": {RegKeys.ACTIVE: False}, "smooth": None, }, # turn off label smoothing "nosmooth": { "smooth": None, }, # turn off negative labels "noneg": { # if mirroring, do not invert mirrored labels "labels_mirror": {"neg_labels": False}, # do not invert far hemisphere labels "make_far_hem_neg": False, }, # turn off labels markers generation "nomarkers": { RegKeys.EDGE_AWARE_REANNOTATION: None, }, # turn off cropping atlas to extent of labels "nocropatlas": { "crop_to_labels": False, }, # turn off cropping labels to original size "nocroplabels": { "crop_to_orig": False, }, # test label smoothing over range "smoothtest": { "smooth": (0, 1, 2, 3, 4, 5, 6, 7, 8), # "smooth": (0, ), }, # test label smoothing over longer range "smoothtestlong": { "smooth": (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), }, # save intermediate steps where supported "savesteps": { "labels_edge": {RegKeys.SAVE_STEPS: True} }, # crop ventral and posterior regions "cropventropost": { "truncate_labels": (None, (0.2, 1.0), (0.45, 1.0)), }, # crop anterior region of labels during single registration "cropanterior": { "truncate_labels": (None, (0.2, 0.8), (0.45, 1.0)), }, # turn off image curation to avoid post-processing with carving # and in-painting "nopostproc": { "curate": False, "truncate_labels": None }, # smoothing by Gaussian blur "smoothgaus": { "smoothing_mode": config.SmoothingModes.gaussian, "smooth": 0.25 }, # smoothing by Gaussian blur "smoothgaustest": { "smoothing_mode": config.SmoothingModes.gaussian, "smooth": (0, 0.25, 0.5, 0.75, 1, 1.25) }, # combine sides for volume stats "combinesides": { "combine_sides": True, }, # more volume stats "morestats": { # "extra_metric_groups": (config.MetricGroups.SHAPES,), "extra_metric_groups": (config.MetricGroups.POINT_CLOUD,), }, # skip metrics "fewerstats": { "meas_smoothing": False, "meas_edge_dists": False, }, # measure interior-border stats "interiorlabels": { "erode_labels": {"markers": True, "interior": True}, }, }
[docs] @staticmethod def get_files(profiles_dir=None, filename_prefix=None): """Get atlas profile files. Args: profiles_dir (str): Directory from which to get files; defaults to None. filename_prefix (str): Only get files starting with this string; defaults to None to use :const:`PATH_PREFIX`. Returns: List[str]: List of files in ``profiles_dir`` matching the given ``filename_prefix``. """ if not filename_prefix: filename_prefix = AtlasProfile.PATH_PREFIX return super(AtlasProfile, AtlasProfile).get_files( profiles_dir, filename_prefix)