Repository: microsoft/torchgeo

Issue:
Getting "B1" is not in list error with 0.4.0 release for Landsat8 dataset 
### Issue

Previously, the following instantiation used to work with 0.3.1 version:
```python
dataset = Landsat8(root=root_path,bands=["B1","B2","B3","B4","B5","B6","B7","B8","B9","B10", "B11","QA_PIXEL"])
```
But with 0.4.0, I am getting the following error stack: 
```
Traceback (most recent call last):
  File "/home/ta6167/diffusion_experiments/improved_ddpm_experiments/main.py", line 14, in <module>
    dataset = Landsat8(root=root_path,bands=["B1","B2","B3","B4","B5","B6","B7","B8","B9","B10", "B11","QA_PIXEL"])
  File "/home/ta6167/.local/lib/python3.10/site-packages/torchgeo/datasets/landsat.py", line 80, in __init__
    super().__init__(root, crs, res, bands, transforms, cache)
  File "/home/ta6167/.local/lib/python3.10/site-packages/torchgeo/datasets/geo.py", line 371, in __init__
    band_indexes = [self.all_bands.index(i) + 1 for i in bands]
  File "/home/ta6167/.local/lib/python3.10/site-packages/torchgeo/datasets/geo.py", line 371, in <listcomp>
    band_indexes = [self.all_bands.index(i) + 1 for i in bands]
ValueError: 'B1' is not in list
```
I am guessing the designation for channel names might have changed, what is the correct way to provide the channel names for 0.4.0 version? 


### Fix

_No response_


Discussion:
Thanks for reporting this bug! This appears to be an unintended consequence of @ashnair1's #687. Let me see if I can come up with a bug fix and we'll fix this in the 0.4.1 release (due in 1–2 weeks).

