Skip to content

Commit e8abdc0

Browse files
authored
Progress on image tile decoding (#2)
* Progress on image tile decoding * Update decoder code * update gitignore * Use Bytes in API instead of Vec<u8> * rename
1 parent 5f9683d commit e8abdc0

File tree

10 files changed

+223
-67
lines changed

10 files changed

+223
-67
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.tif
2+
*.buf
23

34
# Generated by Cargo
45
# will have compiled files and executables

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ edition = "2021"
66
[dependencies]
77
byteorder = "1"
88
bytes = "1.7.0"
9+
flate2 = "1.0.20"
10+
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false }
911
ndarray = "*"
1012
num_enum = "*"
1113
object_store = "0.11"
1214
thiserror = "1"
1315
tiff = "0.9"
16+
weezl = "0.1.0"
1417

1518
[dev-dependencies]
1619
tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread"] }

show_image.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import numpy as np
2+
from rasterio.plot import show, reshape_as_raster
3+
4+
with open("img.buf", "rb") as f:
5+
buffer = f.read()
6+
7+
arr = np.frombuffer(buffer, np.uint8)
8+
arr = arr.reshape(512, 512, 3)
9+
arr = reshape_as_raster(arr)
10+
show(arr, adjust=True)
11+
12+
###########
13+
14+
with open("img_from_tiff.buf", "rb") as f:
15+
buffer = f.read()
16+
17+
arr = np.frombuffer(buffer, np.uint8)
18+
arr.reshape()
19+
20+
# We first need to reshape into "image" axes, then we need to reshape as "raster" axes.
21+
arr = arr.reshape(12410, 9680, 3)
22+
arr2 = reshape_as_raster(arr)
23+
show(arr2, adjust=True)

src/cog.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,40 @@ impl COGReader {
5959

6060
#[cfg(test)]
6161
mod test {
62+
use std::io::BufReader;
63+
6264
use super::*;
6365
use object_store::local::LocalFileSystem;
66+
use tiff::decoder::{DecodingResult, Limits};
6467

6568
#[tokio::test]
6669
async fn tmp() {
6770
let folder = "/Users/kyle/github/developmentseed/aiocogeo-rs/";
6871
let path = Path::parse("m_4007307_sw_18_060_20220803.tif").unwrap();
6972
let store = Arc::new(LocalFileSystem::new_with_prefix(folder).unwrap());
70-
let _reader = COGReader::try_open(store, path).await.unwrap();
73+
let reader = COGReader::try_open(store.clone(), path.clone())
74+
.await
75+
.unwrap();
76+
let cursor = ObjectStoreCursor::new(store.clone(), path.clone());
77+
let ifd = &reader.ifds.as_ref()[4];
78+
dbg!(ifd.compression);
79+
let tile = ifd.get_tile(0, 0, &cursor).await.unwrap();
80+
std::fs::write("img.buf", tile).unwrap();
81+
// dbg!(tile.len());
82+
}
83+
84+
#[test]
85+
fn tmp_tiff_example() {
86+
let path =
87+
"/Users/kyle/github/developmentseed/aiocogeo-rs/m_4007307_sw_18_060_20220803.tif";
88+
let reader = std::fs::File::open(path).unwrap();
89+
let mut decoder = tiff::decoder::Decoder::new(BufReader::new(reader))
90+
.unwrap()
91+
.with_limits(Limits::unlimited());
92+
let result = decoder.read_image().unwrap();
93+
match result {
94+
DecodingResult::U8(content) => std::fs::write("img_from_tiff.buf", content).unwrap(),
95+
_ => todo!(),
96+
}
7197
}
7298
}

src/compression.rs

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/cursor.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::io::Cursor;
2+
use std::ops::Range;
23
use std::sync::Arc;
34

45
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
@@ -94,6 +95,17 @@ impl ObjectStoreCursor {
9495
}
9596
}
9697

