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-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009
010package imagingbook.pub.color.image;
011import ij.IJ;
012import ij.ImagePlus;
013import ij.ImageStack;
014import ij.process.ColorProcessor;
015import ij.process.FloatProcessor;
016
017import java.awt.color.ColorSpace;
018
019/**
020 * This class contains only static methods for handling color images represented as 
021 * multi-plane image stacks.
022 * The 3 components in all these images are assumed to be in [0,1].
023 */
024public abstract class ColorStack { //extends ImageStack {
025        
026        public enum ColorStackType {
027                RGB("R", "G", "B"), 
028                sRGB("sR", "sG", "sB"), 
029                Lab("L", "a", "b"), 
030                Luv("L", "u", "v"),
031//              XYZ("X", "Y", "Z"),             // not currently implemented
032//              YCbCr("Y", "Cb", "Cr"),         // not currently implemented
033                ;
034
035                protected final String[] componentLabels;
036
037                ColorStackType(String... labels) {
038                        this.componentLabels = labels;
039                }
040        }
041        
042//      public static final String ColorStackTypeName = ColorStackType.class.getSimpleName();
043        
044        /**
045         *This static method creates a 3-slice float-stack from a RGB image
046         * @param imp the source (RGB) image.
047         * @return an image stack consisting of 3 slices of type float.
048         */
049        public static ImagePlus createFrom(ImagePlus imp) {
050                if (imp.getType() != ImagePlus.COLOR_RGB)
051                        return null;
052                ColorProcessor cp = (ColorProcessor) imp.getProcessor();
053                int width = cp.getWidth();
054                int height = cp.getHeight();
055                ImageStack stack = new ImageStack(width, height);
056
057                FloatProcessor rp = new FloatProcessor(width, height);
058                FloatProcessor gp = new FloatProcessor(width, height);
059                FloatProcessor bp = new FloatProcessor(width, height);
060                        
061                int[] pixels = (int[]) cp.getPixels(); 
062                float[] rf = (float[]) rp.getPixels();
063                float[] gf = (float[]) gp.getPixels();
064                float[] bf = (float[]) bp.getPixels();
065        for (int i=0; i<pixels.length; i++) {
066            int c = pixels[i];
067            int r = (c&0xff0000)>>16;
068                int g = (c&0xff00)>>8;
069                int b = c&0xff;
070                rf[i] = r / 255f;
071                gf[i] = g / 255f;
072                bf[i] = b / 255f;
073        }
074                stack.addSlice(rp);
075                stack.addSlice(gp);
076                stack.addSlice(bp);
077        ImagePlus cimp = new ImagePlus(imp.getTitle(), stack);
078        //cimp.setProperty(ColorStackType.class.getSimpleName(), ColorStackType.sRGB);
079        setType(cimp, ColorStackType.sRGB);
080                return cimp;
081        }
082        
083        public static void setType(ImagePlus imp, ColorStackType type) {
084                ImageStack stack = imp.getImageStack();
085                if (stack == null)
086                        return;
087                for (int i = 1; i <= 3; i++) {
088                        stack.setSliceLabel(type.componentLabels[i - 1], i);
089                }
090        }
091        
092        public static ColorStackType getType(ImagePlus imp) {
093                if (!isColorStack(imp)) return null;
094                for (ColorStackType types : ColorStackType.values()) {
095                        if (isType(imp, types)) {
096                                return types;
097                        }
098                }
099                return null;
100        }
101        
102        public static boolean isType(ImagePlus imp, ColorStackType type) {
103                if (!isColorStack(imp)) return false;
104                String[] labels = imp.getImageStack().getSliceLabels();
105                return 
106                        type.componentLabels[0].equals(labels[0]) &&
107                        type.componentLabels[1].equals(labels[1]) &&
108                        type.componentLabels[2].equals(labels[2]) ;
109//              Object prop = imp.getProperty(ColorStackTypeName);
110//              return (prop.toString().equals(type.toString()));
111        }
112        
113        public static boolean isColorStack(ImagePlus imp) {     
114                // calling only getStackSize() avoids a call to getImageStack()
115                return (imp != null && imp.getImageStackSize() == 3 
116                                &&      (imp.getImageStack().getProcessor(1) instanceof FloatProcessor)
117//                              &&      (imp.getImageStack().getPixels(1) instanceof float[])
118                                );
119        }
120        
121        public static FloatProcessor[] getProcessors(ImagePlus imp) {
122                if (isColorStack(imp)) {
123                        ImageStack stack = imp.getImageStack();
124                        int n = stack.getSize();
125                        FloatProcessor[] processors = new FloatProcessor[n];
126                        for (int i = 0; i < n; i++) {
127                                processors[i] = (FloatProcessor) stack.getProcessor(i + 1);
128                        }
129                        return processors;
130                }
131                else {
132                        return null;
133                }
134        }
135        
136        //---------------------------------------------------------
137        
138        public static void toSrgb(ImagePlus colstck) {
139                 ColorStackType cst = getType(colstck);
140                 if (cst == null) {
141                         IJ.error("Color stack is in unknown color space.");
142                         return;
143                 }
144                 switch (cst) {
145                        case Lab :      ColorStack.labToSrgb(colstck); break;
146                        case Luv:       ColorStack.luvToSrgb(colstck); break;
147                        case RGB:       ColorStack.rgbToSrgb(colstck); break;
148                        case sRGB:      break;  // colstck is in sRGB already, nothing to do
149                 }
150        }
151        
152        public static ImagePlus toColorImage(ImagePlus colstckimg) {
153                assert isColorStack(colstckimg);
154                ImageStack stack = colstckimg.getImageStack();
155                if (stack == null) 
156                        return null;
157                float[] rPix = (float[]) stack.getPixels(1);
158                float[] gPix = (float[]) stack.getPixels(2);
159                float[] bPix = (float[]) stack.getPixels(3);
160                ColorProcessor cp = new ColorProcessor(stack.getWidth(), stack.getHeight());
161                int[] srgbPix = (int[]) cp.getPixels();
162                for (int i = 0; i < srgbPix.length; i++) {
163            int r = Math.round(rPix[i] * 255);
164                int g = Math.round(gPix[i] * 255);
165                int b = Math.round(bPix[i] * 255);
166                if (r < 0) r = 0; else if (r > 255) r = 255;
167                if (g < 0) g = 0; else if (g > 255) g = 255;
168                if (b < 0) b = 0; else if (b > 255) b = 255;
169                srgbPix[i] = ((r & 0xff)<<16) | ((g & 0xff)<<8) | b & 0xff;
170        }
171                return new ImagePlus(colstckimg.getTitle(), cp);
172        }
173        
174        // sRGB <-> RGB --------------------------------------------------------
175        
176        public static void srgbToRgb(ImagePlus srgbImg) {
177                assert isColorStack(srgbImg);
178                ImageStack stack = srgbImg.getImageStack();
179                for (int k = 1; k <= 3; k++) {
180                        float[] pixels = (float[]) stack.getPixels(k);          
181                        for (int i=0; i<pixels.length; i++) {
182                                float p = pixels[i];
183                                if (p < 0) p = 0f; else if (p > 1) p = 1f;
184                                pixels[i] = gammaInv(p);
185                        }
186                }
187                //srgbImg.setProperty(ColorStackTypeName, ColorStackType.RGB);
188                setType(srgbImg, ColorStackType.RGB);
189        }
190        
191        public static void rgbToSrgb(ImagePlus rgbImg) {
192                assert isColorStack(rgbImg);
193                ImageStack stack = rgbImg.getImageStack();
194                for (int k = 1; k <= 3; k++) {
195                        float[] pixels = (float[]) stack.getPixels(k);          
196                        for (int i=0; i<pixels.length; i++) {
197                                float p = pixels[i];
198                                if (p < 0) p = 0f; else if (p > 1) p = 1f;
199                                pixels[i] = gammaFwd(p);
200                        }
201                }
202                //rgbImg.setProperty(ColorStackTypeName, ColorStackType.sRGB);
203                setType(rgbImg, ColorStackType.sRGB);
204        }
205        
206        // ---------------- numeric operations ---------------------------
207        
208        public static void multiply(ImagePlus rgbImg, double value) {
209                assert isColorStack(rgbImg);
210                ImageStack stack = rgbImg.getImageStack();
211                for (int k = 1; k <= stack.getSize(); k++)  {
212                        stack.getProcessor(k).multiply(value);
213                }
214        }
215        
216        public static void convolve(ImagePlus rgbImg, float[] kernel, int w, int h) {
217                assert isColorStack(rgbImg);
218                ImageStack stack = rgbImg.getImageStack();
219                for (int k = 1; k <= stack.getSize(); k++)  {
220                        stack.getProcessor(k).convolve(kernel, w, h);
221                }
222        }
223        
224        public static FloatProcessor max(ImagePlus rgbImg) { 
225                assert isColorStack(rgbImg);
226                ImageStack stack = rgbImg.getImageStack();
227                FloatProcessor rp = (FloatProcessor) stack.getProcessor(1).duplicate();
228                float[] rpix = (float[]) rp.getPixels();                
229                for (int k = 2; k <= 3; k++) {
230                        float[] pixels = (float[]) stack.getPixels(k);          
231                        for (int i=0; i<pixels.length; i++) {
232                                float p = pixels[i];
233                                if (p > rpix[i]) rpix[i] = p;
234                        }
235                }
236                return rp;
237        }
238        
239        public static FloatProcessor magL2(ImagePlus rgbImg) { 
240                assert isColorStack(rgbImg);
241                //final double scale = 1/Math.sqrt(3);
242                ImageStack stack = rgbImg.getImageStack();
243                FloatProcessor rp = (FloatProcessor) stack.getProcessor(1).duplicate();
244                float[] rpix = (float[]) rp.getPixels();        
245                float[] pixels1 = (float[]) stack.getPixels(1);
246                float[] pixels2 = (float[]) stack.getPixels(2);
247                float[] pixels3 = (float[]) stack.getPixels(3);
248                
249                for (int i=0; i<pixels1.length; i++) {
250                        double p1 = pixels1[i];
251                        double p2 = pixels2[i];
252                        double p3 = pixels3[i];
253                        //rpix[i] = (float) (scale * Math.sqrt(p1*p1 + p2*p2 + p3*p3));
254                        rpix[i] = (float) Math.sqrt(p1*p1 + p2*p2 + p3*p3);
255                }
256                return rp;
257        }
258        
259        public static FloatProcessor magL1(ImagePlus rgbImg) { 
260                assert isColorStack(rgbImg);
261                //final float scale = 1;
262                ImageStack stack = rgbImg.getImageStack();
263                FloatProcessor rp = (FloatProcessor) stack.getProcessor(1).duplicate();
264                float[] rpix = (float[]) rp.getPixels();        
265                float[] pixels1 = (float[]) stack.getPixels(1);
266                float[] pixels2 = (float[]) stack.getPixels(2);
267                float[] pixels3 = (float[]) stack.getPixels(3);
268                
269                for (int i = 0; i < pixels1.length; i++) {
270                        double p1 = pixels1[i];
271                        double p2 = pixels2[i];
272                        double p3 = pixels3[i];
273                        rpix[i] = (float) (Math.abs(p1) + Math.abs(p2) + Math.abs(p3));
274                }
275                return rp;
276        }
277        
278    static float gammaFwd(float lc) {   // input: linear component value
279                return (lc > 0.0031308) ?
280                        (float) (1.055 * Math.pow(lc, 1/2.4f) - 0.055) :
281                        (lc * 12.92f);
282    }
283    
284    static float gammaInv(float nc) {   // input: nonlinear component value
285        return (nc > 0.03928) ?
286                        (float) Math.pow((nc + 0.055)/1.055, 2.4) :
287                        (nc / 12.92f);
288    }
289    
290    // sRGB <-> Lab -----------------------------------------------------
291    
292        public static void srgbToLab(ImagePlus srgbImg) {
293                assert isColorStack(srgbImg);
294                ColorSpace lcs = new LabColorSpace();
295                ImageStack stack = srgbImg.getImageStack();
296                
297                float[] rPix = (float[]) stack.getPixels(1);
298                float[] gPix = (float[]) stack.getPixels(2);
299                float[] bPix = (float[]) stack.getPixels(3);
300                
301                float[] srgb = new float[3];
302                for (int i = 0; i < rPix.length; i++) {
303                        float rp = rPix[i];
304                        float gp = gPix[i];
305                        float bp = bPix[i];
306                        if (rp < 0) rp = 0; else if (rp > 1) rp = 1;
307                        if (gp < 0) gp = 0; else if (gp > 1) gp = 1;
308                        if (bp < 0) bp = 0; else if (bp > 1) bp = 1;
309                        srgb[0] = rp; srgb[1] = gp; srgb[2] = bp; 
310                        float[] lab = lcs.fromRGB(srgb);
311                        rPix[i] = lab[0];
312                        gPix[i] = lab[1];
313                        bPix[i] = lab[2];
314                }
315                setType(srgbImg, ColorStackType.Lab);
316        }
317        
318        public static void labToSrgb(ImagePlus labImg) {
319                assert isColorStack(labImg);
320                LabColorSpace lcs = new LabColorSpace();
321                ImageStack stack = labImg.getImageStack();
322                
323                float[] lPix = (float[]) stack.getPixels(1);
324                float[] aPix = (float[]) stack.getPixels(2);
325                float[] bPix = (float[]) stack.getPixels(3);
326                
327                float[] lab = new float[3];
328                for (int i = 0; i < lPix.length; i++) {
329                        lab[0] = lPix[i]; lab[1] = aPix[i]; lab[2] = bPix[i]; 
330                        float[] srgb = lcs.toRGB(lab);
331                        float rp = srgb[0];
332                        float gp = srgb[1];
333                        float bp = srgb[2];
334                        if (rp < 0) rp = 0; else if (rp > 1) rp = 1;
335                        if (gp < 0) gp = 0; else if (gp > 1) gp = 1;
336                        if (bp < 0) bp = 0; else if (bp > 1) bp = 1;
337                        lPix[i] = rp; // R
338                        aPix[i] = gp; // G
339                        bPix[i] = bp; // B
340                }
341                setType(labImg, ColorStackType.sRGB);
342        }
343   
344    // sRGB <-> Luv -----------------------------------------------------
345    
346        public static void srgbToLuv(ImagePlus srgbImg) {
347                assert isColorStack(srgbImg);
348                LuvColorSpace lcs = new LuvColorSpace();
349                ImageStack stack = srgbImg.getImageStack();
350                
351                float[] rPix = (float[]) stack.getPixels(1);
352                float[] gPix = (float[]) stack.getPixels(2);
353                float[] bPix = (float[]) stack.getPixels(3);
354                
355                float[] srgb = new float[3];
356                for (int i = 0; i < rPix.length; i++) {
357                        float rp = rPix[i];
358                        float gp = gPix[i];
359                        float bp = bPix[i];
360                        if (rp < 0) rp = 0; else if (rp > 1) rp = 1;
361                        if (gp < 0) gp = 0; else if (gp > 1) gp = 1;
362                        if (bp < 0) bp = 0; else if (bp > 1) bp = 1;
363                        srgb[0] = rp; srgb[1] = gp; srgb[2] = bp; 
364                        float[] lab = lcs.fromRGB(srgb);
365                        rPix[i] = lab[0];
366                        gPix[i] = lab[1];
367                        bPix[i] = lab[2];
368                }
369                setType(srgbImg, ColorStackType.Luv);
370        }
371        
372        public static void luvToSrgb(ImagePlus luvImg) {
373                assert isColorStack(luvImg);
374                ColorSpace lcs = new LuvColorSpace();
375                ImageStack stack = luvImg.getImageStack();
376                
377                float[] lPix = (float[]) stack.getPixels(1);
378                float[] aPix = (float[]) stack.getPixels(2);
379                float[] bPix = (float[]) stack.getPixels(3);
380                
381                float[] lab = new float[3];
382                for (int i = 0; i < lPix.length; i++) {
383                        lab[0] = lPix[i]; lab[1] = aPix[i]; lab[2] = bPix[i]; 
384                        float[] srgb = lcs.toRGB(lab);
385                        float rp = srgb[0];
386                        float gp = srgb[1];
387                        float bp = srgb[2];
388                        if (rp < 0) rp = 0; else if (rp > 1) rp = 1;
389                        if (gp < 0) gp = 0; else if (gp > 1) gp = 1;
390                        if (bp < 0) bp = 0; else if (bp > 1) bp = 1;
391                        lPix[i] = rp; // R
392                        aPix[i] = gp; // G
393                        bPix[i] = bp; // B
394                }
395                setType(luvImg, ColorStackType.sRGB);
396        }
397    
398}