Search in sources :

Example 1 with FloatProcessorT2

use of ini.trakem2.imaging.FloatProcessorT2 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)

Example 2 with FloatProcessorT2

use of ini.trakem2.imaging.FloatProcessorT2 in project TrakEM2 by trakem2.

the class Utils method fastConvertToFloat.

/**
 * A method that circumvents the findMinAndMax when creating a float processor from an existing processor.  Ignores color calibrations and does no scaling at all.
 */
public static final FloatProcessor fastConvertToFloat(final ShortProcessor ip) {
    final short[] pix = (short[]) ip.getPixels();
    final float[] data = new float[pix.length];
    for (int i = 0; i < pix.length; i++) data[i] = pix[i] & 0xffff;
    final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
    return fp;
}
Also used : FloatProcessor(ij.process.FloatProcessor) FloatProcessorT2(ini.trakem2.imaging.FloatProcessorT2)

Example 3 with FloatProcessorT2

use of ini.trakem2.imaging.FloatProcessorT2 in project TrakEM2 by trakem2.

the class Loader method scaleImage.

public static ImageProcessor scaleImage(final ImagePlus imp, final int level, final boolean quality) {
    if (level <= 0)
        return imp.getProcessor();
    // else, make a properly scaled image:
    // - gaussian blurred for best quality when resizing with nearest neighbor
    // - direct nearest neighbor otherwise
    ImageProcessor ip = imp.getProcessor();
    final int w = ip.getWidth();
    final int h = ip.getHeight();
    final double mag = 1 / Math.pow(2, level);
    // TODO releseToFit !
    if (quality) {
        // apply proper gaussian filter
        // sigma = sqrt(level^2 - 0.5^2)
        final double sigma = Math.sqrt(Math.pow(2, level) - 0.25);
        ip = new FloatProcessorT2(w, h, ImageFilter.computeGaussianFastMirror(new FloatArray2D((float[]) ip.convertToFloat().getPixels(), w, h), (float) sigma).data, ip.getDefaultColorModel(), ip.getMin(), ip.getMax());
        // better while float
        ip = ip.resize((int) (w * mag), (int) (h * mag));
        return Utils.convertTo(ip, imp.getType(), false);
    } else {
        return ip.resize((int) (w * mag), (int) (h * mag));
    }
}
Also used : ImageProcessor(ij.process.ImageProcessor) FloatArray2D(mpi.fruitfly.math.datastructures.FloatArray2D) FloatProcessorT2(ini.trakem2.imaging.FloatProcessorT2)

Example 4 with FloatProcessorT2

use of ini.trakem2.imaging.FloatProcessorT2 in project TrakEM2 by trakem2.

the class Loader method scaleImage.

public static ImageProcessor scaleImage(final ImagePlus imp, double mag, final boolean quality) {
    if (mag > 1)
        mag = 1;
    ImageProcessor ip = imp.getProcessor();
    if (Math.abs(mag - 1) < 0.000001)
        return ip;
    // else, make a properly scaled image:
    // - gaussian blurred for best quality when resizing with nearest neighbor
    // - direct nearest neighbor otherwise
    final int w = ip.getWidth();
    final int h = ip.getHeight();
    // TODO releseToFit !
    if (quality) {
        // apply proper gaussian filter
        // sigma = sqrt(level^2 - 0.5^2)
        final double sigma = Math.sqrt(Math.pow(2, getMipMapLevel(mag, Math.max(imp.getWidth(), imp.getHeight()))) - 0.25);
        ip = new FloatProcessorT2(w, h, ImageFilter.computeGaussianFastMirror(new FloatArray2D((float[]) ip.convertToFloat().getPixels(), w, h), (float) sigma).data, ip.getDefaultColorModel(), ip.getMin(), ip.getMax());
        // better while float
        ip = ip.resize((int) (w * mag), (int) (h * mag));
        return Utils.convertTo(ip, imp.getType(), false);
    } else {
        return ip.resize((int) (w * mag), (int) (h * mag));
    }
}
Also used : ImageProcessor(ij.process.ImageProcessor) FloatArray2D(mpi.fruitfly.math.datastructures.FloatArray2D) FloatProcessorT2(ini.trakem2.imaging.FloatProcessorT2)

Example 5 with FloatProcessorT2

use of ini.trakem2.imaging.FloatProcessorT2 in project TrakEM2 by trakem2.

the class Utils method fastConvertToFloat.

/**
 * A method that circumvents the findMinAndMax when creating a float processor from an existing processor.  Ignores color calibrations and does no scaling at all.
 */
public static final FloatProcessor fastConvertToFloat(final ByteProcessor ip) {
    final byte[] pix = (byte[]) ip.getPixels();
    final float[] data = new float[pix.length];
    for (int i = 0; i < pix.length; i++) data[i] = pix[i] & 0xff;
    final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
    return fp;
}
Also used : FloatProcessor(ij.process.FloatProcessor) FloatProcessorT2(ini.trakem2.imaging.FloatProcessorT2)

Aggregations

FloatProcessorT2 (ini.trakem2.imaging.FloatProcessorT2)5 FloatProcessor (ij.process.FloatProcessor)3 ImageProcessor (ij.process.ImageProcessor)3 FloatArray2D (mpi.fruitfly.math.datastructures.FloatArray2D)2 ByteProcessor (ij.process.ByteProcessor)1 ColorProcessor (ij.process.ColorProcessor)1 Patch (ini.trakem2.display.Patch)1 ExecutionException (java.util.concurrent.ExecutionException)1 TimeoutException (java.util.concurrent.TimeoutException)1