rio-tiler 8.0.4__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/io/base.py CHANGED
@@ -4,8 +4,9 @@ import abc
4
4
  import contextlib
5
5
  import re
6
6
  import warnings
7
+ from collections.abc import Sequence
7
8
  from functools import cached_property
8
- from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union, cast
9
+ from typing import Any, cast
9
10
 
10
11
  import attr
11
12
  import numpy
@@ -44,9 +45,9 @@ class SpatialMixin:
44
45
  bounds: BBox = attr.ib(init=False)
45
46
  crs: CRS = attr.ib(init=False)
46
47
 
47
- transform: Optional[Affine] = attr.ib(default=None, init=False)
48
- height: Optional[int] = attr.ib(default=None, init=False)
49
- width: Optional[int] = attr.ib(default=None, init=False)
48
+ transform: Affine | None = attr.ib(default=None, init=False)
49
+ height: int | None = attr.ib(default=None, init=False)
50
+ width: int | None = attr.ib(default=None, init=False)
50
51
 
51
52
  def get_geographic_bounds(self, crs: CRS) -> BBox:
52
53
  """Return Geographic Bounds for a Geographic CRS."""
@@ -238,11 +239,11 @@ class BaseReader(SpatialMixin, metaclass=abc.ABCMeta):
238
239
  ...
239
240
 
240
241
  @abc.abstractmethod
241
- def statistics(self) -> Dict[str, BandStatistics]:
242
+ def statistics(self) -> dict[str, BandStatistics]:
242
243
  """Return bands statistics from a dataset.
243
244
 
244
245
  Returns:
245
- Dict[str, rio_tiler.models.BandStatistics]: bands statistics.
246
+ dict[str, rio_tiler.models.BandStatistics]: bands statistics.
246
247
 
247
248
  """
248
249
  ...
@@ -300,7 +301,7 @@ class BaseReader(SpatialMixin, metaclass=abc.ABCMeta):
300
301
  ...
301
302
 
302
303
  @abc.abstractmethod
303
- def feature(self, shape: Dict) -> ImageData:
304
+ def feature(self, shape: dict) -> ImageData:
304
305
  """Read a Dataset for a GeoJSON feature.
305
306
 
306
307
  Args:
@@ -331,16 +332,16 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
331
332
  input: Any = attr.ib()
332
333
  tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
333
334
 
334
- minzoom: int = attr.ib(default=None)
335
- maxzoom: int = attr.ib(default=None)
335
+ minzoom: int | None = attr.ib(default=None)
336
+ maxzoom: int | None = attr.ib(default=None)
336
337
 
337
- reader: Type[BaseReader] = attr.ib(init=False)
338
- reader_options: Dict = attr.ib(factory=dict)
338
+ reader: type[BaseReader] = attr.ib(init=False)
339
+ reader_options: dict = attr.ib(factory=dict)
339
340
 
340
341
  assets: Sequence[str] = attr.ib(init=False)
341
- default_assets: Optional[Sequence[str]] = attr.ib(init=False, default=None)
342
+ default_assets: Sequence[str] | None = attr.ib(init=False, default=None)
342
343
 
343
- ctx: Type[contextlib.AbstractContextManager] = attr.ib(
344
+ ctx: type[contextlib.AbstractContextManager] = attr.ib(
344
345
  init=False, default=contextlib.nullcontext
345
346
  )
346
347
 
@@ -357,34 +358,15 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
357
358
  """Validate asset name and construct url."""
358
359
  ...
359
360
 
360
- def _get_reader(self, asset_info: AssetInfo) -> Tuple[Type[BaseReader], Dict]:
361
+ def _get_reader(self, asset_info: AssetInfo) -> tuple[type[BaseReader], dict]:
361
362
  """Get Asset Reader and options."""
362
363
  return self.reader, {}
363
364
 
364
- def parse_expression(self, expression: str, asset_as_band: bool = False) -> Tuple:
365
- """Parse rio-tiler band math expression."""
366
- input_assets = "|".join(self.assets)
367
-
368
- if asset_as_band:
369
- _re = re.compile(rf"\b({input_assets})\b")
370
- else:
371
- _re = re.compile(rf"\b({input_assets})_b\d+\b")
372
-
373
- assets = tuple(set(re.findall(_re, expression)))
374
- if not assets:
375
- raise InvalidExpression(
376
- f"Could not find any valid assets in '{expression}' expression. Assets are: {self.assets}"
377
- if asset_as_band
378
- else f"Could not find any valid assets in '{expression}' expression, maybe try with `asset_as_band=True`. Assets are: {self.assets}"
379
- )
380
-
381
- return assets
382
-
383
365
  def _update_statistics(
384
366
  self,
385
367
  img: ImageData,
386
- indexes: Optional[Indexes] = None,
387
- statistics: Optional[Sequence[Tuple[float, float]]] = None,
368
+ indexes: Indexes | None = None,
369
+ statistics: Sequence[tuple[float, float]] | None = None,
388
370
  ):
389
371
  """Update ImageData Statistics from AssetInfo."""
390
372
  indexes = cast_to_sequence(indexes)
@@ -400,9 +382,9 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
400
382
 
401
383
  def info(
402
384
  self,
403
- assets: Optional[Union[Sequence[str], str]] = None,
385
+ assets: Sequence[str] | str | None = None,
404
386
  **kwargs: Any,
405
- ) -> Dict[str, Info]:
387
+ ) -> dict[str, Info]:
406
388
  """Return metadata from multiple assets.
