large-image-source-zarr 1.27.5.dev6__tar.gz → 1.28.2.dev12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (15) hide show
  1. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/PKG-INFO +8 -2
  2. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr/__init__.py +398 -4
  3. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/PKG-INFO +8 -2
  4. large-image-source-zarr-1.28.2.dev12/large_image_source_zarr.egg-info/requires.txt +9 -0
  5. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/setup.py +6 -0
  6. large-image-source-zarr-1.27.5.dev6/large_image_source_zarr.egg-info/requires.txt +0 -5
  7. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/LICENSE +0 -0
  8. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/README.rst +0 -0
  9. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr/girder_source.py +0 -0
  10. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/SOURCES.txt +0 -0
  11. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/dependency_links.txt +0 -0
  12. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/entry_points.txt +0 -0
  13. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/large_image_source_zarr.egg-info/top_level.txt +0 -0
  14. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/pyproject.toml +0 -0
  15. {large-image-source-zarr-1.27.5.dev6 → large-image-source-zarr-1.28.2.dev12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-zarr
3
- Version: 1.27.5.dev6
3
+ Version: 1.28.2.dev12
4
4
  Summary: A OME Zarr tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -16,8 +16,14 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Requires-Python: >=3.6
19
- Provides-Extra: girder
20
19
  License-File: LICENSE
20
+ Requires-Dist: large-image>=1.28.2.dev12
21
+ Requires-Dist: zarr
22
+ Requires-Dist: imagecodecs-numcodecs
23
+ Provides-Extra: girder
24
+ Requires-Dist: girder-large-image>=1.28.2.dev12; extra == "girder"
25
+ Provides-Extra: all
26
+ Requires-Dist: large-image-converter; extra == "all"
21
27
 
22
28
  A OME Zarr tilesource for large_image.
23
29
 
@@ -1,8 +1,12 @@
1
1
  import math
2
2
  import os
3
+ import shutil
4
+ import tempfile
3
5
  import threading
6
+ import uuid
4
7
  from importlib.metadata import PackageNotFoundError
5
8
  from importlib.metadata import version as _importlib_version
9
+ from pathlib import Path
6
10
 
7
11
  import numpy as np
8
12
  import packaging.version
@@ -10,10 +14,11 @@ import zarr
10
14
 
11
15
  import large_image
12
16
  from large_image.cache_util import LruCacheMetaclass, methodcache
13
- from large_image.constants import TILE_FORMAT_NUMPY, SourcePriority
17
+ from large_image.constants import NEW_IMAGE_PATH_FLAG, TILE_FORMAT_NUMPY, SourcePriority
14
18
  from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
15
19
  from large_image.tilesource import FileTileSource
16
- from large_image.tilesource.utilities import nearPowerOfTwo
20
+ from large_image.tilesource.resample import ResampleMethod, downsampleTileHalfRes
21
+ from large_image.tilesource.utilities import _imageToNumpy, nearPowerOfTwo
17
22
 
18
23
  try:
19
24
  __version__ = _importlib_version(__name__)
@@ -34,8 +39,17 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
34
39
  'zarr': SourcePriority.PREFERRED,
35
40
  'zgroup': SourcePriority.PREFERRED,
36
41
  'zattrs': SourcePriority.PREFERRED,
42
+ 'zarray': SourcePriority.PREFERRED,
37
43
  'db': SourcePriority.MEDIUM,
44
+ 'zip': SourcePriority.LOWER,
38
45
  }
46
+ mimeTypes = {
47
+ None: SourcePriority.FALLBACK,
48
+ 'application/zip+zarr': SourcePriority.PREFERRED,
49
+ 'application/vnd+zarr': SourcePriority.PREFERRED,
50
+ 'application/x-zarr': SourcePriority.PREFERRED,
51
+ }
52
+ newPriority = SourcePriority.HIGH
39
53
 
40
54
  _tileSize = 512
41
55
  _minTileSize = 128
