@@ -252,6 +252,10 @@ class BioFormatsBackend(SlideBackend):
252
252
filename (str): path to image file on disk
253
253
dtype (numpy.dtype): data type of image. If ``None``, will use BioFormats to infer the data type from the
254
254
image's OME metadata. Defaults to ``None``.
255
+
256
+ Note:
257
+ While the Bio-Formats convention uses XYZCT channel order, we use YXZCT for compatibility with the rest of
258
+ PathML which is based on (i, j) coordinate system.
255
259
"""
256
260
257
261
def __init__ (self , filename , dtype = None ):
@@ -281,7 +285,8 @@ def __init__(self, filename, dtype=None):
281
285
reader .getSizeC (),
282
286
reader .getSizeT (),
283
287
)
284
- sizeSeries .append ((sizex , sizey , sizez , sizec , sizet ))
288
+ # use yxzct for compatibility with the rest of PathML which uses i,j coords (not x, y)
289
+ sizeSeries .append ((sizey , sizex , sizez , sizec , sizet ))
285
290
s = [s [0 ] * s [1 ] for s in sizeSeries ]
286
291
287
292
self .level_count = seriesCount # count of levels
@@ -332,7 +337,7 @@ def get_image_shape(self, level=None):
332
337
Defaults to ``None``.
333
338
334
339
Returns:
335
- Tuple[int, int]: Shape of image (H, W)
340
+ Tuple[int, int]: Shape of image (i, j) at target level
336
341
"""
337
342
if level is None :
338
343
return self .shape [:2 ]
@@ -343,25 +348,29 @@ def get_image_shape(self, level=None):
343
348
), f"input level { level } invalid for slide with { self .level_count } levels total"
344
349
return self .shape_list [level ][:2 ]
345
350
346
- def extract_region (self , location , size , level = 0 , series_as_channels = False ):
351
+ def extract_region (
352
+ self , location , size , level = 0 , series_as_channels = False , normalize = True
353
+ ):
347
354
"""
348
355
Extract a region of the image. All bioformats images have 5 dimensions representing
349
- (x, y , z, channel, time). Even if an image does not have multiple z-series or time-series,
350
- those dimensions will still be kept. For example, a standard RGB image will be of shape (x, y , 1, 3, 1).
356
+ (i, j , z, channel, time). Even if an image does not have multiple z-series or time-series,
357
+ those dimensions will still be kept. For example, a standard RGB image will be of shape (i, j , 1, 3, 1).
351
358
If a tuple with len < 5 is passed, missing dimensions will be
352
359
retrieved in full.
353
360
354
361
Args:
355
- location (Tuple[int, int]): (X,Y ) location of corner of extracted region closest to the origin.
356
- size (Tuple[int, int, ...]): (X,Y ) size of each region. If an integer is passed, will convert to a
357
- tuple of (H, W ) and extract a square region. If a tuple with len < 5 is passed, missing
362
+ location (Tuple[int, int]): (i, j ) location of corner of extracted region closest to the origin.
363
+ size (Tuple[int, int, ...]): (i, j ) size of each region. If an integer is passed, will convert to a
364
+ tuple of (i, j ) and extract a square region. If a tuple with len < 5 is passed, missing
358
365
dimensions will be retrieved in full.
359
366
level (int): level from which to extract chunks. Level 0 is highest resolution. Defaults to 0.
360
367
series_as_channels (bool): Whether to treat image series as channels. If ``True``, multi-level images
361
368
are not supported. Defaults to ``False``.
369
+ normalize (bool, optional): Whether to normalize the image to int8 before returning. Defaults to True.
370
+ If False, image will be returned as-is immediately after reading, typically in float64.
362
371
363
372
Returns:
364
- np.ndarray: image at the specified region. 5-D array of (x, y , z, c, t)
373
+ np.ndarray: image at the specified region. 5-D array of (i, j , z, c, t)
365
374
"""
366
375
if level is None :
367
376
level = 0
@@ -412,7 +421,7 @@ def extract_region(self, location, size, level=0, series_as_channels=False):
412
421
t = 0 ,
413
422
series = level ,
414
423
rescale = False ,
415
- XYWH = (location [0 ], location [1 ], 2 , 2 ),
424
+ XYWH = (location [1 ], location [0 ], 2 , 2 ),
416
425
)
417
426
418
427
# need this part because some facilities output images where the channels are incorrectly stored as series
@@ -426,11 +435,10 @@ def extract_region(self, location, size, level=0, series_as_channels=False):
426
435
t = t ,
427
436
series = c ,
428
437
rescale = False ,
429
- XYWH = (location [0 ], location [1 ], size [0 ], size [1 ]),
438
+ XYWH = (location [1 ], location [0 ], size [1 ], size [0 ]),
430
439
)
431
440
slicearray = np .asarray (slicearray )
432
441
# some file formats read x, y out of order, transpose
433
- slicearray = np .transpose (slicearray )
434
442
array [:, :, z , c , t ] = slicearray
435
443
436
444
# in this case, channels are correctly stored as channels, and we can support multi-level images as series
@@ -442,26 +450,23 @@ def extract_region(self, location, size, level=0, series_as_channels=False):
442
450
t = t ,
443
451
series = level ,
444
452
rescale = False ,
445
- XYWH = (location [0 ], location [1 ], size [0 ], size [1 ]),
453
+ XYWH = (location [1 ], location [0 ], size [1 ], size [0 ]),
446
454
)
447
455
slicearray = np .asarray (slicearray )
448
- # some file formats read x, y out of order, transpose
449
- if slicearray .shape [:2 ] != array .shape [:2 ]:
450
- slicearray = np .transpose (slicearray )
451
- # in 2d undoes transpose
452
- if len (sample .shape ) == 3 :
453
- slicearray = np .moveaxis (slicearray , 0 , - 1 )
454
456
if len (sample .shape ) == 3 :
455
457
array [:, :, z , :, t ] = slicearray
456
458
else :
457
459
array [:, :, z , level , t ] = slicearray
458
460
459
- # scale array before converting: https://github.com/Dana-Farber-AIOS/pathml/issues/271
460
- # first scale to [0-1]
461
- array_scaled = array / (2 ** (8 * self .pixel_dtype .itemsize ))
462
- # then scale to [0-255] and convert to 8 bit
463
- array_scaled = array_scaled * 2 ** 8
464
- return array_scaled .astype (np .uint8 )
461
+ if not normalize :
462
+ return array
463
+ else :
464
+ # scale array before converting: https://github.com/Dana-Farber-AIOS/pathml/issues/271
465
+ # first scale to [0-1]
466
+ array_scaled = array / (2 ** (8 * self .pixel_dtype .itemsize ))
467
+ # then scale to [0-255] and convert to 8 bit
468
+ array_scaled = array_scaled * 2 ** 8
469
+ return array_scaled .astype (np .uint8 )
465
470
466
471
def get_thumbnail (self , size = None ):
467
472
"""
@@ -515,6 +520,7 @@ def generate_tiles(self, shape=3000, stride=None, pad=False, level=0, **kwargs):
515
520
pad (bool): How to handle tiles on the edges. If ``True``, these edge tiles will be zero-padded
516
521
and yielded with the other chunks. If ``False``, incomplete edge chunks will be ignored.
517
522
Defaults to ``False``.
523
+ **kwargs: Other arguments passed through to ``extract_region()`` method.
518
524
519
525
Yields:
520
526
pathml.core.tile.Tile: Extracted Tile object
0 commit comments