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 hsi.open() and hsi.write() functions are able to open/write any of the supported formats. The format is determined by the provided extension. Any hsi.HSImage 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 = hsi.open("<path>.pam")
hsi.write(img, "<output-path>.hdr")

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

Metadata

Hyperspectral images are more than just the raw image data. Common metadata includes the wavelengths of the bands, camera capture metadata (exposure, gain, etc.), data type and image dimensions. The SDK supports reading and adding/modifying arbitrary metadata.

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 hsi.HSIHeader objects and each hsi.HSImage has a hsi.HSImage.header accessor that returns the header for that particular object.

# Show the current header
print(img.header)

# Operations will automatically update the relevant header data
modified = img.ensure_dtype(hsi.float32).binning(2, hsi.bands)
print(modified.header)
// Show the current header
hv_hsi_header_t* header = NULL;
err = hv_hs_image_header(img, &header);
if (err) return err;

char* header_str = hv_hsi_header_debug_fmt(header);
printf("%s\n", header_str);
free(header_str);

hv_hsi_header_free(&header);


// Operations will automatically update the relevant header data

// Ensure dtype
hv_hs_image_t* img_f32;
hv_hs_image_ensure_dtype(img, HV_DTYPE_F32, &img_f32);

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

hv_hsi_header_t* modified_header = NULL;
err = hv_hs_image_header(img, &modified_header);
if (err) return err;

// Show the modified header
char* modified_header_str = hv_hsi_header_debug_fmt(modified_header);
printf("%s\n", modified_header_str);
free(modified_header_str);

hv_hsi_header_free(&modified_header);

hv_hs_image_free(&modified);
hv_hs_image_free(&img_f32);
hv_hs_image_free(&img);

hsi.HSIHeader ’s can also be manually modified. This is especially useful for adding extra metadata or implementing custom operations which change some metadata property.

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

print(header)

# You can modify the metadata to your liking
header.dtype = hsi.float32
header.interleave hsi.bil

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

# It is also possible to create a header from scratch
header = hsi.HSIHeader(
    # These parameters are mandatory
    shape=hsi.Shape((2, 3, 4))
    interleave=hsi.bil,
    dtype=hsi.int64,
    # The rest are optional
    description="Plastics sample 2"
)
// Get header
hv_hsi_header_t* header = NULL;
hv_hs_image_header(img, &header);

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

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

char* header_str = hv_hsi_header_debug_fmt(header);
printf("%s\n", header_str);
free(header_str);

// It is also possible to create a header from scratch
hv_hsi_header_t* header_default = hv_hsi_header_default();

Header modification

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

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

# Instead, there is a special operation to take a modified header
img = img.with_header(header)

# But be careful. There is nothing preventing you from setting invalid header values which will
# lead to errors. The following example will fail if the source image is not large enough (which is unlikely)
header.shape = hsi.Shape(2000, 5000, 10000)
try:
    img.with_header(header).to_numpy()
// To update the header on an image object, you first have to retrieve and modify the header
hv_hsi_header_t* header;
hv_hs_image_header(img, &header);

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

// and then use an operation to update the image header
int err = hv_hs_image_with_header(img, header);

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