001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package Ch03_Point_Operations;
010
011import ij.IJ;
012import ij.ImagePlus;
013import ij.ImageStack;
014import ij.gui.GenericDialog;
015import ij.plugin.filter.PlugInFilter;
016import ij.process.ImageProcessor;
017import imagingbook.common.histogram.HistogramPlot;
018import imagingbook.common.histogram.HistogramUtils;
019import imagingbook.common.ij.DialogUtils;
020import imagingbook.core.jdoc.JavaDocHelp;
021import imagingbook.sampleimages.GeneralSampleImage;
022
023import static imagingbook.common.ij.IjUtils.noCurrentImage;
024
025/**
026 * <p>
027 * ImageJ plugin, matches the histograms of the supplied images which are supplied as a {@link ImageStack} with 2 ore
028 * more frames. The first image of this stack is used as the reference image, i.e., its cumulative histogram is used to
029 * calculate the intensity transformation for the remaining images. See Sec. 3.6.4 (Fig. 3.17) of [1] for additional
030 * details. The plugin displays the modified images as a new image stack. Optionally, the original histograms, the
031 * associated modified histograms and cumulative histograms are shown (again as image stacks). Note that all modified
032 * cumulative histograms should be very similar.
033 * </p>
034 * <p>
035 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
036 * (2022).
037 * </p>
038 *
039 * @author WB
040 * @see HistogramPlot
041 * @see HistogramUtils
042 */
043public class Match_Reference_Histogram_Stack implements PlugInFilter, JavaDocHelp {
044
045        private static boolean ShowOriginalHistograms = true;
046        private static boolean ShowModifiedHistograms = true;
047        private static boolean ShowModifiedCumulativeHistograms = false;
048
049        private ImagePlus im;   // reference image (selected interactively)
050
051        /** Constructor, asks to open a predefined sample image if no other image is currently open. */
052        public Match_Reference_Histogram_Stack() {
053                if (noCurrentImage()) {
054                        DialogUtils.askForSampleImage(GeneralSampleImage.CityscapeSmallStack);
055                }
056        }
057
058        @Override
059        public int setup(String arg, ImagePlus im) {
060                this.im = im;
061                return DOES_8G + STACK_REQUIRED + NO_CHANGES;
062        }
063        
064        @Override
065        public void run(ImageProcessor ip) {
066                ImageStack origImageStack = im.getStack();
067                int n = origImageStack.getSize();
068                if (n < 2) {
069                        IJ.error("Plugin requires stack with at least 2 slices!");
070                        return;
071                }
072
073                if (!runDialog()) // select the reference image
074                        return;
075
076                // get all histograms:
077                int[][] histograms = new int[n][];
078                for (int i = 0; i < n; i++) {
079                        ImageProcessor ipA = origImageStack.getProcessor(i + 1);
080                        histograms[i] = ipA.getHistogram();
081                }
082                // first image specifies the reference histogram:
083                int[] hRef = histograms[0];
084
085                // adapt remaining images to the reference histogram:
086                ImageStack modImageStack = new ImageStack();
087                modImageStack.addSlice(origImageStack.getProcessor(1).duplicate());
088                for (int i = 1; i < n; i++) {
089                        int[] hA = histograms[i];
090                        int[] f = HistogramUtils.matchHistograms(hA, hRef);
091                        ImageProcessor ipA = origImageStack.getProcessor(i + 1).duplicate();
092                        ipA.applyTable(f);
093                        modImageStack.addSlice(ipA);
094                }
095                new ImagePlus("Modified images", modImageStack).show();
096
097                if (ShowOriginalHistograms) {
098                        ImageStack origHistStack = new ImageStack();
099                        for (int i = 0; i < n; i++) {
100                                HistogramPlot plot = new HistogramPlot(histograms[i], null);
101                                origHistStack.addSlice(plot.getProcessor());
102                        }
103                        new ImagePlus("Original histograms", origHistStack).show();
104                }
105
106                if (ShowModifiedHistograms) {
107                        ImageStack modHistStack = new ImageStack();
108                        for (int i = 0; i < n; i++) {
109                                ImageProcessor ipAm = modImageStack.getProcessor(i + 1);
110                                int[] h = ipAm.getHistogram();
111                                modHistStack.addSlice(new HistogramPlot(h, null).getProcessor());
112                        }
113                        new ImagePlus("Modified histograms", modHistStack).show();
114                }
115
116                if (ShowModifiedCumulativeHistograms) {
117                        ImageStack modCumHistStack = new ImageStack();
118                        for (int i = 0; i < n; i++) {
119                                ImageProcessor ipA = modImageStack.getProcessor(i + 1);
120                                int[] h = ipA.getHistogram();
121                                modCumHistStack.addSlice(new HistogramPlot(HistogramUtils.cdf(h), null).getProcessor());
122                        }
123                        new ImagePlus("Modified cumulative histograms", modCumHistStack).show();
124                }
125        }
126
127        private boolean runDialog() {
128                GenericDialog gd = new GenericDialog(this.getClass().getSimpleName());
129                gd.addHelp(getJavaDocUrl());
130                gd.addCheckbox("Show original histograms", ShowOriginalHistograms);
131                gd.addCheckbox("Show modified histograms", ShowModifiedHistograms);
132                gd.addCheckbox("Show modified cum. histograms", ShowModifiedCumulativeHistograms);
133
134                gd.showDialog();
135                if(gd.wasCanceled())
136                        return false;
137
138                ShowOriginalHistograms = gd.getNextBoolean();
139                ShowModifiedHistograms = gd.getNextBoolean();
140                ShowModifiedCumulativeHistograms = gd.getNextBoolean();
141                return true;
142        }
143
144}
145