Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions server/mapcontrol_server/services/geotiff_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,21 @@ def normalize_band(arr, m):
normalized[m] = 0.0
return np.clip(normalized, 0, 255).astype(np.uint8)

r_u8 = normalize_band(r, mask)
g_u8 = normalize_band(g, mask)
b_u8 = normalize_band(b_arr, mask)
if all(ds.dtypes[b - 1] == "uint8" for b in bands):
# 8-bit RGB is already display-ready (a true-colour product, or a
# writer that applied its own fixed stretch). Re-stretching it per
# band lets the scene set the colours: an ocean-dominated image has
# its noise amplified and its colour balance shifted.
def as_is(arr, m):
out = arr.copy()
out[m] = 0.0
return out.astype(np.uint8)

r_u8, g_u8, b_u8 = as_is(r, mask), as_is(g, mask), as_is(b_arr, mask)
else:
r_u8 = normalize_band(r, mask)
g_u8 = normalize_band(g, mask)
b_u8 = normalize_band(b_arr, mask)

# Alpha channel: fully opaque for valid pixels, transparent for nodata/NaN
# (actual translucency is handled by OpenLayers layer opacity, not baked into PNG)
Expand Down
19 changes: 19 additions & 0 deletions server/tests/test_geotiff_masking.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,22 @@ def test_singleband_honors_mask_and_nodata(tmp_path):
assert (alpha[3, :] == 0).all()
assert alpha[1, 1] == 0 # value 5 = nodata tag
assert alpha[0, 0] == 255 and alpha[2, 3] == 255


def test_uint8_rgb_is_drawn_as_is_without_restretching(tmp_path):
data = np.full((3, 4, 4), 12, dtype=np.uint8) # dark ocean, display-scaled
data[:, 0, 0] = (180, 150, 90) # one bright land pixel
data[:, 3, 3] = 0 # no-data
rgba, ds = _process_rgb(_write(tmp_path / "a.tif", data, nodata=0), [1, 2, 3], 1.0, None)
ds.close()
assert tuple(rgba[1, 1, :3]) == (12, 12, 12) # not stretched up to mid-grey
assert tuple(rgba[0, 0, :3]) == (180, 150, 90)
assert rgba[3, 3, 3] == 0 and rgba[1, 1, 3] == 255


def test_non_uint8_rgb_is_still_stretched(tmp_path):
data = np.full((3, 4, 4), 1200, dtype=np.uint16)
data[:, 0, 0] = 3000
rgba, ds = _process_rgb(_write(tmp_path / "a.tif", data), [1, 2, 3], 1.0, None)
ds.close()
assert rgba[0, 0, 0] == 255 and rgba[1, 1, 0] == 0
Loading