407
389
 
408
390
  Args:
@@ -419,33 +401,26 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
419
401
  )
420
402
  assets = cast_to_sequence(assets or self.assets)
421
403
 
422
- def _reader(asset: str, **kwargs: Any) -> Dict:
404
+ def _reader(asset: str, **kwargs: Any) -> dict:
423
405
  asset_info = self._get_asset_info(asset)
424
- reader, options = self._get_reader(asset_info)
406
+ reader, reader_options = self._get_reader(asset_info)
425
407
 
408
+ options = {**self.reader_options, **reader_options}
426
409
  with self.ctx(**asset_info.get("env", {})):
427
- with reader(
428
- asset_info["url"],
429
- tms=self.tms,
430
- **{**self.reader_options, **options},
431
- ) as src:
432
- return src.info()
410
+ with reader(asset_info["url"], tms=self.tms, **options) as src:
411
+ return src.info(**asset_info["method_options"])
433
412
 
434
413
  return multi_values(assets, _reader, **kwargs)
435
414
 
436
415
  def statistics(
437
416
  self,
438
- assets: Optional[Union[Sequence[str], str]] = None,
439
- asset_indexes: Optional[Dict[str, Indexes]] = None,
440
- asset_expression: Optional[Dict[str, str]] = None,
417
+ assets: Sequence[str] | str | None = None,
441
418
  **kwargs: Any,
442
- ) -> Dict[str, Dict[str, BandStatistics]]:
419
+ ) -> dict[str, dict[str, BandStatistics]]:
443
420
  """Return array statistics for multiple assets.
444
421
 
445
422
  Args:
446
423
  assets (sequence of str or str): assets to fetch info from.
447
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
448
- asset_expression (dict, optional): rio-tiler expression for each asset (e.g. {"asset1": "b1/b2+b3", "asset2": ...}).
449
424
  kwargs (optional): Options to forward to the `self.reader.statistics` method.
450
425
 
451
426
  Returns:
@@ -459,46 +434,35 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
459
434
  )
460
435
 
461
436
  assets = cast_to_sequence(assets or self.assets)
462
- asset_indexes = asset_indexes or {}
463
- asset_expression = asset_expression or {}
464
437
 
465
- def _reader(asset: str, *args: Any, **kwargs: Any) -> Dict:
438
+ def _reader(asset: str, *args: Any, **kwargs: Any) -> dict:
466
439
  asset_info = self._get_asset_info(asset)
467
- reader, options = self._get_reader(asset_info)
440
+ reader, reader_options = self._get_reader(asset_info)
468
441
 
442
+ options = {**self.reader_options, **reader_options}
469
443
  with self.ctx(**asset_info.get("env", {})):
470
- with reader(
471
- asset_info["url"],
472
- tms=self.tms,
473
- **{**self.reader_options, **options},
474
- ) as src:
475
- return src.statistics(
476
- *args,
477
- indexes=asset_indexes.get(asset, kwargs.pop("indexes", None)),
478
- expression=asset_expression.get(asset),
479
- **kwargs,
480
- )
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)
481
447
 
482
448
  return multi_values(assets, _reader, **kwargs)
483
449
 
484
450
  def merged_statistics(
485
451
  self,
486
- assets: Optional[Union[Sequence[str], str]] = None,
487
- expression: Optional[str] = None,
488
- asset_indexes: Optional[Dict[str, Indexes]] = None,
452
+ assets: Sequence[str] | str | None = None,
453
+ expression: str | None = None,
489
454
  categorical: bool = False,
490
- categories: Optional[List[float]] = None,
491
- percentiles: Optional[List[int]] = None,
492
- hist_options: Optional[Dict] = None,
455
+ categories: list[float] | None = None,
456
+ percentiles: list[int] | None = None,
457
+ hist_options: dict | None = None,
493
458
  max_size: int = 1024,
494
459
  **kwargs: Any,
495
- ) -> Dict[str, BandStatistics]:
460
+ ) -> dict[str, BandStatistics]:
496
461
  """Return array statistics for multiple assets.
497
462
 
498
463
  Args:
499
464
  assets (sequence of str or str): assets to fetch info from.
500
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
501
- 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).
502
466
  categorical (bool): treat input data as categorical data. Defaults to False.
503
467
  categories (list of numbers, optional): list of categories to return value for.
504
468
  percentiles (list of numbers, optional): list of percentile values to calculate. Defaults to `[2, 98]`.
@@ -511,18 +475,16 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
511
475
  Dict[str, rio_tiler.models.BandStatistics]: bands statistics.
512
476
 
