Skip to content

Commit 03b8c0a

Browse files
committed
unweaving working decently; added more help
1 parent ff43a98 commit 03b8c0a

File tree

7 files changed

+7501
-47
lines changed

7 files changed

+7501
-47
lines changed

docs/_static/unmap.svg

+6,808
Loading

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ User guide
2020
:caption: User guide
2121

2222
readme
23+
userguide/Overview_of_unmap.ipynb
2324
userguide/Unmap_data_from_an_image.ipynb
2425
userguide/Guess_the_colourmap_from_an_image.ipynb
2526

docs/notebooks/Guess_the_colourmap_from_an_image.ipynb

+133-29
Large diffs are not rendered by default.

docs/notebooks/Overview_of_unmap.ipynb

+490
Large diffs are not rendered by default.

setup.cfg

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ install_requires =
3232

3333
[options.extras_require]
3434
test = pytest; pytest-cov
35-
docs = sphinx; sphinxcontrib-apidoc; furo; myst_nb
36-
dev = pytest; pytest-cov; sphinx; sphinxcontrib-apidoc; furo; myst_nb
35+
docs = sphinx; sphinxcontrib-apidoc; furo; myst_nb; gio
36+
dev = pytest; pytest-cov; sphinx; sphinxcontrib-apidoc; furo; myst_nb; gio
3737

3838
[tool:pytest]
3939
# pytest configuration: http://doc.pytest.org/en/latest/customize.html

unmap/unmap.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from matplotlib.colors import ListedColormap, hsv_to_rgb, rgb_to_hsv
1818
from matplotlib.colors import to_rgb, LinearSegmentedColormap
1919

20+
from .unweave import guess_cmap_from_array
21+
2022

