def erode_labels(labels_img_np, erosion, erosion_frac=None, mirrored=True, mirror_mult=-1): """Erode labels image for use as markers or a map of the interior. Args: labels_img_np (:obj:`np.ndarray`): Numpy image array of labels in z,y,x format. erosion (dict): Dictionary of erosion filter settings from :class:`profiles.RegKeys` to pass to :meth:`segmenter.labels_to_markers_erosion`. erosion_frac (int): Target erosion fraction; defaults to None. mirrored (bool): True if the primary image mirrored/symmatrical, in which case erosion will only be performed one symmetric half and mirrored to the other half. If False or no symmetry is found, such as unmirrored atlases or experimental/sample images, erosion will be performed on the full image. mirror_mult (int): Multiplier for mirrored labels; defaults to -1 to make mirrored labels the inverse of their source labels. Returns: :obj:`np.ndarray`, :obj:`pd.DataFrame`: The eroded labels as a new array of same shape as that of ``labels_img_np`` and a data frame of erosion stats. """ labels_to_erode = labels_img_np sym_axis = atlas_refiner.find_symmetric_axis(labels_img_np, mirror_mult) is_mirrored = mirrored and sym_axis >= 0 len_half = None if is_mirrored: # if symmetric, erode only one symmetric half len_half = labels_img_np.shape[sym_axis] // 2 slices = [slice(None)] * labels_img_np.ndim slices[sym_axis] = slice(len_half) labels_to_erode = labels_img_np[tuple(slices)] # convert labels image into markers #eroded = segmenter.labels_to_markers_blob(labels_img_np) eroded, df = segmenter.labels_to_markers_erosion( labels_to_erode, erosion[profiles.RegKeys.MARKER_EROSION], erosion_frac, erosion[profiles.RegKeys.MARKER_EROSION_MIN], skel_eros_filt_size=erosion[profiles.RegKeys.SKELETON_EROSION]) if is_mirrored: # mirror changes onto opposite symmetric half eroded = _mirror_imported_labels( eroded, len_half, mirror_mult, sym_axis) return eroded, df
def edge_aware_segmentation(path_atlas, show=True, atlas=True, suffix=None, exclude_labels=None, mirror_mult=-1): """Segment an atlas using its previously generated edge map. Labels may not match their own underlying atlas image well, particularly in the orthogonal directions in which the labels were not constructed. To improve alignment between the labels and the atlas itself, register the labels to an automated, roughly segmented version of the atlas. The goal is to improve the labels' alignment so that the atlas/labels combination can be used for another form of automated segmentation by registering them to experimental brains via :func:``register``. Edge files are assumed to have been generated by :func:``make_edge_images``. Args: path_atlas (str): Path to the fixed file, typically the atlas file with stained sections. The corresponding edge and labels files will be loaded based on this path. show (bool): True if the output images should be displayed; defaults to True. atlas (bool): True if the primary image is an atlas, which is assumed to be symmetrical. False if the image is an experimental/sample image, in which case segmentation will be performed on the full images, and stats will not be performed. suffix (str): Modifier to append to end of ``path_atlas`` basename for registered image files that were output to a modified name; defaults to None. If ``atlas`` is True, ``suffix`` will only be applied to saved files, with files still loaded based on the original path. exclude_labels (List[int]): Sequence of labels to exclude from the segmentation; defaults to None. mirror_mult (int): Multiplier for mirrored labels; defaults to -1 to make mirrored labels the inverse of their source labels. """ # adjust image path with suffix load_path = path_atlas mod_path = path_atlas if suffix is not None: mod_path = libmag.insert_before_ext(mod_path, suffix) if atlas: load_path = mod_path # load corresponding files via SimpleITK atlas_sitk = sitk_io.load_registered_img( load_path, config.RegNames.IMG_ATLAS.value, get_sitk=True) atlas_sitk_edge = sitk_io.load_registered_img( load_path, config.RegNames.IMG_ATLAS_EDGE.value, get_sitk=True) labels_sitk = sitk_io.load_registered_img( load_path, config.RegNames.IMG_LABELS.value, get_sitk=True) labels_sitk_markers = sitk_io.load_registered_img( load_path, config.RegNames.IMG_LABELS_MARKERS.value, get_sitk=True) # get Numpy arrays of images atlas_img_np = sitk.GetArrayFromImage(atlas_sitk) atlas_edge = sitk.GetArrayFromImage(atlas_sitk_edge) labels_img_np = sitk.GetArrayFromImage(labels_sitk) markers = sitk.GetArrayFromImage(labels_sitk_markers) # segment image from markers sym_axis = atlas_refiner.find_symmetric_axis(atlas_img_np) mirrorred = atlas and sym_axis >= 0 len_half = None seg_args = {"exclude_labels": exclude_labels} edge_prof = config.atlas_profile[profiles.RegKeys.EDGE_AWARE_REANNOTATION] if edge_prof: edge_filt = edge_prof[profiles.RegKeys.WATERSHED_MASK_FILTER] if edge_filt and len(edge_filt) > 1: # watershed mask filter settings from atlas profile seg_args["mask_filt"] = edge_filt[0] seg_args["mask_filt_size"] = edge_filt[1] if mirrorred: # segment only half of image, assuming symmetry len_half = atlas_img_np.shape[sym_axis] // 2 slices = [slice(None)] * labels_img_np.ndim slices[sym_axis] = slice(len_half) sl = tuple(slices) labels_seg = segmenter.segment_from_labels( atlas_edge[sl], markers[sl], labels_img_np[sl], **seg_args) else: # segment the full image, including excluded labels on the opposite side exclude_labels = exclude_labels.tolist().extend( (mirror_mult * exclude_labels).tolist()) seg_args["exclude_labels"] = exclude_labels labels_seg = segmenter.segment_from_labels( atlas_edge, markers, labels_img_np, **seg_args) smoothing = config.atlas_profile["smooth"] if smoothing is not None: # smoothing by opening operation based on profile setting atlas_refiner.smooth_labels( labels_seg, smoothing, config.SmoothingModes.opening) if mirrorred: # mirror back to other half labels_seg = _mirror_imported_labels( labels_seg, len_half, mirror_mult, sym_axis) # expand background to smoothed background of original labels to # roughly match background while still allowing holes to be filled crop = config.atlas_profile["crop_to_orig"] atlas_refiner.crop_to_orig( labels_img_np, labels_seg, crop) if labels_seg.dtype != labels_img_np.dtype: # watershed may give different output type, so cast back if so labels_seg = labels_seg.astype(labels_img_np.dtype) labels_sitk_seg = sitk_io.replace_sitk_with_numpy(labels_sitk, labels_seg) # show DSCs for labels print("\nMeasuring overlap of atlas and combined watershed labels:") atlas_refiner.measure_overlap_combined_labels(atlas_sitk, labels_sitk_seg) print("Measuring overlap of individual original and watershed labels:") atlas_refiner.measure_overlap_labels(labels_sitk, labels_sitk_seg) print("\nMeasuring overlap of combined original and watershed labels:") atlas_refiner.measure_overlap_labels( atlas_refiner.make_labels_fg(labels_sitk), atlas_refiner.make_labels_fg(labels_sitk_seg)) print() # show and write image to same directory as atlas with appropriate suffix sitk_io.write_reg_images( {config.RegNames.IMG_LABELS.value: labels_sitk_seg}, mod_path) if show: sitk.Show(labels_sitk_seg) return path_atlas
def edge_aware_segmentation( path_atlas: str, atlas_profile: atlas_prof.AtlasProfile, show: bool = True, atlas: bool = True, suffix: Optional[str] = None, exclude_labels: Optional[pd.DataFrame] = None, mirror_mult: int = -1): """Segment an atlas using its previously generated edge map. Labels may not match their own underlying atlas image well, particularly in the orthogonal directions in which the labels were not constructed. To improve alignment between the labels and the atlas itself, register the labels to an automated, roughly segmented version of the atlas. The goal is to improve the labels' alignment so that the atlas/labels combination can be used for another form of automated segmentation by registering them to experimental brains via :func:``register``. Edge files are assumed to have been generated by :func:``make_edge_images``. Args: path_atlas: Path to the fixed file, typically the atlas file with stained sections. The corresponding edge and labels files will be loaded based on this path. atlas_profile: Atlas profile. show: True if the output images should be displayed; defaults to True. atlas: True if the primary image is an atlas, which is assumed to be symmetrical. False if the image is an experimental/sample image, in which case segmentation will be performed on the full images, and stats will not be performed. suffix: Modifier to append to end of ``path_atlas`` basename for registered image files that were output to a modified name; defaults to None. If ``atlas`` is True, ``suffix`` will only be applied to saved files, with files still loaded based on the original path. exclude_labels: Sequence of labels to exclude from the segmentation; defaults to None. mirror_mult: Multiplier for mirrored labels; defaults to -1 to make mirrored labels the inverse of their source labels. """ # adjust image path with suffix load_path = path_atlas mod_path = path_atlas if suffix is not None: mod_path = libmag.insert_before_ext(mod_path, suffix) if atlas: load_path = mod_path # load corresponding files via SimpleITK atlas_sitk = sitk_io.load_registered_img( load_path, config.RegNames.IMG_ATLAS.value, get_sitk=True) atlas_sitk_edge = sitk_io.load_registered_img( load_path, config.RegNames.IMG_ATLAS_EDGE.value, get_sitk=True) labels_sitk = sitk_io.load_registered_img( load_path, config.RegNames.IMG_LABELS.value, get_sitk=True) labels_sitk_markers = sitk_io.load_registered_img( load_path, config.RegNames.IMG_LABELS_MARKERS.value, get_sitk=True) # get Numpy arrays of images atlas_img_np = sitk.GetArrayFromImage(atlas_sitk) atlas_edge = sitk.GetArrayFromImage(atlas_sitk_edge) labels_img_np = sitk.GetArrayFromImage(labels_sitk) markers = sitk.GetArrayFromImage(labels_sitk_markers) # segment image from markers sym_axis = atlas_refiner.find_symmetric_axis(atlas_img_np) mirrorred = atlas and sym_axis >= 0 len_half = None seg_args = {"exclude_labels": exclude_labels} edge_prof = atlas_profile[profiles.RegKeys.EDGE_AWARE_REANNOTATION] if edge_prof: edge_filt = edge_prof[profiles.RegKeys.WATERSHED_MASK_FILTER] if edge_filt and len(edge_filt) > 1: # watershed mask filter settings from atlas profile seg_args["mask_filt"] = edge_filt[0] seg_args["mask_filt_size"] = edge_filt[1] if mirrorred: # segment only half of image, assuming symmetry len_half = atlas_img_np.shape[sym_axis] // 2 slices = [slice(None)] * labels_img_np.ndim slices[sym_axis] = slice(len_half) sl = tuple(slices) labels_seg = segmenter.segment_from_labels( atlas_edge[sl], markers[sl], labels_img_np[sl], **seg_args) else: # segment the full image, including excluded labels on the opposite side exclude_labels = exclude_labels.tolist().extend( (mirror_mult * exclude_labels).tolist()) seg_args["exclude_labels"] = exclude_labels labels_seg = segmenter.segment_from_labels( atlas_edge, markers, labels_img_np, **seg_args) smoothing = atlas_profile["smooth"] smoothing_mode = atlas_profile["smoothing_mode"] cond = ["edge-aware_seg"] if smoothing is not None: # smoothing by opening operation based on profile setting meas_smoothing = atlas_profile["meas_smoothing"] cond.append("smoothing") df_aggr, df_raw = atlas_refiner.smooth_labels( labels_seg, smoothing, smoothing_mode, meas_smoothing, labels_sitk.GetSpacing()[::-1]) df_base_path = os.path.splitext(mod_path)[0] if df_raw is not None: # write raw smoothing metrics df_io.data_frames_to_csv( df_raw, f"{df_base_path}_{config.PATH_SMOOTHING_RAW_METRICS}") if df_aggr is not None: # write aggregated smoothing metrics df_io.data_frames_to_csv( df_aggr, f"{df_base_path}_{config.PATH_SMOOTHING_METRICS}") if mirrorred: # mirror back to other half labels_seg = _mirror_imported_labels( labels_seg, len_half, mirror_mult, sym_axis) # expand background to smoothed background of original labels to # roughly match background while still allowing holes to be filled crop = atlas_profile["crop_to_orig"] atlas_refiner.crop_to_orig( labels_img_np, labels_seg, crop) if labels_seg.dtype != labels_img_np.dtype: # watershed may give different output type, so cast back if so labels_seg = labels_seg.astype(labels_img_np.dtype) labels_sitk_seg = sitk_io.replace_sitk_with_numpy(labels_sitk, labels_seg) # show DSCs for labels _logger.info( "\nMeasuring overlap of individual original and watershed labels:") dsc_lbls_comb = atlas_refiner.measure_overlap_labels( labels_sitk, labels_sitk_seg) _logger.info( "\nMeasuring overlap of combined original and watershed labels:") dsc_lbls_indiv = atlas_refiner.measure_overlap_labels( atlas_refiner.make_labels_fg(labels_sitk), atlas_refiner.make_labels_fg(labels_sitk_seg)) _logger.info("") # measure and save whole atlas metrics metrics = { config.AtlasMetrics.SAMPLE: [os.path.basename(mod_path)], config.AtlasMetrics.REGION: config.REGION_ALL, config.AtlasMetrics.CONDITION: "|".join(cond), config.AtlasMetrics.DSC_LABELS_ORIG_NEW_COMBINED: dsc_lbls_comb, config.AtlasMetrics.DSC_LABELS_ORIG_NEW_INDIV: dsc_lbls_indiv, } df_metrics_path = libmag.combine_paths( mod_path, config.PATH_ATLAS_IMPORT_METRICS) atlas_refiner.measure_atlas_refinement( metrics, atlas_sitk, labels_sitk_seg, atlas_profile, df_metrics_path) # show and write image to same directory as atlas with appropriate suffix sitk_io.write_reg_images( {config.RegNames.IMG_LABELS.value: labels_sitk_seg}, mod_path) if show: sitk.Show(labels_sitk_seg) return path_atlas