diff --git a/Cargo.toml b/Cargo.toml index 629b450..3b80981 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "async-tiff" -version = "0.1.0" +version = "0.1.0-beta.1" edition = "2021" +authors = ["Kyle Barron "] +license = "MIT OR Apache-2.0" +repository = "https://github.com/developmentseed/async-tiff" +description = "Low-level asynchronous TIFF reader." +readme = "README.md" [dependencies] byteorder = "1" @@ -11,6 +16,7 @@ futures = "0.3.31" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } num_enum = "0.7.3" # Match the version used by pyo3-object-store +# We'll upgrade to object_store 0.12 ASAP when it comes out object_store = { git = "https://github.com/apache/arrow-rs", rev = "7a15e4b47ca97df2edef689c9f2ebd2f3888b79e" } thiserror = "1" tokio = { version = "1.43.0", optional = true } diff --git a/python/Cargo.toml b/python/Cargo.toml index 71d204a..97c06db 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "async-tiff" +name = "py-async-tiff" version = "0.1.0-beta.1" authors = ["Kyle Barron "] edition = "2021" @@ -18,12 +18,13 @@ crate-type = ["cdylib"] [dependencies] async-tiff = { path = "../" } -bytes = "1.8" +bytes = "1.10.1" +# We'll upgrade to object_store 0.12 ASAP # Match the version used by pyo3-object-store object_store = { git = "https://github.com/apache/arrow-rs", rev = "7a15e4b47ca97df2edef689c9f2ebd2f3888b79e" } pyo3 = { version = "0.23.0", features = ["macros"] } pyo3-async-runtimes = "0.23" -pyo3-bytes = "0.1.2" +pyo3-bytes = "0.1.3" pyo3-object_store = { git = "https://github.com/developmentseed/obstore", rev = "28ba07a621c1c104f084fb47ae7f8d08b1eae3ea" } rayon = "1.10.0" tokio-rayon = "2.1.0" diff --git a/python/src/decoder.rs b/python/src/decoder.rs index 06471a1..572fc7c 100644 --- a/python/src/decoder.rs +++ b/python/src/decoder.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; use async_tiff::decoder::{Decoder, DecoderRegistry}; -use async_tiff::error::AiocogeoError; +use async_tiff::error::{AsyncTiffError, AsyncTiffResult}; use async_tiff::tiff::tags::PhotometricInterpretation; use bytes::Bytes; use pyo3::exceptions::PyTypeError; @@ -74,12 +74,12 @@ impl<'py> FromPyObject<'py> for PyDecoder { impl Decoder for PyDecoder { fn decode_tile( &self, - buffer: bytes::Bytes, + buffer: Bytes, _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> async_tiff::error::Result { + ) -> AsyncTiffResult { let decoded_buffer = Python::with_gil(|py| self.call(py, buffer)) - .map_err(|err| AiocogeoError::General(err.to_string()))?; + .map_err(|err| AsyncTiffError::General(err.to_string()))?; Ok(decoded_buffer.into_inner()) } } diff --git a/python/src/tiff.rs b/python/src/tiff.rs index 2391c7d..1bf040e 100644 --- a/python/src/tiff.rs +++ b/python/src/tiff.rs @@ -1,4 +1,5 @@ -use async_tiff::{AsyncFileReader, COGReader, ObjectReader, PrefetchReader}; +use async_tiff::reader::{AsyncFileReader, ObjectReader, PrefetchReader}; +use async_tiff::TIFF; use pyo3::prelude::*; use pyo3::types::PyType; use pyo3_async_runtimes::tokio::future_into_py; @@ -7,7 +8,7 @@ use pyo3_object_store::PyObjectStore; use crate::PyImageFileDirectory; #[pyclass(name = "TIFF", frozen)] -pub(crate) struct PyTIFF(COGReader); +pub(crate) struct PyTIFF(TIFF); #[pymethods] impl PyTIFF { @@ -32,7 +33,7 @@ impl PyTIFF { } else { Box::new(reader) }; - Ok(PyTIFF(COGReader::try_open(reader).await.unwrap())) + Ok(PyTIFF(TIFF::try_open(reader).await.unwrap())) })?; Ok(cog_reader) } diff --git a/src/cog.rs b/src/cog.rs index 01e498f..405507a 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -1,18 +1,19 @@ -use crate::async_reader::AsyncCursor; -use crate::error::Result; +use crate::error::AsyncTiffResult; use crate::ifd::ImageFileDirectories; +use crate::reader::{AsyncCursor, AsyncFileReader}; use crate::tiff::{TiffError, TiffFormatError}; -use crate::AsyncFileReader; -#[derive(Debug)] -pub struct COGReader { - #[allow(dead_code)] - cursor: AsyncCursor, +/// A TIFF file. +#[derive(Debug, Clone)] +pub struct TIFF { ifds: ImageFileDirectories, } -impl COGReader { - pub async fn try_open(reader: Box) -> Result { +impl TIFF { + /// Open a new TIFF file. + /// + /// This will read all the Image File Directories (IFDs) in the file. + pub async fn try_open(reader: Box) -> AsyncTiffResult { let mut cursor = AsyncCursor::try_open_tiff(reader).await?; let version = cursor.read_u16().await?; @@ -44,9 +45,10 @@ impl COGReader { let ifds = ImageFileDirectories::open(&mut cursor, first_ifd_location, bigtiff).await?; - Ok(Self { cursor, ifds }) + Ok(Self { ifds }) } + /// Access the underlying Image File Directories. pub fn ifds(&self) -> &ImageFileDirectories { &self.ifds } @@ -58,7 +60,7 @@ mod test { use std::sync::Arc; use crate::decoder::DecoderRegistry; - use crate::ObjectReader; + use crate::reader::ObjectReader; use super::*; use object_store::local::LocalFileSystem; @@ -72,7 +74,7 @@ mod test { let store = Arc::new(LocalFileSystem::new_with_prefix(folder).unwrap()); let reader = ObjectReader::new(store, path); - let cog_reader = COGReader::try_open(Box::new(reader.clone())).await.unwrap(); + let cog_reader = TIFF::try_open(Box::new(reader.clone())).await.unwrap(); let ifd = &cog_reader.ifds.as_ref()[1]; let decoder_registry = DecoderRegistry::default(); diff --git a/src/decoder.rs b/src/decoder.rs index fe1dc5c..d4298fc 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,3 +1,5 @@ +//! Decoders for different TIFF compression methods. + use std::collections::HashMap; use std::fmt::Debug; use std::io::{Cursor, Read}; @@ -5,7 +7,7 @@ use std::io::{Cursor, Read}; use bytes::Bytes; use flate2::bufread::ZlibDecoder; -use crate::error::Result; +use crate::error::AsyncTiffResult; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation}; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -46,14 +48,16 @@ impl Default for DecoderRegistry { /// A trait to decode a TIFF tile. pub trait Decoder: Debug + Send + Sync { + /// Decode a TIFF tile. fn decode_tile( &self, buffer: Bytes, photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, - ) -> Result; + ) -> AsyncTiffResult; } +/// A decoder for the Deflate compression method. #[derive(Debug, Clone)] pub struct DeflateDecoder; @@ -63,7 +67,7 @@ impl Decoder for DeflateDecoder { buffer: Bytes, _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> Result { + ) -> AsyncTiffResult { let mut decoder = ZlibDecoder::new(Cursor::new(buffer)); let mut buf = Vec::new(); decoder.read_to_end(&mut buf)?; @@ -71,6 +75,7 @@ impl Decoder for DeflateDecoder { } } +/// A decoder for the JPEG compression method. #[derive(Debug, Clone)] pub struct JPEGDecoder; @@ -80,11 +85,12 @@ impl Decoder for JPEGDecoder { buffer: Bytes, photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, - ) -> Result { + ) -> AsyncTiffResult { decode_modern_jpeg(buffer, photometric_interpretation, jpeg_tables) } } +/// A decoder for the LZW compression method. #[derive(Debug, Clone)] pub struct LZWDecoder; @@ -94,7 +100,7 @@ impl Decoder for LZWDecoder { buffer: Bytes, _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> Result { + ) -> AsyncTiffResult { // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147 let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); let decoded = decoder.decode(&buffer).expect("failed to decode LZW data"); @@ -102,6 +108,7 @@ impl Decoder for LZWDecoder { } } +/// A decoder for uncompressed data. #[derive(Debug, Clone)] pub struct UncompressedDecoder; @@ -111,7 +118,7 @@ impl Decoder for UncompressedDecoder { buffer: Bytes, _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> Result { + ) -> AsyncTiffResult { Ok(buffer) } } @@ -121,7 +128,7 @@ fn decode_modern_jpeg( buf: Bytes, photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, -) -> Result { +) -> AsyncTiffResult { // Construct new jpeg_reader wrapping a SmartReader. // // JPEG compression in TIFF allows saving quantization and/or huffman tables in one central diff --git a/src/error.rs b/src/error.rs index e279417..ff7542d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,13 @@ +//! Error handling. + use std::fmt::Debug; use thiserror::Error; /// Enum with all errors in this crate. #[derive(Error, Debug)] #[non_exhaustive] -pub enum AiocogeoError { +pub enum AsyncTiffError { + /// End of file error. #[error("End of File: expected to read {0} bytes, got {1}")] EndOfFile(usize, usize), @@ -12,18 +15,22 @@ pub enum AiocogeoError { #[error("General error: {0}")] General(String), + /// IO Error. #[error(transparent)] IOError(#[from] std::io::Error), + /// Error while decoding JPEG data. #[error(transparent)] JPEGDecodingError(#[from] jpeg::Error), + /// Error while fetching data using object store. #[error(transparent)] ObjectStore(#[from] object_store::Error), + /// An error during TIFF tag parsing. #[error(transparent)] InternalTIFFError(#[from] crate::tiff::TiffError), } /// Crate-specific result type. -pub type Result = std::result::Result; +pub type AsyncTiffResult = std::result::Result; diff --git a/src/geo/affine.rs b/src/geo/affine.rs index de5cc89..e84989b 100644 --- a/src/geo/affine.rs +++ b/src/geo/affine.rs @@ -5,30 +5,37 @@ use crate::ImageFileDirectory; pub struct AffineTransform(f64, f64, f64, f64, f64, f64); impl AffineTransform { + /// Construct a new Affine Transform pub fn new(a: f64, b: f64, xoff: f64, d: f64, e: f64, yoff: f64) -> Self { Self(a, b, xoff, d, e, yoff) } + /// a pub fn a(&self) -> f64 { self.0 } + /// b pub fn b(&self) -> f64 { self.1 } + /// c pub fn c(&self) -> f64 { self.2 } + /// d pub fn d(&self) -> f64 { self.3 } + /// e pub fn e(&self) -> f64 { self.4 } + /// f pub fn f(&self) -> f64 { self.5 } diff --git a/src/geo/geo_key_directory.rs b/src/geo/geo_key_directory.rs index 3ef6127..acf9aa2 100644 --- a/src/geo/geo_key_directory.rs +++ b/src/geo/geo_key_directory.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +#![allow(missing_docs)] use std::collections::HashMap; diff --git a/src/ifd.rs b/src/ifd.rs index c13731c..e9f90df 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -5,24 +5,22 @@ use std::ops::Range; use bytes::Bytes; use num_enum::TryFromPrimitive; -use crate::async_reader::AsyncCursor; -use crate::error::{AiocogeoError, Result}; +use crate::error::{AsyncTiffError, AsyncTiffResult}; use crate::geo::{GeoKeyDirectory, GeoKeyTag}; +use crate::reader::{AsyncCursor, AsyncFileReader}; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, SampleFormat, Tag, Type, }; -use crate::tiff::TiffError; -use crate::tiff::Value; +use crate::tiff::{TiffError, Value}; use crate::tile::Tile; -use crate::AsyncFileReader; const DOCUMENT_NAME: u16 = 269; /// A collection of all the IFD // TODO: maybe separate out the primary/first image IFD out of the vec, as that one should have // geospatial metadata? -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ImageFileDirectories { /// There's always at least one IFD in a TIFF. We store this separately ifds: Vec, @@ -42,7 +40,7 @@ impl ImageFileDirectories { cursor: &mut AsyncCursor, ifd_offset: u64, bigtiff: bool, - ) -> Result { + ) -> AsyncTiffResult { let mut next_ifd_offset = Some(ifd_offset); let mut ifds = vec![]; @@ -185,7 +183,11 @@ pub struct ImageFileDirectory { impl ImageFileDirectory { /// Read and parse the IFD starting at the given file offset - async fn read(cursor: &mut AsyncCursor, ifd_start: u64, bigtiff: bool) -> Result { + async fn read( + cursor: &mut AsyncCursor, + ifd_start: u64, + bigtiff: bool, + ) -> AsyncTiffResult { cursor.seek(ifd_start); let tag_count = if bigtiff { @@ -235,7 +237,10 @@ impl ImageFileDirectory { self.next_ifd_offset } - fn from_tags(mut tag_data: HashMap, next_ifd_offset: Option) -> Result { + fn from_tags( + mut tag_data: HashMap, + next_ifd_offset: Option, + ) -> AsyncTiffResult { let mut new_subfile_type = None; let mut image_width = None; let mut image_height = None; @@ -495,44 +500,61 @@ impl ImageFileDirectory { }) } + /// A general indication of the kind of data contained in this subfile. + /// pub fn new_subfile_type(&self) -> Option { self.new_subfile_type } /// The number of columns in the image, i.e., the number of pixels per row. + /// pub fn image_width(&self) -> u32 { self.image_width } /// The number of rows of pixels in the image. + /// pub fn image_height(&self) -> u32 { self.image_height } + /// Number of bits per component. + /// pub fn bits_per_sample(&self) -> &[u16] { &self.bits_per_sample } + /// Compression scheme used on the image data. + /// pub fn compression(&self) -> CompressionMethod { self.compression } + /// The color space of the image data. + /// pub fn photometric_interpretation(&self) -> PhotometricInterpretation { self.photometric_interpretation } + /// Document name. pub fn document_name(&self) -> Option<&str> { self.document_name.as_deref() } + /// A string that describes the subject of the image. + /// pub fn image_description(&self) -> Option<&str> { self.image_description.as_deref() } + /// For each strip, the byte offset of that strip. + /// pub fn strip_offsets(&self) -> Option<&[u64]> { self.strip_offsets.as_deref() } + /// The orientation of the image with respect to the rows and columns. + /// pub fn orientation(&self) -> Option { self.orientation } @@ -546,28 +568,38 @@ impl ImageFileDirectory { self.samples_per_pixel } + /// The number of rows per strip. + /// pub fn rows_per_strip(&self) -> Option { self.rows_per_strip } + /// For each strip, the number of bytes in the strip after compression. + /// pub fn strip_byte_counts(&self) -> Option<&[u64]> { self.strip_byte_counts.as_deref() } + /// The minimum component value used. + /// pub fn min_sample_value(&self) -> Option<&[u16]> { self.min_sample_value.as_deref() } + /// The maximum component value used. + /// pub fn max_sample_value(&self) -> Option<&[u16]> { self.max_sample_value.as_deref() } /// The number of pixels per ResolutionUnit in the ImageWidth direction. + /// pub fn x_resolution(&self) -> Option { self.x_resolution } /// The number of pixels per ResolutionUnit in the ImageLength direction. + /// pub fn y_resolution(&self) -> Option { self.y_resolution } @@ -586,15 +618,20 @@ impl ImageFileDirectory { /// that Baseline TIFF readers are not required to support it. /// /// If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included. + /// + /// pub fn planar_configuration(&self) -> PlanarConfiguration { self.planar_configuration } + /// The unit of measurement for XResolution and YResolution. + /// pub fn resolution_unit(&self) -> Option { self.resolution_unit } /// Name and version number of the software package(s) used to create the image. + /// pub fn software(&self) -> Option<&str> { self.software.as_deref() } @@ -604,61 +641,93 @@ impl ImageFileDirectory { /// The format is: "YYYY:MM:DD HH:MM:SS", with hours like those on a 24-hour clock, and one /// space character between the date and the time. The length of the string, including the /// terminating NUL, is 20 bytes. + /// + /// pub fn date_time(&self) -> Option<&str> { self.date_time.as_deref() } + /// Person who created the image. + /// pub fn artist(&self) -> Option<&str> { self.artist.as_deref() } + /// The computer and/or operating system in use at the time of image creation. + /// pub fn host_computer(&self) -> Option<&str> { self.host_computer.as_deref() } + /// A mathematical operator that is applied to the image data before an encoding scheme is + /// applied. + /// pub fn predictor(&self) -> Option { self.predictor } + /// The tile width in pixels. This is the number of columns in each tile. + /// pub fn tile_width(&self) -> Option { self.tile_width } + + /// The tile length (height) in pixels. This is the number of rows in each tile. + /// pub fn tile_height(&self) -> Option { self.tile_height } + /// For each tile, the byte offset of that tile, as compressed and stored on disk. + /// pub fn tile_offsets(&self) -> Option<&[u64]> { self.tile_offsets.as_deref() } + + /// For each tile, the number of (compressed) bytes in that tile. + /// pub fn tile_byte_counts(&self) -> Option<&[u64]> { self.tile_byte_counts.as_deref() } + /// Description of extra components. + /// pub fn extra_samples(&self) -> Option<&[u16]> { self.extra_samples.as_deref() } + /// Specifies how to interpret each data sample in a pixel. + /// pub fn sample_format(&self) -> &[SampleFormat] { &self.sample_format } + /// JPEG quantization and/or Huffman tables. + /// pub fn jpeg_tables(&self) -> Option<&[u8]> { self.jpeg_tables.as_deref() } + /// Copyright notice. + /// pub fn copyright(&self) -> Option<&str> { self.copyright.as_deref() } - // Geospatial tags + /// Geospatial tags + /// pub fn geo_key_directory(&self) -> Option<&GeoKeyDirectory> { self.geo_key_directory.as_ref() } + /// Used in interchangeable GeoTIFF files. + /// pub fn model_pixel_scale(&self) -> Option<&[f64]> { self.model_pixel_scale.as_deref() } + /// Used in interchangeable GeoTIFF files. + /// pub fn model_tiepoint(&self) -> Option<&[f64]> { self.model_tiepoint.as_deref() } @@ -712,10 +781,10 @@ impl ImageFileDirectory { x: usize, y: usize, reader: &dyn AsyncFileReader, - ) -> Result { + ) -> AsyncTiffResult { let range = self .get_tile_byte_range(x, y) - .ok_or(AiocogeoError::General("Not a tiled TIFF".to_string()))?; + .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string()))?; let compressed_bytes = reader.get_bytes(range).await?; Ok(Tile { x, @@ -727,12 +796,13 @@ impl ImageFileDirectory { }) } + /// Fetch the tiles located at `x` column and `y` row using the provided reader. pub async fn fetch_tiles( &self, x: &[usize], y: &[usize], reader: &dyn AsyncFileReader, - ) -> Result> { + ) -> AsyncTiffResult> { assert_eq!(x.len(), y.len(), "x and y should have same len"); // 1: Get all the byte ranges for all tiles @@ -741,9 +811,9 @@ impl ImageFileDirectory { .zip(y) .map(|(x, y)| { self.get_tile_byte_range(*x, *y) - .ok_or(AiocogeoError::General("Not a tiled TIFF".to_string())) + .ok_or(AsyncTiffError::General("Not a tiled TIFF".to_string())) }) - .collect::>>()?; + .collect::>>()?; // 2: Fetch using `get_ranges let buffers = reader.get_byte_ranges(byte_ranges).await?; @@ -774,7 +844,7 @@ impl ImageFileDirectory { } /// Read a single tag from the cursor -async fn read_tag(cursor: &mut AsyncCursor, bigtiff: bool) -> Result<(Tag, Value)> { +async fn read_tag(cursor: &mut AsyncCursor, bigtiff: bool) -> AsyncTiffResult<(Tag, Value)> { let start_cursor_position = cursor.position(); let tag_name = Tag::from_u16_exhaustive(cursor.read_u16().await?); @@ -809,7 +879,7 @@ async fn read_tag_value( tag_type: Type, count: u64, bigtiff: bool, -) -> Result { +) -> AsyncTiffResult { // Case 1: there are no values so we can return immediately. if count == 0 { return Ok(Value::List(vec![])); @@ -951,7 +1021,7 @@ async fn read_tag_value( data.read_exact(&mut buf)?; if buf.is_ascii() && buf.ends_with(&[0]) { let v = std::str::from_utf8(&buf) - .map_err(|err| AiocogeoError::General(err.to_string()))?; + .map_err(|err| AsyncTiffError::General(err.to_string()))?; let v = v.trim_matches(char::from(0)); return Ok(Value::Ascii(v.into())); } else { @@ -1139,7 +1209,7 @@ async fn read_tag_value( out.truncate(first); } Ok(Value::Ascii( - String::from_utf8(out).map_err(|err| AiocogeoError::General(err.to_string()))?, + String::from_utf8(out).map_err(|err| AsyncTiffError::General(err.to_string()))?, )) } } diff --git a/src/lib.rs b/src/lib.rs index fdec48b..6c3445b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ #![doc = include_str!("../README.md")] +#![warn(missing_docs)] -mod async_reader; +pub mod reader; +// TODO: maybe rename this mod mod cog; pub mod decoder; pub mod error; @@ -9,7 +11,6 @@ mod ifd; pub mod tiff; mod tile; -pub use async_reader::{AsyncFileReader, ObjectReader, PrefetchReader}; -pub use cog::COGReader; +pub use cog::TIFF; pub use ifd::{ImageFileDirectories, ImageFileDirectory}; pub use tile::Tile; diff --git a/src/async_reader.rs b/src/reader.rs similarity index 76% rename from src/async_reader.rs rename to src/reader.rs index 3276884..c93f1a8 100644 --- a/src/async_reader.rs +++ b/src/reader.rs @@ -1,3 +1,5 @@ +//! Abstractions for network reading. + use std::fmt::Debug; use std::io::Read; use std::ops::Range; @@ -9,7 +11,7 @@ use bytes::{Buf, Bytes}; use futures::future::{BoxFuture, FutureExt, TryFutureExt}; use object_store::ObjectStore; -use crate::error::{AiocogeoError, Result}; +use crate::error::{AsyncTiffError, AsyncTiffResult}; /// The asynchronous interface used to read COG files /// @@ -29,10 +31,14 @@ use crate::error::{AiocogeoError, Result}; /// [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html pub trait AsyncFileReader: Debug + Send + Sync { /// Retrieve the bytes in `range` - fn get_bytes(&self, range: Range) -> BoxFuture<'_, Result>; - - /// Retrieve multiple byte ranges. The default implementation will call `get_bytes` sequentially - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, Result>> { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult>; + + /// Retrieve multiple byte ranges. The default implementation will call `get_bytes` + /// sequentially + fn get_byte_ranges( + &self, + ranges: Vec>, + ) -> BoxFuture<'_, AsyncTiffResult>> { async move { let mut result = Vec::with_capacity(ranges.len()); @@ -49,11 +55,14 @@ pub trait AsyncFileReader: Debug + Send + Sync { /// This allows Box to be used as an AsyncFileReader, impl AsyncFileReader for Box { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, Result> { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { self.as_ref().get_bytes(range) } - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, Result>> { + fn get_byte_ranges( + &self, + ranges: Vec>, + ) -> BoxFuture<'_, AsyncTiffResult>> { self.as_ref().get_byte_ranges(ranges) } } @@ -62,7 +71,7 @@ impl AsyncFileReader for Box { // impl AsyncFileReader // for T // { -// fn get_bytes(&self, range: Range) -> BoxFuture<'_, Result> { +// fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { // use tokio::io::{AsyncReadExt, AsyncSeekExt}; // async move { @@ -72,7 +81,7 @@ impl AsyncFileReader for Box { // let mut buffer = Vec::with_capacity(to_read); // let read = self.take(to_read as u64).read_to_end(&mut buffer).await?; // if read != to_read { -// return Err(AiocogeoError::EndOfFile(to_read, read)); +// return Err(AsyncTiffError::EndOfFile(to_read, read)); // } // Ok(buffer.into()) @@ -81,6 +90,7 @@ impl AsyncFileReader for Box { // } // } +/// An AsyncFileReader that reads from an [`ObjectStore`] instance. #[derive(Clone, Debug)] pub struct ObjectReader { store: Arc, @@ -97,17 +107,22 @@ impl ObjectReader { } impl AsyncFileReader for ObjectReader { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, Result> { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + let range = range.start as _..range.end as _; self.store .get_range(&self.path, range) .map_err(|e| e.into()) .boxed() } - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, Result>> + fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, AsyncTiffResult>> where Self: Send, { + let ranges = ranges + .into_iter() + .map(|r| r.start as _..r.end as _) + .collect::>(); async move { self.store .get_ranges(&self.path, &ranges) @@ -118,6 +133,7 @@ impl AsyncFileReader for ObjectReader { } } +/// An AsyncFileReader that caches the first `prefetch` bytes of a file. #[derive(Debug)] pub struct PrefetchReader { reader: Box, @@ -125,14 +141,15 @@ pub struct PrefetchReader { } impl PrefetchReader { - pub async fn new(reader: Box, prefetch: u64) -> Result { + /// Construct a new PrefetchReader, catching the first `prefetch` bytes of the file. + pub async fn new(reader: Box, prefetch: u64) -> AsyncTiffResult { let buffer = reader.get_bytes(0..prefetch).await?; Ok(Self { reader, buffer }) } } impl AsyncFileReader for PrefetchReader { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, Result> { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { if range.start < self.buffer.len() as _ { if range.end < self.buffer.len() as _ { let usize_range = range.start as usize..range.end as usize; @@ -147,7 +164,7 @@ impl AsyncFileReader for PrefetchReader { } } - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, Result>> + fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, AsyncTiffResult>> where Self: Send, { @@ -157,9 +174,8 @@ impl AsyncFileReader for PrefetchReader { } } -#[derive(Debug, Clone, Copy, Default)] -pub enum Endianness { - #[default] +#[derive(Debug, Clone, Copy)] +pub(crate) enum Endianness { LittleEndian, BigEndian, } @@ -185,9 +201,9 @@ impl AsyncCursor { /// Create a new AsyncCursor for a TIFF file, automatically inferring endianness from the first /// two bytes. - pub(crate) async fn try_open_tiff(reader: Box) -> Result { - // Initialize with default endianness and then set later - let mut cursor = Self::new(reader, Default::default()); + pub(crate) async fn try_open_tiff(reader: Box) -> AsyncTiffResult { + // Initialize with little endianness and then set later + let mut cursor = Self::new(reader, Endianness::LittleEndian); let magic_bytes = cursor.read(2).await?; let magic_bytes = magic_bytes.as_ref(); @@ -197,7 +213,7 @@ impl AsyncCursor { } else if magic_bytes == Bytes::from_static(b"MM") { cursor.endianness = Endianness::BigEndian; } else { - return Err(AiocogeoError::General(format!( + return Err(AsyncTiffError::General(format!( "unexpected magic bytes {magic_bytes:?}" ))); }; @@ -212,7 +228,7 @@ impl AsyncCursor { } /// Read the given number of bytes, advancing the internal cursor state by the same amount. - pub(crate) async fn read(&mut self, length: u64) -> Result { + pub(crate) async fn read(&mut self, length: u64) -> AsyncTiffResult { let range = self.offset as _..(self.offset + length) as _; self.offset += length; let bytes = self.reader.get_bytes(range).await?; @@ -223,50 +239,50 @@ impl AsyncCursor { } /// Read a u8 from the cursor, advancing the internal state by 1 byte. - pub(crate) async fn read_u8(&mut self) -> Result { + pub(crate) async fn read_u8(&mut self) -> AsyncTiffResult { self.read(1).await?.read_u8() } /// Read a i8 from the cursor, advancing the internal state by 1 byte. - pub(crate) async fn read_i8(&mut self) -> Result { + pub(crate) async fn read_i8(&mut self) -> AsyncTiffResult { self.read(1).await?.read_i8() } /// Read a u16 from the cursor, advancing the internal state by 2 bytes. - pub(crate) async fn read_u16(&mut self) -> Result { + pub(crate) async fn read_u16(&mut self) -> AsyncTiffResult { self.read(2).await?.read_u16() } /// Read a i16 from the cursor, advancing the internal state by 2 bytes. - pub(crate) async fn read_i16(&mut self) -> Result { + pub(crate) async fn read_i16(&mut self) -> AsyncTiffResult { self.read(2).await?.read_i16() } /// Read a u32 from the cursor, advancing the internal state by 4 bytes. - pub(crate) async fn read_u32(&mut self) -> Result { + pub(crate) async fn read_u32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_u32() } /// Read a i32 from the cursor, advancing the internal state by 4 bytes. - pub(crate) async fn read_i32(&mut self) -> Result { + pub(crate) async fn read_i32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_i32() } /// Read a u64 from the cursor, advancing the internal state by 8 bytes. - pub(crate) async fn read_u64(&mut self) -> Result { + pub(crate) async fn read_u64(&mut self) -> AsyncTiffResult { self.read(8).await?.read_u64() } /// Read a i64 from the cursor, advancing the internal state by 8 bytes. - pub(crate) async fn read_i64(&mut self) -> Result { + pub(crate) async fn read_i64(&mut self) -> AsyncTiffResult { self.read(8).await?.read_i64() } - pub(crate) async fn read_f32(&mut self) -> Result { + pub(crate) async fn read_f32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_f32() } - pub(crate) async fn read_f64(&mut self) -> Result { + pub(crate) async fn read_f64(&mut self) -> AsyncTiffResult { self.read(8).await?.read_f64() } @@ -301,65 +317,65 @@ pub(crate) struct EndianAwareReader { impl EndianAwareReader { /// Read a u8 from the cursor, advancing the internal state by 1 byte. - pub(crate) fn read_u8(&mut self) -> Result { + pub(crate) fn read_u8(&mut self) -> AsyncTiffResult { Ok(self.reader.read_u8()?) } /// Read a i8 from the cursor, advancing the internal state by 1 byte. - pub(crate) fn read_i8(&mut self) -> Result { + pub(crate) fn read_i8(&mut self) -> AsyncTiffResult { Ok(self.reader.read_i8()?) } - pub(crate) fn read_u16(&mut self) -> Result { + pub(crate) fn read_u16(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_u16::()?), Endianness::BigEndian => Ok(self.reader.read_u16::()?), } } - pub(crate) fn read_i16(&mut self) -> Result { + pub(crate) fn read_i16(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_i16::()?), Endianness::BigEndian => Ok(self.reader.read_i16::()?), } } - pub(crate) fn read_u32(&mut self) -> Result { + pub(crate) fn read_u32(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_u32::()?), Endianness::BigEndian => Ok(self.reader.read_u32::()?), } } - pub(crate) fn read_i32(&mut self) -> Result { + pub(crate) fn read_i32(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_i32::()?), Endianness::BigEndian => Ok(self.reader.read_i32::()?), } } - pub(crate) fn read_u64(&mut self) -> Result { + pub(crate) fn read_u64(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_u64::()?), Endianness::BigEndian => Ok(self.reader.read_u64::()?), } } - pub(crate) fn read_i64(&mut self) -> Result { + pub(crate) fn read_i64(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_i64::()?), Endianness::BigEndian => Ok(self.reader.read_i64::()?), } } - pub(crate) fn read_f32(&mut self) -> Result { + pub(crate) fn read_f32(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_f32::()?), Endianness::BigEndian => Ok(self.reader.read_f32::()?), } } - pub(crate) fn read_f64(&mut self) -> Result { + pub(crate) fn read_f64(&mut self) -> AsyncTiffResult { match self.endianness { Endianness::LittleEndian => Ok(self.reader.read_f64::()?), Endianness::BigEndian => Ok(self.reader.read_f64::()?), diff --git a/src/tiff/tags.rs b/src/tiff/tags.rs index 798ddaa..524a726 100644 --- a/src/tiff/tags.rs +++ b/src/tiff/tags.rs @@ -1,4 +1,5 @@ #![allow(clippy::no_effect)] +#![allow(missing_docs)] macro_rules! tags { { @@ -43,12 +44,14 @@ macro_rules! tags { // For u16 tags, provide direct inherent primitive conversion methods. ($name:tt, u16, $($unknown_doc:literal)*) => { impl $name { + /// Construct from a u16 value, returning `None` if the value is not a known tag. #[inline(always)] pub fn from_u16(val: u16) -> Option { Self::__from_inner_type(val).ok() } $( + /// Construct from a u16 value, storing `Unknown` if the value is not a known tag. #[inline(always)] pub fn from_u16_exhaustive(val: u16) -> Self { $unknown_doc; @@ -56,6 +59,7 @@ macro_rules! tags { } )* + /// Convert to a u16 value. #[inline(always)] pub fn to_u16(&self) -> u16 { Self::__to_inner_type(self) diff --git a/src/tile.rs b/src/tile.rs index 92a56ee..4b18c80 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -1,7 +1,7 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; -use crate::error::Result; +use crate::error::AsyncTiffResult; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation}; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -60,7 +60,7 @@ impl Tile { /// /// Decoding is separate from fetching so that sync and async operations do not block the same /// runtime. - pub fn decode(self, decoder_registry: &DecoderRegistry) -> Result { + pub fn decode(self, decoder_registry: &DecoderRegistry) -> AsyncTiffResult { let decoder = decoder_registry .as_ref() .get(&self.compression_method) diff --git a/tests/image_tiff/util.rs b/tests/image_tiff/util.rs index 431f749..5755040 100644 --- a/tests/image_tiff/util.rs +++ b/tests/image_tiff/util.rs @@ -1,14 +1,15 @@ use std::env::current_dir; use std::sync::Arc; -use async_tiff::{COGReader, ObjectReader}; +use async_tiff::reader::ObjectReader; +use async_tiff::TIFF; use object_store::local::LocalFileSystem; const TEST_IMAGE_DIR: &str = "tests/image_tiff/images/"; -pub(crate) async fn open_tiff(filename: &str) -> COGReader { +pub(crate) async fn open_tiff(filename: &str) -> TIFF { let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap()); let path = format!("{TEST_IMAGE_DIR}/{filename}"); let reader = Box::new(ObjectReader::new(store.clone(), path.as_str().into())); - COGReader::try_open(reader).await.unwrap() + TIFF::try_open(reader).await.unwrap() }