# Copyright 2018-2024 Descartes Labs.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections.abc import Iterable, Mapping, MutableMapping
from strenum import StrEnum
from ..common.collection import Collection
from ..common.property_filtering import Properties
from .catalog_base import _new_abstract_class
from .named_catalog_base import NamedCatalogObject
from .attributes import (
Attribute,
EnumAttribute,
Resolution,
BooleanAttribute,
ListAttribute,
TupleAttribute,
TypedAttribute,
ModelAttribute,
MappingAttribute,
AttributeValidationError,
)
properties = Properties()
[docs]class DataType(StrEnum):
"""Valid data types for bands.
Attributes
----------
BYTE : enum
An 8 bit unsigned integer value.
UINT16 : enum
A 16 bit unsigned integer value.
INT16 : enum
A 16 bit signed integer value.
UINT32 : enum
A 32 bit unsigned integer value.
INT32 : enum
A 32 bit signed integer value.
FLOAT32 : enum
A 32 bit single-precision floating-point format value.
FLOAT64 : enum
A 64 bit double-precision floating-point format value.
"""
BYTE = "Byte"
UINT16 = "UInt16"
INT16 = "Int16"
UINT32 = "UInt32"
INT32 = "Int32"
FLOAT32 = "Float32"
FLOAT64 = "Float64"
[docs]class BandType(StrEnum):
"""Types of bands with different data interpretation.
The type of band is represented in the specific Band class being used
and is only for informative purposes.
Attributes
----------
CLASS : enum
A band that maps a finite set of values that may not be continuous.
SPECTRAL : enum
A band that lies somewhere on the visible/NIR/SWIR electro-optical wavelength
spectrum.
MASK : enum
A binary band where by convention a 0 means masked and 1 means non-masked.
MICROWAVE : enum
A band that lies in the microwave spectrum, often from SAR or passive radar
sensors.
GENERIC : enum
An unspecified kind of band not fitting any other type.
"""
CLASS = "class"
SPECTRAL = "spectral"
MASK = "mask"
MICROWAVE = "microwave"
GENERIC = "generic"
[docs]class Colormap(StrEnum):
"""Predefined colormaps available to assign to bands.
Most of these colormaps correspond directly to the built-in colormaps of the
same name in matplotlib. See
https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html for an
overview and visual examples.
Attributes
----------
MAGMA : enum
A perceptually uniform sequential colormap, equivalent to matplotlib's
built-in "magma"
INFERNO : enum
A perceptually uniform sequential colormap, equivalent to matplotlib's
built-in "inferno"
PLASMA : enum
A perceptually uniform sequential colormap, equivalent to matplotlib's
built-in "plasma"
VIRIDIS : enum
A perceptually uniform sequential colormap, equivalent to matplotlib's
built-in "viridis"
COOL : enum
A sequential colormap, equivalent to matplotlib's built-in "cool"
HOT : enum
A sequential colormap, equivalent to matplotlib's built-in "hot"
COOLWARM : enum
A diverging colormap, equivalent to matplotlib's built-in "coolwarm"
BWR : enum
A diverging colormap (blue-white-red), equivalent to matplotlib's
built-in "bwr"
GIST_EARTH : enum
A colormap designed to represent topography and water depths together,
equivalent to matplotlib's built-in "gist_earth"
TERRAIN : enum
A colormap designed to represent topography and water depths together,
equivalent to matplotlib's built-in "terrain"
CDL : enum
A standard colormap used in Cropland Data Layer (CDL) products, with a
distinct color for each class in such products
"""
MAGMA = "magma"
INFERNO = "inferno"
PLASMA = "plasma"
VIRIDIS = "viridis"
COOL = "cool"
HOT = "hot"
COOLWARM = "coolwarm"
BWR = "bwr"
GIST_EARTH = "gist_earth"
TERRAIN = "terrain"
CDL = "cdl"
[docs]class ProcessingStepAttribute(MappingAttribute):
"""Processing Levels Step.
Attributes
----------
function : str
Name of the processing function to apply. Required.
parameter : str
Name of the parameter in the image metadata containing
the coefficients for the processing function. Required.
index : int
Optional index into the named parameter (an array) for the band.
data_type : str or DataType
Optional data type for pixel values in this band.
data_range : tuple(float, float)
Optional normal range of pixel values stored in the band.
display_range : tuple(float, float)
Optional normal range of pixel values stored in the band for display purposes.
physical_range : tuple(float, float)
Optional normal range of physical values stored in the band.
physical_range_unit : str
Optional unit of the physical range.
"""
function = TypedAttribute(str)
parameter = TypedAttribute(str)
index = TypedAttribute(int)
data_type = EnumAttribute(DataType)
data_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
)
display_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
)
physical_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
)
physical_range_unit = Attribute()
[docs]class ProcessingLevelsAttribute(ModelAttribute, MutableMapping):
"""An attribute that contains properties (key/value pairs).
Can be set using a dictionary of items or any `Mapping`, or an instance of this
attribute. All keys must be string and values can be string or an iterable
of `ProcessingStepAttribute` items (or compatible mapping).
`ProcessingLevelsAttribute` behaves similar to dictionaries.
"""
# this value is ONLY used for for instances of the attribute that
# are attached to class definitions. It's confusing to put this
# instantiation into __init__, because the value is only ever set
# from AttributeMeta.__new__, after it's already been instantiated
_attribute_name = None
def __init__(self, value=None, validate=True, **kwargs):
self._items = {}
super(ProcessingLevelsAttribute, self).__init__(**kwargs)
if value is not None:
# we always validate, to correctly coerce the values
value = {
key: self.validate_key_and_value(key, val) for key, val in value.items()
}
self._items.update(value)
def __repr__(self):
return "{}{}{}".format(
"{",
", ".join(
[
"{}: {}".format(repr(key), repr(value))
for key, value in self._items.items()
]
),
"}",
)
def validate_key_and_value(self, key, value):
"""Validate the key and value.
The key must be a string, and the value either a string or an iterable of
`ProcessingStepAttribute` items or a compatible mapping.
Returns a fully formed value (a string or a ListAttribute of
`ProcessingStepAttribute` items)
"""
if not isinstance(key, str):
raise AttributeValidationError(
"Keys for property {} must be strings: {}".format(
self._attribute_name, key
)
)
if isinstance(value, str):
return value
elif isinstance(value, ListAttribute) and all(
map(lambda x: isinstance(x, ProcessingStepAttribute), value)
):
value._add_model_object(self)
return value
elif isinstance(value, Iterable):
items = []
for v in value:
if isinstance(v, ProcessingStepAttribute):
items.append(v)
elif isinstance(v, Mapping):
try:
items.append(ProcessingStepAttribute(**v))
except AttributeError as ex:
raise AttributeValidationError(
"The value for property {} with key {} must"
" conform to a valid ProcessingStepAttribute: {}: {}".format(
self._attribute_name, key, v, ex
)
)
else:
break
else:
value = ListAttribute(ProcessingStepAttribute, items=items)
value._add_model_object(self)
return value
raise AttributeValidationError(
"The value for property {} with key {} must be a string"
" or an iterable of ProcessingStepAttribute: {}".format(
self._attribute_name, key, value
)
)
def serialize(self, value, jsonapi_format=False):
"""Serialize a value to a json-serializable type.
See :meth:`Attribute.serialize`.
"""
if value is None:
return None
# Shallow copy for strings, deserialize ListAttributes
return {
k: (
v
if isinstance(v, str)
else v.serialize(v, jsonapi_format=jsonapi_format)
)
for k, v in value.items()
}
def deserialize(self, value, validate=True):
"""Deserialize a value to a native type.
See :meth:`Attribute.deserialize`.
Parameters
----------
value : dict or `ProcessingLevelsAttribute`
A set of values to use to initialize a new `ProcessingLevelsAttribute`
instance. All keys must be strings, and values can be strings or numbers.
Returns
-------
ProcessingLevelsAttribute
A `ProcessingLevelsAttribute` with the given items.
Raises
------
AttributeValidationError
If the value is not a mapping or any of the keys are not strings, or any
of the values are not strings or numbers.
"""
if value is None:
return None
if isinstance(value, ProcessingLevelsAttribute):
return value
if validate:
if not isinstance(value, Mapping):
raise AttributeValidationError(
"A ProcessingLevelsAttribute expects a mapping: {}".format(
self._attribute_name
)
)
value = {
key: self.validate_key_and_value(key, val) for key, val in value.items()
}
return ProcessingLevelsAttribute(
value, validate=validate, **self._get_attr_params()
)
# Mapping methods
def __getitem__(self, key):
return self._items[key]
def __setitem__(self, key, value):
self._raise_if_immutable_or_readonly("set")
value = self.validate_key_and_value(key, value)
old_value = self._items.get(key, None)
changed = key not in self._items or old_value != value
self._set_modified(changed=changed)
self._items[key] = value
if isinstance(old_value, ModelAttribute):
old_value._remove_model_object(self)
def __delitem__(self, key):
self._raise_if_immutable_or_readonly("delete")
if key in self._items:
self._set_modified(changed=True)
old_value = self._items.pop(key)
if isinstance(old_value, ModelAttribute):
old_value._remove_model_object(self)
def __iter__(self):
return iter(self._items)
def __len__(self):
return len(self._items)
[docs]class DerivedParamsAttribute(MappingAttribute):
"""Derived Band Parameters Attribute.
Attributes
----------
function : str
Name of the function to apply. Required.
bands : list(str)
Names of the bands used as input to the function. Required.
source_type : int
Optional index into the named parameter (an array) for the band.
"""
function = TypedAttribute(str)
bands = ListAttribute(TypedAttribute(str), validate=True)
source_type = EnumAttribute(
DataType,
doc="str or DataType: The datatype for extracting pixels from the source bands.",
)
def deserialize(self, value, validate=True):
"""Deserialize a value to a native type.
See :meth:`Attribute.deserialize`.
Parameters
----------
values : dict or MappingAttribute
The values to use to initialize a new MappingAttribute.
Returns
-------
DerivedParamsAttribute
A `DerivedParamsAttribute` instance with the given values.
Raises
------
AttributeValidationError
If the value is not a `DerivedParamsAttribute` or a mapping with
a `function`, `bands`, and (optionally) `source_type key.
"""
result = super(DerivedParamsAttribute, self).deserialize(value, validate)
if result:
if not result.function:
raise AttributeValidationError("'function' field required.")
if not result.bands:
raise AttributeValidationError("'bands' field required.")
return result
[docs]class Band(NamedCatalogObject):
"""A data band in images of a specific product.
This is an abstract class that cannot be instantiated, but can be used for searching
across all types of bands. The concrete bands are represented by the derived
classes.
Common attributes:
:attr:`~descarteslabs.catalog.GenericBand.id`,
:attr:`~descarteslabs.catalog.GenericBand.name`,
:attr:`~descarteslabs.catalog.GenericBand.product_id`,
:attr:`~descarteslabs.catalog.GenericBand.description`,
:attr:`~descarteslabs.catalog.GenericBand.type`,
:attr:`~descarteslabs.catalog.GenericBand.sort_order`,
:attr:`~descarteslabs.catalog.GenericBand.vendor_order`,
:attr:`~descarteslabs.catalog.GenericBand.data_type`,
:attr:`~descarteslabs.catalog.GenericBand.nodata`,
:attr:`~descarteslabs.catalog.GenericBand.data_range`,
:attr:`~descarteslabs.catalog.GenericBand.display_range`,
:attr:`~descarteslabs.catalog.GenericBand.resolution`,
:attr:`~descarteslabs.catalog.GenericBand.band_index`,
:attr:`~descarteslabs.catalog.GenericBand.file_index`,
:attr:`~descarteslabs.catalog.GenericBand.jpx_layer_index`.
:attr:`~descarteslabs.catalog.GenericBand.vendor_band_name`.
To create a new band instantiate one of those specialized classes:
* `SpectralBand`: A band that lies somewhere on the visible/NIR/SWIR electro-optical
wavelength spectrum. Specific attributes:
:attr:`~SpectralBand.physical_range`,
:attr:`~SpectralBand.physical_range_unit`,
:attr:`~SpectralBand.wavelength_nm_center`,
:attr:`~SpectralBand.wavelength_nm_min`,
:attr:`~SpectralBand.wavelength_nm_max`,
:attr:`~SpectralBand.wavelength_nm_fwhm`,
:attr:`~SpectralBand.processing_levels`,
:attr:`~SpectralBand.derived_params`.
* `MicrowaveBand`: A band that lies in the microwave spectrum, often from SAR or
passive radar sensors. Specific attributes:
:attr:`~MicrowaveBand.frequency`,
:attr:`~MicrowaveBand.bandwidth`,
:attr:`~MicrowaveBand.physical_range`,
:attr:`~MicrowaveBand.physical_range_unit`.
* `MaskBand`: A binary band where by convention a 0 means masked and 1 means
non-masked. The :attr:`~Band.data_range` and :attr:`~Band.display_range` for
masks is implicitly ``[0, 1]``. Specific attributes:
:attr:`~MaskBand.is_alpha`.
* `ClassBand`: A band that maps a finite set of values that may not be continuous to
classification categories (e.g. a land use classification). A visualization with
straight pixel values is typically not useful, so commonly a
:attr:`~ClassBand.colormap` is used. Specific attributes:
:attr:`~ClassBand.colormap`,
:attr:`~ClassBand.colormap_name`,
:attr:`~ClassBand.class_labels`.
* `GenericBand`: A generic type for bands that are not represented by the other band
types, e.g., mapping physical values like temperature or angles. Specific
attributes:
:attr:`~GenericBand.colormap`,
:attr:`~GenericBand.colormap_name`,
:attr:`~GenericBand.physical_range`,
:attr:`~GenericBand.physical_range_unit`,
:attr:`~GenericBand.processing_levels`,
:attr:`~GenericBand.derived_params`.
"""
_DOC_DESCRIPTION = """A description with further details on the band.
The description can be up to 80,000 characters and is used by
:py:meth:`Search.find_text`.
*Searchable*
"""
_DOC_DATATYPE = "The data type for pixel values in this band."
_DOC_DATARANGE = """The range of pixel values stored in the band.
The two floats are the minimum and maximum pixel values stored in this band.
"""
_DOC_COLORMAPNAME = """str or Colormap, optional: Name of a predefined colormap for display purposes.
The colormap is applied when this band is rastered by itself in PNG or TIFF
format, including in UIs where imagery is visualized.
"""
_DOC_COLORMAP = """list(tuple), optional: A custom colormap for this band.
A list of tuples, where each nested tuple is a 4-tuple of RGBA values to map
pixels whose value is the index of the list. E.g. the colormap ``[(100, 20,
200, 255)]`` would map pixels whose value is 0 in the original band to the
RGBA color defined by ``(100, 20, 200, 255)``. The number of 4-tuples provided
can be up to the maximum of this band's data range. Omitted values will map
to black by default.
"""
_DOC_PHYSICALRANGE = (
"tuple(float, float), optional: A physical range that pixel values map to."
)
_doc_type = "band"
_url = "/bands"
_derived_type_switch = "type"
_default_includes = ["product"]
# _collection_type set below due to circular problems
description = Attribute(doc="str, optional: " + _DOC_DESCRIPTION)
type = EnumAttribute(
BandType,
doc="""str or BandType: The type of this band, directly corresponding to a `Band` derived class.
The derived classes are `SpectralBand`, `MicrowaveBand`, `MaskBand`,
`ClassBand`, and `GenericBand`. The type never needs to be set explicitly,
this attribute is implied by the derived class used. The type of a band does
not necessarily affect how it is rastered, it mainly conveys useful information
about the data it contains.
*Filterable*.
""",
)
sort_order = TypedAttribute(
int,
doc="""int, optional: A number defining the default sort order for bands within a product.
If not set for newly created bands, this will default to the current maximum
sort order + 1 in the product.
*Sortable*.
""",
)
vendor_order = TypedAttribute(
int,
doc="""int, optional: A number defining the ordering of bands within a product
as defined by the data vendor. 1-based. Used for indexing ``c6s_dlsr``.
Generally only used internally by certain core products.
*Sortable*.
""",
)
data_type = EnumAttribute(DataType, doc="str or DataType: " + _DOC_DATATYPE)
nodata = Attribute(
doc="""float, optional: A value representing missing data in a pixel in this band."""
)
data_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
doc="tuple(float, float): " + _DOC_DATARANGE,
)
display_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
doc="""tuple(float, float): The range of pixel values for display purposes.
The two floats are the minimum and maximum values indicating a default reasonable
range of pixel values usd when rastering this band for display purposes.
""",
)
resolution = Resolution(
doc="""Resolution, optional: The spatial resolution of this band.
*Filterable, sortable*.
"""
)
band_index = TypedAttribute(
int, doc="int: The 0-based index into the source data to access this band."
)
file_index = Attribute(
doc="""int, optional: The 0-based index into the list of source files.
If there are multiple files, it maps the band index to the file index. It defaults
to 0 (first file).
"""
)
jpx_layer_index = TypedAttribute(
int,
doc="""int, optional: The 0-based layer index if the source data is JPEG2000 with layers.
Defaults to 0.
""",
)
vendor_band_name = TypedAttribute(
str,
doc="""str, optional: The name of the band in the source file.
Some source file types require that the band be indexed by name rather than by the ``band_index``.
""",
)
processing_levels = ProcessingLevelsAttribute()
derived_params = DerivedParamsAttribute()
def __new__(cls, *args, **kwargs):
return _new_abstract_class(cls, Band)
def __init__(self, **kwargs):
if self._derived_type_switch not in kwargs:
kwargs[self._derived_type_switch] = self._derived_type
super(Band, self).__init__(**kwargs)
[docs] @classmethod
def search(cls, client=None, request_params=None, headers=None):
"""A search query for all bands.
Returns an instance of the
:py:class:`~descarteslabs.catalog.Search` class configured for
searching bands. Call this on the :py:class:`Band` base class to search all
types of bands or classes :py:class:`SpectralBand`, :py:class:`MicrowaveBand`,
:py:class:`MaskBand`, :py:class:`ClassBand` and :py:class:`GenericBand` to search
only a specific type of band.
Parameters
----------
client : :py:class:`CatalogClient`
A `CatalogClient` instance to use for requests to the Descartes Labs
catalog.
Returns
-------
:py:class:`~descarteslabs.catalog.Search`
An instance of the :py:class:`~descarteslabs.catalog.Search` class
"""
search = super(Band, cls).search(
client, request_params=request_params, headers=headers
)
if cls._derived_type:
search = search.filter(properties.type == cls._derived_type)
return search
[docs]class BandCollection(Collection):
_item_type = Band
# set here due to circular references
Band._collection_type = BandCollection
[docs]class SpectralBand(Band):
"""A band that lies somewhere on the visible/NIR/SWIR electro-optical wavelength spectrum.
Instantiating a spectral band indicates that you want to create a *new* Descartes
Labs catalog spectral band. If you instead want to retrieve an existing catalog
spectral band use `Band.get() <descarteslabs.catalog.Band.get>`, or if
you're not sure use `SpectralBand.get_or_create()
<descarteslabs.catalog.SpectralBand.get_or_create>`. You can also use
`Band.search() <descarteslabs.catalog.Band.search>`. Also see the example
for :py:meth:`~descarteslabs.catalog.Band.save`.
Parameters
----------
client : CatalogClient, optional
A `CatalogClient` instance to use for requests to the Descartes Labs catalog.
The :py:meth:`~descarteslabs.catalog.CatalogClient.get_default_client` will
be used if not set.
kwargs : dict, optional
With the exception of readonly attributes (`created`, `modified`) and with the
exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
attribute listed below can also be used as a keyword argument. Also see
`~SpectralBand.ATTRIBUTES`.
"""
_derived_type = BandType.SPECTRAL.value
physical_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
doc=Band._DOC_PHYSICALRANGE,
)
physical_range_unit = Attribute(doc="str, optional: Unit of the physical range.")
wavelength_nm_center = Attribute(
doc="""float, optional: Weighted center of min/max responsiveness of the band, in nm.
*Filterable, sortable*.
"""
)
wavelength_nm_min = Attribute(
doc="""float, optional: Minimum wavelength this band is sensitive to, in nm.
*Filterable, sortable*.
"""
)
wavelength_nm_max = Attribute(
doc="""float, optional: Maximum wavelength this band is sensitive to, in nm.
*Filterable, sortable*.
"""
)
wavelength_nm_fwhm = Attribute(
doc="""float, optional: Full width at half maximum value of the wavelength spread, in nm.
*Filterable, sortable*.
"""
)
[docs]class MicrowaveBand(Band):
"""A band that lies in the microwave spectrum, often from SAR or passive radar sensors.
Instantiating a microwave band indicates that you want to create a *new* Descartes
Labs catalog microwave band. If you instead want to retrieve an existing catalog
microwave band use `Band.get() <descarteslabs.catalog.Band.get>`, or if
you're not sure use `MicrowaveBand.get_or_create()
<descarteslabs.catalog.MicrowaveBand.get_or_create>`. You can also use
`Band.search() <descarteslabs.catalog.Band.search>`. Also see the example
for :py:meth:`~descarteslabs.catalog.Band.save`.
Parameters
----------
client : CatalogClient, optional
A `CatalogClient` instance to use for requests to the Descartes Labs catalog.
The :py:meth:`~descarteslabs.catalog.CatalogClient.get_default_client` will
be used if not set.
kwargs : dict, optional
With the exception of readonly attributes (`created`, `modified`) and with the
exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
attribute listed below can also be used as a keyword argument. Also see
`~MicrowaveBand.ATTRIBUTES`.
"""
_derived_type = BandType.MICROWAVE.value
frequency = Attribute(
doc="""float, optional: Center frequency of the observed microwave in GHz.
*Filterable, sortable*.
"""
)
bandwidth = Attribute(
doc="""float, optional: Chirp bandwidth of the sensor in MHz.
*Filterable, sortable*.
"""
)
physical_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
doc=Band._DOC_PHYSICALRANGE,
)
physical_range_unit = Attribute(doc="str, optional: Unit of the physical range.")
[docs]class MaskBand(Band):
"""A binary band where by convention a 0 means masked and 1 means non-masked.
The `data_range` and `display_range` for masks is implicitly ``(0, 1)``.
Instantiating a mask band indicates that you want to create a *new* Descartes
Labs catalog mask band. If you instead want to retrieve an existing catalog
mask band use `Band.get() <descarteslabs.catalog.Band.get>`, or if
you're not sure use `MaskBand.get_or_create()
<descarteslabs.catalog.MaskBand.get_or_create>`. You can also use
`Band.search() <descarteslabs.catalog.Band.search>`. Also see the example
for :py:meth:`~descarteslabs.catalog.Band.save`.
Parameters
----------
client : CatalogClient, optional
A `CatalogClient` instance to use for requests to the Descartes Labs catalog.
The :py:meth:`~descarteslabs.catalog.CatalogClient.get_default_client` will
be used if not set.
kwargs : dict, optional
With the exception of readonly attributes (`created`, `modified`) and with the
exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
attribute listed below can also be used as a keyword argument. Also see
`~MaskBand.ATTRIBUTES`.
"""
_derived_type = BandType.MASK.value
is_alpha = BooleanAttribute(
doc="""bool, optional: Whether this band should be useable as an alpha band during rastering.
This enables special behavior for this band during rastering. If this is
``True`` and the band appears as the last band in a raster operation (such as
:meth:`descarteslabs.catalog.imagecollection.ImageCollection.mosaic` or
:meth:`descarteslabs.catalog.imagecollection.ImageCollection.stack`) pixels
with a value of 0 in this band will be treated as transparent.
"""
)
data_range = Attribute(mutable=False, doc="tuple(float, float), readonly: [0, 1].")
display_range = Attribute(
mutable=False, doc="tuple(float, float), readonly: [0, 1]."
)
[docs]class ClassBand(Band):
"""A band that maps a finite set of values that may not be continuous.
For example land use classification. A visualization with straight pixel values
is typically not useful, so commonly a colormap is used.
Instantiating a class band indicates that you want to create a *new* Descartes
Labs catalog class band. If you instead want to retrieve an existing catalog
class band use `Band.get() <descarteslabs.catalog.Band.get>`, or if
you're not sure use `ClassBand.get_or_create()
<descarteslabs.catalog.ClassBand.get_or_create>`. You can also use
`Band.search() <descarteslabs.catalog.Band.search>`. Also see the example
for :py:meth:`~descarteslabs.catalog.Band.save`.
Parameters
----------
client : CatalogClient, optional
A `CatalogClient` instance to use for requests to the Descartes Labs catalog.
The :py:meth:`~descarteslabs.catalog.CatalogClient.get_default_client` will
be used if not set.
kwargs : dict, optional
With the exception of readonly attributes (`created`, `modified`) and with the
exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
attribute listed below can also be used as a keyword argument. Also see
`~ClassBand.ATTRIBUTES`.
"""
_derived_type = BandType.CLASS.value
colormap_name = EnumAttribute(Colormap, doc=Band._DOC_COLORMAPNAME)
colormap = Attribute(doc=Band._DOC_COLORMAP)
class_labels = ListAttribute(
TypedAttribute(str),
doc="""list(str or None), optional: A list of labels.
A list of labels where each element is a name for the class with the value at
that index. Elements can be null if there is no label at that value.
""",
)
[docs]class GenericBand(Band):
"""A generic kind of band not fitting any other type.
For example mapping physical values like temperature or angles.
Instantiating a generic band indicates that you want to create a *new* Descartes
Labs catalog generic band. If you instead want to retrieve an existing catalog
generic band use `Band.get() <descarteslabs.catalog.Band.get>`, or if
you're not sure use `GenericBand.get_or_create()
<descarteslabs.catalog.GenericBand.get_or_create>`. You can also use
`Band.search() <descarteslabs.catalog.Band.search>`. Also see the example
for :py:meth:`~descarteslabs.catalog.Band.save`.
Parameters
----------
client : CatalogClient, optional
A `CatalogClient` instance to use for requests to the Descartes Labs catalog.
The :py:meth:`~descarteslabs.catalog.CatalogClient.get_default_client` will
be used if not set.
kwargs : dict, optional
With the exception of readonly attributes (`created`, `modified`) and with the
exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
attribute listed below can also be used as a keyword argument. Also see
`~GenericBand.ATTRIBUTES`.
"""
_derived_type = BandType.GENERIC.value
physical_range = TupleAttribute(
min_length=2,
max_length=2,
coerce=True,
attribute_type=float,
doc=Band._DOC_PHYSICALRANGE,
)
physical_range_unit = Attribute(doc="str, optional: Unit of the physical range.")
colormap_name = EnumAttribute(Colormap, doc=Band._DOC_COLORMAPNAME)
colormap = Attribute(doc=Band._DOC_COLORMAP)