diff --git a/analysis/3.configure_phenotype_params.ipynb b/analysis/3.configure_phenotype_params.ipynb index be59881..9c21e43 100644 --- a/analysis/3.configure_phenotype_params.ipynb +++ b/analysis/3.configure_phenotype_params.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -51,6 +51,7 @@ "from tifffile import imread\n", "import matplotlib.pyplot as plt\n", "from microfilm.microplot import Microimage\n", + "from skimage import measure\n", "\n", "from lib.shared.configuration_utils import (\n", " CONFIG_FILE_HEADER,\n", @@ -80,11 +81,7 @@ "\n", "### Channels\n", "- `CHANNEL_NAMES`: A list of names for each channel in your phenotyping image. These names will be used in the output data frame to label the features extracted from each channel.\n", - "- `CHANNEL_CMAPS`: A list of color maps to use when showing channel microimages. These need to be a Matplotlib or microfilm colormap. We recommend using: `[\"pure_red\", \"pure_green\", \"pure_blue\", \"pure_cyan\", \"pure_magenta\", \"pure_yellow\"]`.\n", - "\n", - "### Feature Extraction\n", - "\n", - "- `FOCI_CHANNEL`: Name of the channel used for foci detection (e.g., \"GH2AX\", \"DAPI\"). The channel index will be automatically derived from this name." + "- `CHANNEL_CMAPS`: A list of color maps to use when showing channel microimages. These need to be a Matplotlib or microfilm colormap. We recommend using: `[\"pure_red\", \"pure_green\", \"pure_blue\", \"pure_cyan\", \"pure_magenta\", \"pure_yellow\"]`." ] }, { @@ -100,10 +97,7 @@ "WILDCARDS = dict(well=TEST_WELL, tile=TEST_TILE)\n", "\n", "CHANNEL_NAMES = None\n", - "CHANNEL_CMAPS = None\n", - "\n", - "# Parameters for feature extraction\n", - "FOCI_CHANNEL = None" + "CHANNEL_CMAPS = None" ] }, { @@ -173,6 +167,18 @@ "- `UPSAMPLE_FACTOR`: Subpixel alignment precision factor (default: 2). Higher values provide more precise alignment but increase processing time.\n", "- `WINDOW`: Size of the region used for alignment calculation (default: 2). Higher values use a smaller centered region of the image.\n", "\n", + "**Note for multi-round phenotyping**: For more than 2 imaging cycles (e.g., 3 rounds with repeated DAPI channels), perform sequential alignments by calling `align_phenotype_channels` multiple times in the next cell. Each round should align its channels to the same reference (e.g., the first DAPI).\n", + "\n", + "### Intensity Normalization (optional)\n", + "\n", + "These parameters control percentile intensity normalization (applied before alignment). This can improve alignment when channel intensity varies significantly between cycles (e.g., one cycle has more debris than the other).\n", + "\n", + "- `NORMALIZE_PERCENTILE`: Whether to apply percentile normalization before cross-correlation.\n", + "- `NORM_LOWER_PERCENTILE`: Lower percentile for clipping (removes extreme dark values). Default: 1.\n", + "- `NORM_UPPER_PERCENTILE`: Upper percentile for clipping (removes extreme bright values). Default: 99.\n", + "\n", + "**Tip**: If alignment fails for some tiles, try adjusting the percentile range. For images with high background, try `NORM_LOWER_PERCENTILE=30`. For images with very bright spots, try `NORM_UPPER_PERCENTILE=95`.\n", + "\n", "### Custom Alignment (optional)\n", "\n", "- `CUSTOM_CHANNEL_OFFSETS`: Dict mapping channel names to their (y, x) pixel offsets. Can be used independently or in combination with standard alignment for fine-tuning channel registration. Example: `{\"DAPI\": (5, 10), \"AF750\": (3, -2)}` shifts DAPI by 5 pixels up and 10 left, AF750 by 3 up and 2 right. Channel names must match those in `CHANNEL_NAMES`. Offset directions: +y = up, -y = down, +x = left, -x = right." @@ -194,7 +200,12 @@ "WINDOW = 2\n", "\n", "# Set custom channel offsets (use channel names, not indices)\n", - "CUSTOM_CHANNEL_OFFSETS = None # Example: {\"DAPI\": (5, 10), \"AF750\": (3, -2)}\n", + "CUSTOM_CHANNEL_OFFSETS = None # Example: {\"DAPI\": (5, 10), \"AF750\": (3, -2)}\n", + "\n", + "# Percentile normalization\n", + "NORMALIZE_PERCENTILE = False # Apply percentile normalization\n", + "NORM_LOWER_PERCENTILE = 1 # Lower percentile for normalization (clips extreme dark values)\n", + "NORM_UPPER_PERCENTILE = 99 # Upper percentile for normalization (clips extreme bright values)\n", "\n", "# Derive alignment indexes\n", "if ALIGN:\n", @@ -237,6 +248,9 @@ " remove_channel=REMOVE_CHANNEL,\n", " upsample_factor=UPSAMPLE_FACTOR,\n", " window=WINDOW,\n", + " normalize_percentile=NORMALIZE_PERCENTILE,\n", + " lower_percentile=NORM_LOWER_PERCENTILE,\n", + " upper_percentile=NORM_UPPER_PERCENTILE,\n", " verbose=True,\n", " )\n", " # Automatically remove channels based on REMOVE_CHANNEL\n", @@ -298,19 +312,25 @@ "### Segmentation\n", "\n", "**IMPORTANT: GPU Recommendation for CPSAM**\n", - "If testing the CPSAM model (`cyto_model=\"cpsam\"`), we strongly recommend:\n", + "If testing the CPSAM model (`cellpose_model=\"cpsam\"`), we strongly recommend:\n", "- Using a GPU-enabled machine (`GPU=True`)\n", "- Allocating sufficient time (segmentation can take 30+ minutes per tile)\n", "- Consider running this notebook in a GPU-enabled environment or testing on a smaller region\n", "\n", + "#### Common Parameters\n", + "- `GPU`: Set to True to use GPU acceleration (if available).\n", + "- `RECONCILE`: Method for reconciling nuclei and cell masks (typically \"contained_in_cells\", which allows more than one nucleus per cell and is useful for cells that are dividing).\n", + "- `SEGMENT_CELLS`: Whether to segment cells, or only segment nuclei. If your analysis only requires nuclear features, set to False for faster processing.\n", + "\n", "#### Select Segmentation Method\n", "- `SEGMENTATION_METHOD`: Choose from \"cellpose\" or \"stardist\" for cell segmentation.\n", "\n", "#### Cellpose Parameters (if using \"cellpose\")\n", - "- `CELLPOSE_MODEL`: CellPose model to use. Options: \"cyto3\" (default), \"cyto2\", \"cyto\", or \"cpsam\" (requires Cellpose 4.x).\n", + "- `CELLPOSE_MODEL`: CellPose model to use. Options: \"cyto3\" (default), \"cyto2\", \"cyto\", \"nuclei\", or \"cpsam\" (requires Cellpose 4.x).\n", + " - Note: When `SEGMENT_CELLS=False`, you can still use \"cyto3\" instead of \"nuclei\" if the nuclei model produces poor results.\n", "- `CELL_FLOW_THRESHOLD` & `NUCLEI_FLOW_THRESHOLD`: Flow threshold for Cellpose segmentation. Default is 0.4.\n", "- `CELL_CELLPROB_THRESHOLD` & `NUCLEI_CELLPROB_THRESHOLD`: Cell probability threshold for Cellpose. Default is 0.\n", - "- `HELPER_INDEX`: (Optional) Index of additional channel to help with CPSAM segmentation. Only used with `cyto_model=\"cpsam\"`. Default is None.\n", + "- `HELPER_INDEX`: (Optional) Index of additional channel to help with CPSAM segmentation. Only used with `cellpose_model=\"cpsam\"`. Default is None.\n", "- Note: For Cellpose 3.x models (cyto3, cyto2), nuclei and cell diameters will be estimated automatically. For CPSAM (Cellpose 4.x), diameters can be left as None and will be estimated from initial segmentation results.\n", "\n", "#### StarDist Parameters (if using \"stardist\")\n", @@ -329,6 +349,7 @@ "CYTO_CHANNEL = None\n", "GPU = False\n", "RECONCILE = \"contained_in_cells\"\n", + "SEGMENT_CELLS = True\n", "DAPI_INDEX = CHANNEL_NAMES.index(\"DAPI\")\n", "CYTO_INDEX = CHANNEL_NAMES.index(CYTO_CHANNEL)\n", "\n", @@ -337,7 +358,7 @@ "\n", "if SEGMENTATION_METHOD == \"cellpose\":\n", " # Parameters for CellPose method\n", - " CELLPOSE_MODEL = \"cyto3\"\n", + " CELLPOSE_MODEL = \"cpsam\"\n", " NUCLEI_FLOW_THRESHOLD = 0.4\n", " NUCLEI_CELLPROB_THRESHOLD = 0.0\n", " CELL_FLOW_THRESHOLD = 1\n", @@ -352,7 +373,7 @@ " aligned_image,\n", " dapi_index=DAPI_INDEX,\n", " cyto_index=CYTO_INDEX,\n", - " cyto_model=CELLPOSE_MODEL,\n", + " cellpose_model=CELLPOSE_MODEL,\n", " )\n", " else:\n", " print(\"CPSAM model selected. Initial diameters set to None.\")\n", @@ -379,7 +400,7 @@ "\n", "if SEGMENTATION_METHOD == \"cellpose\":\n", " from lib.shared.segment_cellpose import segment_cellpose\n", - " nuclei, cells = segment_cellpose(\n", + " result = segment_cellpose(\n", " aligned_image,\n", " dapi_index=DAPI_INDEX,\n", " cyto_index=CYTO_INDEX,\n", @@ -391,15 +412,16 @@ " cell_flow_threshold=CELL_FLOW_THRESHOLD,\n", " cell_cellprob_threshold=CELL_CELLPROB_THRESHOLD,\n", " ),\n", - " cyto_model=CELLPOSE_MODEL,\n", + " cellpose_model=CELLPOSE_MODEL,\n", " helper_index=HELPER_INDEX,\n", " gpu=GPU,\n", " reconcile=RECONCILE,\n", + " cells=SEGMENT_CELLS,\n", " )\n", "\n", "elif SEGMENTATION_METHOD == \"stardist\":\n", " from lib.shared.segment_stardist import segment_stardist\n", - " nuclei, cells = segment_stardist(\n", + " result = segment_stardist(\n", " aligned_image,\n", " dapi_index=DAPI_INDEX,\n", " cyto_index=CYTO_INDEX,\n", @@ -412,8 +434,16 @@ " ),\n", " gpu=GPU,\n", " reconcile=RECONCILE,\n", + " cells=SEGMENT_CELLS,\n", " )\n", "\n", + "# Handle unpacking based on SEGMENT_CELLS\n", + "if SEGMENT_CELLS:\n", + " nuclei, cells = result\n", + "else:\n", + " nuclei = result\n", + " cells = None # No cell segmentation\n", + "\n", "# Create and display micropanel of nuclei segmentation\n", "print(\"Example microplots for DAPI channel and nuclei segmentation:\")\n", "nuclei_cmap = random_cmap(num_colors=len(np.unique(nuclei)))\n", @@ -428,43 +458,47 @@ "nuclei_seg_panel = create_micropanel(nuclei_seg_microimages, add_channel_label=True)\n", "plt.show()\n", "\n", - "# Create and display micropanel of segmented cells\n", - "print(\"Example microplots for merged channels and cells segmentation:\")\n", - "cells_cmap = random_cmap(num_colors=len(np.unique(cells)))\n", - "cells_seg_microimages = [\n", - " Microimage(\n", - " aligned_image,\n", - " channel_names=\"Merged\",\n", - " cmaps=CHANNEL_CMAPS,\n", - " ),\n", - " Microimage(cells, cmaps=cells_cmap, channel_names=\"Cells\"),\n", - "]\n", - "cells_seg_panel = create_micropanel(cells_seg_microimages, add_channel_label=True)\n", - "plt.show()\n", + "# Create and display micropanel of segmented cells (only if segmenting cells)\n", + "if SEGMENT_CELLS:\n", + " print(\"Example microplots for merged channels and cells segmentation:\")\n", + " cells_cmap = random_cmap(num_colors=len(np.unique(cells)))\n", + " cells_seg_microimages = [\n", + " Microimage(\n", + " aligned_image,\n", + " channel_names=\"Merged\",\n", + " cmaps=CHANNEL_CMAPS,\n", + " ),\n", + " Microimage(cells, cmaps=cells_cmap, channel_names=\"Cells\"),\n", + " ]\n", + " cells_seg_panel = create_micropanel(cells_seg_microimages, add_channel_label=True)\n", + " plt.show()\n", "\n", - "# Create and display micropanel of annotated phenotype data\n", - "print(\"Example microplot for phenotype data annotated with segmentation:\")\n", - "annotated_data = image_segmentation_annotations(aligned_image, nuclei, cells)\n", - "annotated_microimage = [\n", - " Microimage(\n", - " annotated_data, channel_names=\"Merged\", cmaps=CHANNEL_CMAPS + [\"pure_cyan\"]\n", + " # Create and display micropanel of annotated phenotype data\n", + " print(\"Example microplot for phenotype data annotated with segmentation:\")\n", + " annotated_data = image_segmentation_annotations(aligned_image, nuclei, cells)\n", + " annotated_microimage = [\n", + " Microimage(\n", + " annotated_data, channel_names=\"Merged\", cmaps=CHANNEL_CMAPS + [\"pure_cyan\"]\n", + " )\n", + " ]\n", + " annotated_panel = create_micropanel(\n", + " annotated_microimage, num_cols=1, figscaling=10, add_channel_label=False\n", " )\n", - "]\n", - "annotated_panel = create_micropanel(\n", - " annotated_microimage, num_cols=1, figscaling=10, add_channel_label=False\n", - ")\n", - "plt.show()\n", + " plt.show()\n", "\n", - "# Create and display micropanel of cytoplasms\n", - "print(\"Example microplots for cytoplasms relative to nuclei:\")\n", - "cytoplasms = identify_cytoplasm_cellpose(nuclei, cells)\n", - "cytoplasms_cmap = random_cmap(num_colors=len(np.unique(cytoplasms)))\n", - "cytoplasms_microimages = [\n", - " Microimage(nuclei, cmaps=nuclei_cmap, channel_names=\"Nuclei\"),\n", - " Microimage(cytoplasms, cmaps=cytoplasms_cmap, channel_names=\"Cytoplasms\"),\n", - "]\n", - "cytoplasms_panel = create_micropanel(cytoplasms_microimages, add_channel_label=True)\n", - "plt.show()\n", + " # Create and display micropanel of cytoplasms\n", + " print(\"Example microplots for cytoplasms relative to nuclei:\")\n", + " cytoplasms = identify_cytoplasm_cellpose(nuclei, cells)\n", + " cytoplasms_cmap = random_cmap(num_colors=len(np.unique(cytoplasms)))\n", + " cytoplasms_microimages = [\n", + " Microimage(nuclei, cmaps=nuclei_cmap, channel_names=\"Nuclei\"),\n", + " Microimage(cytoplasms, cmaps=cytoplasms_cmap, channel_names=\"Cytoplasms\"),\n", + " ]\n", + " cytoplasms_panel = create_micropanel(cytoplasms_microimages, add_channel_label=True)\n", + " plt.show()\n", + "else:\n", + " print(\"Skipping cell/cytoplasm visualization (SEGMENT_CELLS=False)\")\n", + " cytoplasms = None\n", "\n", "if SEGMENTATION_METHOD == \"cellpose\" and CELLPOSE_MODEL == \"cpsam\":\n", " from skimage.measure import regionprops\n", @@ -476,17 +510,18 @@ " estimated_nuclei_diameter = np.mean(nuclei_diameters)\n", " print(f\"Nuclei - Average diameter: {estimated_nuclei_diameter:.2f} pixels\")\n", "\n", - " # Calculate cell diameters \n", - " cells_props = regionprops(cells)\n", - " cells_diameters = [prop.equivalent_diameter for prop in cells_props]\n", - " estimated_cell_diameter = np.mean(cells_diameters)\n", - " print(f\"Cells - Average diameter: {estimated_cell_diameter:.2f} pixels\")\n", + " if SEGMENT_CELLS:\n", + " # Calculate cell diameters \n", + " cells_props = regionprops(cells)\n", + " cells_diameters = [prop.equivalent_diameter for prop in cells_props]\n", + " estimated_cell_diameter = np.mean(cells_diameters)\n", + " print(f\"Cells - Average diameter: {estimated_cell_diameter:.2f} pixels\")\n", + " CELL_DIAMETER = estimated_cell_diameter\n", + " print(f\"\\nUpdated CELL_DIAMETER to {CELL_DIAMETER:.2f} pixels\")\n", " \n", " # Update the diameter variables for config\n", " NUCLEI_DIAMETER = estimated_nuclei_diameter\n", - " CELL_DIAMETER = estimated_cell_diameter\n", - " print(f\"\\nUpdated NUCLEI_DIAMETER to {NUCLEI_DIAMETER:.2f} pixels\")\n", - " print(f\"Updated CELL_DIAMETER to {CELL_DIAMETER:.2f} pixels\")" + " print(f\"Updated NUCLEI_DIAMETER to {NUCLEI_DIAMETER:.2f} pixels\")" ] }, { @@ -505,8 +540,9 @@ "### Feature extraction\n", "\n", "- `CP_METHOD`: Methodology for phenotype feature extraction. \n", - " - `cp_multichannel`: Use emulated code from original _Feldman et. al. 2019_ to extract CellProfiler-like features.\n", - " - `cp_measure`: Use Pythonic version of [CellProfiler](https://github.com/afermg/cp_measure) directly from Imaging Platform. Still in development, may run slowly in Jupyter notebook for testing purposes." + " - `cp_emulator`: Use emulated code from original _Feldman et. al. 2019_ to extract CellProfiler-like features.\n", + " - `cp_measure`: Use Pythonic version of [CellProfiler](https://github.com/afermg/cp_measure) directly from Imaging Platform. Still in development, may run slowly in Jupyter notebook for testing purposes.\n", + "- `FOCI_CHANNEL`: Name of the channel(s) used for foci detection (e.g., \"GH2AX\", \"DAPI\"). Can be a single channel name (string) or a list of channel names. The channel index(es) will be automatically derived from this name." ] }, { @@ -515,7 +551,8 @@ "metadata": {}, "outputs": [], "source": [ - "CP_METHOD = None" + "CP_METHOD = \"cp_emulator\"\n", + "FOCI_CHANNEL = None" ] }, { @@ -526,33 +563,43 @@ "source": [ "print(\"Extracting phenotype features:\")\n", "\n", - "# Compute foci channel index from channel name\n", - "FOCI_CHANNEL_INDEX = CHANNEL_NAMES.index(FOCI_CHANNEL)\n", + "# Compute foci channel index from channel name(s)\n", + "if FOCI_CHANNEL:\n", + " if isinstance(FOCI_CHANNEL, str):\n", + " FOCI_CHANNEL_INDEX = CHANNEL_NAMES.index(FOCI_CHANNEL)\n", + " else:\n", + " FOCI_CHANNEL_INDEX = [CHANNEL_NAMES.index(ch) for ch in FOCI_CHANNEL]\n", + "else:\n", + " FOCI_CHANNEL_INDEX = None\n", "\n", "if CP_METHOD == \"cp_measure\":\n", " from lib.phenotype.extract_phenotype_cp_measure import extract_phenotype_cp_measure\n", " # Extract features using cp_measure\n", + " # Pass cells=None when SEGMENT_CELLS=False to skip cell/cytoplasm feature extraction\n", " phenotype_cp = extract_phenotype_cp_measure(\n", " aligned_image,\n", " nuclei=nuclei,\n", - " cells=cells,\n", + " cells=cells if SEGMENT_CELLS else None,\n", " cytoplasms=cytoplasms,\n", " channel_names=CHANNEL_NAMES,\n", " )\n", - "else:\n", - " from lib.phenotype.extract_phenotype_cp_multichannel import (\n", - " extract_phenotype_cp_multichannel,\n", + "elif CP_METHOD == \"cp_emulator\":\n", + " from lib.phenotype.extract_phenotype_cp_emulator import (\n", + " extract_phenotype_cp_emulator,\n", " )\n", " # Extract features using CellProfiler emulator\n", - " phenotype_cp = extract_phenotype_cp_multichannel(\n", + " # Pass cells=None when SEGMENT_CELLS=False to skip cell/cytoplasm feature extraction\n", + " phenotype_cp = extract_phenotype_cp_emulator(\n", " aligned_image,\n", " nuclei=nuclei,\n", - " cells=cells,\n", + " cells=cells if SEGMENT_CELLS else None,\n", " wildcards=WILDCARDS,\n", " cytoplasms=cytoplasms,\n", " foci_channel=FOCI_CHANNEL_INDEX,\n", " channel_names=CHANNEL_NAMES,\n", " )\n", + "else:\n", + " raise ValueError(f\"Unknown CP_METHOD: {CP_METHOD}. Choose 'cp_measure' or 'cp_emulator'.\")\n", "\n", "phenotype_cp" ] @@ -593,7 +640,103 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Add phenotype process parameters to config file" + "## SET PARAMETERS\n", + "### Evaluation summary through heatmap plots\n", + "Running the phenotype module will generate heatmap plots for evaluating the segmentation and feature extraction processes. We must configure the look of the plots, so that they mimic the real setup.\n", + "- `HEATMAP_PLATE`: The type of multiwell plate. Options are {'6W', '24W', '96W'}.\n", + "- `HEATMAP_SHAPE`: Shape of subplot for each well. Options are {'square', '6W_ph', '6W_sbs', list}. \n", + " 'square' infers dimensions of the smallest square that fits the number of sites. \n", + " '6W_ph' and '6W_sbs' use a common 6 well tile map from a Nikon Ti2/Elements set-up with 20X and 10X objectives, respectively. \n", + " Alternatively, a list can be passed containing the number of sites in each row of a tile layout. This is mapped into a centered shape within a rectangle. Unused corners of this rectangle are plotted as nan. The summation of this list should equal the total number of sites. \n", + "\n", + "**Default**: `HEATMAP_PLATE`: \\\"6W\\\" , `HEATMAP_SHAPE`: \\\"6W_ph\\\"" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "# Define heatmap parameters for plate and shape\n", + "HEATMAP_PLATE = \"6W\"\n", + "HEATMAP_SHAPE = \"6W_ph\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## SET PARAMETERS\n", + "\n", + "### Secondary object detection (optional)\n", + "\n", + "- `SECOND_OBJ_DETECTION`: Whether to perform secondary object detection (e.g., intracellular pathogen, organelles).\n", + "- `SECOND_OBJ_CHANNEL`: Name of the channel used for secondary object detection.\n", + "- `SECOND_OBJ_METHOD`: Segmentation method to use. Options:\n", + " - `\"threshold\"`: Traditional thresholding-based approach\n", + " - `\"cellpose\"`: ML-based segmentation using Cellpose\n", + " - `\"stardist\"`: ML-based segmentation using StarDist\n", + "\n", + "#### Size Filtering (applies to all methods)\n", + "- `SIZE_FILTER_METHOD`: Method for size filtering. Options:\n", + " - `\"feret\"`: Use Feret diameters (min and max widths of rotated bounding box).\n", + " - `\"area\"`: Use pixel area.\n", + "- `SECOND_OBJ_MIN_SIZE`: Minimum size for valid secondary objects. Interpreted as Feret diameter or area depending on `SIZE_FILTER_METHOD`.\n", + "- `SECOND_OBJ_MAX_SIZE`: Maximum size for valid secondary objects.\n", + "\n", + "\n", + "#### Cell Association (applies to all methods)\n", + "- `MAX_OBJECTS_PER_CELL`: Maximum secondary objects allowed per cell.\n", + "- `OVERLAP_THRESHOLD`: Minimum overlap ratio to associate object with cell.\n", + "- `MAX_TOTAL_OBJECTS`: Failsafe limit on detected objects. Returns empty results if exceeded to avoid processing over-segmented images.\n", + "\n", + "#### Cellpose Parameters (if `SECOND_OBJ_METHOD=\"cellpose\"`)\n", + "- `SECOND_OBJ_CELLPOSE_MODEL`: Cellpose model type. Options: `\"cyto3\"` (default), `\"cyto2\"`, `\"cyto\"`, `\"nuclei\"`, etc.\n", + "- `SECOND_OBJ_DIAMETER`: Expected diameter of objects in pixels. If `None`, will be estimated automatically.\n", + "- `SECOND_OBJ_FLOW_THRESHOLD`: Flow error threshold for Cellpose segmentation (default: 0.4).\n", + "- `SECOND_OBJ_CELLPROB_THRESHOLD`: Cell probability threshold for Cellpose (default: 0.0).\n", + "\n", + "#### StarDist Parameters (if `SECOND_OBJ_METHOD=\"stardist\"`)\n", + "- `SECOND_OBJ_STARDIST_MODEL`: StarDist pretrained model name (default: `\"2D_versatile_fluo\"`).\n", + "- `SECOND_OBJ_PROB_THRESHOLD`: Probability threshold for object detection (default: 0.5).\n", + "- `SECOND_OBJ_NMS_THRESHOLD`: Non-maximum suppression threshold (default: 0.4).\n", + "\n", + "#### Threshold Method Parameters (if `SECOND_OBJ_METHOD=\"threshold\"`)\n", + "\n", + "**Pre-processing**\n", + "- `THRESHOLD_SMOOTHING_SCALE`: Sigma for Gaussian smoothing before thresholding.\n", + "- `THRESHOLD_METHOD`: Thresholding method to use. Options:\n", + " - `\"otsu_two_peak\"`: Standard 2-class Otsu thresholding.\n", + " - `\"otsu_three_peak_mid_bg\"`: 3-class Otsu, keeps only highest intensity class.\n", + " - `\"otsu_three_peak_mid_fg\"`: 3-class Otsu, keeps middle and high intensity classes.\n", + " - `\"min_cross_entropy\"`: Minimum cross entropy (Li) thresholding.\n", + "- `USE_MORPHOLOGICAL_OPENING`: Apply morphological opening to separate weakly connected objects.\n", + "- `OPENING_DISK_RADIUS`: Radius of disk structuring element for morphological opening.\n", + "- `FILL_HOLES`: When to fill holes in segmented objects. Options:\n", + " - `\"threshold\"`: Fill holes only after thresholding (before declumping)\n", + " - `\"declump\"`: Fill holes only after declumping (per-label filling)\n", + " - `\"both\"`: Fill holes after both thresholding and declumping (recommended)\n", + " - `\"none\"`: Do not fill holes at any stage\n", + "\n", + "**Declumping Method**\n", + "- `DECLUMP_METHOD`: Method for separating clumped objects. Options:\n", + " - `\"none\"`: No declumping.\n", + " - `\"shape\"`: Distance transform peaks (radial distance).\n", + " - `\"intensity\"`: Local intensity maxima.\n", + " - `\"shape_intensity\"`: Combined distance + intensity peaks.\n", + "- `DECLUMP_MODE`: Watershed segmentation mode. Options:\n", + " - `\"watershed\"`: Standard watershed from markers\n", + " - `\"propagate\"`: Distance propagation variant\n", + " - `\"none\"`: Use markers only without watershed\n", + "\n", + "**Seed Detection**\n", + "- `SUPPRESS_LOCAL_MAXIMA`: Minimum spacing between seed points in pixels. Controls spatial separation of detected peaks. Default: 20. Decrease if objects are being merged together. Increase if objects are being over-split.\n", + "- `MAXIMA_REDUCTION_FACTOR`: H-minima threshold for suppressing weak peaks (range: 0.0-1.0). Higher values = more aggressive suppression. If None, no h-minima filtering applied. Applied during seed detection (before watershed).\n", + "\n", + "**Shape Refinement**\n", + "- `USE_SHAPE_REFINEMENT`: Apply boundary quality control after declumping. When enabled, evaluates watershed splits and rejects splits where the dividing boundary is long relative to perimeter.\n", + "- `PROPORTION_THRESHOLD`: Boundary/perimeter ratio threshold for shape refinement. Only used when `USE_SHAPE_REFINEMENT=True`. Splits accepted if boundary_length / perimeter < proportion_threshold." ] }, { @@ -601,6 +744,268 @@ "execution_count": null, "metadata": {}, "outputs": [], + "source": [ + "# Set secondary object parameters\n", + "SECOND_OBJ_DETECTION = False\n", + "SECOND_OBJ_CHANNEL = None\n", + "SECOND_OBJ_METHOD = None # \"threshold\", \"cellpose\", or \"stardist\"\n", + "\n", + "# Common parameters (apply to all methods)\n", + "SECOND_OBJ_MIN_SIZE = None\n", + "SECOND_OBJ_MAX_SIZE = None\n", + "SIZE_FILTER_METHOD = None\n", + "MAX_OBJECTS_PER_CELL = None\n", + "OVERLAP_THRESHOLD = 0.1\n", + "MAX_TOTAL_OBJECTS = None\n", + "\n", + "# Cellpose parameters (only used if SECOND_OBJ_METHOD == \"cellpose\")\n", + "SECOND_OBJ_CELLPOSE_MODEL = None\n", + "SECOND_OBJ_DIAMETER = None # None = auto-estimate, or specify in pixels\n", + "SECOND_OBJ_FLOW_THRESHOLD = None\n", + "SECOND_OBJ_CELLPROB_THRESHOLD = None\n", + "\n", + "# StarDist parameters (only used if SECOND_OBJ_METHOD == \"stardist\")\n", + "SECOND_OBJ_STARDIST_MODEL = \"2D_versatile_fluo\"\n", + "SECOND_OBJ_PROB_THRESHOLD = 0.5\n", + "SECOND_OBJ_NMS_THRESHOLD = 0.4\n", + "\n", + "# Threshold method parameters (only used if SECOND_OBJ_METHOD == \"threshold\")\n", + "RETURN_INTERMEDIATE_OUTPUTS = False\n", + "THRESHOLD_SMOOTHING_SCALE = 0\n", + "THRESHOLD_METHOD = \"otsu_two_peak\"\n", + "USE_MORPHOLOGICAL_OPENING = False\n", + "OPENING_DISK_RADIUS = 2\n", + "FILL_HOLES = None\n", + "DECLUMP_METHOD = None\n", + "DECLUMP_MODE = \"watershed\"\n", + "SUPPRESS_LOCAL_MAXIMA = None\n", + "MAXIMA_REDUCTION_FACTOR = None\n", + "USE_SHAPE_REFINEMENT = False\n", + "PROPORTION_THRESHOLD = None\n", + "\n", + "# Derive secondary object channel index from CHANNEL_NAMES\n", + "if SECOND_OBJ_DETECTION:\n", + " SECOND_OBJ_CHANNEL_INDEX = CHANNEL_NAMES.index(SECOND_OBJ_CHANNEL)\n", + " \n", + " # Optionally estimate diameter for Cellpose if set to None\n", + " if SECOND_OBJ_METHOD == \"cellpose\" and SECOND_OBJ_DIAMETER is None:\n", + " from lib.phenotype.segment_secondary_object import estimate_second_obj_diameter\n", + " \n", + " print(f\"Estimating diameter for secondary objects in {SECOND_OBJ_CHANNEL} channel...\")\n", + " \n", + " # Use manual estimation for cpsam (Cellpose 4.x) since it doesn't support auto diameter\n", + " # Use cellpose estimation for cyto3/other models (Cellpose 3.x)\n", + " estimation_method = \"manual\" if SECOND_OBJ_CELLPOSE_MODEL == \"cpsam\" else \"cellpose\"\n", + " \n", + " SECOND_OBJ_DIAMETER = estimate_second_obj_diameter(\n", + " aligned_image,\n", + " SECOND_OBJ_CHANNEL_INDEX,\n", + " method=estimation_method,\n", + " model_type=SECOND_OBJ_CELLPOSE_MODEL,\n", + " gpu=GPU\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Segment secondary objects if enabled\n", + "if SECOND_OBJ_DETECTION:\n", + " print(f\"Performing secondary object segmentation with {SECOND_OBJ_CHANNEL} using {SECOND_OBJ_METHOD} method...\")\n", + " \n", + " # Prepare nuclei centroids for distance calculations (optional)\n", + " nuclei_regions = measure.regionprops(nuclei)\n", + " nuclei_centroids_dict = {region.label: region.centroid for region in nuclei_regions}\n", + "\n", + " if SECOND_OBJ_METHOD in [\"cellpose\", \"stardist\"]:\n", + " # ML-based segmentation\n", + " from lib.phenotype.segment_secondary_object import (\n", + " segment_second_objs_ml,\n", + " create_second_obj_boundary_visualization,\n", + " create_second_obj_standard_visualization,\n", + " )\n", + " \n", + " # Build ML parameters based on method\n", + " ml_params = {\n", + " 'second_obj_method': SECOND_OBJ_METHOD,\n", + " 'gpu': GPU,\n", + " }\n", + " \n", + " if SECOND_OBJ_METHOD == \"cellpose\":\n", + " ml_params.update({\n", + " 'second_obj_cellpose_model': SECOND_OBJ_CELLPOSE_MODEL,\n", + " 'second_obj_diameter': SECOND_OBJ_DIAMETER,\n", + " 'second_obj_flow_threshold': SECOND_OBJ_FLOW_THRESHOLD,\n", + " 'second_obj_cellprob_threshold': SECOND_OBJ_CELLPROB_THRESHOLD,\n", + " })\n", + " elif SECOND_OBJ_METHOD == \"stardist\":\n", + " ml_params.update({\n", + " 'second_obj_stardist_model': SECOND_OBJ_STARDIST_MODEL,\n", + " 'second_obj_prob_threshold': SECOND_OBJ_PROB_THRESHOLD,\n", + " 'second_obj_nms_threshold': SECOND_OBJ_NMS_THRESHOLD,\n", + " })\n", + " \n", + " # Call ML segmentation\n", + " result = segment_second_objs_ml(\n", + " image=aligned_image,\n", + " second_obj_channel_index=SECOND_OBJ_CHANNEL_INDEX,\n", + " cell_masks=cells,\n", + " cytoplasm_masks=cytoplasms,\n", + " second_obj_min_size=SECOND_OBJ_MIN_SIZE,\n", + " second_obj_max_size=SECOND_OBJ_MAX_SIZE,\n", + " size_filter_method=SIZE_FILTER_METHOD,\n", + " max_objects_per_cell=MAX_OBJECTS_PER_CELL,\n", + " overlap_threshold=OVERLAP_THRESHOLD,\n", + " nuclei_centroids=nuclei_centroids_dict,\n", + " max_total_objects=MAX_TOTAL_OBJECTS,\n", + " **ml_params\n", + " )\n", + " \n", + " # Unpack outputs (ML methods don't return threshold_output)\n", + " second_obj_masks, cell_second_obj_table, updated_cytoplasms = result\n", + " threshold_output = None\n", + " \n", + " elif SECOND_OBJ_METHOD == \"threshold\":\n", + " # Traditional thresholding-based segmentation\n", + " from lib.phenotype.segment_secondary_object import (\n", + " segment_second_objs,\n", + " create_second_obj_boundary_visualization,\n", + " create_second_obj_standard_visualization,\n", + " )\n", + " \n", + " # Segment secondary objects with threshold method\n", + " result = segment_second_objs(\n", + " image=aligned_image,\n", + " second_obj_channel_index=SECOND_OBJ_CHANNEL_INDEX,\n", + " cell_masks=cells,\n", + " cytoplasm_masks=cytoplasms,\n", + " second_obj_min_size=SECOND_OBJ_MIN_SIZE,\n", + " second_obj_max_size=SECOND_OBJ_MAX_SIZE,\n", + " size_filter_method=SIZE_FILTER_METHOD,\n", + " threshold_smoothing_scale=THRESHOLD_SMOOTHING_SCALE,\n", + " threshold_method=THRESHOLD_METHOD,\n", + " use_morphological_opening=USE_MORPHOLOGICAL_OPENING,\n", + " opening_disk_radius=OPENING_DISK_RADIUS,\n", + " fill_holes=FILL_HOLES,\n", + " declump_method=DECLUMP_METHOD,\n", + " declump_mode=DECLUMP_MODE,\n", + " suppress_local_maxima=SUPPRESS_LOCAL_MAXIMA,\n", + " maxima_reduction_factor=MAXIMA_REDUCTION_FACTOR,\n", + " use_shape_refinement=USE_SHAPE_REFINEMENT,\n", + " proportion_threshold=PROPORTION_THRESHOLD,\n", + " max_objects_per_cell=MAX_OBJECTS_PER_CELL,\n", + " overlap_threshold=OVERLAP_THRESHOLD,\n", + " nuclei_centroids=nuclei_centroids_dict,\n", + " max_total_objects=MAX_TOTAL_OBJECTS,\n", + " return_threshold_output=RETURN_INTERMEDIATE_OUTPUTS,\n", + " )\n", + " \n", + " # Unpack outputs (threshold method may return threshold_output)\n", + " second_obj_masks, cell_second_obj_table, updated_cytoplasms, *opt = result\n", + " threshold_output = opt[0] if opt else None\n", + " \n", + " else:\n", + " raise ValueError(f\"Unknown SECOND_OBJ_METHOD: {SECOND_OBJ_METHOD}. Use 'threshold', 'cellpose', or 'stardist'\")\n", + "\n", + " cell_summary = cell_second_obj_table[\"cell_summary\"]\n", + "\n", + " # Print statistics\n", + " print(f\"Found secondary objects in {cell_summary['has_second_obj'].sum()} out of {len(cell_summary)} cells\")\n", + " print(f\"Average objects per cell with objects: {cell_summary.loc[cell_summary['has_second_obj'], 'num_second_objs'].mean():.2f}\")\n", + " print(f\"Average secondary object area ratio: {cell_summary['second_obj_area_ratio'].mean():.4f}\")\n", + "\n", + " # Create standard visualizations\n", + " print(\"Example microplots:\")\n", + " panel = create_second_obj_standard_visualization(\n", + " aligned_image,\n", + " SECOND_OBJ_CHANNEL_INDEX,\n", + " SECOND_OBJ_CHANNEL,\n", + " second_obj_masks,\n", + " threshold_output=threshold_output,\n", + " )\n", + " plt.show()\n", + "\n", + " # Create enhanced boundary visualization\n", + " print(\"Enhanced visualization with cell boundaries and secondary object boundaries:\")\n", + " boundary_panel = create_second_obj_boundary_visualization(\n", + " aligned_image,\n", + " SECOND_OBJ_CHANNEL_INDEX,\n", + " cell_masks=cells,\n", + " second_obj_masks=second_obj_masks,\n", + " channel_names=CHANNEL_NAMES,\n", + " channel_cmaps=CHANNEL_CMAPS,\n", + " )\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if SECOND_OBJ_DETECTION:\n", + " # Extract phenotype features for secondary objects\n", + " from lib.phenotype.extract_phenotype_second_objs import extract_phenotype_second_objs\n", + "\n", + " second_obj_phenotype = extract_phenotype_second_objs(\n", + " aligned_image,\n", + " second_objs=second_obj_masks,\n", + " second_obj_cell_mapping_df=cell_second_obj_table['second_obj_cell_mapping'],\n", + " wildcards=WILDCARDS,\n", + " foci_channel=FOCI_CHANNEL_INDEX, \n", + " channel_names=CHANNEL_NAMES\n", + " )\n", + "\n", + " average_diameter = second_obj_phenotype['second_obj_diameter'].mean()\n", + " print(f\"Average diameter of secondary objects: {average_diameter}\")\n", + " average_area = second_obj_phenotype['second_obj_area'].mean()\n", + " print(f\"Average area of secondary objects: {average_area}\")\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if SECOND_OBJ_DETECTION:\n", + " # Display feature list\n", + " second_obj_feature_cols = [\n", + " col for col in second_obj_phenotype.columns \n", + " if col not in [\"label\", \"well\", \"tile\", \"cell_label\"]\n", + " ]\n", + " print(f\"\\nNumber of secondary object features: {len(second_obj_feature_cols)}\")\n", + "\n", + " # Apply the function to remove channel names\n", + " second_obj_feature_types = [\n", + " remove_channel_name(feature, CHANNEL_NAMES) \n", + " for feature in second_obj_feature_cols\n", + " ]\n", + "\n", + " # Get unique feature types\n", + " second_obj_unique_types = sorted(set(second_obj_feature_types))\n", + "\n", + " print(\"Unique secondary object feature types:\")\n", + " display(second_obj_unique_types)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Add phenotype process parameters to config file" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], "source": [ "# Add phenotype section\n", "config[\"phenotype\"] = {\n", @@ -612,7 +1017,10 @@ " \"segmentation_method\": SEGMENTATION_METHOD,\n", " \"reconcile\": RECONCILE,\n", " \"gpu\": GPU,\n", + " \"segment_cells\": SEGMENT_CELLS,\n", " \"cp_method\": CP_METHOD,\n", + " \"heatmap_plate\": HEATMAP_PLATE,\n", + " \"heatmap_shape\": HEATMAP_SHAPE,\n", "}\n", "\n", "# Add method-specific parameters based on segmentation method\n", @@ -624,7 +1032,7 @@ " \"nuclei_cellprob_threshold\": NUCLEI_CELLPROB_THRESHOLD,\n", " \"cell_flow_threshold\": CELL_FLOW_THRESHOLD,\n", " \"cell_cellprob_threshold\": CELL_CELLPROB_THRESHOLD,\n", - " \"cyto_model\": CELLPOSE_MODEL,\n", + " \"cellpose_model\": CELLPOSE_MODEL,\n", " })\n", " # Add helper_index only if it's defined\n", " if HELPER_INDEX is not None:\n", @@ -646,6 +1054,57 @@ " config[\"phenotype\"][\"remove_channel\"] = REMOVE_CHANNEL\n", " config[\"phenotype\"][\"upsample_factor\"] = UPSAMPLE_FACTOR\n", " config[\"phenotype\"][\"window\"] = WINDOW\n", + " config[\"phenotype\"][\"normalize_percentile\"] = NORMALIZE_PERCENTILE\n", + " config[\"phenotype\"][\"lower_percentile\"] = NORM_LOWER_PERCENTILE\n", + " config[\"phenotype\"][\"upper_percentile\"] = NORM_UPPER_PERCENTILE\n", + " \n", + "\n", + "# Add secondary object detection parameters\n", + "if SECOND_OBJ_DETECTION:\n", + " # Determine if using ML-based segmentation method\n", + " use_ml_segmentation = SECOND_OBJ_METHOD in [\"cellpose\", \"stardist\"]\n", + " # Common parameters for all methods\n", + " config[\"phenotype\"].update({\n", + " \"second_obj_detection\": SECOND_OBJ_DETECTION,\n", + " \"second_obj_channel_index\": SECOND_OBJ_CHANNEL_INDEX,\n", + " \"second_obj_method\": SECOND_OBJ_METHOD,\n", + " \"use_ml_segmentation\": use_ml_segmentation,\n", + " \"second_obj_min_size\": SECOND_OBJ_MIN_SIZE,\n", + " \"second_obj_max_size\": SECOND_OBJ_MAX_SIZE,\n", + " \"size_filter_method\": SIZE_FILTER_METHOD,\n", + " \"max_objects_per_cell\": MAX_OBJECTS_PER_CELL,\n", + " \"overlap_threshold\": OVERLAP_THRESHOLD,\n", + " \"max_total_objects\": MAX_TOTAL_OBJECTS,\n", + " })\n", + " \n", + " # Add method-specific parameters\n", + " if SECOND_OBJ_METHOD == \"cellpose\":\n", + " config[\"phenotype\"].update({\n", + " \"second_obj_cellpose_model\": SECOND_OBJ_CELLPOSE_MODEL,\n", + " \"second_obj_diameter\": SECOND_OBJ_DIAMETER,\n", + " \"second_obj_flow_threshold\": SECOND_OBJ_FLOW_THRESHOLD,\n", + " \"second_obj_cellprob_threshold\": SECOND_OBJ_CELLPROB_THRESHOLD,\n", + " })\n", + " elif SECOND_OBJ_METHOD == \"stardist\":\n", + " config[\"phenotype\"].update({\n", + " \"second_obj_stardist_model\": SECOND_OBJ_STARDIST_MODEL,\n", + " \"second_obj_prob_threshold\": SECOND_OBJ_PROB_THRESHOLD,\n", + " \"second_obj_nms_threshold\": SECOND_OBJ_NMS_THRESHOLD,\n", + " })\n", + " elif SECOND_OBJ_METHOD == \"threshold\":\n", + " config[\"phenotype\"].update({\n", + " \"threshold_smoothing_scale\": THRESHOLD_SMOOTHING_SCALE,\n", + " \"threshold_method\": THRESHOLD_METHOD,\n", + " \"use_morphological_opening\": USE_MORPHOLOGICAL_OPENING,\n", + " \"opening_disk_radius\": OPENING_DISK_RADIUS,\n", + " \"fill_holes\": FILL_HOLES,\n", + " \"declump_method\": DECLUMP_METHOD,\n", + " \"declump_mode\": DECLUMP_MODE,\n", + " \"suppress_local_maxima\": SUPPRESS_LOCAL_MAXIMA,\n", + " \"maxima_reduction_factor\": MAXIMA_REDUCTION_FACTOR,\n", + " \"use_shape_refinement\": USE_SHAPE_REFINEMENT,\n", + " \"proportion_threshold\": PROPORTION_THRESHOLD,\n", + " })\n", "\n", "# Add custom channel offsets if defined\n", "if CUSTOM_CHANNEL_OFFSETS:\n", @@ -666,7 +1125,7 @@ ], "metadata": { "kernelspec": { - "display_name": "brieflow_test_env", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -680,9 +1139,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.12" + "version": "3.11.14" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/analysis/3b.finetune_cellpose_model.ipynb b/analysis/3b.finetune_cellpose_model.ipynb new file mode 100644 index 0000000..a97aab5 --- /dev/null +++ b/analysis/3b.finetune_cellpose_model.ipynb @@ -0,0 +1,676 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fine-Tune Cellpose for Segmentation\n", + "\n", + "This notebook provides a workflow for fine-tuning Cellpose models (e.g., CPSAM) using your corrected masks.\n", + "\n", + "## Supported Training Modes\n", + "\n", + "- **`secondary_obj`**: Fine-tune for secondary object segmentation (matches `segment_second_objs_ml`)\n", + "- **`cells`**: Fine-tune for cell segmentation (matches `segment_cellpose` with cells=True)\n", + "- **`nuclei`**: Fine-tune for nuclei-only segmentation (matches `segment_cellpose` with cells=False)\n", + "\n", + "## Workflow\n", + "1. Select training mode and configure channel indices\n", + "2. Load training data (images + corrected masks)\n", + "3. Apply data augmentation (critical for small datasets)\n", + "4. Train/fine-tune the model\n", + "5. Evaluate performance\n", + "6. Save the trained model\n", + "7. Test on new images\n", + "\n", + "## Prerequisites\n", + "- Corrected masks saved as `.npy` or `.tif` files (labeled masks where each object has a unique ID)\n", + "- Corresponding images (TIFF format, multi-channel)\n", + "\n", + "## Key Feature: Preprocessing Consistency\n", + "The preprocessing applied during training now matches the preprocessing used in deployment functions:\n", + "- **secondary_obj**: Log scaling + max normalization (matches `segment_second_objs_ml`)\n", + "- **cells**: Log scaling + percentile normalization + RGB conversion (matches `prepare_cellpose`)\n", + "- **nuclei**: Percentile normalization (matches `segment_cellpose_nuclei_rgb`)\n", + "\n", + "This ensures your fine-tuned models perform optimally when deployed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## SET PARAMETERS\n", + "\n", + "### Training Mode\n", + "- `TRAINING_MODE`: Preprocessing mode that must match your deployment target\n", + " - `\"secondary_obj\"`: For segment_second_objs_ml (single channel, log scaling)\n", + " - `\"cells\"`: For segment_cellpose with cells=True (3-channel RGB)\n", + " - `\"nuclei\"`: For segment_cellpose with cells=False (DAPI only)\n", + "\n", + "### Training Data Paths\n", + "- `IMAGE_DIR`: Directory containing training images (TIFF format)\n", + "- `MASK_DIR`: Directory containing corrected masks (NPY format)\n", + "\n", + "### Channel Configuration\n", + "- For `secondary_obj` mode: Set `SECOND_OBJ_CHANNEL` (e.g., CDPK1 channel)\n", + "- For `cells`/`nuclei` modes: Set `DAPI_INDEX`, `CYTO_INDEX`, `HELPER_INDEX`\n", + "\n", + "### Model Configuration\n", + "- `BASE_MODEL`: Base Cellpose model to fine-tune from (\"cpsam\", \"cyto3\", \"cyto2\", \"nuclei\")\n", + "- `MODEL_NAME`: Name for the fine-tuned model\n", + "- `MODEL_SAVE_DIR`: Directory to save the trained model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "# Training_mode - must match your deployment target\n", + "TRAINING_MODE = None #\"secondary_obj\", \"cells\", or \"nuclei\"\n", + "\n", + "# Training data paths\n", + "IMAGE_DIR = Path(\"training_data/images\")\n", + "MASK_DIR = Path(\"training_data/masks\")\n", + "\n", + "# For cells/nuclei modes: channels matching segment_cellpose\n", + "DAPI_INDEX = 0 # Nuclear/DAPI channel\n", + "CYTO_INDEX = None # Cytoplasmic channel (only needed for cells mode)\n", + "HELPER_INDEX = None # Optional helper channel for CPSAM (can be None)\n", + "SECOND_OBJ_CHANNEL = None\n", + "\n", + "# Model configuration\n", + "BASE_MODEL = None # Options: \"cpsam\", \"cyto3\", \"cyto2\", \"nuclei\"\n", + "MODEL_NAME = None\n", + "MODEL_SAVE_DIR = Path(\"models\")\n", + "\n", + "# Create directories if they don't exist\n", + "IMAGE_DIR.mkdir(parents=True, exist_ok=True)\n", + "MASK_DIR.mkdir(parents=True, exist_ok=True)\n", + "MODEL_SAVE_DIR.mkdir(parents=True, exist_ok=True)\n", + "\n", + "print(f\"Training mode: {TRAINING_MODE}\")\n", + "if TRAINING_MODE == \"secondary_obj\":\n", + " print(f\" Channel index: {SECOND_OBJ_CHANNEL}\")\n", + "elif TRAINING_MODE == \"cells\":\n", + " print(f\" DAPI index: {DAPI_INDEX}, Cyto index: {CYTO_INDEX}, Helper index: {HELPER_INDEX}\")\n", + "elif TRAINING_MODE == \"nuclei\":\n", + " print(f\" DAPI index: {DAPI_INDEX}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from tifffile import imread\n", + "from glob import glob\n", + "\n", + "from lib.shared.cellpose_training import (\n", + " load_training_data,\n", + " augment_training_data,\n", + " prepare_cellpose_training,\n", + " train_cellpose,\n", + " load_trained_model,\n", + " predict_masks,\n", + " evaluate_segmentation,\n", + " visualize_comparison,\n", + " visualize_training_sample,\n", + ")\n", + "\n", + "# For preprocessing new images when testing (uses same function as training/deployment)\n", + "from lib.shared.segment_cellpose import prepare_cellpose" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## SET PARAMETERS\n", + "\n", + "### Training Hyperparameters\n", + "- `N_EPOCHS`: Number of training epochs \n", + "- `LEARNING_RATE`: Initial learning rate. Defaults to 1e-5.\n", + "- `BATCH_SIZE`: Training batch size\n", + "- `TEST_FRACTION`: Fraction of data to hold out for testing. Defaults to 0.1.\n", + "\n", + "### Augmentation Settings\n", + "- `USE_AUGMENTATION`: Enable/disable data augmentation\n", + "- `USE_ROTATIONS`: Apply 90/180/270 degree rotations\n", + "- `USE_FLIPS`: Apply horizontal/vertical flips\n", + "- `USE_INTENSITY_SCALING`: Apply random intensity scaling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Training hyperparameters\n", + "N_EPOCHS = None\n", + "LEARNING_RATE = 1e-5\n", + "BATCH_SIZE = None\n", + "TEST_FRACTION = 0.1\n", + "WEIGHT_DECAY = 0.1\n", + "GPU = True\n", + "\n", + "# Augmentation settings (recommended for small datasets)\n", + "USE_AUGMENTATION = True\n", + "USE_ROTATIONS = True\n", + "USE_FLIPS = True\n", + "USE_INTENSITY_SCALING = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Load Training Data\n", + "\n", + "Load paired images and masks. Images should be in TIFF format, masks can be NPY or TIFF format (labeled masks where each object has a unique ID).\n", + "\n", + "**Expected naming convention:**\n", + "- Images: `tile_001.tif`, `tile_002.tif`, etc.\n", + "- Masks: `tile_001_mask.npy` or `tile_001_mask.tif`, etc.\n", + "\n", + "Or you can manually specify the paths below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Find all image and mask files\n", + "image_files = sorted(IMAGE_DIR.glob(\"*.tif\")) + sorted(IMAGE_DIR.glob(\"*.tiff\"))\n", + "mask_files = sorted(MASK_DIR.glob(\"*.npy\")) + sorted(MASK_DIR.glob(\"*.tif\")) + sorted(MASK_DIR.glob(\"*.tiff\"))\n", + "\n", + "print(f\"Found {len(image_files)} images and {len(mask_files)} masks\")\n", + "\n", + "if len(image_files) == 0:\n", + " print(\"\\n\" + \"=\"*60)\n", + " print(\"No training data found!\")\n", + " print(\"\\nPlease add your training data:\")\n", + " print(f\" - Images (.tif): {IMAGE_DIR.absolute()}\")\n", + " print(f\" - Masks (.npy or .tif): {MASK_DIR.absolute()}\")\n", + " print(\"=\"*60)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Option 1: Auto-pair files by name (assumes matching names)\n", + "# This cell pairs images and masks that share the same base identifier (e.g., A1_200)\n", + "import re\n", + "\n", + "def extract_identifier(filename):\n", + " \"\"\"Extract well + tile identifier (e.g., 'A1_200') from filename.\"\"\"\n", + " # Match pattern like A1_200, B12_150, etc.\n", + " match = re.search(r'([A-Z]\\d+_\\d+)', filename)\n", + " return match.group(1) if match else None\n", + "\n", + "def pair_images_masks(image_files, mask_files):\n", + " \"\"\"Pair images with masks based on identifier matching.\"\"\"\n", + " paired_images = []\n", + " paired_masks = []\n", + " \n", + " # Build mask dict keyed by identifier\n", + " mask_dict = {}\n", + " for m in mask_files:\n", + " identifier = extract_identifier(m.stem)\n", + " if identifier:\n", + " mask_dict[identifier] = m\n", + " \n", + " for img_path in image_files:\n", + " identifier = extract_identifier(img_path.stem)\n", + " if identifier and identifier in mask_dict:\n", + " paired_images.append(img_path)\n", + " paired_masks.append(mask_dict[identifier])\n", + " print(f\" Paired: {img_path.name} <-> {mask_dict[identifier].name}\")\n", + " \n", + " return paired_images, paired_masks\n", + "\n", + "if len(image_files) > 0 and len(mask_files) > 0:\n", + " print(\"Pairing images and masks...\")\n", + " image_paths, mask_paths = pair_images_masks(image_files, mask_files)\n", + " print(f\"\\nSuccessfully paired {len(image_paths)} image-mask pairs\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Option 2: Manually specify paths if auto-pairing doesn't work\n", + "# Uncomment and modify this cell if needed\n", + "\n", + "# image_paths = [\n", + "# Path(\"training_data/images/tile_001.tif\"),\n", + "# Path(\"training_data/images/tile_002.tif\"),\n", + "# ]\n", + "# mask_paths = [\n", + "# Path(\"training_data/masks/tile_001_mask.npy\"),\n", + "# Path(\"training_data/masks/tile_002_mask.npy\"),\n", + "# ]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load the training data with mode-specific preprocessing\n", + "if len(image_paths) > 0:\n", + " if TRAINING_MODE == \"secondary_obj\":\n", + " images, masks = load_training_data(\n", + " image_paths,\n", + " mask_paths,\n", + " mode=\"secondary_obj\",\n", + " channel_index=SECOND_OBJ_CHANNEL,\n", + " )\n", + " elif TRAINING_MODE == \"cells\":\n", + " images, masks = load_training_data(\n", + " image_paths,\n", + " mask_paths,\n", + " mode=\"cells\",\n", + " dapi_index=DAPI_INDEX,\n", + " cyto_index=CYTO_INDEX,\n", + " helper_index=HELPER_INDEX,\n", + " )\n", + " elif TRAINING_MODE == \"nuclei\":\n", + " images, masks = load_training_data(\n", + " image_paths,\n", + " mask_paths,\n", + " mode=\"nuclei\",\n", + " dapi_index=DAPI_INDEX,\n", + " )\n", + " else:\n", + " raise ValueError(f\"Unknown TRAINING_MODE: {TRAINING_MODE}\")\n", + " \n", + " print(f\"\\nImage shape: {images[0].shape}\")\n", + " print(f\"Image dtype: {images[0].dtype}\")\n", + " print(f\"Mask shape: {masks[0].shape}\")\n", + " print(f\"Number of objects in first mask: {masks[0].max()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Visualize Training Data\n", + "\n", + "Inspect a few training samples to verify masks are correctly loaded." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize first few training samples\n", + "n_samples_to_show = min(3, len(images))\n", + "\n", + "for i in range(n_samples_to_show):\n", + " fig = visualize_training_sample(\n", + " images[i], \n", + " masks[i], \n", + " title=f\"Training Sample {i+1}\"\n", + " )\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Data Augmentation\n", + "\n", + "For small datasets (5-20 images), augmentation is critical for successful training.\n", + "\n", + "This will expand your dataset by applying:\n", + "- 90°, 180°, 270° rotations (4x multiplier)\n", + "- Horizontal and vertical flips (2x multiplier)\n", + "- Intensity scaling (optional, adds more variation)\n", + "\n", + "**Expected expansion**: 10 images → ~80-160 training samples" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if USE_AUGMENTATION:\n", + " print(f\"Original dataset: {len(images)} samples\")\n", + " \n", + " aug_images, aug_masks = augment_training_data(\n", + " images,\n", + " masks,\n", + " rotations=USE_ROTATIONS,\n", + " flips=USE_FLIPS,\n", + " intensity_scaling=USE_INTENSITY_SCALING,\n", + " )\n", + " \n", + " print(f\"Augmented dataset: {len(aug_images)} samples\")\n", + "else:\n", + " aug_images = images\n", + " aug_masks = masks\n", + " print(f\"Augmentation disabled. Using {len(aug_images)} original samples.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize some augmented samples\n", + "if USE_AUGMENTATION:\n", + " print(\"Sample augmented images:\")\n", + " indices = [0, len(aug_images)//4, len(aug_images)//2, 3*len(aug_images)//4]\n", + " \n", + " fig, axes = plt.subplots(2, 4, figsize=(16, 8))\n", + " for i, idx in enumerate(indices):\n", + " axes[0, i].imshow(aug_images[idx], cmap='gray')\n", + " axes[0, i].set_title(f'Image {idx}')\n", + " axes[0, i].axis('off')\n", + " \n", + " axes[1, i].imshow(aug_masks[idx], cmap='nipy_spectral')\n", + " axes[1, i].set_title(f'Mask {idx}')\n", + " axes[1, i].axis('off')\n", + " \n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Prepare Training/Test Split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Split into training and test sets\n", + "train_images, train_masks, test_images, test_masks = prepare_cellpose_training(\n", + " aug_images,\n", + " aug_masks,\n", + " test_fraction=TEST_FRACTION,\n", + " seed=42\n", + ")\n", + "\n", + "print(f\"\\nTraining set: {len(train_images)} samples\")\n", + "print(f\"Test set: {len(test_images)} samples\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Train the Model\n", + "\n", + "This cell will fine-tune the Cellpose model. Training progress will be displayed.\n", + "\n", + "**Note**: Training can take 30-60 minutes depending on dataset size and GPU availability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train the model\n", + "print(f\"Starting training...\")\n", + "print(f\" Base model: {BASE_MODEL}\")\n", + "print(f\" Epochs: {N_EPOCHS}\")\n", + "print(f\" Learning rate: {LEARNING_RATE}\")\n", + "print(f\" Batch size: {BATCH_SIZE}\")\n", + "print(f\" GPU: {GPU}\")\n", + "print()\n", + "\n", + "model = train_cellpose(\n", + " train_images=train_images,\n", + " train_masks=train_masks,\n", + " test_images=test_images,\n", + " test_masks=test_masks,\n", + " base_model=BASE_MODEL,\n", + " n_epochs=N_EPOCHS,\n", + " learning_rate=LEARNING_RATE,\n", + " weight_decay=WEIGHT_DECAY,\n", + " batch_size=BATCH_SIZE,\n", + " save_path=MODEL_SAVE_DIR,\n", + " model_name=MODEL_NAME,\n", + " gpu=GPU,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Visualize Predictions\n", + "\n", + "Compare predictions from the fine-tuned model against ground truth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get predictions on test set\n", + "pred_masks = predict_masks(model, test_images)\n", + "\n", + "# Visualize comparisons\n", + "n_to_show = min(5, len(test_images))\n", + "\n", + "for i in range(n_to_show):\n", + " fig = visualize_comparison(\n", + " test_images[i],\n", + " pred_masks[i],\n", + " test_masks[i],\n", + " title=f\"Test Image {i+1}\"\n", + " )\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Save Model Information\n", + "\n", + "Save training configuration for reproducibility." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from datetime import datetime\n", + "\n", + "# Save training configuration\n", + "config = {\n", + " \"model_name\": MODEL_NAME,\n", + " \"base_model\": BASE_MODEL,\n", + " \"training_date\": datetime.now().isoformat(),\n", + " \"n_epochs\": N_EPOCHS,\n", + " \"learning_rate\": LEARNING_RATE,\n", + " \"batch_size\": BATCH_SIZE,\n", + " \"n_original_samples\": len(images),\n", + " \"n_augmented_samples\": len(aug_images),\n", + " \"n_train_samples\": len(train_images),\n", + " \"n_test_samples\": len(test_images),\n", + " \"augmentation\": {\n", + " \"enabled\": USE_AUGMENTATION,\n", + " \"rotations\": USE_ROTATIONS,\n", + " \"flips\": USE_FLIPS,\n", + " \"intensity_scaling\": USE_INTENSITY_SCALING,\n", + " },\n", + " # \"metrics\": {\n", + " # \"finetuned\": {\n", + " # \"mean_iou\": finetuned_metrics[\"mean_iou\"],\n", + " # \"mean_precision\": finetuned_metrics[\"mean_precision\"],\n", + " # \"mean_recall\": finetuned_metrics[\"mean_recall\"],\n", + " # \"mean_f1\": finetuned_metrics[\"mean_f1\"],\n", + " # }\n", + " # }\n", + "}\n", + "\n", + "config_path = MODEL_SAVE_DIR / f\"{MODEL_NAME}_config.json\"\n", + "with open(config_path, \"w\") as f:\n", + " json.dump(config, f, indent=2)\n", + "\n", + "print(f\"Training configuration saved to: {config_path}\")\n", + "print(f\"\\nModel saved to: {MODEL_SAVE_DIR / MODEL_NAME}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Evaluate Performance (Optional)\n", + "\n", + "Compare the fine-tuned model against the base model on the test set. This will be computationally intensive and time-consuming. Our recommendation is trying the new model directly in the phenotyping notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# # Evaluate the fine-tuned model\n", + "# print(\"Evaluating fine-tuned model on test set...\")\n", + "# finetuned_metrics = evaluate_segmentation(\n", + "# model,\n", + "# test_images,\n", + "# test_masks,\n", + "# )\n", + "\n", + "# print(f\"\\nFine-tuned Model Performance:\")\n", + "# print(f\" Mean IoU: {finetuned_metrics['mean_iou']:.3f}\")\n", + "# print(f\" Mean Precision: {finetuned_metrics['mean_precision']:.3f}\")\n", + "# print(f\" Mean Recall: {finetuned_metrics['mean_recall']:.3f}\")\n", + "# print(f\" Mean F1: {finetuned_metrics['mean_f1']:.3f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# # Compare with base model (optional)\n", + "# from cellpose import models as cp_models\n", + "\n", + "# print(\"Evaluating base model for comparison...\")\n", + "# base_model = cp_models.CellposeModel(gpu=GPU, model_type=BASE_MODEL)\n", + "\n", + "# base_metrics = evaluate_segmentation(\n", + "# base_model,\n", + "# test_images,\n", + "# test_masks,\n", + "# )\n", + "\n", + "# print(f\"\\nBase Model ({BASE_MODEL}) Performance:\")\n", + "# print(f\" Mean IoU: {base_metrics['mean_iou']:.3f}\")\n", + "# print(f\" Mean Precision: {base_metrics['mean_precision']:.3f}\")\n", + "# print(f\" Mean Recall: {base_metrics['mean_recall']:.3f}\")\n", + "# print(f\" Mean F1: {base_metrics['mean_f1']:.3f}\")\n", + "\n", + "# print(f\"\\n{'='*50}\")\n", + "# print(\"Improvement Summary:\")\n", + "# print(f\" IoU: {finetuned_metrics['mean_iou'] - base_metrics['mean_iou']:+.3f}\")\n", + "# print(f\" Precision: {finetuned_metrics['mean_precision'] - base_metrics['mean_precision']:+.3f}\")\n", + "# print(f\" Recall: {finetuned_metrics['mean_recall'] - base_metrics['mean_recall']:+.3f}\")\n", + "# print(f\" F1: {finetuned_metrics['mean_f1'] - base_metrics['mean_f1']:+.3f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Integration with Analysis Notebooks\n", + "\n", + "### For Cell/Nuclei Segmentation\n", + "\n", + "If you trained with `TRAINING_MODE=\"cells\"` or `\"nuclei\"`, update the `CELLPOSE_MODEL` parameter in your preprocessing notebook:\n", + "\n", + "```python\n", + "# Instead of:\n", + "SECOND_OBJ_CELLPOSE_MODEL = \"cpsam\"\n", + "\n", + "# For cell segmentation:\n", + "CELLPOSE_MODEL = \"models/cpsam_cells\" # Path to your fine-tuned cell model\n", + "\n", + "# For nuclei-only segmentation:\n", + "CELLPOSE_MODEL = \"models/cpsam_nuclei\" # Path to your fine-tuned nuclei model\n", + "```\n", + "\n", + "### For Secondary Object Segmentation\n", + "\n", + "If you trained with `TRAINING_MODE=\"secondary_object\"`, update the `SECOND_OBJ_CELLPOSE_MODEL`\n", + "\n", + "```python\n", + "# Use:\n", + "SECOND_OBJ_CELLPOSE_MODEL = \"models/cpsam_secondary_obj\" # Path to your fine-tuned model\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/analysis/7.configure_aggregate_params.ipynb b/analysis/7.configure_aggregate_params.ipynb index 0aff8ed..c2c22d6 100644 --- a/analysis/7.configure_aggregate_params.ipynb +++ b/analysis/7.configure_aggregate_params.ipynb @@ -372,6 +372,18 @@ "display(class_features)" ] }, + { + "cell_type": "markdown", + "source": "## SET PARAMETERS\n\n### Secondary object aggregation (optional)\n\nIf secondary object detection was enabled in phenotype processing, per-object features (intensity, shape, correlation) are available. This step controls how per-object data is aggregated to cell-level before entering the aggregate pipeline.\n\n- `SECOND_OBJ_AGG_STRATEGY`: Strategy for aggregating secondary object features into cell-level data.\n - `\"none\"`: Skip \u2014 no secondary object features added to aggregate pipeline.\n - `\"single\"`: Only populate features for cells with exactly 1 secondary object; NaN for cells with 0 or 2+ objects. All cells are preserved.\n - `\"all\"`: Create numbered columns per object (`second_obj_area_1`, `second_obj_area_2`, etc.). Variable width, sparse.\n - `\"average\"`: Mean of numeric features across all secondary objects per cell.\n\n**Note**: Cell-level summary metadata (`has_second_obj`, `num_second_objs`, etc.) is always available regardless of strategy and can be used in `FILTER_QUERIES`.", + "metadata": {} + }, + { + "cell_type": "code", + "source": "SECOND_OBJ_AGG_STRATEGY = \"none\" # \"none\", \"single\", \"all\", or \"average\"", + "metadata": {}, + "execution_count": null, + "outputs": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -717,7 +729,7 @@ " \"metadata_cols_fp\": METADATA_COLS_FP,\n", " \"collapse_cols\": COLLAPSE_COLS,\n", " \"classifier_path\": CLASSIFIER_PATH,\n", - " \"aggregate_combo_fp\": AGGREGATE_COMBO_FP,\n", + " \"aggregate_combo_fp\": AGGREGATE_COMBO_FP,\n \"second_obj_agg_strategy\": SECOND_OBJ_AGG_STRATEGY,\n", " \"filter_queries\": FILTER_QUERIES,\n", " \"perturbation_name_col\": PERTURBATION_NAME_COL,\n", " \"drop_cols_threshold\": DROP_COLS_THRESHOLD,\n", diff --git a/analysis/slurm/config.yaml b/analysis/slurm/config.yaml index 69a4d79..e28a446 100644 --- a/analysis/slurm/config.yaml +++ b/analysis/slurm/config.yaml @@ -3,7 +3,7 @@ # BrieFlow cluster configuration file default-resources: - slurm_partition: 20 + slurm_partition: u20 slurm_account: wibrusers mem_mb: 3000 tasks: 1 @@ -29,7 +29,7 @@ set-resources: calculate_ic_sbs: mem_mb: 50000 calculate_ic_phenotype: - mem_mb: 500000 + mem_mb: 900000 # sbs align_sbs: @@ -46,7 +46,7 @@ set-resources: mem_mb: 2500 segment_sbs: # slurm_partition: "'nvidia-2080ti-20'" - mem_mb: 3500 + mem_mb: 8000 cpus_per_task: 4 # slurm_extra: "'--gres=gpu:1'" extract_bases: @@ -66,7 +66,7 @@ set-resources: eval_segmentation_sbs: mem_mb: 50000 eval_mapping: - mem_mb: 50000 + mem_mb: 200000 cpus_per_task: 12 # phenotype @@ -76,7 +76,7 @@ set-resources: mem_mb: 2000 segment_phenotype: # slurm_partition: "'nvidia-2080ti-20'" - mem_mb: 3000 + mem_mb: 16000 cpus_per_task: 4 # slurm_extra: "'--gres=gpu:1'" identify_cytoplasm: @@ -85,9 +85,18 @@ set-resources: mem_mb: 1000 combine_phenotype_info: mem_mb: 20000 - extract_phenotype_cp: + identify_second_objs: + mem_mb: 8000 + cpus_per_task: 4 + extract_phenotype_second_objs: + mem_mb: 2000 + merge_phenotype_second_objs: + mem_mb: 20000 + merge_second_objs_phenotype_cp: + mem_mb: 2000 + extract_phenotype: mem_mb: 3000 - merge_phenotype_cp: + merge_phenotype: mem_mb: 300000 eval_segmentation_phenotype: mem_mb: 50000 @@ -114,6 +123,8 @@ set-resources: mem_mb: 500000 # aggregate + aggregate_cells_second_objs: + mem_mb: 200000 split_datasets: mem_mb: 200000 filter: diff --git a/brieflow b/brieflow index 5e51b21..38272f4 160000 --- a/brieflow +++ b/brieflow @@ -1 +1 @@ -Subproject commit 5e51b21c5a4cd7e69b9e5588fbb7a15f973c9fb0 +Subproject commit 38272f4dc7fa318e6f02cd493ed0b3d9df68c5cd