From b904b2f68865edf4d9e7d8594eaa3b9a5f062416 Mon Sep 17 00:00:00 2001 From: Daniel Xenes Date: Thu, 3 Oct 2024 13:26:05 -0400 Subject: [PATCH 1/2] adds tiff renderer --- django/bosstiles/renderers.py | 15 +++++++++++++++ django/bosstiles/views.py | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/django/bosstiles/renderers.py b/django/bosstiles/renderers.py index a95e3c52..3c361552 100644 --- a/django/bosstiles/renderers.py +++ b/django/bosstiles/renderers.py @@ -50,3 +50,18 @@ def render(self, data, media_type=None, renderer_context=None): file_obj.seek(0) return file_obj.read() +class TIFFRenderer(renderers.BaseRenderer): + """ A DRF renderer for rendering an image as a TIFF file """ + media_type = 'image/tiff' + format = 'tiff' + charset = None + render_style = 'binary' + + @check_for_403 + @check_for_429 + def render(self, data, media_type=None, renderer_context=None): + file_obj = io.BytesIO() + # Save the image data as TIFF instead of JPEG + data.save(file_obj, "TIFF") + file_obj.seek(0) + return file_obj.read() diff --git a/django/bosstiles/views.py b/django/bosstiles/views.py index 6e18b92e..0d68525e 100644 --- a/django/bosstiles/views.py +++ b/django/bosstiles/views.py @@ -27,7 +27,7 @@ import bossutils -from .renderers import PNGRenderer, JPEGRenderer +from .renderers import PNGRenderer, JPEGRenderer, TIFFRenderer class CutoutTile(APIView): @@ -36,7 +36,7 @@ class CutoutTile(APIView): * Requires authentication. """ - renderer_classes = (PNGRenderer, JPEGRenderer) + renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer) def __init__(self): super().__init__() @@ -175,7 +175,7 @@ class Tile(APIView): * Requires authentication. """ - renderer_classes = (PNGRenderer, JPEGRenderer) + renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer) def __init__(self): super().__init__() From 3a1c9e4b1e6148b50fe7fb74e09b93a03aa7a567 Mon Sep 17 00:00:00 2001 From: Daniel Xenes Date: Thu, 17 Oct 2024 15:09:04 -0400 Subject: [PATCH 2/2] adds jp2 renderer and unit tests --- django/bosstiles/renderers.py | 16 +++++ .../test/int_test_tiles_view_uint8.py | 44 +++++++++++++ django/bosstiles/test/tiles_view_uint8.py | 61 +++++++++++++++++++ django/bosstiles/views.py | 6 +- 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/django/bosstiles/renderers.py b/django/bosstiles/renderers.py index 3c361552..98916021 100644 --- a/django/bosstiles/renderers.py +++ b/django/bosstiles/renderers.py @@ -65,3 +65,19 @@ def render(self, data, media_type=None, renderer_context=None): data.save(file_obj, "TIFF") file_obj.seek(0) return file_obj.read() + +class JPEG2000Renderer(renderers.BaseRenderer): + """ A DRF renderer for rendering an image as a JPEG2000 file """ + media_type = 'image/jp2' + format = 'jp2' + charset = None + render_style = 'binary' + + @check_for_403 + @check_for_429 + def render(self, data, media_type=None, renderer_context=None): + file_obj = io.BytesIO() + # Save the image data as JPEG2000 + data.save(file_obj, "JP2") + file_obj.seek(0) + return file_obj.read() diff --git a/django/bosstiles/test/int_test_tiles_view_uint8.py b/django/bosstiles/test/int_test_tiles_view_uint8.py index 48d51e4c..75321d6e 100644 --- a/django/bosstiles/test/int_test_tiles_view_uint8.py +++ b/django/bosstiles/test/int_test_tiles_view_uint8.py @@ -91,6 +91,50 @@ def test_no_cache_read(self): np.testing.assert_equal(test_img, self.test_data_8[3, 300:800, 400:700]) + def test_no_cache_read_tiff(self): + """Test no-cache option when reading an image""" + factory = APIRequestFactory() + + # Get an image file + request = factory.get('/' + version + '/image/col1/exp1/channel1/xy/0/400:700/300:800/3/?no-cache=true', + Accept='image/tiff') + force_authenticate(request, user=self.user) + + time.sleep(10) + + # Make request + response = CutoutTile.as_view()(request, collection='col1', experiment='exp1', channel='channel1', + orientation='xy', resolution='0', x_args='400:700', y_args='300:800', + z_args='3') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check data is correct (this is pre-renderer) + test_img = np.array(response.data, dtype="uint8") + + np.testing.assert_equal(test_img, self.test_data_8[3, 300:800, 400:700]) + + def test_no_cache_read_jp2(self): + """Test no-cache option when reading an image""" + factory = APIRequestFactory() + + # Get an image file + request = factory.get('/' + version + '/image/col1/exp1/channel1/xy/0/400:700/300:800/3/?no-cache=true', + Accept='image/jp2') + force_authenticate(request, user=self.user) + + time.sleep(10) + + # Make request + response = CutoutTile.as_view()(request, collection='col1', experiment='exp1', channel='channel1', + orientation='xy', resolution='0', x_args='400:700', y_args='300:800', + z_args='3') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check data is correct (this is pre-renderer) + test_img = np.array(response.data, dtype="uint8") + + np.testing.assert_equal(test_img, self.test_data_8[3, 300:800, 400:700]) + class TileViewIntegrationTests(TileInterfaceViewTestMixin, APITestCase): layer = DjangoSetupLayer diff --git a/django/bosstiles/test/tiles_view_uint8.py b/django/bosstiles/test/tiles_view_uint8.py index 08413f78..b24170c9 100644 --- a/django/bosstiles/test/tiles_view_uint8.py +++ b/django/bosstiles/test/tiles_view_uint8.py @@ -266,6 +266,67 @@ def test_png_uint8_yz(self): np.testing.assert_equal(test_img, np.squeeze(self.test_data_8[8:12, 28:32, 0])) + def test_tiff_uint8_xy(self): + """ Test a png xy slice""" + # Post data to the database + factory = APIRequestFactory() + + # Get an image file + request = factory.get('/' + version + '/tile/col1/exp1/channel1/xy/512/0/0/0/5/', + Accept='image/tiff') + force_authenticate(request, user=self.user) + # Make request + response = Tile.as_view()(request, collection='col1', experiment='exp1', channel='channel1', + orientation='xy', tile_size='512', resolution='0', + x_idx='0', y_idx='0', z_idx='5') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check data is correct (this is pre-renderer) + test_img = np.array(response.data, dtype="uint8") + + np.testing.assert_equal(test_img, self.test_data_8[5, 0:512, 0:512]) + + def test_tiff_uint16_xy(self): + """ Test a png xy slice""" + # Post data to the database + factory = APIRequestFactory() + + # Get an image file + request = factory.get('/' + version + '/tile/col1/exp1/channel2/xy/512/0/0/0/5/', + Accept='image/tiff') + force_authenticate(request, user=self.user) + # Make request + response = Tile.as_view()(request, collection='col1', experiment='exp1', channel='channel1', + orientation='xy', tile_size='512', resolution='0', + x_idx='0', y_idx='0', z_idx='5') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check data is correct (this is pre-renderer) + test_img = np.array(response.data, dtype="uint16") + + np.testing.assert_equal(test_img, self.test_data_8[5, 0:512, 0:512]) + + + def test_jp2_uint8_xy(self): + """ Test a png xy slice""" + # Post data to the database + factory = APIRequestFactory() + + # Get an image file + request = factory.get('/' + version + '/tile/col1/exp1/channel1/xy/512/0/0/0/5/', + Accept='image/jp2') + force_authenticate(request, user=self.user) + # Make request + response = Tile.as_view()(request, collection='col1', experiment='exp1', channel='channel1', + orientation='xy', tile_size='512', resolution='0', + x_idx='0', y_idx='0', z_idx='5') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Check data is correct (this is pre-renderer) + test_img = np.array(response.data, dtype="uint8") + + np.testing.assert_equal(test_img, self.test_data_8[5, 0:512, 0:512]) + class TestTileInterfaceView(TileInterfaceViewTestMixin, APITestCase): diff --git a/django/bosstiles/views.py b/django/bosstiles/views.py index 0d68525e..1036516f 100644 --- a/django/bosstiles/views.py +++ b/django/bosstiles/views.py @@ -27,7 +27,7 @@ import bossutils -from .renderers import PNGRenderer, JPEGRenderer, TIFFRenderer +from .renderers import PNGRenderer, JPEGRenderer, TIFFRenderer, JPEG2000Renderer class CutoutTile(APIView): @@ -36,7 +36,7 @@ class CutoutTile(APIView): * Requires authentication. """ - renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer) + renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer, JPEG2000Renderer) def __init__(self): super().__init__() @@ -175,7 +175,7 @@ class Tile(APIView): * Requires authentication. """ - renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer) + renderer_classes = (PNGRenderer, JPEGRenderer, TIFFRenderer, JPEG2000Renderer) def __init__(self): super().__init__()