Skip to content

added Endianness access from IFD to python #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/python/async_tiff/_decoder.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions python/python/async_tiff/_ifd.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .enums import (
Endianness,
CompressionMethod,
PhotometricInterpretation,
PlanarConfiguration,
Expand All @@ -11,6 +12,8 @@ from ._geo import GeoKeyDirectory
Value = int | float | str | tuple[int, int] | list[Value]

class ImageFileDirectory:
@property
def endianness(self) -> Endianness: ...
Comment on lines +15 to +16
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that

@property
def new_subfile_type(self) -> int | None: ...
@property
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions python/python/async_tiff/_thread_pool.pyi
Original file line number Diff line number Diff line change
@@ -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."""
3 changes: 3 additions & 0 deletions python/python/async_tiff/_tiff.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ class TIFF:
Returns:
A TIFF instance.
"""

@property
def ifds(self) -> list[ImageFileDirectory]:
"""Access the underlying IFDs of this 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.

Expand All @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions python/python/async_tiff/_tile.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Comment on lines +9 to +25
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

black added these

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all intents and purposes black and ruff format are the same, but they have some slight differences. Black is the older tool and ruff format is the preferred newer tool. Sorry for the confusion but we want to be using ruff format

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, I'd prefer for ruff format to be added as a separate PR with CI validation first, just like the cargo import ordering

async def decode_async(
self,
*,
Expand Down
19 changes: 19 additions & 0 deletions python/python/async_tiff/enums.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
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):
"""
endianness of the underlying tiff file
"""

LittleEndian = "LittleEndian"
BigEndian = "BigEndian"


class CompressionMethod(IntEnum):
"""
Expand Down
33 changes: 33 additions & 0 deletions python/src/enums.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use async_tiff::reader::Endianness;
use async_tiff::tiff::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit,
SampleFormat,
Expand All @@ -6,6 +7,38 @@ use pyo3::prelude::*;
use pyo3::types::{PyString, PyTuple};
use pyo3::{intern, IntoPyObjectExt};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PyEndianness(Endianness);

impl From<Endianness> for PyEndianness {
fn from(value: Endianness) -> Self {
Self(value)
}
}

impl From<PyEndianness> for Endianness {
fn from(value: PyEndianness) -> Self {
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<Self::Output, Self::Error> {
// 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")),
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct PyCompressionMethod(CompressionMethod);

Expand Down
9 changes: 7 additions & 2 deletions python/src/ifd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +15,11 @@ pub(crate) struct PyImageFileDirectory(ImageFileDirectory);

#[pymethods]
impl PyImageFileDirectory {
#[getter]
pub fn endianness(&self) -> PyEndianness {
self.0.endianness().into()
}
Comment on lines +18 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add this to the pyi file with the ImageFileDirectory type hint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean Endianness type hint?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I mean you need to describe this here:

class ImageFileDirectory:

Copy link
Contributor Author

@feefladder feefladder Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that (lines 15-16, also comment). What I meant is a -> Endianness type hint on the declaration


#[getter]
pub fn new_subfile_type(&self) -> Option<u32> {
self.0.new_subfile_type()
Expand Down
5 changes: 5 additions & 0 deletions src/ifd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ 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.
/// <https://web.archive.org/web/20240329145250/https://www.awaresystems.be/imaging/tiff/tifftags/newsubfiletype.html>
pub fn new_subfile_type(&self) -> Option<u32> {
Expand Down
2 changes: 1 addition & 1 deletion src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down