Skip to content

Utils

Utils

cax.utils.render

Utilities for rendering.

rgba_to_rgb(array)

Convert an RGBA image to RGB by alpha compositing over white.

The function assumes the last dimension encodes channels and that the input is normalized to the range [0, 1] with shape (..., 4). The output preserves the input shape except for the channel dimension, which becomes 3.

Parameters:

Name Type Description Default
array Array

RGBA image with shape (..., 4) and values in [0, 1].

required

Returns:

Type Description
Array

RGB image with shape (..., 3) and values in [0, 1].

Source code in src/cax/utils/render.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def rgba_to_rgb(array: Array) -> Array:
	"""Convert an RGBA image to RGB by alpha compositing over white.

	The function assumes the last dimension encodes channels and that the input is normalized
	to the range ``[0, 1]`` with shape ``(..., 4)``. The output preserves the input shape
	except for the channel dimension, which becomes ``3``.

	Args:
		array: RGBA image with shape ``(..., 4)`` and values in ``[0, 1]``.

	Returns:
		RGB image with shape ``(..., 3)`` and values in ``[0, 1]``.

	"""
	assert array.shape[-1] == 4
	rgb, alpha = array[..., :-1], array[..., -1:]
	alpha = jnp.clip(alpha, min=0.0, max=1.0)
	return (1.0 - alpha) * 1.0 + alpha * rgb

rgb_to_hsv(rgb)

Convert RGB to HSV.

Input and output are in the range [0, 1] and use channel-last layout.

Parameters:

Name Type Description Default
rgb Array

RGB image with shape (..., 3).

required

Returns:

Type Description
Array

HSV image with shape (..., 3).

Source code in src/cax/utils/render.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def rgb_to_hsv(rgb: Array) -> Array:
	"""Convert RGB to HSV.

	Input and output are in the range ``[0, 1]`` and use channel-last layout.

	Args:
		rgb: RGB image with shape ``(..., 3)``.

	Returns:
		HSV image with shape ``(..., 3)``.

	"""
	input_shape = rgb.shape
	rgb = rgb.reshape(-1, 3)
	r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]

	maxc = jnp.maximum(jnp.maximum(r, g), b)
	minc = jnp.minimum(jnp.minimum(r, g), b)
	v = maxc
	deltac = maxc - minc

	s = jnp.where(maxc != 0, deltac / maxc, 0)

	deltac = jnp.where(deltac == 0, 1, deltac)  # Avoid division by zero

	rc = (maxc - r) / deltac
	gc = (maxc - g) / deltac
	bc = (maxc - b) / deltac

	h = jnp.where(r == maxc, bc - gc, jnp.where(g == maxc, 2.0 + rc - bc, 4.0 + gc - rc))

	h = jnp.where(minc == maxc, 0.0, h)
	h = (h / 6.0) % 1.0

	hsv = jnp.stack([h, s, v], axis=-1)
	return hsv.reshape(input_shape)

hsv_to_rgb(hsv)

Convert HSV to RGB.

Input and output are in the range [0, 1] and use channel-last layout.

Parameters:

Name Type Description Default
hsv Array

HSV image with shape (..., 3).

required

Returns:

Type Description
Array

RGB image with shape (..., 3).

Source code in src/cax/utils/render.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def hsv_to_rgb(hsv: Array) -> Array:
	"""Convert HSV to RGB.

	Input and output are in the range ``[0, 1]`` and use channel-last layout.

	Args:
		hsv: HSV image with shape ``(..., 3)``.

	Returns:
		RGB image with shape ``(..., 3)``.

	"""
	input_shape = hsv.shape
	hsv = hsv.reshape(-1, 3)
	h, s, v = hsv[..., 0], hsv[..., 1], hsv[..., 2]

	i = jnp.floor(h * 6.0).astype(jnp.int32)
	f = (h * 6.0) - i
	p = v * (1.0 - s)
	q = v * (1.0 - s * f)
	t = v * (1.0 - s * (1.0 - f))

	i = i % 6

	rgb = jnp.zeros_like(hsv)
	rgb = jnp.where((i == 0)[..., None], jnp.stack([v, t, p], axis=-1), rgb)
	rgb = jnp.where((i == 1)[..., None], jnp.stack([q, v, p], axis=-1), rgb)
	rgb = jnp.where((i == 2)[..., None], jnp.stack([p, v, t], axis=-1), rgb)
	rgb = jnp.where((i == 3)[..., None], jnp.stack([p, q, v], axis=-1), rgb)
	rgb = jnp.where((i == 4)[..., None], jnp.stack([t, p, v], axis=-1), rgb)
	rgb = jnp.where((i == 5)[..., None], jnp.stack([v, p, q], axis=-1), rgb)

	rgb = jnp.where(s[..., None] == 0.0, jnp.full_like(rgb, v[..., None]), rgb)

	return rgb.reshape(input_shape)

