Create PCA colormap for images with channels > 3 (#1929)

* Init pca colormap for images with channels>3

* --amend

* formatting etc

* formatting etc

* docstring abt >3 channels

---------

Co-authored-by: Justin Kerr <justin.g.kerr@gmail.com>
Co-authored-by: Saurav Maheshkar <sauravvmaheshkar@gmail.com>
This commit is contained in:
authored and GitHub committed 2023-05-16 10:46:15 -07:00
1 parent 6c3106004d
commit 7e8fc5c97f
2 files changed
+48 -1

No files matched your search

+46 -1
View File
@@ -48,7 +48,10 @@ def apply_colormap(
eps: float = 1e-9,
) -> TensorType["bs":..., "rgb":3]:
"""
Applies a colormap to a tensor image. Currently only supports 1 and 3 channel inputs
Applies a colormap to a tensor image.
If single channel, applies a colormap to the image.
If 3 channel, treats the channels as RGB.
If more than 3 channel, applies a PCA reduction on the dimensions to 3 channels
Args:
image: Input tensor image.
@@ -80,6 +83,9 @@ def apply_colormap(
if image.dtype == torch.bool:
return apply_boolean_colormap(image)
if image.shape[-1] > 3:
return apply_pca_colormap(image)
raise NotImplementedError
@@ -164,3 +170,42 @@ def apply_boolean_colormap(
colored_image[image[..., 0], :] = true_color
colored_image[~image[..., 0], :] = false_color
return colored_image
def apply_pca_colormap(image: TensorType["bs":..., "dim"]) -> TensorType["bs":..., "rgb":3]:
"""Convert feature image to 3-channel RGB via PCA. The first three principle
components are used for the color channels, with outlier rejection per-channel
Args:
image: image of arbitrary vectors
Returns:
TensorType: Colored image
"""
original_shape = image.shape
image = image.view(-1, image.shape[-1])
_, _, v = torch.pca_lowrank(image)
image = torch.matmul(image, v[..., :3])
d = torch.abs(image - torch.median(image, dim=0).values)
mdev = torch.median(d, dim=0).values
s = d / mdev
m = 3.0 # this is a hyperparam controlling how many std dev outside for outliers
rins = image[s[:, 0] < m, 0]
gins = image[s[:, 1] < m, 1]
bins = image[s[:, 2] < m, 2]
image[:, 0] -= rins.min()
image[:, 1] -= gins.min()
image[:, 2] -= bins.min()
image[:, 0] /= rins.max() - rins.min()
image[:, 1] /= gins.max() - gins.min()
image[:, 2] /= bins.max() - bins.min()
image = torch.clamp(image, 0, 1)
image_long = (image * 255).long()
image_long_min = torch.min(image_long)
image_long_max = torch.max(image_long)
assert image_long_min >= 0, f"the min value is {image_long_min}"
assert image_long_max <= 255, f"the max value is {image_long_max}"
return image.view(*original_shape[:-1], 3)
@@ -362,4 +362,6 @@ def _get_colormap_options(dimensions: int, dtype: type) -> List[Colormaps]:
colormap_options = ["default"]
if dimensions == 1 and dtype == torch.float:
colormap_options = [c for c in list(get_args(Colormaps)) if c != "default"]
if dimensions > 3:
colormap_options = ["pca"]
return colormap_options