@@ -52,6 +66,13 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
52
66
  """
53
67
  super().__init__(path, **kwargs)
54
68
 
69
+ if str(path).startswith(NEW_IMAGE_PATH_FLAG):
70
+ self._initNew(path, **kwargs)
71
+ else:
72
+ self._initOpen(**kwargs)
73
+ self._tileLock = threading.RLock()
74
+
75
+ def _initOpen(self, **kwargs):
55
76
  self._largeImagePath = str(self._getLargeImagePath())
56
77
  self._zarr = None
57
78
  if not os.path.isfile(self._largeImagePath) and '//:' not in self._largeImagePath:
@@ -79,7 +100,48 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
79
100
  except Exception:
80
101
  msg = 'File cannot be opened -- not an OME NGFF file or understandable zarr file.'
81
102
  raise TileSourceError(msg)
82
- self._tileLock = threading.RLock()
103
+
104
+ def _initNew(self, path, **kwargs):
105
+ """
106
+ Initialize the tile class for creating a new image.
107
+ """
108
+ self._tempdir = tempfile.TemporaryDirectory(path)
109
+ self._zarr_store = zarr.DirectoryStore(self._tempdir.name)
110
+ self._zarr = zarr.open(self._zarr_store, mode='w')
111
+ # Make unpickleable
112
+ self._unpickleable = True
113
+ self._largeImagePath = None
114
+ self._dims = {}
115
+ self.sizeX = self.sizeY = self.levels = 0
116
+ self.tileWidth = self.tileHeight = self._tileSize
117
+ self._cacheValue = str(uuid.uuid4())
118
+ self._output = None
119
+ self._editable = True
120
+ self._bandRanges = None
121
+ self._addLock = threading.RLock()
122
+ self._framecount = 0
123
+ self._mm_x = 0
124
+ self._mm_y = 0
125
+ self._levels = []
126
+
127
+ def __del__(self):
128
+ if not hasattr(self, '_derivedSource'):
129
+ try:
130
+ self._zarr.close()
131
+ except Exception:
132
+ pass
133
+ try:
134
+ self._tempdir.cleanup()
135
+ except Exception:
136
+ pass
137
+
138
+ def _checkEditable(self):
139
+ """
140
+ Raise an exception if this is not an editable image.
141
+ """
142
+ if not self._editable:
143
+ msg = 'Not an editable image'
144
+ raise TileSourceError(msg)
83
145
 
84
146
  def _getGeneralAxes(self, arr):
85
147
  """
@@ -283,6 +345,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
283
345
  baseArray.shape[self._axes.get('c')] in {1, 3, 4}):
284
346
  self._bandCount = baseArray.shape[self._axes['c']]
285
347
  self._axes['s'] = self._axes.pop('c')
348
+ elif 's' in self._axes:
349
+ self._bandCount = baseArray.shape[self._axes['s']]
286
350
  self._zarrFindLevels()
287
351
  self._getScale()
288
352
  stride = 1
@@ -325,6 +389,13 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
325
389
  'mm_y': mm_y,
326
390
  }
327
391
 
392
+ def getState(self):
393
+ # Use the _cacheValue to avoid caching the source and tiles if we are
394
+ # creating something new.
395
+ if not hasattr(self, '_cacheValue'):
396
+ return super().getState()
397
+ return super().getState() + ',%s' % (self._cacheValue, )
398
+
328
399
  def getMetadata(self):
329
400
  """
330
401
  Return a dictionary of metadata containing levels, sizeX, sizeY,
@@ -332,6 +403,8 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
332
403
 
333
404
  :returns: metadata dictionary.
334
405
  """
406
+ if self._levels is None:
407
+ self._validateZarr()
335
408
  result = super().getMetadata()
336
409
  if self._framecount > 1:
337
410
  result['frames'] = frames = []
@@ -355,8 +428,11 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
355
428
  result = {}
356
429
  result['zarr'] = {
357
430
  'base': self._zarr.attrs.asdict(),
358
- 'main': self._series[0][0].attrs.asdict(),
359
431
  }
432
+ try:
433
+ result['zarr']['main'] = self._series[0][0].attrs.asdict()
434
+ except Exception:
435
+ pass
360
436
  return result