clip_and_uint8(frame)

Clip a floating-point image to [0, 1] and convert to uint8.

Parameters:

Name Type Description Default
frame Array

Image-like array with values expected in or near [0, 1].

required

Returns:

Type Description
Array

Array of dtype uint8 with values in [0, 255].

Source code in src/cax/utils/render.py
102
103
104
105
106
107
108
109
110
111
112
113
def clip_and_uint8(frame: Array) -> Array:
	"""Clip a floating-point image to ``[0, 1]`` and convert to ``uint8``.

	Args:
		frame: Image-like array with values expected in or near ``[0, 1]``.

	Returns:
		Array of dtype ``uint8`` with values in ``[0, 255]``.

	"""
	frame = jnp.clip(frame, min=0.0, max=1.0)
	return (frame * 255).astype(jnp.uint8)

render_array_with_channels_to_rgb(array)

Render an array with channels as an RGB image.

This function processes an input array and converts it into an RGB image based on the number of channels present in the array. The conversion logic is as follows: - If the array has 1 channel, it is repeated across the RGB channels to produce a grayscale image. - If the array has 2 channels, the first channel is interpreted as hue and the second as saturation. These are converted to RGB using a fixed brightness value, resulting in a colorful representation. - If the array has 3 or more channels, the last three channels are used directly as the RGB values.

The resulting RGB image is clipped to the valid range [0, 1] and converted to uint8 format.

Parameters:

Name Type Description Default
array Array

Input array with shape (..., C) and values in [0, 1].

required

Returns:

Type Description
Array

RGB array with shape (..., 3) and values in [0, 1].

Source code in src/cax/utils/render.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def render_array_with_channels_to_rgb(array: Array) -> Array:
	"""Render an array with channels as an RGB image.

	This function processes an input array and converts it into an RGB image based on the number of
	channels present in the array. The conversion logic is as follows:
	- If the array has 1 channel, it is repeated across the RGB channels to produce a grayscale
		image.
	- If the array has 2 channels, the first channel is interpreted as hue and the second as
		saturation. These are converted to RGB using a fixed brightness value, resulting in a
		colorful representation.
	- If the array has 3 or more channels, the last three channels are used directly as the RGB
		values.

	The resulting RGB image is clipped to the valid range [0, 1] and converted
	to uint8 format.

	Args:
		array: Input array with shape ``(..., C)`` and values in ``[0, 1]``.

	Returns:
		RGB array with shape ``(..., 3)`` and values in ``[0, 1]``.

	"""
	num_channels = array.shape[-1]

	if num_channels == 1:
		# 1 channel
		rgb = jnp.repeat(array, 3, axis=-1)
	elif num_channels == 2:
		# 2 channels
		hue = array[..., 0:1]  # Use the first channel as hue
		saturation = array[..., 1:2]  # and the second as saturation
		value = jnp.ones_like(hue)  # Use full brightness
		hsv = jnp.concatenate([hue, saturation, value], axis=-1)
		rgb = hsv_to_rgb(hsv)
	else:
		# 3 channels or more
		rgb = array[..., -3:]

	return rgb

render_array_with_channels_to_rgba(array)

Render an array with channels as an RGBA image.

