/* * 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; } }