513
477
  """
514
- if not expression:
515
- if not assets:
516
- warnings.warn(
517
- "No `assets` option passed, will fetch statistics for all available assets.",
518
- UserWarning,
519
- )
520
- 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)
521
484
 
522
485
  data = self.preview(
523
486
  assets=assets,
524
487
  expression=expression,
525
- asset_indexes=asset_indexes,
526
488
  max_size=max_size,
527
489
  **kwargs,
528
490
  )
@@ -538,9 +500,8 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
538
500
  tile_x: int,
539
501
  tile_y: int,
540
502
  tile_z: int,
541
- assets: Optional[Union[Sequence[str], str]] = None,
542
- expression: Optional[str] = None,
543
- asset_indexes: Optional[Dict[str, Indexes]] = None,
503
+ assets: Sequence[str] | str | None = None,
504
+ expression: str | None = None,
544
505
  asset_as_band: bool = False,
545
506
  **kwargs: Any,
546
507
  ) -> ImageData:
@@ -551,87 +512,73 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
551
512
  tile_y (int): Tile's vertical index.
552
513
  tile_z (int): Tile's zoom level index.
553
514
  assets (sequence of str or str, optional): assets to fetch info from.
554
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
555
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
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.
556
517
  kwargs (optional): Options to forward to the `self.reader.tile` method.
557
518
 
558
519
  Returns:
559
520
  rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
560
521
 
561
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
+
562
529
  if not self.tile_exists(tile_x, tile_y, tile_z):
563
530
  raise TileOutsideBounds(
564
531
  f"Tile(x={tile_x}, y={tile_y}, z={tile_z}) is outside bounds"
565
532
  )
566
533
 
567
534
  assets = cast_to_sequence(assets)
568
- if assets and expression:
569
- warnings.warn(
570
- "Both expression and assets passed; expression will overwrite assets parameter.",
571
- ExpressionMixingWarning,
572
- )
573
-
574
- if expression:
575
- assets = self.parse_expression(expression, asset_as_band=asset_as_band)
576
-
577
535
  if not assets and self.default_assets:
578
536
  warnings.warn(
579
- f"No assets/expression passed, defaults to {self.default_assets}",
537
+ f"No assets passed, defaults to {self.default_assets}",
580
538
  UserWarning,
581
539
  )
582
540
  assets = self.default_assets
583
541
 
584
542
  if not assets:
585
543
  raise MissingAssets(
586
- "assets must be passed via `expression` or `assets` options, or via class-level `default_assets`."
544
+ "No Asset defined by `assets` option or class-level `default_assets`."
587
545
  )
588
546
 
589
- asset_indexes = asset_indexes or {}
590
-
591
- # We fall back to `indexes` if provided
592
- indexes = kwargs.pop("indexes", None)
593
-
594
547
  def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
595
- idx = asset_indexes.get(asset) or indexes
596
-
597
548
  asset_info = self._get_asset_info(asset)
598
- reader, options = self._get_reader(asset_info)
549
+ asset_name = asset_info["name"]
550
+ reader, reader_options = self._get_reader(asset_info)
599
551
 
552
+ options = {**self.reader_options, **reader_options}
600
553
  with self.ctx(**asset_info.get("env", {})):
601
- with reader(
602
- asset_info["url"],
603
- tms=self.tms,
604
- **{**self.reader_options, **options},
605
- ) as src:
606
- 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)
607
557
 
608
558
  self._update_statistics(
609
- data,
610
- indexes=idx,
611
- statistics=asset_info.get("dataset_statistics"),
559
+ data, statistics=asset_info.get("dataset_statistics")
612
560
  )
613
561
 
614
562
  metadata = data.metadata or {}
615
563
  if m := asset_info.get("metadata"):
616
564
  metadata.update(m)
565
+ data.metadata = {asset_name: metadata}
617
566
  data.metadata = {asset: metadata}
618
567
 
568
+ data.band_descriptions = [
569
+ f"{asset_name}_{n}" for n in data.band_descriptions
570
+ ]
619
571
  if asset_as_band:
620
572
  if len(data.band_names) > 1:
621
573
  raise AssetAsBandError(
622
574
  "Can't use `asset_as_band` for multibands asset"
623
575
  )
624
- data.band_names = [asset]
625
- data.band_descriptions = [asset]
626
- else:
627
- data.band_names = [f"{asset}_{n}" for n in data.band_names]
628
- data.band_descriptions = [
629
- f"{asset}_{n}" for n in data.band_descriptions
630
- ]
576
+ data.band_descriptions = [asset_name]
631
577
 
632
578
  return data
633
579
 
634
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)]
635
582
  if expression:
636
583
  return img.apply_expression(expression)
637
584
 
@@ -640,9 +587,8 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
640
587
  def part(
641
588
  self,
642
589
  bbox: BBox,
643
- assets: Optional[Union[Sequence[str], str]] = None,
644
- expression: Optional[str] = None,
645
- asset_indexes: Optional[Dict[str, Indexes]] = None,
590
+ assets: Sequence[str] | str | None = None,
591
+ expression: str | None = None,
646
592
  asset_as_band: bool = False,
647
593
  **kwargs: Any,
648
594
  ) -> ImageData:
@@ -651,24 +597,21 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
651
597
  Args:
652
598
  bbox (tuple): Output bounds (left, bottom, right, top) in target crs.
653
599
  assets (sequence of str or str, optional): assets to fetch info from.
654
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
655
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
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.
656
602
  kwargs (optional): Options to forward to the `self.reader.part` method.
657
603
 
658
604
  Returns:
659
605
  rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
660
606
 
661
607
  """
662
- assets = cast_to_sequence(assets)
663
- if assets and expression:
608
+ if kwargs.pop("asset_indexes", None):
664
609
  warnings.warn(
665
- "Both expression and assets passed; expression will overwrite assets parameter.",
666
- ExpressionMixingWarning,
610
+ "`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
611
+ DeprecationWarning,
667
612
  )
668
613
 
