visor-py 2024.12.1__tar.gz

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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, VISoR Group
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.1
2
+ Name: visor-py
3
+ Version: 2024.12.1
4
+ Summary: A Python Library for VISoR Image.
5
+ Author-email: Zijian Yi <arrowzeke@gmail.com>
6
+ License: BSD
7
+ Project-URL: repository, https://github.com/visor-tech/visor-py
8
+ Keywords: VISoR
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: dask[complete]==2024.9.1
16
+ Requires-Dist: zarr==2.18.3
17
+ Requires-Dist: numcodecs==0.13.0
18
+
19
+ # visor-py
20
+ A Python Library for VISoR Image.
21
+
22
+ ## Usage
23
+ #### Install Module
24
+ ```sh
25
+ pip install visor-py
26
+ ```
27
+
28
+ #### Import Module
29
+ ```py
30
+ import visor.image as vimg
31
+ ```
32
+
33
+ #### Examples
34
+ - Read .vsr
35
+ ```py
36
+ import visor.image as vimg
37
+
38
+ # Open .vsr file with read-only ('r') mode
39
+ # img is a visor.Image object
40
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
41
+ print(img.info)
42
+ ```
43
+
44
+ - Read raw image
45
+ ```py
46
+ import visor.image as vimg
47
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
48
+
49
+ # Read raw image of slice_1_10x from zarr file
50
+ # arr is a visor.Array object
51
+ arr = img.read(img_type='raw',
52
+ zarr_file='slice_1_10x.zarr',
53
+ resolution=0)
54
+ print(arr.info)
55
+ ```
56
+
57
+ - Read arrays by named stacks or channels
58
+ ```py
59
+ import visor.image as vimg
60
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
61
+ arr = img.read(img_type='raw',
62
+ zarr_file='slice_1_10x.zarr',
63
+ resolution=0)
64
+
65
+ # Read stack by label
66
+ # s1_array is a dask.array, dimensions: c,z,y,x
67
+ s1_array = arr.read(stack='stack_1')
68
+ print(f's1_array shape: {s1_array.shape}, dimensions: c,z,y,x')
69
+
70
+ # Read channel by wavelength
71
+ # c488_array is a dask.array, dimensions: s,z,y,x
72
+ c488_array = arr.read(channel='488')
73
+ print(f'c488_array shape: {c488_array.shape}, dimensions: s,z,y,x')
74
+
75
+ # Read stack and channel
76
+ # s1c488_array is a dask.array, dimensions: z,y,x
77
+ s1c488_array = arr.read(stack='stack_1', channel='488')
78
+ print(f's1c488_array shape: {s1c488_array.shape}, dimensions: z,y,x')
79
+ ```
80
+
81
+ - Read array by index
82
+ ```py
83
+ import visor.image as vimg
84
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
85
+ arr = img.read(img_type='raw',
86
+ zarr_file='slice_1_10x.zarr',
87
+ resolution=0)
88
+
89
+ # Read by index
90
+ # the_array is a dask.array
91
+ the_array = arr.array
92
+ print(f'the_array shape: {the_array.shape}, dimensions: s,c,z,y,x')
93
+ # subarr is a dask.array
94
+ subarr = the_array[0,0,:,:,:]
95
+ print(f'subarr shape: {subarr.shape}, dimensions: z,y,x')
96
+ ```
97
+
98
+ - Write .vsr
99
+ ```py
100
+ import visor.image as vimg
101
+ import numpy as np
102
+ import dask.array as da
103
+
104
+ # Open .vsr file with write ('w') mode
105
+ # img is a visor.Image object
106
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'w')
107
+
108
+ # Generate a random array
109
+ # new_arr is a dask array
110
+ new_arr = da.random.random(size=(2, 2, 4, 4, 4), chunks=(1, 1, 2, 2, 2))
111
+
112
+ # Metadata
113
+ # follow https://visor-tech.github.io/visor-data-schema/
114
+ img_info = {} # info.json
115
+ arr_info = {} # .zattrs
116
+ selected = {} # selected.json
117
+
118
+ # Write array to .vsr
119
+ img.write(arr=new_arr,
120
+ img_type='raw',
121
+ file='slice_1_10x',
122
+ resolution=0,
123
+ img_info=img_info,
124
+ arr_info=arr_info,
125
+ selected=selected)
126
+ ```
127
+
128
+ ## References
129
+ [VISoR Image Schema](https://visor-tech.github.io/visor-data-schema)
@@ -0,0 +1,111 @@
1
+ # visor-py
2
+ A Python Library for VISoR Image.
3
+
4
+ ## Usage
5
+ #### Install Module
6
+ ```sh
7
+ pip install visor-py
8
+ ```
9
+
10
+ #### Import Module
11
+ ```py
12
+ import visor.image as vimg
13
+ ```
14
+
15
+ #### Examples
16
+ - Read .vsr
17
+ ```py
18
+ import visor.image as vimg
19
+
20
+ # Open .vsr file with read-only ('r') mode
21
+ # img is a visor.Image object
22
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
23
+ print(img.info)
24
+ ```
25
+
26
+ - Read raw image
27
+ ```py
28
+ import visor.image as vimg
29
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
30
+
31
+ # Read raw image of slice_1_10x from zarr file
32
+ # arr is a visor.Array object
33
+ arr = img.read(img_type='raw',
34
+ zarr_file='slice_1_10x.zarr',
35
+ resolution=0)
36
+ print(arr.info)
37
+ ```
38
+
39
+ - Read arrays by named stacks or channels
40
+ ```py
41
+ import visor.image as vimg
42
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
43
+ arr = img.read(img_type='raw',
44
+ zarr_file='slice_1_10x.zarr',
45
+ resolution=0)
46
+
47
+ # Read stack by label
48
+ # s1_array is a dask.array, dimensions: c,z,y,x
49
+ s1_array = arr.read(stack='stack_1')
50
+ print(f's1_array shape: {s1_array.shape}, dimensions: c,z,y,x')
51
+
52
+ # Read channel by wavelength
53
+ # c488_array is a dask.array, dimensions: s,z,y,x
54
+ c488_array = arr.read(channel='488')
55
+ print(f'c488_array shape: {c488_array.shape}, dimensions: s,z,y,x')
56
+
57
+ # Read stack and channel
58
+ # s1c488_array is a dask.array, dimensions: z,y,x
59
+ s1c488_array = arr.read(stack='stack_1', channel='488')
60
+ print(f's1c488_array shape: {s1c488_array.shape}, dimensions: z,y,x')
61
+ ```
62
+
63
+ - Read array by index
64
+ ```py
65
+ import visor.image as vimg
66
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
67
+ arr = img.read(img_type='raw',
68
+ zarr_file='slice_1_10x.zarr',
69
+ resolution=0)
70
+
71
+ # Read by index
72
+ # the_array is a dask.array
73
+ the_array = arr.array
74
+ print(f'the_array shape: {the_array.shape}, dimensions: s,c,z,y,x')
75
+ # subarr is a dask.array
76
+ subarr = the_array[0,0,:,:,:]
77
+ print(f'subarr shape: {subarr.shape}, dimensions: z,y,x')
78
+ ```
79
+
80
+ - Write .vsr
81
+ ```py
82
+ import visor.image as vimg
83
+ import numpy as np
84
+ import dask.array as da
85
+
86
+ # Open .vsr file with write ('w') mode
87
+ # img is a visor.Image object
88
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'w')
89
+
90
+ # Generate a random array
91
+ # new_arr is a dask array
92
+ new_arr = da.random.random(size=(2, 2, 4, 4, 4), chunks=(1, 1, 2, 2, 2))
93
+
94
+ # Metadata
95
+ # follow https://visor-tech.github.io/visor-data-schema/
96
+ img_info = {} # info.json
97
+ arr_info = {} # .zattrs
98
+ selected = {} # selected.json
99
+
100
+ # Write array to .vsr
101
+ img.write(arr=new_arr,
102
+ img_type='raw',
103
+ file='slice_1_10x',
104
+ resolution=0,
105
+ img_info=img_info,
106
+ arr_info=arr_info,
107
+ selected=selected)
108
+ ```
109
+
110
+ ## References
111
+ [VISoR Image Schema](https://visor-tech.github.io/visor-data-schema)
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "visor-py"
7
+ version = "2024.12.1"
8
+ description = "A Python Library for VISoR Image."
9
+ authors = [
10
+ { name = "Zijian Yi", email = "arrowzeke@gmail.com" }
11
+ ]
12
+ readme = "README.md"
13
+ license = { text = "BSD" }
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: BSD License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ requires-python = ">=3.11"
20
+ dependencies = [
21
+ "dask[complete]==2024.9.1",
22
+ "zarr==2.18.3",
23
+ "numcodecs==0.13.0"
24
+ ]
25
+ keywords = ["VISoR"]
26
+
27
+ [project.urls]
28
+ repository = "https://github.com/visor-tech/visor-py"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,2 @@
1
+ from .core import open_vsr
2
+ __all__ = ['open_vsr']
@@ -0,0 +1,305 @@
1
+ from pathlib import Path
2
+ import os
3
+ import json
4
+ import zarr
5
+ import dask.array as da
6
+ from numcodecs import Blosc
7
+
8
+ # Schema Reference: https://visor-tech.github.io/visor-data-schema
9
+
10
+ class Image:
11
+
12
+ def __init__(self, path:Path, mode:str):
13
+ """
14
+ Constructor of Image
15
+
16
+ Parameters:
17
+ path : the .vsr file path
18
+ mode : 'r' for read_only
19
+ 'w' for write
20
+ """
21
+ # path
22
+ path = Path(path)
23
+ if path.suffix != '.vsr':
24
+ raise ValueError(f'The path {path} does not have .vsr extension.')
25
+ if not path.exists() or not path.is_dir():
26
+ if 'w' == mode:
27
+ path.mkdir(parents=True, exist_ok=True)
28
+ else:
29
+ raise NotADirectoryError(f'The path {path} is not a directory.')
30
+ self.path = path
31
+ self.mode = mode
32
+
33
+ # info
34
+ info_file = path/'info.json'
35
+ if not info_file.exists():
36
+ if 'w' == mode:
37
+ info_file.touch()
38
+ info_file.write_text('{}')
39
+ else:
40
+ raise FileNotFoundError(f'Metadata file info.json is not found in {path}.')
41
+ with open(info_file) as f:
42
+ self.info = json.load(f)
43
+
44
+ # file structure
45
+ self.image_types = self._get_image_types()
46
+ self.transforms = self._get_transforms()
47
+ self.image_files = {}
48
+ for image_type in self.image_types:
49
+ dir = path/f'visor_{image_type}_images'
50
+ if 'raw' == image_type:
51
+ with open(dir/'selected.json') as sf:
52
+ self.image_files[image_type] = json.load(sf)
53
+ else:
54
+ self.image_files[image_type] = [{
55
+ "path": d,
56
+ "channels": self._get_channels(dir/f/'.zattr')
57
+ } for d in dir.iterdir() if d.suffix == '.zarr']
58
+
59
+
60
+ def _get_image_types(self):
61
+ """
62
+ Private method to get image type list
63
+
64
+ Returns:
65
+ List of image types
66
+ """
67
+
68
+ return [d.name.split('_')[1] for d in self.path.glob('visor_*_images')]
69
+
70
+
71
+ def _get_transforms(self):
72
+ """
73
+ Private method to get transform list
74
+
75
+ Returns:
76
+ List of transforms
77
+ """
78
+
79
+ transforms = []
80
+
81
+ transforms_dir = self.path/'visor_recon_transforms'
82
+ if transforms_dir.is_dir():
83
+ transforms = transforms_dir.iterdir()
84
+
85
+ return transforms
86
+
87
+
88
+ def _get_channels(meta_file):
89
+ """
90
+ Private method to get channel list from metadata file
91
+
92
+ Parameters:
93
+ meta_file : the metadata file path
94
+
95
+ Returns:
96
+ List of channel wavelengths
97
+ """
98
+
99
+ with open(meta_file) as mf:
100
+ meta = json.load(mf)
101
+ return [c['wavelength'] for c in meta['channels']]
102
+
103
+
104
+ def _get_stacks(meta_file):
105
+ """
106
+ Private method to get stack list
107
+
108
+ Parameters:
109
+ meta_file : the metadata file path
110
+
111
+ Returns:
112
+ List of stack labels
113
+ """
114
+
115
+ with open(meta_file) as mf:
116
+ meta = json.load(mf)
117
+ return [s['label'] for s in meta['visor_stacks']]
118
+
119
+
120
+ def list(self, img_type=None):
121
+ """
122
+ List image files
123
+
124
+ Parameters:
125
+ img_type : optional filter of image type
126
+
127
+ Returns:
128
+ Dictionary of image files
129
+ """
130
+
131
+ if None == img_type:
132
+ return self.image_files
133
+ elif img_type not in self.image_files:
134
+ return {}
135
+ else:
136
+ return self.image_files[img_type]
137
+
138
+
139
+ def read(self, img_type:str, zarr_file:str, resolution:int):
140
+ """
141
+ Read Array from Image
142
+
143
+ Parameters:
144
+ img_type : visor image type, e.g. 'raw', 'projn', etc.
145
+ zarr_file : zarr file name with '.zarr' extension
146
+ resolution : resolution level
147
+
148
+ Returns:
149
+ Array
150
+ """
151
+
152
+ return Array(path=self.path/f'visor_{img_type}_images'/zarr_file,
153
+ resolution=resolution)
154
+
155
+
156
+ def write(self, arr:da, img_type:str, file:str,
157
+ resolution:int, img_info:dict,
158
+ arr_info:dict, selected:dict=None,
159
+ compression:str='zstd', clevel:int=5):
160
+ """
161
+ Read Array from Image
162
+
163
+ Parameters:
164
+ arr : dask array to write
165
+ img_type : visor image type, e.g. 'raw', 'projn', etc.
166
+ file : file name without extension
167
+ resolution : resolution level
168
+ img_info : metadata to write to info.json
169
+ arr_info : metadata to write to .zattrs
170
+ selected : optional metadata to write to selected.json
171
+ compression : compression algorithm to use for zarr, default to 'zstd'
172
+ clevel : compression level to use for zarr, default to 5
173
+ """
174
+
175
+ if self.mode != 'w':
176
+ raise PermissionError('"write" method is only available in "w" mode.')
177
+
178
+ zarr_file = self.path/f'visor_{img_type}_images'/f'{file}.zarr'
179
+ component = str(resolution)
180
+ da.to_zarr(arr, zarr_file,
181
+ dimension_separator=os.sep,
182
+ component=component,
183
+ compressor=Blosc(cname=compression, clevel=clevel))
184
+
185
+ img_info_file = self.path/'info.json'
186
+ with open(img_info_file, 'w') as iif:
187
+ json.dump(img_info, iif, indent=2)
188
+
189
+ arr_info_file = zarr_file/'.zattrs'
190
+ with open(arr_info_file, 'w') as aif:
191
+ json.dump(arr_info, aif, indent=2)
192
+
193
+ if selected and 'raw' == img_type:
194
+ selected_file = self.path/'visor_raw_images'
195
+ with open(selected_file, 'w') as sf:
196
+ json.dump(selected, sf, indent=2)
197
+
198
+
199
+ class Array:
200
+
201
+ def __init__(self, path:Path, resolution:int):
202
+ """
203
+ Constructor of Array
204
+
205
+ Parameters:
206
+ path : the .zarr file path
207
+ resolution : resolution level
208
+ """
209
+ # path
210
+ if path.suffix != '.zarr':
211
+ raise ValueError(f'{path} is not a zarr file.')
212
+ if not path.is_dir():
213
+ raise NotADirectoryError(f'The path {path} is not a directory.')
214
+ self.path = path
215
+
216
+ # resolution
217
+ self.resolution = resolution
218
+
219
+ # .zarray
220
+ zarray_file = path/str(resolution)/'.zarray'
221
+ if not zarray_file.exists():
222
+ raise FileNotFoundError(f'Must contain a .zarray file in {path} directory.')
223
+
224
+ # info
225
+ meta_file = path/'.zattrs'
226
+ with open(meta_file) as mf:
227
+ self.info = json.load(mf)
228
+
229
+ # dictionares of named dimension
230
+ self.channel_map = {}
231
+ if 'channels' in self.info:
232
+ for channel in self.info['channels']:
233
+ self.channel_map[channel['wavelength']] = channel['index']
234
+ self.stack_map = {}
235
+ if 'visor_stacks' in self.info:
236
+ for stack in self.info['visor_stacks']:
237
+ self.stack_map[stack['label']] = stack['index']
238
+
239
+ # array
240
+ _store = zarr.DirectoryStore(path/str(resolution))
241
+ z_array = zarr.open(_store, mode='r')
242
+ self.array = da.from_array(z_array, chunks=z_array.chunks)
243
+
244
+
245
+ def read(self, channel=None, stack=None):
246
+ """
247
+ Read a subarray from this array with optional `s` and `c` dimensions.
248
+
249
+ Parameters:
250
+ stack: str or None, the visor_stack label
251
+ Get index from stack_map. If None, take all `s` subarrays.
252
+ channel: str or None, the channel wavelength
253
+ Get index from channel_map. If None, take all `c` subarrays.
254
+
255
+ Returns:
256
+ dask.array
257
+ """
258
+ # Total dimensions in the array
259
+ ndim = self.array.ndim
260
+
261
+ # Define slices for each dimension
262
+ arr_slices = [slice(None)] * ndim
263
+
264
+ # Validate `stack` and `channel` based on array dimensions
265
+ if ndim == 5: # (s, c, z, y, x)
266
+ if (stack is not None) and (stack not in self.stack_map):
267
+ raise KeyError(f'stack {stack} does not exist, valid stacks: {list(self.stack_map.keys())}')
268
+ if (channel is not None) and (channel not in self.channel_map):
269
+ raise KeyError(f'channel {channel} does not exist, valid channels: {list(self.channel_map.keys())}')
270
+ elif ndim == 4: # (c, z, y, x)
271
+ if (channel is not None) and (channel not in self.channel_map):
272
+ raise KeyError(f'channel {channel} does not exist, valid channels: {list(self.channel_map.keys())}')
273
+ elif ndim == 3: # (z, y, x)
274
+ if stack is not None or channel is not None:
275
+ raise IndexError("'s' or 'c' dimensions do not exist in a 3D array")
276
+
277
+ # Map the dimensions dynamically based on `ndim`
278
+ if ndim == 5: # (s, c, z, y, x)
279
+ if stack is not None:
280
+ arr_slices[0] = self.stack_map[stack]
281
+ if channel is not None:
282
+ arr_slices[1] = self.channel_map[channel]
283
+ elif ndim == 4: # (c, z, y, x)
284
+ if channel is not None:
285
+ arr_slices[0] = self.channel_map[channel]
286
+ elif ndim == 3: # (z, y, x)
287
+ pass # No `s` or `c` dimensions to handle
288
+
289
+ # Apply the arr_slices to the array
290
+ return self.array[tuple(arr_slices)]
291
+
292
+
293
+ def open_vsr(path:str, mode:str):
294
+ """
295
+ Open vsr file, as an Image object
296
+
297
+ Parameters:
298
+ path : the .vsr file path
299
+ mode : 'r' for read_only
300
+ 'w' for write
301
+
302
+ Returns:
303
+ Image
304
+ """
305
+ return Image(Path(path), mode)
@@ -0,0 +1,225 @@
1
+ from pathlib import Path
2
+ import unittest
3
+ import shutil
4
+
5
+ import visor.image as vimg
6
+
7
+ class TestBase(unittest.TestCase):
8
+
9
+ def setUp(self):
10
+
11
+ # Test Data: validated with https://ome.github.io/ome-ngff-validator
12
+ # simply use ome_zarr command line tool as below:
13
+ # $ ome_zarr view visor/image/tests/data/VISOR001/visor_raw_images/slice_1_10x.zarr
14
+ self.path = Path(__file__).parent/'data'/'VISOR001.vsr'
15
+
16
+
17
+ class TestCore(TestBase):
18
+
19
+ def test_open_vsr_r(self):
20
+
21
+ img = vimg.open_vsr(self.path, 'r')
22
+
23
+ self.assertIsInstance(img, vimg.core.Image)
24
+ self.assertEqual(img.info['animal_id'], 'VISOR001')
25
+ self.assertEqual(img.info['project_name'], 'VISOR')
26
+ self.assertEqual(img.info['species'], 'Mouse')
27
+ self.assertEqual(img.info['subproject_name'], 'XXX-XXXX-1X7-3X')
28
+ self.assertEqual(img.image_types, ['raw'])
29
+ self.assertEqual(img.transforms, [])
30
+ self.assertEqual(img.image_files, {'raw': [
31
+ {'path':'slice_1_10x.zarr', 'channels':["488","561"]},
32
+ {'path':'slice_1_10x_1.zarr', 'channels':["405","640"]},
33
+ ]})
34
+
35
+
36
+ def test_open_vsr_w(self):
37
+
38
+ img = vimg.open_vsr(self.path, 'w')
39
+ self.assertIsInstance(img, vimg.core.Image)
40
+
41
+
42
+ class TestImageReadOnly(TestBase):
43
+
44
+ def setUp(self):
45
+
46
+ super().setUp()
47
+ self.img = vimg.open_vsr(self.path, 'r')
48
+
49
+ self.zarr_file = 'slice_1_10x.zarr'
50
+ self.resolution = 0
51
+
52
+
53
+ def test_image_list(self):
54
+
55
+ self.assertEqual(self.img.list(), {'raw': [
56
+ {'path':'slice_1_10x.zarr', 'channels':["488","561"]},
57
+ {'path':'slice_1_10x_1.zarr', 'channels':["405","640"]},
58
+ ]})
59
+ self.assertEqual(self.img.list('raw'), [
60
+ {'path':'slice_1_10x.zarr', 'channels':["488","561"]},
61
+ {'path':'slice_1_10x_1.zarr', 'channels':["405","640"]},
62
+ ])
63
+ self.assertEqual(self.img.list('projn'), {})
64
+
65
+
66
+ def test_image_read(self):
67
+
68
+ arr = self.img.read('raw',self.zarr_file,0)
69
+
70
+ self.assertIsInstance(arr, vimg.core.Array)
71
+ self.assertIn('multiscales', arr.info)
72
+ self.assertIn('visor_stacks', arr.info)
73
+ self.assertIn('channels', arr.info)
74
+ self.assertEqual(arr.channel_map, {'488':0, '561':1})
75
+ self.assertEqual(arr.stack_map, {'stack_1':0, 'stack_2':1})
76
+ self.assertEqual(arr.array.shape, (2,2,4,4,4))
77
+
78
+ def test_image_write_exception(self):
79
+
80
+ arr = self.img.read('raw',self.zarr_file,resolution=self.resolution)
81
+ with self.assertRaises(PermissionError) as context:
82
+ self.img.write(arr,'raw',self.zarr_file,0,{},{})
83
+ self.assertEqual(str(context.exception), '"write" method is only available in "w" mode.')
84
+
85
+
86
+ class TestArrayRead(TestBase):
87
+
88
+ def setUp(self):
89
+
90
+ super().setUp()
91
+ img = vimg.open_vsr(self.path, 'w')
92
+ self.arr = img.read('raw','slice_1_10x.zarr',0)
93
+
94
+
95
+ def test_array_read(self):
96
+
97
+ arr = self.arr.read()
98
+ self.assertEqual(arr.shape, (2,2,4,4,4))
99
+
100
+
101
+ def test_array_read_channel(self):
102
+
103
+ arr = self.arr.read(channel='488')
104
+ self.assertEqual(arr.shape, (2,4,4,4))
105
+
106
+
107
+ def test_array_read_stack(self):
108
+
109
+ arr = self.arr.read(stack='stack_1')
110
+ self.assertEqual(arr.shape, (2,4,4,4))
111
+
112
+
113
+ def test_array_read_channel_and_stack(self):
114
+
115
+ arr = self.arr.read(channel='488',stack='stack_1')
116
+ self.assertEqual(arr.shape, (4,4,4))
117
+
118
+
119
+ def test_array_read_channel_not_exist(self):
120
+
121
+ with self.assertRaises(KeyError) as context:
122
+ self.arr.read(channel='777')
123
+ self.assertEqual(str(context.exception), '"channel 777 does not exist, valid channels: [\'488\', \'561\']"')
124
+
125
+
126
+ class TestImageWrite(TestBase):
127
+
128
+ def setUp(self):
129
+
130
+ super().setUp()
131
+ self.src_img_type = 'raw'
132
+ self.src_img_file = 'slice_1_10x'
133
+
134
+ self.dst_img_type = 'raw'
135
+ self.dst_img_file = 'slice_1_10x'
136
+
137
+ self.resolution = 0
138
+
139
+ src_img = vimg.open_vsr(self.path, 'r')
140
+ self.src_img_info = src_img.info
141
+ self.arr = src_img.read(self.src_img_type,
142
+ f'{self.src_img_file}.zarr',
143
+ self.resolution)
144
+
145
+ self.dst_path = self.path.parent/'tmp.vsr'
146
+
147
+
148
+ def tearDown(self):
149
+
150
+ if self.dst_path.exists():
151
+ shutil.rmtree(self.dst_path)
152
+
153
+
154
+ def test_image_write(self):
155
+
156
+ dst_img = vimg.open_vsr(self.dst_path, 'w')
157
+ dst_img.write(self.arr.array,
158
+ self.dst_img_type,
159
+ self.dst_img_file,
160
+ self.resolution,
161
+ self.src_img_info,
162
+ self.arr.info)
163
+ arr = dst_img.read(self.dst_img_type,
164
+ f'{self.dst_img_file}.zarr',
165
+ 0)
166
+ self.assertIsInstance(arr, vimg.core.Array)
167
+ self.assertIn('multiscales', arr.info)
168
+ self.assertIn('visor_stacks', arr.info)
169
+ self.assertIn('channels', arr.info)
170
+ self.assertEqual(arr.channel_map, {'488':0, '561':1})
171
+ self.assertEqual(arr.stack_map, {'stack_1':0, 'stack_2':1})
172
+ self.assertEqual(arr.array.shape, (2,2,4,4,4))
173
+
174
+
175
+ # class TestImageUpdate(TestBase):
176
+
177
+ # def setUp(self):
178
+
179
+ # super().setUp()
180
+ # self.src_img_type = 'raw'
181
+ # self.src_img_file = 'slice_1_10x'
182
+
183
+ # self.dst_img_type = 'icorr'
184
+ # self.dst_img_file = 'slice_1_10x'
185
+
186
+ # self.resolution = 0
187
+
188
+ # src_img = vimg.open_vsr(self.path, 'r')
189
+ # self.src_img_info = src_img.info
190
+ # self.arr = src_img.read(self.src_img_type,
191
+ # f'{self.src_img_file}.zarr',
192
+ # self.resolution)
193
+
194
+ # self.dst_path = self.path/'visor_icorr_images'
195
+
196
+
197
+ # def tearDown(self):
198
+
199
+ # if self.dst_path.exists():
200
+ # shutil.rmtree(self.dst_path)
201
+
202
+
203
+ # def test_image_write(self):
204
+
205
+ # dst_img = vimg.open_vsr(self.dst_path, 'w')
206
+ # dst_img.write(self.arr.array,
207
+ # self.dst_img_type,
208
+ # self.dst_img_file,
209
+ # self.resolution,
210
+ # self.src_img_info,
211
+ # self.arr.info)
212
+ # arr = dst_img.read(self.dst_img_type,
213
+ # f'{self.dst_img_file}.zarr',
214
+ # 0)
215
+ # self.assertIsInstance(arr, vimg.core.Array)
216
+ # self.assertIn('multiscales', arr.info)
217
+ # self.assertIn('visor_stacks', arr.info)
218
+ # self.assertIn('channels', arr.info)
219
+ # self.assertEqual(arr.channel_map, {'488':0, '561':1})
220
+ # self.assertEqual(arr.stack_map, {'stack_1':0, 'stack_2':1})
221
+ # self.assertEqual(arr.array.shape, (2,2,4,4,4))
222
+
223
+
224
+ if __name__ == '__main__':
225
+ unittest.main()
File without changes
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.1
2
+ Name: visor-py
3
+ Version: 2024.12.1
4
+ Summary: A Python Library for VISoR Image.
5
+ Author-email: Zijian Yi <arrowzeke@gmail.com>
6
+ License: BSD
7
+ Project-URL: repository, https://github.com/visor-tech/visor-py
8
+ Keywords: VISoR
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: dask[complete]==2024.9.1
16
+ Requires-Dist: zarr==2.18.3
17
+ Requires-Dist: numcodecs==0.13.0
18
+
19
+ # visor-py
20
+ A Python Library for VISoR Image.
21
+
22
+ ## Usage
23
+ #### Install Module
24
+ ```sh
25
+ pip install visor-py
26
+ ```
27
+
28
+ #### Import Module
29
+ ```py
30
+ import visor.image as vimg
31
+ ```
32
+
33
+ #### Examples
34
+ - Read .vsr
35
+ ```py
36
+ import visor.image as vimg
37
+
38
+ # Open .vsr file with read-only ('r') mode
39
+ # img is a visor.Image object
40
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
41
+ print(img.info)
42
+ ```
43
+
44
+ - Read raw image
45
+ ```py
46
+ import visor.image as vimg
47
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
48
+
49
+ # Read raw image of slice_1_10x from zarr file
50
+ # arr is a visor.Array object
51
+ arr = img.read(img_type='raw',
52
+ zarr_file='slice_1_10x.zarr',
53
+ resolution=0)
54
+ print(arr.info)
55
+ ```
56
+
57
+ - Read arrays by named stacks or channels
58
+ ```py
59
+ import visor.image as vimg
60
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
61
+ arr = img.read(img_type='raw',
62
+ zarr_file='slice_1_10x.zarr',
63
+ resolution=0)
64
+
65
+ # Read stack by label
66
+ # s1_array is a dask.array, dimensions: c,z,y,x
67
+ s1_array = arr.read(stack='stack_1')
68
+ print(f's1_array shape: {s1_array.shape}, dimensions: c,z,y,x')
69
+
70
+ # Read channel by wavelength
71
+ # c488_array is a dask.array, dimensions: s,z,y,x
72
+ c488_array = arr.read(channel='488')
73
+ print(f'c488_array shape: {c488_array.shape}, dimensions: s,z,y,x')
74
+
75
+ # Read stack and channel
76
+ # s1c488_array is a dask.array, dimensions: z,y,x
77
+ s1c488_array = arr.read(stack='stack_1', channel='488')
78
+ print(f's1c488_array shape: {s1c488_array.shape}, dimensions: z,y,x')
79
+ ```
80
+
81
+ - Read array by index
82
+ ```py
83
+ import visor.image as vimg
84
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'r')
85
+ arr = img.read(img_type='raw',
86
+ zarr_file='slice_1_10x.zarr',
87
+ resolution=0)
88
+
89
+ # Read by index
90
+ # the_array is a dask.array
91
+ the_array = arr.array
92
+ print(f'the_array shape: {the_array.shape}, dimensions: s,c,z,y,x')
93
+ # subarr is a dask.array
94
+ subarr = the_array[0,0,:,:,:]
95
+ print(f'subarr shape: {subarr.shape}, dimensions: z,y,x')
96
+ ```
97
+
98
+ - Write .vsr
99
+ ```py
100
+ import visor.image as vimg
101
+ import numpy as np
102
+ import dask.array as da
103
+
104
+ # Open .vsr file with write ('w') mode
105
+ # img is a visor.Image object
106
+ img = vimg.open_vsr('path/to/VISOR001.vsr', 'w')
107
+
108
+ # Generate a random array
109
+ # new_arr is a dask array
110
+ new_arr = da.random.random(size=(2, 2, 4, 4, 4), chunks=(1, 1, 2, 2, 2))
111
+
112
+ # Metadata
113
+ # follow https://visor-tech.github.io/visor-data-schema/
114
+ img_info = {} # info.json
115
+ arr_info = {} # .zattrs
116
+ selected = {} # selected.json
117
+
118
+ # Write array to .vsr
119
+ img.write(arr=new_arr,
120
+ img_type='raw',
121
+ file='slice_1_10x',
122
+ resolution=0,
123
+ img_info=img_info,
124
+ arr_info=arr_info,
125
+ selected=selected)
126
+ ```
127
+
128
+ ## References
129
+ [VISoR Image Schema](https://visor-tech.github.io/visor-data-schema)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ visor/__init__.py
5
+ visor/image/__init__.py
6
+ visor/image/core.py
7
+ visor/image/tests/test_core.py
8
+ visor/transform/__init__.py
9
+ visor_py.egg-info/PKG-INFO
10
+ visor_py.egg-info/SOURCES.txt
11
+ visor_py.egg-info/dependency_links.txt
12
+ visor_py.egg-info/requires.txt
13
+ visor_py.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ dask[complete]==2024.9.1
2
+ zarr==2.18.3
3
+ numcodecs==0.13.0
@@ -0,0 +1 @@
1
+ visor