From b82b5326dea69655de1b0c75cd97f043510513fa Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sun, 3 Sep 2023 13:10:29 +0300 Subject: [PATCH 1/8] Fixed error for non-jpeg compressed images when working with original chunks --- cvat/apps/engine/media_extractors.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 65ab80ae4745..1797488e422d 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -662,7 +662,12 @@ def save_as_chunk(self, images, chunk_path): output = io.BytesIO() if self._dimension == DimensionType.DIM_2D: pil_image = rotate_within_exif(Image.open(image)) - pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=100, subsampling=0) + try: + pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=95, subsampling=0) + except ValueError: + # try to save without additional settings if image writer does not support them + # for example tiff format supports quality only with jpeg compression + pil_image.save(output, format=pil_image.format) else: output, ext = self._write_pcd_file(image)[0:2] arcname = '{:06d}.{}'.format(idx, ext) From ddf34a240019586d5c5749560aa851fc771ee943 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 4 Sep 2023 10:44:36 +0300 Subject: [PATCH 2/8] Fixed saving tiff images in original chunks --- cvat/apps/engine/media_extractors.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 1797488e422d..cd25ad8263f5 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -662,12 +662,12 @@ def save_as_chunk(self, images, chunk_path): output = io.BytesIO() if self._dimension == DimensionType.DIM_2D: pil_image = rotate_within_exif(Image.open(image)) - try: + # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html + if pil_image.format == 'TIFF': + # use loseless lzw compression for tiff images + pil_image.save(output, format='TIFF', compression='tiff_lzw') + else: pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=95, subsampling=0) - except ValueError: - # try to save without additional settings if image writer does not support them - # for example tiff format supports quality only with jpeg compression - pil_image.save(output, format=pil_image.format) else: output, ext = self._write_pcd_file(image)[0:2] arcname = '{:06d}.{}'.format(idx, ext) From 8d2cd2568585f5d1ad8b1aa972a9725d327a1e86 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 4 Sep 2023 10:50:53 +0300 Subject: [PATCH 3/8] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2acbc678de40..e881898d1115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Zooming canvas when scrooling comments list in an issue () - Issues can be created many times when initial submit () +- Running deep learning models on non-jpeg compressed images () - Paddings on tasks/projects/models pages () ### Security From 35efb098560c338f03b1bc5e13bddaaddb0ec28a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 4 Sep 2023 11:54:25 +0300 Subject: [PATCH 4/8] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e881898d1115..5020202524f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Zooming canvas when scrooling comments list in an issue () - Issues can be created many times when initial submit () -- Running deep learning models on non-jpeg compressed images () +- Running deep learning models on non-jpeg compressed tif images () - Paddings on tasks/projects/models pages () ### Security From b6094f59bdfd15fa5fed9908aee50b90bd99e84d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 4 Sep 2023 18:30:23 +0300 Subject: [PATCH 5/8] Do not rewrite a file if rotation was not applied --- cvat/apps/engine/frame_provider.py | 2 +- cvat/apps/engine/media_extractors.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cvat/apps/engine/frame_provider.py b/cvat/apps/engine/frame_provider.py index d37cedd27c57..331ca14419c2 100644 --- a/cvat/apps/engine/frame_provider.py +++ b/cvat/apps/engine/frame_provider.py @@ -175,7 +175,7 @@ def get_preview(self, frame_number): else: preview, _ = self.get_frame(frame_number, self.Quality.COMPRESSED, self.Type.PIL) - preview = rotate_within_exif(preview) + preview = rotate_within_exif(preview)[0] preview.thumbnail(PREVIEW_SIZE) output_buf = BytesIO() diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index cd25ad8263f5..da84ffd36ac9 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -94,7 +94,7 @@ def rotate_within_exif(img: Image): ]: img = img.transpose(Image.FLIP_LEFT_RIGHT) - return img + return img, orientation > 1 class IMediaReader(ABC): def __init__(self, source_path, step, start, stop, dimension): @@ -124,7 +124,7 @@ def _get_preview(obj): preview = Image.open(obj) else: preview = obj - preview = rotate_within_exif(preview) + preview = rotate_within_exif(preview)[0] # TODO - Check if the other formats work. I'm only interested in I;16 for now. Sorry @:-| # Summary: # Images in the Format I;16 definitely don't work. Most likely I;16B/L/N won't work as well. @@ -602,7 +602,7 @@ def __init__(self, quality, dimension=DimensionType.DIM_2D): @staticmethod def _compress_image(image_path, quality): image = image_path.to_image() if isinstance(image_path, av.VideoFrame) else Image.open(image_path) - image = rotate_within_exif(image) + image = rotate_within_exif(image)[0] # Ensure image data fits into 8bit per pixel before RGB conversion as PIL clips values on conversion if image.mode == "I": # Image mode is 32bit integer pixels. @@ -661,9 +661,11 @@ def save_as_chunk(self, images, chunk_path): ext = os.path.splitext(path)[1].replace('.', '') output = io.BytesIO() if self._dimension == DimensionType.DIM_2D: - pil_image = rotate_within_exif(Image.open(image)) - # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html - if pil_image.format == 'TIFF': + pil_image, rotated = rotate_within_exif(Image.open(image)) + if not rotated: + output = image + elif pil_image.format == 'TIFF': + # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html # use loseless lzw compression for tiff images pil_image.save(output, format='TIFF', compression='tiff_lzw') else: From 4571d8609549b6a693723494460152aba84c458e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 4 Sep 2023 22:21:18 +0300 Subject: [PATCH 6/8] Update media_extractors.py --- cvat/apps/engine/media_extractors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index da84ffd36ac9..07c0a4685f36 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -673,7 +673,11 @@ def save_as_chunk(self, images, chunk_path): else: output, ext = self._write_pcd_file(image)[0:2] arcname = '{:06d}.{}'.format(idx, ext) - zip_chunk.writestr(arcname, output.getvalue()) + + if isinstance(output, io.BytesIO): + zip_chunk.writestr(arcname, output.getvalue()) + else: + zip_chunk.write(filename=output, arcname=arcname) # return empty list because ZipChunkWriter write files as is # and does not decode it to know img size. return [] From c888bf055d7a39034cd676f999400a1aa779c72e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 5 Sep 2023 11:19:37 +0300 Subject: [PATCH 7/8] Update media_extractors.py --- cvat/apps/engine/media_extractors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 07c0a4685f36..73f782441012 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -669,7 +669,7 @@ def save_as_chunk(self, images, chunk_path): # use loseless lzw compression for tiff images pil_image.save(output, format='TIFF', compression='tiff_lzw') else: - pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=95, subsampling=0) + pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=100, subsampling=0) else: output, ext = self._write_pcd_file(image)[0:2] arcname = '{:06d}.{}'.format(idx, ext) From 6df08ee1c7b20c960eeda01f40ae8ad46ead2479 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 5 Sep 2023 12:17:08 +0300 Subject: [PATCH 8/8] has_exif --- cvat/apps/engine/frame_provider.py | 2 +- cvat/apps/engine/media_extractors.py | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cvat/apps/engine/frame_provider.py b/cvat/apps/engine/frame_provider.py index 331ca14419c2..d37cedd27c57 100644 --- a/cvat/apps/engine/frame_provider.py +++ b/cvat/apps/engine/frame_provider.py @@ -175,7 +175,7 @@ def get_preview(self, frame_number): else: preview, _ = self.get_frame(frame_number, self.Quality.COMPRESSED, self.Type.PIL) - preview = rotate_within_exif(preview)[0] + preview = rotate_within_exif(preview) preview.thumbnail(PREVIEW_SIZE) output_buf = BytesIO() diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 73f782441012..c81efc82b145 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -80,6 +80,9 @@ def image_size_within_orientation(img: Image): return img.height, img.width return img.width, img.height +def has_exif_rotation(img: Image): + return img.getexif().get(ORIENTATION_EXIF_TAG, ORIENTATION.NORMAL_HORIZONTAL) != ORIENTATION.NORMAL_HORIZONTAL + def rotate_within_exif(img: Image): orientation = img.getexif().get(ORIENTATION_EXIF_TAG, ORIENTATION.NORMAL_HORIZONTAL) if orientation in [ORIENTATION.NORMAL_180_ROTATED, ORIENTATION.MIRROR_VERTICAL]: @@ -94,7 +97,7 @@ def rotate_within_exif(img: Image): ]: img = img.transpose(Image.FLIP_LEFT_RIGHT) - return img, orientation > 1 + return img class IMediaReader(ABC): def __init__(self, source_path, step, start, stop, dimension): @@ -124,7 +127,7 @@ def _get_preview(obj): preview = Image.open(obj) else: preview = obj - preview = rotate_within_exif(preview)[0] + preview = rotate_within_exif(preview) # TODO - Check if the other formats work. I'm only interested in I;16 for now. Sorry @:-| # Summary: # Images in the Format I;16 definitely don't work. Most likely I;16B/L/N won't work as well. @@ -602,7 +605,7 @@ def __init__(self, quality, dimension=DimensionType.DIM_2D): @staticmethod def _compress_image(image_path, quality): image = image_path.to_image() if isinstance(image_path, av.VideoFrame) else Image.open(image_path) - image = rotate_within_exif(image)[0] + image = rotate_within_exif(image) # Ensure image data fits into 8bit per pixel before RGB conversion as PIL clips values on conversion if image.mode == "I": # Image mode is 32bit integer pixels. @@ -661,15 +664,17 @@ def save_as_chunk(self, images, chunk_path): ext = os.path.splitext(path)[1].replace('.', '') output = io.BytesIO() if self._dimension == DimensionType.DIM_2D: - pil_image, rotated = rotate_within_exif(Image.open(image)) - if not rotated: - output = image - elif pil_image.format == 'TIFF': - # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html - # use loseless lzw compression for tiff images - pil_image.save(output, format='TIFF', compression='tiff_lzw') + pil_image = Image.open(image) + if has_exif_rotation(pil_image): + rot_image = rotate_within_exif(pil_image) + if rot_image.format == 'TIFF': + # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html + # use loseless lzw compression for tiff images + rot_image.save(output, format='TIFF', compression='tiff_lzw') + else: + rot_image.save(output, format=rot_image.format if rot_image.format else self.IMAGE_EXT, quality=100, subsampling=0) else: - pil_image.save(output, format=pil_image.format if pil_image.format else self.IMAGE_EXT, quality=100, subsampling=0) + output = image else: output, ext = self._write_pcd_file(image)[0:2] arcname = '{:06d}.{}'.format(idx, ext)