rio-tiler 8.0.5__py3-none-any.whl → 9.0.0a1__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.
- rio_tiler/__init__.py +1 -1
- rio_tiler/expression.py +1 -1
- rio_tiler/io/base.py +158 -284
- rio_tiler/io/rasterio.py +22 -11
- rio_tiler/io/stac.py +25 -6
- rio_tiler/io/xarray.py +27 -19
- rio_tiler/models.py +24 -7
- rio_tiler/reader.py +3 -4
- rio_tiler/types.py +8 -6
- {rio_tiler-8.0.5.dist-info → rio_tiler-9.0.0a1.dist-info}/METADATA +1 -1
- {rio_tiler-8.0.5.dist-info → rio_tiler-9.0.0a1.dist-info}/RECORD +14 -14
- {rio_tiler-8.0.5.dist-info → rio_tiler-9.0.0a1.dist-info}/WHEEL +0 -0
- {rio_tiler-8.0.5.dist-info → rio_tiler-9.0.0a1.dist-info}/licenses/AUTHORS.txt +0 -0
- {rio_tiler-8.0.5.dist-info → rio_tiler-9.0.0a1.dist-info}/licenses/LICENSE +0 -0
rio_tiler/io/base.py
CHANGED
|
@@ -362,25 +362,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
362
362
|
"""Get Asset Reader and options."""
|
|
363
363
|
return self.reader, {}
|
|
364
364
|
|
|
365
|
-
def parse_expression(self, expression: str, asset_as_band: bool = False) -> tuple:
|
|
366
|
-
"""Parse rio-tiler band math expression."""
|
|
367
|
-
input_assets = "|".join(self.assets)
|
|
368
|
-
|
|
369
|
-
if asset_as_band:
|
|
370
|
-
_re = re.compile(rf"\b({input_assets})\b")
|
|
371
|
-
else:
|
|
372
|
-
_re = re.compile(rf"\b({input_assets})_b\d+\b")
|
|
373
|
-
|
|
374
|
-
assets = tuple(set(re.findall(_re, expression)))
|
|
375
|
-
if not assets:
|
|
376
|
-
raise InvalidExpression(
|
|
377
|
-
f"Could not find any valid assets in '{expression}' expression. Assets are: {self.assets}"
|
|
378
|
-
if asset_as_band
|
|
379
|
-
else f"Could not find any valid assets in '{expression}' expression, maybe try with `asset_as_band=True`. Assets are: {self.assets}"
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
return assets
|
|
383
|
-
|
|
384
365
|
def _update_statistics(
|
|
385
366
|
self,
|
|
386
367
|
img: ImageData,
|
|
@@ -422,31 +403,24 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
422
403
|
|
|
423
404
|
def _reader(asset: str, **kwargs: Any) -> dict:
|
|
424
405
|
asset_info = self._get_asset_info(asset)
|
|
425
|
-
reader,
|
|
406
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
426
407
|
|
|
408
|
+
options = {**self.reader_options, **reader_options}
|
|
427
409
|
with self.ctx(**asset_info.get("env", {})):
|
|
428
|
-
with reader(
|
|
429
|
-
asset_info["
|
|
430
|
-
tms=self.tms,
|
|
431
|
-
**{**self.reader_options, **options},
|
|
432
|
-
) as src:
|
|
433
|
-
return src.info()
|
|
410
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
411
|
+
return src.info(**asset_info["method_options"])
|
|
434
412
|
|
|
435
413
|
return multi_values(assets, _reader, **kwargs)
|
|
436
414
|
|
|
437
415
|
def statistics(
|
|
438
416
|
self,
|
|
439
417
|
assets: Sequence[str] | str | None = None,
|
|
440
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
441
|
-
asset_expression: dict[str, str] | None = None,
|
|
442
418
|
**kwargs: Any,
|
|
443
419
|
) -> dict[str, dict[str, BandStatistics]]:
|
|
444
420
|
"""Return array statistics for multiple assets.
|
|
445
421
|
|
|
446
422
|
Args:
|
|
447
423
|
assets (sequence of str or str): assets to fetch info from.
|
|
448
|
-
asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
|
|
449
|
-
asset_expression (dict, optional): rio-tiler expression for each asset (e.g. {"asset1": "b1/b2+b3", "asset2": ...}).
|
|
450
424
|
kwargs (optional): Options to forward to the `self.reader.statistics` method.
|
|
451
425
|
|
|
452
426
|
Returns:
|
|
@@ -460,25 +434,16 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
460
434
|
)
|
|
461
435
|
|
|
462
436
|
assets = cast_to_sequence(assets or self.assets)
|
|
463
|
-
asset_indexes = asset_indexes or {}
|
|
464
|
-
asset_expression = asset_expression or {}
|
|
465
437
|
|
|
466
438
|
def _reader(asset: str, *args: Any, **kwargs: Any) -> dict:
|
|
467
439
|
asset_info = self._get_asset_info(asset)
|
|
468
|
-
reader,
|
|
440
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
469
441
|
|
|
442
|
+
options = {**self.reader_options, **reader_options}
|
|
470
443
|
with self.ctx(**asset_info.get("env", {})):
|
|
471
|
-
with reader(
|
|
472
|
-
asset_info["
|
|
473
|
-
|
|
474
|
-
**{**self.reader_options, **options},
|
|
475
|
-
) as src:
|
|
476
|
-
return src.statistics(
|
|
477
|
-
*args,
|
|
478
|
-
indexes=asset_indexes.get(asset, kwargs.pop("indexes", None)),
|
|
479
|
-
expression=asset_expression.get(asset),
|
|
480
|
-
**kwargs,
|
|
481
|
-
)
|
|
444
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
445
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
446
|
+
return src.statistics(*args, **method_options)
|
|
482
447
|
|
|
483
448
|
return multi_values(assets, _reader, **kwargs)
|
|
484
449
|
|
|
@@ -486,7 +451,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
486
451
|
self,
|
|
487
452
|
assets: Sequence[str] | str | None = None,
|
|
488
453
|
expression: str | None = None,
|
|
489
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
490
454
|
categorical: bool = False,
|
|
491
455
|
categories: list[float] | None = None,
|
|
492
456
|
percentiles: list[int] | None = None,
|
|
@@ -498,8 +462,7 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
498
462
|
|
|
499
463
|
Args:
|
|
500
464
|
assets (sequence of str or str): assets to fetch info from.
|
|
501
|
-
expression (str, optional): rio-tiler expression
|
|
502
|
-
asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
|
|
465
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
503
466
|
categorical (bool): treat input data as categorical data. Defaults to False.
|
|
504
467
|
categories (list of numbers, optional): list of categories to return value for.
|
|
505
468
|
percentiles (list of numbers, optional): list of percentile values to calculate. Defaults to `[2, 98]`.
|
|
@@ -512,18 +475,16 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
512
475
|
Dict[str, rio_tiler.models.BandStatistics]: bands statistics.
|
|
513
476
|
|
|
514
477
|
"""
|
|
515
|
-
if not
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
assets = cast_to_sequence(assets or self.assets)
|
|
478
|
+
if not assets:
|
|
479
|
+
warnings.warn(
|
|
480
|
+
"No `assets` option passed, will fetch statistics for all available assets.",
|
|
481
|
+
UserWarning,
|
|
482
|
+
)
|
|
483
|
+
assets = cast_to_sequence(assets or self.assets)
|
|
522
484
|
|
|
523
485
|
data = self.preview(
|
|
524
486
|
assets=assets,
|
|
525
487
|
expression=expression,
|
|
526
|
-
asset_indexes=asset_indexes,
|
|
527
488
|
max_size=max_size,
|
|
528
489
|
**kwargs,
|
|
529
490
|
)
|
|
@@ -541,7 +502,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
541
502
|
tile_z: int,
|
|
542
503
|
assets: Sequence[str] | str | None = None,
|
|
543
504
|
expression: str | None = None,
|
|
544
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
545
505
|
asset_as_band: bool = False,
|
|
546
506
|
**kwargs: Any,
|
|
547
507
|
) -> ImageData:
|
|
@@ -552,87 +512,73 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
552
512
|
tile_y (int): Tile's vertical index.
|
|
553
513
|
tile_z (int): Tile's zoom level index.
|
|
554
514
|
assets (sequence of str or str, optional): assets to fetch info from.
|
|
555
|
-
expression (str, optional): rio-tiler expression
|
|
556
|
-
|
|
515
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
516
|
+
asset_as_band (bool, optional): treat each asset as a separate band. Defaults to False.
|
|
557
517
|
kwargs (optional): Options to forward to the `self.reader.tile` method.
|
|
558
518
|
|
|
559
519
|
Returns:
|
|
560
520
|
rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
|
|
561
521
|
|
|
562
522
|
"""
|
|
523
|
+
if kwargs.pop("asset_indexes", None):
|
|
524
|
+
warnings.warn(
|
|
525
|
+
"`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
|
|
526
|
+
DeprecationWarning,
|
|
527
|
+
)
|
|
528
|
+
|
|
563
529
|
if not self.tile_exists(tile_x, tile_y, tile_z):
|
|
564
530
|
raise TileOutsideBounds(
|
|
565
531
|
f"Tile(x={tile_x}, y={tile_y}, z={tile_z}) is outside bounds"
|
|
566
532
|
)
|
|
567
533
|
|
|
568
534
|
assets = cast_to_sequence(assets)
|
|
569
|
-
if assets and expression:
|
|
570
|
-
warnings.warn(
|
|
571
|
-
"Both expression and assets passed; expression will overwrite assets parameter.",
|
|
572
|
-
ExpressionMixingWarning,
|
|
573
|
-
)
|
|
574
|
-
|
|
575
|
-
if expression:
|
|
576
|
-
assets = self.parse_expression(expression, asset_as_band=asset_as_band)
|
|
577
|
-
|
|
578
535
|
if not assets and self.default_assets:
|
|
579
536
|
warnings.warn(
|
|
580
|
-
f"No assets
|
|
537
|
+
f"No assets passed, defaults to {self.default_assets}",
|
|
581
538
|
UserWarning,
|
|
582
539
|
)
|
|
583
540
|
assets = self.default_assets
|
|
584
541
|
|
|
585
542
|
if not assets:
|
|
586
543
|
raise MissingAssets(
|
|
587
|
-
"
|
|
544
|
+
"No Asset defined by `assets` option or class-level `default_assets`."
|
|
588
545
|
)
|
|
589
546
|
|
|
590
|
-
asset_indexes = asset_indexes or {}
|
|
591
|
-
|
|
592
|
-
# We fall back to `indexes` if provided
|
|
593
|
-
indexes = kwargs.pop("indexes", None)
|
|
594
|
-
|
|
595
547
|
def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
596
|
-
idx = asset_indexes.get(asset) or indexes
|
|
597
|
-
|
|
598
548
|
asset_info = self._get_asset_info(asset)
|
|
599
|
-
|
|
549
|
+
asset_name = asset_info["name"]
|
|
550
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
600
551
|
|
|
552
|
+
options = {**self.reader_options, **reader_options}
|
|
601
553
|
with self.ctx(**asset_info.get("env", {})):
|
|
602
|
-
with reader(
|
|
603
|
-
asset_info["
|
|
604
|
-
|
|
605
|
-
**{**self.reader_options, **options},
|
|
606
|
-
) as src:
|
|
607
|
-
data = src.tile(*args, indexes=idx, **kwargs)
|
|
554
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
555
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
556
|
+
data = src.tile(*args, **method_options)
|
|
608
557
|
|
|
609
558
|
self._update_statistics(
|
|
610
|
-
data,
|
|
611
|
-
indexes=idx,
|
|
612
|
-
statistics=asset_info.get("dataset_statistics"),
|
|
559
|
+
data, statistics=asset_info.get("dataset_statistics")
|
|
613
560
|
)
|
|
614
561
|
|
|
615
562
|
metadata = data.metadata or {}
|
|
616
563
|
if m := asset_info.get("metadata"):
|
|
617
564
|
metadata.update(m)
|
|
565
|
+
data.metadata = {asset_name: metadata}
|
|
618
566
|
data.metadata = {asset: metadata}
|
|
619
567
|
|
|
568
|
+
data.band_descriptions = [
|
|
569
|
+
f"{asset_name}_{n}" for n in data.band_descriptions
|
|
570
|
+
]
|
|
620
571
|
if asset_as_band:
|
|
621
572
|
if len(data.band_names) > 1:
|
|
622
573
|
raise AssetAsBandError(
|
|
623
574
|
"Can't use `asset_as_band` for multibands asset"
|
|
624
575
|
)
|
|
625
|
-
data.
|
|
626
|
-
data.band_descriptions = [asset]
|
|
627
|
-
else:
|
|
628
|
-
data.band_names = [f"{asset}_{n}" for n in data.band_names]
|
|
629
|
-
data.band_descriptions = [
|
|
630
|
-
f"{asset}_{n}" for n in data.band_descriptions
|
|
631
|
-
]
|
|
576
|
+
data.band_descriptions = [asset_name]
|
|
632
577
|
|
|
633
578
|
return data
|
|
634
579
|
|
|
635
580
|
img = multi_arrays(assets, _reader, tile_x, tile_y, tile_z, **kwargs)
|
|
581
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
636
582
|
if expression:
|
|
637
583
|
return img.apply_expression(expression)
|
|
638
584
|
|
|
@@ -643,7 +589,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
643
589
|
bbox: BBox,
|
|
644
590
|
assets: Sequence[str] | str | None = None,
|
|
645
591
|
expression: str | None = None,
|
|
646
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
647
592
|
asset_as_band: bool = False,
|
|
648
593
|
**kwargs: Any,
|
|
649
594
|
) -> ImageData:
|
|
@@ -652,24 +597,21 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
652
597
|
Args:
|
|
653
598
|
bbox (tuple): Output bounds (left, bottom, right, top) in target crs.
|
|
654
599
|
assets (sequence of str or str, optional): assets to fetch info from.
|
|
655
|
-
expression (str, optional): rio-tiler expression
|
|
656
|
-
|
|
600
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
601
|
+
asset_as_band (bool, optional): treat each asset as a separate band. Defaults to False.
|
|
657
602
|
kwargs (optional): Options to forward to the `self.reader.part` method.
|
|
658
603
|
|
|
659
604
|
Returns:
|
|
660
605
|
rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
|
|
661
606
|
|
|
662
607
|
"""
|
|
663
|
-
|
|
664
|
-
if assets and expression:
|
|
608
|
+
if kwargs.pop("asset_indexes", None):
|
|
665
609
|
warnings.warn(
|
|
666
|
-
"
|
|
667
|
-
|
|
610
|
+
"`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
|
|
611
|
+
DeprecationWarning,
|
|
668
612
|
)
|
|
669
613
|
|
|
670
|
-
|
|
671
|
-
assets = self.parse_expression(expression, asset_as_band=asset_as_band)
|
|
672
|
-
|
|
614
|
+
assets = cast_to_sequence(assets)
|
|
673
615
|
if not assets and self.default_assets:
|
|
674
616
|
warnings.warn(
|
|
675
617
|
f"No assets/expression passed, defaults to {self.default_assets}",
|
|
@@ -679,55 +621,44 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
679
621
|
|
|
680
622
|
if not assets:
|
|
681
623
|
raise MissingAssets(
|
|
682
|
-
"
|
|
624
|
+
"No Asset defined by `assets` option or class-level `default_assets`."
|
|
683
625
|
)
|
|
684
626
|
|
|
685
|
-
asset_indexes = asset_indexes or {}
|
|
686
|
-
|
|
687
|
-
# We fall back to `indexes` if provided
|
|
688
|
-
indexes = kwargs.pop("indexes", None)
|
|
689
|
-
|
|
690
627
|
def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
691
|
-
idx = asset_indexes.get(asset) or indexes
|
|
692
|
-
|
|
693
628
|
asset_info = self._get_asset_info(asset)
|
|
694
|
-
|
|
629
|
+
asset_name = asset_info["name"]
|
|
630
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
695
631
|
|
|
632
|
+
options = {**self.reader_options, **reader_options}
|
|
696
633
|
with self.ctx(**asset_info.get("env", {})):
|
|
697
|
-
with reader(
|
|
698
|
-
asset_info["
|
|
699
|
-
|
|
700
|
-
**{**self.reader_options, **options},
|
|
701
|
-
) as src:
|
|
702
|
-
data = src.part(*args, indexes=idx, **kwargs)
|
|
634
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
635
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
636
|
+
data = src.part(*args, **method_options)
|
|
703
637
|
|
|
704
638
|
self._update_statistics(
|
|
705
639
|
data,
|
|
706
|
-
indexes=idx,
|
|
707
640
|
statistics=asset_info.get("dataset_statistics"),
|
|
708
641
|
)
|
|
709
642
|
|
|
710
643
|
metadata = data.metadata or {}
|
|
711
644
|
if m := asset_info.get("metadata"):
|
|
712
645
|
metadata.update(m)
|
|
713
|
-
data.metadata = {
|
|
646
|
+
data.metadata = {asset_name: metadata}
|
|
714
647
|
|
|
648
|
+
data.band_descriptions = [
|
|
649
|
+
f"{asset_name}_{n}" for n in data.band_descriptions
|
|
650
|
+
]
|
|
715
651
|
if asset_as_band:
|
|
716
652
|
if len(data.band_names) > 1:
|
|
717
653
|
raise AssetAsBandError(
|
|
718
654
|
"Can't use `asset_as_band` for multibands asset"
|
|
719
655
|
)
|
|
720
|
-
data.
|
|
721
|
-
data.band_descriptions = [asset]
|
|
722
|
-
else:
|
|
723
|
-
data.band_names = [f"{asset}_{n}" for n in data.band_names]
|
|
724
|
-
data.band_descriptions = [
|
|
725
|
-
f"{asset}_{n}" for n in data.band_descriptions
|
|
726
|
-
]
|
|
656
|
+
data.band_descriptions = [asset_name]
|
|
727
657
|
|
|
728
658
|
return data
|
|
729
659
|
|
|
730
660
|
img = multi_arrays(assets, _reader, bbox, **kwargs)
|
|
661
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
731
662
|
if expression:
|
|
732
663
|
return img.apply_expression(expression)
|
|
733
664
|
|
|
@@ -737,7 +668,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
737
668
|
self,
|
|
738
669
|
assets: Sequence[str] | str | None = None,
|
|
739
670
|
expression: str | None = None,
|
|
740
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
741
671
|
asset_as_band: bool = False,
|
|
742
672
|
**kwargs: Any,
|
|
743
673
|
) -> ImageData:
|
|
@@ -745,82 +675,68 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
745
675
|
|
|
746
676
|
Args:
|
|
747
677
|
assets (sequence of str or str, optional): assets to fetch info from.
|
|
748
|
-
expression (str, optional): rio-tiler expression
|
|
749
|
-
|
|
678
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
679
|
+
asset_as_band (bool, optional): treat each asset as a separate band. Defaults to False.
|
|
750
680
|
kwargs (optional): Options to forward to the `self.reader.preview` method.
|
|
751
681
|
|
|
752
682
|
Returns:
|
|
753
683
|
rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
|
|
754
684
|
|
|
755
685
|
"""
|
|
756
|
-
|
|
757
|
-
if assets and expression:
|
|
686
|
+
if kwargs.pop("asset_indexes", None):
|
|
758
687
|
warnings.warn(
|
|
759
|
-
"
|
|
760
|
-
|
|
688
|
+
"`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
|
|
689
|
+
DeprecationWarning,
|
|
761
690
|
)
|
|
762
691
|
|
|
763
|
-
|
|
764
|
-
assets = self.parse_expression(expression, asset_as_band=asset_as_band)
|
|
765
|
-
|
|
692
|
+
assets = cast_to_sequence(assets)
|
|
766
693
|
if not assets and self.default_assets:
|
|
767
694
|
warnings.warn(
|
|
768
|
-
f"No assets
|
|
695
|
+
f"No assets passed, defaults to {self.default_assets}",
|
|
769
696
|
UserWarning,
|
|
770
697
|
)
|
|
771
698
|
assets = self.default_assets
|
|
772
699
|
|
|
773
700
|
if not assets:
|
|
774
701
|
raise MissingAssets(
|
|
775
|
-
"
|
|
702
|
+
"No Asset defined by `assets` option or class-level `default_assets`."
|
|
776
703
|
)
|
|
777
704
|
|
|
778
|
-
asset_indexes = asset_indexes or {}
|
|
779
|
-
|
|
780
|
-
# We fall back to `indexes` if provided
|
|
781
|
-
indexes = kwargs.pop("indexes", None)
|
|
782
|
-
|
|
783
705
|
def _reader(asset: str, **kwargs: Any) -> ImageData:
|
|
784
|
-
idx = asset_indexes.get(asset) or indexes
|
|
785
|
-
|
|
786
706
|
asset_info = self._get_asset_info(asset)
|
|
787
|
-
|
|
707
|
+
asset_name = asset_info["name"]
|
|
708
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
788
709
|
|
|
710
|
+
options = {**self.reader_options, **reader_options}
|
|
789
711
|
with self.ctx(**asset_info.get("env", {})):
|
|
790
|
-
with reader(
|
|
791
|
-
asset_info["
|
|
792
|
-
|
|
793
|
-
**{**self.reader_options, **options},
|
|
794
|
-
) as src:
|
|
795
|
-
data = src.preview(indexes=idx, **kwargs)
|
|
712
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
713
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
714
|
+
data = src.preview(**method_options)
|
|
796
715
|
|
|
797
716
|
self._update_statistics(
|
|
798
717
|
data,
|
|
799
|
-
indexes=idx,
|
|
800
718
|
statistics=asset_info.get("dataset_statistics"),
|
|
801
719
|
)
|
|
802
720
|
|
|
803
721
|
metadata = data.metadata or {}
|
|
804
722
|
if m := asset_info.get("metadata"):
|
|
805
723
|
metadata.update(m)
|
|
806
|
-
data.metadata = {
|
|
724
|
+
data.metadata = {asset_name: metadata}
|
|
807
725
|
|
|
726
|
+
data.band_descriptions = [
|
|
727
|
+
f"{asset_name}_{n}" for n in data.band_descriptions
|
|
728
|
+
]
|
|
808
729
|
if asset_as_band:
|
|
809
730
|
if len(data.band_names) > 1:
|
|
810
731
|
raise AssetAsBandError(
|
|
811
732
|
"Can't use `asset_as_band` for multibands asset"
|
|
812
733
|
)
|
|
813
|
-
data.
|
|
814
|
-
data.band_descriptions = [asset]
|
|
815
|
-
else:
|
|
816
|
-
data.band_names = [f"{asset}_{n}" for n in data.band_names]
|
|
817
|
-
data.band_descriptions = [
|
|
818
|
-
f"{asset}_{n}" for n in data.band_descriptions
|
|
819
|
-
]
|
|
734
|
+
data.band_descriptions = [asset_name]
|
|
820
735
|
|
|
821
736
|
return data
|
|
822
737
|
|
|
823
738
|
img = multi_arrays(assets, _reader, **kwargs)
|
|
739
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
824
740
|
if expression:
|
|
825
741
|
return img.apply_expression(expression)
|
|
826
742
|
|
|
@@ -832,7 +748,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
832
748
|
lat: float,
|
|
833
749
|
assets: Sequence[str] | str | None = None,
|
|
834
750
|
expression: str | None = None,
|
|
835
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
836
751
|
asset_as_band: bool = False,
|
|
837
752
|
**kwargs: Any,
|
|
838
753
|
) -> PointData:
|
|
@@ -842,76 +757,63 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
842
757
|
lon (float): Longitude.
|
|
843
758
|
lat (float): Latitude.
|
|
844
759
|
assets (sequence of str or str, optional): assets to fetch info from.
|
|
845
|
-
expression (str, optional): rio-tiler expression
|
|
846
|
-
|
|
760
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
761
|
+
asset_as_band (bool, optional): treat each asset as a separate band. Defaults to False.
|
|
847
762
|
kwargs (optional): Options to forward to the `self.reader.point` method.
|
|
848
763
|
|
|
849
764
|
Returns:
|
|
850
765
|
PointData
|
|
851
766
|
|
|
852
767
|
"""
|
|
853
|
-
|
|
854
|
-
if assets and expression:
|
|
768
|
+
if kwargs.pop("asset_indexes", None):
|
|
855
769
|
warnings.warn(
|
|
856
|
-
"
|
|
857
|
-
|
|
770
|
+
"`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
|
|
771
|
+
DeprecationWarning,
|
|
858
772
|
)
|
|
859
773
|
|
|
860
|
-
|
|
861
|
-
assets = self.parse_expression(expression, asset_as_band=asset_as_band)
|
|
862
|
-
|
|
774
|
+
assets = cast_to_sequence(assets)
|
|
863
775
|
if not assets and self.default_assets:
|
|
864
776
|
warnings.warn(
|
|
865
|
-
f"No assets
|
|
777
|
+
f"No assets passed, defaults to {self.default_assets}",
|
|
866
778
|
UserWarning,
|
|
867
779
|
)
|
|
868
780
|
assets = self.default_assets
|
|
869
781
|
|
|
870
782
|
if not assets:
|
|
871
783
|
raise MissingAssets(
|
|
872
|
-
"
|
|
784
|
+
"No Asset defined by `assets` option or class-level `default_assets`."
|
|
873
785
|
)
|
|
874
786
|
|
|
875
|
-
asset_indexes = asset_indexes or {}
|
|
876
|
-
|
|
877
|
-
# We fall back to `indexes` if provided
|
|
878
|
-
indexes = kwargs.pop("indexes", None)
|
|
879
|
-
|
|
880
787
|
def _reader(asset: str, *args: Any, **kwargs: Any) -> PointData:
|
|
881
|
-
idx = asset_indexes.get(asset) or indexes
|
|
882
|
-
|
|
883
788
|
asset_info = self._get_asset_info(asset)
|
|
884
|
-
|
|
789
|
+
asset_name = asset_info["name"]
|
|
790
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
885
791
|
|
|
792
|
+
options = {**self.reader_options, **reader_options}
|
|
886
793
|
with self.ctx(**asset_info.get("env", {})):
|
|
887
|
-
with reader(
|
|
888
|
-
asset_info["
|
|
889
|
-
|
|
890
|
-
**{**self.reader_options, **options},
|
|
891
|
-
) as src:
|
|
892
|
-
data = src.point(*args, indexes=idx, **kwargs)
|
|
794
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
795
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
796
|
+
data = src.point(*args, **method_options)
|
|
893
797
|
|
|
894
798
|
metadata = data.metadata or {}
|
|
895
799
|
if m := asset_info.get("metadata"):
|
|
896
800
|
metadata.update(m)
|
|
897
|
-
data.metadata = {
|
|
801
|
+
data.metadata = {asset_name: metadata}
|
|
898
802
|
|
|
803
|
+
data.band_descriptions = [
|
|
804
|
+
f"{asset_name}_{n}" for n in data.band_descriptions
|
|
805
|
+
]
|
|
899
806
|
if asset_as_band:
|
|
900
807
|
if len(data.band_names) > 1:
|
|
901
808
|
raise AssetAsBandError(
|
|
902
809
|
"Can't use `asset_as_band` for multibands asset"
|
|
903
810
|
)
|
|
904
|
-
data.
|
|
905
|
-
data.band_descriptions = [asset]
|
|
906
|
-
else:
|
|
907
|
-
data.band_names = [f"{asset}_{n}" for n in data.band_names]
|
|
908
|
-
data.band_descriptions = [
|
|
909
|
-
f"{asset}_{n}" for n in data.band_descriptions
|
|
910
|
-
]
|
|
811
|
+
data.band_descriptions = [asset_name]
|
|
911
812
|
|
|
912
813
|
return data
|
|
913
814
|
|
|
914
815
|
data = multi_points(assets, _reader, lon, lat, **kwargs)
|
|
816
|
+
data.band_names = [f"b{ix + 1}" for ix in range(data.count)]
|
|
915
817
|
if expression:
|
|
916
818
|
return data.apply_expression(expression)
|
|
917
819
|
|
|
@@ -922,7 +824,6 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
922
824
|
shape: dict,
|
|
923
825
|
assets: Sequence[str] | str | None = None,
|
|
924
826
|
expression: str | None = None,
|
|
925
|
-
asset_indexes: dict[str, Indexes] | None = None,
|
|
926
827
|
asset_as_band: bool = False,
|
|
927
828
|
**kwargs: Any,
|
|
928
829
|
) -> ImageData:
|
|
@@ -931,24 +832,21 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
931
832
|
Args:
|
|
932
833
|
shape (dict): Valid GeoJSON feature.
|
|
933
834
|
assets (sequence of str or str, optional): assets to fetch info from.
|
|
934
|
-
expression (str, optional): rio-tiler expression
|
|
935
|
-
|
|
835
|
+
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
|
|
836
|
+
asset_as_band (bool, optional): treat each asset as a separate band. Defaults to False.
|
|
936
837
|
kwargs (optional): Options to forward to the `self.reader.feature` method.
|
|
937
838
|
|
|
938
839
|
Returns:
|
|
939
840
|
rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
|
|
940
841
|
|
|
941
842
|
"""
|
|
942
|
-
|
|
943
|
-
if assets and expression:
|
|
843
|
+
if kwargs.pop("asset_indexes", None):
|
|
944
844
|
warnings.warn(
|
|
945
|
-
"
|
|
946
|
-
|
|
845
|
+
"`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
|
|
846
|
+
DeprecationWarning,
|
|
947
847
|
)
|
|
948
848
|
|
|
949
|
-
|
|
950
|
-
assets = self.parse_expression(expression, asset_as_band=asset_as_band)
|
|
951
|
-
|
|
849
|
+
assets = cast_to_sequence(assets)
|
|
952
850
|
if not assets and self.default_assets:
|
|
953
851
|
warnings.warn(
|
|
954
852
|
f"No assets/expression passed, defaults to {self.default_assets}",
|
|
@@ -958,55 +856,44 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
958
856
|
|
|
959
857
|
if not assets:
|
|
960
858
|
raise MissingAssets(
|
|
961
|
-
"
|
|
859
|
+
"No Asset defined by `assets` option or class-level `default_assets`."
|
|
962
860
|
)
|
|
963
861
|
|
|
964
|
-
asset_indexes = asset_indexes or {}
|
|
965
|
-
|
|
966
|
-
# We fall back to `indexes` if provided
|
|
967
|
-
indexes = kwargs.pop("indexes", None)
|
|
968
|
-
|
|
969
862
|
def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
970
|
-
idx = asset_indexes.get(asset) or indexes
|
|
971
|
-
|
|
972
863
|
asset_info = self._get_asset_info(asset)
|
|
973
|
-
|
|
864
|
+
asset_name = asset_info["name"]
|
|
865
|
+
reader, reader_options = self._get_reader(asset_info)
|
|
974
866
|
|
|
867
|
+
options = {**self.reader_options, **reader_options}
|
|
975
868
|
with self.ctx(**asset_info.get("env", {})):
|
|
976
|
-
with reader(
|
|
977
|
-
asset_info["
|
|
978
|
-
|
|
979
|
-
**{**self.reader_options, **options},
|
|
980
|
-
) as src:
|
|
981
|
-
data = src.feature(*args, indexes=idx, **kwargs)
|
|
869
|
+
with reader(asset_info["url"], tms=self.tms, **options) as src:
|
|
870
|
+
method_options = {**asset_info["method_options"], **kwargs}
|
|
871
|
+
data = src.feature(*args, **method_options)
|
|
982
872
|
|
|
983
873
|
self._update_statistics(
|
|
984
874
|
data,
|
|
985
|
-
indexes=idx,
|
|
986
875
|
statistics=asset_info.get("dataset_statistics"),
|
|
987
876
|
)
|
|
988
877
|
|
|
989
878
|
metadata = data.metadata or {}
|
|
990
879
|
if m := asset_info.get("metadata"):
|
|
991
880
|
metadata.update(m)
|
|
992
|
-
data.metadata = {
|
|
881
|
+
data.metadata = {asset_name: metadata}
|
|
993
882
|
|
|
883
|
+
data.band_descriptions = [
|
|
884
|
+
f"{asset_name}_{n}" for n in data.band_descriptions
|
|
885
|
+
]
|
|
994
886
|
if asset_as_band:
|
|
995
887
|
if len(data.band_names) > 1:
|
|
996
888
|
raise AssetAsBandError(
|
|
997
889
|
"Can't use `asset_as_band` for multibands asset"
|
|
998
890
|
)
|
|
999
|
-
data.
|
|
1000
|
-
data.band_descriptions = [asset]
|
|
1001
|
-
else:
|
|
1002
|
-
data.band_names = [f"{asset}_{n}" for n in data.band_names]
|
|
1003
|
-
data.band_descriptions = [
|
|
1004
|
-
f"{asset}_{n}" for n in data.band_descriptions
|
|
1005
|
-
]
|
|
891
|
+
data.band_descriptions = [asset_name]
|
|
1006
892
|
|
|
1007
893
|
return data
|
|
1008
894
|
|
|
1009
895
|
img = multi_arrays(assets, _reader, shape, **kwargs)
|
|
896
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
1010
897
|
if expression:
|
|
1011
898
|
return img.apply_expression(expression)
|
|
1012
899
|
|
|
@@ -1040,6 +927,14 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1040
927
|
bands: Sequence[str] = attr.ib(init=False)
|
|
1041
928
|
default_bands: Sequence[str] | None = attr.ib(init=False, default=None)
|
|
1042
929
|
|
|
930
|
+
def __attrs_post_init__(self):
|
|
931
|
+
"""deprecation warning."""
|
|
932
|
+
warnings.warn(
|
|
933
|
+
"MultiBandReader is deprecated and will be removed in a future release. "
|
|
934
|
+
"Please use MultiBaseReader instead.",
|
|
935
|
+
DeprecationWarning,
|
|
936
|
+
)
|
|
937
|
+
|
|
1043
938
|
def __enter__(self):
|
|
1044
939
|
"""Support using with Context Managers."""
|
|
1045
940
|
return self
|
|
@@ -1066,6 +961,13 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1066
961
|
|
|
1067
962
|
return bands
|
|
1068
963
|
|
|
964
|
+
def _expression_to_bidx(self, bands: Sequence[str], expression: str) -> str:
|
|
965
|
+
mapexpr = {b: f"b{idx + 1}" for idx, b in enumerate(bands)}
|
|
966
|
+
for b in bands:
|
|
967
|
+
_re = re.compile(rf"\b{b}\b")
|
|
968
|
+
expression = _re.sub(mapexpr[b], expression)
|
|
969
|
+
return expression
|
|
970
|
+
|
|
1069
971
|
def info(
|
|
1070
972
|
self,
|
|
1071
973
|
bands: Sequence[str] | str | None = None,
|
|
@@ -1090,11 +992,7 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1090
992
|
|
|
1091
993
|
def _reader(band: str, **kwargs: Any) -> Info:
|
|
1092
994
|
url = self._get_band_url(band)
|
|
1093
|
-
with self.reader(
|
|
1094
|
-
url,
|
|
1095
|
-
tms=self.tms,
|
|
1096
|
-
**self.reader_options,
|
|
1097
|
-
) as src:
|
|
995
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1098
996
|
return src.info()
|
|
1099
997
|
|
|
1100
998
|
bands_metadata = multi_values(bands, _reader, **kwargs)
|
|
@@ -1220,24 +1118,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1220
1118
|
|
|
1221
1119
|
def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
1222
1120
|
url = self._get_band_url(band)
|
|
1223
|
-
with self.reader(
|
|
1224
|
-
url,
|
|
1225
|
-
tms=self.tms,
|
|
1226
|
-
**self.reader_options,
|
|
1227
|
-
) as src:
|
|
1121
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1228
1122
|
data = src.tile(*args, **kwargs)
|
|
1229
|
-
|
|
1230
1123
|
if data.metadata:
|
|
1231
1124
|
data.metadata = {band: data.metadata}
|
|
1232
1125
|
|
|
1233
|
-
# use `band` as name instead of band index
|
|
1234
|
-
data.
|
|
1235
|
-
|
|
1126
|
+
# use `band` as name/description instead of band index
|
|
1127
|
+
data.band_descriptions = [band]
|
|
1236
1128
|
return data
|
|
1237
1129
|
|
|
1238
1130
|
img = multi_arrays(bands, _reader, tile_x, tile_y, tile_z, **kwargs)
|
|
1239
|
-
|
|
1131
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
1240
1132
|
if expression:
|
|
1133
|
+
expression = self._expression_to_bidx(img.band_descriptions, expression)
|
|
1241
1134
|
return img.apply_expression(expression)
|
|
1242
1135
|
|
|
1243
1136
|
return img
|
|
@@ -1285,24 +1178,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1285
1178
|
|
|
1286
1179
|
def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
1287
1180
|
url = self._get_band_url(band)
|
|
1288
|
-
with self.reader(
|
|
1289
|
-
url,
|
|
1290
|
-
tms=self.tms,
|
|
1291
|
-
**self.reader_options,
|
|
1292
|
-
) as src:
|
|
1181
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1293
1182
|
data = src.part(*args, **kwargs)
|
|
1294
|
-
|
|
1295
1183
|
if data.metadata:
|
|
1296
1184
|
data.metadata = {band: data.metadata}
|
|
1297
1185
|
|
|
1298
|
-
# use `band` as name instead of band index
|
|
1299
|
-
data.
|
|
1300
|
-
|
|
1186
|
+
# use `band` as name/description instead of band index
|
|
1187
|
+
data.band_descriptions = [band]
|
|
1301
1188
|
return data
|
|
1302
1189
|
|
|
1303
1190
|
img = multi_arrays(bands, _reader, bbox, **kwargs)
|
|
1304
|
-
|
|
1191
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
1305
1192
|
if expression:
|
|
1193
|
+
expression = self._expression_to_bidx(img.band_descriptions, expression)
|
|
1306
1194
|
return img.apply_expression(expression)
|
|
1307
1195
|
|
|
1308
1196
|
return img
|
|
@@ -1348,24 +1236,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1348
1236
|
|
|
1349
1237
|
def _reader(band: str, **kwargs: Any) -> ImageData:
|
|
1350
1238
|
url = self._get_band_url(band)
|
|
1351
|
-
with self.reader(
|
|
1352
|
-
url,
|
|
1353
|
-
tms=self.tms,
|
|
1354
|
-
**self.reader_options,
|
|
1355
|
-
) as src:
|
|
1239
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1356
1240
|
data = src.preview(**kwargs)
|
|
1357
|
-
|
|
1358
1241
|
if data.metadata:
|
|
1359
1242
|
data.metadata = {band: data.metadata}
|
|
1360
1243
|
|
|
1361
|
-
# use `band` as name instead of band index
|
|
1362
|
-
data.
|
|
1363
|
-
|
|
1244
|
+
# use `band` as name/description instead of band index
|
|
1245
|
+
data.band_descriptions = [band]
|
|
1364
1246
|
return data
|
|
1365
1247
|
|
|
1366
1248
|
img = multi_arrays(bands, _reader, **kwargs)
|
|
1367
|
-
|
|
1249
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
1368
1250
|
if expression:
|
|
1251
|
+
expression = self._expression_to_bidx(img.band_descriptions, expression)
|
|
1369
1252
|
return img.apply_expression(expression)
|
|
1370
1253
|
|
|
1371
1254
|
return img
|
|
@@ -1415,23 +1298,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1415
1298
|
|
|
1416
1299
|
def _reader(band: str, *args: Any, **kwargs: Any) -> PointData:
|
|
1417
1300
|
url = self._get_band_url(band)
|
|
1418
|
-
with self.reader(
|
|
1419
|
-
url,
|
|
1420
|
-
tms=self.tms,
|
|
1421
|
-
**self.reader_options,
|
|
1422
|
-
) as src:
|
|
1301
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1423
1302
|
data = src.point(*args, **kwargs)
|
|
1424
|
-
|
|
1425
1303
|
if data.metadata:
|
|
1426
1304
|
data.metadata = {band: data.metadata}
|
|
1427
1305
|
|
|
1428
|
-
# use `band` as name instead of band index
|
|
1429
|
-
data.
|
|
1430
|
-
|
|
1306
|
+
# use `band` as name/description instead of band index
|
|
1307
|
+
data.band_descriptions = [band]
|
|
1431
1308
|
return data
|
|
1432
1309
|
|
|
1433
1310
|
data = multi_points(bands, _reader, lon, lat, **kwargs)
|
|
1311
|
+
data.band_names = [f"b{ix + 1}" for ix in range(data.count)]
|
|
1434
1312
|
if expression:
|
|
1313
|
+
expression = self._expression_to_bidx(data.band_descriptions, expression)
|
|
1435
1314
|
return data.apply_expression(expression)
|
|
1436
1315
|
|
|
1437
1316
|
return data
|
|
@@ -1479,24 +1358,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
|
|
|
1479
1358
|
|
|
1480
1359
|
def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
|
|
1481
1360
|
url = self._get_band_url(band)
|
|
1482
|
-
with self.reader(
|
|
1483
|
-
url,
|
|
1484
|
-
tms=self.tms,
|
|
1485
|
-
**self.reader_options,
|
|
1486
|
-
) as src:
|
|
1361
|
+
with self.reader(url, tms=self.tms, **self.reader_options) as src:
|
|
1487
1362
|
data = src.feature(*args, **kwargs)
|
|
1488
|
-
|
|
1489
1363
|
if data.metadata:
|
|
1490
1364
|
data.metadata = {band: data.metadata}
|
|
1491
1365
|
|
|
1492
|
-
# use `band` as name instead of band index
|
|
1493
|
-
data.
|
|
1494
|
-
|
|
1366
|
+
# use `band` as name/description instead of band index
|
|
1367
|
+
data.band_descriptions = [band]
|
|
1495
1368
|
return data
|
|
1496
1369
|
|
|
1497
1370
|
img = multi_arrays(bands, _reader, shape, **kwargs)
|
|
1498
|
-
|
|
1371
|
+
img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
|
|
1499
1372
|
if expression:
|
|
1373
|
+
expression = self._expression_to_bidx(img.band_descriptions, expression)
|
|
1500
1374
|
return img.apply_expression(expression)
|
|
1501
1375
|
|
|
1502
1376
|
return img
|