Search in sources :

Example 21 with ByteProcessor

use of ij.process.ByteProcessor in project TrakEM2 by trakem2.

the class Patch method makeFlatImage.

/**
 * Creates an ImageProcessor of the specified type.
 *  @param type Any of ImagePlus.GRAY_8, GRAY_16, GRAY_32 or COLOR_RGB.
 *  @param srcRect the box in world coordinates to make an image out of.
 *  @param scale may be up to 1.0.
 *  @param patches The list of patches to paint. The first gets painted first (at the bottom).
 *  @param background The color with which to paint the outsides where no image paints into.
 *  @param setMinAndMax defines whether the min and max of each Patch is set before pasting the Patch.
 *
 * For exporting while blending the display ranges (min,max) and respecting alpha masks, see {@link ExportUnsignedShort}.
 */
public static ImageProcessor makeFlatImage(final int type, final Layer layer, final Rectangle srcRect, final double scale, final Collection<Patch> patches, final Color background, final boolean setMinAndMax) {
    final ImageProcessor ip;
    final int W, H;
    if (scale < 1) {
        W = (int) (srcRect.width * scale);
        H = (int) (srcRect.height * scale);
    } else {
        W = srcRect.width;
        H = srcRect.height;
    }
    switch(type) {
        case ImagePlus.GRAY8:
            ip = new ByteProcessor(W, H);
            break;
        case ImagePlus.GRAY16:
            ip = new ShortProcessor(W, H);
            break;
        case ImagePlus.GRAY32:
            ip = new FloatProcessor(W, H);
            break;
        case ImagePlus.COLOR_RGB:
            ip = new ColorProcessor(W, H);
            break;
        default:
            Utils.logAll("Cannot create an image of type " + type + ".\nSupported types: 8-bit, 16-bit, 32-bit and RGB.");
            return null;
    }
    // Fill with background
    if (null != background && Color.black != background) {
        ip.setColor(background);
        ip.fill();
    }
    AffineModel2D sc = null;
    if (scale < 1.0) {
        sc = new AffineModel2D();
        sc.set(scale, 0, 0, scale, 0, 0);
    }
    for (final Patch p : patches) {
        // TODO patches seem to come in in inverse order---find out why
        // A list to represent all the transformations that the Patch image has to go through to reach the scaled srcRect image
        final CoordinateTransformList<CoordinateTransform> list = new CoordinateTransformList<CoordinateTransform>();
        final AffineTransform at = new AffineTransform();
        at.translate(-srcRect.x, -srcRect.y);
        at.concatenate(p.getAffineTransform());
        // 1. The coordinate tranform of the Patch, if any
        if (p.hasCoordinateTransform()) {
            final CoordinateTransform ct = p.getCoordinateTransform();
            list.add(ct);
            // Remove the translation in the patch_affine that the ct added to it
            final Rectangle box = Patch.getCoordinateTransformBoundingBox(p, ct);
            at.translate(-box.x, -box.y);
        }
        // 2. The affine transform of the Patch
        final AffineModel2D patch_affine = new AffineModel2D();
        patch_affine.set(at);
        list.add(patch_affine);
        // 3. The desired scaling
        if (null != sc)
            patch_affine.preConcatenate(sc);
        final CoordinateTransformMesh mesh = new CoordinateTransformMesh(list, p.meshResolution, p.getOWidth(), p.getOHeight());
        final mpicbg.ij.TransformMeshMapping<CoordinateTransformMesh> mapping = new mpicbg.ij.TransformMeshMapping<CoordinateTransformMesh>(mesh);
        // 4. Convert the patch to the required type
        ImageProcessor pi = p.getImageProcessor();
        if (setMinAndMax) {
            pi = pi.duplicate();
            pi.setMinAndMax(p.min, p.max);
        }
        switch(type) {
            case ImagePlus.GRAY8:
                pi = pi.convertToByte(true);
                break;
            case ImagePlus.GRAY16:
                pi = pi.convertToShort(true);
                break;
            case ImagePlus.GRAY32:
                pi = pi.convertToFloat();
                break;
            default:
                // ImagePlus.COLOR_RGB and COLOR_256
                pi = pi.convertToRGB();
                break;
        }
        /* TODO for taking into account independent min/max setting for each patch,
			 * we will need a mapping with an `intensity transfer function' to be implemented.
			 * --> EXISTS already as mpicbg/trakem2/transform/ExportUnsignedShort.java
			 */
        mapping.mapInterpolated(pi, ip);
    }
    return ip;
}
Also used : ByteProcessor(ij.process.ByteProcessor) TransformMeshMapping(mpicbg.trakem2.transform.TransformMeshMapping) FloatProcessor(ij.process.FloatProcessor) CoordinateTransformList(mpicbg.trakem2.transform.CoordinateTransformList) Rectangle(java.awt.Rectangle) ShortProcessor(ij.process.ShortProcessor) ImageProcessor(ij.process.ImageProcessor) ColorProcessor(ij.process.ColorProcessor) CoordinateTransformMesh(mpicbg.models.CoordinateTransformMesh) AffineModel2D(mpicbg.trakem2.transform.AffineModel2D) AffineTransform(java.awt.geom.AffineTransform) CoordinateTransform(mpicbg.trakem2.transform.CoordinateTransform)

Example 22 with ByteProcessor

use of ij.process.ByteProcessor in project TrakEM2 by trakem2.

the class Loader method makeTile.

/**
 * Will overwrite if the file path exists.
 */
private void makeTile(final Layer layer, final Rectangle srcRect, final double mag, final int c_alphas, final int type, final Class<?> clazz, final String file_path, final Saver saver) throws Exception {
    ImagePlus imp = null;
    if (srcRect.width > 0 && srcRect.height > 0) {
        // with quality
        imp = getFlatImage(layer, srcRect, mag, c_alphas, type, clazz, null, true);
    } else {
        // black tile
        imp = new ImagePlus("", new ByteProcessor(256, 256));
    }
    // correct cropped tiles
    if (imp.getWidth() < 256 || imp.getHeight() < 256) {
        final ImagePlus imp2 = new ImagePlus(imp.getTitle(), imp.getProcessor().createProcessor(256, 256));
        // ensure black background for color images
        if (imp2.getType() == ImagePlus.COLOR_RGB) {
            final Roi roi = new Roi(0, 0, 256, 256);
            imp2.setRoi(roi);
            // black
            imp2.getProcessor().setValue(0);
            imp2.getProcessor().fill();
        }
        imp2.getProcessor().insert(imp.getProcessor(), 0, 0);
        imp = imp2;
    }
    // debug
    // Utils.log("would save: " + srcRect + " at " + file_path);
    // ImageSaver.saveAsJpeg(imp.getProcessor(), file_path, jpeg_quality, ImagePlus.COLOR_RGB != type);
    saver.save(imp, file_path);
}
Also used : ByteProcessor(ij.process.ByteProcessor) ImagePlus(ij.ImagePlus) Roi(ij.gui.Roi)

Example 23 with ByteProcessor

use of ij.process.ByteProcessor in project TrakEM2 by trakem2.

the class Loader method makePrescaledTiles.

/**
 * Generate 256x256 tiles, as many as necessary, to cover the given srcRect, starting at max_scale. Designed to be slow but memory-capable.
 *
 * filename = z + "/" + row + "_" + column + "_" + s + ".jpg";
 *
 * row and column run from 0 to n stepsize 1
 * that is, row = y / ( 256 * 2^s ) and column = x / ( 256 * 2^s )
 *
 * z : z-level (slice)
 * x,y: the row and column
 * s: scale, which is 1 / (2^s), in integers: 0, 1, 2 ...
 *
 *  var MAX_S = Math.floor( Math.log( MAX_Y + 1 ) / Math.LN2 ) - Math.floor( Math.log( Y_TILE_SIZE ) / Math.LN2 ) - 1;
 *
 * The module should not be more than 5
 * At al levels, there should be an even number of rows and columns, except for the coarsest level.
 * The coarsest level should be at least 5x5 tiles.
 *
 * Best results obtained when the srcRect approaches or is a square. Black space will pad the right and bottom edges when the srcRect is not exactly a square.
 * Only the area within the srcRect is ever included, even if actual data exists beyond.
 *
 * @return The watcher thread, for joining purposes, or null if the dialog is canceled or preconditions are not passed.
 * @throws IllegalArgumentException if the type is not ImagePlus.GRAY8 or Imageplus.COLOR_RGB.
 */
