/*
* 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.sequence;
import icy.common.listener.ProgressListener;
import icy.image.IcyBufferedImage;
import icy.image.IcyBufferedImageUtil;
import icy.image.IcyBufferedImageUtil.FilterType;
import icy.image.colormap.IcyColorMap;
import icy.image.colormap.LinearColorMap;
import icy.image.lut.LUT;
import icy.math.Scaler;
import icy.painter.Overlay;
import icy.roi.BooleanMask2D;
import icy.roi.ROI;
import icy.type.DataType;
import icy.type.collection.array.Array1DUtil;
import icy.type.rectangle.Rectangle5D;
import icy.util.OMEUtil;
import icy.util.StringUtil;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.swing.SwingConstants;
import loci.formats.ome.OMEXMLMetadataImpl;
/**
* {@link Sequence} utilities class.<br>
* You can find here tools to manipulate the sequence organization, its data type, its size...
*
* @author Stephane
*/
public class SequenceUtil
{
public static class AddZHelper
{
public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition,
int numInsert, int copyLast)
{
if (z < insertPosition)
return sequence.getImage(t, z);
final int pos = z - insertPosition;
// return new image
if (pos < numInsert)
{
// return copy of previous image(s)
if ((insertPosition > 0) && (copyLast > 0))
{
// should be < insert position
final int duplicate = Math.min(insertPosition, copyLast);
final int baseReplicate = insertPosition - duplicate;
return sequence.getImage(t, baseReplicate + (pos % duplicate));
}
// return new empty image
return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
sequence.getDataType_());
}
return sequence.getImage(t, z - numInsert);
}
}
public static class AddTHelper
{
public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition,
int numInsert, int copyLast)
{
if (t < insertPosition)
return sequence.getImage(t, z);
final int pos = t - insertPosition;
// return new image
if (pos < numInsert)
{
// return copy of previous image(s)
if ((insertPosition > 0) && (copyLast > 0))
{
// should be < insert position
final int duplicate = Math.min(insertPosition, copyLast);
final int baseReplicate = insertPosition - duplicate;
return sequence.getImage(baseReplicate + (pos % duplicate), z);
}
// return new empty image
return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
sequence.getDataType_());
}
return sequence.getImage(t - numInsert, z);
}
}
public static class MergeCHelper
{
private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, int c,
boolean fillEmpty)
{
IcyBufferedImage img = seq.getImage(t, z, c);
if ((img == null) && fillEmpty)
{
int curZ = z;
// missing Z slice ?
if (z >= seq.getSizeZ())
{
// searching in previous slice
while ((img == null) && (curZ > 0))
img = seq.getImage(t, --curZ, c);
}
if (img == null)
{
int curT = t;
// searching in previous frame
while ((img == null) && (curT > 0))
img = seq.getImage(--curT, z, c);
}
return img;
}
return img;
}
public static IcyBufferedImage getImage(Sequence[] sequences, int[] channels, int sizeX, int sizeY, int t,
int z, boolean fillEmpty, boolean rescale) throws IllegalArgumentException
{
if (sequences.length == 0)
return null;
final List<BufferedImage> images = new ArrayList<BufferedImage>();
for (int i = 0; i < sequences.length; i++)
{
final Sequence seq = sequences[i];
final int c = channels[i];
IcyBufferedImage img = getImageFromSequenceInternal(seq, t, z, c, fillEmpty);
// create an empty image
if (img == null)
img = new IcyBufferedImage(sizeX, sizeY, 1, seq.getDataType_());
// resize X and Y dimension if needed
else if ((img.getSizeX() != sizeX) || (img.getSizeY() != sizeY))
img = IcyBufferedImageUtil.scale(img, sizeX, sizeY, rescale, SwingConstants.CENTER,
SwingConstants.CENTER, FilterType.BILINEAR);
images.add(img);
}
return IcyBufferedImage.createFrom(images);
}
}
public static class MergeZHelper
{
private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty)
{
IcyBufferedImage img = seq.getImage(t, z);
if ((img == null) && fillEmpty)
{
int curZ = z;
// missing Z slice ?
if (z >= seq.getSizeZ())
{
// searching in previous slice
while ((img == null) && (curZ > 0))
img = seq.getImage(t, --curZ);
}
if (img == null)
{
int curT = t;
// searching in previous frame
while ((img == null) && (curT > 0))
img = seq.getImage(--curT, z);
}
return img;
}
return img;
}
private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced,
boolean fillEmpty)
{
int zRemaining = z;
if (interlaced)
{
int zInd = 0;
while (zRemaining >= 0)
{
for (Sequence seq : sequences)
{
if (zInd < seq.getSizeZ())
{
if (zRemaining-- == 0)
return getImageFromSequenceInternal(seq, t, zInd, fillEmpty);
}
}
zInd++;
}
}
else
{
for (Sequence seq : sequences)
{
final int sizeZ = seq.getSizeZ();
// we found the sequence
if (zRemaining < sizeZ)
return getImageFromSequenceInternal(seq, t, zRemaining, fillEmpty);
zRemaining -= sizeZ;
}
}
return null;
}
public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z,
boolean interlaced, boolean fillEmpty, boolean rescale)
{
IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty);
if (result != null)
{
// resize X and Y dimension if needed
if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY))
result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER,
SwingConstants.CENTER, FilterType.BILINEAR);
final int imgSizeC = result.getSizeC();
// resize C dimension if needed
if (imgSizeC < sizeC)
return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC);
}
return result;
}
}
public static class MergeTHelper
{
private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty)
{
IcyBufferedImage img = seq.getImage(t, z);
if ((img == null) && fillEmpty)
{
int curT = t;
// missing T frame?
if (t >= seq.getSizeT())
{
// searching in previous frame
while ((img == null) && (curT > 0))
img = seq.getImage(--curT, z);
}
if (img == null)
{
int curZ = z;
// searching in previous slice
while ((img == null) && (curZ > 0))
img = seq.getImage(t, --curZ);
}
return img;
}
return img;
}
private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced,
boolean fillEmpty)
{
int tRemaining = t;
if (interlaced)
{
int tInd = 0;
while (tRemaining >= 0)
{
for (Sequence seq : sequences)
{
if (tInd < seq.getSizeT())
{
if (tRemaining-- == 0)
return getImageFromSequenceInternal(seq, tInd, z, fillEmpty);
}
}
tInd++;
}
}
else
{
for (Sequence seq : sequences)
{
final int sizeT = seq.getSizeT();
// we found the sequence
if (tRemaining < sizeT)
return getImageFromSequenceInternal(seq, tRemaining, z, fillEmpty);
tRemaining -= sizeT;
}
}
return null;
}
public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z,
boolean interlaced, boolean fillEmpty, boolean rescale)
{
IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty);
if (result != null)
{
// resize X and Y dimension if needed
if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY))
result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER,
SwingConstants.CENTER, FilterType.BILINEAR);
final int imgSizeC = result.getSizeC();
// resize C dimension if needed
if (imgSizeC < sizeC)
return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC);
}
return result;
}
}
public static class AdjustZTHelper
{
public static IcyBufferedImage getImage(Sequence sequence, int t, int z, int newSizeZ, int newSizeT,
boolean reverseOrder)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
// out of range
if ((t >= newSizeT) || (z >= newSizeZ))
return null;
final int index;
// calculate index of wanted image
if (reverseOrder)
index = (z * newSizeT) + t;
else
index = (t * newSizeZ) + z;
final int tOrigin = index / sizeZ;
final int zOrigin = index % sizeZ;
// bounding --> return new image
if (tOrigin >= sizeT)
return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
sequence.getDataType_());
return sequence.getImage(tOrigin, zOrigin);
}
}
/**
* Add one or severals frames at position t.
*
* @param t
* Position where to add frame(s)
* @param num
* Number of frame to add
* @param copyLast
* Number of last frame(s) to copy to fill added frames.<br>
* 0 means that new frames are empty.<br>
* 1 means we duplicate the last frame.<br>
* 2 means we duplicate the two last frames.<br>
* and so on...
*/
public static void addT(Sequence sequence, int t, int num, int copyLast)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
sequence.beginUpdate();
try
{
moveT(sequence, t, sizeT - 1, num);
for (int i = 0; i < num; i++)
for (int z = 0; z < sizeZ; z++)
sequence.setImage(t + i, z, IcyBufferedImageUtil.getCopy(AddTHelper.getExtendedImage(sequence, t
+ i, z, t, num, copyLast)));
}
finally
{
sequence.endUpdate();
}
}
/**
* Add one or severals frames at position t.
*
* @param t
* Position where to add frame(s)
* @param num
* Number of frame to add
*/
public static void addT(Sequence sequence, int t, int num)
{
addT(sequence, t, num, 0);
}
/**
* Add one or severals frames at position t.
*
* @param num
* Number of frame to add
*/
public static void addT(Sequence sequence, int num)
{
addT(sequence, sequence.getSizeT(), num, 0);
}
/**
* Add one or severals frames at position t.
*
* @param num
* Number of frame to add
* @param copyLast
* If true then the last frame is copied in added frames.
*/
public static void addT(Sequence sequence, int num, boolean copyLast)
{
addT(sequence, sequence.getSizeT(), num, 0);
}
/**
* Exchange 2 frames position on the sequence.
*/
public static void swapT(Sequence sequence, int t1, int t2)
{
final int sizeT = sequence.getSizeT();
if ((t1 < 0) || (t2 < 0) || (t1 >= sizeT) || (t2 >= sizeT))
return;
// get volume images at position t1 & t2
final VolumetricImage vi1 = sequence.getVolumetricImage(t1);
final VolumetricImage vi2 = sequence.getVolumetricImage(t2);
sequence.beginUpdate();
try
{
// start by removing old volume image (if any)
sequence.removeAllImages(t1);
sequence.removeAllImages(t2);
// safe volume image copy (TODO : check if we can't direct set volume image internally)
if (vi1 != null)
{
final Map<Integer, IcyBufferedImage> images = vi1.getImages();
// copy images of volume image 1 at position t2
for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
sequence.setImage(t2, entry.getKey().intValue(), entry.getValue());
}
if (vi2 != null)
{
final Map<Integer, IcyBufferedImage> images = vi2.getImages();
// copy images of volume image 2 at position t1
for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
sequence.setImage(t1, entry.getKey().intValue(), entry.getValue());
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Modify frame position.<br>
* The previous frame present at <code>newT</code> position is lost.
*
* @param sequence
* @param t
* current t position
* @param newT
* wanted t position
*/
public static void moveT(Sequence sequence, int t, int newT)
{
final int sizeT = sequence.getSizeT();
if ((t < 0) || (t >= sizeT) || (newT < 0) || (t == newT))
return;
// get volume image at position t
final VolumetricImage vi = sequence.getVolumetricImage(t);
sequence.beginUpdate();
try
{
// remove volume image (if any) at position newT
sequence.removeAllImages(newT);
if (vi != null)
{
final TreeMap<Integer, IcyBufferedImage> images = vi.getImages();
// copy images of volume image at position newT
for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
sequence.setImage(newT, entry.getKey().intValue(), entry.getValue());
// remove volume image at position t
sequence.removeAllImages(t);
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Modify T position of a range of frame by the specified offset
*
* @param sequence
* @param from
* start of range (t position)
* @param to
* end of range (t position)
* @param offset
* position shift
*/
public static void moveT(Sequence sequence, int from, int to, int offset)
{
sequence.beginUpdate();
try
{
if (offset > 0)
{
for (int t = to; t >= from; t--)
moveT(sequence, t, t + offset);
}
else
{
for (int t = from; t <= to; t++)
moveT(sequence, t, t + offset);
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Remove a frame at position t.
*
* @param sequence
* @param t
*/
public static void removeT(Sequence sequence, int t)
{
final int sizeT = sequence.getSizeT();
if ((t < 0) || (t >= sizeT))
return;
sequence.removeAllImages(t);
}
/**
* Remove a frame at position t and shift all the further t by -1.
*
* @param sequence
* @param t
*/
public static void removeTAndShift(Sequence sequence, int t)
{
final int sizeT = sequence.getSizeT();
if ((t < 0) || (t >= sizeT))
return;
sequence.beginUpdate();
try
{
removeT(sequence, t);
moveT(sequence, t + 1, sizeT - 1, -1);
}
finally
{
sequence.endUpdate();
}
}
/**
* Reverse T frames order.
*/
public static void reverseT(Sequence sequence)
{
final int sizeT = sequence.getSizeT();
final int sizeZ = sequence.getSizeZ();
final Sequence save = new Sequence();
save.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
save.setImage(t, z, sequence.getImage(t, z));
}
finally
{
save.endUpdate();
}
sequence.beginUpdate();
try
{
sequence.removeAllImages();
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
sequence.setImage(sizeT - (t + 1), z, save.getImage(t, z));
}
finally
{
sequence.endUpdate();
}
// to avoid memory leak as images now contained in sequence will retain 'save' sequence forever
save.removeAllImages();
}
/**
* Add one or severals slices at position z.
*
* @param z
* Position where to add slice(s)
* @param num
* Number of slice to add
* @param copyLast
* Number of last slice(s) to copy to fill added slices.<br>
* 0 means that new slices are empty.<br>
* 1 means we duplicate the last slice.<br>
* 2 means we duplicate the two last slices.<br>
* and so on...
*/
public static void addZ(Sequence sequence, int z, int num, int copyLast)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
sequence.beginUpdate();
try
{
moveZ(sequence, z, sizeZ - 1, num);
for (int i = 0; i < num; i++)
for (int t = 0; t < sizeT; t++)
sequence.setImage(t, z + i, IcyBufferedImageUtil.getCopy(AddZHelper.getExtendedImage(sequence, t, z
+ i, z, num, copyLast)));
}
finally
{
sequence.endUpdate();
}
}
/**
* Add one or severals slices at position z.
*
* @param z
* Position where to add slice(s)
* @param num
* Number of slice to add
*/
public static void addZ(Sequence sequence, int z, int num)
{
addZ(sequence, z, num, 0);
}
/**
* Add one or severals slices at position z.
*
* @param num
* Number of slice to add
*/
public static void addZ(Sequence sequence, int num)
{
addZ(sequence, sequence.getSizeZ(), num, 0);
}
/**
* Add one or severals slices at position z.
*
* @param num
* Number of slice to add
* @param copyLast
* If true then the last slice is copied in added slices.
*/
public static void addZ(Sequence sequence, int num, boolean copyLast)
{
addZ(sequence, sequence.getSizeZ(), num, 0);
}
/**
* Exchange 2 slices position on the sequence.
*/
public static void swapZ(Sequence sequence, int z1, int z2)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
if ((z1 < 0) || (z2 < 0) || (z1 >= sizeZ) || (z2 >= sizeZ))
return;
sequence.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
{
final IcyBufferedImage image1 = sequence.getImage(t, z1);
final IcyBufferedImage image2 = sequence.getImage(t, z2);
// set image at new position
if (image1 != null)
sequence.setImage(t, z2, image1);
else
sequence.removeImage(t, z2);
if (image2 != null)
sequence.setImage(t, z1, image2);
else
sequence.removeImage(t, z1);
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Modify slice position.<br>
* The previous slice present at <code>newZ</code> position is lost.
*
* @param sequence
* @param z
* current z position
* @param newZ
* wanted z position
*/
public static void moveZ(Sequence sequence, int z, int newZ)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
if ((z < 0) || (z >= sizeZ) || (newZ < 0) || (z == newZ))
return;
sequence.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
{
final IcyBufferedImage image = sequence.getImage(t, z);
if (image != null)
{
// set image at new position
sequence.setImage(t, newZ, image);
// and remove image at old position z
sequence.removeImage(t, z);
}
else
// just set null image at new position (equivalent to no image)
sequence.removeImage(t, newZ);
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Modify Z position of a range of slice by the specified offset
*
* @param sequence
* @param from
* start of range (z position)
* @param to
* end of range (z position)
* @param offset
* position shift
*/
public static void moveZ(Sequence sequence, int from, int to, int offset)
{
sequence.beginUpdate();
try
{
if (offset > 0)
{
for (int z = to; z >= from; z--)
moveZ(sequence, z, z + offset);
}
else
{
for (int z = from; z <= to; z++)
moveZ(sequence, z, z + offset);
}
}
finally
{
sequence.endUpdate();
}
}
/**
* Remove a slice at position Z.
*
* @param sequence
* @param z
*/
public static void removeZ(Sequence sequence, int z)
{
final int sizeZ = sequence.getSizeZ();
if ((z < 0) || (z >= sizeZ))
return;
sequence.beginUpdate();
try
{
final int maxT = sequence.getSizeT();
for (int t = 0; t < maxT; t++)
sequence.removeImage(t, z);
}
finally
{
sequence.endUpdate();
}
}
/**
* Remove a slice at position t and shift all the further t by -1.
*
* @param sequence
* @param z
*/
public static void removeZAndShift(Sequence sequence, int z)
{
final int sizeZ = sequence.getSizeZ();
if ((z < 0) || (z >= sizeZ))
return;
sequence.beginUpdate();
try
{
removeZ(sequence, z);
moveZ(sequence, z + 1, sizeZ - 1, -1);
}
finally
{
sequence.endUpdate();
}
}
/**
* Reverse Z slices order.
*/
public static void reverseZ(Sequence sequence)
{
final int sizeT = sequence.getSizeT();
final int sizeZ = sequence.getSizeZ();
final Sequence save = new Sequence();
save.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
save.setImage(t, z, sequence.getImage(t, z));
}
finally
{
save.endUpdate();
}
sequence.beginUpdate();
try
{
sequence.removeAllImages();
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
sequence.setImage(t, sizeZ - (z + 1), save.getImage(t, z));
}
finally
{
sequence.endUpdate();
}
// to avoid memory leak as images now contained in sequence will retain 'save' sequence forever
save.removeAllImages();
}
/**
* Set all images of the sequence in T dimension.
*/
public static void convertToTime(Sequence sequence)
{
sequence.beginUpdate();
try
{
final List<IcyBufferedImage> images = sequence.getAllImage();
sequence.removeAllImages();
for (int i = 0; i < images.size(); i++)
sequence.setImage(i, 0, images.get(i));
}
finally
{
sequence.endUpdate();
}
}
/**
* Set all images of the sequence in Z dimension.
*/
public static void convertToStack(Sequence sequence)
{
sequence.beginUpdate();
try
{
final List<IcyBufferedImage> images = sequence.getAllImage();
sequence.removeAllImages();
for (int i = 0; i < images.size(); i++)
sequence.setImage(0, i, images.get(i));
}
finally
{
sequence.endUpdate();
}
}
/**
* @deprecated Use {@link #convertToStack(Sequence)} instead.
*/
@Deprecated
public static void convertToVolume(Sequence sequence)
{
convertToStack(sequence);
}
/**
* Remove the specified channel from the source sequence.
*
* @param source
* Source sequence
* @param channel
* Channel index to remove
*/
public static void removeChannel(Sequence source, int channel)
{
final int sizeC = source.getSizeC();
if (channel >= sizeC)
return;
final int[] keep = new int[sizeC - 1];
int i = 0;
for (int c = 0; c < sizeC; c++)
if (c != channel)
keep[i++] = c;
final Sequence tmp = extractChannels(source, keep);
source.beginUpdate();
try
{
// we need to clear the source sequence to change its type
source.removeAllImages();
// get back all images
for (int t = 0; t < tmp.getSizeT(); t++)
for (int z = 0; z < tmp.getSizeZ(); z++)
source.setImage(t, z, tmp.getImage(t, z));
// get back modified metadata
source.setMetaData(tmp.getMetadata());
// and colormaps
for (int c = 0; c < tmp.getSizeC(); c++)
source.setDefaultColormap(c, tmp.getDefaultColorMap(c), false);
// to avoid memory leak as images now contained in source will retain this sequence forever
tmp.removeAllImages();
}
finally
{
source.endUpdate();
}
}
/**
* Returns the max size of specified dimension for the given sequences.
*/
public static int getMaxDim(Sequence[] sequences, DimensionId dim)
{
int result = 0;
for (Sequence seq : sequences)
{
switch (dim)
{
case X:
result = Math.max(result, seq.getSizeX());
break;
case Y:
result = Math.max(result, seq.getSizeY());
break;
case C:
result = Math.max(result, seq.getSizeC());
break;
case Z:
result = Math.max(result, seq.getSizeZ());
break;
case T:
result = Math.max(result, seq.getSizeT());
break;
}
}
return result;
}
/**
* Create and returns a new sequence by concatenating all given sequences on C dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param channels
* Selected channel for each sequence (<code>channels.length = sequences.length</code>)<br>
* If you want to select 2 or more channels from a sequence, just duplicate the sequence
* entry in the <code>sequences</code> parameter :</code><br>
* <code>sequences[n] = Sequence1; channels[n] = 0;</code><br>
* <code>sequences[n+1] = Sequence1; channels[n+1] = 2;</code><br>
* <code>...</code>
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
* @param pl
* ProgressListener to indicate processing progress.
* @throws IllegalArgumentException
* if sequences contains incompatible sequence for merge operation.
*/
public static Sequence concatC(Sequence[] sequences, int[] channels, boolean fillEmpty, boolean rescale,
ProgressListener pl) throws IllegalArgumentException
{
final int sizeX = getMaxDim(sequences, DimensionId.X);
final int sizeY = getMaxDim(sequences, DimensionId.Y);
final int sizeZ = getMaxDim(sequences, DimensionId.Z);
final int sizeT = getMaxDim(sequences, DimensionId.T);
final Sequence result = new Sequence();
if (sequences.length > 0)
result.setMetaData(OMEUtil.createOMEMetadata(sequences[0].getMetadata()));
result.setName("C Merge");
int ind = 0;
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
if (pl != null)
pl.notifyProgress(ind, sizeT * sizeZ);
result.setImage(t, z,
MergeCHelper.getImage(sequences, channels, sizeX, sizeY, t, z, fillEmpty, rescale));
ind++;
}
}
int c = 0;
for (Sequence seq : sequences)
{
for (int sc = 0; sc < seq.getSizeC(); sc++, c++)
{
final String channelName = seq.getChannelName(sc);
final IcyColorMap channelColor = seq.getColorMap(sc);
// not default channel name --> we keep it
if (!StringUtil.equals(seq.getDefaultChannelName(sc), channelName))
result.setChannelName(c, channelName);
// not default white color map --> we keep it
if (!channelColor.equals(LinearColorMap.white_))
result.setColormap(c, channelColor, true);
}
}
return result;
}
/**
* Create and returns a new sequence by concatenating all given sequences on C dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
* @param pl
* ProgressListener to indicate processing progress.
*/
public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale, ProgressListener pl)
{
final int channels[] = new int[sequences.length];
Arrays.fill(channels, -1);
return concatC(sequences, channels, fillEmpty, rescale, pl);
}
/**
* Create and returns a new sequence by concatenating all given sequences on C dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
*/
public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale)
{
return concatC(sequences, fillEmpty, rescale, null);
}
/**
* Create and returns a new sequence by concatenating all given sequences on C dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
*/
public static Sequence concatC(Sequence[] sequences)
{
return concatC(sequences, true, false, null);
}
/**
* Create and returns a new sequence by concatenating all given sequences on Z dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param interlaced
* Interlace images.<br>
* normal : 1,1,1,2,2,2,3,3,..<br>
* interlaced : 1,2,3,1,2,3,..<br>
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
* @param pl
* ProgressListener to indicate processing progress.
*/
public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale,
ProgressListener pl)
{
final int sizeX = getMaxDim(sequences, DimensionId.X);
final int sizeY = getMaxDim(sequences, DimensionId.Y);
final int sizeC = getMaxDim(sequences, DimensionId.C);
final int sizeT = getMaxDim(sequences, DimensionId.T);
int sizeZ = 0;
for (Sequence seq : sequences)
sizeZ += seq.getSizeZ();
final Sequence result = new Sequence();
if (sequences.length > 0)
result.setMetaData(OMEUtil.createOMEMetadata(sequences[0].getMetadata()));
result.setName("Z Merge");
int ind = 0;
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
if (pl != null)
pl.notifyProgress(ind, sizeT * sizeZ);
result.setImage(t, z, IcyBufferedImageUtil.getCopy(MergeZHelper.getImage(sequences, sizeX, sizeY,
sizeC, t, z, interlaced, fillEmpty, rescale)));
ind++;
}
}
return result;
}
/**
* Create and returns a new sequence by concatenating all given sequences on Z dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param interlaced
* Interlace images.<br>
* normal : 1,1,1,2,2,2,3,3,..<br>
* interlaced : 1,2,3,1,2,3,..<br>
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
*/
public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale)
{
return concatZ(sequences, interlaced, fillEmpty, rescale, null);
}
/**
* Create and returns a new sequence by concatenating all given sequences on Z dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
*/
public static Sequence concatZ(Sequence[] sequences)
{
return concatZ(sequences, false, true, false, null);
}
/**
* Create and returns a new sequence by concatenating all given sequences on T dimension.
*
* @param sequences
* sequences to concatenate (use array order).
* @param interlaced
* interlace images.<br>
* normal : 1,1,1,2,2,2,3,3,..<br>
* interlaced : 1,2,3,1,2,3,..<br>
* @param fillEmpty
* replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
* @param pl
* ProgressListener to indicate processing progress.
*/
public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale,
ProgressListener pl)
{
final int sizeX = getMaxDim(sequences, DimensionId.X);
final int sizeY = getMaxDim(sequences, DimensionId.Y);
final int sizeC = getMaxDim(sequences, DimensionId.C);
final int sizeZ = getMaxDim(sequences, DimensionId.Z);
int sizeT = 0;
for (Sequence seq : sequences)
sizeT += seq.getSizeT();
final Sequence result = new Sequence();
if (sequences.length > 0)
result.setMetaData(OMEUtil.createOMEMetadata(sequences[0].getMetadata()));
result.setName("T Merge");
int ind = 0;
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
if (pl != null)
pl.notifyProgress(ind, sizeT * sizeZ);
result.setImage(t, z, IcyBufferedImageUtil.getCopy(MergeTHelper.getImage(sequences, sizeX, sizeY,
sizeC, t, z, interlaced, fillEmpty, rescale)));
ind++;
}
}
return result;
}
/**
* Create and returns a new sequence by concatenating all given sequences on T dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
* @param interlaced
* Interlace images.<br>
* normal : 1,1,1,2,2,2,3,3,..<br>
* interlaced : 1,2,3,1,2,3,..<br>
* @param fillEmpty
* Replace empty image by the previous non empty one.
* @param rescale
* Images are scaled to all fit in the same XY dimension.
*/
public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale)
{
return concatT(sequences, interlaced, fillEmpty, rescale, null);
}
/**
* Create and returns a new sequence by concatenating all given sequences on T dimension.
*
* @param sequences
* Sequences to concatenate (use array order).
*/
public static Sequence concatT(Sequence[] sequences)
{
return concatT(sequences, false, true, false, null);
}
/**
* Adjust Z and T dimension of the sequence.
*
* @param reverseOrder
* Means that images are T-Z ordered instead of Z-T ordered
* @param newSizeZ
* New Z size of the sequence
* @param newSizeT
* New T size of the sequence
*/
public static void adjustZT(Sequence sequence, int newSizeZ, int newSizeT, boolean reverseOrder)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
final Sequence tmp = new Sequence();
tmp.beginUpdate();
sequence.beginUpdate();
try
{
try
{
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
tmp.setImage(t, z, sequence.getImage(t, z));
sequence.removeImage(t, z);
}
}
}
finally
{
tmp.endUpdate();
}
for (int t = 0; t < newSizeT; t++)
for (int z = 0; z < newSizeZ; z++)
sequence.setImage(t, z, AdjustZTHelper.getImage(tmp, t, z, newSizeZ, newSizeT, reverseOrder));
// to avoid memory leak as images now contained in sequence will 'tmp' sequence forever
tmp.removeAllImages();
}
finally
{
sequence.endUpdate();
}
}
/**
* Build a new single channel sequence (grey) from the specified channel of the source sequence.
*
* @param source
* Source sequence
* @param channel
* Channel index to extract from the source sequence.
* @return Sequence
*/
public static Sequence extractChannel(Sequence source, int channel)
{
return extractChannels(source, channel);
}
/**
* @deprecated Use {@link #extractChannels(Sequence, int...)} instead.
*/
@Deprecated
public static Sequence extractChannels(Sequence source, List<Integer> channels)
{
final Sequence outSequence = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
outSequence.beginUpdate();
try
{
for (int t = 0; t < source.getSizeT(); t++)
for (int z = 0; z < source.getSizeZ(); z++)
outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels));
}
finally
{
outSequence.endUpdate();
}
// sequence name
if (channels.size() > 1)
{
String s = "";
for (int i = 0; i < channels.size(); i++)
s += " " + channels.get(i).toString();
outSequence.setName(source.getName() + " (channels" + s + ")");
}
else if (channels.size() == 1)
outSequence.setName(source.getName() + " (" + source.getChannelName(channels.get(0).intValue()) + ")");
// channel name
int c = 0;
for (Integer i : channels)
{
outSequence.setChannelName(c, source.getChannelName(i.intValue()));
c++;
}
return outSequence;
}
/**
* Build a new sequence by extracting the specified channels from the source sequence.
*
* @param source
* Source sequence
* @param channels
* Channel indexes to extract from the source sequence.
* @return Sequence
*/
public static Sequence extractChannels(Sequence source, int... channels)
{
final Sequence outSequence = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
final int sizeT = source.getSizeT();
final int sizeZ = source.getSizeZ();
final int sizeC = source.getSizeC();
outSequence.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels));
}
finally
{
outSequence.endUpdate();
}
final OMEXMLMetadataImpl metadata = outSequence.getMetadata();
// remove channel metadata
for (int ch = MetaDataUtil.getNumChannel(metadata, 0) - 1; ch >= 0; ch--)
{
boolean remove = true;
for (int i : channels)
{
if (i == ch)
{
remove = false;
break;
}
}
if (remove)
MetaDataUtil.removeChannel(metadata, 0, ch);
}
// sequence name
if (channels.length > 1)
{
String s = "";
for (int i = 0; i < channels.length; i++)
s += " " + channels[i];
outSequence.setName(source.getName() + " (channels" + s + ")");
}
else if (channels.length == 1)
outSequence.setName(source.getName() + " (" + source.getChannelName(channels[0]) + ")");
// copy channel name and colormap
int c = 0;
for (int channel : channels)
{
if (channel < sizeC)
{
outSequence.setChannelName(c, source.getChannelName(channel));
outSequence.setDefaultColormap(c, source.getDefaultColorMap(channel), false);
}
c++;
}
return outSequence;
}
/**
* Build a new sequence by extracting the specified Z slice from the source sequence.
*
* @param source
* Source sequence
* @param z
* Slice index to extract from the source sequence.
* @return Sequence
*/
public static Sequence extractSlice(Sequence source, int z)
{
final OMEXMLMetadataImpl metadata = OMEUtil.createOMEMetadata(source.getMetadata());
final Sequence outSequence = new Sequence(metadata);
// keep only metadata for specified slice
MetaDataUtil.keepPlanes(metadata, 0, -1, z, -1);
outSequence.beginUpdate();
try
{
for (int t = 0; t < source.getSizeT(); t++)
outSequence.setImage(t, 0, source.getImage(t, z));
}
finally
{
outSequence.endUpdate();
}
outSequence.setName(source.getName() + " (slice " + z + ")");
return outSequence;
}
/**
* Build a new sequence by extracting the specified T frame from the source sequence.
*
* @param source
* Source sequence
* @param t
* Frame index to extract from the source sequence.
* @return Sequence
*/
public static Sequence extractFrame(Sequence source, int t)
{
final OMEXMLMetadataImpl metadata = OMEUtil.createOMEMetadata(source.getMetadata());
final Sequence outSequence = new Sequence(metadata);
// keep only metadata for specified frame
MetaDataUtil.keepPlanes(metadata, 0, t, -1, -1);
outSequence.beginUpdate();
try
{
for (int z = 0; z < source.getSizeZ(); z++)
outSequence.setImage(0, z, source.getImage(t, z));
}
finally
{
outSequence.endUpdate();
}
outSequence.setName(source.getName() + " (frame " + t + ")");
return outSequence;
}
/**
* Converts the source sequence to the specified data type.<br>
* This method returns a new sequence (the source sequence is not modified).
*
* @param source
* Source sequence to convert
* @param dataType
* Data type wanted
* @param rescale
* Indicate if we want to scale data value according to data (or data type) range
* @param useDataBounds
* Only used when <code>rescale</code> parameter is true.<br>
* Specify if we use the data bounds for rescaling instead of data type bounds.
* @return converted sequence
*/
public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale, boolean useDataBounds)
{
if (source == null)
return null;
if (!rescale)
return convertType(source, dataType, null);
// convert with rescale
final double boundsDst[] = dataType.getDefaultBounds();
final int sizeC = source.getSizeC();
final Scaler[] scalers = new Scaler[sizeC];
// build scalers
for (int c = 0; c < sizeC; c++)
{
final double boundsSrc[];
if (useDataBounds)
boundsSrc = source.getChannelBounds(c);
else
boundsSrc = source.getChannelTypeBounds(c);
scalers[c] = new Scaler(boundsSrc[0], boundsSrc[1], boundsDst[0], boundsDst[1], false);
}
// use scaler to scale data
return convertType(source, dataType, scalers);
}
/**
* Converts the source sequence to the specified data type.<br>
* This method returns a new sequence (the source sequence is not modified).
*
* @param source
* Source sequence to convert
* @param dataType
* data type wanted
* @param rescale
* indicate if we want to scale data value according to data type range
* @return converted sequence
*/
public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale)
{
return convertToType(source, dataType, rescale, false);
}
/**
* @deprecated Use {@link #convertType(Sequence, DataType, Scaler[])} instead.
*/
@Deprecated
public static Sequence convertToType(Sequence source, DataType dataType, Scaler scaler)
{
final Sequence output = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
output.beginUpdate();
try
{
for (int t = 0; t < source.getSizeT(); t++)
{
for (int z = 0; z < source.getSizeZ(); z++)
{
final IcyBufferedImage converted = IcyBufferedImageUtil.convertToType(source.getImage(t, z),
dataType, scaler);
// FIXME : why we did that ??
// this is not a good idea to force bounds when rescale = false
// set bounds manually for the converted image
// for (int c = 0; c < getSizeC(); c++)
// {
// converted.setComponentBounds(c, boundsDst);
// converted.setComponentUserBounds(c, boundsDst);
// }
output.setImage(t, z, converted);
}
}
output.setName(source.getName() + " (" + output.getDataType_() + ")");
}
finally
{
output.endUpdate();
}
return output;
}
/**
* Converts the source sequence to the specified data type.<br>
* This method returns a new sequence (the source sequence is not modified).
*
* @param source
* Source sequence to convert
* @param dataType
* data type wanted.
* @param scalers
* scalers for scaling internal data during conversion (1 scaler per channel).<br>
* Can be set to <code>null</code> to avoid value conversion.
* @return converted image
*/
public static Sequence convertType(Sequence source, DataType dataType, Scaler[] scalers)
{
final Sequence output = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
output.beginUpdate();
try
{
for (int t = 0; t < source.getSizeT(); t++)
{
for (int z = 0; z < source.getSizeZ(); z++)
{
final IcyBufferedImage converted = IcyBufferedImageUtil.convertType(source.getImage(t, z),
dataType, scalers);
// FIXME : why we did that ??
// this is not a good idea to force bounds when rescale = false
// set bounds manually for the converted image
// for (int c = 0; c < getSizeC(); c++)
// {
// converted.setComponentBounds(c, boundsDst);
// converted.setComponentUserBounds(c, boundsDst);
// }
output.setImage(t, z, converted);
}
}
// preserve channel informations
for (int c = 0; c < source.getSizeC(); c++)
{
output.setChannelName(c, source.getChannelName(c));
output.setDefaultColormap(c, source.getDefaultColorMap(c), true);
output.setColormap(c, source.getColorMap(c));
}
output.setName(source.getName() + " (" + output.getDataType_() + ")");
}
finally
{
output.endUpdate();
}
return output;
}
/**
* Return a rotated version of the source sequence with specified parameters.
*
* @param source
* source image
* @param xOrigin
* X origin for the rotation
* @param yOrigin
* Y origin for the rotation
* @param angle
* rotation angle in radian
* @param filterType
* filter resampling method used
*/
public static Sequence rotate(Sequence source, double xOrigin, double yOrigin, double angle, FilterType filterType)
{
final int sizeT = source.getSizeT();
final int sizeZ = source.getSizeZ();
final Sequence result = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
result.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
result.setImage(t, z,
IcyBufferedImageUtil.rotate(source.getImage(t, z), xOrigin, yOrigin, angle, filterType));
}
finally
{
result.endUpdate();
}
// preserve channel informations
for (int c = 0; c < source.getSizeC(); c++)
{
result.setChannelName(c, source.getChannelName(c));
result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
result.setColormap(c, source.getColorMap(c));
}
result.setName(source.getName() + " (rotated)");
return result;
}
/**
* Return a rotated version of the source Sequence with specified parameters.
*
* @param source
* source image
* @param angle
* rotation angle in radian
* @param filterType
* filter resampling method used
*/
public static Sequence rotate(Sequence source, double angle, FilterType filterType)
{
if (source == null)
return null;
return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, filterType);
}
/**
* Return a rotated version of the source Sequence with specified parameters.
*
* @param source
* source image
* @param angle
* rotation angle in radian
*/
public static Sequence rotate(Sequence source, double angle)
{
if (source == null)
return null;
return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, FilterType.BILINEAR);
}
/**
* Return a copy of the source sequence with specified size, alignment rules and filter type.
*
* @param source
* source sequence
* @param resizeContent
* indicate if content should be resized or not (empty area are 0 filled)
* @param xAlign
* horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
* (used only if resizeContent is false)
* @param yAlign
* vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
* (used only if resizeContent is false)
* @param filterType
* filter method used for scale (used only if resizeContent is true)
*/
public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign,
FilterType filterType)
{
final int sizeT = source.getSizeT();
final int sizeZ = source.getSizeZ();
final Sequence result = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
result.beginUpdate();
try
{
for (int t = 0; t < sizeT; t++)
for (int z = 0; z < sizeZ; z++)
result.setImage(t, z, IcyBufferedImageUtil.scale(source.getImage(t, z), width, height,
resizeContent, xAlign, yAlign, filterType));
}
finally
{
result.endUpdate();
}
// preserve channel informations
for (int c = 0; c < source.getSizeC(); c++)
{
result.setChannelName(c, source.getChannelName(c));
result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
result.setColormap(c, source.getColorMap(c));
}
result.setName(source.getName() + " (resized)");
// content was resized ?
if (resizeContent)
{
final double sx = (double) source.getSizeX() / result.getSizeX();
final double sy = (double) source.getSizeY() / result.getSizeY();
// update pixel size
if ((sx != 0d) && !Double.isInfinite(sx))
result.setPixelSizeX(result.getPixelSizeX() * sx);
if ((sy != 0d) && !Double.isInfinite(sy))
result.setPixelSizeY(result.getPixelSizeY() * sy);
}
return result;
}
/**
* Return a copy of the sequence with specified size.<br>
* By default the FilterType.BILINEAR is used as filter method if resizeContent is true
*
* @param source
* source sequence
* @param resizeContent
* indicate if content should be resized or not (empty area are 0 filled)
* @param xAlign
* horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
* (used only if resizeContent is false)
* @param yAlign
* vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
* (used only if resizeContent is false)
*/
public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign)
{
return scale(source, width, height, resizeContent, xAlign, yAlign, FilterType.BILINEAR);
}
/**
* Return a copy of the sequence with specified size.
*
* @param source
* source sequence
* @param filterType
* filter method used for scale (used only if resizeContent is true)
*/
public static Sequence scale(Sequence source, int width, int height, FilterType filterType)
{
return scale(source, width, height, true, 0, 0, filterType);
}
/**
* Return a copy of the sequence with specified size.<br>
* By default the FilterType.BILINEAR is used as filter method.
*/
public static Sequence scale(Sequence source, int width, int height)
{
return scale(source, width, height, FilterType.BILINEAR);
}
/**
* Creates a new sequence from the specified region of the source sequence.
*/
public static Sequence getSubSequence(Sequence source, Rectangle5D.Integer region)
{
final Sequence result = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
final Rectangle region2d = region.toRectangle2D().getBounds();
final int startZ;
final int endZ;
final int startT;
final int endT;
final int startC;
final int endC;
if (region.isInfiniteZ())
{
startZ = 0;
endZ = source.getSizeZ();
}
else
{
startZ = Math.max(0, region.z);
endZ = Math.min(source.getSizeZ(), region.z + region.sizeZ);
}
if (region.isInfiniteT())
{
startT = 0;
endT = source.getSizeT();
}
else
{
startT = Math.max(0, region.t);
endT = Math.min(source.getSizeT(), region.t + region.sizeT);
}
if (region.isInfiniteC())
{
startC = 0;
endC = source.getSizeC();
}
else
{
startC = Math.max(0, region.c);
endC = Math.min(source.getSizeC(), region.c + region.sizeC);
}
result.beginUpdate();
try
{
for (int t = startT; t < endT; t++)
{
for (int z = startZ; z < endZ; z++)
{
IcyBufferedImage img = source.getImage(t, z);
if (img != null)
img = IcyBufferedImageUtil.getSubImage(img, region2d, startC, (endC - startC) + 1);
result.setImage(t - startT, z - startZ, img);
}
}
}
finally
{
result.endUpdate();
}
// preserve channel informations
for (int c = startC; c < endC; c++)
{
result.setChannelName(c - startC, source.getChannelName(c));
result.setDefaultColormap(c - startC, source.getDefaultColorMap(c), true);
result.setColormap(c - startC, source.getColorMap(c));
}
result.setName(source.getName() + " (crop)");
// adjust position X, Y, Z
result.setPositionX(source.getPositionX() + (region2d.x * source.getPixelSizeX()));
result.setPositionY(source.getPositionY() + (region2d.y * source.getPixelSizeY()));
result.setPositionZ(source.getPositionZ() + (startZ * source.getPixelSizeZ()));
return result;
}
/**
* @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead.
*/
@Deprecated
public static Sequence getSubSequence(Sequence source, int startX, int startY, int startC, int startZ, int startT,
int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT)
{
return getSubSequence(source, new Rectangle5D.Integer(startX, startY, startZ, startT, startC, sizeX, sizeY,
sizeZ, sizeT, sizeC));
}
/**
* @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead.
*/
@Deprecated
public static Sequence getSubSequence(Sequence source, int startX, int startY, int startZ, int startT, int sizeX,
int sizeY, int sizeZ, int sizeT)
{
return getSubSequence(source, startX, startY, 0, startZ, startT, sizeX, sizeY, source.getSizeC(), sizeZ, sizeT);
}
/**
* Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI} bounds.<br>
*
* @param source
* the source sequence
* @param roi
* used to define to region to retain.
* @param nullValue
* the returned sequence is created by using the ROI rectangular bounds.<br>
* if <code>nullValue</code> is different of <code>Double.NaN</code> then any pixel
* outside the ROI region will be set to <code>nullValue</code>
*/
public static Sequence getSubSequence(Sequence source, ROI roi, double nullValue)
{
final Rectangle5D.Integer bounds = roi.getBounds5D().toInteger();
final Sequence result = getSubSequence(source, bounds);
// use null value ?
if (!Double.isNaN(nullValue))
{
final int offX = (bounds.x == Integer.MIN_VALUE) ? 0 : (int) bounds.x;
final int offY = (bounds.y == Integer.MIN_VALUE) ? 0 : (int) bounds.y;
final int offZ = (bounds.z == Integer.MIN_VALUE) ? 0 : (int) bounds.z;
final int offT = (bounds.t == Integer.MIN_VALUE) ? 0 : (int) bounds.t;
final int offC = (bounds.c == Integer.MIN_VALUE) ? 0 : (int) bounds.c;
final int sizeX = result.getSizeX();
final int sizeY = result.getSizeY();
final int sizeZ = result.getSizeZ();
final int sizeT = result.getSizeT();
final int sizeC = result.getSizeC();
final DataType dataType = result.getDataType_();
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
for (int c = 0; c < sizeC; c++)
{
final BooleanMask2D mask = roi.getBooleanMask2D(z + offZ, t + offT, c + offC, false);
final Object data = result.getDataXY(t, z, c);
int offset = 0;
for (int y = 0; y < sizeY; y++)
for (int x = 0; x < sizeX; x++, offset++)
if (!mask.contains(x + offX, y + offY))
Array1DUtil.setValue(data, offset, dataType, nullValue);
}
}
}
result.dataChanged();
}
return result;
}
/**
* Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI} bounds.
*/
public static Sequence getSubSequence(Sequence source, ROI roi)
{
return getSubSequence(source, roi, Double.NaN);
}
/**
* Creates and return a copy of the sequence.
*
* @param source
* the source sequence to copy
* @param copyROI
* Copy the ROI from source sequence.<br>
* Warning: by doing that the ROI will retain the result sequence as long the source sequence is alive.
* @param copyOverlay
* Copy the Overlay from source sequence.<br>
* Warning: by doing that the Overlay will retain the result sequence as long the source sequence is alive.
* @param nameSuffix
* add the suffix <i>" (copy)"</i> to the new Sequence name to distinguish it
*/
public static Sequence getCopy(Sequence source, boolean copyROI, boolean copyOverlay, boolean nameSuffix)
{
final Sequence result = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
result.beginUpdate();
try
{
result.copyDataFrom(source);
if (copyROI)
{
for (ROI roi : source.getROIs())
result.addROI(roi);
}
if (copyOverlay)
{
for (Overlay overlay : source.getOverlays())
result.addOverlay(overlay);
}
// preserve channel informations
for (int c = 0; c < source.getSizeC(); c++)
{
result.setChannelName(c, source.getChannelName(c));
result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
result.setColormap(c, source.getColorMap(c));
}
if (nameSuffix)
result.setName(source.getName() + " (copy)");
}
finally
{
result.endUpdate();
}
return result;
}
/**
* Creates and return a copy of the sequence.<br>
* Note that only data and metadata are copied, overlays and ROIs are not preserved.
*/
public static Sequence getCopy(Sequence source)
{
return getCopy(source, false, false, true);
}
/**
* Convert the specified sequence to gray sequence (single channel)
*/
public static Sequence toGray(Sequence source)
{
return convertColor(source, BufferedImage.TYPE_BYTE_GRAY, null);
}
/**
* Convert the specified sequence to RGB sequence (3 channels)
*/
public static Sequence toRGB(Sequence source)
{
return convertColor(source, BufferedImage.TYPE_INT_RGB, null);
}
/**
* Convert the specified sequence to ARGB sequence (4 channels)
*/
public static Sequence toARGB(Sequence source)
{
return convertColor(source, BufferedImage.TYPE_INT_ARGB, null);
}
/**
* Do color conversion of the specified {@link Sequence} into the specified type.<br>
* The resulting Sequence will have 4, 3 or 1 channel(s) depending the selected type.
*
* @param source
* source sequence
* @param imageType
* wanted image type, only the following is accepted :<br>
* BufferedImage.TYPE_INT_ARGB (4 channels)<br>
* BufferedImage.TYPE_INT_RGB (3 channels)<br>
* BufferedImage.TYPE_BYTE_GRAY (1 channel)<br>
* @param lut
* lut used for color calculation (source sequence lut is used if null)
*/
public static Sequence convertColor(Sequence source, int imageType, LUT lut)
{
final Sequence result = new Sequence(OMEUtil.createOMEMetadata(source.getMetadata()));
// image receiver
final BufferedImage imgOut = new BufferedImage(source.getSizeX(), source.getSizeY(), imageType);
result.beginUpdate();
try
{
for (int t = 0; t < source.getSizeT(); t++)
for (int z = 0; z < source.getSizeZ(); z++)
result.setImage(t, z, IcyBufferedImageUtil.toBufferedImage(source.getImage(t, z), imgOut, lut));
// rename channels and set final name
switch (imageType)
{
default:
case BufferedImage.TYPE_INT_ARGB:
result.setChannelName(0, "red");
result.setChannelName(1, "green");
result.setChannelName(2, "blue");
result.setChannelName(3, "alpha");
result.setName(source.getName() + " (ARGB rendering)");
break;
case BufferedImage.TYPE_INT_RGB:
result.setChannelName(0, "red");
result.setChannelName(1, "green");
result.setChannelName(2, "blue");
result.setName(source.getName() + " (RGB rendering)");
break;
case BufferedImage.TYPE_BYTE_GRAY:
result.setChannelName(0, "gray");
result.setName(source.getName() + " (gray rendering)");
break;
}
}
finally
{
result.endUpdate();
}
return result;
}
/**
* Convert the given Point2D coordinate from an input resolution and a wanted output resolution level (0/1/2/3/...)
*
* @see Sequence#getOriginResolution()
*/
public static Point2D convertPoint(Point2D pt, int inputResolution, int outputResolution)
{
if (pt == null)
return null;
final double factor = Math.pow(2, inputResolution - outputResolution);
return new Point2D.Double(pt.getX() * factor, pt.getY() * factor);
}
/**
* Convert the given Rectangle2D from an input resolution and a wanted output resolution level (0/1/2/3/...)
*
* @see Sequence#getOriginResolution()
*/
public static Rectangle2D convertRectangle(Rectangle2D rect, int inputResolution, int outputResolution)
{
if (rect == null)
return null;
final double factor = Math.pow(2, inputResolution - outputResolution);
return new Rectangle2D.Double(rect.getX() * factor, rect.getY() * factor, rect.getWidth() * factor,
rect.getHeight() * factor);
}
/**
* Convert the given Point coordinate from the source Sequence into the original image coordinate (pixel)<br>
* This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations
* to compute the original image position.
*
* @see Sequence#getOriginResolution()
* @see Sequence#getOriginXYRegion()
*/
public static Point getOriginPoint(Point pt, Sequence source)
{
if (pt == null)
return null;
final Point2D adjPt = convertPoint(pt, source.getOriginResolution(), 0);
final Point result = new Point((int) adjPt.getX(), (int) adjPt.getY());
final Rectangle region = source.getOriginXYRegion();
if (region != null)
result.setLocation(result.x + region.x, result.y + region.y);
return result;
}
/**
* Convert the given Rectangle region from the source Sequence into the original image region coordinates (pixel)<br>
* This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations
* to compute the original image region coordinates.
*
* @see Sequence#getOriginResolution()
* @see Sequence#getOriginXYRegion()
*/
public static Rectangle getOriginRectangle(Rectangle rect, Sequence source)
{
if (rect == null)
return null;
final Rectangle2D adjRect = convertRectangle(rect, source.getOriginResolution(), 0);
final Rectangle result = new Rectangle((int) adjRect.getX(), (int) adjRect.getY(), (int) adjRect.getWidth(),
(int) adjRect.getHeight());
final Rectangle region = source.getOriginXYRegion();
if (region != null)
result.setLocation(result.x + region.x, result.y + region.y);
return result;
}
}