Search in sources :

Example 71 with Patch

use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.

the class Loader method importImage.

/**
 * Import a new image at the given coordinates; does not puts it into any layer, unless it's a stack -in which case importStack is called with the current front layer of the given project as target. If a path is not provided it will be asked for.
 */
public Patch importImage(final Project project, final double x, final double y, String path, final boolean synch_mipmap_generation) {
    if (null == path) {
        final OpenDialog od = new OpenDialog("Import image", "");
        final String name = od.getFileName();
        if (null == name || 0 == name.length())
            return null;
        String dir = od.getDirectory().replace('\\', '/');
        if (!dir.endsWith("/"))
            dir += "/";
        path = dir + name;
    } else {
        path = path.replace('\\', '/');
    }
    // avoid opening trakem2 projects
    if (path.toLowerCase().endsWith(".xml")) {
        Utils.showMessage("Cannot import " + path + " as a stack.");
        return null;
    }
    releaseToFit(new File(path).length() * 3);
    IJ.redirectErrorMessages();
    final ImagePlus imp = openImagePlus(path);
    if (null == imp)
        return null;
    if (imp.getNSlices() > 1) {
        // a stack!
        final Layer layer = Display.getFrontLayer(project);
        if (null == layer)
            return null;
        importStack(layer, x, y, imp, true, path, true);
        return null;
    }
    if (0 == imp.getWidth() || 0 == imp.getHeight()) {
        Utils.showMessage("Can't import image of zero width or height.");
        flush(imp);
        return null;
    }
    final Patch p = new Patch(project, imp.getTitle(), x, y, imp);
    addedPatchFrom(path, p);
    // WARNING may be altered concurrently
    last_opened_path = path;
    if (isMipMapsRegenerationEnabled()) {
        if (synch_mipmap_generation) {
            try {
                // wait
                regenerateMipMaps(p).get();
            } catch (final Exception e) {
                IJError.print(e);
            }
        } else
            // queue for regeneration
            regenerateMipMaps(p);
    }
    return p;
}
Also used : File(java.io.File) ImagePlus(ij.ImagePlus) Layer(ini.trakem2.display.Layer) Patch(ini.trakem2.display.Patch) IOException(java.io.IOException) FormatException(loci.formats.FormatException) OpenDialog(ij.io.OpenDialog)

Example 72 with Patch

use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.

the class Loader method preProcess.

protected final void preProcess(final Patch p, ImagePlus imp, final long image_n_bytes) {
    if (null == p)
        return;
    try {
        final String path = preprocessors.get(p);
        boolean update = false;
        if (null != path) {
            final File f = new File(path);
            if (!f.exists()) {
                Utils.log("ERROR: preprocessor script file does NOT exist: " + path);
                return;
            } else if (!f.canRead()) {
                Utils.log("ERROR: can NOT read preprocessor script file at: " + path);
                return;
            }
            if (null == imp) {
                // uninitialized: the script may generate its data
                imp = new ImagePlus();
            } else {
                // Prepare image for pre-processing
                // for 8-bit and RGB images, your problem: setting min and max will expand the range.
                imp.getProcessor().setMinAndMax(p.getMin(), p.getMax());
            }
            // Free 10 times the memory taken by the image, as a gross estimate of memory consumption by the script
            releaseToFit(Math.min(10 * image_n_bytes, MAX_MEMORY / 4));
            // Run the script
            ini.trakem2.scripting.PatchScript.run(p, imp, path);
            // Update Patch image properties:
            if (null != imp.getProcessor() && null != imp.getProcessor().getPixels() && imp.getWidth() > 0 && imp.getHeight() > 0) {
                update = true;
            } else {
                Utils.log("ERROR: preprocessor script failed to create a valid image:" + "\n  ImageProcessor: " + imp.getProcessor() + "\n  pixel array: " + (null == imp.getProcessor() ? null : imp.getProcessor().getPixels()) + "\n  width: " + imp.getWidth() + "\n  height: " + imp.getHeight());
            }
        }
        // Now apply the Patch filters, if any
        final IFilter[] fs = p.getFilters();
        if (null != fs && fs.length > 0) {
            ImageProcessor ip = imp.getProcessor();
            for (final IFilter filter : fs) {
                ip = filter.process(ip);
            }
            if (ip != imp.getProcessor()) {
                imp.setProcessor(ip);
            }
            update = true;
        }
        // Now apply intensity correction if available
        update |= mapIntensities(p, imp);
        if (update) {
            // 1: Tag the ImagePlus as altered (misuses fileFormat field, which is unused in any case)
            final FileInfo fi = imp.getOriginalFileInfo();
            // otherwise, the null original FileInfo is a valid tag by itself in the persistence.Cache
            if (null != fi)
                fi.fileFormat = Loader.PREPROCESSED;
            // 2: cache
            cache(p, imp);
            // 3: update properties of the Patch
            p.updatePixelProperties(imp);
        }
    } catch (final Exception e) {
        IJError.print(e);
    }
}
Also used : ImageProcessor(ij.process.ImageProcessor) FileInfo(ij.io.FileInfo) IFilter(ini.trakem2.imaging.filters.IFilter) File(java.io.File) ImagePlus(ij.ImagePlus) IOException(java.io.IOException) FormatException(loci.formats.FormatException)