669
- if expression:
670
- assets = self.parse_expression(expression, asset_as_band=asset_as_band)
671
-
614
+ assets = cast_to_sequence(assets)
672
615
  if not assets and self.default_assets:
673
616
  warnings.warn(
674
617
  f"No assets/expression passed, defaults to {self.default_assets}",
@@ -678,55 +621,44 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
678
621
 
679
622
  if not assets:
680
623
  raise MissingAssets(
681
- "assets must be passed via `expression` or `assets` options, or via class-level `default_assets`."
624
+ "No Asset defined by `assets` option or class-level `default_assets`."
682
625
  )
683
626
 
684
- asset_indexes = asset_indexes or {}
685
-
686
- # We fall back to `indexes` if provided
687
- indexes = kwargs.pop("indexes", None)
688
-
689
627
  def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
690
- idx = asset_indexes.get(asset) or indexes
691
-
692
628
  asset_info = self._get_asset_info(asset)
693
- reader, options = self._get_reader(asset_info)
629
+ asset_name = asset_info["name"]
630
+ reader, reader_options = self._get_reader(asset_info)
694
631
 
632
+ options = {**self.reader_options, **reader_options}
695
633
  with self.ctx(**asset_info.get("env", {})):
696
- with reader(
697
- asset_info["url"],
698
- tms=self.tms,
699
- **{**self.reader_options, **options},
700
- ) as src:
701
- 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)
702
637
 
703
638
  self._update_statistics(
704
639
  data,
705
- indexes=idx,
706
640
  statistics=asset_info.get("dataset_statistics"),
707
641
  )
708
642
 
709
643
  metadata = data.metadata or {}
710
644
  if m := asset_info.get("metadata"):
711
645
  metadata.update(m)
712
- data.metadata = {asset: metadata}
646
+ data.metadata = {asset_name: metadata}
713
647
 
648
+ data.band_descriptions = [
649
+ f"{asset_name}_{n}" for n in data.band_descriptions
650
+ ]
714
651
  if asset_as_band:
715
652
  if len(data.band_names) > 1:
716
653
  raise AssetAsBandError(
717
654
  "Can't use `asset_as_band` for multibands asset"
718
655
  )
719
- data.band_names = [asset]
720
- data.band_descriptions = [asset]
721
- else:
722
- data.band_names = [f"{asset}_{n}" for n in data.band_names]
723
- data.band_descriptions = [
724
- f"{asset}_{n}" for n in data.band_descriptions
725
- ]
656
+ data.band_descriptions = [asset_name]
726
657
 
727
658
  return data
728
659
 
729
660
  img = multi_arrays(assets, _reader, bbox, **kwargs)
661
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
730
662
  if expression:
731
663
  return img.apply_expression(expression)
732
664
 
@@ -734,9 +666,8 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
734
666
 
735
667
  def preview(
736
668
  self,
737
- assets: Optional[Union[Sequence[str], str]] = None,
738
- expression: Optional[str] = None,
739
- asset_indexes: Optional[Dict[str, Indexes]] = None,
669
+ assets: Sequence[str] | str | None = None,
670
+ expression: str | None = None,
740
671
  asset_as_band: bool = False,
741
672
  **kwargs: Any,
742
673
  ) -> ImageData:
@@ -744,82 +675,68 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
744
675
 
745
676
  Args:
746
677
  assets (sequence of str or str, optional): assets to fetch info from.
747
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
748
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
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.
749
680
  kwargs (optional): Options to forward to the `self.reader.preview` method.
750
681
 
751
682
  Returns:
752
683
  rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
753
684
 
