Source code for segmentation_postprocessing


"""
Author: Md Abdul Kader Sagar
Email: sagarm2@nih.gov
Institute: National Cancer Institute/NIH

This code is designed for analyzing metaphase chromosomes using the Napari platform.
It facilitates the visualization and segmentation of chromosome images, enabling users 
to efficiently assess chromosome structures and perform quantitative analysis.
The code integrates tools for detecting centromeres and measuring CENP-A levels 
within metaphase chromosome regions, enhancing the accuracy of chromosome analysis.
"""



from skimage import draw, morphology
import scipy.ndimage as ndi
import numpy as np
import os
from scipy import ndimage  # Add this import



from napari.utils.notifications import show_info


[docs] class SegmentationPostprocessing: def __init__(self, viewer, processor, chromosome_counter): self.viewer = viewer self.processor = processor self.chromosome_counter = chromosome_counter self.segmentation_modified = False self.current_folder_path = None # Add this line
[docs] def set_current_folder(self, folder_path): """Set the current folder path""" self.current_folder_path = folder_path
def _save_updated_segmentation(self, updated_labels): """Helper function to save updated segmentation""" try: if self.current_folder_path: intermediate_path = os.path.join(self.current_folder_path, "intermediate_results") os.makedirs(intermediate_path, exist_ok=True) np.save(os.path.join(intermediate_path, "segmentation.npy"), updated_labels) print("Saved updated segmentation") else: print("No folder path available to save segmentation") except Exception as e: print(f"Error saving segmentation: {str(e)}")
[docs] def save_segmentation(self): try: if self.processor.nuclei is None: self.show_info("No segmentation to save. Please segment chromosomes first.") return if not self.current_folder_path: self.show_info("No folder selected. Please load images first.") return # Create intermediate_results directory if it doesn't exist intermediate_path = os.path.join(self.current_folder_path, "intermediate_results") os.makedirs(intermediate_path, exist_ok=True) # Save segmentation seg_file = os.path.join(intermediate_path, "segmentation.npy") np.save(seg_file, self.processor.nuclei) self.show_info(f"Segmentation saved to {seg_file}") except Exception as e: self.show_info(f"Error saving segmentation: {str(e)}")
[docs] def remove_chromosome(self): try: shapes_layer = self.viewer.layers['Shapes'] if self.processor.nuclei is None: show_info("No segmented chromosomes found. Please segment chromosomes first.") return current_labels = self.processor.nuclei.copy() if len(shapes_layer.data) == 0: show_info("Please draw a line or select points to remove chromosomes.") return # Get all unique labels along the line or at points labels_to_remove = set() for shape_coords in shapes_layer.data: # For each shape (line or point) if len(shape_coords) == 1: # Single point y, x = int(shape_coords[0][0]), int(shape_coords[0][1]) label_value = current_labels[y, x] if label_value > 0: labels_to_remove.add(label_value) else: # Line or multiple points # Create line mask mask = np.zeros_like(current_labels, dtype=bool) for i in range(len(shape_coords) - 1): start_y, start_x = int(shape_coords[i][0]), int(shape_coords[i][1]) end_y, end_x = int(shape_coords[i+1][0]), int(shape_coords[i+1][1]) rr, cc = draw.line(start_y, start_x, end_y, end_x) valid_coords = (rr < current_labels.shape[0]) & (cc < current_labels.shape[1]) rr, cc = rr[valid_coords], cc[valid_coords] mask[rr, cc] = True # Get unique labels along the line line_labels = set(current_labels[mask]) line_labels.discard(0) # Remove background label labels_to_remove.update(line_labels) if len(labels_to_remove) == 0: show_info("No chromosomes selected for removal.") return # Remove selected chromosomes for label in labels_to_remove: current_labels[current_labels == label] = 0 print(f"Removed chromosomes with labels: {labels_to_remove}") # Update processor and display self.processor.nuclei = current_labels.copy() self.processor.nuclei_split = current_labels.copy() # Update or create the labels layer if 'Segmentation updated' in self.viewer.layers: self.viewer.layers['Segmentation updated'].data = self.processor.nuclei else: self.viewer.add_labels(self.processor.nuclei, name='Segmentation updated') # Update chromosome count unique_chromosomes = len(np.unique(self.processor.nuclei)) - 1 self.chromosome_counter.update_count(unique_chromosomes) # Set modified flag self.segmentation_modified = True # Clear the shapes layer shapes_layer.data = [] show_info(f"Removed chromosomes. New total: {unique_chromosomes}") except Exception as e: show_info(f"Error removing chromosomes: {str(e)}")
[docs] def split_chromosome(self): try: shapes_layer = self.viewer.layers['Shapes'] if self.processor.nuclei is None: show_info("No segmented chromosomes found. Please segment chromosomes first.") return current_labels = self.processor.nuclei.copy() if len(shapes_layer.data) == 0: show_info("Please draw a line to split chromosome.") return # Create line mask for the drawn line shape_coords = shapes_layer.data[0] # Use first line mask = np.zeros_like(current_labels, dtype=bool) for i in range(len(shape_coords) - 1): start_y, start_x = int(shape_coords[i][0]), int(shape_coords[i][1]) end_y, end_x = int(shape_coords[i+1][0]), int(shape_coords[i+1][1]) rr, cc = draw.line(start_y, start_x, end_y, end_x) valid_coords = (rr < current_labels.shape[0]) & (cc < current_labels.shape[1]) rr, cc = rr[valid_coords], cc[valid_coords] mask[rr, cc] = True # Get unique labels along the line line_labels = set(current_labels[mask]) line_labels.discard(0) # Remove background label if len(line_labels) != 1: show_info("Please draw a line through a single chromosome.") return label_to_split = line_labels.pop() # Create a mask of the selected chromosome chrom_mask = current_labels == label_to_split # Dilate the line mask slightly to ensure separation split_line = morphology.binary_dilation(mask, morphology.disk(1)) # Remove the line from the chromosome chrom_mask[split_line] = False # Label the separated regions labeled_regions, num_regions = ndi.label(chrom_mask) if num_regions < 2: show_info("Split failed - please draw the line completely across the chromosome.") return # Update the labels new_label = np.max(current_labels) + 1 region_mask = labeled_regions == 2 # Use second region current_labels[region_mask] = new_label # Update processor and display self._update_display(current_labels) print(f"Split chromosome {label_to_split} into {label_to_split} and {new_label}") show_info(f"Split chromosome. New total: {len(np.unique(current_labels)) - 1}") # Set modified flag self.segmentation_modified = True except Exception as e: show_info(f"Error splitting chromosome: {str(e)}")
[docs] def merge_chromosomes(self): try: shapes_layer = self.viewer.layers['Shapes'] if self.processor.nuclei is None: show_info("No segmented chromosomes found. Please segment chromosomes first.") return current_labels = self.processor.nuclei.copy() if len(shapes_layer.data) == 0: show_info("Please draw a line or select points to merge chromosomes.") return # Get all unique labels along the line or at points labels_to_merge = set() for shape_coords in shapes_layer.data: # For each shape (line or point) if len(shape_coords) == 1: # Single point y, x = int(shape_coords[0][0]), int(shape_coords[0][1]) label_value = current_labels[y, x] if label_value > 0: labels_to_merge.add(label_value) else: # Line or multiple points # Create line mask mask = np.zeros_like(current_labels, dtype=bool) for i in range(len(shape_coords) - 1): start_y, start_x = int(shape_coords[i][0]), int(shape_coords[i][1]) end_y, end_x = int(shape_coords[i+1][0]), int(shape_coords[i+1][1]) rr, cc = draw.line(start_y, start_x, end_y, end_x) valid_coords = (rr < current_labels.shape[0]) & (cc < current_labels.shape[1]) rr, cc = rr[valid_coords], cc[valid_coords] mask[rr, cc] = True # Get unique labels along the line line_labels = set(current_labels[mask]) line_labels.discard(0) # Remove background label labels_to_merge.update(line_labels) if len(labels_to_merge) < 2: show_info("Please select different chromosomes to merge.") return # Merge to the smallest label value target_label = min(labels_to_merge) for label in labels_to_merge: current_labels[current_labels == label] = target_label print(f"Merged chromosomes with labels: {labels_to_merge} to label {target_label}") # Update processor and display self.processor.nuclei = current_labels.copy() self.processor.nuclei_split = current_labels.copy() # Update or create the labels layer if 'Segmentation updated' in self.viewer.layers: self.viewer.layers['Segmentation updated'].data = self.processor.nuclei else: self.viewer.add_labels(self.processor.nuclei, name='Segmentation updated') # Update chromosome count unique_chromosomes = len(np.unique(self.processor.nuclei)) - 1 self.chromosome_counter.update_count(unique_chromosomes) # Set modified flag self.segmentation_modified = True # Clear the shapes layer shapes_layer.data = [] show_info(f"Merged chromosomes. New total: {unique_chromosomes}") except Exception as e: show_info(f"Error merging chromosomes: {str(e)}")
def _update_display(self, current_labels): """ Helper method to update the display and processor data """ self.processor.nuclei = current_labels.copy() self.processor.nuclei_split = current_labels.copy() if 'Segmentation updated' in self.viewer.layers: self.viewer.layers['Segmentation updated'].data = self.processor.nuclei else: self.viewer.add_labels(self.processor.nuclei, name='Segmentation updated') unique_chromosomes = len(np.unique(self.processor.nuclei)) - 1 self.chromosome_counter.update_count(unique_chromosomes) self.viewer.layers['Shapes'].data = []
[docs] def show_info(self, message): """ Helper method to show information messages """ print(message)