/**
TrakEM2 plugin for ImageJ(C).
Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
You may contact Albert Cardona at acardona at ini.phys.ethz.ch
Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
**/
package ini.trakem2.persistence;
import ij.process.ByteProcessor;
import ini.trakem2.Project;
import ini.trakem2.display.AreaList;
import ini.trakem2.display.AreaTree;
import ini.trakem2.display.Ball;
import ini.trakem2.display.Connector;
import ini.trakem2.display.DLabel;
import ini.trakem2.display.Display;
import ini.trakem2.display.Displayable;
import ini.trakem2.display.Dissector;
import ini.trakem2.display.Layer;
import ini.trakem2.display.LayerSet;
import ini.trakem2.display.Node;
import ini.trakem2.display.Patch;
import ini.trakem2.display.Pipe;
import ini.trakem2.display.Polyline;
import ini.trakem2.display.Profile;
import ini.trakem2.display.Stack;
import ini.trakem2.display.Tag;
import ini.trakem2.display.Taggable;
import ini.trakem2.display.Tree;
import ini.trakem2.display.Treeline;
import ini.trakem2.display.ZDisplayable;
import ini.trakem2.imaging.filters.IFilter;
import ini.trakem2.tree.DTDParser;
import ini.trakem2.tree.LayerThing;
import ini.trakem2.tree.ProjectThing;
import ini.trakem2.tree.TemplateThing;
import ini.trakem2.tree.Thing;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.ReconstructArea;
import ini.trakem2.utils.Utils;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import mpicbg.models.TransformList;
import mpicbg.trakem2.transform.CoordinateTransform;
import mpicbg.trakem2.transform.CoordinateTransformList;
import mpicbg.trakem2.transform.InvertibleCoordinateTransform;
import mpicbg.trakem2.transform.InvertibleCoordinateTransformList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/** Creates the project objects from an XML file (TrakEM2 Markup Language Handler). */
public class TMLHandler extends DefaultHandler {
private LayerThing root_lt = null;
private ProjectThing root_pt = null;
private TemplateThing root_tt = null;
private TemplateThing project_tt = null;
private TemplateThing template_layer_thing = null;
private TemplateThing template_layer_set_thing = null;
private Project project = null;
private final FSLoader loader;
private boolean skip = false;
//private String base_dir;
//private String xml_path;
/** Stores the object and its String with coma-separated links, to construct links when all objects exist. */
final private HashMap<Displayable,String> ht_links = new HashMap<Displayable,String>();
final private ArrayList<Thing> al_open = new ArrayList<Thing>();
final private ArrayList<Layer> al_layers = new ArrayList<Layer>();
final private ArrayList<LayerSet> al_layer_sets = new ArrayList<LayerSet>();
final private ArrayList<HashMap<String,String>> al_displays = new ArrayList<HashMap<String,String>>(); // contains HashMap instances with display data.
/** To accumulate Displayable types for relinking and assigning their proper layer. */
final private HashMap<Long,Displayable> ht_displayables = new HashMap<Long,Displayable>();
final private HashMap<Long,ZDisplayable> ht_zdispl = new HashMap<Long,ZDisplayable>();
final private HashMap<Long,ProjectThing> ht_oid_pt = new HashMap<Long,ProjectThing>();
final private HashMap<ProjectThing,Boolean> ht_pt_expanded = new HashMap<ProjectThing,Boolean>();
private Ball last_ball = null;
private AreaList last_area_list = null;
private long last_area_list_layer_id = -1;
private Dissector last_dissector = null;
private Stack last_stack = null;
private Patch last_patch = null;
private CoordinateTransform last_ct = null;
private InvertibleCoordinateTransform last_ict = null;
private final ArrayList<IFilter> last_patch_filters = new ArrayList<IFilter>();
private Treeline last_treeline = null;
private AreaTree last_areatree = null;
private Connector last_connector = null;
private Tree<?> last_tree = null;
final private LinkedList<Taggable> taggables = new LinkedList<Taggable>();
private ReconstructArea reca = null;
private Node<?> last_root_node = null;
final private LinkedList<Node<?>> nodes = new LinkedList<Node<?>>();
final private Map<Long,List<Node<?>>> node_layer_table = new HashMap<Long,List<Node<?>>>();
final private Map<Tree<?>,Node<?>> tree_root_nodes = new HashMap<Tree<?>,Node<?>>();
final private Map<Color,Collection<Node<?>>> node_colors = new HashMap<Color,Collection<Node<?>>>();
private StringBuilder last_treeline_data = null;
private Displayable last_displayable = null;
private StringBuilder last_annotation = null;
final private ArrayList< TransformList< Object > > ct_list_stack = new ArrayList< TransformList< Object > >();
private boolean open_displays = true;
final private LinkedList<Runnable> legacy = new LinkedList<Runnable>();
/** @param path The XML file that contains the project data in XML format.
* @param loader The FSLoader for the project.
* Expects the path with '/' as folder separator char.
*/
public TMLHandler(final String path, FSLoader loader) {
this.loader = loader;
//this.base_dir = path.substring(0, path.lastIndexOf('/') + 1); // not File.separatorChar: TrakEM2 uses '/' always
//this.xml_path = path;
final TemplateThing[] tt;
try {
tt = DTDParser.extractTemplate(path);
if (null == tt) {
Utils.log("TMLHandler: can't read DTD in file " + path);
loader = null;
return;
}
} catch (Exception e) {
loader = null;
IJError.print(e);
return;
}
/*
// debug:
Utils.log2("ROOTS #####################");
for (int k=0; k < tt.length; k++) {
Utils.log2("tt root " + k + ": " + tt[k]);
}
*/
this.root_tt = tt[0];
// There should only be one root. There may be more than one
// when objects in the DTD are declared but do not exist in a
// TemplateThing hierarchy, such as ict_* and iict_*.
// Find the first proper root:
if (tt.length > 1) {
final Pattern icts = Pattern.compile("^i{1,2}ct_transform.*$");
this.root_tt = null;
for (int k=0; k<tt.length; k++) {
if (icts.matcher(tt[k].getType()).matches()) {
continue;
}
this.root_tt = tt[k];
break;
}
}
// TODO the above should be a better filtering rule in the DTDParser.
// create LayerThing templates
this.template_layer_thing = new TemplateThing("layer");
this.template_layer_set_thing = new TemplateThing("layer set");
this.template_layer_set_thing.addChild(this.template_layer_thing);
this.template_layer_thing.addChild(this.template_layer_set_thing);
// create Project template
this.project_tt = new TemplateThing("project");
project_tt.addChild(this.root_tt);
//TODO//project_tt.addAttribute("title", "Project");
}
public boolean isUnreadable() {
return null == loader;
}
/** returns 4 objects packed in an array:
<pre>
[0] = root TemplateThing
[1] = root ProjectThing (contains Project instance)
[2] = root LayerThing (contains the top-level LayerSet)
[3] = expanded states of all ProjectThing objects
</pre>
* <p>
* Also, triggers the reconstruction of links and assignment of Displayable objects to their layer.
* </p>
*/
public Object[] getProjectData(final boolean open_displays) {
if (null == project) return null;
this.open_displays = open_displays;
// 1 - Reconstruct links using ht_links
// Links exist between Displayable objects.
for (final Displayable d : ht_displayables.values()) {
String olinks = ht_links.get(d);
if (null == olinks) continue; // not linked
String[] links = olinks.split(",");
Long lid = null;
for (int i=0; i<links.length; i++) {
try {
lid = new Long(links[i]);
} catch (NumberFormatException nfe) {
Utils.log2("Ignoring incorrectly formated link '" + links[i] + "' for ob " + d);
continue;
}
Displayable partner = ht_displayables.get(lid);
if (null != partner) d.link(partner, false);
else Utils.log("TMLHandler: can't find partner with id=" + links[i] + " for Displayable with id=" + d.getId());
}
}
// 1.2 - Reconstruct linked properties
for (final Map.Entry<Displayable,Map<Long,Map<String,String>>> lpe : all_linked_props.entrySet()) {
final Displayable origin = lpe.getKey();
for (final Map.Entry<Long,Map<String,String>> e : lpe.getValue().entrySet()) {
final Displayable target = ht_displayables.get(e.getKey());
if (null == target) {
Utils.log("Setting linked properties for origin " + origin.getId() + ":\n\t* Could not find target displayable #" + e.getKey());
continue;
}
origin.setLinkedProperties(target, e.getValue());
}
}
// 2 - Add Displayable objects to ProjectThing that can contain them
for (final Map.Entry<Long,ProjectThing> entry : ht_oid_pt.entrySet()) {
ProjectThing pt = entry.getValue();
Object od = ht_displayables.remove(entry.getKey());
//Utils.log("==== processing: Displayable [" + od + "] vs. ProjectThing [" + pt + "]");
if (null != od) {
pt.setObject(od);
} else {
Utils.log("#### Failed to find a Displayable for ProjectThing " + pt + " #####");
}
}
// debug:
/*
for (Iterator it = al_layer_sets.iterator(); it.hasNext(); ) {
LayerSet ls = (LayerSet)it.next();
Utils.log2("ls #id " + ls.getId() + " size: " +ls.getLayers().size());
}
*/
// 3 - Assign a layer pointer to ZDisplayable objects
for (final ZDisplayable zd : ht_zdispl.values()) {
//zd.setLayer((Layer)zd.getLayerSet().getLayers().get(0));
zd.setLayer(zd.getLayerSet().getLayer(0));
}
// 4 - Assign layers to Treeline nodes
for (final Layer la : al_layers) {
final List<Node<?>> list = node_layer_table.remove(la.getId());
if (null == list) continue;
for (final Node<?> nd : list) nd.setLayer(la);
}
if (!node_layer_table.isEmpty()) {
Utils.log("ERROR: node_layer_table is not empty!");
}
// 5 - Assign root nodes to Treelines, now that all nodes have a layer
for (final Map.Entry<Tree<?>,Node<?>> e : tree_root_nodes.entrySet()) {
if (null == e.getValue()) {
//Utils.log2("Ignoring, applies to new Treeline format only.");
continue;
}
// Can't compile with <?>
e.getKey().setRoot((Node)e.getValue()); // will generate node caches of each Treeline
}
tree_root_nodes.clear();
// Assign colors to nodes
for (final Map.Entry<Color,Collection<Node<?>>> e : node_colors.entrySet()) {
for (final Node<?> nd : e.getValue()) {
nd.setColor(e.getKey());
}
}
node_colors.clear();
// 6 - Run legacy operations
for (final Runnable r : legacy) {
r.run();
}
try {
// Create a table with all layer ids vs layer instances:
final HashMap<Long,Layer> ht_lids = new HashMap<Long,Layer>();
for (final Layer layer : al_layers) {
ht_lids.put(new Long(layer.getId()), layer);
}
// Spawn threads to recreate buckets, starting from the subset of displays to open
int n = Runtime.getRuntime().availableProcessors();
switch (n) {
case 1:
break;
case 2:
case 3:
case 4:
n--;
break;
default:
n -= 2;
break;
}
final ExecutorService exec = Utils.newFixedThreadPool(n, "TMLHandler-recreateBuckets");
final Set<Long> dlids = new HashSet<Long>();
final LayerSet layer_set = (LayerSet) root_lt.getObject();
final List<Future<?>> fus = new ArrayList<Future<?>>();
final List<Future<?>> fus2 = new ArrayList<Future<?>>();
for (final HashMap<String,String> ht_attributes : al_displays) {
String ob = ht_attributes.get("layer_id");
if (null == ob) continue;
final Long lid = new Long(ob);
dlids.add(lid);
final Layer la = ht_lids.get(lid);
if (null == la) {
ht_lids.remove(lid);
continue;
}
// to open later:
new Display(project, Long.parseLong(ht_attributes.get("id")), la, ht_attributes);
fus.add(exec.submit(new Runnable() { public void run() {
la.recreateBuckets();
}}));
}
fus.add(exec.submit(new Runnable() { public void run() {
layer_set.recreateBuckets(false); // only for ZDisplayable
}}));
// Ensure launching:
if (dlids.isEmpty() && layer_set.size() > 0) {
dlids.add(layer_set.getLayer(0).getId());
}
final List<Layer> layers = layer_set.getLayers();
for (final Long lid : new HashSet<Long>(dlids)) {
fus.add(exec.submit(new Runnable() { public void run() {
int start = layer_set.indexOf(layer_set.getLayer(lid.longValue()));
int next = start + 1;
int prev = start -1;
while (next < layer_set.size() || prev > -1) {
if (prev > -1) {
final Layer lprev = layers.get(prev);
synchronized (dlids) {
if (dlids.add(lprev.getId())) { // returns true if not there already
fus2.add(exec.submit(new Runnable() { public void run() {
lprev.recreateBuckets();
}}));
}
}
prev--;
}
if (next < layers.size()) {
final Layer lnext = layers.get(next);
synchronized (dlids) {
if (dlids.add(lnext.getId())) { // returns true if not there already
fus2.add(exec.submit(new Runnable() { public void run() {
lnext.recreateBuckets();
}}));
}
}
next++;
}
}
Utils.log2("done recreateBuckets chunk");
}}));
}
Utils.wait(fus);
exec.submit(new Runnable() { public void run() {
Utils.log2("waiting for TMLHandler fus...");
Utils.wait(fus2);
Utils.log2("done waiting TMLHandler fus.");
exec.shutdown();
}});
} catch (Throwable t) {
IJError.print(t);
}
// debug:
//root_tt.debug("");
//root_pt.debug("");
//root_lt.debug("");
return new Object[]{root_tt, root_pt, root_lt, ht_pt_expanded};
}
private int counter = 0;
public void startElement(String namespace_URI, String local_name, String qualified_name, Attributes attributes) throws SAXException {
if (null == loader) return;
//Utils.log2("startElement: " + qualified_name);
this.counter++;
if (0 == counter % 100) { // davi-experimenting: don't talk so much when you have > 600,000 patches to load!
Utils.showStatus("Loading " + counter, false);
}
try {
// failsafe:
qualified_name = qualified_name.toLowerCase();
final HashMap<String,String> ht_attributes = new HashMap<String,String>();
for (int i=attributes.getLength() -1; i>-1; i--) {
ht_attributes.put(attributes.getQName(i).toLowerCase(), attributes.getValue(i));
}
// get the id, which whenever possible it's the id of the encapsulating Thing object. The encapsulated object id is the oid
// The type is specified by the qualified_name
Thing thing = null;
if (0 == qualified_name.indexOf("t2_")) {
if (qualified_name.equals("t2_display")) {
if (open_displays) al_displays.add(ht_attributes); // store for later, until the layers exist
} else {
// a Layer, LayerSet or Displayable object
thing = makeLayerThing(qualified_name, ht_attributes);
if (null != thing) {
if (null == root_lt && thing.getObject() instanceof LayerSet) {
root_lt = (LayerThing)thing;
}
}
}
} else if (qualified_name.equals("project")) {
if (null != this.root_pt) {
Utils.log("WARNING: more than one project definitions.");
return;
}
// Create the project
this.project = new Project(Long.parseLong(ht_attributes.remove("id")), ht_attributes.remove("title"));
this.project.setTempLoader(this.loader); // temp, but will be the same anyway
this.project.parseXMLOptions(ht_attributes);
this.project.addToDatabase(); // register id
String title = ht_attributes.get("title");
if (null != title) this.project.setTitle(title);
// Add all unique TemplateThing types to the project
for (Iterator<TemplateThing> it = root_tt.getUniqueTypes(new HashMap<String,TemplateThing>()).values().iterator(); it.hasNext(); ) {
this.project.addUniqueType(it.next());
}
this.project.addUniqueType(this.project_tt);
this.root_pt = new ProjectThing(this.project_tt, this.project, this.project);
// Add a project pointer to all template things
this.root_tt.addToDatabase(this.project);
thing = root_pt;
} else if (qualified_name.startsWith("ict_transform")||qualified_name.startsWith("iict_transform")) {
makeCoordinateTransform(qualified_name, ht_attributes);
} else if (!qualified_name.equals("trakem2")) {
// Any abstract object
thing = makeProjectThing(qualified_name, ht_attributes);
}
if (null != thing) {
// get the previously open thing and add this new_thing to it as a child
int size = al_open.size();
if (size > 0) {
Thing parent = al_open.get(size -1);
parent.addChild(thing);
//Utils.log2("Adding child " + thing + " to parent " + parent);
}
// add the new thing as open
al_open.add(thing);
}
} catch (Exception e) {
IJError.print(e);
skip = true;
}
}
public void endElement(String namespace_URI, String local_name, String qualified_name) {
if (null == loader) return;
if (skip) {
skip = false; // reset
return;
}
String orig_qualified_name = qualified_name;
//Utils.log2("endElement: " + qualified_name);
// iterate over all open things and find the one that matches the qualified_name, and set it closed (pop it out of the list):
qualified_name = qualified_name.toLowerCase().trim();
if (0 == qualified_name.indexOf("t2_")) {
qualified_name = qualified_name.substring(3);
}
for (int i=al_open.size() -1; i>-1; i--) {
Thing thing = al_open.get(i);
if (thing.getType().toLowerCase().equals(qualified_name)) {
al_open.remove(i);
break;
}
}
if (null != last_annotation && null != last_displayable) {
last_displayable.setAnnotation(last_annotation.toString().trim().replaceAll("<", "<"));
last_annotation = null;
}
// terminate non-single clause objects
if (orig_qualified_name.equals("t2_node")) {
// Remove one node from the stack
nodes.removeLast();
taggables.removeLast();
} else if (orig_qualified_name.equals("t2_connector")) {
if (null != last_connector) {
tree_root_nodes.put(last_connector, last_root_node);
last_root_node = null;
last_connector = null;
last_tree = null;
nodes.clear();
}
last_displayable = null;
} else if (orig_qualified_name.equals("t2_area_list")) {
last_area_list = null;
last_displayable = null;
} else if (orig_qualified_name.equals("t2_area")) {
if (null != reca) {
if (null != last_area_list) {
last_area_list.addArea(last_area_list_layer_id, reca.getArea()); // it's local
} else {
((AreaTree.AreaNode)nodes.getLast()).setData(reca.getArea());
}
reca = null;
}
} else if (orig_qualified_name.equals("ict_transform_list")) {
ct_list_stack.remove( ct_list_stack.size() - 1 );
} else if (orig_qualified_name.equals("t2_patch")) {
if (last_patch_filters.size() > 0) {
last_patch.setFilters(last_patch_filters.toArray(new IFilter[last_patch_filters.size()]));
}
if (null != last_ct) {
last_patch.setCoordinateTransformSilently(last_ct);
last_ct = null;
} else if (!last_patch.checkCoordinateTransformFile()) {
Utils.log("ERROR: could not find a file for the coordinate transform #" + last_patch.getCoordinateTransformId() + " of Patch #" + last_patch.getId());
}
if (!last_patch.checkAlphaMaskFile()) {
Utils.log("ERROR: could not find a file for the alpha mask #" + last_patch.getAlphaMaskId() + " of Patch #" + last_patch.getId());
}
last_patch = null;
last_patch_filters.clear();
last_displayable = null;
} else if (orig_qualified_name.equals("t2_ball")) {
last_ball = null;
last_displayable = null;
} else if (orig_qualified_name.equals("t2_dissector")) {
last_dissector = null;
last_displayable = null;
} else if (orig_qualified_name.equals("t2_treeline")) {
if (null != last_treeline) {
// old format:
if (null == last_root_node && null != last_treeline_data && last_treeline_data.length() > 0) {
last_root_node = parseBranch(Utils.trim(last_treeline_data));
last_treeline_data = null;
}
// new
tree_root_nodes.put(last_treeline, last_root_node);
last_root_node = null;
// always:
last_treeline = null;
last_tree = null;
nodes.clear();
}
last_displayable = null;
} else if (orig_qualified_name.equals("t2_areatree")) {
if (null != last_areatree) {
tree_root_nodes.put(last_areatree, last_root_node);
last_root_node = null;
last_areatree = null;
last_tree = null;
nodes.clear(); // the absence of this line would have made the nodes list grow with all nodes of all areatrees, which is ok but consumes memory
}
last_displayable = null;
} else if (orig_qualified_name.equals( "t2_stack" )) {
if (null != last_ict) {
last_stack.setInvertibleCoordinateTransformSilently(last_ict);
last_ict = null;
}
last_stack = null;
last_displayable = null;
} else if (in(orig_qualified_name, all_displayables)) {
last_displayable = null;
}
}
static private final String[] all_displayables = new String[]{"t2_area_list", "t2_patch", "t2_pipe", "t2_polyline", "t2_ball", "t2_label", "t2_dissector", "t2_profile", "t2_stack", "t2_treeline", "t2_areatree", "t2_connector"};
private final boolean in(final String s, final String[] all) {
for (int i=all.length-1; i>-1; i--) {
if (s.equals(all[i])) return true;
}
return false;
}
public void characters(char[] c, int start, int length) {
if (null != last_treeline) {
// for old format:
last_treeline_data.append(c, start, length);
} else if (null != last_annotation) {
last_annotation.append(c, start, length);
}
}
public void fatalError(SAXParseException e) {
Utils.log("Fatal error: column=" + e.getColumnNumber() + " line=" + e.getLineNumber());
}
public void skippedEntity(String name) {
Utils.log("SAX Parser has skipped: " + name);
}
public void notationDeclaration(String name, String publicId, String systemId) {
Utils.log("Notation declaration: " + name + ", " + publicId + ", " + systemId);
}
public void warning(SAXParseException e) {
Utils.log("SAXParseException : " + e);
}
private ProjectThing makeProjectThing(String type, final HashMap<String,String> ht_attributes) {
try {
type = type.toLowerCase();
// debug:
//Utils.log2("TMLHander.makeProjectThing for type=" + type);
long id = -1;
final String sid = ht_attributes.remove("id");
if (null != sid) {
id = Long.parseLong(sid);
}
long oid = -1;
final String soid = ht_attributes.remove("oid");
if (null != soid) {
oid = Long.parseLong(soid);
}
Boolean expanded = new Boolean(false); // default: collapsed
Object eob = ht_attributes.remove("expanded");
if (null != eob) {
expanded = new Boolean((String)eob);
}
String title = ht_attributes.remove("title");
TemplateThing tt = this.project.getTemplateThing(type);
if (null == tt) {
Utils.log("No template for type " + type);
return null;
}
ProjectThing pt = new ProjectThing(tt, this.project, id, null == title ? type : title, null);
pt.addToDatabase();
ht_pt_expanded.put(pt, expanded);
// store the oid vs. pt relationship to fill in the object later.
if (-1 != oid) {
ht_oid_pt.put(new Long(oid), pt);
}
return pt;
} catch (Exception e) {
IJError.print(e);
}
// default:
return null;
}
private void addToLastOpenLayer(Displayable d) {
// find last open layer
for (int i = al_layers.size() -1; i>-1;) {
al_layers.get(i).addSilently(d);
break;
}
}
private void addToLastOpenLayerSet(ZDisplayable zd) {
// find last open layer set
for (int i = al_layer_sets.size() -1; i>-1;) {
al_layer_sets.get(i).addSilently(zd);
break;
}
}
final private Map<Displayable,Map<Long,Map<String,String>>> all_linked_props = new HashMap<Displayable,Map<Long,Map<String,String>>>();
private void putLinkedProperty(final Displayable origin, final HashMap<String,String> ht_attributes) {
final String stid = ht_attributes.get("target_id");
if (null == stid) {
Utils.log2("Can't setLinkedProperty to null target id for origin Displayable " + origin.getId());
return;
}
Long target_id;
try {
target_id = Long.parseLong(stid);
} catch (NumberFormatException e) {
Utils.log2("Unparseable target_id: " + stid + ", for origin " + origin.getId());
return;
}
String key = ht_attributes.get("key");
String value = ht_attributes.get("value");
if (null == key || null == value) {
Utils.log("Skipping linked property for Displayable " + origin.getId() + ": null key or value");
return;
}
key = key.trim();
value = value.trim();
if (0 == key.length() || 0 == value.length()) {
Utils.log("Skipping linked property for Displayable " + origin.getId() + ": empty key or value");
return;
}
Map<Long,Map<String,String>> linked_props = all_linked_props.get(origin);
if (null == linked_props) {
linked_props = new HashMap<Long,Map<String,String>>();
all_linked_props.put(origin, linked_props);
}
Map<String,String> p = linked_props.get(target_id);
if (null == p) {
p = new HashMap<String,String>();
linked_props.put(target_id, p);
}
p.put(key, value);
}
private LayerThing makeLayerThing(String type, final HashMap<String,String> ht_attributes) {
try {
type = type.toLowerCase();
if (0 == type.indexOf("t2_")) {
type = type.substring(3);
}
//long id = -1;
//final String sid = ht_attributes.get("id");
//if (null != sid) id = Long.parseLong(sid);
long oid = -1;
final String soid = ht_attributes.get("oid");
if (null != soid) oid = Long.parseLong(soid);
if (type.equals("node")) {
if (null == last_tree) {
throw new NullPointerException("Can't create a node for null last_tree!");
}
final Node<?> node = last_tree.newNode(ht_attributes);
taggables.add(node);
// Put node into the list of nodes with that layer id, to update to proper Layer pointer later
final long ndlid = Long.parseLong(ht_attributes.get("lid"));
List<Node<?>> list = node_layer_table.get(ndlid);
if (null == list) {
list = new ArrayList<Node<?>>();
node_layer_table.put(ndlid, list);
}
list.add(node);
// Set node as root node or add as child to last node in the stack
if (null == last_root_node) {
last_root_node = node;
} else {
final String sconf = ht_attributes.get("c");
nodes.getLast().add((Node)node, null == sconf ? Node.MAX_EDGE_CONFIDENCE : Byte.parseByte(sconf));
}
// color?
final String scolor = ht_attributes.get("color");
if (null != scolor) {
final Color color = Utils.getRGBColorFromHex(scolor);
Collection<Node<?>> nodes = node_colors.get(color);
if (null == nodes) {
nodes = new ArrayList<Node<?>>();
node_colors.put(color, nodes);
}
nodes.add(node);
}
// Put node into stack of nodes (to be removed on closing the tag)
nodes.add(node);
} else if (type.equals("profile")) {
Profile profile = new Profile(this.project, oid, ht_attributes, ht_links);
profile.addToDatabase();
ht_displayables.put(oid, profile);
addToLastOpenLayer(profile);
last_displayable = profile;
return null;
} else if (type.equals("pipe")) {
Pipe pipe = new Pipe(this.project, oid, ht_attributes, ht_links);
pipe.addToDatabase();
ht_displayables.put(new Long(oid), pipe);
ht_zdispl.put(new Long(oid), pipe);
last_displayable = pipe;
addToLastOpenLayerSet(pipe);
return null;
} else if (type.equals("polyline")) {
Polyline pline = new Polyline(this.project, oid, ht_attributes, ht_links);
pline.addToDatabase();
last_displayable = pline;
ht_displayables.put(new Long(oid), pline);
ht_zdispl.put(new Long(oid), pline);
addToLastOpenLayerSet(pline);
return null;
} else if (type.equals("connector")) {
final Connector con = new Connector(this.project, oid, ht_attributes, ht_links);
if (ht_attributes.containsKey("origin")) {
legacy.add(new Runnable() {
public void run() {
con.readLegacyXML(al_layer_sets.get(al_layer_sets.size()-1), ht_attributes, ht_links);
}
});
}
con.addToDatabase();
last_connector = con;
last_tree = con;
last_displayable = con;
ht_displayables.put(new Long(oid), con);
ht_zdispl.put(new Long(oid), con);
addToLastOpenLayerSet(con);
return null;
} else if (type.equals("path")) {
if (null != reca) {
reca.add(ht_attributes.get("d"));
return null;
}
return null;
} else if (type.equals("area")) {
reca = new ReconstructArea();
if (null != last_area_list) {
last_area_list_layer_id = Long.parseLong(ht_attributes.get("layer_id"));
}
return null;
} else if (type.equals("area_list")) {
AreaList area = new AreaList(this.project, oid, ht_attributes, ht_links);
area.addToDatabase(); // why? This looks like an onion
last_area_list = area;
last_displayable = area;
ht_displayables.put(new Long(oid), area);
ht_zdispl.put(new Long(oid), area);
addToLastOpenLayerSet(area);
return null;
} else if (type.equals("tag")) {
Taggable t = taggables.getLast();
if (null != t) {
Object ob = ht_attributes.get("key");
int keyCode = KeyEvent.VK_T; // defaults to 't'
if (null != ob) keyCode = (int)((String)ob).toUpperCase().charAt(0); // KeyEvent.VK_U is char U, not u
Tag tag = al_layer_sets.get(al_layer_sets.size()-1).putTag(ht_attributes.get("name"), keyCode);
if (null != tag) t.addTag(tag); // could be null if name is not found
}
} else if (type.equals("ball_ob")) {
// add a ball to the last open Ball
if (null != last_ball) {
last_ball.addBall(Double.parseDouble(ht_attributes.get("x")),
Double.parseDouble(ht_attributes.get("y")),
Double.parseDouble(ht_attributes.get("r")),
Long.parseLong(ht_attributes.get("layer_id")));
}
return null;
} else if (type.equals("ball")) {
Ball ball = new Ball(this.project, oid, ht_attributes, ht_links);
ball.addToDatabase();
last_ball = ball;
last_displayable = ball;
ht_displayables.put(new Long(oid), ball);
ht_zdispl.put(new Long(oid), ball);
addToLastOpenLayerSet(ball);
return null;
} else if (type.equals("stack")) {
Stack stack = new Stack(this.project, oid, ht_attributes, ht_links);
stack.addToDatabase();
last_stack = stack;
last_displayable = stack;
ht_displayables.put(new Long(oid), stack);
ht_zdispl.put( new Long(oid), stack );
addToLastOpenLayerSet( stack );
} else if (type.equals("treeline")) {
Treeline tline = new Treeline(this.project, oid, ht_attributes, ht_links);
tline.addToDatabase();
last_treeline = tline;
last_tree = tline;
last_treeline_data = new StringBuilder();
last_displayable = tline;
ht_displayables.put(oid, tline);
ht_zdispl.put(oid, tline);
addToLastOpenLayerSet(tline);
} else if (type.equals("areatree")) {
AreaTree art = new AreaTree(this.project, oid, ht_attributes, ht_links);
art.addToDatabase();
last_areatree = art;
last_tree = art;
last_displayable = art;
ht_displayables.put(oid, art);
ht_zdispl.put(oid, art);
addToLastOpenLayerSet(art);
} else if (type.equals("dd_item")) {
if (null != last_dissector) {
last_dissector.addItem(Integer.parseInt(ht_attributes.get("tag")),
Integer.parseInt(ht_attributes.get("radius")),
ht_attributes.get("points"));
}
} else if (type.equals("label")) {
DLabel label = new DLabel(project, oid, ht_attributes, ht_links);
label.addToDatabase();
ht_displayables.put(new Long(oid), label);
addToLastOpenLayer(label);
last_displayable = label;
return null;
} else if (type.equals("annot")) {
last_annotation = new StringBuilder();
return null;
} else if (type.equals("patch")) {
Patch patch = new Patch(project, oid, ht_attributes, ht_links);
patch.addToDatabase();
ht_displayables.put(new Long(oid), patch);
addToLastOpenLayer(patch);
last_patch = patch;
last_displayable = patch;
checkAlphaMasks(patch);
return null;
} else if (type.equals("filter")) {
last_patch_filters.add(newFilter(ht_attributes));
} else if (type.equals("dissector")) {
Dissector dissector = new Dissector(this.project, oid, ht_attributes, ht_links);
dissector.addToDatabase();
last_dissector = dissector;
last_displayable = dissector;
ht_displayables.put(new Long(oid), dissector);
ht_zdispl.put(new Long(oid), dissector);
addToLastOpenLayerSet(dissector);
} else if (type.equals("layer")) {
// find last open LayerSet, if any
for (int i = al_layer_sets.size() -1; i>-1;) {
LayerSet set = al_layer_sets.get(i);
Layer layer = new Layer(project, oid, ht_attributes);
layer.addToDatabase();
set.addSilently(layer);
al_layers.add(layer);
Object ot = ht_attributes.get("title");
return new LayerThing(template_layer_thing, project, -1, (null == ot ? null : (String)ot), layer, null);
}
} else if (type.equals("layer_set")) {
LayerSet set = new LayerSet(project, oid, ht_attributes, ht_links);
last_displayable = set;
set.addToDatabase();
ht_displayables.put(new Long(oid), set);
al_layer_sets.add(set);
addToLastOpenLayer(set);
Object ot = ht_attributes.get("title");
return new LayerThing(template_layer_set_thing, project, -1, (null == ot ? null : (String)ot), set, null);
} else if (type.equals("calibration")) {
// find last open LayerSet if any
for (int i = al_layer_sets.size() -1; i>-1;) {
LayerSet set = al_layer_sets.get(i);
set.restoreCalibration(ht_attributes);
return null;
}
} else if (type.equals("prop")) {
// Add property to last created Displayable
if (null != last_displayable) {
last_displayable.setProperty(ht_attributes.get("key"), ht_attributes.get("value"));
}
} else if (type.equals("linked_prop")) {
// Add linked property to last created Displayable. Has to wait until the Displayable ids have been resolved to instances.
if (null != last_displayable) {
putLinkedProperty(last_displayable, ht_attributes);
}
} else {
Utils.log2("TMLHandler Unknown type: " + type);
}
} catch (Exception e) {
IJError.print(e);
}
// default:
return null;
}
/**
* Backwards compatibility for alpha masks:
* create the file path as it was before, and see if the file exists.
* If it does, set it to the patch as the alpha mask.
*/
private void checkAlphaMasks(Patch patch) {
if (0 == patch.getAlphaMaskId()) {
final File f = new File(patch.getImageFilePath());
final String path = new StringBuilder(loader.getMasksFolder()).append(FSLoader.createIdPath(Long.toString(patch.getId()), f.getName(), ".zip")).toString();
if (new File(path).exists()) {
try {
if (patch.setAlphaMask((ByteProcessor)loader.openImagePlus(path).getProcessor().convertToByte(false))) {
// On success, queue the file for deletion when saving the XML file
loader.markStaleFileForDeletionUponSaving(path);
Utils.log("Upgraded alpha mask for patch #" + patch.getId());
} else {
Utils.log("ERROR: failed to upgrade alpha mask for patch #" + patch.getId());
}
} catch (Exception e) {
Utils.log("FAILED to restore alpha mask for patch #" + patch.getId() + ":");
IJError.print(e);
}
}
}
}
final private IFilter newFilter(final HashMap<String, String> ht_attributes) {
try {
return (IFilter) Class.forName(ht_attributes.remove("class")).getConstructor(Map.class).newInstance(ht_attributes);
} catch (Exception e) {
throw new RuntimeException("Could not create filter for Patch #" + last_patch.getId(), e);
}
}
final private void makeCoordinateTransform( String type, final HashMap<String,String> ht_attributes )
{
try
{
type = type.toLowerCase();
if ( type.equals( "ict_transform" ) )
{
final CoordinateTransform ct = ( CoordinateTransform )Class.forName( ht_attributes.get( "class" ) ).newInstance();
ct.init( ht_attributes.get( "data" ) );
if ( ct_list_stack.isEmpty() )
{
if ( last_patch != null )
last_ct = ct;
}
else
{
ct_list_stack.get( ct_list_stack.size() - 1 ).add( ct );
}
}
else if ( type.equals( "iict_transform" ) )
{
final InvertibleCoordinateTransform ict = ( InvertibleCoordinateTransform )Class.forName( ht_attributes.get( "class" ) ).newInstance();
ict.init( ht_attributes.get( "data" ) );
if ( ct_list_stack.isEmpty() )
{
if ( last_patch != null )
last_ct = ict;
else if ( last_stack != null )
last_ict = ict;
}
else
{
ct_list_stack.get( ct_list_stack.size() - 1 ).add( ict );
}
}
else if ( type.equals( "ict_transform_list" ) )
{
final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
if ( ct_list_stack.isEmpty() )
{
if ( last_patch != null )
last_ct = ctl;
}
else
ct_list_stack.get( ct_list_stack.size() - 1 ).add( ctl );
ct_list_stack.add( ( TransformList )ctl );
}
else if ( type.equals( "iict_transform_list" ) )
{
final InvertibleCoordinateTransformList< InvertibleCoordinateTransform > ictl = new InvertibleCoordinateTransformList< InvertibleCoordinateTransform >();
if ( ct_list_stack.isEmpty() )
{
if ( last_patch != null )
last_ct = ictl;
else if ( last_stack != null )
last_ict = ictl;
}
else
ct_list_stack.get( ct_list_stack.size() - 1 ).add( ictl );
ct_list_stack.add( ( TransformList )ictl );
}
}
catch ( Exception e ) { IJError.print(e); }
}
private final Node<Float> parseBranch(final String s) {
// 1 - Parse the slab
final int first = s.indexOf('(');
final int last = s.indexOf(')', first+1);
final String[] coords = s.substring(first+1, last).split(" ");
Node<Float> prev = null;
final List<Node<Float>> nodes = new ArrayList<Node<Float>>();
for (int i=0; i<coords.length; i+=3) {
long lid = Long.parseLong(coords[i+2]);
Node<Float> nd = new Treeline.RadiusNode(Float.parseFloat(coords[i]), Float.parseFloat(coords[i+1]), null);
nd.setData(0f);
nodes.add(nd);
// Add to node_layer_table for later assignment of a Layer object to the node
List<Node<?>> list = node_layer_table.get(lid);
if (null == list) {
list = new ArrayList<Node<?>>();
node_layer_table.put(lid, list);
}
list.add(nd);
//
if (null == prev) prev = nd;
else {
prev.add(nd, Node.MAX_EDGE_CONFIDENCE);
prev = nd; // new parent
}
}
// 2 - Parse the branches
final int ibranches = s.indexOf(":branches", last+1);
if (-1 != ibranches) {
final int len = s.length();
int open = s.indexOf('{', ibranches + 9);
while (-1 != open) {
int end = open + 1; // don't read the first curly bracket
int level = 1;
for(; end < len; end++) {
switch (s.charAt(end)) {
case '{': level++; break;
case '}': level--; break;
}
if (0 == level) break;
}
// Add the root node of the new branch to the node at branch index
int openbranch = s.indexOf('{', open+1);
int branchindex = Integer.parseInt(s.substring(open+1, openbranch-1));
nodes.get(branchindex).add(parseBranch(s.substring(open, end)), Node.MAX_EDGE_CONFIDENCE);
open = s.indexOf('{', end+1);
}
}
return nodes.get(0);
}
}