use of ini.trakem2.display.Patch 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.display.Patch in project TrakEM2 by trakem2.
the class FSLoader method updatePaths.
/**
* For the Patch and for any associated slices if the patch is part of a stack.
*/
private void updatePaths(final Patch patch, final String new_path, final boolean is_stack) {
synchronized (db_lock) {
try {
// ensure the old path is cached in the Patch, to get set as the original if there is no original.
String old_path = getAbsolutePath(patch);
if (is_stack) {
old_path = old_path.substring(0, old_path.lastIndexOf("-----#slice"));
for (Patch p : patch.getStackPatches()) {
long pid = p.getId();
String str = ht_paths.get(pid);
int isl = str.lastIndexOf("-----#slice=");
updatePatchPath(p, new_path + str.substring(isl));
}
} else {
Utils.log2("path to set: " + new_path);
Utils.log2("path before: " + ht_paths.get(patch.getId()));
updatePatchPath(patch, new_path);
Utils.log2("path after: " + ht_paths.get(patch.getId()));
}
mawts.updateImagePlusPath(old_path, new_path);
} catch (Throwable e) {
IJError.print(e);
}
}
}
use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.
the class FSLoader method fetchMipMap.
/**
* Does the actual fetching of the file. Returns null if the file does not exist.
* Does NOT pre-release memory from the cache;
* call releaseToFit to do that.
*/
public final MipMapImage fetchMipMap(final Patch patch, int level, final long n_bytes) {
final int max_level = getHighestMipMapLevel(patch);
if (level > max_level)
level = max_level;
final double scale = Math.pow(2.0, level);
final String filename = getInternalFileName(patch);
if (null == filename) {
Utils.log2("null internal filename!");
return null;
}
// New style:
final String path = new StringBuilder(dir_mipmaps).append(level).append('/').append(createIdPath(Long.toString(patch.getId()), filename, mExt)).toString();
if (patch.hasAlphaChannel()) {
final Image img = mmio.open(path);
return img == null ? null : new MipMapImage(img, scale, scale);
} else if (patch.paintsWithFalseColor()) {
// AKA Patch has a LUT or is LUT image like a GIF
final Image img = mmio.open(path);
// considers c_alphas
return img == null ? null : new MipMapImage(img, scale, scale);
} else {
final Image img;
switch(patch.getType()) {
case ImagePlus.GRAY16:
case ImagePlus.GRAY8:
case ImagePlus.GRAY32:
// ImageSaver.openGreyJpeg(path);
img = mmio.openGrey(path);
return img == null ? null : new MipMapImage(img, scale, scale);
default:
// For color images: (considers URL as well)
img = mmio.open(path);
// considers c_alphas
return img == null ? null : new MipMapImage(img, scale, scale);
}
}
}
use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.
the class FilePathRepair method fixPath.
private static void fixPath(final JTable table, final PathTableModel data, final int row, final boolean fix_similar, final boolean fix_all, final boolean update_mipmaps) throws Exception {
synchronized (projects) {
final Patch patch = data.vp.get(row);
if (null == patch)
return;
final String old_path = patch.getImageFilePath();
final File f = new File(old_path);
if (f.exists()) {
Utils.log("File exists for " + patch + " at " + f.getAbsolutePath() + "\n --> not updating path.");
data.remove(row);
return;
}
// Else, pop up file dialog
OpenDialog od = new OpenDialog("Select image file", OpenDialog.getDefaultDirectory(), null);
String dir = od.getDirectory();
final String filename = od.getFileName();
// dialog was canceled
if (null == dir)
return;
if (IJ.isWindows())
dir = dir.replace('\\', '/');
if (!dir.endsWith("/"))
dir += "/";
// Compare filenames
if (!filename.equals(f.getName())) {
YesNoDialog yn = new YesNoDialog(projects.get(patch.getProject()).frame, "WARNING", "Different file names!\n old: " + f.getName() + "\n new: " + filename + "\nSet to new file name?");
if (!yn.yesPressed())
return;
// Remove mipmaps: would not be found with the new name and the old ones would remain behind unused
if (!f.getName().equals(new File(old_path).getName())) {
// remove mipmaps: the name wouldn't match otherwise
patch.getProject().getLoader().removeMipMaps(patch);
}
}
//
String wrong_parent_path = new File(data.vpath.get(row)).getParent();
wrong_parent_path = wrong_parent_path.replace('\\', '/');
// not File.separatorChar, TrakEM2 uses '/' as folder separator
if (!wrong_parent_path.endsWith("/"))
wrong_parent_path = new StringBuilder(wrong_parent_path).append('/').toString();
final String path = new StringBuilder(dir).append(filename).toString();
// keep track of fixed slices to avoid calling n_slices * n_slices times!
final HashSet<Patch> fixed = new HashSet<Patch>();
int n_fixed = 0;
if (-1 == patch.getFilePath().lastIndexOf("-----#slice=")) {
if (!fixPatchPath(patch, path, update_mipmaps)) {
return;
}
data.remove(patch);
fixed.add(patch);
n_fixed += 1;
} else {
int n = fixStack(data, fixed, patch.getStackPatches(), old_path, path, update_mipmaps);
if (0 == n) {
// some error ocurred, no paths fixed
return;
}
n_fixed += n;
// data already cleared of removed patches by fixStack
}
String good_parent_path = dir;
// not File.separatorChar, TrakEM2 uses '/' as folder separator
if (!dir.endsWith("/"))
good_parent_path = new StringBuilder(good_parent_path).append('/').toString();
// Check for similar parent paths and see if they can be fixed
if (fix_similar) {
for (int i = data.vp.size() - 1; i > -1; i--) {
final String wrong_path = data.vpath.get(i);
final Patch p = data.vp.get(i);
if (wrong_path.startsWith(wrong_parent_path)) {
// try to fix as well
final File file = new File(new StringBuilder(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString());
if (file.exists()) {
if (-1 == p.getFilePath().lastIndexOf("-----#slice=")) {
if (!fixed.contains(p) && fixPatchPath(p, file.getAbsolutePath(), update_mipmaps)) {
// not by 'i' but by Patch, since if some fail the order is not the same
data.remove(p);
n_fixed++;
fixed.add(p);
}
} else {
if (fixed.contains(p))
continue;
n_fixed += fixStack(data, fixed, p.getStackPatches(), wrong_path, file.getAbsolutePath(), update_mipmaps);
}
}
}
}
}
if (fix_all) {
// traverse all Patch from the entire project, minus those already fixed
for (final Displayable d : patch.getLayerSet().getDisplayables(Patch.class)) {
final Patch p = (Patch) d;
final String wrong_path = p.getImageFilePath();
if (wrong_path.startsWith(wrong_parent_path)) {
File file = new File(new StringBuilder(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString());
if (file.exists()) {
if (-1 == p.getFilePath().lastIndexOf("-----#slice=")) {
if (!fixed.contains(p) && fixPatchPath(p, file.getAbsolutePath(), update_mipmaps)) {
// not by 'i' but by Patch, since if some fail the order is not the same
data.remove(p);
n_fixed++;
fixed.add(p);
}
} else {
if (fixed.contains(p))
continue;
n_fixed += fixStack(data, fixed, p.getStackPatches(), wrong_path, file.getAbsolutePath(), update_mipmaps);
}
}
}
}
}
// if table is empty, close
if (0 == data.vp.size()) {
FilePathRepair fpr = projects.remove(patch.getProject());
fpr.frame.dispose();
}
Utils.logAll("Fixed " + n_fixed + " image file path" + (n_fixed > 1 ? "s" : ""));
}
}
use of ini.trakem2.display.Patch in project TrakEM2 by trakem2.
the class IntegralImageMipMaps method createGRAY8.
@SuppressWarnings({ "unused", "unchecked", "null" })
private static final BufferedImage[] createGRAY8(final Patch patch, final ByteProcessor ip, final ByteProcessor mask) {
final int w = ip.getWidth();
final int h = ip.getHeight();
final int[] dims = new int[] { w, h };
final ScaleAreaAveraging2d<LongType, UnsignedByteType> saai, saam;
{
// Integral of the image
final IntegralImage<UnsignedByteType, LongType> oa = new IntegralImage<UnsignedByteType, LongType>(wrap((byte[]) ip.getPixels(), dims), new LongType(), new IntegerTypeConverter<UnsignedByteType, LongType>());
oa.process();
saai = new ScaleAreaAveraging2d<LongType, UnsignedByteType>(oa.getResult(), new UnsignedByteType(), dims);
// Integral of the mask, if any
if (null != mask) {
final IntegralImage<UnsignedByteType, LongType> ma = new IntegralImage<UnsignedByteType, LongType>(wrap((byte[]) mask.getPixels(), dims), new LongType(), new IntegerTypeConverter<UnsignedByteType, LongType>());
ma.process();
saam = new ScaleAreaAveraging2d<LongType, UnsignedByteType>(ma.getResult(), new UnsignedByteType(), dims);
} else {
saam = null;
}
}
// Generate images
final BufferedImage[] bis = new BufferedImage[Loader.getHighestMipMapLevel(patch) + 1];
//
if (null == saam) {
// mask is null
// Save images as grayscale
// sharing the byte[]
bis[0] = ImageSaver.createGrayImage((byte[]) ip.getPixels(), w, h);
for (int i = 1; i < bis.length; i++) {
final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K;
// An image of the scaled size
saai.setOutputDimensions(wk, hk);
saai.process();
bis[i] = ImageSaver.createGrayImage(((Array<UnsignedByteType, ByteArray>) saai.getResult().getContainer()).update(null).getCurrentStorageArray(), wk, hk);
}
} else {
// Save images as RGBA, where all 3 color channels are the same
bis[0] = ImageSaver.createARGBImage(blend((byte[]) ip.getPixels(), (byte[]) mask.getPixels()), w, h);
for (int i = 1; i < bis.length; i++) {
final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K;
// An image of the scaled size
saai.setOutputDimensions(wk, hk);
saai.process();
// A mask of the scaled size
saam.setOutputDimensions(wk, hk);
saam.process();
//
bis[i] = ImageSaver.createARGBImage(blend(((Array<UnsignedByteType, ByteArray>) saai.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType, ByteArray>) saam.getResult().getContainer()).update(null).getCurrentStorageArray()), wk, hk);
}
}
return bis;
}
Aggregations