Files and metadata

The Hypervision SDK supports multiple different hyperspectral image formats with a simple and unified interface for reading/writing data.

The supported file types are: ENVI, PAM, and Tiff. The table below shows the supported features of each format.

Table 1 File format features

Feature

ENVI

PAM

Tiff

Interleave BSQ

Interleave BIL/BIP

(with custom TUPLTYPE)

Wavelength

Arbitrary metadata

Jpeg compression

Working with files

The qtec_hv_sdk.open() and qtec_hv_sdk.write() functions are able to open/write any of the supported formats. The format is determined by the provided extension. Any qtec_hv_sdk.Image pipeline can be written directly to file.

# All supported file formats are compatible with each other.
# The open/write functions determine the desired format from the file extension.
img = hs.open("<path>.pam")
hs.write(img, "<output-path>.hdr")

# You can write arbitrary qtec_hv_sdk.Image's to a file.
img = qtec_hv_sdk.Image.from_numpy(np.ones((200, 100, 50), dtype=np.uint8), hs.bsq)
hs.write(img, "<output-path>")

Metadata

Hyperspectral images are more than just the raw image data. Common metadata includes dimensions and data type, wavelengths, camera and optics information, radiometric scaling, acquisition timing, calibration provenance, sensor artifacts, pixel encoding, and display hints. The SDK stores these values in a single metadata object with grouped sub-objects for each category.

Warning

Both ENVI and Tiff support arbitrary metadata, but PAM only supports the image size and interleave. Therefore, all other metadata will be ignored when writing a PAM file.

All metadata is stored in qtec_hv_sdk.ImageMeta objects and each qtec_hv_sdk.Image has a qtec_hv_sdk.Image.meta accessor that returns the metadata for that particular object.

# Show the current metadata
print(img.meta)

# Operations will automatically update the relevant metadata
modified = img.ensure_dtype(hs.float32).binning(2, hs.bands)
print(modified.meta)
// Show the current metadata
hv_image_meta_t* meta = NULL;
err = hv_image_meta(img, &meta);
if (err) return err;

char* meta_str = hv_image_meta_debug_fmt(meta);
printf("%s\n", meta_str);
free(meta_str);

hv_image_meta_free(&meta);


// Operations will automatically update the relevant metadata

// Ensure dtype
hv_image_t* img_f32;
hv_image_ensure_dtype(img, HV_DTYPE_F32, &img_f32);

// Apply a binning of 2 for the bands
hv_image_t* modified;
hv_image_binning(img_f32, 2, HV_AXIS_BANDS, &modified);

hv_image_meta_t* modified_meta = NULL;
err = hv_image_meta(img, &modified_meta);
if (err) return err;

// Show the modified metadata
char* modified_meta_str = hv_image_meta_debug_fmt(modified_meta);
printf("%s\n", modified_meta_str);
free(modified_meta_str);

hv_image_meta_free(&modified_meta);

hv_image_free(&modified);
hv_image_free(&img_f32);
hv_image_free(&img);

qtec_hv_sdk.ImageMeta objects can also be manually modified. This is especially useful for adding extra metadata or implementing custom operations which change some metadata property. The core image layout is stored directly on the metadata object, while richer metadata is grouped into categories:

Table 2 Metadata categories

Property

Python class

Typical contents

shape_meta

qtec_hv_sdk.ShapeMeta

Raw memory layout, plane shape, and interleave

camera

qtec_hv_sdk.CameraMeta

Camera, lens, serial numbers, and pixel pitch

spectral

qtec_hv_sdk.SpectralMeta

Wavelengths, spectral binning, response FWHM

spatial

qtec_hv_sdk.SpatialMeta

Focal length, slit width, spatial ROI, FOV

radiometric

qtec_hv_sdk.RadiometricMeta

Measurement unit, limits, scaling, gain, noise

temporal

qtec_hv_sdk.TemporalMeta

Exposure, frame time, averaging, trigger mode, timestamps

calibration

qtec_hv_sdk.CalibrationMeta

Calibration process, date, and extra calibration info

sensor

qtec_hv_sdk.SensorMeta

