Skip to content

Update tiff reading - writing methods for f32 and u8 datatype #345

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 6 commits into
base: main
Choose a base branch
from

Conversation

viktorho
Copy link

@viktorho viktorho commented Apr 7, 2025

  • add traits TiffValueConverter

  • add write_image_tiff_internal(), and example for u8 only

  • add read_image_tiff_internal(), and example for u8, f32 (experiments)

@edgarriba edgarriba requested a review from Copilot April 7, 2025 09:40
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (2)

crates/kornia-io/src/tiff.rs:52

  • [nitpick] Consider translating the comment 'Đảm bảo T::Inner là u8' to English for consistency with the rest of the codebase.
    T: ColorType<Inner = u8>,  // Đảm bảo T::Inner là u8

crates/kornia-io/src/tiff.rs:203

  • [nitpick] The test function name 'test_read_image_tiff_rgb8' does not match the usage of 'read_image_tiff_rgba8'; consider renaming the test function for clarity.
        let image = read_image_tiff_rgba8(&file_path).unwrap();

Copy link
Contributor

@AS1100K AS1100K left a comment

Choose a reason for hiding this comment

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

Make sure to format your code with rustfmt and run the tests as one of your test fails.

@@ -26,6 +26,15 @@ pub enum IoError {
#[error("Error with Jpeg encoding")]
JpegEncodingError(#[from] jpeg_encoder::EncodingError),


/// Error decoding the TIFF image.
#[error("Error with TIFF decoding")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[error("Error with TIFF decoding")]
#[error("Error with TIFF decoding. {0}")]

Let's make the errors more informational. I have also made them in #336

Copy link
Member

Choose a reason for hiding this comment

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

i advocate more to use #[error(transparent)] to forward directly the original errors
https://github.com/kornia/kornia-rs/blob/main/crates/kornia-io/src/stream/error.rs#L5

TiffError(#[from] tiff::TiffError),

/// Error to create the TIFF image.
#[error("Error with TIFF encoding")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[error("Error with TIFF encoding")]
#[error("Error with TIFF encoding. {0}")]

Same

image: &Image<u8, C>,
) -> Result<(), IoError>
where
T: ColorType<Inner = u8>, // Đảm bảo T::Inner là u8
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick, consider writing the comment in english

Comment on lines 55 to 60
if image.num_channels() != C {
return Err(IoError::TiffEncodingError(
format!("Image has {} channels, but expected {}", image.num_channels(), C)
));

}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if image.num_channels() != C {
return Err(IoError::TiffEncodingError(
format!("Image has {} channels, but expected {}", image.num_channels(), C)
));
}

Consider removing this check, as it is a internal function and the public facing functions ensure that the correct number of channels are provided.

Comment on lines +72 to +75
/// Reads a TIFF image with a single channel (rgb8).
/// #Arguments
/// file_path - The path to the TIFF file.
///
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Reads a TIFF image with a single channel (rgb8).
/// #Arguments
/// file_path - The path to the TIFF file.
///
/// Reads a TIFF image with a three channel (rgb8).
///
/// # Arguments
///
/// - `file_path` - The path to the TIFF file.

Make it in markdown format, so it render properly on docs.rs

Comment on lines +124 to +131
#[test]
fn read_jpeg_f32() -> Result<(), IoError> {
let image = read_image_jpeg_mono8("../../tests/data/dog.jpeg")?;
assert_eq!(image.cols(), 258);
assert_eq!(image.rows(), 195);
Ok(())
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[test]
fn read_jpeg_f32() -> Result<(), IoError> {
let image = read_image_jpeg_mono8("../../tests/data/dog.jpeg")?;
assert_eq!(image.cols(), 258);
assert_eq!(image.rows(), 195);
Ok(())
}

Firstly, the name of the test doesn't make sense as jpeg doesn't support f32 and you are using read_image_jpeg_mono8 in it.

And secondly, this doesn't work. It will give ImageCreationError(InvalidChannelShape(150930, 50310)) error.

Make sure to run the tests first.

///
/// This trait is used to convert TIFF values (u8 or f32) to a specific type (e.g., u8 or f32).
/// It provides two methods:
pub trait TiffValueConverter: Sized {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should have a simple trait (could be internal?) with only one function like type_ and should have it's implementation for all numeric primitive types that could be bound to generic T (P in your case, change it to T so that it's globally same).
The type_ function could be used to verify if the decoded image is of same type, if not return error.

This could be used to to only allow integer primitive types. Also, we should not deal with any conversion and make it up to the user if they want to covert the type.

Then your functions should look something like:

pub trait NumericTrait {
    fn type_() -> NumericType;
}

pub fn read_image_tiff_rgba8<T: NumericTrait>(file_path: impl AsRef<Path>) -> Result<Image<T, 4>, IoError> {
    todo!()
}

pub fn read_image_tiff_internal<T: NumericTrait>(/* arguments */) -> /* ... */

What do you say @edgarriba ?

Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me

Copy link
Author

Choose a reason for hiding this comment

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

Hello, I think we should provide more details when implementing images for our application. It could be a marker type instead of struct u8 in this context, for example:
let rgb_image: Image<u8, 4> = read_image_any::<Rgba8>("image.jpg")?;
From my vision, this could be more sustainable in case we would provide more features for the fourth channel, like depth or heat instead of alpha.

Copy link
Author

Choose a reason for hiding this comment

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

@edgarriba , @AS1100K
Would it be a good one in this case? If it's good to go, i'm gonna include these for the next pull request

Copy link
Member

Choose a reason for hiding this comment

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

for now we decided in a previous conversation in discord to hold on the read_any and leave generalizations for later

use std::fs;
use std::path::PathBuf;
use kornia_image::ImageSize;
#[test]
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, add a read_write test

Refer

fn read_write_jpeg() -> Result<(), IoError> {
let tmp_dir = tempfile::tempdir()?;
create_dir_all(tmp_dir.path())?;
let file_path = tmp_dir.path().join("dog.jpeg");
let image_data = read_image_jpeg_rgb8("../../tests/data/dog.jpeg")?;
write_image_jpeg_rgb8(&file_path, &image_data)?;
let image_data_back = read_image_jpeg_rgb8(&file_path)?;
assert!(file_path.exists(), "File does not exist: {:?}", file_path);
assert_eq!(image_data_back.cols(), 258);
assert_eq!(image_data_back.rows(), 195);
assert_eq!(image_data_back.num_channels(), 3);
Ok(())
}

let image = Image::new(ImageSize { width: 100, height: 100 }, vec![0; 30000]).unwrap();
let file_path = PathBuf::from("test_rgb8.tiff");
assert!(write_image_tiff_rgb8(&file_path, &image).is_ok());
fs::remove_file(file_path).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Use tempfile crate for this, as all the similar tests in this crate uses it

let image = Image::new(ImageSize { width: 100, height: 100 }, vec![0; 10000]).unwrap();
let file_path = PathBuf::from("test_gray8.tiff");
assert!(write_image_tiff_gray8(&file_path, &image).is_ok());
fs::remove_file(file_path).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

@@ -26,6 +26,15 @@ pub enum IoError {
#[error("Error with Jpeg encoding")]
JpegEncodingError(#[from] jpeg_encoder::EncodingError),


/// Error decoding the TIFF image.
#[error("Error with TIFF decoding")]
Copy link
Member

Choose a reason for hiding this comment

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

i advocate more to use #[error(transparent)] to forward directly the original errors
https://github.com/kornia/kornia-rs/blob/main/crates/kornia-io/src/stream/error.rs#L5

};
use std::fs::File;
use std::path::Path;
use std::io::BufWriter;
Copy link
Member

Choose a reason for hiding this comment

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

group properly the imports, probably cargo fmt will do this for you

///
/// This trait is used to convert TIFF values (u8 or f32) to a specific type (e.g., u8 or f32).
/// It provides two methods:
pub trait TiffValueConverter: Sized {
Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me

}

let file = File::open(&file_path)?;
let mut decoder = Decoder::new(file).map_err(IoError::TiffError)?;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let mut decoder = Decoder::new(file).map_err(IoError::TiffError)?;
let mut decoder = Decoder::new(file)?;

forward error diectly like this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants