diff --git a/extensions/chaeo/batch_jobs/proj0004-exp0038-fixed.py b/extensions/chaeo/batch_jobs/proj0004-exp0038-fixed.py index 6c85309040c09637899cdd43f75846b188c7d3f9..9ad9208e1d9b8f8d31e475872db982c864c9fcc0 100644 --- a/extensions/chaeo/batch_jobs/proj0004-exp0038-fixed.py +++ b/extensions/chaeo/batch_jobs/proj0004-exp0038-fixed.py @@ -2,6 +2,7 @@ from pathlib import Path from model_server.util import autonumber_new_directory, get_matching_files, loop_workflow from extensions.chaeo.workflows import export_patches_from_multichannel_zstack +from extensions.ilastik.models import IlastikPixelClassifierModel if __name__ == '__main__': where_czi = 'z:/rhodes/projects/proj0004-marine-photoactivation/data/exp0038/AutoMic/20230906-163415/Selection' @@ -13,12 +14,11 @@ if __name__ == '__main__': px_ilp = Path.home() / 'model-server' / 'ilastik' / 'AF405-bodies_boundaries.ilp' params = { - 'ilastik_project_file': px_ilp.__str__(), 'pxmap_threshold': 0.25, - 'pixel_class': 0, - 'zmask_channel': 0, + 'pxmap_foreground_channel': 0, + 'segmentation_channel': 0, 'patches_channel': 4, - 'mask_type': 'boxes', + 'zmask_type': 'boxes', 'zmask_filters': {'area': (1e3, 1e8)}, 'zmask_expand_box_by': (128, 3), 'export_pixel_probabilities': False, @@ -35,7 +35,9 @@ if __name__ == '__main__': input_files, where_output, export_patches_from_multichannel_zstack, + [IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)})], params, + catch_and_continue=False, ) print('Finished') \ No newline at end of file diff --git a/extensions/chaeo/workflows.py b/extensions/chaeo/workflows.py index 21880fed686bf50668d2ee805004c455c9d11870..597475f927d155e181d20f550aa76b24d43f4e35 100644 --- a/extensions/chaeo/workflows.py +++ b/extensions/chaeo/workflows.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Dict +from typing import Dict, List from uuid import uuid4 import numpy as np @@ -11,22 +11,77 @@ from extensions.ilastik.models import IlastikPixelClassifierModel from extensions.chaeo.annotators import draw_boxes_on_3d_image from extensions.chaeo.products import export_patches_from_zstack, export_patch_masks_from_zstack, export_multichannel_patches_from_zstack from extensions.chaeo.zmask import build_zmask_from_object_mask, project_stack_from_focal_points +from extensions.ilastik.models import IlastikPixelClassifierModel + from model_server.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file +from model_server.models import Model from model_server.process import rescale from model_server.workflows import Timer +def get_zmask_meta( + input_file_path: str, + ilastik_pixel_classifier: IlastikPixelClassifierModel, + segmentation_channel: int, + pxmap_threshold: float, + pxmap_foreground_channel: int = 0, + zmask_zindex: int = None, + zmask_clip: int = None, + zmask_expand_box_by: int = None, + zmask_filters: Dict = None, + zmask_type: str = 'boxes', + + +) -> Dict: + ti = Timer() + stack = generate_file_accessor(Path(input_file_path)) + fstem = Path(input_file_path).stem + ti.click('file_input') + + # MIP if no zmask z-index is given, then classify pixels + if isinstance(zmask_zindex, int): + assert 0 < zmask_zindex < stack.nz + zmask_data = stack.get_one_channel_data(channel=segmentation_channel).data[:, :, :, zmask_zindex] + else: + zmask_data = stack.get_one_channel_data(channel=segmentation_channel).data.max(axis=-1, keepdims=True) + if zmask_clip: + zmask_data = rescale(zmask_data, zmask_clip) + mip = InMemoryDataAccessor( + zmask_data, + ) + pxmap, _ = ilastik_pixel_classifier.infer(mip) + ti.click('infer_pixel_probability') + + obmask = InMemoryDataAccessor( + pxmap.data > pxmap_threshold + ) + ti.click('threshold_pixel_mask') + + # make zmask + zmask, zmask_meta, df, interm = build_zmask_from_object_mask( + obmask.get_one_channel_data(pxmap_foreground_channel), + stack.get_one_channel_data(segmentation_channel), + mask_type=zmask_type, + filters=zmask_filters, + expand_box_by=zmask_expand_box_by, + ) + zmask_acc = InMemoryDataAccessor(zmask) + ti.click('generate_zmasks') + + return ti, stack, fstem, pxmap, zmask, zmask_meta, df, interm + + # TODO: unpack and validate inputs def export_patches_from_multichannel_zstack( input_file_path: str, output_folder_path: str, - ilastik_project_file: str, + models: List[Model], pxmap_threshold: float, - pixel_class: int, - zmask_channel: int, + pxmap_foreground_channel: int, + segmentation_channel: int, patches_channel: int, zmask_zindex: int = None, # None for MIP, - rescale_zmask_clip: int = None, - mask_type: str = 'boxes', + zmask_clip: int = None, + zmask_type: str = 'boxes', zmask_filters: Dict = None, zmask_expand_box_by: int = None, export_pixel_probabilities=True, @@ -41,27 +96,20 @@ def export_patches_from_multichannel_zstack( rgb_overlay_channels=(None, None, None), rgb_overlay_weights=(1.0, 1.0, 1.0), ) -> Dict: - ti = Timer() - stack = generate_file_accessor(Path(input_file_path)) - fstem = Path(input_file_path).stem - ti.click('file_input') + pixel_classifier = models[0] - # MIP and classify pixels - if isinstance(zmask_zindex, int): - assert 0 < zmask_zindex < stack.nz - zmask_data = stack.get_one_channel_data(channel=zmask_channel).data[:, :, :, zmask_zindex] - else: - zmask_data = stack.get_one_channel_data(channel=zmask_channel).data.max(axis=-1, keepdims=True) - if rescale_zmask_clip: - zmask_data = rescale(zmask_data, rescale_zmask_clip) - mip = InMemoryDataAccessor( - zmask_data, - ) - px_model = IlastikPixelClassifierModel( - params={'project_file': Path(ilastik_project_file)} + ti, stack, fstem, pxmap, zmask, zmask_meta, df, interm = get_zmask_meta( + input_file_path, + pixel_classifier, + segmentation_channel, + pxmap_threshold, + pxmap_foreground_channel=pxmap_foreground_channel, + zmask_zindex=zmask_zindex, + zmask_clip=zmask_clip, + zmask_expand_box_by=zmask_expand_box_by, + zmask_filters=zmask_filters, + zmask_type=zmask_type, ) - pxmap, _ = px_model.infer(mip) - ti.click('infer_pixel_probability') if export_pixel_probabilities: write_accessor_data_to_file( @@ -70,22 +118,6 @@ def export_patches_from_multichannel_zstack( ) ti.click('export_pixel_probability') - obmask = InMemoryDataAccessor( - pxmap.data > pxmap_threshold - ) - ti.click('threshold_pixel_mask') - - # make zmask - zmask, zmask_meta, df, interm = build_zmask_from_object_mask( - obmask.get_one_channel_data(pixel_class), - stack.get_one_channel_data(zmask_channel), - mask_type=mask_type, - filters=zmask_filters, - expand_box_by=zmask_expand_box_by, - ) - zmask_acc = InMemoryDataAccessor(zmask) - ti.click('generate_zmasks') - if export_3d_patches: files = export_patches_from_zstack( Path(output_folder_path) / '3d_patches', @@ -168,7 +200,7 @@ def export_patches_from_multichannel_zstack( ) return { - 'pixel_model_id': px_model.model_id, + 'pixel_model_id': pixel_classifier.model_id, 'input_filepath': input_file_path, 'number_of_objects': len(zmask_meta), 'pixeL_scale_in_micrometers': stack.pixel_scale_in_micrometers, diff --git a/extensions/chaeo/zmask.py b/extensions/chaeo/zmask.py index cc04a7282dc9756561bbcd72cd6dd560d4c1fd60..e9967e70630820d876d49a0b21a2bbe39d2cf082 100644 --- a/extensions/chaeo/zmask.py +++ b/extensions/chaeo/zmask.py @@ -11,7 +11,7 @@ def build_zmask_from_object_mask( obmask: GenericImageDataAccessor, zstack: GenericImageDataAccessor, filters=None, - mask_type='contour', + mask_type='contours', expand_box_by=(0, 0), ): """ diff --git a/model_server/util.py b/model_server/util.py index 8bc071ab056570a594658a0be4fa99eeb9c875dd..1cc33753dbe477e3ddff686f2a5459a93386b7bb 100644 --- a/model_server/util.py +++ b/model_server/util.py @@ -1,10 +1,12 @@ from pathlib import Path import re from time import localtime, strftime +from typing import List import pandas as pd from model_server.accessors import InMemoryDataAccessor, write_accessor_data_to_file +from model_server.models import Model def autonumber_new_directory(where: str, prefix: str) -> str: """ @@ -75,6 +77,7 @@ def loop_workflow( files: list, output_folder_path: str, workflow_func: callable, + models: List[Model], params: dict, export_batch_csvs: bool = True, write_intermediate_products: bool = True, @@ -95,6 +98,7 @@ def loop_workflow( export_kwargs = { 'input_file_path': ff, 'output_folder_path': output_folder_path, + 'models': models, **params, }