3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2006, 2007 Albert Cardona.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.vector
;
25 import ini
.trakem2
.Project
;
26 import ini
.trakem2
.ControlWindow
;
27 import ini
.trakem2
.display
.*;
28 import ini
.trakem2
.utils
.*;
29 import ini
.trakem2
.tree
.ProjectThing
;
30 import mpi
.fruitfly
.general
.MultiThreading
;
31 import mpicbg
.models
.MovingLeastSquaresTransform
;
32 import mpicbg
.models
.PointMatch
;
33 import mpicbg
.models
.AffineModel3D
;
36 import ij
.gui
.GenericDialog
;
37 import ij
.measure
.Calibration
;
38 import ij
.io
.SaveDialog
;
39 import ij
.io
.OpenDialog
;
40 import ij
.io
.FileSaver
;
42 import ij
.io
.DirectoryChooser
;
44 import java
.awt
.Color
;
45 import java
.awt
.Component
;
46 import java
.awt
.Checkbox
;
47 import java
.awt
.Dimension
;
48 import java
.awt
.event
.*;
49 import java
.awt
.Container
;
50 import java
.awt
.Choice
;
52 import javax
.swing
.border
.LineBorder
;
53 import javax
.swing
.event
.*;
54 import javax
.swing
.table
.*;
55 import java
.util
.concurrent
.atomic
.AtomicInteger
;
57 import java
.awt
.geom
.AffineTransform
;
58 import java
.awt
.Rectangle
;
60 import javax
.vecmath
.Tuple3d
;
61 import javax
.vecmath
.Point3d
;
62 import javax
.vecmath
.Vector3d
;
63 import javax
.vecmath
.Matrix4d
;
64 import javax
.media
.j3d
.Transform3D
;
70 public class Compare
{
72 static public final int TRANS_ROT
= 0;
73 static public final int TRANS_ROT_SCALE
= 1;
74 static public final int TRANS_ROT_SCALE_SHEAR
= 2;
76 static private JLabel label
= null;
77 static private JTabbedPane tabs
= null;
78 static private JFrame frame
= null;
79 static private Hashtable
<JScrollPane
,Chain
> ht_tabs
= null;
80 static private KeyListener kl
= null;
84 static public final Bureaucrat
findSimilar(final Line3D pipe
) {
85 ArrayList
<Project
> pro
= Project
.getProjects();
86 Project
[] all
= new Project
[pro
.size()];
89 GenericDialog gd
= new GenericDialog("Identify");
91 gd
.addMessage("Choose a project to search into");
92 String
[] options
= new String
[all
.length
+ 1];
93 options
[0] = "[-- ALL --]";
94 for (int i
=0; i
<all
.length
; i
++) {
95 options
[i
+1] = all
[i
].toString();
97 gd
.addChoice("Project: ", options
, options
[0]);
102 gd
.addCheckbox("Ignore orientation", true);
103 gd
.addCheckbox("Mirror", false);
104 Utils
.addEnablerListener((Checkbox
)gd
.getCheckboxes().get(0), new Component
[]{(Component
)gd
.getCheckboxes().get(1)}, null);
105 gd
.addCheckbox("Ignore calibration", false);
106 gd
.addCheckbox("Chain_branches", true);
109 if (gd
.wasCanceled()) return null;
111 boolean ignore_orientation
= gd
.getNextBoolean();
112 boolean ignore_calibration
= gd
.getNextBoolean();
113 boolean mirror
= gd
.getNextBoolean();
114 boolean chain_branches
= gd
.getNextBoolean();
116 if (all
.length
> 1) {
117 int choice
= gd
.getNextChoiceIndex();
121 ref
= new Project
[1];
122 ref
[0] = all
[choice
-1];
126 // check that the calibration units are the same
127 if (!ignore_calibration
) {
128 String unit1
= pipe
.getLayerSet().getCalibration().getUnit();
129 for (int i
=0; i
<ref
.length
; i
++) {
130 if (!matchUnits(unit1
, ref
[i
].getRootLayerSet().getCalibration().getUnit(), ref
[i
].getTitle())) {
136 return findSimilar(pipe
, ref
, ignore_orientation
, ignore_calibration
, mirror
, chain_branches
, true);
139 static public final Bureaucrat
findSimilarWithAxes(final Line3D pipe
) {
140 final ArrayList
<Project
> pro
= Project
.getProjects();
141 if (pro
.size() < 2) {
142 Utils
.log("Compare.findSimilarWithAxes needs at least 2 open projects.");
145 final int iother
= 0 == pro
.indexOf(pipe
.getProject()) ?
1 : 0;
146 final Project
[] all
= new Project
[pro
.size()];
148 GenericDialog gd
= new GenericDialog("Indentify with axes");
149 gd
.addMessage("Will search for a match to:");
150 gd
.addMessage(pipe
.getProject().getShortMeaningfulTitle((ZDisplayable
)pipe
));
152 ArrayList
<ZDisplayable
> pipes
= pipe
.getLayerSet().getZDisplayables(Line3D
.class, true);
153 final String
[] pipe_names
= new String
[pipes
.size()];
155 final String
[][] presets
= {{"medial lobe", "dorsal lobe", "peduncle"}};
156 final String
[] preset_names
= new String
[]{"X - 'medial lobe', Y - 'dorsal lobe', Z - 'peduncle'"};
157 /* 0 */ gd
.addChoice("Presets: ", preset_names
, preset_names
[0]);
158 final Choice cpre
= (Choice
)gd
.getChoices().get(0);
160 final ArrayList
<ZDisplayable
> pipes_ref
= all
[iother
].getRootLayerSet().getZDisplayables(Line3D
.class, true);
161 final String
[] pipe_names_ref
= new String
[pipes_ref
.size()];
162 final Object
[] holder
= new Object
[]{pipe_names_ref
};
164 // automatically find for the first preset
165 final Line3D
[] query_axes
= findXYZAxes(presets
[0], pipe
);
166 final int[] s
= findXYZAxes(query_axes
, pipes
, pipe_names
);
167 final int[] t
= findFirstXYZAxes(presets
[0], pipes_ref
, pipe_names_ref
);
169 // check if none found
170 for (int i
=0; i
<3; i
++) {
171 if (-1 == s
[i
]) s
[i
] = 0;
172 if (-1 == t
[i
]) t
[i
] = 0;
175 gd
.addMessage("Source project \"" + pipe
.getProject().getTitle() + ":\"");
176 /* 1 */ gd
.addChoice("X_source: ", pipe_names
, pipe_names
[s
[0]]);
177 /* 2 */ gd
.addChoice("Y_source: ", pipe_names
, pipe_names
[s
[1]]);
178 /* 3 */ gd
.addChoice("Z_source: ", pipe_names
, pipe_names
[s
[2]]);
180 gd
.addMessage("Reference project:");
181 final String
[] options
= new String
[all
.length
];
182 for (int i
=0; i
<all
.length
; i
++) {
183 options
[i
] = all
[i
].toString();
186 /* 4 */ gd
.addChoice("Project: ", options
, options
[iother
]);
187 /* 5 */ gd
.addChoice("X_ref: ", pipe_names_ref
, pipe_names_ref
[t
[0]]);
188 /* 6 */ gd
.addChoice("Y_ref: ", pipe_names_ref
, pipe_names_ref
[t
[1]]);
189 /* 7 */ gd
.addChoice("Z_ref: ", pipe_names_ref
, pipe_names_ref
[t
[2]]);
191 // refresh reference project choices
192 final Choice project_choice
= (Choice
)gd
.getChoices().get(4);
193 final Choice
[] ref
= new Choice
[3];
194 ref
[0] = (Choice
)gd
.getChoices().get(5);
195 ref
[1] = (Choice
)gd
.getChoices().get(6);
196 ref
[2] = (Choice
)gd
.getChoices().get(7);
197 project_choice
.addItemListener(new ItemListener() {
198 public void itemStateChanged(ItemEvent ie
) {
199 String project_name
= (String
)ie
.getItem();
200 Project project
= null;
201 for (int i
=0; i
<all
.length
; i
++) {
202 if (all
[i
].toString().equals(project_name
)) {
207 if (null == project
) return;
209 pipes_ref
.addAll(project
.getRootLayerSet().getZDisplayables(Line3D
.class, true));
210 String
[] pipe_names_ref
= new String
[pipes_ref
.size()];
211 holder
[0] = pipe_names_ref
;
212 int[] s
= findFirstXYZAxes(presets
[cpre
.getSelectedIndex()], pipes_ref
, pipe_names_ref
);
213 for (int i
=0; i
<3; i
++) {
214 if (-1 == s
[i
]) s
[i
] = 0;
216 for (int k
=0; k
<pipe_names_ref
.length
; k
++) {
217 ref
[i
].add(pipe_names_ref
[k
]);
219 if (0 != s
[i
]) ref
[i
].select(s
[i
]);
220 else ref
[i
].select(0);
225 gd
.addCheckbox("skip insertion/deletion strings at ends when scoring", false);
226 gd
.addNumericField("maximum_ignorable consecutive muts in endings: ", 2, 0);
227 gd
.addSlider("minimum_percentage that must remain: ", 1, 100, 100);
228 Utils
.addEnablerListener((Checkbox
)gd
.getCheckboxes().get(0), new Component
[]{(Component
)gd
.getNumericFields().get(0), (Component
)gd
.getNumericFields().get(1)}, null);
230 final String
[] transforms
= {"translate and rotate",
231 "translate, rotate and scale",
232 "translate, rotate, scale and shear"};
233 gd
.addChoice("Transform_type: ", transforms
, transforms
[2]);
234 gd
.addCheckbox("Chain_branches", true);
235 gd
.addCheckbox("Score mutations only", false);
236 gd
.addCheckbox("Substring matching", false);
237 gd
.addCheckbox("Direct (no reverse matches)", true);
238 gd
.addNumericField("Point interdistance (calibrated; zero means auto): ", 0, 2);
239 gd
.addChoice("Sort by: ", distance_types
, distance_types
[3]);
244 if (gd
.wasCanceled()) return null;
247 int ipresets
= gd
.getNextChoiceIndex();
249 Line3D
[] axes
= new Line3D
[]{
250 (Line3D
)pipes
.get(gd
.getNextChoiceIndex()),
251 (Line3D
)pipes
.get(gd
.getNextChoiceIndex()),
252 (Line3D
)pipes
.get(gd
.getNextChoiceIndex())
255 int iproject
= gd
.getNextChoiceIndex();
257 Line3D
[] axes_ref
= new Line3D
[]{
258 (Line3D
)pipes_ref
.get(gd
.getNextChoiceIndex()),
259 (Line3D
)pipes_ref
.get(gd
.getNextChoiceIndex()),
260 (Line3D
)pipes_ref
.get(gd
.getNextChoiceIndex())
263 boolean skip_ends
= gd
.getNextBoolean();
264 int max_mut
= (int)gd
.getNextNumber();
265 float min_chunk
= (float)gd
.getNextNumber() / 100;
267 if (max_mut
< 0) max_mut
= 0;
268 if (min_chunk
<= 0) skip_ends
= false;
269 if (min_chunk
> 1) min_chunk
= 1;
272 int transform_type
= gd
.getNextChoiceIndex();
273 boolean chain_branches
= gd
.getNextBoolean();
274 boolean score_mut
= gd
.getNextBoolean();
275 boolean substring_matching
= gd
.getNextBoolean();
276 boolean direct
= gd
.getNextBoolean();
277 double delta
= gd
.getNextNumber();
278 if (Double
.isNaN(delta
) || delta
< 0) {
279 Utils
.log("Nonsense delta: " + delta
);
282 final int distance_type
= gd
.getNextChoiceIndex();
284 // check that the Calibration units are the same
285 if (!matchUnits(pipe
.getLayerSet().getCalibration().getUnit(), all
[iproject
].getRootLayerSet().getCalibration().getUnit(), all
[iproject
].getTitle())) {
289 return findSimilarWithAxes(pipe
, axes
, axes_ref
, pipes_ref
, skip_ends
, max_mut
, min_chunk
, transform_type
, chain_branches
, true, score_mut
, substring_matching
, direct
, delta
, distance_type
);
292 /** Finds the first three X,Y,Z axes as specified by the names in preset. */
293 static private int[] findFirstXYZAxes(final String
[] preset
, final ArrayList
<ZDisplayable
> pipes
, final String
[] pipe_names
) {
294 final int[] s
= new int[]{-1, -1, -1};
296 for (ZDisplayable zd
: pipes
) {
297 pipe_names
[next
] = zd
.getProject().getShortMeaningfulTitle(zd
);
298 if (-1 != s
[0] && -1 != s
[1] && -1 != s
[2]) {
299 // Already all found, just filling names
303 final String name
= pipe_names
[next
].toLowerCase();
304 if (-1 != name
.indexOf(preset
[0])) s
[0] = next
;
305 else if (-1 != name
.indexOf(preset
[1])) s
[1] = next
;
306 else if (-1 != name
.indexOf(preset
[2])) s
[2] = next
;
312 /** Find the indices of the axes pipes in the pipes array list, and fills in the pipe_names array. */
313 static private int[] findXYZAxes(final Line3D
[] axes
, final ArrayList
<ZDisplayable
> pipes
, final String
[] pipe_names
) {
314 final int[] s
= new int[]{-1, -1, -1};
316 for (ZDisplayable pipe
: pipes
) {
317 pipe_names
[next
] = pipe
.getProject().getShortMeaningfulTitle(pipe
);
318 if (pipe
== axes
[0]) s
[0] = next
;
319 else if (pipe
== axes
[1]) s
[1] = next
;
320 else if (pipe
== axes
[2]) s
[2] = next
;
327 static private void trySetAsAxis(final Line3D
[] axes
, final String
[] preset
, final ProjectThing pt
) {
328 final Object ob
= pt
.getObject();
329 if (null == ob
|| !(ob
instanceof Line3D
)) return;
330 final Line3D pipe
= (Line3D
)ob
;
331 final String title
= pipe
.getProject().getShortMeaningfulTitle(pt
, (ZDisplayable
)pipe
).toLowerCase();
332 //Utils.log2("title is " + title);
333 if (-1 != title
.indexOf(preset
[0])) axes
[0] = pipe
;
334 else if (-1 != title
.indexOf(preset
[1])) axes
[1] = pipe
;
335 else if (-1 != title
.indexOf(preset
[2])) axes
[2] = pipe
;
338 /** Finds the 3 pipes named according to the presets (such as "medial lobe", "dorsal lobe" and "peduncle"), that are structurally closest to the query_pipe in the Project Tree. In this fashion, if there are more than one hemisphere, the correct set of axes will be found for the query pipe.*/
339 static public Line3D
[] findXYZAxes(final String
[] preset
, final Line3D query_pipe
) {
340 if (preset
.length
< 3) {
341 Utils
.log2("findXYZAxes: presets must be of length 3");
344 final Line3D
[] axes
= new Line3D
[3];
345 // Step up level by level looking for pipes with the given preset names
346 final Project project
= query_pipe
.getProject();
347 ProjectThing last
= project
.findProjectThing((ZDisplayable
)query_pipe
);
348 trySetAsAxis(axes
, preset
, last
);
349 final String type
= last
.getType();
351 final ProjectThing parent
= (ProjectThing
)last
.getParent();
352 if (null == parent
) return axes
;
353 for (ProjectThing child
: parent
.getChildren()) {
354 if (child
== last
) continue; // already searched
355 HashSet
<ProjectThing
> al
= child
.findChildrenOfTypeR(new HashSet
<ProjectThing
>(), type
);
356 for (ProjectThing pt
: al
) {
357 trySetAsAxis(axes
, preset
, pt
);
359 if (null != axes
[0] && null != axes
[1] && null != axes
[2]) return axes
;
365 /** Generate calibrated origin of coordinates. */
366 static public Object
[] obtainOrigin(final Line3D
[] axes
, final int transform_type
, final Vector3d
[] o_ref
) {
368 final VectorString3D
[] vs
= new VectorString3D
[3];
369 for (int i
=0; i
<3; i
++) vs
[i
] = axes
[i
].asVectorString3D();
371 final Calibration cal
= (null != axes
[0].getLayerSet() ? axes
[0].getLayerSet().getCalibration() : null);
374 for (int i
=0; i
<3; i
++) vs
[i
].calibrate(cal
);
376 // 2 - resample (although it's done before transforming, it's only for aesthetic purposes: it doesn't matter, won't ever go into dynamic programming machinery)
378 for (int i
=0; i
<3; i
++) delta
+= vs
[i
].getAverageDelta();
380 for (int i
=0; i
<3; i
++) vs
[i
].resample(delta
);
382 // return origin vectors for pipe's project
383 final Vector3d
[] o
= VectorString3D
.createOrigin(vs
[0], vs
[1], vs
[2], transform_type
, o_ref
); // requires resampled vs
385 return new Object
[]{vs
, o
};
388 /** Compare pipe to all pipes in pipes_ref, by first transforming to match both sets of axes. */
389 static public final Bureaucrat
findSimilarWithAxes(final Line3D pipe
, final Line3D
[] axes
, final Line3D
[] axes_ref
, final ArrayList
<ZDisplayable
> pipes_ref
, final boolean skip_ends
, final int max_mut
, final float min_chunk
, final int transform_type
, final boolean chain_branches
, final boolean show_gui
, final boolean score_mut
, final boolean substring_matching
, final boolean direct
, final double delta
, final int sort_by_distance_type
) {
390 Worker worker
= new Worker("Comparing pipes...") {
395 if (axes
.length
< 3 || axes_ref
.length
< 3) {
396 Utils
.log("Need three axes for each.");
400 if (pipe
.length() < 1) {
401 Utils
.log("Query pipe has less than 2 points");
406 // NEW style: bring query brain to reference brain space.
408 final Calibration cal1
= (null != pipe
.getLayerSet() ? pipe
.getLayerSet().getCalibration() : null);
409 final Calibration cal2
= pipes_ref
.get(0).getLayerSet().getCalibration();
411 // obtain origin vectors for reference project
412 Object
[] pack2
= obtainOrigin(axes_ref
, transform_type
, null); // calibrates axes
413 VectorString3D
[] vs_axes_ref
= (VectorString3D
[])pack2
[0];
414 Vector3d
[] o2
= (Vector3d
[])pack2
[1];
416 final Transform3D M_ref
= Compare
.createTransform(o2
);
418 // obtain axes origin vectors for pipe's project
419 Object
[] pack1
= obtainOrigin(axes
, transform_type
, o2
); // calibrates axes
420 VectorString3D
[] vs_axes
= (VectorString3D
[])pack1
[0];
421 Vector3d
[] o1
= (Vector3d
[])pack1
[1];
423 final Transform3D M_query
= Compare
.createTransform(o1
);
425 // The transfer transform: from query axes to reference axes: M_ref * M_query^{-1}
426 final Transform3D T
= new Transform3D(M_ref
);
427 T
.mulInverse(M_query
); // in place
429 // Transform the query axes only (the reference ones stay the same)
430 for (int i
=0; i
<3; i
++) {
431 // axes are already calibrated
432 //Utils.log2("Axis " + i + " Length before transforming: " + vs_axes[i].computeLength());
433 vs_axes
[i
].transform(T
);
434 //Utils.log2("Axis " + i + " Length after transforming: " + vs_axes[i].computeLength());
437 // If chain_branches, the query must also be split into as many branches as it generates.
438 // Should generate a tab for each potential branch? Or better, a tab but not with a table but with a list of labels, one per potential branch, and underneath as many tables in more tabs?
440 // the queries to do, according to how many different chains the query pipe is part of
441 final QueryHolder qh
= new QueryHolder(cal1
, cal2
, T
);
443 ArrayList
<Chain
> chains_ref
;
445 if (chain_branches
) {
446 // create all chained ref branches
447 chains_ref
= createPipeChains(pipes_ref
.get(0).getProject().getRootProjectThing(), pipes_ref
.get(0).getLayerSet()); // TODO WARNING: if the parent has more than one pipe, this will query them all!
449 // add all possible query chains, starting at the parent of the chosen pipe
450 for (Chain chain
: createPipeChains((ProjectThing
)pipe
.getProject().findProjectThing(pipe
).getParent(), pipe
.getLayerSet())) {
451 qh
.addQuery(chain
, 0 == delta ? chain
.vs
.getAverageDelta() : delta
);
454 // no branching: single query of one single-pipe chain
455 Chain ch
= new Chain(pipe
);
456 qh
.addQuery(ch
, 0 == delta ? ch
.vs
.getAverageDelta() : delta
);
457 // just add a single-pipe chain for each ref pipe
458 chains_ref
= new ArrayList
<Chain
>();
459 for (ZDisplayable zd
: pipes_ref
) {
460 chains_ref
.add(new Chain((Line3D
)zd
));
464 // set and calibrate them all
465 qh
.setReferenceChains(chains_ref
);
468 // each thread handles a ref pipe, which is to be matched against all queries
469 final int n_ref_chains
= qh
.chains_ref
.size();
470 final QueryMatchList
[] qm
= new QueryMatchList
[qh
.queries
.size()];
472 for (Chain query
: qh
.queries
) qm
[ne
++] = new QueryMatchList(query
, n_ref_chains
);
474 final Thread
[] threads
= new Thread
[1]; //MultiThreading.newThreads();
475 final AtomicInteger ai
= new AtomicInteger(0);
477 for (int ithread
= 0; ithread
< threads
.length
; ++ithread
) {
478 threads
[ithread
] = new Thread(new Runnable() {
479 final public void run() {
481 for (int k
= ai
.getAndIncrement(); k
< n_ref_chains
; k
= ai
.getAndIncrement()) {
483 // obtain a calibrated ref chain, to be uniquely processed by this thread
484 final Chain ref
= qh
.chains_ref
.get(k
);
485 // match it against all queries
487 for (Chain query
: qh
.queries
) {
488 final VectorString3D vs1
= query
.vs
;
489 final double delta1
= 0 == delta ? vs1
.getDelta() : delta
; // WARNING unchecked delta value
490 final VectorString3D vs2
= qh
.makeVS2(ref
, delta1
);
491 final Object
[] ob
= findBestMatch(vs1
, vs2
, delta1
, skip_ends
, max_mut
, min_chunk
, 1, direct
, substring_matching
);
492 final Editions ed
= (Editions
)ob
[0];
493 //qh.addMatch(query, ref, ed, seq_sim, ed.getPhysicalDistance(skip_ends, max_mut, min_chunk));
495 final float prop_len
= substring_matching ?
497 : ((float)vs1
.length()) / vs2
.length();
499 final double[] stats
= ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, score_mut
);
500 qm
[next
++].cm
[k
] = new ChainMatch(query
, ref
, ed
, stats
, prop_len
, score(ed
.getSimilarity(), ed
.getDistance(), ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[3], Compare
.W
));
502 } catch (Exception e
) {
510 MultiThreading
.startAndJoin(threads
);
514 // put result into the Worker
518 // add to the GUI (will sort them by phys_dist)
520 qh
.sortMatches(new ChainMatchComparator(sort_by_distance_type
));
521 qh
.createGUI(vs_axes
, vs_axes_ref
);
525 } catch (Exception e
) {
532 Project other
= pipes_ref
.get(0).getProject();
534 if (other
.equals(pipe
.getProject())) p
= new Project
[]{other
};
535 else p
= new Project
[]{other
, pipe
.getProject()};
536 return Bureaucrat
.createAndStart(worker
, p
);
539 /** For a given pipe, create a VectorString3D for each possible path, as determined by the Project Tree structure and the parent/child relationships.
540 * A pipe is considered a potential branch when it is contained in an abstract child at the same tree level that the pipe itself in the tree. So:
549 * Results in an ArrayList with:
554 * An so on, recursively from the given pipe's parent.
556 static public ArrayList
<Chain
> createPipeChains(final ProjectThing root_pt
, final LayerSet ls
) throws Exception
{
557 final ArrayList
<Chain
> chains
= new ArrayList
<Chain
>();
558 appendAndFork(root_pt
, null, null, chains
, ls
);
563 static private void appendAndFork(final ProjectThing parent
, Chain chain
, HashSet
<ProjectThing
> hs_c_done
, final ArrayList
<Chain
> chains
, final LayerSet ls
) throws Exception
{
564 final ArrayList children
= parent
.getChildren();
565 if (null == children
) return;
567 if (null == hs_c_done
) hs_c_done
= new HashSet
<ProjectThing
>();
569 for (Iterator it
= children
.iterator(); it
.hasNext(); ) {
570 ProjectThing child
= (ProjectThing
)it
.next();
571 if (hs_c_done
.contains(child
)) continue;
572 hs_c_done
.add(child
);
574 if (child
.getObject() instanceof Line3D
) {
575 Line3D pipe
= (Line3D
)child
.getObject();
576 if (!pipe
.getLayerSet().equals(ls
) || pipe
.length() < 2) continue; // not from the same LayerSet, maybe from a nested one.
578 chain
= new Chain(pipe
);
583 // find other children in the parent who contain children with child pipes
584 boolean first
= true;
585 final Chain base
= chain
.duplicate();
586 for (Iterator cc
= children
.iterator(); cc
.hasNext(); ) {
587 ProjectThing c
= (ProjectThing
)cc
.next();
588 if (hs_c_done
.contains(c
)) continue; // already visited
589 // c is at the same tree level as child (which contains a pipe directly)
590 ArrayList child_pipes
= c
.findChildrenOfType(Line3D
.class);
591 if (child_pipes
.size() > 0) {
598 // otherwise make a copy to branch out
599 ca
= base
.duplicate(); // can't duplicate from chain itself, because it would have the previous child added to it.
602 appendAndFork(c
, ca
, hs_c_done
, chains
, ls
);
605 // pipe wrapping ProjectThing objects cannot have any children
609 // if it does not have direct pipe children, cut chain - but keep inspecting
610 if (0 == child
.findChildrenOfType(Line3D
.class).size()) {
614 // inspect others down the unvisited tree nodes
615 appendAndFork(child
, chain
, hs_c_done
, chains
, ls
);
619 /** Represents a list of concatenated pipes, where each pipe is parent of the next within the ProjectTree. */
620 static public class Chain
{
621 final ArrayList
<Line3D
> pipes
= new ArrayList
<Line3D
>();
622 public VectorString3D vs
; // the complete path of chained pipes
624 public Chain(Line3D root
) {
625 this.pipes
.add(root
);
626 this.vs
= root
.asVectorString3D();
628 final public void append(Line3D p
) throws Exception
{
629 //if (pipes.contains(p)) throw new Exception("Already contains pipe #" + p.getId());
631 vs
= vs
.chain(p
.asVectorString3D());
633 public final Chain
duplicate() {
634 Chain chain
= new Chain();
635 chain
.pipes
.addAll(this.pipes
);
636 chain
.vs
= (VectorString3D
)this.vs
.clone();
639 public String
toString() {
640 StringBuffer sb
= new StringBuffer("len: ");
641 sb
.append(pipes
.size()).append(" ");
642 for (Line3D p
: pipes
) sb
.append('#').append(p
.getId()).append(' ');
643 return sb
.toString();
645 final public String
getTitle() {
646 final StringBuffer sb
= new StringBuffer(pipes
.get(0).getProject().getTitle());
648 for (Line3D p
: pipes
) sb
.append(' ').append('#').append(p
.getId());
649 return sb
.toString();
651 final public String
getCellTitle() {
652 Line3D root
= pipes
.get(0);
653 String mt
= root
.getProject().getShortMeaningfulTitle((ZDisplayable
)root
);
654 if (1 == pipes
.size()) return mt
;
655 //else, chain the ids of the rest
656 final StringBuffer sb
= new StringBuffer(mt
);
657 for (int i
=1; i
<pipes
.size(); i
++) sb
.append(' ').append('#').append(pipes
.get(i
).getId());
658 return sb
.toString();
660 /** Returns max 10 chars, solely the name of the parent's parent node of the root pipe (aka the [lineage] containing the [branch]) or the id if too long. Intended for the 10-digit limitation in the problem in .dis files for Phylip. */
661 final public String
getShortCellTitle() {
662 Line3D root
= pipes
.get(0);
663 ProjectThing pt
= root
.getProject().findProjectThing((ZDisplayable
)root
);
664 String short_title
= null;
665 // investigate the [branch] title
666 pt
= (ProjectThing
)pt
.getParent(); // the [branch]
667 String title
= pt
.getTitle();
668 if (!title
.equals(pt
.getType())) short_title
= title
; // the [branch] was named
669 // investigate the lineage title
670 if (null == short_title
) {
671 pt
= (ProjectThing
)pt
.getParent(); // the [lineage]
672 title
= pt
.getTitle();
673 if (!title
.equals(pt
.getType())) short_title
= title
; // the [lineage] was named
676 if (null != short_title
&& short_title
.length() > 10) {
677 short_title
= null; // too long!
679 // else fall back to unique id
680 if (null == short_title
) {
681 short_title
= Long
.toString(root
.getId());
682 if (short_title
.length() <= 8) short_title
= "id" + short_title
;
684 while (short_title
.length() > 10) {
685 short_title
= short_title
.substring(1);
689 /** Returns the color of the root pipe. */
690 final public Color
getColor() {
691 return pipes
.get(0).getColor();
693 final public Line3D
getRoot() {
696 /** Show centered, set visible and select. */
697 final public void showCentered2D(boolean shift_down
) {
699 Display display
= Display
.getFront();
700 for (Line3D line3d
: pipes
) {
701 ZDisplayable p
= (ZDisplayable
)line3d
;
702 if (null == b
) b
= p
.getBoundingBox();
703 else b
.add(p
.getBoundingBox());
705 display
.select(p
, shift_down
);
707 display
.select((ZDisplayable
)pipes
.get(0), shift_down
); // the root as active
708 display
.getCanvas().showCentered(b
);
713 /** Contains all que query chains created from the single pipe selected for matching against another reference project, their matches with the chains made from the reference project, and some general data such as the transforms and the axes. */
714 static private class QueryHolder
{
716 final ArrayList
<Chain
> queries
= new ArrayList
<Chain
>();
720 // The transfer transform: from query space to reference space
723 final Hashtable
<Chain
,ArrayList
<ChainMatch
>> matches
= new Hashtable
<Chain
,ArrayList
<ChainMatch
>>();
725 VectorString3D
[] vs_axes
= null,
728 boolean relative
= false;
730 // these chains are kept only calibrated, NOT transformed. Because each query will resample it to its own delta, and then transform it.
731 final ArrayList
<Chain
> chains_ref
= new ArrayList
<Chain
>();
733 QueryHolder(Calibration cal1
, Calibration cal2
, Transform3D T
) {
739 /** Will calibrate and transform the chain's VectorString3D. */
740 final void addQuery(final Chain chain
, final double delta
) {
741 final VectorString3D vs
= chain
.vs
;
742 // Order is important:
743 // 1 - calibrate: bring to user-space microns, whatever
744 if (null != cal1
) vs
.calibrate(cal1
);
745 // 2 - transform into reference
746 if (null != T
) vs
.transform(T
);
747 // 3 - resample, within reference space
754 final void addMatch(final ChainMatch cm
) {
755 ArrayList
<ChainMatch
> al
= matches
.get(cm
.query
);
757 al
= new ArrayList
<ChainMatch
>();
758 matches
.put(cm
.query
, al
);
763 /** Will calibrate them all to cal2. */
764 void setReferenceChains(final ArrayList
<Chain
> chains_ref
) {
765 this.chains_ref
.addAll(chains_ref
);
766 if (null == cal2
) return;
767 for (Chain c
: chains_ref
) {
768 c
.vs
.calibrate(cal2
);
772 /** Returns a resampled and transformed copy of the pipe's VectorString3D. */
773 final VectorString3D
makeVS2(final Chain ref
, final double delta
) {
774 return asVS2((VectorString3D
)ref
.vs
.clone(), delta
);
776 /** Returns a resampled and transformed copy of the pipe's VectorString3D. */
777 final VectorString3D
makeVS2(final Line3D ref
, final double delta
) {
778 return asVS2(ref
.asVectorString3D(), delta
);
781 final private VectorString3D
asVS2(final VectorString3D vs
, final double delta
) {
782 if (null != cal2
&& !vs
.isCalibrated()) vs
.calibrate(cal2
);
787 /** Bring Chain q (query) to reference space. */
788 final VectorString3D
makeVS1(final Chain q
, final double delta
) {
789 return asVS1((VectorString3D
)q
.vs
.clone(), delta
);
791 /** Bring Chain q (query) to reference space. Will resample after transforming it, to its own average delta. */
792 final VectorString3D
makeVS1(final Chain q
) {
793 return asVS1((VectorString3D
)q
.vs
.clone(), 0);
796 /** Returns a resampled and transformed copy of the pipe's VectorString3D. */
797 final VectorString3D
makeVS1(final Line3D q
, final double delta
) {
798 return asVS1(q
.asVectorString3D(), delta
);
801 final private VectorString3D
asVS1(final VectorString3D vs
, final double delta
) {
802 if (null != cal1
&& !vs
.isCalibrated()) vs
.calibrate(cal1
);
803 //Utils.log2("VS1: Length before transforming: " + vs.computeLength());
804 if (null != T
) vs
.transform(T
);
805 //Utils.log2("VS1: Length after transforming: " + vs.computeLength());
806 // Resample after transforming, of course!
807 vs
.resample(0 == delta ? vs
.getAverageDelta() : delta
);
811 /** For each list of matches corresponding to each query chain, sort the matches by physical distance. */
812 void sortMatches(Comparator comp
) {
813 for (ArrayList
<ChainMatch
> list
: matches
.values()) {
814 Collections
.sort(list
, comp
);
817 /** Returns all pipes involved in the query chains. */
818 HashSet
<Line3D
> getAllQueriedLine3Ds() {
819 final HashSet
<Line3D
> hs
= new HashSet
<Line3D
>();
820 for (Chain c
: queries
) {
825 Hashtable
<Line3D
,VectorString3D
> getAllQueried() {
826 Hashtable
<Line3D
,VectorString3D
> ht
= new Hashtable
<Line3D
,VectorString3D
>();
827 for (Chain c
: queries
) {
828 double delta
= c
.vs
.getDelta();
829 for (Line3D p
: c
.pipes
) {
830 VectorString3D vs
= p
.asVectorString3D();
831 if (null != cal1
) vs
.calibrate(cal1
);
832 if (null != T
) vs
.transform(T
); // to reference space
839 void addMatches(final QueryMatchList
[] qm
) {
841 for (int i
=0; i
<qm
.length
; i
++) {
842 for (int k
=0; k
<qm
[i
].cm
.length
; k
++) {
843 addMatch(qm
[i
].cm
[k
]);
847 // One table entry per query chain
848 void createGUI(final VectorString3D
[] vs_axes
, final VectorString3D
[] vs_axes_ref
) {
850 this.vs_axes
= vs_axes
;
851 this.vs_axes_ref
= vs_axes_ref
;
852 for (Chain query
: queries
) {
853 QueryHolderTableModel qt
= new QueryHolderTableModel(this, new Visualizer(this, vs_axes
, vs_axes_ref
), query
, matches
.get(query
));
854 JTable table
= new JTable(qt
);
855 table
.addMouseListener(new QueryHolderTableListener());
856 table
.addKeyListener(kl
);
857 JScrollPane jsp
= new JScrollPane(table
);
858 ht_tabs
.put(jsp
, query
);
859 tabs
.addTab(query
.getCellTitle(), jsp
);
860 tabs
.setSelectedComponent(jsp
); // sets the label
864 void remove(final Displayable d
) {
865 // from the queries (and thus the tabs as well)
866 for (Iterator
<Chain
> i
= queries
.iterator(); i
.hasNext(); ) {
867 Chain chain
= i
.next();
868 if (chain
.pipes
.contains(d
)) {
869 Component comp
= findTab(chain
);
871 ht_tabs
.remove(comp
);
875 matches
.remove(chain
);
878 // from the matches list of each query chain
879 for (ArrayList
<ChainMatch
> acm
: matches
.values()) {
880 for (Iterator
<ChainMatch
> i
= acm
.iterator(); i
.hasNext(); ) {
881 if (i
.next().ref
.pipes
.contains(equals(d
))) {
886 Utils
.updateComponent(frame
);
889 /** Remove all queries and refs that belong to the given project. */
890 void remove(Project project
) {
891 // from the queries (and thus the tabs as well)
892 for (Iterator
<Chain
> i
= queries
.iterator(); i
.hasNext(); ) {
893 Chain chain
= i
.next();
894 if (chain
.pipes
.get(0).getProject() == project
) {
895 Component comp
= findTab(chain
);
897 ht_tabs
.remove(comp
);
901 matches
.remove(chain
);
904 // from the matches list of each query chain
905 for (ArrayList
<ChainMatch
> acm
: matches
.values()) {
906 for (Iterator
<ChainMatch
> i
= acm
.iterator(); i
.hasNext(); ) {
907 if (i
.next().ref
.pipes
.get(0).getProject() == project
) {
912 Utils
.updateComponent(frame
);
916 static private Component
findTab(Chain chain
) {
917 for (Iterator it
= ht_tabs
.entrySet().iterator(); it
.hasNext(); ) {
918 Map
.Entry entry
= (Map
.Entry
)it
.next();
919 if (entry
.getValue().equals(chain
)) return (Component
)entry
.getKey();
924 /** Represents the scored match between any two Chain objects. */
925 static private class ChainMatch
{
929 double score
; // combined score, made from several of the parameters below (S, L and M as of 20080823)
930 double seq_sim
; // similarity measure made of num 1 - ((num insertions + num deletions) / max (len1, len2)).
931 double phys_dist
; // average distance between mutation pair interdistances
932 double cum_phys_dist
; // cummulative physical distance
933 double stdDev
; // between mutation pairs
934 double median
; // of matched mutation pair interdistances
935 double prop_mut
; // the proportion of mutation pairs relative to the length of the queried sequence
936 float prop_len
; // the proportion of length of query sequence versus reference sequence
937 double proximity
; // unitless value: cummulative distance of pairs relative to query sequence length
938 double proximity_mut
; // unitless value: cummulative distance of only mutation pairs relative to query sequence length
939 ChainMatch(final Chain query
, final Chain ref
, final Editions ed
, final double[] stats
, final float prop_len
, final double score
) {
943 this.phys_dist
= stats
[0];
944 this.cum_phys_dist
= stats
[1];
945 this.stdDev
= stats
[2];
946 this.median
= stats
[3];
947 this.prop_mut
= stats
[4];
948 this.prop_len
= prop_len
;
949 this.score
= score
; // combined
950 this.seq_sim
= stats
[6];
951 this.proximity
= stats
[7];
952 this.proximity_mut
= stats
[8];
956 static private class QueryMatchList
{
959 QueryMatchList(final Chain query
, final int n_ref_chains
) {
961 this.cm
= new ChainMatch
[n_ref_chains
];
965 static private class ChainMatchComparator
implements Comparator
{
966 /** Sort by the given distance type. */
967 final int distance_type
;
968 ChainMatchComparator(final int distance_type
) {
969 this.distance_type
= distance_type
;
971 public int compare(final Object ob1
, final Object ob2
) {
972 ChainMatch cm1
= (ChainMatch
)ob1
;
973 ChainMatch cm2
= (ChainMatch
)ob2
;
974 // select for smallest physical distance of the center of mass
975 // double val = cm1.phys_dist - cm2.phys_dist;
977 final double val = cm1.median - cm2.median;
978 if (val < 0) return -1; // m1 is closer
979 if (val > 0) return 1; // m1 is further away
980 return 0; // same distance
983 // Select the largest score
987 switch (distance_type
) {
989 val
= cm2
.score
- cm1
.score
;
991 case LEVENSHTEIN
: // Levenshtein
992 val
= cm2
.ed
.getDistance() - cm1
.ed
.getDistance();
994 case DISSIMILARITY
: // Dissimilarity
995 val
= cm1
.seq_sim
- cm2
.seq_sim
; // INVERTED ORDER because each would need an inversion: 1 - cmX.seq_sim
997 case AVG_PHYS_DIST
: // average physical distance between mutation pairs only
998 val
= cm2
.phys_dist
- cm1
.phys_dist
;
1000 case MEDIAN_PHYS_DIST
: // median physical distance between all pairs
1001 val
= cm2
.median
- cm1
.median
;
1003 case CUM_PHYST_DIST
: // cummulative physical distance between all pairs
1004 val
= cm2
.cum_phys_dist
- cm1
.cum_phys_dist
;
1006 case STD_DEV
: // stdDev of distances between mutation pairs only
1007 val
= cm2
.stdDev
- cm1
.stdDev
;
1009 case PROXIMITY
: // cummulative distance relative to largest physical length of the two sequences
1010 val
= cm2
.proximity
- cm1
.proximity
;
1012 case PROXIMITY_MUT
: // cummulative distance of mutation pairs relative to largest physical length of the two sequences
1013 val
= cm2
.proximity_mut
- cm1
.proximity_mut
;
1017 if (val
> 0) return -1;
1018 if (val
< 0) return 1;
1022 static private class ChainMatchComparatorSim
implements Comparator
{
1023 public int compare(final Object ob1
, final Object ob2
) {
1024 ChainMatch cm1
= (ChainMatch
)ob1
;
1025 ChainMatch cm2
= (ChainMatch
)ob2
;
1026 // select for largest score
1027 double val
= cm1
.score
- cm2
.score
;
1028 if (val
< 0) return 1; // m2 is more similar
1029 if (val
> 0) return -1; // m2 is less similar
1034 static private class Visualizer
{
1036 LayerSet common
; // calibrated to queried pipe space, which is now also the space of all others.
1037 boolean query_shows
= false;
1038 VectorString3D
[] vs_axes
, vs_axes_ref
;
1040 Visualizer(QueryHolder qh
, VectorString3D
[] vs_axes
, VectorString3D
[] vs_axes_ref
) {
1042 this.vs_axes
= vs_axes
;
1043 this.vs_axes_ref
= vs_axes_ref
;
1044 // create common LayerSet space
1045 Line3D pipe
= qh
.getAllQueriedLine3Ds().iterator().next();
1046 LayerSet ls
= pipe
.getLayerSet();
1047 Calibration cal1
= ls
.getCalibration();
1048 this.common
= new LayerSet(pipe
.getProject(), pipe
.getProject().getLoader().getNextId(), "Common", 10, 10, 0, 0, 0, ls
.getLayerWidth() * cal1
.pixelWidth
, ls
.getLayerHeight() * cal1
.pixelHeight
, false, 2, new AffineTransform());
1049 Calibration cal
= new Calibration();
1050 cal
.setUnit(cal1
.getUnit()); // homogeneous on all axes
1051 this.common
.setCalibration(cal
);
1053 /** Shows the matched chain in 3D. */
1054 public void show3D(Chain query
, Chain ref
) {
1056 if (!query_shows
) showAxesAndQueried();
1057 VectorString3D vs
= qh
.makeVS2(ref
, query
.vs
.getDelta()); // was: makeVS
1058 // The LayerSet is that of the Line3D being queried, not the given pipe to show which belongs to the reference project (and thus not to the queried pipe project)
1059 String title
= ref
.getCellTitle();
1060 if (Display3D
.contains(common
, title
)) return;
1061 Display3D
.addMesh(common
, vs
, title
, ref
.getColor());
1063 public void showFull3D(Chain query
, Chain ref
) {
1066 showAxes(query
.getRoot().getColor());
1067 showNode3D(query
, true);
1069 showNode3D(ref
, false);
1071 public void showNearby(Chain query
) {
1073 if (!query_shows
) showAxesAndQueried();
1074 ArrayList
<ChainMatch
> matches
= qh
.matches
.get(query
);
1075 final VectorString3D vs_query
= qh
.makeVS1(query
);
1076 final double radius
= vs_query
.computeLength() * 2;
1077 for (ChainMatch match
: matches
) {
1078 VectorString3D vs_ref
= qh
.makeVS2(match
.ref
, query
.vs
.getDelta());
1079 if (vs_query
.isNear(vs_ref
, radius
)) {
1080 Display3D
.addMesh(common
, vs_ref
, match
.ref
.getTitle(), match
.ref
.getColor());
1084 void showNode3D(Chain chain
, boolean as_query
) {
1085 Line3D root
= chain
.getRoot();
1086 ProjectThing pt
= (ProjectThing
)root
.getProject().findProjectThing((ZDisplayable
)root
).getParent();
1087 HashSet hs
= pt
.findChildrenOfTypeR(Line3D
.class);
1088 for (Iterator it
= hs
.iterator(); it
.hasNext(); ) {
1089 Line3D p
= (Line3D
)((ProjectThing
)it
.next()).getObject();
1090 String title
= p
.getProject().getShortMeaningfulTitle((ZDisplayable
)p
);
1091 if (Display3D
.contains(common
, title
)) continue; // add only any missing ones
1094 vs
= qh
.makeVS1(p
, chain
.vs
.getDelta());
1096 vs
= qh
.makeVS2(p
, chain
.vs
.getDelta());
1098 Display3D
.addMesh(common
, vs
, title
, p
.getColor());
1101 public void showAxesAndQueried() {
1103 Color qcolor
= null;
1104 final Hashtable
<Line3D
,VectorString3D
> queried
= qh
.getAllQueried();
1105 for (Iterator it
= queried
.entrySet().iterator(); it
.hasNext(); ) {
1106 Map
.Entry entry
= (Map
.Entry
)it
.next();
1107 ZDisplayable p
= (ZDisplayable
)entry
.getKey();
1108 // if already there, ignore request
1109 String title
= p
.getProject().getShortMeaningfulTitle(p
);
1110 if (Display3D
.contains(common
, title
)) continue;
1111 VectorString3D vs
= (VectorString3D
)entry
.getValue();
1112 if (null == qcolor
) qcolor
= p
.getColor();
1113 Display3D
.addMesh(common
, vs
, title
, qcolor
);
1117 void showAxes(Color qcolor
) {
1118 if (null != vs_axes
) {
1119 Color color
= Color
.pink
.equals(qcolor
) ? Color
.red
: qcolor
;
1121 Display3D
.addMesh(common
, vs_axes
[0], "X query", color
);
1122 Display3D
.addMesh(common
, vs_axes
[1], "Y query", color
);
1123 Display3D
.addMesh(common
, vs_axes
[2], "Z query", color
);
1125 if (null != vs_axes_ref
) {
1126 Display3D
.addMesh(common
, vs_axes_ref
[0], "X ref", Color
.pink
);
1127 Display3D
.addMesh(common
, vs_axes_ref
[1], "Y ref", Color
.pink
);
1128 Display3D
.addMesh(common
, vs_axes_ref
[2], "Z ref", Color
.pink
);
1132 public void showInterpolated(Editions ed
, Chain query
, Chain ref
) {
1134 if (!query_shows
) showAxesAndQueried();
1135 String title
= "Av. " + query
.getTitle() + " - " + ref
.getTitle();
1136 // if already there, ignore request
1137 if (Display3D
.contains(common
, title
)) return;
1138 VectorString3D vs
= VectorString3D
.createInterpolatedPoints(ed
, 0.5f
);
1139 Display3D
.addMesh(common
, vs
, title
, Utils
.mix(query
.getColor(), ref
.getColor()));
1141 private void reset() {
1142 if (null == Display3D
.getDisplay(common
)) query_shows
= false;
1146 static protected final Object
[] findBestMatch(final VectorString3D vs1
, final VectorString3D vs2
, double delta
, boolean skip_ends
, int max_mut
, float min_chunk
) {
1147 return findBestMatch(vs1
, vs2
, delta
, skip_ends
, max_mut
, min_chunk
, COMBINED
, false, false);
1150 /** Since comparing two sequences starting from one end or starting from the other
1151 * is not the same at all, this method performs the match starting first from one
1152 * end and then from the other.
1153 * Then it performs a match starting from the middle of the longest stretches of
1154 * pure mutations detected in the best of the matches above.
1155 * Also, since strings may be reversed, the test against the reversed one is done as well.
1158 * vs1.reversed() vs2.reversed()
1160 * vs1 vs2.reversed()
1161 * vs1.reversed() vs2
1163 * ASSUMES both VectorString3D are open.
1165 * @param direct Whether to test vs1 against vs2 only, or to try all 4 possible combinations of reversed versus non-reversed and pick the best.
1167 static protected final Object
[] findBestMatch(final VectorString3D vs1
, final VectorString3D vs2
, double delta
, boolean skip_ends
, int max_mut
, float min_chunk
, final int distance_type
, final boolean direct
, final boolean substring_matching
) {
1169 if (substring_matching
) {
1170 // identify shorter chain
1171 final VectorString3D shorter
= vs1
.length() < vs2
.length() ? vs1
: vs2
;
1172 final VectorString3D longer
= vs1
== shorter ? vs2
: vs1
;
1174 // iterate matching of shorter string inside longer string:
1175 // (so that the match is always between two equally long strings)
1176 // aaaaaaaa : 8 elements
1177 // bbbbb : 5 elements
1178 // bbbbb --- total 4 matches to try
1182 final int max_offset
= longer
.length() - shorter
.length() + 1;
1183 Object
[] best
= null;
1184 for (int k
=0; k
<max_offset
; k
++) {
1185 final VectorString3D longer_sub
= longer
.substring(k
, k
+shorter
.length());
1186 //Utils.log2("substring_matching lengths: shorter, longer : " + shorter.length() + ", " + longer_sub.length());
1187 final Object
[] ob
= direct ?
1188 matchDirect(shorter
, longer_sub
, delta
, skip_ends
, max_mut
, min_chunk
, distance_type
)
1189 : matchFwdRev(shorter
, longer_sub
, delta
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1190 if (null == best
) best
= ob
;
1191 else if (((Double
)ob
[1]).doubleValue() > ((Double
)best
[1]).doubleValue()) best
= ob
;
1196 return matchDirect(vs1
, vs2
, delta
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1198 return matchFwdRev(vs1
, vs2
, delta
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1203 static public final int LEVENSHTEIN
= 0;
1204 static public final int DISSIMILARITY
= 1;
1205 static public final int AVG_PHYS_DIST
= 2;
1206 static public final int MEDIAN_PHYS_DIST
= 3;
1207 static public final int CUM_PHYST_DIST
= 4;
1208 static public final int STD_DEV
= 5;
1209 static public final int COMBINED
= 6;
1210 static public final int PROXIMITY
= 7;
1211 static public final int PROXIMITY_MUT
= 8;
1212 static public final int STD_DEV_ALL
= 9;
1214 static private final String
[] distance_types
= {"Levenshtein", "Dissimilarity", "Average physical distance", "Median physical distance", "Cummulative physical distance", "Standard deviation", "Combined SLM", "Proximity", "Proximity of mutation pairs"};
1216 // Weights as empirically approximated with some lineages, with S. Preibisch ( see Test_Scoring.java )
1217 static public final double[] W
= new double[]{1.3345290383577453, -0.0012626693452889859, -0.012764729437173508, -0.13344076489951817};
1219 static public final double score(final double seq_sim
, final double levenshtein
, final double median_phys_dist
, final double[] w
) {
1221 return seq_sim
* w
[0] + levenshtein
* w
[1] + median_phys_dist
* w
[2] + w
[3];
1224 /** Zero is best; gets bad towards positive infinite. */
1225 static private final double getScore(Editions ed
, boolean skip_ends
, int max_mut
, float min_chunk
, int distance_type
) {
1226 switch (distance_type
) {
1227 case LEVENSHTEIN
: // Levenshtein
1228 return ed
.getDistance();
1229 case DISSIMILARITY
: // Dissimilarity
1230 return 1 - ed
.getSimilarity(skip_ends
, max_mut
, min_chunk
);
1231 case AVG_PHYS_DIST
: // average physical distance between mutation pairs only
1232 return ed
.getPhysicalDistance(skip_ends
, max_mut
, min_chunk
, true);
1233 case MEDIAN_PHYS_DIST
: // median physical distance between all pairs
1234 return ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[3]; // 3 is median
1235 case CUM_PHYST_DIST
: // cummulative physical distance between all pairs
1236 return ed
.getPhysicalDistance(skip_ends
, max_mut
, min_chunk
, false);
1237 case STD_DEV
: // stdDev of distances between mutation pairs only
1238 return ed
.getStdDev(skip_ends
, max_mut
, min_chunk
);
1239 case STD_DEV_ALL
: // stdDev of distances between all pairs
1240 return ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[2];
1241 case COMBINED
: // combined score
1242 return 1 / score(ed
.getSimilarity(), ed
.getDistance(), ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[3], Compare
.W
);
1243 case PROXIMITY
: // cummulative distance relative to largest physical length of the two sequences
1244 return ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[7]; // 7 is proximity
1245 case PROXIMITY_MUT
: // cummulative distance of mutation pairs relative to largest physical length of the two sequences
1246 return ed
.getStatistics(skip_ends
, max_mut
, min_chunk
, false)[8]; // 8 is proximity
1251 static private final Object
[] matchDirect(final VectorString3D vs1
, final VectorString3D vs2
, double delta
, boolean skip_ends
, int max_mut
, float min_chunk
, int distance_type
) {
1252 // Levenshtein is unfortunately not commutative: must try both
1253 final Editions ed1
= new Editions(vs1
, vs2
, delta
, false);
1254 double score1
= getScore(ed1
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1255 final Editions ed2
= new Editions(vs2
, vs1
, delta
, false);
1256 double score2
= getScore(ed2
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1257 return score1
< score2 ?
1258 new Object
[]{ed1
, score1
}
1259 : new Object
[]{ed2
, score2
};
1262 // Match in all possible ways
1263 static private final Object
[] matchFwdRev(final VectorString3D vs1
, final VectorString3D vs2
, double delta
, boolean skip_ends
, int max_mut
, float min_chunk
, int distance_type
) {
1265 final VectorString3D vs1rev
= vs1
.makeReversedCopy();
1266 final VectorString3D vs2rev
= vs2
.makeReversedCopy();
1268 final Editions
[] ed
= new Editions
[4];
1271 ed
[0] = new Editions(vs1
, vs2
, delta
, false);
1273 ed
[1] = new Editions(vs1rev
, vs2rev
, delta
, false);
1275 ed
[2] = new Editions(vs1
, vs2rev
, delta
, false);
1277 ed
[3] = new Editions(vs1rev
, vs2
, delta
, false);
1279 //double best_score1 = 0;
1280 double best_score
= Double
.MAX_VALUE
; // worst possible
1282 Editions best_ed
= null;
1283 for (int i
=0; i
<ed
.length
; i
++) {
1284 double score
= getScore(ed
[i
], skip_ends
, max_mut
, min_chunk
, distance_type
);
1285 if (score
< best_score
) {
1288 //best_score1 = score1;
1291 //Utils.log2("score, score1: " + best_score + ", " + best_score1);
1293 // now test also starting from the middle of the longest mutation chunk of the best matching
1295 Editions ed_center
= best_ed
.recreateFromCenter(max_mut
);
1296 // is null if no chunks were found
1297 if (null != ed_center
) {
1298 double score_center
= getScore(ed_center
, skip_ends
, max_mut
, min_chunk
, distance_type
);
1299 if (score_center
< best_score
) {
1300 best_ed
= ed_center
;
1301 best_score
= score_center
;
1304 } catch (Exception e
) {
1305 e
.printStackTrace();
1308 return new Object
[]{best_ed
, new Double(best_score
)};
1311 static private boolean matchUnits(String unit1
, String unit2
, String project_title
) {
1312 if (unit1
.equals(unit2
)) return true;
1313 // else, record problem
1314 Utils
.log("WARNING: the calibration units of the queried pipe (" + unit1
+ ") does not match with that of the reference project '" + project_title
+ "' (" + unit2
+ ").");
1318 /** Compare the given pipe with other pipes in the given standard project(s). WARNING: the calibrations will work ONLY IF all pipes found to compare with come from LayerSets which have the same units of measurement! For example, all in microns. */
1319 static public final Bureaucrat
findSimilar(final Line3D pipe
, final Project
[] ref
, final boolean ignore_orientation
, final boolean ignore_calibration
, final boolean mirror
, final boolean chain_branches
, final boolean show_gui
) {
1320 final Worker worker
= new Worker("Comparing pipes...") {
1324 if (pipe
.length() < 2) {
1325 Utils
.log("Query pipe has less than 2 points!");
1330 Utils
.log2("Will search into " + ref
.length
+ " projects.");
1332 Calibration cal
= null;
1333 if (!ignore_calibration
) cal
= (null != pipe
.getLayerSet() ? pipe
.getLayerSet().getCalibration() : null);
1335 final QueryHolder qh
= new QueryHolder(cal
, null, null);
1336 ArrayList
<Chain
> chains_ref
= new ArrayList
<Chain
>();
1338 final ArrayList
<VectorString3D
> reversed_queries
= new ArrayList
<VectorString3D
>();
1340 if (chain_branches
) {
1341 // collect the full set of ref pipes from each project
1342 for (int i
=0; i
<ref
.length
; i
++) {
1343 for (Chain c
: createPipeChains(ref
[i
].getRootProjectThing(), ref
[i
].getRootLayerSet())) {
1344 if (c
.getRoot().equals(pipe
)) continue; // skip!
1348 // add all possible query chains, starting at the parent of the chosen pipe
1349 for (Chain chain
: createPipeChains((ProjectThing
)pipe
.getProject().findProjectThing(pipe
).getParent(), pipe
.getLayerSet())) {
1350 qh
.addQuery(chain
, chain
.vs
.getAverageDelta()); // calibrates it
1351 if (ignore_orientation
) {
1352 if (mirror
) chain
.vs
.mirror(VectorString3D
.X_AXIS
);
1353 chain
.vs
.relative();
1355 reversed_queries
.add(chain
.vs
.makeReversedCopy());
1358 // no branching: single query of one single-pipe chain
1359 Chain chain
= new Chain(pipe
);
1360 qh
.addQuery(chain
, chain
.vs
.getAverageDelta()); // calibrates it
1361 reversed_queries
.add(chain
.vs
.makeReversedCopy());
1362 for (int i
=0; i
<ref
.length
; i
++) {
1363 for (ZDisplayable zd
: ref
[i
].getRootLayerSet().getZDisplayables(Line3D
.class, true)) {
1364 if (zd
== pipe
) continue; // skip!
1365 chains_ref
.add(new Chain((Line3D
)zd
));
1370 qh
.setReferenceChains(chains_ref
);
1371 chains_ref
= null; // == qh.chains_ref
1373 // each thread handles a ref pipe, which is to be matched against all queries and reversed queries
1374 final int n_ref_chains
= qh
.chains_ref
.size();
1375 final QueryMatchList
[] qm
= new QueryMatchList
[qh
.queries
.size()];
1377 for (Chain query
: qh
.queries
) qm
[ne
++] = new QueryMatchList(query
, n_ref_chains
);
1379 final Thread
[] threads
= MultiThreading
.newThreads();
1380 final AtomicInteger ai
= new AtomicInteger(0);
1382 for (int ithread
= 0; ithread
< threads
.length
; ++ithread
) {
1383 threads
[ithread
] = new Thread(new Runnable() {
1386 for (int k
= ai
.getAndIncrement(); k
< n_ref_chains
; k
= ai
.getAndIncrement()) {
1389 // obtain a ref chain, to be uniquely processed by this thread
1390 final Chain ref
= qh
.chains_ref
.get(k
);
1391 final Calibration cal2
= ref
.getRoot().getLayerSet().getCalibration();
1392 if (!ignore_calibration
) ref
.vs
.calibrate(cal2
);
1393 for (int q
=qh
.queries
.size()-1; q
>-1; q
--) {
1394 Chain query
= qh
.queries
.get(q
);
1395 VectorString3D vs1
= query
.vs
; // calibrated
1396 VectorString3D vs1_rev
= reversed_queries
.get(q
); // calibrated
1397 double delta1
= vs1
.getDelta();
1399 VectorString3D vs2
= qh
.makeVS2(ref
, delta1
);// was: makeVS // makes resampled copy
1400 if (ignore_orientation
) {
1401 if (mirror
) vs2
.mirror(VectorString3D
.X_AXIS
);
1407 if (ignore_orientation
) {
1408 ed
= new Editions(vs1
, vs2
, delta1
, false);
1409 score
= ed
.getSimilarity();
1410 Editions ed_rev
= new Editions(vs1_rev
, vs2
, delta1
, false);
1411 double score_rev
= ed_rev
.getSimilarity();
1412 // the higher the better
1413 if (score_rev
> score
) {
1416 query
.vs
= vs1_rev
; //swap!
1419 Object
[] ob
= findBestMatch(vs1
, vs2
, delta1
, false, 0, 1);
1420 ed
= (Editions
)ob
[0];
1421 score
= ((Double
)ob
[1]).doubleValue();
1423 //qh.addMatch(query, ref, ed, score, ed.getPhysicalDistance(false, 0, 1));
1424 double[] stats
= ed
.getStatistics(false, 0, 1, false);
1425 float prop_len
= ((float)vs1
.length()) / vs2
.length();
1426 qm
[q
].cm
[k
] = new ChainMatch(query
, ref
, ed
, stats
, prop_len
, score(ed
.getSimilarity(), ed
.getDistance(), ed
.getStatistics(false, 0, 0, false)[3], Compare
.W
));
1429 } catch (Exception e
) {
1437 MultiThreading
.startAndJoin(threads
);
1440 // put result into the Worker
1444 // Now, sort matches by physical distance.
1446 qh
.relative
= ignore_orientation
;
1447 qh
.sortMatches(ignore_orientation ?
new ChainMatchComparatorSim() : new ChainMatchComparator(MEDIAN_PHYS_DIST
));
1448 qh
.createGUI(null, null);
1452 } catch (Exception e
) {
1459 HashSet hsp
= new HashSet();
1460 hsp
.add(pipe
.getProject());
1461 for (int i
=0; i
<ref
.length
; i
++) hsp
.add(ref
[i
]);
1462 Project
[] p
= (Project
[])hsp
.toArray(new Project
[0]);
1463 return Bureaucrat
.createAndStart(worker
, p
);
1466 static private class Match
{
1471 Match(Displayable displ
, Editions ed
, double score
) {
1476 Match(Displayable displ
, double phys_dist
, Editions ed
, double score
) {
1477 this(displ
, ed
, score
);
1478 this.phys_dist
= phys_dist
;
1482 static private class OrderMatch
implements Comparator
{
1483 public int compare(Object obm1
, Object obm2
) {
1484 Match m1
= (Match
)obm1
;
1485 Match m2
= (Match
)obm2
; // I hate java
1486 // select for largest score
1487 double val
= m1
.score
- m2
.score
;
1488 //double val = m1.ed.getDistance() - m2.ed.getDistance();
1489 if (val
< 0) return -1; // m1 is smaller
1490 if (val
> 0) return 1; // m1 is larger
1495 static private class OrderByDistance
implements Comparator
{
1496 public int compare(Object obm1
, Object obm2
) {
1497 Match m1
= (Match
)obm1
;
1498 Match m2
= (Match
)obm2
; // I hate java
1499 // select for smallest physical distance of the center of mass
1500 double val
= m1
.phys_dist
- m2
.phys_dist
;
1501 if (val
< 0) return -1; // m1 is closer
1502 if (val
> 0) return 1; // m1 is further away
1503 return 0; // same distance
1508 /* // WOULD HAVE to create my own sorter: can't sort numerically even (it's bit sort), plus the getValueAt(int row, int col) gets messed up - no proper backend data update. But to create my own sorter, I need a TableSorter class which is new 1.6.0, i..e would have tobe very convolutedly generated.
1509 if (ij.IJ.isJava16()) {
1511 java.lang.reflect.Method msort = JTable.class.getMethod("setAutoCreateRowSorter", new Class[]{Boolean.TYPE});
1512 msort.invoke(table, new Object[]{Boolean.TRUE});
1513 } catch (Exception e) {
1514 e.printStackTrace();
1520 static private void tryCloseTab(KeyEvent ke
) {
1521 switch (ke
.getKeyCode()) {
1523 if (!ke
.isControlDown()) return;
1524 int ntabs
= tabs
.getTabCount();
1529 int sel
= tabs
.getSelectedIndex();
1530 ht_tabs
.remove(tabs
.getComponentAt(sel
));
1532 if (0 == ht_tabs
.size()) label
.setText("[ -- empty -- ]");
1539 static private final void makeGUI() {
1540 if (null == frame
) {
1541 frame
= ControlWindow
.createJFrame("Comparator");
1542 frame
.addWindowListener(new WindowAdapter() {
1543 public void windowClosing(WindowEvent we
) {
1547 if (null == ht_tabs
) ht_tabs
= new Hashtable
<JScrollPane
,Chain
>();
1548 tabs
= new JTabbedPane();
1549 tabs
.setPreferredSize(new Dimension(800,500));
1550 // a listener to change the label text when the tab is selected
1551 ChangeListener tabs_listener
= new ChangeListener() {
1552 public void stateChanged(ChangeEvent ce
) {
1553 if (null == frame
|| null == ht_tabs
|| null == tabs
|| null == label
) return; // the event fires during instantiation ... pffff!
1554 Object ob
= tabs
.getSelectedComponent();
1555 if (null == ob
) return;
1556 Chain query
= ht_tabs
.get(ob
);
1557 if (null == query
) return;
1558 label
.setText(query
.getRoot().getProject().toString() + ": " + query
.getCellTitle());
1561 kl
= new KeyAdapter() {
1562 public void keyPressed(KeyEvent ke
) {
1566 tabs
.addKeyListener(kl
);
1567 JPanel all
= new JPanel();
1568 label
= new JLabel("None compared.");
1569 label
.addMouseListener(new MouseAdapter() {
1570 public void mousePressed(MouseEvent me
) {
1571 if (2 != me
.getClickCount()) return;
1572 ht_tabs
.get(tabs
.getSelectedComponent()).showCentered2D(me
.isShiftDown());
1575 BoxLayout bl
= new BoxLayout(all
, BoxLayout
.Y_AXIS
);
1576 JPanel plabel
= new JPanel();
1577 plabel
.setBorder(new LineBorder(Color
.black
, 1, true));
1578 plabel
.setMinimumSize(new Dimension(400, 50));
1579 plabel
.setMaximumSize(new Dimension(1000, 50));
1584 frame
.getContentPane().add(all
);
1586 tabs
.addChangeListener(tabs_listener
); // to avoid firing it during instantiation, must be added last
1587 ij
.gui
.GUI
.center(frame
);
1589 frame
.setVisible(true);
1593 static public void remove(Displayable displ
) {
1594 if (null == frame
) return;
1595 for (int i
=tabs
.getTabCount()-1; i
>-1; i
--) {
1596 Container jsp
= (Container
)tabs
.getComponentAt(0);
1597 JTable table
= (JTable
)jsp
.getComponent(i
);
1598 QueryHolderTableModel model
= (QueryHolderTableModel
)table
.getModel();
1599 model
.remove(displ
);
1603 static public void destroy() {
1604 if (null != frame
) {
1605 frame
.setVisible(false);
1618 static private class QueryHolderTableModel
extends AbstractTableModel
{
1621 ArrayList
<ChainMatch
> cm
;
1624 QueryHolderTableModel(QueryHolder qh
, Visualizer vis
, Chain query
, ArrayList
<ChainMatch
> cm
) {
1630 public Visualizer
getVisualizer() { return vis
; }
1631 public String
getColumnName(int col
) {
1633 case 0: return "Project";
1634 case 1: return "Match";
1635 case 2: return "Score";
1636 case 3: return "Seq Sim"; // sequence similarity
1637 case 4: return "Lev Dist";
1638 case 5: return null != qh
.cal2 ?
"Median (" + qh
.cal2
.getUnits() + ")" : "Median";
1639 case 6: return null != qh
.cal2 ?
"Avg Dist (" + qh
.cal2
.getUnits() + ")" : "Avg Dist";
1640 case 7: return null != qh
.cal2 ?
"Cum Dist (" + qh
.cal2
.getUnits() + ")" : "Cum Dist";
1641 case 8: return "Std Dev";
1642 case 9: return "Prop Mut";
1643 case 10: return "Prop Lengths";
1644 case 11: return "Proximity";
1645 case 12: return "Prox Mut";
1649 public int getRowCount() { return cm
.size(); }
1650 public int getColumnCount() { return 13; }
1651 public Object
getValueAt(int row
, int col
) {
1653 case 0: return cm
.get(row
).ref
.getRoot().getProject();
1654 case 1: return cm
.get(row
).ref
.getCellTitle();
1655 case 2: return Utils
.cutNumber(cm
.get(row
).score
, 2); // combined score
1656 case 3: return Utils
.cutNumber(cm
.get(row
).seq_sim
* 100, 2) + " %"; // 1 - seq_sim, to convert from dissimilarity to similarity
1657 case 4: return Utils
.cutNumber(cm
.get(row
).ed
.getDistance(), 2); // Levenhtein
1658 case 5: return Utils
.cutNumber(cm
.get(row
).median
, 2);
1659 case 6: return Utils
.cutNumber(cm
.get(row
).phys_dist
, 2); // average
1660 case 7: return Utils
.cutNumber(cm
.get(row
).cum_phys_dist
, 2);
1661 case 8: return Utils
.cutNumber(cm
.get(row
).stdDev
, 2);
1662 case 9: return Utils
.cutNumber(cm
.get(row
).prop_mut
* 100, 2) + " %"; // the proportion of mutations, relative to the query sequence length
1663 case 10: return Utils
.cutNumber(cm
.get(row
).prop_len
* 100, 2) + " %"; // the proportion of lengths = len(query) / len(ref)
1664 case 11: return Utils
.cutNumber(cm
.get(row
).proximity
, 2);
1665 case 12: return Utils
.cutNumber(cm
.get(row
).proximity_mut
, 2);
1669 public boolean isCellEditable(int row
, int col
) {
1672 public void setValueAt(Object value
, int row
, int col
) {} // ignore
1673 public void remove(Displayable d
) { qh
.remove(d
); }
1676 static private class QueryHolderTableListener
extends MouseAdapter
{
1677 public void mousePressed(final MouseEvent me
) {
1678 final Object source
= me
.getSource();
1679 final JTable table
= (JTable
)source
;
1680 final QueryHolderTableModel model
= (QueryHolderTableModel
)table
.getModel();
1682 final int row
= table
.rowAtPoint(me
.getPoint());
1683 if (-1 == row
) return;
1684 final Chain ref
= model
.cm
.get(row
).ref
;
1686 if (2 == me
.getClickCount()) {
1687 if (me
.isShiftDown()) {
1688 model
.vis
.show3D(model
.query
, ref
);
1690 ref
.showCentered2D(false);
1695 if (Utils
.isPopupTrigger(me
)) {
1696 // select row under mouse
1697 table
.getSelectionModel().setSelectionInterval(row
, row
);
1698 final int[] sel
= table
.getSelectedRows();
1699 JPopupMenu popup
= new JPopupMenu();
1700 final String show3D
= "Show match in 3D";
1701 final String interp3D
= "Show interpolated in 3D";
1702 final String showfull3D
= "Show full node in 3D";
1703 final String showCentered
= "Show centered in 2D";
1704 final String showAxes
= "Show axes and queried";
1705 final String showNearby
= "Show all nearby";
1707 // TODO: need better
1708 // - to show the matched chains alone
1709 // - to show the full matched nodes
1711 ActionListener listener
= new ActionListener() {
1712 public void actionPerformed(ActionEvent ae
) {
1713 final String command
= ae
.getActionCommand();
1714 if (command
.equals(interp3D
)) {
1715 // for now, use the first selected only
1717 model
.vis
.showInterpolated(model
.cm
.get(sel
[0]).ed
, model
.query
, ref
);
1718 } catch (Exception e
) {
1722 } else if (command
.equals(show3D
)) {
1723 model
.vis
.show3D(model
.query
, ref
);
1724 } else if (command
.equals(showCentered
)) {
1725 ref
.showCentered2D(0 != (ae
.getModifiers() & ActionEvent
.SHIFT_MASK
));
1726 } else if (command
.equals(showAxes
)) {
1727 model
.vis
.showAxesAndQueried();
1728 } else if (command
.equals(showfull3D
)) {
1729 model
.vis
.showFull3D(model
.query
, ref
);
1730 } else if (command
.equals(showNearby
)) {
1731 model
.vis
.showNearby(model
.query
);
1736 item
= new JMenuItem(show3D
); popup
.add(item
); item
.addActionListener(listener
);
1737 item
= new JMenuItem(interp3D
); popup
.add(item
); item
.addActionListener(listener
);
1738 if (model
.qh
.relative
) item
.setEnabled(false);
1739 item
= new JMenuItem(showfull3D
); popup
.add(item
); item
.addActionListener(listener
);
1740 item
= new JMenuItem(showCentered
); popup
.add(item
); item
.addActionListener(listener
);
1741 item
= new JMenuItem(showAxes
); popup
.add(item
); item
.addActionListener(listener
);
1742 if (null == model
.qh
.vs_axes
) item
.setEnabled(false);
1743 item
= new JMenuItem(showNearby
); popup
.add(item
); item
.addActionListener(listener
);
1745 popup
.show(table
, me
.getX(), me
.getY());
1750 /** Compare all to all parameters. */
1751 static public class CATAParameters
{
1752 public double delta
;
1753 public boolean skip_ends
;
1755 public float min_chunk
;
1756 public int transform_type
;
1757 public boolean chain_branches
;
1758 public String
[] preset
;
1759 public final String
[][] presets
= {{"medial lobe", "dorsal lobe", "peduncle"}};
1760 public final String
[] preset_names
= new String
[]{"X - 'medial lobe', Y - 'dorsal lobe', Z - 'peduncle'"};
1761 public String format
;
1762 public final String
[] formats
= {"ggobi XML", ".csv", "Phylip .dis"};
1763 public int distance_type
;
1764 public boolean normalize
;
1765 public boolean direct
;
1766 public boolean substring_matching
;
1767 public String regex
;
1768 public boolean with_source
= false;
1769 public double plot_max_x
, plot_max_y
;
1770 public int plot_width
, plot_height
;
1771 public boolean cut_uneven_ends
= true;
1772 public int envelope_type
= 2;
1774 public CATAParameters() {}
1776 public boolean setup(final boolean to_file
, final String regex
, final boolean plot
, final boolean condense
) {
1777 final GenericDialog gd
= new GenericDialog("All to all");
1778 gd
.addMessage("Choose a point interdistance to resample to, or 0 for the average of all.");
1779 gd
.addNumericField("point_interdistance: ", 0, 2);
1780 gd
.addCheckbox("skip insertion/deletion strings at ends when scoring", false);
1781 gd
.addNumericField("maximum_ignorable consecutive muts in endings: ", 5, 0);
1782 gd
.addNumericField("minimum_percentage that must remain: ", 0.5, 2);
1783 Utils
.addEnablerListener((Checkbox
)gd
.getCheckboxes().get(0), new Component
[]{(Component
)gd
.getNumericFields().get(0), (Component
)gd
.getNumericFields().get(1)}, null);
1785 final String
[] transforms
= {"translate and rotate",
1786 "translate, rotate and scale",
1787 "translate, rotate, scale and shear",
1790 gd
.addChoice("Transform_type: ", transforms
, transforms
[2]);
1791 gd
.addCheckbox("Chain_branches", true);
1793 gd
.addChoice("Presets: ", preset_names
, preset_names
[0]);
1795 gd
.addChoice("Scoring type: ", distance_types
, distance_types
[3]);
1797 gd
.addChoice("File format: ", formats
, formats
[2]);
1799 gd
.addCheckbox("normalize", false);
1800 gd
.addCheckbox("direct", true);
1801 gd
.addCheckbox("substring_matching", false);
1802 gd
.addStringField("regex: ", null != regex ? regex
: "");
1804 gd
.addNumericField("plot_width: ", 700, 0);
1805 gd
.addNumericField("plot_height: ", 400, 0);
1806 gd
.addNumericField("plot_max_x: ", 270, 2);
1807 gd
.addNumericField("plot_max_y: ", 30, 2);
1810 gd
.addCheckbox("cut_uneven_ends", false);
1811 final String
[] env
= {"1 std dev", "2 std dev", "3 std dev", "average", "maximum"};
1812 gd
.addChoice("envelope", env
, env
[0]);
1818 if (gd
.wasCanceled()) return false;
1820 delta
= gd
.getNextNumber();
1821 skip_ends
= gd
.getNextBoolean();
1822 max_mut
= (int)gd
.getNextNumber();
1823 min_chunk
= (float)gd
.getNextNumber();
1825 if (max_mut
< 0) max_mut
= 0;
1826 if (min_chunk
<= 0) skip_ends
= false;
1827 if (min_chunk
> 1) min_chunk
= 1;
1829 transform_type
= gd
.getNextChoiceIndex();
1830 chain_branches
= gd
.getNextBoolean();
1831 preset
= presets
[gd
.getNextChoiceIndex()];
1833 distance_type
= gd
.getNextChoiceIndex();
1835 format
= formats
[0];
1836 if (to_file
) format
= gd
.getNextChoice().trim();
1838 normalize
= gd
.getNextBoolean();
1839 direct
= gd
.getNextBoolean();
1840 substring_matching
= gd
.getNextBoolean();
1842 this.regex
= gd
.getNextString();
1843 if (0 == this.regex
.length()) this.regex
= null;
1846 plot_width
= (int)gd
.getNextNumber();
1847 plot_height
= (int)gd
.getNextNumber();
1848 plot_max_x
= gd
.getNextNumber();
1849 plot_max_y
= gd
.getNextNumber();
1852 cut_uneven_ends
= gd
.getNextBoolean();
1853 envelope_type
= gd
.getNextChoiceIndex();
1860 /** Gather chains for all projects considering the cp.regex. */
1861 static public final Object
[] gatherChains(final Project
[] p
, final CATAParameters cp
) throws Exception
{
1862 // gather all chains
1863 final ArrayList
[] p_chains
= new ArrayList
[p
.length
]; // to keep track of each project's chains
1864 final ArrayList
<Chain
> chains
= new ArrayList
<Chain
>();
1865 for (int i
=0; i
<p
.length
; i
++) { // for each project:
1866 if (null == cp
.regex
) {
1867 p_chains
[i
] = createPipeChains(p
[i
].getRootProjectThing(), p
[i
].getRootLayerSet());
1869 // Search (shallow) for cp.regex matches
1870 for (ProjectThing pt
: p
[i
].getRootProjectThing().findChildren(cp
.regex
, true)) {
1871 final ArrayList
<Chain
> ac
= createPipeChains(pt
, p
[i
].getRootLayerSet());
1872 if (null == p_chains
[i
]) p_chains
[i
] = ac
;
1873 else p_chains
[i
].addAll(ac
);
1875 if (null == p_chains
[i
]) p_chains
[i
] = new ArrayList
<Chain
>(); // empty
1877 chains
.addAll(p_chains
[i
]);
1879 final Calibration cal
= p
[i
].getRootLayerSet().getCalibrationCopy();
1880 for (Chain chain
: (ArrayList
<Chain
>)p_chains
[i
]) chain
.vs
.calibrate(cal
);
1882 final int n_chains
= chains
.size();
1884 // register all, or relative
1885 if (3 == cp
.transform_type
) {
1886 // '3' means relative
1887 // compute global average delta
1888 if (0 == cp
.delta
) {
1889 for (Chain chain
: chains
) {
1890 cp
.delta
+= ( chain
.vs
.getAverageDelta() / n_chains
);
1893 Utils
.log2("Using delta: " + cp
.delta
);
1895 for (Chain chain
: chains
) {
1896 chain
.vs
.resample(cp
.delta
, cp
.with_source
); // BEFORE making it relative
1897 chain
.vs
.relative();
1900 if (cp
.transform_type
< 3) {
1901 // '1', '2' and '3' involve a 3D affine
1902 // no need //VectorString3D[][] vs_axes = new VectorString3D[p.length][];
1903 Vector3d
[][] o
= new Vector3d
[p
.length
][];
1904 for (int i
=0; i
<p
.length
; i
++) {
1905 // 1 - find pipes to work as axes for each project
1906 ArrayList
<ZDisplayable
> pipes
= p
[i
].getRootLayerSet().getZDisplayables(Line3D
.class, true);
1907 String
[] pipe_names
= new String
[pipes
.size()];
1908 for (int k
=0; k
<pipes
.size(); k
++) {
1909 pipe_names
[k
] = p
[i
].getMeaningfulTitle(pipes
.get(k
));
1911 int[] s
= findFirstXYZAxes(cp
.preset
, pipes
, pipe_names
);
1913 // if axes are -1, forget it: not found
1914 if (-1 == s
[0] || -1 == s
[1] || -1 == s
[2]) {
1915 Utils
.log("Can't find axes for project " + p
[i
]);
1920 // obtain axes and origin
1921 Object
[] pack
= obtainOrigin(new Line3D
[]{(Line3D
)pipes
.get(s
[0]),
1922 (Line3D
)pipes
.get(s
[1]),
1923 (Line3D
)pipes
.get(s
[2])},
1925 o
[0]); // will be null for the first, which will then be non-null and act as the reference for the others.
1927 // no need //vs_axes[i] = (VectorString3D[])pack[0];
1928 o
[i
] = (Vector3d
[])pack
[1];
1931 // match the scales to make the largest be 1.0
1932 final double scaling_factor = VectorString3D.matchOrigins(o, transform_type);
1933 Utils.log2("matchOrigins scaling factor: " + scaling_factor + " for transform_type " + transform_type);
1935 // transform all except the first (which acts as reference)
1936 final Transform3D M_ref
= Compare
.createTransform(o
[0]);
1937 for (int i
=1; i
<p
.length
; i
++) {
1938 Vector3d trans
= new Vector3d(-o
[i
][3].x
, -o
[i
][3].y
, -o
[i
][3].z
);
1939 final Transform3D M_query
= Compare
.createTransform(o
[i
]);
1940 // The transfer T transform: from query space to reference space.
1941 final Transform3D T
= new Transform3D(M_ref
);
1942 T
.mulInverse(M_query
);
1943 for (Chain chain
: (ArrayList
<Chain
>)p_chains
[i
]) {
1944 chain
.vs
.transform(T
); // in place
1950 // compute global average delta, after correcting calibration and transformation
1951 if (0 == cp
.delta
) {
1952 for (Chain chain
: chains
) {
1953 cp
.delta
+= ( chain
.vs
.getAverageDelta() / n_chains
);
1956 Utils
.log2("Using delta: " + cp
.delta
);
1958 // After calibration and transformation, resample all to the same delta
1959 for (Chain chain
: chains
) chain
.vs
.resample(cp
.delta
, cp
.with_source
);
1962 return new Object
[]{chains
, p_chains
};
1965 /** Gets pipes for all open projects, and generates a matrix of dissimilarities, which gets passed on to the Worker thread and also to a file, if desired.
1967 * @param to_file Whether to save the results to a file and popup a save dialog for it or not. In any case the results are stored in the worker's load, which you can retrieve like:
1968 * Bureaucrat bu = Compare.compareAllToAll(true);
1969 * Object result = bu.getWorker().getResult();
1970 * float[][] scores = (float[][])result[0];
1971 * ArrayList<Compare.Chain> chains = (ArrayList<Compare.Chain>)result[1];
1972 * @param normalize Whether to normalize the score values so that the maximum value is 1 and the minimum is 0, or not.
1974 static public Bureaucrat
compareAllToAll(final boolean to_file
, final String regex
) {
1976 // gather all open projects
1977 final Project
[] p
= Project
.getProjects().toArray(new Project
[0]);
1979 final Worker worker
= new Worker("Comparing all to all") {
1984 final CATAParameters cp
= new CATAParameters();
1985 if (!cp
.setup(to_file
, regex
, false, false)) {
1991 String filename
= null,
1995 SaveDialog sd
= new SaveDialog("Save matrix", OpenDialog
.getDefaultDirectory(), null, ".csv");
1996 filename
= sd
.getFileName();
1997 if (null == filename
) {
2001 dir
= sd
.getDirectory().replace('\\', '/');
2002 if (!dir
.endsWith("/")) dir
+= "/";
2005 Object
[] ob
= gatherChains(p
, cp
);
2006 final ArrayList
<Chain
> chains
= (ArrayList
<Chain
>)ob
[0];
2007 final ArrayList
[] p_chains
= (ArrayList
[])ob
[2]; // to keep track of each project's chains
2009 if (null == chains
) {
2014 final int n_chains
= chains
.size();
2016 // compare all to all
2017 final VectorString3D
[] vs
= new VectorString3D
[n_chains
];
2018 for (int i
=0; i
<n_chains
; i
++) vs
[i
] = chains
.get(i
).vs
;
2019 final float[][] scores
= Compare
.scoreAllToAll(vs
, cp
.distance_type
, cp
.delta
, cp
.skip_ends
, cp
.max_mut
, cp
.min_chunk
, cp
.direct
, cp
.substring_matching
, this);
2021 if (null == scores
) {
2026 // store matrix and chains into the worker
2027 this.result
= new Object
[]{scores
, chains
};
2035 File f
= new File(dir
+ filename
);
2036 final OutputStreamWriter dos
= new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f
)), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
2038 // Normalize matrix to largest value of 1.0
2041 for (int i
=0; i
<scores
.length
; i
++) { // traverse half matrix ony: it's mirrored
2042 for (int j
=i
; j
<scores
[0].length
; j
++) {
2043 if (scores
[i
][j
] > max
) max
= scores
[i
][j
];
2046 for (int i
=0; i
<scores
.length
; i
++) {
2047 for (int j
=i
; j
<scores
[0].length
; j
++) {
2048 scores
[i
][j
] = scores
[j
][i
] /= max
;
2053 // write chain titles, with project prefix
2054 if (cp
.format
.equals(cp
.formats
[0])) {
2057 StringBuffer
[] titles
= new StringBuffer
[n_chains
];
2059 for (int i
=0; i
<p
.length
; i
++) {
2060 String prefix
= Utils
.getCharacter(i
+1);
2061 dos
.write("\"\""); //empty upper left corner
2062 for (Chain chain
: (ArrayList
<Chain
>)p_chains
[i
]) {
2064 titles
[next
] = new StringBuffer().append('\"').append(prefix
).append(' ').append(chain
.getCellTitle()).append('\"');
2065 dos
.write(titles
[next
].toString());
2070 for (int i
=0; i
<n_chains
; i
++) {
2071 StringBuffer line
= new StringBuffer();
2072 line
.append(titles
[i
]);
2073 for (int j
=0; j
<n_chains
; j
++) line
.append(',').append(scores
[i
][j
]);
2075 dos
.write(line
.toString());
2078 } catch (Exception e
) {
2079 e
.printStackTrace();
2081 } else if (cp
.format
.equals(cp
.formats
[1])) {
2084 StringBuffer sb
= new StringBuffer("<?xml version=\"1.0\"?>\n<!DOCTYPE ggobidata SYSTEM \"ggobi.dtd\">\n");
2085 sb
.append("<ggobidata count=\"2\">\n");
2087 sb
.append("<data name=\"Pipe Chains\">\n");
2088 sb
.append("<description />\n");
2089 sb
.append("<variables count=\"0\">\n</variables>\n"); // ggobi: what a crappy XML parser it has
2090 sb
.append("<records count=\"").append(chains
.size()).append("\" glyph=\"fr 1\" color=\"3\">\n");
2092 for (int i
=0; i
<p
.length
; i
++) {
2093 String prefix
= Utils
.getCharacter(i
+1);
2094 String color
= new StringBuffer("color=\"").append(i
+1).append('\"').toString();
2095 for (Chain chain
: (ArrayList
<Chain
>)p_chains
[i
]) {
2096 sb
.append("<record id=\"").append(next
+1).append("\" label=\"").append(prefix
).append(' ').append(chain
.getCellTitle()).append("\" ").append(color
).append("></record>\n");
2100 sb
.append("</records>\n</data>\n");
2102 sb
.append("<data name=\"distances\">\n");
2103 sb
.append("<description />\n");
2104 sb
.append("<variables count=\"1\">\n<realvariable name=\"D\" />\n</variables>\n");
2105 sb
.append("<records count=\"").append(n_chains
*(n_chains
-1)).append("\" glyph=\"fr 1\" color=\"0\">\n");
2106 for (int i
=0; i
<n_chains
; i
++) {
2107 for (int j
=0; j
<n_chains
; j
++) {
2108 if (i
== j
) continue;
2109 sb
.append("<record source=\"").append(i
+1).append("\" destination=\"").append(j
+1).append("\">").append(scores
[i
][j
]).append("</record>\n");
2112 sb
.append("</records>\n</data>\n");
2114 sb
.append("</ggobidata>");
2116 dos
.write(sb
.toString());
2119 } catch (Exception e
) {
2120 e
.printStackTrace();
2122 } else if (cp
.format
.equals(cp
.formats
[2])) {
2125 // collect different projects
2126 final ArrayList
<Project
> projects
= new ArrayList
<Project
>();
2127 for (Chain chain
: chains
) {
2128 Project p
= chain
.getRoot().getProject();
2129 if (!projects
.contains(p
)) projects
.add(p
);
2131 final HashSet names
= new HashSet();
2132 final StringBuffer sb
= new StringBuffer();
2133 sb
.append(scores
.length
).append('\n');
2134 dos
.write(sb
.toString());
2135 for (int i
=0; i
<scores
.length
; i
++) {
2137 String title
= chains
.get(i
).getShortCellTitle().replace(' ', '_').replace('\t', '_').replace('[', '-').replace(']', '-');
2138 // Crop title to 6 chars
2139 if (title
.length() > 8) {
2140 String title2
= title
.substring(0, 8);
2141 Utils
.log2("Cropping " + title
+ " to " + title2
);
2145 String name
= title
;
2146 // Prepend a project char identifier
2147 String project_name
= "";
2148 if (projects
.size() > 1) {
2149 project_name
= Utils
.getCharacter(projects
.indexOf(chains
.get(i
).getRoot().getProject()) + 1).toLowerCase();
2150 name
= project_name
+ title
;
2152 // Append a char index when name is used multiple times (mostly because of branching, and other reasons)
2153 if (names
.contains(name
)) {
2155 names
.add(name
+ "a");
2157 Utils
.log2("Contained name " + name
+ ", thus set to " + name
+ "a");
2158 } else if (names
.contains(name
+ "a")) name
+= "a"; // so the 'while' will find it
2159 while (names
.contains(name
)) {
2161 name
= project_name
+ title
+ Utils
.getCharacter(k
).toLowerCase();
2167 for (int j
=len
- name
.length(); j
>0; j
--) sb
.append(' '); // pad with spaces up to len
2169 for (int j
=0; j
<scores
[0].length
; j
++) {
2170 sb
.append(' ').append(scores
[i
][j
]);
2172 if (7 == count
&& j
< scores
[0].length
-1) {
2175 while (++count
< len
) sb
.append(' ');
2181 dos
.write(sb
.toString());
2184 } catch (Exception e
) {
2185 e
.printStackTrace();
2192 } catch (Exception e
) {
2193 e
.printStackTrace();
2199 return Bureaucrat
.createAndStart(worker
, p
);
2202 /** Returns the half matrix of scores, with values copied from one half matrix to the other, and a diagonal of zeros.
2203 * @param distance_type ranges from 0 to 5, and includes: 0=Levenshtein, 1=Dissimilarity, 2=Average physical distance, 3=Median physical distance, 4=Cummulative physical distance and 5=Standard deviation. */
2204 static public float[][] scoreAllToAll(final VectorString3D
[] vs
, final int distance_type
, final double delta
, final boolean skip_ends
, final int max_mut
, final float min_chunk
, final boolean direct
, final boolean substring_matching
, final Worker worker
) {
2205 final float[][] scores
= new float[vs
.length
][vs
.length
];
2208 final AtomicInteger ai
= new AtomicInteger(0);
2210 final Thread
[] threads
= MultiThreading
.newThreads();
2211 for (int ithread
=0; ithread
<threads
.length
; ithread
++) {
2212 threads
[ithread
] = new Thread() { public void run() {
2215 for (int i
=ai
.getAndIncrement(); i
<vs
.length
; i
=ai
.getAndIncrement()) {
2216 final VectorString3D vs1
= vs
[i
];
2217 for (int j
=i
+1; j
<vs
.length
; j
++) {
2218 if (null != worker
&& worker
.hasQuitted()) return;
2219 final Object
[] ob
= findBestMatch(vs
[i
], vs
[j
], delta
, skip_ends
, max_mut
, min_chunk
, distance_type
, direct
, substring_matching
); // TODO should add 'distance_type' as well for the selection of the best match when not direct.
2221 switch (distance_type) {
2222 case 0: // Levenshtein
2223 scores[i][j] = (float)((Editions)ob[0]).getDistance();
2225 case 1: // dissimilarity
2226 scores[i][j] = (float)((Double)ob[1]).doubleValue();
2228 case 2: // average physical distance between mutation pairs
2229 scores[i][j] = (float)((Editions)ob[0]).getPhysicalDistance(skip_ends, max_mut, min_chunk, true);
2231 case 3: // median physical distance between mutation pairs
2232 scores[i][j] = (float)((Editions)ob[0]).getStatistics(skip_ends, max_mut, min_chunk, false)[3]; // 3 is median
2234 case 4: // cummulative physical distance between mutation pairs
2235 scores[i][j] = (float)((Editions)ob[0]).getPhysicalDistance(skip_ends, max_mut, min_chunk, false);
2237 case 5: // stdDev of distances between mutation pairs
2238 scores[i][j] = (float)((Editions)ob[0]).getStdDev(skip_ends, max_mut, min_chunk);
2243 final Editions ed
= (Editions
)ob
[0];
2244 scores
[i
][j
] = (float)getScore(ed
, skip_ends
, max_mut
, min_chunk
, distance_type
);
2248 scores
[j
][i
] = scores
[i
][j
];
2255 MultiThreading
.startAndJoin(threads
);
2257 if (null != worker
&& worker
.hasQuitted()) return null;
2262 /** Creates a transform with the 4 given vectors: X, Y, Z and translation of origin. */
2263 static public Transform3D
createTransform(final Vector3d
[] o
) {
2264 return new Transform3D(new Matrix4d(
2266 o
[0].x
, o
[1].x
, o
[2].x
, o
[3].x
,
2267 o
[0].y
, o
[1].y
, o
[2].y
, o
[3].y
,
2268 o
[0].z
, o
[1].z
, o
[2].z
, o
[3].z
,
2272 static public Bureaucrat
variabilityAnalysis() {
2273 return variabilityAnalysis(null, null, false, false, null);
2276 /** If @param reference_project is null, then the first one found in the Project.getProjects() lists is used.
2277 * If regex is not null, then only ProjectThing nodes with the matching regex are analyzed (shallow: none of their children are questioned, but pipes will be built from them all).
2278 * if show_plots, plots are shown -- otherwise, a directory is asked for and they are saved as png files.
2280 static public Bureaucrat
variabilityAnalysis(final Project reference_project
, final String regex
, final boolean show_plots
, final boolean show_3D
, final Map
<String
,VectorString3D
> map_condensed
) {
2281 // gather all open projects
2282 final Project
[] p
= Project
.getProjects().toArray(new Project
[0]);
2283 // make the reference_project be the first in the array
2284 if (null != reference_project
&& reference_project
!= p
[0]) {
2285 for (int i
=0; i
<p
.length
; i
++) {
2286 if (reference_project
== p
[i
]) {
2288 p
[0] = reference_project
;
2294 final Worker worker
= new Worker("Comparing all to all") {
2299 Utils
.log2("Asking for CATAParameters...");
2301 final CATAParameters cp
= new CATAParameters();
2302 if (!cp
.setup(false, regex
, true, true)) { // no regex used so far.
2306 cp
.with_source
= true; // so source points are stored in VectorString3D for each resampled and interpolated point
2308 String plot_dir
= null;
2311 DirectoryChooser dc
= new DirectoryChooser("Choose plots directory");
2312 plot_dir
= dc
.getDirectory();
2313 if (null == plot_dir
) {
2317 if (IJ
.isWindows()) plot_dir
= plot_dir
.replace('\\', '/');
2318 if (plot_dir
.endsWith("/")) plot_dir
+= "/";
2321 Utils
.log2("Gathering chains...");
2323 Object
[] ob
= gatherChains(p
, cp
); // will transform them as well to the reference found in the first project in the p array
2324 ArrayList
<Chain
> chains
= (ArrayList
<Chain
>)ob
[0];
2325 final ArrayList
[] p_chains
= (ArrayList
[])ob
[1]; // to keep track of each project's chains
2327 if (null == chains
) {
2332 Utils
.log2("Collecting bundles...");
2334 // Sort out into groups by unique names of lineage bundles
2335 final HashMap
<String
,ArrayList
<Chain
>> bundles
= new HashMap
<String
,ArrayList
<Chain
>>();
2336 for (Chain chain
: chains
) {
2337 String title
= chain
.getCellTitle();
2338 final String t
= title
.toLowerCase();
2340 if (-1 != t
.indexOf("unknown")) continue;
2341 if (-1 != t
.indexOf("peduncle")) continue;
2342 if (-1 != t
.indexOf("medial lobe")) continue;
2343 if (-1 != t
.indexOf("dorsal lobe")) continue;
2344 if (0 == t
.indexOf("lineage") || 0 == t
.indexOf("branch")) continue; // unnamed
2345 if (0 == t
.indexOf('[') || 0 == t
.indexOf('#')) continue; // unnamed
2348 //if (! (title.startsWith("DPLd") || title.startsWith("BAmv1")) ) continue;
2350 Utils
.log("Accepting " + title
);
2352 title
= title
.substring(0, title
.indexOf(' '));
2354 ArrayList
<Chain
> bc
= bundles
.get(title
); // lineage bundle instance chains
2356 bc
= new ArrayList
<Chain
>();
2357 bundles
.put(title
, bc
);
2362 Utils
.log2("Found " + bundles
.size() + " bundles.");
2366 final HashMap
<String
,VectorString3D
> condensed
= new HashMap
<String
,VectorString3D
>();
2368 Utils
.log2("Condensing each bundle...");
2370 // Condense each into a single VectorString3D
2371 for (Map
.Entry
<String
,ArrayList
<Chain
>> entry
: bundles
.entrySet()) {
2372 ArrayList
<Chain
> bc
= entry
.getValue();
2373 if (bc
.size() < 2) {
2374 Utils
.log2("Skipping single: " + entry
.getKey());
2377 VectorString3D
[] vs
= new VectorString3D
[bc
.size()];
2378 for (int i
=0; i
<vs
.length
; i
++) vs
[i
] = bc
.get(i
).vs
;
2379 condensed
.put(entry
.getKey(), condense(cp
, vs
, this));
2380 if (this.hasQuitted()) return;
2383 Utils
.log2("Plotting stdDev for each condensed bundle...");
2385 // Gather source for each, compute stdDev at each point and make a plot with it
2386 // X axis: from first to last point
2387 // Y axis: the stdDev at each point, computed from the group of points that contribute to each
2388 for (Map
.Entry
<String
,VectorString3D
> e
: condensed
.entrySet()) {
2389 final String name
= e
.getKey();
2390 final VectorString3D c
= e
.getValue();
2391 final Plot plot
= makePlot(cp
, name
, c
);
2392 //FAILS//plot.addLabel(10, cp.plot_height-5, name); // must be added after setting size
2393 if (show_plots
) plot
.show();
2394 else if (null != plot_dir
) new FileSaver(plot
.getImagePlus()).saveAsPng(plot_dir
+ name
.replace('/', '-') + ".png");
2397 // Show all condensed in 3D
2399 final LayerSet common_ls
= new LayerSet(p
[0], -1, "Common", 10, 10, 0, 0, 0, 512, 512, false, 2, new AffineTransform());
2400 final Display3D d3d
= Display3D
.getDisplay(common_ls
);
2401 for (String name
: bundles
.keySet()) {
2402 ArrayList
<Chain
> bc
= bundles
.get(name
);
2403 VectorString3D vs_merged
= condensed
.get(name
);
2404 for (Chain chain
: bc
) d3d
.addMesh(common_ls
, chain
.vs
, chain
.getCellTitle(), Color
.gray
);
2405 d3d
.addMesh(common_ls
, vs_merged
, name
, Color
.red
);
2408 if (null != map_condensed
) {
2409 map_condensed
.putAll(condensed
);
2412 Utils
.log2("Done.");
2414 } catch (Exception e
) {
2421 return Bureaucrat
.createAndStart(worker
, p
[0]);
2424 static public Plot
makePlot(final CATAParameters cp
, final String name
, final VectorString3D c
) {
2425 final double[] stdDev
= c
.getStdDevAtEachPoint();
2426 final double[] index
= new double[stdDev
.length
];
2427 for (int i
=0; i
<index
.length
; i
++) index
[i
] = i
;
2428 final Plot plot
= new Plot(name
, name
+ " -- Point index (delta: " + Utils
.cutNumber(c
.getDelta(), 2) + " " + c
.getCalibrationCopy().getUnits() + ")", "Std Dev", index
, stdDev
);
2429 plot
.setLimits(0, cp
.plot_max_x
, 0, cp
.plot_max_y
);
2430 plot
.setSize(cp
.plot_width
, cp
.plot_height
);
2431 plot
.setLineWidth(2);
2435 /** From a condensed VectorString3D, create the radius at each point. */
2436 static public double[] makeEnvelope(final CATAParameters cp
, final VectorString3D c
) {
2437 if (cp
.envelope_type
<= 0) { // defensive programming
2439 return c
.getStdDevAtEachPoint();
2442 final double[] width
= new double[c
.length()];
2444 if (cp
.envelope_type
< 3) { // 1 or 2
2445 // two or three std dev
2446 final double[] std
= c
.getStdDevAtEachPoint();
2447 final int f
= cp
.envelope_type
+ 1; // so: 2 or 3
2448 for (int i
=0; i
<std
.length
; i
++) {
2449 width
[i
] = f
* std
[i
];
2451 } else if (3 == cp
.envelope_type
) {
2452 // average distance from condensed to all sources
2454 for (ArrayList
<Point3d
> ap
: c
.getSource()) {
2456 for (Point3d p
: ap
) sum
+= c
.distance(i
, p
);
2457 width
[i
] = sum
/ ap
.size();
2460 } else if (4 == cp
.envelope_type
) {
2461 // max distance from condensed to all sources
2463 for (ArrayList
<Point3d
> ap
: c
.getSource()) {
2465 for (Point3d p
: ap
) max
= Math
.max(max
, c
.distance(i
, p
));
2474 static private final class Cell
<T
> {
2476 Cell(final T t1
, final T t2
) {
2480 public final boolean equals(final Object ob1
, final Object ob2
) {
2481 final Cell cell1
= (Cell
)ob1
;
2482 final Cell cell2
= (Cell
)ob2
;
2483 return (cell1
.t1
== cell2
.t1
&& cell1
.t2
== cell2
.t2
)
2484 || (cell2
.t1
== cell1
.t1
&& cell2
.t2
== cell1
.t2
);
2488 /** Do an all-to-all distance matrix of the given vs, then do a neighbor joining, do a weighted merge of the two VectorString3D being merged, and then finally output the resulting condensed unique VectorString3D with its source array full with all points that make each point in it. Expects VectorString3D which are already calibrated and transformed. */
2489 static public VectorString3D
condense(final CATAParameters cp
, final VectorString3D
[] vs
, final Worker worker
) throws Exception
{
2491 if (1 == vs
.length
) return vs
[0];
2494 if (0 == cp
.delta
) {
2495 for (int i
=0; i
<vs
.length
; i
++) {
2496 cp
.delta
+= vs
[i
].getAverageDelta();
2498 cp
.delta
/= vs
.length
;
2501 for (int i
=0; i
<vs
.length
; i
++) vs
[i
].resample(cp
.delta
, true);
2505 if (2 == vs
.length
) VectorString3D
.createInterpolatedPoints(new Editions(vs
[0], vs
[1], cp
.delta
, false), 0.5f
);
2506 } catch (Exception e
) {
2511 // Else, do neighbor joining
2512 final float[][] scores
= Compare
.scoreAllToAll(vs
, cp
.distance_type
, cp
.delta
, cp
.skip_ends
, cp
.max_mut
, cp
.min_chunk
, cp
.direct
, cp
.substring_matching
, worker
);
2513 final HashMap
<Compare
.Cell
<VectorString3D
>,Float
> table
= new HashMap
<Compare
.Cell
<VectorString3D
>,Float
>();
2514 // Input the half matrix only into the table, since it's mirrored. And without the diagonal of zeros:
2515 for (int i
=1; i
<scores
.length
; i
++) {
2516 for (int j
=0; j
<i
; j
++) {
2517 table
.put(new Cell(vs
[i
], vs
[j
]), scores
[i
][j
]);
2521 final HashSet
<VectorString3D
> remaining
= new HashSet
<VectorString3D
>();
2522 for (VectorString3D v
: vs
) remaining
.add(v
);
2524 while (table
.size() > 0) {
2525 if (null != worker
&& worker
.hasQuitted()) {
2528 // find smallest value
2529 float min
= Float
.MAX_VALUE
;
2530 Cell
<VectorString3D
> cell
= null;
2531 for (Map
.Entry
<Cell
<VectorString3D
>,Float
> e
: table
.entrySet()) {
2532 final float f
= e
.getValue();
2538 // pop cells from table
2539 // done below//table.remove(cell);
2540 for (Iterator
<Cell
<VectorString3D
>> it
= table
.keySet().iterator(); it
.hasNext(); ) {
2541 final Cell
<VectorString3D
> c
= it
.next();
2542 if (c
.t1
== cell
.t1
|| c
.t2
== cell
.t2
2543 || c
.t2
== cell
.t1
|| c
.t1
== cell
.t2
) {
2547 // pop the two merged VectorString3D
2548 remaining
.remove(cell
.t1
);
2549 remaining
.remove(cell
.t2
);
2551 // merge, weighted by number of sources of each
2552 final double alpha
= (double)(cell
.t1
.getNSources()) / (double)(cell
.t1
.getNSources() + cell
.t2
.getNSources()); // in createInterpolated, the alpha is the opposite of what one would think: a 0.2 alpha means 0.8 for the first and 0.2 for the second. So alpha should be 1-alpha
2553 final Editions eds
= new Editions(cell
.t1
, cell
.t2
, cp
.delta
, false);
2554 VectorString3D vs_merged
= null;
2555 if (cp
.cut_uneven_ends
) {
2556 // crop ends to eliminate strings of insertions or deletions sparsed by strings of max cp.max_mut mutations inside
2557 // (This reduces or eliminates variability noise caused by unequal sequence length)
2558 final int[][] editions
= eds
.getEditions();
2560 int last
= editions
.length
-1;
2562 for (int i
=0; i
<last
; i
++) {
2563 if (Editions
.MUTATION
== editions
[i
][0]) {
2565 if (n_mut
> cp
.max_mut
) {
2566 first
= i
- n_mut
+ 1;
2572 for (int i
=last
; i
>first
; i
--) {
2573 if (Editions
.MUTATION
== editions
[i
][0]) {
2575 if (n_mut
> cp
.max_mut
) {
2576 last
= i
+ n_mut
- 1;
2581 vs_merged
= VectorString3D
.createInterpolatedPoints(eds
, alpha
, first
, last
);
2583 vs_merged
= VectorString3D
.createInterpolatedPoints(eds
, alpha
);
2585 vs_merged
.resample(cp
.delta
, true);
2587 // add a new cell for each possible comparison with all other unique vs
2588 for (VectorString3D v
: remaining
) {
2589 Object
[] ob
= findBestMatch(vs_merged
, v
, cp
.delta
, cp
.skip_ends
, cp
.max_mut
, cp
.min_chunk
, cp
.distance_type
, cp
.direct
, cp
.substring_matching
);
2590 Editions ed
= (Editions
)ob
[0];
2591 float score
= (float)getScore(ed
, cp
.skip_ends
, cp
.max_mut
, cp
.min_chunk
, cp
.distance_type
);
2592 table
.put(new Cell(vs_merged
, v
), score
);
2595 // add the new VectorString3D
2596 remaining
.add(vs_merged
);
2599 // Only the last vs_merged should remain:
2602 if (1 != remaining
.size()) {
2603 Utils
.log2("WARNING: remaining.size() == " + remaining
.size());
2606 return remaining
.iterator().next();
2609 /** Iterates all tabs, removing those that contain queries from the given project. */
2610 static public void removeProject(final Project project
) {
2611 if (null == ht_tabs
) return;
2612 // avoid concurrent modifications
2613 HashSet
<Map
.Entry
<JScrollPane
,Chain
>> set
= new HashSet
<Map
.Entry
<JScrollPane
,Chain
>>();
2614 set
.addAll(ht_tabs
.entrySet());
2615 for (Iterator
<Map
.Entry
<JScrollPane
,Chain
>> it
= set
.iterator(); it
.hasNext(); ) {
2616 Map
.Entry
<JScrollPane
,Chain
> e
= it
.next();
2617 JScrollPane jsp
= e
.getKey();
2618 JTable table
= (JTable
)jsp
.getViewport().getView();
2619 QueryHolderTableModel qt
= (QueryHolderTableModel
)table
.getModel();
2621 qt
.qh
.remove(project
);
2622 Chain chain
= e
.getValue();
2623 if (0 == table
.getRowCount() || chain
.pipes
.get(0).getProject() == project
) {
2624 ht_tabs
.remove(jsp
);
2630 /** Transform all points of all VectorString3D in vs using a Moving Least Squares Transform defined by the pairing of points in source to those in target.
2631 * In short, bring source into target. */
2632 static public List
<VectorString3D
> transferVectorStrings(final List
<VectorString3D
> vs
, final List
<Tuple3d
> source
, final List
<Tuple3d
> target
) throws Exception
{
2633 if (source
.size() != target
.size()) {
2634 Utils
.log2("Could not generate a MovingLeastSquaresTransform: different number of source and target points.");
2637 if (source
.size() < 1 || target
.size() < 1) {
2638 Utils
.log2("Cannot transform with less than one point correspondence!");
2642 // 1 - Create the MovingLeastSquaresTransform from the point matches
2644 ArrayList
<PointMatch
> pm
= new ArrayList
<PointMatch
>();
2645 for (final Iterator
<Tuple3d
> si
= source
.iterator(), ti
= target
.iterator(); si
.hasNext(); ) {
2646 Tuple3d sp
= si
.next();
2647 Tuple3d tp
= ti
.next();
2648 pm
.add(new PointMatch(new mpicbg
.models
.Point(new float[]{(float)sp
.x
, (float)sp
.y
, (float)sp
.z
}), new mpicbg
.models
.Point(new float[]{(float)tp
.x
, (float)tp
.y
, (float)tp
.z
}), 1));
2651 MovingLeastSquaresTransform mls
= new MovingLeastSquaresTransform();
2652 mls
.setModel(AffineModel3D
.class);
2655 final float[] point
= new float[3];
2657 // 1.1 - Test: transfer source points
2658 for (final Iterator
<Tuple3d
> si
= source
.iterator(), ti
= target
.iterator(); si
.hasNext(); ) {
2659 Tuple3d sp
= si
.next();
2660 point
[0] = (float) sp
.x
;
2661 point
[1] = (float) sp
.y
;
2662 point
[2] = (float) sp
.z
;
2663 mls
.applyInPlace(point
);
2665 Tuple3d tp
= ti
.next();
2666 Utils
.log2("== target: " + (float)tp
.x
+ ", " + (float)tp
.y
+ ", " + (float)tp
.z
+
2667 "\n o source: " + (float)sp
.x
+ ", " + (float)sp
.y
+ ", " + (float)sp
.z
+
2669 "\n source: " + point
[0] + ", " + point
[1] + ", " + point
[2]);
2672 // 2 - Transfer each VectorString3D in vs with mls
2673 final List
<VectorString3D
> vt
= new ArrayList
<VectorString3D
>();
2674 for (final VectorString3D vi
: vs
) {
2675 // The points of the VectorString3D:
2676 final double[] x
= vi
.getPoints(0);
2677 final double[] y
= vi
.getPoints(1);
2678 final double[] z
= vi
.getPoints(2);
2679 // Empty arrays to fill with the points to transfer:
2680 final double[] tx
= new double[x
.length
];
2681 final double[] ty
= new double[x
.length
];
2682 final double[] tz
= new double[x
.length
];
2683 // Transfer point by point:
2684 for (int i
=0; i
<x
.length
; i
++) {
2685 point
[0] = (float)x
[i
];
2686 point
[1] = (float)y
[i
];
2687 point
[2] = (float)z
[i
];
2688 mls
.applyInPlace(point
);
2694 vt
.add(new VectorString3D(tx
, ty
, tz
, vi
.isClosed()));
2695 } catch (Exception e
) {}
2701 /** Transfer vs via a moving least squares transform by matching source named points into equally named target named points.
2702 * If no points in common, returns null. */
2703 static public List
<VectorString3D
> transferVectorStrings(final List
<VectorString3D
> vs
, final Map
<String
,Tuple3d
> source
, final Map
<String
,Tuple3d
> target
) throws Exception
{
2704 if (null == source
|| null == target
) return null;
2705 final List
<Tuple3d
> so
= new ArrayList
<Tuple3d
>();
2706 final List
<Tuple3d
> ta
= new ArrayList
<Tuple3d
>();
2707 for (final Map
.Entry
<String
,Tuple3d
> e
: target
.entrySet()) {
2708 final Tuple3d point
= source
.get(e
.getKey());
2709 if (null != point
) {
2711 ta
.add(e
.getValue());
2714 if (0 == so
.size()) {
2715 Utils
.log2("No points in common!");
2718 Utils
.log2("Found points in common: " + so
.size());
2719 return transferVectorStrings(vs
, so
, ta
);
2722 static public List
<VectorString3D
> transferVectorStrings(final List
<VectorString3D
> vs
, final ProjectThing source_fiduciary
, final ProjectThing target_fiduciary
) throws Exception
{
2723 return transferVectorStrings(vs
, extractPoints(source_fiduciary
), extractPoints(target_fiduciary
));
2726 /** Extracts the list of fiduciary points from the fiducial parent and, if their name is different than "ball", adds their title as key and their first ball as a fiduciary point value of the returned map. The map is never null but could be empty.
2727 * The values are calibrated. */
2728 static public Map
<String
,Tuple3d
> extractPoints(final ProjectThing fiducial
) {
2729 if (!fiducial
.getType().equals("fiducial_points")) {
2730 Utils
.log("Can only extract points from 'fiducial_points' type.");
2733 ArrayList
<ProjectThing
> fiducials
= fiducial
.getChildren();
2734 if (null == fiducials
|| 0 == fiducials
.size()) {
2735 Utils
.log("No fiducial points can be extracted from " + fiducial
);
2738 final Map
<String
,Tuple3d
> fide
= new HashMap
<String
,Tuple3d
>();
2739 for (final ProjectThing child
: fiducials
) {
2740 ArrayList
<ProjectThing
> balls
= child
.findChildrenOfType("ball");
2741 if (null == balls
|| 0 == balls
.size()) {
2742 Utils
.log2("Ignoring empty fiducial " + child
);
2745 // pick the first one only
2746 final Ball ball
= (Ball
) balls
.get(0).getObject();
2747 final String title
= child
.getType();
2748 final double[][] b
= ball
.getWorldBalls(); // calibrated
2750 // get the first one only
2751 fide
.put(title
.toLowerCase(), new Point3d(b
[0][0], b
[0][1], b
[0][2]));
2752 Utils
.log2("Found fiducial point " + title
.toLowerCase());