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