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}