use of ini.trakem2.display.Layer in project TrakEM2 by trakem2.
the class Project method findById.
public DBObject findById(final long id) {
if (this.id == id)
return this;
DBObject dbo = layer_set.findById(id);
if (null != dbo)
return dbo;
// could call findObject(id), but all objects must exist in layer sets anyway.
dbo = root_pt.findChild(id);
if (null != dbo)
return dbo;
return (DBObject) root_tt.findChild(id);
}
use of ini.trakem2.display.Layer in project TrakEM2 by trakem2.
the class AreaList method measure.
/**
* Returns a double array with 0=volume, 1=lower_bound_surface, 2=upper_bound_surface_smoothed, 3=upper_bound_surface, 4=max_diameter, 5=all_tops_and_bottoms
* All measures are approximate.
* [0] Volume: sum(area * thickness) for all sections
* [1] Lower Bound Surface: measure area per section, compute radius of circumference of identical area, compute then area of the sides of the truncated cone of height thickness, for each section. Plus top and bottom areas when visiting sections without a painted area.
* [2] Upper Bound Surface Smoothed: measure smoothed perimeter lengths per section, multiply by thickness to get lateral area. Plus tops and bottoms.
* [3] Upper Bound Surface: measure raw pixelated perimeter lengths per section, multiply by thickness to get lateral area. Plus top and bottoms.
* [4] Maximum diameter: longest distance between any two points in the contours of all painted areas.
* [5] All tops and bottoms: Sum of all included surface areas that are not part of side area.
* [6] X coordinate of the center of mass.
* [7] Y coordinate of the center of mass.
* [8] Z coordinate of the center of mass.
*/
public double[] measure() {
// zeros
if (0 == ht_areas.size())
return new double[6];
// prepare suitable transform
final AffineTransform aff = new AffineTransform(this.at);
AffineTransform aff2 = new AffineTransform();
// remove translation (for no reason other than historical, and that it may
// help avoid numerical overflows)
final Rectangle box = getBoundingBox(null);
aff2.translate(-box.x, -box.y);
aff.preConcatenate(aff2);
aff2 = null;
double volume = 0;
double lower_bound_surface_h = 0;
double upper_bound_surface = 0;
double upper_bound_surface_smoothed = 0;
double prev_surface = 0;
double prev_perimeter = 0;
double prev_smooth_perimeter = 0;
double prev_thickness = 0;
// i.e. surface area that is not part of the side area
double all_tops_and_bottoms = 0;
final Calibration cal = layer_set.getCalibration();
final double pixelWidth = cal.pixelWidth;
final double pixelHeight = cal.pixelHeight;
// Put areas in order of their layer index:
final TreeMap<Integer, Area> ias = new TreeMap<Integer, Area>();
for (final Map.Entry<Long, Area> e : ht_areas.entrySet()) {
final int ilayer = layer_set.indexOf(layer_set.getLayer(e.getKey()));
if (-1 == ilayer) {
Utils.log("Could not find a layer with id " + e.getKey());
continue;
}
ias.put(ilayer, e.getValue());
}
final ArrayList<Layer> layers = layer_set.getLayers();
int last_layer_index = -1;
final ArrayList<Point3f> points = new ArrayList<Point3f>();
final float[] coords = new float[6];
final float fpixelWidth = (float) pixelWidth;
final float fpixelHeight = (float) pixelHeight;
final float resampling_delta = project.getProperty("measurement_resampling_delta", 1.0f);
// for each area, measure its area and its perimeter, to compute volume and surface
for (final Map.Entry<Integer, Area> e : ias.entrySet()) {
// fetch Layer
final int layer_index = e.getKey();
if (layer_index > layers.size()) {
Utils.log("Could not find a layer at index " + layer_index);
continue;
}
final Layer la = layers.get(layer_index);
// fetch Area
Area area = e.getValue();
if (UNLOADED == area)
area = loadLayer(la.getId());
// Transform area to world coordinates
area = area.createTransformedArea(aff);
// measure surface
final double pixel_area = Math.abs(AreaCalculations.area(area.getPathIterator(null)));
final double surface = pixel_area * pixelWidth * pixelHeight;
// Utils.log2(layer_index + " pixel_area: " + pixel_area + " surface " + surface);
// measure volume
// the last one is NOT pixelDepth because layer thickness and Z are in pixels
final double thickness = la.getThickness() * pixelWidth;
volume += surface * thickness;
final double pix_perimeter = AreaCalculations.circumference(area.getPathIterator(null));
final double perimeter = pix_perimeter * pixelWidth;
double smooth_perimeter = 0;
// smoothed perimeter:
{
double smooth_pix_perimeter = 0;
for (final Polygon pol : M.getPolygons(area)) {
try {
if (pol.npoints < 5) {
// No point in smoothing out such a short polygon:
// (Plus can't convolve it with a gaussian that needs 5 points adjacent)
smooth_perimeter += new PolygonRoi(pol, PolygonRoi.POLYGON).getLength();
continue;
}
/*
// Works but it is not the best smoothing of the Area's countour
double[] xp = new double[pol.npoints];
double[] yp = new double[pol.npoints];
for (int p=0; p<pol.npoints; p++) {
xp[p] = pol.xpoints[p];
yp[p] = pol.ypoints[p];
}
VectorString2D v = new VectorString2D(xp, yp, 0, true);
v.resample(resampling_delta);
smooth_pix_perimeter += v.length() * resampling_delta;
*/
// The best solution I've found:
// 1. Run getInterpolatedPolygon with an interval of 1 to get a point at every pixel
// 2. convolve with a gaussian
// Resample to 1 so that at every one pixel of the contour there is a point
FloatPolygon fpol = new FloatPolygon(new float[pol.npoints], new float[pol.npoints], pol.npoints);
for (int i = 0; i < pol.npoints; ++i) {
fpol.xpoints[i] = pol.xpoints[i];
fpol.ypoints[i] = pol.ypoints[i];
}
fpol = M.createInterpolatedPolygon(fpol, 1, false);
final FloatPolygon fp;
if (fpol.npoints < 5) {
smooth_pix_perimeter += fpol.getLength(false);
fp = fpol;
} else {
// Convolve with a sigma of 1 to smooth it out
final FloatPolygon gpol = new FloatPolygon(new float[fpol.npoints], new float[fpol.npoints], fpol.npoints);
final CircularSequence seq = new CircularSequence(fpol.npoints);
M.convolveGaussianSigma1(fpol.xpoints, gpol.xpoints, seq);
M.convolveGaussianSigma1(fpol.ypoints, gpol.ypoints, seq);
// Resample it to the desired resolution (also facilitates measurement: npoints * resampling_delta)
if (gpol.npoints > resampling_delta) {
fp = M.createInterpolatedPolygon(gpol, resampling_delta, false);
} else {
fp = gpol;
}
// Measure perimeter: last line segment is potentially shorter or longer than resampling_delta
smooth_pix_perimeter += (fp.npoints - 1) * resampling_delta + Math.sqrt(Math.pow(fp.xpoints[0] - fp.xpoints[fp.npoints - 1], 2) + Math.pow(fp.ypoints[0] - fp.ypoints[fp.npoints - 1], 2));
}
// TEST:
// ij.plugin.frame.RoiManager.getInstance().addRoi(new PolygonRoi(fp, PolygonRoi.POLYGON));
// TESTING: make a polygon roi and show it
// ... just in case to see that resampling works as expected, without weird endings
/*
int[] x = new int[v.length()];
int[] y = new int[x.length];
double[] xd = v.getPoints(0);
double[] yd = v.getPoints(1);
for (int p=0; p<x.length; p++) {
x[p] = (int)xd[p];
y[p] = (int)yd[p];
}
PolygonRoi proi = new PolygonRoi(x, y, x.length, PolygonRoi.POLYGON);
Rectangle b = proi.getBounds();
for (int p=0; p<x.length; p++) {
x[p] -= b.x;
y[p] -= b.y;
}
ImagePlus imp = new ImagePlus("test", new ByteProcessor(b.width, b.height));
imp.setRoi(new PolygonRoi(x, y, x.length, PolygonRoi.POLYGON));
imp.show();
*/
} catch (final Exception le) {
le.printStackTrace();
}
}
smooth_perimeter = smooth_pix_perimeter * pixelWidth;
}
if (-1 == last_layer_index) {
// Start of the very first continuous set:
lower_bound_surface_h += surface;
upper_bound_surface += surface;
upper_bound_surface_smoothed += surface;
all_tops_and_bottoms += surface;
} else if (layer_index - last_layer_index > 1) {
// End of a continuous set ...
// Sum the last surface and its side:
// (2x + 2x) / 2 == 2x
lower_bound_surface_h += prev_surface + prev_thickness * 2 * Math.sqrt(prev_surface * Math.PI);
upper_bound_surface += prev_surface + prev_perimeter * prev_thickness;
upper_bound_surface_smoothed += prev_surface + prev_smooth_perimeter * prev_thickness;
all_tops_and_bottoms += prev_surface;
// ... and start of a new set
lower_bound_surface_h += surface;
upper_bound_surface += surface;
upper_bound_surface_smoothed += surface;
all_tops_and_bottoms += surface;
} else {
// Continuation of a set: use this Area and the previous as continuous
final double diff_surface = Math.abs(prev_surface - surface);
upper_bound_surface += prev_perimeter * (prev_thickness / 2) + perimeter * (prev_thickness / 2) + diff_surface;
upper_bound_surface_smoothed += prev_smooth_perimeter * (prev_thickness / 2) + smooth_perimeter * (prev_thickness / 2) + diff_surface;
// Compute area of the mantle of the truncated cone defined by the radiuses of the circles of same area as the two areas
// PI * s * (r1 + r2) where s is the hypothenusa
final double r1 = Math.sqrt(prev_surface / Math.PI);
final double r2 = Math.sqrt(surface / Math.PI);
final double hypothenusa = Math.sqrt(Math.pow(Math.abs(r1 - r2), 2) + Math.pow(thickness, 2));
lower_bound_surface_h += Math.PI * hypothenusa * (r1 + r2);
// Adjust volume too:
volume += diff_surface * prev_thickness / 2;
}
// store for next iteration:
prev_surface = surface;
prev_perimeter = perimeter;
prev_smooth_perimeter = smooth_perimeter;
last_layer_index = layer_index;
prev_thickness = thickness;
// Iterate points:
final float z = (float) la.getZ();
for (final PathIterator pit = area.getPathIterator(null); !pit.isDone(); pit.next()) {
switch(pit.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
case PathIterator.SEG_CLOSE:
points.add(new Point3f(coords[0] * fpixelWidth, coords[1] * fpixelHeight, z * fpixelWidth));
break;
default:
Utils.log2("WARNING: unhandled seg type.");
break;
}
}
}
// finish last:
lower_bound_surface_h += prev_surface + prev_perimeter * prev_thickness;
upper_bound_surface += prev_surface + prev_perimeter * prev_thickness;
upper_bound_surface_smoothed += prev_surface + prev_smooth_perimeter * prev_thickness;
all_tops_and_bottoms += prev_surface;
// Compute maximum diameter
final boolean measure_largest_diameter = project.getBooleanProperty("measure_largest_diameter");
double max_diameter_sq = measure_largest_diameter ? 0 : Double.NaN;
final int lp = points.size();
final Point3f c;
if (lp > 0) {
// center of mass
c = new Point3f(points.get(0));
for (int i = 0; i < lp; i++) {
final Point3f p = points.get(i);
if (measure_largest_diameter) {
for (int j = i; j < lp; j++) {
final double len = p.distanceSquared(points.get(j));
if (len > max_diameter_sq)
max_diameter_sq = len;
}
}
if (0 == i)
continue;
c.x += p.x;
c.y += p.y;
c.z += p.z;
}
} else {
c = new Point3f(Float.NaN, Float.NaN, Float.NaN);
}
// Translate the center of mass
c.x = box.x + c.x / lp;
c.y = box.y + c.y / lp;
c.z /= lp;
return new double[] { volume, lower_bound_surface_h, upper_bound_surface_smoothed, upper_bound_surface, Math.sqrt(max_diameter_sq), all_tops_and_bottoms, c.x, c.y, c.z };
}
use of ini.trakem2.display.Layer in project TrakEM2 by trakem2.
the class AreaList method exportAsLabels.
/**
* Export all given AreaLists as one per pixel value, what is called a "labels" file; a file dialog is offered to save the image as a tiff stack.
*/
public static void exportAsLabels(final List<Displayable> listToPaint, final ij.gui.Roi roi, final float scale, int first_layer, int last_layer, final boolean visible_only, final boolean to_file, final boolean as_amira_labels) {
// survive everything:
if (null == listToPaint || 0 == listToPaint.size()) {
Utils.log("Null or empty list.");
return;
}
if (scale < 0 || scale > 1) {
Utils.log("Improper scale value. Must be 0 < scale <= 1");
return;
}
// Select the subset to paint
final ArrayList<AreaList> list = new ArrayList<AreaList>();
for (final Displayable d : listToPaint) {
if (visible_only && !d.isVisible())
continue;
if (d instanceof AreaList)
list.add((AreaList) d);
}
Utils.log2("exportAsLabels: list.size() is " + list.size());
// Current AmiraMeshEncoder supports ByteProcessor only: 256 labels max, including background at zero.
if (as_amira_labels && list.size() > 255) {
Utils.log("Saving ONLY first 255 AreaLists!\nDiscarded:");
final StringBuilder sb = new StringBuilder();
for (final Displayable d : list.subList(255, list.size())) {
sb.append(" ").append(d.getProject().getShortMeaningfulTitle(d)).append('\n');
}
Utils.log(sb.toString());
final ArrayList<AreaList> li = new ArrayList<AreaList>(list);
list.clear();
list.addAll(li.subList(0, 255));
}
String path = null;
if (to_file) {
final String ext = as_amira_labels ? ".am" : ".tif";
final File f = Utils.chooseFile("labels", ext);
if (null == f)
return;
path = f.getAbsolutePath().replace('\\', '/');
}
final LayerSet layer_set = list.get(0).getLayerSet();
if (first_layer > last_layer) {
final int tmp = first_layer;
first_layer = last_layer;
last_layer = tmp;
if (first_layer < 0)
first_layer = 0;
if (last_layer >= layer_set.size())
last_layer = layer_set.size() - 1;
}
// Create image according to roi and scale
final int width, height;
final Rectangle broi;
if (null == roi) {
broi = null;
width = (int) (layer_set.getLayerWidth() * scale);
height = (int) (layer_set.getLayerHeight() * scale);
} else {
broi = roi.getBounds();
width = (int) (broi.width * scale);
height = (int) (broi.height * scale);
}
// Compute highest label value, which affects of course the stack image type
final TreeSet<Integer> label_values = new TreeSet<Integer>();
for (final Displayable d : list) {
final String label = d.getProperty("label");
if (null != label)
label_values.add(Integer.parseInt(label));
}
int lowest = 0, highest = 0;
if (label_values.size() > 0) {
lowest = label_values.first();
highest = label_values.last();
}
final int n_non_labeled = list.size() - label_values.size();
final int max_label_value = highest + n_non_labeled;
int type_ = ImagePlus.GRAY8;
if (max_label_value > 255) {
type_ = ImagePlus.GRAY16;
if (max_label_value > 65535) {
type_ = ImagePlus.GRAY32;
}
}
final int type = type_;
final ImageStack stack = new ImageStack(width, height);
final Calibration cal = layer_set.getCalibration();
String amira_params = null;
if (as_amira_labels) {
final StringBuilder sb = new StringBuilder("CoordType \"uniform\"\nMaterials {\nExterior {\n Id 0,\nColor 0 0 0\n}\n");
final float[] c = new float[3];
int value = 0;
for (final Displayable d : list) {
// 0 is background
value++;
d.getColor().getRGBColorComponents(c);
String s = d.getProject().getShortMeaningfulTitle(d);
s = s.replace('-', '_').replaceAll(" #", " id");
sb.append(Utils.makeValidIdentifier(s)).append(" {\n").append("Id ").append(value).append(",\n").append("Color ").append(c[0]).append(' ').append(c[1]).append(' ').append(c[2]).append("\n}\n");
}
sb.append("}\n");
amira_params = sb.toString();
}
final float len = last_layer - first_layer + 1;
// Assign labels
final HashMap<AreaList, Integer> labels = new HashMap<AreaList, Integer>();
for (final AreaList d : list) {
final String slabel = d.getProperty("label");
int label;
if (null != slabel) {
label = Integer.parseInt(slabel);
} else {
// 0 is background
label = (++highest);
}
labels.put(d, label);
}
final ExecutorService exec = Utils.newFixedThreadPool("labels");
final Map<Integer, ImageProcessor> slices = Collections.synchronizedMap(new TreeMap<Integer, ImageProcessor>());
final List<Future<?>> fus = new ArrayList<Future<?>>();
final List<Layer> layers = layer_set.getLayers().subList(first_layer, last_layer + 1);
for (int k = 0; k < layers.size(); k++) {
final Layer la = layers.get(k);
final int slice = k;
fus.add(exec.submit(new Runnable() {
@Override
public void run() {
Utils.showProgress(slice / len);
final ImageProcessor ip;
if (ImagePlus.GRAY8 == type) {
final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
final Graphics2D g = bi.createGraphics();
for (final AreaList ali : list) {
final Area area = ali.getArea(la);
if (null == area || area.isEmpty())
continue;
// Transform: the scale and the roi
final AffineTransform aff = new AffineTransform();
/* 3 - To scale: */
if (1 != scale)
aff.scale(scale, scale);
/* 2 - To roi coordinates: */
if (null != broi)
aff.translate(-broi.x, -broi.y);
/* 1 - To world coordinates: */
aff.concatenate(ali.at);
g.setTransform(aff);
final int label = labels.get(ali);
g.setColor(new Color(label, label, label));
g.fill(area);
}
g.dispose();
ip = new ByteProcessor(bi);
bi.flush();
} else if (ImagePlus.GRAY16 == type) {
final USHORTPaint paint = new USHORTPaint((short) 0);
final BufferedImage bi = new BufferedImage(paint.getComponentColorModel(), paint.getComponentColorModel().createCompatibleWritableRaster(width, height), false, null);
final Graphics2D g = bi.createGraphics();
// final ColorSpace ugray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
int painted = 0;
for (final AreaList ali : list) {
final Area area = ali.getArea(la);
if (null == area || area.isEmpty())
continue;
// Transform: the scale and the roi
final AffineTransform aff = new AffineTransform();
/* 3 - To scale: */
if (1 != scale)
aff.scale(scale, scale);
/* 2 - To roi coordinates: */
if (null != broi)
aff.translate(-broi.x, -broi.y);
/* 1 - To world coordinates: */
aff.concatenate(ali.at);
// Fill
g.setTransform(aff);
// The color doesn't work: paints in a stretched 8-bit mode
// g.setColor(new Color(ugray, new float[]{((float)labels.get(d)) / range}, 1));
Utils.log2("value: " + labels.get(ali).shortValue());
paint.setValue(labels.get(ali).shortValue());
g.setPaint(paint);
// .createTransformedArea(aff));
g.fill(area);
painted += 1;
}
g.dispose();
ip = new ShortProcessor(bi);
bi.flush();
Utils.log2("painted: " + painted);
} else {
// Option 1: could use the same as above, but shifted by 65536, so that 65537 is 1, 65538 is 2, etc.
// and keep doing it until no more need to be shifted.
// The PROBLEM: cannot keep the order without complicated gymnastics to remember
// which label in which image has to be merged to the final image, which prevent
// a simple one-pass blitter.
//
// Option 2: paint each arealist, extract the image, use it as a mask for filling:
final FloatProcessor fp = new FloatProcessor(width, height);
final float[] fpix = (float[]) fp.getPixels();
ip = fp;
final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
final Graphics2D gbi = bi.createGraphics();
for (final AreaList ali : list) {
final Area area = ali.getArea(la);
if (null == area || area.isEmpty()) {
continue;
}
// Transform: the scale and the roi
// reverse order of transformations:
final AffineTransform aff = new AffineTransform();
/* 3 - To scale: */
if (1 != scale)
aff.scale(scale, scale);
/* 2 - To ROI coordinates: */
if (null != broi)
aff.translate(-broi.x, -broi.y);
/* 1 - To world coordinates: */
aff.concatenate(ali.at);
final Area s = area.createTransformedArea(aff);
final Rectangle sBounds = s.getBounds();
// Need to paint at all?
if (0 == sBounds.width || 0 == sBounds.height || !sBounds.intersects(0, 0, width, height))
continue;
// Paint shape
gbi.setColor(Color.white);
gbi.fill(s);
// Read out painted region
final int x0 = Math.max(0, sBounds.x);
final int y0 = Math.max(0, sBounds.y);
final int xN = Math.min(width, sBounds.x + sBounds.width);
final int yN = Math.min(height, sBounds.y + sBounds.height);
// Get the array
final byte[] bpix = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
final float value = labels.get(ali);
// For every non-black pixel, set a 'value' pixel in the FloatProcessor
for (int y = y0; y < yN; ++y) {
for (int x = x0; x < xN; ++x) {
final int pos = y * width + x;
// black
if (0 == bpix[pos])
continue;
fpix[pos] = value;
}
}
// Clear image region
gbi.setColor(Color.black);
gbi.fill(s);
}
gbi.dispose();
bi.flush();
}
slices.put(slice, ip);
}
}));
}
Utils.wait(fus);
exec.shutdownNow();
for (final Map.Entry<Integer, ImageProcessor> e : slices.entrySet()) {
final Layer la = layers.get(e.getKey());
stack.addSlice(la.getZ() * cal.pixelWidth + "", e.getValue());
if (ImagePlus.GRAY8 != type) {
e.getValue().setMinAndMax(lowest, highest);
}
}
Utils.showProgress(1);
// Save via file dialog:
final ImagePlus imp = new ImagePlus("Labels", stack);
if (as_amira_labels)
imp.setProperty("Info", amira_params);
imp.setCalibration(layer_set.getCalibrationCopy());
if (to_file) {
if (as_amira_labels) {
final AmiraMeshEncoder ame = new AmiraMeshEncoder(path);
if (!ame.open()) {
Utils.log("Could not write to file " + path);
return;
}
if (!ame.write(imp)) {
Utils.log("Error in writing Amira file!");
return;
}
} else {
new FileSaver(imp).saveAsTiff(path);
}
} else
imp.show();
}
use of ini.trakem2.display.Layer in project TrakEM2 by trakem2.
the class DisplayCanvas method keyPressed.
@Override
public void keyPressed(final KeyEvent ke) {
final Displayable active = display.getActive();
if (null != freehandProfile && ProjectToolbar.getToolId() == ProjectToolbar.PENCIL && ke.getKeyCode() == KeyEvent.VK_ESCAPE && null != freehandProfile) {
freehandProfile.abort();
ke.consume();
return;
}
final int keyCode = ke.getKeyCode();
try {
// Enable tagging system for any alphanumeric key:
if (!input_disabled && null != active && active instanceof Tree<?> && ProjectToolbar.isDataEditTool(ProjectToolbar.getToolId())) {
if (tagging) {
if (KeyEvent.VK_0 == keyCode && KeyEvent.VK_0 != last_keyCode) {
// do nothing: keep tagging as true
} else {
// last step of tagging: a char after t or after t and a number (and the char itself can be a number)
tagging = false;
}
active.keyPressed(ke);
return;
} else if (KeyEvent.VK_T == keyCode) {
tagging = true;
active.keyPressed(ke);
return;
}
}
} finally {
last_keyCode = keyCode;
}
tagging = false;
if (ke.isConsumed())
return;
if (!zoom_and_pan) {
if (KeyEvent.VK_ESCAPE == keyCode) {
cancelAnimations();
}
return;
}
final int keyChar = ke.getKeyChar();
boolean used = false;
switch(keyChar) {
case '+':
case '=':
zoomIn();
used = true;
break;
case '-':
case '_':
zoomOut();
used = true;
break;
default:
break;
}
if (used) {
// otherwise ImageJ would use it!
ke.consume();
return;
}
if (input_disabled) {
if (KeyEvent.VK_ESCAPE == keyCode) {
// cancel last job if any
if (Utils.checkYN("Really cancel job?")) {
display.getProject().getLoader().quitJob(null);
display.repairGUI();
}
}
ke.consume();
// only zoom is enabled, above
return;
}
if (KeyEvent.VK_S == keyCode && 0 == ke.getModifiers() && display.getProject().getLoader().isAsynchronous()) {
display.getProject().getLoader().saveTask(display.getProject(), "Save");
ke.consume();
return;
} else if (KeyEvent.VK_F == keyCode && Utils.isControlDown(ke)) {
Search.showWindow();
ke.consume();
return;
}
// if display is not read-only, check for other keys:
switch(keyChar) {
case '<':
case // select next Layer up
',':
// repaints as well
display.previousLayer(ke.getModifiers());
ke.consume();
return;
case '>':
case // select next Layer down
'.':
display.nextLayer(ke.getModifiers());
ke.consume();
return;
}
if (null == active && null != imp.getRoi() && KeyEvent.VK_A != keyCode) {
// control+a and a roi should select under roi
IJ.getInstance().keyPressed(ke);
return;
}
// end here if display is read-only
if (display.isReadOnly()) {
ke.consume();
display.repaintAll();
return;
}
if (KeyEvent.VK_ENTER == keyCode) {
if (isTransforming()) {
applyTransform();
ke.consume();
return;
} else {
IJ.getInstance().toFront();
ke.consume();
return;
}
}
// check preconditions (or the keys are meaningless). Allow 'enter' to
// bring forward the ImageJ window, and 'v' to paste a patch.
/*if (null == active && KeyEvent.VK_ENTER != keyCode && KeyEvent.VK_V != keyCode && KeyEvent) {
return;
}*/
final Layer layer = display.getLayer();
final int mod = ke.getModifiers();
switch(keyCode) {
case KeyEvent.VK_COMMA:
case // select next Layer up
0xbc:
display.nextLayer(ke.getModifiers());
break;
case KeyEvent.VK_PERIOD:
case // select next Layer down
0xbe:
display.previousLayer(ke.getModifiers());
break;
case KeyEvent.VK_Z:
// UNDO: shift+z or ctrl+z
if (0 == (mod ^ Event.SHIFT_MASK) || 0 == (mod ^ Utils.getControlModifier())) {
Bureaucrat.createAndStart(new Worker.Task("Undo") {
@Override
public void exec() {
if (isTransforming())
display.getMode().undoOneStep();
else
display.getLayerSet().undoOneStep();
Display.repaint(display.getLayerSet());
}
}, display.getProject());
ke.consume();
// REDO: alt+z or ctrl+shift+z
} else if (0 == (mod ^ Event.ALT_MASK) || 0 == (mod ^ (Event.SHIFT_MASK | Utils.getControlModifier()))) {
Bureaucrat.createAndStart(new Worker.Task("Redo") {
@Override
public void exec() {
if (isTransforming())
display.getMode().redoOneStep();
else
display.getLayerSet().redoOneStep();
Display.repaint(display.getLayerSet());
}
}, display.getProject());
ke.consume();
}
// else, the 'z' command restores the image using ImageJ internal undo
break;
case KeyEvent.VK_T:
// Enable with any tool to the left of the PENCIL
if (null != active && !isTransforming() && ProjectToolbar.getToolId() < ProjectToolbar.PENCIL) {
ProjectToolbar.setTool(ProjectToolbar.SELECT);
if (0 == ke.getModifiers()) {
display.setMode(new AffineTransformMode(display));
} else if (Event.SHIFT_MASK == ke.getModifiers()) {
for (final Displayable d : display.getSelection().getSelected()) {
if (d.isLinked()) {
Utils.showMessage("Can't enter manual non-linear transformation mode:\nat least one image is linked.");
return;
}
}
display.setMode(new NonLinearTransformMode(display));
}
ke.consume();
}
// else, let ImageJ grab the ROI into the Manager, if any
break;
case KeyEvent.VK_A:
if (0 == (ke.getModifiers() ^ Utils.getControlModifier())) {
final Roi roi = getFakeImagePlus().getRoi();
if (null != roi)
display.getSelection().selectAll(roi, true);
else
display.getSelection().selectAllVisible();
Display.repaint(display.getLayer(), display.getSelection().getBox(), 0);
ke.consume();
// INSIDE the 'if' block, so that it can bleed to the default block which forwards to active!
break;
} else if (null != active) {
active.keyPressed(ke);
if (ke.isConsumed())
break;
// TODO this is just a hack really. Should just fall back to default switch option.
// The whole keyPressed method needs revision: should not break from it when not using the key.
}
break;
case // cancel transformation
KeyEvent.VK_ESCAPE:
if (isTransforming())
cancelTransform();
else {
// deselect
display.select(null);
// repaint out the brush if present
if (ProjectToolbar.BRUSH == ProjectToolbar.getToolId()) {
repaint(old_brush_box, 0);
}
}
ke.consume();
break;
case KeyEvent.VK_SPACE:
if (0 == ke.getModifiers()) {
if (null != active) {
invalidateVolatile();
if (Math.abs(active.getAlpha() - 0.5f) > 0.001f)
active.setAlpha(0.5f);
else
active.setAlpha(1.0f);
display.setTransparencySlider(active.getAlpha());
Display.repaint();
ke.consume();
}
} else {
// ;)
final int kem = ke.getModifiers();
if (0 != (kem & KeyEvent.SHIFT_MASK) && 0 != (kem & KeyEvent.ALT_MASK) && 0 != (kem & KeyEvent.CTRL_MASK)) {
Utils.showMessage("A mathematician, like a painter or poet,\nis a maker of patterns.\nIf his patterns are more permanent than theirs,\nit is because they are made with ideas\n \nG. H. Hardy.");
ke.consume();
}
}
break;
case KeyEvent.VK_S:
if (ke.isAltDown()) {
snapping = true;
ke.consume();
} else if (dragging) {
// ignore improper 's' that open ImageJ's save dialog (linux problem ... in macosx, a single dialog opens with lots of 'ssss...' in the text field)
ke.consume();
}
break;
case KeyEvent.VK_H:
handleHide(ke);
ke.consume();
break;
case KeyEvent.VK_J:
if (!display.getSelection().isEmpty()) {
display.adjustMinAndMaxGUI();
ke.consume();
}
break;
case KeyEvent.VK_F1:
case KeyEvent.VK_F2:
case KeyEvent.VK_F3:
case KeyEvent.VK_F4:
case KeyEvent.VK_F5:
case KeyEvent.VK_F6:
case KeyEvent.VK_F7:
case KeyEvent.VK_F8:
case KeyEvent.VK_F9:
case KeyEvent.VK_F10:
case KeyEvent.VK_F11:
case KeyEvent.VK_F12:
ProjectToolbar.keyPressed(ke);
ke.consume();
break;
case KeyEvent.VK_M:
if (0 == ke.getModifiers() && ProjectToolbar.getToolId() == ProjectToolbar.SELECT) {
display.getSelection().measure();
ke.consume();
}
break;
}
switch(keyChar) {
case ':':
case ';':
if (null != active && active instanceof ZDisplayable) {
if (null != display.getProject().getProjectTree().tryAddNewConnector(active, true)) {
ProjectToolbar.setTool(ProjectToolbar.PEN);
}
ke.consume();
}
break;
}
if (ke.isConsumed())
return;
if (null != active) {
if (display.getMode().getClass() == DefaultMode.class) {
active.keyPressed(ke);
}
if (ke.isConsumed())
return;
}
// Else:
switch(keyCode) {
case KeyEvent.VK_G:
if (browseToNodeLayer(ke.isShiftDown())) {
ke.consume();
}
break;
case KeyEvent.VK_I:
if (ke.isAltDown()) {
if (ke.isShiftDown())
display.importImage();
else
display.importNextImage();
ke.consume();
}
break;
case // as in Inkscape
KeyEvent.VK_PAGE_UP:
if (null != active) {
update_graphics = true;
layer.getParent().addUndoMoveStep(active);
layer.getParent().move(LayerSet.UP, active);
layer.getParent().addUndoMoveStep(active);
Display.repaint(layer, active, 5);
Display.updatePanelIndex(layer, active);
ke.consume();
}
break;
case // as in Inkscape
KeyEvent.VK_PAGE_DOWN:
if (null != active) {
update_graphics = true;
layer.getParent().addUndoMoveStep(active);
layer.getParent().move(LayerSet.DOWN, active);
layer.getParent().addUndoMoveStep(active);
Display.repaint(layer, active, 5);
Display.updatePanelIndex(layer, active);
ke.consume();
}
break;
case // as in Inkscape
KeyEvent.VK_HOME:
if (null != active) {
update_graphics = true;
layer.getParent().addUndoMoveStep(active);
layer.getParent().move(LayerSet.TOP, active);
layer.getParent().addUndoMoveStep(active);
Display.repaint(layer, active, 5);
Display.updatePanelIndex(layer, active);
ke.consume();
}
break;
case // as in Inkscape
KeyEvent.VK_END:
if (null != active) {
update_graphics = true;
layer.getParent().addUndoMoveStep(active);
layer.getParent().move(LayerSet.BOTTOM, active);
layer.getParent().addUndoMoveStep(active);
Display.repaint(layer, active, 5);
Display.updatePanelIndex(layer, active);
ke.consume();
}
break;
case KeyEvent.VK_V:
if (0 == ke.getModifiers()) {
if (null == active || active.getClass() == Patch.class) {
// paste a new image
final ImagePlus clipboard = ImagePlus.getClipboard();
if (null != clipboard) {
final ImagePlus imp = new ImagePlus(clipboard.getTitle() + "_" + System.currentTimeMillis(), clipboard.getProcessor().crop());
final Object info = clipboard.getProperty("Info");
if (null != info)
imp.setProperty("Info", (String) info);
final double x = srcRect.x + srcRect.width / 2 - imp.getWidth() / 2;
final double y = srcRect.y + srcRect.height / 2 - imp.getHeight() / 2;
// save the image somewhere:
final Patch pa = display.getProject().getLoader().addNewImage(imp, x, y);
display.getLayer().add(pa);
ke.consume();
}
// TODO there isn't much ImageJ integration in the pasting. Can't paste to a selected image, for example.
} else {
// Each type may know how to paste data from the copy buffer into itself:
active.keyPressed(ke);
ke.consume();
}
}
break;
case KeyEvent.VK_P:
if (0 == ke.getModifiers()) {
display.getLayerSet().color_cues = !display.getLayerSet().color_cues;
Display.repaint(display.getLayerSet());
ke.consume();
}
break;
case KeyEvent.VK_F:
if (0 == (ke.getModifiers() ^ KeyEvent.SHIFT_MASK)) {
// toggle visibility of tags
display.getLayerSet().paint_tags = !display.getLayerSet().paint_tags;
Display.repaint();
ke.consume();
} else if (0 == (ke.getModifiers() ^ KeyEvent.ALT_MASK)) {
// toggle visibility of edge arrows
display.getLayerSet().paint_arrows = !display.getLayerSet().paint_arrows;
Display.repaint();
ke.consume();
} else if (0 == ke.getModifiers()) {
// toggle visibility of edge confidence boxes
display.getLayerSet().paint_edge_confidence_boxes = !display.getLayerSet().paint_edge_confidence_boxes;
Display.repaint();
ke.consume();
}
break;
case KeyEvent.VK_DELETE:
if (0 == ke.getModifiers()) {
display.getSelection().deleteAll();
}
break;
case KeyEvent.VK_B:
if (0 == ke.getModifiers() && null != active && active.getClass() == Profile.class) {
display.duplicateLinkAndSendTo(active, 0, active.getLayer().getParent().previous(layer));
ke.consume();
}
break;
case KeyEvent.VK_N:
if (0 == ke.getModifiers() && null != active && active.getClass() == Profile.class) {
display.duplicateLinkAndSendTo(active, 1, active.getLayer().getParent().next(layer));
ke.consume();
}
break;
case KeyEvent.VK_1:
case KeyEvent.VK_2:
case KeyEvent.VK_3:
case KeyEvent.VK_4:
case KeyEvent.VK_5:
case KeyEvent.VK_6:
case KeyEvent.VK_7:
case KeyEvent.VK_8:
case KeyEvent.VK_9:
// run a plugin, if any
if (null != Utils.launchTPlugIn(ke, "Display", display.getProject(), display.getActive())) {
ke.consume();
break;
}
}
if (!(keyCode == KeyEvent.VK_UNDEFINED || keyChar == KeyEvent.CHAR_UNDEFINED) && !ke.isConsumed() && null != active && active instanceof Patch) {
// TODO should allow forwarding for all, not just Patch
// forward to ImageJ for a final try
IJ.getInstance().keyPressed(ke);
repaint(active, 5);
ke.consume();
}
// Utils.log2("keyCode, keyChar: " + keyCode + ", " + keyChar + " ref: " + KeyEvent.VK_UNDEFINED + ", " + KeyEvent.CHAR_UNDEFINED);
}
use of ini.trakem2.display.Layer in project TrakEM2 by trakem2.
the class Display method importNextImage.
protected Bureaucrat importNextImage() {
final Worker worker = new // / all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
Worker(// / all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
"Import image") {
@Override
public void run() {
startedWorking();
try {
final Rectangle srcRect = canvas.getSrcRect();
// - imp.getWidth() / 2;
final int x = srcRect.x + srcRect.width / 2;
// - imp.getHeight()/ 2;
final int y = srcRect.y + srcRect.height / 2;
final Patch p = project.getLoader().importNextImage(project, x, y);
if (null == p) {
Utils.showMessage("Could not open next image.");
finishedWorking();
return;
}
Display.this.getLayerSet().addLayerContentStep(layer);
// will add it to the proper Displays
layer.add(p);
Display.this.getLayerSet().addLayerContentStep(layer);
} catch (final Exception e) {
IJError.print(e);
}
finishedWorking();
}
};
return Bureaucrat.createAndStart(worker, getProject());
}
Aggregations