Rhapso 0.1.92__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 (101) hide show
  1. Rhapso/__init__.py +1 -0
  2. Rhapso/data_prep/__init__.py +2 -0
  3. Rhapso/data_prep/n5_reader.py +188 -0
  4. Rhapso/data_prep/s3_big_stitcher_reader.py +55 -0
  5. Rhapso/data_prep/xml_to_dataframe.py +215 -0
  6. Rhapso/detection/__init__.py +5 -0
  7. Rhapso/detection/advanced_refinement.py +203 -0
  8. Rhapso/detection/difference_of_gaussian.py +324 -0
  9. Rhapso/detection/image_reader.py +117 -0
  10. Rhapso/detection/metadata_builder.py +130 -0
  11. Rhapso/detection/overlap_detection.py +327 -0
  12. Rhapso/detection/points_validation.py +49 -0
  13. Rhapso/detection/save_interest_points.py +265 -0
  14. Rhapso/detection/view_transform_models.py +67 -0
  15. Rhapso/fusion/__init__.py +0 -0
  16. Rhapso/fusion/affine_fusion/__init__.py +2 -0
  17. Rhapso/fusion/affine_fusion/blend.py +289 -0
  18. Rhapso/fusion/affine_fusion/fusion.py +601 -0
  19. Rhapso/fusion/affine_fusion/geometry.py +159 -0
  20. Rhapso/fusion/affine_fusion/io.py +546 -0
  21. Rhapso/fusion/affine_fusion/script_utils.py +111 -0
  22. Rhapso/fusion/affine_fusion/setup.py +4 -0
  23. Rhapso/fusion/affine_fusion_worker.py +234 -0
  24. Rhapso/fusion/multiscale/__init__.py +0 -0
  25. Rhapso/fusion/multiscale/aind_hcr_data_transformation/__init__.py +19 -0
  26. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/__init__.py +3 -0
  27. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/czi_to_zarr.py +698 -0
  28. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/zarr_writer.py +265 -0
  29. Rhapso/fusion/multiscale/aind_hcr_data_transformation/models.py +81 -0
  30. Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/__init__.py +3 -0
  31. Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/utils.py +526 -0
  32. Rhapso/fusion/multiscale/aind_hcr_data_transformation/zeiss_job.py +249 -0
  33. Rhapso/fusion/multiscale/aind_z1_radial_correction/__init__.py +21 -0
  34. Rhapso/fusion/multiscale/aind_z1_radial_correction/array_to_zarr.py +257 -0
  35. Rhapso/fusion/multiscale/aind_z1_radial_correction/radial_correction.py +557 -0
  36. Rhapso/fusion/multiscale/aind_z1_radial_correction/run_capsule.py +98 -0
  37. Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/__init__.py +3 -0
  38. Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/utils.py +266 -0
  39. Rhapso/fusion/multiscale/aind_z1_radial_correction/worker.py +89 -0
  40. Rhapso/fusion/multiscale_worker.py +113 -0
  41. Rhapso/fusion/neuroglancer_link_gen/__init__.py +8 -0
  42. Rhapso/fusion/neuroglancer_link_gen/dispim_link.py +235 -0
  43. Rhapso/fusion/neuroglancer_link_gen/exaspim_link.py +127 -0
  44. Rhapso/fusion/neuroglancer_link_gen/hcr_link.py +368 -0
  45. Rhapso/fusion/neuroglancer_link_gen/iSPIM_top.py +47 -0
  46. Rhapso/fusion/neuroglancer_link_gen/link_utils.py +239 -0
  47. Rhapso/fusion/neuroglancer_link_gen/main.py +299 -0
  48. Rhapso/fusion/neuroglancer_link_gen/ng_layer.py +1434 -0
  49. Rhapso/fusion/neuroglancer_link_gen/ng_state.py +1123 -0
  50. Rhapso/fusion/neuroglancer_link_gen/parsers.py +336 -0
  51. Rhapso/fusion/neuroglancer_link_gen/raw_link.py +116 -0
  52. Rhapso/fusion/neuroglancer_link_gen/utils/__init__.py +4 -0
  53. Rhapso/fusion/neuroglancer_link_gen/utils/shader_utils.py +85 -0
  54. Rhapso/fusion/neuroglancer_link_gen/utils/transfer.py +43 -0
  55. Rhapso/fusion/neuroglancer_link_gen/utils/utils.py +303 -0
  56. Rhapso/fusion/neuroglancer_link_gen_worker.py +30 -0
  57. Rhapso/matching/__init__.py +0 -0
  58. Rhapso/matching/load_and_transform_points.py +458 -0
  59. Rhapso/matching/ransac_matching.py +544 -0
  60. Rhapso/matching/save_matches.py +120 -0
  61. Rhapso/matching/xml_parser.py +302 -0
  62. Rhapso/pipelines/__init__.py +0 -0
  63. Rhapso/pipelines/ray/__init__.py +0 -0
  64. Rhapso/pipelines/ray/aws/__init__.py +0 -0
  65. Rhapso/pipelines/ray/aws/alignment_pipeline.py +227 -0
  66. Rhapso/pipelines/ray/aws/config/__init__.py +0 -0
  67. Rhapso/pipelines/ray/evaluation.py +71 -0
  68. Rhapso/pipelines/ray/interest_point_detection.py +137 -0
  69. Rhapso/pipelines/ray/interest_point_matching.py +110 -0
  70. Rhapso/pipelines/ray/local/__init__.py +0 -0
  71. Rhapso/pipelines/ray/local/alignment_pipeline.py +167 -0
  72. Rhapso/pipelines/ray/matching_stats.py +104 -0
  73. Rhapso/pipelines/ray/param/__init__.py +0 -0
  74. Rhapso/pipelines/ray/solver.py +120 -0
  75. Rhapso/pipelines/ray/split_dataset.py +78 -0
  76. Rhapso/solver/__init__.py +0 -0
  77. Rhapso/solver/compute_tiles.py +562 -0
  78. Rhapso/solver/concatenate_models.py +116 -0
  79. Rhapso/solver/connected_graphs.py +111 -0
  80. Rhapso/solver/data_prep.py +181 -0
  81. Rhapso/solver/global_optimization.py +410 -0
  82. Rhapso/solver/model_and_tile_setup.py +109 -0
  83. Rhapso/solver/pre_align_tiles.py +323 -0
  84. Rhapso/solver/save_results.py +97 -0
  85. Rhapso/solver/view_transforms.py +75 -0
  86. Rhapso/solver/xml_to_dataframe_solver.py +213 -0
  87. Rhapso/split_dataset/__init__.py +0 -0
  88. Rhapso/split_dataset/compute_grid_rules.py +78 -0
  89. Rhapso/split_dataset/save_points.py +101 -0
  90. Rhapso/split_dataset/save_xml.py +377 -0
  91. Rhapso/split_dataset/split_images.py +537 -0
  92. Rhapso/split_dataset/xml_to_dataframe_split.py +219 -0
  93. rhapso-0.1.92.dist-info/METADATA +39 -0
  94. rhapso-0.1.92.dist-info/RECORD +101 -0
  95. rhapso-0.1.92.dist-info/WHEEL +5 -0
  96. rhapso-0.1.92.dist-info/licenses/LICENSE +21 -0
  97. rhapso-0.1.92.dist-info/top_level.txt +2 -0
  98. tests/__init__.py +1 -0
  99. tests/test_detection.py +17 -0
  100. tests/test_matching.py +21 -0
  101. tests/test_solving.py +21 -0