754
685
  """
755
- assets = cast_to_sequence(assets)
756
- if assets and expression:
686
+ if kwargs.pop("asset_indexes", None):
757
687
  warnings.warn(
758
- "Both expression and assets passed; expression will overwrite assets parameter.",
759
- ExpressionMixingWarning,
688
+ "`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
689
+ DeprecationWarning,
760
690
  )
761
691
 
762
- if expression:
763
- assets = self.parse_expression(expression, asset_as_band=asset_as_band)
764
-
692
+ assets = cast_to_sequence(assets)
765
693
  if not assets and self.default_assets:
766
694
  warnings.warn(
767
- f"No assets/expression passed, defaults to {self.default_assets}",
695
+ f"No assets passed, defaults to {self.default_assets}",
768
696
  UserWarning,
769
697
  )
770
698
  assets = self.default_assets
771
699
 
772
700
  if not assets:
773
701
  raise MissingAssets(
774
- "assets must be passed via `expression` or `assets` options, or via class-level `default_assets`."
702
+ "No Asset defined by `assets` option or class-level `default_assets`."
775
703
  )
776
704
 
777
- asset_indexes = asset_indexes or {}
778
-
779
- # We fall back to `indexes` if provided
780
- indexes = kwargs.pop("indexes", None)
781
-
782
705
  def _reader(asset: str, **kwargs: Any) -> ImageData:
783
- idx = asset_indexes.get(asset) or indexes
784
-
785
706
  asset_info = self._get_asset_info(asset)
786
- reader, options = self._get_reader(asset_info)
707
+ asset_name = asset_info["name"]
708
+ reader, reader_options = self._get_reader(asset_info)
787
709
 
710
+ options = {**self.reader_options, **reader_options}
788
711
  with self.ctx(**asset_info.get("env", {})):
789
- with reader(
790
- asset_info["url"],
791
- tms=self.tms,
792
- **{**self.reader_options, **options},
793
- ) as src:
794
- 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)
795
715
 
796
716
  self._update_statistics(
797
717
  data,
798
- indexes=idx,
799
718
  statistics=asset_info.get("dataset_statistics"),
800
719
  )
801
720
 
802
721
  metadata = data.metadata or {}
803
722
  if m := asset_info.get("metadata"):
804
723
  metadata.update(m)
805
- data.metadata = {asset: metadata}
724
+ data.metadata = {asset_name: metadata}
806
725
 
726
+ data.band_descriptions = [
727
+ f"{asset_name}_{n}" for n in data.band_descriptions
728
+ ]
807
729
  if asset_as_band:
808
730
  if len(data.band_names) > 1:
809
731
  raise AssetAsBandError(
810
732
  "Can't use `asset_as_band` for multibands asset"
811
733
  )
812
- data.band_names = [asset]
813
- data.band_descriptions = [asset]
814
- else:
815
- data.band_names = [f"{asset}_{n}" for n in data.band_names]
816
- data.band_descriptions = [
817
- f"{asset}_{n}" for n in data.band_descriptions
818
- ]
734
+ data.band_descriptions = [asset_name]
819
735
 
820
736
  return data
821
737
 
822
738
  img = multi_arrays(assets, _reader, **kwargs)
739
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
823
740
  if expression:
824
741
  return img.apply_expression(expression)
825
742
 
@@ -829,9 +746,8 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
829
746
  self,
830
747
  lon: float,
831
748
  lat: float,
832
- assets: Optional[Union[Sequence[str], str]] = None,
833
- expression: Optional[str] = None,
834
- asset_indexes: Optional[Dict[str, Indexes]] = None,
749
+ assets: Sequence[str] | str | None = None,
750
+ expression: str | None = None,
835
751
  asset_as_band: bool = False,
836
752
  **kwargs: Any,
837
753
  ) -> PointData:
@@ -841,76 +757,63 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
841
757
  lon (float): Longitude.
842
758
  lat (float): Latitude.
843
759
  assets (sequence of str or str, optional): assets to fetch info from.
844
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
845
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
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.
846
762
  kwargs (optional): Options to forward to the `self.reader.point` method.
847
763
 
848
764
  Returns:
849
765
  PointData
850
766
 
851
767
  """
852
- assets = cast_to_sequence(assets)
853
- if assets and expression:
768
+ if kwargs.pop("asset_indexes", None):
854
769
  warnings.warn(
855
- "Both expression and assets passed; expression will overwrite assets parameter.",
856
- ExpressionMixingWarning,
770
+ "`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
771
+ DeprecationWarning,
857
772
  )
858
773
 
859
- if expression:
860
- assets = self.parse_expression(expression, asset_as_band=asset_as_band)
861
-
774
+ assets = cast_to_sequence(assets)
862
775
  if not assets and self.default_assets:
863
776
  warnings.warn(
864
- f"No assets/expression passed, defaults to {self.default_assets}",
777
+ f"No assets passed, defaults to {self.default_assets}",
865
778
  UserWarning,
866
779
  )
867
780
  assets = self.default_assets
868
781
 
869
782
  if not assets:
870
783
  raise MissingAssets(
871
- "assets must be passed via `expression` or `assets` options, or via class-level `default_assets`."
784
+ "No Asset defined by `assets` option or class-level `default_assets`."
872
785
  )
873
786
 
874
- asset_indexes = asset_indexes or {}
875
-
876
- # We fall back to `indexes` if provided
877
- indexes = kwargs.pop("indexes", None)
878
-
879
787
  def _reader(asset: str, *args: Any, **kwargs: Any) -> PointData:
880
- idx = asset_indexes.get(asset) or indexes
881
-
882
788
  asset_info = self._get_asset_info(asset)
883
- reader, options = self._get_reader(asset_info)
789
+ asset_name = asset_info["name"]
790
+ reader, reader_options = self._get_reader(asset_info)
884
791
 
792
+ options = {**self.reader_options, **reader_options}
885
793
  with self.ctx(**asset_info.get("env", {})):
886
- with reader(
887
- asset_info["url"],
888
- tms=self.tms,
889
- **{**self.reader_options, **options},
890
- ) as src:
891
- 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)
892
797
 
893
798
  metadata = data.metadata or {}
894
799
  if m := asset_info.get("metadata"):
895
800
  metadata.update(m)
896
- data.metadata = {asset: metadata}
801
+ data.metadata = {asset_name: metadata}
897
802
 
803
+ data.band_descriptions = [
804
+ f"{asset_name}_{n}" for n in data.band_descriptions
805
+ ]
898
806
  if asset_as_band:
899
807
  if len(data.band_names) > 1:
900
808
  raise AssetAsBandError(
901
809
  "Can't use `asset_as_band` for multibands asset"
902
810
  )
903
- data.band_names = [asset]
904
- data.band_descriptions = [asset]
905
- else:
906
- data.band_names = [f"{asset}_{n}" for n in data.band_names]
907
- data.band_descriptions = [
908
- f"{asset}_{n}" for n in data.band_descriptions
909
- ]
811
+ data.band_descriptions = [asset_name]
910
812
 
911
813
  return data
912
814
 
913
815
  data = multi_points(assets, _reader, lon, lat, **kwargs)
