Skip to content

Envi Writer

Module: spectralbridge.envi_writer.

ENVI header and float32 BSQ writing helpers adapted from HyTools.

The current implementation is modified and narrowed to SpectralBridge's output contracts; its implementation lineage remains documented.

HyTools: Hyperspectral image processing library Copyright (C) 2021 University of Wisconsin Authors named in the relevant source notices: Adam Chlus, Zhiwei Ye, Philip Townsend. License: GNU General Public License, version 3 Upstream: https://github.com/EnSpec/hytools Audited revision: 31286d64541791a9815d29443a33726fa4d54031

Modified for SpectralBridge. See LICENSE, NOTICE, and HYTOOLS_PROVENANCE.md.

API glossary ยท Workflow / scientific context

Signatures and documentation below are collected from the current source. Advanced objects are not newly declared stable by inclusion here.

Audience: advanced.

build_envi_header_text

build_envi_header_text(header_dict: Dict) -> str

Build the ENVI .hdr file text from a header_dict.

Attribution: Adapted from hytools.io.envi.write_envi_header() and hytools.io.envi.envi_header_from_neon() (GPLv3). HyTools Authors: Adam Chlus, Zhiwei Ye, Philip Townsend. This simplified version is NEON-only and assumes BSQ float32 output.

Parameters:

Name Type Description Default
header_dict dict

A dictionary with at least the following keys: 'samples' int 'lines' int 'bands' int 'interleave' str (must be 'bsq' or 'BSQ') 'data type' int (ENVI code 4 -> float32) 'byte order' int (0 for little endian) 'wavelength' list[float] 'fwhm' list[float] 'wavelength units' str 'map info' list or str 'projection' str

You may also include optional keys like: 'description' 'transform' 'ulx' 'uly'

required

Returns:

Type Description
str

A string containing the full .hdr contents to be written to disk.

Behavior
- You MUST write a header that ENVI (and typical GDAL ENVI readers)
  understand.
- The header should begin with "ENVI" on the first line.
- Then list key/value lines in ENVI style, for example:

    samples = 1000
    lines   = 500
    bands   = 426
    data type = 4
    interleave = bsq
    byte order = 0
    map info = {UTM, 1.0, 1.0, 500000.0, 4420000.0, 1.0, -1.0, 13, North, WGS-84}
    projection = <...WKT or similar...>
    wavelength = { 400.0, 401.0, ... }
    fwhm = { ... }
    wavelength units = Nanometers

- You MUST serialize lists (like wavelength, fwhm) into ENVI-style
  curly-brace lists separated by commas.
- You MUST serialize "map info" consistently with how hytools writes it.
  "map info" in ENVI is written as:

      map info = {UTM, 1.0, 1.0, <ulx>, <uly>, <xres>, <yres>, <utm zone>, North, WGS-84}

  If header_dict['map info'] is already such a list, just join it.

- Include 'description' in the header if header_dict has it. Wrap it
  ENVI-style like:
      description = { BRDF+topographic corrected reflectance ... }

Implementation details: - Make sure numeric types are converted to Python floats/ints before string join, so numpy scalars don't show up weird.

Audience: advanced.

EnviWriter

EnviWriter(out_stem: Path, header_dict: Dict)

EnviWriter assembles a corrected hyperspectral cube into a BSQ ENVI file.

Portions adapted from hytools.io.envi (GPLv3). HyTools Authors: Adam Chlus, Zhiwei Ye, Philip Townsend. This version is NEON-only (no GLT, no NetCDF).

Usage pattern:

header = cube.build_envi_header()
# optionally edit header['description'] before writing

writer = EnviWriter(out_stem=Path("/path/to/output_stem"),
                    header_dict=header)

for (ys, ye, xs, xe, chunk) in cube.iter_chunks(...):
    corrected = ... # run apply_topo_correct(), apply_brdf_correct(), etc.
    corrected = corrected.astype("float32", copy=False)
    writer.write_chunk(corrected, ys, xs)

writer.close()

After close(), you'll have: /path/to/output_stem.img /path/to/output_stem.hdr

Parameters:

Name Type Description Default
out_stem Path

Output path WITHOUT extension. We will create: .img .hdr

required
header_dict dict

The header dict produced by NeonCube.build_envi_header(), possibly updated to include a human-readable description like "BRDF+topographic corrected reflectance".

required
Behavior
  • Extract: samples = header_dict["samples"] lines = header_dict["lines"] bands = header_dict["bands"]

  • Allocate a memory-mapped array shaped: (bands, lines, samples) dtype float32, order='C'.

This is a BSQ layout: band-major on disk.

  • Store that memmap in self._mm so we can fill it incrementally.
  • Store header_dict and out_stem so we can write the .hdr in close().

Implementation detail: Use numpy.memmap with mode='w+'. The filename MUST be f"{out_stem}.img".

write_chunk

write_chunk(corrected_chunk: ndarray, ys: int, xs: int) -> None

Write a corrected reflectance chunk into the BSQ memmap.

Parameters:

Name Type Description Default
corrected_chunk ndarray

Shape (y_size, x_size, bands), float32. This chunk is aligned with cube.data[ys:ys+y_size, xs:xs+x_size, :].

required
ys int

Row offset (top of chunk in full image coordinates).

required
xs int

Column offset (left of chunk in full image coordinates).

required
Behavior

For each band index b: self._mm[b, ys:ys+y_size, xs:xs+x_size] = corrected_chunk[:, :, b]

You MUST do band-major assignment to match BSQ layout.

This method should NOT flush to disk; flushing happens in close().

Assumptions: - corrected_chunk is already float32 - corrected_chunk has no-data values already handled

close

close() -> None

Finalize the ENVI output:

Behavior
  • Flush/close the memmap.
  • Write the .hdr file next to the .img file using the build_envi_header_text() function.

Implementation detail: - .img path is f"{self.out_stem}.img" - .hdr path is f"{self.out_stem}.hdr" - build the header string via build_envi_header_text(self.header_dict) and write it as ASCII/UTF-8 text.