@@ -0,0 +1,698 @@
1
+ """
2
+ CZI to Zarr writer. It takes an input path
3
+ where 3D stacks are located, then these
4
+ stacks are loaded into memory and written
5
+ to zarr.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from typing import Dict, Hashable, List, Optional, Sequence, Tuple, Union, cast
11
+
12
+ import czifile
13
+ import dask
14
+ import dask.array as da
15
+ import numpy as np
16
+ import xarray_multiscale
17
+ import zarr
18
+ from numcodecs import blosc
19
+ from ome_zarr.format import CurrentFormat
20
+ from ome_zarr.io import parse_url
21
+ from ome_zarr.writer import write_multiscales_metadata
22
+
23
+ from .zarr_writer import (
24
+ BlockedArrayWriter,
25
+ )
26
+ from ..utils.utils import (
27
+ czi_block_generator,
28
+ pad_array_n_d,
29
+ )
30
+
31
+
32
+ def _build_ome(
33
+ data_shape: Tuple[int, ...],
34
+ image_name: str,
35
+ channel_names: Optional[List[str]] = None,
36
+ channel_colors: Optional[List[int]] = None,
37
+ channel_minmax: Optional[List[Tuple[float, float]]] = None,
38
+ channel_startend: Optional[List[Tuple[float, float]]] = None,
39
+ ) -> Dict:
40
+ """
41
+ Create the necessary metadata for an OME tiff image
42
+
43
+ Parameters
44
+ ----------
45
+ data_shape: A 5-d tuple, assumed to be TCZYX order
46
+ image_name: The name of the image
47
+ channel_names: The names for each channel
48
+ channel_colors: List of all channel colors
49
+ channel_minmax: List of all (min, max) pairs of channel pixel
50
+ ranges (min value of darkest pixel, max value of brightest)
51
+ channel_startend: List of all pairs for rendering where start is
52
+ a pixel value of darkness and end where a pixel value is
53
+ saturated
54
+
55
+ Returns
56
+ -------
57
+ Dict: An "omero" metadata object suitable for writing to ome-zarr
58
+ """
59
+ if channel_names is None:
60
+ channel_names = [
61
+ f"Channel:{image_name}:{i}" for i in range(data_shape[1])
62
+ ]
63
+ if channel_colors is None:
64
+ channel_colors = [i for i in range(data_shape[1])]
65
+ if channel_minmax is None:
66
+ channel_minmax = [(0.0, 1.0) for _ in range(data_shape[1])]
67
+ if channel_startend is None:
68
+ channel_startend = channel_minmax
69
+
70
+ ch = []
71
+ for i in range(data_shape[1]):
72
+ ch.append(
73
+ {
74
+ "active": True,
75
+ "coefficient": 1,
76
+ "color": f"{channel_colors[i]:06x}",
77
+ "family": "linear",
78
+ "inverted": False,
79
+ "label": channel_names[i],
80
+ "window": {
81
+ "end": float(channel_startend[i][1]),
82
+ "max": float(channel_minmax[i][1]),
83
+ "min": float(channel_minmax[i][0]),
84
+ "start": float(channel_startend[i][0]),
85
+ },
86
+ }
87
+ )
88
+
89
+ omero = {
90
+ "id": 1, # ID in OMERO
91
+ "name": image_name, # Name as shown in the UI
92
+ "version": "0.4", # Current version
93
+ "channels": ch,
94
+ "rdefs": {
95
+ "defaultT": 0, # First timepoint to show the user
96
+ "defaultZ": data_shape[2] // 2, # First Z section to show the user
97
+ "model": "color", # "color" or "greyscale"
98
+ },
99
+ }
100
+ return omero
101
+
102
+
103
+ def _compute_scales(
104
+ scale_num_levels: int,
105
+ scale_factor: Tuple[float, float, float],
106
+ pixelsizes: Tuple[float, float, float],
107
+ chunks: Tuple[int, int, int, int, int],
108
+ data_shape: Tuple[int, int, int, int, int],
109
+ translations: Optional[List[float]] = None,
110
+ ) -> Tuple[List, List]:
111
+ """
112
+ Generate the list of coordinate transformations
113
+ and associated chunk options.
114
+
115
+ Parameters
116
+ ----------
117
+ scale_num_levels: the number of downsampling levels
118
+ scale_factor: a tuple of scale factors in each spatial dimension (Z, Y, X)
119
+ pixelsizes: a list of pixel sizes in each spatial dimension (Z, Y, X)
120
+ chunks: a 5D tuple of integers with size of each
121
+ chunk dimension (T, C, Z, Y, X)
122
+ data_shape: a 5D tuple of the full resolution image's shape
123
+ translation: a 5 element list specifying the offset
124
+ in physical units in each dimension
125
+
126
+ Returns
127
+ -------
128
+ A tuple of the coordinate transforms and chunk options
129
+ """
130
+ transforms = [
131
+ [
132
+ # the voxel size for the first scale level
133
+ {
134
+ "type": "scale",
135
+ "scale": [
136
+ 1.0,
137
+ 1.0,
138
+ pixelsizes[0],
139
+ pixelsizes[1],
140
+ pixelsizes[2],
141
+ ],
142
+ }
143
+ ]
144
+ ]
145
+ if translations is not None:
146
+ transforms[0].append(
147
+ {"type": "translation", "translation": translations[0]}
148
+ )
149
+ chunk_sizes = []
150
+ lastz = data_shape[2]
151
+ lasty = data_shape[3]
152
+ lastx = data_shape[4]
153
+ opts = dict(
154
+ chunks=(
155
+ 1,
156
+ 1,
157
+ min(lastz, chunks[2]),
158
+ min(lasty, chunks[3]),
159
+ min(lastx, chunks[4]),
160
+ )
161
+ )
162
+ chunk_sizes.append(opts)
163
+ if scale_num_levels > 1:
164
+ for i in range(scale_num_levels - 1):
165
+ last_transform = transforms[-1][0]
166
+ last_scale = cast(List, last_transform["scale"])
167
+ transforms.append(
168
+ [
169
+ {
170
+ "type": "scale",
171
+ "scale": [
172
+ 1.0,
173
+ 1.0,
174
+ last_scale[2] * scale_factor[0],
175
+ last_scale[3] * scale_factor[1],
176
+ last_scale[4] * scale_factor[2],
177
+ ],
178
+ }
179
+ ]
180
+ )
181
+ if translations is not None:
182
+ transforms[-1].append(
183
+ {"type": "translation", "translation": translations[i + 1]}
184
+ )
185
+ lastz = int(np.ceil(lastz / scale_factor[0]))
186
+ lasty = int(np.ceil(lasty / scale_factor[1]))
187
+ lastx = int(np.ceil(lastx / scale_factor[2]))
188
+ opts = dict(
189
+ chunks=(
190
+ 1,
191
+ 1,
192
+ min(lastz, chunks[2]),
193
+ min(lasty, chunks[3]),
194
+ min(lastx, chunks[4]),
195
+ )
196
+ )
197
+ chunk_sizes.append(opts)
198
+
199
+ return transforms, chunk_sizes
200
+
201
+
202
+ def _get_axes_5d(
203
+ time_unit: str = "millisecond", space_unit: str = "micrometer"
204
+ ) -> List[Dict]:
205
+ """Generate the list of axes.
206
+
207
+ Parameters
208
+ ----------
209
+ time_unit: the time unit string, e.g., "millisecond"
210
+ space_unit: the space unit string, e.g., "micrometer"
211
+
212
+ Returns
213
+ -------
214
+ A list of dictionaries for each axis
215
+ """
216
+ axes_5d = [
217
+ {"name": "t", "type": "time", "unit": f"{time_unit}"},
218
+ {"name": "c", "type": "channel"},
219
+ {"name": "z", "type": "space", "unit": f"{space_unit}"},
220
+ {"name": "y", "type": "space", "unit": f"{space_unit}"},
221
+ {"name": "x", "type": "space", "unit": f"{space_unit}"},
222
+ ]
223
+ return axes_5d
224
+
225
+
226
+ def _downscale_origin(
227
+ array_shape: List[int],
228
+ origin: List[float],
229
+ voxel_size: List[float],
230
+ scale_factors: List[int],
231
+ n_levels: int,
232
+ ):
233
+ """
234
+ Calculate new origins for downscaled coordinate grids.
235
+
236
+ Parameters
237
+ ----------
238
+ array_shape : List[int]
239
+ Shape of the array in [t, c, z, y, x] order.
240
+ origin : list or tuple of float
241
+ The initial origin coordinates (z, y, x) of the array.
242
+ voxel_size : list or tuple of float
243
+ The size of each voxel along the (z, y, x) dimensions.
244
+ scale_factors : list or tuple of int
245
+ The factors by which to downscale the coordinates along each axis
246
+ (z, y, x).
247
+ n_levels : int
248
+ The number of downscaling levels to calculate.
249
+
250
+ Returns
251
+ -------
252
+ new_origins : list of list of float
253
+ A list of new origin coordinates for each downscaled level.
254
+ """
255
+ current_shape = np.array(array_shape[-3:], dtype=np.int32)
256
+ current_origin = np.array(origin[-3:], dtype=np.float64)
257
+ current_voxel_size = np.array(voxel_size[-3:], dtype=np.float64)
258
+ scale_factors = np.array(scale_factors[-3:], dtype=np.int32)
259
+
260
+ new_origins = [current_origin.tolist()]
261
+
262
+ for _ in range(n_levels - 1):
263
+
264
+ # Calculate the center shift for the new origin
265
+ center_shift = (current_voxel_size * (scale_factors - 1)) / 2
266
+ current_origin += center_shift
267
+
268
+ current_shape = np.ceil(current_shape / scale_factors).astype(int)
269
+ next_voxel_size = current_voxel_size * scale_factors
270
+ current_voxel_size = next_voxel_size
271
+
272
+ # Append the new origin
273
+ new_origins.append([0.0, 0.0] + current_origin.tolist())
274
+
275
+ # Ensure the initial origin is 5D
276
+ if len(new_origins[0]) < 5:
277
+ new_origins[0] = [0.0, 0.0] + new_origins[0]
278
+
279
+ return new_origins
280
+
281
+
282
+ def write_ome_ngff_metadata(
283
+ group: zarr.Group,
284
+ arr_shape: List[int],
285
+ final_chunksize: List[int],
286
+ image_name: str,
287
+ n_lvls: int,
288
+ scale_factors: tuple,
289
+ voxel_size: tuple,
290
+ channel_names: List[str] = None,
291
+ channel_colors: List[str] = None,
292
+ channel_minmax: List[float] = None,
293
+ channel_startend: List[float] = None,
294
+ origin: list = None,
295
+ metadata: dict = None,
296
+ ):
297
+ """
298
+ Write OME-NGFF metadata to a Zarr group.
299
+
300
+ Parameters
301
+ ----------
302
+ group : zarr.Group
303
+ The output Zarr group.
304
+ arr_shape : List[int]
305
+ List of ints with the dataset shape.
306
+ image_name : str
307
+ The name of the image.
308
+ n_lvls : int
309
+ The number of pyramid levels.
310
+ scale_factors : tuple
311
+ The scale factors for downsampling along each dimension.
312
+ voxel_size : tuple
313
+ The voxel size along each dimension.
314
+ channel_names: List[str]
315
+ List of channel names to add to the OMENGFF metadata
316
+ channel_colors: List[str]
317
+ List of channel colors to visualize the data
318
+ chanel_minmax: List[float]
319
+ List of channel min and max values based on the
320
+ image dtype
321
+ channel_startend: List[float]
322
+ List of the channel start and end metadata. This is
323
+ used for visualization. The start and end range might be
324
+ different from the min max and it is usually inside the
325
+ range
326
+ metadata: dict
327
+ Extra metadata to write in the OME-NGFF metadata
328
+ """
329
+ if metadata is None:
330
+ metadata = {}
331
+ fmt = CurrentFormat()
332
+
333
+ # Building the OMERO metadata
334
+ ome_json = _build_ome(
335
+ arr_shape,
336
+ image_name,
337
+ channel_names=channel_names,
338
+ channel_colors=channel_colors,
339
+ channel_minmax=channel_minmax,
340
+ channel_startend=channel_startend,
341
+ )
342
+ group.attrs["omero"] = ome_json
343
+ axes_5d = _get_axes_5d()
344
+
345
+ if origin is not None:
346
+ origin = _downscale_origin(
347
+ arr_shape, origin[-3:], voxel_size[-3:], scale_factors[-3:], n_lvls
348
+ )
349
+
350
+ coordinate_transformations, chunk_opts = _compute_scales(
351
+ n_lvls, scale_factors, voxel_size, final_chunksize, arr_shape, origin
352
+ )
353
+ fmt.validate_coordinate_transformations(
354
+ len(arr_shape), n_lvls, coordinate_transformations
355
+ )
356
+ # Setting coordinate transfomations
357
+ datasets = [{"path": str(i)} for i in range(n_lvls)]
358
+ if coordinate_transformations is not None:
359
+ for dataset, transform in zip(datasets, coordinate_transformations):
360
+ dataset["coordinateTransformations"] = transform
361
+
362
+ # Writing the multiscale metadata
363
+ write_multiscales_metadata(group, datasets, fmt, axes_5d, **metadata)
364
+
365
+
366
+ def create_czi_opts(codec: str, compression_level: int) -> dict:
367
+ """
368
+ Creates CZI options for writing
369
+ the OMEZarr.
370
+
371
+ Parameters
372
+ ----------
373
+ codec: str
374
+ Image codec used to write the image
375
+
376
+ compression_level: int
377
+ Compression level for the image
378
+
379
+ Returns
380
+ -------
381
+ dict
382
+ Dictionary with the blosc compression
383
+ to write the CZI image
384
+ """
385
+ return {
386
+ "compressor": blosc.Blosc(
387
+ cname=codec, clevel=compression_level, shuffle=blosc.SHUFFLE
388
+ )
389
+ }
390
+
391
+
392
+ def _get_pyramid_metadata():
393
+ """
394
+ Gets the image pyramid metadata
395
+ using xarray_multiscale package
396
+ """
397
+ # Try to get version, fallback to 'unknown' if not available
398
+ try:
399
+ version = str(xarray_multiscale.__version__)
400
+ except AttributeError:
401
+ version = "unknown"
402
+
403
+ return {
404
+ "metadata": {
405
+ "description": "Downscaling using the windowed mean",
406
+ "method": "xarray_multiscale.reducers.windowed_mean",
407
+ "version": version,
408
+ "args": "[false]",
409
+ # No extra parameters were used different
410
+ # from the orig. array and scales
411
+ "kwargs": {},
412
+ }
413
+ }
414
+
415
+
416
+ def compute_pyramid(
417
+ data: dask.array.core.Array,
418
+ n_lvls: int,
419
+ scale_axis: Tuple[int],
420
+ chunks: Union[str, Sequence[int], Dict[Hashable, int]] = "auto",
421
+ ) -> List[dask.array.core.Array]:
422
+ """
423
+ Computes the pyramid levels given an input full resolution image data
424
+
425
+ Parameters
426
+ ------------------------
427
+
428
+ data: dask.array.core.Array
429
+ Dask array of the image data
430
+
431
+ n_lvls: int
432
+ Number of downsampling levels
433
+ that will be applied to the original image
434
+
435
+ scale_axis: Tuple[int]
436
+ Scaling applied to each axis
437
+
438
+ chunks: Union[str, Sequence[int], Dict[Hashable, int]]
439
+ chunksize that will be applied to the multiscales
440
+ Default: "auto"
441
+
442
+ Returns
443
+ ------------------------
444
+
445
+ Tuple[List[dask.array.core.Array], Dict]:
446
+ List with the downsampled image(s) and dictionary
447
+ with image metadata
448
+ """
449
+
450
+ metadata = _get_pyramid_metadata()
451
+
452
+ pyramid = xarray_multiscale.multiscale(
453
+ array=data,
454
+ reduction=xarray_multiscale.reducers.windowed_mean, # func
455
+ scale_factors=scale_axis, # scale factors
456
+ preserve_dtype=True,
457
+ chunks=chunks,
458
+ )[:n_lvls]
459
+
460
+ return [pyramid_level.data for pyramid_level in pyramid], metadata
461
+
462
+
463
+ def czi_stack_zarr_writer(
464
+ czi_path: str,
465
+ output_path: str,
466
+ voxel_size: List[float],
467
+ final_chunksize: List[int],
468
+ scale_factor: List[int],
469
+ n_lvls: int,
470
+ channel_name: str,
471
+ logger: logging.Logger,
472
+ stack_name: str,
473
+ writing_options,
474
+ target_size_mb: Optional[int] = 24000,
475
+ ):
476
+ """
477
+ Writes a fused Zeiss channel in OMEZarr
478
+ format. This channel was read as a lazy array.
479
+
480
+ Parameters
481
+ ----------
482
+ czi_path: str
483
+ Path where the CZI file is stored.
484
+
485
+ output_path: PathLike
486
+ Path where we want to write the OMEZarr
487
+ channel
488
+
489
+ voxel_size: List[float]
490
+ Voxel size representing the dataset
491
+
492
+ final_chunksize: List[int]
493
+ Final chunksize we want to use to write
494
+ the final dataset
495
+
496
+ codec: str
497
+ Image codec for writing the Zarr
498
+
499
+ compression_level: int
500
+ Compression level
501
+
502
+ scale_factor: List[int]
503
+ Scale factor per axis. The dimensionality
504
+ is organized as ZYX.
505
+
506
+ n_lvls: int
507
+ Number of levels on the pyramid (multiresolution)
508
+ for better visualization
509
+
510
+ channel_name: str
511
+ Channel name we are currently writing
512
+
513
+ logger: logging.Logger
514
+ Logger object
515
+
516
+ target_size_mb: Optional[int]
517
+ Target size to pull from the CZI array.
518
+
519
+ """
520
+ written_pyramid = []
521
+ start_time = time.time()
522
+
523
+ with czifile.CziFile(str(czi_path)) as czi:
524
+ dataset_shape = tuple(i for i in czi.shape if i != 1)
525
+ extra_axes = (1,) * (5 - len(dataset_shape))
526
+ dataset_shape = extra_axes + dataset_shape
527
+
528
+ final_chunksize = ([1] * (5 - len(final_chunksize))) + final_chunksize
529
+ # Getting channel color
530
+ channel_colors = None
531
+
532
+ print(f"Writing {dataset_shape} from {stack_name} to {output_path}")
533
+
534
+ # Creating Zarr dataset
535
+ store = parse_url(path=output_path, mode="w").store
536
+ root_group = zarr.group(store=store)
537
+
538
+ # Using 1 thread since is in single machine.
539
+ # Avoiding the use of multithreaded due to GIL
540
+
541
+ if np.issubdtype(czi.dtype, np.integer):
542
+ np_info_func = np.iinfo
543
+
544
+ else:
545
+ # Floating point
546
+ np_info_func = np.finfo
547
+
548
+ # Getting min max metadata for the dtype
549
+ channel_minmax = [
550
+ (
551
+ np_info_func(czi.dtype).min,
552
+ np_info_func(czi.dtype).max,
553
+ )
554
+ for _ in range(dataset_shape[1])
555
+ ]
556
+
557
+ # Setting values for CZI
558
+ # Ideally we would use da.percentile(image_data, (0.1, 95))
559
+ # However, it would take so much time and resources and it is
560
+ # not used that much on neuroglancer
561
+ channel_startend = [(0.0, 550.0) for _ in range(dataset_shape[1])]
562
+
563
+ new_channel_group = root_group.create_group(
564
+ name=stack_name, overwrite=True
565
+ )
566
+
567
+ # Writing OME-NGFF metadata
568
+ write_ome_ngff_metadata(
569
+ group=new_channel_group,
570
+ arr_shape=dataset_shape,
571
+ image_name=stack_name,
572
+ n_lvls=n_lvls,
573
+ scale_factors=scale_factor,
574
+ voxel_size=voxel_size,
575
+ channel_names=[channel_name],
576
+ channel_colors=channel_colors,
577
+ channel_minmax=channel_minmax,
578
+ channel_startend=channel_startend,
579
+ metadata=_get_pyramid_metadata(),
580
+ final_chunksize=final_chunksize,
581
+ origin=[0, 0, 0],
582
+ )
583
+
584
+ # performance_report_path = f"{output_path}/report_{stack_name}.html"
585
+
586
+ # Writing zarr and performance report
587
+ # with performance_report(filename=performance_report_path):
588
+ logger.info(f"Writing channel {channel_name}/{stack_name}")
589
+
590
+ # Writing first multiscale by default
591
+ pyramid_group = new_channel_group.create_dataset(
592
+ name="0",
593
+ shape=dataset_shape,
594
+ chunks=final_chunksize,
595
+ dtype=czi.dtype,
596
+ compressor=writing_options,
597
+ dimension_separator="/",
598
+ overwrite=True,
599
+ )
600
+
601
+ # final_chunksize must be TCZYX order
602
+ for block, axis_area in czi_block_generator(
603
+ czi,
604
+ axis_jumps=final_chunksize[-3],
605
+ slice_axis="z",
606
+ ):
607
+ region = (
608
+ slice(None),
609
+ slice(None),
610
+ axis_area,
611
+ slice(0, dataset_shape[-2]),
612
+ slice(0, dataset_shape[-1]),
613
+ )
614
+ pyramid_group[region] = pad_array_n_d(block)
615
+
616
+ # Writing multiscales
617
+ previous_scale = da.from_zarr(pyramid_group, pyramid_group.chunks)
618
+ written_pyramid.append(previous_scale)
619
+
620
+ block_shape = list(
621
+ BlockedArrayWriter.get_block_shape(
622
+ arr=previous_scale,
623
+ target_size_mb=target_size_mb,
624
+ chunks=final_chunksize,
625
+ )
626
+ )
627
+ block_shape = extra_axes + tuple(block_shape)
628
+
629
+ for level in range(1, n_lvls):
630
+ previous_scale = da.from_zarr(pyramid_group, pyramid_group.chunks)
631
+ new_scale_factor = (
632
+ [1] * (len(previous_scale.shape) - len(scale_factor))
633
+ ) + scale_factor
634
+
635
+ previous_scale_pyramid, _ = compute_pyramid(
636
+ data=previous_scale,
637
+ scale_axis=new_scale_factor,
638
+ chunks=final_chunksize,
639
+ n_lvls=2,
640
+ )
641
+ array_to_write = previous_scale_pyramid[-1]
642
+
643
+ logger.info(
644
+ f"[level {level}]: pyramid level: {array_to_write.shape}"
645
+ )
646
+
647
+ pyramid_group = new_channel_group.create_dataset(
648
+ name=str(level),
649
+ shape=array_to_write.shape,
650
+ chunks=final_chunksize,
651
+ dtype=array_to_write.dtype,
652
+ compressor=writing_options,
653
+ dimension_separator="/",
654
+ overwrite=True,
655
+ )
656
+ BlockedArrayWriter.store(
657
+ array_to_write, pyramid_group, block_shape
658
+ )
659
+ written_pyramid.append(array_to_write)
660
+
661
+ end_time = time.time()
662
+ logger.info(f"Time to write the dataset: {end_time - start_time}")
663
+ print(f"Time to write the dataset: {end_time - start_time}")
664
+ logger.info(f"Written pyramid: {written_pyramid}")
665
+
666
+
667
+ def example():
668
+ """
669
+ Conversion example
670
+ """
671
+ from pathlib import Path
672
+
673
+ czi_test_stack = Path("path/to/data/tiles_test/SPIM/488_large.czi")
674
+
675
+ if czi_test_stack.exists():
676
+ writing_opts = create_czi_opts(codec="zstd", compression_level=3)
677
+
678
+ # for channel_name in
679
+ # for i, chn_name in enumerate(czi_file_reader.channel_names):
680
+ czi_stack_zarr_writer(
681
+ czi_path=str(czi_test_stack),
682
+ output_path=f"./{czi_test_stack.stem}",
683
+ voxel_size=[1.0, 1.0, 1.0],
684
+ final_chunksize=[128, 128, 128],
685
+ scale_factor=[2, 2, 2],
686
+ n_lvls=4,
687
+ channel_name=czi_test_stack.stem,
688
+ logger=logging.Logger(name="test"),
689
+ stack_name="test_conversion_czi_package.zarr",
690
+ writing_options=writing_opts["compressor"],
691
+ )
692
+
693
+ else:
694
+ print(f"File does not exist: {czi_test_stack}")
695
+
696
+
697
+ if __name__ == "__main__":
698
+ example()