public Bureaucrat makePrescaledTiles(final Layer[] layers, final Class<?> clazz, final Rectangle srcRect, double max_scale_, final int c_alphas, final int type, String target_dir, final boolean from_original_images, final Saver saver, final int tileSide) {
    if (null == layers || 0 == layers.length)
        return null;
    switch(type) {
        case ImagePlus.GRAY8:
        case ImagePlus.COLOR_RGB:
            break;
        default:
            throw new IllegalArgumentException("Can only export for web with 8-bit or RGB");
    }
    // choose target directory
    if (null == target_dir) {
        final DirectoryChooser dc = new DirectoryChooser("Choose target directory");
        target_dir = dc.getDirectory();
        if (null == target_dir)
            return null;
    }
    if (IJ.isWindows())
        target_dir = target_dir.replace('\\', '/');
    if (!target_dir.endsWith("/"))
        target_dir += "/";
    if (max_scale_ > 1) {
        Utils.log("Prescaled Tiles: using max scale of 1.0");
        // no point
        max_scale_ = 1;
    }
    final String dir = target_dir;
    final double max_scale = max_scale_;
    final Worker worker = new Worker("Creating prescaled tiles") {

        private void cleanUp() {
            finishedWorking();
        }

        @Override
        public void run() {
            startedWorking();
            try {
                // project name
                // String pname = layer[0].getProject().getTitle();
                // create 'z' directories if they don't exist: check and ask!
                // start with the highest scale level
                final int[] best = determineClosestPowerOfTwo(srcRect.width > srcRect.height ? srcRect.width : srcRect.height);
                final int edge_length = best[0];
                final int n_edge_tiles = edge_length / tileSide;
                Utils.log2("srcRect: " + srcRect);
                Utils.log2("edge_length, n_edge_tiles, best[1] " + best[0] + ", " + n_edge_tiles + ", " + best[1]);
                // thumbnail dimensions
                // LayerSet ls = layer[0].getParent();
                final double ratio = srcRect.width / (double) srcRect.height;
                double thumb_scale = 1.0;
                if (ratio >= 1) {
                    // width is larger or equal than height
                    thumb_scale = 192.0 / srcRect.width;
                } else {
                    thumb_scale = 192.0 / srcRect.height;
                }
                // Figure out layer indices, given that layers are not necessarily evenly spaced
                final TreeMap<Integer, Layer> indices = new TreeMap<Integer, Layer>();
                final ArrayList<Integer> missingIndices = new ArrayList<Integer>();
                final double resolution_z_px;
                final int smallestIndex, largestIndex;
                if (1 == layers.length) {
                    indices.put(0, layers[0]);
                    resolution_z_px = layers[0].getZ();
                    smallestIndex = 0;
                    largestIndex = 0;
                } else {
                    // Ensure layers are sorted by Z index and are unique pointers and unique in Z coordinate:
                    final TreeMap<Double, Layer> t = new TreeMap<Double, Layer>();
                    for (final Layer l1 : new HashSet<Layer>(Arrays.asList(layers))) {
                        final Layer l2 = t.get(l1.getZ());
                        if (null == l2) {
                            t.put(l1.getZ(), l1);
                        } else {
                            // Ignore the layer with less objects
                            if (l1.getDisplayables().size() > l2.getDisplayables().size()) {
                                t.put(l1.getZ(), l1);
                                Utils.log("Ignoring duplicate layer: " + l2);
                            }
                        }
                    }
                    // What is the mode thickness, measured by Z(i-1) - Z(i)?
                    // (Distance between the Z of two consecutive layers)
                    final HashMap<Double, Integer> counts = new HashMap<Double, Integer>();
                    final Layer prev = t.get(t.firstKey());
                    double modeThickness = 0;
                    int modeThicknessCount = 0;
                    for (final Layer la : t.tailMap(prev.getZ(), false).values()) {
                        // Thickness with 3-decimal precision only
                        final double d = ((int) ((la.getZ() - prev.getZ()) * 1000 + 0.5)) / 1000.0;
                        Integer c = counts.get(d);
                        // 
                        if (null == c)
                            c = 0;
                        ++c;
                        counts.put(d, c);
                        // 
                        if (c > modeThicknessCount) {
                            modeThicknessCount = c;
                            modeThickness = d;
                        }
                    }
                    // Not pixelDepth
                    resolution_z_px = modeThickness * prev.getParent().getCalibration().pixelWidth;
                    // Assign an index to each layer, approximating each layer at modeThickness intervals
                    for (final Layer la : t.values()) {
                        indices.put((int) (la.getZ() / modeThickness + 0.5), la);
                    }
                    // First and last
                    smallestIndex = indices.firstKey();
                    largestIndex = indices.lastKey();
                    Utils.logAll("indices: " + smallestIndex + ", " + largestIndex);
                    // Which indices are missing?
                    for (int i = smallestIndex + 1; i < largestIndex; ++i) {
                        if (!indices.containsKey(i)) {
                            missingIndices.add(i);
                        }
                    }
                }
                // JSON metadata for CATMAID
                {
                    final StringBuilder sb = new StringBuilder("{");
                    final LayerSet ls = layers[0].getParent();
                    final Calibration cal = ls.getCalibration();
                    sb.append("\"volume_width_px\": ").append(srcRect.width).append(',').append('\n').append("\"volume_height_px\": ").append(srcRect.height).append(',').append('\n').append("\"volume_sections\": ").append(largestIndex - smallestIndex + 1).append(',').append('\n').append("\"extension\": \"").append(saver.getExtension()).append('\"').append(',').append('\n').append("\"resolution_x\": ").append(cal.pixelWidth).append(',').append('\n').append("\"resolution_y\": ").append(cal.pixelHeight).append(',').append('\n').append("\"resolution_z\": ").append(resolution_z_px).append(',').append('\n').append("\"units\": \"").append(cal.getUnit()).append('"').append(',').append('\n').append("\"offset_x_px\": 0,\n").append("\"offset_y_px\": 0,\n").append("\"offset_z_px\": ").append(indices.get(indices.firstKey()).getZ() * cal.pixelWidth / cal.pixelDepth).append(',').append('\n').append("\"missing_layers\": [");
                    for (final Integer i : missingIndices) sb.append(i - smallestIndex).append(',');
                    // remove last comma
                    sb.setLength(sb.length() - 1);
                    sb.append("]}");
                    if (!Utils.saveToFile(new File(dir + "metadata.json"), sb.toString())) {
                        Utils.logAll("WARNING: could not save " + dir + "metadata.json\nThe contents was:\n" + sb.toString());
                    }
                }
                for (final Map.Entry<Integer, Layer> entry : indices.entrySet()) {
                    if (this.quit) {
                        cleanUp();
                        return;
                    }
                    final int index = entry.getKey() - smallestIndex;
                    final Layer layer = entry.getValue();
                    // 1 - create a directory 'z' named as the layer's index
                    String tile_dir = dir + index;
                    File fdir = new File(tile_dir);
                    final int tag = 1;
                    // Ensure there is a usable directory:
                    while (fdir.exists() && !fdir.isDirectory()) {
                        fdir = new File(tile_dir + "_" + tag);
                    }
                    if (!fdir.exists()) {
                        fdir.mkdir();
                        Utils.log("Created directory " + fdir);
                    }
                    // if the directory exists already just reuse it, overwritting its files if so.
                    final String tmp = fdir.getAbsolutePath().replace('\\', '/');
                    if (!tile_dir.equals(tmp))
                        Utils.log("\tWARNING: directory will not be in the standard location.");
                    // debug:
                    Utils.log2("tile_dir: " + tile_dir + "\ntmp: " + tmp);
                    tile_dir = tmp;
                    if (!tile_dir.endsWith("/"))
                        tile_dir += "/";
                    // 2 - create layer thumbnail, max 192x192
                    ImagePlus thumb = getFlatImage(layer, srcRect, thumb_scale, c_alphas, type, clazz, true);
                    saver.save(thumb, tile_dir + "small");
                    // ImageSaver.saveAsJpeg(thumb.getProcessor(), tile_dir + "small.jpg", jpeg_quality, ImagePlus.COLOR_RGB != type);
                    flush(thumb);
                    thumb = null;
                    // 3 - fill directory with tiles
                    if (edge_length < tileSide) {
                        // edge_length is the largest length of the tileSide x tileSide tile map that covers an area equal or larger than the desired srcRect (because all tiles have to be tileSide x tileSide in size)
                        // create single tile per layer
                        makeTile(layer, srcRect, max_scale, c_alphas, type, clazz, tile_dir + "0_0_0", saver);
                    } else {
                        // create pyramid of tiles
                        if (from_original_images) {
                            Utils.log("Exporting from web using original images");
                            // Create a giant 8-bit image of the whole layer from original images
                            double scale = 1;
                            Utils.log("Export srcRect: " + srcRect);
                            // WARNING: the snapshot will most likely be smaller than the virtual square image being chopped into tiles
                            ImageProcessor snapshot = null;
                            if (ImagePlus.COLOR_RGB == type) {
                                Utils.log("WARNING: ignoring alpha masks for 'use original images' and 'RGB color' options");
                                snapshot = Patch.makeFlatImage(type, layer, srcRect, scale, (ArrayList<Patch>) (List) layer.getDisplayables(Patch.class, true), Color.black, true);
                            } else if (ImagePlus.GRAY8 == type) {
                                // Respect alpha masks and display range:
                                Utils.log("WARNING: ignoring scale for 'use original images' and '8-bit' options");
                                snapshot = ExportUnsignedShort.makeFlatImage((ArrayList<Patch>) (List) layer.getDisplayables(Patch.class, true), srcRect, 0).convertToByte(true);
                            } else {
                                Utils.log("ERROR: don't know how to generate mipmaps for type '" + type + "'");
                                cleanUp();
                                return;
                            }
                            int scale_pow = 0;
                            int n_et = n_edge_tiles;
                            final ExecutorService exe = Utils.newFixedThreadPool("export-for-web");
                            final ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
                            try {
                                while (n_et >= best[1]) {
                                    final int snapWidth = snapshot.getWidth();
                                    final int snapHeight = snapshot.getHeight();
                                    final ImageProcessor source = snapshot;
                                    for (int row = 0; row < n_et; row++) {
                                        for (int col = 0; col < n_et; col++) {
                                            final String path = new StringBuilder(tile_dir).append(row).append('_').append(col).append('_').append(scale_pow).toString();
                                            final int tileXStart = col * tileSide;
                                            final int tileYStart = row * tileSide;
                                            final int pixelOffset = tileYStart * snapWidth + tileXStart;
                                            fus.add(exe.submit(new Callable<Boolean>() {

                                                @Override
                                                public Boolean call() {
                                                    if (ImagePlus.GRAY8 == type) {
                                                        final byte[] pixels = (byte[]) source.getPixels();
                                                        final byte[] p = new byte[tileSide * tileSide];
                                                        for (int y = 0, sourceIndex = pixelOffset; y < tileSide && tileYStart + y < snapHeight; sourceIndex = pixelOffset + y * snapWidth, y++) {
                                                            final int offsetL = y * tileSide;
                                                            for (int x = 0; x < tileSide && tileXStart + x < snapWidth; sourceIndex++, x++) {
                                                                p[offsetL + x] = pixels[sourceIndex];
                                                            }
                                                        }
                                                        return saver.save(new ImagePlus(path, new ByteProcessor(tileSide, tileSide, p, GRAY_LUT)), path);
                                                    } else {
                                                        final int[] pixels = (int[]) source.getPixels();
                                                        final int[] p = new int[tileSide * tileSide];
                                                        for (int y = 0, sourceIndex = pixelOffset; y < tileSide && tileYStart + y < snapHeight; sourceIndex = pixelOffset + y * snapWidth, y++) {
                                                            final int offsetL = y * tileSide;
                                                            for (int x = 0; x < tileSide && tileXStart + x < snapWidth; sourceIndex++, x++) {
                                                                p[offsetL + x] = pixels[sourceIndex];
                                                            }
                                                        }
                                                        return saver.save(new ImagePlus(path, new ColorProcessor(tileSide, tileSide, p)), path);
                                                    }
                                                }
                                            }));
                                        }
                                    }
                                    // 
                                    scale_pow++;
                                    // works as magnification
                                    scale = 1 / Math.pow(2, scale_pow);
                                    n_et /= 2;
                                    // 
                                    Utils.wait(fus);
                                    fus.clear();
                                    // Scale snapshot in half with area averaging
                                    final ImageProcessor nextSnapshot;
                                    if (ImagePlus.GRAY8 == type) {
                                        nextSnapshot = new ByteProcessor((int) (srcRect.width * scale), (int) (srcRect.height * scale));
                                        final byte[] p1 = (byte[]) snapshot.getPixels();
                                        final byte[] p2 = (byte[]) nextSnapshot.getPixels();
                                        final int width1 = snapshot.getWidth();
                                        final int width2 = nextSnapshot.getWidth();
                                        final int height2 = nextSnapshot.getHeight();
                                        int i = 0;
                                        for (int y1 = 0, y2 = 0; y2 < height2; y1 += 2, y2++) {
                                            final int offset1a = y1 * width1;
                                            final int offset1b = (y1 + 1) * width1;
                                            for (int x1 = 0, x2 = 0; x2 < width2; x1 += 2, x2++) {
                                                p2[i++] = (byte) (((p1[offset1a + x1] & 0xff) + (p1[offset1a + x1 + 1] & 0xff) + (p1[offset1b + x1] & 0xff) + (p1[offset1b + x1 + 1] & 0xff)) / 4);
                                            }
                                        }
                                    } else {
                                        nextSnapshot = new ColorProcessor((int) (srcRect.width * scale), (int) (srcRect.height * scale));
                                        final int[] p1 = (int[]) snapshot.getPixels();
                                        final int[] p2 = (int[]) nextSnapshot.getPixels();
                                        final int width1 = snapshot.getWidth();
                                        final int width2 = nextSnapshot.getWidth();
                                        final int height2 = nextSnapshot.getHeight();
                                        int i = 0;
                                        for (int y1 = 0, y2 = 0; y2 < height2; y1 += 2, y2++) {
                                            final int offset1a = y1 * width1;
                                            final int offset1b = (y1 + 1) * width1;
                                            for (int x1 = 0, x2 = 0; x2 < width2; x1 += 2, x2++) {
                                                final int ka = p1[offset1a + x1], kb = p1[offset1a + x1 + 1], kc = p1[offset1b + x1], kd = p1[offset1b + x1 + 1];
                                                // Average each channel independently
                                                p2[i++] = (((// red
                                                ((ka >> 16) & 0xff) + ((kb >> 16) & 0xff) + ((kc >> 16) & 0xff) + ((kd >> 16) & 0xff)) / 4) << 16) + (((// green
                                                ((ka >> 8) & 0xff) + ((kb >> 8) & 0xff) + ((kc >> 8) & 0xff) + ((kd >> 8) & 0xff)) / 4) << 8) + (// blue
                                                (ka & 0xff) + (kb & 0xff) + (kc & 0xff) + (kd & 0xff)) / 4;
                                            }
                                        }
                                    }
                                    // Assign for next iteration
                                    snapshot = nextSnapshot;
                                // Scale snapshot with a TransformMesh
                                /*
							AffineModel2D aff = new AffineModel2D();
							aff.set(0.5f, 0, 0, 0.5f, 0, 0);
							ImageProcessor scaledSnapshot = new ByteProcessor((int)(snapshot.getWidth() * scale), (int)(snapshot.getHeight() * scale));
							final CoordinateTransformMesh mesh = new CoordinateTransformMesh( aff, 32, snapshot.getWidth(), snapshot.getHeight() );
							final mpicbg.ij.TransformMeshMapping<CoordinateTransformMesh> mapping = new mpicbg.ij.TransformMeshMapping<CoordinateTransformMesh>( mesh );
							mapping.mapInterpolated(snapshot, scaledSnapshot, Runtime.getRuntime().availableProcessors());
							// Assign for next iteration
							snapshot = scaledSnapshot;
							snapshotPixels = (byte[]) scaledSnapshot.getPixels();
							*/
                                }
                            } catch (final Throwable t) {
                                IJError.print(t);
                            } finally {
                                exe.shutdown();
                            }
                        } else {
                            // max_scale; // WARNING if scale is different than 1, it will FAIL to set the next scale properly.
                            double scale = 1;
                            int scale_pow = 0;
                            // cached for local modifications in the loop, works as loop controler
                            int n_et = n_edge_tiles;
                            while (n_et >= best[1]) {
                                // best[1] is the minimal root found, i.e. 1,2,3,4,5 from which then powers of two were taken to make up for the edge_length
                                // 0 < scale <= 1, so no precision lost
                                final int tile_side = (int) (256 / scale);
                                for (int row = 0; row < n_et; row++) {
                                    for (int col = 0; col < n_et; col++) {
                                        final int i_tile = row * n_et + col;
                                        Utils.showProgress(i_tile / (double) (n_et * n_et));
                                        if (0 == i_tile % 100) {
                                            // RGB int[] images
                                            releaseToFit(tile_side * tile_side * 4 * 2);
                                        }
                                        if (this.quit) {
                                            cleanUp();
                                            return;
                                        }
                                        final Rectangle tile_src = new // TODO row and col are inverted
                                        Rectangle(// TODO row and col are inverted
                                        srcRect.x + tile_side * row, srcRect.y + tile_side * col, tile_side, // in absolute coords, magnification later.
                                        tile_side);
                                        // crop bounds
                                        if (tile_src.x + tile_src.width > srcRect.x + srcRect.width)
                                            tile_src.width = srcRect.x + srcRect.width - tile_src.x;
                                        if (tile_src.y + tile_src.height > srcRect.y + srcRect.height)
                                            tile_src.height = srcRect.y + srcRect.height - tile_src.y;
                                        // negative tile sizes will be made into black tiles
                                        // (negative dimensions occur for tiles beyond the edges of srcRect, since the grid of tiles has to be of equal number of rows and cols)
                                        // should be row_col_scale, but results in transposed tiles in googlebrains, so I reversed the order.
                                        makeTile(layer, tile_src, scale, c_alphas, type, clazz, new StringBuilder(tile_dir).append(col).append('_').append(row).append('_').append(scale_pow).toString(), saver);
                                    }
                                }
                                scale_pow++;
                                // works as magnification
                                scale = 1 / Math.pow(2, scale_pow);
                                n_et /= 2;
                            }
                        }
                    }
                }
            } catch (final Exception e) {
                IJError.print(e);
            } finally {
                Utils.showProgress(1);
            }
            cleanUp();
            finishedWorking();
        }
    };
    // watcher thread
    return Bureaucrat.createAndStart(worker, layers[0].getProject());
}
Also used : ByteProcessor(ij.process.ByteProcessor) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) Callable(java.util.concurrent.Callable) ImageProcessor(ij.process.ImageProcessor) ColorProcessor(ij.process.ColorProcessor) Worker(ini.trakem2.utils.Worker) HashSet(java.util.HashSet) LayerSet(ini.trakem2.display.LayerSet) Calibration(ij.measure.Calibration) TreeMap(java.util.TreeMap) Layer(ini.trakem2.display.Layer) ImagePlus(ij.ImagePlus) IOException(java.io.IOException) FormatException(loci.formats.FormatException) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) File(java.io.File) Map(java.util.Map) TreeMap(java.util.TreeMap) HashMap(java.util.HashMap) DirectoryChooser(ij.io.DirectoryChooser)