816
+ data.band_names = [f"b{ix + 1}" for ix in range(data.count)]
914
817
  if expression:
915
818
  return data.apply_expression(expression)
916
819
 
@@ -918,10 +821,9 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
918
821
 
919
822
  def feature(
920
823
  self,
921
- shape: Dict,
922
- assets: Optional[Union[Sequence[str], str]] = None,
923
- expression: Optional[str] = None,
924
- asset_indexes: Optional[Dict[str, Indexes]] = None,
824
+ shape: dict,
825
+ assets: Sequence[str] | str | None = None,
826
+ expression: str | None = None,
925
827
  asset_as_band: bool = False,
926
828
  **kwargs: Any,
927
829
  ) -> ImageData:
@@ -930,24 +832,21 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
930
832
  Args:
931
833
  shape (dict): Valid GeoJSON feature.
932
834
  assets (sequence of str or str, optional): assets to fetch info from.
933
- expression (str, optional): rio-tiler expression for the asset list (e.g. asset1/asset2+asset3).
934
- asset_indexes (dict, optional): Band indexes for each asset (e.g {"asset1": 1, "asset2": (1, 2,)}).
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.
935
837
  kwargs (optional): Options to forward to the `self.reader.feature` method.
936
838
 
937
839
  Returns:
938
840
  rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info.
939
841
 
940
842
  """
941
- assets = cast_to_sequence(assets)
942
- if assets and expression:
843
+ if kwargs.pop("asset_indexes", None):
943
844
  warnings.warn(
944
- "Both expression and assets passed; expression will overwrite assets parameter.",
945
- ExpressionMixingWarning,
845
+ "`asset_indexes` parameter is deprecated in `tile` method and will be ignored.",
846
+ DeprecationWarning,
946
847
  )
947
848
 
948
- if expression:
949
- assets = self.parse_expression(expression, asset_as_band=asset_as_band)
950
-
849
+ assets = cast_to_sequence(assets)
951
850
  if not assets and self.default_assets:
952
851
  warnings.warn(
953
852
  f"No assets/expression passed, defaults to {self.default_assets}",
@@ -957,55 +856,44 @@ class MultiBaseReader(SpatialMixin, metaclass=abc.ABCMeta):
957
856
 
958
857
  if not assets:
959
858
  raise MissingAssets(
960
- "assets must be passed via `expression` or `assets` options, or via class-level `default_assets`."
859
+ "No Asset defined by `assets` option or class-level `default_assets`."
961
860
  )
962
861
 
963
- asset_indexes = asset_indexes or {}
964
-
965
- # We fall back to `indexes` if provided
966
- indexes = kwargs.pop("indexes", None)
967
-
968
862
  def _reader(asset: str, *args: Any, **kwargs: Any) -> ImageData:
969
- idx = asset_indexes.get(asset) or indexes
970
-
971
863
  asset_info = self._get_asset_info(asset)
972
- reader, options = self._get_reader(asset_info)
864
+ asset_name = asset_info["name"]
865
+ reader, reader_options = self._get_reader(asset_info)
973
866
 
867
+ options = {**self.reader_options, **reader_options}
974
868
  with self.ctx(**asset_info.get("env", {})):
975
- with reader(
976
- asset_info["url"],
977
- tms=self.tms,
978
- **{**self.reader_options, **options},
979
- ) as src:
980
- 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)
981
872
 
982
873
  self._update_statistics(
983
874
  data,
984
- indexes=idx,
985
875
  statistics=asset_info.get("dataset_statistics"),
986
876
  )
987
877
 
988
878
  metadata = data.metadata or {}
989
879
  if m := asset_info.get("metadata"):
990
880
  metadata.update(m)
991
- data.metadata = {asset: metadata}
881
+ data.metadata = {asset_name: metadata}
992
882
 
883
+ data.band_descriptions = [
884
+ f"{asset_name}_{n}" for n in data.band_descriptions
885
+ ]
993
886
  if asset_as_band:
994
887
  if len(data.band_names) > 1:
995
888
  raise AssetAsBandError(
996
889
  "Can't use `asset_as_band` for multibands asset"
997
890
  )
998
- data.band_names = [asset]
999
- data.band_descriptions = [asset]
1000
- else:
1001
- data.band_names = [f"{asset}_{n}" for n in data.band_names]
1002
- data.band_descriptions = [
1003
- f"{asset}_{n}" for n in data.band_descriptions
1004
- ]
891
+ data.band_descriptions = [asset_name]
1005
892
 
1006
893
  return data
1007
894
 
1008
895
  img = multi_arrays(assets, _reader, shape, **kwargs)
896
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
1009
897
  if expression:
1010
898
  return img.apply_expression(expression)
1011
899
 
@@ -1030,14 +918,22 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1030
918
  input: Any = attr.ib()
1031
919
  tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
1032
920
 
1033
- minzoom: int = attr.ib(default=None)
1034
- maxzoom: int = attr.ib(default=None)
921
+ minzoom: int | None = attr.ib(default=None)
922
+ maxzoom: int | None = attr.ib(default=None)
1035
923
 
1036
- reader: Type[BaseReader] = attr.ib(init=False)
1037
- reader_options: Dict = attr.ib(factory=dict)
924
+ reader: type[BaseReader] = attr.ib(init=False)
925
+ reader_options: dict = attr.ib(factory=dict)
1038
926
 
1039
927
  bands: Sequence[str] = attr.ib(init=False)
1040
- default_bands: Optional[Sequence[str]] = attr.ib(init=False, default=None)
928
+ default_bands: Sequence[str] | None = attr.ib(init=False, default=None)
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
+ )
1041
937
 
1042
938
  def __enter__(self):
1043
939
  """Support using with Context Managers."""
@@ -1052,7 +948,7 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1052
948
  """Validate band name and construct url."""
1053
949
  ...
1054
950
 
1055
- def parse_expression(self, expression: str) -> Tuple:
951
+ def parse_expression(self, expression: str) -> tuple:
1056
952
  """Parse rio-tiler band math expression."""
1057
953
  input_bands = "|".join([rf"\b{band}\b" for band in self.bands])
1058
954
  _re = re.compile(input_bands.replace("\\\\", "\\"))
@@ -1065,9 +961,16 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1065
961
 
1066
962
  return bands
1067
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
+
1068
971
  def info(
1069
972
  self,
1070
- bands: Optional[Union[Sequence[str], str]] = None,
973
+ bands: Sequence[str] | str | None = None,
1071
974
  **kwargs: Any,
1072
975
  ) -> Info:
1073
976
  """Return metadata from multiple bands.
