Skip to content

Raster loading API

open_geotiff() returns an instance of Image, a class defined by OBIA. It keeps the pixel array together with its coordinate reference system, transform, source dataset, and valid-pixel mask. Image.to_image() creates a Pillow image for display.

Raster data

obia.handlers.geotif

Image

Image A class for handling geographic raster image data, providing utilities to manage its properties and to convert it into visualization formats.

Attributes

img_data : numpy.ndarray The image data array. crs : str or dict Coordinate reference system of the image. transform : affine.Affine Affine transformation matrix. affine_transformation : affine.Affine Deprecated attribute for affine transformation. rasterio_obj : rasterio.io.DatasetReader Rasterio dataset object for the image. valid_mask : numpy.ndarray or None Valid source pixels in loaded band order, shaped (height, width, bands). Feature extraction excludes pixels marked False by raster NoData or masks.

Methods

init(self, img_data, crs, affine_transformation, transform, rasterio_obj) Constructs all the necessary attributes for the Image object. to_image(self, bands) Converts specified bands of the image into an RGB image.

Source code in obia/handlers/geotif.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Image:
    """
        Image
        A class for handling geographic raster image data, providing utilities to manage its properties and to convert it into visualization formats.

        Attributes
        ----------
        img_data : numpy.ndarray
            The image data array.
        crs : str or dict
            Coordinate reference system of the image.
        transform : affine.Affine
            Affine transformation matrix.
        affine_transformation : affine.Affine
            Deprecated attribute for affine transformation.
        rasterio_obj : rasterio.io.DatasetReader
            Rasterio dataset object for the image.
        valid_mask : numpy.ndarray or None
            Valid source pixels in loaded band order, shaped (height, width, bands).
            Feature extraction excludes pixels marked False by raster NoData or masks.

        Methods
        -------
        __init__(self, img_data, crs, affine_transformation, transform, rasterio_obj)
            Constructs all the necessary attributes for the Image object.
        to_image(self, bands)
            Converts specified bands of the image into an RGB image.
    """
    img_data = None
    crs = None
    transform = None
    affine_transformation = None
    rasterio_obj = None

    def __init__(self, img_data, crs, affine_transformation, transform, rasterio_obj, valid_mask=None):
        self.img_data = img_data
        self.crs = crs
        self.affine_transformation = affine_transformation
        self.transform = transform
        self.rasterio_obj = rasterio_obj
        self.valid_mask = valid_mask

    def to_image(self, bands, p_min=2, p_max=98, stretch_type=None):
        """
        always applies a linear stretch
        :param bands: A list or tuple of three integers representing the indices of the bands to use for the RGB image.
        :param p_min: Minimum percentile for rescaling.
        :param p_max: Maximum percentile for rescaling.
        :param stretch_type: Type of contrast stretching to apply ("linear", "histogram_equalization", "clahe").
        :return: A rescaled and contrast-stretched RGB image.
        """
        if not isinstance(bands, (list, tuple)) or len(bands) != 3:
            raise ValueError("'bands' should be a list or tuple of exactly three elements")

        num_bands = self.img_data.shape[2]
        rgb_data = np.empty((self.img_data.shape[0], self.img_data.shape[1], 3), dtype=np.float32)

        for i, band in enumerate(bands):
            if band >= num_bands or band < 0:
                raise IndexError(f"Band index {band} out of range. Available bands indices: 0 to {num_bands - 1}.")
            rgb_data[:, :, i] = self.img_data[:, :, band]

        rgb_data_rescaled = rescale_to_8bit(rgb_data, min=p_min, max=p_max)

        if stretch_type == "histogram_equalization":
            rgb_data_rescaled = apply_histogram_equalization(rgb_data_rescaled)
        elif stretch_type == "clahe":
            rgb_data_rescaled = apply_clahe(rgb_data_rescaled)
        elif stretch_type != None:
            raise ValueError(f"Unknown stretch_type: {stretch_type}")

        return fromarray(rgb_data_rescaled.astype(np.uint8))

to_image(bands, p_min=2, p_max=98, stretch_type=None)

always applies a linear stretch :param bands: A list or tuple of three integers representing the indices of the bands to use for the RGB image. :param p_min: Minimum percentile for rescaling. :param p_max: Maximum percentile for rescaling. :param stretch_type: Type of contrast stretching to apply ("linear", "histogram_equalization", "clahe"). :return: A rescaled and contrast-stretched RGB image.

Source code in obia/handlers/geotif.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def to_image(self, bands, p_min=2, p_max=98, stretch_type=None):
    """
    always applies a linear stretch
    :param bands: A list or tuple of three integers representing the indices of the bands to use for the RGB image.
    :param p_min: Minimum percentile for rescaling.
    :param p_max: Maximum percentile for rescaling.
    :param stretch_type: Type of contrast stretching to apply ("linear", "histogram_equalization", "clahe").
    :return: A rescaled and contrast-stretched RGB image.
    """
    if not isinstance(bands, (list, tuple)) or len(bands) != 3:
        raise ValueError("'bands' should be a list or tuple of exactly three elements")

    num_bands = self.img_data.shape[2]
    rgb_data = np.empty((self.img_data.shape[0], self.img_data.shape[1], 3), dtype=np.float32)

    for i, band in enumerate(bands):
        if band >= num_bands or band < 0:
            raise IndexError(f"Band index {band} out of range. Available bands indices: 0 to {num_bands - 1}.")
        rgb_data[:, :, i] = self.img_data[:, :, band]

    rgb_data_rescaled = rescale_to_8bit(rgb_data, min=p_min, max=p_max)

    if stretch_type == "histogram_equalization":
        rgb_data_rescaled = apply_histogram_equalization(rgb_data_rescaled)
    elif stretch_type == "clahe":
        rgb_data_rescaled = apply_clahe(rgb_data_rescaled)
    elif stretch_type != None:
        raise ValueError(f"Unknown stretch_type: {stretch_type}")

    return fromarray(rgb_data_rescaled.astype(np.uint8))