Resolving patch:
diff --git a/torchgeo/datasets/geo.py b/torchgeo/datasets/geo.py
--- a/torchgeo/datasets/geo.py
+++ b/torchgeo/datasets/geo.py
@@ -323,6 +323,7 @@ def __init__(
         super().__init__(transforms)
 
         self.root = root
+        self.bands = bands or self.all_bands
         self.cache = cache
 
         # Populate the dataset index
@@ -367,21 +368,20 @@ def __init__(
                 f"No {self.__class__.__name__} data was found in '{root}'"
             )
 
-        if bands and self.all_bands:
-            band_indexes = [self.all_bands.index(i) + 1 for i in bands]
-            self.bands = bands
-            assert len(band_indexes) == len(self.bands)
-        elif bands:
-            msg = (
-                f"{self.__class__.__name__} is missing an `all_bands` attribute,"
-                " so `bands` cannot be specified."
-            )
-            raise AssertionError(msg)
-        else:
-            band_indexes = None
-            self.bands = self.all_bands
+        if not self.separate_files:
+            self.band_indexes = None
+            if self.bands:
+                if self.all_bands:
+                    self.band_indexes = [
+                        self.all_bands.index(i) + 1 for i in self.bands
+                    ]
+                else:
+                    msg = (
+                        f"{self.__class__.__name__} is missing an `all_bands` "
+                        "attribute, so `bands` cannot be specified."
+                    )
+                    raise AssertionError(msg)
 
-        self.band_indexes = band_indexes
         self._crs = cast(CRS, crs)
         self.res = cast(float, res)
 
diff --git a/torchgeo/datasets/landsat.py b/torchgeo/datasets/landsat.py
--- a/torchgeo/datasets/landsat.py
+++ b/torchgeo/datasets/landsat.py
@@ -4,7 +4,7 @@
 """Landsat datasets."""
 
 import abc
-from typing import Any, Callable, Dict, Optional, Sequence
+from typing import Any, Callable, Dict, List, Optional, Sequence
 
 import matplotlib.pyplot as plt
 from rasterio.crs import CRS
@@ -49,6 +49,11 @@ class Landsat(RasterDataset, abc.ABC):
 
     separate_files = True
 
+    @property
+    @abc.abstractmethod
+    def default_bands(self) -> List[str]:
+        """Bands to load by default."""
+
     def __init__(
         self,
         root: str = "data",
@@ -74,7 +79,7 @@ def __init__(
         Raises:
             FileNotFoundError: if no files are found in ``root``
         """
-        bands = bands or self.all_bands
+        bands = bands or self.default_bands
         self.filename_glob = self.filename_glob.format(bands[0])
 
         super().__init__(root, crs, res, bands, transforms, cache)
@@ -133,7 +138,7 @@ class Landsat1(Landsat):
 
     filename_glob = "LM01_*_{}.*"
 
-    all_bands = ["SR_B4", "SR_B5", "SR_B6", "SR_B7"]
+    default_bands = ["SR_B4", "SR_B5", "SR_B6", "SR_B7"]
     rgb_bands = ["SR_B6", "SR_B5", "SR_B4"]
 
 
@@ -154,7 +159,7 @@ class Landsat4MSS(Landsat):
 
     filename_glob = "LM04_*_{}.*"
 
-    all_bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4"]
+    default_bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4"]
     rgb_bands = ["SR_B3", "SR_B2", "SR_B1"]
 
 
@@ -163,7 +168,7 @@ class Landsat4TM(Landsat):
 
     filename_glob = "LT04_*_{}.*"
 
-    all_bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7"]
+    default_bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7"]
     rgb_bands = ["SR_B3", "SR_B2", "SR_B1"]
 
 
@@ -184,7 +189,16 @@ class Landsat7(Landsat):
 
     filename_glob = "LE07_*_{}.*"
 
-    all_bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7", "SR_B8"]
+    default_bands = [
+        "SR_B1",
+        "SR_B2",
+        "SR_B3",
+        "SR_B4",
+        "SR_B5",
+        "SR_B6",
+        "SR_B7",
+        "SR_B8",
+    ]
     rgb_bands = ["SR_B3", "SR_B2", "SR_B1"]
 
 
@@ -193,7 +207,7 @@ class Landsat8(Landsat):
 
     filename_glob = "LC08_*_{}.*"
 
-    all_bands = [
+    default_bands = [
         "SR_B1",
         "SR_B2",
         "SR_B3",


Test patch:
diff --git a/tests/datasets/test_geo.py b/tests/datasets/test_geo.py
--- a/tests/datasets/test_geo.py
+++ b/tests/datasets/test_geo.py
@@ -52,6 +52,7 @@ class CustomVectorDataset(VectorDataset):
 
 class CustomSentinelDataset(Sentinel2):
     all_bands: List[str] = []
+    separate_files = False
 
 
 class CustomNonGeoDataset(NonGeoDataset):
@@ -214,7 +215,7 @@ def test_no_data(self, tmp_path: Path) -> None:
         with pytest.raises(FileNotFoundError, match="No RasterDataset data was found"):
             RasterDataset(str(tmp_path))
 
-    def test_no_allbands(self) -> None:
+    def test_no_all_bands(self) -> None:
         root = os.path.join("tests", "data", "sentinel2")
         bands = ["B04", "B03", "B02"]
         transforms = nn.Identity()
diff --git a/tests/datasets/test_landsat.py b/tests/datasets/test_landsat.py
--- a/tests/datasets/test_landsat.py
+++ b/tests/datasets/test_landsat.py
@@ -8,6 +8,7 @@
 import pytest
 import torch
 import torch.nn as nn
+from _pytest.fixtures import SubRequest
 from _pytest.monkeypatch import MonkeyPatch
 from rasterio.crs import CRS
 
@@ -15,10 +16,15 @@
 
 
 class TestLandsat8:
-    @pytest.fixture
-    def dataset(self, monkeypatch: MonkeyPatch) -> Landsat8:
+    @pytest.fixture(
+        params=[
+            ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7"],
+            ["SR_B4", "SR_B3", "SR_B2", "SR_QA_AEROSOL"],
+        ]
+    )
+    def dataset(self, monkeypatch: MonkeyPatch, request: SubRequest) -> Landsat8:
         root = os.path.join("tests", "data", "landsat8")
-        bands = ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7"]
+        bands = request.param
         transforms = nn.Identity()
         return Landsat8(root, bands=bands, transforms=transforms)