def setUp(self): """Create test data and mock pycoast/pydecorate.""" from trollimage.xrimage import XRImage from pyresample.geometry import AreaDefinition import xarray as xr import dask.array as da proj_dict = {'proj': 'lcc', 'datum': 'WGS84', 'ellps': 'WGS84', 'lon_0': -95., 'lat_0': 25, 'lat_1': 25, 'units': 'm', 'no_defs': True} self.area_def = AreaDefinition( 'test', 'test', 'test', proj_dict, 200, 400, (-1000., -1500., 1000., 1500.), ) self.orig_rgb_img = XRImage( xr.DataArray(da.arange(75., chunks=10).reshape(3, 5, 5) / 75., dims=('bands', 'y', 'x'), coords={'bands': ['R', 'G', 'B']}, attrs={'name': 'test_ds', 'area': self.area_def}) ) self.orig_l_img = XRImage( xr.DataArray(da.arange(25., chunks=10).reshape(5, 5) / 75., dims=('y', 'x'), attrs={'name': 'test_ds', 'area': self.area_def}) ) self.decorate = { 'decorate': [ {'logo': {'logo_path': '', 'height': 143, 'bg': 'white', 'bg_opacity': 255}}, {'text': { 'txt': 'TEST', 'align': {'top_bottom': 'bottom', 'left_right': 'right'}, 'font': '', 'font_size': 22, 'height': 30, 'bg': 'black', 'bg_opacity': 255, 'line': 'white'}}, {'scale': { 'colormap': greys, 'extend': False, 'width': 1670, 'height': 110, 'tick_marks': 5, 'minor_tick_marks': 1, 'cursor': [0, 0], 'bg':'white', 'title':'TEST TITLE OF SCALE', 'fontsize': 110, 'align': 'cc' }} ] } import_mock = mock.MagicMock() modules = {'pycoast': import_mock.pycoast, 'pydecorate': import_mock.pydecorate} self.module_patcher = mock.patch.dict('sys.modules', modules) self.module_patcher.start()
def test_colormap_write(self): """Test writing an image with a colormap.""" from satpy.writers.geotiff import GeoTIFFWriter from trollimage.xrimage import XRImage from trollimage.colormap import spectral datasets = self._get_test_datasets() w = GeoTIFFWriter(base_dir=self.base_dir) # we'd have to customize enhancements to test this through # save_datasets. We'll use `save_image` as a workaround. img = XRImage(datasets[0]) img.palettize(spectral) w.save_image(img, keep_palette=True)
def palettize(img, min_out, max_out, min_in=0, max_in=1.0, colormap=None, alpha=True, **kwargs): """Apply a colormap to data and return the indices in to that colormap.""" import xarray as xr import dask.array as da from trollimage.xrimage import XRImage import trollimage.colormap as ticolormap from satpy import CHUNK_SIZE good_data_mask = kwargs['good_data_mask'] if img.ndim > 2: raise ValueError("Not sure how to palettize more than 2 dimensions") if colormap is None: raise ValueError("'colormap' is required for 'palettize' rescaling") elif not isinstance(colormap, ticolormap.Colormap): raise ValueError("Unknown 'colormap' type: %s", str(type(colormap))) dims = ('y', 'x') if img.ndim == 2 else ('y',) attrs = kwargs.get('attrs', {}) xrimg = XRImage( xr.DataArray(da.from_array(img, chunks=CHUNK_SIZE), dims=dims, attrs=attrs)) if alpha: # use colormap as is tmp_cmap = colormap # produce LA image xrimg = xrimg.convert(xrimg.mode + 'A') else: # the colormap has a value at 0 (first position) that represents # invalid data. We should palettize based on the colormap without # the 0 and then increment tmp_cmap = ticolormap.Colormap(*zip(colormap.values[1:], colormap.colors[1:])) tmp_cmap.set_range(min_in, max_in) xrimg.palettize(tmp_cmap) img_data = xrimg.data.values if alpha: # multiply alpha by the output size img_data[1, :, :] *= max_out else: # get the single band (L) img_data = img_data[0] # increment the indexes by 1 because the colormap has a 0 fill value img_data += 1 img_data[~good_data_mask] = 0 # our data values are now integers that can't be scaled to the output type # because they need to match the colormap return img_data
def setUp(self): """Create test data and mock pycoast/pydecorate.""" from trollimage.xrimage import XRImage from pyresample.geometry import AreaDefinition import xarray as xr import dask.array as da proj_dict = {'proj': 'lcc', 'datum': 'WGS84', 'ellps': 'WGS84', 'lon_0': -95., 'lat_0': 25, 'lat_1': 25, 'units': 'm', 'no_defs': True} self.area_def = AreaDefinition( 'test', 'test', 'test', proj_dict, 200, 400, (-1000., -1500., 1000., 1500.), ) self.orig_rgb_img = XRImage( xr.DataArray(da.arange(75., chunks=10).reshape(3, 5, 5) / 75., dims=('bands', 'y', 'x'), coords={'bands': ['R', 'G', 'B']}, attrs={'name': 'test_ds', 'area': self.area_def}) ) self.orig_l_img = XRImage( xr.DataArray(da.arange(25., chunks=10).reshape(5, 5) / 75., dims=('y', 'x'), attrs={'name': 'test_ds', 'area': self.area_def}) ) self.decorate = { 'decorate': [ {'logo': {'logo_path': '', 'height': 143, 'bg': 'white', 'bg_opacity': 255}}, {'text': { 'txt': 'TEST', 'align': {'top_bottom': 'bottom', 'left_right': 'right'}, 'font': '', 'font_size': 22, 'height': 30, 'bg': 'black', 'bg_opacity': 255, 'line': 'white'}} ] } self.contour_writer = mock.patch('pycoast.ContourWriterAGG') self.dec_writer = mock.patch('pydecorate.DecoratorAGG') self.cw = self.contour_writer.start() self.dw = self.dec_writer.start()
def _pil_to_xrimage(img, adef, fill_value=None): """Convert PIL image to trollimage.xrimage.XRImage""" # Get image mode, width and height mode = img.mode width = img.width height = img.height # Convert to Numpy array img = np.array(img) # Get the minimum and maximum values of the input datatype min_val = np.iinfo(img.dtype).min max_val = np.iinfo(img.dtype).max img = img.reshape((height, width, len(mode))) # Reorder for XRImage img = np.rollaxis(img, 2, -3) # Remove alpha channel if fill_value is set if fill_value is not None: if 'A' in mode: mask = img[-1, :, :] == min_val # Remove alpha channel img = img[:-1, :, :] if np.any(mask): # Set fill_value for each channel for i in range(img.shape[0]): chan = img[i, :, :] chan[mask] = fill_value bands = BANDS[img.shape[0]] # Add image data to scene as xarray.DataArray img = xr.DataArray(img, dims=['bands', 'y', 'x']) img['bands'] = bands # Add area definition img.attrs['area'] = adef # Convert to XRImage img = XRImage(img) # Static stretch img.crude_stretch(min_val, max_val) return img
def to_image(dataset, copy=False, **kwargs): # Only add keywords if they are present for key in ["mode", "fill_value", "palette"]: if key in dataset.attrs: kwargs.setdefault(key, dataset.attrs[key]) dataset = dataset.squeeze() if dataset.ndim < 2: raise ValueError("Need at least a 2D array to make an image.") else: return XRImage(dataset)
def test_write_rgba(): """Test saving an RGBA image.""" area = STEREOGRAPHIC_AREA fill_value = np.nan arr = create_hsv_color_disk(fill_value) attrs = dict([('platform_name', 'NOAA-18'), ('resolution', 1050), ('polarization', None), ('start_time', TIME - datetime.timedelta(minutes=55)), ('end_time', TIME - datetime.timedelta(minutes=50)), ('level', None), ('sensor', 'avhrr-3'), ('ancillary_variables', []), ('area', area), ('wavelength', None), ('optional_datasets', []), ('standard_name', 'overview'), ('name', 'overview'), ('prerequisites', [0.6, 0.8, 10.8]), ('optional_prerequisites', []), ('calibration', None), ('modifiers', None), ('mode', 'RGBA'), ('enhancement_history', [{'scale': np.array([1, 1, -1]), 'offset': np.array([0, 0, 1])}, {'scale': np.array([0.0266347, 0.03559078, 0.01329783]), 'offset': np.array([-0.02524969, -0.01996642, 3.8918446])}, {'gamma': 1.6}])]) kwargs = {'compute': True, 'fill_value': None, 'sat_id': 6300014, 'chan_id': 6500015, 'data_cat': 'PPRN', 'data_source': 'SMHI', 'nbits': 8} alpha = np.where(np.isnan(arr[0, :, :]), 0, 1) arr = np.nan_to_num(arr) arr = np.vstack((arr, alpha[np.newaxis, :, :])) data = da.from_array(arr.clip(0, 1), chunks=1024) data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B', 'A']}, dims=[ 'bands', 'y', 'x'], attrs=attrs) img = XRImage(data) with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile: filename = tmpfile.name if not DELETE_FILES: print(filename) save(img, filename, data_is_scaled_01=True, **kwargs) tif = TiffFile(filename) res = tif[0].asarray() for idx in range(4): np.testing.assert_allclose(res[:, :, idx], np.round( np.nan_to_num(arr[idx, :, :]) * 255).astype(np.uint8)) np.testing.assert_allclose(res[:, :, 3] == 0, alpha == 0)
def _test_enhancement(self, func, data, expected, **kwargs): from trollimage.xrimage import XRImage pre_attrs = data.attrs img = XRImage(data) func(img, **kwargs) self.assertIsInstance(img.data.data, da.Array) self.assertListEqual(sorted(pre_attrs.keys()), sorted(img.data.attrs.keys()), "DataArray attributes were not preserved") np.testing.assert_allclose(img.data.values, expected, atol=1.e-6, rtol=0)
def test_cimss_true_color_contrast(self): """Test the cimss_true_color_contrast enhancement.""" from satpy.enhancements.abi import cimss_true_color_contrast from trollimage.xrimage import XRImage expected = np.array([[ [0., 0., 0.05261956, 0.13396146], [0.21530335, 0.29664525, 0.37798715, 0.45932905], [0.54067095, 0.62201285, 0.70335475, 0.78469665], [0.86603854, 0.94738044, 1., 1.], ]]) img = XRImage(self.da) cimss_true_color_contrast(img) np.testing.assert_almost_equal(img.data.compute(), expected)
def run_and_check_enhancement(func, data, expected, **kwargs): """Perform basic checks that apply to multiple tests.""" from trollimage.xrimage import XRImage pre_attrs = data.attrs img = XRImage(data) func(img, **kwargs) assert isinstance(img.data.data, da.Array) old_keys = set(pre_attrs.keys()) # It is OK to have "enhancement_history" added new_keys = set(img.data.attrs.keys()) - {"enhancement_history"} assert old_keys == new_keys np.testing.assert_allclose(img.data.values, expected, atol=1.e-6, rtol=0)
def test_jma_true_color_reproduction(self): """Test the jma_true_color_reproduction enhancement.""" from trollimage.xrimage import XRImage from satpy.enhancements.ahi import jma_true_color_reproduction expected = [[[-109.98, 10.998, 131.976, 252.954, 373.932], [494.91, 615.888, 736.866, 857.844, 978.822]], [[-97.6, 9.76, 117.12, 224.48, 331.84], [439.2, 546.56, 653.92, 761.28, 868.64]], [[-94.27, 9.427, 113.124, 216.821, 320.518], [424.215, 527.912, 631.609, 735.306, 839.003]]] img = XRImage(self.rgb) jma_true_color_reproduction(img) np.testing.assert_almost_equal(img.data.compute(), expected)
def _test_enhancement(self, func, data, expected, **kwargs): """Perform basic checks that apply to multiple tests.""" from trollimage.xrimage import XRImage pre_attrs = data.attrs img = XRImage(data) func(img, **kwargs) assert isinstance(img.data.data, da.Array) assert sorted(pre_attrs.keys()) == sorted( img.data.attrs.keys()), "DataArray attributes were not preserved" np.testing.assert_allclose(img.data.values, expected, atol=1.e-6, rtol=0)
def test_write_rgb_classified(): """Test saving a transparent RGB.""" area = STEREOGRAPHIC_AREA x_size, y_size = 1024, 1024 arr = np.zeros((3, y_size, x_size)) attrs = dict([('platform_name', 'NOAA-18'), ('resolution', 1050), ('polarization', None), ('start_time', TIME - datetime.timedelta(minutes=65)), ('end_time', TIME - datetime.timedelta(minutes=60)), ('level', None), ('sensor', 'avhrr-3'), ('ancillary_variables', []), ('area', area), ('wavelength', None), ('optional_datasets', []), ('standard_name', 'overview'), ('name', 'overview'), ('prerequisites', [0.6, 0.8, 10.8]), ('optional_prerequisites', []), ('calibration', None), ('modifiers', None), ('mode', 'P')]) kwargs = {'compute': True, 'fill_value': None, 'sat_id': 6300014, 'chan_id': 1700015, 'data_cat': 'PPRN', 'data_source': 'SMHI', 'nbits': 8} data1 = da.tile(da.repeat(da.arange(4, chunks=1024), 256), 256).reshape((1, 256, 1024)) datanan = da.ones((1, 256, 1024), chunks=1024) * 4 data2 = da.tile(da.repeat(da.arange(4, chunks=1024), 256), 512).reshape((1, 512, 1024)) data = da.concatenate((data1, datanan, data2), axis=1) data = xr.DataArray(data, coords={'bands': ['P']}, dims=['bands', 'y', 'x'], attrs=attrs) img = XRImage(data) with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile: filename = tmpfile.name if not DELETE_FILES: print(filename) save(img, filename, data_is_scaled_01=True, **kwargs) tif = TiffFile(filename) res = tif[0].asarray() for idx in range(3): np.testing.assert_allclose(res[:, :, idx], np.round( np.nan_to_num(arr[idx, :, :]) * 255).astype(np.uint8)) np.testing.assert_allclose(res[:, :, 3] == 0, np.isnan(arr[0, :, :]))
def to_image(dataset): """Convert ``dataset`` into a :class:`~trollimage.xrimage.XRImage` instance. Convert the ``dataset`` into an instance of the :class:`~trollimage.xrimage.XRImage` class. This function makes no other changes. To get an enhanced image, possibly with overlays and decoration, see :func:`~get_enhanced_image`. Args: dataset (xarray.DataArray): Data to be converted to an image. Returns: Instance of :class:`~trollimage.xrimage.XRImage`. """ dataset = dataset.squeeze() if dataset.ndim < 2: raise ValueError("Need at least a 2D array to make an image.") return XRImage(dataset)
def add_scale(orig, dc, img, scale): """Add scale to an image using the pydecorate package. All the features of pydecorate's ``add_scale`` are available. See documentation of :doc:`pydecorate:index` for more info. """ LOG.info("Add scale to image.") dc.add_scale(**scale) arr = da.from_array(np.array(img) / 255.0, chunks=CHUNK_SIZE) new_data = xr.DataArray(arr, dims=['y', 'x', 'bands'], coords={'y': orig.data.coords['y'], 'x': orig.data.coords['x'], 'bands': list(img.mode)}, attrs=orig.data.attrs) return XRImage(new_data)
def _fake_get_enhanced_image(img, enhance=None, overlay=None, decorate=None): from trollimage.xrimage import XRImage return XRImage(img)
def save_image(self, img: XRImage, filename: Optional[str] = None, compute: bool = True, dtype: Optional[DTypeLike] = None, fill_value: Optional[Union[int, float]] = None, keep_palette: bool = False, cmap: Optional[Colormap] = None, tags: Optional[dict[str, Any]] = None, overviews: Optional[list[int]] = None, overviews_minsize: int = 256, overviews_resampling: Optional[str] = None, include_scale_offset: bool = False, scale_offset_tags: Optional[tuple[str, str]] = None, colormap_tag: Optional[str] = None, driver: Optional[str] = None, tiled: bool = True, **kwargs): """Save the image to the given ``filename`` in geotiff_ format. Note for faster output and reduced memory usage the ``rasterio`` library must be installed. This writer currently falls back to using ``gdal`` directly, but that will be deprecated in the future. Args: img (xarray.DataArray): Data to save to geotiff. filename (str): Filename to save the image to. Defaults to ``filename`` passed during writer creation. Unlike the creation ``filename`` keyword argument, this filename does not get formatted with data attributes. compute (bool): Compute dask arrays and save the image immediately. If ``False`` then the return value can be passed to :func:`~satpy.writers.compute_writer_results` to do the computation. This is useful when multiple images may share input calculations where dask can benefit from not repeating them multiple times. Defaults to ``True`` in the writer by itself, but is typically passed as ``False`` by callers where calculations can be combined. dtype (DTypeLike): Numpy data type to save the image as. Defaults to 8-bit unsigned integer (``np.uint8``) or the data type of the data to be saved if ``enhance=False``. If the ``dtype`` argument is provided during writer creation then that will be used as the default. fill_value (float or int): Value to use where data values are NaN/null. If this is specified in the writer configuration file that value will be used as the default. keep_palette (bool): Save palette/color table to geotiff. To be used with images that were palettized with the "palettize" enhancement. Setting this to ``True`` will cause the colormap of the image to be written as a "color table" in the output geotiff and the image data values will represent the index values in to that color table. By default, this will use the colormap used in the "palettize" operation. See the ``cmap`` option for other options. This option defaults to ``False`` and palettized images will be converted to RGB/A. cmap (trollimage.colormap.Colormap or None): Colormap to save as a color table in the output geotiff. See ``keep_palette`` for more information. Defaults to the palette of the provided ``img`` object. The colormap's range should be set to match the index range of the palette (ex. `cmap.set_range(0, len(colors))`). tags (dict): Extra metadata to store in geotiff. overviews (list): The reduction factors of the overviews to include in the image, eg:: scn.save_datasets(overviews=[2, 4, 8, 16]) If provided as an empty list, then levels will be computed as powers of two until the last level has less pixels than `overviews_minsize`. Default is to not add overviews. overviews_minsize (int): Minimum number of pixels for the smallest overview size generated when `overviews` is auto-generated. Defaults to 256. overviews_resampling (str): Resampling method to use when generating overviews. This must be the name of an enum value from :class:`rasterio.enums.Resampling` and only takes effect if the `overviews` keyword argument is provided. Common values include `nearest` (default), `bilinear`, `average`, and many others. See the rasterio documentation for more information. scale_offset_tags (Tuple[str, str]): If set, include inclusion of scale and offset in the GeoTIFF headers in the GDALMetaData tag. The value of this argument should be a keyword argument ``(scale_label, offset_label)``, for example, ``("scale", "offset")``, indicating the labels to be used. colormap_tag (Optional[str]): If set and the image being saved was colorized or palettized then a comma-separated version of the colormap is saved to a custom geotiff tag with the provided name. See :meth:`trollimage.colormap.Colormap.to_csv` for more information. driver (Optional[str]): Name of GDAL driver to use to save the geotiff. If not specified or None (default) the "GTiff" driver is used. Another common option is "COG" for Cloud Optimized GeoTIFF. See GDAL documentation for more information. tiled (bool): For performance this defaults to ``True``. Pass ``False`` to created striped TIFF files. include_scale_offset (deprecated, bool): Deprecated. Use ``scale_offset_tags=("scale", "offset")`` to include scale and offset tags. .. _geotiff: http://trac.osgeo.org/geotiff/ """ filename = filename or self.get_filename(**img.data.attrs) gdal_options = self._get_gdal_options(kwargs) if fill_value is None: # fall back to fill_value from configuration file fill_value = self.info.get('fill_value') dtype = dtype if dtype is not None else self.dtype if dtype is None and self.enhancer is not False: dtype = np.uint8 elif dtype is None: dtype = img.data.dtype.type if "alpha" in kwargs: raise ValueError( "Keyword 'alpha' is automatically set based on 'fill_value' " "and should not be specified") if np.issubdtype(dtype, np.floating): if img.mode != "L": raise ValueError("Image must be in 'L' mode for floating " "point geotiff saving") if fill_value is None: LOG.debug("Alpha band not supported for float geotiffs, " "setting fill value to 'NaN'") fill_value = np.nan if keep_palette and cmap is None and img.palette is not None: from satpy.enhancements import create_colormap cmap = create_colormap({'colors': img.palette}) cmap.set_range(0, len(img.palette) - 1) if tags is None: tags = {} tags.update(self.tags) return img.save(filename, fformat='tif', driver=driver, fill_value=fill_value, dtype=dtype, compute=compute, keep_palette=keep_palette, cmap=cmap, tags=tags, include_scale_offset_tags=include_scale_offset, scale_offset_tags=scale_offset_tags, colormap_tag=colormap_tag, overviews=overviews, overviews_resampling=overviews_resampling, overviews_minsize=overviews_minsize, tiled=tiled, **gdal_options)
def add_overlay(orig, area, coast_dir, color=(0, 0, 0), width=0.5, resolution=None, level_coast=1, level_borders=1, fill_value=None): """Add coastline and political borders to image. Uses ``color`` for feature colors where ``color`` is a 3-element tuple of integers between 0 and 255 representing (R, G, B). .. warning:: This function currently loses the data mask (alpha band). ``resolution`` is chosen automatically if None (default), otherwise it should be one of: +-----+-------------------------+---------+ | 'f' | Full resolution | 0.04 km | | 'h' | High resolution | 0.2 km | | 'i' | Intermediate resolution | 1.0 km | | 'l' | Low resolution | 5.0 km | | 'c' | Crude resolution | 25 km | +-----+-------------------------+---------+ """ if area is None: raise ValueError("Area of image is None, can't add overlay.") from pycoast import ContourWriterAGG if isinstance(area, str): area = get_area_def(area) LOG.info("Add coastlines and political borders to image.") if resolution is None: x_resolution = ((area.area_extent[2] - area.area_extent[0]) / area.x_size) y_resolution = ((area.area_extent[3] - area.area_extent[1]) / area.y_size) res = min(x_resolution, y_resolution) if res > 25000: resolution = "c" elif res > 5000: resolution = "l" elif res > 1000: resolution = "i" elif res > 200: resolution = "h" else: resolution = "f" LOG.debug("Automagically choose resolution %s", resolution) if hasattr(orig, 'convert'): # image must be in RGB space to work with pycoast/pydecorate orig = orig.convert('RGBA' if orig.mode.endswith('A') else 'RGB') elif not orig.mode.startswith('RGB'): raise RuntimeError("'trollimage' 1.6+ required to support adding " "overlays/decorations to non-RGB data.") img = orig.pil_image(fill_value=fill_value) cw_ = ContourWriterAGG(coast_dir) cw_.add_coastlines(img, area, outline=color, resolution=resolution, width=width, level=level_coast) cw_.add_borders(img, area, outline=color, resolution=resolution, width=width, level=level_borders) arr = da.from_array(np.array(img) / 255.0, chunks=CHUNK_SIZE) new_data = xr.DataArray(arr, dims=['y', 'x', 'bands'], coords={'y': orig.data.coords['y'], 'x': orig.data.coords['x'], 'bands': list(img.mode)}, attrs=orig.data.attrs) return XRImage(new_data)
def _fake_get_enhanced_image(img): from trollimage.xrimage import XRImage return XRImage(img)
def add_overlay(orig, area, coast_dir, color=(0, 0, 0), width=0.5, resolution=None, level_coast=1, level_borders=1, fill_value=None, grid=None): """Add coastline, political borders and grid(graticules) to image. Uses ``color`` for feature colors where ``color`` is a 3-element tuple of integers between 0 and 255 representing (R, G, B). .. warning:: This function currently loses the data mask (alpha band). ``resolution`` is chosen automatically if None (default), otherwise it should be one of: +-----+-------------------------+---------+ | 'f' | Full resolution | 0.04 km | +-----+-------------------------+---------+ | 'h' | High resolution | 0.2 km | +-----+-------------------------+---------+ | 'i' | Intermediate resolution | 1.0 km | +-----+-------------------------+---------+ | 'l' | Low resolution | 5.0 km | +-----+-------------------------+---------+ | 'c' | Crude resolution | 25 km | +-----+-------------------------+---------+ ``grid`` is a dictionary with key values as documented in detail in pycoast eg. overlay={'grid': {'major_lonlat': (10, 10), 'write_text': False, 'outline': (224, 224, 224), 'width': 0.5}} Here major_lonlat is plotted every 10 deg for both longitude and latitude, no labels for the grid lines are plotted, the color used for the grid lines is light gray, and the width of the gratucules is 0.5 pixels. For grid if aggdraw is used, font option is mandatory, if not ``write_text`` is set to False:: font = aggdraw.Font('black', '/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', opacity=127, size=16) """ if area is None: raise ValueError("Area of image is None, can't add overlay.") from pycoast import ContourWriterAGG if isinstance(area, str): area = get_area_def(area) LOG.info("Add coastlines and political borders to image.") if resolution is None: x_resolution = ((area.area_extent[2] - area.area_extent[0]) / area.x_size) y_resolution = ((area.area_extent[3] - area.area_extent[1]) / area.y_size) res = min(x_resolution, y_resolution) if res > 25000: resolution = "c" elif res > 5000: resolution = "l" elif res > 1000: resolution = "i" elif res > 200: resolution = "h" else: resolution = "f" LOG.debug("Automagically choose resolution %s", resolution) if hasattr(orig, 'convert'): # image must be in RGB space to work with pycoast/pydecorate orig = orig.convert('RGBA' if orig.mode.endswith('A') else 'RGB') elif not orig.mode.startswith('RGB'): raise RuntimeError("'trollimage' 1.6+ required to support adding " "overlays/decorations to non-RGB data.") img = orig.pil_image(fill_value=fill_value) cw_ = ContourWriterAGG(coast_dir) cw_.add_coastlines(img, area, outline=color, resolution=resolution, width=width, level=level_coast) cw_.add_borders(img, area, outline=color, resolution=resolution, width=width, level=level_borders) # Only add grid if major_lonlat is given. if grid and 'major_lonlat' in grid and grid['major_lonlat']: major_lonlat = grid.pop('major_lonlat') minor_lonlat = grid.pop('minor_lonlat', major_lonlat) cw_.add_grid(img, area, major_lonlat, minor_lonlat, **grid) arr = da.from_array(np.array(img) / 255.0, chunks=CHUNK_SIZE) new_data = xr.DataArray(arr, dims=['y', 'x', 'bands'], coords={ 'y': orig.data.coords['y'], 'x': orig.data.coords['x'], 'bands': list(img.mode) }, attrs=orig.data.attrs) return XRImage(new_data)
def to_image(dataset): dataset = dataset.squeeze() if dataset.ndim < 2: raise ValueError("Need at least a 2D array to make an image.") else: return XRImage(dataset)
def test_write_rgb_with_a(): """Test saving a transparent RGB.""" from pyninjotiff.ninjotiff import save from pyninjotiff.tifffile import TiffFile area = FakeArea( { 'ellps': 'WGS84', 'lat_0': '90.0', 'lat_ts': '60.0', 'lon_0': '0.0', 'proj': 'stere' }, (-1000000.0, -4500000.0, 2072000.0, -1428000.0), 1024, 1024) x_size, y_size = 1024, 1024 arr = np.zeros((3, y_size, x_size)) radius = min(x_size, y_size) / 2.0 centre = x_size / 2, y_size / 2 for x in range(x_size): for y in range(y_size): rx = x - centre[0] ry = y - centre[1] s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius if s <= 1.0: h = ((np.arctan2(ry, rx) / np.pi) + 1.0) / 2.0 rgb = colorsys.hsv_to_rgb(h, s, 1.0) arr[:, y, x] = np.array(rgb) else: arr[:, y, x] = np.nan attrs = dict([('platform_name', 'NOAA-18'), ('resolution', 1050), ('polarization', None), ('start_time', TIME - datetime.timedelta(minutes=55)), ('end_time', TIME - datetime.timedelta(minutes=50)), ('level', None), ('sensor', 'avhrr-3'), ('ancillary_variables', []), ('area', area), ('wavelength', None), ('optional_datasets', []), ('standard_name', 'overview'), ('name', 'overview'), ('prerequisites', [0.6, 0.8, 10.8]), ('optional_prerequisites', []), ('calibration', None), ('modifiers', None), ('mode', 'RGB'), ('enhancement_history', [{ 'scale': np.array([1, 1, -1]), 'offset': np.array([0, 0, 1]) }, { 'scale': np.array([0.0266347, 0.03559078, 0.01329783]), 'offset': np.array([-0.02524969, -0.01996642, 3.8918446]) }, { 'gamma': 1.6 }])]) kwargs = { 'compute': True, 'fill_value': None, 'sat_id': 6300014, 'chan_id': 6500015, 'data_cat': 'PPRN', 'data_source': 'SMHI', 'nbits': 8 } data = da.from_array(arr.clip(0, 1), chunks=1024) data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B']}, dims=['bands', 'y', 'x'], attrs=attrs) from trollimage.xrimage import XRImage img = XRImage(data) with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile: filename = tmpfile.name if not DELETE_FILES: print(filename) save(img, filename, data_is_scaled_01=True, **kwargs) tif = TiffFile(filename) res = tif[0].asarray() for idx in range(3): np.testing.assert_allclose( res[:, :, idx], np.round(np.nan_to_num(arr[idx, :, :]) * 255).astype(np.uint8)) np.testing.assert_allclose(res[:, :, 3] == 0, np.isnan(arr[0, :, :]))
def test_write_rgb_tb(): """Test saving a non-trasparent RGB with thumbnails.""" from pyninjotiff.ninjotiff import save from pyninjotiff.tifffile import TiffFile area = FakeArea( { 'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere' }, (-1000000.0, -4500000.0, 2072000.0, -1428000.0), 1024, 1024) x_size, y_size = 1024, 1024 arr = np.zeros((3, y_size, x_size)) radius = min(x_size, y_size) / 2.0 centre = x_size / 2, y_size / 2 for x in range(x_size): for y in range(y_size): rx = x - centre[0] ry = y - centre[1] s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius if s <= 1.0: h = ((np.arctan2(ry, rx) / np.pi) + 1.0) / 2.0 rgb = colorsys.hsv_to_rgb(h, s, 1.0) arr[:, y, x] = np.array(rgb) attrs = dict([('platform_name', 'NOAA-18'), ('resolution', 1050), ('polarization', None), ('level', None), ('sensor', 'avhrr-3'), ('ancillary_variables', []), ('area', area), ('start_time', TIME - datetime.timedelta(minutes=45)), ('end_time', TIME - datetime.timedelta(minutes=40)), ('wavelength', None), ('optional_datasets', []), ('standard_name', 'overview'), ('name', 'overview'), ('prerequisites', [0.6, 0.8, 10.8]), ('optional_prerequisites', []), ('calibration', None), ('modifiers', None), ('mode', 'RGB'), ('enhancement_history', [{ 'scale': np.array([1, 1, -1]), 'offset': np.array([0, 0, 1]) }, { 'scale': np.array([0.0266347, 0.03559078, 0.01329783]), 'offset': np.array([-0.02524969, -0.01996642, 3.8918446]) }, { 'gamma': 1.6 }])]) kwargs = { 'compute': True, 'fill_value': None, 'sat_id': 6300014, 'chan_id': 6500015, 'data_cat': 'PPRN', 'data_source': 'SMHI', 'nbits': 8, 'tile_length': 256, 'tile_width': 256 } data = da.from_array(arr.clip(0, 1), chunks=1024) data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B']}, dims=['bands', 'y', 'x'], attrs=attrs) from trollimage.xrimage import XRImage img = XRImage(data) with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile: filename = tmpfile.name if not DELETE_FILES: print(filename) save(img, filename, data_is_scaled_01=False, **kwargs) tif = TiffFile(filename) res = tif[0].asarray() assert (tif.pages[0].tags['tile_length'].value == 256) assert (tif.pages[1].tags['tile_length'].value == 128) assert (tif.pages[0].tags['tile_width'].value == 256) assert (tif.pages[1].tags['tile_width'].value == 128) assert (len(tif.pages) == 2) assert (tif.pages[0].shape == (1024, 1024, 4)) assert (tif.pages[1].shape == (512, 512, 4)) for idx in range(3): np.testing.assert_allclose( res[:, :, idx], np.round(arr[idx, :, :] * 255).astype(np.uint8)) tags = { 'new_subfile_type': 0, 'image_width': 1024, 'image_length': 1024, 'bits_per_sample': (8, 8, 8, 8), 'compression': 32946, 'photometric': 2, 'orientation': 1, 'samples_per_pixel': 4, 'planar_configuration': 1, 'software': b'tifffile/pytroll', 'datetime': b'2020:01:17 14:17:23', 'tile_width': 256, 'tile_length': 256, 'tile_offsets': (951, 24414, 77352, 126135, 141546, 206260, 272951, 318709, 349650, 413166, 475735, 519168, 547960, 570326, 615924, 666705), 'tile_byte_counts': (23463, 52938, 48783, 15411, 64714, 66691, 45758, 30941, 63516, 62569, 43433, 28792, 22366, 45598, 50781, 13371), 'extra_samples': 2, 'sample_format': (1, 1, 1, 1), 'model_pixel_scale': (0.026949458523585643, 0.027040118922685666, 0.0), 'model_tie_point': (0.0, 0.0, 0.0, -35.00279008179894, 73.3850622630575, 0.0), '40000': b'NINJO', '40001': 6300014, '40002': 1579264321, '40003': 1579267043, '40004': 6500015, '40005': 2, '40006': b'/tmp/tmpb4kn93qt', '40007': b'PPRN', '40008': b'', '40009': 24, '40010': b'SMHI', '40011': 1, '40012': 1024, '40013': 1, '40014': 1024, '40015': b'NPOL', '40016': -35.00278854370117, '40017': 24.72344398498535, '40018': 6378137.0, '40019': 6356752.5, '40021': 60.0, '40022': 0.0, '40023': 0.0, '40024': b'None', '40025': b'None', '40026': 0, '40027': 255, '40028': 1.0, '40029': 0.0, '40040': 0, '40041': 0, '40042': 1, '40043': 0, '50000': 0, 'fill_order': 1, 'rows_per_strip': 4294967295, 'resolution_unit': 2, 'predictor': 1, 'ycbcr_subsampling': 1, 'ycbcr_positioning': 1 } read_tags = tif.pages[0].tags assert (read_tags.keys() == tags.keys()) for key, val in tags.items(): if key in ['datetime', '40002', '40003', '40006']: continue assert (val == read_tags[key].value)