Example 24 with ByteProcessor

use of ij.process.ByteProcessor in project TrakEM2 by trakem2.

the class Loader method importImages.

/**
 * <p>Import images from the given text file, which is expected to contain 4 columns or optionally 9 columns:</p>
 * <ul>
 * <li>column 1: image file path (if base_dir is not null, it will be prepended)</li>
 * <li>column 2: x coord [px]</li>
 * <li>column 3: y coord [px]</li>
 * <li>column 4: z coord [px] (layer_thickness will be multiplied to it if not zero)</li>
 * </ul>
 * <p>optional columns, if a property is not known, it can be set to "-" which makes TrakEM2 open the file and find out by itself</p>
 * <ul>
 * <li>column 5: width [px]</li>
 * <li>column 6: height [px]</li>
 * <li>column 7: min intensity [double] (for screen display)</li>
 * <li>column 8: max intensity [double] (for screen display)</li>
 * <li>column 9: type [integer] (pixel types according to ImagepPlus types: 0=8bit int gray, 1=16bit int gray, 2=32bit float gray, 3=8bit indexed color, 4=32-bit RGB color</li>
 * </ul>
 *
 * <p>This function implements the "Import from text file" command.</p>
 *
 * <p>Layers will be automatically created as needed inside the LayerSet to which the given ref_layer belongs.</p>
 * <p>
 * The text file can contain comments that start with the # sign.
 * </p>
 * <p>
 * Images will be imported in parallel, using as many cores as your machine has.
 * </p>
 * @param calibration_ transforms the read coordinates into pixel coordinates, including x,y,z, and layer thickness.
 * @param scale_ Between 0 and 1. When lower than 1, a preprocessor script is created for the imported images, to scale them down.
 */
