├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── autoDeepLearn.m ├── autoDeploy.m ├── autoNormalize.m ├── autoTrainOnFixed.m ├── autoVisualize.m ├── cliniData ├── .DS_Store ├── TCGA-BRCA-DX_CLINI.xlsx ├── TCGA-BRCA-DX_SLIDE.csv ├── TCGA-CESC-DX_CLINI.xlsx ├── TCGA-CESC-DX_SLIDE.csv ├── TCGA-CHOL-DX_CLINI.xlsx ├── TCGA-CRC-DX_CLINI.xlsx ├── TCGA-CRC-DX_SLIDE.csv ├── TCGA-HNSC-DX_CLINI.xlsx ├── TCGA-HNSC-DX_SLIDE.csv ├── TCGA-KICH-DX_SLIDE.csv ├── TCGA-KIRC-DX_SLIDE.csv ├── TCGA-KIRP-DX_SLIDE.csv ├── TCGA-KIXX-DX_CLINI.xlsx ├── TCGA-LIHC-DX_CLINI.xlsx ├── TCGA-LIHC-DX_SLIDE.csv ├── TCGA-LUAD-DX_CLINI.xlsx ├── TCGA-LUAD-DX_SLIDE.csv ├── TCGA-LUSC-DX_CLINI.xlsx ├── TCGA-LUSC-DX_SLIDE.csv ├── TCGA-PAAD-DX_CLINI.xlsx ├── TCGA-PAAD-DX_SLIDE.csv ├── TCGA-PRAD-DX_CLINI.xlsx ├── TCGA-PRAD-DX_SLIDE.csv ├── TCGA-SKCM-DX_CLINI.xlsx ├── TCGA-SKCM-DX_SLIDE.csv ├── TCGA-STAD-DX_CLINI.xlsx └── TCGA-STAD-DX_SLIDE.csv ├── deployOnValidation_blind.m ├── experiments ├── .DS_Store ├── tcga-brca-generated.txt ├── tcga-cesc-generated.txt ├── tcga-crc-generated.txt ├── tcga-hnsc-generated.txt ├── tcga-kixx-generated.txt ├── tcga-lihc-generated.txt ├── tcga-luad-generated.txt ├── tcga-lusc-generated.txt ├── tcga-paad-generated.txt ├── tcga-prad-generated.txt ├── tcga-skcm-generated.txt └── tcga-stad-generated.txt ├── figure1.jpg ├── littleHelpers ├── exportPatientPredictions.m └── showThumbnails.m ├── networks ├── densenet512.mat ├── mobilenetv2_512.mat ├── resnet18_512.mat ├── shufflenet1024.mat ├── shufflenet128.mat ├── shufflenet196.mat ├── shufflenet256.mat └── shufflenet512.mat ├── pretrainedModels └── TWVIHFFKIHVH_macenko-TCCPT_isMSIH_lastModel_v6.mat ├── subroutines ├── anyAUC.m ├── assignTileLabel.m ├── binarizeNumerical.m ├── block2filename.m ├── brewermap.m ├── calcPrediPval.m ├── combineTables.m ├── concatenatePredictions.m ├── containsmember.m ├── copyFilesByLabel.m ├── copyIsField.m ├── copyfields.m ├── createLgraphUsingConnections.m ├── deployModel.m ├── deployModelBlind.m ├── dictionaryReplace.m ├── dispAllFields.m ├── dispUniqueValues.m ├── doGPUworkaround.m ├── equalizeClasses.m ├── exportTiles.m ├── forest.m ├── freezeWeights.m ├── getAndModifyNet.m ├── getAnnotationData.m ├── getDeepHistologyVersion.m ├── getDeepestFolder.m ├── getDefaultDictionary.m ├── getHyperparameters.m ├── getInputParser.m ├── getROCstats.m ├── getTargetClass.m ├── getTopTiles.m ├── getTrainingOptions.m ├── getUcategoriesInOrder.m ├── initializeDeepImagePipeline.m ├── loadExperiment.m ├── loadTileFiles.m ├── modifyBlockDir.m ├── motivationalQuotes.txt ├── myStruct2array.m ├── overrideBaseDir.m ├── parseStatistics.m ├── plotForestChart.m ├── plotForestChartSub.m ├── plotMyConfusion.m ├── plotROCcurves.m ├── plotViolinChartSub.m ├── plotVolcanoChartSub.m ├── predictions2performance.m ├── readProjectTables.m ├── removeCols.m ├── removeDupliRows.m ├── removeEmptyCells.m ├── removeExcessIndices.m ├── sanityCheck.m ├── saveTopTiles.m ├── softSanityCheck.m ├── splitImdsForXVal.m ├── splitList.m ├── splitListFixedNum.m ├── stat2pat.m ├── stat2slide.m ├── suptitle.m ├── trainMyNetwork.m ├── transposeStruct.m ├── violin.m └── writeTopTiles.m └── subroutines_normalization ├── Deconvolve.m ├── Images ├── Ref.png └── Source_small.png ├── PseudoColourStains.m ├── README.txt ├── Ref.png ├── SCDTraining ├── BuildImageFeatureVector.m ├── ClassifyStainRegions.m ├── ComputeSCDs.m ├── Default │ ├── ClassifyRF.m │ ├── DefaultStainTrainingStruct.mat │ └── TrainRF.m ├── GenerateHistogram.m ├── MakeClassifier.m ├── README.txt └── otq.m ├── StainMatrixEstimation ├── EstUsingMacenko.m └── EstUsingSCD.m ├── StainNormalisation ├── Norm.m ├── NormMacenko.m ├── NormRGBHist.m ├── NormReinhard.m ├── NormSCD.m └── NormSCDLeeds.m ├── bin └── LeedsSCD │ ├── ColourNormalisation.exe │ ├── HE.colourmodel │ ├── msvcp100.dll │ ├── msvcr100.dll │ ├── opencv_calib3d220.dll │ ├── opencv_contrib220.dll │ ├── opencv_core220.dll │ ├── opencv_features2d220.dll │ ├── opencv_flann220.dll │ ├── opencv_gpu220.dll │ ├── opencv_highgui220.dll │ ├── opencv_imgproc220.dll │ ├── opencv_legacy220.dll │ ├── opencv_ml220.dll │ ├── opencv_objdetect220.dll │ ├── opencv_video220.dll │ └── vcomp100.dll ├── colour_deconvolution.c ├── colour_deconvolution.mexw64 ├── demo.m └── install.m /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | experiments/.DS_Store 3 | .DS_Store 4 | experiments/.DS_Store 5 | .DS_Store 6 | .DS_Store 7 | .DS_Store 8 | *.asv 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepHistology 2 | This is a pan-cancer platform for mutation prediction from routine histology by the Kather lab (http://kather.ai). It is implemented in MATLAB and requires version R2019a+ (for some types of visualization, R2019b+; the code is tested up to R2020b). The following Matlab toolboxes are required: Image processing toolbox, Deep learning toolbox, Parallel processing toolbox; also, shufflenet model from the Matlab Add-On manager. 3 | 4 | ![Fig1](figure1.jpg) 5 | 6 | Briefly, to use these scripts, you should 7 | 1. prepare your data according to the Aachen protocol as described here: https://zenodo.org/record/3694994 8 | 2. prepare an "experiment file" which specifies the name of the project, the location of the tiles and the targets to be predicted 9 | 3. run autoDeepLearn('experiment','') to run a cross-validated experiment 10 | 4. visualize the results with autoVisualize('experiment','') 11 | 12 | An example for a possible data structure (project TCGA-CRC-DX) is: 13 | - experiment file 'tcga-crc-generated' is located in './experiments' 14 | - CLINI table 'TCGA-CRC-DX_CLINI.xlsx' is located in './cliniData' and has a column called PATIENT 15 | - SLIDE table 'TCGA-CRC-DX_SLIDE.csv' is located in './cliniData' and has a column called PATIENT and a column called FILENAME 16 | - image tiles were generated with QuPath, are named according to the Aachen protocol and are located at 'E:/TCGA-CRC-DX/BLOCKS/' 17 | 18 | ## Releases 19 | 20 | This project is constantly evolving and is being updated regularly. To reproduce our previous research findings, you can use "frozen" releases of the code. 21 | 22 | - v0.2 was used for the project "Pan-cancer image-based detection of clinically actionable genetic alterations" 23 | 24 | more info: https://github.com/jnkather/DeepHistology/releases 25 | 26 | ## Main scripts and their input arguments 27 | 28 | ### autoDeepLearn 29 | 30 | This is the main function for training networks and to evaluate networks via cross-validation. 31 | 32 | Argument | Default Value | Description 33 | --- | --- | --- 34 | experiment | '' | which experiment to load 35 | gpuDev | 1 | GPU Device (1 or greater) 36 | maxBlockNum | 1000 | number of blocks (tiles) per whole slide image, upper limit for computational efficiency, applies in any mode (xval, deploy, train-test, ...) 37 | trainFull | false | train on full dataset after xval 38 | modelTemplate | shufflenet512 | which pretrained model, possible options are defined in getAndModifyNet() 39 | backwards | false | for each experiment, work backwards in the list of targets 40 | binarizeQuantile | [] | split high/low at this quantile, possible options: floating point number between 0 and 0.5 or empty (use mean) 41 | foldxval | 3 | if cross validation is used, this is the fold, possible option: any positive integer. If this is 0, no cross validation will be done. 42 | aggregateMode | majority | how to pool block predictions per patient, possible otions: 'majority', 'ignoreClass' (variant of majority, see below), 'mean' or 'max' 43 | tableModeClini | XLSX | file format of clinical table, XLSX or CSV 44 | hyper | default | set of hyperparameters as defined in getDeepHyperparameters(), possible options: default, lowresource or verylowresource 45 | valSet | [] | validation set proportion of training set to stop training early 46 | filterBlocks | | can be 'normalized' or 'stroma' or 'tumor' for specific training tasks. Default: empty. 47 | subsetTargetsBy | [] | subset the patients by a variable 48 | subsetTargetsLevel | [] | subset the patients by this level in the variable 49 | skipExistingTargets | false | skip target prediction if result file exists 50 | forgiveError | false | ignore errors during training and go on to the next task 51 | maxBlocksPerClass | 1e9 | hard limit on the number of tiles per class, with default this will never be reached; if you set this to lower values this will enforce additional random undersampling (in xval mode only!) 52 | nBootstrapAUC | 10 | bootstrap number to calculate AUROC confidence intervals 53 | whichIgnoreClass | '' | ignore this class for score calculation (e.g. this is a neutral class such as "no_tumor".) This will NOT be used as a negative class for all the other classes. Only works if aggregateMode == ignoreClass 54 | 55 | ### autoDeploy 56 | 57 | This function will use a trained network and allows to deploy it on a different patient cohort. 58 | 59 | Argument | Default Value | Description 60 | --- | --- | --- 61 | trainedModelID | [] | use a previously trained model for deployment 62 | trainedModelFolder | [] | path to previously trained model for deployment 63 | 64 | ### autoVisualize 65 | 66 | This function will load results from the 'DUMP' folder and create plots. 67 | 68 | Argument | Default Value | Description 69 | --- | --- | --- 70 | doPlot | false | show the ROC curve on screen 71 | doPrint | false | save the ROC to PDF and Imgs to PNG 72 | plotThreshold | false | plot the operating threshold on top 73 | exportBlockPred | false | save CSV file for maps 74 | exportTopTiles | 0 | save top tiles 75 | topPatients | 3 | showcase tiles from these patients 76 | plotFontSize | 12 | font size for plots 77 | saveFormat | v6 | version of results file (debug only) 78 | plotAUCthreshold | 0.75 | detailed visualization only for high performance targets 79 | onlyExplicitTargets | true | visualize only targets specified in experiment file 80 | debugMode | false | debug mode 81 | 82 | ### autoNormalize 83 | 84 | This function will normalize tile images with the Macenko method. Caution: This function relies on third party subroutines in "./subroutines_normalization" - please observe the third party license in that sub-folder. 85 | 86 | Argument | Default Value | Description 87 | --- | --- | --- 88 | experiment | '' | name of the experiment file 89 | numParWorkers | 1 | number of parallel CPUs (requires Parallel Processing Toolbox) 90 | 91 | 92 | ## License 93 | See separate License file which applies to all files within this repository unless noted otherwise. Please note that some functions in the subroutine folder are from third party sources and have their own license included in the file. 94 | 95 | ## Questions? 96 | Get in touch at https://jnkather.github.io/contact/ -------------------------------------------------------------------------------- /autoDeploy.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2021 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is the main function to apply a trained deep 7 | % neural network to another cohort 8 | 9 | function autoDeploy(varargin) 10 | addpath(genpath('./subroutines/')); % add dependencies 11 | iPrs = getInputParser(varargin); % get input parser, define default values 12 | gpuDevice(iPrs.Results.gpuDev); % select GPU device (Windows only) 13 | cnst = loadExperiment(iPrs.Results.experiment); % load experiment from JSON 14 | 15 | disp('-- starting DEPLOYMENT job with these input (or default) settings:'); 16 | dispAllFields(iPrs.Results); 17 | cnst = copyfields(cnst,iPrs.Results,fieldnames(iPrs.Results)); % apply input 18 | [cnst,fCollect] = initializeDeepImagePipeline(cnst); % initialize 19 | hyperprm = getHyperparameters(cnst.hyper); % load DL hyperparams 20 | dispAllFields(cnst); % display all constants on console 21 | dispAllFields(hyperprm); % display all hyperparameters on console 22 | 23 | disp('-- loading the trained model'); 24 | if ~isempty(cnst.trainedModelFolder) && ~isempty(cnst.trainedModelID) 25 | load(fullfile(cnst.trainedModelFolder,[cnst.trainedModelID,'_lastModel_v6.mat']),'finalModel'); 26 | else 27 | error(['for deployment, please specify a trained model ',... 28 | 'using options trainedModelFolder and trainedModelID']); 29 | end 30 | 31 | disp('--- done. starting prediction'); 32 | 33 | for ti = 1:numel(cnst.allTargets) 34 | rng('shuffle'); 35 | cnst.annotation.targetCol = char(cnst.allTargets{ti}); 36 | cnst.experimentName = [cnst.baseName,'-',randseq(5,'alphabet','AA'),'_',cnst.annotation.targetCol]; 37 | 38 | disp([newline,newline,'#################',newline,newline,... 39 | 'starting new experiment: ',cnst.annotation.targetCol ]); 40 | 41 | z1 = tic; 42 | % load image tiles and assign a label to each tile 43 | allBlocks = copy(fCollect.Blocks); 44 | AnnData = getAnnotationData(cnst); 45 | [allBlocks, AnnData, ~] = assignTileLabel(allBlocks,AnnData,cnst); 46 | 47 | blockLevelPredictions = deployModel(cnst,hyperprm,finalModel,allBlocks); 48 | 49 | % combine stats 50 | resultCollection{ti}.blockStats = concatenatePredictions(blockLevelPredictions); 51 | resultCollection{ti}.patientStats = predictions2performance(... 52 | resultCollection{ti}.blockStats,AnnData,cnst); 53 | 54 | disp('finished deploy function'); 55 | % add some more results 56 | totalTime = toc(z1); 57 | resultCollection{ti}.cnst = cnst; 58 | resultCollection{ti}.hyperprm = hyperprm; 59 | resultCollection{ti}.totalTime = totalTime; 60 | 61 | save(fullfile(cnst.folderName.Dump,[cnst.experimentName,... 62 | '_lastResult_v6.mat']),'resultCollection','-v7.3'); 63 | 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /autoNormalize.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will color-normalize all tiles of a given project 7 | % requires color normalization subroutines (third party License) 8 | 9 | function autoNormalize(varargin) 10 | 11 | addpath(genpath('./subroutines/')); % add dependencies 12 | addpath(genpath('./subroutines_normalization/')); % add normalization dependencies 13 | iPrs = getInputParser(varargin); % get input parser, define default values 14 | cnst = loadExperiment(iPrs.Results.experiment); % load experiment from JSON 15 | cnst.skipLoadingBlocks = true; % never load tiles for visualization 16 | disp('-- starting VISUALIZE job with these input (or default) settings:'); 17 | dispAllFields(iPrs.Results); 18 | cnst = copyfields(cnst,iPrs.Results,fieldnames(iPrs.Results)); % apply input 19 | [cnst,~] = initializeDeepImagePipeline(cnst); % initialize 20 | 21 | warning ('on','all'); 22 | ref_image = imread('./subroutines_normalization/Ref.png'); % reference image for image color normalization 23 | 24 | if ~isfield(cnst,'overrideFolder') || isempty(cnst.overrideFolder) % this is the default 25 | cnst.folderName.BlocksNorm = strrep(cnst.folderName.Blocks,fullfile(cnst.ProjectName,'BLOCKS'),fullfile(cnst.ProjectName,'BLOCKS_NORM')); 26 | cnst.folderName.BlocksFail = strrep(cnst.folderName.Blocks,fullfile(cnst.ProjectName,'BLOCKS'),fullfile(cnst.ProjectName,'BLOCKS_NORM_FAIL')); 27 | else % this is needed to maintain compatibility if overrideFolder is non-empty 28 | cnst.folderName.BlocksNorm = strrep(cnst.folderName.Blocks,fullfile(cnst.overrideFolder,'BLOCKS'),fullfile(cnst.overrideFolder,'BLOCKS_NORM')); 29 | cnst.folderName.BlocksFail = strrep(cnst.folderName.Blocks,fullfile(cnst.overrideFolder,'BLOCKS'),fullfile(cnst.overrideFolder,'BLOCKS_NORM_FAIL')); 30 | end 31 | 32 | disp(['--- will save normalized blocks (tiles) to ',cnst.folderName.BlocksNorm]); 33 | mkdir(cnst.folderName.BlocksNorm); 34 | mkdir(cnst.folderName.BlocksFail); 35 | 36 | % read all source files 37 | disp('-- reading all source files'); 38 | tic 39 | allSourceFiles = imageDatastore(cnst.folderName.Blocks,'FileExtensions',cnst.fileformatBlocks,'IncludeSubfolders',true); 40 | [~,snames,~] = cellfun(@fileparts,allSourceFiles.Files,'UniformOutput',false); 41 | toc 42 | disp('-- reading all target files'); 43 | tic 44 | preLoadTarget = dir(cnst.folderName.BlocksNorm); 45 | if numel(preLoadTarget)==2 46 | disp('-- target dir is empty'); 47 | tnames = []; 48 | overlapnames = []; 49 | else 50 | allTargetFiles = imageDatastore(cnst.folderName.BlocksNorm,'FileExtensions',cnst.fileformatBlocks,'IncludeSubfolders',true); 51 | % find out which files have been already processed 52 | [~,tnames,~] = cellfun(@fileparts,allTargetFiles.Files,'UniformOutput',false); 53 | [overlapnames,ia,~] = intersect(snames,tnames); 54 | end 55 | toc 56 | 57 | disp(['--- files in the source directory: ',num2str(numel(snames))]); 58 | disp(['--- files in the target directory: ',num2str(numel(tnames))]); 59 | disp(['--- overlapping files : ',num2str(numel(overlapnames))]); 60 | disp(['--- remaining files : ',num2str(numel(snames)-numel(overlapnames))]); 61 | disp('... will process all remaining files'); 62 | 63 | if numel(overlapnames)>0 64 | disp('--- removed processed files from source list'); 65 | allSourceFiles.Files(ia) = []; 66 | end 67 | 68 | allFnames = allSourceFiles.Files; 69 | maxFn = numel(allFnames); 70 | 71 | if cnst.numParWorkers>0 72 | % restart the parallel pool to enforce settings 73 | delete(gcp('nocreate')) 74 | parpool(cnst.numParWorkers); 75 | end 76 | 77 | parfor (i=1:maxFn,cnst.numParWorkers) 78 | currFn = allFnames{i}; 79 | currFolder = fullfile(fileparts(cnst.folderName.BlocksNorm),getDeepestFolder(currFn)); 80 | try 81 | currIm = imread(currFn); 82 | [~,cimname,cimtype] = fileparts(currFn); 83 | currIm = Norm(currIm, ref_image, 'Macenko', 255, 0.15, 1, false); 84 | mkdir(currFolder); 85 | imwrite(currIm,fullfile(currFolder,[cimname,cimtype])); 86 | catch 87 | warning('fail') 88 | mkdir(fileparts(cnst.folderName.BlocksFail)); 89 | copyfile(currFn,cnst.folderName.BlocksFail); 90 | end 91 | 92 | if mod(i,500)==1 93 | disp(['finished file ',num2str(i),' of ',num2str(maxFn)]); 94 | end 95 | end 96 | 97 | disp('-- FINISHED ALL --'); 98 | end 99 | 100 | 101 | -------------------------------------------------------------------------------- /autoTrainOnFixed.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this will train a network on a fixed folder 7 | 8 | function autoTrainOnFixed(varargin) 9 | addpath(genpath('./subroutines/')); % add dependencies 10 | iPrs = getInputParser(varargin); % get input parser, define default values 11 | gpuDevice(iPrs.Results.gpuDev); % select GPU device (Windows only) 12 | cnst.ProjectName = 'autoTrainOn'; 13 | cnst.baseName = 'autoTrainOn'; 14 | cnst.blocks.resizeMethod = 'resize'; 15 | 16 | disp('-- starting job with these input (or default) settings:'); 17 | dispAllFields(iPrs.Results); 18 | cnst = copyfields(cnst,iPrs.Results,fieldnames(iPrs.Results)); % apply input 19 | 20 | cnst.folderName.Temp = cnst.trainOnFolder; 21 | 22 | cnst.folderName.Dump = fullfile(cnst.folderName.Temp,cnst.ProjectName,'/DUMP/'); % abs. path to block save folder 23 | [~,~,~] = mkdir(char(cnst.folderName.Dump)); 24 | 25 | hyperprm = getHyperparameters(cnst.hyper); % load DL hyperparams 26 | rng('shuffle'); 27 | cnst.experimentName = [cnst.baseName,'-',randseq(5,'Alphabet','AA')]; 28 | disp([newline,'#################',newline,... 29 | 'starting new experiment: autoTrainOn' ]); 30 | 31 | inpImds = imageDatastore(fullfile(cnst.trainOnFolder),'IncludeSubfolders',true,'LabelSource','foldernames'); 32 | 33 | inpImds %# ok 34 | 35 | z1 = tic; 36 | myNet = getAndModifyNet(cnst,hyperprm,numel(unique(inpImds.Labels))); % load pretrained net 37 | 38 | [finalModel,~] = trainMyNetwork(myNet,inpImds,[],cnst,hyperprm); 39 | 40 | totalTime = toc(z1); 41 | disp('training was successful'); 42 | resultCollection.cnst = cnst; 43 | resultCollection.hyperprm = hyperprm; 44 | resultCollection.totalTime = totalTime; 45 | 46 | save(fullfile(cnst.folderName.Dump,[cnst.experimentName,'_lastResult_',cnst.saveFormat,'.mat']),'resultCollection'); 47 | save(fullfile(cnst.folderName.Dump,[cnst.experimentName,'_lastModel_',cnst.saveFormat,'.mat']),'finalModel'); 48 | 49 | end 50 | -------------------------------------------------------------------------------- /cliniData/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/.DS_Store -------------------------------------------------------------------------------- /cliniData/TCGA-BRCA-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-BRCA-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-CESC-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-CESC-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-CHOL-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-CHOL-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-CRC-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-CRC-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-HNSC-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-HNSC-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-KIXX-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-KIXX-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-LIHC-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-LIHC-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-LUAD-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-LUAD-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-LUSC-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-LUSC-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-PAAD-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-PAAD-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-PRAD-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-PRAD-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-SKCM-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-SKCM-DX_CLINI.xlsx -------------------------------------------------------------------------------- /cliniData/TCGA-STAD-DX_CLINI.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/cliniData/TCGA-STAD-DX_CLINI.xlsx -------------------------------------------------------------------------------- /deployOnValidation_blind.m: -------------------------------------------------------------------------------- 1 | % JN Kather Aachen / Chicago 2019 2 | % this script is part of the digital pathology deep learning project 3 | % MELANIE = seMiautomatic dEep LeArniNg pIplinE 4 | % 5 | % Step 1: open all SVS images and draw regions 6 | % Step 2: cut the ROIs into blocks 7 | % Step 3: load blocks, train/test OR crossvalidate then train on all 8 | % THIS IS Step 4: deploy a trained model on an external image set 9 | % 10 | % we need two tables: 11 | % the SLIDE table associates SLIDES (images) to PATIENTS 12 | % the CLINI table associates PATIENTS to outcome 13 | % 14 | % this is the *BLIND* version of our script which will be used whenever we 15 | % want to predict a validation cohort but stay blinded to the ground truth 16 | % --- caution this function is not yet properly embedded in the whole pipeline and 17 | % may need some manual babysitting 18 | 19 | %% Header 20 | clear variables, format compact, close all; clc % clean up 21 | %setenv CUDA_VISIBLE_DEVICES 1 % use only first GPU 22 | gpuDevice(1) 23 | addpath(genpath('./subroutines/')); % add dependencies 24 | cnst = loadExperiment('romanmsi-rtx'); 25 | cnst.fileformatBlocks = {'.jpg'}; 26 | 27 | % load deep learning hyperparameters and initialize deep learning model 28 | hyperprm = getHyperparameters('default'); 29 | hyperprm.ExecutionEnvironment = 'gpu'; 30 | hyperprm.MiniBatchSize = 2048; 31 | cnst.debugMode = false; 32 | 33 | [cnst,fCollect] = initializeDeepImagePipeline(cnst); % initialize 34 | 35 | cnst.verbose = false; % show intermediary steps? 36 | cnst.simulate = false; % simulate only? default false (-> do it!) 37 | 38 | % load the trained model 39 | cnst.trainedModelFolder = fullfile('E:\RAINBOW-CRC-DX--DACHS-CRCFULL-DX--TCGA-CRC-DX--QUASAR-CRC-DX\DUMP\'); 40 | cnst.trainedModelID = 'HLVDDREQHWQK-WPDRE_isMSIH'; 41 | 42 | load(fullfile(cnst.trainedModelFolder,[cnst.trainedModelID,'_lastModel_v6.mat'])); 43 | 44 | % define SLIDE table and CLINI table 45 | % the SLIDE table needs to have the columns FILENAME and PATIENT 46 | % the CLINI table needs to have the column PATIENT plus an target column 47 | cnst.annotation.Dir = './cliniData/'; 48 | cnst.annotation.SlideTable = [cnst.ProjectName,'_SLIDE.csv']; 49 | cnst.annotation.CliniTable = [cnst.ProjectName,'_CLINI.xlsx']; 50 | cnst.blocks.maxBlockNum = 1000; 51 | cnst.aggregateMode = 'majority'; 52 | 53 | z1 = tic; 54 | allBlocksLabeled = copy(fCollect.Blocks); 55 | cnst.annotation.targetCol = ''; % no target in blind mode 56 | 57 | resultCollection = deployModelBlind(hyperprm,finalModel,allBlocksLabeled); 58 | disp('finished deploy function'); 59 | % add some more results 60 | resultCollection.unmatchedBlocks = []; 61 | totalTime = toc(z1); 62 | resultCollection.cnst = cnst; 63 | resultCollection.hyperprm = hyperprm; 64 | resultCollection.totalTime = totalTime; 65 | save(fullfile(cnst.folderName.Dump,[cnst.experimentName,... 66 | '_from_',cnst.trainedModelID,'_lastResult_v6.mat']),'resultCollection'); 67 | 68 | % convert tile level predictions to patient level predictions 69 | slideTable = readtable(fullfile(cnst.annotation.Dir,cnst.annotation.SlideTable),'Delimiter',','); 70 | 71 | tic 72 | [patients,meanScore] = stat2pat(resultCollection.stats.blockStats,slideTable); 73 | toc 74 | 75 | tout = table(patients',meanScore') 76 | 77 | -------------------------------------------------------------------------------- /experiments/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/experiments/.DS_Store -------------------------------------------------------------------------------- /experiments/tcga-brca-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-BRCA-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-brca-generated","allTargets":["ajcc_pathologic_stage","gender","ARID1A_driver","ATM_driver","BRCA1_driver","BRCA2_driver","CASP8_driver","CDKN1B_driver","ERBB2_driver","ERBB3_driver","FAT1_driver","FBXW7_driver","FOXA1_driver","GATA3_driver","KDM6A_driver","KMT2A_driver","KMT2B_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MAP2K4_driver","MAP3K1_driver","MED12_driver","NF1_driver","PIK3CA_driver","PIK3R1_driver","PTEN_driver","RB1_driver","SETD2_driver","SMAD4_driver","SMARCA4_driver","TP53_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDKN1B_anymut","CHD1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FOXA1_anymut","FZD3_anymut","GATA3_anymut","GNAS_anymut","HRAS_anymut","IDH1_anymut","JAK2_anymut","KDM6A_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K4_anymut","MAP3K1_anymut","MAP7_anymut","MED12_anymut","MET_anymut","MGA_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RHOA_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD2_anymut","SMAD4_anymut","SMARCA4_anymut","SPOP_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","BRCA_Pathology","BRCA_Subtype_PAM50","Pan_GynClusters","pathologic_stage","ERStatus","HER2FinalStatus","PRStatus"]} -------------------------------------------------------------------------------- /experiments/tcga-cesc-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-CESC-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-cesc-generated","allTargets":["ARID1A_driver","B2M_driver","BRCA2_driver","CASP8_driver","ERBB2_driver","ERBB3_driver","FAT1_driver","FBXW7_driver","HLA_A_driver","KDM6A_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MED12_driver","NF1_driver","NFE2L2_driver","NOTCH1_driver","NSD1_driver","PIK3CA_driver","PIK3R1_driver","PTEN_driver","RASA1_driver","RB1_driver","SMAD4_driver","STK11_driver","TGFBR2_driver","TP53_driver","TSC2_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDKN2A_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","GNAS_anymut","HLA_A_anymut","JAK2_anymut","KDM6A_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP3K1_anymut","MED12_anymut","MGA_anymut","MTOR_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PIK3CA_anymut","PIK3R1_anymut","PTEN_anymut","PTPN12_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","SETD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCellsCD8","TGF_betaResponse","WoundHealing","Pan_GynClusters","Tumor_Grade","pathologic_stage"]} -------------------------------------------------------------------------------- /experiments/tcga-crc-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-CRC-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-crc-generated","allTargets":["ajcc_pathologic_stage","gender","AMER1_driver","APC_driver","ARID1A_driver","ATM_driver","B2M_driver","BRAF_driver","BRCA2_driver","CASP8_driver","CDK12_driver","CDKN1B_driver","CTNNB1_driver","ERBB2_driver","ERBB3_driver","FAT1_driver","FBXW7_driver","FLT3_driver","GATA3_driver","GNAS_driver","HLA_A_driver","KMT2A_driver","KMT2B_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MAP2K1_driver","MAP2K4_driver","MAP3K1_driver","MED12_driver","MGA_driver","MSH6_driver","MTOR_driver","NF1_driver","NRAS_driver","PBRM1_driver","PIK3CA_driver","PIK3R1_driver","PTCH1_driver","PTEN_driver","RASA1_driver","RB1_driver","RBM10_driver","RNF43_driver","SETD2_driver","SMAD2_driver","SMAD4_driver","SMARCA4_driver","TCF7L2_driver","TGFBR2_driver","TP53_driver","TSC1_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDK4_anymut","CDKN1B_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","FZD3_anymut","GATA3_anymut","GNAS_anymut","HLA_A_anymut","IDH1_anymut","IDH2_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP2K4_anymut","MAP3K1_anymut","MAP7_anymut","MED12_anymut","MET_anymut","MGA_anymut","MIER3_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NRAS_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PPP6C_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RHOA_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","U2AF1_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","STUDY_currTable","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","ColorectalCMS","HypermethylationCategory","Hypermutated","InterferonGammaResponse_C1Hallmark","MSIStatus","StemnessRNABased","WntBetaCateningSignaling_C1Hallmark","STUDY_ORIG"]} -------------------------------------------------------------------------------- /experiments/tcga-hnsc-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-HNSC-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-hnsc-generated","allTargets":["ajcc_pathologic_stage","gender","APC_driver","ARID1A_driver","B2M_driver","CASP8_driver","CDKN2A_driver","FAT1_driver","FBXW7_driver","HLA_A_driver","HRAS_driver","KDM6A_driver","KEAP1_driver","KMT2A_driver","KMT2B_driver","KMT2C_driver","KMT2D_driver","NFE2L2_driver","NOTCH1_driver","NSD1_driver","PBRM1_driver","PIK3CA_driver","PIK3R1_driver","PTCH1_driver","PTEN_driver","RAC1_driver","RASA1_driver","RB1_driver","RHOA_driver","SMAD4_driver","TGFBR2_driver","TP53_driver","ABL1_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDK4_anymut","CDKN2A_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FLT3_anymut","GATA3_anymut","GNAS_anymut","HLA_A_anymut","HRAS_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","MED12_anymut","MGA_anymut","MSH3_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RAC1_anymut","RASA1_anymut","RB1_anymut","RHOA_anymut","RREB1_anymut","SETD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","STK19_anymut","TCERG1_anymut","TGFBR2_anymut","TP53_anymut","TSC2_anymut","TTN_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","egfr_amplication_status","neoplasm_histologic_grade_tm","perineural_invasion_present"]} -------------------------------------------------------------------------------- /experiments/tcga-kixx-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-KIXX-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-kixx-generated","allTargets":["ajcc_pathologic_stage","gender","ARID1A_driver","ATM_driver","FAT1_driver","KDM6A_driver","KMT2A_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MET_driver","MGA_driver","MTOR_driver","NF1_driver","NFE2L2_driver","PBRM1_driver","PIK3CA_driver","PTEN_driver","SETD2_driver","SMARCA4_driver","TP53_driver","TSC1_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARID1A_anymut","ATM_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CDK12_anymut","CHD1_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","GNAS_anymut","KDM6A_anymut","KEAP1_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP3K1_anymut","MED12_anymut","MET_anymut","MGA_anymut","MSH3_anymut","MTOR_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PIK3CA_anymut","PTEN_anymut","RASA1_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMARCA4_anymut","TCERG1_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","STUDY_currTable","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","PanKidneyPathology","neoplasm_histologic_grade_currTable","STUDY_ORIG"]} -------------------------------------------------------------------------------- /experiments/tcga-lihc-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-LIHC-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-lihc-generated","allTargets":["ajcc_pathologic_stage","gender","ARID1A_driver","CDKN2A_driver","CTNNB1_driver","KEAP1_driver","KMT2A_driver","KMT2B_driver","KMT2D_driver","KRAS_driver","NFE2L2_driver","PIK3CA_driver","PTEN_driver","RASA1_driver","RB1_driver","SETD2_driver","TP53_driver","TSC2_driver","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARID1A_anymut","ATM_anymut","BRCA2_anymut","CDKN2A_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB3_anymut","FAT1_anymut","GNAS_anymut","IDH1_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K4_anymut","MED12_anymut","MGA_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NRAS_anymut","NSD1_anymut","PBRM1_anymut","PIK3CA_anymut","PTEN_anymut","RASA1_anymut","RB1_anymut","SETD2_anymut","SMARCA4_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","GrowthPattern_major","HBV_consensus","HCCSubtypes","HCV_consensus","NAFLD"]} -------------------------------------------------------------------------------- /experiments/tcga-luad-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-LUAD-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-luad-generated","allTargets":["ajcc_pathologic_stage","gender","APC_driver","ARID1A_driver","ATM_driver","B2M_driver","BRAF_driver","BRCA1_driver","CDKN2A_driver","CTNNB1_driver","EGFR_driver","ERBB2_driver","FAT1_driver","FBXW7_driver","GNAS_driver","KEAP1_driver","KMT2A_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MAP2K1_driver","MAP2K4_driver","MED12_driver","MET_driver","MGA_driver","NF1_driver","NFE2L2_driver","NOTCH1_driver","PBRM1_driver","PIK3CA_driver","PTEN_driver","RASA1_driver","RB1_driver","RBM10_driver","RIT1_driver","SETD2_driver","SMAD4_driver","SMARCA4_driver","STK11_driver","TP53_driver","U2AF1_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDK12_anymut","CDKN1B_anymut","CDKN2A_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","GATA3_anymut","GNAS_anymut","HLA_A_anymut","IDH1_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP2K4_anymut","MAP3K1_anymut","MAP7_anymut","MED12_anymut","MET_anymut","MGA_anymut","MIER3_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PTCH1_anymut","PTEN_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RHOA_anymut","RIT1_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","TCERG1_anymut","TCF7L2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","U2AF1_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing"]} -------------------------------------------------------------------------------- /experiments/tcga-lusc-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-LUSC-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-lusc-generated","allTargets":["ajcc_pathologic_stage","gender","APC_driver","ARID1A_driver","ATM_driver","B2M_driver","BRAF_driver","BRCA1_driver","BRCA2_driver","CASP8_driver","CDKN2A_driver","EGFR_driver","FAT1_driver","FBXW7_driver","FGFR2_driver","FLT3_driver","HLA_A_driver","HRAS_driver","KDM6A_driver","KEAP1_driver","KIT_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MED12_driver","NF1_driver","NFE2L2_driver","NOTCH1_driver","NRAS_driver","NSD1_driver","PBRM1_driver","PIK3CA_driver","PIK3R1_driver","PTEN_driver","RASA1_driver","RB1_driver","SETD2_driver","SMAD4_driver","SMARCA4_driver","TP53_driver","TSC1_driver","TSC2_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDK12_anymut","CDKN2A_anymut","CHD1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","FZD3_anymut","GATA3_anymut","GNAS_anymut","HLA_A_anymut","HRAS_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP2K4_anymut","MAP3K1_anymut","MED12_anymut","MET_anymut","MGA_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NMS_anymut","NOTCH1_anymut","NRAS_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RAC1_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing"]} -------------------------------------------------------------------------------- /experiments/tcga-paad-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-PAAD-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-paad-generated","allTargets":["ajcc_pathologic_stage","gender","ARID1A_driver","CDKN2A_driver","GNAS_driver","KDM6A_driver","KRAS_driver","RNF43_driver","SMAD4_driver","TGFBR2_driver","TP53_driver","ARID1A_anymut","ATM_anymut","CDKN2A_anymut","FBXW7_anymut","GNAS_anymut","KDM6A_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","PBRM1_anymut","PIK3CA_anymut","RNF43_anymut","RREB1_anymut","SMAD4_anymut","TGFBR2_anymut","TP53_anymut","TTN_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCellsCD8","TGF_betaResponse","WoundHealing","Grade","HistoryOfChronicPancreatitis"]} -------------------------------------------------------------------------------- /experiments/tcga-prad-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-PRAD-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-prad-generated","allTargets":["APC_driver","ATM_driver","BRAF_driver","BRCA2_driver","CDK12_driver","CDKN1B_driver","CTNNB1_driver","FOXA1_driver","KDM6A_driver","KMT2C_driver","KMT2D_driver","MED12_driver","PIK3CA_driver","PTEN_driver","SPOP_driver","TP53_driver","APC_anymut","ATM_anymut","BRAF_anymut","BRCA2_anymut","CDK12_anymut","CDKN1B_anymut","CHD1_anymut","CTNNB1_anymut","FOXA1_anymut","IDH1_anymut","KDM6A_anymut","KMT2A_anymut","KMT2C_anymut","KMT2D_anymut","MED12_anymut","MGA_anymut","MSH3_anymut","PIK3CA_anymut","PTEN_anymut","RNF43_anymut","SETD2_anymut","SMAD4_anymut","SPOP_anymut","TP53_anymut","TTN_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","AR_protein","Clinical_Gleason_sum","ERG_status","ETV1_status","ETV4_status","SCNA_cluster"]} -------------------------------------------------------------------------------- /experiments/tcga-skcm-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-SKCM-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-skcm-generated","allTargets":["ajcc_pathologic_stage","gender","AMER1_driver","APC_driver","ARID1A_driver","ATM_driver","B2M_driver","BRAF_driver","BRCA1_driver","BRCA2_driver","CDK12_driver","CDK4_driver","CDKN2A_driver","CTNNB1_driver","FAT1_driver","FBXW7_driver","FLT3_driver","IDH1_driver","KIT_driver","KMT2A_driver","KMT2B_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MAP2K1_driver","MED12_driver","MGA_driver","NF1_driver","NOTCH1_driver","NRAS_driver","PBRM1_driver","PIK3R1_driver","PPP6C_driver","PTEN_driver","RAC1_driver","RB1_driver","SETD2_driver","SMARCA4_driver","STK11_driver","STK19_driver","TP53_driver","TSC1_driver","TSC2_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDK4_anymut","CDKN2A_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","FOXA1_anymut","FZD3_anymut","GATA3_anymut","GNAS_anymut","HRAS_anymut","IDH1_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP2K4_anymut","MAP3K1_anymut","MAP7_anymut","MED12_anymut","MET_anymut","MGA_anymut","MIER3_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NFE2L2_anymut","NMS_anymut","NOTCH1_anymut","NRAS_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PPP6C_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RAC1_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RHOA_anymut","RIT1_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD2_anymut","SMAD4_anymut","SMARCA4_anymut","SPOP_anymut","STK11_anymut","STK19_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","U2AF1_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing"]} -------------------------------------------------------------------------------- /experiments/tcga-stad-generated.txt: -------------------------------------------------------------------------------- 1 | {"ProjectName":"TCGA-STAD-DX","folderName":{"Temp":"E:\\"},"codename":"tcga-stad-generated","allTargets":["ajcc_pathologic_stage","gender","AMER1_driver","APC_driver","ARID1A_driver","ATM_driver","B2M_driver","BRCA1_driver","BRCA2_driver","CASP8_driver","CDK12_driver","CDKN2A_driver","CTNNB1_driver","EGFR_driver","ERBB2_driver","ERBB3_driver","FAT1_driver","FBXW7_driver","GATA3_driver","GNAS_driver","KDM6A_driver","KMT2A_driver","KMT2B_driver","KMT2C_driver","KMT2D_driver","KRAS_driver","MAP2K1_driver","MAP2K4_driver","MAP3K1_driver","MGA_driver","MSH3_driver","NF1_driver","NOTCH1_driver","NSD1_driver","PBRM1_driver","PIK3CA_driver","PIK3R1_driver","PTCH1_driver","PTEN_driver","RASA1_driver","RB1_driver","RHOA_driver","RNF43_driver","SETD2_driver","SMAD2_driver","SMAD4_driver","SMARCA4_driver","TGFBR2_driver","TP53_driver","ABL1_anymut","ACVR2A_anymut","ALK_anymut","AMER1_anymut","APC_anymut","ARHGAP26_anymut","ARHGAP6_anymut","ARID1A_anymut","ATM_anymut","B2M_anymut","BRAF_anymut","BRCA1_anymut","BRCA2_anymut","CASP8_anymut","CDC27_anymut","CDK12_anymut","CDKN2A_anymut","CHD1_anymut","CTNNB1_anymut","EGFR_anymut","ERBB2_anymut","ERBB3_anymut","FAT1_anymut","FBXW7_anymut","FGFR2_anymut","FLT3_anymut","FZD3_anymut","GATA3_anymut","GNAS_anymut","HLA_A_anymut","JAK2_anymut","KDM6A_anymut","KEAP1_anymut","KIT_anymut","KMT2A_anymut","KMT2B_anymut","KMT2C_anymut","KMT2D_anymut","KRAS_anymut","MAP2K1_anymut","MAP2K4_anymut","MAP3K1_anymut","MED12_anymut","MET_anymut","MGA_anymut","MSH3_anymut","MSH6_anymut","MTOR_anymut","MYO1B_anymut","NF1_anymut","NOTCH1_anymut","NSD1_anymut","PBRM1_anymut","PDGFRB_anymut","PIK3CA_anymut","PIK3R1_anymut","PTCH1_anymut","PTEN_anymut","PTPN12_anymut","RASA1_anymut","RB1_anymut","RBM10_anymut","RHOA_anymut","RNF43_anymut","RREB1_anymut","SETD2_anymut","SMAD2_anymut","SMAD4_anymut","SMARCA4_anymut","STK11_anymut","TCERG1_anymut","TCF7L2_anymut","TGFBR2_anymut","TP53_anymut","TSC1_anymut","TSC2_anymut","TTN_anymut","U2AF1_anymut","VEGFC_anymut","ZMYM3_anymut","DendriticCellsActivated","HomologousRecombinationDefects","IFN_gammaResponse","ImmuneSubtype","MacrophageRegulation","Proliferation","TCGASubtype","TCellsCD8","TGF_betaResponse","WoundHealing","GastricHistologicalClassification","HypermethylationCategory","Hypermutated","InterferonGammaResponse_C1Hallmark","MSIStatus","StemnessRNABased","WntBetaCateningSignaling_C1Hallmark"]} -------------------------------------------------------------------------------- /figure1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/figure1.jpg -------------------------------------------------------------------------------- /littleHelpers/exportPatientPredictions.m: -------------------------------------------------------------------------------- 1 | 2 | % assumes that the result file is in the workspace 3 | 4 | trueCategory = 'MSIH'; 5 | 6 | try 7 | res = resultCollection; 8 | cnst = resultCollection.cnst; 9 | catch 10 | warning('detected deploy file'); 11 | res = resultCollection{1}; 12 | end 13 | 14 | tt = table(res.patientStats.rawData.patientNames',... 15 | res.patientStats.rawData.trueCategory',... 16 | res.patientStats.rawData.predictions.(trueCategory)'); 17 | tt.Properties.VariableNames = {'PATIENT','TRUE','SCORE'}; 18 | writetable(tt,strcat(res.cnst.codename,'-',res.cnst.experimentName,'.xlsx')); 19 | 20 | clear all -------------------------------------------------------------------------------- /littleHelpers/showThumbnails.m: -------------------------------------------------------------------------------- 1 | clear all, close all, clc 2 | 3 | folder = 'K:\MUCBERN-CRCDX\SVS only\H&E\'; % 'J:\LEEDS-SMALLBOWEL-DX\SVSonly'; % 'N:\DUESSEL-CRC-DX' 4 | outdir = 'J:\ThumbsOutMUCBERN\'; 5 | mkdir(outdir) 6 | 7 | allFiles = dir(folder); 8 | 9 | 10 | for i=3:numel(allFiles) 11 | if i==3 || mod(i,15)==0 12 | drawnow 13 | figure 14 | set(gcf,'Color','w') 15 | tiledlayout('flow','TileSpacing','compact') 16 | end 17 | try 18 | currFile = allFiles(i).name; 19 | currInfo = imfinfo(fullfile(folder,currFile)); 20 | numChannels = numel(currInfo); 21 | thumbImg = histeq(imread(fullfile(folder,currFile),numChannels-1)); 22 | imagesc(thumbImg); 23 | axis equal tight off 24 | title(currFile) 25 | nexttile 26 | imwrite(thumbImg,fullfile(outdir,[currFile,'.png'])); 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /networks/densenet512.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/densenet512.mat -------------------------------------------------------------------------------- /networks/mobilenetv2_512.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/mobilenetv2_512.mat -------------------------------------------------------------------------------- /networks/resnet18_512.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/resnet18_512.mat -------------------------------------------------------------------------------- /networks/shufflenet1024.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/shufflenet1024.mat -------------------------------------------------------------------------------- /networks/shufflenet128.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/shufflenet128.mat -------------------------------------------------------------------------------- /networks/shufflenet196.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/shufflenet196.mat -------------------------------------------------------------------------------- /networks/shufflenet256.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/shufflenet256.mat -------------------------------------------------------------------------------- /networks/shufflenet512.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/networks/shufflenet512.mat -------------------------------------------------------------------------------- /pretrainedModels/TWVIHFFKIHVH_macenko-TCCPT_isMSIH_lastModel_v6.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/pretrainedModels/TWVIHFFKIHVH_macenko-TCCPT_isMSIH_lastModel_v6.mat -------------------------------------------------------------------------------- /subroutines/anyAUC.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is an auxiliary function to parse the classification results 7 | 8 | function ishigh = anyAUC(resultCollection,thresh) 9 | 10 | classes = fieldnames(resultCollection.patientStats.FPR_TPR.AUC); 11 | for i = 1:numel(classes) 12 | AUC(i) = resultCollection.patientStats.FPR_TPR.AUC.(classes{i})(1); 13 | end 14 | 15 | ishigh = any(AUC>=thresh); 16 | 17 | end -------------------------------------------------------------------------------- /subroutines/assignTileLabel.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this script will assign a label to each block (tile) 7 | % the label is inherited from the parent patient 8 | 9 | function [allBlocks,AnnData,unmatchedBlockNames] = assignTileLabel(allBlocks,AnnData,cnst) 10 | 11 | disp('-- starting to parse all whole slide image names, overwriting block labels... '); 12 | 13 | allBlockFileNames = allBlocks.Files; % extract all block names 14 | allBlocks.Labels = repmat(categorical(cellstr('UNDEF')),1,numel(allBlocks.Labels)); % reset all block labels 15 | numImages = numel(AnnData.FILENAME); 16 | 17 | sanityCheck(numel(AnnData.FILENAME)==numel(AnnData.TARGET),'each image has a target label'); 18 | 19 | for ci = 1:numImages 20 | dispOn = (mod(ci,round(numImages/5))==0); % display some results 21 | currImageName = AnnData.FILENAME{ci}; 22 | matchingBlocks = contains(allBlockFileNames,currImageName); 23 | if sum(matchingBlocks)>cnst.maxBlockNum 24 | if dispOn 25 | disp(['---- will remove ',num2str(sum(matchingBlocks)-cnst.maxBlockNum),' excess blocks']); 26 | end 27 | matchingBlocks = removeExcessIndices(matchingBlocks,cnst.maxBlockNum); 28 | end 29 | if dispOn 30 | disp(['--- matched ',num2str(sum(matchingBlocks)),' blocks to ', currImageName]); 31 | disp(['--- progress: ',num2str(100*ci/numImages,2),'%']); 32 | end 33 | AnnData.NUMBLOCKS(ci) = sum(matchingBlocks); 34 | if any(matchingBlocks) % overwrite block labels 35 | allBlocks.Labels(matchingBlocks) = AnnData.TARGET(ci); 36 | end 37 | end 38 | disp([newline,'-- found ', num2str(sum(AnnData.NUMBLOCKS==0)),' whole slide image(s) without any matching blocks']); 39 | 40 | removeMe = (AnnData.NUMBLOCKS==0); 41 | for cfn = fieldnames(AnnData)' 42 | AnnData.(char(cfn))(removeMe) = []; 43 | end 44 | 45 | disp(['---- (I have removed these whole slide images,',... 46 | ' remaining whole slide images with >0 blocks: ',num2str(numel(AnnData.FILENAME))]); 47 | 48 | % remove all Blocks without a label 49 | disp(['--- there are ',num2str(numel(allBlocks.Files)),' blocks in total']); 50 | disp('--- removing unlabeled blocks'); 51 | unmatched = (allBlocks.Labels==categorical(cellstr('UNDEF'))) | isundefined(allBlocks.Labels); 52 | if ~isfield(cnst,'saveUnmatchedBlocks') || cnst.saveUnmatchedBlocks 53 | unmatchedBlockNames = allBlocks.Files(unmatched); 54 | else 55 | unmatchedBlockNames = []; 56 | end 57 | allBlocks.Files(unmatched) = []; 58 | disp(['--- after cleanup, there are ',num2str(numel(allBlocks.Files)),' blocks in total']); 59 | 60 | % --- fix a Matlab bug by which the UNDEF category is preserved even if 61 | % no element is undef (convert back and forth) 62 | allBlocks.Labels = categorical(cellstr(allBlocks.Labels)); 63 | 64 | end -------------------------------------------------------------------------------- /subroutines/binarizeNumerical.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function converts continuous numeric values to categories 7 | % input is numeric, output is cellstring 8 | 9 | function targetDataOut = binarizeNumerical(targetData,binarizeQuantile) 10 | removeMe = isnan(targetData); 11 | 12 | if ~isempty(binarizeQuantile) 13 | if isnumeric(binarizeQuantile) 14 | disp('-- detected numerical binarizeQuantile argument'); 15 | if binarizeQuantile>0.5 16 | warning('inverted binarizeQuantile (should be between 0 and 0.5)'); 17 | binarizeQuantile = 1-binarizeQuantile; 18 | end 19 | disp(['--- binarized at ',num2str(binarizeQuantile)]); 20 | 21 | targetDataOut = strrep(strrep(cellstr(num2str(double(targetData>=... 22 | quantile(targetData, 1-binarizeQuantile)))),... 23 | '0','NA'),'1',['TOP_',num2str(binarizeQuantile)]); % top percentile 24 | targetDataOut(targetData<=quantile(targetData, binarizeQuantile)) = ... 25 | {['BOT_',num2str(binarizeQuantile)]}; % bottom percentile 26 | else 27 | disp('-- detected NON-numerical binarizeQuantile argument'); 28 | if ischar(binarizeQuantile) && strcmp(binarizeQuantile,'grzero') 29 | disp('---- binarizing at ZERO (argument nonzero)'); 30 | targetDataOut = strrep(strrep(cellstr(num2str(double(targetData>... 31 | 0))),'0','_zero'),'1','_grzero'); 32 | end 33 | end 34 | else 35 | disp('--- binarize quantile is empty ... will binarize at the mean (omit NaN)'); 36 | targetDataOut = strrep(strrep(cellstr(num2str(double(targetData>=... 37 | mean(targetData,'omitnan')))),'0','LOmean'),'1','HImean'); 38 | end 39 | targetDataOut(removeMe) = {'undef'}; 40 | end -------------------------------------------------------------------------------- /subroutines/block2filename.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will parse block (tile) filenames and 7 | % extract the name of the parent whole slide image (WSI) 8 | 9 | 10 | function myList = block2filename(myList) 11 | 12 | maxList = numel(myList); 13 | % THIS IS for blocks that have been produced with the matlatb script and 14 | % have the syntax -blk- 15 | 16 | disp(['--will use ',myList{1},' to determine name format']); 17 | 18 | if contains(myList{1},'-blk-') 19 | disp('-- auto detected block syntax -blk-'); 20 | 21 | for i=1:maxList 22 | [~,myList{i},~] = fileparts(myList{i}); % remove path and suffix 23 | blk = strfind(myList{i},'-blk-'); 24 | myList{i} = myList{i}(1:(blk-1)); % remove "-blk-*" 25 | if mod(i,round(maxList/5))==0 26 | disp(['this block belongs to image ',... 27 | char(myList{i}),' parsed ',num2str(round(i/maxList*100)),'%']); 28 | end 29 | end 30 | 31 | elseif contains(myList{1},'_(') 32 | 33 | % THIS IS for blocks created with the QuPath script and have the syntax 34 | % _(,) 35 | 36 | disp('-- auto detected block syntax _(,)'); 37 | 38 | for i=1:maxList 39 | [~,myList{i},~] = fileparts(myList{i}); % remove path and suffix 40 | blk = strfind(myList{i},'_('); 41 | if isempty(blk) 42 | warning(['found corrupt tile file ',char(myList{i})]); 43 | myList{i} = 'CORRUPT_TILE_FILE'; 44 | else 45 | myList{i} = myList{i}(1:(blk(1)-1)); 46 | end 47 | 48 | if mod(i,round(maxList/5))==0 49 | disp(['this block belongs to image ',... 50 | char(myList{i}),' parsed ',num2str(round(i/maxList*100)),'%']); 51 | end 52 | end 53 | 54 | elseif contains(myList{1},'_tile') 55 | 56 | % THIS IS for blocks that have been produced at UChicago 57 | % have the syntax _ 58 | 59 | disp('-- auto detected block syntax _tile'); 60 | 61 | for i=1:maxList 62 | [~,myList{i},~] = fileparts(myList{i}); % remove path and suffix 63 | blk = strfind(myList{i},'_tile'); 64 | myList{i} = myList{i}(1:(blk(1)-1)); 65 | if mod(i,round(maxList/5))==0 66 | disp(['this block belongs to image ',... 67 | char(myList{i}),' parsed ',num2str(round(i/maxList*100)),'%']); 68 | end 69 | end 70 | 71 | elseif contains(myList{1},'_') 72 | 73 | % THIS IS for blocks that have been produced at UChicago 74 | % have the syntax _ 75 | 76 | disp('-- auto detected block syntax _'); 77 | warning('--- this can lead to mismatches, make sure format is correct!'); 78 | 79 | for i=1:maxList 80 | [~,myList{i},~] = fileparts(myList{i}); % remove path and suffix 81 | blk = strfind(myList{i},'_'); 82 | myList{i} = myList{i}(1:(blk(1)-1)); 83 | if mod(i,round(maxList/5))==0 84 | disp(['this block belongs to image ',... 85 | char(myList{i}),' parsed ',num2str(round(i/maxList*100)),'%']); 86 | end 87 | end 88 | 89 | else 90 | 91 | % THIS IS for blocks that have been produced with the matlab script and 92 | % have the syntax . 93 | 94 | disp('-- auto detected block syntax .'); 95 | 96 | for i=1:maxList 97 | [~,myList{i},~] = fileparts(myList{i}); % remove path and suffix 98 | blk = strfind(myList{i},'.'); 99 | myList{i} = myList{i}(1:(blk(1)-1)); 100 | if mod(i,round(maxList/5))==0 101 | disp(['this block belongs to image ',... 102 | char(myList{i}),' parsed ',num2str(round(i/maxList*100)),'%']); 103 | end 104 | end 105 | 106 | end 107 | 108 | end 109 | -------------------------------------------------------------------------------- /subroutines/calcPrediPval.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function claculates a p value for tile-level 7 | % preditions of a given category 8 | 9 | function [meanVal,meanOther,Pval] = ... 10 | calcPrediPval(trueClasses,rawPredictions,currCategory) 11 | 12 | maskThis = (trueClasses == categorical(currCategory)); 13 | 14 | allPred = rawPredictions.(char(currCategory)); 15 | predThis = (allPred(maskThis)); 16 | predOther = (allPred(~maskThis)); 17 | 18 | meanVal = mean(predThis); 19 | meanOther = mean(predOther); 20 | [~,Pval] = ttest2(predThis,predOther); 21 | end 22 | -------------------------------------------------------------------------------- /subroutines/combineTables.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is used to combine CLINI tables or SLIDE 7 | % tables from different projects. E.g. this happens 8 | % when you train on more than one cohort at once 9 | 10 | function tCombined = combineTables(tRaw,colnames) 11 | 12 | if isempty(colnames) 13 | disp('-- will concatenate tables, assume identical column names'); 14 | for i = 1:numel(tRaw) 15 | if i == 1 16 | tCombined = tRaw{i}; 17 | else 18 | tCombined = [tCombined;tRaw{i}]; 19 | end 20 | end 21 | else 22 | disp(['-- will concatenate tables using these column names: ',... 23 | strjoin(colnames,', ')]); 24 | for cc = 1:numel(colnames) 25 | currCol = colnames{cc}; 26 | for i = 1:numel(tRaw) 27 | if i == 1 28 | tCombined.(currCol) = tRaw{i}.(currCol); 29 | else 30 | tCombined.(currCol) = [tCombined.(currCol);tRaw{i}.(currCol)]; 31 | end 32 | end 33 | end 34 | tCombined = struct2table(tCombined); 35 | end -------------------------------------------------------------------------------- /subroutines/concatenatePredictions.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will concatenate predictions in a cross- 7 | % validated experiment 8 | 9 | function outStats = concatenatePredictions(stats) 10 | 11 | if numel(stats) == 1 && isstruct(stats) 12 | disp('-- there is only one partition, containing the full stats (no crossval)'); 13 | outStats = stats.blockStats; 14 | elseif numel(stats) == 1 && iscell(stats) 15 | disp('-- there is only one full partition in a multi-partition struct'); 16 | outStats = stats{1}.blockStats; 17 | else 18 | disp('-- stats from multiple partitions will be concatenated'); 19 | for i = 1:numel(stats) % iterate test sets from each crossval experiment and merge 20 | disp(['--- parsing partition ',num2str(i)]); 21 | if i ==1 22 | outStats.PLabels = stats{i}.blockStats.PLabels; 23 | outStats.Scores = stats{i}.blockStats.Scores; 24 | outStats.BlockNames = stats{i}.blockStats.BlockNames; 25 | outStats.outClasses = stats{i}.blockStats.outClasses; 26 | else 27 | outStats.PLabels = [outStats.PLabels;stats{i}.blockStats.PLabels]; 28 | outStats.Scores = [outStats.Scores;stats{i}.blockStats.Scores]; 29 | outStats.BlockNames = [outStats.BlockNames;stats{i}.blockStats.BlockNames]; 30 | sanityCheck(~any(outStats.outClasses~=stats{i}.blockStats.outClasses),... 31 | ['output classes in partition ',num2str(i),' match the 1st']); 32 | end 33 | outStats.partitions{i} = stats{i}.blockStats.BlockNames; 34 | end 35 | end 36 | end 37 | 38 | 39 | -------------------------------------------------------------------------------- /subroutines/containsmember.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is a general auxiliary function: 7 | % containsmember is to ismember 8 | % as contains is to strcmp 9 | 10 | function maskOut = containsmember(A,B) 11 | 12 | maskOut = zeros(size(A)); 13 | 14 | for i=1:numel(B) 15 | maskOut = maskOut | contains(A,B{i}); 16 | if mod(i,round(numel(B)/5))==0 17 | disp(['... parsed ',num2str(i/numel(B)*100,2),'%']); 18 | end 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /subroutines/copyFilesByLabel.m: -------------------------------------------------------------------------------- 1 | function copyFilesByLabel(myImds,tFolder) 2 | 3 | allLabels = unique(myImds.Labels); 4 | for i = 1:numel(allLabels) 5 | currLabel = allLabels(i); 6 | currTargetFolder = fullfile(tFolder,char(cellstr(currLabel))); 7 | mkdir(currTargetFolder); 8 | sourceFiles = myImds.Files(myImds.Labels == currLabel); 9 | for j = 1:numel(sourceFiles) 10 | copyfile(sourceFiles{j},currTargetFolder); 11 | end 12 | end 13 | 14 | end 15 | 16 | 17 | -------------------------------------------------------------------------------- /subroutines/copyIsField.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is a general auxiliary function 7 | % it will copy a field from structure A to structure B 8 | % if the field exists 9 | 10 | function out = copyIsField(fin,tfield,defvalue) 11 | 12 | if isfield(fin,tfield) && ~isempty(fin.(tfield)) 13 | out = fin.(tfield); 14 | else 15 | out = defvalue; 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /subroutines/copyfields.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is a general auxiliary function 7 | % copy field values from struct B to struct A 8 | 9 | function A = copyfields(A,B,flds) 10 | 11 | for i = 1:numel(flds) 12 | A.(flds{i}) = B.(flds{i}); 13 | end 14 | 15 | end -------------------------------------------------------------------------------- /subroutines/createLgraphUsingConnections.m: -------------------------------------------------------------------------------- 1 | % This function is copied from https://blogs.mathworks.com/deep-learning/2018/03/19/creating-a-dag-network-from-dag-parts/ 2 | % please see the original source for license 3 | 4 | % lgraph = createLgraphUsingConnections(layers,connections) creates a layer 5 | % graph with the layers in the layer array |layers| connected by the 6 | % connections in |connections|. 7 | 8 | function lgraph = createLgraphUsingConnections(layers,connections) 9 | 10 | lgraph = layerGraph(); 11 | for i = 1:numel(layers) 12 | lgraph = addLayers(lgraph,layers(i)); 13 | end 14 | 15 | for c = 1:size(connections,1) 16 | lgraph = connectLayers(lgraph,connections.Source{c},connections.Destination{c}); 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /subroutines/deployModel.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will use a trained model and deploy 7 | % it to an imageDatastore 8 | 9 | 10 | function stats = deployModel(cnst,hyperprm,finalModel,imdsTST) 11 | 12 | externaltest_AUG = ... 13 | augmentedImageDatastore(finalModel.Layers(1).InputSize(1:2),imdsTST); 14 | externaltest_AUG.MiniBatchSize = hyperprm.MiniBatchSize; 15 | disp('starting prediction...'); 16 | 17 | [stats.blockStats.PLabels,stats.blockStats.Scores] = classify(finalModel, ... 18 | externaltest_AUG, 'ExecutionEnvironment',hyperprm.ExecutionEnvironment,... 19 | 'MiniBatchSize',hyperprm.MiniBatchSize); 20 | 21 | disp('finished prediction.'); 22 | stats.blockStats.Accuracy = mean(stats.blockStats.PLabels == imdsTST.Labels); 23 | stats.blockStats.BlockNames = imdsTST.Files; 24 | stats.blockStats.outClasses = finalModel.Layers(end).Classes; 25 | 26 | if isfield(cnst,'saveTopTiles') && cnst.saveTopTiles>0 27 | saveTopTiles(stats,cnst,finalModel,imdsTST); 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /subroutines/deployModelBlind.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is similar to DeployModel but is used for cases in 7 | % which we do not own the target labels of the images (blinded deployment) 8 | 9 | function outputSummary = deployModelBlind(hyperprm,finalModel,allBlocksLabeled) 10 | 11 | test_AUG = augmentedImageDatastore(finalModel.Layers(1).InputSize(1:2),allBlocksLabeled); 12 | disp('starting prediction...'); 13 | test_AUG.MiniBatchSize = hyperprm.MiniBatchSize; 14 | %aa = activations(finalModel,test_AUG,'classoutput','ExecutionEnvironment','gpu'); 15 | 16 | [stats.blockStats.PLabels,stats.blockStats.Scores] = classify(finalModel, ... 17 | test_AUG, 'ExecutionEnvironment',hyperprm.ExecutionEnvironment,... 18 | 'MiniBatchSize',hyperprm.MiniBatchSize); 19 | disp('finished prediction.'); 20 | 21 | outputSummary.allAUC = []; 22 | outputSummary.stats = []; 23 | outputSummary.stats.blockStats.blockNames = allBlocksLabeled.Files; 24 | outputSummary.stats.blockStats.PLabels = stats.blockStats.PLabels; 25 | outputSummary.stats.blockStats.Scores = stats.blockStats.Scores; 26 | outputSummary.stats.blockStats.TLabels = []; 27 | outputSummary.stats.blockStats.targetCategories = cellstr(finalModel.Layers(end).Classes); 28 | 29 | end 30 | -------------------------------------------------------------------------------- /subroutines/dictionaryReplace.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is for batch-replacing substrings in 7 | % a list of strings 8 | 9 | function myText = dictionaryReplace(myText,dict) 10 | 11 | for i = 1:size(dict,1) 12 | 13 | myText = strrep(myText,dict{i,1},dict{i,2}); 14 | 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /subroutines/dispAllFields.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % display all fields of a given struct, for debugging 7 | 8 | function dispAllFields(structIn,varargin) 9 | 10 | allFields = fieldnames(structIn); 11 | if nargin == 1 12 | disp('++++++++'); 13 | indent = ''; 14 | else 15 | indent = varargin{1}; 16 | end 17 | 18 | for i = 1:numel(allFields) 19 | currField = allFields{i}; 20 | switch class(structIn.(currField)) 21 | case 'double' 22 | disp([indent,currField,': ',num2str(structIn.(currField))]); 23 | case 'logical' 24 | disp([indent,currField,': ',num2str(double(structIn.(currField)))]); 25 | case 'char' 26 | disp([indent,currField,': ',structIn.(currField)]); 27 | case 'cell' 28 | disp([indent,currField,': ']); 29 | case 'struct' 30 | % recursive call this function 31 | disp([indent,currField,': ...']); 32 | dispAllFields(structIn.(currField),' '); 33 | otherwise 34 | disp([indent,currField,': <',class(structIn.(currField)),'>']); 35 | end 36 | end 37 | 38 | if nargin == 1 39 | disp('++++++++'); 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /subroutines/dispUniqueValues.m: -------------------------------------------------------------------------------- 1 | function dispUniqueValues(inValues,numMax) 2 | 3 | dispU = cellstr(unique(inValues)); 4 | if numel(dispU)>numMax 5 | disp(dispU(1:numMax)); 6 | disp('... and others!...'); 7 | else 8 | disp(dispU); 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /subroutines/doGPUworkaround.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is used to do a GPU workaround on Ubuntu 7 | % which is caused by a CUDA problem. Not needed on 8 | % Windows computers. 9 | 10 | function doGPUworkaround() 11 | 12 | workaroundSuccess = false; 13 | 14 | % try N times 15 | for i = 1:10 16 | try 17 | warning off parallel:gpu:device:DeviceLibsNeedsRecompiling 18 | warning('on','nnet_cnn:warning:GPULowOnMemory'); % set low memory warning on 19 | nnet.internal.cnngpu.reluForward(1); % workaround for CUDA problem on Ubuntu 20 | gpuArray.eye(2)^2; % workaround for CUDA problem on Ubuntu 21 | workaroundSuccess = true; 22 | continue 23 | catch 24 | warning('-- GPU workaround failed, try again'); 25 | pause(2); 26 | end 27 | end 28 | 29 | if workaroundSuccess 30 | disp('--- GPU workaround succeeded'); 31 | else 32 | error('GPU workaround failed after max. number of attempts'); 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /subroutines/equalizeClasses.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this script will equalize the clases in an image datastore object so that 7 | % there is the same number of training instances for each label 8 | 9 | function imds = equalizeClasses(imds,maxBlockNum) 10 | 11 | disp('-- starting to equalize the classes on TILE level just before training'); 12 | numLabels = countEachLabel(imds); % count each label 13 | % the minimum number of tiles per label is EITHER the number of the 14 | % lowest prevalent label OR a fixed number maxTargetNum, whichever is 15 | % smaller 16 | 17 | disp([' -- number of Tiles in the least abundant class is ',num2str(min(numLabels.Count))]); 18 | disp([' -- the hard upper limit for tiles per class is ',num2str(maxBlockNum)]); 19 | 20 | targetNum = min(maxBlockNum,min(numLabels.Count)); 21 | 22 | disp([' -- therefore I will limit tiles per class to ',num2str(targetNum)]); 23 | 24 | ulabels = numLabels.Label(:); 25 | 26 | for iu = 1:numel(ulabels) 27 | disp(['... equalizing label ',char(cellstr(ulabels(iu)))]); 28 | classIndices = (imds.Labels == ulabels(iu)); 29 | if sum(classIndices) > targetNum % have to delete some labels 30 | allInstances = find(classIndices); 31 | rng('default'); % for reproducibility 32 | allInstances = allInstances(randperm(numel(allInstances))); % shuffle elements 33 | imds.Files(allInstances((targetNum+1):end)) = []; % fixed the missing +1 bug 34 | end 35 | end 36 | 37 | end -------------------------------------------------------------------------------- /subroutines/exportTiles.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is used to export the training set just before training. 7 | % That means that class balancing by undersampling has been performed. 8 | % Thus, the training set can be re-used for other tasks. This is an 9 | % experimental function and is not part of production-ready pipelines 10 | 11 | function exportTiles(cnst,trainSet,testSet) 12 | 13 | disp('will copy train set and test set to export folders...'); 14 | 15 | targetFolder = strrep(cnst.folderName.Dump,'DUMP',['EXPORT_',cnst.baseName]); 16 | 17 | if ~isempty(trainSet) 18 | copyFilesByLabel(trainSet,[targetFolder,'TRAIN']); 19 | disp('... finished copying training files'); 20 | end 21 | 22 | 23 | if ~isempty(testSet) 24 | copyFilesByLabel(testSet,[targetFolder,'TEST']); 25 | disp('... finished copying training files'); 26 | end 27 | 28 | end -------------------------------------------------------------------------------- /subroutines/forest.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is used for visualization of prediction performance 7 | 8 | function forest(IDs,vmed,vlow,vhigh,mytitle,fontSi) 9 | 10 | % flip all values 11 | IDs = flipud(IDs); 12 | vmed = flipud(vmed); 13 | vlow = flipud(vlow); 14 | vhigh = flipud(vhigh); 15 | 16 | ebline = 1; % base line width 17 | circsize = 120; % base circle size 18 | xlow = 0; % x axis lower limit 19 | % fontSi = 18; % font size 20 | fontNa = 'Arial'; % font name 21 | fontWe = 'normal'; % font weight 22 | 23 | ymax = numel(IDs); 24 | 25 | hold on 26 | 27 | plot([0.5 0.5],[0 ymax+1],'k','LineWidth',ebline); 28 | 29 | % x grid 30 | xes = xlow:0.05:1; 31 | plot([xes;xes],[0 ymax+1],'color',0.85*[1 1 1],'LineWidth',ebline*0.75); 32 | 33 | % y grid 34 | yes = 1:ymax; 35 | plot([xlow-0.05,1],[yes;yes],'color',0.85*[1 1 1],'LineWidth',ebline*0.75); 36 | 37 | eb = errorbar(vmed,1:ymax,... 38 | vmed-vlow,... 39 | vhigh-vmed,'horizontal','k.','LineWidth',ebline*1.2); 40 | 41 | sc = scatter(vmed,1:ymax,circsize,'k','filled'); 42 | 43 | %ntext = strrep(strcat(repmat({'n='},ymax,1),cellstr(num2str(vnum))),' ',' '); 44 | tx = @(vin) cellstr(num2str(vin,'%.3f')); 45 | auctext = strcat(repmat({''},ymax,1),tx(vmed),{' ['},tx(vlow),{','},tx(vhigh),{']'}); 46 | % add n numbers 47 | % text(xlow+0.01+0*(1:ymax),1:ymax,... %myT.AUROC_hig(targets)+0.01 48 | % ntext,'VerticalAlignment','middle','FontSize',fontSi,'FontName',fontNa,... 49 | % 'BackgroundColor','w','FontWeight',fontWe); 50 | % add auc 51 | text(1.01+0*(1:ymax),1:ymax,... 52 | auctext,... 53 | 'VerticalAlignment','middle','HorizontalAlignment','left','FontSize',fontSi,... 54 | 'FontName',fontNa,'FontWeight',fontWe); 55 | 56 | set(gca,'YTick',1:ymax); 57 | set(gca,'YTickLabel',IDs); 58 | 59 | set(gca,'XTick',xlow:0.1:1); 60 | set(gca,'XTickLabel',(num2str((xlow:0.1:1)','%.1f'))); 61 | set(gca,'FontSize',fontSi); 62 | set(gca,'FontName',fontNa); 63 | set(gca,'FontWeight',fontWe); 64 | xlim([xlow-0.05,1.2]); 65 | eb.LineWidth = ebline; 66 | 67 | sc.MarkerEdgeColor = 'k'; 68 | 69 | set(gca,'TickLength',[0 0]); 70 | xlabel(['AUROC +/- confidence interval']); 71 | 72 | ylim([0,ymax+1]); 73 | set(gca,'box','off'); 74 | 75 | set(gcf,'Color','w'); 76 | 77 | title(mytitle); 78 | %grid('on'); 79 | 80 | 81 | end 82 | -------------------------------------------------------------------------------- /subroutines/freezeWeights.m: -------------------------------------------------------------------------------- 1 | % source for this function (see also for license info) 2 | % https://www.mathworks.com/help/deeplearning/examples/train-deep-learning-network-to-classify-new-images.html 3 | 4 | % layers = freezeWeights(layers) sets the learning rates of all the 5 | % parameters of the layers in the layer array |layers| to zero. 6 | 7 | function layers = freezeWeights(layers) 8 | 9 | for ii = 1:size(layers,1) 10 | props = properties(layers(ii)); 11 | for p = 1:numel(props) 12 | propName = props{p}; 13 | if ~isempty(regexp(propName, 'LearnRateFactor$', 'once')) 14 | layers(ii).(propName) = 0; 15 | end 16 | end 17 | end 18 | 19 | end 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /subroutines/getDeepHistologyVersion.m: -------------------------------------------------------------------------------- 1 | function version = getDeepHistologyVersion() 2 | 3 | version = 'v6.33 - 01 Apr 2021'; 4 | 5 | disp('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); 6 | disp(['~~~~ this is version: ', version, ' ~~~~']); 7 | disp('~~~~ caution, experimental software, see license/disclaimer ~~~~'); 8 | disp('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); 9 | 10 | end -------------------------------------------------------------------------------- /subroutines/getDeepestFolder.m: -------------------------------------------------------------------------------- 1 | function foldr = getDeepestFolder(pathIn) 2 | 3 | parentf = fileparts(pathIn); 4 | seps = strfind(parentf,filesep); 5 | foldr = parentf((seps(end)+1):end); 6 | 7 | end -------------------------------------------------------------------------------- /subroutines/getHyperparameters.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function contains hard-coded default hyperparameters 7 | % for neural network training and deployment 8 | 9 | function hyperprm = getHyperparameters(paramset) 10 | 11 | % hard-code our default deep learning hyperparameters 12 | hyperprm.ValidationFrequency = 50; % check validation performance every N iterations, 500 is 3x per epoch 13 | hyperprm.ValidationPatience = 5; % wait N times before abort 14 | hyperprm.L2Regularization = 1e-4; % optimization L2 constraint 15 | hyperprm.MiniBatchSize = 512; % mini batch size, limited by GPU RAM, default 256 on P6000 16 | hyperprm.MaxEpochs = 4; % max. epochs for training, default 4 17 | hyperprm.learnRateFactor = 2; % learning rate factor for rewired layers 18 | hyperprm.ExecutionEnvironment = 'gpu'; % environment for training and classification 19 | hyperprm.InitialLearnRate = 5e-5; % linear learning rate, default 5e-5 20 | hyperprm.hotLayers = 30; % number of hot layers in the network (count from end, default 30) 21 | hyperprm.GradientDecayFactor = 0.9; % adam's default (explicit is better than implicit) 22 | hyperprm.Epsilon = 1e-8; % offset to avoid zero division (adam's default) 23 | hyperprm.DispatchInBackground = false; % asynchronous prefetch queuing of datastore (insane RAM explosion) 24 | hyperprm.MBSclassify = 128; % mini batch size for classification in xval mode 25 | 26 | % hyperparameter sets allow to mandate changes with command line options 27 | disp(['- loading hyperparameter set: ',paramset]); 28 | switch lower(paramset) 29 | case 'default' 30 | disp('-- no hyperparam modification needed'); 31 | case 'px224_80' 32 | disp('-- large mini batch size and a lot of trainable layers'); 33 | hyperprm.MiniBatchSize = 1024; 34 | hyperprm.MBSclassify = 1024; 35 | hyperprm.hotLayers = 80; 36 | case 'megaproject' 37 | hyperprm.MaxEpochs = 8; 38 | hyperprm.ValidationPatience = 5; 39 | hyperprm.ValidationFrequency = 200; 40 | disp('-- colorectal megaproject'); 41 | case 'subflexi' 42 | disp('-- enabled semi flexible learning'); 43 | hyperprm.hotLayers = 30; 44 | hyperprm.MaxEpochs = 8; 45 | hyperprm.MiniBatchSize = 512; 46 | case 'vram48' 47 | disp('-- hyperparameters for VRAM 48 GB'); 48 | hyperprm.MiniBatchSize = 1024; 49 | case 'flexi' 50 | disp('-- enabled flexible learning (40 hot layers) & long training (careful, overfitting!)'); 51 | hyperprm.hotLayers = 40; 52 | hyperprm.MaxEpochs = 25; 53 | hyperprm.MiniBatchSize = 256; 54 | case 'ganmode' 55 | disp('-- enabled GAN mode learning'); 56 | hyperprm.hotLayers = 40; 57 | hyperprm.MaxEpochs = 10; 58 | hyperprm.MiniBatchSize = 128; 59 | case 'lowresource' 60 | disp('--- low resource hyperparams'); 61 | hyperprm.MiniBatchSize = 256; 62 | case 'deploy' 63 | disp('--- deploy hyperparams'); 64 | hyperprm.MiniBatchSize = 1024; 65 | case 'debug' 66 | disp('--- debug hyperparams'); 67 | hyperprm.MiniBatchSize = 128; 68 | case 'verylowresource' 69 | disp('--- low resource hyperparams'); 70 | hyperprm.MiniBatchSize = 128; 71 | hyperprm.MaxEpochs = 1; 72 | hyperprm.hotLayers = 5; 73 | case 'mega' 74 | disp('--- mega block hyperparams'); 75 | hyperprm.hotLayers = 10; 76 | hyperprm.MiniBatchSize = 32; 77 | hyperprm.MaxEpochs = 6; 78 | hyperprm.MBSclassify = 32; 79 | hyperprm.InitialLearnRate = 1e-4; 80 | 81 | otherwise 82 | error('-- invalid hyperparameter set selected'); 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /subroutines/getInputParser.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this funcion defines the input arguments for the main scripts 7 | 8 | function p = getInputParser(myVargs) 9 | 10 | p = inputParser; % prepare to parse user input 11 | p.CaseSensitive = false; % input is not case sensitive 12 | p.PartialMatching = false; % strict input parser options 13 | p.KeepUnmatched = false; % strict input parser options 14 | 15 | %% general parameters 16 | addParameter(p,'saveFormat','v6',@ischar); % version of results file 17 | 18 | %% training of deep learning models 19 | addParameter(p,'experiment','',@ischar); % which experiment to load 20 | addParameter(p,'gpuDev',1,@isnumeric); % GPU Device (1 or greater) 21 | addParameter(p,'maxBlockNum',1000,@isnumeric); % number of blocks 22 | addParameter(p,'trainFull',false,@islogical); % train on full dataset after xval 23 | addParameter(p,'modelTemplate','shufflenet512',@ischar); % which pretrained model 24 | addParameter(p,'backwards',false,@islogical); % for each experiments, work backwards 25 | addParameter(p,'binarizeQuantile',[]); % split HI LO at this quantile (between 0 and 0.5) 26 | addParameter(p,'foldxval',3,@isnumeric); % if cross validation is used, this is the fold, default 3 27 | addParameter(p,'xvalmode','xval',@ischar); % can be 'xval' or 'holdout' (= only first run) 28 | addParameter(p,'aggregateMode','majority',@ischar); % how to pool block predictions per patient,... 29 | % 'majority', 'majorityRobust' (works also if one of the classes is not present in the test set), 'ignoreClass', 'mean' or 'max' 30 | addParameter(p,'tableModeClini','XLSX',@ischar); % file format of clinical table, XLSX or CSV 31 | addParameter(p,'hyper','default',@ischar); % set of hyperparameters 32 | addParameter(p,'valSet',[],@isnumeric); % validation set proportion of training set, default no validation (recommend 0.05) 33 | addParameter(p,'filterBlocks','',@ischar); % use an alternative set of tiles, such as normalized tiles 34 | addParameter(p,'subsetTargetsBy',[]); % subset the patients by a variable 35 | addParameter(p,'subsetTargetsLevel',[]); % subset the patients by this level in the variable 36 | addParameter(p,'skipExistingTargets',false,@islogical); % skip target prediction if result file exists 37 | addParameter(p,'forgiveError',false,@islogical); % ignore errors during training and go on to the next task 38 | addParameter(p,'maxBlocksPerClass',1e9,@isnumeric); % hard limit to the number of tiles per class in xval mode 39 | addParameter(p,'nBootstrapAUC',10,@isnumeric); % bootstrap for AUC confidence interval, default 10 40 | addParameter(p,'whichIgnoreClass','',@ischar); % ignore this class for statistics, only works if aggregateMode is ignoreClass 41 | addParameter(p,'evalAfterEachRun',false,@islogical); % ignore this class for statistics, only works if aggregateMode is ignoreClass 42 | 43 | % holy input parameters, do not change 44 | addParameter(p,'saveUnmatchedBlocks',false,@islogical); % save list of unmatchable blocks, may blow up output file size 45 | addParameter(p,'undersampleTrainingSet',true,@islogical); % equalize training set just before training 46 | addParameter(p,'binarizeThresh',5,@isnumeric); % convert num targets with more than N levels to HI / LO 47 | addParameter(p,'fileformatBlocks','.jpg',@ischar); % define format for Blocks 48 | 49 | % experimental parameters, do not change in production mode 50 | addParameter(p,'exportTiles',false,@islogical); % export tiles of first 'xval' experiment just before training 51 | 52 | %% deployment of pre-trained models 53 | addParameter(p,'trainedModelID',[]); % use a previously trained model for deployment 54 | addParameter(p,'trainedModelFolder',[]); % path to previously trained model for deployment 55 | addParameter(p,'saveTopTiles',false,@islogical); % save the top tiles after deployment 56 | 57 | %% visualization of results 58 | addParameter(p,'doPlot',false,@islogical); % show the ROC on screen 59 | addParameter(p,'doPrint',false,@islogical); % save the ROC to PDF and Imgs to PNG 60 | addParameter(p,'plotThreshold',false,@islogical); % plot the operating threshold on top 61 | addParameter(p,'exportBlockPred',false,@islogical); % save tile-level table for maps 62 | addParameter(p,'exportBlockFormat','csv',@ischar); % must be csv or xlsx 63 | addParameter(p,'exportTopTiles',0,@isnumeric); % save showcase tiles 64 | addParameter(p,'topPatients',3,@isnumeric); % showcase tiles from these patients 65 | addParameter(p,'visualizeBaselineTiles',false,@islogical); % for visualization of top tiles, fall back to BLOCKS 66 | addParameter(p,'plotFontSize',12,@isnumeric); % font size for plots 67 | addParameter(p,'plotAUCthreshold',0.75,@isnumeric); % detailed visualization only for high performance targets 68 | addParameter(p,'onlyExplicitTargets',true,@islogical); % visualize only targets specified in experiment file 69 | addParameter(p,'plotForest',false,@islogical); % plot a forest chart 70 | addParameter(p,'forestLevels','',@ischar); % restrict forest plot names to these levels 71 | addParameter(p,'debugMode',false,@islogical); % debug mode 72 | addParameter(p,'overrideTileDrive',false,@islogical); % override the drive letter for tiles for vis 73 | addParameter(p,'overrideDriveFrom',' ',@ischar); % override the drive letter from this 74 | addParameter(p,'overrideDriveTo',' ',@ischar); % override the drive letter to this 75 | 76 | %% fixed visualization parameters, do not change 77 | addParameter(p,'axTicks',0:0.2:1); % primary axis tick labels for ROC curves 78 | addParameter(p,'axTicksFine',0:0.1:1); % secondary axis tick labels for ROC curves 79 | addParameter(p,'scaleYprerec',false,@islogical); % scale y axis of pre rec curve, default false 80 | 81 | %% train on previously exported dataset 82 | addParameter(p,'trainOnFolder','',@ischar); % scale y axis of pre rec curve, default false 83 | 84 | %% normalization of tiles (blocks) 85 | addParameter(p,'numParWorkers',0,@isnumeric); % default: do not use parallel workers 86 | 87 | parse(p,myVargs{:}); % parse input arguments 88 | end 89 | 90 | -------------------------------------------------------------------------------- /subroutines/getROCstats.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a parsing function for ROC statistics 7 | 8 | function out = getROCstats(xin,yin,precision,yval) 9 | 10 | y2 = round(yin,precision); 11 | yval = round(yval,precision); 12 | yhit = min(find(y2==yval)); 13 | if ~isempty(yhit) 14 | out = [round(xin(yhit),precision),round(yin(yhit),precision),yhit]; 15 | else 16 | out = []; 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /subroutines/getTargetClass.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will take a target name (something to be predicted from 7 | % histology images, e.g. "MSI") and assign it to one of these classes: 8 | % mutation-any, mutation-driver, pathology-subtype, signature, etc ... 9 | % this is for visualization of results 10 | 11 | function classOut = getTargetClass(dataIn) 12 | 13 | disp('--- replacing class names for better legibility'); 14 | 15 | % preprocess 16 | classOut = dictionaryReplace(dataIn,getDefaultDictionary('classes')); 17 | 18 | % replace 19 | classOut(contains(classOut,'anymut')) = {'mutation (any)'}; 20 | classOut(contains(classOut,'driver')) = {'mutation (driver)'}; 21 | classOut(contains(classOut,'subtype')) = {'signature/subtype'}; 22 | classOut(contains(classOut,'signature')) = {'signature/subtype'}; 23 | classOut(contains(classOut,'standard')) = {'standard'}; 24 | classOut(contains(classOut,'experimental')) = {'experimental'}; 25 | 26 | end -------------------------------------------------------------------------------- /subroutines/getTopTiles.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is used to collect the top tiles for the top 7 | % patients for visualization and interpretability 8 | 9 | function [dcollect,sparsePatients] = getTopTiles(resultCollection,cnst,currTarget) 10 | 11 | disp('loading all blocks in image datastore for top tile creation'); 12 | uPatCat = unique(resultCollection.patientStats.rawData.trueCategory); 13 | uCategories = getUcategoriesInOrder(uPatCat,resultCollection.blockStats); 14 | % prepare data for tile-patient assignment 15 | cnst.annotation.targetCol = currTarget; 16 | AnnData = getAnnotationData(cnst); 17 | [~,newBlockStats] = predictions2performance(resultCollection.blockStats,AnnData,cnst); 18 | 19 | if isfield(cnst,'doPlotHistogram') && cnst.doPlotHistogram 20 | figure 21 | hi = histogram(categorical(newBlockStats.parentPatient),'DisplayOrder','ascend'); 22 | disp('--- found patients with few tiles, you might want to review their WSIs:'); 23 | sparsePatients = hi.Categories(hi.Values<=cnst.exportTopTiles); 24 | disp(sparsePatients); 25 | else 26 | sparsePatients = []; 27 | end 28 | % use highest scoring tiles from the highest scoring true positive patients 29 | % require each patient to have at least as many tiles as will be plotted 30 | for icat = 1:numel(uCategories) 31 | currCategory = uCategories(icat); % get current category 32 | currPatientsMask = resultCollection.patientStats.rawData.trueCategory == currCategory; 33 | 34 | % remove sparse patients from this list 35 | if ~isempty(sparsePatients) 36 | [~,rmpatients,~] = intersect(resultCollection.patientStats.rawData.patientNames,sparsePatients); 37 | disp(['--- removed ',num2str(sum(rmpatients)),' sparse patients from visualization candidates']); 38 | currPatientsMask(rmpatients) = 0; % removing sparse patients 39 | end 40 | 41 | % find all patients belonging to this category (ground 42 | % truth data) & get their patient-level predictions, then 43 | % find the N top patients 44 | currPatients = resultCollection.patientStats.rawData.patientNames(currPatientsMask); 45 | currPatPredictions = resultCollection.patientStats.rawData.predictions.(char(cellstr(currCategory)))(currPatientsMask); 46 | [uup,uip] = sort(currPatPredictions,'descend'); 47 | disp(['--- will use these patients for class ',char(cellstr(currCategory))]); 48 | targetPatients = currPatients(uip(1:min(cnst.topPatients,numel(currPatients)))) %#ok 49 | 50 | % iterate top patients and find their N top tiles 51 | for ipat = 1:numel(targetPatients) 52 | thisPatientID = targetPatients(ipat); 53 | thisPatientTiles = find(strcmp(newBlockStats.parentPatient, thisPatientID)); 54 | 55 | % % do a sanity check on the order of categories 56 | % [~,mm] = max(mean(newBlockStats.Scores(thisPatientTiles,:),1)); % which column has the highest mean 57 | % find(uCategories==currCategory); % the current true category 58 | 59 | [uus,uis] = sort(newBlockStats.Scores(thisPatientTiles,uCategories==currCategory),'descend'); 60 | 61 | % collect tiles and prepare montage for export 62 | uis((cnst.exportTopTiles+1):end) = []; 63 | uus((cnst.exportTopTiles+1):end) = []; 64 | if ipat ==1 65 | dcollect.(char(currCategory)).TileNames = newBlockStats.BlockNames(thisPatientTiles(uis)); 66 | dcollect.(char(currCategory)).ParentNames = repmat(cellstr(thisPatientID),cnst.exportTopTiles,1); 67 | dcollect.(char(currCategory)).TileScores = uus; 68 | dcollect.(char(currCategory)).PatientScores = repmat(uup(ipat),cnst.exportTopTiles,1); 69 | else 70 | dcollect.(char(currCategory)).TileNames = [dcollect.(char(currCategory)).TileNames; newBlockStats.BlockNames(thisPatientTiles(uis))]; 71 | dcollect.(char(currCategory)).ParentNames = [dcollect.(char(currCategory)).ParentNames; repmat(cellstr(thisPatientID),cnst.exportTopTiles,1)]; 72 | dcollect.(char(currCategory)).TileScores = [dcollect.(char(currCategory)).TileScores;uus]; 73 | dcollect.(char(currCategory)).PatientScores = [dcollect.(char(currCategory)).PatientScores; repmat(uup(ipat),cnst.exportTopTiles,1)]; 74 | end 75 | 76 | if cnst.visualizeBaselineTiles 77 | disp('--- for visualization of top tiles, I will replace BLOCKS_NORM and BLOCKS_MACENKO by BLOCKS') 78 | dcollect.(char(currCategory)).TileNames = strrep(dcollect.(char(currCategory)).TileNames,'BLOCKS_NORM','BLOCKS') 79 | dcollect.(char(currCategory)).TileNames = strrep(dcollect.(char(currCategory)).TileNames,'BLOCKS_MACENKO','BLOCKS') 80 | end 81 | 82 | 83 | end 84 | 85 | end % end of category iteration 86 | 87 | 88 | 89 | end -------------------------------------------------------------------------------- /subroutines/getTrainingOptions.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function returns hard coded default training options 7 | 8 | function opts = getTrainingOptions(hyperprm,imdsVAL) 9 | 10 | if isempty(imdsVAL) % without internal validation 11 | opts = trainingOptions('adam',... 12 | 'InitialLearnRate',hyperprm.InitialLearnRate,... 13 | 'L2Regularization',hyperprm.L2Regularization,... 14 | 'MiniBatchSize',hyperprm.MiniBatchSize,... 15 | 'MaxEpochs',hyperprm.MaxEpochs,... 16 | 'ExecutionEnvironment',hyperprm.ExecutionEnvironment,... 17 | 'VerboseFrequency',50,... 18 | 'GradientDecayFactor',hyperprm.GradientDecayFactor,... 19 | 'Epsilon',hyperprm.Epsilon,... 20 | 'DispatchInBackground',hyperprm.DispatchInBackground,... 21 | 'Plots','none'); 22 | else % with internal validation 23 | opts = trainingOptions('adam',... 24 | 'InitialLearnRate',hyperprm.InitialLearnRate,... 25 | 'L2Regularization',hyperprm.L2Regularization,... 26 | 'MiniBatchSize',hyperprm.MiniBatchSize,... 27 | 'MaxEpochs',hyperprm.MaxEpochs,... 28 | 'ExecutionEnvironment',hyperprm.ExecutionEnvironment,... 29 | 'ValidationFrequency',hyperprm.ValidationFrequency,... 30 | 'ValidationPatience',hyperprm.ValidationPatience,... 31 | 'ValidationData',imdsVAL,... 32 | 'VerboseFrequency',50,... 33 | 'GradientDecayFactor',hyperprm.GradientDecayFactor,... 34 | 'Epsilon',hyperprm.Epsilon,... 35 | 'DispatchInBackground',hyperprm.DispatchInBackground,... 36 | 'Plots','none'); 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /subroutines/getUcategoriesInOrder.m: -------------------------------------------------------------------------------- 1 | function outCats = getUcategoriesInOrder(uPatCat,blockStats) 2 | % this function will return the HARD prediction categories in the right 3 | % order (same order as columns for soft predictions) 4 | 5 | % for EACH category, find the first block that has this cat as a hard 6 | % pred, then find the corresponding soft pred dimension 7 | 8 | for i=1:numel(uPatCat) 9 | currCat = uPatCat(i); 10 | firstBlockInCat = find(blockStats.PLabels==currCat,1); 11 | [~,thisCatDim] = max(blockStats.Scores(firstBlockInCat,:)); % first block larger soft pred 12 | 13 | outDims(i) = thisCatDim; 14 | outCats(i) = currCat; 15 | end 16 | 17 | [~,xi] = sort(outDims); % bring dims in right order 18 | outCats = outCats(xi); % reorder out cats 19 | 20 | warning('reordered categories for highly predictive tiles, be careful'); 21 | disp('-- old order was'); 22 | uPatCat %#ok 23 | disp('-- new order is'); 24 | outCats %#ok 25 | disp('--- (in previous versions of the script, this could lead to label switching in visualization maps in rare cases)'); 26 | 27 | end -------------------------------------------------------------------------------- /subroutines/initializeDeepImagePipeline.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this script will prepare everything for the deep image pipeline 7 | % all constants will be set, subfolders included etc. 8 | 9 | function [cnst,fCollect] = initializeDeepImagePipeline(cnst) 10 | 11 | cnst.version = getDeepHistologyVersion(); 12 | 13 | rng('default'); % reset random number generator for reproducibility 14 | sq = @(varargin) varargin; % helper function 15 | 16 | % do a GPU workaround on Linux 17 | if ~isfield(cnst,'nogpu') && ~ismac() && ~ispc() 18 | doGPUworkaround(); % this is needed on Linux :-/ 19 | else 20 | disp('- GPU workaround not needed'); 21 | end 22 | 23 | % check if multiple cohorts should be merged 24 | if isa(cnst.ProjectName,'cell') && numel(cnst.ProjectName)>1 % multi cohorts 25 | disp('-- this is a *merged* cohort (made up of multiple cohorts)'); 26 | cnst.multipleCohorts = true; 27 | cnst.allProjects = cnst.ProjectName; 28 | cnst.ProjectName = strjoin(sq(cnst.ProjectName{:}),'--'); 29 | for i = 1:numel(cnst.allProjects) 30 | disp(['-- adding block folder of cohort: ',cnst.allProjects{i}]); 31 | if isfield(cnst,'filterBlocks') && ~isempty(cnst.filterBlocks) && ~strcmp(cnst.filterBlocks,'') % non-standard block dir 32 | disp('-- will modify experiment name to account for non-standard block dir'); 33 | cnst.experimentName = matlab.lang.makeValidName(... 34 | [cnst.experimentName,'-',cnst.filterBlocks]); 35 | cnst.folderName.Blocks{i} = modifyBlockDir(cnst,fullfile(cnst.folderName.Temp,cnst.allProjects{i})); % abs. path to block save folder 36 | else % standard block dir 37 | disp('-- load blocks from standard block dir'); 38 | cnst.folderName.Blocks{i} = fullfile(cnst.folderName.Temp,cnst.allProjects{i},'/BLOCKS/'); % abs. path to block save folder 39 | end 40 | end 41 | if isfield(cnst,'overrideBaseDir') 42 | cnst.folderName.Blocks = overrideBaseDir(cnst.folderName.Blocks,cnst.folderName.Temp,cnst.overrideBaseDir); 43 | end 44 | else % standard single cohort 45 | cnst.multipleCohorts = false; 46 | disp('-- preparing single cohort'); 47 | 48 | % check for overridden tile folder (defined in experiment file) 49 | if isfield(cnst,'overrideFolder') && ~isempty(cnst.overrideFolder) 50 | disp(['-- manual OVERRIDE of tile (blocks) folder from [',cnst.ProjectName,... 51 | '] to [',cnst.overrideFolder,'] /[BLOCKDIR]/']); 52 | cnst.projectFolderName = cnst.overrideFolder; % abs. path to block save folder 53 | else 54 | cnst.projectFolderName = cnst.ProjectName; 55 | disp('-- no manual override of tile folder'); 56 | end 57 | 58 | % check for subset tile folder (e.g. stroma only; defined on command line) 59 | if isfield(cnst,'filterBlocks') && ~isempty(cnst.filterBlocks) && ~strcmp(cnst.filterBlocks,'') % non-standard block dir 60 | disp('-- will modify experiment name to account for non-standard block dir'); 61 | cnst.experimentName = matlab.lang.makeValidName(... 62 | [cnst.experimentName,'-',cnst.filterBlocks]); 63 | cnst.folderName.Blocks = modifyBlockDir(cnst,fullfile(cnst.folderName.Temp,cnst.projectFolderName)); % abs. path to block save folder 64 | else % standard block dir 65 | cnst.folderName.Blocks = fullfile(cnst.folderName.Temp,cnst.projectFolderName,'/BLOCKS/'); % abs. path to block save folder 66 | end 67 | 68 | 69 | end 70 | 71 | disp(['--- I will load tiles from: ', cnst.folderName.Blocks]); 72 | 73 | % prepare path names and create folders if needed 74 | cnst.folderName.Dump = fullfile(cnst.folderName.Temp,cnst.ProjectName,'/DUMP/'); % abs. path to block save folder 75 | [~,~,~] = mkdir(char(cnst.folderName.Dump)); 76 | 77 | % define SLIDE table and CLINI table 78 | % the SLIDE table needs to have the columns FILENAME and PATIENT 79 | % the CLINI table needs to have the column PATIENT plus an target column 80 | cnst.annotation.Dir = './cliniData/'; 81 | if cnst.multipleCohorts 82 | for i = 1:numel(cnst.allProjects) 83 | disp('-- preparing table references for multiple cohorts'); 84 | currProj = cnst.allProjects{i}; 85 | cnst.annotation.SlideTable{i} = [currProj,'_SLIDE.csv']; 86 | cnst.annotation.CliniTable{i} = [currProj,'_CLINI.csv']; 87 | end 88 | else 89 | disp('-- preparing table reference for single cohort'); 90 | cnst.annotation.SlideTable = [cnst.ProjectName,'_SLIDE.csv']; 91 | cnst.annotation.CliniTable = [cnst.ProjectName,'_CLINI.csv']; 92 | end 93 | cnst.baseName = cnst.experimentName; % backup the current experiment name 94 | 95 | if ~isfield(cnst,'skipLoadingBlocks') || ~cnst.skipLoadingBlocks 96 | disp('--- starting to load blocks'); 97 | fCollect = loadTileFiles(cnst); % load tile (block) files 98 | else 99 | disp('--- will SKIP loading blocks'); 100 | fCollect = []; 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /subroutines/loadExperiment.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will load an experiment file, specifying 7 | % the name of the cohort, the location of the tiles and the 8 | % target variables to be predicted 9 | 10 | function cnst = loadExperiment(codename) 11 | 12 | try % load source data 13 | currentData = fileread(fullfile('./experiments/',[codename,'.txt'])); 14 | catch 15 | error(['did not find this source file: ',codename]); 16 | end 17 | 18 | try % decode data and dump to cnst (thereby overwriting previous params) 19 | cnst = jsondecode(currentData); 20 | catch 21 | error(['could not decode source data for: ',codename]); 22 | end 23 | 24 | % ensure consistent file names 25 | cnst.folderName.Temp = fullfile(cnst.folderName.Temp); 26 | 27 | % create random experiment ID 28 | rng('shuffle'); 29 | cnst.experimentName = randseq(12,'Alphabet','AA'); 30 | disp(['--- this is the unique experiment name: ',cnst.experimentName]); 31 | 32 | % save file name as codename 33 | cnst.codename = codename; 34 | 35 | % output status 36 | disp(['successfully loaded source file: ',codename]); 37 | 38 | % add holy parameters, do not change 39 | cnst.blocks.resizeMethod = 'resize'; % how to make the blocks fit the neural network input size 40 | % options: 'resize', 'randcrop', 'centercrop'; default 'resize' 41 | 42 | end -------------------------------------------------------------------------------- /subroutines/loadTileFiles.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this script will load Blocks (Tiles) from the disk 7 | 8 | function fileCollection = loadTileFiles(cnst) 9 | 10 | disp(['loading blocks for project ',cnst.ProjectName]); 11 | 12 | % load blocks (patches, tiles) - this works for one cohort 13 | % (cnst.folderName.Blocks has only one element) or multiple cohorts 14 | % (cnst.folderName.Blocks is a cell with multiple elements) 15 | fileCollection.Blocks = imageDatastore(cnst.folderName.Blocks, ... 16 | 'IncludeSubfolders',true,'FileExtensions',cnst.fileformatBlocks,'LabelSource','foldernames'); 17 | 18 | if cnst.debugMode 19 | disp('--- will not reset block labels for debug purposes'); 20 | else 21 | disp('--- resetting block labels'); 22 | fileCollection.Blocks.Labels = repmat(categorical({'NA'}),numel(fileCollection.Blocks.Labels),1); % remove labels 23 | end 24 | 25 | disp(['I found ',num2str(numel(fileCollection.Blocks.Files)),' blocks (=tiles)']); 26 | end -------------------------------------------------------------------------------- /subroutines/modifyBlockDir.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this script modifies the standard tile directory for 7 | % projects using only a subset of tiles 8 | 9 | function outdir = modifyBlockDir(cnst,basedir) 10 | 11 | disp('-- will modify block directory (load non-standard set of blocks=tiles)'); 12 | switch upper(cnst.filterBlocks) 13 | case 'TUMOR' 14 | disp(['--- will only load TUMOR blocks',newline,... 15 | '(automatically defined pure tumor tiles)']); 16 | outdir = fullfile(basedir,'/BLOCKS_TUSTRO/TUMOR/'); 17 | case 'STROMA' 18 | disp(['--- will only load STROMA blocks',newline,... 19 | '(automatically defined pure stroma tiles)']); 20 | outdir = fullfile(basedir,'/BLOCKS_TUSTRO/STROMA/'); 21 | case 'STROMA128' 22 | disp(['--- will only load STROMA128 blocks',newline,... 23 | '(manually outlined pure stroma areas)']); 24 | outdir = fullfile(basedir,'/BLOCKS_STROMA128/'); 25 | case 'BIOPSY' 26 | disp(['--- will only load BIOPSY blocks',newline,... 27 | '(manually outlined luminal approx 2 mm region)']); 28 | outdir = fullfile(basedir,'/BLOCKS-BIOPSY/'); 29 | case 'NORMALIZED' 30 | disp(['--- will only load NORMALIZED blocks',newline]); 31 | outdir = fullfile(basedir,'/BLOCKS_NORM/'); 32 | case 'MACENKO' 33 | disp(['--- will only load MACENKO NORMALIZED blocks',newline]); 34 | outdir = fullfile(basedir,'/BLOCKS_NORM_MACENKO/'); 35 | otherwise 36 | disp(['trying to set non-standard alternative block dir: ',newline]); 37 | outdir = fullfile(basedir,cnst.filterBlocks); 38 | disp(outdir); 39 | end 40 | end -------------------------------------------------------------------------------- /subroutines/myStruct2array.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2020 2 | % introduced to circumvent Matlab's error for missing undocumented funtion 3 | % struct2array, see https://de.mathworks.com/support/search.html/answers/ 4 | % 493612-any-fixes-for-undefined-function-or-variable-struct2array?q=&fq=asset_type_name:answer%20category:matlab/graphics-objects-programming&page=-4 5 | 6 | function out = myStruct2array(structIn) 7 | temp = struct2cell(structIn); 8 | out = horzcat(temp{:}); 9 | end -------------------------------------------------------------------------------- /subroutines/overrideBaseDir.m: -------------------------------------------------------------------------------- 1 | function outDirs = overrideBaseDir(inDirs,origBaseDir,overrideDirs) 2 | 3 | warning('--- will override base directories for multiple input project ... be careful!'); 4 | disp('---- original tile input directories are:') 5 | inDirs' 6 | 7 | sanityCheck(numel(inDirs)==numel(overrideDirs),'correct number of overrideDirs'); 8 | 9 | outDirs = inDirs; % preallocate 10 | for i=1:numel(inDirs) 11 | outDirs{i} = strrep(inDirs{i},origBaseDir,overrideDirs{i}); 12 | end 13 | 14 | disp('---- new tile input directories are:') 15 | outDirs' 16 | 17 | end -------------------------------------------------------------------------------- /subroutines/parseStatistics.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function parses prediction statistics and 7 | % provides a summary 8 | 9 | function res = parseStatistics(currStats) 10 | 11 | res.varN = currStats.cnst.annotation.targetCol; 12 | 13 | if isfield(currStats.cnst,'subsetTargets') 14 | res.proj = strcat(currStats.cnst.subsetTargets.by,'-',currStats.cnst.subsetTargets.level); 15 | else 16 | res.proj = {'noSubsetTargets'}; 17 | end 18 | 19 | if isfield(currStats.cnst,'filterBlocks') 20 | res.filterBlocks = strcat(currStats.cnst.filterBlocks); 21 | else 22 | res.filterBlocks = {'noFilterBlocks'}; 23 | end 24 | 25 | levelNames = fieldnames(currStats.patientStats.FPR_TPR.AUC)'; 26 | 27 | for nl = 1:numel(levelNames) % iterate levels (categories) 28 | outTable.levelNames{nl} = levelNames{nl}; 29 | outTable.nPat(nl) = currStats.patientStats.nPats.(levelNames{nl}); 30 | 31 | % extract AUC of standard ROC (FPR vs TPR) 32 | outTable.AUROC_avg(nl) = round(currStats.patientStats.FPR_TPR.AUC.(levelNames{nl})(1),3); 33 | outTable.AUROC_low(nl) = round(currStats.patientStats.FPR_TPR.AUC.(levelNames{nl})(2),3); 34 | outTable.AUROC_hig(nl) = round(currStats.patientStats.FPR_TPR.AUC.(levelNames{nl})(3),3); 35 | 36 | % extract AUC of precision-recall curve 37 | outTable.AUCPR_avg(nl) = round(currStats.patientStats.PRE_REC.AUC.(levelNames{nl})(1),3); 38 | outTable.AUCPR_low(nl) = round(currStats.patientStats.PRE_REC.AUC.(levelNames{nl})(2),3); 39 | outTable.AUCPR_hig(nl) = round(currStats.patientStats.PRE_REC.AUC.(levelNames{nl})(3),3); 40 | 41 | % calculate P value for patient-level prediction between categories 42 | [outTable.meanCat(nl),outTable.meanOth(nl),outTable.pVal(nl)] = ... 43 | calcPrediPval(currStats.patientStats.rawData.trueCategory,... 44 | currStats.patientStats.rawData.predictions,... 45 | levelNames(nl)); 46 | 47 | end 48 | outTable.fracPat = outTable.nPat / sum(outTable.nPat); 49 | res.outT = struct2table(transposeStruct(outTable)); 50 | disp([newline,'this is the result table ',newline]); 51 | disp(res.outT) 52 | disp([newline,'*********',newline]); 53 | 54 | end -------------------------------------------------------------------------------- /subroutines/plotForestChart.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is used for visualization of prediction performance 7 | % 8 | % this function will be deprecated in future releases, 9 | % please use forest() which is more generic 10 | 11 | function plotForestChart(currIDs,targets,myT,cnst,myTitle) 12 | 13 | ymax = numel(targets); 14 | %group = myT.group(ui); 15 | 16 | plot([0.5 0.5],[1 ymax],'k','LineWidth',cnst.ebline*0.5); 17 | hold on 18 | %plot([0.75 0.75],[1 sum(currMask)],'k-','LineWidth',0.5); 19 | eb = errorbar(myT.AUROC_avg(targets),1:ymax,... 20 | myT.AUROC_avg(targets)-myT.AUROC_low(targets),... 21 | myT.AUROC_hig(targets)-myT.AUROC_avg(targets),'horizontal','k.','LineWidth',cnst.ebline); 22 | 23 | sc = scatter(myT.AUROC_avg(targets),1:ymax,cnst.circleSize,myT.meanAUC(targets),'filled'); 24 | colormap([1,1,1;(brewermap(255,'YlGnBu'))]); 25 | 26 | stars = strrep(strrep(cellstr(num2str(myT.fdrPval(targets)<1)),'1',' '),'0',' '); % preallocate 27 | stars(myT.fdrPval(targets) cnst.sigThreshold) = {' '}; 27 | ntext = strrep(strcat(repmat({'n='},ymax,1),cellstr(num2str(currT.nPat))),' ',' '); 28 | auctext = strrep(strcat(repmat({''},ymax,1),cellstr(num2str(round(currT.AUROC_avg,2)))),' ',''); 29 | % add n numbers 30 | text(cnst.xlim(1)+0.01+0*(1:ymax),1:ymax,... %currT.AUROC_hig+0.01 31 | ntext,'VerticalAlignment','middle','FontSize',cnst.fontSizeAlternate,'FontName',cnst.fontName,'BackgroundColor','w','FontWeight',cnst.fontWeight); 32 | % add auc 33 | text(1.01+0*(1:ymax),1:ymax,... %currT.AUROC_hig+0.01 34 | strcat(auctext,stars),... 35 | 'VerticalAlignment','middle','HorizontalAlignment','left','FontSize',cnst.fontSize,'FontName',cnst.fontName,'FontWeight',cnst.fontWeight); 36 | 37 | set(gca,'YTick',1:ymax); 38 | 39 | % prepare IDs 40 | currIDs = strrep(strcat(currT.varN,'-',currT.levelNames),'_','-'); 41 | currIDs = dictionaryReplace(currIDs, getDefaultDictionary('plot')); 42 | 43 | set(gca,'YTickLabel',currIDs); 44 | 45 | set(gca,'XTick',cnst.xticks); 46 | set(gca,'XTickLabel',cnst.xticks); 47 | set(gca,'FontSize',cnst.fontSize); 48 | set(gca,'FontName',cnst.fontName); 49 | set(gca,'FontWeight',cnst.fontWeight); 50 | xlim(cnst.xlim); 51 | eb.LineWidth = 1; 52 | %sc.CData = [0 0 0]; 53 | sc.MarkerEdgeColor = 'k'; 54 | caxis(cnst.caxis); 55 | %colorbar('eastoutside'); 56 | set(gca,'TickLength',[0 0]); 57 | xlabel('AUROC'); 58 | 59 | ylim([0,cnst.maxYCats+1]); 60 | set(gca,'box','off'); 61 | title(dictionaryReplace(currTitle,getDefaultDictionary('tumor_types'))); 62 | 63 | 64 | 65 | end -------------------------------------------------------------------------------- /subroutines/plotMyConfusion.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this auxiliary script can plot a confusion matrix 7 | 8 | % JN Kather 2018, plot confusion matrix for image classifier 9 | 10 | function plotMyConfusion(trueLabels,predictedLabels) 11 | 12 | allgroups = cellstr(unique(trueLabels)); 13 | figure(),imagesc(confusionmat(trueLabels,predictedLabels)); 14 | xlabel('true'),ylabel('predicted'); 15 | set(gca,'XTick',1:numel(allgroups)); % add decorations 16 | set(gca,'YTick',1:numel(allgroups)); 17 | set(gca,'XTickLabel',allgroups); % add decorations 18 | set(gca,'YTickLabel',allgroups); 19 | axis square, set(gcf,'Color','w'); 20 | colorbar 21 | colz = caxis; 22 | caxis([0,colz(2)]) 23 | drawnow 24 | end -------------------------------------------------------------------------------- /subroutines/plotViolinChartSub.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is an auxiliary function to plot violin charts 7 | 8 | function plotViolinChartSub(currT,cnst,currTitle) 9 | 10 | mylog = @(vin) -log10((vin)); 11 | 12 | violin(min(cnst.maxYplotSig,mylog(currT.pFDRglobal)),... 13 | 'facecolor',[.5 .5 .5],'edgecolor',[1 1 1],'bw',0.5); 14 | hold on 15 | 16 | plot(cnst.xlim,repmat((mylog(cnst.sigThreshold)),2,1),... 17 | 'k','LineWidth',cnst.ebline*0.5); 18 | hold on 19 | 20 | 21 | 22 | xlabel('') 23 | ylabel('p') 24 | 25 | title(currTitle); 26 | caxis(cnst.caxis); 27 | colormap([1,1,1;(brewermap(255,'YlGnBu'))]); 28 | xlim(cnst.xlim); 29 | ylim([0,cnst.maxYplotSig+1]); 30 | 31 | set(gca,'box','off'); 32 | title(dictionaryReplace(currTitle,getDefaultDictionary('tumor_types'))); 33 | set(gca,'FontSize',cnst.fontSize); 34 | set(gca,'FontName',cnst.fontName); 35 | set(gca,'FontWeight',cnst.fontWeight); 36 | set(gca,'XTick',[]); 37 | set(gca,'XTickLabel',[]); 38 | 39 | set(gca,'YTick',1:cnst.maxYplotSig); 40 | set(gca,'YTickLabel',cellstr("1e-"+(1:cnst.maxYplotSig)')); 41 | axis square 42 | 43 | end 44 | -------------------------------------------------------------------------------- /subroutines/plotVolcanoChartSub.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is an auxiliary function to plot volcano charts 7 | 8 | function plotVolcanoChartSub(currT,cnst,currTitle,violincolor) 9 | 10 | 11 | 12 | violin(min(cnst.maxYplotSig,cnst.mylog(currT.pFDRglobal)),... 13 | 'facecolor',violincolor,'edgecolor',[1 1 1],'bw',cnst.violinBW); 14 | hold on 15 | 16 | plot(cnst.xlim,repmat((cnst.mylog(cnst.sigThreshold)),2,1),... 17 | 'k','LineWidth',cnst.ebline*0.5); 18 | hold on 19 | 20 | sc = scatter(max(min(cnst.xlim),currT.AUROC_avg),... 21 | min(cnst.maxYplotSig,cnst.mylog(currT.pFDRglobal)),... 22 | 75,currT.meanAUC,'filled','LineWidth',cnst.ebline*0.5,... 23 | 'MarkerEdgeColor','k'); 24 | 25 | if cnst.plotTextVolcano 26 | currT.cleanVarN = dictionaryReplace(strrep(currT.varN,'_','-'),getDefaultDictionary('plot')); 27 | sigs = find(currT.pFDRglobal<=cnst.sigThreshold); 28 | hold on 29 | for i=1:numel(sigs) 30 | text(currT.AUROC_avg(sigs(i)),... 31 | 0.025*randn()+min(cnst.maxYplotSig,cnst.mylog(currT.pFDRglobal(sigs(i)))),... 32 | currT.cleanVarN(sigs(i))); 33 | end 34 | end 35 | 36 | xlabel('AUROC') 37 | ylabel('FDR-corrected p-value') 38 | 39 | title(currTitle); 40 | caxis(cnst.caxis); 41 | colormap([1,1,1;(brewermap(255,'YlGnBu'))]); 42 | xlim(cnst.xlim); 43 | ylim([0,cnst.maxYplotSig+1]); 44 | 45 | set(gca,'box','off'); 46 | title(dictionaryReplace(currTitle,getDefaultDictionary('tumor_types'))); 47 | set(gca,'FontSize',cnst.fontSize); 48 | set(gca,'FontName',cnst.fontName); 49 | set(gca,'FontWeight',cnst.fontWeight); 50 | set(gca,'XTick',cnst.xticks); 51 | set(gca,'XTickLabel',cnst.xticks); 52 | 53 | set(gca,'YTick',1:cnst.maxYplotSig); 54 | set(gca,'YTickLabel',cellstr("1e-"+(1:cnst.maxYplotSig)')); 55 | axis square 56 | 57 | 58 | end 59 | -------------------------------------------------------------------------------- /subroutines/readProjectTables.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will read clinical and slide tables for the current 7 | % project 8 | 9 | function [tCLINI,tSLIDE] = readProjectTables(cnst) 10 | 11 | if cnst.multipleCohorts 12 | for i = 1:numel(cnst.allProjects) 13 | disp('-- preparing table references for multiple cohorts'); 14 | switch cnst.tableModeClini 15 | case 'CSV' 16 | cnst.annotation.CliniTable = [cnst.allProjects{i},'_CLINI.csv']; 17 | tCLINI_raw{i} = readtable(fullfile(cnst.annotation.Dir,... 18 | cnst.annotation.CliniTable),'Delimiter',',','ReadVariableNames',true); 19 | case 'XLSX' 20 | cnst.annotation.CliniTable = [cnst.allProjects{i},'_CLINI.xlsx']; 21 | tCLINI_raw{i} = readtable(... 22 | fullfile(cnst.annotation.Dir,cnst.annotation.CliniTable)); 23 | otherwise 24 | error('unspecified clinical table mode'); 25 | end 26 | cnst.annotation.SlideTable = [cnst.allProjects{i},'_SLIDE.csv']; 27 | slidepath = fullfile(cnst.annotation.Dir,cnst.annotation.SlideTable); 28 | disp(['--- reading this cohort slide table from ',slidepath]); 29 | tSLIDE_raw{i} = readtable(slidepath,'Delimiter',',','ReadVariableNames',true); 30 | end 31 | cnst.annotation.SlideTable = ''; 32 | cnst.annotation.CliniTable = ''; 33 | % convert all slide names to string 34 | for iT = 1:numel(tSLIDE_raw) 35 | if isnumeric(tSLIDE_raw{iT}.FILENAME) 36 | warning('found numeric FILENAME column in a slide table. will convert to string'); 37 | tSLIDE_raw{iT}.FILENAME = cellstr(num2str(tSLIDE_raw{iT}.FILENAME)); 38 | end 39 | end 40 | tSLIDE = combineTables(tSLIDE_raw,{'PATIENT','FILENAME'}); 41 | if isfield(cnst,'subsetTargetsBy') && ~isempty(cnst.subsetTargetsBy) 42 | disp('--- combining CLINI tables while keeping subset variable and target variable'); 43 | tCLINI = combineTables(tCLINI_raw,{'PATIENT',cnst.annotation.targetCol,cnst.subsetTargetsBy}); 44 | else 45 | disp('--- combining CLINI tables, keeping target variable only'); 46 | tCLINI = combineTables(tCLINI_raw,{'PATIENT',cnst.annotation.targetCol}); 47 | end 48 | else 49 | disp('-- preparing table reference for single cohort'); 50 | switch cnst.tableModeClini 51 | case 'CSV' 52 | cnst.annotation.CliniTable = [cnst.ProjectName,'_CLINI.csv']; 53 | tCLINI = readtable(fullfile(cnst.annotation.Dir,cnst.annotation.CliniTable),'Delimiter',',','ReadVariableNames',true); 54 | case 'XLSX' 55 | cnst.annotation.CliniTable = [cnst.ProjectName,'_CLINI.xlsx']; 56 | tCLINI = readtable(fullfile(cnst.annotation.Dir,cnst.annotation.CliniTable)); 57 | otherwise 58 | error('unspecified clinical table mode'); 59 | end 60 | cnst.annotation.SlideTable = [cnst.ProjectName,'_SLIDE.csv']; 61 | tSLIDE = readtable(fullfile(cnst.annotation.Dir,cnst.annotation.SlideTable),'Delimiter',',','ReadVariableNames',true); 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /subroutines/removeCols.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is an auxiliary function to remove columns from a table 7 | 8 | function tableIn = removeCols(tableIn,ColNames) 9 | 10 | for i = 1:numel(ColNames) 11 | try 12 | tableIn.(ColNames{i}) = []; 13 | catch 14 | warning(['could not remove column ',ColNames{i}]); 15 | end 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /subroutines/removeDupliRows.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is an auxiliary function to remove 7 | % rows with duplicate entries from a table 8 | 9 | function myT = removeDupliRows(myT,varIn) 10 | 11 | [urows,uu] = unique(myT.(varIn)); 12 | myDupliT = myT; 13 | myDupliT(uu,:) = []; 14 | 15 | if numel(myT.(varIn)) == numel(urows) 16 | disp(' no duplicates detected in table'); 17 | else 18 | disp(' detected duplicate rows as follows:'); 19 | myDupliT 20 | myT = myT(uu,:); 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /subroutines/removeEmptyCells.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will find missing values in a cell 7 | 8 | 9 | function myCell = removeEmptyCells(myCell) 10 | % remove tiles with missing patient name 11 | disp('-- detecting empty elements in cell array...') 12 | emptyCells = cellfun(@isempty,myCell); 13 | if sum(emptyCells>0) 14 | disp(['--- detected ',num2str(sum(emptyCells)),' empty cells... ',... 15 | ' in a total of ',num2str(numel(emptyCells)),' elements.']); 16 | myCell(emptyCells) = []; 17 | disp('----- removed empty cells, continue...'); 18 | else 19 | disp('--- there were no empty cells... continue'); 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /subroutines/removeExcessIndices.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a general auxiliary function to remove randomly 7 | % chosen entries from a list 8 | 9 | function myBoolMatOut = removeExcessIndices(myBoolMat,numTrue) 10 | rng('default'); 11 | myBoolMat = logical(myBoolMat); % ensure that bool is bool 12 | myBoolMatOut = false(size(myBoolMat)); 13 | excess = sum(myBoolMat)-numTrue; 14 | if excess>0 15 | myHits = find(myBoolMat); 16 | myHits = myHits(randperm(numel(myHits))); % shuffle hits 17 | myBoolMatOut(myHits(1:numTrue)) = true; 18 | else 19 | error('this should never happen (there is no excess)'); 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /subroutines/sanityCheck.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a general auxiliary function 7 | % for performing data checks 8 | 9 | function sanityCheck(criterion,message) 10 | 11 | if criterion 12 | disp(['-- sanity check PASSED for: ',char(message)]); 13 | else 14 | error(['-- sanity check FAILED for: ',char(message)]); 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /subroutines/saveTopTiles.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function will identify and save images 7 | % which are highly ranked by a neural network 8 | 9 | function saveTopTiles(stats,cnst,finalModel,imdsTST) 10 | 11 | disp('-- will start to save the top tiles'); 12 | for i = 1:size(stats.blockStats.Scores,2) % for each category 13 | disp(['--- starting category ',num2str(i)]); 14 | targetDir = fullfile('./output_blocks/',cnst.trainedModelID,'/',... 15 | cnst.ProjectName,'/',char(cellstr(finalModel.Layers(end).Classes(i)))); 16 | mkdir(targetDir); 17 | [~,ui] = sort(stats.blockStats.Scores(:,i),'descend'); 18 | for j = 1:cnst.saveTopTiles 19 | montageData(:,:,:,j) = imread(char(imdsTST.Files(ui==j))); 20 | % copyfile(sourceImage,targetDir); 21 | end 22 | m = montage(montageData,'ThumbnailSize',[512, 512]); 23 | imwrite(m.CData,[targetDir,'/lastMontage_512.png']); 24 | end 25 | 26 | end -------------------------------------------------------------------------------- /subroutines/softSanityCheck.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a general auxiliary function 7 | % for performing data checks 8 | 9 | function softSanityCheck(criterion,message) 10 | 11 | if criterion 12 | disp(['-- soft sanity check PASSED for: ',char(message)]); 13 | else 14 | warning(['-- soft sanity check FAILED for: ',char(message)]); 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /subroutines/splitImdsForXVal.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % split image data store of blocks for cross validation 7 | 8 | function [imdsContainer,AnnData] = splitImdsForXVal(allBlocksLabeled,AnnData,cnst) 9 | 10 | disp('-- starting stratified patient-level split for cross validation'); 11 | ugroups = unique(AnnData.TARGET); 12 | 13 | AnnData.PARTITION = uint8(nan(1,numel(AnnData.TARGET))); % preallocate 14 | for ui = 1:numel(ugroups) % split each target group separately for balance 15 | disp(['--- splitting patient-level label ',char(cellstr(ugroups(ui)))]); 16 | % locate all unique patients in the target group 17 | currUniqPats = unique(AnnData.PATIENT(AnnData.TARGET == ugroups(ui))); 18 | % calculate the fraction of this group in the total cohort 19 | currUniqPatsFraction = numel(currUniqPats)/numel(unique(AnnData.PATIENT)); 20 | disp(['-- the current group makes up ',num2str(currUniqPatsFraction),' of the total cohort']); 21 | 22 | if isfield(cnst,'patientsPerPartition') && ~isempty(cnst.patientsPerPartition) 23 | disp('-- enforcing upper limit for # patients in partition (for learning curve)'); 24 | ids = splitListFixedNum(numel(currUniqPats),cnst.foldxval,ui, round(currUniqPatsFraction*cnst.patientsPerPartition)); 25 | else 26 | disp('-- stratified splitting on patient level (default behavior)'); 27 | ids = splitList(numel(currUniqPats),cnst.foldxval,ui); 28 | end 29 | if isempty(ids) 30 | warning('split patient list failed... aborting (possible reason: wrong CLINI table)'); 31 | imdsContainer = []; 32 | AnnData = []; 33 | return; 34 | end 35 | for up = 1:numel(currUniqPats) 36 | currPatient = currUniqPats{up}; 37 | AnnData.PARTITION(strcmp(AnnData.PATIENT,currPatient)) = ids(up); 38 | end 39 | end 40 | 41 | allBlockNames = allBlocksLabeled.Files; % gather TILE names 42 | allBlockWSIs = block2filename(allBlockNames); % match TILEs to SLIDEs 43 | % SPLIT PATIENT COHORT for a balanced cross validation 44 | for ua = 1:cnst.foldxval 45 | disp(['-- creating partition ',num2str(ua),' of ',num2str(cnst.foldxval)]); 46 | % find all patients for current partition 47 | matchingWSI = AnnData.FILENAME(AnnData.PARTITION == ua); 48 | sanityCheck(numel(matchingWSI)== numel(unique(matchingWSI)),'number of filenames'); 49 | mB = containsmember(allBlockWSIs',matchingWSI'); 50 | disp(['--- matched ',num2str(sum(mB)),' blocks to this group']); 51 | sanityCheck(sum(mB)>0,'not zero blocks'); 52 | disp('--- starting to subset image datastore...'); 53 | imdsContainer{ua} = copy(allBlocksLabeled); %#ok 54 | imdsContainer{ua}.Files(~mB) = []; % remove non-matching blocks 55 | disp('--- removed non-matching blocks...'); 56 | end 57 | 58 | end -------------------------------------------------------------------------------- /subroutines/splitList.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a general function to split a list into multiple 7 | % parts 8 | 9 | function ids = splitList(listLength,numParts,randSeed) 10 | 11 | if numParts<=listLength 12 | disp('--- there are more patients than partitions in this group... good.'); 13 | % create group ID list 14 | ids = repmat(1:numParts,1,ceil(listLength/numParts)); 15 | % shuffle list 16 | rng(randSeed); 17 | ids = ids(randperm(numel(ids))); 18 | ids = ids(1:listLength); % crop list to target length 19 | else 20 | disp(['--- there are ',num2str(numParts),' partitions and ',num2str(listLength),... 21 | ' patients in this group :-(']); 22 | warning('there are fewer patients than partitions in this group ... aborting'); 23 | ids = []; 24 | end 25 | 26 | end -------------------------------------------------------------------------------- /subroutines/splitListFixedNum.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is the same function as splitList, but enforce a fixed number of elements for 7 | % the first N-1 partitions.... all remaining items will be allocated to 8 | % partition N 9 | 10 | function ids = splitListFixedNum(listLength,numParts,randSeed,numItems) 11 | 12 | if numParts<=listLength 13 | disp('--- there are more patients than partitions in this group... good.'); 14 | % create group ID list 15 | ids = repmat(1:numParts,1,min(numItems,ceil(listLength/numParts))); 16 | if length(ids) max_y 73 | max_y=pos(4)+pos(2)+ff/5*2; 74 | end 75 | else 76 | oldtitle = h(i); 77 | end 78 | end 79 | 80 | if max_y > plotregion 81 | scale = (plotregion-min_y)/(max_y-min_y); 82 | for i=1:numAxes 83 | pos = thePositions(i,:); 84 | pos(2) = (pos(2)-min_y)*scale+min_y; 85 | pos(4) = pos(4)*scale-(1-scale)*ff/5*3; 86 | set(h(i),'position',pos); 87 | end 88 | end 89 | 90 | np = get(gcf,'nextplot'); 91 | set(gcf,'nextplot','add'); 92 | if ~isempty(oldtitle) 93 | delete(oldtitle); 94 | end 95 | axes('pos',[0 1 1 1],'visible','off','Tag','suptitle'); 96 | ht=text(.5,titleypos-1,str);set(ht,'horizontalalignment','center','fontsize',fs); 97 | set(gcf,'nextplot',np); 98 | axes(haold); 99 | if nargout 100 | hout=ht; 101 | end 102 | end 103 | 104 | function resetUnits(h, oldUnits) 105 | % Reset units on axes object. Note that one of these objects could have 106 | % been an old supertitle that has since been deleted. 107 | valid = isgraphics(h); 108 | set(h(valid), {'Units'}, oldUnits(valid)); 109 | end 110 | -------------------------------------------------------------------------------- /subroutines/trainMyNetwork.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this function is used to train the actual neural network 7 | 8 | function [postNet,myPredictions] = trainMyNetwork(preNet,imdsTRN,imdsTST,cnst,hyperprm) 9 | rng('default'); 10 | 11 | if isempty(imdsTST) 12 | disp([newline,'-- starting the training with ',num2str(numel(imdsTRN.Files)),... 13 | ' training blocks and NO test blocks']); 14 | else 15 | disp(['-- starting the training with ',num2str(numel(imdsTRN.Files)),... 16 | ' training blocks and ',num2str(numel(imdsTST.Files)),' test blocks']); 17 | end 18 | 19 | % prepare data augmenter 20 | trainingAugmenter = imageDataAugmenter('RandXReflection',true,'RandYReflection',true); 21 | disp(['---- network image input size is: ',num2str(preNet.imageInputSize)]); 22 | 23 | if isfield(cnst,'valSet')&&~isempty(cnst.valSet)&&cnst.valSet<1&&cnst.valSet>0 24 | disp(['-- will chop off a validation set (',num2str(cnst.valSet),') from training']); 25 | [imdsVAL,imdsTRN_final] = splitEachLabel(copy(imdsTRN),cnst.valSet,'randomized'); 26 | opts = getTrainingOptions(hyperprm,augmentedImageDatastore(preNet.imageInputSize,imdsVAL)); 27 | else 28 | disp('-- will NOT use a validation set'); 29 | imdsTRN_final = copy(imdsTRN); 30 | opts = getTrainingOptions(hyperprm,[]); 31 | end 32 | imdsTRN_AUG = augmentedImageDatastore(preNet.imageInputSize,imdsTRN_final,... 33 | 'DataAugmentation',trainingAugmenter,'OutputSizeMode',cnst.blocks.resizeMethod); 34 | 35 | t = tic; 36 | postNet = trainNetwork(imdsTRN_AUG, preNet.lgraph, opts); 37 | myPredictions.trainTime = toc(t); 38 | myPredictions.blockStats.outClasses = postNet.Layers(end).Classes; 39 | 40 | if ~isempty(imdsTST) % evaluate test set 41 | disp('- starting to evaluate test set'); 42 | externalTST_AUG = augmentedImageDatastore(preNet.imageInputSize,imdsTST,... 43 | 'OutputSizeMode',cnst.blocks.resizeMethod); 44 | externalTST_AUG.MiniBatchSize = hyperprm.MiniBatchSize; 45 | [myPredictions.blockStats.PLabels,myPredictions.blockStats.Scores] = classify(postNet, ... 46 | externalTST_AUG, 'ExecutionEnvironment',hyperprm.ExecutionEnvironment,... 47 | 'MiniBatchSize',hyperprm.MBSclassify); 48 | myPredictions.blockStats.Accuracy = mean(myPredictions.blockStats.PLabels == imdsTST.Labels); 49 | myPredictions.blockStats.BlockNames = imdsTST.Files; 50 | else % no test set defined, so cannot return any stats 51 | disp('no test set defined, so cannot return any stats'); 52 | myPredictions.blockStats = []; 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /subroutines/transposeStruct.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is a general function similar to the 7 | % transpose operator that works on structs 8 | 9 | function structOut = transposeStruct(structIn) 10 | 11 | allNames = fieldnames(structIn); 12 | 13 | for i = 1:numel(allNames) 14 | currName = allNames{i}; 15 | structOut.(currName) = structIn.(currName)'; 16 | if iscell(structOut.(currName)) & isnumeric(structOut.(currName){1}) 17 | structOut.(currName) = cell2mat(structOut.(currName)); 18 | end 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /subroutines/violin.m: -------------------------------------------------------------------------------- 1 | % violin plot 2 | % Based on the work by Hoffmann H, 2015 (but heavily modified by JN Kather, 2020) 3 | 4 | function violin(Y,varargin) 5 | 6 | if isempty(Y) 7 | return 8 | end 9 | 10 | fc=[1 0.5 0]; 11 | lc='k'; 12 | alp=0.5; 13 | b=[]; %bandwidth 14 | 15 | if size(Y,2) == 1 % scale single violin 16 | sx = 0.4; % scale x 17 | mx = 0.7; % move x 18 | else 19 | sx = 0.4; % scale x 20 | mx = 0; 21 | end 22 | 23 | if iscell(Y)==0 % convert single columns to cells 24 | Y = num2cell(Y,1); 25 | end 26 | 27 | % parse input params 28 | if any(strcmp(varargin,'facecolor')) 29 | fc = varargin{find(strcmp(varargin,'facecolor'))+1}; 30 | end 31 | if any(strcmp(varargin,'edgecolor')) 32 | lc = varargin{find(strcmp(varargin,'edgecolor'))+1}; 33 | end 34 | if any(strcmp(varargin,'facealpha')) 35 | alp = varargin{find(strcmp(varargin,'facealpha'))+1}; 36 | end 37 | if any(strcmp(varargin,'mx')) 38 | mx = varargin{find(strcmp(varargin,'mx'))+1}; 39 | end 40 | if any(strcmp(varargin,'bw')) 41 | b = varargin{find(strcmp(varargin,'bw'))+1}; 42 | b=repmat(b,size(Y,2),1); 43 | end 44 | 45 | if size(fc,1)==1 46 | fc=repmat(fc,size(Y,2),1); 47 | end 48 | 49 | % call kernel density estimate 50 | for i=1:size(Y,2) 51 | 52 | if ~isempty(b) 53 | [f, u, bb]=ksdensity(Y{i},'bandwidth',b(i)); 54 | elseif isempty(b) 55 | [f, u, bb]=ksdensity(Y{i}); 56 | end 57 | 58 | f=f/max(f)*0.3; %normalize 59 | F(:,i)=f; 60 | U(:,i)=u; 61 | 62 | end 63 | x = zeros(size(Y,2)); 64 | setX = 0; 65 | 66 | % plot violins 67 | for i=1:size(Y,2) 68 | if isempty(lc) == 1 69 | if setX == 0 70 | fill([F(:,i)+i;flipud(i-F(:,i))].*sx+mx,[U(:,i);flipud(U(:,i))],fc(i,:),'FaceAlpha',alp,'EdgeColor','none'); 71 | else 72 | fill([F(:,i)+x(i);flipud(x(i)-F(:,i))].*sx+mx,[U(:,i);flipud(U(:,i))],fc(i,:),'FaceAlpha',alp,'EdgeColor','none'); 73 | end 74 | else 75 | if setX == 0 76 | fill([F(:,i)+i;flipud(i-F(:,i))].*sx+mx,[U(:,i);flipud(U(:,i))],fc(i,:),'FaceAlpha',alp,'EdgeColor',lc); 77 | else 78 | fill([F(:,i)+x(i);flipud(x(i)-F(:,i))].*sx+mx,[U(:,i);flipud(U(:,i))],fc(i,:),'FaceAlpha',alp,'EdgeColor',lc); 79 | end 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /subroutines/writeTopTiles.m: -------------------------------------------------------------------------------- 1 | % JN Kather 2018-2020 2 | % This is part of the DeepHistology repository 3 | % License: see separate LICENSE file 4 | % 5 | % documentation for this function: 6 | % this is used to create a montage of image tilse and save it 7 | 8 | function writeTopTiles(dcollect,cnst,currE) 9 | 10 | figure; 11 | pause(0.00001); 12 | frame_h = get(handle(gcf),'JavaFrame'); 13 | set(frame_h,'Maximized',1); 14 | 15 | allClass = fieldnames(dcollect); 16 | for i=1:numel(allClass) 17 | subplot(1,numel(allClass),i) 18 | currClass = allClass{i}; 19 | imgname = fullfile(cnst.folderName.Dump,[char(currE),'---',currClass,'_lastMontage.png']); 20 | img = montage(dcollect.(currClass).TileNames,'BorderSize',[5 5],'BackgroundColor','w'); 21 | set(gcf,'Color','w'); 22 | title(strrep([char(currE),'---',currClass],'_','-')); 23 | drawnow 24 | 25 | if cnst.doPrint 26 | disp(['-- writing montage to ',imgname]); 27 | imwrite(img.CData,imgname); 28 | end 29 | end 30 | 31 | end -------------------------------------------------------------------------------- /subroutines_normalization/Deconvolve.m: -------------------------------------------------------------------------------- 1 | function [ DCh, M ] = Deconvolve( I, M, verbose ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Deconvolve: Deconvolution of an RGB image into its constituent stain 5 | % channels 6 | % 7 | % 8 | % Input: 9 | % I - RGB input image. 10 | % M - (optional) Stain matrix. 11 | % (default Ruifrok & Johnston H&E matrix) 12 | % verbose - (optional) Display results. (default 0) 13 | % 14 | % 15 | % Note: M must be an 2x3 or 3x3 matrix, where rows corrrespond to the stain 16 | % vectors. If only two rows are given the third is estimated as a 17 | % cross product of the first two. 18 | % 19 | % 20 | % Output: 21 | % DCh - Deconvolved Channels concatatenated to form a stack. 22 | % Each channel is a double in Optical Density space. 23 | % M - Stain matrix. 24 | % 25 | % 26 | % References: 27 | % [1] AC Ruifrok, DA Johnston. "Quantification of histochemical staining by 28 | % color deconvolution". Analytical & Quantitative Cytology & Histology, 29 | % vol.23, no.4, pp.291-299, 2001. 30 | % 31 | % 32 | % Acknowledgements: 33 | % This function is inspired by Mitko Veta's Stain Unmixing and Normalisation 34 | % code, which is available for download at Amida's Website: 35 | % http://amida13.isi.uu.nl/?q=node/69 36 | % 37 | % 38 | % Example: 39 | % I = imread('hestain.png'); 40 | % [ DCh, H, E, Bg, M ] = Deconvolve( I, [], 1); 41 | % 42 | % 43 | % Copyright (c) 2013, Adnan Khan 44 | % Department of Computer Science, 45 | % University of Warwick, UK. 46 | % 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 | 49 | % Run in DEMO Mode 50 | if nargin<1 51 | I = imread('hestain.png'); 52 | end 53 | 54 | % Convert to double 55 | I = double(I); 56 | 57 | %% Sanity check 58 | 59 | [h, w, c] = size(I); 60 | 61 | % Image must be RGB 62 | if c<3 63 | error('Image must be RGB'); 64 | elseif c>3 65 | I = I(:,:,1:3); 66 | end 67 | 68 | 69 | %% Display results or not? 70 | if ~exist('verbose', 'var') || isempty(verbose) 71 | verbose = 0; 72 | end 73 | 74 | 75 | %% Default Color Deconvolution Matrix proposed in Ruifork and Johnston 76 | if ~exist('M', 'var') || isempty(M) 77 | M = [ 0.644211 0.716556 0.266844; 78 | 0.092789 0.954111 0.283111; 79 | ]; 80 | end 81 | 82 | %% Add third Stain vector, if only two stain vectors are provided. 83 | % This stain vector is obtained as the cross product of first two 84 | % stain vectors 85 | if size (M,1) < 3 86 | M = [M; cross(M(1, :), M(2, :))]; 87 | end 88 | 89 | % Normalise the input so that each stain vector has a Euclidean norm of 1 90 | M = (M./repmat(sqrt(sum(M.^2, 2)), [1 3])); 91 | 92 | 93 | %% MAIN IMPLEMENTATION OF METHOD 94 | 95 | % the intensity of light entering the specimen (see section 2a of [1]) 96 | Io = 255; 97 | 98 | % Vectorize 99 | J = reshape(I, [], 3); 100 | 101 | % calculate optical density 102 | OD = -log((J+1)/Io); 103 | Y = reshape(OD, [], 3); 104 | 105 | % determine concentrations of the individual stains 106 | % M is 3 x 3, Y is N x 3, C is N x 3 107 | C = Y / M; 108 | %C = Y * pinv(M); 109 | 110 | % Stack back deconvolved channels 111 | DCh = reshape(C, h, w, 3); 112 | 113 | %% VISUALISATION 114 | % Display pseudo coloured version of results if verbose mode is true 115 | if verbose, 116 | [ H, E, Bg ] = PseudoColourStains(DCh, M); 117 | 118 | figure, 119 | subplot(141); imshow(uint8(I)); title('Source'); 120 | subplot(144); imshow(Bg); title('Background'); 121 | subplot(142); imshow(H); title('Haematoxylin'); 122 | subplot(143); imshow(E); title('Eosin'); 123 | end 124 | 125 | -------------------------------------------------------------------------------- /subroutines_normalization/Images/Ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/Images/Ref.png -------------------------------------------------------------------------------- /subroutines_normalization/Images/Source_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/Images/Source_small.png -------------------------------------------------------------------------------- /subroutines_normalization/PseudoColourStains.m: -------------------------------------------------------------------------------- 1 | function [ H, E, Bg ] = PseudoColourStains( Stains, M ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % PseudoColourStains: Convert a grayscale stain channel into a colour image 5 | % 6 | % 7 | % Input: 8 | % Stains - The Deconvolved Stain channels. 9 | % M - (optional) Stain matrix. 10 | % (default Ruifrok & Johnston H&E matrix) 11 | % verbose - (optional) Display results. (default 0) 12 | % 13 | % 14 | % Output: 15 | % H - RGB image for First Stain (Usually the Hematoxylin Channel). 16 | % E - RGB image for Second Stain (Usually the Eosin Channel). 17 | % Bg - RGB image for Third Stain (Usually the Background Channel). 18 | % 19 | % 20 | % Note: Input Stain channels must be in Optical Density (OD) space. If your 21 | % channels are not already in OD space (such as the channels returned 22 | % by the Colour Deconvolution C code) then please apply the following 23 | % conversion before using this function: 24 | % 25 | % Stains_OD = log(Io/double(Stains)) 26 | % 27 | % Where Io is the transmitted light intensity (typically 255 for a 28 | % uint8 image). 29 | % 30 | % M must be an 2x3 or 3x3 matrix, where rows corrrespond to the stain 31 | % vectors. If only two rows are given the third is estimated as a 32 | % cross product of the first two. 33 | % 34 | % 35 | % Acknowledgements: 36 | % This function is inspired by Mitko Veta's Stain Unmixing and Normalisation 37 | % code, which is available for download at Amida's Website: 38 | % http://amida13.isi.uu.nl/?q=node/69 39 | % 40 | % 41 | % Copyright (c) 2013, Adnan Khan 42 | % Department of Computer Science, 43 | % University of Warwick, UK. 44 | % 45 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 46 | 47 | 48 | [h, w, c] = size(Stains); 49 | 50 | %% Sanity check 51 | if c < 3 52 | errordlg('Must provide 3 stain channels'); 53 | return; 54 | end 55 | 56 | %% Default Color Deconvolution Matrix proposed in Ruifork and Johnston [1] 57 | if ~exist('M', 'var') || isempty(M) 58 | M = [ 0.644211 0.716556 0.266844; 59 | 0.092789 0.954111 0.283111; 60 | ]; 61 | end 62 | 63 | 64 | %% Add third Stain vector, if only two stain vectors are provided. 65 | % This stain vector is obtained as the cross product of first two 66 | % stain vectors. 67 | if size (M,1) < 3 68 | M = [M; cross(M(1, :), M(2, :))]; 69 | end 70 | 71 | % Normalise the input so that each stain vector has a Euclidean norm of 1 72 | M = (M./repmat(sqrt(sum(M.^2, 2)), [1 3])); 73 | 74 | % the intensity of light entering the specimen 75 | Io = 255; 76 | 77 | 78 | %% Make stain concentration matrix by stacking the 3 channels 79 | C = reshape(Stains, [], 3); 80 | 81 | %% Generate pseudo-colour stain images 82 | % We use the input stain matrix to determine the colour for each stain. 83 | H = Io*exp(C(:, 1) * -M(1, :)); 84 | H = reshape(H, h, w, 3); 85 | H = uint8(H); 86 | 87 | E = Io*exp(C(:, 2) * -M(2, :)); 88 | E = reshape(E, h, w, 3); 89 | E = uint8(E); 90 | 91 | Bg = Io*exp(C(:, 3) * -M(3, :)); 92 | Bg = reshape(Bg, h, w, 3); 93 | Bg = uint8(Bg); 94 | 95 | end 96 | 97 | -------------------------------------------------------------------------------- /subroutines_normalization/Ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/Ref.png -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/BuildImageFeatureVector.m: -------------------------------------------------------------------------------- 1 | function [ IFV ] = BuildImageFeatureVector( Image, SCD ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % BuildImageFeatureVector: Constructs the Image Feature Vector (IFV) of an 5 | % input image for training or classification. 6 | % 7 | % 8 | % Input: 9 | % Image - RGB input image. 10 | % SCD - The Stain Colour Descriptor (SCD) for RGB image I. 11 | % 12 | % 13 | % Output: 14 | % IFV - The Image Feature Vector. 15 | % 16 | % Note: Returns the IFV in the form of a nx4 vector where the first three 17 | % columns correspond to the pixel values of the red, blue, and green 18 | % channels of the image. The forth column corresponds to the SCD 19 | % value, which will be the same for every row. 20 | % 21 | % 22 | % References: 23 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 24 | % Approach to Stain Normalisation in Digital Histopathology Images 25 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 26 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 27 | % 28 | % 29 | % Copyright (c) 2015, Nicholas Trahearn 30 | % Department of Computer Science, 31 | % University of Warwick, UK. 32 | % 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | 35 | IFV = reshape(double(Image), [], 3); 36 | IFV = [IFV repmat(SCD(:)', size(IFV, 1), 1)]; 37 | 38 | end 39 | 40 | -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/ClassifyStainRegions.m: -------------------------------------------------------------------------------- 1 | function [ ProbabilityMaps, ClassifiedLabels ] = ClassifyStainRegions( Image, TrainingStruct, ClassificationArgs ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % ClassifyStainRegions: Classify an input image into regions corresponding 5 | % to each stain (and background). 6 | % 7 | % 8 | % Input: 9 | % Image - RGB input image. 10 | % TrainingStruct - (optional) A struct containing the trained stain 11 | % classifier. If the structure is not provided then 12 | % the built-in Random Forest Classifier is used. 13 | % ClassificationArgs - (optional) A cell array of additional arguments 14 | % to the classifier. 15 | % 16 | % Output: 17 | % ProbabilityMaps - An nxmxl matrix of classification probabilities, 18 | % where l is the number of labels/stains. Each 19 | % channel is a probability map for a given stain, 20 | % and provides the probability of each pixel being 21 | % classified as the associated stain. 22 | % ClassifiedLabels - An nxm label matrix, showing the stain with the 23 | % highest classification probability at each pixel 24 | % of the input image. 25 | % 26 | % 27 | % References: 28 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 29 | % Approach to Stain Normalisation in Digital Histopathology Images 30 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 31 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 32 | % 33 | % 34 | % Copyright (c) 2015, Nicholas Trahearn 35 | % Department of Computer Science, 36 | % University of Warwick, UK. 37 | % 38 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 39 | 40 | % If no classifier is provided load the default classifier 41 | if nargin < 2 || isempty(TrainingStruct) 42 | load('DefaultStainTrainingStruct.mat'); 43 | end 44 | 45 | if nargin < 3 || isempty(ClassificationArgs) 46 | ClassificationArgs = {}; 47 | end 48 | 49 | %% Preprocessing 50 | % Convert the input image to uint8, if not already in that form 51 | Image = im2uint8(Image); 52 | 53 | % Find the histogram of pallet values for the input image, using the 54 | % precalculated pallet from training 55 | Histogram = GenerateHistogram(Image, TrainingStruct.Pallet); 56 | 57 | % Normalise the histogram so that its values sum to 1 58 | Histogram = double(Histogram)/sum(double(Histogram)); 59 | 60 | % Compute the SCD of the input images, using the precalculated 61 | % Principal Component Histogram (PCH) from training 62 | SCD = bsxfun(@minus, Histogram, TrainingStruct.PCH.H)*TrainingStruct.PCH.E; 63 | 64 | % Convert the input image to double 65 | Image = im2double(Image); 66 | 67 | 68 | %% Prepare the Image Feature Vector 69 | % This is the input for classification 70 | X = BuildImageFeatureVector(Image, SCD); 71 | 72 | %% Classify Input Image 73 | % Classifier returns probability maps for each possible stain (and 74 | % background). 75 | ProbabilityMaps = TrainingStruct.ClassificationFunction(TrainingStruct.Classifier, X, ClassificationArgs); 76 | 77 | % Create a label image, taking the stain with the highest probability at 78 | % each pixel as the label for that pixel 79 | [~, ClassifiedLabels] = max(ProbabilityMaps, [], 2); 80 | ClassifiedLabels = TrainingStruct.Labels(ClassifiedLabels); 81 | 82 | % Reshape the output to the dimensions of the input image 83 | ProbabilityMaps = reshape(ProbabilityMaps, size(Image, 1), size(Image, 2), []); 84 | ClassifiedLabels = reshape(ClassifiedLabels, size(Image, 1), size(Image, 2)); 85 | 86 | end -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/ComputeSCDs.m: -------------------------------------------------------------------------------- 1 | function [ SCDs, SCDKernel ] = ComputeSCDs( Histograms, r ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % ComputeSCDs: Compute the Stain Colour Descriptors (SCDs) of a set of 5 | % input colour pallet histograms. 6 | % 7 | % An SCD describes the global colour distribution of a stained 8 | % image using just a few values. It can be viewed as a 9 | % dimensionality reduced representation of the colour pallet 10 | % histogram, calculated using Principal Component Analysis. 11 | % 12 | % 13 | % Input: 14 | % Histograms - A cell array of colour pallet histograms. All histograms 15 | % must be of the same length. 16 | % r - (optional) The desired length of the SCD. Must not be 17 | % greater than the length of the input histograms. 18 | % (default 1) 19 | % 20 | % 21 | % Output: 22 | % SCDs - A cell array of SCDs, corresponding to the input 23 | % colour pallet histograms. 24 | % SCDKernel - A struct containing the information used to compute the 25 | % SCDs. Contains two pieces of information: 26 | % H - A mean colour pallet histogram. Calculated by 27 | % finding the mean value of each entry of the 28 | % histogram across all of the input histograms. 29 | % E - The eigenvectors corresponding to the r largest 30 | % eigenvalues of the histograms. 31 | % 32 | % 33 | % Notes: SCDKernel can be used to calculate the SCD of a given histogram, 34 | % Hist, as follows: 35 | % SCD = (Hist - SCDKernel.H) * SCDKernel.E 36 | % 37 | % For SCDKernel to produce a reliable SCD all histograms must be 38 | % computed using the same colour pallet. 39 | % 40 | % 41 | % References: 42 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 43 | % Approach to Stain Normalisation in Digital Histopathology Images 44 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 45 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 46 | % 47 | % 48 | % Copyright (c) 2015, Nicholas Trahearn 49 | % Department of Computer Science, 50 | % University of Warwick, UK. 51 | % 52 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 53 | 54 | % If no value of r is given, use the default 55 | if nargin < 2 || isempty(r) 56 | r = 1; 57 | end 58 | 59 | if r > 0 60 | % Concatenate the histograms together to produce a matrix where each 61 | % row is a colour pallet histogram. 62 | rowHistograms = double(cat(1, Histograms{:})); 63 | 64 | % Calculate the components of SCDKernel 65 | H = mean(rowHistograms, 1); 66 | [E, ~] = eig(cov(rowHistograms)); 67 | 68 | SCDKernel = struct('H', H, 'E', E(:, end:-1:(end-r+1))); 69 | 70 | % Use SCDKernel to compute the SCDs of each histogram 71 | SCDs = cellfun(@(x) bsxfun(@minus,double(x),SCDKernel.H)*SCDKernel.E, Histograms, 'UniformOutput', false); 72 | else 73 | SCDs = {}; 74 | SCDKernel = []; 75 | end 76 | 77 | end 78 | 79 | -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/Default/ClassifyRF.m: -------------------------------------------------------------------------------- 1 | function Probabilities = ClassifyRF( Classifier, Data, ClassificationArgs ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % ClassifyRF: Classifies data using a Random Forest Classifier. 5 | % 6 | % 7 | % Input: 8 | % Classifier - An object containing the Random Forest Classifier. 9 | % Data - An nx4 matrix of data to be classified. 10 | % ClassificationArgs - A cell array of additional arguments to the 11 | % classifier. (unused) 12 | % 13 | % 14 | % Output: 15 | % Probabilities - An nxl matrix of classification probabilities, where 16 | % l is the number of labels/stains. Each column is a 17 | % probability vector for a given stain, and provides 18 | % the probability of each pixel being classified as 19 | % the associated stain. 20 | % 21 | % 22 | % References: 23 | % [1] L Breiman. "Random forests". Machine learning, vol.45, no.1, pp.5-32, 24 | % 2001. 25 | % 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | 28 | [~, Probabilities] = Classifier.predict(Data); 29 | end 30 | 31 | -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/Default/DefaultStainTrainingStruct.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/SCDTraining/Default/DefaultStainTrainingStruct.mat -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/Default/TrainRF.m: -------------------------------------------------------------------------------- 1 | function Classifier = TrainRF( TrainingData, TrainingLabels, TrainingArgs ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % TrainRF: Trains a Random Forest Classifier. 5 | % 6 | % 7 | % Input: 8 | % TrainingData - An nx4 matrix of training data. 9 | % TrainingLabels - An nx1 vector of training labels. 10 | % TrainingArgs - A cell array of additional arguments to the trainer. 11 | % 12 | % 13 | % Output: 14 | % Classifier - An object containing the Random Forest Classifier. 15 | % 16 | % 17 | % References: 18 | % [1] L Breiman. "Random forests". Machine learning, vol.45, no.1, pp.5-32, 19 | % 2001. 20 | % 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | 23 | Classifier = TreeBagger(TrainingArgs{1}, TrainingData, TrainingLabels); 24 | 25 | end 26 | 27 | -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/GenerateHistogram.m: -------------------------------------------------------------------------------- 1 | function [ Hist ] = GenerateHistogram( Image, Pallet ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % GenerateHistogram: Generates a histogram of colours, with respect to 5 | % the input colour pallet. 6 | % 7 | % Input: 8 | % Image - RGB input image. 9 | % Pallet - An nx6 matrix, where each row defines a colour in 10 | % the pallet. 11 | % 12 | % 13 | % Output: 14 | % Hist - The colour pallet histogram. An nx1 vector, where 15 | % each entry is the count for the number of pixels 16 | % from the input Image that correspond to the given 17 | % colour in Pallet. 18 | % 19 | % 20 | % Notes: Both Image and Pallet must be uint8s. 21 | % 22 | % The format of a row (or colour) in Pallet is: 23 | % [R_Start R_End G_Start G_End B_Start B_End] 24 | % Where R_Start is the smallest value in the red channel accepted as 25 | % this colour, and R_End is the largest, the remaining entries are 26 | % the same for the green and blue channels, respectively. 27 | % These define the range of RGB values that are considered to be the 28 | % same colour in the pallet. 29 | % 30 | % 31 | % Copyright (c) 2015, Nicholas Trahearn 32 | % Department of Computer Science, 33 | % University of Warwick, UK. 34 | % 35 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 36 | 37 | 38 | % Generate a lookup table for the pallet colours 39 | % The value of lookup(R, G, B) will be the index of the colour in Pallet 40 | % that a pixel of value [R G B] belongs to 41 | lookup = zeros(256, 256, 256); 42 | 43 | % Increment each entry in Pallet to account for Matlab's 1-based 44 | % indexing of the lookup table 45 | Pallet = uint16(Pallet)+1; 46 | 47 | % Fill the lookup table 48 | for i=1:size(Pallet, 1) 49 | lookup(Pallet(i, 1):Pallet(i, 2), Pallet(i, 3):Pallet(i, 4), Pallet(i, 5):Pallet(i, 6)) = i; 50 | end 51 | 52 | columnImage = reshape(uint32(Image), [], 3); 53 | 54 | % Increment each pixel of Image to account for Matlab's 1-based 55 | % indexing of the lookup table 56 | columnImage = columnImage+1; 57 | 58 | % Convert the pixel's RGB values to Pallet indices using the lookup table 59 | PalletIndices = lookup(sub2ind(size(lookup), columnImage(:, 1), columnImage(:, 2), columnImage(:, 3))); 60 | 61 | % Compute the histogram from the Pallet indices 62 | Hist = histc(PalletIndices(PalletIndices~=0), 1:size(Pallet, 1))'; 63 | 64 | end -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/MakeClassifier.m: -------------------------------------------------------------------------------- 1 | function [ TrainingStruct ] = MakeClassifier( TrainingImages, TrainingLabels, TrainingFun, TFargs, ClassificationFun ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % MakeClassifier: Create a custom stain classifier 5 | % 6 | % 7 | % Input: 8 | % TrainingImages - A cell array of RGB training images. 9 | % TrainingLabels - A cell array of training label images. 10 | % TrainingFun - (optional) A function handle to the desired 11 | % training method. (default: @TrainRF) 12 | % TFargs - (optional) A cell array of additional arguments 13 | % for the training function. (default: {50}) 14 | % ClassificationFun - (optional) A function handle to the desired 15 | % classification method. (default: @ClassifyRF) 16 | % 17 | % Output: 18 | % TrainingStruct - A struct containing the trained classifier and 19 | % other auxillary information needed for 20 | % classification. 21 | % 22 | % 23 | % Notes: If a custom training method is used then all optional arguments 24 | % must be provided. 25 | % 26 | % A valid training function must take the following input arguments, 27 | % in this order: 28 | % TrainingData - An nx4 matrix of training data. 29 | % TrainingLabels - An nx1 vector of training labels. 30 | % TrainingArgs - A cell array of additional arguments to the 31 | % trainer. 32 | % And output the following: 33 | % Classifier - An object containing the stain classifier. The 34 | % exact form of the object is not strict and 35 | % may vary between different training methods. 36 | % 37 | % A valid classification function must take the following arguments 38 | % in this order: 39 | % Classifier - An object containing the stain classifier. 40 | % Data - An nx4 matrix of data to be classified. 41 | % ClassificationArgs - A cell array of additional arguments to 42 | % the classifier. 43 | % And output the following: 44 | % Probabilities - An nxl matrix of classification 45 | % probabilities, where l is the number of 46 | % labels/stains. Each column is a 47 | % probability vector for a given stain, and 48 | % provides the probability of each pixel 49 | % being classified as the associated stain. 50 | % 51 | % If your existing training or classification function is not of 52 | % this form: 53 | % Please create a new function that accepts these arguments and 54 | % then calls the existing function as appropriate. 55 | % This new function can then be used here instead. 56 | % 57 | % See the provided TrainRF and ClassifyRF functions for more details. 58 | % 59 | % 60 | % References: 61 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 62 | % Approach to Stain Normalisation in Digital Histopathology Images 63 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 64 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 65 | % 66 | % 67 | % Copyright (c) 2015, Nicholas Trahearn 68 | % Department of Computer Science, 69 | % University of Warwick, UK. 70 | % 71 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 72 | 73 | % If no (or incomplete) classifier details are given use the default Random 74 | % Forest classifier 75 | if nargin < 5 76 | TrainingStruct.ClassifierType = 'RandomForest'; 77 | TrainingFun = @TrainRF; 78 | TFargs = {20}; 79 | ClassificationFun = @ClassifyRF; 80 | else 81 | TrainingStruct.ClassifierType = 'CustomClassifier'; 82 | end 83 | 84 | %% Preprocessing 85 | % Convert the training images to uint8s, if not already in that form 86 | TrainingImages = cellfun(@(x) im2uint8(x), TrainingImages, 'UniformOutput', false); 87 | 88 | % Calculate a 256 colour pallet of the training images using Octree 89 | % quantisation 90 | TrainingStruct.Pallet = otq(cell2mat(cellfun(@(x) reshape(x, 1, [], 3), TrainingImages, 'UniformOutput', false))); 91 | 92 | % Find the histogram of pallet values for each training image 93 | TrainingHistograms = cellfun(@(x) GenerateHistogram(x, TrainingStruct.Pallet), TrainingImages, 'UniformOutput', false); 94 | 95 | % Normalise the histograms so that each histogram sums to 1 96 | TrainingHistograms = cellfun(@(x) double(x)/sum(double(x)), TrainingHistograms, 'UniformOutput', false); 97 | 98 | % Find the SCDs of the training images and store the Principal Component 99 | % Histogram (PCH) 100 | [TrainingSCDs, TrainingStruct.PCH] = ComputeSCDs(TrainingHistograms, 1); 101 | 102 | % Convert the training images to doubles 103 | TrainingImages = cellfun(@(x) im2double(x), TrainingImages, 'UniformOutput', false); 104 | 105 | %% Prepare the Image Feature Vector 106 | % This is the input for training 107 | X = cellfun(@BuildImageFeatureVector, TrainingImages, TrainingSCDs, 'UniformOutput', false); 108 | X = cat(1, X{:}); 109 | 110 | %% Prepare the labels for training 111 | Y = cellfun(@(x) double(x(:)), TrainingLabels, 'UniformOutput', false); 112 | Y = cat(1, Y{:}); 113 | 114 | Y = Y-min(Y(:)); 115 | 116 | %% Train the classifier 117 | TrainingStruct.Classifier = TrainingFun(X, Y, TFargs); 118 | TrainingStruct.ClassificationFunction = ClassificationFun; 119 | TrainingStruct.Labels = unique(Y(:)); 120 | 121 | end 122 | 123 | -------------------------------------------------------------------------------- /subroutines_normalization/SCDTraining/README.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | Non-Linear Stain Mapping: Training & Classification Instructions 3 | Nicholas Trahearn 4 | BIALab, Department of Computer Science, University of Warwick 5 | ------------------------------------------------------------- 6 | 7 | ------------------------------------------------------------- 8 | 0. Contents 9 | ------------------------------------------------------------- 10 | 11 | 1. Introduction 12 | 2. Built-in H&E Classifier 13 | 3. Training Your Own Classifier 14 | 15 | 16 | ------------------------------------------------------------- 17 | 1. Introduction 18 | ------------------------------------------------------------- 19 | 20 | The Non-Linear Stain Mapping method of Stain Normalisation is a supervised method. As such it requires a classifier to be trained before use. 21 | Please refer to the following paper for further details on the approach: 22 | Khan, A.M., Rajpoot, N., Treanor, D., Magee, D., A Non-Linear Mapping Approach to Stain Normalisation in Digital Histopathology Images using Image-Specific Colour Deconvolution, IEEE Transactions on Biomedical Engineering, 2014. 23 | 24 | 25 | ------------------------------------------------------------- 26 | 2. Built-in H&E Classifier 27 | ------------------------------------------------------------- 28 | 29 | Provided is a pre-trained classifier struct for Haematoxylin and Eosin (H&E) stained images of varying stain intensity. 30 | The classifier can be found at SCDTraining/Default/DefaultStainTrainingStruct.mat in the toolbox. 31 | This pre-trained classifier is suitable for general purpose Non-Linear Normalisation of H&E stained images. 32 | 33 | 34 | ------------------------------------------------------------- 35 | 2. Training Your Own Classifier 36 | ------------------------------------------------------------- 37 | 38 | If you would like to train your own classifier please refer to the MakeClassifier function, found at SCDTraining/MakeClassifier.m. 39 | By default MakeClassifier uses Matlab's implementation of Random Forest for training and classificiation, using the functions TrainRF and ClassifyRF (found at SCDTraining/Default/TrainRF.m and SCDTraining/Default/ClassifyRF.m, respectively), please refer to these files for further details. 40 | If you would like to use a different training and classification functions you may create your own custom functions for this purpose and provide handles to them as input to the MakeClassifier function, please refer to SCDTraining/Default/MakeClassifier.m for more details on the formatting of the custom functions. -------------------------------------------------------------------------------- /subroutines_normalization/StainMatrixEstimation/EstUsingMacenko.m: -------------------------------------------------------------------------------- 1 | function [ M ] = EstUsingMacenko( I, Io, beta, alpha ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % EstUsingMacenko: Estimate the stain separation matrix using 5 | % Macenko's method 6 | % 7 | % 8 | % Input: 9 | % I - RGB input image 10 | % beta - OD threshold for transparent pixels 11 | % alpha - tolerance for the pseudo-min and pseudo-max 12 | % 13 | % 14 | % Output: 15 | % M - Stain separation matrix with columns corresponding to 16 | % stain vectors 17 | % 18 | % 19 | % References: 20 | % [1] M Macenko, M Niethammer, JS Marron, D Borland, JT Woosley, X Guan, C 21 | % Schmitt, NE Thomas. "A method for normalizing histology slides for 22 | % quantitative analysis". IEEE International Symposium on Biomedical 23 | % Imaging: From Nano to Macro, 2009 vol.9, pp.1107-1110, 2009. 24 | % 25 | % 26 | % Acknowledgements: 27 | % This function is inspired by Mitko Veta's Stain Unmixing and Normalisation 28 | % code, which is available for download at Amida's Website: 29 | % http://amida13.isi.uu.nl/?q=node/69 30 | % 31 | % 32 | % Copyright (c) 2013, Adnan Khan 33 | % Department of Computer Science, 34 | % University of Warwick, UK. 35 | % 36 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | 38 | % Run in DEMO Mode 39 | if nargin<1 40 | I = imread('hestain.png'); 41 | end 42 | 43 | 44 | 45 | % OD threshold for transparent pixels 46 | if ~exist('beta', 'var') || isempty(beta) 47 | beta = 0.15; 48 | end 49 | 50 | % tolerance for the pseudo-min and pseudo-max 51 | if ~exist('alpha', 'var') || isempty(alpha) 52 | alpha = 1; 53 | end 54 | 55 | % transmitted light intensity 56 | if ~exist('Io', 'var') || isempty(Io) 57 | Io = 255; 58 | end 59 | 60 | if size(I,3)<3 61 | error('Image must be RGB'); 62 | end 63 | 64 | I = reshape(double(I), [], 3); 65 | 66 | % calculate optical density 67 | OD = -log((I+1)/Io); 68 | 69 | % remove transparent pixels 70 | ODhat = OD(~any(OD < beta, 2), :); 71 | 72 | % calculate eigenvectors 73 | [V, ~] = eig(cov(ODhat)); 74 | 75 | % project on the plane spanned by the eigenvectors corresponding to the two 76 | % largest eigenvalues 77 | THETA = ODhat*V(:,2:3); 78 | 79 | PHI = atan2(THETA(:,2), THETA(:,1)); 80 | 81 | % find the robust extremees (min and max angles) 82 | minPhi = prctile(PHI, alpha); 83 | maxPhi = prctile(PHI, 100-alpha); 84 | 85 | % Bring the extreme angles back to OD Space 86 | VEC1 = V(:,2:3)*[cos(minPhi); sin(minPhi)]; 87 | VEC2 = V(:,2:3)*[cos(maxPhi); sin(maxPhi)]; 88 | 89 | % Make sure that Hematoxylin is first and Eosin is second vector 90 | if VEC1(1) > VEC2(1) 91 | M = [VEC1 VEC2]'; 92 | else 93 | M = [VEC2 VEC1]'; 94 | end 95 | 96 | end 97 | 98 | -------------------------------------------------------------------------------- /subroutines_normalization/StainMatrixEstimation/EstUsingSCD.m: -------------------------------------------------------------------------------- 1 | function [ M, Labels ] = EstUsingSCD( I, TrainingStruct ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % EstUsingSCD: Estimate the stain separation matrix using the Stain Colour 5 | % Descriptor method 6 | % 7 | % 8 | % Input: 9 | % I - RGB input image. 10 | % TrainingStruct - (optional) A struct containing the trained stain 11 | % classifier. If the structure is not provided then 12 | % the built-in Random Forest Classifier is used. 13 | % 14 | % 15 | % Output: 16 | % M - Stain separation matrix with columns corresponding 17 | % to stain vectors. 18 | % Labels - A label image showing the results of classification. 19 | % 20 | % 21 | % References: 22 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 23 | % Approach to Stain Normalisation in Digital Histopathology Images 24 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 25 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 26 | % 27 | % 28 | % Copyright (c) 2015, Nicholas Trahearn 29 | % Department of Computer Science, 30 | % University of Warwick, UK. 31 | % 32 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 33 | 34 | % If no classifier is provided load the default classifier 35 | if nargin < 2 || isempty(TrainingStruct) 36 | load('DefaultStainTrainingStruct.mat'); 37 | end 38 | 39 | %% Generate Stain Probability Maps 40 | ProbabilityMaps = ClassifyStainRegions( I, TrainingStruct ); 41 | 42 | % Vectorise Image 43 | ColumnImage = reshape(im2double(I(:,:,1:3)), [], 3); 44 | 45 | % List of potential Stain Labels from Classifier 46 | StainLabels = cat(1, TrainingStruct.Labels); 47 | 48 | % Vectorise Probability Maps 49 | ProbabilityMaps = reshape(ProbabilityMaps, [], length(StainLabels)); 50 | 51 | % Background probability threshold 52 | Tbg = 0.75; 53 | 54 | % Stain probability threshold 55 | Tfg = 0.75; 56 | 57 | % Label used by classifier for background pixels, should always be zero 58 | bgLabel = 0; 59 | bgIdx = -1; 60 | 61 | %% Generate the Stain Label image 62 | Labels = -ones(size(ColumnImage, 1), 1); 63 | 64 | for i=1:length(StainLabels) 65 | if StainLabels(i)==bgLabel 66 | bgIdx = i; 67 | else 68 | % Set the label to the current stain's label for all pixels with a 69 | % classification probability above the stain threshold 70 | Labels(ProbabilityMaps(:, i) > Tfg) = StainLabels(i); 71 | end 72 | end 73 | 74 | % Remove the background label from the list of stain labels 75 | StainLabels = StainLabels(StainLabels~=bgLabel); 76 | 77 | % Set the label for all pixels with a background classification probability 78 | % above the background threshold to the background's label 79 | if bgIdx ~= -1 80 | Labels(ProbabilityMaps(:, bgIdx) > Tbg) = bgLabel; 81 | end 82 | 83 | %% Generate the Stain Separation Matrix 84 | % Takes the mean of the OD values of the pixels classified as a given stain 85 | % To compute its stain vector 86 | M = zeros(3); 87 | M(1, :) = -log(mean(ColumnImage(Labels==StainLabels(2), 1:3))+(1/256)); 88 | M(2, :) = -log(mean(ColumnImage(Labels==StainLabels(1), 1:3))+(1/256)); 89 | 90 | % Third stain vector is computed as a cross product of the first two 91 | M(3, :) = cross(M(1, :), M(2, :)); 92 | 93 | % Normalise the matrix such that each stain vector has a Euclidean Norm of 1 94 | M = M./repmat(sqrt(sum(M.^2, 2)), [1 3]); 95 | 96 | % Reshape Label image to have the width and height of the original image 97 | Labels = reshape(Labels, size(I, 1), size(I, 2)); 98 | 99 | end 100 | -------------------------------------------------------------------------------- /subroutines_normalization/StainNormalisation/Norm.m: -------------------------------------------------------------------------------- 1 | function Norm = Norm( Source, Target, Method, varargin ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Norm: Normalise the appearance of a Source Image to a Target Image using 5 | % the specified method. 6 | % 7 | % 8 | % Input: 9 | % Source - RGB Source image. 10 | % Target - RGB Reference image. 11 | % Method - Stain Normalisation method. 12 | % varargin - Any additional arguments for the target Normalisation 13 | % method. The order of the arguments must match that of 14 | % the target function. 15 | % 16 | % 17 | % Output: 18 | % Norm - Normalised RGB Source image. 19 | % 20 | % 21 | % Notes: Valid values for Method and their associated normalisation 22 | % function are as follows: 23 | % 'SCD' - Non-Linear SCD-based Normalisation. 24 | % (Matlab Implementation) 25 | % 'SCDLeeds' - Non-Linear SCD-based Normalisation. 26 | % (Windows Executable) 27 | % 'Macenko' - Macenko Linear Stain Channel Normalisation. 28 | % 'Reinhard' - Reinhard Colour Normalisation. 29 | % 'RGBHist' - RGB Histogram Specification. 30 | % 'Custom' - An external Stain Normalisation method. 31 | % 32 | % If you wish to use your own Stain Normalisation function, please 33 | % select the 'Custom' option for Method and provide a function 34 | % handle to your custom function as the forth argument. 35 | % 36 | % A valid custom Normalisation function must take the following input 37 | % arguments, in this order: 38 | % Source - RGB Source image. 39 | % Target - RGB Reference image. 40 | % varargin - Any additional arguments for the custom Normalisation 41 | % method. 42 | % And output the following: 43 | % Norm - Normalised RGB Source image. 44 | % 45 | % If your existing Stain Normalisation function is not of this form: 46 | % Please create a new function that accepts these arguments and 47 | % then calls the existing function as appropriate. 48 | % This new function can then be used here instead. 49 | % 50 | % 51 | % References: 52 | % See each normalisation function for its associated references. 53 | % 54 | % 55 | % Copyright (c) 2015, Nicholas Trahearn 56 | % Department of Computer Science, 57 | % University of Warwick, UK. 58 | % 59 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 60 | 61 | if ~exist('Source', 'var') || isempty(Source) 62 | error('Please supply a Source Image.'); 63 | end 64 | 65 | if ~exist('Target', 'var') || isempty(Target) 66 | error('Please supply a Target Image.'); 67 | end 68 | 69 | if ~exist('Method', 'var') || isempty(Method) 70 | error('Please supply a Stain Normalisation Method.'); 71 | end 72 | 73 | switch Method 74 | case 'SCD' 75 | % Call the Non-Linear SCD-based method. 76 | Norm = NormSCD(Source, Target, varargin{:}); 77 | case 'SCDLeeds' 78 | % Call the Leeds Implementation of the Non-Linear SCD-based method. 79 | Norm = NormSCDLeeds(Source, Target, varargin{:}); 80 | case 'Macenko' 81 | % Call Macenko's Method. 82 | Norm = NormMacenko(Source, Target, varargin{:}); 83 | case 'Reinhard' 84 | % Call Reinhard's method. 85 | Norm = NormReinhard(Source, Target, varargin{:}); 86 | case 'RGBHist' 87 | % Call the RGB Histogram Specification method. 88 | Norm = NormRGBHist(Source, Target, varargin{:}); 89 | case 'Custom' 90 | % Call a Custom Stain Normalisation Method. 91 | normfun = varargin{1}; 92 | Norm = normfun(Source, Target, varargin{2:end}); 93 | otherwise 94 | error('Unknown Stain Normalisation Method.'); 95 | end 96 | 97 | %{ 98 | if verbose 99 | figure; 100 | subplot(1,3,1); imshow(Source); title('Source Image'); 101 | subplot(1,3,2); imshow(Target); title('Target Image'); 102 | subplot(1,3,3); imshow(Norm); title(['Normalised (' Method ')']); 103 | end 104 | %} 105 | 106 | end 107 | 108 | -------------------------------------------------------------------------------- /subroutines_normalization/StainNormalisation/NormMacenko.m: -------------------------------------------------------------------------------- 1 | function [ Norm ] = NormMacenko( Source, Target, Io, beta, alpha, verbose ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % NormMacenko: Normalise the appearance of a Source Image to a Target 5 | % image using Macenko's method. 6 | % 7 | % 8 | % Input: 9 | % Source - RGB Source image. 10 | % Target - RGB Reference image. 11 | % Io - (optional) Transmitted light intensity. (default 255) 12 | % beta - (optional) OD threshold for transparent pixels. (default 0.15) 13 | % alpha - (optional) Tolerance for the pseudo-min and pseudo-max. 14 | % (default 1) 15 | % verbose - (optional) Display results. (default 0) 16 | % 17 | % 18 | % Output: 19 | % Norm - Normalised RGB Source image. 20 | % 21 | % 22 | % References: 23 | % [1] M Macenko, M Niethammer, JS Marron, D Borland, JT Woosley, X Guan, C 24 | % Schmitt, NE Thomas. "A method for normalizing histology slides for 25 | % quantitative analysis". IEEE International Symposium on Biomedical 26 | % Imaging: From Nano to Macro, 2009 vol.9, pp.1107-1110, 2009. 27 | % 28 | % 29 | % Acknowledgements: 30 | % This function is inspired by Mitko Veta's Stain Unmixing and Normalisation 31 | % code, which is available for download at Amida's Website: 32 | % http://amida13.isi.uu.nl/?q=node/69 33 | % 34 | % 35 | % Copyright (c) 2013, Adnan Khan 36 | % Department of Computer Science, 37 | % University of Warwick, UK. 38 | % 39 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 40 | 41 | if ~exist('verbose', 'var') || isempty(verbose) 42 | verbose = 0; 43 | %else 44 | % warning('Verbose mode for individual normalisation functions is likely to be removed in a future release. Please use Norm.m for Visualisation.'); 45 | end 46 | 47 | if ~exist('Source', 'var') || isempty(Source) 48 | error('Please supply a Source Image.'); 49 | end 50 | 51 | if ~exist('Target', 'var') || isempty(Target) 52 | error('Please supply a Target Image.'); 53 | end 54 | 55 | % transmitted light intensity 56 | if ~exist('A', 'var') || isempty(Io) 57 | Io = 255; 58 | end 59 | 60 | % OD threshold for transparent pixels 61 | if ~exist('beta', 'var') || isempty(beta) 62 | beta = 0.15; 63 | end 64 | 65 | % tolerance for the pseudo-min and pseudo-max 66 | if ~exist('alpha', 'var') || isempty(alpha) 67 | alpha = 1; 68 | end 69 | 70 | [h, w, ~] = size(Source); 71 | 72 | % Estimate Stain Matrix for Taret Image 73 | MTarget = EstUsingMacenko(Target, Io, beta, alpha); 74 | 75 | % Perform Color Deconvolution of Target Image to get stain concentration 76 | % matrix 77 | [ C, MTarget ] = Deconvolve( Target, MTarget ); 78 | 79 | % Vectorize to N x 3 matrix 80 | C = reshape(C, [], 3); 81 | 82 | % Find the 99th percentile of stain concentration (for each channel) 83 | maxCTarget = prctile(C, 99, 1); 84 | 85 | 86 | %% Repeat the same process for input/source image 87 | 88 | % Estimate Stain Matrix for Source Image 89 | MSource = EstUsingMacenko(Source, Io, beta, alpha); 90 | 91 | % Perform Color Deconvolution of Source Image to get stain concentration 92 | % matrix 93 | C = Deconvolve( Source, MSource ); 94 | 95 | % Vectorize to N x 3 matrix 96 | C = reshape(C, [], 3); 97 | 98 | % Find the 99th percentile of stain concentration (for each channel) 99 | maxCSource = prctile(C, 99, 1); 100 | 101 | 102 | %% MAIN NORMALIZATION STUFF 103 | % 104 | C = bsxfun(@rdivide, C, maxCSource); 105 | C = bsxfun(@times, C, maxCTarget); 106 | 107 | 108 | %% Reconstruct the RGB image 109 | Norm = Io*exp(C * -MTarget); 110 | Norm = reshape(Norm, h, w, 3); 111 | Norm = uint8(Norm); 112 | 113 | %% VISUALISATION 114 | % Display results if verbose mode is true 115 | if verbose 116 | figure; 117 | subplot(1,3,1); imshow(Source); title('Source Image'); 118 | subplot(1,3,2); imshow(Target); title('Target Image'); 119 | subplot(1,3,3); imshow(Norm); title('Normalised (Macenko)'); 120 | end 121 | 122 | end 123 | 124 | 125 | -------------------------------------------------------------------------------- /subroutines_normalization/StainNormalisation/NormRGBHist.m: -------------------------------------------------------------------------------- 1 | function [ Norm ] = NormRGBHist( Source, Target, verbose ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % NormRGBHist: Normalise the RGB histogram of Source Image with respect to 5 | % a Target image using the Histogram Specification Method 6 | % 7 | % 8 | % Input: 9 | % Source - RGB Source image. 10 | % Target - RGB Reference image. 11 | % verbose - (optional) Display Results (including histograms). 12 | % (default 0) 13 | % 14 | % Output: 15 | % Norm - Normalised RGB Source image. 16 | % 17 | % 18 | % References: 19 | % [1] A Jain. Fundamentals of digital image processing. Prentice-Hall, 1989. 20 | % 21 | % 22 | % Copyright (c) 2013, Adnan Khan 23 | % Department of Computer Science, 24 | % University of Warwick, UK. 25 | % 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | 28 | if ~exist('verbose', 'var') || isempty(verbose) 29 | verbose = 0; 30 | %else 31 | % warning('Verbose mode for individual normalisation functions is likely to be removed in a future release. Please use Norm.m for Visualisation.'); 32 | end 33 | 34 | if ~exist('Source', 'var') || isempty(Source) 35 | error('Please supply a Source Image.'); 36 | end 37 | 38 | if ~exist('Target', 'var') || isempty(Target) 39 | error('Please supply a Target Image.'); 40 | end 41 | 42 | % Separate Source image's color channel 43 | SourceR = Source(:,:,1); 44 | SourceG = Source(:,:,2); 45 | SourceB = Source(:,:,3); 46 | 47 | %Separate Target/reference image's color channel 48 | 49 | TargetR = Target(:,:,1); 50 | TargetG = Target(:,:,2); 51 | TargetB = Target(:,:,3); 52 | 53 | % Compute Target/reference image histograms 54 | HnTargetR = imhist(TargetR)./numel(TargetR); 55 | HnTargetG = imhist(TargetG)./numel(TargetG); 56 | HnTargetB = imhist(TargetB)./numel(TargetB); 57 | 58 | % Histogram specification, using Target/reference image's histogram 59 | NormR = histeq(SourceR,HnTargetR); 60 | NormG = histeq(SourceG,HnTargetG); 61 | NormB = histeq(SourceB,HnTargetB); 62 | 63 | % Concatenate Channels 64 | Norm = cat(3, NormR, NormG, NormB); 65 | 66 | %% Plot histogram & Display Image 67 | if verbose 68 | figure; 69 | subplot(1,3,1); imshow(Source); title('Source Image'); 70 | subplot(1,3,2); imshow(Target); title('Target Image'); 71 | subplot(1,3,3); imshow(Norm); title('Normalised (Histogram Specification)'); 72 | end 73 | end 74 | 75 | -------------------------------------------------------------------------------- /subroutines_normalization/StainNormalisation/NormReinhard.m: -------------------------------------------------------------------------------- 1 | function [ Norm ] = NormReinhard( Source, Target, verbose ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % NormRienhard: Normalise a Source image with respect to a Target image 5 | % using Reinhard's method. 6 | % 7 | % This routine takes a Source and Target image as input and uses Reinhard's 8 | % method (Reference below) to normalise the stain of Source image 9 | % 10 | % 11 | % Input: 12 | % Source - RGB Source image. 13 | % Target - RGB Reference image. 14 | % verbose - (optional) Display results. 15 | % (default 0) 16 | % 17 | % 18 | % Output: 19 | % Norm - Normalised RGB Source image. 20 | % 21 | % 22 | % References: 23 | % [1] E Reinhard, M Adhikhmin, B Gooch, P Shirley. "Color transfer between 24 | % images". IEEE Computer Graphics and Applications, vol.21 no.5, pp. 25 | % 34-41, 2001. 26 | % 27 | % Copyright (c) 2013, Adnan Khan 28 | % Department of Computer Science, 29 | % University of Warwick, UK. 30 | % 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | 33 | if ~exist('verbose', 'var') || isempty(verbose) 34 | verbose = 0; 35 | %else 36 | % warning('Verbose mode for individual normalisation functions is likely to be removed in a future release. Please use Norm.m for Visualisation.'); 37 | end 38 | 39 | if ~exist('Source', 'var') || isempty(Source) 40 | error('Please supply a Source Image.'); 41 | end 42 | 43 | if ~exist('Target', 'var') || isempty(Target) 44 | error('Please supply a Target Image.'); 45 | end 46 | 47 | % RGB to LAB colour space conversion for Source/Ref Image 48 | SourceLab = applycform(im2double(Source), makecform('srgb2lab')); 49 | 50 | % Means of Source image channels in Lab Colourspace 51 | ms = mean(reshape(SourceLab, [], 3)); 52 | 53 | % Standard deviations of Source image channels in Lab Colourspace 54 | stds = std(reshape(SourceLab, [], 3)); 55 | 56 | 57 | % RGB to LAB colour space conversion for Target Image 58 | TargetLab = applycform(im2double(Target), makecform('srgb2lab')); 59 | 60 | % Means of Target image channels in Lab Colourspace 61 | mt = mean(reshape(TargetLab, [], 3)); 62 | 63 | % Standard deviations of Target image channels in Lab Colourspace 64 | stdt = std(reshape(TargetLab, [], 3)); 65 | 66 | 67 | % Normalise each channel based on statistics of source and target images 68 | NormLab(:,:,1) = ((SourceLab(:,:,1)-ms(1))*(stdt(1)/stds(1)))+mt(1); 69 | NormLab(:,:,2) = ((SourceLab(:,:,2)-ms(2))*(stdt(2)/stds(2)))+mt(2); 70 | NormLab(:,:,3) = ((SourceLab(:,:,3)-ms(3))*(stdt(3)/stds(3)))+mt(3); 71 | 72 | % LAB to RGB conversion 73 | Norm = applycform(NormLab, makecform('lab2srgb')); 74 | 75 | % Display results if verbose mode is true 76 | if verbose 77 | figure; 78 | subplot(1,3,1); imshow(Source); title('Source Image'); 79 | subplot(1,3,2); imshow(Target); title('Target Image'); 80 | subplot(1,3,3); imshow(Norm); title('Normalised (Reinhard)'); 81 | end 82 | 83 | end 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /subroutines_normalization/StainNormalisation/NormSCDLeeds.m: -------------------------------------------------------------------------------- 1 | function [ Norm ] = NormSCDLeeds( Source, Target, verbose ) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % NormSCDLeeds: Normalise the appearance of a Source Image to a Target 5 | % Image using the Leeds implementation of the Non-Linear 6 | % Spline Mapping Method with a built-in colour model. 7 | % 8 | % 9 | % Input: 10 | % Source - RGB Source image. 11 | % Target - RGB Reference image. 12 | % verbose - (optional) Display results. 13 | % (default 0) 14 | % 15 | % 16 | % Output: 17 | % Norm - Normalised RGB Source image 18 | % 19 | % 20 | % Notes: Only available on Windows platforms. 21 | % This version of the algorithm cannot be retrained. 22 | % 23 | % References: 24 | % [1] AM Khan, NM Rajpoot, D Treanor, D Magee. "A Non-Linear Mapping 25 | % Approach to Stain Normalisation in Digital Histopathology Images 26 | % using Image-Specific Colour Deconvolution". IEEE Transactions on 27 | % Biomedical Engineering, vol.61, no.6, pp.1729-1738, 2014. 28 | % 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | 31 | if ~exist('verbose', 'var') || isempty(verbose) 32 | verbose = 0; 33 | %else 34 | % warning('Verbose mode for individual normalisation functions is likely to be removed in a future release. Please use Norm.m for Visualisation.'); 35 | end 36 | 37 | if ~exist('Source', 'var') || isempty(Source) 38 | error('Please supply a Source Image.'); 39 | end 40 | 41 | if ~exist('Target', 'var') || isempty(Target) 42 | error('Please supply a Target Image.'); 43 | end 44 | 45 | if ~ispc 46 | error('This function is only available on Windows platforms.'); 47 | end 48 | 49 | currentDir = pwd; 50 | functionDir = mfilename('fullpath'); 51 | functionDir = functionDir(1:(end-length(mfilename))); 52 | cd(functionDir); 53 | 54 | mkdir('.temp'); 55 | mkdir('.temp/normalised'); 56 | 57 | cd('./.temp'); 58 | 59 | imwrite(Source, './source.png', 'PNG'); 60 | imwrite(Target, './target.png', 'PNG'); 61 | 62 | fileID = fopen('./filename.txt','w'); 63 | fprintf(fileID, 'source.png\n'); 64 | fclose(fileID); 65 | 66 | dos('..\..\bin\LeedsSCD\ColourNormalisation.exe BimodalDeconvRVM filename.txt target.png ..\..\bin\LeedsSCD\HE.colourmodel'); 67 | 68 | Norm = imread('./normalised/source.png'); 69 | 70 | cd('./..') 71 | rmdir('.temp','s'); 72 | cd(currentDir); 73 | 74 | if verbose 75 | figure; 76 | subplot(1,3,1); imshow(Source); title('Source Image'); 77 | subplot(1,3,2); imshow(Target); title('Target Image'); 78 | subplot(1,3,3); imshow(Norm); title('Normalised (SCD-Leeds)'); 79 | end 80 | 81 | end -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/ColourNormalisation.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/ColourNormalisation.exe -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/msvcp100.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/msvcp100.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/msvcr100.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/msvcr100.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_calib3d220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_calib3d220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_contrib220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_contrib220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_core220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_core220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_features2d220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_features2d220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_flann220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_flann220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_gpu220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_gpu220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_highgui220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_highgui220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_imgproc220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_imgproc220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_legacy220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_legacy220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_ml220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_ml220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_objdetect220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_objdetect220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/opencv_video220.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/opencv_video220.dll -------------------------------------------------------------------------------- /subroutines_normalization/bin/LeedsSCD/vcomp100.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/bin/LeedsSCD/vcomp100.dll -------------------------------------------------------------------------------- /subroutines_normalization/colour_deconvolution.mexw64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnkather/DeepHistology/8741d925910e793415d2fff20860d358c45e407e/subroutines_normalization/colour_deconvolution.mexw64 -------------------------------------------------------------------------------- /subroutines_normalization/demo.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % A demonstration of the included Stain Normalisation methods. 3 | % 4 | % 5 | % Adnan Khan and Nicholas Trahearn 6 | % Department of Computer Science, 7 | % University of Warwick, UK. 8 | % 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | 11 | 12 | % Clear all previous data 13 | clc, clear all, close all; 14 | 15 | 16 | %% Display results of each method 17 | verbose = 1; 18 | 19 | 20 | %% Load Source & Target images 21 | SourceImage = imread('Images/Source_small.png'); 22 | TargetImage = imread('Images/Ref.png'); 23 | 24 | 25 | %% Stain Normalisation using RGB Histogram Specification Method 26 | 27 | disp('Stain Normalisation using RGB Histogram Specification Method'); 28 | 29 | [ NormHS ] = Norm( SourceImage, TargetImage, 'RGBHist', verbose ); 30 | 31 | 32 | %% Stain Normalisation using Reinhard Method 33 | 34 | disp('Stain Normalisation using Reinhard''s Method'); 35 | 36 | [ NormRH ] = Norm( SourceImage, TargetImage, 'Reinhard', verbose ); 37 | 38 | 39 | %% Color Deconvolution using online available code with Standard Stain Matrix 40 | % Original credit for C code to G.Landini 41 | 42 | disp('Stain Separation using Lindini''s Color Deconvolution C code'); 43 | 44 | [s1, s2, s3] = colour_deconvolution(SourceImage, 'H&E'); 45 | 46 | % Get pseudo-coloured deconvolved channels 47 | stainsc = log(255./(double(cat(3, s1, s2, s3))+0.0001)); 48 | [ Hc, Ec, Bgc ] = PseudoColourStains( stainsc, [] ); 49 | 50 | 51 | %% Color Deconvolution using Our Implementation with Standard Stain Matrix 52 | 53 | disp(['Stain Separation using Our Implementation with Standard Stain'... 54 | ' Matrix ']); 55 | 56 | % Get pseudo-coloured deconvolved channels 57 | stains = Deconvolve( SourceImage, [], 0 ); 58 | [H, E, Bg] = PseudoColourStains( stains, [] ); 59 | 60 | 61 | %% Display comparative results of the two deconvolution implementations 62 | 63 | figure, 64 | subplot(231); imshow(Hc); title('H (C code)'); 65 | subplot(232); imshow(Ec); title('E (C code)'); 66 | subplot(233); imshow(Bgc); title('Bg (C code)'); 67 | subplot(234); imshow(H); title('H (Our Implementation)'); 68 | subplot(235); imshow(E); title('E (Our Implementation)'); 69 | subplot(236); imshow(Bg); title('Bg (Our Implementation)'); 70 | set(gcf,'units','normalized','outerposition',[0 0 1 1]); 71 | 72 | 73 | %% Stain Separation using Macenko's Image specific Stain Matrix for H&E 74 | 75 | disp(['Stain Separation using an Image specific Stain matrix '... 76 | 'estimated using Macenko''s method']); 77 | 78 | MacenkoMatrix = EstUsingMacenko( SourceImage ); 79 | 80 | Deconvolve( SourceImage, MacenkoMatrix, verbose ); 81 | 82 | 83 | %% Stain Normalisation using Macenko's Method 84 | 85 | disp('Stain Normalisation using Macenko''s Method'); 86 | 87 | [ NormMM ] = Norm(SourceImage, TargetImage, 'Macenko', 255, 0.15, 1, verbose); 88 | 89 | 90 | %% Stain Separation using Image specific Stain Matrix for H&E 91 | 92 | disp(['Stain Separation using an Image specific Stain matrix estimated '... 93 | 'using the Stain Colour Descriptor Method']); 94 | 95 | SCDMatrix = EstUsingSCD( SourceImage ); 96 | 97 | Deconvolve( SourceImage, SCDMatrix, verbose ); 98 | 99 | 100 | %% Stain Normalisation using the Non-Linear Spline Mapping Method 101 | 102 | disp('Stain Normalisation using the Non-Linear Spline Mapping Method'); 103 | 104 | [ NormSM ] = Norm(SourceImage, TargetImage, 'SCD', [], verbose); 105 | 106 | 107 | %% Comparitive Results 108 | 109 | disp(' Now Displaying all Results for comparison'); 110 | 111 | figure, 112 | subplot(231); imshow(TargetImage); title('Reference'); 113 | subplot(234); imshow(SourceImage); title('Source'); 114 | subplot(232); imshow(NormHS); title('Histogram Specification'); 115 | subplot(235); imshow(NormRH); title('Reinhard'); 116 | subplot(233); imshow(NormMM); title('Macenko'); 117 | subplot(236); imshow(NormSM); title('SCD'); 118 | set(gcf,'units','normalized','outerposition',[0 0 1 1]); 119 | 120 | 121 | %% End of Demo 122 | disp('End of Demo'); 123 | -------------------------------------------------------------------------------- /subroutines_normalization/install.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Installation script for the Stain Normalisation Toolbox 3 | % 4 | % 5 | % Nicholas Trahearn 6 | % Department of Computer Science, 7 | % University of Warwick, UK. 8 | % 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | 11 | % Add this folder to the Matlab path. 12 | functionDir = mfilename('fullpath'); 13 | functionDir = functionDir(1:(end-length(mfilename))); 14 | 15 | addpath(genpath(functionDir)); 16 | 17 | % Set up colour deconvolution C code. 18 | mex colour_deconvolution.c; 19 | 20 | clear functionDir; --------------------------------------------------------------------------------