large-image 1.27.1.dev44__tar.gz → 1.27.1.dev59__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 (47) hide show
  1. {large-image-1.27.1.dev44/large_image.egg-info → large-image-1.27.1.dev59}/PKG-INFO +1 -1
  2. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/base.py +22 -514
  3. large-image-1.27.1.dev59/large_image/tilesource/tileiterator.py +544 -0
  4. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59/large_image.egg-info}/PKG-INFO +1 -1
  5. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image.egg-info/SOURCES.txt +1 -0
  6. large-image-1.27.1.dev59/large_image.egg-info/requires.txt +145 -0
  7. large-image-1.27.1.dev44/large_image.egg-info/requires.txt +0 -145
  8. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/LICENSE +0 -0
  9. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/NOTICE +0 -0
  10. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/README.rst +0 -0
  11. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/annotations.rst +0 -0
  12. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/caching.rst +0 -0
  13. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/conf.py +0 -0
  14. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/config_options.rst +0 -0
  15. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/development.rst +0 -0
  16. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/example_usage.rst +0 -0
  17. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/girder_annotation_config_options.rst +0 -0
  18. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/girder_config_options.rst +0 -0
  19. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/image_conversion.rst +0 -0
  20. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/index.rst +0 -0
  21. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/large_image_examples.ipynb +0 -0
  22. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/make_docs.sh +0 -0
  23. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/multi_source_specification.rst +0 -0
  24. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/notebooks.rst +0 -0
  25. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/static/custom.css +0 -0
  26. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/tilesource_options.rst +0 -0
  27. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/docs/upgrade.rst +0 -0
  28. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/__init__.py +0 -0
  29. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/cache_util/__init__.py +0 -0
  30. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/cache_util/base.py +0 -0
  31. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/cache_util/cache.py +0 -0
  32. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/cache_util/cachefactory.py +0 -0
  33. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/cache_util/memcache.py +0 -0
  34. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/config.py +0 -0
  35. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/constants.py +0 -0
  36. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/exceptions.py +0 -0
  37. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/__init__.py +0 -0
  38. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/geo.py +0 -0
  39. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/jupyter.py +0 -0
  40. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/stylefuncs.py +0 -0
  41. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/tiledict.py +0 -0
  42. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image/tilesource/utilities.py +0 -0
  43. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image.egg-info/dependency_links.txt +0 -0
  44. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image.egg-info/not-zip-safe +0 -0
  45. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/large_image.egg-info/top_level.txt +0 -0
  46. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/setup.cfg +0 -0
  47. {large-image-1.27.1.dev44 → large-image-1.27.1.dev59}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image
