multiview-stitcher 0.1.44__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. multiview_stitcher/__init__.py +6 -0
  2. multiview_stitcher/_benchmarks/__init__.py +0 -0
  3. multiview_stitcher/_benchmarks/test_core.py +199 -0
  4. multiview_stitcher/_tests/__init__.py +0 -0
  5. multiview_stitcher/_tests/_create_test_params.ipynb +218 -0
  6. multiview_stitcher/_tests/quickstart/0_data_preparation.py +33 -0
  7. multiview_stitcher/_tests/quickstart/1_registration.py +13 -0
  8. multiview_stitcher/_tests/quickstart/2_fusion.py +12 -0
  9. multiview_stitcher/_tests/quickstart/3_fusion_large.py +20 -0
  10. multiview_stitcher/_tests/test_bench.py +318 -0
  11. multiview_stitcher/_tests/test_fusion.py +404 -0
  12. multiview_stitcher/_tests/test_integration.py +89 -0
  13. multiview_stitcher/_tests/test_io.py +60 -0
  14. multiview_stitcher/_tests/test_msi_utils.py +164 -0
  15. multiview_stitcher/_tests/test_mv_graph.py +228 -0
  16. multiview_stitcher/_tests/test_ngff_utils.py +225 -0
  17. multiview_stitcher/_tests/test_param_utils.py +24 -0
  18. multiview_stitcher/_tests/test_quickstart.py +52 -0
  19. multiview_stitcher/_tests/test_registration.py +786 -0
  20. multiview_stitcher/_tests/test_sample_data.py +41 -0
  21. multiview_stitcher/_tests/test_spatial_image_utils.py +50 -0
  22. multiview_stitcher/_tests/test_vis_utils.py +164 -0
  23. multiview_stitcher/_tests/test_weights.py +131 -0
  24. multiview_stitcher/_version.py +34 -0
  25. multiview_stitcher/czi_utils.py +492 -0
  26. multiview_stitcher/czifile_patch.py +137 -0
  27. multiview_stitcher/fusion.py +1220 -0
  28. multiview_stitcher/io.py +405 -0
  29. multiview_stitcher/misc_utils.py +86 -0
  30. multiview_stitcher/msi_utils.py +418 -0
  31. multiview_stitcher/mv_graph.py +1059 -0
  32. multiview_stitcher/ngff_utils.py +673 -0
  33. multiview_stitcher/param_utils.py +214 -0
  34. multiview_stitcher/registration.py +2364 -0
  35. multiview_stitcher/sample_data.py +152 -0
  36. multiview_stitcher/spatial_image_utils.py +444 -0
  37. multiview_stitcher/test-datasets/mosaic_test.czi +0 -0
  38. multiview_stitcher/transformation.py +107 -0
  39. multiview_stitcher/transforms.py +168 -0
  40. multiview_stitcher/vis_utils.py +710 -0
  41. multiview_stitcher/weights.py +246 -0
  42. multiview_stitcher-0.1.44.dist-info/METADATA +314 -0
  43. multiview_stitcher-0.1.44.dist-info/RECORD +46 -0
  44. multiview_stitcher-0.1.44.dist-info/WHEEL +5 -0
  45. multiview_stitcher-0.1.44.dist-info/licenses/LICENSE +28 -0
  46. multiview_stitcher-0.1.44.dist-info/top_level.txt +1 -0
