Skip to content

Commit 483327b

Browse files
authored
Split tile fetching and decoding (#37)
1 parent fe5681f commit 483327b

File tree

7 files changed

+147
-88
lines changed

7 files changed

+147
-88
lines changed

python/python/async_tiff/_decoder.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ from collections.abc import Buffer
44
from .enums import CompressionMethod
55

66
class Decoder(Protocol):
7+
# In the future, we could pass in photometric interpretation and jpeg tables as
8+
# well.
79
@staticmethod
810
def __call__(buffer: Buffer) -> Buffer: ...
911

src/async_reader.rs

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ use crate::error::{AiocogeoError, Result};
2929
/// [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html
3030
pub trait AsyncFileReader: Debug + Send + Sync {
3131
/// Retrieve the bytes in `range`
32-
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>>;
32+
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>>;
3333

3434
/// Retrieve multiple byte ranges. The default implementation will call `get_bytes` sequentially
35-
fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
35+
fn get_byte_ranges(&self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
3636
async move {
3737
let mut result = Vec::with_capacity(ranges.len());
3838

@@ -49,37 +49,37 @@ pub trait AsyncFileReader: Debug + Send + Sync {
4949

5050
/// This allows Box<dyn AsyncFileReader + '_> to be used as an AsyncFileReader,
5151
impl AsyncFileReader for Box<dyn AsyncFileReader + '_> {
52-
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
53-
self.as_mut().get_bytes(range)
52+
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
53+
self.as_ref().get_bytes(range)
5454
}
5555

56-
fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
57-
self.as_mut().get_byte_ranges(ranges)
56+
fn get_byte_ranges(&self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
57+
self.as_ref().get_byte_ranges(ranges)
5858
}
5959
}
6060

61-
#[cfg(feature = "tokio")]
62-
impl<T: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin + Debug + Send + Sync> AsyncFileReader
63-
for T
64-
{
65-
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
66-
use tokio::io::{AsyncReadExt, AsyncSeekExt};
67-
68-
async move {
69-
self.seek(std::io::SeekFrom::Start(range.start)).await?;
70-
71-
let to_read = (range.end - range.start).try_into().unwrap();
72-
let mut buffer = Vec::with_capacity(to_read);
73-
let read = self.take(to_read as u64).read_to_end(&mut buffer).await?;
74-
if read != to_read {
75-
return Err(AiocogeoError::EndOfFile(to_read, read));
76-
}
77-
78-
Ok(buffer.into())
79-
}
80-
.boxed()
81-
}
82-
}
61+
// #[cfg(feature = "tokio")]
62+
// impl<T: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin + Debug + Send + Sync> AsyncFileReader
63+
// for T
64+
// {
65+
// fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
66+
// use tokio::io::{AsyncReadExt, AsyncSeekExt};
67+
68+
// async move {
69+
// self.seek(std::io::SeekFrom::Start(range.start)).await?;
70+
71+
// let to_read = (range.end - range.start).try_into().unwrap();
72+
// let mut buffer = Vec::with_capacity(to_read);
73+
// let read = self.take(to_read as u64).read_to_end(&mut buffer).await?;
74+
// if read != to_read {
75+
// return Err(AiocogeoError::EndOfFile(to_read, read));
76+
// }
77+
78+
// Ok(buffer.into())
79+
// }
80+
// .boxed()
81+
// }
82+
// }
8383

8484
#[derive(Clone, Debug)]
8585
pub struct ObjectReader {
@@ -97,14 +97,14 @@ impl ObjectReader {
9797
}
9898

9999
impl AsyncFileReader for ObjectReader {
100-
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
100+
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
101101
self.store
102102
.get_range(&self.path, range)
103103
.map_err(|e| e.into())
104104
.boxed()
105105
}
106106

107-
fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>>
107+
fn get_byte_ranges(&self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>>
108108
where
109109
Self: Send,
110110
{
@@ -125,14 +125,14 @@ pub struct PrefetchReader {
125125
}
126126

127127
impl PrefetchReader {
128-
pub async fn new(mut reader: Box<dyn AsyncFileReader>, prefetch: u64) -> Result<Self> {
128+
pub async fn new(reader: Box<dyn AsyncFileReader>, prefetch: u64) -> Result<Self> {
129129
let buffer = reader.get_bytes(0..prefetch).await?;
130130
Ok(Self { reader, buffer })
131131
}
132132
}
133133

134134
impl AsyncFileReader for PrefetchReader {
135-
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
135+
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
136136
if range.start < self.buffer.len() as _ {
137137
if range.end < self.buffer.len() as _ {
138138
let usize_range = range.start as usize..range.end as usize;
@@ -147,7 +147,7 @@ impl AsyncFileReader for PrefetchReader {
147147
}
148148
}
149149

150-
fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>>
150+
fn get_byte_ranges(&self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>>
151151
where
152152
Self: Send,
153153
{

src/cog.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,8 @@ mod test {
9696

9797
let ifd = &cog_reader.ifds.as_ref()[1];
9898
let decoder_registry = DecoderRegistry::default();
99-
let tile = ifd
100-
.get_tile(0, 0, Box::new(reader), &decoder_registry)
101-
.await
102-
.unwrap();
99+
let tile = ifd.fetch_tile(0, 0, &reader).await.unwrap();
100+
let tile = tile.decode(&decoder_registry).unwrap();
103101
std::fs::write("img.buf", tile).unwrap();
104102
}
105103

src/decoder.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,26 +116,6 @@ impl Decoder for UncompressedDecoder {
116116
}
117117
}
118118

119-
// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L370
120-
pub(crate) fn decode_tile(
121-
buf: Bytes,
122-
photometric_interpretation: PhotometricInterpretation,
123-
compression_method: CompressionMethod,
124-
// compressed_length: u64,
125-
jpeg_tables: Option<&[u8]>,
126-
decoder_registry: &DecoderRegistry,
127-
) -> Result<Bytes> {
128-
let decoder =
129-
decoder_registry
130-
.0
131-
.get(&compression_method)
132-
.ok_or(TiffError::UnsupportedError(
133-
TiffUnsupportedError::UnsupportedCompressionMethod(compression_method),
134-
))?;
135-
136-
decoder.decode_tile(buf, photometric_interpretation, jpeg_tables)
137-
}
138-
139119
// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450
140120
fn decode_modern_jpeg(
141121
buf: Bytes,

src/ifd.rs

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use bytes::Bytes;
66
use num_enum::TryFromPrimitive;
77

88
use crate::async_reader::AsyncCursor;
9-
use crate::decoder::{decode_tile, DecoderRegistry};
109
use crate::error::{AiocogeoError, Result};
1110
use crate::geo::{AffineTransform, GeoKeyDirectory, GeoKeyTag};
1211
use crate::tiff::tags::{
@@ -15,6 +14,7 @@ use crate::tiff::tags::{
1514
};
1615
use crate::tiff::TiffError;
1716
use crate::tiff::Value;
17+
use crate::tile::TiffTile;
1818
use crate::AsyncFileReader;
1919

2020
const DOCUMENT_NAME: u16 = 269;
@@ -166,7 +166,7 @@ pub struct ImageFileDirectory {
166166

167167
pub(crate) sample_format: Vec<SampleFormat>,
168168

169-
pub(crate) jpeg_tables: Option<Vec<u8>>,
169+
pub(crate) jpeg_tables: Option<Bytes>,
170170

171171
pub(crate) copyright: Option<String>,
172172

@@ -339,7 +339,7 @@ impl ImageFileDirectory {
339339
.collect(),
340340
);
341341
}
342-
Tag::JPEGTables => jpeg_tables = Some(value.into_u8_vec()?),
342+
Tag::JPEGTables => jpeg_tables = Some(value.into_u8_vec()?.into()),
343343
Tag::Copyright => copyright = Some(value.into_string()?),
344344

345345
// Geospatial tags
@@ -728,33 +728,33 @@ impl ImageFileDirectory {
728728
Some(offset as _..(offset + byte_count) as _)
729729
}
730730

731-
pub async fn get_tile(
731+
/// Fetch the tile located at `x` column and `y` row using the provided reader.
732+
pub async fn fetch_tile(
732733
&self,
733734
x: usize,
734735
y: usize,
735-
mut reader: Box<dyn AsyncFileReader>,
736-
decoder_registry: &DecoderRegistry,
737-
) -> Result<Bytes> {
736+
reader: &dyn AsyncFileReader,
737+
) -> Result<TiffTile> {
738738
let range = self
739739
.get_tile_byte_range(x, y)
740740
.ok_or(AiocogeoError::General("Not a tiled TIFF".to_string()))?;
741-
let buf = reader.get_bytes(range).await?;
742-
decode_tile(
743-
buf,
744-
self.photometric_interpretation,
745-
self.compression,
746-
self.jpeg_tables.as_deref(),
747-
decoder_registry,
748-
)
741+
let compressed_bytes = reader.get_bytes(range).await?;
742+
Ok(TiffTile {
743+
x,
744+
y,
745+
compressed_bytes,
746+
compression_method: self.compression,
747+
photometric_interpretation: self.photometric_interpretation,
748+
jpeg_tables: self.jpeg_tables.clone(),
749+
})
749750
}
750751

751-
pub async fn get_tiles(
752+
pub async fn fetch_tiles(
752753
&self,
753754
x: &[usize],
754755
y: &[usize],
755-
mut reader: Box<dyn AsyncFileReader>,
756-
decoder_registry: &DecoderRegistry,
757-
) -> Result<Vec<Bytes>> {
756+
reader: &dyn AsyncFileReader,
757+
) -> Result<Vec<TiffTile>> {
758758
assert_eq!(x.len(), y.len(), "x and y should have same len");
759759

760760
// 1: Get all the byte ranges for all tiles
@@ -770,19 +770,20 @@ impl ImageFileDirectory {
770770
// 2: Fetch using `get_ranges
771771
let buffers = reader.get_byte_ranges(byte_ranges).await?;
772772

773-
// 3: Decode tiles (in the future, separate API)
774-
let mut decoded_tiles = vec![];
775-
for buf in buffers {
776-
let decoded = decode_tile(
777-
buf,
778-
self.photometric_interpretation,
779-
self.compression,
780-
self.jpeg_tables.as_deref(),
781-
decoder_registry,
782-
)?;
783-
decoded_tiles.push(decoded);
773+
// 3: Create tile objects
774+
let mut tiles = vec![];
775+
for ((compressed_bytes, &x), &y) in buffers.into_iter().zip(x).zip(y) {
776+
let tile = TiffTile {
777+
x,
778+
y,
779+
compressed_bytes,
780+
compression_method: self.compression,
781+
photometric_interpretation: self.photometric_interpretation,
782+
jpeg_tables: self.jpeg_tables.clone(),
783+
};
784+
tiles.push(tile);
784785
}
785-
Ok(decoded_tiles)
786+
Ok(tiles)
786787
}
787788

788789
/// Return the number of x/y tiles in the IFD

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod error;
77
pub mod geo;
88
mod ifd;
99
pub mod tiff;
10+
mod tile;
1011

1112
pub use async_reader::{AsyncFileReader, ObjectReader, PrefetchReader};
1213
pub use cog::COGReader;

src/tile.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use bytes::Bytes;
2+
3+
use crate::decoder::DecoderRegistry;
4+
use crate::error::Result;
5+
use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation};
6+
use crate::tiff::{TiffError, TiffUnsupportedError};
7+
8+
/// A TIFF Tile response.
9+
///
10+
/// This contains the required information to decode the tile. Decoding is separated from fetching
11+
/// so that sync and async operations can be separated and non-blocking.
12+
///
13+
/// This is returned by `fetch_tile`.
14+
#[derive(Debug)]
15+
pub struct TiffTile {
16+
pub(crate) x: usize,
17+
pub(crate) y: usize,
18+
pub(crate) compressed_bytes: Bytes,
19+
pub(crate) compression_method: CompressionMethod,
20+
pub(crate) photometric_interpretation: PhotometricInterpretation,
21+
pub(crate) jpeg_tables: Option<Bytes>,
22+
}
23+
24+
impl TiffTile {
25+
/// The column index of this tile.
26+
pub fn x(&self) -> usize {
27+
self.x
28+
}
29+
30+
/// The row index of this tile.
31+
pub fn y(&self) -> usize {
32+
self.y
33+
}
34+
35+
/// Access the compressed bytes underlying this tile.
36+
///
37+
/// Note that [`Bytes`] is reference-counted, so it is very cheap to clone if needed.
38+
pub fn compressed_bytes(&self) -> &Bytes {
39+
&self.compressed_bytes
40+
}
41+
42+
/// Access the compression tag representing this tile.
43+
pub fn compression_method(&self) -> CompressionMethod {
44+
self.compression_method
45+
}
46+
47+
/// Access the photometric interpretation tag representing this tile.
48+
pub fn photometric_interpretation(&self) -> PhotometricInterpretation {
49+
self.photometric_interpretation
50+
}
51+
52+
/// Access the JPEG Tables, if any, from the IFD producing this tile.
53+
///
54+
/// Note that [`Bytes`] is reference-counted, so it is very cheap to clone if needed.
55+
pub fn jpeg_tables(&self) -> Option<&Bytes> {
56+
self.jpeg_tables.as_ref()
57+
}
58+
59+
/// Decode this tile.
60+
///
61+
/// Decoding is separate from fetching so that sync and async operations do not block the same
62+
/// runtime.
63+
pub fn decode(&self, decoder_registry: &DecoderRegistry) -> Result<Bytes> {
64+
let decoder = decoder_registry
65+
.as_ref()
66+
.get(&self.compression_method)
67+
.ok_or(TiffError::UnsupportedError(
68+
TiffUnsupportedError::UnsupportedCompressionMethod(self.compression_method),
69+
))?;
70+
71+
decoder.decode_tile(
72+
self.compressed_bytes.clone(),
73+
self.photometric_interpretation,
74+
self.jpeg_tables.as_deref(),
75+
)
76+
}
77+
}

0 commit comments

Comments
 (0)