diff --git a/python/python/async_tiff/_tiff.pyi b/python/python/async_tiff/_tiff.pyi index 4c62204..d9bcade 100644 --- a/python/python/async_tiff/_tiff.pyi +++ b/python/python/async_tiff/_tiff.pyi @@ -1,11 +1,13 @@ from typing import Protocol -from ._tile import Tile -from ._ifd import ImageFileDirectory -from .store import ObjectStore # Fix exports from obspec._get import GetRangeAsync, GetRangesAsync +from ._ifd import ImageFileDirectory +from ._tile import Tile +from .enums import Endianness +from .store import ObjectStore + class ObspecInput(GetRangeAsync, GetRangesAsync, Protocol): """Supported obspec input to reader.""" @@ -33,6 +35,10 @@ class TIFF: Returns: A TIFF instance. """ + + @property + def endianness(self) -> Endianness: + """The endianness of this TIFF file.""" @property def ifds(self) -> list[ImageFileDirectory]: """Access the underlying IFDs of this TIFF. diff --git a/python/python/async_tiff/enums.py b/python/python/async_tiff/enums.py index 724b77b..b40818b 100644 --- a/python/python/async_tiff/enums.py +++ b/python/python/async_tiff/enums.py @@ -21,6 +21,11 @@ class CompressionMethod(IntEnum): PackBits = 0x8005 +class Endianness(IntEnum): + LittleEndian = 0 + BigEndian = 1 + + class PhotometricInterpretation(IntEnum): WhiteIsZero = 0 BlackIsZero = 1 diff --git a/python/src/enums.rs b/python/src/enums.rs index e3a6de7..844a9d0 100644 --- a/python/src/enums.rs +++ b/python/src/enums.rs @@ -1,3 +1,4 @@ +use async_tiff::reader::Endianness; use async_tiff::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, SampleFormat, @@ -39,6 +40,30 @@ impl<'py> IntoPyObject<'py> for PyCompressionMethod { } } +pub(crate) struct PyEndianness(Endianness); + +impl From for PyEndianness { + fn from(value: Endianness) -> Self { + Self(value) + } +} + +impl<'py> IntoPyObject<'py> for PyEndianness { + type Target = PyAny; + type Output = Bound<'py, PyAny>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let enums_mod = py.import(intern!(py, "async_tiff.enums"))?; + let endianness_enum = enums_mod.getattr(intern!(py, "Endianness"))?; + + match self.0 { + Endianness::LittleEndian => endianness_enum.getattr("LittleEndian"), + Endianness::BigEndian => endianness_enum.getattr("BigEndian"), + } + } +} + pub(crate) struct PyPhotometricInterpretation(PhotometricInterpretation); impl From for PyPhotometricInterpretation { @@ -132,6 +157,7 @@ impl<'py> IntoPyObject<'py> for PySampleFormat { to_py_enum_variant(py, intern!(py, "SampleFormat"), self.0.to_u16()) } } + fn to_py_enum_variant<'py>( py: Python<'py>, enum_name: &Bound<'py, PyString>, diff --git a/python/src/tiff.rs b/python/src/tiff.rs index bbf9c25..e366341 100644 --- a/python/src/tiff.rs +++ b/python/src/tiff.rs @@ -9,6 +9,7 @@ use pyo3::prelude::*; use pyo3::types::PyType; use pyo3_async_runtimes::tokio::future_into_py; +use crate::enums::PyEndianness; use crate::error::PyAsyncTiffResult; use crate::reader::StoreInput; use crate::tile::PyTile; @@ -29,8 +30,7 @@ async fn open( .with_initial_size(prefetch) .with_multiplier(multiplier); let mut metadata_reader = TiffMetadataReader::try_open(&metadata_fetch).await?; - let ifds = metadata_reader.read_all_ifds(&metadata_fetch).await?; - let tiff = TIFF::new(ifds); + let tiff = metadata_reader.read(&metadata_fetch).await?; Ok(PyTIFF { tiff, reader }) } @@ -56,6 +56,11 @@ impl PyTIFF { Ok(cog_reader) } + #[getter] + fn endianness(&self) -> PyEndianness { + self.tiff.endianness().into() + } + #[getter] fn ifds(&self) -> Vec { let ifds = self.tiff.ifds(); diff --git a/python/tests/test_cog.py b/python/tests/test_cog.py index 05f5ca0..499fc73 100644 --- a/python/tests/test_cog.py +++ b/python/tests/test_cog.py @@ -13,6 +13,8 @@ async def test_cog_s3(): store = S3Store("sentinel-cogs", region="us-west-2", skip_signature=True) tiff = await TIFF.open(path=path, store=store) + assert tiff.endianness == enums.Endianness.LittleEndian + ifds = tiff.ifds assert len(ifds) == 5 diff --git a/src/cog.rs b/src/cog.rs index b58f2be..fe43efb 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -1,21 +1,28 @@ use crate::ifd::ImageFileDirectory; +use crate::reader::Endianness; /// A TIFF file. #[derive(Debug, Clone)] pub struct TIFF { + endianness: Endianness, ifds: Vec, } impl TIFF { /// Create a new TIFF from existing IFDs. - pub fn new(ifds: Vec) -> Self { - Self { ifds } + pub fn new(ifds: Vec, endianness: Endianness) -> Self { + Self { ifds, endianness } } /// Access the underlying Image File Directories. pub fn ifds(&self) -> &[ImageFileDirectory] { &self.ifds } + + /// Get the endianness of the TIFF file. + pub fn endianness(&self) -> Endianness { + self.endianness + } } #[cfg(test)] @@ -41,7 +48,7 @@ mod test { let cached_reader = ReadaheadMetadataCache::new(reader.clone()); let mut metadata_reader = TiffMetadataReader::try_open(&cached_reader).await.unwrap(); let ifds = metadata_reader.read_all_ifds(&cached_reader).await.unwrap(); - let tiff = TIFF::new(ifds); + let tiff = TIFF::new(ifds, metadata_reader.endianness()); let ifd = &tiff.ifds[1]; let tile = ifd.fetch_tile(0, 0, reader.as_ref()).await.unwrap(); diff --git a/src/metadata/reader.rs b/src/metadata/reader.rs index 53e0b9e..3a6fe0c 100644 --- a/src/metadata/reader.rs +++ b/src/metadata/reader.rs @@ -9,7 +9,7 @@ use crate::metadata::MetadataFetch; use crate::reader::Endianness; use crate::tiff::tags::{Tag, Type}; use crate::tiff::{TiffError, TiffFormatError, Value}; -use crate::ImageFileDirectory; +use crate::{ImageFileDirectory, TIFF}; /// Entry point to reading TIFF metadata. /// @@ -135,6 +135,12 @@ impl TiffMetadataReader { } Ok(ifds) } + + /// Read all IFDs from the file and return a complete TIFF structure. + pub async fn read(&mut self, fetch: &F) -> AsyncTiffResult { + let ifds = self.read_all_ifds(fetch).await?; + Ok(TIFF::new(ifds, self.endianness)) + } } /// Reads the [`ImageFileDirectory`] metadata. diff --git a/tests/image_tiff/util.rs b/tests/image_tiff/util.rs index 0b7e0c3..0caba64 100644 --- a/tests/image_tiff/util.rs +++ b/tests/image_tiff/util.rs @@ -14,6 +14,5 @@ pub(crate) async fn open_tiff(filename: &str) -> TIFF { let reader = Arc::new(ObjectReader::new(store.clone(), path.as_str().into())) as Arc; let mut metadata_reader = TiffMetadataReader::try_open(&reader).await.unwrap(); - let ifds = metadata_reader.read_all_ifds(&reader).await.unwrap(); - TIFF::new(ifds) + metadata_reader.read(&reader).await.unwrap() } diff --git a/tests/ome_tiff.rs b/tests/ome_tiff.rs index 2864cbe..6532fad 100644 --- a/tests/ome_tiff.rs +++ b/tests/ome_tiff.rs @@ -16,8 +16,7 @@ async fn open_remote_tiff(url: &str) -> TIFF { let reader = Arc::new(ObjectReader::new(Arc::new(store), path)) as Arc; let cached_reader = ReadaheadMetadataCache::new(reader.clone()); let mut metadata_reader = TiffMetadataReader::try_open(&cached_reader).await.unwrap(); - let ifds = metadata_reader.read_all_ifds(&cached_reader).await.unwrap(); - TIFF::new(ifds) + metadata_reader.read(&cached_reader).await.unwrap() } #[tokio::test]