Example 73 with Patch

use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.

the class Loader method fetchAWTImage.

public final MipMapImage fetchAWTImage(final Patch p, final int level, final int max_level) {
    // Below, the complexity of the synchronized blocks is to provide sufficient granularity. Keep in mind that only one thread at at a time can access a synchronized block for the same object (in this case, the db_lock), and thus calling lock() and unlock() is not enough. One needs to break the statement in as many synch blocks as possible for maximizing the number of threads concurrently accessing different parts of this function.
    // find an equal or larger existing pyramid awt
    final long id = p.getId();
    ImageLoadingLock plock = null;
    synchronized (db_lock) {
        try {
            if (null == mawts) {
                // when lazy repainting after closing a project, the awts is null
                return new MipMapImage(NOT_FOUND, p.getWidth() / NOT_FOUND.getWidth(), p.getHeight() / NOT_FOUND.getHeight());
            }
            if (level >= 0 && isMipMapsRegenerationEnabled()) {
                // 1 - check if the exact level is cached
                final Image mawt = mawts.get(id, level);
                if (null != mawt) {
                    // Utils.log2("returning cached exact mawt for level " + level);
                    final double scale = Math.pow(2.0, level);
                    return new MipMapImage(mawt, scale, scale);
                }
                plock = getOrMakeImageLoadingLock(p.getId(), level);
            }
        } catch (final Exception e) {
            IJError.print(e);
        }
    }
    MipMapImage mipMap = null;
    // 2 - check if the exact file is present for the desired level
    if (level >= 0 && isMipMapsRegenerationEnabled()) {
        synchronized (plock) {
            final Image mawt;
            synchronized (db_lock) {
                mawt = mawts.get(id, level);
            }
            if (null != mawt) {
                final double scale = Math.pow(2.0, level);
                // was loaded by a different thread
                return new MipMapImage(mawt, scale, scale);
            }
        }
        final long n_bytes = estimateImageFileSize(p, level);
        // going to load:
        releaseToFit(n_bytes * 8);
        synchronized (plock) {
            try {
                mipMap = fetchMipMapAWT(p, level, n_bytes);
            } catch (final Throwable t) {
                IJError.print(t);
                mipMap = null;
            }
            synchronized (db_lock) {
                try {
                    if (null != mipMap) {
                        // Utils.log2("returning exact mawt from file for level " + level);
                        if (REGENERATING != mipMap.image) {
                            mawts.put(id, mipMap.image, level);
                            Display.repaintSnapshot(p);
                        }
                        return mipMap;
                    }
                    // Check if an appropriate level is cached
                    mipMap = mawts.getClosestAbove(id, level);
                    if (mipMap == null) {
                        // 3 - else, load closest level to it but still giving a larger image
                        // finds the file for the returned level, otherwise returns zero
                        final int lev = getClosestMipMapLevel(p, level, max_level);
                        // Utils.log2("closest mipmap level is " + lev);
                        if (lev > -1) {
                            // overestimating n_bytes
                            mipMap = fetchMipMapAWT(p, lev, n_bytes);
                            if (null != mipMap) {
                                mawts.put(id, mipMap.image, lev);
                                // Utils.log2("from getClosestMipMapLevel: mawt is " + mawt);
                                Display.repaintSnapshot(p);
                                // Utils.log2("returning from getClosestMipMapAWT with level " + lev);
                                return mipMap;
                            }
                        } else if (ERROR_PATH_NOT_FOUND == lev) {
                            mipMap = new MipMapImage(NOT_FOUND, p.getWidth() / NOT_FOUND.getWidth(), p.getHeight() / NOT_FOUND.getHeight());
                        }
                    } else {
                        return mipMap;
                    }
                } catch (final Throwable t) {
                    handleCacheError(t);
                } finally {
                    removeImageLoadingLock(plock);
                }
            }
        }
    }
    synchronized (db_lock) {
        try {
            // 4 - check if any suitable level is cached (whithout mipmaps, it may be the large image)
            mipMap = mawts.getClosestAbove(id, level);
            if (null != mipMap) {
                // Utils.log2("returning from getClosest with level " + level);
                return mipMap;
            }
        } catch (final Exception e) {
            IJError.print(e);
        }
    }
    if (hs_unloadable.contains(p))
        return new MipMapImage(NOT_FOUND, p.getWidth() / NOT_FOUND.getWidth(), p.getHeight() / NOT_FOUND.getHeight());
    synchronized (db_lock) {
        try {
            plock = getOrMakeImageLoadingLock(p.getId(), level);
        } catch (final Exception e) {
            // TODO there may be a flaw in the image loading locks: when removing it, if it had been acquired by another thread, then a third thread will create it new. The image loading locks should count the number of threads that have them, and remove themselves when zero.
            if (null != plock)
                removeImageLoadingLock(plock);
            return new MipMapImage(NOT_FOUND, p.getWidth() / NOT_FOUND.getWidth(), p.getHeight() / NOT_FOUND.getHeight());
        }
    }
    synchronized (plock) {
        // Check if a previous call made it while waiting:
        mipMap = mawts.getClosestAbove(id, level);
        if (null != mipMap) {
            synchronized (db_lock) {
                removeImageLoadingLock(plock);
            }
            return mipMap;
        }
    }
    Image mawt = null;
    try {
        // Else, create the mawt:
        final Patch.PatchImage pai = p.createTransformedImage();
        synchronized (plock) {
            if (null != pai && null != pai.target) {
                mawt = pai.createImage(p.getMin(), p.getMax());
            }
        }
    } catch (final Exception e) {
        Utils.log2("Could not create an image for Patch " + p);
        mawt = null;
    }
    synchronized (db_lock) {
        try {
            if (null != mawt) {
                mawts.put(id, mawt, 0);
                Display.repaintSnapshot(p);
                // Utils.log2("Created mawt from scratch.");
                return new MipMapImage(mawt, 1.0, 1.0);
            }
        } catch (final Throwable t) {
            handleCacheError(t);
        } finally {
            removeImageLoadingLock(plock);
        }
    }
    return new MipMapImage(NOT_FOUND, p.getWidth() / NOT_FOUND.getWidth(), p.getHeight() / NOT_FOUND.getHeight());
}
Also used : MipMapImage(ini.trakem2.display.MipMapImage) Image(java.awt.Image) BufferedImage(java.awt.image.BufferedImage) MipMapImage(ini.trakem2.display.MipMapImage) Patch(ini.trakem2.display.Patch) IOException(java.io.IOException) FormatException(loci.formats.FormatException)

