From 5487c3adcfa14eaeb2e46086c2d5854e9e275b0c Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Sat, 14 Jun 2025 14:41:21 +0200 Subject: [PATCH 1/8] added Endianness access from IFD to python --- python/src/enums.rs | 35 ++++++++++++++++++++++++++++++++--- python/src/ifd.rs | 9 +++++++-- src/ifd.rs | 6 ++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/python/src/enums.rs b/python/src/enums.rs index 14656bd..8ba8ade 100644 --- a/python/src/enums.rs +++ b/python/src/enums.rs @@ -1,11 +1,40 @@ -use async_tiff::tiff::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, - SampleFormat, +use async_tiff::{ + reader::Endianness, + tiff::tags::{ + CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, + ResolutionUnit, SampleFormat, + }, }; use pyo3::prelude::*; use pyo3::types::{PyString, PyTuple}; use pyo3::{intern, IntoPyObjectExt}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[pyclass(eq, eq_int, name = "Endianness")] +#[repr(u16)] +pub(crate) enum PyEndianness { + LittleEndian = 0x4949, // b"II" + BigEndian = 0x4D4D, // b"MM" +} + +impl From for PyEndianness { + fn from(value: Endianness) -> Self { + match value { + Endianness::LittleEndian => Self::LittleEndian, + Endianness::BigEndian => Self::BigEndian, + } + } +} + +impl From for Endianness { + fn from(value: PyEndianness) -> Self { + match value { + PyEndianness::LittleEndian => Self::LittleEndian, + PyEndianness::BigEndian => Self::BigEndian, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct PyCompressionMethod(CompressionMethod); diff --git a/python/src/ifd.rs b/python/src/ifd.rs index c8c59c6..dcbccde 100644 --- a/python/src/ifd.rs +++ b/python/src/ifd.rs @@ -4,8 +4,8 @@ use async_tiff::ImageFileDirectory; use pyo3::prelude::*; use crate::enums::{ - PyCompressionMethod, PyPhotometricInterpretation, PyPlanarConfiguration, PyPredictor, - PyResolutionUnit, PySampleFormat, + PyCompressionMethod, PyEndianness, PyPhotometricInterpretation, PyPlanarConfiguration, + PyPredictor, PyResolutionUnit, PySampleFormat, }; use crate::geo::PyGeoKeyDirectory; use crate::value::PyValue; @@ -15,6 +15,11 @@ pub(crate) struct PyImageFileDirectory(ImageFileDirectory); #[pymethods] impl PyImageFileDirectory { + #[getter] + pub fn endianness(&self) -> PyEndianness { + self.0.endianness().into() + } + #[getter] pub fn new_subfile_type(&self) -> Option { self.0.new_subfile_type() diff --git a/src/ifd.rs b/src/ifd.rs index f0da9c1..863bc1e 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -402,6 +402,12 @@ impl ImageFileDirectory { }) } + + /// The Endianness of the file + pub fn endianness(&self) -> Endianness { + self.endianness + } + /// A general indication of the kind of data contained in this subfile. /// pub fn new_subfile_type(&self) -> Option { From caa423ca2b790d1f519210afecad458ae3e4ad9b Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Sat, 14 Jun 2025 15:04:30 +0200 Subject: [PATCH 2/8] registered PyEndianness to Python --- python/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/src/lib.rs b/python/src/lib.rs index f3cd219..66189ec 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -17,6 +17,7 @@ use crate::geo::PyGeoKeyDirectory; use crate::ifd::PyImageFileDirectory; use crate::thread_pool::PyThreadPool; use crate::tiff::PyTIFF; +use crate::enums::PyEndianness; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -50,6 +51,7 @@ fn _async_tiff(py: Python, m: &Bound) -> PyResult<()> { check_debug_build(py)?; m.add_wrapped(wrap_pyfunction!(___version))?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; From 9b17ba445baf98bf0501cfb4d7c0a9b6a1405f1f Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Sat, 14 Jun 2025 15:12:32 +0200 Subject: [PATCH 3/8] cargo fmt --- python/src/lib.rs | 2 +- src/ifd.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/src/lib.rs b/python/src/lib.rs index 66189ec..6189776 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -13,11 +13,11 @@ mod value; use pyo3::prelude::*; use crate::decoder::PyDecoderRegistry; +use crate::enums::PyEndianness; use crate::geo::PyGeoKeyDirectory; use crate::ifd::PyImageFileDirectory; use crate::thread_pool::PyThreadPool; use crate::tiff::PyTIFF; -use crate::enums::PyEndianness; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/ifd.rs b/src/ifd.rs index 863bc1e..0dfc6e1 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -402,7 +402,6 @@ impl ImageFileDirectory { }) } - /// The Endianness of the file pub fn endianness(&self) -> Endianness { self.endianness From 4d0eabd4e1fb197f5424b556c7786acf82a089b9 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 17 Jun 2025 14:59:57 +0200 Subject: [PATCH 4/8] incorporated feedback --- python/python/async_tiff/enums.py | 8 ++++++++ python/src/enums.rs | 34 ++++++++++++++++++------------- python/src/lib.rs | 2 -- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/python/python/async_tiff/enums.py b/python/python/async_tiff/enums.py index 724b77b..bfec9d6 100644 --- a/python/python/async_tiff/enums.py +++ b/python/python/async_tiff/enums.py @@ -1,5 +1,13 @@ from enum import IntEnum +class Endianness(StrEnum): + """ + endianness of the underlying tiff file + """ + + LittleEndian = "LittleEndian" + BigEndian = "BigEndian" + class CompressionMethod(IntEnum): """ diff --git a/python/src/enums.rs b/python/src/enums.rs index 8ba8ade..450acb9 100644 --- a/python/src/enums.rs +++ b/python/src/enums.rs @@ -9,28 +9,34 @@ use pyo3::prelude::*; use pyo3::types::{PyString, PyTuple}; use pyo3::{intern, IntoPyObjectExt}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[pyclass(eq, eq_int, name = "Endianness")] -#[repr(u16)] -pub(crate) enum PyEndianness { - LittleEndian = 0x4949, // b"II" - BigEndian = 0x4D4D, // b"MM" -} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct PyEndianness(Endianness); impl From for PyEndianness { fn from(value: Endianness) -> Self { - match value { - Endianness::LittleEndian => Self::LittleEndian, - Endianness::BigEndian => Self::BigEndian, - } + Self(value) } } impl From for Endianness { fn from(value: PyEndianness) -> Self { - match value { - PyEndianness::LittleEndian => Self::LittleEndian, - PyEndianness::BigEndian => Self::BigEndian, + value.0 + } +} + +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 { + // import the python module + let enums_mod = py.import(intern!(py, "async_tiff.enums"))?; + // get our python enum + let enum_cls = enums_mod.getattr(intern!(py, "Endianness"))?; + match self.0 { + Endianness::LittleEndian => enum_cls.getattr(intern!(py, "LittleEndian")), + Endianness::BigEndian => enum_cls.getattr(intern!(py, "BigEndian")), } } } diff --git a/python/src/lib.rs b/python/src/lib.rs index 6189776..f3cd219 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -13,7 +13,6 @@ mod value; use pyo3::prelude::*; use crate::decoder::PyDecoderRegistry; -use crate::enums::PyEndianness; use crate::geo::PyGeoKeyDirectory; use crate::ifd::PyImageFileDirectory; use crate::thread_pool::PyThreadPool; @@ -51,7 +50,6 @@ fn _async_tiff(py: Python, m: &Bound) -> PyResult<()> { check_debug_build(py)?; m.add_wrapped(wrap_pyfunction!(___version))?; - m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; From a34b39871ab55549789c3442536e2b379283d5fb Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 17 Jun 2025 15:22:40 +0200 Subject: [PATCH 5/8] added PartialEq derive on Endianness; cargo fmt --- python/src/enums.rs | 2 +- src/reader.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/src/enums.rs b/python/src/enums.rs index 450acb9..2e3a659 100644 --- a/python/src/enums.rs +++ b/python/src/enums.rs @@ -29,7 +29,7 @@ impl<'py> IntoPyObject<'py> for PyEndianness { type Output = Bound<'py, PyAny>; type Error = PyErr; - fn into_pyobject(self, py: Python<'py>) -> Result { + fn into_pyobject(self, py: Python<'py>) -> Result { // import the python module let enums_mod = py.import(intern!(py, "async_tiff.enums"))?; // get our python enum diff --git a/src/reader.rs b/src/reader.rs index 06520ec..94090e0 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -228,7 +228,7 @@ impl AsyncFileReader for ReqwestReader { } /// Endianness -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Endianness { /// Little Endian LittleEndian, From ae9e557241728d24a5336bb0329f8c2c9cc294db Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 17 Jun 2025 15:24:59 +0200 Subject: [PATCH 6/8] import StrEnum --- python/python/async_tiff/enums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/python/async_tiff/enums.py b/python/python/async_tiff/enums.py index bfec9d6..407eb47 100644 --- a/python/python/async_tiff/enums.py +++ b/python/python/async_tiff/enums.py @@ -1,4 +1,4 @@ -from enum import IntEnum +from enum import IntEnum, StrEnum class Endianness(StrEnum): """ From 0ca7e742de9480611d44d6b2bb3e6956bbbb0035 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 17 Jun 2025 15:33:40 +0200 Subject: [PATCH 7/8] StrEnum polyfill --- python/python/async_tiff/enums.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/python/async_tiff/enums.py b/python/python/async_tiff/enums.py index 407eb47..39b87d8 100644 --- a/python/python/async_tiff/enums.py +++ b/python/python/async_tiff/enums.py @@ -1,4 +1,13 @@ -from enum import IntEnum, StrEnum +import sys +from enum import IntEnum + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + from enum import Enum + class StrEnum(str, Enum): + def __str__(self): + return str(self.value) class Endianness(StrEnum): """ From d63e2993e5cd30948d2db109baf1bff53df0e77b Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 17 Jun 2025 17:15:32 +0200 Subject: [PATCH 8/8] ran black ., some formatting --- python/python/async_tiff/_decoder.pyi | 2 ++ python/python/async_tiff/_ifd.pyi | 4 ++++ python/python/async_tiff/_thread_pool.pyi | 1 + python/python/async_tiff/_tiff.pyi | 3 +++ python/python/async_tiff/_tile.pyi | 5 +++++ python/python/async_tiff/enums.py | 4 +++- python/src/enums.rs | 10 ++++------ 7 files changed, 22 insertions(+), 7 deletions(-) diff --git a/python/python/async_tiff/_decoder.pyi b/python/python/async_tiff/_decoder.pyi index d69b27e..7f032e9 100644 --- a/python/python/async_tiff/_decoder.pyi +++ b/python/python/async_tiff/_decoder.pyi @@ -5,6 +5,7 @@ from .enums import CompressionMethod class Decoder(Protocol): """A custom Python-provided decompression algorithm.""" + # In the future, we could pass in photometric interpretation and jpeg tables as # well. @staticmethod @@ -13,6 +14,7 @@ class Decoder(Protocol): class DecoderRegistry: """A registry holding multiple decoder methods.""" + def __init__( self, custom_decoders: dict[CompressionMethod | int, Decoder] | None = None ) -> None: diff --git a/python/python/async_tiff/_ifd.pyi b/python/python/async_tiff/_ifd.pyi index 5bf173c..dc3f5f9 100644 --- a/python/python/async_tiff/_ifd.pyi +++ b/python/python/async_tiff/_ifd.pyi @@ -1,4 +1,5 @@ from .enums import ( + Endianness, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, @@ -11,6 +12,8 @@ from ._geo import GeoKeyDirectory Value = int | float | str | tuple[int, int] | list[Value] class ImageFileDirectory: + @property + def endianness(self) -> Endianness: ... @property def new_subfile_type(self) -> int | None: ... @property @@ -30,6 +33,7 @@ class ImageFileDirectory: An `int` will be returned if the compression is not one of the values in `CompressionMethod`. """ + @property def photometric_interpretation(self) -> PhotometricInterpretation: ... @property diff --git a/python/python/async_tiff/_thread_pool.pyi b/python/python/async_tiff/_thread_pool.pyi index aa1165a..3f60446 100644 --- a/python/python/async_tiff/_thread_pool.pyi +++ b/python/python/async_tiff/_thread_pool.pyi @@ -1,4 +1,5 @@ class ThreadPool: """A Rust-managed thread pool.""" + def __init__(self, num_threads: int) -> None: """Construct a new ThreadPool with the given number of threads.""" diff --git a/python/python/async_tiff/_tiff.pyi b/python/python/async_tiff/_tiff.pyi index dcc0f67..e703642 100644 --- a/python/python/async_tiff/_tiff.pyi +++ b/python/python/async_tiff/_tiff.pyi @@ -28,6 +28,7 @@ class TIFF: Returns: A TIFF instance. """ + @property def ifds(self) -> list[ImageFileDirectory]: """Access the underlying IFDs of this TIFF. @@ -35,6 +36,7 @@ class TIFF: Each ImageFileDirectory (IFD) represents one of the internal "sub images" of this file. """ + async def fetch_tile(self, x: int, y: int, z: int) -> Tile: """Fetch a single tile. @@ -46,6 +48,7 @@ class TIFF: Returns: Tile response. """ + async def fetch_tiles(self, x: list[int], y: list[int], z: int) -> list[Tile]: """Fetch multiple tiles concurrently. diff --git a/python/python/async_tiff/_tile.pyi b/python/python/async_tiff/_tile.pyi index 6dc072a..2f47380 100644 --- a/python/python/async_tiff/_tile.pyi +++ b/python/python/async_tiff/_tile.pyi @@ -6,18 +6,23 @@ from ._thread_pool import ThreadPool class Tile: """A representation of a TIFF image tile.""" + @property def x(self) -> int: """The column index this tile represents.""" + @property def y(self) -> int: """The row index this tile represents.""" + @property def compressed_bytes(self) -> Buffer: """The compressed bytes underlying this tile.""" + @property def compression_method(self) -> CompressionMethod | int: """The compression method used by this tile.""" + async def decode_async( self, *, diff --git a/python/python/async_tiff/enums.py b/python/python/async_tiff/enums.py index 39b87d8..0d17701 100644 --- a/python/python/async_tiff/enums.py +++ b/python/python/async_tiff/enums.py @@ -5,10 +5,12 @@ from enum import StrEnum else: from enum import Enum + class StrEnum(str, Enum): def __str__(self): return str(self.value) + class Endianness(StrEnum): """ endianness of the underlying tiff file @@ -16,7 +18,7 @@ class Endianness(StrEnum): LittleEndian = "LittleEndian" BigEndian = "BigEndian" - + class CompressionMethod(IntEnum): """ diff --git a/python/src/enums.rs b/python/src/enums.rs index 2e3a659..416d4c9 100644 --- a/python/src/enums.rs +++ b/python/src/enums.rs @@ -1,9 +1,7 @@ -use async_tiff::{ - reader::Endianness, - tiff::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, - ResolutionUnit, SampleFormat, - }, +use async_tiff::reader::Endianness; +use async_tiff::tiff::tags::{ + CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, + SampleFormat, }; use pyo3::prelude::*; use pyo3::types::{PyString, PyTuple};