public Bureaucrat importImages(Layer ref_layer, String abs_text_file_path_, String column_separator_, double layer_thickness_, double calibration_, boolean homogenize_contrast_, float scale_, int border_width_) {
    // check parameters: ask for good ones if necessary
    if (null == abs_text_file_path_) {
        final String[] file = Utils.selectFile("Select text file");
        // user canceled dialog
        if (null == file)
            return null;
        abs_text_file_path_ = file[0] + file[1];
    }
    if (null == column_separator_ || 0 == column_separator_.length() || Double.isNaN(layer_thickness_) || layer_thickness_ <= 0 || Double.isNaN(calibration_) || calibration_ <= 0) {
        final Calibration cal = ref_layer.getParent().getCalibrationCopy();
        final GenericDialog gdd = new GenericDialog("Options");
        final String[] separators = new String[] { "tab", "space", "comma (,)" };
        gdd.addMessage("Choose a layer to act as the zero for the Z coordinates:");
        Utils.addLayerChoice("Base layer", ref_layer, gdd);
        gdd.addChoice("Column separator: ", separators, separators[0]);
        // default: 60 nm
        gdd.addNumericField("Layer thickness: ", cal.pixelDepth, 2);
        gdd.addNumericField("Calibration (data to pixels): ", 1, 2);
        gdd.addCheckbox("Homogenize contrast layer-wise", homogenize_contrast_);
        gdd.addSlider("Scale:", 0, 100, 100);
        gdd.addNumericField("Hide border with alpha mask", 0, 0, 6, "pixels");
        gdd.showDialog();
        if (gdd.wasCanceled())
            return null;
        layer_thickness_ = gdd.getNextNumber();
        if (layer_thickness_ < 0 || Double.isNaN(layer_thickness_)) {
            Utils.log("Improper layer thickness value.");
            return null;
        }
        calibration_ = gdd.getNextNumber();
        if (0 == calibration_ || Double.isNaN(calibration_)) {
            Utils.log("Improper calibration value.");
            return null;
        }
        // not pixelDepth!
        layer_thickness_ /= cal.pixelWidth;
        ref_layer = ref_layer.getParent().getLayer(gdd.getNextChoiceIndex());
        column_separator_ = "\t";
        switch(gdd.getNextChoiceIndex()) {
            case 1:
                column_separator_ = " ";
                break;
            case 2:
                column_separator_ = ",";
                break;
            default:
                break;
        }
        homogenize_contrast_ = gdd.getNextBoolean();
        final double sc = gdd.getNextNumber();
        if (Double.isNaN(sc))
            scale_ = 1.0f;
        else
            scale_ = ((float) sc) / 100.0f;
        final int border = (int) gdd.getNextNumber();
        if (border < 0) {
            Utils.log("Nonsensical border value: " + border);
            return null;
        }
        border_width_ = border;
    }
    if (Float.isNaN(scale_) || scale_ < 0 || scale_ > 1) {
        Utils.log("Non-sensical scale: " + scale_ + "\nUsing scale of 1 instead.");
        scale_ = 1;
    }
    // make vars accessible from inner threads:
    final Layer base_layer = ref_layer;
    final String abs_text_file_path = abs_text_file_path_;
    final String column_separator = column_separator_;
    final double layer_thickness = layer_thickness_;
    final double calibration = calibration_;
    final boolean homogenize_contrast = homogenize_contrast_;
    final float scale = (float) scale_;
    final int border_width = border_width_;
    return Bureaucrat.createAndStart(new Worker.Task("Importing images", true) {

        @Override
        public void exec() {
            try {
                // 1 - read text file
                final String[] lines = Utils.openTextFileLines(abs_text_file_path);
                if (null == lines || 0 == lines.length) {
                    Utils.log2("No images to import from " + abs_text_file_path);
                    return;
                }
                ContrastEnhancerWrapper cew = null;
                if (homogenize_contrast) {
                    cew = new ContrastEnhancerWrapper();
                    cew.showDialog();
                }
                final String sep2 = column_separator + column_separator;
                // 2 - set a base dir path if necessary
                String base_dir = null;
                // to wait on mipmap regeneration
                final Vector<Future<?>> fus = new Vector<Future<?>>();
                final LayerSet layer_set = base_layer.getParent();
                final double z_zero = base_layer.getZ();
                final AtomicInteger n_imported = new AtomicInteger(0);
                final Set<Layer> touched_layers = new HashSet<Layer>();
                final int NP = Runtime.getRuntime().availableProcessors();
                int np = NP;
                switch(np) {
                    case 1:
                    case 2:
                        break;
                    default:
                        np = np / 2;
                        break;
                }
                final ExecutorService ex = Utils.newFixedThreadPool(np, "import-images");
                final List<Future<?>> imported = new ArrayList<Future<?>>();
                final Worker wo = this;
                final String script_path;
                // If scale is at least 1/100 lower than 1, then:
                if (Math.abs(scale - (int) scale) > 0.01) {
                    // Assume source and target sigma of 0.5
                    final double sigma = Math.sqrt(Math.pow(1 / scale, 2) - 0.25);
                    final String script = new StringBuilder().append("import ij.ImagePlus;\n").append("import ij.process.ImageProcessor;\n").append("import ij.plugin.filter.GaussianBlur;\n").append("GaussianBlur blur = new GaussianBlur();\n").append(// as in ij.plugin.filter.GaussianBlur
                    "double accuracy = (imp.getType() == ImagePlus.GRAY8 || imp.getType() == ImagePlus.COLOR_RGB) ? 0.002 : 0.0002;\n").append("imp.getProcessor().setInterpolationMethod(ImageProcessor.NONE);\n").append("blur.blurGaussian(imp.getProcessor(),").append(sigma).append(',').append(sigma).append(",accuracy);\n").append("imp.setProcessor(imp.getTitle(), imp.getProcessor().resize((int)(imp.getWidth() * ").append(scale).append("), (int)(imp.getHeight() * ").append(scale).append(")));").toString();
                    File f = new File(getStorageFolder() + "resize-" + scale + ".bsh");
                    int v = 1;
                    while (f.exists()) {
                        f = new File(getStorageFolder() + "resize-" + scale + "." + v + ".bsh");
                        v++;
                    }
                    script_path = Utils.saveToFile(f, script) ? f.getAbsolutePath() : null;
                    if (null == script_path) {
                        Utils.log("Could NOT save a preprocessor script for image scaling\nat path " + f.getAbsolutePath());
                    }
                } else {
                    script_path = null;
                }
                Utils.log("Scaling script path is " + script_path);
                final AtomicReference<Triple<Integer, Integer, ByteProcessor>> last_mask = new AtomicReference<Triple<Integer, Integer, ByteProcessor>>();
                // 3 - parse each line
                for (int i = 0; i < lines.length; i++) {
                    if (Thread.currentThread().isInterrupted() || hasQuitted()) {
                        this.quit();
                        return;
                    }
                    // process line
                    // first thing is the backslash removal, before they get processed at all
                    String line = lines[i].replace('\\', '/').trim();
                    final int ic = line.indexOf('#');
                    // remove comment at end of line if any
                    if (-1 != ic)
                        line = line.substring(0, ic);
                    if (0 == line.length() || '#' == line.charAt(0))
                        continue;
                    // reduce line, so that separators are really unique
                    while (-1 != line.indexOf(sep2)) {
                        line = line.replaceAll(sep2, column_separator);
                    }
                    final String[] column = line.split(column_separator);
                    if (column.length < 4) {
                        Utils.log("Less than 4 columns: can't import from line " + i + " : " + line);
                        continue;
                    }
                    // obtain coordinates
                    double x = 0, y = 0, z = 0;
                    try {
                        x = Double.parseDouble(column[1].trim());
                        y = Double.parseDouble(column[2].trim());
                        z = Double.parseDouble(column[3].trim());
                    } catch (final NumberFormatException nfe) {
                        Utils.log("Non-numeric value in a numeric column at line " + i + " : " + line);
                        continue;
                    }
                    x *= calibration;
                    y *= calibration;
                    z = z * calibration + z_zero;
                    // obtain path
                    String path = column[0].trim();
                    if (0 == path.length())
                        continue;
                    // check if path is relative
                    if ((!IJ.isWindows() && '/' != path.charAt(0)) || (IJ.isWindows() && 1 != path.indexOf(":/"))) {
                        // path is relative.
                        if (null == base_dir) {
                            // may not be null if another thread that got the lock first set it to non-null
                            // Ask for source directory
                            final DirectoryChooser dc = new DirectoryChooser("Choose source directory");
                            final String dir = dc.getDirectory();
                            if (null == dir) {
                                // quit all threads
                                return;
                            }
                            base_dir = Utils.fixDir(dir);
                        }
                    }
                    if (null != base_dir)
                        path = base_dir + path;
                    final File f = new File(path);
                    if (!f.exists()) {
                        Utils.log("No file found for path " + path);
                        continue;
                    }
                    // will create a new Layer if necessary
                    final Layer layer = layer_set.getLayer(z, layer_thickness, true);
                    touched_layers.add(layer);
                    final String imagefilepath = path;
                    final double xx = x * scale;
                    final double yy = y * scale;
                    final Callable<Patch> creator;
                    if (column.length >= 9) {
                        creator = new Callable<Patch>() {

                            private final int parseInt(final String t) {
                                if (t.equals("-"))
                                    return -1;
                                return Integer.parseInt(t);
                            }

                            private final double parseDouble(final String t) {
                                if (t.equals("-"))
                                    return Double.NaN;
                                return Double.parseDouble(t);
                            }

                            @Override
                            public Patch call() throws Exception {
                                int o_width = parseInt(column[4].trim());
                                int o_height = parseInt(column[5].trim());
                                double min = parseDouble(column[6].trim());
                                double max = parseDouble(column[7].trim());
                                int type = parseInt(column[8].trim());
                                if (-1 == type || -1 == o_width || -1 == o_height) {
                                    // Read them from the file header
                                    final ImageFileHeader ifh = new ImageFileHeader(imagefilepath);
                                    o_width = ifh.width;
                                    o_height = ifh.height;
                                    type = ifh.type;
                                    if (!ifh.isSupportedType()) {
                                        Utils.log("Incompatible image type: " + imagefilepath);
                                        return null;
                                    }
                                }
                                ImagePlus imp = null;
                                if (Double.isNaN(min) || Double.isNaN(max)) {
                                    imp = openImagePlus(imagefilepath);
                                    min = imp.getProcessor().getMin();
                                    max = imp.getProcessor().getMax();
                                }
                                final Patch patch = new Patch(layer.getProject(), new File(imagefilepath).getName(), o_width, o_height, o_width, o_height, type, 1.0f, Color.yellow, false, min, max, new AffineTransform(1, 0, 0, 1, xx, yy), imagefilepath);
                                if (null != script_path && null != imp) {
                                    // For use in setting the preprocessor script
                                    cacheImagePlus(patch.getId(), imp);
                                }
                                return patch;
                            }
                        };
                    } else {
                        creator = new Callable<Patch>() {

                            @Override
                            public Patch call() throws Exception {
                                IJ.redirectErrorMessages();
                                final ImageFileHeader ifh = new ImageFileHeader(imagefilepath);
                                final int o_width = ifh.width;
                                final int o_height = ifh.height;
                                final int type = ifh.type;
                                if (!ifh.isSupportedType()) {
                                    Utils.log("Incompatible image type: " + imagefilepath);
                                    return null;
                                }
                                double min = 0;
                                double max = 255;
                                switch(type) {
                                    case ImagePlus.GRAY16:
                                    case ImagePlus.GRAY32:
                                        // Determine suitable min and max
                                        // TODO Stream through the image, do not load it!
                                        final ImagePlus imp = openImagePlus(imagefilepath);
                                        if (null == imp) {
                                            Utils.log("Ignoring unopenable image from " + imagefilepath);
                                            return null;
                                        }
                                        min = imp.getProcessor().getMin();
                                        max = imp.getProcessor().getMax();
                                        break;
                                }
                                // add Patch
                                final Patch patch = new Patch(layer.getProject(), new File(imagefilepath).getName(), o_width, o_height, o_width, o_height, type, 1.0f, Color.yellow, false, min, max, new AffineTransform(1, 0, 0, 1, xx, yy), imagefilepath);
                                return patch;
                            }
                        };
                    }
                    // Otherwise, images would end up loaded twice for no reason
                    if (0 == (i % (NP + NP))) {
                        final ArrayList<Future<?>> a = new ArrayList<Future<?>>(NP + NP);
                        synchronized (fus) {
                            // .add is also synchronized, fus is a Vector
                            int k = 0;
                            while (!fus.isEmpty() && k < NP) {
                                a.add(fus.remove(0));
                                k++;
                            }
                        }
                        for (final Future<?> fu : a) {
                            try {
                                if (wo.hasQuitted())
                                    return;
                                fu.get();
                            } catch (final Throwable t) {
                                t.printStackTrace();
                            }
                        }
                    }
                    imported.add(ex.submit(new Runnable() {

                        @Override
                        public void run() {
                            if (wo.hasQuitted())
                                return;
                            /* */
                            IJ.redirectErrorMessages();
                            Patch patch;
                            try {
                                patch = creator.call();
                            } catch (final Exception e) {
                                e.printStackTrace();
                                Utils.log("Could not load patch from " + imagefilepath);
                                return;
                            }
                            // Set the script if any
                            if (null != script_path) {
                                try {
                                    patch.setPreprocessorScriptPath(script_path);
                                } catch (final Throwable t) {
                                    Utils.log("FAILED to set a scaling preprocessor script to patch " + patch);
                                    IJError.print(t);
                                }
                            }
                            // Set an alpha mask to crop away the borders
                            if (border_width > 0) {
                                final Triple<Integer, Integer, ByteProcessor> m = last_mask.get();
                                if (null != m && m.a == patch.getOWidth() && m.b == patch.getOHeight()) {
                                    // Reuse
                                    patch.setAlphaMask(m.c);
                                } else {
                                    // Create new mask
                                    final ByteProcessor mask = new ByteProcessor(patch.getOWidth(), patch.getOHeight());
                                    mask.setValue(255);
                                    mask.setRoi(new Roi(border_width, border_width, mask.getWidth() - 2 * border_width, mask.getHeight() - 2 * border_width));
                                    mask.fill();
                                    patch.setAlphaMask(mask);
                                    // Store as last
                                    last_mask.set(new Triple<Integer, Integer, ByteProcessor>(mask.getWidth(), mask.getHeight(), mask));
                                }
                            }
                            if (!homogenize_contrast) {
                                fus.add(regenerateMipMaps(patch));
                            }
                            synchronized (layer) {
                                layer.add(patch, true);
                            }
                            wo.setTaskName("Imported " + (n_imported.incrementAndGet() + 1) + "/" + lines.length);
                        }
                    }));
                }
                Utils.wait(imported);
                ex.shutdown();
                if (0 == n_imported.get()) {
                    Utils.log("No images imported.");
                    return;
                }
                base_layer.getParent().setMinimumDimensions();
                Display.repaint(base_layer.getParent());
                recreateBuckets(touched_layers);
                if (homogenize_contrast) {
                    setTaskName("Enhance contrast");
                    // layer-wise (layer order is irrelevant):
                    cew.applyLayerWise(touched_layers);
                    cew.shutdown();
                }
                Utils.wait(fus);
            } catch (final Exception e) {
                IJError.print(e);
            }
        }
    }, base_layer.getProject());
}
Also used : ByteProcessor(ij.process.ByteProcessor) Set(java.util.Set) HashSet(java.util.HashSet) LayerSet(ini.trakem2.display.LayerSet) ArrayList(java.util.ArrayList) Callable(java.util.concurrent.Callable) GenericDialog(ij.gui.GenericDialog) Worker(ini.trakem2.utils.Worker) AreaList(ini.trakem2.display.AreaList) ArrayList(java.util.ArrayList) List(java.util.List) Vector(java.util.Vector) LayerSet(ini.trakem2.display.LayerSet) AtomicReference(java.util.concurrent.atomic.AtomicReference) ImageFileHeader(ini.trakem2.io.ImageFileHeader) Calibration(ij.measure.Calibration) Layer(ini.trakem2.display.Layer) ImagePlus(ij.ImagePlus) Roi(ij.gui.Roi) IOException(java.io.IOException) FormatException(loci.formats.FormatException) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Triple(mpicbg.trakem2.util.Triple) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) AffineTransform(java.awt.geom.AffineTransform) ContrastEnhancerWrapper(ini.trakem2.imaging.ContrastEnhancerWrapper) File(java.io.File) Patch(ini.trakem2.display.Patch) DirectoryChooser(ij.io.DirectoryChooser)

