large-image-source-openslide 1.27.1.dev65__py3-none-any.whl → 1.33.4.dev39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of large-image-source-openslide might be problematic. Click here for more details.

@@ -20,6 +20,7 @@ import os
20
20
  from importlib.metadata import PackageNotFoundError
21
21
  from importlib.metadata import version as _importlib_version
22
22
 
23
+ import numpy as np
23
24
  import openslide
24
25
  import PIL
25
26
  import tifftools
@@ -47,10 +48,13 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
47
48
  extensions = {
48
49
  None: SourcePriority.MEDIUM,
49
50
  'bif': SourcePriority.LOW, # Ventana
51
+ 'czi': SourcePriority.PREFERRED,
52
+ 'dcm': SourcePriority.MEDIUM, # DICOM
53
+ 'ini': SourcePriority.LOW, # Part of mrxs
50
54
  'mrxs': SourcePriority.PREFERRED, # MIRAX
51
55
  'ndpi': SourcePriority.PREFERRED, # Hamamatsu
52
56
  'scn': SourcePriority.LOW, # Leica
53
- 'svs': SourcePriority.PREFERRED,
57
+ 'svs': SourcePriority.HIGH,
54
58
  'svslide': SourcePriority.PREFERRED,
55
59
  'tif': SourcePriority.MEDIUM,
56
60
  'tiff': SourcePriority.MEDIUM,
@@ -59,6 +63,8 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
59
63
  }
60
64
  mimeTypes = {
61
65
  None: SourcePriority.FALLBACK,
66
+ 'image/czi': SourcePriority.PREFERRED,
67
+ 'application/dicom': SourcePriority.MEDIUM,
62
68
  'image/mirax': SourcePriority.PREFERRED, # MIRAX
63
69
  'image/tiff': SourcePriority.MEDIUM,
64
70
  'image/x-tiff': SourcePriority.MEDIUM,
@@ -92,6 +98,13 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
92
98
  tifftools.Tag.ICCProfile.value]['data']]
93
99
  except Exception:
94
100
  pass
101
+ if hasattr(self, '_tiffinfo'):
102
+ for ifd in self._tiffinfo['ifds']:
103
+ if (tifftools.Tag.NDPI_FOCAL_PLANE.value in ifd['tags'] and
104
+ ifd['tags'][tifftools.Tag.NDPI_FOCAL_PLANE.value]['data'][0] != 0):
105
+ msg = ('File will not be opened via OpenSlide; '
106
+ 'non-zero focal planes would be missed.')
107
+ raise TileSourceError(msg)
95
108
 
96
109
  svsAvailableLevels = self._getAvailableLevels(self._largeImagePath)
97
110
  if not len(svsAvailableLevels):
@@ -126,19 +139,22 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
126
139
  # load an appropriate area and scale it to the tile size later.
127
140
  maxSize = 16384 # This should probably be based on available memory
128
141
  for level in range(self.levels):
129
- levelW = max(1, self.sizeX / 2 ** (self.levels - 1 - level))
130
- levelH = max(1, self.sizeY / 2 ** (self.levels - 1 - level))
142
+ levelpow = 2 ** (self.levels - 1 - level)
143
+ levelW = max(1, self.sizeX / levelpow)
144
+ levelH = max(1, self.sizeY / levelpow)
131
145
  # bestlevel and scale will be the picked svs level and the scale
132
146
  # between that level and what we really wanted. We expect scale to
133
147
  # always be a positive integer power of two.
134
148
  bestlevel = svsAvailableLevels[0]['level']
135
149
  scale = 1
150
+ svsscale = 0
136
151
  for svslevel in range(len(svsAvailableLevels)):
137
152
  if (svsAvailableLevels[svslevel]['width'] < levelW - 1 or
138
153
  svsAvailableLevels[svslevel]['height'] < levelH - 1):
139
154
  break
140
155
  bestlevel = svsAvailableLevels[svslevel]['level']
141
156
  scale = int(round(svsAvailableLevels[svslevel]['width'] / levelW))
157
+ svsscale = svsAvailableLevels[svslevel].get('downsample', 0)
142
158
  # If there are no tiles at a particular level, we have to read a
143
159
  # larger area of a higher resolution level. If such an area would
144
160
  # be excessively large, we could have memory issues, so raise an
@@ -152,7 +168,38 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
152
168
  self._svslevels.append({
153
169
  'svslevel': bestlevel,
154
170
  'scale': scale,
171
+ 'svsscale': ((svsscale / levelpow) if svsscale else 1) * scale,
155
172
  })