Example 74 with Patch

use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.

the class Loader method getFlatAWTImage.

/**
 * @param layer The layer from which to collect visible Displayable instances that intersect the srcRect.
 * @param srcRect_ Rectangle in World coordinates representing the field of view to paint into the image, and defines the width and height of the image together with the scale.
 * @param scale Value between 0 and 1.
 * @param c_alphas Which color channels to include when painting Patch instances that hold an RGB image.
 * @param type Either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB
 * @param clazz Include only Displayable instances of this class; use Displayable.class for all.
 * @param al_displ List of Displayable instances to include. Use null to include all visible intersected by srcRect.
 * @param quality Whether to attempt to create a larger image and then scale it down with area averaging for best quality.
 * @param background The color of the areas of the image where no Displayable paints.
 * @param active Whether to paint a particular Displayable instance in an active state (as if it was selected in the UI).
 * @return
 */
public Image getFlatAWTImage(final Layer layer, final Rectangle srcRect_, final double scale, final int c_alphas, final int type, final Class<?> clazz, List<? extends Displayable> al_displ, boolean quality, final Color background, final Displayable active) {
    try {
        // dimensions
        int w = 0;
        int h = 0;
        Rectangle srcRect = (null == srcRect_) ? null : (Rectangle) srcRect_.clone();
        if (null != srcRect) {
            w = srcRect.width;
            h = srcRect.height;
        } else {
            w = (int) Math.ceil(layer.getLayerWidth());
            h = (int) Math.ceil(layer.getLayerHeight());
            srcRect = new Rectangle(0, 0, w, h);
        }
        Utils.log2("Loader.getFlatImage: using rectangle " + srcRect);
        /*
			 * output size including excess space for not entirely covered
			 * pixels
			 */
        final int ww = (int) Math.ceil(w * scale);
        final int hh = (int) Math.ceil(h * scale);
        /* The size of the buffered image to be generated */
        final int biw, bih;
        final double scaleP, scalePX, scalePY;
        // on scaling down to output resolution.
        if (quality) {
            if (ww * hh >= Math.pow(2, 30)) {
                // 1 GB
                // While perhaps scale could be increased to an image size of up to 2 GB, it is not advisable
                scaleP = scalePX = scalePY = scale;
                quality = false;
                biw = ww;
                bih = hh;
                Utils.log("Can't use 'quality' flag for getFlatAWTImage: would be too large");
            // If the image is larger than 2 GB, it will thrown a NegativeArraySizeException below and stop.
            } else {
                // Max area: the smallest of the srcRect at 100x magnification and 1 GB
                final double max_area = Math.min(srcRect.width * srcRect.height, Math.pow(2, 30));
                final double ratio = ww / (double) hh;
                // area = w * h
                // ratio = w / h
                // w = ratio * h
                // area = ratio * h * h
                // h = sqrt(area / ratio)
                // scaleP is then the ratio between the real-world height and the target height
                // (And clamp to a maximum of 1.0: above makes no sense)
                scaleP = Math.min(1.0, srcRect.height / Math.sqrt(max_area / ratio));
                biw = (int) Math.ceil(ww / scale);
                bih = (int) Math.ceil(hh / scale);
                /* compensate for excess space due to ceiling */
                scalePX = (double) biw / (double) ww * scale;
                scalePY = (double) bih / (double) hh * scale;
                Utils.log("getFlatAWTImage -- scale: " + scale + "; quality scale: " + scaleP);
            }
        } else {
            scaleP = scalePX = scalePY = scale;
            biw = ww;
            bih = hh;
        }
        // estimate image size
        final long n_bytes = (long) ((biw * bih * (ImagePlus.GRAY8 == type ? 1.0 : /*byte*/
        4.0)));
        Utils.log2("Flat image estimated size in bytes: " + Long.toString(n_bytes) + "  w,h : " + (int) Math.ceil(biw) + "," + (int) Math.ceil(bih) + (quality ? " (using 'quality' flag: scaling to " + scale + " is done later with area averaging)" : ""));
        releaseToFit(n_bytes);
        // go
        BufferedImage bi = null;
        switch(type) {
            case ImagePlus.GRAY8:
                bi = new BufferedImage(biw, bih, BufferedImage.TYPE_BYTE_INDEXED, GRAY_LUT);
                break;
            case ImagePlus.COLOR_RGB:
                bi = new BufferedImage(biw, bih, BufferedImage.TYPE_INT_ARGB);
                break;
            default:
                Utils.log2("Left bi,icm as null");
                break;
        }
        final Graphics2D g2d = bi.createGraphics();
        g2d.setColor(background);
        g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        // TODO Stephan, test if that introduces offset vs nearest neighbor
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        // to smooth edges of the images
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ArrayList<ZDisplayable> al_zdispl = null;
        if (null == al_displ) {
            al_displ = new ArrayList<Displayable>(layer.find(clazz, srcRect, true, true));
            al_zdispl = new ArrayList<ZDisplayable>((Collection) layer.getParent().findZDisplayables(clazz, layer, srcRect, true, true));
        } else {
            // separate ZDisplayables into their own array
            al_displ = new ArrayList<Displayable>(al_displ);
            final HashSet<ZDisplayable> az = new HashSet<ZDisplayable>();
            for (final Iterator<?> it = al_displ.iterator(); it.hasNext(); ) {
                final Object ob = it.next();
                if (ob instanceof ZDisplayable) {
                    it.remove();
                    az.add((ZDisplayable) ob);
                }
            }
            // order ZDisplayables by their stack order
            final ArrayList<ZDisplayable> al_zdispl2 = layer.getParent().getZDisplayables();
            for (final Iterator<ZDisplayable> it = al_zdispl2.iterator(); it.hasNext(); ) {
                if (!az.contains(it.next()))
                    it.remove();
            }
            al_zdispl = al_zdispl2;
        }
        // prepare the canvas for the srcRect and magnification
        final AffineTransform at_original = g2d.getTransform();
        final AffineTransform atc = new AffineTransform();
        atc.scale(scalePX, scalePY);
        atc.translate(-srcRect.x, -srcRect.y);
        at_original.preConcatenate(atc);
        g2d.setTransform(at_original);
        // Utils.log2("will paint: " + al_displ.size() + " displ and " + al_zdispl.size() + " zdispl");
        // int total = al_displ.size() + al_zdispl.size();
        int count = 0;
        boolean zd_done = false;
        final List<Layer> layers = layer.getParent().getColorCueLayerRange(layer);
        for (final Displayable d : al_displ) {
            // paint the ZDisplayables before the first label, if any
            if (!zd_done && d instanceof DLabel) {
                zd_done = true;
                for (final ZDisplayable zd : al_zdispl) {
                    if (!zd.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                        zd.paint(g2d, srcRect, scaleP, active == zd, c_alphas, layer, layers);
                    }
                    count++;
                // Utils.log2("Painted " + count + " of " + total);
                }
            }
            if (!d.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                d.paintOffscreen(g2d, srcRect, scaleP, active == d, c_alphas, layer, layers);
            // Utils.log("painted: " + d + "\n with: " + scaleP + ", " + c_alphas + ", " + layer);
            } else {
            // Utils.log2("out: " + d);
            }
            count++;
        // Utils.log2("Painted " + count + " of " + total);
        }
        if (!zd_done) {
            zd_done = true;
            for (final ZDisplayable zd : al_zdispl) {
                if (!zd.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                    zd.paint(g2d, srcRect, scaleP, active == zd, c_alphas, layer, layers);
                }
                count++;
            // Utils.log2("Painted " + count + " of " + total);
            }
        }
        // ensure enough memory is available for the processor and a new awt from it
        // locks on its own
        releaseToFit((long) (n_bytes * 2.3));
        try {
            if (quality) {
                // need to scale back down
                Image scaled = null;
                if (!isMipMapsRegenerationEnabled() || scale >= 0.499) {
                    // there are no proper mipmaps above 50%, so there's need for SCALE_AREA_AVERAGING.
                    // very slow, but best by far
                    scaled = bi.getScaledInstance(ww, hh, Image.SCALE_AREA_AVERAGING);
                    if (ImagePlus.GRAY8 == type) {
                        // getScaledInstance generates RGB images for some reason.
                        final BufferedImage bi8 = new BufferedImage(ww, hh, BufferedImage.TYPE_BYTE_GRAY);
                        bi8.createGraphics().drawImage(scaled, 0, 0, null);
                        scaled.flush();
                        scaled = bi8;
                    }
                } else {
                    // faster, but requires gaussian blurred images (such as the mipmaps)
                    if (bi.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
                        scaled = new BufferedImage(ww, hh, bi.getType(), GRAY_LUT);
                    } else {
                        scaled = new BufferedImage(ww, hh, bi.getType());
                    }
                    final Graphics2D gs = (Graphics2D) scaled.getGraphics();
                    // gs.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                    gs.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                    gs.drawImage(bi, 0, 0, ww, hh, null);
                }
                bi.flush();
                return scaled;
            } else {
                // else the image was made scaled down already, and of the proper type
                return bi;
            }
        } catch (final OutOfMemoryError oome) {
            Utils.log("Not enough memory to create the ImagePlus. Try scaling it down or not using the 'quality' flag.");
        }
    } catch (final Exception e) {
        IJError.print(e);
    }
    return null;
}
Also used : ZDisplayable(ini.trakem2.display.ZDisplayable) Displayable(ini.trakem2.display.Displayable) Rectangle(java.awt.Rectangle) Image(java.awt.Image) BufferedImage(java.awt.image.BufferedImage) MipMapImage(ini.trakem2.display.MipMapImage) Layer(ini.trakem2.display.Layer) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) FormatException(loci.formats.FormatException) Graphics2D(java.awt.Graphics2D) ZDisplayable(ini.trakem2.display.ZDisplayable) DLabel(ini.trakem2.display.DLabel) Collection(java.util.Collection) AffineTransform(java.awt.geom.AffineTransform) HashSet(java.util.HashSet)

