Introduction¶
The Hypervision SDK is built around the concept of declarative pipelines that are constructed by applying operations
on the hsi.HSImage hsi.HSImage
In general, we can group the kinds of hsi.HSImage
- Inputs
Any type that either holds its own data or represents an interface to a data producer like a file or camera.
- Operations
Any type that is composed of other
hsi.HSImageelements and performs some operation or transformation of their data.
Important
In this documentation, instances of the hsi.HSImage
Let us look at an example. We start by opening a file using the hsi.open()
img: hsi.HSImage = hsi.open("<path-to-file>")
hv_hs_image_t* img = hv_hsi_file_open("<path-to-file>");
The returned image object represents a file, but the data has not yet been loaded. Using the API, it is possible to fetch the data in a number of ways:
# All of these operations are blocking
array = img.to_numpy() # read the entire file into a NumPy array
plane = img.array_plane(200, hsi.bands) # read a single 2d plane
img = img.resolve() # read the file into an in-memory buffer
# This operation does not block
stream = img.stream() # create a stream object
// All of these operations are blocking
// read the entire file into an array
hv_array_t* array;
hv_hs_image_full(img, &array);
// read a single 2d plane
hv_array_t* plane;
hv_hs_image_array_plane(img, 200, HV_AXIS_BANDS, &plane);
// read the file into an in-memory buffer
hv_hs_image_t* img_array;
hv_hs_image_resolve(img, &img_array);
// This operation does not block
// create a stream object
hv_stream_t* stream;
hv_hs_image_stream(img, &stream);
Not that all of these are blocking, except the hsi.HSImage.stream() hsi.Stream
Note
The other operations use hsi.Stream
Important
A running hsi.Stream hsi.HSImage.to_numpy()
Only use hsi.HSImage.stream()
Warning
The hsi.Stream
You can expect the type to be particularly useful for interfacing the SDK with your own custom processes.
A complete example¶
To better understand how pipelines work in the SDK, let’s take a look at a complete example of a calculation that is used in hyperspectral image analysis, reflectance calibration:
import hsi
# Open the input files
img = hsi.open("image.pam")
white_img = hsi.open("white_ref.pam")
dark_img = hsi.open("dark_ref.pam")
# Convert the main image to float32
img = img.ensure_dtype(hsi.float32)
# Pre-calculate the white reference
white_ref = white_img
.ensure_dtype(hsi.float32)
.mean_axis(hsi.lines)
.resolve()
# Pre-calculate the dark reference
dark_ref = dark_img
.ensure_dtype(hsi.float32)
.mean_axis(hsi.lines)
.resolve()
# Perform the calibration itself
calibrated = (img - dark_ref) / (white_ref - dark_ref)
# Write the output to disk
hsi.write(calibrated, "calibrated.pam")
int err = 0;
// Open files and get HSImage handles
hv_hsi_file_t* f_img = NULL;
hv_hsi_file_t* f_white = NULL;
hv_hsi_file_t* f_dark = NULL;
err = hv_hsi_file_open("image.pam", &f_img);
if (err) return err;
err = hv_hsi_file_open("white_ref.pam", &f_white);
if (err) return err;
err = hv_hsi_file_open("dark_ref.pam", &f_dark);
if (err) return err;
hv_hs_image_t* img = NULL;
hv_hs_image_t* white_img = NULL;
hv_hs_image_t* dark_img = NULL;
img = hv_hsi_file_to_image(&f_img);
white_img = hv_hsi_file_to_image(&f_white);
dark_img = hv_hsi_file_to_image(&f_dark);
// Create a float32_t version of the image
hv_hs_image_t* img_f32 = NULL;
err = hv_hs_image_as_dtype(img, HV_DTYPE_F32, &img_f32);
if (err) return err;
hv_hs_image_free(&img);
// Create the white reference using the following steps:
// 1. Convert the data type to float32_t
hv_hs_image_t* white_f32 = NULL;
err = hv_hs_image_as_dtype(white_img, HV_DTYPE_F32, &white_f32);
if (err) return err;
// 2. Compute the mean image along the lines axis
hv_hs_image_t* white_mean = NULL;
err = hv_hs_image_mean_axis(white_f32, HV_AXIS_LINES, &white_mean);
if (err) return err;
// 3. Resolve the computation to an in-memory array
hv_hs_image_t* white_ref = NULL;
err = hv_hs_image_resolve(white_mean, &white_ref);
if (err) return err;
hv_hs_image_free(&white_img);
hv_hs_image_free(&white_f32);
hv_hs_image_free(&white_mean);
// Create the dark reference using the same steps.
hv_hs_image_t* dark_f32 = NULL;
err = hv_hs_image_as_dtype(dark_img, HV_DTYPE_F32, &dark_f32);
if (err) return err;
hv_hs_image_t* dark_mean = NULL;
err = hv_hs_image_mean_axis(dark_f32, HV_AXIS_LINES, &dark_mean);
if (err) return err;
hv_hs_image_t* dark_ref = NULL;
err = hv_hs_image_resolve(dark_mean, &dark_ref);
if (err) return err;
hv_hs_image_free(&dark_img);
hv_hs_image_free(&dark_f32);
hv_hs_image_free(&dark_mean);
// Calculate the reflectance using the formula (img - dark) / (white - dark)
// 1. Subtract the dark reference from the image
hv_hs_image_t* img_minus_dark = NULL;
err = hv_hs_image_sub(img_f32, dark_ref, &img_minus_dark);
if (err) return err;
// 2. Subtract the dark reference from the white reference
hv_hs_image_t* white_minus_dark = NULL;
err = hv_hs_image_sub(white_ref, dark_ref, &white_minus_dark);
if (err) return err;
// 3. Divide the two intermediary results
hv_hs_image_t* calibrated = NULL;
err = hv_hs_image_div(img_minus_dark, white_minus_dark, &calibrated);
if (err) return err;
// Write the result to disk (adjust path/format as needed)
err = hv_hs_image_write(calibrated_buf, "calibrated.pam");
if (err) return err;
// Cleanup
hv_hs_image_free(&img_f32);
hv_hs_image_free(&white_ref);
hv_hs_image_free(&dark_ref);
hv_hs_image_free(&img_minus_dark);
hv_hs_image_free(&white_minus_dark);
hv_hs_image_free(&calibrated);
hv_hs_image_free(&calibrated_buf);
The example combines multiple source images and operations to achieve a result. Much of the actual computation is
spent generating each 2d reference from a reference data cube. Note how hsi.HSImage.resolve() white_ref or
dark_ref would run the computation again.
Knowing when to use hsi.HSImage.resolve() hsi.HSImage.mean_axis()
Tip
Use caching whenever you have to reuse the result of a computation, especially when that computation is expensive to run.
Warning
Be mindful about caching large images if memory is limited.
For example, a \(1000\times 1296\) image with \(920\) bands taken using the camera’s 8-bit mode, takes up
\(\sim 1.2 \text{gb}\). If the data type is changed to hsi.float64