Mercurial > repos > imgteam > color_deconvolution
comparison color_deconvolution.py @ 4:5bd113d38acc draft default tip
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/color_deconvolution/ commit f546b3cd5cbd3a8613cd517975c7ad1d1f83514e
author | imgteam |
---|---|
date | Thu, 06 Mar 2025 18:12:27 +0000 |
parents | 612aa1478fe1 |
children |
comparison
equal
deleted
inserted
replaced
3:be70a57d7174 | 4:5bd113d38acc |
---|---|
1 import argparse | 1 import argparse |
2 import sys | 2 import sys |
3 import warnings | 3 import warnings |
4 | 4 |
5 import giatools.io | |
5 import numpy as np | 6 import numpy as np |
6 import skimage.color | 7 import skimage.color |
7 import skimage.io | 8 import skimage.io |
8 import skimage.util | 9 import skimage.util |
10 import tifffile | |
9 from sklearn.decomposition import FactorAnalysis, FastICA, NMF, PCA | 11 from sklearn.decomposition import FactorAnalysis, FastICA, NMF, PCA |
10 | 12 |
13 # Stain separation matrix for H&E color deconvolution, extracted from ImageJ/FIJI | |
14 rgb_from_he = np.array([ | |
15 [0.64431860, 0.7166757, 0.26688856], | |
16 [0.09283128, 0.9545457, 0.28324000], | |
17 [0.63595444, 0.0010000, 0.77172660], | |
18 ]) | |
19 | |
11 convOptions = { | 20 convOptions = { |
21 # General color space conversion operations | |
12 'hed2rgb': lambda img_raw: skimage.color.hed2rgb(img_raw), | 22 'hed2rgb': lambda img_raw: skimage.color.hed2rgb(img_raw), |
13 'hsv2rgb': lambda img_raw: skimage.color.hsv2rgb(img_raw), | 23 'hsv2rgb': lambda img_raw: skimage.color.hsv2rgb(img_raw), |
14 'lab2lch': lambda img_raw: skimage.color.lab2lch(img_raw), | 24 'lab2lch': lambda img_raw: skimage.color.lab2lch(img_raw), |
15 'lab2rgb': lambda img_raw: skimage.color.lab2rgb(img_raw), | 25 'lab2rgb': lambda img_raw: skimage.color.lab2rgb(img_raw), |
16 'lab2xyz': lambda img_raw: skimage.color.lab2xyz(img_raw), | 26 'lab2xyz': lambda img_raw: skimage.color.lab2xyz(img_raw), |
26 'rgbcie2rgb': lambda img_raw: skimage.color.rgbcie2rgb(img_raw), | 36 'rgbcie2rgb': lambda img_raw: skimage.color.rgbcie2rgb(img_raw), |
27 'xyz2lab': lambda img_raw: skimage.color.xyz2lab(img_raw), | 37 'xyz2lab': lambda img_raw: skimage.color.xyz2lab(img_raw), |
28 'xyz2luv': lambda img_raw: skimage.color.xyz2luv(img_raw), | 38 'xyz2luv': lambda img_raw: skimage.color.xyz2luv(img_raw), |
29 'xyz2rgb': lambda img_raw: skimage.color.xyz2rgb(img_raw), | 39 'xyz2rgb': lambda img_raw: skimage.color.xyz2rgb(img_raw), |
30 | 40 |
41 # Color deconvolution operations | |
42 'hed_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hed_from_rgb), | |
43 'hdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hdx_from_rgb), | |
44 'fgx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.fgx_from_rgb), | |
45 'bex_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bex_from_rgb), | |
46 'rbd_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.rbd_from_rgb), | |
47 'gdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.gdx_from_rgb), | |
48 'hax_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hax_from_rgb), | |
49 'bro_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bro_from_rgb), | |
50 'bpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bpx_from_rgb), | |
51 'ahx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.ahx_from_rgb), | |
52 'hpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hpx_from_rgb), | |
53 | |
54 # Recomposition operations (reverse color deconvolution) | |
31 'rgb_from_hed': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hed), | 55 'rgb_from_hed': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hed), |
32 'rgb_from_hdx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hdx), | 56 'rgb_from_hdx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hdx), |
33 'rgb_from_fgx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_fgx), | 57 'rgb_from_fgx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_fgx), |
34 'rgb_from_bex': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bex), | 58 'rgb_from_bex': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bex), |
35 'rgb_from_rbd': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_rbd), | 59 'rgb_from_rbd': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_rbd), |
38 'rgb_from_bro': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bro), | 62 'rgb_from_bro': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bro), |
39 'rgb_from_bpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bpx), | 63 'rgb_from_bpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bpx), |
40 'rgb_from_ahx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_ahx), | 64 'rgb_from_ahx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_ahx), |
41 'rgb_from_hpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hpx), | 65 'rgb_from_hpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hpx), |
42 | 66 |
43 'hed_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hed_from_rgb), | 67 # Custom color deconvolution and recomposition operations |
44 'hdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hdx_from_rgb), | 68 'rgb_from_he': lambda img_raw: skimage.color.combine_stains(img_raw, rgb_from_he), |
45 'fgx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.fgx_from_rgb), | 69 'he_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, np.linalg.inv(rgb_from_he)), |
46 'bex_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bex_from_rgb), | |
47 'rbd_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.rbd_from_rgb), | |
48 'gdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.gdx_from_rgb), | |
49 'hax_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hax_from_rgb), | |
50 'bro_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bro_from_rgb), | |
51 'bpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bpx_from_rgb), | |
52 'ahx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.ahx_from_rgb), | |
53 'hpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hpx_from_rgb), | |
54 | 70 |
71 # Unsupervised machine learning-based operations | |
55 'pca': lambda img_raw: np.reshape(PCA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 72 'pca': lambda img_raw: np.reshape(PCA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
56 [img_raw.shape[0], img_raw.shape[1], -1]), | 73 [img_raw.shape[0], img_raw.shape[1], -1]), |
57 'nmf': lambda img_raw: np.reshape(NMF(n_components=3, init='nndsvda').fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 74 'nmf': lambda img_raw: np.reshape(NMF(n_components=3, init='nndsvda').fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
58 [img_raw.shape[0], img_raw.shape[1], -1]), | 75 [img_raw.shape[0], img_raw.shape[1], -1]), |
59 'ica': lambda img_raw: np.reshape(FastICA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 76 'ica': lambda img_raw: np.reshape(FastICA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
64 | 81 |
65 parser = argparse.ArgumentParser() | 82 parser = argparse.ArgumentParser() |
66 parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file') | 83 parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file') |
67 parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (TIFF)') | 84 parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (TIFF)') |
68 parser.add_argument('conv_type', choices=convOptions.keys(), help='conversion type') | 85 parser.add_argument('conv_type', choices=convOptions.keys(), help='conversion type') |
86 parser.add_argument('--isolate_channel', type=int, help='set all other channels to zero (1-3)', default=0) | |
69 args = parser.parse_args() | 87 args = parser.parse_args() |
70 | 88 |
71 img_in = skimage.io.imread(args.input_file.name)[:, :, 0:3] | 89 # Read and normalize the input image as TZYXC |
72 res = convOptions[args.conv_type](img_in) | 90 img_in = giatools.io.imread(args.input_file.name) |
73 res[res < -1] = -1 | 91 |
74 res[res > +1] = +1 | 92 # Verify input image |
93 assert img_in.shape[0] == 1, f'Image must have 1 frame (it has {img_in.shape[0]} frames)' | |
94 assert img_in.shape[1] == 1, f'Image must have 1 slice (it has {img_in.shape[1]} slices)' | |
95 assert img_in.shape[4] == 3, f'Image must have 3 channels (it has {img_in.shape[4]} channels)' | |
96 | |
97 # Normalize the image from TZYXC to YXC | |
98 img_in = img_in.squeeze() | |
99 assert img_in.ndim == 3 | |
100 | |
101 # Apply channel isolation | |
102 if args.isolate_channel: | |
103 for ch in range(3): | |
104 if ch + 1 != args.isolate_channel: | |
105 img_in[:, :, ch] = 0 | |
106 | |
107 result = convOptions[args.conv_type](img_in) | |
108 | |
109 # It is sufficient to store 32bit floating point data, the precision loss is tolerable | |
110 if result.dtype == np.float64: | |
111 result = result.astype(np.float32) | |
75 | 112 |
76 with warnings.catch_warnings(): | 113 with warnings.catch_warnings(): |
77 warnings.simplefilter('ignore') | 114 warnings.simplefilter('ignore') |
78 res = skimage.util.img_as_uint(res) # Attention: precision loss | 115 tifffile.imwrite(args.out_file.name, result) |
79 skimage.io.imsave(args.out_file.name, res, plugin='tifffile') |