2123
def check_arr(arr):
2224
"""
@@ -41,8 +43,8 @@ def check_arr(arr):
4143
elif (c == 0) or (c == 2) or (c >= 5):
4244
raise ValueError('Array must be a grayscale or RGB(A) image.')
4345
elif c == 4:
44-
arr = arr[..., :3]
4546
alpha = arr[..., 3]
47+
arr = arr[..., :3]
4648

4749
return arr, alpha
4850

@@ -150,6 +152,10 @@ def get_cmap(cmap, arr=None, levels=256, quantize=False):
150152
elif isinstance(cmap, LinearSegmentedColormap) or isinstance(cmap, ListedColormap):
151153
return cmap
152154

155+
elif cmap is None:
156+
# Try to guess from array.
157+
return guess_cmap_from_array(arr, source_colors=levels)
158+
153159
else:
154160
try:
155161
cmap = np.array(cmap)
@@ -221,7 +227,7 @@ def is_greyscale(arr, epsilon=1e-6):
221227

222228

223229
def unmap(arr,
224-
cmap,
230+
cmap=None,
225231
crop=None,
226232
vrange=(0, 1),
227233
levels=256,
@@ -237,9 +243,7 @@ def unmap(arr,
237243
Args:
238244
arr: NumPy array of image data.
239245
cmap: Either another array, or pixel extent of colourbar in image,
240-
or name of mpl colourbar or if None, just use lightness (say)
241-
If get pixels and they're a few wide, then average them along
242-
short axis? Again, depends on scatter.
246+
or name of mpl colourbar or if None, try to guess it.
243247
crop: If not None, crop the image to the given rectangle.
244248
Given as a tuple (left, top, right, bottom).
245249
vrange: (vmin, vmax) for the colourbar.

unmap/unweave.py

+58-11
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,35 @@
1717
from skimage.feature import graycomatrix
1818

1919

20-
def read_image(fname, colors=256):
20+
def ordered_unique(seq):
21+
return list(dict.fromkeys(seq))
22+
23+
24+
def convert_imarray(imarray, colors=256):
2125
"""
22-
Read an image and return an index array and colourtable.
26+
Convert an RGB image array to an index array and colourtable. The array
27+
will be quantized to the specified number of colours, and will be no larger
28+
than 512x512 pixels.
2329
2430
Args:
25-
fname (str): Path to image file, file handle, or a URL.
31+
imarray (np.ndarray): The RGB or RGBA image array.
2632
colors (int): Number of colours to reduce to.
2733
2834
Returns:
2935
imarray (np.ndarray): Array of indices into the colourtable.
3036
unique_colors (np.ndarray): Colourtable.
3137
"""
32-
with fsspec.open(fname) as f:
33-
imp = Image.open(f)
34-
imp = imp.quantize(colors=colors, dither=Image.NONE)
35-
imp.thumbnail((512, 512))
38+
if np.min(imarray) < 0 or np.max(imarray) > 255:
39+
raise ValueError("Image array must be in the range [0, 255] or [0, 1].")
40+
elif np.max(imarray) <= 1.0:
41+
imarray = imarray * 255
42+
imp = Image.fromarray(np.uint8(imarray))
43+
imp = imp.quantize(colors=colors, dither=Image.NONE)
44+
imp.thumbnail((512, 512))
3645
imarray = np.asarray(imp)
3746
palette = np.asarray(imp.getpalette()).reshape(-1, 3)
38-
return imarray.astype(np.int16), palette[:colors]/255
47+
unique = ordered_unique(tuple(i) for i in palette[:colors]/255)
48+
return imarray, np.array(unique)
3949

4050

4151
def construct_graph(imarray, colors=256, normed=True):
@@ -219,7 +229,7 @@ def path_to_cmap(path, unique_colors, colors=256, reverse='auto', equilibrate=Fa
219229
return cmap
220230

221231

222-
def guess_cmap(fname,
232+
def guess_cmap_from_array(array,
223233
source_colors=256,
224234
target_colors=256,
225235
min_weight=0.025,
@@ -232,7 +242,7 @@ def guess_cmap(fname,
232242
Guess the colormap of an image.
233243
234244
Args:
235-
fname (str): Filename or URL of image to guess.
245+
array (np.ndarray): The RGB or RGBA image array.
236246
source_colors (int): Number of colours to detect in the source image.
237247
target_colors (int): Number of colours to return in the colormap.
238248
min_weight (float): Minimum weight to keep. See `prune_graph`.
@@ -243,8 +253,45 @@ def guess_cmap(fname,
243253
the direction is essentially random.
244254
245255
"""
246-
imarray, uniq = read_image(fname, colors=source_colors)
256+
imarray, uniq = convert_imarray(array, colors=source_colors)
247257
G = construct_graph(imarray, colors=source_colors)
248258
G0 = prune_graph(G, uniq, min_weight=min_weight, max_dist=max_dist, max_neighbours=max_neighbours)
249259
path = longest_shortest_path(G0)
250260
return path_to_cmap(path, uniq, colors=target_colors, reverse=reverse, equilibrate=equilibrate)
261+
262+
263+
def guess_cmap_from_image(fname,
264+
source_colors=256,
265+
target_colors=256,
266+
min_weight=0.025,
267+
max_dist=0.25,
268+
max_neighbours=20,
269+
reverse='auto',
270+
equilibrate=False
271+
):
272+
"""
273+
Guess the colormap of an image.
274+
275+
Args:
276+
fname (str): Filename or URL of image to guess.
277+
source_colors (int): Number of colours to detect in the source image.
278+
target_colors (int): Number of colours to return in the colormap.
279+
min_weight (float): Minimum weight to keep. See `prune_graph`.
280+
max_dist (float): Maximum distance to keep. See `prune_graph`.
281+
max_neighbours (int): Maximum number of neighbours to allow. See `prune_graph`.
282+
reverse (bool): Whether to reverse the colormap. If 'auto', the
283+
colormap will start with the end closest to dark blue. If False,
284+
the direction is essentially random.
285+
286+
"""
287+
with fsspec.open(fname) as f:
288+
img = Image.open(f)
289+
return guess_cmap_from_array(np.asarray(img),
290+
source_colors=source_colors,
291+
target_colors=target_colors,
292+
min_weight=min_weight,
293+
max_dist=max_dist,
294+
max_neighbours=max_neighbours,
295+
reverse=reverse,
296+
equilibrate=equilibrate
297+
)

0 commit comments

Comments
 (0)