3
- Version: 1.27.1.dev44
3
+ Version: 1.27.1.dev59
4
4
  Summary: Python modules to work with large, multiresolution images.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -27,6 +27,7 @@ from ..constants import (TILE_FORMAT_IMAGE, TILE_FORMAT_NUMPY, TILE_FORMAT_PIL,
27
27
  from . import utilities
28
28
  from .jupyter import IPyLeafletMixin
29
29
  from .tiledict import LazyTileDict
30
+ from .tileiterator import TileIterator
30
31
  from .utilities import (ImageBytes, JSONDict, _imageToNumpy, # noqa: F401
31
32
  _imageToPIL, dictToEtree, etreeToDict,
32
33
  getPaletteColors, histogramThreshold, nearPowerOfTwo)
@@ -488,478 +489,6 @@ class TileSource(IPyLeafletMixin):
488
489
 
489
490
  return cast(int, left), cast(int, top), cast(int, right), cast(int, bottom)
490
491
 
491
- def _tileIteratorInfo(self, **kwargs) -> Optional[Dict[str, Any]]: # noqa
492
- """
493
- Get information necessary to construct a tile iterator.
494
- If one of width or height is specified, the other is determined by
495
- preserving aspect ratio. If both are specified, the result may not be
496
- that size, as aspect ratio is always preserved. If neither are
497
- specified, magnification, mm_x, and/or mm_y are used to determine the
498
- size. If none of those are specified, the original maximum resolution
499
- is returned.
500
-
501
- :param format: a tuple of allowed formats. Formats are members of
502
- TILE_FORMAT_*. This will avoid converting images if they are
503
- in the desired output encoding (regardless of subparameters).
504
- Otherwise, TILE_FORMAT_NUMPY is returned.
505
- :param region: a dictionary of optional values which specify the part
506
- of the image to process.
507
-
508
- :left: the left edge (inclusive) of the region to process.
509
- :top: the top edge (inclusive) of the region to process.
510
- :right: the right edge (exclusive) of the region to process.
511
- :bottom: the bottom edge (exclusive) of the region to process.
512
- :width: the width of the region to process.
513
- :height: the height of the region to process.
514
- :units: either 'base_pixels' (default), 'pixels', 'mm', or
515
- 'fraction'. base_pixels are in maximum resolution pixels.
516
- pixels is in the specified magnification pixels. mm is in the
517
- specified magnification scale. fraction is a scale of 0 to 1.
518
- pixels and mm are only available if the magnification and mm
519
- per pixel are defined for the image.
520
- :unitsWH: if not specified, this is the same as `units`.
521
- Otherwise, these units will be used for the width and height if
522
- specified.
523
-
524
- :param output: a dictionary of optional values which specify the size
525
- of the output.
526
-
527
- :maxWidth: maximum width in pixels.
528
- :maxHeight: maximum height in pixels.
529
-
530
- :param scale: a dictionary of optional values which specify the scale
531
- of the region and / or output. This applies to region if
532
- pixels or mm are used for units. It applies to output if
533
- neither output maxWidth nor maxHeight is specified.
534
-
535
- :magnification: the magnification ratio.
536
- :mm_x: the horizontal size of a pixel in millimeters.
537
- :mm_y: the vertical size of a pixel in millimeters.
538
- :exact: if True, only a level that matches exactly will be
539
- returned. This is only applied if magnification, mm_x, or mm_y
540
- is used.
541
-
542
- :param tile_position: if present, either a number to only yield the
543
- (tile_position)th tile [0 to (xmax - min) * (ymax - ymin)) that the
544
- iterator would yield, or a dictionary of {region_x, region_y} to
545
- yield that tile, where 0, 0 is the first tile yielded, and
546
- xmax - xmin - 1, ymax - ymin - 1 is the last tile yielded, or a
547
- dictionary of {level_x, level_y} to yield that specific tile if it
548
- is in the region.
549
- :param tile_size: if present, retile the output to the specified tile
550
- size. If only width or only height is specified, the resultant
551
- tiles will be square. This is a dictionary containing at least
552
- one of:
553
-
554
- :width: the desired tile width.
555
- :height: the desired tile height.
556
-
557
- :param tile_overlap: if present, retile the output adding a symmetric
558
- overlap to the tiles. If either x or y is not specified, it
559
- defaults to zero. The overlap does not change the tile size,
560
- only the stride of the tiles. This is a dictionary containing:
561
-
562
- :x: the horizontal overlap in pixels.
563
- :y: the vertical overlap in pixels.
564
- :edges: if True, then the edge tiles will exclude the overlap
565
- distance. If unset or False, the edge tiles are full size.
566
-
567
- :param tile_offset: if present, adjust tile positions so that the
568
- corner of one tile is at the specified location.
569
-
570
- :left: the left offset in pixels.
571
- :top: the top offset in pixels.
572
- :auto: a boolean, if True, automatically set the offset to align
573
- with the region's left and top.
574
-
575
- :param kwargs: optional arguments. Some options are encoding,
576
- jpegQuality, jpegSubsampling, tiffCompression, frame.
577
- :returns: a dictionary of information needed for the tile iterator.
578
- This is None if no tiles will be returned. Otherwise, this
579
- contains:
580
-
581
- :region: a dictionary of the source region information:
582
-
583
- :width, height: the total output of the iterator in pixels.
584
- This may be larger than the requested resolution (given by
585
- output width and output height) if there isn't an exact
586
- match between the requested resolution and available native
587
- tiles.
588
- :left, top, right, bottom: the coordinates within the image of
589
- the region returned in the level pixel space.
590
-
591
- :xmin, ymin, xmax, ymax: the tiles that will be included during the
592
- iteration: [xmin, xmax) and [ymin, ymax).
593
- :mode: either 'RGB' or 'RGBA'. This determines the color space
594
- used for tiles.
595
- :level: the tile level used for iteration.
596
- :metadata: tile source metadata (from getMetadata)
597
- :output: a dictionary of the output resolution information.
598
-
599
- :width, height: the requested output resolution in pixels. If
600
- this is different that region width and region height, then
601
- the original request was asking for a different scale than
602
- is being delivered.
603
-
604
- :frame: the frame value for the base image.
605
- :format: a tuple of allowed output formats.
606
- :encoding: if the output format is TILE_FORMAT_IMAGE, the desired
607
- encoding.
608
- :requestedScale: the scale needed to convert from the region width
609
- and height to the output width and height.
610
- """
611
- maxWidth = kwargs.get('output', {}).get('maxWidth')
612
- maxHeight = kwargs.get('output', {}).get('maxHeight')
613
- if ((maxWidth is not None and
614
- (not isinstance(maxWidth, int) or maxWidth < 0)) or
615
- (maxHeight is not None and
616
- (not isinstance(maxHeight, int) or maxHeight < 0))):
617
- msg = 'Invalid output width or height. Minimum value is 0.'
618
- raise ValueError(msg)
619
-
620
- magLevel = None
621
- mag = None
622
- if maxWidth is None and maxHeight is None:
623
- # If neither width nor height as specified, see if magnification,
624
- # mm_x, or mm_y are requested.
625
- magArgs = (kwargs.get('scale') or {}).copy()
626
- magArgs['rounding'] = None
627
- magLevel = self.getLevelForMagnification(**magArgs)
628
- if magLevel is None and kwargs.get('scale', {}).get('exact'):
629
- return None
630
- mag = self.getMagnificationForLevel(magLevel)
631
- metadata = self.getMetadata()
632
- left, top, right, bottom = self._getRegionBounds(
633
- metadata, desiredMagnification=mag, **kwargs.get('region', {}))
634
- regionWidth = right - left
635
- regionHeight = bottom - top
636
- magRequestedScale: Optional[float] = None
637
- if maxWidth is None and maxHeight is None and mag:
638
- if mag.get('scale') in (1.0, None):
639
- maxWidth, maxHeight = regionWidth, regionHeight
640
- magRequestedScale = 1
641
- else:
642
- maxWidth = regionWidth / cast(float, mag['scale'])
643
- maxHeight = regionHeight / cast(float, mag['scale'])
644
- magRequestedScale = cast(float, mag['scale'])
645
- outWidth, outHeight, calcScale = utilities._calculateWidthHeight(
646
- maxWidth, maxHeight, regionWidth, regionHeight)
647
- requestedScale = calcScale if magRequestedScale is None else magRequestedScale
648
- if (regionWidth < 0 or regionHeight < 0 or outWidth == 0 or
649
- outHeight == 0):
650
- return None
651
-
652
- preferredLevel = metadata['levels'] - 1
653
- # If we are scaling the result, pick the tile level that is at least
654
- # the resolution we need and is preferred by the tile source.
655
- if outWidth != regionWidth or outHeight != regionHeight:
656
- newLevel = self.getPreferredLevel(preferredLevel + int(
657
- math.ceil(round(math.log(max(float(outWidth) / regionWidth,
658
- float(outHeight) / regionHeight)) /
659
- math.log(2), 4))))
660
- if newLevel < preferredLevel:
661
- # scale the bounds to the level we will use
662
- factor = 2 ** (preferredLevel - newLevel)
663
- left = int(left / factor)
664
- right = int(right / factor)
665
- regionWidth = right - left
666
- top = int(top / factor)
667
- bottom = int(bottom / factor)
668
- regionHeight = bottom - top
669
- preferredLevel = newLevel
670
- requestedScale /= factor
671
- # If an exact magnification was requested and this tile source doesn't
672
- # have tiles at the appropriate level, indicate that we won't return
673
- # anything.
674
- if (magLevel is not None and magLevel != preferredLevel and
675
- kwargs.get('scale', {}).get('exact')):
676
- return None
677
-
678
- tile_size = {
679
- 'width': metadata['tileWidth'],
680
- 'height': metadata['tileHeight'],
681
- }
682
- tile_overlap = {
683
- 'x': int(kwargs.get('tile_overlap', {}).get('x', 0) or 0),
684
- 'y': int(kwargs.get('tile_overlap', {}).get('y', 0) or 0),
685
- 'edges': kwargs.get('tile_overlap', {}).get('edges', False),
686
- 'offset_x': 0,
687
- 'offset_y': 0,
688
- 'range_x': 0,
689
- 'range_y': 0,
690
- }
691
- if not tile_overlap['edges']:
692
- # offset by half the overlap
693
- tile_overlap['offset_x'] = tile_overlap['x'] // 2
694
- tile_overlap['offset_y'] = tile_overlap['y'] // 2
695
- tile_overlap['range_x'] = tile_overlap['x']
696
- tile_overlap['range_y'] = tile_overlap['y']
697
- if 'tile_size' in kwargs:
698
- tile_size['width'] = int(kwargs['tile_size'].get(
699
- 'width', kwargs['tile_size'].get('height', tile_size['width'])))
700
- tile_size['height'] = int(kwargs['tile_size'].get(
701
- 'height', kwargs['tile_size'].get('width', tile_size['height'])))
702
- # Tile size includes the overlap
703
- tile_size['width'] -= tile_overlap['x']
704
- tile_size['height'] -= tile_overlap['y']
705
- if tile_size['width'] <= 0 or tile_size['height'] <= 0:
706
- msg = 'Invalid tile_size or tile_overlap.'
707
- raise ValueError(msg)
708
-
709
- resample = (
710
- False if round(requestedScale, 2) == 1.0 or
711
- kwargs.get('resample') in (None, False) else kwargs.get('resample'))
712
- # If we need to resample to make tiles at a non-native resolution,
713
- # adjust the tile size and tile overlap parameters appropriately.
714
- if resample is not False:
715
- tile_size['width'] = max(1, int(math.ceil(tile_size['width'] * requestedScale)))
716
- tile_size['height'] = max(1, int(math.ceil(tile_size['height'] * requestedScale)))
717
- tile_overlap['x'] = int(math.ceil(tile_overlap['x'] * requestedScale))
718
- tile_overlap['y'] = int(math.ceil(tile_overlap['y'] * requestedScale))
719
-
720
- offset_x = kwargs.get('tile_offset', {}).get('left', 0)
721
- offset_y = kwargs.get('tile_offset', {}).get('top', 0)
722
- if kwargs.get('tile_offset', {}).get('auto'):
723
- offset_x = left
724
- offset_y = top
725
- offset_x = (left - left % tile_size['width']) if offset_x > left else offset_x
726
- offset_y = (top - top % tile_size['height']) if offset_y > top else offset_y
727
- # If the overlapped tiles don't run over the edge, then the functional
728
- # size of the region is reduced by the overlap. This factor is stored
729
- # in the overlap offset_*.
730
- xmin = int((left - offset_x) / tile_size['width'])
731
- xmax = max(int(math.ceil((float(right - offset_x) - tile_overlap['range_x']) /
732
- tile_size['width'])), xmin + 1)
733
- ymin = int((top - offset_y) / tile_size['height'])
734
- ymax = max(int(math.ceil((float(bottom - offset_y) - tile_overlap['range_y']) /
735
- tile_size['height'])), ymin + 1)
736
- tile_overlap.update({'xmin': xmin, 'xmax': xmax,
737
- 'ymin': ymin, 'ymax': ymax})
738
- tile_overlap['offset_x'] += offset_x
739
- tile_overlap['offset_y'] += offset_y
740
-
741
- # Use RGB for JPEG, RGBA for PNG
742
- mode = 'RGBA' if kwargs.get('encoding') in {'PNG', 'TIFF', 'TILED'} else 'RGB'
743
-
744
- info = {
745
- 'region': {
746
- 'top': top,
747
- 'left': left,
748
- 'bottom': bottom,
749
- 'right': right,
750
- 'width': regionWidth,
751
- 'height': regionHeight,
752
- },
753
- 'xmin': xmin,
754
- 'ymin': ymin,
755
- 'xmax': xmax,
756
- 'ymax': ymax,
757
- 'mode': mode,
758
- 'level': preferredLevel,
759
- 'metadata': metadata,
760
- 'output': {
761
- 'width': outWidth,
762
- 'height': outHeight,
763
- },
764
- 'frame': kwargs.get('frame'),
765
- 'format': kwargs.get('format', (TILE_FORMAT_NUMPY, )),
766
- 'encoding': kwargs.get('encoding'),
767
- 'requestedScale': requestedScale,
768
- 'resample': resample,
769
- 'tile_overlap': tile_overlap,
770
- 'tile_position': kwargs.get('tile_position'),
771
- 'tile_size': tile_size,
772
- }
773
- return info
774
-
775
- def _tileIterator(self, iterInfo: Dict[str, Any]) -> Iterator[LazyTileDict]:
776
- """
777
- Given tile iterator information, iterate through the tiles.
778
- Each tile is returned as part of a dictionary that includes
779
-
780
- :x, y: (left, top) coordinate in current magnification pixels
781
- :width, height: size of current tile in current magnification
782
- pixels
783
- :tile: cropped tile image
784
- :format: format of the tile. One of TILE_FORMAT_NUMPY,
785
- TILE_FORMAT_PIL, or TILE_FORMAT_IMAGE. TILE_FORMAT_IMAGE is
786
- only returned if it was explicitly allowed and the tile is
787
- already in the correct image encoding.
788
- :level: level of the current tile
789
- :level_x, level_y: the tile reference number within the level.
790
- Tiles are numbered (0, 0), (1, 0), (2, 0), etc. The 0th tile
791
- yielded may not be (0, 0) if a region is specified.
792
- :tile_position: a dictionary of the tile position within the
793
- iterator, containing:
794
-
795
- :level_x, level_y: the tile reference number within the level.
796
- :region_x, region_y: 0, 0 is the first tile in the full
797
- iteration (when not restricting the iteration to a single
798
- tile).
799
- :position: a 0-based value for the tile within the full
800
- iteration.
801
-
802
- :iterator_range: a dictionary of the output range of the iterator:
803
-
804
- :level_x_min, level_x_max: the tiles that are be included
805
- during the full iteration: [layer_x_min, layer_x_max).
806
- :level_y_min, level_y_max: the tiles that are be included
807
- during the full iteration: [layer_y_min, layer_y_max).
808
- :region_x_max, region_y_max: the number of tiles included during
809
- the full iteration. This is layer_x_max - layer_x_min,
810
- layer_y_max - layer_y_min.
811
- :position: the total number of tiles included in the full
812
- iteration. This is region_x_max * region_y_max.
813
-
814
- :magnification: magnification of the current tile
815
- :mm_x, mm_y: size of the current tile pixel in millimeters.
816
- :gx, gy: (left, top) coordinates in maximum-resolution pixels
817
- :gwidth, gheight: size of of the current tile in maximum-resolution
818
- pixels.
819
- :tile_overlap: the amount of overlap with neighboring tiles (left,
820
- top, right, and bottom). Overlap never extends outside of the
821
- requested region.
822
-
823
- If a region that includes partial tiles is requested, those tiles are
824
- cropped appropriately. Most images will have tiles that get cropped
825
- along the right and bottom edges in any case.
826
-
827
- :param iterInfo: tile iterator information. See _tileIteratorInfo.
828
- :yields: an iterator that returns a dictionary as listed above.
829
- """
830
- regionWidth = iterInfo['region']['width']
831
- regionHeight = iterInfo['region']['height']
832
- left = iterInfo['region']['left']
833
- top = iterInfo['region']['top']
834
- xmin = iterInfo['xmin']
835
- ymin = iterInfo['ymin']
836
- xmax = iterInfo['xmax']
837
- ymax = iterInfo['ymax']
838
- level = iterInfo['level']
839
- metadata = iterInfo['metadata']
840
- tileSize = iterInfo['tile_size']
841
- tileOverlap = iterInfo['tile_overlap']
842
- format = iterInfo['format']
843
- encoding = iterInfo['encoding']
844
-
845
- self.logger.debug(
846
- 'Fetching region of an image with a source size of %d x %d; '
847
- 'getting %d tile%s',
848
- regionWidth, regionHeight, (xmax - xmin) * (ymax - ymin),
849
- '' if (xmax - xmin) * (ymax - ymin) == 1 else 's')
850
-
851
- # If tile is specified, return at most one tile
852
- if iterInfo.get('tile_position') is not None:
853
- tilePos = iterInfo.get('tile_position')
854
- if isinstance(tilePos, dict):
855
- if tilePos.get('position') is not None:
856
- tilePos = tilePos['position']
857
- elif 'region_x' in tilePos and 'region_y' in tilePos:
858
- tilePos = (tilePos['region_x'] +
859
- tilePos['region_y'] * (xmax - xmin))
860
- elif 'level_x' in tilePos and 'level_y' in tilePos:
861
- tilePos = ((tilePos['level_x'] - xmin) +
862
- (tilePos['level_y'] - ymin) * (xmax - xmin))
863
- if tilePos < 0 or tilePos >= (ymax - ymin) * (xmax - xmin):
864
- xmax = xmin
865
- else:
866
- ymin += int(tilePos / (xmax - xmin))
867
- ymax = ymin + 1
868
- xmin += int(tilePos % (xmax - xmin))
869
- xmax = xmin + 1
870
- mag = self.getMagnificationForLevel(level)
871
- scale = mag.get('scale', 1.0)
872
- retile = (tileSize['width'] != metadata['tileWidth'] or
873
- tileSize['height'] != metadata['tileHeight'] or
874
- tileOverlap['x'] or tileOverlap['y'])
875
- for y in range(ymin, ymax):
876
- for x in range(xmin, xmax):
877
- crop = None
878
- posX = int(x * tileSize['width'] - tileOverlap['x'] // 2 +
879
- tileOverlap['offset_x'] - left)
880
- posY = int(y * tileSize['height'] - tileOverlap['y'] // 2 +
881
- tileOverlap['offset_y'] - top)
882
- tileWidth = tileSize['width'] + tileOverlap['x']
883
- tileHeight = tileSize['height'] + tileOverlap['y']
884
- # crop as needed
885
- if (posX < 0 or posY < 0 or posX + tileWidth > regionWidth or
886
- posY + tileHeight > regionHeight):
887
- crop = (max(0, -posX),
888
- max(0, -posY),
889
- int(min(tileWidth, regionWidth - posX)),
890
- int(min(tileHeight, regionHeight - posY)))
891
- posX += crop[0]
892
- posY += crop[1]
893
- tileWidth = crop[2] - crop[0]
894
- tileHeight = crop[3] - crop[1]
895
- overlap = {
896
- 'left': max(0, x * tileSize['width'] + tileOverlap['offset_x'] - left - posX),
897
- 'top': max(0, y * tileSize['height'] + tileOverlap['offset_y'] - top - posY),
898
- }
899
- overlap['right'] = (
900
- max(0, tileWidth - tileSize['width'] - overlap['left'])
901
- if x != xmin or not tileOverlap['range_x'] else
902
- min(tileWidth, tileOverlap['range_x'] - tileOverlap['offset_x']))
903
- overlap['bottom'] = (
904
- max(0, tileHeight - tileSize['height'] - overlap['top'])
905
- if y != ymin or not tileOverlap['range_y'] else
906
- min(tileHeight, tileOverlap['range_y'] - tileOverlap['offset_y']))
907
- if tileOverlap['range_x']:
908
- overlap['left'] = 0 if x == tileOverlap['xmin'] else overlap['left']
909
- overlap['right'] = 0 if x + 1 == tileOverlap['xmax'] else overlap['right']
910
- if tileOverlap['range_y']:
911
- overlap['top'] = 0 if y == tileOverlap['ymin'] else overlap['top']
912
- overlap['bottom'] = 0 if y + 1 == tileOverlap['ymax'] else overlap['bottom']
913
- tile = LazyTileDict({
914
- 'x': x,
915
- 'y': y,
916
- 'frame': iterInfo.get('frame'),
917
- 'level': level,
918
- 'format': format,
919
- 'encoding': encoding,
920
- 'crop': crop,
921
- 'requestedScale': iterInfo['requestedScale'],
922
- 'retile': retile,
923
- 'metadata': metadata,
924
- 'source': self,
925
- }, {
926
- 'x': posX + left,
927
- 'y': posY + top,
928
- 'width': tileWidth,
929
- 'height': tileHeight,
930
- 'level': level,
931
- 'level_x': x,
932
- 'level_y': y,
933
- 'magnification': mag['magnification'],
934
- 'mm_x': mag['mm_x'],
935
- 'mm_y': mag['mm_y'],
936
- 'tile_position': {
937
- 'level_x': x,
938
- 'level_y': y,
939
- 'region_x': x - iterInfo['xmin'],
940
- 'region_y': y - iterInfo['ymin'],
941
- 'position': ((x - iterInfo['xmin']) +
942
- (y - iterInfo['ymin']) *
943
- (iterInfo['xmax'] - iterInfo['xmin'])),
944
- },
945
- 'iterator_range': {
946
- 'level_x_min': iterInfo['xmin'],
947
- 'level_y_min': iterInfo['ymin'],
948
- 'level_x_max': iterInfo['xmax'],
949
- 'level_y_max': iterInfo['ymax'],
950
- 'region_x_max': iterInfo['xmax'] - iterInfo['xmin'],
951
- 'region_y_max': iterInfo['ymax'] - iterInfo['ymin'],
952
- 'position': ((iterInfo['xmax'] - iterInfo['xmin']) *
953
- (iterInfo['ymax'] - iterInfo['ymin'])),
954
- },
955
- 'tile_overlap': overlap,
956
- })
957
- tile['gx'] = tile['x'] * scale
958
- tile['gy'] = tile['y'] * scale
959
- tile['gwidth'] = tile['width'] * scale
960
- tile['gheight'] = tile['height'] * scale
961
- yield tile
962
-
963
492
  def _pilFormatMatches(self, image: Any, match: Union[bool, str] = True, **kwargs) -> bool:
964
493
  """
965
494
  Determine if the specified PIL image matches the format of the tile
@@ -2199,20 +1728,20 @@ class TileSource(IPyLeafletMixin):
2199
1728
  'width': max(self.tileWidth, 4096),
2200
1729
  'height': max(self.tileHeight, 4096)}
2201
1730
  kwargs['tile_offset'] = {'auto': True}
2202
- iterInfo = self._tileIteratorInfo(**kwargs)
2203
- if iterInfo is None:
1731
+ tileIter = TileIterator(self, format=TILE_FORMAT_NUMPY, resample=None, **kwargs)
1732
+ if tileIter.info is None:
2204
1733
  pilimage = PIL.Image.new('RGB', (0, 0))
2205
1734
  return utilities._encodeImage(pilimage, format=format, **kwargs)
2206
- regionWidth = iterInfo['region']['width']
2207
- regionHeight = iterInfo['region']['height']
2208
- top = iterInfo['region']['top']
2209
- left = iterInfo['region']['left']
2210
- mode = None if TILE_FORMAT_NUMPY in format else iterInfo['mode']
2211
- outWidth = iterInfo['output']['width']
2212
- outHeight = iterInfo['output']['height']
1735
+ regionWidth = tileIter.info['region']['width']
1736
+ regionHeight = tileIter.info['region']['height']
1737
+ top = tileIter.info['region']['top']
1738
+ left = tileIter.info['region']['left']
1739
+ mode = None if TILE_FORMAT_NUMPY in format else tileIter.info['mode']
1740
+ outWidth = tileIter.info['output']['width']
1741
+ outHeight = tileIter.info['output']['height']
2213
1742
  image: Optional[Union[np.ndarray, PIL.Image.Image, ImageBytes, bytes]] = None
2214
1743
  tiledimage = None
2215
- for tile in self._tileIterator(iterInfo):
1744
+ for tile in tileIter:
2216
1745
  # Add each tile to the image
2217
1746
  subimage, _ = _imageToNumpy(tile['tile'])
2218
1747
  x0, y0 = tile['x'] - left, tile['y'] - top
@@ -2230,7 +1759,7 @@ class TileSource(IPyLeafletMixin):
2230
1759
  outHeight = int(math.floor(outHeight))
2231
1760
  if tiled:
2232
1761
  return self._encodeTiledImage(
2233
- cast(Dict[str, Any], tiledimage), outWidth, outHeight, iterInfo, **kwargs)
1762
+ cast(Dict[str, Any], tiledimage), outWidth, outHeight, tileIter.info, **kwargs)
2234
1763
  if outWidth != regionWidth or outHeight != regionHeight:
2235
1764
  dtype = cast(np.ndarray, image).dtype
2236
1765
  image = _imageToPIL(cast(np.ndarray, image), mode).resize(
@@ -2401,19 +1930,20 @@ class TileSource(IPyLeafletMixin):
2401
1930
  if not isinstance(format, (tuple, set, list)):
2402
1931
  format = (format, )
2403
1932
  tiled = TILE_FORMAT_IMAGE in format and kwargs.get('encoding') == 'TILED'
2404
- iterInfo = self._tileIteratorInfo(frame=frameList[0], **kwargs)
2405
- if iterInfo is None:
1933
+ tileIter = TileIterator(self, format=TILE_FORMAT_NUMPY, resample=None,
1934
+ frame=frameList[0], **kwargs)
1935
+ if tileIter.info is None:
2406
1936
  pilimage = PIL.Image.new('RGB', (0, 0))
2407
1937
  return utilities._encodeImage(pilimage, format=format, **kwargs)
2408
- frameWidth = iterInfo['output']['width']
2409
- frameHeight = iterInfo['output']['height']
1938
+ frameWidth = tileIter.info['output']['width']
1939
+ frameHeight = tileIter.info['output']['height']
2410
1940
  maxWidth = kwargs.get('output', {}).get('maxWidth')
2411
1941
  maxHeight = kwargs.get('output', {}).get('maxHeight')
2412
1942
  if kwargs.get('fill') and maxWidth and maxHeight:
2413
1943
  frameWidth, frameHeight = maxWidth, maxHeight
2414
1944
  outWidth = frameWidth * framesAcross
2415
1945
  outHeight = frameHeight * framesHigh
2416
- tile = next(self._tileIterator(iterInfo))
1946
+ tile = next(tileIter)
2417
1947
  image = None
2418
1948
  tiledimage = None
2419
1949
  if max_workers is not None and max_workers < 0:
@@ -2446,7 +1976,7 @@ class TileSource(IPyLeafletMixin):
2446
1976
  outWidth, outHeight)
2447
1977
  if tiled:
2448
1978
  return self._encodeTiledImage(
2449
- cast(Dict[str, Any], tiledimage), outWidth, outHeight, iterInfo, **kwargs)
1979
+ cast(Dict[str, Any], tiledimage), outWidth, outHeight, tileIter.info, **kwargs)
2450
1980
  return utilities._encodeImage(cast(np.ndarray, image), format=format, **kwargs)
2451
1981
 
2452
1982
  def getRegionAtAnotherScale(
@@ -2750,26 +2280,7 @@ class TileSource(IPyLeafletMixin):
2750
2280
  :param kwargs: optional arguments.
2751
2281
  :yields: an iterator that returns a dictionary as listed above.
2752
2282
  """
2753
- if not isinstance(format, tuple):
2754
- format = (format, )
2755
- if TILE_FORMAT_IMAGE in format:
2756
- encoding = kwargs.get('encoding')
2757
- if encoding not in TileOutputMimeTypes:
2758
- raise ValueError('Invalid encoding "%s"' % encoding)
2759
- iterFormat = format if resample in (False, None) else (
2760
- TILE_FORMAT_PIL, )
2761
- iterInfo = self._tileIteratorInfo(format=iterFormat, resample=resample,
2762
- **kwargs)
2763
- if not iterInfo:
2764
- return
2765
- # check if the desired scale is different from the actual scale and
2766
- # resampling is needed. Ignore small scale differences.
2767
- if (resample in (False, None) or
2768
- round(iterInfo['requestedScale'], 2) == 1.0):
2769
- resample = False
2770
- for tile in self._tileIterator(iterInfo):
2771
- tile.setFormat(format, resample, kwargs)
2772
- yield tile
2283
+ return TileIterator(self, format=format, resample=resample, **kwargs)
2773
2284
 
2774
2285
  def tileIteratorAtAnotherScale(
2775
2286
  self, sourceRegion: Dict[str, Any],
@@ -2879,11 +2390,8 @@ class TileSource(IPyLeafletMixin):
2879
2390
  # This could be
2880
2391
  # img, format = self.getRegion(format=TILE_FORMAT_PIL, **regionArgs)
2881
2392
  # where img is the PIL image (rather than tile['tile'], but using
2882
- # _tileIteratorInfo and the _tileIterator is slightly more efficient.
2883
- iterInfo = self._tileIteratorInfo(format=TILE_FORMAT_NUMPY, **regionArgs)
2884
- if iterInfo is None:
2885
- return JSONDict(pixel)
2886
- tile = next(self._tileIterator(iterInfo), None)
2393
+ # TileIterator is slightly more efficient.
2394
+ tile = next(TileIterator(self, format=TILE_FORMAT_NUMPY, **regionArgs), None)
2887
2395
  if tile is None:
2888
2396
  return JSONDict(pixel)
2889
2397
  if includeTileRecord: