From 5b4538a40a066b6a72d2158c5c475bbd30f08a93 Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Tue, 2 Jul 2024 15:59:14 -0700 Subject: [PATCH 01/13] non-working draft --- mantis/analysis/deskew.py | 15 +++++++++++++++ mantis/tests/test_analysis/test_deskew.py | 2 +- pyproject.toml | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index ec42c57f..227be4e0 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -187,6 +187,21 @@ def deskew_data( cval=cval, ) + from monai.transforms.spatial.array import Affine + + print("Computing w/ MONAI...") + my_matrix = np.vstack((matrix, np.array([[0, 0, 0, 1]]))) + my_affine = Affine( + affine=my_matrix, mode="bilinear", padding_mode="zeros", image_only=True + ) + deskewed_data2 = my_affine(raw_data[None], spatial_size=output_shape)[0] + + print(deskewed_data) + print(deskewed_data2) + import pdb + + pdb.set_trace() + # Apply averaging averaged_deskewed_data = _average_n_slices( deskewed_data, average_window_width=average_n_slices diff --git a/mantis/tests/test_analysis/test_deskew.py b/mantis/tests/test_analysis/test_deskew.py index 28997685..955d427f 100644 --- a/mantis/tests/test_analysis/test_deskew.py +++ b/mantis/tests/test_analysis/test_deskew.py @@ -26,7 +26,7 @@ def test_average_n_slices(): def test_deskew_data(): - raw_data = np.random.random((2, 3, 4)) + raw_data = np.arange(24).reshape((2, 3, 4)) px_to_scan_ratio = 0.386 pixel_size_um = 1.0 ls_angle_deg = 36 diff --git a/pyproject.toml b/pyproject.toml index b3d2d79e..34c2eac1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dependencies = [ "largestinteriorrectangle", "antspyx", "pystackreg", + ] From 0c1b4738a78815b4678865d34e0a7262e0bc1b80 Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 09:44:48 -0700 Subject: [PATCH 02/13] minimal monai deskew --- mantis/analysis/deskew.py | 45 +++++++++++++++++---------------------- mantis/cli/deskew.py | 6 ++++++ 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 227be4e0..c0032adb 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -1,5 +1,7 @@ import numpy as np -import scipy +from monai.transforms.spatial.array import Affine +from monai import transforms +import torch def _average_n_slices(data, average_window_width=1): @@ -158,9 +160,6 @@ def deskew_data( Z, Y, X = raw_data.shape ct = np.cos(ls_angle_deg * np.pi / 180) - Z_shift = 0 - if not keep_overhang: - Z_shift = int(np.floor(Y * ct * px_to_scan_ratio)) matrix = np.array( [ @@ -168,40 +167,34 @@ def deskew_data( -px_to_scan_ratio * ct, 0, px_to_scan_ratio, - Z_shift, + 0, ], - [-1, 0, 0, Y - 1], - [0, -1, 0, X - 1], + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 0, 1], ] ) output_shape, _ = get_deskewed_data_shape( raw_data.shape, ls_angle_deg, px_to_scan_ratio, keep_overhang ) - # Apply transforms - deskewed_data = scipy.ndimage.affine_transform( - raw_data, - matrix, - output_shape=output_shape, - order=order, - cval=cval, - ) + import time + start = time.time() - from monai.transforms.spatial.array import Affine + # to tensor on GPU + if torch.cuda.is_available(): + raw_data = transforms.ToDevice("cuda")(torch.tensor(raw_data)) - print("Computing w/ MONAI...") - my_matrix = np.vstack((matrix, np.array([[0, 0, 0, 1]]))) - my_affine = Affine( - affine=my_matrix, mode="bilinear", padding_mode="zeros", image_only=True - ) - deskewed_data2 = my_affine(raw_data[None], spatial_size=output_shape)[0] + # Returns callable + affine_func = Affine(affine=matrix, mode=order, padding_mode="zeros", image_only=True) - print(deskewed_data) - print(deskewed_data2) - import pdb + # affine_func accepts CZYX array, so for ZYX input we need [None] and for ZYX output we need [0] + deskewed_data = affine_func(raw_data[None], mode="bilinear", spatial_size=output_shape)[0] - pdb.set_trace() + # to numpy array on CPU + deskewed_data = deskewed_data.cpu().numpy() + print(f"Elapsed: {time.time() - start:.2f}") # Apply averaging averaged_deskewed_data = _average_n_slices( deskewed_data, average_window_width=average_n_slices diff --git a/mantis/cli/deskew.py b/mantis/cli/deskew.py index eb49da59..aa986e0c 100644 --- a/mantis/cli/deskew.py +++ b/mantis/cli/deskew.py @@ -13,6 +13,12 @@ from mantis.cli.parsing import config_filepath, input_position_dirpaths, output_dirpath from mantis.cli.utils import yaml_to_model +# Needed for multiprocessing with GPUs +# https://github.com/pytorch/pytorch/issues/40403#issuecomment-1422625325 +import torch + +torch.multiprocessing.set_start_method('spawn', force=True) + @click.command() @input_position_dirpaths() From 0e607978ffe55c6f6298b8ce2b9b94b40edf8ed4 Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 09:45:20 -0700 Subject: [PATCH 03/13] remove profiling --- mantis/analysis/deskew.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index c0032adb..b31a0720 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -178,9 +178,6 @@ def deskew_data( raw_data.shape, ls_angle_deg, px_to_scan_ratio, keep_overhang ) - import time - start = time.time() - # to tensor on GPU if torch.cuda.is_available(): raw_data = transforms.ToDevice("cuda")(torch.tensor(raw_data)) @@ -194,7 +191,6 @@ def deskew_data( # to numpy array on CPU deskewed_data = deskewed_data.cpu().numpy() - print(f"Elapsed: {time.time() - start:.2f}") # Apply averaging averaged_deskewed_data = _average_n_slices( deskewed_data, average_window_width=average_n_slices From 148b71d298d41c0c57bef4469a10459b3a64ae29 Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 09:56:16 -0700 Subject: [PATCH 04/13] isort --- mantis/analysis/deskew.py | 5 +++-- mantis/cli/deskew.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index b31a0720..946f7cb7 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -1,8 +1,9 @@ import numpy as np -from monai.transforms.spatial.array import Affine -from monai import transforms import torch +from monai import transforms +from monai.transforms.spatial.array import Affine + def _average_n_slices(data, average_window_width=1): """Average an array over its first axis diff --git a/mantis/cli/deskew.py b/mantis/cli/deskew.py index aa986e0c..e03cee57 100644 --- a/mantis/cli/deskew.py +++ b/mantis/cli/deskew.py @@ -5,6 +5,10 @@ import click +# Needed for multiprocessing with GPUs +# https://github.com/pytorch/pytorch/issues/40403#issuecomment-1422625325 +import torch + from iohub.ngff import open_ome_zarr from mantis.analysis.AnalysisSettings import DeskewSettings @@ -13,10 +17,6 @@ from mantis.cli.parsing import config_filepath, input_position_dirpaths, output_dirpath from mantis.cli.utils import yaml_to_model -# Needed for multiprocessing with GPUs -# https://github.com/pytorch/pytorch/issues/40403#issuecomment-1422625325 -import torch - torch.multiprocessing.set_start_method('spawn', force=True) From d1d568c4cf41c6c37fb5d24d30abd1536e11131d Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 09:59:38 -0700 Subject: [PATCH 05/13] depend on monai --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 34c2eac1..089abf41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "ndtiff>=2.0", "nidaqmx", "numpy", + "monai", "pandas~=2.1", "pycromanager==0.28.1", "pydantic", From ec963652e806443befb3d19c9f7b0928f467c9ec Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 10:50:08 -0700 Subject: [PATCH 06/13] clean up --- mantis/cli/deskew.py | 5 ++--- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mantis/cli/deskew.py b/mantis/cli/deskew.py index e03cee57..f766bf1c 100644 --- a/mantis/cli/deskew.py +++ b/mantis/cli/deskew.py @@ -4,9 +4,6 @@ from typing import List import click - -# Needed for multiprocessing with GPUs -# https://github.com/pytorch/pytorch/issues/40403#issuecomment-1422625325 import torch from iohub.ngff import open_ome_zarr @@ -17,6 +14,8 @@ from mantis.cli.parsing import config_filepath, input_position_dirpaths, output_dirpath from mantis.cli.utils import yaml_to_model +# Needed for multiprocessing with GPUs +# https://github.com/pytorch/pytorch/issues/40403#issuecomment-1422625325 torch.multiprocessing.set_start_method('spawn', force=True) diff --git a/pyproject.toml b/pyproject.toml index 089abf41..f4afabf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,6 @@ dependencies = [ "largestinteriorrectangle", "antspyx", "pystackreg", - ] From 9f851ad0283f82585dfc2a8051cd42c2489d77bd Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 10:52:00 -0700 Subject: [PATCH 07/13] revert changed test --- mantis/tests/test_analysis/test_deskew.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mantis/tests/test_analysis/test_deskew.py b/mantis/tests/test_analysis/test_deskew.py index 955d427f..28997685 100644 --- a/mantis/tests/test_analysis/test_deskew.py +++ b/mantis/tests/test_analysis/test_deskew.py @@ -26,7 +26,7 @@ def test_average_n_slices(): def test_deskew_data(): - raw_data = np.arange(24).reshape((2, 3, 4)) + raw_data = np.random.random((2, 3, 4)) px_to_scan_ratio = 0.386 pixel_size_um = 1.0 ls_angle_deg = 36 From 7d7b0de218627c775697a02835696910f567f068 Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 10:57:14 -0700 Subject: [PATCH 08/13] remove unused `order` and `cval` options --- mantis/analysis/deskew.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 946f7cb7..25f4fb9b 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -142,11 +142,6 @@ def deskew_data( If false, only compute the deskewed volume within a cuboid region. average_n_slices : int, optional after deskewing, averages every n slices (default = 1 applies no averaging) - order : int, optional - interpolation order (default 1 is linear interpolation) - cval : float, optional - fill value area outside of the measured volume (default None fills - with the minimum value of the input array) Returns ------- deskewed_data : NDArray with ndim == 3 @@ -154,9 +149,6 @@ def deskew_data( axis 1 is the Y axis, input axis 2 in the plane of the coverslip axis 2 is the X axis, the scanning axis """ - if cval is None: - cval = np.min(np.ravel(raw_data)) - # Prepare transforms Z, Y, X = raw_data.shape @@ -184,7 +176,7 @@ def deskew_data( raw_data = transforms.ToDevice("cuda")(torch.tensor(raw_data)) # Returns callable - affine_func = Affine(affine=matrix, mode=order, padding_mode="zeros", image_only=True) + affine_func = Affine(affine=matrix, padding_mode="zeros", image_only=True) # affine_func accepts CZYX array, so for ZYX input we need [None] and for ZYX output we need [0] deskewed_data = affine_func(raw_data[None], mode="bilinear", spatial_size=output_shape)[0] From 46a2b7ca9a7e1da146d50e4dcf5c552dfd3e3d4f Mon Sep 17 00:00:00 2001 From: Talon Chandler Date: Fri, 5 Jul 2024 16:20:27 -0700 Subject: [PATCH 09/13] use `to.("cuda")` instead of `transforms.ToDevice("cuda")` --- mantis/analysis/deskew.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 25f4fb9b..4a10f4b1 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -1,7 +1,6 @@ import numpy as np import torch -from monai import transforms from monai.transforms.spatial.array import Affine @@ -173,7 +172,7 @@ def deskew_data( # to tensor on GPU if torch.cuda.is_available(): - raw_data = transforms.ToDevice("cuda")(torch.tensor(raw_data)) + raw_data = torch.tensor(raw_data).to("cuda") # Returns callable affine_func = Affine(affine=matrix, padding_mode="zeros", image_only=True) From 86bc44e212db04f0fd7ef003bd031ec7d8befd87 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 10 Jul 2024 14:33:08 -0700 Subject: [PATCH 10/13] keep data on CPU by default --- mantis/analysis/deskew.py | 22 +++++++++++----------- mantis/cli/deskew.py | 3 +++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 4a10f4b1..1d8a8008 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -118,8 +118,7 @@ def deskew_data( px_to_scan_ratio: float, keep_overhang: bool, average_n_slices: int = 1, - order: int = 1, - cval: float = None, + device='cpu' ): """Deskews fluorescence data from the mantis microscope Parameters @@ -129,18 +128,20 @@ def deskew_data( - axis 0 corresponds to the scanning axis - axis 1 corresponds to the "tilted" axis - axis 2 corresponds to the axis in the plane of the coverslip + ls_angle_deg : float + angle of light sheet with respect to the optical axis in degrees px_to_scan_ratio : float (pixel spacing / scan spacing) in object space e.g. if camera pixels = 6.5 um and mag = 1.4*40, then the pixel spacing is 6.5/(1.4*40) = 0.116 um. If the scan spacing is 0.3 um, then px_to_scan_ratio = 0.116 / 0.3 = 0.386 - ls_angle_deg : float - angle of light sheet with respect to the optical axis in degrees keep_overhang : bool If true, compute the whole volume within the tilted parallelepiped. If false, only compute the deskewed volume within a cuboid region. average_n_slices : int, optional after deskewing, averages every n slices (default = 1 applies no averaging) + device : str, optional + torch device to use for computation. Default is 'cpu'. Returns ------- deskewed_data : NDArray with ndim == 3 @@ -149,10 +150,7 @@ def deskew_data( axis 2 is the X axis, the scanning axis """ # Prepare transforms - Z, Y, X = raw_data.shape - ct = np.cos(ls_angle_deg * np.pi / 180) - matrix = np.array( [ [ @@ -170,15 +168,17 @@ def deskew_data( raw_data.shape, ls_angle_deg, px_to_scan_ratio, keep_overhang ) - # to tensor on GPU - if torch.cuda.is_available(): - raw_data = torch.tensor(raw_data).to("cuda") + # convert to tensor on GPU + # convert raw_data to int32 if it is uint16 + if raw_data.dtype == np.uint16: + raw_data = raw_data.astype(np.int32) + raw_data_tensor = torch.as_tensor(raw_data, device=device) # Returns callable affine_func = Affine(affine=matrix, padding_mode="zeros", image_only=True) # affine_func accepts CZYX array, so for ZYX input we need [None] and for ZYX output we need [0] - deskewed_data = affine_func(raw_data[None], mode="bilinear", spatial_size=output_shape)[0] + deskewed_data = affine_func(raw_data_tensor[None], mode="bilinear", spatial_size=output_shape)[0] # to numpy array on CPU deskewed_data = deskewed_data.cpu().numpy() diff --git a/mantis/cli/deskew.py b/mantis/cli/deskew.py index f766bf1c..3bb23e4b 100644 --- a/mantis/cli/deskew.py +++ b/mantis/cli/deskew.py @@ -93,3 +93,6 @@ def deskew( num_processes=num_processes, **deskew_args, ) + +if __name__ == "__main__": + deskew() From 254cb367cc97b6aab6529b6bd9e83877dea2b9f1 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 10 Jul 2024 14:54:01 -0700 Subject: [PATCH 11/13] style --- mantis/analysis/deskew.py | 6 ++++-- mantis/cli/deskew.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 1d8a8008..36788601 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -118,7 +118,7 @@ def deskew_data( px_to_scan_ratio: float, keep_overhang: bool, average_n_slices: int = 1, - device='cpu' + device='cpu', ): """Deskews fluorescence data from the mantis microscope Parameters @@ -178,7 +178,9 @@ def deskew_data( affine_func = Affine(affine=matrix, padding_mode="zeros", image_only=True) # affine_func accepts CZYX array, so for ZYX input we need [None] and for ZYX output we need [0] - deskewed_data = affine_func(raw_data_tensor[None], mode="bilinear", spatial_size=output_shape)[0] + deskewed_data = affine_func( + raw_data_tensor[None], mode="bilinear", spatial_size=output_shape + )[0] # to numpy array on CPU deskewed_data = deskewed_data.cpu().numpy() diff --git a/mantis/cli/deskew.py b/mantis/cli/deskew.py index 3bb23e4b..7ed185f1 100644 --- a/mantis/cli/deskew.py +++ b/mantis/cli/deskew.py @@ -94,5 +94,6 @@ def deskew( **deskew_args, ) + if __name__ == "__main__": deskew() From 2e991ac3741932be1f8848152ea1dde084411380 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 11 Jul 2024 15:56:52 -0700 Subject: [PATCH 12/13] bumping iohub as well --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 039e8267..c2dd3f3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ # list package dependencies here dependencies = [ "copylot @ git+https://github.com/czbiohub-sf/coPylot", - "iohub==0.1.0.dev5", + "iohub==0.1.0", "matplotlib", "napari; 'arm64' in platform_machine", # without Qt5 and skimage "napari[all]; 'arm64' not in platform_machine", # with Qt5 and skimage From d2487e57505d7b565a587aa38d40deb8faeb0b69 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 11 Jul 2024 16:02:28 -0700 Subject: [PATCH 13/13] Update raw_data type casting Co-authored-by: Ziwen Liu <67518483+ziw-liu@users.noreply.github.com> --- mantis/analysis/deskew.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mantis/analysis/deskew.py b/mantis/analysis/deskew.py index 36788601..15c79084 100644 --- a/mantis/analysis/deskew.py +++ b/mantis/analysis/deskew.py @@ -170,9 +170,7 @@ def deskew_data( # convert to tensor on GPU # convert raw_data to int32 if it is uint16 - if raw_data.dtype == np.uint16: - raw_data = raw_data.astype(np.int32) - raw_data_tensor = torch.as_tensor(raw_data, device=device) + raw_data_tensor = torch.from_numpy(raw_data.astype(np.float32)).to(device) # Returns callable affine_func = Affine(affine=matrix, padding_mode="zeros", image_only=True)