Example 75 with Patch

use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.

the class ProjectTiler method createRetiledSibling.

/**
 * Take a {@link Project}, a size for the image tiles, and a target directory,
 * and create a new copy of the current project in that folder but with the underlying
 * images converted to tiles with a translation-only transform (saved as zipped TIFFs,
 * with extension ".tif.zip").
 * The new, returned {@link Project} represents the given project but with much
 * simpler transformations (just translation) for the images and a defined size for
 * the latter, which helps a lot regarding storage space of the XML (and parsing and
 * saving time) and performance when browsing layers (keep in mind that, for a 32k x 32k image,
 * at 100% zoom one would have to load a 32k x 32k image and render just a tiny bit
 * of it). The copied Project preserves the ID of the {@link Layer}s of the original
 * {@link Project}, as well as the dimensions; this means the copy is a sibling of
 * the original, and it is possible to send segmentations from one to the other "as is"
 * (directly, without having to transform along with the images which would not be possible).
 *
 * Image files are stored as
 *
 * The non-image objects of the given project are copied into the new project as well.
 *
 * @param srcProject The project to create a sibling of.
 * @param targetDirectory The directory in which to create all the necessary data and mipmap folders for the new Project.
 * @param tileWidth The width of the tiles to create for the data of the new project.
 * @param tileHeight The height of the tiles.
 * @param exportImageType Any of {@link ImagePlus#GRAY8}, {@link ImagePlus#GRAY16} or {@link ImagePlus#COLOR_RGB}, otherwise an {@link IllegalArgumentException} is thrown.
 * @param onlyVisibleImages Whether to consider visible images only.
 * @param nExportThreads Number of layers to export in parallel. Use a small number when original images are huge (such as larger than 4096 x 4096 pixels).
 * @param createMipMaps Whether to generate the mipmaps when done or not.
 *
 * @throws Exception IllegalArgumentException When {@code exportImageType} is not {@link ImagePlus#GRAY16} or {@link ImagePlus#COLOR_RGB}, or when the directory exists and cannot be written to.
 */