@@ -1089,11 +992,7 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1089
992
 
1090
993
  def _reader(band: str, **kwargs: Any) -> Info:
1091
994
  url = self._get_band_url(band)
1092
- with self.reader(
1093
- url,
1094
- tms=self.tms,
1095
- **self.reader_options,
1096
- ) as src:
995
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1097
996
  return src.info()
1098
997
 
1099
998
  bands_metadata = multi_values(bands, _reader, **kwargs)
@@ -1121,15 +1020,15 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1121
1020
 
1122
1021
  def statistics(
1123
1022
  self,
1124
- bands: Optional[Union[Sequence[str], str]] = None,
1125
- expression: Optional[str] = None,
1023
+ bands: Sequence[str] | str | None = None,
1024
+ expression: str | None = None,
1126
1025
  categorical: bool = False,
1127
- categories: Optional[List[float]] = None,
1128
- percentiles: Optional[List[int]] = None,
1129
- hist_options: Optional[Dict] = None,
1026
+ categories: list[float] | None = None,
1027
+ percentiles: list[int] | None = None,
1028
+ hist_options: dict | None = None,
1130
1029
  max_size: int = 1024,
1131
1030
  **kwargs: Any,
1132
- ) -> Dict[str, BandStatistics]:
1031
+ ) -> dict[str, BandStatistics]:
1133
1032
  """Return array statistics for multiple assets.
1134
1033
 
1135
1034
  Args:
@@ -1172,8 +1071,8 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1172
1071
  tile_x: int,
1173
1072
  tile_y: int,
1174
1073
  tile_z: int,
1175
- bands: Optional[Union[Sequence[str], str]] = None,
1176
- expression: Optional[str] = None,
1074
+ bands: Sequence[str] | str | None = None,
1075
+ expression: str | None = None,
1177
1076
  **kwargs: Any,
1178
1077
  ) -> ImageData:
1179
1078
  """Read and merge Web Map tiles multiple bands.
@@ -1219,24 +1118,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1219
1118
 
1220
1119
  def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
1221
1120
  url = self._get_band_url(band)
1222
- with self.reader(
1223
- url,
1224
- tms=self.tms,
1225
- **self.reader_options,
1226
- ) as src:
1121
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1227
1122
  data = src.tile(*args, **kwargs)
1228
-
1229
1123
  if data.metadata:
1230
1124
  data.metadata = {band: data.metadata}
1231
1125
 
1232
- # use `band` as name instead of band index
1233
- data.band_names = [band]
1234
-
1126
+ # use `band` as name/description instead of band index
1127
+ data.band_descriptions = [band]
1235
1128
  return data
1236
1129
 
1237
1130
  img = multi_arrays(bands, _reader, tile_x, tile_y, tile_z, **kwargs)
1238
-
1131
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
1239
1132
  if expression:
1133
+ expression = self._expression_to_bidx(img.band_descriptions, expression)
1240
1134
  return img.apply_expression(expression)
1241
1135
 
1242
1136
  return img
@@ -1244,8 +1138,8 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1244
1138
  def part(
1245
1139
  self,
1246
1140
  bbox: BBox,
1247
- bands: Optional[Union[Sequence[str], str]] = None,
1248
- expression: Optional[str] = None,
1141
+ bands: Sequence[str] | str | None = None,
1142
+ expression: str | None = None,
1249
1143
  **kwargs: Any,
1250
1144
  ) -> ImageData:
