Commit 704cea5d authored by lafabregue's avatar lafabregue

allow channel index modification for rgb color

parent 1e2ec39f
This diff is collapsed.
......@@ -12,62 +12,89 @@ import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import mustic.gui.ImageSession;
import mustic.gui.MainFrame;
import mustic.io.RawImage;
public class RgbBandChooserInternalFrame extends JInternalFrame {
/** liste de choix pour la bande bleu */
private JComboBox blueComboBox;
/** liste de choix pour la bande verte */
private JComboBox greenComboBox;
/** */
private static final long serialVersionUID = 1L;
/** liste de choix pour la bande rouge */
private JComboBox rougeComboBoxr;
private JComboBox<String> redComboBox;
private JTextArea redMin;
private JTextArea redMax;
/** liste de choix si noir et blanc */
private JComboBox whiteBlackComboBox;
/** liste de choix pour la bande verte */
private JComboBox<String> greenComboBox;
private JTextArea greenMin;
private JTextArea greenMax;
/** liste de choix pour la bande bleu */
private JComboBox<String> blueComboBox;
private JTextArea blueMin;
private JTextArea blueMax;
public RgbBandChooserInternalFrame(ImageSession currentImageSession) {
/** liste de choix si noir et blanc */
private JComboBox<String> whiteBlackComboBox;
/** min/max used for scale image colors */
private double[] scaledMax;
private double[] scaledMin;
public RgbBandChooserInternalFrame(final RawImage rawImage) {
super();
this.rougeComboBoxr = new JComboBox();
this.greenComboBox = new JComboBox();
this.blueComboBox = new JComboBox();
this.whiteBlackComboBox = new JComboBox();
this.scaledMax = rawImage.getScaledMaxValues();
this.scaledMin = rawImage.getScaledMinValues();
this.redComboBox = new JComboBox<String>();
this.redMin = new JTextArea();
this.redMax = new JTextArea();
this.greenComboBox = new JComboBox<String>();
this.greenMin = new JTextArea();
this.greenMax = new JTextArea();
this.blueComboBox = new JComboBox<String>();
this.blueMin = new JTextArea();
this.blueMax = new JTextArea();
this.whiteBlackComboBox = new JComboBox<String>();
this.setTitle(Messages.getString("RgbBandChooserInternalFrame.0")); //$NON-NLS-1$
this.whiteBlackComboBox.addItem(""); //$NON-NLS-1$
int nb_band = currentImageSession.getRawImage().getNbBands();
int nb_band = rawImage.getNbBands();
for (int i = 1; i <= nb_band; i++) {
this.rougeComboBoxr.addItem(Messages.getString("RgbBandChooserInternalFrame.2") + i); //$NON-NLS-1$
this.greenComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.3") + i); //$NON-NLS-1$
this.blueComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.4") + i); //$NON-NLS-1$
this.whiteBlackComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.5") + i); //$NON-NLS-1$
this.redComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.2") + i); //$NON-NLS-1$
this.greenComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.2") + i); //$NON-NLS-1$
this.blueComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.2") + i); //$NON-NLS-1$
this.whiteBlackComboBox.addItem(Messages.getString("RgbBandChooserInternalFrame.2") + i); //$NON-NLS-1$
}
this.rougeComboBoxr.setSelectedIndex(currentImageSession
.getBirdViewPanel().getR());
this.greenComboBox.setSelectedIndex(currentImageSession
.getBirdViewPanel().getG());
this.blueComboBox.setSelectedIndex(currentImageSession
.getBirdViewPanel().getB());
this.redComboBox.setSelectedIndex(rawImage.getR());
this.redMin.setText(""+scaledMin[rawImage.getR()]);
this.redMax.setText(""+scaledMax[rawImage.getR()]);
this.greenComboBox.setSelectedIndex(rawImage.getG());
this.greenMin.setText(""+scaledMin[rawImage.getG()]);
this.greenMax.setText(""+scaledMax[rawImage.getG()]);
this.blueComboBox.setSelectedIndex(rawImage.getB());
this.blueMin.setText(""+scaledMin[rawImage.getB()]);
this.blueMax.setText(""+scaledMax[rawImage.getB()]);
this.whiteBlackComboBox.setSelectedIndex(0);
JPanel centerPanel = new JPanel(new BorderLayout());
JPanel panelColor = new JPanel(new GridLayout(3, 2));
JPanel panelColor = new JPanel(new GridLayout(3, 1));
panelColor.setBorder(BorderFactory
.createTitledBorder(Messages.getString("RgbBandChooserInternalFrame.6"))); //$NON-NLS-1$
panelColor.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.7"), SwingConstants.CENTER)); //$NON-NLS-1$
panelColor.add(this.rougeComboBoxr);
panelColor.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.8"), SwingConstants.CENTER)); //$NON-NLS-1$
panelColor.add(this.greenComboBox);
panelColor.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.9"), SwingConstants.CENTER)); //$NON-NLS-1$
panelColor.add(this.blueComboBox);
panelColor.add(createChannelPanel(redComboBox, redMin, redMax,
Messages.getString("RgbBandChooserInternalFrame.7"))); //$NON-NLS-1$
panelColor.add(createChannelPanel(greenComboBox, greenMin, greenMax,
Messages.getString("RgbBandChooserInternalFrame.8"))); //$NON-NLS-1$
panelColor.add(createChannelPanel(blueComboBox, blueMin, blueMax,
Messages.getString("RgbBandChooserInternalFrame.9"))); //$NON-NLS-1$
centerPanel.add(panelColor, BorderLayout.NORTH);
JPanel panelNG = new JPanel(new GridLayout(1, 2));
panelNG.setBorder(BorderFactory
......@@ -99,44 +126,40 @@ public class RgbBandChooserInternalFrame extends JInternalFrame {
int green;
int blue;
// avec la methode .getParent() on parcurir la hierarchie des
// elements graphiques pour ariver a la component
// RgbBandChooserInternalFrame
RgbBandChooserInternalFrame rgbBandChooserInternalFrame = (RgbBandChooserInternalFrame) (((JButton) e
.getSource()).getParent().getParent().getParent()
.getParent().getParent());
if (!rgbBandChooserInternalFrame.whiteBlackComboBox
if (!whiteBlackComboBox
.getSelectedItem().equals("")) { //$NON-NLS-1$
red = rgbBandChooserInternalFrame.whiteBlackComboBox
red = whiteBlackComboBox
.getSelectedIndex() - 1;
green = rgbBandChooserInternalFrame.whiteBlackComboBox
green = whiteBlackComboBox
.getSelectedIndex() - 1;
blue = rgbBandChooserInternalFrame.whiteBlackComboBox
blue = whiteBlackComboBox
.getSelectedIndex() - 1;
} else {
red = rgbBandChooserInternalFrame.rougeComboBoxr
.getSelectedIndex();
green = rgbBandChooserInternalFrame.greenComboBox
.getSelectedIndex();
blue = rgbBandChooserInternalFrame.blueComboBox
.getSelectedIndex();
red = redComboBox.getSelectedIndex();
green = greenComboBox.getSelectedIndex();
blue = blueComboBox.getSelectedIndex();
}
System.out.println(red+" "+green+" "+blue);
ImageSession currentImageSession = MainFrame.getInstance()
.getCurrentImageSession();
currentImageSession.getBirdViewPanel().setR(red);
currentImageSession.getBirdViewPanel().setG(green);
currentImageSession.getBirdViewPanel().setB(blue);
currentImageSession.getBirdViewPanel().syncImage();
MainFrame.getInstance().getCurrentImageSession().getBirdViewPanel()
.refresh();
rgbBandChooserInternalFrame.setVisible(false);
MainFrame.getInstance().getDesktop()
.setSelectedFrame(currentImageSession.associatedFrame);
scaledMin[red] = Double.parseDouble(redMin.getText());
scaledMax[red] = Double.parseDouble(redMax.getText());
scaledMin[green] = Double.parseDouble(greenMin.getText());
scaledMax[green] = Double.parseDouble(greenMax.getText());
scaledMin[blue] = Double.parseDouble(blueMin.getText());
scaledMax[blue] = Double.parseDouble(blueMax.getText());
rawImage.setR(red);
rawImage.setG(green);
rawImage.setB(blue);
rawImage.setScaledMinValues(scaledMin);
rawImage.setScaledMaxValues(scaledMax);
rawImage.notifyObservers();
RgbBandChooserInternalFrame.this.setVisible(false);
MainFrame.getInstance().getDesktop().setSelectedFrame(
MainFrame.getInstance().getCurrentImageSession().associatedFrame);
}
});
......@@ -181,7 +204,7 @@ public class RgbBandChooserInternalFrame extends JInternalFrame {
}
});*/
this.setSize(180, 195);
this.setSize(400, 500);
this.setResizable(false);
this.setMaximizable(true);
this.setIconifiable(true);
......@@ -189,5 +212,24 @@ public class RgbBandChooserInternalFrame extends JInternalFrame {
this.setLocation(90, 90);
this.setVisible(true);
}
private JPanel createChannelPanel(JComboBox<String> cb, JTextArea taMin, JTextArea taMax, String title) {
JPanel result = new JPanel(new GridLayout(2,2));
result.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.22")));
result.add(cb);
JPanel minPanel = new JPanel(new GridLayout(1, 2));
minPanel.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.20")));
minPanel.add(taMin);
result.add(minPanel);
JPanel maxPanel = new JPanel(new GridLayout(1, 2));
maxPanel.add(new JLabel(Messages.getString("RgbBandChooserInternalFrame.21")));
maxPanel.add(taMax);
result.add(maxPanel);
result.setBorder(BorderFactory.createTitledBorder(title));
return result;
}
}
......@@ -572,7 +572,7 @@ public class ConstraintsSelectionDialog extends JInternalFrame implements mustic
return true;
}
private void addConstraint(List<String> line, boolean isImageRelated) {
void addConstraint(List<String> line, boolean isImageRelated) {
switch (Integer.parseInt(line.get(line.size() - 1))) {
case Constraint.MUST_LINK_TYPE:
try {
......
......@@ -245,14 +245,14 @@ RgbBandChooserInternalFrame.12=Ok
RgbBandChooserInternalFrame.15=To delete some channels look at
RgbBandChooserInternalFrame.16="Clustering" menu or
RgbBandChooserInternalFrame.17="Crop" menu.
RgbBandChooserInternalFrame.2=channel
RgbBandChooserInternalFrame.3=channel
RgbBandChooserInternalFrame.4=channel
RgbBandChooserInternalFrame.5=channel
RgbBandChooserInternalFrame.2=Channel
RgbBandChooserInternalFrame.6=Color display
RgbBandChooserInternalFrame.7=R
RgbBandChooserInternalFrame.8=G
RgbBandChooserInternalFrame.9=B
RgbBandChooserInternalFrame.20=Min
RgbBandChooserInternalFrame.21=Max
RgbBandChooserInternalFrame.22=Channel index :
SegmentationDialog.0=Browse
SegmentationDialog.1=<html>Apply the algorithm of Region Merging.</html>
SegmentationDialog.14=Segmentation \:
......
......@@ -210,13 +210,13 @@ RgbBandChooserInternalFrame.15=Pour supprimer des bandes voir le
RgbBandChooserInternalFrame.16=menu "Clustering" ou le menu
RgbBandChooserInternalFrame.17="Crop".
RgbBandChooserInternalFrame.2=bande
RgbBandChooserInternalFrame.3=bande
RgbBandChooserInternalFrame.4=bande
RgbBandChooserInternalFrame.5=bande
RgbBandChooserInternalFrame.6=Coleur d'affichage
RgbBandChooserInternalFrame.7=R
RgbBandChooserInternalFrame.8=G
RgbBandChooserInternalFrame.9=B
RgbBandChooserInternalFrame.20=Min
RgbBandChooserInternalFrame.21=Max
RgbBandChooserInternalFrame.22=Index de bande :
SegmentationDialog.0=Parcourir...
SegmentationDialog.1=<html>Appliquez l'algorithme de Fusion de Rgion.</html>
SegmentationDialog.14=Segmentation \:
......
......@@ -171,6 +171,11 @@ public class BirdViewPanel extends JPanel implements MouseListener,
}
/** Get the red value */
public int getR() {
return this.r;
}
/** Get the blue value */
public int getB() {
return this.b;
......@@ -189,11 +194,6 @@ public class BirdViewPanel extends JPanel implements MouseListener,
return this.mImage.getNbBands();
}
/** Get the red value */
public int getR() {
return this.r;
}
public BufferedImage getSaveImage() {
return this.bImage;
}
......
......@@ -3,6 +3,8 @@ package mustic.gui.panels;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;
import javax.swing.*;
......@@ -23,7 +25,7 @@ import mustic.utils.image.Zoomable;
/**
* Display an image and allows the user to do some operations on it (zoom, contrast..)
*/
public class ImagePanel extends JPanel implements Zoomable {
public class ImagePanel extends JPanel implements Zoomable, Observer {
/** */
private static final long serialVersionUID = 1L;
......@@ -121,6 +123,7 @@ public class ImagePanel extends JPanel implements Zoomable {
*/
public ImagePanel(RawImage aImage, ImageSession session) {
this.rawImage = aImage;
rawImage.addObserver(this);
if (session.isDisplayEnable()) {
this.viewer = new PyramidImageViewer(aImage, true);
......@@ -1212,4 +1215,20 @@ public class ImagePanel extends JPanel implements Zoomable {
return new Point(0,0);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof RawImage) {
RawImage rawImg = (RawImage) o;
int[] rgb = new int[3];
rgb[0] = rawImg.getR();
rgb[1] = rawImg.getG();
rgb[2] = rawImg.getB();
viewer.setRgbChannels(rgb);
viewer.setMinMaxValues(rawImg.getMinValues(), rawImg.getMaxValues());
displayPanel.repaint();
}
}
}
......@@ -824,9 +824,13 @@ public class ImageResultPanel extends ResultPanel implements TreeSelectionListen
BufferedImage bi = imgPanel.getFullColorImage();
String outputPath = "imageColor.tif";
File outputfile = new File(outputPath);
Vector<Color> colors = new Vector<Color>();
for (int k = 0 ; k < imgPanel.getClassification().getNbClusters(); k++) {
colors.add(imgPanel.getClassification().getClusteringResult().getCluster(k).getColor());
}
// ImageIO.write(bi, "png", outputfile);
TiffUtils.writeRGBTiffwithMetaData(bi, outputPath,
imgPanel.getImgData().getDataFilesName().get(0));
TiffUtils.writeIndexColorTiffwithMetaData(bi, outputPath,
imgPanel.getImgData().getDataFilesName().get(0), colors);
Packager.zipFile(out, name + "/imageColor.tif", outputfile);
outputfile.delete();
out.closeEntry();
......
......@@ -4,6 +4,7 @@ import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Vector;
import java.util.Observable;
import javax.imageio.ImageReadParam;
import javax.swing.JOptionPane;
......@@ -25,13 +26,12 @@ import jcl.utils.exceptions.MethodNotImplementedException;
/**
* <p>
* RawImage is the generic class for all geoimages and provides functions for
* loading parts and birdview from the image.
* RawImage is the generic class for all geoimages.
* </p>
*
* @author Benoit Pradelle (benoit.pradelle@eturs.u-strasbg.fr)
*/
public class RawImage implements Serializable, MemoryFlush
public class RawImage extends Observable implements Serializable, MemoryFlush
{
/**
......@@ -138,6 +138,13 @@ public class RawImage implements Serializable, MemoryFlush
/** max values for each channels */
protected double[] maxValues = null;
/** min values for each channels */
protected double[] scaledMinValues = null;
/** max values for each channels */
protected double[] scaledMaxValues = null;
/**
* <p>
* Represents number of bands in the image.
......@@ -2056,21 +2063,75 @@ public class RawImage implements Serializable, MemoryFlush
this.y_offset = yoffset;
}
/**
* Get the red channel index
*
* @return the index
*/
public int getR() {
return this.r;
}
/**
* Get the blue channel index
*
* @return the index
*/
public int getB() {
return this.b;
}
/**
* Get the green channel index
*
* @return the index
*/
public int getG() {
return this.g;
}
/**
* Set the index used for the red channel
*
* @param r
* the channel index
*/
public void setR(int r)
{
if (this.r != r) {
setChanged();
}
this.r = r;
}
/**
* Set the index used for the green channel
*
* @param g
* the channel index
*/
public void setG(int g)
{
if (this.g != g) {
setChanged();
}
this.g = g;
}
/**
* Set the index used for the blue channel
*
* @param b
* the channel index
*/
public void setB(int b)
{
if (this.b != b) {
setChanged();
}
this.b = b;
}
......@@ -2143,6 +2204,57 @@ public class RawImage implements Serializable, MemoryFlush
this.maxValues = maxValues;
}
/**
* Return the minimum value used for scaling for each channel
* @return the minimums
*/
public double[] getScaledMinValues() {
if(scaledMinValues == null) {
computeMinMax();
}
return scaledMinValues;
}
/**
* Set the minimum value used for scaling for each channel
* @param minValues
* the minimums
*/
public void setScaledMinValues(double[] minValues) {
for (int i = 0 ; i < minValues.length ; i++) {
if (minValues[i] != scaledMinValues[i]) {
setChanged();
}
}
this.scaledMinValues = minValues;
}
/**
* Return the maximum value used for scaling for each channel
* @return the maximums
*/
public double[] getScaledMaxValues() {
if(scaledMaxValues == null) {
computeMinMax();
}
return maxValues;
}
/**
* Set the maximum value for each channel
* @param maxValues
* the maximums
*/
public void setScaledMaxValues(double[] maxValues) {
for (int i = 0 ; i < maxValues.length ; i++) {
if (maxValues[i] != scaledMaxValues[i]) {
setChanged();
}
}
this.scaledMaxValues = maxValues;
}
/**
* Compute teh min/max values per channel
*/
......@@ -2159,6 +2271,8 @@ public class RawImage implements Serializable, MemoryFlush
}
minValues = reader.getMinValues();
maxValues = reader.getMaxValues();
scaledMinValues = reader.getMinValues();
scaledMaxValues = reader.getMaxValues();
}
@Override
......
......@@ -53,9 +53,6 @@ public class BufferedImageHelper {
BufferedImage result = new BufferedImage(cm, ra, false, null);
for (int x = 0 ; x < raster[0].length ; x++) {
for(int y = 0 ; y < raster[0][0].length ; y++) {
// int rgb = (raster[r][x][y] << 16)
// & (raster[g][x][y] << 8)
// & (raster[b][x][y]);
int rgb = new Color(raster[r][x][y],
raster[g][x][y],
raster[b][x][y]).getRGB();
......@@ -64,6 +61,65 @@ public class BufferedImageHelper {
}
return result;
}
/**
* Transform an image on short array format into a RGB BufferedImage
* The raster should follow the order bellow :
* short[channel][x][y]
*
* @param raster
* an image raster to convert to bufferImage
* @param r
* the index of the red channel
* @param g
* the index of the green channel
* @param b
* the index of the blue channel
* @param minValues
* the min values for each channel used to perform a rescale,
* null to don't do any rescale
* @param maxValues
* the max values for each channel used to perform a rescale,
* null to don't do any rescale
* @return the corresponding BufferdImage
*/
public static BufferedImage createBufferedImageFromImage(double[][][] raster, int r,
int g, int b, double[] minValues, double[] maxValues) {
if (r < 0 || r >= raster.length || g < 0 || g >= raster.length
|| b < 0 || b >= raster.length) {
throw new IndexOutOfBoundsException("R, G and B index should respect"
+ " the number of channels in the image.");
}
DirectColorModel cm = (DirectColorModel) DirectColorModel.getRGBdefault();
WritableRaster ra = cm.createCompatibleWritableRaster(raster[0].length, raster[0][0].length);
BufferedImage result = new BufferedImage(cm, ra, false, null);
for (int x = 0 ; x < raster[0].length ; x++) {
for(int y = 0 ; y < raster[0][0].length ; y++) {
if (minValues != null && maxValues != null) {
/*
* max contains the max value for r g b, we can create a valid
* representation of data. the data values must be between 0 and 255
* to be valid, so let's convert them if needed
*/
int rgb = new Color((short) (255 * (raster[r][x][y] - minValues[b])
/ (maxValues[b] - minValues[b])),
(short) (255 * (raster[g][x][y] - minValues[b])
/ (maxValues[b] - minValues[b])),
(short) (255 * (raster[b][x][y] - minValues[b])
/ (maxValues[b] - minValues[b]))).getRGB();
result.setRGB(x, y, rgb);
} else {
int rgb = new Color((short) raster[r][x][y],
(short) raster[g][x][y],
(short) raster[b][x][y]).getRGB();
result.setRGB(x, y, rgb);
}
}
}
return result;
}
/**
* Return the list of all min/max for each channel in an image raster.
......
......@@ -47,7 +47,7 @@ public class BufferedImageViewer extends ImageViewer {
/** an array that contains the whole image in a raster format
* - not used if instantiate directly with a BufferedImage */
private short[][][] raster = null;
private double[][][] raster = null;
BufferedImageViewer comparedImage = null;
......@@ -123,16 +123,14 @@ public class BufferedImageViewer extends ImageViewer {
*/
private void loadImage(boolean doRescale) throws FormatException, IOException {
StreamedImageReaderWrapper reader = new StreamedImageReaderWrapper(imagePath);
double[][][] doubleRaster = new double[reader.getChannelCount()]
[reader.getImageWidth()][reader.getImageHeight()];
raster = new short[reader.getChannelCount()]
raster = new double[reader.getChannelCount()]
[reader.getImageWidth()][reader.getImageHeight()];
for (int x = 0 ; x < reader.getImageWidth() ; x++) {
for (int y = 0 ; y < reader.getImageHeight() ; y++) {
double[] pixel = reader.getPixel(x, y);
for(int c = 0 ; c < reader.getChannelCount() ; c++) {
doubleRaster[c][x][y] = pixel[c];
raster[c][x][y] = pixel[c];
}
}
}
......@@ -145,39 +143,19 @@ public class BufferedImageViewer extends ImageViewer {
// we first convert the raster in short format
if (doRescale) {
double[][] minMax = BufferedImageHelper.computeMinMax(doubleRaster);
/*
* max contains the max value for r g b, we can create a valid
* representation of data. the data values must be between 0 and 255
* to be valid, so let's convert them if needed
*/
for (int b = 0; b < doubleRaster.length; b++) {
for (int x = 0; x < doubleRaster[0].length; x++) {
for (int y = 0; y < doubleRaster[0][0].length; y++) {
// convert only if needed to avoid color deformation
// on 8 bits images
raster[b][x][y] = (short) (255 * (doubleRaster[b][x][y] - minMax[b][0])
/ (minMax[b][1] - minMax[b][0]));
}
}
}
} else {
for (int b = 0; b < doubleRaster.length; b++) {
for (int x = 0; x < doubleRaster[0].length; x++) {
for (int y = 0; y < doubleRaster[0][0].length; y++) {
// convert only if needed to avoid color deformation
// on 8 bits images
raster[b][x][y] = (short) doubleRaster[b][x][y];
}
}
double[][] minMax = BufferedImageHelper.computeMinMax(raster);
minValues = new double[minMax.length];
maxValues = new double[minMax.length];
for (int i = 0 ; i < minMax.length ; i++) {
minValues[i] = minMax[i][0];
maxValues[i] = minMax[i][1];
}
}
// the we generate the source image from it
// then we generate the source image from it
mSourceImage = BufferedImageHelper.createBufferedImageFromImage(raster,
rgbBands[0], rgbBands[1], rgbBands[2]);
rgbBands[0], rgbBands[1], rgbBands[2], minValues, maxValues);
}
@Override
......@@ -326,6 +304,14 @@ public class BufferedImageViewer extends ImageViewer {
}
}