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))