From 1f118e854c37f9a55cb5624250d426df3dce92dd Mon Sep 17 00:00:00 2001 From: Christopher Rhodes <christopher.rhodes@embl.de> Date: Fri, 20 Oct 2023 10:48:39 +0200 Subject: [PATCH] Lifted z-stack assertion, made util.loop_workflow more generic e.g. for on-the-fly data conversion tasks --- .../actual_runs/20230805_kristineberg_PA.py | 44 ++++++++++--------- extensions/chaeo/util.py | 40 +++++++++-------- extensions/chaeo/workflows.py | 34 +++++++------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/extensions/chaeo/actual_runs/20230805_kristineberg_PA.py b/extensions/chaeo/actual_runs/20230805_kristineberg_PA.py index 09cd9b87..2a59dfe7 100644 --- a/extensions/chaeo/actual_runs/20230805_kristineberg_PA.py +++ b/extensions/chaeo/actual_runs/20230805_kristineberg_PA.py @@ -3,18 +3,21 @@ from pathlib import Path from extensions.chaeo.util import autonumber_new_directory, get_matching_files, loop_workflow from extensions.chaeo.workflows import export_patches_from_multichannel_zstack -from model_server.accessors import CziImageFileAccessor, write_accessor_data_to_file +from model_server.accessors import CziImageFileAccessor, write_accessor_data_to_file, InMemoryDataAccessor +from model_server.process import rescale -def export_single_channel_tif_from_multichannel_czi(files, where_output, channel): - for czif in files: - in_acc = CziImageFileAccessor(czif) - outf = Path(where_output) / (Path(czif).stem + '.tif') - write_accessor_data_to_file( - outf, - in_acc.get_one_channel_data(channel), - ) - print(f'Wrote file: {outf}') +def export_single_channel_tif_from_multichannel_czi(input_file_path, output_folder_path, channel, **kwargs): + in_acc = CziImageFileAccessor(input_file_path) + data = in_acc.get_one_channel_data(channel).data + if 'rescale_zmask_clip' in kwargs: + data = rescale(data, clip=kwargs['rescale_zmask_clip']) + outf = Path(output_folder_path) / (Path(input_file_path).stem + '.tif') + write_accessor_data_to_file( + outf, + InMemoryDataAccessor(data), + ) + print(f'Wrote file: {outf}') if __name__ == '__main__': where_czi = 'c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/TREC_STOP_15_Kristineberg/230805_automic_AI_PA/20230805-122525_AI_PA_successfulrun_recognitiononPLL405cilindionas/Selection' @@ -45,17 +48,18 @@ if __name__ == '__main__': input_files = get_matching_files(where_czi, 'czi') tif_export_params = { - 'channel': 0 + 'channel': 0, + 'rescale_zmask_clip': 0.01, } - export_single_channel_tif_from_multichannel_czi(input_files, where_output, 0) - - # loop_workflow( - # input_files, - # where_output, - # export_single_channel_tif_from_multichannel_czi, - # tif_export_params, - # catch_and_continue=False, - # ) + loop_workflow( + input_files, + where_output, + export_single_channel_tif_from_multichannel_czi, + tif_export_params, + catch_and_continue=False, + export_batch_csvs=False, + write_intermediate_products=False, + ) print('Finished') \ No newline at end of file diff --git a/extensions/chaeo/util.py b/extensions/chaeo/util.py index 88f522f2..b127d937 100644 --- a/extensions/chaeo/util.py +++ b/extensions/chaeo/util.py @@ -49,13 +49,14 @@ def get_matching_files(where: str, ext: str, coord_filter: dict={}) -> list: return files -def loop_workflow(files, where_output, workflow_func, params, +def loop_workflow(files, output_folder_path, workflow_func, params, + export_batch_csvs=True, write_intermediate_products=True, catch_and_continue=True): failures = [] for ii, ff in enumerate(files): export_kwargs = { - 'input_zstack_path': ff, # TODO: use a more generic name for implied args - 'where_output': where_output, + 'input_file_path': ff, + 'output_folder_path': output_folder_path, **params, } @@ -74,25 +75,26 @@ def loop_workflow(files, where_output, workflow_func, params, raise e # record dataframes associated with workflow results - batch_csv = { - 'workflow_data': result['dataframe'], - 'timer_results': pd.DataFrame(result['timer_results'], index=[0]), - 'workflow_parameters': pd.json_normalize(export_kwargs), - } - for k in batch_csv.keys(): - df = batch_csv[k] - df['input_file'] = ff - if ii == 0: - csv_args = {'mode': 'w', 'header': True} - else: # append to existing file - csv_args = {'mode': 'a', 'header': False} - csv_path = Path(where_output) / f'{k}.csv' - df.to_csv(csv_path, index=False, **csv_args) + if export_batch_csvs: + batch_csv = { + 'workflow_data': result['dataframe'], + 'timer_results': pd.DataFrame(result['timer_results'], index=[0]), + 'workflow_parameters': pd.json_normalize(export_kwargs), + } + for k in batch_csv.keys(): + df = batch_csv[k] + df['input_file'] = ff + if ii == 0: + csv_args = {'mode': 'w', 'header': True} + else: # append to existing file + csv_args = {'mode': 'a', 'header': False} + csv_path = Path(output_folder_path) / f'{k}.csv' + df.to_csv(csv_path, index=False, **csv_args) # export intermediate data if flagged if write_intermediate_products: for k in result['interm'].keys(): - path = Path(where_output) / k / (Path(ff).stem + '.tif') + path = Path(output_folder_path) / k / (Path(ff).stem + '.tif') path.parent.mkdir(parents=True, exist_ok=True) write_accessor_data_to_file( path, @@ -100,4 +102,4 @@ def loop_workflow(files, where_output, workflow_func, params, ) if len(failures) > 0: - pd.DataFrame(failures).to_csv(Path(where_output) / 'failures.csv') \ No newline at end of file + pd.DataFrame(failures).to_csv(Path(output_folder_path) / 'failures.csv') \ No newline at end of file diff --git a/extensions/chaeo/workflows.py b/extensions/chaeo/workflows.py index f84cf2eb..a5cf3f6a 100644 --- a/extensions/chaeo/workflows.py +++ b/extensions/chaeo/workflows.py @@ -12,18 +12,20 @@ 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 model_server.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file +from model_server.process import rescale from model_server.workflows import Timer # TODO: unpack and validate inputs # TODO: expose channel indices and color balance vectors to caller def export_patches_from_multichannel_zstack( - input_zstack_path: str, + input_file_path: str, + output_folder_path: str, ilastik_project_file: str, pxmap_threshold: float, pixel_class: int, zmask_channel: int, patches_channel: int, - where_output: str, + rescale_zmask_clip: int = None, mask_type: str = 'boxes', zmask_filters: Dict = None, zmask_expand_box_by: int = None, @@ -35,14 +37,16 @@ def export_patches_from_multichannel_zstack( export_patch_masks=True, ) -> Dict: ti = Timer() - stack = generate_file_accessor(Path(input_zstack_path)) - fstem = Path(input_zstack_path).stem + stack = generate_file_accessor(Path(input_file_path)) + fstem = Path(input_file_path).stem ti.click('file_input') - # assert stack.nz > 1, 'Expecting z-stack' # MIP and classify pixels + 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( - stack.get_one_channel_data(channel=0).data.max(axis=-1, keepdims=True) + zmask_data, ) px_model = IlastikPixelClassifierModel( params={'project_file': Path(ilastik_project_file)} @@ -52,7 +56,7 @@ def export_patches_from_multichannel_zstack( if export_pixel_probabilities: write_accessor_data_to_file( - Path(where_output) / 'pixel_probabilities' / (fstem + '.tif'), + Path(output_folder_path) / 'pixel_probabilities' / (fstem + '.tif'), pxmap ) ti.click('export_pixel_probability') @@ -75,7 +79,7 @@ def export_patches_from_multichannel_zstack( if export_3d_patches: files = export_patches_from_zstack( - Path(where_output) / '3d_patches', + Path(output_folder_path) / '3d_patches', stack.get_one_channel_data(patches_channel), zmask_meta, prefix=fstem, @@ -87,14 +91,14 @@ def export_patches_from_multichannel_zstack( if export_2d_patches_for_annotation: files = export_multichannel_patches_from_zstack( - Path(where_output) / '2d_patches_annotation', + Path(output_folder_path) / '2d_patches_annotation', stack, zmask_meta, prefix=fstem, rescale_clip=0.001, make_3d=False, focus_metric='max_sobel', - ch_white=4, + ch_white=patches_channel, ch_rgb_overlay=(3, None, None), draw_bounding_box=False, bounding_box_channel=1, @@ -112,7 +116,7 @@ def export_patches_from_multichannel_zstack( if export_2d_patches_for_training: files = export_multichannel_patches_from_zstack( - Path(where_output) / '2d_patches_training', + Path(output_folder_path) / '2d_patches_training', stack.get_one_channel_data(4), zmask_meta, prefix=fstem, @@ -130,8 +134,8 @@ def export_patches_from_multichannel_zstack( if export_patch_masks: files = export_patch_masks_from_zstack( - Path(where_output) / 'patch_masks', - stack.get_one_channel_data(4), + Path(output_folder_path) / 'patch_masks', + stack.get_one_channel_data(patches_channel), zmask_meta, prefix=fstem, ) @@ -144,7 +148,7 @@ def export_patches_from_multichannel_zstack( ) ) write_accessor_data_to_file( - Path(where_output) / 'annotated_zstacks' / (fstem + '.tif'), + Path(output_folder_path) / 'annotated_zstacks' / (fstem + '.tif'), annotated ) ti.click('export_annotated_zstack') @@ -161,7 +165,7 @@ def export_patches_from_multichannel_zstack( return { 'pixel_model_id': px_model.model_id, - 'input_filepath': input_zstack_path, + 'input_filepath': input_file_path, 'number_of_objects': len(zmask_meta), 'success': True, 'timer_results': ti.events, -- GitLab