Mercurial > repos > iuc > idr_download_by_ids
comparison idr_download_by_ids.py @ 13:f92941d1a85e draft default tip
planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/idr_download commit a9a143bae95eb6c553fd3d5955c2adb34352480f
author | iuc |
---|---|
date | Thu, 26 Sep 2024 12:32:17 +0000 |
parents | cbd605a24336 |
children |
comparison
equal
deleted
inserted
replaced
12:4b794652dcdc | 13:f92941d1a85e |
---|---|
2 import json | 2 import json |
3 import os | 3 import os |
4 import sys | 4 import sys |
5 import tarfile | 5 import tarfile |
6 from contextlib import ExitStack | 6 from contextlib import ExitStack |
7 from itertools import product | |
7 from tempfile import TemporaryDirectory | 8 from tempfile import TemporaryDirectory |
8 | 9 |
9 from libtiff import TIFF | 10 import numpy |
10 from omero.cli import cli_login | 11 from omero.cli import cli_login |
11 from omero.gateway import BlitzGateway # noqa | 12 from omero.constants.namespaces import NSBULKANNOTATIONS |
12 from omero.constants.namespaces import NSBULKANNOTATIONS # noqa | 13 from omero.gateway import _ImageWrapper, BlitzGateway |
13 | 14 from tifffile import imwrite |
14 | 15 |
15 def warn(message, image_identifier, warn_skip=False): | 16 |
17 def warn(message: str, image_identifier: str, warn_skip: bool = False) -> None: | |
18 """Print an error `message` to stderr and | |
19 - prefix with the `image_identifier` | |
20 - suffix with 'Skipping download!' if `warn_skip` is True | |
21 | |
22 Args: | |
23 message (string): Message to print to stderr | |
24 image_identifier (string): Image identifier | |
25 warn_skip (bool, optional): Whether 'skipping download' should be suffix to the message. Defaults to False. | |
26 """ | |
16 message = message.rstrip() | 27 message = message.rstrip() |
17 if warn_skip: | 28 if warn_skip: |
18 if message[-1] in ['.', '!', '?']: | 29 if message[-1] in [".", "!", "?"]: |
19 skip_msg = ' Skipping download!' | 30 skip_msg = " Skipping download!" |
20 else: | 31 else: |
21 skip_msg = '. Skipping download!' | 32 skip_msg = ". Skipping download!" |
22 else: | 33 else: |
23 skip_msg = '' | 34 skip_msg = "" |
24 print( | 35 print( |
25 'ImageSpecWarning for {0}: {1}{2}' | 36 "ImageSpecWarning for {0}: {1}{2}".format(image_identifier, message, skip_msg), |
26 .format( | 37 file=sys.stderr, |
27 image_identifier, | 38 ) |
28 message, | 39 |
29 skip_msg | 40 |
30 ), | 41 def find_channel_index(image: _ImageWrapper, channel_name: str) -> int: |
31 file=sys.stderr | 42 """Identify the channel index from the `image` and the `channel_name` |
32 ) | 43 |
33 | 44 Args: |
34 | 45 image (_ImageWrapper): image wrapper on which the channel should be identified |
35 def find_channel_index(image, channel_name): | 46 channel_name (string): name of the channel to look for |
47 | |
48 Returns: | |
49 int: Index of the channel or -1 if was not found. | |
50 """ | |
36 channel_name = channel_name.lower() | 51 channel_name = channel_name.lower() |
37 for n, channel in enumerate(image.getChannelLabels()): | 52 for n, channel in enumerate(image.getChannelLabels()): |
38 if channel_name == channel.lower(): | 53 if channel_name == channel.lower(): |
39 return n | 54 return n |
40 # Check map annotation for information (this is necessary for some images) | 55 # Check map annotation for information (this is necessary for some images) |
42 pairs = ann.getValue() | 57 pairs = ann.getValue() |
43 for p in pairs: | 58 for p in pairs: |
44 if p[0] == "Channels": | 59 if p[0] == "Channels": |
45 channels = p[1].replace(" ", "").split(";") | 60 channels = p[1].replace(" ", "").split(";") |
46 for n, c in enumerate(channels): | 61 for n, c in enumerate(channels): |
47 for value in c.split(':'): | 62 for value in c.split(":"): |
48 if channel_name == value.lower(): | 63 if channel_name == value.lower(): |
49 return n | 64 return n |
50 return -1 | 65 return -1 |
51 | 66 |
52 | 67 |
53 def get_clipping_region(image, x, y, w, h): | 68 def get_clipping_region( |
69 image: _ImageWrapper, x: int, y: int, w: int, h: int | |
70 ) -> list[int]: | |
71 """Check `x`, `y` and adjust `w`, `h` to image size to be able to crop the `image` with these coordinates | |
72 | |
73 Args: | |
74 image (_ImageWrapper): image wrapper on which region want to be cropped | |
75 x (int): left x coordinate | |
76 y (int): top y coordinate | |
77 w (int): width | |
78 h (int): height | |
79 | |
80 Raises: | |
81 ValueError: if the x or y coordinates are negative. | |
82 ValueError: if the x or y coordinates are larger than the width or height of the image. | |
83 | |
84 Returns: | |
85 list[int]: new [x, y, width, height] adjusted to the image | |
86 """ | |
54 # If the (x, y) coordinate falls outside the image boundaries, we | 87 # If the (x, y) coordinate falls outside the image boundaries, we |
55 # cannot just shift it because that would render the meaning of | 88 # cannot just shift it because that would render the meaning of |
56 # w and h undefined (should width and height be decreased or the whole | 89 # w and h undefined (should width and height be decreased or the whole |
57 # region be shifted to keep them fixed?). | 90 # region be shifted to keep them fixed?). |
58 # It may be better to abort in this situation. | 91 # It may be better to abort in this situation. |
59 if x < 0 or y < 0: | 92 if x < 0 or y < 0: |
60 raise ValueError( | 93 raise ValueError( |
61 'Too small upper left coordinate ({0}, {1}) for clipping region.' | 94 "Too small upper left coordinate ({0}, {1}) for clipping region.".format( |
62 .format(x, y) | 95 x, y |
96 ) | |
63 ) | 97 ) |
64 size_x = image.getSizeX() | 98 size_x = image.getSizeX() |
65 size_y = image.getSizeY() | 99 size_y = image.getSizeY() |
66 if x >= size_x or y >= size_y: | 100 if x >= size_x or y >= size_y: |
67 raise ValueError( | 101 raise ValueError( |
68 'Upper left coordinate ({0}, {1}) of clipping region lies ' | 102 "Upper left coordinate ({0}, {1}) of clipping region lies " |
69 'outside of image.' | 103 "outside of image.".format(x, y) |
70 .format(x, y) | |
71 ) | 104 ) |
72 # adjust width and height to the image dimensions | 105 # adjust width and height to the image dimensions |
73 if w <= 0 or x + w > size_x: | 106 if w <= 0 or x + w > size_x: |
74 w = size_x - x | 107 w = size_x - x |
75 if h <= 0 or y + h > size_y: | 108 if h <= 0 or y + h > size_y: |
76 h = size_y - y | 109 h = size_y - y |
77 return [x, y, w, h] | 110 return [x, y, w, h] |
78 | 111 |
79 | 112 |
80 def confine_plane(image, z): | 113 def confine_plane(image: _ImageWrapper, z: int) -> int: |
114 """Adjust/Confine `z` to be among the possible z for the `image` | |
115 | |
116 Args: | |
117 image (_ImageWrapper): image wrapper for which the z is adjusted | |
118 z (int): plane index that need to be confined | |
119 | |
120 Returns: | |
121 int: confined z | |
122 """ | |
81 if z < 0: | 123 if z < 0: |
82 z = 0 | 124 z = 0 |
83 else: | 125 else: |
84 max_z = image.getSizeZ() - 1 | 126 max_z = image.getSizeZ() - 1 |
85 if z > max_z: | 127 if z > max_z: |
86 z = max_z | 128 z = max_z |
87 return z | 129 return z |
88 | 130 |
89 | 131 |
90 def confine_frame(image, t): | 132 def confine_frame(image: _ImageWrapper, t: int) -> int: |
133 """Adjust/Confine `t` to be among the possible t for the `image` | |
134 | |
135 Args: | |
136 image (_ImageWrapper): image wrapper for which the t is adjusted | |
137 t (int): frame index that need to be confined | |
138 | |
139 Returns: | |
140 int: confined t | |
141 """ | |
91 if t < 0: | 142 if t < 0: |
92 t = 0 | 143 t = 0 |
93 else: | 144 else: |
94 max_t = image.getSizeT() - 1 | 145 max_t = image.getSizeT() - 1 |
95 if t > max_t: | 146 if t > max_t: |
96 t = max_t | 147 t = max_t |
97 return t | 148 return t |
98 | 149 |
99 | 150 |
100 def get_image_array(image, tile, z, c, t): | 151 def get_image_array( |
152 image: _ImageWrapper, tile: list[int], z: int, c: int, t: int | |
153 ) -> numpy.ndarray: | |
154 """Get a 2D numpy array from an `image` wrapper for a given `tile`, `z`, `c`, `t` | |
155 | |
156 Args: | |
157 image (_ImageWrapper): image wrapper from which values are taken | |
158 tile (list[int]): [x, y, width, height] where x,y is the top left coordinate of the region to crop | |
159 z (int): plane index | |
160 c (int): channel index | |
161 t (int): frame index | |
162 | |
163 Returns: | |
164 numpy.ndarray: image values of the selected area (2 dimensions) | |
165 """ | |
101 pixels = image.getPrimaryPixels() | 166 pixels = image.getPrimaryPixels() |
102 try: | 167 try: |
103 selection = pixels.getTile(theZ=z, theT=t, theC=c, tile=tile) | 168 selection = pixels.getTile(theZ=z, theT=t, theC=c, tile=tile) |
104 except Exception: | 169 except Exception: |
105 warning = '{0} (ID: {1})'.format(image.getName(), | 170 warning = "{0} (ID: {1})".format(image.getName(), image.getId()) |
106 image.getId()) | 171 warn("Could not download the requested region", warning) |
107 warn('Could not download the requested region', warning) | |
108 return | 172 return |
109 | 173 |
110 return selection | 174 return selection |
111 | 175 |
112 | 176 |
177 def get_full_image_array(image: _ImageWrapper) -> numpy.ndarray: | |
178 """Get a 5D numpy array with all values from an `image` wrapper | |
179 | |
180 Args: | |
181 image (_ImageWrapper): image wrapper from which values are taken | |
182 | |
183 Returns: | |
184 numpy.ndarray: image values in the TZCYX order (5 dimensions) | |
185 """ | |
186 # The goal is to get the image in TZCYX order | |
187 pixels = image.getPrimaryPixels() | |
188 # Get the final tzclist in the order that will make the numpy reshape work | |
189 tzclist = list( | |
190 product( | |
191 range(image.getSizeT()), range(image.getSizeZ()), range(image.getSizeC()) | |
192 ) | |
193 ) | |
194 # As getPlanes requires the indices in the zct order | |
195 # We keep the final order but switch indices | |
196 zctlist = [(z, c, t) for (t, z, c) in tzclist] | |
197 try: | |
198 all_planes = numpy.array(list(pixels.getPlanes(zctlist))) | |
199 all_planes_reshaped = all_planes.reshape( | |
200 image.getSizeT(), | |
201 image.getSizeZ(), | |
202 image.getSizeC(), | |
203 all_planes.shape[-2], | |
204 all_planes.shape[-1], | |
205 ) | |
206 except Exception as e: | |
207 warning = "{0} (ID: {1})".format(image.getName(), image.getId()) | |
208 warn(f"Could not download the full image \n {e.msg}", warning) | |
209 return | |
210 | |
211 return all_planes_reshaped | |
212 | |
213 | |
113 def download_image_data( | 214 def download_image_data( |
114 image_ids_or_dataset_id, dataset=False, | 215 image_ids_or_dataset_id: str, |
115 download_original=False, | 216 dataset: bool = False, |
116 channel=None, z_stack=0, frame=0, | 217 download_original: bool = False, |
117 coord=(0, 0), width=0, height=0, region_spec='rectangle', | 218 download_full: bool = False, |
118 skip_failed=False, download_tar=False, omero_host='idr.openmicroscopy.org', omero_secured=False, config_file=None | 219 channel: str = None, |
119 ): | 220 z_stack: int = 0, |
221 frame: int = 0, | |
222 coord: tuple[int, int] = (0, 0), | |
223 width: int = 0, | |
224 height: int = 0, | |
225 region_spec: str = "rectangle", | |
226 skip_failed: bool = False, | |
227 download_tar: bool = False, | |
228 omero_host: str = "idr.openmicroscopy.org", | |
229 omero_secured: bool = False, | |
230 config_file: str = None, | |
231 ) -> None: | |
232 """Download the image data of | |
233 either a list of image ids or all images from a dataset. | |
234 The image data can be: | |
235 - a 2D cropped region or | |
236 - a hyperstack written in a tiff file | |
237 - the original image uploaded in omero | |
238 Optionally, the final file can be in a tar | |
239 | |
240 Args: | |
241 image_ids_or_dataset_id (list[str]): Can be either a list with a single id (int) of a dataset or a list with images ids (int) or images ids prefixed by 'image-' | |
242 dataset (bool, optional): Whether the image_ids_or_dataset_id is a dataset id and all images from this dataset should be retrieved (true) or image_ids_or_dataset_id are individual image ids (false). Defaults to False. | |
243 download_original (bool, optional): Whether the original file uploded to omero should be downloaded (ignored if `download_full` is set to True). Defaults to False. | |
244 download_full (bool, optional): Whether the full image (hyperstack) on omero should be written to TIFF. Defaults to False. | |
245 channel (string, optional): Channel name (ignored if `download_full` or `download_original` is set to True). Defaults to None. | |
246 z_stack (int, optional): Z stack (plane) index (ignored if `download_full` or `download_original` is set to True). Defaults to 0. | |
247 frame (int, optional): T frame index (ignored if `download_full` or `download_original` is set to True). Defaults to 0. | |
248 coord (tuple[int, int], optional): Coordinates of the top left or center of the region to crop (ignored if `download_full` or `download_original` is set to True). Defaults to (0, 0). | |
249 width (int, optional): Width of the region to crop (ignored if `download_full` or `download_original` is set to True). Defaults to 0. | |
250 height (int, optional): Height of the region to crop (ignored if `download_full` or `download_original` is set to True). Defaults to 0. | |
251 region_spec (str, optional): How the region is specified ('rectangle' = coord is top left or 'center' = the region is center, ignored if `download_full` or `download_original` is set to True). Defaults to "rectangle". | |
252 skip_failed (bool, optional): Do not stop the downloads if one fails. Defaults to False. | |
253 download_tar (bool, optional): Put all downloaded images into a tar file. Defaults to False. | |
254 omero_host (str, optional): omero host url. Defaults to "idr.openmicroscopy.org". | |
255 omero_secured (bool, optional): Whether the omero connects with secure connection. Defaults to False. | |
256 config_file (string, optional): File path with config file with credentials to connect to OMERO. Defaults to None. | |
257 | |
258 Raises: | |
259 ValueError: If the region_spec is not 'rectangle' nor 'center' and a cropped region is wanted. | |
260 ValueError: If there is no dataset with this number in OMERO | |
261 ValueError: If there is no image with this number in OMERO | |
262 Exception: If the command to download the original image fails | |
263 ValueError: If the channel name could not be identified | |
264 """ | |
120 | 265 |
121 if config_file is None: # IDR connection | 266 if config_file is None: # IDR connection |
122 omero_username = 'public' | 267 omero_username = "public" |
123 omero_password = 'public' | 268 omero_password = "public" |
124 else: # other omero instance | 269 else: # other omero instance |
125 with open(config_file) as f: | 270 with open(config_file) as f: |
126 cfg = json.load(f) | 271 cfg = json.load(f) |
127 omero_username = cfg['username'] | 272 omero_username = cfg["username"] |
128 omero_password = cfg['password'] | 273 omero_password = cfg["password"] |
129 | 274 |
130 if omero_username == "" or omero_password == "": | 275 if omero_username == "" or omero_password == "": |
131 omero_username = 'public' | 276 omero_username = "public" |
132 omero_password = 'public' | 277 omero_password = "public" |
133 | 278 |
134 if not download_original and region_spec not in ['rectangle', 'center']: | 279 if ( |
280 not download_original | |
281 and not download_full | |
282 and region_spec not in ["rectangle", "center"] | |
283 ): | |
135 raise ValueError( | 284 raise ValueError( |
136 'Got unknown value "{0}" as region_spec argument' | 285 'Got unknown value "{0}" as region_spec argument'.format(region_spec) |
137 .format(region_spec) | |
138 ) | 286 ) |
139 with ExitStack() as exit_stack: | 287 with ExitStack() as exit_stack: |
140 conn = exit_stack.enter_context( | 288 conn = exit_stack.enter_context( |
141 BlitzGateway( | 289 BlitzGateway( |
142 omero_username, omero_password, | 290 omero_username, omero_password, host=omero_host, secure=omero_secured |
143 host=omero_host, | |
144 secure=omero_secured | |
145 ) | 291 ) |
146 ) | 292 ) |
147 # exit_stack.callback(conn.connect().close) | |
148 if download_tar: | 293 if download_tar: |
149 # create an archive file to write images to | 294 # create an archive file to write images to |
150 archive = exit_stack.enter_context( | 295 archive = exit_stack.enter_context(tarfile.open("images.tar", mode="w")) |
151 tarfile.open('images.tar', mode='w') | 296 tempdir = exit_stack.enter_context(TemporaryDirectory()) |
152 ) | |
153 tempdir = exit_stack.enter_context( | |
154 TemporaryDirectory() | |
155 ) | |
156 | 297 |
157 if dataset: | 298 if dataset: |
158 dataset_warning_id = 'Dataset-ID: {0}'.format(image_ids_or_dataset_id[0]) | 299 dataset_warning_id = "Dataset-ID: {0}".format(image_ids_or_dataset_id[0]) |
159 try: | 300 try: |
160 dataset_id = int(image_ids_or_dataset_id[0]) | 301 dataset_id = int(image_ids_or_dataset_id[0]) |
161 except ValueError: | 302 except ValueError: |
162 image_ids = None | 303 image_ids = None |
163 else: | 304 else: |
173 image_ids = [image.id for image in dataset.listChildren()] | 314 image_ids = [image.id for image in dataset.listChildren()] |
174 | 315 |
175 if image_ids is None: | 316 if image_ids is None: |
176 if skip_failed: | 317 if skip_failed: |
177 warn( | 318 warn( |
178 'Unable to find a dataset with this ID in the ' | 319 "Unable to find a dataset with this ID in the " "database.", |
179 'database.', | |
180 dataset_warning_id, | 320 dataset_warning_id, |
181 warn_skip=True | 321 warn_skip=True, |
182 ) | 322 ) |
183 else: | 323 else: |
184 raise ValueError( | 324 raise ValueError( |
185 '{0}: Unable to find a dataset with this ID in the ' | 325 "{0}: Unable to find a dataset with this ID in the " |
186 'database. Aborting!' | 326 "database. Aborting!".format(dataset_warning_id) |
187 .format(dataset_warning_id) | |
188 ) | 327 ) |
189 | 328 |
190 else: | 329 else: |
191 # basic argument sanity checks and adjustments | 330 # basic argument sanity checks and adjustments |
192 prefix = 'image-' | 331 prefix = "image-" |
193 # normalize image ids by stripping off prefix if it exists | 332 # normalize image ids by stripping off prefix if it exists |
194 image_ids = [ | 333 image_ids = [ |
195 iid[len(prefix):] if iid[:len(prefix)] == prefix else iid | 334 iid[len(prefix):] if iid[:len(prefix)] == prefix else iid |
196 for iid in image_ids_or_dataset_id | 335 for iid in image_ids_or_dataset_id |
197 ] | 336 ] |
198 for image_id in image_ids: | 337 for image_id in image_ids: |
199 image_warning_id = 'Image-ID: {0}'.format(image_id) | 338 image_warning_id = "Image-ID: {0}".format(image_id) |
200 try: | 339 try: |
201 image_id = int(image_id) | 340 image_id = int(image_id) |
202 except ValueError: | 341 except ValueError: |
203 image = None | 342 image = None |
204 else: | 343 else: |
213 raise | 352 raise |
214 | 353 |
215 if image is None: | 354 if image is None: |
216 if skip_failed: | 355 if skip_failed: |
217 warn( | 356 warn( |
218 'Unable to find an image with this ID in the ' | 357 "Unable to find an image with this ID in the database.", |
219 'database.', | |
220 image_warning_id, | 358 image_warning_id, |
221 warn_skip=True | 359 warn_skip=True, |
222 ) | 360 ) |
223 continue | 361 continue |
224 raise ValueError( | 362 raise ValueError( |
225 '{0}: Unable to find an image with this ID in the ' | 363 "{0}: Unable to find an image with this ID in the " |
226 'database. Aborting!' | 364 "database. Aborting!".format(image_warning_id) |
227 .format(image_warning_id) | |
228 ) | 365 ) |
229 if not download_original: | 366 try: |
367 # try to extract image name | |
368 # if anything goes wrong here skip the image | |
369 # or abort. | |
370 image_name = os.path.splitext(image.getName())[0] | |
371 image_warning_id = "{0} (ID: {1})".format(image_name, image_id) | |
372 except Exception as e: | |
373 # respect skip_failed on unexpected errors | |
374 if skip_failed: | |
375 warn(str(e), image_warning_id, warn_skip=True) | |
376 continue | |
377 else: | |
378 raise | |
379 if download_full: | |
380 fname = ( | |
381 "__".join([image_name.replace(" ", "_"), str(image_id), "full"]) | |
382 + ".tiff" | |
383 ) | |
384 # download and save the region as TIFF | |
385 try: | |
386 im_array = get_full_image_array(image) | |
387 | |
388 if download_tar: | |
389 fname = os.path.join(tempdir, fname) | |
390 | |
391 imwrite(fname, im_array, imagej=True) | |
392 # move image into tarball | |
393 if download_tar: | |
394 archive.add(fname, os.path.basename(fname)) | |
395 os.remove(fname) | |
396 except Exception as e: | |
397 if skip_failed: | |
398 # respect skip_failed on unexpected errors | |
399 warn(str(e), image_warning_id, warn_skip=True) | |
400 continue | |
401 else: | |
402 raise | |
403 elif download_original: | |
230 try: | 404 try: |
231 # try to extract image properties | 405 # try to extract image properties |
232 # if anything goes wrong here skip the image | 406 # if anything goes wrong here skip the image |
233 # or abort. | 407 # or abort. |
234 image_name = os.path.splitext(image.getName())[0] | 408 original_image_name = image.getFileset().listFiles()[0].getName() |
235 image_warning_id = '{0} (ID: {1})'.format( | 409 fname = ( |
236 image_name, image_id | 410 image_name |
237 ) | 411 + "__" |
238 | 412 + str(image_id) |
239 if region_spec == 'rectangle': | 413 + os.path.splitext(original_image_name)[1] |
414 ) | |
415 fname = fname.replace(" ", "_") | |
416 fname = fname.replace("/", "_") | |
417 download_directory = "./" | |
418 if download_tar: | |
419 download_directory = tempdir | |
420 with cli_login( | |
421 "-u", omero_username, "-s", omero_host, "-w", omero_password | |
422 ) as cli: | |
423 cli.invoke( | |
424 ["download", f"Image:{image_id}", download_directory] | |
425 ) | |
426 if cli.rv != 0: | |
427 raise Exception("Download failed.") | |
428 # This will download to download_directory/original_image_name | |
429 os.rename( | |
430 os.path.join(download_directory, original_image_name), | |
431 os.path.join(download_directory, fname), | |
432 ) | |
433 # move image into tarball | |
434 if download_tar: | |
435 archive.add( | |
436 os.path.join(download_directory, fname), | |
437 os.path.basename(fname), | |
438 ) | |
439 os.remove(os.path.join(download_directory, fname)) | |
440 except Exception as e: | |
441 # respect skip_failed on unexpected errors | |
442 if skip_failed: | |
443 warn(str(e), image_warning_id, warn_skip=True) | |
444 continue | |
445 else: | |
446 raise | |
447 else: | |
448 try: | |
449 # try to extract image properties | |
450 # if anything goes wrong here skip the image | |
451 # or abort. | |
452 if region_spec == "rectangle": | |
240 tile = get_clipping_region(image, *coord, width, height) | 453 tile = get_clipping_region(image, *coord, width, height) |
241 elif region_spec == 'center': | 454 elif region_spec == "center": |
242 tile = get_clipping_region( | 455 tile = get_clipping_region( |
243 image, | 456 image, *_center_to_ul(*coord, width, height) |
244 *_center_to_ul(*coord, width, height) | |
245 ) | 457 ) |
246 | 458 |
247 ori_z, z_stack = z_stack, confine_plane(image, z_stack) | 459 ori_z, z_stack = z_stack, confine_plane(image, z_stack) |
248 ori_frame, frame = frame, confine_frame(image, frame) | 460 ori_frame, frame = frame, confine_frame(image, frame) |
249 num_channels = image.getSizeC() | 461 num_channels = image.getSizeC() |
262 # region sanity checks and warnings | 474 # region sanity checks and warnings |
263 if tile[2] < width or tile[3] < height: | 475 if tile[2] < width or tile[3] < height: |
264 # The downloaded image region will have smaller dimensions | 476 # The downloaded image region will have smaller dimensions |
265 # than the specified width x height. | 477 # than the specified width x height. |
266 warn( | 478 warn( |
267 'Downloaded image dimensions ({0} x {1}) will be smaller ' | 479 "Downloaded image dimensions ({0} x {1}) will be smaller " |
268 'than the specified width and height ({2} x {3}).' | 480 "than the specified width and height ({2} x {3}).".format( |
269 .format(tile[2], tile[3], width, height), | 481 tile[2], tile[3], width, height |
270 image_warning_id | 482 ), |
483 image_warning_id, | |
271 ) | 484 ) |
272 | 485 |
273 # z-stack sanity checks and warnings | 486 # z-stack sanity checks and warnings |
274 if z_stack != ori_z: | 487 if z_stack != ori_z: |
275 warn( | 488 warn( |
276 'Specified image plane ({0}) is out of bounds. Using {1} ' | 489 "Specified image plane ({0}) is out of bounds. Using {1} " |
277 'instead.' | 490 "instead.".format(ori_z, z_stack), |
278 .format(ori_z, z_stack), | 491 image_warning_id, |
279 image_warning_id | |
280 ) | 492 ) |
281 | 493 |
282 # frame sanity checks and warnings | 494 # frame sanity checks and warnings |
283 if frame != ori_frame: | 495 if frame != ori_frame: |
284 warn( | 496 warn( |
285 'Specified image frame ({0}) is out of bounds. Using ' | 497 "Specified image frame ({0}) is out of bounds. Using " |
286 'frame {1} instead.' | 498 "frame {1} instead.".format(ori_frame, frame), |
287 .format(ori_frame, frame), | 499 image_warning_id, |
288 image_warning_id | |
289 ) | 500 ) |
290 | 501 |
291 # channel index sanity checks and warnings | 502 # channel index sanity checks and warnings |
292 if channel is None: | 503 if channel is None: |
293 if num_channels > 1: | 504 if num_channels > 1: |
294 warn( | 505 warn( |
295 'No specific channel selected for multi-channel ' | 506 "No specific channel selected for multi-channel " |
296 'image. Using first of {0} channels.' | 507 "image. Using first of {0} channels.".format(num_channels), |
297 .format(num_channels), | 508 image_warning_id, |
298 image_warning_id | |
299 ) | 509 ) |
300 else: | 510 else: |
301 if channel_index == -1 or channel_index >= num_channels: | 511 if channel_index == -1 or channel_index >= num_channels: |
302 if skip_failed: | 512 if skip_failed: |
303 warn( | 513 warn( |
304 str(channel) | 514 str(channel) |
305 + ' is not a known channel name for this image.', | 515 + " is not a known channel name for this image.", |
306 image_warning_id, | 516 image_warning_id, |
307 warn_skip=True | 517 warn_skip=True, |
308 ) | 518 ) |
309 continue | 519 continue |
310 else: | 520 else: |
311 raise ValueError( | 521 raise ValueError( |
312 '"{0}" is not a known channel name for image {1}. ' | 522 '"{0}" is not a known channel name for image {1}. ' |
313 'Aborting!' | 523 "Aborting!".format(channel, image_warning_id) |
314 .format(channel, image_warning_id) | |
315 ) | 524 ) |
316 | 525 |
526 fname = "__".join([image_name, str(image_id)] + [str(x) for x in tile]) | |
527 fname += ".tiff" | |
528 fname = fname.replace(" ", "_") | |
317 # download and save the region as TIFF | 529 # download and save the region as TIFF |
318 fname = '__'.join( | |
319 [image_name, str(image_id)] + [str(x) for x in tile] | |
320 ) | |
321 try: | 530 try: |
322 if fname[-5:] != '.tiff': | 531 im_array = get_image_array( |
323 fname += '.tiff' | 532 image, tile, z_stack, channel_index, frame |
324 | 533 ) |
325 fname = fname.replace(' ', '_') | |
326 | |
327 im_array = get_image_array(image, tile, z_stack, channel_index, frame) | |
328 | 534 |
329 if download_tar: | 535 if download_tar: |
330 fname = os.path.join(tempdir, fname) | 536 fname = os.path.join(tempdir, fname) |
331 try: | 537 imwrite(fname, im_array) |
332 tiff = TIFF.open(fname, mode='w') | |
333 tiff.write_image(im_array) | |
334 finally: | |
335 tiff.close() | |
336 # move image into tarball | 538 # move image into tarball |
337 if download_tar: | 539 if download_tar: |
338 archive.add(fname, os.path.basename(fname)) | 540 archive.add(fname, os.path.basename(fname)) |
339 os.remove(fname) | 541 os.remove(fname) |
340 except Exception as e: | 542 except Exception as e: |
342 # respect skip_failed on unexpected errors | 544 # respect skip_failed on unexpected errors |
343 warn(str(e), image_warning_id, warn_skip=True) | 545 warn(str(e), image_warning_id, warn_skip=True) |
344 continue | 546 continue |
345 else: | 547 else: |
346 raise | 548 raise |
347 else: | 549 |
348 try: | 550 |
349 # try to extract image properties | 551 def _center_to_ul(center_x: int, center_y: int, width: int, height: int) -> list[int]: |
350 # if anything goes wrong here skip the image | 552 """Convert the center coordinates (`center_x`, `center_y`), `width`, `height` to upper left coordinates, width, height |
351 # or abort. | 553 |
352 image_name = os.path.splitext(image.getName())[0] | 554 Args: |
353 image_warning_id = '{0} (ID: {1})'.format( | 555 center_x (int): x coordinate of center |
354 image_name, image_id | 556 center_y (int): y coordinate of center |
355 ) | 557 width (int): width |
356 original_image_name = image.getFileset().listFiles()[0].getName() | 558 height (int): height |
357 fname = image_name + "__" + str(image_id) + os.path.splitext(original_image_name)[1] | 559 |
358 fname = fname.replace(' ', '_') | 560 Returns: |
359 fname = fname.replace('/', '_') | 561 list[int]: [x, y, width, height] where x,y are the upper left coordinates |
360 download_directory = "./" | 562 """ |
361 if download_tar: | |
362 download_directory = tempdir | |
363 with cli_login("-u", omero_username, "-s", omero_host, "-w", omero_password) as cli: | |
364 cli.invoke(["download", f"Image:{image_id}", download_directory]) | |
365 if cli.rv != 0: | |
366 raise Exception("Download failed.") | |
367 # This will download to download_directory/original_image_name | |
368 os.rename(os.path.join(download_directory, original_image_name), | |
369 os.path.join(download_directory, fname)) | |
370 # move image into tarball | |
371 if download_tar: | |
372 archive.add(os.path.join(download_directory, fname), | |
373 os.path.basename(fname)) | |
374 os.remove(os.path.join(download_directory, fname)) | |
375 except Exception as e: | |
376 # respect skip_failed on unexpected errors | |
377 if skip_failed: | |
378 warn(str(e), image_warning_id, warn_skip=True) | |
379 continue | |
380 else: | |
381 raise | |
382 | |
383 | |
384 def _center_to_ul(center_x, center_y, width, height): | |
385 if width > 0: | 563 if width > 0: |
386 ext_x = (width - 1) // 2 | 564 ext_x = (width - 1) // 2 |
387 ul_x = max([center_x - ext_x, 0]) | 565 ul_x = max([center_x - ext_x, 0]) |
388 width = center_x + ext_x + 1 - ul_x | 566 width = center_x + ext_x + 1 - ul_x |
389 else: | 567 else: |
398 | 576 |
399 | 577 |
400 if __name__ == "__main__": | 578 if __name__ == "__main__": |
401 p = argparse.ArgumentParser() | 579 p = argparse.ArgumentParser() |
402 p.add_argument( | 580 p.add_argument( |
403 'image_ids_or_dataset_id', nargs='*', default=[], | 581 "image_ids_or_dataset_id", |
404 help='one or more IDR image ids or a single dataset id' | 582 nargs="*", |
405 'for which to retrieve data (default: ' | 583 default=[], |
406 'read ids from stdin).' | 584 help="one or more IDR image ids or a single dataset id" |
407 ) | 585 "for which to retrieve data (default: " |
408 p.add_argument( | 586 "read ids from stdin).", |
409 '--download-original', dest='download_original', action='store_true', | |
410 help="download the original file uploaded to omero" | |
411 ) | |
412 p.add_argument( | |
413 '-c', '--channel', | |
414 help='name of the channel to retrieve data for ' | |
415 '(note: the first channel of each image will be downloaded if ' | |
416 'left unspecified)' | |
417 ) | 587 ) |
418 region = p.add_mutually_exclusive_group() | 588 region = p.add_mutually_exclusive_group() |
419 region.add_argument( | 589 region.add_argument( |
420 '--rectangle', nargs=4, type=int, default=argparse.SUPPRESS, | 590 "--rectangle", |
421 help='specify a clipping region for the image as x y width height, ' | 591 nargs=4, |
422 'where x and y give the upper left coordinate of the rectangle ' | 592 type=int, |
423 'to clip to. Set width and height to 0 to extend the rectangle ' | 593 default=argparse.SUPPRESS, |
424 'to the actual size of the image.' | 594 help="specify a clipping region for the image as x y width height, " |
595 "where x and y give the upper left coordinate of the rectangle " | |
596 "to clip to. Set width and height to 0 to extend the rectangle " | |
597 "to the actual size of the image.", | |
425 ) | 598 ) |
426 region.add_argument( | 599 region.add_argument( |
427 '--center', nargs=4, type=int, default=argparse.SUPPRESS, | 600 "--center", |
428 help='specify a clipping region for the image as x y width height, ' | 601 nargs=4, |
429 'where x and y define the center of a width x height rectangle. ' | 602 type=int, |
430 'Set either width or height to 0 to extend the region to the ' | 603 default=argparse.SUPPRESS, |
431 'actual size of the image along the x- or y-axis.\n' | 604 help="specify a clipping region for the image as x y width height, " |
432 'Note: Even values for width and height will be rounded down to ' | 605 "where x and y define the center of a width x height rectangle. " |
433 'the nearest odd number.' | 606 "Set either width or height to 0 to extend the region to the " |
607 "actual size of the image along the x- or y-axis.\n" | |
608 "Note: Even values for width and height will be rounded down to " | |
609 "the nearest odd number.", | |
610 ) | |
611 region.add_argument( | |
612 "--download-original", | |
613 dest="download_original", | |
614 action="store_true", | |
615 help="download the original file uploaded to omero", | |
616 ) | |
617 region.add_argument( | |
618 "--download-full", | |
619 dest="download_full", | |
620 action="store_true", | |
621 help="download the full image on omero", | |
434 ) | 622 ) |
435 p.add_argument( | 623 p.add_argument( |
436 '-f', '--frame', type=int, default=0 | 624 "-c", |
625 "--channel", | |
626 help="name of the channel to retrieve data for " | |
627 "(note: the first channel of each image will be downloaded if " | |
628 "left unspecified), ignored with `--download-original` and " | |
629 "`--download-full`", | |
437 ) | 630 ) |
438 p.add_argument( | 631 p.add_argument( |
439 '-z', '--z-stack', type=int, default=0 | 632 "-f", |
633 "--frame", | |
634 type=int, | |
635 default=0, | |
636 help="index of the frame to retrive data for (first frame is 0)," | |
637 " ignored with `--download-original` and `--download-full`", | |
440 ) | 638 ) |
441 p.add_argument( | 639 p.add_argument( |
442 '--skip-failed', action='store_true' | 640 "-z", |
443 ) | 641 "--z-stack", |
444 p.add_argument( | 642 type=int, |
445 '--download-tar', action='store_true' | 643 default=0, |
446 ) | 644 help="index of the slice to retrive data for (first slice is 0)," |
447 p.add_argument( | 645 " ignored with `--download-original` and `--download-full`", |
448 '-oh', '--omero-host', type=str, default="idr.openmicroscopy.org" | 646 ) |
449 ) | 647 p.add_argument("--skip-failed", action="store_true") |
450 p.add_argument( | 648 p.add_argument("--download-tar", action="store_true") |
451 '--omero-secured', action='store_true', default=True | 649 p.add_argument("-oh", "--omero-host", type=str, default="idr.openmicroscopy.org") |
452 ) | 650 p.add_argument("--omero-secured", action="store_true", default=True) |
453 p.add_argument( | 651 p.add_argument("-cf", "--config-file", dest="config_file", default=None) |
454 '-cf', '--config-file', dest='config_file', default=None | 652 p.add_argument("--dataset", action="store_true") |
455 ) | |
456 p.add_argument( | |
457 '--dataset', action='store_true' | |
458 ) | |
459 args = p.parse_args() | 653 args = p.parse_args() |
460 if not args.image_ids_or_dataset_id: | 654 if not args.image_ids_or_dataset_id: |
461 args.image_ids_or_dataset_id = sys.stdin.read().split() | 655 args.image_ids_or_dataset_id = sys.stdin.read().split() |
462 if args.dataset and len(args.image_ids_or_dataset_id) > 1: | 656 if args.dataset and len(args.image_ids_or_dataset_id) > 1: |
463 warn("Multiple dataset ids provided. Only the first one will be used.") | 657 warn("Multiple dataset ids provided. Only the first one will be used.") |
464 if 'center' in args: | 658 if "center" in args: |
465 args.coord, args.width, args.height = ( | 659 args.coord, args.width, args.height = ( |
466 args.center[:2], args.center[2], args.center[3] | 660 args.center[:2], |
467 ) | 661 args.center[2], |
468 args.region_spec = 'center' | 662 args.center[3], |
663 ) | |
664 args.region_spec = "center" | |
469 del args.center | 665 del args.center |
470 elif 'rectangle' in args: | 666 elif "rectangle" in args: |
471 args.coord, args.width, args.height = ( | 667 args.coord, args.width, args.height = ( |
472 args.rectangle[:2], args.rectangle[2], args.rectangle[3] | 668 args.rectangle[:2], |
473 ) | 669 args.rectangle[2], |
474 args.region_spec = 'rectangle' | 670 args.rectangle[3], |
671 ) | |
672 args.region_spec = "rectangle" | |
475 del args.rectangle | 673 del args.rectangle |
476 download_image_data(**vars(args)) | 674 download_image_data(**vars(args)) |