11
11
from affine import dumpsw
12
12
from harmony_service_lib .message import Message as HarmonyMessage
13
13
from harmony_service_lib .message import Source as HarmonySource
14
- from matplotlib .cm import ScalarMappable
15
14
from matplotlib .colors import Normalize
16
15
from numpy import ndarray , uint8
17
16
from osgeo_utils .auxiliary .color_palette import ColorPalette
18
17
from PIL import Image
19
18
from rasterio .io import DatasetReader
20
- from rasterio .plot import reshape_as_image , reshape_as_raster
21
19
from rasterio .warp import Resampling , reproject
22
20
from rioxarray import open_rasterio
23
21
from xarray import DataArray
28
26
NODATA_RGBA ,
29
27
OPAQUE ,
30
28
TRANSPARENT ,
29
+ ColorMap ,
31
30
all_black_color_map ,
31
+ colormap_from_colors ,
32
32
get_color_palette ,
33
+ greyscale_colormap ,
33
34
palette_from_remote_colortable ,
34
- remove_alpha ,
35
35
)
36
36
from hybig .exceptions import HyBIGError
37
37
from hybig .sizes import (
@@ -171,18 +171,19 @@ def create_browse_imagery(
171
171
color_palette = get_color_palette (
172
172
in_dataset , source , item_color_palette
173
173
)
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
+ )
175
177
elif rio_in_array .rio .count in (3 , 4 ):
176
178
raster = convert_mulitband_to_raster (rio_in_array )
179
+ color_map = None
180
+ if output_driver == 'JPEG' :
181
+ raster = raster [0 :3 , :, :]
177
182
else :
178
183
raise HyBIGError (
179
184
f'incorrect number of bands for image: { rio_in_array .rio .count } '
180
185
)
181
186
182
- raster , color_map = standardize_raster_for_writing (
183
- raster , output_driver , rio_in_array .rio .count
184
- )
185
-
186
187
grid_parameters = get_target_grid_parameters (message , rio_in_array )
187
188
grid_parameter_list , tile_locators = create_tiled_output_parameters (
188
189
grid_parameters
@@ -283,69 +284,65 @@ def original_dtype(data_array: DataArray) -> str | None:
283
284
def convert_singleband_to_raster (
284
285
data_array : DataArray ,
285
286
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 .
288
289
289
- Use a palette if provided otherwise return a greyscale image.
290
+ Uses a palette if provided otherwise returns a greyscale image.
290
291
"""
291
292
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 )
294
295
295
296
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 ."""
298
299
band = data_array [0 , :, :]
299
- cmap = matplotlib .colormaps ['Greys_r' ]
300
- cmap .set_bad (NODATA_RGBA )
301
300
norm = Normalize (vmin = np .nanmin (band ), vmax = np .nanmax (band ))
302
- scalar_map = ScalarMappable (cmap = cmap , norm = norm )
303
301
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
308
304
309
- return reshape_as_raster (rgba_image )
305
+ # Set any missing to missing
306
+ normalized_data [np .isnan (normalized_data )] = NODATA_IDX
310
307
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
311
311
312
- def convert_paletted_1band_to_raster (
312
+
313
+ def scale_paletted_1band (
313
314
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
+ """
316
322
band = data_array [0 , :, :]
317
323
levels = list (palette .pal .keys ())
318
324
colors = [
319
325
palette .color_to_color_entry (value , with_alpha = True )
320
326
for value in palette .pal .values ()
321
327
]
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 )
329
329
330
330
# handle palette no data value
331
+ nodata_color = (0 , 0 , 0 , 0 )
331
332
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 )
341
334
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
349
346
350
347
351
348
def image_driver (mime : str ) -> str :
@@ -355,81 +352,6 @@ def image_driver(mime: str) -> str:
355
352
return 'PNG'
356
353
357
354
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
-
433
355
def get_color_map_from_image (image : Image ) -> dict :
434
356
"""Get a writable color map
435
357
0 commit comments