001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.image.lut; 020 021import icy.common.CollapsibleEvent; 022import icy.common.UpdateEventHandler; 023import icy.common.listener.ChangeListener; 024import icy.file.xml.XMLPersistent; 025import icy.image.colormap.IcyColorMap; 026import icy.image.colormodel.IcyColorModel; 027import icy.image.colorspace.IcyColorSpace; 028import icy.image.colorspace.IcyColorSpaceEvent; 029import icy.image.colorspace.IcyColorSpaceListener; 030import icy.image.lut.LUT.LUTChannelEvent.LUTChannelEventType; 031import icy.image.lut.LUTEvent.LUTEventType; 032import icy.math.Scaler; 033import icy.math.ScalerEvent; 034import icy.math.ScalerListener; 035import icy.type.DataType; 036import icy.util.XMLUtil; 037 038import java.util.ArrayList; 039import java.util.EventListener; 040import java.util.List; 041 042import org.w3c.dom.Node; 043 044public class LUT implements IcyColorSpaceListener, ScalerListener, ChangeListener, XMLPersistent 045{ 046 private final static String ID_NUM_CHANNEL = "numChannel"; 047 private final static String ID_SCALER = "scaler"; 048 private final static String ID_COLORMAP = "colormap"; 049 050 public static interface LUTChannelListener extends EventListener 051 { 052 public void lutChannelChanged(LUTChannelEvent e); 053 } 054 055 public static class LUTChannelEvent 056 { 057 public enum LUTChannelEventType 058 { 059 SCALER_CHANGED, COLORMAP_CHANGED 060 } 061 062 private final LUTChannel lutChannel; 063 private final LUTChannelEventType type; 064 065 public LUTChannelEvent(LUTChannel lutChannel, LUTChannelEventType type) 066 { 067 super(); 068 069 this.lutChannel = lutChannel; 070 this.type = type; 071 } 072 073 /** 074 * @return the lutChannel 075 */ 076 public LUTChannel getLutChannel() 077 { 078 return lutChannel; 079 } 080 081 /** 082 * @return the type 083 */ 084 public LUTChannelEventType getType() 085 { 086 return type; 087 } 088 } 089 090 public class LUTChannel 091 { 092 /** 093 * band index 094 */ 095 private final int channel; 096 097 /** 098 * listeners 099 */ 100 private final List<LUTChannelListener> channelListeners; 101 102 public LUTChannel(int channel) 103 { 104 this.channel = channel; 105 106 channelListeners = new ArrayList<LUT.LUTChannelListener>(); 107 } 108 109 public LUT getLut() 110 { 111 return LUT.this; 112 } 113 114 /** 115 * Copy the colormap and scaler data from the specified source {@link LUTChannel} 116 */ 117 public void copyFrom(LUTChannel source) 118 { 119 setColorMap(source.getColorMap(), true); 120 setScaler(source.getScaler()); 121 } 122 123 public Scaler getScaler() 124 { 125 return getScalers()[channel]; 126 } 127 128 /** 129 * Set the specified scaler (do a copy). 130 * 131 * @param source 132 * source scaler to copy from 133 */ 134 public void setScaler(Scaler source) 135 { 136 final Scaler scaler = getScaler(); 137 138 scaler.beginUpdate(); 139 try 140 { 141 scaler.setAbsLeftRightIn(source.getAbsLeftIn(), source.getAbsRightIn()); 142 scaler.setLeftRightIn(source.getLeftIn(), source.getRightIn()); 143 scaler.setLeftRightOut(source.getLeftOut(), source.getRightOut()); 144 } 145 finally 146 { 147 scaler.endUpdate(); 148 } 149 } 150 151 public IcyColorMap getColorMap() 152 { 153 return getColorSpace().getColorMap(channel); 154 } 155 156 /** 157 * Set the specified colormap (do a copy). 158 * 159 * @param colorMap 160 * source colorspace to copy 161 * @param setAlpha 162 * also set the alpha information 163 */ 164 public void setColorMap(IcyColorMap colorMap, boolean setAlpha) 165 { 166 getColorSpace().setColorMap(channel, colorMap, setAlpha); 167 } 168 169 /** 170 * @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead. 171 */ 172 @Deprecated 173 public void setColorMap(IcyColorMap colorMap) 174 { 175 setColorMap(colorMap, true); 176 } 177 178 /** 179 * @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead. 180 */ 181 @Deprecated 182 public void copyColorMap(IcyColorMap colorMap) 183 { 184 setColorMap(colorMap, true); 185 } 186 187 public double getMin() 188 { 189 return getScaler().getLeftIn(); 190 } 191 192 public void setMin(double value) 193 { 194 getScaler().setLeftIn(value); 195 } 196 197 public double getMax() 198 { 199 return getScaler().getRightIn(); 200 } 201 202 public void setMax(double value) 203 { 204 getScaler().setRightIn(value); 205 } 206 207 public void setMinMax(double min, double max) 208 { 209 getScaler().setLeftRightIn(min, max); 210 } 211 212 public double getMinBound() 213 { 214 return getScaler().getAbsLeftIn(); 215 } 216 217 public double getMaxBound() 218 { 219 return getScaler().getAbsRightIn(); 220 } 221 222 public void setMinBound(double value) 223 { 224 getScaler().setAbsLeftIn(value); 225 } 226 227 public void setMaxBound(double value) 228 { 229 getScaler().setAbsRightIn(value); 230 } 231 232 /** 233 * Returns the <i>enabled</i> state of this channel LUT 234 */ 235 public boolean isEnabled() 236 { 237 return getColorMap().isEnabled(); 238 } 239 240 /** 241 * Enable/disable specified channel LUT 242 */ 243 public void setEnabled(boolean value) 244 { 245 getColorMap().setEnabled(value); 246 } 247 248 /** 249 * @deprecated Use {@link #getChannel()} instead. 250 */ 251 @Deprecated 252 public int getComponent() 253 { 254 return getChannel(); 255 } 256 257 /** 258 * @return the component 259 */ 260 public int getChannel() 261 { 262 return channel; 263 } 264 265 /** 266 * Add a listener. 267 */ 268 public void addListener(LUTChannelListener listener) 269 { 270 channelListeners.add(listener); 271 } 272 273 /** 274 * Remove a listener. 275 */ 276 public void removeListener(LUTChannelListener listener) 277 { 278 channelListeners.remove(listener); 279 } 280 281 /** 282 * Fire change event. 283 */ 284 public void fireEvent(LUTChannelEvent e) 285 { 286 for (LUTChannelListener listener : new ArrayList<LUTChannelListener>(channelListeners)) 287 listener.lutChannelChanged(e); 288 } 289 } 290 291 private List<LUTChannel> lutChannels = new ArrayList<LUTChannel>(); 292 293 final IcyColorSpace colorSpace; 294 final Scaler[] scalers; 295 final int numChannel; 296 297 private boolean enabled = true; 298 299 /** 300 * listeners 301 */ 302 private final List<LUTListener> listeners; 303 304 /** 305 * internal updater 306 */ 307 private final UpdateEventHandler updater; 308 309 public LUT(IcyColorModel cm) 310 { 311 colorSpace = cm.getIcyColorSpace(); 312 scalers = cm.getColormapScalers(); 313 numChannel = colorSpace.getNumComponents(); 314 315 if (scalers.length != numChannel) 316 { 317 throw new IllegalArgumentException( 318 "Incorrect size for scalers : " + scalers.length + ". Expected : " + numChannel); 319 } 320 321 final DataType dataType = cm.getDataType_(); 322 323 for (int channel = 0; channel < numChannel; channel++) 324 { 325 // BYTE data type --> fix bounds to data type bounds 326 if (dataType == DataType.UBYTE) 327 scalers[channel].setLeftRightIn(dataType.getMinValue(), dataType.getMaxValue()); 328 329 lutChannels.add(new LUTChannel(channel)); 330 } 331 332 listeners = new ArrayList<LUTListener>(); 333 updater = new UpdateEventHandler(this, false); 334 335 // add listener 336 for (Scaler scaler : scalers) 337 scaler.addListener(this); 338 colorSpace.addListener(this); 339 340 } 341 342 protected int indexOf(Scaler scaler) 343 { 344 for (int i = 0; i < scalers.length; i++) 345 if (scalers[i] == scaler) 346 return i; 347 348 return -1; 349 } 350 351 public IcyColorSpace getColorSpace() 352 { 353 return colorSpace; 354 } 355 356 public Scaler[] getScalers() 357 { 358 return scalers; 359 } 360 361 public boolean isEnabled() 362 { 363 return enabled; 364 } 365 366 public void setEnabled(boolean enabled) 367 { 368 this.enabled = enabled; 369 } 370 371 public ArrayList<LUTChannel> getLutChannels() 372 { 373 return new ArrayList<LUTChannel>(lutChannels); 374 } 375 376 /** 377 * Return the {@link LUTChannel} for specified channel index. 378 */ 379 public LUTChannel getLutChannel(int channel) 380 { 381 return lutChannels.get(channel); 382 } 383 384 /** 385 * @deprecated Use {@link #getLutChannels()} instead. 386 */ 387 @Deprecated 388 public ArrayList<LUTBand> getLutBands() 389 { 390 final ArrayList<LUTBand> result = new ArrayList<LUTBand>(); 391 392 for (LUTChannel lutChannel : lutChannels) 393 result.add(new LUTBand(this, lutChannel.getChannel())); 394 395 return result; 396 } 397 398 /** 399 * @deprecated Use {@link #getLutChannel(int)} instead. 400 */ 401 @Deprecated 402 public LUTBand getLutBand(int band) 403 { 404 return getLutBands().get(band); 405 } 406 407 /** 408 * @return the number of channel. 409 */ 410 public int getNumChannel() 411 { 412 return numChannel; 413 } 414 415 /** 416 * @deprecated Use {@link #getNumChannel()} instead. 417 */ 418 @Deprecated 419 public int getNumComponents() 420 { 421 return getNumChannel(); 422 } 423 424 /** 425 * Copy LUT from the specified source lut 426 */ 427 public void copyFrom(LUT lut) 428 { 429 beginUpdate(); 430 try 431 { 432 setColorMaps(lut, true); 433 setScalers(lut); 434 } 435 finally 436 { 437 endUpdate(); 438 } 439 } 440 441 /** 442 * Set the scalers from the specified source lut (do a copy) 443 */ 444 public void setScalers(LUT lut) 445 { 446 final Scaler[] srcScalers = lut.getScalers(); 447 final int len = Math.min(scalers.length, srcScalers.length); 448 449 beginUpdate(); 450 try 451 { 452 for (int i = 0; i < len; i++) 453 { 454 final Scaler src = srcScalers[i]; 455 final Scaler dst = scalers[i]; 456 457 dst.setAbsLeftRightIn(src.getAbsLeftIn(), src.getAbsRightIn()); 458 dst.setLeftRightIn(src.getLeftIn(), src.getRightIn()); 459 dst.setLeftRightOut(src.getLeftOut(), src.getRightOut()); 460 } 461 } 462 finally 463 { 464 endUpdate(); 465 } 466 } 467 468 /** 469 * @deprecated Use {@link #setScalers(LUT)} instead. 470 */ 471 @Deprecated 472 public void copyScalers(LUT lut) 473 { 474 setScalers(lut); 475 } 476 477 /** 478 * Set colormaps from the specified source lut (do a copy). 479 * 480 * @param lut 481 * source lut to use 482 * @param setAlpha 483 * also set the alpha information 484 */ 485 public void setColorMaps(LUT lut, boolean setAlpha) 486 { 487 getColorSpace().setColorMaps(lut.getColorSpace(), setAlpha); 488 } 489 490 /** 491 * @deprecated USe {@link #setColorMaps(LUT, boolean)} instead. 492 */ 493 @Deprecated 494 public void setColormaps(LUT lut) 495 { 496 setColorMaps(lut, true); 497 } 498 499 /** 500 * @deprecated Use {@link #setColorMaps(LUT, boolean)} instead. 501 */ 502 @Deprecated 503 public void copyColormaps(LUT lut) 504 { 505 setColorMaps(lut, true); 506 } 507 508 /** 509 * Set the alpha channel to full opaque for all LUT channel 510 */ 511 public void setAlphaToOpaque() 512 { 513 beginUpdate(); 514 try 515 { 516 for (LUTChannel lutChannel : getLutChannels()) 517 if (!lutChannel.getColorMap().isAlpha()) 518 lutChannel.getColorMap().setAlphaToOpaque(); 519 } 520 finally 521 { 522 endUpdate(); 523 } 524 } 525 526 /** 527 * Set the alpha channel to linear opacity (0 to 1) for all LUT channel 528 */ 529 public void setAlphaToLinear() 530 { 531 beginUpdate(); 532 try 533 { 534 for (LUTChannel lutChannel : getLutChannels()) 535 lutChannel.getColorMap().setAlphaToLinear(); 536 } 537 finally 538 { 539 endUpdate(); 540 } 541 } 542 543 /** 544 * Set the alpha channel to an optimized linear transparency for 3D volume display on all LUT 545 * channel 546 */ 547 public void setAlphaToLinear3D() 548 { 549 beginUpdate(); 550 try 551 { 552 for (LUTChannel lutChannel : getLutChannels()) 553 lutChannel.getColorMap().setAlphaToLinear3D(); 554 } 555 finally 556 { 557 endUpdate(); 558 } 559 } 560 561 /** 562 * Return true if LUT is compatible with specified ColorModel.<br> 563 * (Same number of channels with same data type) 564 */ 565 public boolean isCompatible(LUT lut) 566 { 567 if (numChannel != lut.getNumChannel()) 568 return false; 569 570 final Scaler[] cmScalers = lut.getScalers(); 571 572 // check that data type is compatible 573 for (int channel = 0; channel < numChannel; channel++) 574 if (scalers[channel].isIntegerData() != cmScalers[channel].isIntegerData()) 575 return false; 576 577 return true; 578 } 579 580 /** 581 * Return true if LUT is compatible with specified ColorModel.<br> 582 * (Same number of channels with same data type) 583 */ 584 public boolean isCompatible(IcyColorModel colorModel) 585 { 586 if (numChannel != colorModel.getNumComponents()) 587 return false; 588 589 final Scaler[] cmScalers = colorModel.getColormapScalers(); 590 591 // check that data type is compatible 592 for (int comp = 0; comp < numChannel; comp++) 593 if (scalers[comp].isIntegerData() != cmScalers[comp].isIntegerData()) 594 return false; 595 596 return true; 597 } 598 599 /** 600 * Add a listener 601 * 602 * @param listener 603 */ 604 public void addListener(LUTListener listener) 605 { 606 listeners.add(listener); 607 } 608 609 /** 610 * Remove a listener 611 * 612 * @param listener 613 */ 614 public void removeListener(LUTListener listener) 615 { 616 listeners.remove(listener); 617 } 618 619 public void fireLUTChanged(LUTEvent e) 620 { 621 for (LUTListener lutListener : new ArrayList<LUTListener>(listeners)) 622 lutListener.lutChanged(e); 623 } 624 625 @Override 626 public void onChanged(CollapsibleEvent compare) 627 { 628 final LUTEvent event = (LUTEvent) compare; 629 630 // notify listener we have changed 631 fireLUTChanged(event); 632 633 // propagate event to LUTChannel 634 final int channel = event.getComponent(); 635 final LUTChannelEventType type = (event.getType() == LUTEventType.COLORMAP_CHANGED) 636 ? LUTChannelEventType.COLORMAP_CHANGED 637 : LUTChannelEventType.SCALER_CHANGED; 638 639 if (channel == -1) 640 { 641 for (LUTChannel lutChannel : lutChannels) 642 lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type)); 643 } 644 else 645 { 646 final LUTChannel lutChannel = getLutChannel(channel); 647 lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type)); 648 } 649 } 650 651 @Override 652 public void colorSpaceChanged(IcyColorSpaceEvent e) 653 { 654 // notify LUT colormap changed 655 updater.changed(new LUTEvent(this, e.getComponent(), LUTEventType.COLORMAP_CHANGED)); 656 } 657 658 @Override 659 public void scalerChanged(ScalerEvent e) 660 { 661 // notify LUTBand changed 662 updater.changed(new LUTEvent(this, indexOf(e.getScaler()), LUTEventType.SCALER_CHANGED)); 663 } 664 665 public void beginUpdate() 666 { 667 updater.beginUpdate(); 668 } 669 670 public void endUpdate() 671 { 672 updater.endUpdate(); 673 } 674 675 public boolean isUpdating() 676 { 677 return updater.isUpdating(); 678 } 679 680 @Override 681 public boolean loadFromXML(Node node) 682 { 683 if (node == null) 684 return false; 685 686 // different channel number --> exit 687 if (numChannel != XMLUtil.getElementIntValue(node, ID_NUM_CHANNEL, 1)) 688 return false; 689 690 beginUpdate(); 691 try 692 { 693 for (int ch = 0; ch < numChannel; ch++) 694 { 695 Node n; 696 697 n = XMLUtil.getElement(node, ID_SCALER + ch); 698 if (n != null) 699 scalers[ch].loadFromXML(n); 700 n = XMLUtil.getElement(node, ID_COLORMAP + ch); 701 if (n != null) 702 colorSpace.getColorMap(ch).loadFromXML(n); 703 } 704 } 705 finally 706 { 707 endUpdate(); 708 } 709 710 return true; 711 } 712 713 @Override 714 public boolean saveToXML(Node node) 715 { 716 if (node == null) 717 return false; 718 719 XMLUtil.setElementIntValue(node, ID_NUM_CHANNEL, numChannel); 720 721 for (int ch = 0; ch < numChannel; ch++) 722 { 723 Node n; 724 725 n = XMLUtil.setElement(node, ID_SCALER + ch); 726 if (n != null) 727 scalers[ch].saveToXML(n); 728 n = XMLUtil.setElement(node, ID_COLORMAP + ch); 729 if (n != null) 730 colorSpace.getColorMap(ch).saveToXML(n); 731 } 732 733 return true; 734 } 735}