1251
1145
  """Read and merge parts from multiple bands.
@@ -1284,32 +1178,27 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1284
1178
 
1285
1179
  def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
1286
1180
  url = self._get_band_url(band)
1287
- with self.reader(
1288
- url,
1289
- tms=self.tms,
1290
- **self.reader_options,
1291
- ) as src:
1181
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1292
1182
  data = src.part(*args, **kwargs)
1293
-
1294
1183
  if data.metadata:
1295
1184
  data.metadata = {band: data.metadata}
1296
1185
 
1297
- # use `band` as name instead of band index
1298
- data.band_names = [band]
1299
-
1186
+ # use `band` as name/description instead of band index
1187
+ data.band_descriptions = [band]
1300
1188
  return data
1301
1189
 
1302
1190
  img = multi_arrays(bands, _reader, bbox, **kwargs)
1303
-
1191
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
1304
1192
  if expression:
1193
+ expression = self._expression_to_bidx(img.band_descriptions, expression)
1305
1194
  return img.apply_expression(expression)
1306
1195
 
1307
1196
  return img
1308
1197
 
1309
1198
  def preview(
1310
1199
  self,
1311
- bands: Optional[Union[Sequence[str], str]] = None,
1312
- expression: Optional[str] = None,
1200
+ bands: Sequence[str] | str | None = None,
1201
+ expression: str | None = None,
1313
1202
  **kwargs: Any,
1314
1203
  ) -> ImageData:
1315
1204
  """Read and merge previews from multiple bands.
@@ -1347,24 +1236,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1347
1236
 
1348
1237
  def _reader(band: str, **kwargs: Any) -> ImageData:
1349
1238
  url = self._get_band_url(band)
1350
- with self.reader(
1351
- url,
1352
- tms=self.tms,
1353
- **self.reader_options,
1354
- ) as src:
1239
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1355
1240
  data = src.preview(**kwargs)
1356
-
1357
1241
  if data.metadata:
1358
1242
  data.metadata = {band: data.metadata}
1359
1243
 
1360
- # use `band` as name instead of band index
1361
- data.band_names = [band]
1362
-
1244
+ # use `band` as name/description instead of band index
1245
+ data.band_descriptions = [band]
1363
1246
  return data
1364
1247
 
1365
1248
  img = multi_arrays(bands, _reader, **kwargs)
1366
-
1249
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
1367
1250
  if expression:
1251
+ expression = self._expression_to_bidx(img.band_descriptions, expression)
1368
1252
  return img.apply_expression(expression)
1369
1253
 
1370
1254
  return img
@@ -1373,8 +1257,8 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1373
1257
  self,
1374
1258
  lon: float,
1375
1259
  lat: float,
1376
- bands: Optional[Union[Sequence[str], str]] = None,
1377
- expression: Optional[str] = None,
1260
+ bands: Sequence[str] | str | None = None,
1261
+ expression: str | None = None,
1378
1262
  **kwargs: Any,
1379
1263
  ) -> PointData:
1380
1264
  """Read a pixel values from multiple bands.
@@ -1414,32 +1298,28 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1414
1298
 
1415
1299
  def _reader(band: str, *args: Any, **kwargs: Any) -> PointData:
1416
1300
  url = self._get_band_url(band)
1417
- with self.reader(
1418
- url,
1419
- tms=self.tms,
1420
- **self.reader_options,
1421
- ) as src:
1301
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1422
1302
  data = src.point(*args, **kwargs)
1423
-
1424
1303
  if data.metadata:
1425
1304
  data.metadata = {band: data.metadata}
1426
1305
 
1427
- # use `band` as name instead of band index
1428
- data.band_names = [band]
1429
-
1306
+ # use `band` as name/description instead of band index
1307
+ data.band_descriptions = [band]
1430
1308
  return data
1431
1309
 
1432
1310
  data = multi_points(bands, _reader, lon, lat, **kwargs)
1311
+ data.band_names = [f"b{ix + 1}" for ix in range(data.count)]
1433
1312
  if expression:
1313
+ expression = self._expression_to_bidx(data.band_descriptions, expression)
1434
1314
  return data.apply_expression(expression)
1435
1315
 
1436
1316
  return data
1437
1317
 
1438
1318
  def feature(
1439
1319
  self,
1440
- shape: Dict,
1441
- bands: Optional[Union[Sequence[str], str]] = None,
1442
- expression: Optional[str] = None,
1320
+ shape: dict,
1321
+ bands: Sequence[str] | str | None = None,
1322
+ expression: str | None = None,
1443
1323
  **kwargs: Any,
1444
1324
  ) -> ImageData:
1445
1325
  """Read and merge parts defined by geojson feature from multiple bands.
@@ -1478,24 +1358,19 @@ class MultiBandReader(SpatialMixin, metaclass=abc.ABCMeta):
1478
1358
 
1479
1359
  def _reader(band: str, *args: Any, **kwargs: Any) -> ImageData:
1480
1360
  url = self._get_band_url(band)
1481
- with self.reader(
1482
- url,
1483
- tms=self.tms,
1484
- **self.reader_options,
1485
- ) as src:
1361
+ with self.reader(url, tms=self.tms, **self.reader_options) as src:
1486
1362
  data = src.feature(*args, **kwargs)
1487
-
1488
1363
  if data.metadata:
1489
1364
  data.metadata = {band: data.metadata}
1490
1365
 
1491
- # use `band` as name instead of band index
1492
- data.band_names = [band]
1493
-
1366
+ # use `band` as name/description instead of band index
1367
+ data.band_descriptions = [band]
1494
1368
  return data
1495
1369
 
1496
1370
  img = multi_arrays(bands, _reader, shape, **kwargs)
1497
-
1371
+ img.band_names = [f"b{ix + 1}" for ix in range(img.count)]
1498
1372
  if expression:
1373
+ expression = self._expression_to_bidx(img.band_descriptions, expression)
1499
1374
  return img.apply_expression(expression)
1500
1375
 
1501
1376
  return img