package gdsc.smlm.results;
import java.awt.Rectangle;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import com.thoughtworks.xstream.io.xml.DomDriver;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2013 Alex Herbert
* Genome Damage and Stability Centre
* University of Sussex, UK
*
* This program 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.
*---------------------------------------------------------------------------*/
/**
* Abstract base class for the image source for peak results.
*/
public abstract class ImageSource
{
private String name;
@XStreamOmitField
private int startFrame;
@XStreamOmitField
private int endFrame;
protected int width;
protected int height;
protected int frames;
/**
* Create the image source
*
* @param name
* The name of the image source
*/
public ImageSource(String name)
{
setName(name);
}
/**
* Opens the source
*/
public boolean open()
{
startFrame = endFrame = 0;
return openSource();
}
/**
* Opens the source
*/
protected abstract boolean openSource();
/**
* Closes the source
*/
public abstract void close();
/**
* Get the width of the results. The frame returned by {@link #next()} will be equal to width * height.
*
* @return
*/
public int getWidth()
{
return width;
}
/**
* Get the height of the results. The frame returned by {@link #next()} will be equal to width * height.
*
* @return
*/
public int getHeight()
{
return height;
}
/**
* Get the number of frames that can be extracted from the image source with calls to {@link #next()}
*
* @return The total number of frames
*/
public int getFrames()
{
return frames;
}
/**
* Get the start frame number of the source returned by the last call to {@link #get(int)} or {@link #next()}.
* <p>
* This may be larger than the result returned by {@link #getFrames()} if the ImageSource is selecting a subset of
* the possible frames.
*
* @return The start frame number of the latest block of data
*/
public int getStartFrameNumber()
{
return startFrame;
}
/**
* Get the end frame number of the source returned by the last call to {@link #get(int)} or {@link #next()}.
* <p>
* This may be larger than the result returned by {@link #getFrames()} if the ImageSource is selecting a subset of
* the possible frames.
*
* @return The end frame number of the latest block of data
*/
public int getEndFrameNumber()
{
return endFrame;
}
/**
* Set the current frame number(s) of the source returned by the last call to {@link #get(int)} or {@link #next()}.
* <p>
* This should be called by subclasses that perform more complex frame manipulation than just getting a single
* frame.
*
* @param startFrame
* the start frame of the current block of data
* @param endFrame
* the end frame of the current block of data
*/
protected void setFrameNumber(int startFrame, int endFrame)
{
this.startFrame = startFrame;
this.endFrame = endFrame;
}
/**
* Get the next frame. Return null if the frame is not available and set the current frame to
* zero. The data is is packed in yx order: index = y * width + x;
* <p>
* Provides serial access to the data after a successful call to {@link #openSource()}
*
* @return the next frame (or null if at the end)
*/
public float[] next()
{
return next(null);
}
/**
* Get the next frame. Return null if the frame is not available and set the current frame to
* zero. The data is is packed in yx order: index = y * width + x;
* <p>
* Provides serial access to the data after a successful call to {@link #openSource()}
* <p>
* Note: bounds.x + bounds.width must be less or equal to than {@link #getWidth()}, similarly for height.
*
* @param bounds
* The bounding limits of the frame to extract
* @return the next frame (or null if at the end)
*/
public float[] next(Rectangle bounds)
{
startFrame = endFrame = (startFrame + 1);
float[] data = nextFrame(bounds);
if (data == null)
startFrame = endFrame = 0;
return data;
}
/**
* Get the next frame. The data is is packed in yx order: index = y * width + x;
* <p>
* Must be implemented by sub-classes.
*
* @param bounds
* The bounding limits of the frame to extract
* @return the next frame (or null if at the end)
*/
protected abstract float[] nextFrame(Rectangle bounds);
/**
* Get a specific frame from the results. Return null if the frame is not available and set the current frame to
* zero.
* <p>
* Provides random access to the data after a successful call to {@link #openSource()}. This operation may be
* significantly slower than using {@link #next()} to read all the data.
*
* @param frame
* @return the frame (or null)
*/
public float[] get(int frame)
{
return get(frame, null);
}
/**
* Get a specific frame from the results. Return null if the frame is not available and set the current frame to
* zero.
* <p>
* Provides random access to the data after a successful call to {@link #openSource()}. This operation may be
* significantly slower than using {@link #next()} to read all the data.
* <p>
* Note: bounds.x + bounds.width must be less or equal to than {@link #getWidth()}, similarly for height.
*
* @param frame
* @param bounds
* The bounding limits of the frame to extract
* @return the frame (or null)
*/
public float[] get(int frame, Rectangle bounds)
{
startFrame = endFrame = frame;
float[] data = getFrame(frame, bounds);
if (data == null)
startFrame = endFrame = 0;
return data;
}
/**
* Get a specific frame from the results. Return null if the frame is not available.
* <p>
* Must be implemented by sub-classes.
*
* @param frame
* @param bounds
* @return The frame data
*/
protected abstract float[] getFrame(int frame, Rectangle bounds);
/**
* Get the name of the results source
*
* @return
*/
public String getName()
{
return name;
}
/**
* Get the parent source upon which this source is based. The default is to return null.
*
* @return The parent source
*/
public ImageSource getParent()
{
return null;
}
/**
* Get the original source for the data provided. The default is to return this object.
*
* @return The original source
*/
public ImageSource getOriginal()
{
return this;
}
/**
* Set the name of the results source
*
* @param name
*/
public void setName(String name)
{
if (name != null && name.length() > 0)
this.name = name;
else
this.name = "";
}
/**
* Return true if the frame is within the limits of the image source.
* <p>
* Note that the {@link #get(int)} method may still return null. This method can be used to determine if the
* {@link #get(int)} method has skipped data, e.g. if interlaced, or if the data has actually ended.
*
* @param frame
* @return true if valid
*/
public abstract boolean isValid(int frame);
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
// Over-ride this to produce a nicer output description of the results source
return String.format("%s [%dx%dx%d]", name, width, height, frames);
}
/**
* @return An XML representation of this object
*/
public String toXML()
{
XStream xs = new XStream(new DomDriver());
try
{
xs.autodetectAnnotations(true);
return xs.toXML(this);
}
catch (XStreamException ex)
{
//ex.printStackTrace();
}
return "";
}
public static ImageSource fromXML(String xml)
{
XStream xs = new XStream(new DomDriver());
try
{
xs.autodetectAnnotations(true);
return (ImageSource) xs.fromXML(xml);
}
catch (ClassCastException ex)
{
ex.printStackTrace();
}
catch (XStreamException ex)
{
ex.printStackTrace();
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* Check if the bounds fit inside the image.
*
* @param bounds
* @throws RuntimeException
* if the bounds do not fit in the image
* @return True if the bounds are not null and are within the image, false if null or the bounds fit the image
* exactly
*/
public boolean checkBounds(Rectangle bounds)
{
return checkBounds(width, height, bounds);
}
/**
* Check if the bounds fit inside the image.
*
* @param width
* @param height
* @param bounds
* @throws RuntimeException
* if the bounds do not fit in the image
* @return True if the bounds are not null and are within the image, false if null or the bounds fit the image
* exactly
*/
public static boolean checkBounds(int width, int height, Rectangle bounds)
{
if (bounds != null)
{
final int maxx = bounds.x + bounds.width;
final int maxy = bounds.y + bounds.height;
if (bounds.x < 0 || maxx > width || bounds.y < 0 || maxy > height)
throw new RuntimeException("The bounds do not fit within the image");
return bounds.x != 0 || bounds.y != 0 || bounds.width != width || bounds.height != height;
}
return false;
}
}