1111from affine import dumpsw
1212from harmony_service_lib .message import Message as HarmonyMessage
1313from harmony_service_lib .message import Source as HarmonySource
14- from matplotlib .cm import ScalarMappable
1514from matplotlib .colors import Normalize
1615from numpy import ndarray , uint8
1716from osgeo_utils .auxiliary .color_palette import ColorPalette
1817from PIL import Image
1918from rasterio .io import DatasetReader
20- from rasterio .plot import reshape_as_image , reshape_as_raster
2119from rasterio .warp import Resampling , reproject
2220from rioxarray import open_rasterio
2321from xarray import DataArray
2826 NODATA_RGBA ,
2927 OPAQUE ,
3028 TRANSPARENT ,
29+ ColorMap ,
3130 all_black_color_map ,
31+ colormap_from_colors ,
3232 get_color_palette ,
33+ greyscale_colormap ,
3334 palette_from_remote_colortable ,
34- remove_alpha ,
3535)
3636from hybig .exceptions import HyBIGError
3737from hybig .sizes import (
@@ -171,18 +171,19 @@ def create_browse_imagery(
171171 color_palette = get_color_palette (
172172 in_dataset , source , item_color_palette
173173 )
174- raster = convert_singleband_to_raster (rio_in_array , color_palette )
174+ raster , color_map = convert_singleband_to_raster (
175+ rio_in_array , color_palette
176+ )
175177 elif rio_in_array .rio .count in (3 , 4 ):
176178 raster = convert_mulitband_to_raster (rio_in_array )
179+ color_map = None
180+ if output_driver == 'JPEG' :
181+ raster = raster [0 :3 , :, :]
177182 else :
178183 raise HyBIGError (
179184 f'incorrect number of bands for image: { rio_in_array .rio .count } '
180185 )
181186
182- raster , color_map = standardize_raster_for_writing (
183- raster , output_driver , rio_in_array .rio .count
184- )
185-
186187 grid_parameters = get_target_grid_parameters (message , rio_in_array )
187188 grid_parameter_list , tile_locators = create_tiled_output_parameters (
188189 grid_parameters
@@ -283,69 +284,65 @@ def original_dtype(data_array: DataArray) -> str | None:
283284def convert_singleband_to_raster (
284285 data_array : DataArray ,
285286 color_palette : ColorPalette | None = None ,
286- ) -> ndarray :
287- """Convert input dataset to a 4 band raster image.
287+ ) -> tuple [ ndarray , ColorMap ] :
288+ """Convert input dataset to a 1- band palettized image with colormap .
288289
289- Use a palette if provided otherwise return a greyscale image.
290+ Uses a palette if provided otherwise returns a greyscale image.
290291 """
291292 if color_palette is None :
292- return convert_gray_1band_to_raster (data_array )
293- return convert_paletted_1band_to_raster (data_array , color_palette )
293+ return scale_grey_1band (data_array )
294+ return scale_paletted_1band (data_array , color_palette )
294295
295296
296- def convert_gray_1band_to_raster (data_array : DataArray ) -> ndarray :
297- """Convert a 1-band raster without a color association ."""
297+ def scale_grey_1band (data_array : DataArray ) -> tuple [ ndarray , ColorMap ] :
298+ """Normalize input array and return scaled data with greyscale ColorMap ."""
298299 band = data_array [0 , :, :]
299- cmap = matplotlib .colormaps ['Greys_r' ]
300- cmap .set_bad (NODATA_RGBA )
301300 norm = Normalize (vmin = np .nanmin (band ), vmax = np .nanmax (band ))
302- scalar_map = ScalarMappable (cmap = cmap , norm = norm )
303301
304- rgba_image = np .zeros ((* band .shape , 4 ), dtype = 'uint8' )
305- for row_no in range (band .shape [0 ]):
306- rgba_image_slice = scalar_map .to_rgba (band [row_no , :], bytes = True )
307- rgba_image [row_no , :, :] = rgba_image_slice
302+ # Scale input data from 0 to 254
303+ normalized_data = norm (band ) * 254.0
308304
309- return reshape_as_raster (rgba_image )
305+ # Set any missing to missing
306+ normalized_data [np .isnan (normalized_data )] = NODATA_IDX
310307
308+ grey_colormap = greyscale_colormap ()
309+ raster_data = np .expand_dims (np .round (normalized_data ).data , 0 )
310+ return np .array (raster_data , dtype = 'uint8' ), grey_colormap
311311
312- def convert_paletted_1band_to_raster (
312+
313+ def scale_paletted_1band (
313314 data_array : DataArray , palette : ColorPalette
314- ) -> ndarray :
315- """Convert a 1 band image with palette into a rgba raster image."""
315+ ) -> tuple [ndarray , ColorMap ]:
316+ """Scale a 1-band image with palette into modified image and associated color_map.
317+
318+ Use the palette's levels and values, transform the input data_array into
319+ the correct levels indexed from 0-255 return the scaled array along side of
320+ a colormap corresponding to the new levels.
321+ """
316322 band = data_array [0 , :, :]
317323 levels = list (palette .pal .keys ())
318324 colors = [
319325 palette .color_to_color_entry (value , with_alpha = True )
320326 for value in palette .pal .values ()
321327 ]
322- scaled_colors = [
323- (r / 255.0 , g / 255.0 , b / 255.0 , a / 255.0 ) for r , g , b , a in colors
324- ]
325-
326- cmap , norm = matplotlib .colors .from_levels_and_colors (
327- levels , scaled_colors , extend = 'max'
328- )
328+ norm = matplotlib .colors .BoundaryNorm (levels , len (levels ) - 1 )
329329
330330 # handle palette no data value
331+ nodata_color = (0 , 0 , 0 , 0 )
331332 if palette .ndv is not None :
332- nodata_colors = palette .color_to_color_entry (palette .ndv , with_alpha = True )
333- cmap .set_bad (
334- (
335- nodata_colors [0 ] / 255.0 ,
336- nodata_colors [1 ] / 255.0 ,
337- nodata_colors [2 ] / 255.0 ,
338- nodata_colors [3 ] / 255.0 ,
339- )
340- )
333+ nodata_color = palette .color_to_color_entry (palette .ndv , with_alpha = True )
341334
342- scalar_map = matplotlib .cm .ScalarMappable (norm = norm , cmap = cmap )
343- rgba_image = np .zeros ((* band .shape , 4 ), dtype = 'uint8' )
344- for row_no in range (band .shape [0 ]):
345- rgba_image [row_no , :, :] = scalar_map .to_rgba (
346- np .ma .masked_invalid (band [row_no , :]), bytes = True
347- )
348- return reshape_as_raster (rgba_image )
335+ colors = [* colors , nodata_color ]
336+
337+ scaled_band = norm (band )
338+
339+ # Set underflow and nan values to nodata
340+ scaled_band [scaled_band == - 1 ] = len (colors ) - 1
341+ scaled_band [np .isnan (band )] = len (colors ) - 1
342+
343+ color_map = colormap_from_colors (colors )
344+ raster_data = np .expand_dims (scaled_band .data , 0 )
345+ return np .array (raster_data , dtype = 'uint8' ), color_map
349346
350347
351348def image_driver (mime : str ) -> str :
@@ -355,81 +352,6 @@ def image_driver(mime: str) -> str:
355352 return 'PNG'
356353
357354
358- def standardize_raster_for_writing (
359- raster : ndarray ,
360- driver : str ,
361- band_count : int ,
362- ) -> tuple [ndarray , dict | None ]:
363- """Standardize raster data for writing to browse image.
364-
365- Args:
366- raster: Input raster data array
367- driver: Output image format ('JPEG' or 'PNG')
368- band_count: Number of bands in original input data
369-
370- The function handles two special cases:
371- - JPEG output with 4-band data -> Drop alpha channel and return 3-band RGB
372- - PNG output with single-band data -> Convert to paletted format
373-
374- Returns:
375- tuple: (prepared_raster, color_map) where:
376- - prepared_raster is the processed ndarray
377- - color_map is either None or a dict mapping palette indices to RGBA values
378-
379-
380- """
381- if driver == 'JPEG' and raster .shape [0 ] == 4 :
382- return raster [0 :3 , :, :], None
383-
384- if driver == 'PNG' and band_count == 1 :
385- # Only palettize single band input data that has been converted to an
386- # RGBA raster.
387- return palettize_raster (raster )
388-
389- return raster , None
390-
391-
392- def palettize_raster (raster : ndarray ) -> tuple [ndarray , dict ]:
393- """Convert an RGB or RGBA image into a 1band image and palette.
394-
395- Converts a 3 or 4 band np raster into a PIL image.
396- Quantizes the image into a 1band raster with palette
397-
398- Transparency is handled by first removing the Alpha layer and creating
399- quantized raster from just the RGB layers. Next the Alpha layer values are
400- treated as either transparent or opaque and any transparent values are
401- written to the final raster as 254 and add the mapped RGBA value to the
402- color palette.
403- """
404- # reserves index 255 for transparent and off grid fill values
405- # 0 to 254
406- max_colors = 255
407- rgb_raster , alpha = remove_alpha (raster )
408-
409- multiband_image = Image .fromarray (reshape_as_image (rgb_raster ))
410- quantized_image = multiband_image .quantize (colors = max_colors )
411-
412- color_map = get_color_map_from_image (quantized_image )
413-
414- quantized_array , color_map = add_alpha (alpha , np .array (quantized_image ), color_map )
415-
416- one_band_raster = np .expand_dims (quantized_array , 0 )
417- return one_band_raster , color_map
418-
419-
420- def add_alpha (
421- alpha : ndarray | None , quantized_array : ndarray , color_map : dict
422- ) -> tuple [ndarray , dict ]:
423- """If the input data had alpha values, manually set the quantized_image
424- index to the transparent index in those places.
425- """
426- if alpha is not None and np .any (alpha != OPAQUE ):
427- # Set any alpha to the transparent index value
428- quantized_array = np .where (alpha != OPAQUE , NODATA_IDX , quantized_array )
429- color_map [NODATA_IDX ] = NODATA_RGBA
430- return quantized_array , color_map
431-
432-
433355def get_color_map_from_image (image : Image ) -> dict :
434356 """Get a writable color map
435357
0 commit comments