173
+ self._bounds = None
174
+ try:
175
+ bounds = {
176
+ 'x': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_X]),
177
+ 'y': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_Y]),
178
+ 'width': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_WIDTH]),
179
+ 'height': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_HEIGHT]),
180
+ }
181
+ if (
182
+ bounds['x'] >= 0 and bounds['width'] > 0 and
183
+ bounds['x'] + bounds['width'] <= self.sizeX and
184
+ bounds['y'] >= 0 and bounds['height'] > 0 and
185
+ bounds['y'] + bounds['height'] <= self.sizeY and
186
+ (bounds['width'] < self.sizeX or bounds['height'] < self.sizeY)
187
+ ):
188
+ self._bounds = bounds
189
+ self.sizeX, self.sizeY = bounds['width'], bounds['height']
190
+ prevlevels = self.levels
191
+ self.levels = int(math.ceil(max(
192
+ math.log(float(self.sizeX) / self.tileWidth),
193
+ math.log(float(self.sizeY) / self.tileHeight)) / math.log(2))) + 1
194
+ self._svslevels = self._svslevels[prevlevels - self.levels:]
195
+ except Exception:
196
+ pass
197
+ try:
198
+ self._background = tuple(int(
199
+ self._openslide.properties['openslide.background-color']
200
+ [i * 2:i * 2 + 2], 16) for i in range(3))
201
+ except Exception:
202
+ self._background = None
156
203
  self._populatedLevels = len({l['svslevel'] for l in self._svslevels})
157
204
 
158
205
  def _getTileSize(self):
