/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.image.lut;
import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.file.xml.XMLPersistent;
import icy.image.colormap.IcyColorMap;
import icy.image.colormodel.IcyColorModel;
import icy.image.colorspace.IcyColorSpace;
import icy.image.colorspace.IcyColorSpaceEvent;
import icy.image.colorspace.IcyColorSpaceListener;
import icy.image.lut.LUT.LUTChannelEvent.LUTChannelEventType;
import icy.image.lut.LUTEvent.LUTEventType;
import icy.math.Scaler;
import icy.math.ScalerEvent;
import icy.math.ScalerListener;
import icy.type.DataType;
import icy.util.XMLUtil;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import org.w3c.dom.Node;
public class LUT implements IcyColorSpaceListener, ScalerListener, ChangeListener, XMLPersistent
{
private final static String ID_NUM_CHANNEL = "numChannel";
private final static String ID_SCALER = "scaler";
private final static String ID_COLORMAP = "colormap";
public static interface LUTChannelListener extends EventListener
{
public void lutChannelChanged(LUTChannelEvent e);
}
public static class LUTChannelEvent
{
public enum LUTChannelEventType
{
SCALER_CHANGED, COLORMAP_CHANGED
}
private final LUTChannel lutChannel;
private final LUTChannelEventType type;
public LUTChannelEvent(LUTChannel lutChannel, LUTChannelEventType type)
{
super();
this.lutChannel = lutChannel;
this.type = type;
}
/**
* @return the lutChannel
*/
public LUTChannel getLutChannel()
{
return lutChannel;
}
/**
* @return the type
*/
public LUTChannelEventType getType()
{
return type;
}
}
public class LUTChannel
{
/**
* band index
*/
private final int channel;
/**
* listeners
*/
private final List<LUTChannelListener> channelListeners;
public LUTChannel(int channel)
{
this.channel = channel;
channelListeners = new ArrayList<LUT.LUTChannelListener>();
}
public LUT getLut()
{
return LUT.this;
}
/**
* Copy the colormap and scaler data from the specified source {@link LUTChannel}
*/
public void copyFrom(LUTChannel source)
{
setColorMap(source.getColorMap(), true);
setScaler(source.getScaler());
}
public Scaler getScaler()
{
return getScalers()[channel];
}
/**
* Set the specified scaler (do a copy).
*
* @param source
* source scaler to copy from
*/
public void setScaler(Scaler source)
{
final Scaler scaler = getScaler();
scaler.beginUpdate();
try
{
scaler.setAbsLeftRightIn(source.getAbsLeftIn(), source.getAbsRightIn());
scaler.setLeftRightIn(source.getLeftIn(), source.getRightIn());
scaler.setLeftRightOut(source.getLeftOut(), source.getRightOut());
}
finally
{
scaler.endUpdate();
}
}
public IcyColorMap getColorMap()
{
return getColorSpace().getColorMap(channel);
}
/**
* Set the specified colormap (do a copy).
*
* @param colorMap
* source colorspace to copy
* @param setAlpha
* also set the alpha information
*/
public void setColorMap(IcyColorMap colorMap, boolean setAlpha)
{
getColorSpace().setColorMap(channel, colorMap, setAlpha);
}
/**
* @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead.
*/
@Deprecated
public void setColorMap(IcyColorMap colorMap)
{
setColorMap(colorMap, true);
}
/**
* @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead.
*/
@Deprecated
public void copyColorMap(IcyColorMap colorMap)
{
setColorMap(colorMap, true);
}
public double getMin()
{
return getScaler().getLeftIn();
}
public void setMin(double value)
{
getScaler().setLeftIn(value);
}
public double getMax()
{
return getScaler().getRightIn();
}
public void setMax(double value)
{
getScaler().setRightIn(value);
}
public void setMinMax(double min, double max)
{
getScaler().setLeftRightIn(min, max);
}
public double getMinBound()
{
return getScaler().getAbsLeftIn();
}
public double getMaxBound()
{
return getScaler().getAbsRightIn();
}
public void setMinBound(double value)
{
getScaler().setAbsLeftIn(value);
}
public void setMaxBound(double value)
{
getScaler().setAbsRightIn(value);
}
/**
* Returns the <i>enabled</i> state of this channel LUT
*/
public boolean isEnabled()
{
return getColorMap().isEnabled();
}
/**
* Enable/disable specified channel LUT
*/
public void setEnabled(boolean value)
{
getColorMap().setEnabled(value);
}
/**
* @deprecated Use {@link #getChannel()} instead.
*/
@Deprecated
public int getComponent()
{
return getChannel();
}
/**
* @return the component
*/
public int getChannel()
{
return channel;
}
/**
* Add a listener.
*/
public void addListener(LUTChannelListener listener)
{
channelListeners.add(listener);
}
/**
* Remove a listener.
*/
public void removeListener(LUTChannelListener listener)
{
channelListeners.remove(listener);
}
/**
* Fire change event.
*/
public void fireEvent(LUTChannelEvent e)
{
for (LUTChannelListener listener : new ArrayList<LUTChannelListener>(channelListeners))
listener.lutChannelChanged(e);
}
}
private List<LUTChannel> lutChannels = new ArrayList<LUTChannel>();
final IcyColorSpace colorSpace;
final Scaler[] scalers;
final int numChannel;
private boolean enabled = true;
/**
* listeners
*/
private final List<LUTListener> listeners;
/**
* internal updater
*/
private final UpdateEventHandler updater;
public LUT(IcyColorModel cm)
{
colorSpace = cm.getIcyColorSpace();
scalers = cm.getColormapScalers();
numChannel = colorSpace.getNumComponents();
if (scalers.length != numChannel)
{
throw new IllegalArgumentException("Incorrect size for scalers : " + scalers.length + ". Expected : "
+ numChannel);
}
final DataType dataType = cm.getDataType_();
for (int channel = 0; channel < numChannel; channel++)
{
// BYTE data type --> fix bounds to data type bounds
if (dataType == DataType.UBYTE)
scalers[channel].setLeftRightIn(dataType.getMinValue(), dataType.getMaxValue());
lutChannels.add(new LUTChannel(channel));
}
listeners = new ArrayList<LUTListener>();
updater = new UpdateEventHandler(this, false);
// add listener
for (Scaler scaler : scalers)
scaler.addListener(this);
colorSpace.addListener(this);
}
protected int indexOf(Scaler scaler)
{
for (int i = 0; i < scalers.length; i++)
if (scalers[i] == scaler)
return i;
return -1;
}
public IcyColorSpace getColorSpace()
{
return colorSpace;
}
public Scaler[] getScalers()
{
return scalers;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public ArrayList<LUTChannel> getLutChannels()
{
return new ArrayList<LUTChannel>(lutChannels);
}
/**
* Return the {@link LUTChannel} for specified channel index.
*/
public LUTChannel getLutChannel(int channel)
{
return lutChannels.get(channel);
}
/**
* @deprecated Use {@link #getLutChannels()} instead.
*/
@Deprecated
public ArrayList<LUTBand> getLutBands()
{
final ArrayList<LUTBand> result = new ArrayList<LUTBand>();
for (LUTChannel lutChannel : lutChannels)
result.add(new LUTBand(this, lutChannel.getChannel()));
return result;
}
/**
* @deprecated Use {@link #getLutChannel(int)} instead.
*/
@Deprecated
public LUTBand getLutBand(int band)
{
return getLutBands().get(band);
}
/**
* @return the number of channel.
*/
public int getNumChannel()
{
return numChannel;
}
/**
* @deprecated Use {@link #getNumChannel()} instead.
*/
@Deprecated
public int getNumComponents()
{
return getNumChannel();
}
/**
* Copy LUT from the specified source lut
*/
public void copyFrom(LUT lut)
{
beginUpdate();
try
{
setColorMaps(lut, true);
setScalers(lut);
}
finally
{
endUpdate();
}
}
/**
* Set the scalers from the specified source lut (do a copy)
*/
public void setScalers(LUT lut)
{
final Scaler[] srcScalers = lut.getScalers();
final int len = Math.min(scalers.length, srcScalers.length);
beginUpdate();
try
{
for (int i = 0; i < len; i++)
{
final Scaler src = srcScalers[i];
final Scaler dst = scalers[i];
dst.setAbsLeftRightIn(src.getAbsLeftIn(), src.getAbsRightIn());
dst.setLeftRightIn(src.getLeftIn(), src.getRightIn());
dst.setLeftRightOut(src.getLeftOut(), src.getRightOut());
}
}
finally
{
endUpdate();
}
}
/**
* @deprecated Use {@link #setScalers(LUT)} instead.
*/
@Deprecated
public void copyScalers(LUT lut)
{
setScalers(lut);
}
/**
* Set colormaps from the specified source lut (do a copy).
*
* @param lut
* source lut to use
* @param setAlpha
* also set the alpha information
*/
public void setColorMaps(LUT lut, boolean setAlpha)
{
getColorSpace().setColorMaps(lut.getColorSpace(), setAlpha);
}
/**
* @deprecated USe {@link #setColorMaps(LUT, boolean)} instead.
*/
@Deprecated
public void setColormaps(LUT lut)
{
setColorMaps(lut, true);
}
/**
* @deprecated Use {@link #setColorMaps(LUT, boolean)} instead.
*/
@Deprecated
public void copyColormaps(LUT lut)
{
setColorMaps(lut, true);
}
/**
* Set the alpha channel to full opaque for all LUT channel
*/
public void setAlphaToOpaque()
{
beginUpdate();
try
{
for (LUTChannel lutChannel : getLutChannels())
lutChannel.getColorMap().setAlphaToOpaque();
}
finally
{
endUpdate();
}
}
/**
* Set the alpha channel to linear opacity (0 to 1) for all LUT channel
*/
public void setAlphaToLinear()
{
beginUpdate();
try
{
for (LUTChannel lutChannel : getLutChannels())
lutChannel.getColorMap().setAlphaToLinear();
}
finally
{
endUpdate();
}
}
/**
* Set the alpha channel to an optimized linear transparency for 3D volume display on all LUT
* channel
*/
public void setAlphaToLinear3D()
{
beginUpdate();
try
{
for (LUTChannel lutChannel : getLutChannels())
lutChannel.getColorMap().setAlphaToLinear3D();
}
finally
{
endUpdate();
}
}
/**
* Return true if LUT is compatible with specified ColorModel.<br>
* (Same number of channels with same data type)
*/
public boolean isCompatible(LUT lut)
{
if (numChannel != lut.getNumChannel())
return false;
final Scaler[] cmScalers = lut.getScalers();
// check that data type is compatible
for (int channel = 0; channel < numChannel; channel++)
if (scalers[channel].isIntegerData() != cmScalers[channel].isIntegerData())
return false;
return true;
}
/**
* Return true if LUT is compatible with specified ColorModel.<br>
* (Same number of channels with same data type)
*/
public boolean isCompatible(IcyColorModel colorModel)
{
if (numChannel != colorModel.getNumComponents())
return false;
final Scaler[] cmScalers = colorModel.getColormapScalers();
// check that data type is compatible
for (int comp = 0; comp < numChannel; comp++)
if (scalers[comp].isIntegerData() != cmScalers[comp].isIntegerData())
return false;
return true;
}
/**
* Add a listener
*
* @param listener
*/
public void addListener(LUTListener listener)
{
listeners.add(listener);
}
/**
* Remove a listener
*
* @param listener
*/
public void removeListener(LUTListener listener)
{
listeners.remove(listener);
}
public void fireLUTChanged(LUTEvent e)
{
for (LUTListener lutListener : new ArrayList<LUTListener>(listeners))
lutListener.lutChanged(e);
}
@Override
public void onChanged(CollapsibleEvent compare)
{
final LUTEvent event = (LUTEvent) compare;
// notify listener we have changed
fireLUTChanged(event);
// propagate event to LUTChannel
final int channel = event.getComponent();
final LUTChannelEventType type = (event.getType() == LUTEventType.COLORMAP_CHANGED) ? LUTChannelEventType.COLORMAP_CHANGED
: LUTChannelEventType.SCALER_CHANGED;
if (channel == -1)
{
for (LUTChannel lutChannel : lutChannels)
lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type));
}
else
{
final LUTChannel lutChannel = getLutChannel(channel);
lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type));
}
}
@Override
public void colorSpaceChanged(IcyColorSpaceEvent e)
{
// notify LUT colormap changed
updater.changed(new LUTEvent(this, e.getComponent(), LUTEventType.COLORMAP_CHANGED));
}
@Override
public void scalerChanged(ScalerEvent e)
{
// notify LUTBand changed
updater.changed(new LUTEvent(this, indexOf(e.getScaler()), LUTEventType.SCALER_CHANGED));
}
public void beginUpdate()
{
updater.beginUpdate();
}
public void endUpdate()
{
updater.endUpdate();
}
public boolean isUpdating()
{
return updater.isUpdating();
}
@Override
public boolean loadFromXML(Node node)
{
if (node == null)
return false;
// different channel number --> exit
if (numChannel != XMLUtil.getElementIntValue(node, ID_NUM_CHANNEL, 1))
return false;
beginUpdate();
try
{
for (int ch = 0; ch < numChannel; ch++)
{
Node n;
n = XMLUtil.getElement(node, ID_SCALER + ch);
if (n != null)
scalers[ch].loadFromXML(n);
n = XMLUtil.getElement(node, ID_COLORMAP + ch);
if (n != null)
colorSpace.getColorMap(ch).loadFromXML(n);
}
}
finally
{
endUpdate();
}
return true;
}
@Override
public boolean saveToXML(Node node)
{
if (node == null)
return false;
XMLUtil.setElementIntValue(node, ID_NUM_CHANNEL, numChannel);
for (int ch = 0; ch < numChannel; ch++)
{
Node n;
n = XMLUtil.setElement(node, ID_SCALER + ch);
if (n != null)
scalers[ch].saveToXML(n);
n = XMLUtil.setElement(node, ID_COLORMAP + ch);
if (n != null)
colorSpace.getColorMap(ch).saveToXML(n);
}
return true;
}
}