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);
}
}
}
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;
}
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));
}
}
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));
}
}
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;
}
Aggregations