@@ -0,0 +1,6 @@
1
+ try:
2
+ from ._version import version as __version__
3
+ except ImportError:
4
+ __version__ = "unknown"
5
+
6
+ __all__ = ()
File without changes
@@ -0,0 +1,199 @@
1
+ import os
2
+ import tempfile
3
+ from pathlib import Path
4
+
5
+ import multiview_stitcher.spatial_image_utils as si_utils
6
+ from multiview_stitcher import (
7
+ fusion,
8
+ msi_utils,
9
+ param_utils,
10
+ registration,
11
+ sample_data,
12
+ )
13
+
14
+ # to be used in the future when we have test data to be cached
15
+ # @pytest.fixture(scope="session")
16
+ # def testdata_dir_path() -> Path:
17
+ # TEST_DIR = Path(__file__).parent
18
+ # if not TEST_DIR.exists():
19
+ # raise FileNotFoundError(f"Test data directory not found: {TEST_DIR}")
20
+ # return TEST_DIR / "data/"
21
+
22
+
23
+ def create_input(
24
+ test_params=None,
25
+ tmp_dir="/tmp/test_benchmarking",
26
+ ):
27
+ """
28
+ Test that the circumvent_dask_for_zarr_backed_input flag works as expected.
29
+ """
30
+
31
+ # create minimal example grid with overlap
32
+ if test_params is None:
33
+ test_params = {
34
+ "t": 1,
35
+ "c": 1,
36
+ "ndim": 3,
37
+ "input_chunking": 50,
38
+ "tiles_x": 3,
39
+ "tiles_y": 3,
40
+ "tile_size": 100,
41
+ "overlap": 20,
42
+ }
43
+
44
+ ndim = test_params["ndim"]
45
+ sims = sample_data.generate_tiled_dataset(
46
+ ndim=ndim,
47
+ overlap=test_params["overlap"],
48
+ N_c=test_params["c"],
49
+ N_t=test_params["t"],
50
+ tile_size=test_params["tile_size"],
51
+ tiles_x=test_params["tiles_x"],
52
+ tiles_y=test_params["tiles_y"],
53
+ tiles_z=1,
54
+ spacing_x=1,
55
+ spacing_y=1,
56
+ spacing_z=1,
57
+ )
58
+
59
+ # rechunk and jiggle input data
60
+ for sim in sims:
61
+ sim.data = sim.data.rechunk(test_params["input_chunking"])
62
+ p = param_utils.affine_to_xaffine(
63
+ param_utils.random_affine(
64
+ ndim=ndim,
65
+ rotation_scale=0,
66
+ scale_scale=0,
67
+ translation_scale=10,
68
+ )
69
+ )
70
+ si_utils.set_sim_affine(sim, p, transform_key="random")
71
+
72
+ # persist input data to zarr
73
+ for iview in range(len(sims)):
74
+ msi_utils.multiscale_spatial_image_to_zarr(
75
+ msi_utils.get_msim_from_sim(sims[iview], scale_factors=[]),
76
+ os.path.join(tmp_dir, f"msim_view_{iview}.zarr"),
77
+ )
78
+
79
+ return
80
+
81
+
82
+ def fuse_zarr_to_zarr(
83
+ tmp_input_dir,
84
+ ):
85
+ input_filepaths = sorted(
86
+ [
87
+ os.path.join(tmp_input_dir, f)
88
+ for f in os.listdir(tmp_input_dir)
89
+ if "fused" not in f
90
+ ]
91
+ )
92
+
93
+ msims = [
94
+ msi_utils.multiscale_spatial_image_from_zarr(fp)
95
+ for fp in input_filepaths
96
+ ]
97
+
98
+ sims = [msi_utils.get_sim_from_msim(msim) for msim in msims]
99
+
100
+ sdims = si_utils.get_spatial_dims_from_sim(sims[0])
101
+
102
+ fused_sim = fusion.fuse(
103
+ sims,
104
+ transform_key="random",
105
+ output_chunksize={dim: 100 for dim in sdims},
106
+ )
107
+
108
+ with tempfile.TemporaryDirectory() as tmp_dir:
109
+ fused_sim.data.to_zarr(
110
+ os.path.join(tmp_dir, "fused_sim.zarr"),
111
+ overwrite=True,
112
+ return_stored=True,
113
+ compute=True,
114
+ )
115
+
116
+ return
117
+
118
+
119
+ def test_fusion(
120
+ benchmark,
121
+ ):
122
+ test_params = {
123
+ "t": 1,
124
+ "c": 1,
125
+ "ndim": 3,
126
+ "input_chunking": 50,
127
+ "tiles_x": 3,
128
+ "tiles_y": 3,
129
+ "tile_size": 100,
130
+ "overlap": 20,
131
+ }
132
+
133
+ with tempfile.TemporaryDirectory() as tmpdir:
134
+ testdata_dir_path = Path(tmpdir)
135
+
136
+ create_input(
137
+ tmp_dir=testdata_dir_path,
138
+ test_params=test_params,
139
+ )
140
+
141
+ # benchmark something
142
+ benchmark.pedantic(
143
+ fuse_zarr_to_zarr,
144
+ args=(testdata_dir_path,),
145
+ iterations=1,
146
+ rounds=3,
147
+ )
148
+
149
+
150
+ def register_zarrs(
151
+ tmp_dir,
152
+ ):
153
+ input_filepaths = sorted(
154
+ [os.path.join(tmp_dir, f) for f in os.listdir(tmp_dir)]
155
+ )
156
+
157
+ msims = [
158
+ msi_utils.multiscale_spatial_image_from_zarr(fp)
159
+ for fp in input_filepaths
160
+ ]
161
+
162
+ registration.register(
163
+ msims,
164
+ transform_key="random",
165
+ reg_channel_index=0,
166
+ )
167
+
168
+ return
169
+
170
+
171
+ def test_registration(
172
+ benchmark,
173
+ ):
174
+ test_params = {
175
+ "t": 1,
176
+ "c": 1,
177
+ "ndim": 2,
178
+ "input_chunking": 50,
179
+ "tiles_x": 2,
180
+ "tiles_y": 2,
181
+ "tile_size": 100,
182
+ "overlap": 20,
183
+ }
184
+
185
+ with tempfile.TemporaryDirectory() as tmpdir:
186
+ testdata_dir_path = Path(tmpdir)
187
+
188
+ create_input(
189
+ tmp_dir=testdata_dir_path,
190
+ test_params=test_params,
191
+ )
192
+
193
+ # benchmark something
194
+ benchmark.pedantic(
195
+ register_zarrs,
196
+ args=(testdata_dir_path,),
197
+ iterations=1,
198
+ rounds=3,
199
+ )
File without changes
@@ -0,0 +1,218 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Obtain ground truth params for test datasets\n",
8
+ "\n",
9
+ "Convenience notebook for creating ground truth parameters for test datasets."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": 1,
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "data": {
19
+ "text/plain": [
20
+ "<module 'multiview_stitcher._tests.test_bench' from '/Users/malbert/software/multiview-stitcher/src/multiview_stitcher/_tests/test_bench.py'>"
21
+ ]
22
+ },
23
+ "execution_count": 1,
24
+ "metadata": {},
25
+ "output_type": "execute_result"
26
+ }
27
+ ],
28
+ "source": [
29
+ "from pathlib import Path\n",
30
+ "\n",
31
+ "from multiview_stitcher._tests import test_bench\n",
32
+ "from multiview_stitcher import vis_utils, io\n",
33
+ "\n",
34
+ "import dask.diagnostics\n",
35
+ "\n",
36
+ "import importlib\n",
37
+ "importlib.reload(test_bench)"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": 9,
43
+ "metadata": {},
44
+ "outputs": [
45
+ {
46
+ "name": "stdout",
47
+ "output_type": "stream",
48
+ "text": [
49
+ "/Users/malbert/software/multiview-stitcher/image-datasets/test_bench_data\n"
50
+ ]
51
+ },
52
+ {
53
+ "data": {
54
+ "image/png": "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B9Xrk729PbRabb2vp6GxtbWFjY2NxfXb2F9zvLcaEmtra9jb21tUn4HbnyXNmjWzuH4DgKOjY433uSW8Djz79y5yc3Ph4+ODrKwseHh4qF0ONSEGgwEXLlxA27Ztm+zZj0T3o9FoYGtrC41Go3Yp9YKhehdCCJSWlsLR0bHJDjwRkRo0Gk2T/lxlqBIREUnC/U9ERESSMFSJiIgkYagSERFJwlAlIiKShKFKREQkCUOViIhIEoYqERGRJAxVIiIiSRiqREREkjBUiYiIJGGoEhERScJQJSIikoShSkREJAlDlYiISBKGKhERkSQMVSIiIkkYqkRERJIwVImIiCRhqBIREUnCUCUiIpKEoUpERCQJQ5WIiEgShioREZEkDFUiIiJJGKpERESSMFSJiIgkYagSERFJwlAlIiKShKFKREQkCUOViIhIEoYqERGRJAxVIiIiSRiqREREkjBUiYiIJGGoEhERScJQJSIikoShSkREJAlDlYiISBKGKhERkSQMVSIiIkkYqkRERJIwVImIiCRhqBIREUnCUCUiIpKEoUpERCQJQ5WIiEgShioREZEkDFUiIiJJGKpERESSMFSJiIgkYagSERFJwlAlIiKShKFKREQkCUOViIhIEoYqERGRJAxVIiIiSRiqREREkjBUiYiIJGGoEhERScJQJSIikoShSkREJAlDlYiISBKGKhERkSQMVSIiIkkYqkRERJIwVImIiCRhqBIREUnCUCUiIpKEoUpERCQJQ5WIiEgShioREZEkDFUiIiJJGKpERESSMFSJiIgkYagSERFJwlAlIiKShKFKREQkCUOViIhIEoYqERGRJAxVIiIiSRiqREREkjBUiYiIJPl/nMQKk7yoi08AAAAASUVORK5CYII=",
55
+ "text/plain": [
56
+ "<Figure size 640x480 with 1 Axes>"
57
+ ]
58
+ },
59
+ "metadata": {},
60
+ "output_type": "display_data"
61
+ },
62
+ {
63
+ "data": {
64
+ "text/plain": [
65
+ "(<Figure size 640x480 with 1 Axes>, <Axes3D: ylabel='x [μm]', zlabel='y [μm]'>)"
66
+ ]
67
+ },
68
+ "execution_count": 9,
69
+ "metadata": {},
70
+ "output_type": "execute_result"
71
+ }
72
+ ],
73
+ "source": [
74
+ "print(test_bench.test_bench_data_dir)\n",
75
+ "\n",
76
+ "dataset = test_bench.datasets[2]\n",
77
+ "msims = test_bench.get_msims_from_dataset(dataset)\n",
78
+ "\n",
79
+ "vis_utils.plot_positions(msims, io.METADATA_TRANSFORM_KEY, use_positional_colors=False)"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "markdown",
84
+ "metadata": {},
85
+ "source": [
86
+ "## Registration"
87
+ ]
88
+ },
89
+ {
90
+ "cell_type": "code",
91
+ "execution_count": 10,
92
+ "metadata": {},
93
+ "outputs": [
94
+ {
95
+ "name": "stdout",
96
+ "output_type": "stream",
97
+ "text": [
98
+ "[########################################] | 100% Completed | 1.58 ss\n",
99
+ "[########################################] | 100% Completed | 5.12 sms\n"
100
+ ]
101
+ }
102
+ ],
103
+ "source": [
104
+ "with dask.diagnostics.ProgressBar():\n",
105
+ " params = test_bench.register_dataset(msims, dataset)"
106
+ ]
107
+ },
108
+ {
109
+ "cell_type": "markdown",
110
+ "metadata": {},
111
+ "source": [
112
+ "## Visualization"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": 7,
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "# napari\n",
122
+ "\n",
123
+ "from napari_stitcher import viewer_utils\n",
124
+ "import napari\n",
125
+ "\n",
126
+ "viewer = napari.Viewer(ndisplay=2)\n",
127
+ "lds = viewer_utils.create_image_layer_tuples_from_msims(\n",
128
+ " msims,\n",
129
+ " transform_key='reg_0',\n",
130
+ " n_colors=4,\n",
131
+ " # contrast_limits=[0, 1000],\n",
132
+ " )\n",
133
+ "viewer_utils.add_image_layer_tuples_to_viewer(viewer, lds, manage_viewer_transformations=True)\n",
134
+ "viewer.reset_view()\n",
135
+ " "
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "code",
140
+ "execution_count": null,
141
+ "metadata": {},
142
+ "outputs": [],
143
+ "source": [
144
+ "# # neuroglancer\n",
145
+ "\n",
146
+ "# # doesn't work if time dimension is present\n",
147
+ "\n",
148
+ "# from multiview_stitcher import vis_utils, ngff_utils, msi_utils\n",
149
+ "# import importlib\n",
150
+ "# importlib.reload(vis_utils)\n",
151
+ "\n",
152
+ "# import tempfile\n",
153
+ "\n",
154
+ "# with tempfile.TemporaryDirectory() as tmp_dir:\n",
155
+ "\n",
156
+ "# tmp_dir = Path(tmp_dir)\n",
157
+ "\n",
158
+ "# zarr_paths = [tmp_dir / f\"sim_{imsim}.zarr\" for imsim, msim in enumerate(msims)]\n",
159
+ "\n",
160
+ "# sims = [msi_utils.get_sim_from_msim(msim) for msim in msims]\n",
161
+ "\n",
162
+ "# for imsim, msim in enumerate(msims):\n",
163
+ "# ngff_utils.write_sim_to_ome_zarr(\n",
164
+ "# msi_utils.get_sim_from_msim(msim),\n",
165
+ "# zarr_paths[imsim], overwrite=False)\n",
166
+ "\n",
167
+ "# # sims = [ngff_utils.write_sim_to_ome_zarr(\n",
168
+ "# # msi_utils.get_sim_from_msim(msim),\n",
169
+ "# # zarr_paths[imsim], overwrite=False) for imsim, msim in enumerate(msims)]\n",
170
+ "\n",
171
+ "# vis_utils.view_neuroglancer(\n",
172
+ "# sims,\n",
173
+ "# ome_zarr_paths=zarr_paths,\n",
174
+ "# # channel_coord='DAPI',\n",
175
+ "# transform_key=\"registered\",\n",
176
+ "# # single_layer=False,\n",
177
+ "# )"
178
+ ]
179
+ },
180
+ {
181
+ "cell_type": "markdown",
182
+ "metadata": {},
183
+ "source": [
184
+ "## Save parameters"
185
+ ]
186
+ },
187
+ {
188
+ "cell_type": "code",
189
+ "execution_count": 11,
190
+ "metadata": {},
191
+ "outputs": [],
192
+ "source": [
193
+ "test_bench.write_params(params, dataset[\"parameter_path\"])"
194
+ ]
195
+ }
196
+ ],
197
+ "metadata": {
198
+ "kernelspec": {
199
+ "display_name": "ms_ns",
200
+ "language": "python",
201
+ "name": "python3"
202
+ },
203
+ "language_info": {
204
+ "codemirror_mode": {
205
+ "name": "ipython",
206
+ "version": 3
207
+ },
208
+ "file_extension": ".py",
209
+ "mimetype": "text/x-python",
210
+ "name": "python",
211
+ "nbconvert_exporter": "python",
212
+ "pygments_lexer": "ipython3",
213
+ "version": "3.11.11"
214
+ }
215
+ },
216
+ "nbformat": 4,
217
+ "nbformat_minor": 2
218
+ }
@@ -0,0 +1,33 @@
1
+ import numpy as np
2
+ from multiview_stitcher import msi_utils
3
+ from multiview_stitcher import spatial_image_utils as si_utils
4
+
5
+ # input data (can be any numpy compatible array: numpy, dask, cupy, etc.)
6
+ tile_arrays = [np.random.randint(0, 100, (2, 10, 100, 100)) for _ in range(3)]
7
+
8
+ # indicate the tile offsets and spacing
9
+ tile_translations = [
10
+ {"z": 2.5, "y": -10, "x": 30},
11
+ {"z": 2.5, "y": 30, "x": 10},
12
+ {"z": 2.5, "y": 30, "x": 50},
13
+ ]
14
+ spacing = {"z": 2, "y": 0.5, "x": 0.5}
15
+
16
+ channels = ["DAPI", "GFP"]
17
+
18
+ # build input for stitching
19
+ msims = []
20
+ for tile_array, tile_translation in zip(tile_arrays, tile_translations):
21
+ sim = si_utils.get_sim_from_array(
22
+ tile_array,
23
+ dims=["c", "z", "y", "x"],
24
+ scale=spacing,
25
+ translation=tile_translation,
26
+ transform_key="stage_metadata",
27
+ c_coords=channels,
28
+ )
29
+ msims.append(msi_utils.get_msim_from_sim(sim, scale_factors=[]))
30
+
31
+ # plot the tile configuration
32
+ # from multiview_stitcher import vis_utils
33
+ # fig, ax = vis_utils.plot_positions(msims, transform_key='stage_metadata', use_positional_colors=False)
@@ -0,0 +1,13 @@
1
+ from dask.diagnostics import ProgressBar
2
+ from multiview_stitcher import registration
3
+
4
+ with ProgressBar():
5
+ params = registration.register(
6
+ msims,
7
+ reg_channel="DAPI", # channel to use for registration
8
+ transform_key="stage_metadata",
9
+ new_transform_key="translation_registered",
10
+ )
11
+
12
+ # plot the tile configuration after registration
13
+ # vis_utils.plot_positions(msims, transform_key='translation_registered', use_positional_colors=False)
@@ -0,0 +1,12 @@
1
+ from multiview_stitcher import fusion
2
+
3
+ fused_sim = fusion.fuse(
4
+ [msi_utils.get_sim_from_msim(msim) for msim in msims],
5
+ transform_key="translation_registered",
6
+ )
7
+
8
+ # get fused array as a dask array
9
+ fused_sim.data
10
+
11
+ # get fused array as a numpy array
12
+ fused_sim.data.compute()
@@ -0,0 +1,20 @@
1
+ from multiview_stitcher import fusion, misc_utils
2
+
3
+ fused = fusion.fuse(
4
+ sims=[msi_utils.get_sim_from_msim(msim) for msim in msims],
5
+ transform_key="translation_registered",
6
+ # ... further optional args for fusion.fuse
7
+ output_zarr_url="fused_output.ome.zarr",
8
+ zarr_options={
9
+ "ome_zarr": True,
10
+ # "ngff_version": "0.4", # optional
11
+ },
12
+ # optionally, we can use ray for parallelization (`pip install "ray[default]"`)
13
+ # batch_options={
14
+ # "batch_func": misc_utils.process_batch_using_ray,
15
+ # "n_batch": 4, # number of chunk fusions to schedule / submit at a time
16
+ # "batch_func_kwargs": {
17
+ # 'num_cpus': 4 # number of processes for parallel processing to use with ray
18
+ # },
19
+ # },
20
+ )