Example 25 with ByteProcessor

use of ij.process.ByteProcessor in project TrakEM2 by trakem2.

the class FSLoader method generateMipMaps.

/**
 * Given an image and its source file name (without directory prepended), generate
 * a pyramid of images until reaching an image not smaller than 32x32 pixels.
 * <p>
 * Such images are stored as jpeg 85% quality in a folder named trakem2.mipmaps.
 * </p>
 * <p>
 * The Patch id and the right extension will be appended to the filename in all cases.
 * </p>
 * <p>
 * Any equally named files will be overwritten.
 * </p>
 */
protected boolean generateMipMaps(final Patch patch) {
    Utils.log2("mipmaps for " + patch);
    final String path = getAbsolutePath(patch);
    if (null == path) {
        Utils.log("generateMipMaps: null path for Patch " + patch);
        cannot_regenerate.add(patch);
        return false;
    }
    if (hs_unloadable.contains(patch)) {
        FilePathRepair.add(patch);
        return false;
    }
    synchronized (gm_lock) {
        try {
            if (null == dir_mipmaps)
                createMipMapsDir(null);
            if (null == dir_mipmaps || isURL(dir_mipmaps))
                return false;
        } catch (Exception e) {
            IJError.print(e);
        }
    }
    /**
     * Record Patch as modified
     */
    touched_mipmaps.add(patch);
    /**
     * Remove serialized features, if any
     */
    removeSerializedFeatures(patch);
    /**
     * Remove serialized pointmatches, if any
     */
    removeSerializedPointMatches(patch);
    /**
     * Alpha mask: setup to check if it was modified while regenerating.
     */
    final long alpha_mask_id = patch.getAlphaMaskId();
    final int resizing_mode = patch.getProject().getMipMapsMode();
    try {
        ImageProcessor ip;
        ByteProcessor alpha_mask = null;
        ByteProcessor outside_mask = null;
        int type = patch.getType();
        // Aggressive cache freeing
        releaseToFit(patch.getOWidth() * patch.getOHeight() * 4 + MIN_FREE_BYTES);
        // Obtain an image which may be coordinate-transformed, and an alpha mask.
        Patch.PatchImage pai = patch.createTransformedImage();
        if (null == pai || null == pai.target) {
            Utils.log("Can't regenerate mipmaps for patch " + patch);
            cannot_regenerate.add(patch);
            return false;
        }
        ip = pai.target;
        // can be null
        alpha_mask = pai.mask;
        // can be null
        outside_mask = pai.outside;
        pai = null;
        // Old style:
        // final String filename = new StringBuilder(new File(path).getName()).append('.').append(patch.getId()).append(mExt).toString();
        // New style:
        final String filename = createMipMapRelPath(patch, mExt);
        int w = ip.getWidth();
        int h = ip.getHeight();
        // sigma = sqrt(2^level - 0.5^2)
        // where 0.5 is the estimated sigma for a full-scale image
        // which means sigma = 0.75 for the full-scale image (has level 0)
        // prepare a 0.75 sigma image from the original
        double min = patch.getMin(), max = patch.getMax();
        // (The -1,-1 are flags really for "not set")
        if (-1 == min && -1 == max) {
            switch(type) {
                case ImagePlus.COLOR_RGB:
                case ImagePlus.COLOR_256:
                case ImagePlus.GRAY8:
                    patch.setMinAndMax(0, 255);
                    break;
                // Find and flow through to default:
                case ImagePlus.GRAY16:
                    ((ij.process.ShortProcessor) ip).findMinAndMax();
                    patch.setMinAndMax(ip.getMin(), ip.getMax());
                    break;
                case ImagePlus.GRAY32:
                    ((FloatProcessor) ip).findMinAndMax();
                    patch.setMinAndMax(ip.getMin(), ip.getMax());
                    break;
            }
            // may have changed
            min = patch.getMin();
            max = patch.getMax();
        }
        // Set for the level 0 image, which is a duplicate of the one in the cache in any case
        ip.setMinAndMax(min, max);
        // ImageJ no longer stretches the bytes for ByteProcessor with setMinAndmax
        if (ByteProcessor.class == ip.getClass()) {
            if (0 != min && 255 != max) {
                final byte[] b = (byte[]) ip.getPixels();
                final double scale = 255 / (max - min);
                for (int i = 0; i < b.length; ++i) {
                    final int val = b[i] & 0xff;
                    if (val < min)
                        b[i] = 0;
                    else
                        b[i] = (byte) Math.min(255, ((val - min) * scale));
                }
            }
        }
        // Proper support for LUT images: treat them as RGB
        if (ip.isColorLut() || type == ImagePlus.COLOR_256) {
            ip = ip.convertToRGB();
            type = ImagePlus.COLOR_RGB;
        }
        final int first_mipmap_level_saved = patch.getProject().getFirstMipMapLevelSaved();
        if (Loader.AREA_DOWNSAMPLING == resizing_mode) {
            long t0 = System.currentTimeMillis();
            final ImageBytes[] b = DownsamplerMipMaps.create(patch, type, ip, alpha_mask, outside_mask);
            long t1 = System.currentTimeMillis();
            for (int i = 0; i < b.length; ++i) {
                if (i < first_mipmap_level_saved) {
                    // Ignore level i
                    if (null != b[i])
                        CachingThread.storeForReuse(b[i].c);
                } else {
                    boolean written = mmio.save(getLevelDir(dir_mipmaps, i) + filename, b[i].c, b[i].width, b[i].height, 0.85f);
                    if (!written) {
                        Utils.log("Failed to save mipmap with area downsampling at level=" + i + " for patch " + patch);
                        cannot_regenerate.add(patch);
                        break;
                    }
                }
            }
            long t2 = System.currentTimeMillis();
            System.out.println("MipMaps with area downsampling: creation took " + (t1 - t0) + "ms, saving took " + (t2 - t1) + "ms, total: " + (t2 - t0) + "ms\n");
        } else if (Loader.GAUSSIAN == resizing_mode) {
            if (ImagePlus.COLOR_RGB == type) {
                // TODO releaseToFit proper
                releaseToFit(w * h * 4 * 10);
                final ColorProcessor cp = (ColorProcessor) ip;
                final FloatProcessorT2 red = new FloatProcessorT2(w, h, 0, 255);
                cp.toFloat(0, red);
                final FloatProcessorT2 green = new FloatProcessorT2(w, h, 0, 255);
                cp.toFloat(1, green);
                final FloatProcessorT2 blue = new FloatProcessorT2(w, h, 0, 255);
                cp.toFloat(2, blue);
                FloatProcessorT2 alpha;
                final FloatProcessorT2 outside;
                if (null != alpha_mask) {
                    alpha = new FloatProcessorT2(alpha_mask);
                } else {
                    alpha = null;
                }
                if (null != outside_mask) {
                    outside = new FloatProcessorT2(outside_mask);
                    if (null == alpha) {
                        alpha = outside;
                        alpha_mask = outside_mask;
                    }
                } else {
                    outside = null;
                }
                final String target_dir0 = getLevelDir(dir_mipmaps, 0);
                if (Thread.currentThread().isInterrupted())
                    return false;
                // Generate level 0 first:
                if (0 == first_mipmap_level_saved) {
                    boolean written;
                    if (null == alpha) {
                        written = mmio.save(cp, target_dir0 + filename, 0.85f, false);
                    } else {
                        written = mmio.save(target_dir0 + filename, P.asRGBABytes((int[]) cp.getPixels(), (byte[]) alpha_mask.getPixels(), null == outside ? null : (byte[]) outside_mask.getPixels()), w, h, 0.85f);
                    }
                    if (!written) {
                        Utils.log("Failed to save mipmap for COLOR_RGB, 'alpha = " + alpha + "', level = 0  for  patch " + patch);
                        cannot_regenerate.add(patch);
                    }
                }
                // Generate all other mipmap levels
                // TODO: for best performance, it should start from a direct Gaussian downscaling at the first level to write.
                // the scale level. Proper scale is: 1 / pow(2, k)
                int k = 0;
                do {
                    if (Thread.currentThread().isInterrupted())
                        return false;
                    // 1 - Prepare values for the next scaled image
                    k++;
                    // 2 - Check that the target folder for the desired scale exists
                    final String target_dir = getLevelDir(dir_mipmaps, k);
                    if (null == target_dir)
                        break;
                    // 3 - Blur the previous image to 0.75 sigma, and scale it
                    // will resize 'red' FloatProcessor in place.
                    final byte[] r = gaussianBlurResizeInHalf(red);
                    // idem
                    final byte[] g = gaussianBlurResizeInHalf(green);
                    // idem
                    final byte[] b = gaussianBlurResizeInHalf(blue);
                    // idem
                    final byte[] a = null == alpha ? null : gaussianBlurResizeInHalf(alpha);
                    if (null != outside) {
                        final byte[] o;
                        if (alpha != outside)
                            // idem
                            o = gaussianBlurResizeInHalf(outside);
                        else
                            o = a;
                        // If there was no alpha mask, alpha is the outside itself
                        for (int i = 0; i < o.length; i++) {
                            // TODO I am sure there is a bitwise operation to do this in one step. Some thing like: a[i] &= 127;
                            if ((o[i] & 0xff) != 255)
                                a[i] = 0;
                        }
                    }
                    w = red.getWidth();
                    h = red.getHeight();
                    // 4 - Compose ColorProcessor
                    if (first_mipmap_level_saved < k) {
                        // Skip saving this mipmap level
                        continue;
                    }
                    if (null == alpha) {
                        // 5 - Save as jpeg
                        if (!mmio.save(target_dir + filename, new byte[][] { r, g, b }, w, h, 0.85f)) {
                            Utils.log("Failed to save mipmap for COLOR_RGB, 'alpha = " + alpha + "', level = " + k + " for  patch " + patch);
                            cannot_regenerate.add(patch);
                            break;
                        }
                    } else {
                        if (!mmio.save(target_dir + filename, new byte[][] { r, g, b, a }, w, h, 0.85f)) {
                            Utils.log("Failed to save mipmap for COLOR_RGB, 'alpha = " + alpha + "', level = " + k + " for  patch " + patch);
                            cannot_regenerate.add(patch);
                            break;
                        }
                    }
                } while (// not smaller than 32x32
                w >= 32 && h >= 32);
            } else {
                long t0 = System.currentTimeMillis();
                // Greyscale:
                releaseToFit(w * h * 4 * 10);
                if (Thread.currentThread().isInterrupted())
                    return false;
                final FloatProcessorT2 fp = new FloatProcessorT2((FloatProcessor) ip.convertToFloat());
                if (ImagePlus.GRAY8 == type) {
                    // for 8-bit, the min,max has been applied when going to FloatProcessor
                    // just set it
                    fp.setMinMax(0, 255);
                } else {
                    fp.setMinAndMax(patch.getMin(), patch.getMax());
                }
                // fp.debugMinMax(patch.toString());
                FloatProcessorT2 alpha, outside;
                if (null != alpha_mask) {
                    alpha = new FloatProcessorT2(alpha_mask);
                } else {
                    alpha = null;
                }
                if (null != outside_mask) {
                    outside = new FloatProcessorT2(outside_mask);
                    if (null == alpha) {
                        alpha = outside;
                        alpha_mask = outside_mask;
                    }
                } else {
                    outside = null;
                }
                // the scale level. Proper scale is: 1 / pow(2, k)
                int k = 0;
                do {
                    if (Thread.currentThread().isInterrupted())
                        return false;
                    if (0 != k) {
                        // not doing so at the end because it would add one unnecessary blurring
                        gaussianBlurResizeInHalf(fp);
                        if (null != alpha) {
                            gaussianBlurResizeInHalf(alpha);
                            if (alpha != outside && outside != null) {
                                gaussianBlurResizeInHalf(outside);
                            }
                        }
                    }
                    w = fp.getWidth();
                    h = fp.getHeight();
                    // 1 - check that the target folder for the desired scale exists
                    final String target_dir = getLevelDir(dir_mipmaps, k);
                    if (null == target_dir)
                        break;
                    if (k < first_mipmap_level_saved) {
                        // Skip saving this mipmap level
                        k++;
                        continue;
                    }
                    if (null != alpha) {
                        // If there was no alpha mask, alpha is the outside itself
                        if (!mmio.save(target_dir + filename, new byte[][] { fp.getScaledBytePixels(), P.merge(alpha.getBytePixels(), null == outside ? null : outside.getBytePixels()) }, w, h, 0.85f)) {
                            Utils.log("Failed to save mipmap for GRAY8, 'alpha = " + alpha + "', level = " + k + " for  patch " + patch);
                            cannot_regenerate.add(patch);
                            break;
                        }
                    } else {
                        // 3 - save as 8-bit jpeg
                        if (!mmio.save(target_dir + filename, new byte[][] { fp.getScaledBytePixels() }, w, h, 0.85f)) {
                            Utils.log("Failed to save mipmap for GRAY8, 'alpha = " + alpha + "', level = " + k + " for  patch " + patch);
                            cannot_regenerate.add(patch);
                            break;
                        }
                    }
                    // 4 - prepare values for the next scaled image
                    k++;
                } while (// not smaller than 32x32
                fp.getWidth() >= 32 && fp.getHeight() >= 32);
                long t1 = System.currentTimeMillis();
                System.out.println("MipMaps took " + (t1 - t0));
            }
        } else {
            Utils.log("ERROR: unknown image resizing mode for mipmaps: " + resizing_mode);
        }
        return true;
    } catch (Throwable e) {
        Utils.log("*** ERROR: Can't generate mipmaps for patch " + patch);
        IJError.print(e);
        cannot_regenerate.add(patch);
        return false;
    } finally {
        // flush any cached tiles
        flushMipMaps(patch.getId());
        // flush any cached layer screenshots
        if (null != patch.getLayer()) {
            try {
                patch.getLayer().getParent().removeFromOffscreens(patch.getLayer());
            } catch (Exception e) {
                IJError.print(e);
            }
        }
        // gets executed even when returning from the catch statement or within the try/catch block
        synchronized (gm_lock) {
            regenerating_mipmaps.remove(patch);
        }
        // Has the alpha mask changed?
        if (patch.getAlphaMaskId() != alpha_mask_id) {
            Utils.log2("Alpha mask changed: resubmitting mipmap regeneration for " + patch);
            regenerateMipMaps(patch);
        }
    }
}
Also used : ByteProcessor(ij.process.ByteProcessor) FloatProcessor(ij.process.FloatProcessor) TimeoutException(java.util.concurrent.TimeoutException) ExecutionException(java.util.concurrent.ExecutionException) ImageProcessor(ij.process.ImageProcessor) ColorProcessor(ij.process.ColorProcessor) Patch(ini.trakem2.display.Patch) FloatProcessorT2(ini.trakem2.imaging.FloatProcessorT2)

Aggregations

ByteProcessor (ij.process.ByteProcessor)86 ImagePlus (ij.ImagePlus)30 ImageProcessor (ij.process.ImageProcessor)23 FloatProcessor (ij.process.FloatProcessor)21 ShortProcessor (ij.process.ShortProcessor)19 ColorProcessor (ij.process.ColorProcessor)14 ArrayList (java.util.ArrayList)13 Point (java.awt.Point)12 Rectangle (java.awt.Rectangle)11 Roi (ij.gui.Roi)10 AffineTransform (java.awt.geom.AffineTransform)10 ImageStack (ij.ImageStack)9 Patch (ini.trakem2.display.Patch)9 Calibration (ij.measure.Calibration)8 Pair (mpicbg.trakem2.util.Pair)7 Color (java.awt.Color)6 LUT (ij.process.LUT)5 BufferedImage (java.awt.image.BufferedImage)5 IOException (java.io.IOException)5 CoordinateTransformMesh (mpicbg.models.CoordinateTransformMesh)5