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')