public static final Project createRetiledSibling(final Project srcProject, final String targetDirectory, final int tileWidth, final int tileHeight, final int exportImageType, final boolean onlyVisibleImages, final int nExportThreads, final boolean createMipMaps) throws Exception {
    // Validate exportImageType
    switch(exportImageType) {
        case ImagePlus.GRAY8:
        case ImagePlus.GRAY16:
        case ImagePlus.COLOR_RGB:
            break;
        default:
            throw new IllegalArgumentException("Can only accept GRAY8, GRAY16 or COLOR_RGB as values for 'exportImageType'!");
    }
    // Validate targetDirectory
    final File fdir = new File(targetDirectory);
    if (fdir.exists()) {
        if (!fdir.isDirectory() || !fdir.canWrite())
            throw new IllegalArgumentException("Invalid directory: not a directory or cannot write to: " + targetDirectory);
    } else {
        if (!fdir.mkdirs()) {
            throw new IllegalArgumentException("Cannot create directory at: " + targetDirectory);
        }
    }
    final String targetDir = Utils.fixDir(targetDirectory);
    // Create "data" directory
    final String dataDir = new StringBuilder(targetDir).append("data/").toString();
    final File fDataDir = new File(dataDir);
    if (fDataDir.exists() && (!fDataDir.isDirectory() || !fDataDir.canWrite())) {
        throw new IllegalArgumentException("Cannot create or write to 'data' directory in the targetDirectory at: " + targetDir);
    } else {
        fDataDir.mkdir();
    }
    // Create new Project, plain, without any automatic creation of a Layer or a Display
    final Project newProject = Project.newFSProject("blank", null, targetDir, false);
    final LayerSet newLayerSet = newProject.getRootLayerSet();
    newLayerSet.setCalibration(srcProject.getRootLayerSet().getCalibrationCopy());
    if (!createMipMaps) {
        Utils.log("MipMaps are DISABLED:\n --> When done, right-click and choose 'Display - Properties...' and enable mipmaps,\n     and then run 'Project - Regenerate all mipmaps'\n");
        newProject.getLoader().setMipMapsRegeneration(false);
        Utils.log("mipmaps enabled? " + newProject.getLoader().isMipMapsRegenerationEnabled());
    }
    // Copy the Template Tree of types
    newProject.resetRootTemplateThing(srcProject.getRootTemplateThing().clone(newProject, true), null);
    for (final TemplateThing tt : newProject.getRootTemplateThing().getUniqueTypes(new HashMap<String, TemplateThing>()).values()) {
        newProject.addUniqueType(tt);
    }
    // Clone layers with the exact same IDs, so that the two projects are siblings at the layer-level:
    // (Being siblings allows for treelines, arealists, etc. to be transferred from one to another "as is").
    final List<Layer> srcLayers = srcProject.getRootLayerSet().getLayers();
    final List<Layer> newLayers = new ArrayList<Layer>();
    for (final Layer srcLayer : srcLayers) {
        final Layer newLayer = new Layer(newProject, srcLayer.getId(), srcLayer.getZ(), srcLayer.getThickness());
        // to update the ID generator in FSLoader
        newLayer.addToDatabase();
        newLayerSet.add(newLayer);
        newLayers.add(newLayer);
        newProject.getRootLayerThing().addChild(new LayerThing(newProject.getRootLayerThing().getChildTemplate("layer"), newProject, newLayer));
    }
    newProject.getLayerTree().rebuild();
    // Update the LayerSet
    newLayerSet.setDimensions(srcProject.getRootLayerSet().getLayerWidth(), srcProject.getRootLayerSet().getLayerHeight(), LayerSet.NORTHWEST);
    Display.updateLayerScroller(newLayerSet);
    Display.update(newLayerSet);
    // Copy template from the src Project
    // (It's done after creating layers so the IDs will not collide with those of the Layers)
    newProject.resetRootTemplateThing(srcProject.getRootTemplateThing().clone(newProject, false), null);
    // Export tiles as new Patch instances, creating new image files in disk
    final int numThreads = Math.max(1, Math.min(nExportThreads, Runtime.getRuntime().availableProcessors()));
    int i = 0;
    for (final Layer srcLayer : srcLayers) {
        Utils.log("Processing layer " + (i + 1) + "/" + srcLayers.size() + " -- " + new Date());
        final int layerIndex = i++;
        // Create subDirectory
        final String dir = dataDir + "/" + layerIndex + "/";
        new File(dir).mkdir();
        // Create a new Layer with the same Z and thickness
        final Layer newLayer = newLayers.get(layerIndex);
        // Export layer tiles
        final ArrayList<Patch> patches = new ArrayList<Patch>();
        if (ImagePlus.GRAY16 == exportImageType) {
            Process.progressive(ExportUnsignedShort.exportTiles(srcLayer, tileWidth, tileHeight, onlyVisibleImages), new CountingTaskFactory<Callable<ExportedTile>, Patch>() {

                public Patch process(final Callable<ExportedTile> c, final int index) {
                    try {
                        // Create the tile
                        final ExportedTile t = c.call();
                        // Store the file
                        final String title = layerIndex + "-" + index;
                        final String path = dir + title + ".tif.zip";
                        final ImagePlus imp = new ImagePlus(title, t.sp);
                        if (!new FileSaver(imp).saveAsZip(path)) {
                            throw new Exception("Could not save tile: " + path);
                        }
                        // Create a Patch
                        final Patch patch = new Patch(newProject, title, t.x, t.y, imp);
                        patch.setLocked(true);
                        newProject.getLoader().addedPatchFrom(path, patch);
                        return patch;
                    } catch (Exception e) {
                        IJError.print(e);
                        return null;
                    }
                }
            }, patches, numThreads);
        } else {
            // GRAY8 or COLOR_RGB: created from mipmaps
            Process.progressive(tileSequence(srcLayer, tileWidth, tileHeight, onlyVisibleImages), new CountingTaskFactory<Rectangle, Patch>() {

                @Override
                public Patch process(final Rectangle bounds, final int index) {
                    try {
                        // Create the tile
                        final ImagePlus imp = srcLayer.getProject().getLoader().getFlatImage(srcLayer, bounds, 1.0, -1, exportImageType, Patch.class, null, false, Color.black);
                        final String title = layerIndex + "-" + index;
                        imp.setTitle(title);
                        final String path = dir + title + ".tif.zip";
                        if (!new FileSaver(imp).saveAsZip(path)) {
                            throw new Exception("Could not save tile: " + path);
                        }
                        // Create a Patch
                        final Patch patch = new Patch(newProject, title, bounds.x, bounds.y, imp);
                        patch.setLocked(true);
                        newProject.getLoader().addedPatchFrom(path, patch);
                        return patch;
                    } catch (Exception e) {
                        IJError.print(e);
                        return null;
                    }
                }
            }, patches, numThreads);
        }
        // Add all Patches to the new Layer
        for (final Patch p : patches) {
            newLayer.add(p);
        }
    }
    // Copy all segmentations "As is"
    final ProjectThing root = srcProject.getRootProjectThing();
    if (null != root.getChildren() && !root.getChildren().isEmpty()) {
        final ProjectThing source_pt = srcProject.getRootProjectThing().getChildren().get(0);
        // "As is"
        final int transfer_mode = 0;
        final ProjectThing landing_parent = newProject.getRootProjectThing();
        srcProject.getProjectTree().rawSendToSiblingProject(source_pt, transfer_mode, newProject, landing_parent);
    }
    // Copy all floating text labels
    i = 0;
    for (final Layer srcLayer : srcLayers) {
        for (final DLabel srcLabel : srcLayer.getAll(DLabel.class)) {
            newLayers.get(i++).add(srcLabel.clone(newProject, false));
        }
    }
    if (createMipMaps) {
        final LinkedList<Future<?>> fus = new LinkedList<Future<?>>();
        final int batch = Runtime.getRuntime().availableProcessors();
        for (final Layer newLayer : newLayers) {
            for (final Patch p : newLayer.getAll(Patch.class)) {
                fus.add(p.updateMipMaps());
                // Don't build-up too much
                if (fus.size() > batch * 3) {
                    while (fus.size() > batch) {
                        try {
                            fus.removeFirst().get();
                        } catch (Exception e) {
                            IJError.print(e);
                        }
                    }
                }
            }
        }
        Utils.wait(fus);
    }
    // Save:
    newProject.saveAs(targetDir + "exported.xml", false);
    return newProject;
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) Callable(java.util.concurrent.Callable) FileSaver(ij.io.FileSaver) ProjectThing(ini.trakem2.tree.ProjectThing) LayerSet(ini.trakem2.display.LayerSet) LayerThing(ini.trakem2.tree.LayerThing) Layer(ini.trakem2.display.Layer) ImagePlus(ij.ImagePlus) Date(java.util.Date) LinkedList(java.util.LinkedList) Project(ini.trakem2.Project) DLabel(ini.trakem2.display.DLabel) TemplateThing(ini.trakem2.tree.TemplateThing) Future(java.util.concurrent.Future) File(java.io.File) Patch(ini.trakem2.display.Patch) ExportedTile(mpicbg.trakem2.transform.ExportedTile)

Aggregations

Patch (ini.trakem2.display.Patch)69 ArrayList (java.util.ArrayList)46 Layer (ini.trakem2.display.Layer)39 ImagePlus (ij.ImagePlus)34 Rectangle (java.awt.Rectangle)28 Point (mpicbg.models.Point)26 HashSet (java.util.HashSet)24 Displayable (ini.trakem2.display.Displayable)23 AffineTransform (java.awt.geom.AffineTransform)20 Loader (ini.trakem2.persistence.Loader)15 File (java.io.File)15 Future (java.util.concurrent.Future)15 NotEnoughDataPointsException (mpicbg.models.NotEnoughDataPointsException)15 PointMatch (mpicbg.models.PointMatch)14 Worker (ini.trakem2.utils.Worker)13 HashMap (java.util.HashMap)13 ExecutorService (java.util.concurrent.ExecutorService)12 ImageProcessor (ij.process.ImageProcessor)11 AffineModel2D (mpicbg.models.AffineModel2D)11 GenericDialog (ij.gui.GenericDialog)10