98+
pub(crate) fn store(&self) -> &Arc<dyn ObjectStore> {
99+
&self.store
100+
}
101+
102+
pub(crate) async fn get_range(
103+
&self,
104+
range: Range<usize>,
105+
) -> Result<Bytes, object_store::Error> {
106+
Ok(self.store.get_range(&self.path, range).await?)
107+
}
108+
97109
/// Advance cursor position by a set amount
98110
pub(crate) fn advance(&mut self, amount: usize) {
99111
self.offset += amount;

src/decoder.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::io::{Cursor, Read};
2+
3+
use bytes::Bytes;
4+
use flate2::bufread::ZlibDecoder;
5+
use tiff::tags::{CompressionMethod, PhotometricInterpretation};
6+
use tiff::{TiffError, TiffUnsupportedError};
7+
8+
use crate::error::Result;
9+
10+
// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L370
11+
pub(crate) fn decode_tile(
12+
buf: Bytes,
13+
photometric_interpretation: PhotometricInterpretation,
14+
compression_method: CompressionMethod,
15+
// compressed_length: u64,
16+
jpeg_tables: Option<&Vec<u8>>,
17+
) -> Result<Bytes> {
18+
match compression_method {
19+
CompressionMethod::None => Ok(buf),
20+
CompressionMethod::LZW => decode_lzw(buf),
21+
CompressionMethod::Deflate | CompressionMethod::OldDeflate => decode_deflate(buf),
22+
CompressionMethod::ModernJPEG => {
23+
decode_modern_jpeg(buf, photometric_interpretation, jpeg_tables)
24+
}
25+
method => Err(TiffError::UnsupportedError(
26+
TiffUnsupportedError::UnsupportedCompressionMethod(method),
27+
)
28+
.into()),
29+
}
30+
}
31+
32+
fn decode_lzw(buf: Bytes) -> Result<Bytes> {
33+
// https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147
34+
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
35+
let decoded = decoder.decode(&buf).expect("failed to decode LZW data");
36+
Ok(decoded.into())
37+
}
38+
39+
fn decode_deflate(buf: Bytes) -> Result<Bytes> {
40+
let mut decoder = ZlibDecoder::new(Cursor::new(buf));
41+
let mut buf = Vec::new();
42+
decoder.read_to_end(&mut buf)?;
43+
Ok(buf.into())
44+
}
45+
46+
// https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450
47+
fn decode_modern_jpeg(
48+
buf: Bytes,
49+
photometric_interpretation: PhotometricInterpretation,
50+
jpeg_tables: Option<&Vec<u8>>,
51+
) -> Result<Bytes> {
52+
// Construct new jpeg_reader wrapping a SmartReader.
53+
//
54+
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one central
55+
// location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. Because
56+
// these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker which is
57+
// also at the beginning of the remaining JPEG image data and would confuse the JPEG renderer,
58+
// one of these has to be taken off. In this case the first two bytes of the remaining JPEG
59+
// data is removed because it follows `jpeg_tables`. Similary, `jpeg_tables` ends with a `EOI`
60+
// (HEX: `0xFFD9`) or __end of image__ marker, this has to be removed as well (last two bytes
61+
// of `jpeg_tables`).
62+
let reader = Cursor::new(buf);
63+
64+
let jpeg_reader = match jpeg_tables {
65+
Some(jpeg_tables) => {
66+
let mut reader = reader;
67+
reader.read_exact(&mut [0; 2])?;
68+
69+
Box::new(Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]).chain(reader))
70+
as Box<dyn Read>
71+
}
72+
None => Box::new(reader),
73+
};
74+
75+
let mut decoder = jpeg::Decoder::new(jpeg_reader);
76+
77+
match photometric_interpretation {
78+
PhotometricInterpretation::RGB => decoder.set_color_transform(jpeg::ColorTransform::RGB),
79+
PhotometricInterpretation::WhiteIsZero => {
80+
decoder.set_color_transform(jpeg::ColorTransform::None)
81+
}
82+
PhotometricInterpretation::BlackIsZero => {
83+
decoder.set_color_transform(jpeg::ColorTransform::None)
84+
}
85+
PhotometricInterpretation::TransparencyMask => {
86+
decoder.set_color_transform(jpeg::ColorTransform::None)
87+
}
88+
PhotometricInterpretation::CMYK => decoder.set_color_transform(jpeg::ColorTransform::CMYK),
89+
PhotometricInterpretation::YCbCr => {
90+
decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
91+
}
92+
photometric_interpretation => {
93+
return Err(TiffError::UnsupportedError(
94+
TiffUnsupportedError::UnsupportedInterpretation(photometric_interpretation),
95+
)
96+
.into());
97+
}
98+
}
99+
100+
let data = decoder.decode()?;
101+
Ok(data.into())
102+
}

src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ pub enum AiocogeoError {
88
/// General error.
99
#[error("General error: {0}")]
1010
General(String),
11+
12+
#[error(transparent)]
13+
IOError(#[from] std::io::Error),
14+
15+
#[error(transparent)]
16+
JPEGDecodingError(#[from] jpeg::Error),
17+
18+
#[error(transparent)]
19+
ObjectStore(#[from] object_store::Error),
20+
21+
#[error(transparent)]
22+
TIFFError(#[from] tiff::TiffError),
1123
}
1224

1325
/// Crate-specific result type.

0 commit comments

Comments
 (0)