361
437
 
362
438
  def getAssociatedImagesList(self):
@@ -396,6 +472,9 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
396
472
 
397
473
  @methodcache()
398
474
  def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs):
475
+ if self._levels is None:
476
+ self._validateZarr()
477
+
399
478
  frame = self._getFrame(**kwargs)
400
479
  self._xyzInRange(x, y, z, frame, self._framecount)
401
480
  x0, y0, x1, y1, step = self._xyzToCorners(x, y, z)
@@ -438,6 +517,313 @@ class ZarrFileTileSource(FileTileSource, metaclass=LruCacheMetaclass):
438
517
  return self._outputTile(tile, TILE_FORMAT_NUMPY, x, y, z,
439
518
  pilImageAllowed, numpyAllowed, **kwargs)
440
519
 
520
+ def _validateNewTile(self, tile, mask, placement, axes):
521
+ if not isinstance(tile, np.ndarray) or axes is None:
522
+ axes = 'yxs'
523
+ tile, mode = _imageToNumpy(tile)
524
+ elif not isinstance(axes, str) and not isinstance(axes, list):
525
+ err = 'Invalid type for axes. Must be str or list[str].'
526
+ raise ValueError(err)
527
+ axes = [x.lower() for x in axes]
528
+ if axes[-1] != 's':
529
+ axes.append('s')
530
+ if mask is not None and len(axes) - 1 == len(mask.shape):
531
+ mask = mask[:, :, np.newaxis]
532
+ if 'x' not in axes or 'y' not in axes:
533
+ err = 'Invalid value for axes. Must contain "y" and "x".'
534
+ raise ValueError(err)
535
+ for k in placement:
536
+ if k not in axes:
537
+ axes[0:0] = [k]
538
+ while len(tile.shape) < len(axes):
539
+ tile = np.expand_dims(tile, axis=0)
540
+ while mask is not None and len(mask.shape) < len(axes):
541
+ mask = np.expand_dims(mask, axis=0)
542
+
543
+ return tile, mask, placement, axes
544
+
545
+ def addTile(self, tile, x=0, y=0, mask=None, axes=None, **kwargs):
546
+ """
547
+ Add a numpy or image tile to the image, expanding the image as needed
548
+ to accommodate it. Note that x and y can be negative. If so, the
549
+ output image (and internal memory access of the image) will act as if
550
+ the 0, 0 point is the most negative position. Cropping is applied
551
+ after this offset.
552
+
553
+ :param tile: a numpy array, PIL Image, or a binary string
554
+ with an image. The numpy array can have 2 or 3 dimensions.
555
+ :param x: location in destination for upper-left corner.
556
+ :param y: location in destination for upper-left corner.
557
+ :param mask: a 2-d numpy array (or 3-d if the last dimension is 1).
558
+ If specified, areas where the mask is false will not be altered.
559
+ :param axes: a string or list of strings specifying the names of axes
560
+ in the same order as the tile dimensions.
561
+ :param kwargs: start locations for any additional axes. Note that
562
+ ``level`` is a reserved word and not permitted for an axis name.
563
+ """
564
+ # TODO: improve band bookkeeping
565
+
566
+ self._checkEditable()
567
+ store_path = str(kwargs.pop('level', 0))
568
+ placement = {
569
+ 'x': x,
570
+ 'y': y,
571
+ **kwargs,
572
+ }
573
+ tile, mask, placement, axes = self._validateNewTile(tile, mask, placement, axes)
574
+
575
+ with self._addLock:
576
+ self._axes = {k: i for i, k in enumerate(axes)}
577
+ new_dims = {
578
+ a: max(
579
+ self._dims.get(store_path, {}).get(a, 0),
580
+ placement.get(a, 0) + tile.shape[i],
581
+ )
582
+ for a, i in self._axes.items()
583
+ }
584
+ self._dims[store_path] = new_dims
585
+ placement_slices = tuple([
586
+ slice(placement.get(a, 0), placement.get(a, 0) + tile.shape[i], 1)
587
+ for i, a in enumerate(axes)
588
+ ])
589
+
590
+ current_arrays = dict(self._zarr.arrays())
591
+ if store_path == '0':
592
+ # if writing to base data, invalidate generated levels
593
+ for path in current_arrays:
594
+ if path != store_path:
595
+ self._zarr_store.rmdir(path)
596
+ chunking = None
597
+ if store_path not in current_arrays:
598
+ arr = np.empty(tuple(new_dims.values()), dtype=tile.dtype)
599
+ chunking = tuple([
600
+ self._tileSize if a in ['x', 'y'] else
601
+ new_dims.get('s') if a == 's' else 1
602
+ for a in axes
603
+ ])
604
+ else:
605
+ arr = current_arrays[store_path]
606
+ arr.resize(*tuple(new_dims.values()))
607
+ if arr.chunks[-1] != new_dims.get('s'):
608
+ # rechunk if length of samples axis changes
609
+ chunking = tuple([
610
+ self._tileSize if a in ['x', 'y'] else
611
+ new_dims.get('s') if a == 's' else 1
612
+ for a in axes
613
+ ])
614
+
615
+ if mask is not None:
616
+ arr[placement_slices] = np.where(mask, tile, arr[placement_slices])
617
+ else:
618
+ arr[placement_slices] = tile
619
+ if chunking:
620
+ zarr.array(
621
+ arr,
622
+ chunks=chunking,
623
+ overwrite=True,
624
+ store=self._zarr_store,
625
+ path=store_path,
626
+ )
627
+
628
+ # If base data changed, update large_image attributes and OME metadata
629
+ if store_path == '0':
630
+ self._zarr.attrs.update({
631
+ 'multiscales': [{
632
+ 'version': '0.5-dev',
633
+ 'axes': [{
634
+ 'name': a,
635
+ 'type': 'space' if a in ['x', 'y'] else 'other',
636
+ } for a in axes],
637
+ 'datasets': [{'path': 0}],
638
+ }],
639
+ 'omero': {'version': '0.5-dev'},
640
+ })
641
+
642
+ self._dtype = tile.dtype
643
+ self._bandCount = new_dims.get(axes[-1]) # last axis is assumed to be bands
644
+ self.sizeX = new_dims.get('x')
645
+ self.sizeY = new_dims.get('y')
646
+ self._framecount = np.prod([
647
+ length
648
+ for axis, length in new_dims.items()
649
+ if axis in axes[:-3]
650
+ ])
651
+ self._cacheValue = str(uuid.uuid4())
652
+ self._levels = None
653
+ self.levels = int(max(1, math.ceil(math.log(max(
654
+ self.sizeX / self.tileWidth, self.sizeY / self.tileHeight)) / math.log(2)) + 1))
655
+
656
+ @property
657
+ def crop(self):
658
+ """
659
+ Crop only applies to the output file, not the internal data access.
660
+
661
+ It consists of x, y, w, h in pixels.
662
+ """
663
+ return getattr(self, '_crop', None)
664
+
665
+ @crop.setter
666
+ def crop(self, value):
667
+ self._checkEditable()
668
+ if value is None:
669
+ self._crop = None
670
+ return
671
+ x, y, w, h = value
672
+ x = int(x)
673
+ y = int(y)
674
+ w = int(w)
675
+ h = int(h)
676
+ if x < 0 or y < 0 or w <= 0 or h <= 0:
677
+ msg = 'Crop must have non-negative x, y and positive w, h'
678
+ raise TileSourceError(msg)
679
+ self._crop = (x, y, w, h)
680
+
681
+ def _generateDownsampledLevels(self, resample_method):
682
+ self._checkEditable()
683
+ current_arrays = dict(self._zarr.arrays())
684
+ if '0' not in current_arrays:
685
+ msg = 'No root data found, cannot generate lower resolution levels.'
686
+ raise TileSourceError(msg)
687
+ if 'x' not in self._axes or 'y' not in self._axes:
688
+ msg = 'Data must have an X axis and Y axis to generate lower resolution levels.'
689
+ raise TileSourceError(msg)
690
+
691
+ metadata = self.getMetadata()
692
+
693
+ if (
694
+ resample_method.value < ResampleMethod.PIL_MAX_ENUM.value and
695
+ resample_method != ResampleMethod.PIL_NEAREST
696
+ ):
697
+ tile_overlap = dict(x=4, y=4, edges=True)
698
+ else:
699
+ tile_overlap = dict(x=0, y=0)
700
+ tile_size = dict(
701
+ width=4096 + tile_overlap['x'],
702
+ height=4096 + tile_overlap['y'],
703
+ )
704
+ for level in range(1, self.levels):
705
+ scale_factor = 2 ** level
706
+ iterator_output = dict(
707
+ maxWidth=self.sizeX // scale_factor,
708
+ maxHeight=self.sizeY // scale_factor,
709
+ )
710
+ for frame in metadata.get('frames', [{'Index': 0}]):
711
+ frame_position = {
712
+ k.replace('Index', '').lower(): v
713
+ for k, v in frame.items()
714
+ if k.replace('Index', '').lower() in self._axes
715
+ }
716
+ for tile in self.tileIterator(
717
+ tile_size=tile_size,
718
+ tile_overlap=tile_overlap,
719
+ frame=frame['Index'],
720
+ output=iterator_output,
721
+ resample=False, # TODO: incorporate resampling in core
722
+ ):
723
+ new_tile = downsampleTileHalfRes(tile['tile'], resample_method)
724
+ overlap = {k: int(v / 2) for k, v in tile['tile_overlap'].items()}
725
+ new_tile = new_tile[
726
+ slice(overlap['top'], new_tile.shape[0] - overlap['bottom']),
727
+ slice(overlap['left'], new_tile.shape[1] - overlap['right']),
728
+ ]
729
+
730
+ x = int(tile['x'] / 2 + overlap['left'])
731
+ y = int(tile['y'] / 2 + overlap['top'])
732
+
733
+ self.addTile(
734
+ new_tile,
735
+ x=x,
736
+ y=y,
737
+ **frame_position,
738
+ axes=list(self._axes.keys()),
739
+ level=level,
740
+ )
741
+ self._validateZarr() # refresh self._levels before continuing
742
+
743
+ def write(
744
+ self,
745
+ path,
746
+ lossy=True,
747
+ alpha=True,
748
+ overwriteAllowed=True,
749
+ resample=None,
750
+ ):
751
+ """
752
+ Output the current image to a file.
753
+
754
+ :param path: output path.
755
+ :param lossy: if false, emit a lossless file.
756
+ :param alpha: True if an alpha channel is allowed.
757
+ :param overwriteAllowed: if False, raise an exception if the output
758
+ path exists.
759
+ :param resample: one of the ``ResampleMethod`` enum values. Defaults
760
+ to ``NP_NEAREST`` for lossless and non-uint8 data and to
761
+ ``PIL_LANCZOS`` for lossy uint8 data.
762
+ """
763
+ if os.path.exists(path):
764
+ if overwriteAllowed:
765
+ if os.path.isdir(path):
766
+ shutil.rmtree(path)
767
+ else:
768
+ os.remove(path)
769
+ else:
770
+ raise TileSourceError('Output path exists (%s).' % str(path))
771
+
772
+ suffix = Path(path).suffix
773
+ source = self
774
+
775
+ if self.crop:
776
+ left, top, width, height = self.crop
777
+ source = new()
778
+ source._zarr.attrs.update(self._zarr.attrs)
779
+ for frame in self.getMetadata().get('frames', [{'Index': 0}]):
780
+ frame_position = {
781
+ k.replace('Index', '').lower(): v
782
+ for k, v in frame.items()
783
+ if k.replace('Index', '').lower() in self._axes
784
+ }
785
+ for tile in self.tileIterator(
786
+ frame=frame['Index'],
787
+ region=dict(top=top, left=left, width=width, height=height),
788
+ resample=False,
789
+ ):
790
+ source.addTile(
791
+ tile['tile'],
792
+ x=tile['x'] - left,
793
+ y=tile['y'] - top,
794
+ axes=list(self._axes.keys()),
795
+ **frame_position,
796
+ )
797
+
798
+ if suffix in ['.zarr', '.db', '.sqlite', '.zip']:
799
+ if resample is None:
800
+ resample = (
801
+ ResampleMethod.PIL_LANCZOS
802
+ if lossy and source.dtype == np.uint8
803
+ else ResampleMethod.NP_NEAREST
804
+ )
805
+ source._generateDownsampledLevels(resample)
806
+
807
+ if suffix == '.zarr':
808
+ shutil.copytree(source._tempdir.name, path)
809
+ elif suffix in ['.db', '.sqlite']:
810
+ sqlite_store = zarr.SQLiteStore(path)
811
+ zarr.copy_store(source._zarr_store, sqlite_store, if_exists='replace')
812
+ sqlite_store.close()
813
+ elif suffix == '.zip':
814
+ zip_store = zarr.ZipStore(path)
815
+ zarr.copy_store(source._zarr_store, zip_store, if_exists='replace')
816
+ zip_store.close()
817
+
818
+ else:
819
+ from large_image_converter import convert
820
+
821
+ attrs_path = Path(source._tempdir.name) / '.zattrs'
822
+ params = {}
823
+ if lossy and self.dtype == np.uint8:
824
+ params['compression'] = 'jpeg'
825
+ convert(str(attrs_path), path, overwrite=overwriteAllowed, **params)
826
+
441
827
 