@@ -202,6 +249,7 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
202
249
  """
203
250
  levels = []
204
251
  svsLevelDimensions = self._openslide.level_dimensions
252
+
205
253
  for svslevel in range(len(svsLevelDimensions)):
206
254
  try:
207
255
  self._openslide.read_region((0, 0), svslevel, (1, 1))
@@ -210,6 +258,10 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
210
258
  'width': svsLevelDimensions[svslevel][0],
211
259
  'height': svsLevelDimensions[svslevel][1],
212
260
  }
261
+ try:
262
+ level['downsample'] = self._openslide.level_downsamples[svslevel]
263
+ except Exception:
264
+ pass
213
265
  if level['width'] > 0 and level['height'] > 0:
214
266
  # add to the list so that we can sort by resolution and
215
267
  # then by earlier entries
@@ -291,25 +343,66 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
291
343
  scale = 2 ** (self.levels - 1 - z)
292
344
  offsetx = x * self.tileWidth * scale
293
345
  offsety = y * self.tileHeight * scale
346
+ if self._bounds is not None:
347
+ offsetx += self._bounds['x'] // svslevel['scale']
348
+ offsety += self._bounds['y'] // svslevel['scale']
294
349
  # We ask to read an area that will cover the tile at the z level. The
295
350
  # scale we computed in the __init__ process for this svs level tells
296
351
  # how much larger a region we need to read.
297
352
  if svslevel['scale'] > 2 ** self._maxSkippedLevels:
298
- tile = self._getTileFromEmptyLevel(x, y, z, **kwargs)
353
+ tile, format = self._getTileFromEmptyLevel(x, y, z, **kwargs)
299
354
  else:
300
- try:
301
- tile = self._openslide.read_region(
302
- (offsetx, offsety), svslevel['svslevel'],
303
- (self.tileWidth * svslevel['scale'],
304
- self.tileHeight * svslevel['scale']))
305
- except openslide.lowlevel.OpenSlideError as exc:
306
- raise TileSourceError(
307
- 'Failed to get OpenSlide region (%r).' % exc)
355
+ retries = 3
356
+ svsTileWidth = self.tileWidth * svslevel['scale']
357
+ svsTileHeight = self.tileHeight * svslevel['scale']
358
+ # Peculiarly, openslide has a "downsample" factor which isn't the
359
+ # power of 2 one would expect. This is computed based on the
360
+ # actual dimensions of levels, but since higher-resolution levels
361
+ # are not fully populated at the right and bottom, this ends up
362
+ # with not the actual downsampling, but some slightly higher number
363
+ # (e.g., 16.0018 rather than 16). Internally, when asking for a
364
+ # region for anything other than the maximum resolution lever, the
365
+ # openslide library is passed coordinates in what _seems_ to be
366
+ # base image coordinates, but is actually inflated by the ratio of
367
+ # their downsample value and the actual downsample value (e.g.,
368
+ # 16.0018 / 16). We multiple our values by this ratio so when
369
+ # openslide misapplies its downsampling we get the region we
370
+ # actually want
371
+ if svslevel['svsscale'] != 1:
372
+ offsetx = int(round(offsetx * svslevel['svsscale']))
373
+ offsety = int(round(offsety * svslevel['svsscale']))
374
+ svsTileWidth = int(round(svsTileWidth * svslevel['svsscale']))
375
+ svsTileHeight = int(round(svsTileHeight * svslevel['svsscale']))
376
+ while retries > 0:
377
+ try:
378
+ tile = self._openslide.read_region(
379
+ (offsetx, offsety), svslevel['svslevel'],
380
+ (svsTileWidth, svsTileHeight))
381
+ format = TILE_FORMAT_PIL
382
+ break
383
+ except openslide.lowlevel.OpenSlideError as exc:
384
+ self._largeImagePath = str(self._getLargeImagePath())
385
+ msg = (
386
+ 'Failed to get OpenSlide region '
387
+ f'({exc} on {self._largeImagePath}: {self}).')
388
+ self.logger.info(msg)
389
+ # Reopen handle after a lowlevel error
390
+ try:
391
+ self._openslide = openslide.OpenSlide(self._largeImagePath)
392
+ except Exception:
393
+ raise TileSourceError(msg)
394
+ retries -= 1
395
+ if retries <= 0:
396
+ raise TileSourceError(msg)
397
+ if tile.mode == 'RGBA' and self._background:
398
+ tile = np.array(tile)
399
+ tile[tile[:, :, -1] == 0, :3] = self._background
400
+ tile = PIL.Image.fromarray(tile, 'RGBA')
308
401
  # Always scale to the svs level 0 tile size.
309
402
  if svslevel['scale'] != 1:
310
403
  tile = tile.resize((self.tileWidth, self.tileHeight),
311
404
  getattr(PIL.Image, 'Resampling', PIL.Image).LANCZOS)
312
- return self._outputTile(tile, TILE_FORMAT_PIL, x, y, z, pilImageAllowed,
405
+ return self._outputTile(tile, format, x, y, z, pilImageAllowed,
313
406
  numpyAllowed, **kwargs)
314
407
 
315
408
  def getPreferredLevel(self, level):
@@ -381,7 +474,15 @@ class OpenslideFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
381
474
  self._openslide = openslide.OpenSlide(self._largeImagePath)
382
475
  return None
383
476
  tiff_buffer = io.BytesIO()
384
- tifftools.write_tiff(self._tiffinfo['ifds'][images[imageKey]], tiff_buffer)
477
+ ifd = self._tiffinfo['ifds'][images[imageKey]]
478
+ if (tifftools.Tag.Photometric.value in ifd['tags'] and
479
+ ifd['tags'][tifftools.Tag.Photometric.value]['data'][0] ==
480
+ tifftools.constants.Photometric.RGB and
481
+ tifftools.Tag.SamplesPerPixel.value in ifd['tags'] and
482
+ ifd['tags'][tifftools.Tag.SamplesPerPixel.value]['data'][0] == 1):
483
+ ifd['tags'][tifftools.Tag.Photometric.value]['data'][
484
+ 0] = tifftools.constants.Photometric.MinIsBlack
485
+ tifftools.write_tiff(ifd, tiff_buffer)
385
486
  return PIL.Image.open(tiff_buffer)
386
487
 
387
488
 
@@ -28,5 +28,5 @@ class OpenslideGirderTileSource(OpenslideFileTileSource, GirderTileSource):
28
28
  cacheName = 'tilesource'
29
29
  name = 'openslide'
30
30
 
31
- extensionsWithAdjacentFiles = {'mrxs'}
32
- mimeTypesWithAdjacentFiles = {'image/mirax'}
31
+ extensionsWithAdjacentFiles = {'mrxs', 'dcm', 'dicom'}
32
+ mimeTypesWithAdjacentFiles = {'image/mirax', 'application/dicom'}
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: large-image-source-openslide
3
+ Version: 1.33.4.dev39
4
+ Summary: An Openslide tilesource for large_image.
5
+ Home-page: https://github.com/girder/large_image
6
+ Author: Kitware, Inc.
7
+ Author-email: kitware@kitware.com
8
+ License: Apache-2.0
9
+ Keywords: large_image,tile source
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/x-rst
19
+ License-File: LICENSE
20
+ Requires-Dist: large-image>=1.33.4.dev39
21
+ Requires-Dist: openslide-python>=1.4.1
22
+ Requires-Dist: openslide-bin; platform_system == "Linux" and platform_machine == "x86_64"
23
+ Requires-Dist: openslide-bin; platform_system == "Linux" and platform_machine == "aarch64"
24
+ Requires-Dist: openslide-bin; platform_system == "Windows" and platform_machine == "AMD64"
25
+ Requires-Dist: openslide-bin; platform_system == "Darwin" and platform_machine == "arm64"
26
+ Requires-Dist: openslide-bin; platform_system == "Darwin" and platform_machine == "x86_64"
27
+ Requires-Dist: tifftools>=1.2.0
28
+ Provides-Extra: girder
29
+ Requires-Dist: girder-large-image>=1.33.4.dev39; extra == "girder"
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: keywords
37
+ Dynamic: license
38
+ Dynamic: license-file
39
+ Dynamic: provides-extra
40
+ Dynamic: requires-dist
41
+ Dynamic: requires-python
42
+ Dynamic: summary
43
+
44
+ An Openslide tilesource for large_image.
45
+
46
+ See the large-image package for more details.
@@ -0,0 +1,8 @@
1
+ large_image_source_openslide/__init__.py,sha256=CEfTufsAco3V87GHdRtJz7KbIuLrgu1vGyc5-dtOXd4,22822
2
+ large_image_source_openslide/girder_source.py,sha256=wPa7xBoJ5-PKHMVy9joUNSUHTN6HCZKyR45lhR0SsRc,1236
3
+ large_image_source_openslide-1.33.4.dev39.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
4
+ large_image_source_openslide-1.33.4.dev39.dist-info/METADATA,sha256=D0LgghIyCslMY4vpBg9nqjKwpXgT3mNIWmzy454td60,1750
5
+ large_image_source_openslide-1.33.4.dev39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ large_image_source_openslide-1.33.4.dev39.dist-info/entry_points.txt,sha256=4Z5cf63yeesvHxiR9W1dY3rfz5_V4Ck2xYhPUp4A1qA,196
7
+ large_image_source_openslide-1.33.4.dev39.dist-info/top_level.txt,sha256=wqaQQDWQl9a_l9s6n07tTxfjeEXePtHBhz1np6aUwQE,29
8
+ large_image_source_openslide-1.33.4.dev39.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: large-image-source-openslide
3
- Version: 1.27.1.dev65
4
- Summary: An Openslide tilesource for large_image.
5
- Home-page: https://github.com/girder/large_image
6
- Author: Kitware, Inc.
7
- Author-email: kitware@kitware.com
8
- License: Apache Software License 2.0
9
- Keywords: large_image,tile source
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: License :: OSI Approved :: Apache Software License
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Requires-Python: >=3.8
19
- License-File: LICENSE
20
- Requires-Dist: large-image >=1.27.1.dev65
21
- Requires-Dist: openslide-python >=1.1.0
22
- Requires-Dist: tifftools >=1.2.0
23
- Provides-Extra: girder
24
- Requires-Dist: girder-large-image >=1.27.1.dev65 ; extra == 'girder'
25
-
26
- An Openslide tilesource for large_image.
27
-
28
- See the large-image package for more details.
@@ -1,8 +0,0 @@
1
- large_image_source_openslide/__init__.py,sha256=OKhFazrlt2siQeqkAoWDofaA8ufDgJkdA4eyhGM_TSE,17321
2
- large_image_source_openslide/girder_source.py,sha256=ntLgOinO7xcO85wFbjx6XEBmTgEtx5cuPZ_K6wXZzqA,1199
3
- large_image_source_openslide-1.27.1.dev65.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
4
- large_image_source_openslide-1.27.1.dev65.dist-info/METADATA,sha256=pXkV6EMV1i4XiTpQSvYhxlAEvIRI-EGUHozZlGx7Q4U,1066
5
- large_image_source_openslide-1.27.1.dev65.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- large_image_source_openslide-1.27.1.dev65.dist-info/entry_points.txt,sha256=4Z5cf63yeesvHxiR9W1dY3rfz5_V4Ck2xYhPUp4A1qA,196
7
- large_image_source_openslide-1.27.1.dev65.dist-info/top_level.txt,sha256=wqaQQDWQl9a_l9s6n07tTxfjeEXePtHBhz1np6aUwQE,29
8
- large_image_source_openslide-1.27.1.dev65.dist-info/RECORD,,