diff --git a/fixtures/other/grayscale_no_samplesperpixel.tif b/fixtures/other/grayscale_no_samplesperpixel.tif new file mode 100644 index 00000000..f158ddee Binary files /dev/null and b/fixtures/other/grayscale_no_samplesperpixel.tif differ diff --git a/fixtures/other/readme.md b/fixtures/other/readme.md index 94efdd04..870c60ac 100644 --- a/fixtures/other/readme.md +++ b/fixtures/other/readme.md @@ -1,3 +1,11 @@ +`grayscale_no_samplesperpixel.tif` is a minimal (114-byte) little-endian classic +TIFF: a 2x2 uint8 grayscale image, uncompressed, single strip. It deliberately +**omits the `SamplesPerPixel` tag (277)**, which the TIFF 6.0 specification +defines with a default of 1. It is a regression fixture for parsing files that +omit the tag (e.g. PerkinElmer/Phenix microscopy images), which previously +panicked with "samples_per_pixel not found". It was generated by hand-writing +the TIFF header, IFD and pixel bytes with Python's `struct` module. + `geogtowgs_subset_USGS_13_s14w171.tif` was created from "s3://prd-tnm/StagedProducts/Elevation/13/TIFF/current/s14w171/USGS_13_s14w171.tif" using these commands: ```bash diff --git a/src/ifd.rs b/src/ifd.rs index 43032488..60bc6dee 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -372,7 +372,10 @@ impl ImageFileDirectory { geo_key_directory = Some(GeoKeyDirectory::from_tags(tags)?); } - let samples_per_pixel = samples_per_pixel.expect("samples_per_pixel not found"); + // SamplesPerPixel (tag 277) defaults to 1 when the tag is absent. + // TIFF 6.0 spec, "SamplesPerPixel ... Default = 1": https://download.osgeo.org/libtiff/doc/TIFF6.pdf + // Many single-channel grayscale TIFFs (e.g. PerkinElmer/Phenix microscopy) omit it. + let samples_per_pixel = samples_per_pixel.unwrap_or(1); let planar_configuration = if let Some(planar_configuration) = planar_configuration { planar_configuration } else if samples_per_pixel == 1 { diff --git a/src/test/missing_samples_per_pixel.rs b/src/test/missing_samples_per_pixel.rs new file mode 100644 index 00000000..1a8632ee --- /dev/null +++ b/src/test/missing_samples_per_pixel.rs @@ -0,0 +1,23 @@ +use crate::test::util::open_tiff; +use crate::tags::PhotometricInterpretation; + +/// Regression test for TIFFs that omit the `SamplesPerPixel` tag (277). +/// +/// The TIFF 6.0 specification defines a default of 1 for `SamplesPerPixel`, and +/// some single-channel grayscale TIFFs (e.g. PerkinElmer/Phenix microscopy +/// images) omit the tag entirely. Parsing such files used to panic with +/// "samples_per_pixel not found"; it must instead fall back to the spec default. +#[tokio::test] +async fn test_missing_samples_per_pixel_defaults_to_one() { + let filename = "other/grayscale_no_samplesperpixel.tif"; + let (_reader, tiff) = open_tiff(filename).await; + let ifd = &tiff.ifds()[0]; + + assert_eq!(ifd.samples_per_pixel(), 1); + assert_eq!(ifd.image_width(), 2); + assert_eq!(ifd.image_height(), 2); + assert_eq!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + ); +} diff --git a/src/test/mod.rs b/src/test/mod.rs index 200a5622..9634ae9f 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -1,5 +1,6 @@ mod geo; mod geotiff_test_data; mod image_tiff; +mod missing_samples_per_pixel; mod ome_tiff; pub(crate) mod util;