442
828
  def open(*args, **kwargs):
443
829
  """
@@ -451,3 +837,11 @@ def canRead(*args, **kwargs):
451
837
  Check if an input can be read by the module class.
452
838
  """
453
839
  return ZarrFileTileSource.canRead(*args, **kwargs)
840
+
841
+
842
+ def new(*args, **kwargs):
843
+ """
844
+ Create a new image, collecting the results from patches of numpy arrays or
845
+ smaller images.
846
+ """
847
+ return ZarrFileTileSource(NEW_IMAGE_PATH_FLAG + str(uuid.uuid4()), *args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-zarr
3
- Version: 1.27.5.dev6
3
+ Version: 1.28.2.dev12
4
4
  Summary: A OME Zarr tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -16,8 +16,14 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Requires-Python: >=3.6
19
- Provides-Extra: girder
20
19
  License-File: LICENSE
20
+ Requires-Dist: large-image>=1.28.2.dev12
21
+ Requires-Dist: zarr
22
+ Requires-Dist: imagecodecs-numcodecs
23
+ Provides-Extra: girder
24
+ Requires-Dist: girder-large-image>=1.28.2.dev12; extra == "girder"
25
+ Provides-Extra: all
26
+ Requires-Dist: large-image-converter; extra == "all"
21
27
 
22
28
  A OME Zarr tilesource for large_image.
23
29
 
@@ -0,0 +1,9 @@
1
+ large-image>=1.28.2.dev12
2
+ zarr
3
+ imagecodecs-numcodecs
4
+
5
+ [all]
6
+ large-image-converter
7
+
8
+ [girder]
9
+ girder-large-image>=1.28.2.dev12
@@ -53,9 +53,15 @@ setup(
53
53
  install_requires=[
54
54
  f'large-image{limit_version}',
55
55
  'zarr',
56
+ # I am uncertain why this is required, since numcodecs is required by
57
+ # zarr; but without it some jpeg encoded data cannot be read
58
+ 'imagecodecs-numcodecs',
56
59
  ],
57
60
  extras_require={
58
61
  'girder': f'girder-large-image{limit_version}',
62
+ 'all': [
63
+ 'large-image-converter',
64
+ ],
59
65
  },
60
66
  keywords='large_image, tile source',
61
67
  packages=find_packages(exclude=['test', 'test.*']),
@@ -1,5 +0,0 @@
1
- large-image>=1.27.5.dev6
2
- zarr
3
-
4
- [girder]
5
- girder-large-image>=1.27.5.dev6