Bad pixels, bad bands, repaired pixels

encoding

qtec_hv_sdk.EncodingMeta

Byte order, scale factor, zero point, bits per pixel

display

qtec_hv_sdk.DisplayMeta

Default display bands and band names

Common fields from the original API remain available as convenience properties. For example, meta.default_bands updates meta.display.default_bands, meta.wavelength_info updates meta.spectral.wavelengths, meta.measurement_info maps to the matching radiometric fields, and meta.byte_order updates meta.encoding.byte_order.

# Get the metadata of the image (note that this is a read-only property, you can't set a new header on an image directly)
meta = img.meta

print(meta)

# You can modify the core metadata to your liking
meta.dtype = hs.float32
meta.interleave = hs.bil

# New metadata is grouped by category
meta.camera = hs.CameraMeta(
    manufacturer="Qtechnology",
    model_name="Hypervision 1700",
    serial="1234",
)
meta.spectral = hs.SpectralMeta(
    wavelengths=hs.WavelengthMeta(
        [450.0, 550.0, 650.0],
        hs.WavelengthUnit.Nanometer,
    ),
)
meta.radiometric = hs.RadiometricMeta(
    unit=hs.MeasurementUnit.Reflectance,
    limits=hs.MeasurementLimits(0.0, 1.0),
    scaling=1000.0,
)
meta.temporal = hs.TemporalMeta(exposure_time_ms=12.5)
meta.display = hs.DisplayMeta(default_bands=[2, 1, 0])

# You can even set arbitrary attributes on the metadata
# These will be saved for the supported file formats.
meta["custom_field"] = {"val": 2, "otherval": [True, False]}
print(meta["custom_field"])

# It is also possible to create metadata from scratch.
meta = hs.ImageMeta(
    shape=hs.ShapeMeta(
        streaming_dim=500,
        plane_dim=[920, 1096],
        interleave=hs.bil,
    ),
    dtype=hs.int64,
    description="Plastics sample 2",
    camera=hs.CameraMeta(model_name="Lab camera"),
    display=hs.DisplayMeta(default_bands=[0, 1, 1]),
)
// Get metadata
hv_image_meta_t* meta = NULL;
hv_image_meta(img, &meta);

// You can modify the metadata to your liking
meta->dtype = HV_DTYPE_F32;
hv_shape_meta_set_interleave(meta->shape, HV_INTERLEAVE_B_I_L);

// You can even set arbitrary attributes on the metadata
// These will be saved for the supported file formats.
hv_image_meta_set(meta, "custom_field", "{\"val\": 2, \"otherval\": [true, false]");

char* meta_str = hv_image_meta_debug_fmt(meta);
printf("%s\n", meta_str);
free(meta_str);

// It is also possible to create a metadata object from scratch
hv_image_meta_t* meta_default = hv_image_meta_default();

Metadata modification

At some point, you want to use your modified metadata objects in a qtec_hv_sdk.Image object. This has to be done by using the API to create a pipeline operation as shown below:

# You can't modify the metadata of an HSImage directly
try:
    img.meta = meta
except AttributeError:
    print("Can't write image metadata.")

# Instead, there is a special operation to take a modified metadata object
img = img.with_meta(meta)

# But be careful. There is nothing preventing you from setting invalid metadata values which will
# lead to errors. The following example will fail if the source image is not large enough (which is unlikely)
meta.shape = hs.Shape(2000, 5000, 10000)
try:
    img.with_meta(meta).to_numpy()
except ValueError:
    print("Shape does not match the source image.")
// To update the metadata on an image object, you first have to retrieve and modify the metadata
hv_image_meta_t* meta;
hv_image_meta(img, &meta);

hv_shape_meta_set_lines(meta->shape, 2000);
hv_shape_meta_set_samples(meta->shape, 5000);
hv_shape_meta_set_bands(meta->shape, 10000);

// and then use an operation to update the image metadata
int err = hv_image_with_meta(img, meta);

// But be careful. There is nothing preventing you from setting invalid meta values which will
// lead to errors. The example above will fail if the source image is not large enough (which is unlikely)