mergechannels 0.2.0__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
Potentially problematic release.
This version of mergechannels might be problematic. Click here for more details.
- mergechannels/__init__.py +13 -0
- mergechannels/__init__.pyi +30 -0
- mergechannels/_blending.py +3 -0
- mergechannels/_internal.py +65 -0
- mergechannels/_luts.py +81 -0
- mergechannels/mergechannels.cpython-313-aarch64-linux-gnu.so +0 -0
- mergechannels/py.typed +0 -0
- mergechannels-0.2.0.dist-info/METADATA +145 -0
- mergechannels-0.2.0.dist-info/RECORD +11 -0
- mergechannels-0.2.0.dist-info/WHEEL +4 -0
- mergechannels-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Literal, Sequence
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from python.mergechannels._blending import BLENDING_OPTIONS
|
|
6
|
+
from python.mergechannels._luts import COLORMAPS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def merge(
|
|
10
|
+
arrs: np.ndarray | Sequence[np.ndarray],
|
|
11
|
+
colors: Sequence[COLORMAPS] = (),
|
|
12
|
+
blending: Literal[BLENDING_OPTIONS] = 'max',
|
|
13
|
+
saturation_limits: tuple[float, float] = (0.2, 99.8),
|
|
14
|
+
) -> np.ndarray:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
def dispatch_single_channel(
|
|
18
|
+
array_reference: np.ndarray,
|
|
19
|
+
cmap_name: str,
|
|
20
|
+
limits: tuple[float, float],
|
|
21
|
+
) -> np.ndarray:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def dispatch_multi_channel(
|
|
25
|
+
array_references: Sequence[np.ndarray],
|
|
26
|
+
cmap_names: Sequence[str],
|
|
27
|
+
blending: Literal[BLENDING_OPTIONS],
|
|
28
|
+
limits: Sequence[tuple[float, float]],
|
|
29
|
+
) -> np.ndarray:
|
|
30
|
+
...
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from mergechannels import (
|
|
6
|
+
dispatch_single_channel,
|
|
7
|
+
dispatch_multi_channel,
|
|
8
|
+
)
|
|
9
|
+
from ._luts import COLORMAPS
|
|
10
|
+
from ._blending import BLENDING_OPTIONS
|
|
11
|
+
|
|
12
|
+
def merge(
|
|
13
|
+
arrs: Sequence[np.ndarray],
|
|
14
|
+
colors: Sequence[COLORMAPS],
|
|
15
|
+
blending: BLENDING_OPTIONS = 'max',
|
|
16
|
+
saturation_limits: tuple[float, float] = (0.011, 0.999),
|
|
17
|
+
) -> np.ndarray:
|
|
18
|
+
'''
|
|
19
|
+
apply cmaps to arrays and blend the colors
|
|
20
|
+
'''
|
|
21
|
+
# region validation
|
|
22
|
+
n_arrs = len(arrs)
|
|
23
|
+
n_colors = len(colors)
|
|
24
|
+
if not n_arrs == n_colors and n_arrs > 0:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
'Expected an equal number of arrays to colorize and colormap names to apply. '
|
|
27
|
+
f'Got {n_arrs} arrays and {n_colors} colors'
|
|
28
|
+
)
|
|
29
|
+
arr_shapes = [arr.shape for arr in arrs]
|
|
30
|
+
if not len(set(arr_shapes)) == 1:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f'Expected every array to have the same shape, got {arr_shapes}'
|
|
33
|
+
)
|
|
34
|
+
if len(arr_shapes[0]) not in (2, 3):
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f'Expected every array to be 2D or 3D, got {arr_shapes[0]}'
|
|
37
|
+
)
|
|
38
|
+
arr_dtypes = [arr.dtype for arr in arrs]
|
|
39
|
+
if not len(set(arr_dtypes)) == 1:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f'Expected every array to have the same dtype, got {arr_dtypes}'
|
|
42
|
+
)
|
|
43
|
+
# endregion
|
|
44
|
+
if n_arrs == 1:
|
|
45
|
+
if arrs[0].dtype == 'uint8':
|
|
46
|
+
limits = (0, 255)
|
|
47
|
+
else:
|
|
48
|
+
low, high = np.percentile(arrs[0], np.array(saturation_limits) * 100)
|
|
49
|
+
limits = (low, high)
|
|
50
|
+
return dispatch_single_channel(
|
|
51
|
+
array_reference=arrs[0],
|
|
52
|
+
cmap_name=colors[0],
|
|
53
|
+
limits=limits,
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
if all(arr.dtype == 'uint8' for arr in arrs):
|
|
57
|
+
limits = (0, 255)
|
|
58
|
+
else:
|
|
59
|
+
limits = tuple(np.percentile(arr, np.array(saturation_limits) * 100) for arr in arrs)
|
|
60
|
+
return dispatch_multi_channel(
|
|
61
|
+
array_references=arrs,
|
|
62
|
+
cmap_names=colors,
|
|
63
|
+
blending=blending,
|
|
64
|
+
limits=limits, # type: ignore
|
|
65
|
+
)
|
mergechannels/_luts.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
COLORMAPS = Literal[
|
|
4
|
+
'Rainbow RGB',
|
|
5
|
+
'3color-RMB',
|
|
6
|
+
'Red Hot',
|
|
7
|
+
'Yellow',
|
|
8
|
+
'betterRed',
|
|
9
|
+
'blue_orange_icb',
|
|
10
|
+
'mpl-inferno',
|
|
11
|
+
'I Blue',
|
|
12
|
+
'BOP purple',
|
|
13
|
+
'betterOrange',
|
|
14
|
+
'Blue',
|
|
15
|
+
'6_shades',
|
|
16
|
+
'OIMB3',
|
|
17
|
+
'Red/Green',
|
|
18
|
+
'Turbo',
|
|
19
|
+
'mpl-viridis',
|
|
20
|
+
'smart',
|
|
21
|
+
'Thermal',
|
|
22
|
+
'OIMB2',
|
|
23
|
+
'glow',
|
|
24
|
+
'OPF orange',
|
|
25
|
+
'physics',
|
|
26
|
+
'OIMB1',
|
|
27
|
+
'betterCyan',
|
|
28
|
+
'mpl-magma',
|
|
29
|
+
'edges',
|
|
30
|
+
'OPF purple',
|
|
31
|
+
'I Yellow',
|
|
32
|
+
'16_colors',
|
|
33
|
+
'sepia',
|
|
34
|
+
'ICA2',
|
|
35
|
+
'I Forest',
|
|
36
|
+
'ICA3',
|
|
37
|
+
'BOP orange',
|
|
38
|
+
'I Red',
|
|
39
|
+
'3color-CGY',
|
|
40
|
+
'I Green',
|
|
41
|
+
'Fire',
|
|
42
|
+
'Magenta Hot',
|
|
43
|
+
'Orange Hot',
|
|
44
|
+
'unionjack',
|
|
45
|
+
'I Cyan',
|
|
46
|
+
'Cyan Hot',
|
|
47
|
+
'I Magenta',
|
|
48
|
+
'Grays',
|
|
49
|
+
'Cyan',
|
|
50
|
+
'phase',
|
|
51
|
+
'gem',
|
|
52
|
+
'OPF fresh',
|
|
53
|
+
'betterGreen',
|
|
54
|
+
'3color-BMR',
|
|
55
|
+
'betterBlue',
|
|
56
|
+
'Magenta',
|
|
57
|
+
'Red',
|
|
58
|
+
'I Bordeaux',
|
|
59
|
+
'glasbey',
|
|
60
|
+
'glasbey_on_dark',
|
|
61
|
+
'mpl-plasma',
|
|
62
|
+
'3color-YGC',
|
|
63
|
+
'Green',
|
|
64
|
+
'ICA',
|
|
65
|
+
'Yellow Hot',
|
|
66
|
+
'thallium',
|
|
67
|
+
'Spectrum',
|
|
68
|
+
'royal',
|
|
69
|
+
'glasbey_inverted',
|
|
70
|
+
'betterYellow',
|
|
71
|
+
'HiLo',
|
|
72
|
+
'cool',
|
|
73
|
+
'Green Fire Blue',
|
|
74
|
+
'BOP blue',
|
|
75
|
+
'thal',
|
|
76
|
+
'Ice',
|
|
77
|
+
'brgbcmyw',
|
|
78
|
+
'I Purple',
|
|
79
|
+
'3-3-2 RGB',
|
|
80
|
+
'5_ramps',
|
|
81
|
+
]
|
|
Binary file
|
mergechannels/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mergechannels
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: numpy>1.25.0
|
|
15
|
+
Provides-Extra: nvim
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Summary: Apply and merge colormaps
|
|
18
|
+
Author-email: Zac Swider <zac.swider@gmail.com>
|
|
19
|
+
License: MIT
|
|
20
|
+
Requires-Python: >=3.9, <=3.13
|
|
21
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
22
|
+
|
|
23
|
+
[](https://github.com/zacswider/mergechannels/actions/workflows/CI.yml)
|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
# mergechannels
|
|
29
|
+
|
|
30
|
+
This project was originally conceived because I often find myself wanting to apply and blend colormaps to images while working from Python, and for historical reasons many of my favorite colormaps are distributed as [FIJI](https://imagej.net/software/fiji/) lookup tables. I also care about things likes speed and memory usage (e.g., not casting large arrays to floating point dtypes, not creating multiple whole arrays just to add them together), so I was interested in seeing if I could at least match matplotlib's colormapping performance with my own hand-rolled colorizer in Rust.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Install pre-compiled binaries from [PyPI](https://pypi.org/project/mergechannels/):
|
|
37
|
+
```bash
|
|
38
|
+
pip install mergechannels
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Build from source on your own machine:
|
|
42
|
+
```bash
|
|
43
|
+
pip install git+https://github.com/zacswider/mergechannels.git
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
*NOTE*: scikit-image is not a dependency of this project, but is used in the examples below to fetch images.
|
|
48
|
+
|
|
49
|
+
### colorize a single image
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from skimage import data
|
|
53
|
+
import mergechannels as mc
|
|
54
|
+
|
|
55
|
+
img = data.camera()
|
|
56
|
+
colorized = mc.apply_color_map(img, 'Red/Green')
|
|
57
|
+
fig, axes = plt.subplots(1, 2, figsize=(6, 3), dpi=150)
|
|
58
|
+
for ax in axes: ax.axis('off')
|
|
59
|
+
(a, b) = axes
|
|
60
|
+
a.imshow(img, cmap='gray')
|
|
61
|
+
b.imshow(colorized)
|
|
62
|
+
plt.show()
|
|
63
|
+
print(colorized.shape, colorized.dtype)
|
|
64
|
+
>> (512, 512, 3) uint8
|
|
65
|
+
```
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
### apply a different colormap to each channel
|
|
70
|
+
```python
|
|
71
|
+
from skimage import data
|
|
72
|
+
import matplotlib.pyplot as plt
|
|
73
|
+
import mergechannels as mc
|
|
74
|
+
|
|
75
|
+
cells, nuclei = data.cells3d().max(axis=0)
|
|
76
|
+
assert cells.dtype == 'uint16' and nuclei.dtype == 'uint16'
|
|
77
|
+
fig, axes = plt.subplots(1, 2, figsize=(3, 6), dpi=300)
|
|
78
|
+
for ax in axes.ravel(): ax.axis('off')
|
|
79
|
+
(a, b) = axes.ravel()
|
|
80
|
+
a.imshow(mc.merge([cells, nuclei],['Orange Hot', 'Cyan Hot']))
|
|
81
|
+
b.imshow(mc.merge([cells, nuclei],['I Blue', 'I Forest'], blending='min'))
|
|
82
|
+
fig.tight_layout()
|
|
83
|
+
plt.show()
|
|
84
|
+
```
|
|
85
|
+

|
|
86
|
+
|
|
87
|
+
### apply a colormap to a whole stack
|
|
88
|
+
```python
|
|
89
|
+
from skimage import data
|
|
90
|
+
from matplotlib import pyplot as plt
|
|
91
|
+
import mergechannels as mc
|
|
92
|
+
|
|
93
|
+
volume = data.cells3d()
|
|
94
|
+
cells = volume[:, 0]
|
|
95
|
+
nuclei = volume[:, 1]
|
|
96
|
+
merged = mc.merge([cells, nuclei],['Orange Hot', 'Cyan Hot'])
|
|
97
|
+
plt.imshow(merged[24]); plt.show()
|
|
98
|
+
```
|
|
99
|
+

|
|
100
|
+
|
|
101
|
+
### adjust the saturation limits when applying colormaps
|
|
102
|
+
``` python
|
|
103
|
+
from skimage import data
|
|
104
|
+
import matplotlib.pyplot as plt
|
|
105
|
+
import mergechannels as mc
|
|
106
|
+
|
|
107
|
+
cells, nuclei = data.cells3d().max(axis=0)
|
|
108
|
+
channels = [cells, nuclei]
|
|
109
|
+
colormaps = ['I Blue', 'I Forest']
|
|
110
|
+
fig, axes = plt.subplots(1, 2, figsize=(3, 6), dpi=300)
|
|
111
|
+
for ax in axes.ravel(): ax.axis('off')
|
|
112
|
+
(a, b) = axes.ravel()
|
|
113
|
+
a.imshow(mc.merge(channels, colormaps, blending='min'))
|
|
114
|
+
b.imshow(
|
|
115
|
+
mc.merge(
|
|
116
|
+
channels,
|
|
117
|
+
colormaps,
|
|
118
|
+
blending='min',
|
|
119
|
+
saturation_limits=(
|
|
120
|
+
0.01, # bottom 1% of pixels set to black point
|
|
121
|
+
0.97, # top 3% of pixels set to white point
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
fig.tight_layout()
|
|
126
|
+
plt.show()
|
|
127
|
+
```
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Roadmap
|
|
132
|
+
mergechannels is currently incredibly simple. It can apply one or more colormaps to one or more 2D and 3D 8-bit or 16-bit images and that's it.
|
|
133
|
+
- Add support for any numerical dtype
|
|
134
|
+
- Add option to return any colormap as a matplotlib colormap
|
|
135
|
+
- Add option to pass external colormaps to mergechannels
|
|
136
|
+
- Add support for directly passing matplotlib colormaps instead of colormap names
|
|
137
|
+
- Parallelize colormap application on large images (if it's helpful)
|
|
138
|
+
- Add option to overlay binary or instance masks onto colorized images
|
|
139
|
+
|
|
140
|
+
## Acknowledgements
|
|
141
|
+
|
|
142
|
+
There are other great colormapping libraries available (e.g., [microfilm](https://github.com/guiwitz/microfilm), [cmap](https://github.com/pyapp-kit/cmap)) that are more feature-rich than this one, but which don't address my goals. My hope is that this project can fill an un-met niche and otherwise maintain full compatibility with these and similar libraries.
|
|
143
|
+
|
|
144
|
+
This project incorporates a number of colormaps that were hand-crafted by Christophe Leterrier and were originally distributed here under the MIT license: https://github.com/cleterrier/ChrisLUTs
|
|
145
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
mergechannels-0.2.0.dist-info/METADATA,sha256=t-okrnqyvIiZxuLqTlubmCkXCxKz9U43u7bhSIEiNBc,5725
|
|
2
|
+
mergechannels-0.2.0.dist-info/WHEEL,sha256=uhW5MjwJWhgG7tRveeDv2-hvQtvaPxbnARBNpsAjvcw,131
|
|
3
|
+
mergechannels-0.2.0.dist-info/licenses/LICENSE,sha256=csvD60rgtSorbYEM3f8867qNyPCzmIXyFNj8h01Bd6c,1071
|
|
4
|
+
mergechannels/__init__.py,sha256=TTNiDGOOJ6_-yR-qvX_nXph646Sz6-MEORc8ItksCRE,232
|
|
5
|
+
mergechannels/__init__.pyi,sha256=tiDx6v14J0cedFdk6fzVRRyA21b4Z8eB_rtSTUjI804,749
|
|
6
|
+
mergechannels/_blending.py,sha256=mE5Cr9wvVJRcwfymg_UUZUjdIKx2mldfLU8QJT95bVM,84
|
|
7
|
+
mergechannels/_internal.py,sha256=ZcdBUFEq8k2Ve_ASOZLTuPBaFEEPqadc9LqAGmBqOas,1773
|
|
8
|
+
mergechannels/_luts.py,sha256=DxwCMBnAAJc1ZX-D4cq8HabW-sLuBQpUqOWMWXqt_84,1035
|
|
9
|
+
mergechannels/mergechannels.cpython-313-aarch64-linux-gnu.so,sha256=PQSvshvIjLQX899INU6ZvIblMF5obgU8H7QiHEy61tg,901680
|
|
10
|
+
mergechannels/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
mergechannels-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Zachary Swider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|