├── MyOutput.png ├── FijiOutput.jpg ├── MyOutputFull.png ├── Tumor_CD31_HiRes.png ├── Tumor_CD31_LoRes.png ├── SeparateStains.m ├── mask9toRGB.m ├── normalizeImage.m ├── RecombineStains.m ├── LICENSE ├── ColorDeconvolutionDemo.m └── readme.md /MyOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/ColorDeconvolutionMatlab/HEAD/MyOutput.png -------------------------------------------------------------------------------- /FijiOutput.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/ColorDeconvolutionMatlab/HEAD/FijiOutput.jpg -------------------------------------------------------------------------------- /MyOutputFull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/ColorDeconvolutionMatlab/HEAD/MyOutputFull.png -------------------------------------------------------------------------------- /Tumor_CD31_HiRes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/ColorDeconvolutionMatlab/HEAD/Tumor_CD31_HiRes.png -------------------------------------------------------------------------------- /Tumor_CD31_LoRes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/ColorDeconvolutionMatlab/HEAD/Tumor_CD31_LoRes.png -------------------------------------------------------------------------------- /SeparateStains.m: -------------------------------------------------------------------------------- 1 | % color deconvolution project by Jakob Nikolas Kather, 2015 2 | % contact: www.kather.me 3 | 4 | function imageOut = SeparateStains(imageRGB, Matrix) 5 | 6 | % convert input image to double precision float 7 | % add 2 to avoid artifacts of log transformation 8 | imageRGB = double(imageRGB)+2; 9 | 10 | % perform color deconvolution 11 | imageOut = reshape(-log(imageRGB),[],3) * Matrix; 12 | imageOut = reshape(imageOut, size(imageRGB)); 13 | 14 | % post-processing 15 | imageOut = normalizeImage(imageOut,'stretch'); 16 | end 17 | -------------------------------------------------------------------------------- /mask9toRGB.m: -------------------------------------------------------------------------------- 1 | function [rgbout, currstats] = mask9toRGB(mask,colors) 2 | 3 | rgbout = zeros(size(mask,1),size(mask,2),3); 4 | 5 | numchannel = size(mask,3); 6 | currstats = nan(numchannel,1); 7 | 8 | % iterate channels 9 | for i=1:numchannel 10 | 11 | 12 | basecolor = zeros(1,1,3); 13 | basecolor(1,1,:) = colors(i,:); 14 | currActivation = mask(:,:,i); 15 | addRGB = currActivation .* basecolor; 16 | 17 | rgbout = rgbout + addRGB; 18 | 19 | % calculate mean activation for each class 20 | currstats(i) = mean(currActivation(:)); 21 | 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /normalizeImage.m: -------------------------------------------------------------------------------- 1 | % color deconvolution project by Jakob Nikolas Kather, 2015 2 | % contact: www.kather.me 3 | 4 | function imageOut = normalizeImage(imageIn, opt) 5 | 6 | imageOut = imageIn; 7 | for i=1:size(imageIn,3) 8 | Ch = imageOut(:,:,i); 9 | 10 | % normalize output range to 0...1 11 | imageOut(:,:,i) = (imageOut(:,:,i)-min(Ch(:)))/(max(Ch(:)-min(Ch(:)))); 12 | 13 | % invert image 14 | imageOut(:,:,i) = 1 - imageOut(:,:,i); 15 | 16 | % optional: stretch histogram 17 | if strcmp(opt,'stretch') 18 | imageOut(:,:,i) = imadjust(imageOut(:,:,i),stretchlim(imageOut(:,:,i)),[]); 19 | end 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /RecombineStains.m: -------------------------------------------------------------------------------- 1 | % color deconvolution project by Jakob Nikolas Kather, 2015 2 | % contact: www.kather.me 3 | 4 | % caution: recombination of low contrast images does not preserve 5 | % low contrast, but enhances contrast in each channel. This should 6 | % be corrected. 7 | 8 | function imageOut = RecombineStains(imageIn, Matrix) 9 | 10 | lo = min(imageIn(:)); 11 | hi = max(imageIn(:)); 12 | 13 | if ~(lo>0) && ~(hi<1) % image range is 0...1 -> continue 14 | if strcmp(class(imageIn),'double') % image is double -> continue 15 | % input is alright, continue 16 | imageOut = -reshape(imageIn,[],3) * Matrix; 17 | imageOut = exp(imageOut); 18 | imageOut = reshape(imageOut, size(imageIn)); 19 | imageOut = normalizeImage(imageOut, 'stretch'); 20 | else % image is not double 21 | warning('image is not double') 22 | imageOut = imageIn; 23 | end 24 | else % image range is below 0 or above 1 25 | warning('image out of bounds') 26 | imageOut = imageIn; 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jakob Nikolas Kather 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. 22 | 23 | -------------------------------------------------------------------------------- /ColorDeconvolutionDemo.m: -------------------------------------------------------------------------------- 1 | % color deconvolution project by Jakob Nikolas Kather, 2015 2 | % contact: www.kather.me 3 | 4 | % initialize 5 | format compact, close all, clear all, clc; 6 | 7 | % specify source image 8 | % this image is from www.proteinatlas.com, used in accordance with the license 9 | % found under http://www.proteinatlas.org/about/datausage 10 | %imageURL = 'http://www.proteinatlas.org/images/20416/45828_A_4_7_rna_selected.jpg'; 11 | imageURL = 'Tumor_CD31_HiRes.png'; 12 | imageRGB = imread(imageURL); 13 | 14 | % set of standard values for stain vectors (from python scikit) 15 | % He = [0.65; 0.70; 0.29]; 16 | % Eo = [0.07; 0.99; 0.11]; 17 | % DAB = [0.27; 0.57; 0.78]; 18 | 19 | % alternative set of standard values (HDAB from Fiji) 20 | He = [ 0.6500286; 0.704031; 0.2860126 ]; 21 | DAB = [ 0.26814753; 0.57031375; 0.77642715]; 22 | Res = [ 0.7110272; 0.42318153; 0.5615672 ]; % residual 23 | 24 | % combine stain vectors to deconvolution matrix 25 | HDABtoRGB = [He/norm(He) DAB/norm(DAB) Res/norm(Res)]'; 26 | RGBtoHDAB = inv(HDABtoRGB); 27 | 28 | % separate stains = perform color deconvolution 29 | tic 30 | imageHDAB = SeparateStains(imageRGB, RGBtoHDAB); 31 | toc 32 | 33 | % % show images 34 | fig1 = figure(); 35 | set(gcf,'color','w'); 36 | subplot(2,4,1); imshow(imageRGB); title('Original'); 37 | subplot(2,4,2); imshow(imageHDAB(:,:,1),[]); title('Hematoxylin'); 38 | subplot(2,4,3); imshow(imageHDAB(:,:,2),[]); title('DAB'); 39 | subplot(2,4,4); imshow(imageHDAB(:,:,3),[]); title('Residual'); 40 | 41 | subplot(2,4,5); imhist(rgb2gray(imageRGB)); title('Original'); 42 | subplot(2,4,6); imhist(imageHDAB(:,:,1)); title('Hematoxylin'); 43 | subplot(2,4,7); imhist(imageHDAB(:,:,2)); title('DAB'); 44 | subplot(2,4,8); imhist(imageHDAB(:,:,3)); title('Residual'); 45 | 46 | 47 | % combine stains = restore the original image 48 | % tic 49 | % imageRGB_restored = RecombineStains(imageHDAB, HDABtoRGB); 50 | % toc 51 | 52 | % fig2 = figure() 53 | % subplot(2,2,1); imshow(imageRGB); title('Orig'); 54 | % subplot(2,2,2); imshow(imageRGB_restored); title('restored'); -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Color deconvolution in MATLAB 2 | 3 | ## Background 4 | 5 | Color deconvolution was introduced by Ruifrok et al. in 2001 [1] and describes a method to extract stain intensities from RGB images of histological slides. Color deconvolution is widely used for image processing in histology and there are very efficient Fiji and Python implementation available (see below, [2]). This repository provides an efficient Matlab implementation of color deconvolution. The code is partly based on the python implementation in [scikit-image](https://github.com/scikit-image/scikit-image/blob/master/skimage/color/colorconv.py). 6 | 7 | ## Example 8 | 9 | ### Original image preview 10 | ![Original image thumbnail](Tumor_CD31_LoRes.png "Original image thumbnail") 11 | 12 | ### Fiji Output 13 | ![Fiji Output](FijiOutput.jpg "Fiji Output") 14 | Panels: Original - Hematoxylin - DAB - residual 15 | 16 | The source image is stained with Hematoxylin and DAB. The third channel represents the residual and should be empty. Here, the standard values for the deconvolution matrix fit pretty well and the residual is small. 17 | 18 | ### Output of my code 19 | 20 | ![my Output](MyOutputFull.png "My Output") 21 | The output is similar to Fiji's output (note that the contrast is enhanced by stretching the histograms). The residual's histogram is also stretched and it can be appreciated that the residual is essentially random noise, so it does not contain a lot of information. 22 | 23 | ## To do 24 | 25 | * fix contrast scaling in inverse color deconvolution function (RecombineStains) 26 | 27 | ## More resources on color deconvolution 28 | 29 | * Fiji implementation explained on the [Fiji website](http://fiji.sc/Colour_Deconvolution) 30 | * python scikit-image implementation on [GitHub](https://github.com/scikit-image/scikit-image/blob/master/skimage/color/colorconv.py) 31 | * in-depth explanation of the fiji implementation on [mecourse.com](http://www.mecourse.com/landinig/software/cdeconv/cdeconv.html) 32 | * another Matlab implementation by Antony Chan on [web.hku.hk](http://web.hku.hk/~ccsigma/color-deconv/color-deconv.html) (slower because of two nested for loops) 33 | * yet another Matlab implementation: [imagenebula](https://code.google.com/p/imagenebula/source/browse/imagenebula/matlab/color/colordeconv.m?r=ec8fb69176f28ba49b38d5556452c38f7e02fa5a) 34 | 35 | ## References 36 | 37 | [1] Ruifrok AC, Johnston DA. Quantification of histochemical staining by color deconvolution. Anal Quant Cytol Histol. 2001 Aug;23(4) 291-299. PubMed PMID: 11531144. 38 | 39 | [2] van der Walt S, Schönberger JL, Nunez-Iglesias J, Boulogne F, Warner JD, Yager N, Gouillart E, Yu T, scikit-image contributors. scikit-image: image processing in Python. PeerJ. 2014;2 e453. doi:10.7717/peerj.453. PubMed PMID: 25024921; PubMed Central PMCID: PMC4081273. 40 | 41 | [3] Pontén F, Jirström K, Uhlen M. The Human Protein Atlas--a tool for pathology. J Pathol. 2008 Dec;216(4) 387-393. doi:10.1002/path.2440. PubMed PMID: 18853439. 42 | --------------------------------------------------------------------------------