ENH: Add Image Reader/Writer that depend on Tiff and Stb libraries.#1585
Conversation
be6fe5f to
1a73598
Compare
JDuffeyBQ
left a comment
There was a problem hiding this comment.
The filter documentation needs to be added.
|
@JDuffeyBQ thanks for the review. Here's what changed: IImageIO layer — All three new filter headers — removed All three filter .cpp files — removed the now-empty Algorithm constructors — signatures changed from ReadImageFilter.cpp:
WriteImageFilter.cpp:
WriteImage.cpp:
ReadImageStack.cpp:
ReadImageStackFilter.cpp — the two Tests:
Documentation — wrote the three docs pages ( Deferred / pushed back on two items — replies on the inline threads:
All 42 image-IO unit tests pass locally on the Rel build. Happy to revisit the two deferred items if you'd rather have them in this PR. |
|
Did the scoped-enum cleanup and consolidate across both plugins. Pushed the following: New shared header — enum class OriginSpacingProcessing : uint64 { Preprocessed = 0, Postprocessed = 1 };
enum class ImageFlipTransform : uint64 { None = 0, FlipAboutXAxis = 1, FlipAboutYAxis = 2 };The underlying type is SimplnxCore filters
ITKImageProcessing — migrated to the same shared enum so there's only one vocabulary across both plugins:
Tests — all 84 image-IO tests ( I did NOT convert |
2d1992e to
032babf
Compare
JDuffeyBQ
left a comment
There was a problem hiding this comment.
Also unresolved some comments that weren't addressed.
|
@JDuffeyBQ round 3 pushed as 0a01e83. Summary of changes since 032babf: TiffImageIO — full rewrite of the error-handling path
ReadImageStack algorithm
Origin/spacing parameters switched to VectorFloat32
WriteImage
ScopedTempDir (test helper)
Verified
|
0a01e83 to
3361a60
Compare
Introduces three new SimplnxCore image I/O filters backed by a new format-agnostic IImageIO abstraction layer (stb for PNG/JPEG/BMP, libtiff for TIFF), fixes a multi-component conversion bug in the legacy ITK reader, and consolidates the test data archives for both filter families into two versioned _v3 archives. New SimplnxCore filters - ReadImageFilter, WriteImageFilter, ReadImageStackFilter with full feature parity with their ITKImageReaderFilter / ITKImageWriterFilter / ITKImportImageStackFilter counterparts (origin/spacing overrides with pre/post-processed timing, voxel and physical cropping, data type conversion, XY/XZ/YZ plane slicing on write, flip transforms, grayscale conversion, resampling). - Sub-filter invocation goes through direct filter construction on the stack; no FilterList/FilterHandle lookups. - Origin/spacing parameters are VectorFloat32 (matches ImageGeom's float32 storage; no narrowing at the filter boundary). New IImageIO abstraction layer (simplnx/Utilities/ImageIO/) - Strategy pattern with StbImageIO (PNG/JPEG/BMP via stb) and TiffImageIO (TIFF via libtiff) backends selected by a CreateImageIO extension-based factory. - Out-of-core safe: data is copied tuple-by-tuple into AbstractDataStore<T> via operator[] -- no raw pointers to DataArray internals. Write temp buffers are never larger than a single 2D slice. - TiffImageIO uses libtiff 4.5 per-handle error handlers (TIFFOpenOptionsSetErrorHandlerExtR via TIFFOpenExt) so captured error messages are not shared across threads or handles. - TiffImageIO rejects unsupported (bits-per-sample, sample-format) combinations up front instead of silently coercing to uint8. - Shared CreateIndexString<T> helper in ImageIOUtilities.hpp for the stack writers' zero-padded filename generation. Upstream ITK reader bug fix - ITKImageProcessing/Common/ReadImageUtils.hpp: ConvertImageToDataStoreAsType() was iterating only pixelContainer ->Size() elements, which is the pixel count, not the scalar element count. For multi-component images (e.g. RGB itk::Vector<uint8, 3>) this left two-thirds of the destination uninitialized. Fixed by multiplying by itk::NumericTraits<PixelT>::GetLength(). ImageIOEnums shared across SimplnxCore and ITKImageProcessing - OriginSpacingProcessing and ImageFlipTransform scoped enums in simplnx/Utilities/ImageIO/ImageIOEnums.hpp. - ITKImageReaderFilter, ITKImportImageStackFilter, ReadImageUtils migrated to use the same enums so both plugins share a single vocabulary. Tests - 42 SimplnxCore image-IO tests (ReadImage*, WriteImage*, ReadImageStack*) + 23 ITKImageProcessing image-IO tests pass. - Test data packaged as two versioned archives: itk_image_reader_test_v3.tar.gz and import_image_stack_test_v3.tar.gz (downloaded via download_test_data in CMakeLists.txt). - ScopedTempDir RAII helper in WriteImageTest for temp-dir cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3361a60 to
6087c40
Compare
- TiffImageIO: require SamplesPerPixel tag (TIFF 6.0 §7) instead of defaulting to 1, which would mis-read multi-channel files lacking the tag. - TiffImageIO: collapse the per-handle libtiff error/warning handlers + TIFFOpenOptions wrapper into a single TiffOpenOptions class that owns both the options pointer and the captured error string. Drops the OpenTiffFile helper and the FallbackMessage shim. - TypesUtility: add GetDataTypeSize(DataType) covering every DataType (matches the existing GetNumericTypeSize style) and replace the image-IO-only BytesPerImageElement helper with it. - ImageIOUtilities: inline ChoiceToImageDataType and the inverse in the header and have them throw std::runtime_error for unsupported inputs instead of silently returning a default. Removes the now empty ImageIOUtilities.cpp.
SummaryOverall this is a solid first iteration of the new image IO stack. The C++ best practices
Object-oriented design
Defensive programming
Memory allocation
CPU performance
Top 3 things to fix before merge
Honorable mention follow-ups (not blockers but worth filing): the index-based Posted by an automated reviewer agent (Opus 4.7) — use as input, not gospel. |
Design Review: PR #1585 (
|
Acts on the cross-cutting code review and design-review agent comments
posted on the PR.
Correctness fixes:
- ReadImage: float32 → integer pixel conversion divided by float32::max()
(~3.4e38) and produced black images for any HDR input. Fixed by
treating floating-point sources/destinations as having a [0, 1]
saturation range (stb's HDR convention) and clamping into that range
before normalizing.
- ReadImage: physical-cropping path now rejects out-of-bounds X/Y
minima with a meaningful error instead of producing a garbage xStart
that triggers an unhelpful "crop window does not fit" message.
- ReadImageStack: physical Z-cropping now errors out if
ImageGeom::getIndex returns nullopt instead of silently leaving the
full slice range; added a sanity check on the computed slice range.
- TiffImageIO: TIFFScanlineSize <= 0 is now an error rather than
cast-to-usize-and-allocate-16-EiB.
Robustness:
- ReadImageStackFilter: replaced four dynamic_cast-by-index lookups
on sub-filter outputActions with a FindFirstActionOfType<T> helper.
Sub-filters can now reorder or interleave actions without silently
breaking the stack filter at runtime.
- ImageIOFactory / StbImageIO: ::tolower replaced with an unsigned-char
lambda — the original was UB on platforms where char is signed.
Cosmetic / consistency:
- TiffImageIO: error-code constants use the simplnx int32 alias to
match StbImageIO.
- WriteImage: dropped the dead std::fill on sliceBuffer — the extract
loop overwrites every byte.
- ReadImageStack: renamed misleading tempDataStore variables in
FlipAbout{X,Y}Axis to dataStoreRef per project convention.
- ImageIOEnums: doc comment no longer claims reinterpret_cast<> from
ChoicesParameter::ValueType is valid (it isn't — only static_cast<>
from the underlying integral type is well-defined).
Discoverability:
- ITKImageReaderFilter: dropped the per-format tags ("jpg", "tiff",
"bmp", "png") so the new SimplnxCore ReadImageFilter wins
format-specific searches in the filter palette. Users still find
the ITK reader via "io"/"image" for the formats stb/libtiff don't
cover.
Acted on review-agent feedback in 3d9ae56Going through the items the two review agents flagged. Fixed in this commit: Correctness (top-3-blocker items from the cross-cutting review):
Robustness:
Cosmetic / consistency:
Discoverability (from the design review):
Deferred to follow-up issues (intentionally not in this PR):
42/42 image-IO tests pass on |
Acts on the cross-cutting code review and design-review agent comments
posted on the PR.
Correctness fixes:
- ReadImage: float32 → integer pixel conversion divided by float32::max()
(~3.4e38) and produced black images for any HDR input. Fixed by
treating floating-point sources/destinations as having a [0, 1]
saturation range (stb's HDR convention) and clamping into that range
before normalizing.
- ReadImage: physical-cropping path now rejects out-of-bounds X/Y
minima with a meaningful error instead of producing a garbage xStart
that triggers an unhelpful "crop window does not fit" message.
- ReadImageStack: physical Z-cropping now errors out if
ImageGeom::getIndex returns nullopt instead of silently leaving the
full slice range; added a sanity check on the computed slice range.
- TiffImageIO: TIFFScanlineSize <= 0 is now an error rather than
cast-to-usize-and-allocate-16-EiB.
Robustness:
- ReadImageStackFilter: replaced four dynamic_cast-by-index lookups
on sub-filter outputActions with a FindFirstActionOfType<T> helper.
Sub-filters can now reorder or interleave actions without silently
breaking the stack filter at runtime.
- ImageIOFactory / StbImageIO: ::tolower replaced with an unsigned-char
lambda — the original was UB on platforms where char is signed.
Cosmetic / consistency:
- TiffImageIO: error-code constants use the simplnx int32 alias to
match StbImageIO.
- WriteImage: dropped the dead std::fill on sliceBuffer — the extract
loop overwrites every byte.
- ReadImageStack: renamed misleading tempDataStore variables in
FlipAbout{X,Y}Axis to dataStoreRef per project convention.
- ImageIOEnums: doc comment no longer claims reinterpret_cast<> from
ChoicesParameter::ValueType is valid (it isn't — only static_cast<>
from the underlying integral type is well-defined).
Discoverability:
- ITKImageReaderFilter: dropped the per-format tags ("jpg", "tiff",
"bmp", "png") so the new SimplnxCore ReadImageFilter wins
format-specific searches in the filter palette. Users still find
the ITK reader via "io"/"image" for the formats stb/libtiff don't
cover.
3d9ae56 to
e60ba83
Compare
Acts on the cross-cutting code review and design-review agent comments
posted on the PR.
Correctness fixes:
- ReadImage: float32 → integer pixel conversion divided by float32::max()
(~3.4e38) and produced black images for any HDR input. Fixed by
treating floating-point sources/destinations as having a [0, 1]
saturation range (stb's HDR convention) and clamping into that range
before normalizing.
- ReadImage: physical-cropping path now rejects out-of-bounds X/Y
minima with a meaningful error instead of producing a garbage xStart
that triggers an unhelpful "crop window does not fit" message.
- ReadImageStack: physical Z-cropping now errors out if
ImageGeom::getIndex returns nullopt instead of silently leaving the
full slice range; added a sanity check on the computed slice range.
- TiffImageIO: TIFFScanlineSize <= 0 is now an error rather than
cast-to-usize-and-allocate-16-EiB.
Robustness:
- ReadImageStackFilter: replaced four dynamic_cast-by-index lookups
on sub-filter outputActions with a FindFirstActionOfType<T> helper.
Sub-filters can now reorder or interleave actions without silently
breaking the stack filter at runtime.
- ImageIOFactory / StbImageIO: ::tolower replaced with an unsigned-char
lambda — the original was UB on platforms where char is signed.
Cosmetic / consistency:
- TiffImageIO: error-code constants use the simplnx int32 alias to
match StbImageIO.
- WriteImage: dropped the dead std::fill on sliceBuffer — the extract
loop overwrites every byte.
- ReadImageStack: renamed misleading tempDataStore variables in
FlipAbout{X,Y}Axis to dataStoreRef per project convention.
- ImageIOEnums: doc comment no longer claims reinterpret_cast<> from
ChoicesParameter::ValueType is valid (it isn't — only static_cast<>
from the underlying integral type is well-defined).
Discoverability:
- ITKImageReaderFilter: dropped the per-format tags ("jpg", "tiff",
"bmp", "png") so the new SimplnxCore ReadImageFilter wins
format-specific searches in the filter palette. Users still find
the ITK reader via "io"/"image" for the formats stb/libtiff don't
cover.
e60ba83 to
59130ad
Compare
… TiffFile
Round-2 review feedback partially merged the libtiff error handlers and
TIFFOpenOptions wrapper into one class but left TiffHandleGuard
separate. JDuffeyBQ's original ask was for a single class — completing
the merge.
TiffFile now owns the full lifecycle:
- constructor allocates TIFFOpenOptions, registers per-handle error /
warning handlers, calls TIFFOpenExt, then frees the options (libtiff
copies the options into the TIFF* on open, so they don't need to
outlive the call);
- destructor calls TIFFClose;
- non-copyable AND non-movable (libtiff stores &m_ErrorMessage as
opaque user_data, so the address must stay stable);
- exposes get(), valid(), errorMessage().
Each call site reduces from two locals + a conditional construct to
one: TiffFile tiffFile(pathStr, "r"); if(!tiffFile.valid()) { ... }.
260b599 to
e9c104c
Compare
* Replace inline std::transform/std::tolower lambdas in ImageIOFactory and StbImageIO with nx::core::StringUtilities::toLower. * Extract the duplicated FindStb.cmake content from conda/bld.bat and conda/build.sh into conda/FindStb.cmake; both build scripts now copy the file rather than echo it line-by-line. * Trim implementation-detail prose from the OriginSpacingProcessing doc comment per JDuffey's suggestion. Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
Depends on BlueQuartzSoftware/simplnx-registry#30
Introduces three new SimplnxCore image I/O filters (ReadImageFilter, WriteImageFilter, ReadImageStackFilter) backed by a new format-agnostic IImageIO
abstraction layer, fixes a long-standing multi-component conversion bug in the legacy ITK reader, and consolidates the test data archives for both filter
families into two versioned _v3 archives.
New SimplnxCore filters
ITKImportImageStackFilter counterparts (origin/spacing overrides with pre/post-processed timing, voxel and physical cropping, data type conversion, XY/XZ/YZ
plane slicing on write, flip transforms, grayscale conversion, resampling).
New IImageIO abstraction layer (simplnx/Utilities/ImageIO/)
never larger than a single 2D slice.
Upstream ITK reader bug fix
count, not the scalar element count. For multi-component images (e.g. RGB itk::Vector<uint8, 3>) this left two-thirds of the destination uninitialized. Fixed
by multiplying by itk::NumericTraits::GetLength().