diff --git a/satpy/tests/writer_tests/test_mitiff.py b/satpy/tests/writer_tests/test_mitiff.py index 498ed0e5a3..a7e9589f1f 100644 --- a/satpy/tests/writer_tests/test_mitiff.py +++ b/satpy/tests/writer_tests/test_mitiff.py @@ -20,8 +20,15 @@ Based on the test for geotiff writer """ +import logging +import os import unittest +import numpy as np +from PIL import Image + +logger = logging.getLogger() + class TestMITIFFWriter(unittest.TestCase): """Test the MITIFF Writer class.""" @@ -270,7 +277,6 @@ def _get_test_dataset_with_bad_values(self, bands=3): """Create a single test dataset.""" from datetime import datetime - import numpy as np import xarray as xr from pyresample.geometry import AreaDefinition from pyresample.utils import proj4_str_to_dict @@ -531,6 +537,19 @@ def _get_test_dataset_three_bands_prereq(self, bands=3): 10.8]}) return ds1 + def _read_back_mitiff_and_check(self, filename, expected, test_shape=(100, 200)): + pillow_tif = Image.open(filename) + for frame_no in range(pillow_tif.n_frames): + pillow_tif.seek(frame_no) + np.testing.assert_allclose(np.asarray(pillow_tif.getdata()).reshape(test_shape), + expected[frame_no], atol=1.e-6, rtol=0) + + def _imagedescription_from_mitiff(self, filename): + pillow_tif = Image.open(filename) + IMAGEDESCRIPTION = 270 + imgdesc = (pillow_tif.tag_v2.get(IMAGEDESCRIPTION)).split('\n') + return imgdesc + def test_init(self): """Test creating the writer with no arguments.""" from satpy.writers.mitiff import MITIFFWriter @@ -545,81 +564,50 @@ def test_simple_write(self): def test_save_datasets(self): """Test basic writer operation save_datasets.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - expected = np.full((100, 200), 0) + expected = [np.full((100, 200), 0)] dataset = self._get_test_datasets() w = MITIFFWriter(base_dir=self.base_dir) w.save_datasets(dataset) filename = (dataset[0].attrs['metadata_requirements']['file_pattern']).format( start_time=dataset[0].attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - for image in tif.iter_images(): - np.testing.assert_allclose(image, expected, atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected) def test_save_datasets_sensor_set(self): """Test basic writer operation save_datasets.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - expected = np.full((100, 200), 0) + expected = [np.full((100, 200), 0)] dataset = self._get_test_datasets_sensor_set() w = MITIFFWriter(base_dir=self.base_dir) w.save_datasets(dataset) filename = (dataset[0].attrs['metadata_requirements']['file_pattern']).format( start_time=dataset[0].attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - for image in tif.iter_images(): - np.testing.assert_allclose(image, expected, atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected) def test_save_one_dataset(self): """Test basic writer operation with one dataset ie. no bands.""" - import os - - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter dataset = self._get_test_one_dataset() w = MITIFFWriter(base_dir=self.base_dir) w.save_dataset(dataset) - tif = TIFF.open(os.path.join(self.base_dir, os.listdir(self.base_dir)[0])) - IMAGEDESCRIPTION = 270 - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, os.listdir(self.base_dir)[0])) for key in imgdesc: if 'In this file' in key: self.assertEqual(key, ' Channels: 1 In this file: 1') - def test_save_one_dataset_sesnor_set(self): + def test_save_one_dataset_sensor_set(self): """Test basic writer operation with one dataset ie. no bands.""" - import os - - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter dataset = self._get_test_one_dataset_sensor_set() w = MITIFFWriter(base_dir=self.base_dir) w.save_dataset(dataset) - tif = TIFF.open(os.path.join(self.base_dir, os.listdir(self.base_dir)[0])) - IMAGEDESCRIPTION = 270 - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, os.listdir(self.base_dir)[0])) for key in imgdesc: if 'In this file' in key: self.assertEqual(key, ' Channels: 1 In this file: 1') def test_save_dataset_with_calibration(self): """Test writer operation with calibration.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter expected_ir = np.full((100, 200), 255) @@ -755,9 +743,8 @@ def test_save_dataset_with_calibration(self): w.save_dataset(dataset) filename = (dataset.attrs['metadata_requirements']['file_pattern']).format( start_time=dataset.attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - IMAGEDESCRIPTION = 270 - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, filename)) found_table_calibration = False number_of_calibrations = 0 for key in imgdesc: @@ -785,19 +772,13 @@ def test_save_dataset_with_calibration(self): self.fail("Not a valid channel description i the given key.") self.assertTrue(found_table_calibration, "Table_calibration is not found in the imagedescription.") self.assertEqual(number_of_calibrations, 6) - for i, image in enumerate(tif.iter_images()): - np.testing.assert_allclose(image, expected[i], atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected) def test_save_dataset_with_calibration_one_dataset(self): """Test saving if mitiff as dataset with only one channel.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - expected = np.full((100, 200), 255) + expected = [np.full((100, 200), 255)] expected_key_channel = [u'Table_calibration: BT, BT, °[C], 8, [ 50.00 49.22 48.43 47.65 46.86 46.08 45.29 ' '44.51 43.73 42.94 42.16 41.37 40.59 39.80 39.02 38.24 37.45 36.67 35.88 35.10 34.31 ' '33.53 32.75 31.96 31.18 30.39 29.61 28.82 28.04 27.25 26.47 25.69 24.90 24.12 23.33 ' @@ -826,9 +807,8 @@ def test_save_dataset_with_calibration_one_dataset(self): w.save_dataset(dataset) filename = (dataset.attrs['metadata_requirements']['file_pattern']).format( start_time=dataset.attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - IMAGEDESCRIPTION = 270 - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, filename)) found_table_calibration = False number_of_calibrations = 0 for key in imgdesc: @@ -839,29 +819,21 @@ def test_save_dataset_with_calibration_one_dataset(self): number_of_calibrations += 1 self.assertTrue(found_table_calibration, "Expected table_calibration is not found in the imagedescription.") self.assertEqual(number_of_calibrations, 1) - for image in tif.iter_images(): - np.testing.assert_allclose(image, expected, atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected) def test_save_dataset_with_bad_value(self): """Test writer operation with bad values.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - expected = np.array([[0, 4, 1, 37, 73], - [110, 146, 183, 219, 255]]) - + _expected = np.array([[0, 4, 1, 37, 73], + [110, 146, 183, 219, 255]]) + expected = [_expected, _expected, _expected] dataset = self._get_test_dataset_with_bad_values() w = MITIFFWriter(base_dir=self.base_dir) w.save_dataset(dataset) filename = "{:s}_{:%Y%m%d_%H%M%S}.mitiff".format(dataset.attrs['name'], dataset.attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - for image in tif.iter_images(): - np.testing.assert_allclose(image, expected, atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected, test_shape=(2, 5)) def test_convert_proj4_string(self): """Test conversion of geolocations.""" @@ -913,42 +885,37 @@ def test_convert_proj4_string(self): def test_save_dataset_palette(self): """Test writer operation as palette.""" - import os - - import numpy as np - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - expected = np.full((100, 200), 0) - - exp_c = ([0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - color_map = [[0, 3], [1, 4], [2, 5]] + expected = [np.full((100, 200), 0)] + + exp_c = [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + color_map = (0, 1, 2, 3, 4, 5) pal_desc = ['test', 'test2'] unit = "Test" @@ -963,14 +930,14 @@ def test_save_dataset_palette(self): w.save_dataset(dataset, **palette) filename = "{:s}_{:%Y%m%d_%H%M%S}.mitiff".format(dataset.attrs['name'], dataset.attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) + pillow_tif = Image.open(os.path.join(self.base_dir, filename)) # Need to check PHOTOMETRIC is 3, ie palette - self.assertEqual(tif.GetField('PHOTOMETRIC'), 3) - colormap = tif.GetField('COLORMAP') + self.assertEqual(pillow_tif.tag_v2.get(262), 3) # Check the colormap of the palette image + palette = pillow_tif.palette + colormap = list((palette.getdata())[1]) self.assertEqual(colormap, exp_c) - IMAGEDESCRIPTION = 270 - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, filename)) found_color_info = False unit_name_found = False name_length_found = False @@ -997,8 +964,7 @@ def test_save_dataset_palette(self): self.assertEqual(unit_name, ' Test') # Check the palette description of the palette self.assertEqual(names, [' test', ' test2']) - for image in tif.iter_images(): - np.testing.assert_allclose(image, expected, atol=1.e-6, rtol=0) + self._read_back_mitiff_and_check(os.path.join(self.base_dir, filename), expected) def test_simple_write_two_bands(self): """Test basic writer operation with 3 bands from 2 prerequisites.""" @@ -1009,20 +975,69 @@ def test_simple_write_two_bands(self): def test_get_test_dataset_three_bands_prereq(self): """Test basic writer operation with 3 bands with DataQuery prerequisites with missing name.""" - import os - - from libtiff import TIFF - from satpy.writers.mitiff import MITIFFWriter - IMAGEDESCRIPTION = 270 dataset = self._get_test_dataset_three_bands_prereq() w = MITIFFWriter(base_dir=self.base_dir) w.save_dataset(dataset) filename = "{:s}_{:%Y%m%d_%H%M%S}.mitiff".format(dataset.attrs['name'], dataset.attrs['start_time']) - tif = TIFF.open(os.path.join(self.base_dir, filename)) - imgdesc = (tif.GetField(IMAGEDESCRIPTION)).decode('utf-8').split('\n') + imgdesc = self._imagedescription_from_mitiff(os.path.join(self.base_dir, filename)) for element in imgdesc: if ' Channels:' in element: self.assertEqual(element, ' Channels: 3 In this file: 1 2 3') + + def test_save_dataset_with_calibration_error_one_dataset(self): + """Test saving if mitiff as dataset with only one channel with invalid calibration.""" + import sys + + from satpy.tests.utils import make_dsq + from satpy.writers.mitiff import MITIFFWriter + logger.level = logging.DEBUG + + dataset = self._get_test_dataset_calibration_one_dataset() + prereqs = [make_dsq(name='4', calibration='not_valid_calibration_name')] + dataset.attrs['prerequisites'] = prereqs + w = MITIFFWriter(filename=dataset.attrs['metadata_requirements']['file_pattern'], base_dir=self.base_dir) + _reverse_offset = 0. + _reverse_scale = 1. + _decimals = 2 + stream_handler = logging.StreamHandler(sys.stdout) + logger.addHandler(stream_handler) + try: + with self.assertLogs(logger) as lc: + w._add_calibration_datasets(4, dataset, _reverse_offset, _reverse_scale, _decimals) + for _op in lc.output: + self.assertIn("Unknown calib type. Must be Radiance, Reflectance or BT.", _op) + finally: + logger.removeHandler(stream_handler) + + def test_save_dataset_with_missing_palette(self): + """Test saving if mitiff missing palette.""" + import sys + + from satpy.writers.mitiff import MITIFFWriter + stream_handler = logging.StreamHandler(sys.stdout) + logger.addHandler(stream_handler) + logger.setLevel(logging.DEBUG) + + dataset = self._get_test_one_dataset() + pal_desc = ['test', 'test2'] + unit = "Test" + palette = {'palette': True, + 'palette_description': pal_desc, + 'palette_unit': unit, + 'palette_channel_name': dataset.attrs['name']} + w = MITIFFWriter(base_dir=self.base_dir) + tiffinfo = {} + tiffinfo[270] = "Just dummy image desc".encode('utf-8') + filename = "{:s}_{:%Y%m%d_%H%M%S}.mitiff".format(dataset.attrs['name'], + dataset.attrs['start_time']) + try: + with self.assertLogs(logger, logging.ERROR) as lc: + w._save_as_palette(dataset.compute(), os.path.join(self.base_dir, filename), tiffinfo, **palette) + for _op in lc.output: + self.assertIn(("In a mitiff palette image a color map must be provided: " + "palette_color_map is missing."), _op) + finally: + logger.removeHandler(stream_handler) diff --git a/satpy/writers/mitiff.py b/satpy/writers/mitiff.py index ef07417527..d85527e7a6 100644 --- a/satpy/writers/mitiff.py +++ b/satpy/writers/mitiff.py @@ -18,9 +18,11 @@ """MITIFF writer objects for creating MITIFF files from `Dataset` objects.""" import logging +import os import dask import numpy as np +from PIL import Image, ImagePalette from satpy.dataset import DataID, DataQuery from satpy.writers import ImageWriter, get_enhanced_image @@ -367,7 +369,7 @@ def _add_calibration_datasets(self, ch, datasets, reverse_offset, reverse_scale, elif ds.attrs['prerequisites'][i].get('calibration') == 'brightness_temperature': found_calibration = True _table_calibration += ', BT, ' - _table_calibration += u'\u00B0' # '\u2103' + _table_calibration += "\N{DEGREE SIGN}" _table_calibration += u'[C]' _reverse_offset = 255. @@ -621,7 +623,7 @@ def _calibrate_data(self, dataset, calibration, min_val, max_val): (float(max_val) - float(min_val))) * 255. return _data.clip(0, 255) - def _save_as_palette(self, tif, datasets, **kwargs): + def _save_as_palette(self, datasets, tmp_gen_filename, tiffinfo, **kwargs): # MITIFF palette has only one data channel if len(datasets.dims) == 2: LOG.debug("Palette ok with only 2 dimensions. ie only x and y") @@ -629,40 +631,45 @@ def _save_as_palette(self, tif, datasets, **kwargs): # The value of the component is used as an index into the red, green and blue curves # in the ColorMap field to retrieve an RGB triplet that defines the color. When # PhotometricInterpretation=3 is used, ColorMap must be present and SamplesPerPixel must be 1. - tif.SetField('PHOTOMETRIC', 3) - - # As write_image can not save tiff image as palette, this has to be done basicly - # ie. all needed tags needs to be set. - tif.SetField('IMAGEWIDTH', datasets.sizes['x']) - tif.SetField('IMAGELENGTH', datasets.sizes['y']) - tif.SetField('BITSPERSAMPLE', 8) - tif.SetField('COMPRESSION', tif.get_tag_define('deflate')) + tiffinfo[270] = tiffinfo[270].decode('utf-8') + img = Image.fromarray(datasets.data.astype(np.uint8), mode='P') if 'palette_color_map' in kwargs: - tif.SetField('COLORMAP', kwargs['palette_color_map']) + img.putpalette(ImagePalette.ImagePalette('RGB', kwargs['palette_color_map'])) else: - LOG.ERROR("In a mitiff palette image a color map must be provided: palette_color_map is missing.") + LOG.error("In a mitiff palette image a color map must be provided: palette_color_map is missing.") + return - data_type = np.uint8 - # Looks like we need to pass the data to writeencodedstrip as ctypes - cont_data = np.ascontiguousarray(datasets.data, data_type) - tif.WriteEncodedStrip(0, cont_data.ctypes.data, - datasets.sizes['x'] * datasets.sizes['y']) - tif.WriteDirectory() + img.save(tmp_gen_filename, compression='tiff_deflate', compress_level=9, tiffinfo=tiffinfo) - def _save_as_enhanced(self, tif, datasets, **kwargs): + def _save_as_enhanced(self, datasets, tmp_gen_filename, **kwargs): """Save datasets as an enhanced RGB image.""" img = get_enhanced_image(datasets.squeeze(), enhance=self.enhancer) + tiffinfo = {} if 'bands' in img.data.sizes and 'bands' not in datasets.sizes: LOG.debug("Datasets without 'bands' become image with 'bands' due to enhancement.") LOG.debug("Needs to regenerate mitiff image description") - image_description = self._make_image_description(img.data, **kwargs) - tif.SetField(IMAGEDESCRIPTION, (image_description).encode('utf-8')) + image_description = self._make_image_description(img.data, **kwargs) + tiffinfo[IMAGEDESCRIPTION] = (image_description).encode('utf-8') + + mitiff_frames = [] for band in img.data['bands']: chn = img.data.sel(bands=band) data = chn.values.clip(0, 1) * 254. + 1 data = data.clip(0, 255) - tif.write_image(data.astype(np.uint8), compression='deflate') + mitiff_frames.append(Image.fromarray(data.astype(np.uint8), mode='L')) + mitiff_frames[0].save(tmp_gen_filename, save_all=True, append_images=mitiff_frames[1:], + compression='tiff_deflate', compress_level=9, tiffinfo=tiffinfo) + + def _generate_intermediate_filename(self, gen_filename): + """Replace mitiff ext because pillow doesn't recognise the file type.""" + bs, ex = os.path.splitext(gen_filename) + tmp_gen_filename = gen_filename + if ex.endswith('mitiff'): + bd = os.path.dirname(bs) + bn = os.path.basename(bs) + tmp_gen_filename = os.path.join(bd, '.' + bn + '.tif') + return tmp_gen_filename def _save_datasets_as_mitiff(self, datasets, image_description, gen_filename, **kwargs): @@ -671,15 +678,14 @@ def _save_datasets_as_mitiff(self, datasets, image_description, Include the special tags making it a mitiff file. """ - from libtiff import TIFF - - tif = TIFF.open(gen_filename, mode='wb') - - tif.SetField(IMAGEDESCRIPTION, (image_description).encode('utf-8')) + tmp_gen_filename = self._generate_intermediate_filename(gen_filename) + tiffinfo = {} + tiffinfo[IMAGEDESCRIPTION] = (image_description).encode('latin-1') cns = self.translate_channel_name.get(kwargs['sensor'], {}) if isinstance(datasets, list): LOG.debug("Saving datasets as list") + mitiff_frames = [] for _cn in self.channel_order[kwargs['sensor']]: for dataset in datasets: if dataset.attrs['name'] == _cn: @@ -688,19 +694,22 @@ def _save_datasets_as_mitiff(self, datasets, image_description, data = self._calibrate_data(dataset, dataset.attrs['calibration'], self.mitiff_config[kwargs['sensor']][cn]['min-val'], self.mitiff_config[kwargs['sensor']][cn]['max-val']) - tif.write_image(data.astype(np.uint8), compression='deflate') + mitiff_frames.append(Image.fromarray(data.astype(np.uint8), mode='L')) break + mitiff_frames[0].save(tmp_gen_filename, save_all=True, append_images=mitiff_frames[1:], + compression='tiff_deflate', compress_level=9, tiffinfo=tiffinfo) elif 'dataset' in datasets.attrs['name']: - self._save_single_dataset(datasets, cns, tif, kwargs) + LOG.debug("Saving dataset as single dataset.") + self._save_single_dataset(datasets, cns, tmp_gen_filename, tiffinfo, kwargs) elif self.palette: LOG.debug("Saving dataset as palette.") - self._save_as_palette(tif, datasets, **kwargs) + self._save_as_palette(datasets, tmp_gen_filename, tiffinfo, **kwargs) else: LOG.debug("Saving datasets as enhanced image") - self._save_as_enhanced(tif, datasets, **kwargs) - del tif + self._save_as_enhanced(datasets, tmp_gen_filename, **kwargs) + os.rename(tmp_gen_filename, gen_filename) - def _save_single_dataset(self, datasets, cns, tif, kwargs): + def _save_single_dataset(self, datasets, cns, tmp_gen_filename, tiffinfo, kwargs): LOG.debug("Saving %s as a dataset.", datasets.attrs['name']) if len(datasets.dims) == 2 and (all('bands' not in i for i in datasets.dims)): # Special case with only one channel ie. no bands @@ -712,8 +721,8 @@ def _save_single_dataset(self, datasets, cns, tif, kwargs): data = self._calibrate_data(datasets, datasets.attrs['prerequisites'][0].get('calibration'), self.mitiff_config[kwargs['sensor']][cn]['min-val'], self.mitiff_config[kwargs['sensor']][cn]['max-val']) - - tif.write_image(data.astype(np.uint8), compression='deflate') + Image.fromarray(data.astype(np.uint8)).save(tmp_gen_filename, compression='tiff_deflate', + compress_level=9, tiffinfo=tiffinfo) else: for _cn_i, _cn in enumerate(self.channel_order[kwargs['sensor']]): for band in datasets['bands']: @@ -727,5 +736,6 @@ def _save_single_dataset(self, datasets, cns, tif, kwargs): self.mitiff_config[kwargs['sensor']][cn]['min-val'], self.mitiff_config[kwargs['sensor']][cn]['max-val']) - tif.write_image(data.astype(np.uint8), compression='deflate') + Image.fromarray(data.astype(np.uint8)).save(tmp_gen_filename, compression='tiff_deflate', + compress_level=9, tiffinfo=tiffinfo) break