ADD georeferenced GeoTIFF export via to_raster() - #55
Merged
mhdned merged 1 commit intoAug 21, 2026
Merged
Conversation
With the exception of MosaicCalculator, no tool could write a georeferenced raster. _export_file() renders the result with matplotlib and saves a PNG, so the only documented output of every workflow was a picture of the computed array rather than the array. Three things happened at export. Values were quantised through a colormap to 256 levels per channel, so a pixel that was 0.6237 could not be recovered, ruling out thresholding, zonal statistics and date differencing. The pixel grid was resampled by dpi and bbox_inches with margins, colorbar and watermark baked in, leaving no pixel correspondence to the input - a 998x998 NDVI became a 9389x8035 RGBA canvas. And CRS and transform were discarded, so the result could not be loaded over other layers in a GIS. Add FileHandler.get_raster_profile(), reading CRS, transform, nodata and dtype via rasterio without touching the existing array-loading path, so no current tool changes behavior. Add BaseTool.to_raster(), writing the computed array as a GeoTIFF carrying the spatial referencing of the source band. Multi-component outputs such as PCA's (6, height, width) are written as multi-band rasters. Defaults follow raster-product convention: float32 for continuous results, which is well beyond reflectance precision and half the size of float64, and int32 for integer label maps such as classifications; NaN nodata for floating point; tiled, deflate-compressed with a horizontal differencing predictor, and BIGTIFF=IF_SAFER. A source without a CRS raises rather than silently writing an identity transform, which would produce a file that looks georeferenced while placing the scene at the coordinate origin. PNG export remains the default. Both paths are documented, including which one is the data product, and the paper's description of the package's outputs is corrected to match. Verified against the bundled Landsat subset: EPSG:32639 and the 30 m affine transform are preserved exactly, values round-trip with zero difference, and the output holds 517,535 distinct values where the PNG path allowed 256.
Member
|
Thank you, Mmz. These changes are approved by me. Regarding the changes to the paper, I read through them and didn’t find any problems. However, when working with agents, you should also consider the JOSS guidelines for the paper. For example, there are limits on the number of words, so we should make sure the changes comply with those requirements. Submitting a paper to JOSS: https://joss.readthedocs.io/en/latest/submitting.html @aradfarahani, please check if everything is okay to merge and close this. |
aradfarahani
approved these changes
Aug 21, 2026
aradfarahani
approved these changes
Aug 21, 2026
Member
|
@mhdned Merge it! |
mhdned
reviewed
Aug 21, 2026
| FEZrs is organized around a shared abstract base class (`BaseTool`) that standardizes the lifecycle of every analysis module: input validation, processing, visualization customization, and export. Each calculator (for example, `NDVICalculator`, `GaussianCalculator`, or `KMeansCalculator`) inherits this interface, accepts band file paths through a common file-handling layer, and exposes a small set of methods such as `execute` and `histogram_export`. This pattern keeps the public API uniform across spectral indices, filters, enhancement tools, change-detection utilities, and machine-learning modules, reducing cognitive overhead when composing multi-step workflows. | ||
|
|
||
| The package is modular by domain: (`spectral_indices`, `filters`, `image_enhancement`, `change_detection`, `clustering`, `glcm`, `pca`, `svm`, and others), while shared utilities handle band-path typing, file I/O, and histogram support. Raster inputs are typically multi-band geospatial imagery. Outputs are written as figures and processed products suitable for inspection, reporting, and further analysis. The design deliberately favors composition of independent calculators over a single monolithic pipeline object, so researchers can select only the methods required for a given study while still benefiting from consistent validation and export behavior. | ||
| The package is modular by domain: (`spectral_indices`, `filters`, `image_enhancement`, `change_detection`, `clustering`, `glcm`, `pca`, `svm`, and others), while shared utilities handle band-path typing, file I/O, and histogram support. Raster inputs are typically multi-band geospatial imagery. Results can be exported two ways: as rendered figures via `execute()`, for inspection and reporting, and as georeferenced GeoTIFF rasters via `to_raster()`, which preserve full numerical precision along with the coordinate reference system and affine transform of the source imagery, and are therefore suitable for GIS overlay, zonal statistics, and multitemporal analysis. The design deliberately favors composition of independent calculators over a single monolithic pipeline object, so researchers can select only the methods required for a given study while still benefiting from consistent validation and export behavior. |
Member
There was a problem hiding this comment.
According to the new changes, this paragraph has no issues.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #54 — review #50–#54 first; this PR targets
feat/svm-non-interactive-training.Problem
With the exception of
MosaicCalculator, no tool could write a georeferenced raster.BaseTool._export_file()renders through matplotlib and saves a PNG, so the only documented output of every workflow was a picture of the computed array rather than the array.process()returned the data but was not documented as the way to obtain results.Three separate losses at export:
0.6237cannot be recovered, so thresholding, zonal statistics over mapped units and multitemporal differencing are all off the table.dpi=1000withfigsizeandbbox_inches="tight"resamples the array into a figure canvas with margins, colorbar and watermark baked in — a 998×998 NDVI became a 9389×8035 RGBA canvas with no pixel correspondence to the input.Changes
FileHandler.get_raster_profile()reads CRS, transform, nodata and dtype through rasterio — already a declared dependency, previously used only by the mosaic tool. The existing array-loading path is untouched, so this is metadata capture alongside what is already there and no current tool changes behaviour.BaseTool.to_raster(output_path)writes the computed array as a GeoTIFF carrying the source band's spatial referencing. Multi-component outputs such as PCA's(6, height, width)become multi-band rasters.Defaults chosen for scene-scale raster products:
float32for continuous results — well beyond reflectance precision and half the size offloat64— andint32for integer label maps such as classifications, since writing a classification as float with NaN nodata is simply wrong.nodata=NaNfor floating point,tiled=True,compress="deflate"withpredictor=3for float data, andBIGTIFF="IF_SAFER". All overridable.A source without a CRS raises rather than silently writing an identity transform, which would produce a file that looks georeferenced while placing the scene at the coordinate origin — worse than refusing.
PNG export stays the default.
execute()is unchanged.Verified against the bundled Landsat subset
PCA writes 6 bands at 998×998, each round-tripping exactly.
On the framing question
The issue notes that if PNG-only output were a deliberate design decision, that would be a legitimate choice for a visualisation-oriented package but should be stated plainly, since "Geospatial Multispectral Image Processing" and "processed products suitable for inspection, reporting, and further analysis" lead a reader to expect georeferenced data products.
I went the other way and added the raster path, since the package's own framing promises it. Both outputs are now documented side by side with an explicit table of what each preserves, and
paper.md's description of the package's outputs is corrected to describe both rather than implying figures are data products.Not included
The full rasterio input rewrite — multi-band stacked GeoTIFF input, and nodata masking on read — is out of scope here. That would touch every calculator's array-loading path, and this PR is deliberately additive so nothing currently working changes. Worth its own issue; the
get_raster_profile()groundwork makes it straightforward. Note that until then, a source-declared nodata fill is still treated as valid data on read.Tests
421 pass (406 on #54, 15 new).
tests/raster_export_test.pycovers CRS and transform propagation, exact value round-trip, unresampled pixel grid, dtype selection for continuous versus label output, NaN nodata survival, multi-band PCA output, tiling and compression, the missing-CRS error, parent directory creation, and an end-to-end check against the bundled scene asserting the written raster matches both the source georeferencing andprocess()output exactly.