open_binary_geotiff_as_mask(mask_path)

:param mask_path: Path to the binary GeoTIFF file to be opened. :return: A tuple containing the binary mask array, bounding box, affine transform, and profile of the raster.

Source code in obia/handlers/geotif.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def open_binary_geotiff_as_mask(mask_path):
    """
    :param mask_path: Path to the binary GeoTIFF file to be opened.
    :return: A tuple containing the binary mask array, bounding box, affine transform, and profile of the raster.
    """
    with rasterio.open(mask_path) as src:
        mask_array = src.read(1).astype(bool)  # Read the mask as a binary array
        transform = src.transform  # Get the affine transform of the raster
        width, height = src.width, src.height  # Get the dimensions of the raster
        profile = src.profile  # Capture the profile to use later

        # Calculate the bounding box from the transform and dimensions
        left, top = transform * (0, 0)
        right, bottom = transform * (width, height)
        bbox = (left, bottom, right, top)

    return mask_array, bbox, transform, profile

open_geotiff(image_path, bands=None)

:param image_path: Path to the GeoTIFF file to be opened. :type image_path: str :param bands: List of band indices to be read from the GeoTIFF file. If None, all bands are read. :type bands: list of int, optional :return: An Image object containing the raster data, coordinate reference system, affine transformation matrix, and the original rasterio object. :rtype: Image

Source code in obia/handlers/geotif.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def open_geotiff(image_path, bands=None):
    """
    :param image_path: Path to the GeoTIFF file to be opened.
    :type image_path: str
    :param bands: List of band indices to be read from the GeoTIFF file. If None, all bands are read.
    :type bands: list of int, optional
    :return: An Image object containing the raster data, coordinate reference system, affine transformation matrix, and the original rasterio object.
    :rtype: Image
    """
    rasterio_obj = rasterio.open(image_path)

    crs = rasterio_obj.crs
    transform = rasterio_obj.transform
    affine_transformation = [transform.a, transform.b, transform.d, transform.e, transform.c, transform.f]

    x_size = rasterio_obj.width
    y_size = rasterio_obj.height
    num_bands = rasterio_obj.count

    if bands is None:
        bands = list(range(1, num_bands + 1))

    data = np.empty((y_size, x_size, len(bands)), dtype=np.float32)
    valid_mask = np.empty(data.shape, dtype=bool)

    for i, b in enumerate(bands):
        band_array = rasterio_obj.read(b, masked=True)
        data[:, :, i] = band_array.data
        valid_mask[:, :, i] = ~np.ma.getmaskarray(band_array)

    return Image(data, crs, affine_transformation, transform, rasterio_obj, valid_mask)

Bundled example data

obia.datasets.chesapeake_sample(split='train')

Locate a bundled Chesapeake imagery/reference pair without a download.

Parameters:

Name Type Description Default
split str

"train" or "test". These are 1024 × 1024 pixel crops from separate tiles in the publisher's Maryland train/test sets.

'train'

Returns:

Name Type Description
dict dict

image and reference are local GeoTIFF Paths; class_names maps integer reference codes to names; metadata records the source tile, crop window, grid, URLs, and checksums. The imagery has four bands in original order, selected in OBIA as indices 0–3. The reference uses codes 1–6, with 15 reserved for NoData.

The reference is a land-cover map, not independent field observations. See the accompanying chesapeake/README.md for attribution and terms.

Source code in obia/datasets/__init__.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def chesapeake_sample(split: str = "train") -> dict:
    """Locate a bundled Chesapeake imagery/reference pair without a download.

    Args:
        split: ``"train"`` or ``"test"``. These are 1024 × 1024 pixel crops
            from separate tiles in the publisher's Maryland train/test sets.

    Returns:
        dict: ``image`` and ``reference`` are local GeoTIFF Paths;
            ``class_names`` maps integer reference codes to names; ``metadata``
            records the source tile, crop window, grid, URLs, and checksums.
            The imagery has four bands in original order, selected in OBIA as
            indices 0–3. The reference uses codes 1–6, with 15 reserved for NoData.

    The reference is a land-cover map, not independent field observations.
    See the accompanying ``chesapeake/README.md`` for attribution and terms.
    """
    if split not in ("train", "test"):
        raise ValueError("split must be 'train' or 'test'.")
    directory = Path(__file__).parent / "chesapeake"
    metadata = json.loads((directory / "metadata.json").read_text(encoding="utf-8"))
    sample = metadata["samples"][split]
    return {
        "image": directory / sample["files"]["image"]["filename"],
        "reference": directory / sample["files"]["reference"]["filename"],
        "class_names": {int(code): name for code, name in metadata["class_names"].items()},
        "metadata": sample,
    }