mergechannels 0.5.8__cp39-cp39-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ from ._internal import (
2
+ apply_color_map,
3
+ get_cmap_array,
4
+ get_mpl_cmap,
5
+ merge,
6
+ )
7
+ from ._luts import COLORMAPS
8
+ from .mergechannels import (
9
+ dispatch_multi_channel,
10
+ dispatch_single_channel,
11
+ )
12
+
13
+ __all__ = [
14
+ 'dispatch_single_channel',
15
+ 'dispatch_multi_channel',
16
+ 'merge',
17
+ 'apply_color_map',
18
+ 'get_cmap_array',
19
+ 'get_mpl_cmap',
20
+ 'COLORMAPS',
21
+ ]
@@ -0,0 +1,3 @@
1
+ from typing import Literal
2
+
3
+ BLENDING_OPTIONS = Literal['max', 'sum', 'mean', 'min']
@@ -0,0 +1,600 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Sequence,
7
+ Tuple,
8
+ Union,
9
+ )
10
+
11
+ import numpy as np
12
+
13
+ from ._blending import BLENDING_OPTIONS
14
+ from ._luts import COLORMAPS
15
+ from .mergechannels import ( # type: ignore
16
+ dispatch_multi_channel,
17
+ dispatch_single_channel,
18
+ )
19
+ from .mergechannels import (
20
+ get_cmap_array as _get_cmap_array, # just aliasing this to inject a docstring
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from cmap import Colormap as CmapColormap
25
+ from matplotlib.colors import Colormap as MatplotlibColormap
26
+ from matplotlib.colors import ListedColormap
27
+ from nptyping import (
28
+ NDArray,
29
+ Shape,
30
+ UInt8,
31
+ )
32
+
33
+ # Type alias for mask color specification
34
+ MaskColor = Union[COLORMAPS, Tuple[int, int, int], Sequence[int]]
35
+
36
+ # Default mask color (purple) and alpha
37
+ DEFAULT_MASK_COLOR: Tuple[int, int, int] = (128, 0, 128)
38
+ DEFAULT_MASK_ALPHA: float = 0.5
39
+
40
+
41
+ def _parse_cmap_arguments(
42
+ color: Union[
43
+ COLORMAPS,
44
+ NDArray[Shape['256, 3'], UInt8],
45
+ MatplotlibColormap,
46
+ CmapColormap,
47
+ ],
48
+ ) -> Tuple[Union[COLORMAPS, None], Union[NDArray[Shape['256, 3'], UInt8], None]]:
49
+ """
50
+ Parse the color argument and return the corresponding cmap name and cmap values
51
+
52
+ Args:
53
+ color: a user-specified argument which may be the name of mergechannels colormap,
54
+ a ndarray lookup table, and matplotlib colormap, or a cmap colormap.
55
+
56
+ Returns:
57
+ A tuple specifying the corresponding mergechannels colormap name (or None if N/A),
58
+ and an array of the lookup table (or None if N/A)
59
+ """
60
+ if isinstance(color, str):
61
+ return color, None
62
+ else:
63
+ try: # try to convert from a matplotlib colormap
64
+ if not color._isinit: # type: ignore
65
+ color._init() # type: ignore
66
+ cmap_values = (color._lut[: color.N, :3] * 255).astype('uint8') # type: ignore
67
+ except AttributeError: # try to convert from a cmaps ColorMap
68
+ try:
69
+ cmap_values = (np.asarray(color.lut()[:, :3]) * 255).astype('uint8') # type: ignore
70
+ except AttributeError: # must be a list of lists or an array castable to u8 (256, 3)
71
+ cmap_values = np.asarray(color).astype('uint8') # type: ignore
72
+
73
+ if not (
74
+ isinstance(cmap_values, np.ndarray)
75
+ and cmap_values.shape == (256, 3)
76
+ and cmap_values.dtype == np.uint8
77
+ ):
78
+ raise ValueError(
79
+ 'Expected a matplotlib colormap, a cmaps colormap, or an object directly castable to '
80
+ f'an 8-bit array of shape (256, 3), got {type(cmap_values)}: {color}'
81
+ )
82
+ return None, cmap_values
83
+
84
+
85
+ def _hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
86
+ """
87
+ Convert a hex color string to RGB tuple.
88
+
89
+ Args:
90
+ hex_color: Hex color string like '#FF00FF', 'FF00FF', '#f0f', or 'f0f'
91
+
92
+ Returns:
93
+ Tuple of (R, G, B) values in range 0-255
94
+
95
+ Raises:
96
+ ValueError: If the hex string is invalid
97
+ """
98
+ # Remove leading '#' if present
99
+ hex_color = hex_color.lstrip('#')
100
+
101
+ # Handle shorthand hex (e.g., 'f0f' -> 'ff00ff')
102
+ if len(hex_color) == 3:
103
+ hex_color = ''.join(c * 2 for c in hex_color)
104
+
105
+ if len(hex_color) != 6:
106
+ raise ValueError(
107
+ f"Invalid hex color '{hex_color}': expected 3 or 6 hex digits"
108
+ " (with optional '#' prefix)"
109
+ )
110
+
111
+ # Validate hex characters
112
+ if not re.match(r'^[0-9a-fA-F]{6}$', hex_color):
113
+ raise ValueError(f"Invalid hex color '{hex_color}': contains non-hexadecimal characters")
114
+
115
+ r = int(hex_color[0:2], 16)
116
+ g = int(hex_color[2:4], 16)
117
+ b = int(hex_color[4:6], 16)
118
+ return (r, g, b)
119
+
120
+
121
+ def _parse_mask_color(color: MaskColor | None) -> Tuple[int, int, int]:
122
+ """
123
+ Parse a mask color specification into an RGB tuple.
124
+
125
+ Args:
126
+ color: Can be:
127
+ - None: returns default purple (128, 0, 128)
128
+ - A colormap name (str): uses the color at index 255 of that colormap
129
+ - A hex string: '#FF00FF', 'FF00FF', '#f0f', 'f0f'
130
+ - An RGB tuple/sequence: (R, G, B) with values 0-255
131
+
132
+ Returns:
133
+ Tuple of (R, G, B) values in range 0-255
134
+
135
+ Raises:
136
+ ValueError: If the color specification is invalid
137
+ """
138
+ if color is None:
139
+ return DEFAULT_MASK_COLOR
140
+
141
+ if isinstance(color, str):
142
+ # Check if it's a hex color (starts with # or is all hex digits)
143
+ if color.startswith('#') or re.match(r'^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$', color):
144
+ return _hex_to_rgb(color)
145
+
146
+ # Otherwise, treat as a colormap name
147
+ try:
148
+ cmap_array = _get_cmap_array(color)
149
+ # Use the color at index 255 (brightest value in the colormap)
150
+ return tuple(cmap_array[255]) # type: ignore
151
+ except ValueError as e:
152
+ raise ValueError(
153
+ f"Invalid mask color '{color}': not a valid hex color or colormap name. "
154
+ f"Hex colors should be like '#FF00FF' or 'f0f'. "
155
+ f'Available colormaps can be found in mergechannels.COLORMAPS.'
156
+ ) from e
157
+
158
+ # Must be a sequence of RGB values
159
+ try:
160
+ rgb = tuple(color) # type: ignore
161
+ if len(rgb) != 3:
162
+ raise ValueError(f'Invalid mask color: expected 3 RGB values, got {len(rgb)}')
163
+ r, g, b = rgb
164
+ # Validate range
165
+ for val, name in [(r, 'R'), (g, 'G'), (b, 'B')]:
166
+ if not isinstance(val, (int, np.integer)):
167
+ raise ValueError(
168
+ f'Invalid mask color: {name} value must be an integer, got {type(val).__name__}'
169
+ )
170
+ if not 0 <= val <= 255:
171
+ raise ValueError(f'Invalid mask color: {name} value {val} out of range [0, 255]')
172
+ return (int(r), int(g), int(b))
173
+ except TypeError:
174
+ raise ValueError(
175
+ f'Invalid mask color type: expected str, tuple, or sequence, got {type(color).__name__}'
176
+ )
177
+
178
+
179
+ def _validate_mask(
180
+ mask: np.ndarray,
181
+ expected_shape: tuple,
182
+ mask_index: int | None = None,
183
+ ) -> None:
184
+ """
185
+ Validate a mask array.
186
+
187
+ Args:
188
+ mask: The mask array to validate
189
+ expected_shape: Expected shape of the mask (should match the data array shape)
190
+ mask_index: Optional index for error messages when validating multiple masks
191
+
192
+ Raises:
193
+ TypeError: If mask is not a numpy array
194
+ ValueError: If mask shape doesn't match or dtype is invalid
195
+ """
196
+ idx_str = f' at index {mask_index}' if mask_index is not None else ''
197
+
198
+ if not isinstance(mask, np.ndarray):
199
+ raise TypeError(f'Mask{idx_str} must be a numpy array, got {type(mask).__name__}')
200
+
201
+ if mask.shape != expected_shape:
202
+ raise ValueError(
203
+ f'Mask{idx_str} shape {mask.shape} does not match array shape {expected_shape}'
204
+ )
205
+
206
+ if mask.dtype not in (np.bool_, np.int32):
207
+ raise ValueError(f'Mask{idx_str} dtype must be bool or int32, got {mask.dtype}')
208
+
209
+
210
+ def _parse_mask_arguments(
211
+ masks: Sequence[np.ndarray] | np.ndarray | None,
212
+ mask_colors: Sequence[MaskColor] | MaskColor | None,
213
+ mask_alphas: Sequence[float] | float | None,
214
+ expected_shape: tuple,
215
+ ) -> Tuple[list[np.ndarray] | None, list[Tuple[int, int, int]] | None, list[float] | None]:
216
+ """
217
+ Parse and validate mask arguments, handling single values and sequences.
218
+
219
+ Args:
220
+ masks: Single mask array, sequence of masks, or None
221
+ mask_colors: Single color, sequence of colors, or None
222
+ mask_alphas: Single alpha, sequence of alphas, or None
223
+ expected_shape: Expected shape for all masks (should match data array shape)
224
+
225
+ Returns:
226
+ Tuple of (masks_list, colors_list, alphas_list) or (None, None, None) if no masks
227
+
228
+ Raises:
229
+ ValueError: If arguments are inconsistent or invalid
230
+ """
231
+ if masks is None:
232
+ return None, None, None
233
+
234
+ # Normalize masks to a list
235
+ if isinstance(masks, np.ndarray):
236
+ masks_list = [masks]
237
+ else:
238
+ masks_list = list(masks)
239
+
240
+ if len(masks_list) == 0:
241
+ return None, None, None
242
+
243
+ # Validate all masks
244
+ for i, mask in enumerate(masks_list):
245
+ _validate_mask(mask, expected_shape, mask_index=i if len(masks_list) > 1 else None)
246
+
247
+ n_masks = len(masks_list)
248
+
249
+ # Parse colors
250
+ if mask_colors is None:
251
+ colors_list = [DEFAULT_MASK_COLOR] * n_masks
252
+ elif isinstance(mask_colors, (str, tuple)) or (
253
+ isinstance(mask_colors, Sequence)
254
+ and len(mask_colors) == 3
255
+ and isinstance(mask_colors[0], (int, np.integer))
256
+ ):
257
+ # Single color specification - apply to all masks
258
+ parsed_color = _parse_mask_color(mask_colors) # type: ignore
259
+ colors_list = [parsed_color] * n_masks
260
+ else:
261
+ # Sequence of colors
262
+ colors_list = [_parse_mask_color(c) for c in mask_colors] # type: ignore
263
+ if len(colors_list) != n_masks:
264
+ raise ValueError(
265
+ f'Number of mask colors ({len(colors_list)}) does not match '
266
+ f'number of masks ({n_masks})'
267
+ )
268
+
269
+ # Parse alphas
270
+ if mask_alphas is None:
271
+ alphas_list = [DEFAULT_MASK_ALPHA] * n_masks
272
+ elif isinstance(mask_alphas, (int, float)):
273
+ # Single alpha - apply to all masks
274
+ alpha = float(mask_alphas)
275
+ if not 0.0 <= alpha <= 1.0:
276
+ raise ValueError(f'Mask alpha {alpha} out of range [0.0, 1.0]')
277
+ alphas_list = [alpha] * n_masks
278
+ else:
279
+ # Sequence of alphas
280
+ alphas_list = []
281
+ for i, a in enumerate(mask_alphas):
282
+ alpha = float(a)
283
+ if not 0.0 <= alpha <= 1.0:
284
+ raise ValueError(f'Mask alpha at index {i} ({alpha}) out of range [0.0, 1.0]')
285
+ alphas_list.append(alpha)
286
+ if len(alphas_list) != n_masks:
287
+ raise ValueError(
288
+ f'Number of mask alphas ({len(alphas_list)}) does not match '
289
+ f'number of masks ({n_masks})'
290
+ )
291
+
292
+ return masks_list, colors_list, alphas_list
293
+
294
+
295
+ def apply_color_map(
296
+ arr: np.ndarray,
297
+ color: Union[
298
+ COLORMAPS,
299
+ NDArray[Shape['256, 3'], UInt8],
300
+ MatplotlibColormap,
301
+ CmapColormap,
302
+ ],
303
+ percentiles: Union[tuple[float, float], None] = None,
304
+ saturation_limits: Union[tuple[float, float], None] = None,
305
+ masks: Sequence[np.ndarray] | np.ndarray | None = None,
306
+ mask_colors: Sequence[MaskColor] | MaskColor | None = None,
307
+ mask_alphas: Sequence[float] | float | None = None,
308
+ parallel: bool = True,
309
+ ) -> np.ndarray:
310
+ """
311
+ Apply a colormap to a grayscale array.
312
+
313
+ Parameters
314
+ ----------
315
+ arr : np.ndarray
316
+ Input array of shape (H, W) or (Z, H, W) with dtype uint8 or uint16.
317
+ color : COLORMAPS or NDArray or MatplotlibColormap or CmapColormap
318
+ The colormap to apply. Can be:
319
+ - A built-in colormap name (see mergechannels.COLORMAPS)
320
+ - A (256, 3) uint8 numpy array
321
+ - A matplotlib Colormap object
322
+ - A cmap Colormap object
323
+ percentiles : tuple[float, float] | None, optional
324
+ Percentile values (low, high) for auto-scaling intensity. Ignored if saturation_limits is
325
+ provided. Default is (1.1, 99.9).
326
+ saturation_limits : tuple[float, float] | None, optional
327
+ Explicit intensity limits (low, high) to set the black and white points.
328
+ masks : Sequence[np.ndarray] | np.ndarray | None, optional
329
+ Mask array(s) to overlay on the result. Each mask must have the same shape as the input
330
+ array and dtype of bool or int32. For bool masks, True pixels are overlaid. For int32
331
+ masks, any non-zero value is overlaid.
332
+ mask_colors : Sequence[MaskColor] | MaskColor | None, optional
333
+ Color(s) for the mask overlay. Can be:
334
+ - A colormap name (uses the color at index 255)
335
+ - A hex string ('#FF00FF', 'f0f')
336
+ - An RGB tuple (R, G, B) with values 0-255
337
+ If a single color is provided, it applies to all masks.
338
+ Default is purple (128, 0, 128).
339
+ mask_alphas : Sequence[float] | float | None, optional
340
+ Alpha value(s) for mask blending (0.0-1.0). If a single value is provided, it applies
341
+ to all masks. Default is 0.5.
342
+ parallel : bool, optional
343
+ Whether to use a Rayon threadpool on the Rust side for parallel processing. Default is True.
344
+
345
+ Returns
346
+ -------
347
+ np.ndarray
348
+ RGB array with shape (..., 3) and dtype uint8.
349
+
350
+ Raises
351
+ ------
352
+ ValueError
353
+ If the colormap name is not found, color format is invalid, or mask arguments are invalid.
354
+ TypeError
355
+ If masks are not numpy arrays.
356
+
357
+ Examples
358
+ --------
359
+ >>> import mergechannels as mc
360
+ >>> import numpy as np
361
+ >>> arr = np.random.randint(0, 256, (512, 512), dtype=np.uint8)
362
+ >>> rgb = mc.apply_color_map(arr, 'betterBlue', saturation_limits=(0, 255))
363
+ >>> rgb.shape
364
+ (512, 512, 3)
365
+
366
+ With a mask overlay:
367
+
368
+ >>> mask = arr > 200 # Highlight bright pixels
369
+ >>> rgb = mc.apply_color_map(
370
+ ... arr, 'Grays', saturation_limits=(0, 255),
371
+ ... masks=[mask], mask_colors=['#FF0000'], mask_alphas=[0.5]
372
+ ... )
373
+ """
374
+ if saturation_limits is None:
375
+ if percentiles is None:
376
+ percentiles = (1.1, 99.9)
377
+ low, high = np.percentile(arr, percentiles)
378
+ saturation_limits = (low, high)
379
+
380
+ cmap_name, cmap_values = _parse_cmap_arguments(color)
381
+
382
+ # Parse mask arguments
383
+ masks_list, colors_list, alphas_list = _parse_mask_arguments(
384
+ masks, mask_colors, mask_alphas, expected_shape=arr.shape
385
+ )
386
+
387
+ return dispatch_single_channel(
388
+ array_reference=arr,
389
+ cmap_name=cmap_name,
390
+ cmap_values=cmap_values,
391
+ limits=saturation_limits,
392
+ parallel=parallel,
393
+ mask_arrays=masks_list,
394
+ mask_colors=colors_list,
395
+ mask_alphas=alphas_list,
396
+ )
397
+
398
+
399
+ def merge(
400
+ arrs: Sequence[np.ndarray],
401
+ colors: Sequence[COLORMAPS],
402
+ blending: BLENDING_OPTIONS = 'max',
403
+ percentiles: Sequence[tuple[float, float]] | None = None,
404
+ saturation_limits: Sequence[tuple[float, float]] | None = None,
405
+ masks: Sequence[np.ndarray] | np.ndarray | None = None,
406
+ mask_colors: Sequence[MaskColor] | MaskColor | None = None,
407
+ mask_alphas: Sequence[float] | float | None = None,
408
+ parallel: bool = True,
409
+ ) -> np.ndarray:
410
+ """
411
+ Apply colormaps to multiple arrays and blend them into a single RGB image.
412
+
413
+ Parameters
414
+ ----------
415
+ arrs : Sequence[np.ndarray]
416
+ Sequence of input arrays, each with shape (H, W) or (Z, H, W).
417
+ All arrays must have the same shape and dtype (uint8 or uint16).
418
+ colors : Sequence[COLORMAPS]
419
+ Sequence of colormap names or colormap objects, one per input array. Can be built-in names
420
+ (see mergechannels.COLORMAPS), (256, 3) uint8 arrays, matplotlib Colormap objects, or cmap
421
+ Colormap objects.
422
+ blending : BLENDING_OPTIONS, optional
423
+ Blending mode for combining colored channels. One of:
424
+ - 'max': Maximum intensity projection (default)
425
+ - 'sum': Additive blending (clamped to 255)
426
+ - 'min': Minimum intensity projection
427
+ - 'mean': Average of all channels
428
+ percentiles : Sequence[tuple[float, float]] | None, optional
429
+ Per-channel percentile values (low, high) for auto-scaling. Ignored if saturation_limits is
430
+ provided. Default is (1.1, 99.9) for each.
431
+ saturation_limits : Sequence[tuple[float, float]] | None, optional
432
+ Per-channel explicit intensity limits (low, high) for scaling.
433
+ masks : Sequence[np.ndarray] | np.ndarray | None, optional
434
+ Mask array(s) to overlay on the blended result. Each mask must have the same shape as the
435
+ input arrays and dtype of bool or int32. For bool masks, True pixels are overlaid. For int32
436
+ masks, any non-zero value is overlaid.
437
+ mask_colors : Sequence[MaskColor] | MaskColor | None, optional
438
+ Color(s) for the mask overlay. Can be:
439
+ - A colormap name (uses the color at index 255)
440
+ - A hex string ('#FF00FF', 'f0f')
441
+ - An RGB tuple (R, G, B) with values 0-255
442
+ If a single color is provided, it applies to all masks.
443
+ Default is purple (128, 0, 128).
444
+ mask_alphas : Sequence[float] | float | None, optional
445
+ Alpha value(s) for mask blending (0.0-1.0). If a single value is provided, it applies
446
+ to all masks. Default is 0.5.
447
+ parallel : bool, optional
448
+ Whether to use a Rayon threadpool on the Rust side for parallel processing. Default is True.
449
+
450
+ Returns
451
+ -------
452
+ np.ndarray
453
+ Blended RGB array with shape (..., 3) and dtype uint8.
454
+
455
+ Raises
456
+ ------
457
+ ValueError
458
+ If a colormap name is not found, color format is invalid, or mask arguments are invalid.
459
+ TypeError
460
+ If masks are not numpy arrays.
461
+
462
+ Examples
463
+ --------
464
+ >>> import mergechannels as mc
465
+ >>> import numpy as np
466
+ >>> ch1 = np.random.randint(0, 256, (512, 512), dtype=np.uint8)
467
+ >>> ch2 = np.random.randint(0, 256, (512, 512), dtype=np.uint8)
468
+ >>> rgb = mc.merge(
469
+ ... [ch1, ch2],
470
+ ... ['betterBlue', 'betterOrange'],
471
+ ... blending='max',
472
+ ... saturation_limits=[(0, 255), (0, 255)],
473
+ ... )
474
+ >>> rgb.shape
475
+ (512, 512, 3)
476
+
477
+ With a mask overlay:
478
+
479
+ >>> mask = ch1 > 200 # Highlight bright pixels from channel 1
480
+ >>> rgb = mc.merge(
481
+ ... [ch1, ch2],
482
+ ... ['betterBlue', 'betterOrange'],
483
+ ... saturation_limits=[(0, 255), (0, 255)],
484
+ ... masks=[mask], mask_colors=[(255, 0, 0)], mask_alphas=[0.5]
485
+ ... )
486
+ """
487
+ cmap_names, cmap_values = zip(*[_parse_cmap_arguments(color) for color in colors])
488
+ if saturation_limits is None:
489
+ if percentiles is None:
490
+ percentiles = [(1.1, 99.9)] * len(arrs)
491
+ saturation_limits = tuple(
492
+ np.percentile(arr, ch_percentiles)
493
+ for arr, ch_percentiles in zip(arrs, percentiles) # type: ignore
494
+ )
495
+
496
+ # Get expected shape from first array
497
+ expected_shape = arrs[0].shape
498
+ for a in arrs[1:]:
499
+ if not a.shape == expected_shape:
500
+ raise ValueError(
501
+ f'Expected all input arrays to have the same shape, {a.shape} != {expected_shape}'
502
+ )
503
+
504
+ # Parse mask arguments
505
+ masks_list, colors_list, alphas_list = _parse_mask_arguments(
506
+ masks, mask_colors, mask_alphas, expected_shape=expected_shape
507
+ )
508
+
509
+ return dispatch_multi_channel(
510
+ array_references=arrs,
511
+ cmap_names=cmap_names,
512
+ cmap_values=cmap_values,
513
+ blending=blending,
514
+ limits=saturation_limits, # type: ignore
515
+ parallel=parallel,
516
+ mask_arrays=masks_list,
517
+ mask_colors=colors_list,
518
+ mask_alphas=alphas_list,
519
+ )
520
+
521
+
522
+ def get_cmap_array(name: COLORMAPS) -> np.ndarray:
523
+ """
524
+ Get the RGB values for a built-in colormap.
525
+
526
+ Parameters
527
+ ----------
528
+ name : COLORMAPS
529
+ The name of the colormap to retrieve. Use mergechannels.COLORMAPS
530
+ to see available colormap names.
531
+
532
+ Returns
533
+ -------
534
+ np.ndarray
535
+ A (256, 3) uint8 array of RGB values, where each row represents
536
+ the RGB color for that intensity level (0-255).
537
+
538
+ Raises
539
+ ------
540
+ ValueError
541
+ If the colormap name is not found.
542
+
543
+ Examples
544
+ --------
545
+ >>> import mergechannels as mc
546
+ >>> cmap = mc.get_cmap_array('betterBlue')
547
+ >>> cmap.shape
548
+ (256, 3)
549
+ >>> cmap.dtype
550
+ dtype('uint8')
551
+ """
552
+ return _get_cmap_array(name)
553
+
554
+
555
+ def get_mpl_cmap(name: COLORMAPS) -> ListedColormap:
556
+ """
557
+ Get a built-in colormap as a matplotlib ListedColormap.
558
+
559
+ Parameters
560
+ ----------
561
+ name : COLORMAPS
562
+ The name of the colormap to retrieve. Use mergechannels.COLORMAPS
563
+ to see available colormap names.
564
+
565
+ Returns
566
+ -------
567
+ matplotlib.colors.ListedColormap
568
+ A matplotlib ListedColormap object that can be used with matplotlib
569
+ plotting functions.
570
+
571
+ Raises
572
+ ------
573
+ ImportError
574
+ If matplotlib is not installed. Install it with:
575
+ ``uv pip install matplotlib`` or
576
+ ``uv pip install "mergechannels[matplotlib]>=0.5.5"``
577
+ ValueError
578
+ If the colormap name is not found.
579
+
580
+ Examples
581
+ --------
582
+ >>> import mergechannels as mc
583
+ >>> cmap = mc.get_mpl_cmap('betterBlue')
584
+ >>> cmap.name
585
+ 'betterBlue'
586
+ >>> import matplotlib.pyplot as plt
587
+ >>> plt.imshow(data, cmap=cmap) # doctest: +SKIP
588
+ """
589
+ try:
590
+ from matplotlib.colors import ListedColormap
591
+ except ImportError as e:
592
+ raise ImportError(
593
+ 'matplotlib is required for get_mpl_cmap(). '
594
+ 'Install it with: uv pip install matplotlib '
595
+ 'or uv pip install "mergechannels[matplotlib]>=0.5.5"'
596
+ ) from e
597
+
598
+ cmap_array = get_cmap_array(name)
599
+ colors = cmap_array / 255.0 # Convert from uint8 (0-255) to float (0-1) for matplotlib
600
+ return ListedColormap(colors, name=name)