Mercurial > repos > imgteam > color_deconvolution
comparison color_deconvolution.py @ 2:387414aa6496 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:13 +0000 |
parents | 29110ca1b63a |
children |
comparison
equal
deleted
inserted
replaced
1:29110ca1b63a | 2:387414aa6496 |
---|---|
1 import argparse | 1 import argparse |
2 import sys | 2 import sys |
3 import warnings | 3 import warnings |
4 | |
5 import giatools.io | |
4 import numpy as np | 6 import numpy as np |
7 import skimage.color | |
5 import skimage.io | 8 import skimage.io |
6 import skimage.color | |
7 import skimage.util | 9 import skimage.util |
8 from sklearn.decomposition import PCA, NMF, FastICA, FactorAnalysis | 10 import tifffile |
11 from sklearn.decomposition import FactorAnalysis, FastICA, NMF, PCA | |
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 ]) | |
9 | 19 |
10 convOptions = { | 20 convOptions = { |
11 'hed2rgb' : lambda img_raw: skimage.color.hed2rgb(img_raw), | 21 # General color space conversion operations |
12 'hsv2rgb' : lambda img_raw: skimage.color.hsv2rgb(img_raw), | 22 'hed2rgb': lambda img_raw: skimage.color.hed2rgb(img_raw), |
13 'lab2lch' : lambda img_raw: skimage.color.lab2lch(img_raw), | 23 'hsv2rgb': lambda img_raw: skimage.color.hsv2rgb(img_raw), |
14 'lab2rgb' : lambda img_raw: skimage.color.lab2rgb(img_raw), | 24 'lab2lch': lambda img_raw: skimage.color.lab2lch(img_raw), |
15 'lab2xyz' : lambda img_raw: skimage.color.lab2xyz(img_raw), | 25 'lab2rgb': lambda img_raw: skimage.color.lab2rgb(img_raw), |
16 'lch2lab' : lambda img_raw: skimage.color.lch2lab(img_raw), | 26 'lab2xyz': lambda img_raw: skimage.color.lab2xyz(img_raw), |
17 'luv2rgb' : lambda img_raw: skimage.color.luv2rgb(img_raw), | 27 'lch2lab': lambda img_raw: skimage.color.lch2lab(img_raw), |
18 'luv2xyz' : lambda img_raw: skimage.color.luv2xyz(img_raw), | 28 'luv2rgb': lambda img_raw: skimage.color.luv2rgb(img_raw), |
19 'rgb2hed' : lambda img_raw: skimage.color.rgb2hed(img_raw), | 29 'luv2xyz': lambda img_raw: skimage.color.luv2xyz(img_raw), |
20 'rgb2hsv' : lambda img_raw: skimage.color.rgb2hsv(img_raw), | 30 'rgb2hed': lambda img_raw: skimage.color.rgb2hed(img_raw), |
21 'rgb2lab' : lambda img_raw: skimage.color.rgb2lab(img_raw), | 31 'rgb2hsv': lambda img_raw: skimage.color.rgb2hsv(img_raw), |
22 'rgb2luv' : lambda img_raw: skimage.color.rgb2luv(img_raw), | 32 'rgb2lab': lambda img_raw: skimage.color.rgb2lab(img_raw), |
23 'rgb2rgbcie' : lambda img_raw: skimage.color.rgb2rgbcie(img_raw), | 33 'rgb2luv': lambda img_raw: skimage.color.rgb2luv(img_raw), |
24 'rgb2xyz' : lambda img_raw: skimage.color.rgb2xyz(img_raw), | 34 'rgb2rgbcie': lambda img_raw: skimage.color.rgb2rgbcie(img_raw), |
25 #'rgb2ycbcr' : lambda img_raw: skimage.color.rgb2ycbcr(img_raw), | 35 'rgb2xyz': lambda img_raw: skimage.color.rgb2xyz(img_raw), |
26 #'rgb2yiq' : lambda img_raw: skimage.color.rgb2yiq(img_raw), | 36 'rgbcie2rgb': lambda img_raw: skimage.color.rgbcie2rgb(img_raw), |
27 #'rgb2ypbpr' : lambda img_raw: skimage.color.rgb2ypbpr(img_raw), | 37 'xyz2lab': lambda img_raw: skimage.color.xyz2lab(img_raw), |
28 #'rgb2yuv' : lambda img_raw: skimage.color.rgb2yuv(img_raw), | 38 'xyz2luv': lambda img_raw: skimage.color.xyz2luv(img_raw), |
29 #'rgba2rgb' : lambda img_raw: skimage.color.rgba2rgb(img_raw), | 39 'xyz2rgb': lambda img_raw: skimage.color.xyz2rgb(img_raw), |
30 'rgbcie2rgb' : lambda img_raw: skimage.color.rgbcie2rgb(img_raw), | 40 |
31 'xyz2lab' : lambda img_raw: skimage.color.xyz2lab(img_raw), | 41 # Color deconvolution operations |
32 'xyz2luv' : lambda img_raw: skimage.color.xyz2luv(img_raw), | 42 'hed_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hed_from_rgb), |
33 'xyz2rgb' : lambda img_raw: skimage.color.xyz2rgb(img_raw), | 43 'hdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hdx_from_rgb), |
34 #'ycbcr2rgb' : lambda img_raw: skimage.color.ycbcr2rgb(img_raw), | 44 'fgx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.fgx_from_rgb), |
35 #'yiq2rgb' : lambda img_raw: skimage.color.yiq2rgb(img_raw), | 45 'bex_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bex_from_rgb), |
36 #'ypbpr2rgb' : lambda img_raw: skimage.color.ypbpr2rgb(img_raw), | 46 'rbd_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.rbd_from_rgb), |
37 #'yuv2rgb' : lambda img_raw: skimage.color.yuv2rgb(img_raw), | 47 'gdx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.gdx_from_rgb), |
38 | 48 'hax_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hax_from_rgb), |
39 'rgb_from_hed' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hed), | 49 'bro_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bro_from_rgb), |
40 'rgb_from_hdx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hdx), | 50 'bpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bpx_from_rgb), |
41 'rgb_from_fgx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_fgx), | 51 'ahx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.ahx_from_rgb), |
42 'rgb_from_bex' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bex), | 52 'hpx_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hpx_from_rgb), |
43 'rgb_from_rbd' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_rbd), | 53 |
44 'rgb_from_gdx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_gdx), | 54 # Recomposition operations (reverse color deconvolution) |
45 'rgb_from_hax' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hax), | 55 'rgb_from_hed': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hed), |
46 'rgb_from_bro' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bro), | 56 'rgb_from_hdx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hdx), |
47 'rgb_from_bpx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bpx), | 57 'rgb_from_fgx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_fgx), |
48 'rgb_from_ahx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_ahx), | 58 'rgb_from_bex': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bex), |
49 'rgb_from_hpx' : lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hpx), | 59 'rgb_from_rbd': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_rbd), |
50 | 60 'rgb_from_gdx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_gdx), |
51 'hed_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hed_from_rgb), | 61 'rgb_from_hax': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hax), |
52 'hdx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hdx_from_rgb), | 62 'rgb_from_bro': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bro), |
53 'fgx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.fgx_from_rgb), | 63 'rgb_from_bpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_bpx), |
54 'bex_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bex_from_rgb), | 64 'rgb_from_ahx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_ahx), |
55 'rbd_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.rbd_from_rgb), | 65 'rgb_from_hpx': lambda img_raw: skimage.color.combine_stains(img_raw, skimage.color.rgb_from_hpx), |
56 'gdx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.gdx_from_rgb), | 66 |
57 'hax_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hax_from_rgb), | 67 # Custom color deconvolution and recomposition operations |
58 'bro_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bro_from_rgb), | 68 'rgb_from_he': lambda img_raw: skimage.color.combine_stains(img_raw, rgb_from_he), |
59 'bpx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.bpx_from_rgb), | 69 'he_from_rgb': lambda img_raw: skimage.color.separate_stains(img_raw, np.linalg.inv(rgb_from_he)), |
60 'ahx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.ahx_from_rgb), | 70 |
61 'hpx_from_rgb' : lambda img_raw: skimage.color.separate_stains(img_raw, skimage.color.hpx_from_rgb), | 71 # Unsupervised machine learning-based operations |
62 | 72 'pca': lambda img_raw: np.reshape(PCA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
63 'pca' : lambda img_raw: np.reshape(PCA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 73 [img_raw.shape[0], img_raw.shape[1], -1]), |
64 [img_raw.shape[0],img_raw.shape[1],-1]), | 74 'nmf': lambda img_raw: np.reshape(NMF(n_components=3, init='nndsvda').fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
65 'nmf' : lambda img_raw: np.reshape(NMF(n_components=3, init='nndsvda').fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 75 [img_raw.shape[0], img_raw.shape[1], -1]), |
66 [img_raw.shape[0],img_raw.shape[1],-1]), | 76 'ica': lambda img_raw: np.reshape(FastICA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
67 'ica' : lambda img_raw: np.reshape(FastICA(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 77 [img_raw.shape[0], img_raw.shape[1], -1]), |
68 [img_raw.shape[0],img_raw.shape[1],-1]), | 78 'fa': lambda img_raw: np.reshape(FactorAnalysis(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), |
69 'fa' : lambda img_raw: np.reshape(FactorAnalysis(n_components=3).fit_transform(np.reshape(img_raw, [-1, img_raw.shape[2]])), | 79 [img_raw.shape[0], img_raw.shape[1], -1]) |
70 [img_raw.shape[0],img_raw.shape[1],-1]) | |
71 } | 80 } |
72 | 81 |
73 parser = argparse.ArgumentParser() | 82 parser = argparse.ArgumentParser() |
74 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') |
75 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)') |
76 parser.add_argument('conv_type', choices=convOptions.keys(), help='conversion type') | 85 parser.add_argument('conv_type', choices=convOptions.keys(), help='conversion type') |
77 args = parser.parse_args() | 86 parser.add_argument('--isolate_channel', type=int, help='set all other channels to zero (1-3)', default=0) |
87 args = parser.parse_args() | |
78 | 88 |
79 img_in = skimage.io.imread(args.input_file.name)[:,:,0:3] | 89 # Read and normalize the input image as TZYXC |
80 res = convOptions[args.conv_type](img_in) | 90 img_in = giatools.io.imread(args.input_file.name) |
81 res[res<-1]=-1 | 91 |
82 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) | |
83 | 112 |
84 with warnings.catch_warnings(): | 113 with warnings.catch_warnings(): |
85 warnings.simplefilter("ignore") | 114 warnings.simplefilter('ignore') |
86 res = skimage.util.img_as_uint(res) #Attention: precision loss | 115 tifffile.imwrite(args.out_file.name, result) |
87 skimage.io.imsave(args.out_file.name, res, plugin='tifffile') |