This function processes an input array and converts it into an RGBA image based on the number of channels present in the array. The conversion logic is as follows: - If the array has 1 channel, it is repeated across the RGBA channels. - If the array has 2 channels, the first channel is used for RGB, and the second for alpha. - If the array has 3 channels, the first channel is interpreted as hue and the second as saturation. These are converted to RGB using a fixed brightness value, and the last channel is used as the alpha channel. - If the array has 4 or more channels, the last four channels are used directly as RGBA.

Parameters:

Name Type Description Default
array Array

Input array with shape (..., C) and values in [0, 1].

required

Returns:

Type Description
Array

RGBA array with shape (..., 4) and values in [0, 1].

Source code in src/cax/utils/render.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def render_array_with_channels_to_rgba(array: Array) -> Array:
	"""Render an array with channels as an RGBA image.

	This function processes an input array and converts it into an RGBA image based on the number of
	channels present in the array. The conversion logic is as follows:
	- If the array has 1 channel, it is repeated across the RGBA channels.
	- If the array has 2 channels, the first channel is used for RGB, and the second for alpha.
	- If the array has 3 channels, the first channel is interpreted as hue and the second as
		saturation. These are converted to RGB using a fixed brightness value, and the last channel
		is used as the alpha channel.
	- If the array has 4 or more channels, the last four channels are used directly as RGBA.

	Args:
		array: Input array with shape ``(..., C)`` and values in ``[0, 1]``.

	Returns:
		RGBA array with shape ``(..., 4)`` and values in ``[0, 1]``.

	"""
	num_channels = array.shape[-1]

	if num_channels == 1:
		# 1 channel
		rgba = jnp.repeat(array, 4, axis=-1)
	elif num_channels == 2:
		# 2 channels
		rgb = jnp.repeat(array[..., 0:1], 3, axis=-1)
		alpha = array[..., 1:2]
		rgba = jnp.concatenate([rgb, alpha], axis=-1)
	elif num_channels == 3:
		# 3 channels
		hue = array[..., 0:1]  # Use the first channel as hue
		saturation = array[..., 1:2]  # and the second as saturation
		value = jnp.ones_like(hue)  # Use full brightness
		hsv = jnp.concatenate([hue, saturation, value], axis=-1)
		rgb = hsv_to_rgb(hsv)
		alpha = array[..., 2:3]  # Use the last channel as alpha
		rgba = jnp.concatenate([rgb, alpha], axis=-1)
	else:
		# 4 or more channels
		rgba = array[..., -4:]

	return rgba

cax.utils.emoji

Utilities for emojis.

get_image_from_url(url)

Fetch an image from a given URL.

Parameters:

Name Type Description Default
url str

The URL of the image to fetch.

required

Returns:

Type Description
Image

The fetched image as a PIL Image object.

Source code in src/cax/utils/emoji.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def get_image_from_url(url: str) -> Image:
	"""Fetch an image from a given URL.

	Args:
		url: The URL of the image to fetch.

	Returns:
		The fetched image as a PIL Image object.

	"""
	with urlopen(url) as response:
		image_data = response.read()

	image_pil = PIL.Image.open(io.BytesIO(image_data))
	return image_pil

get_emoji(emoji)

Fetch and return an emoji as a PIL Image.

The emoji glyph is downloaded from Google's Noto Emoji repository (PNG, 128 px). The image is returned as a PIL Image without further processing. Callers may convert to arrays or resize as needed.

Parameters:

Name Type Description Default
emoji str

The emoji character to fetch.

required

Returns:

Type Description
Image

A PIL.Image.Image instance containing the emoji.

Source code in src/cax/utils/emoji.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def get_emoji(emoji: str) -> Image:
	"""Fetch and return an emoji as a PIL Image.

	The emoji glyph is downloaded from Google's Noto Emoji repository (PNG, 128 px). The image
	is returned as a PIL Image without further processing. Callers may convert to arrays or
	resize as needed.

	Args:
		emoji: The emoji character to fetch.

	Returns:
		A ``PIL.Image.Image`` instance containing the emoji.

	"""
	# Get the emoji image
	code = hex(ord(emoji))[2:].lower()
	url = f"https://github.com/googlefonts/noto-emoji/blob/main/png/128/emoji_u{code}.png?raw=true"
	image_pil = get_image_from_url(url)
	return image_pil