Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
AlignmentAnnotation | 45 | 459 | 298 | ||
AlignmentAnnotation.AnnotCharSequence | 542 | 11 | 17 |
1 | /* | |
2 | * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) | |
3 | * Copyright (C) $$Year-Rel$$ The Jalview Authors | |
4 | * | |
5 | * This file is part of Jalview. | |
6 | * | |
7 | * Jalview is free software: you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation, either version 3 | |
10 | * of the License, or (at your option) any later version. | |
11 | * | |
12 | * Jalview is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty | |
14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
15 | * PURPOSE. See the GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with Jalview. If not, see <http://www.gnu.org/licenses/>. | |
19 | * The Jalview Authors are detailed in the 'AUTHORS' file. | |
20 | */ | |
21 | package jalview.datamodel; | |
22 | ||
23 | import java.util.ArrayList; | |
24 | import java.util.Arrays; | |
25 | import java.util.Collection; | |
26 | import java.util.Collections; | |
27 | import java.util.HashMap; | |
28 | import java.util.Iterator; | |
29 | import java.util.List; | |
30 | import java.util.Locale; | |
31 | import java.util.Map; | |
32 | import java.util.Map.Entry; | |
33 | ||
34 | import jalview.analysis.Rna; | |
35 | import jalview.analysis.SecStrConsensus.SimpleBP; | |
36 | import jalview.analysis.WUSSParseException; | |
37 | import jalview.structure.StructureImportSettings; | |
38 | ||
39 | /** | |
40 | * DOCUMENT ME! | |
41 | * | |
42 | * @author $author$ | |
43 | * @version $Revision$ | |
44 | */ | |
45 | public class AlignmentAnnotation | |
46 | { | |
47 | ||
48 | private static final String ANNOTATION_ID_PREFIX = "ann"; | |
49 | ||
50 | /* | |
51 | * Identifers for different types of profile data | |
52 | */ | |
53 | public static final int SEQUENCE_PROFILE = 0; | |
54 | ||
55 | public static final int STRUCTURE_PROFILE = 1; | |
56 | ||
57 | public static final int CDNA_PROFILE = 2; | |
58 | ||
59 | private static long counter = 0; | |
60 | ||
61 | private long noOfSequencesIncluded = -1; | |
62 | ||
63 | /** | |
64 | * If true, this annotations is calculated every edit, eg consensus, quality | |
65 | * or conservation graphs | |
66 | */ | |
67 | public boolean autoCalculated = false; | |
68 | ||
69 | /** | |
70 | * unique ID for this annotation, used to match up the same annotation row | |
71 | * shown in multiple views and alignments | |
72 | */ | |
73 | public String annotationId; | |
74 | ||
75 | /** | |
76 | * the sequence this annotation is associated with (or null) | |
77 | */ | |
78 | public SequenceI sequenceRef; | |
79 | ||
80 | /** label shown in dropdown menus and in the annotation label area */ | |
81 | public String label; | |
82 | ||
83 | /** longer description text shown as a tooltip */ | |
84 | public String description; | |
85 | ||
86 | /** Array of annotations placed in the current coordinate system */ | |
87 | public Annotation[] annotations; | |
88 | ||
89 | public List<SimpleBP> bps = null; | |
90 | ||
91 | /** | |
92 | * RNA secondary structure contact positions | |
93 | */ | |
94 | public SequenceFeature[] _rnasecstr = null; | |
95 | ||
96 | /** | |
97 | * position of annotation resulting in invalid WUSS parsing or -1. -2 means | |
98 | * there was no RNA structure in this annotation | |
99 | */ | |
100 | private long invalidrnastruc = -2; | |
101 | ||
102 | /** | |
103 | * the type of temperature factor plot (if it is one) | |
104 | */ | |
105 | private StructureImportSettings.TFType tfType = StructureImportSettings.TFType.DEFAULT; | |
106 | ||
107 | 203 | public void setTFType(StructureImportSettings.TFType t) |
108 | { | |
109 | 203 | tfType = t; |
110 | } | |
111 | ||
112 | 0 | public StructureImportSettings.TFType getTFType() |
113 | { | |
114 | 0 | return tfType; |
115 | } | |
116 | ||
117 | /** | |
118 | * Updates the _rnasecstr field Determines the positions that base pair and | |
119 | * the positions of helices based on secondary structure from a Stockholm file | |
120 | * | |
121 | * @param rnaAnnotation | |
122 | */ | |
123 | 1350 | private void _updateRnaSecStr(CharSequence rnaAnnotation) |
124 | { | |
125 | 1350 | try |
126 | { | |
127 | 1350 | _rnasecstr = Rna.getHelixMap(rnaAnnotation); |
128 | 974 | invalidrnastruc = -1; |
129 | } catch (WUSSParseException px) | |
130 | { | |
131 | // DEBUG jalview.bin.Console.outPrintln(px); | |
132 | 376 | invalidrnastruc = px.getProblemPos(); |
133 | } | |
134 | 1350 | if (invalidrnastruc > -1) |
135 | { | |
136 | 376 | return; |
137 | } | |
138 | ||
139 | 974 | if (_rnasecstr != null && _rnasecstr.length > 0) |
140 | { | |
141 | // show all the RNA secondary structure annotation symbols. | |
142 | 974 | isrna = true; |
143 | 974 | showAllColLabels = true; |
144 | 974 | scaleColLabel = true; |
145 | 974 | _markRnaHelices(); |
146 | } | |
147 | // jalview.bin.Console.outPrintln("featuregroup " + | |
148 | // _rnasecstr[0].getFeatureGroup()); | |
149 | ||
150 | } | |
151 | ||
152 | 974 | private void _markRnaHelices() |
153 | { | |
154 | 974 | int mxval = 0; |
155 | // Figure out number of helices | |
156 | // Length of rnasecstr is the number of pairs of positions that base pair | |
157 | // with each other in the secondary structure | |
158 | 19791 | for (int x = 0; x < _rnasecstr.length; x++) |
159 | { | |
160 | ||
161 | /* | |
162 | * jalview.bin.Console.outPrintln(this.annotation._rnasecstr[x] + " Begin" + | |
163 | * this.annotation._rnasecstr[x].getBegin()); | |
164 | */ | |
165 | // jalview.bin.Console.outPrintln(this.annotation._rnasecstr[x].getFeatureGroup()); | |
166 | 18817 | int val = 0; |
167 | 18817 | try |
168 | { | |
169 | 18817 | val = Integer.valueOf(_rnasecstr[x].getFeatureGroup()); |
170 | 18817 | if (mxval < val) |
171 | { | |
172 | 311 | mxval = val; |
173 | } | |
174 | } catch (NumberFormatException q) | |
175 | { | |
176 | } | |
177 | 18817 | ; |
178 | ||
179 | 18817 | annotations[_rnasecstr[x].getBegin()].value = val; |
180 | 18817 | annotations[_rnasecstr[x].getEnd()].value = val; |
181 | ||
182 | // annotations[_rnasecstr[x].getBegin()].displayCharacter = "" + val; | |
183 | // annotations[_rnasecstr[x].getEnd()].displayCharacter = "" + val; | |
184 | } | |
185 | 974 | setScore(mxval); |
186 | } | |
187 | ||
188 | /** | |
189 | * Get the RNA Secondary Structure SequenceFeature Array if present | |
190 | */ | |
191 | 12 | public SequenceFeature[] getRnaSecondaryStructure() |
192 | { | |
193 | 12 | return this._rnasecstr; |
194 | } | |
195 | ||
196 | /** | |
197 | * Check the RNA Secondary Structure is equivalent to one in given | |
198 | * AlignmentAnnotation param | |
199 | */ | |
200 | 4 | public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that) |
201 | { | |
202 | 4 | return rnaSecondaryStructureEquivalent(that, true); |
203 | } | |
204 | ||
205 | 4 | public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, |
206 | boolean compareType) | |
207 | { | |
208 | 4 | SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure(); |
209 | 4 | SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure(); |
210 | 4 | if (thisSfArray == null || thatSfArray == null) |
211 | { | |
212 | 0 | return thisSfArray == null && thatSfArray == null; |
213 | } | |
214 | 4 | if (thisSfArray.length != thatSfArray.length) |
215 | { | |
216 | 1 | return false; |
217 | } | |
218 | 3 | Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted |
219 | // like this | |
220 | 3 | Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted |
221 | // like this | |
222 | 15 | for (int i = 0; i < thisSfArray.length; i++) |
223 | { | |
224 | 13 | SequenceFeature thisSf = thisSfArray[i]; |
225 | 13 | SequenceFeature thatSf = thatSfArray[i]; |
226 | 13 | if (compareType) |
227 | { | |
228 | 13 | if (thisSf.getType() == null || thatSf.getType() == null) |
229 | { | |
230 | 0 | if (thisSf.getType() == null && thatSf.getType() == null) |
231 | { | |
232 | 0 | continue; |
233 | } | |
234 | else | |
235 | { | |
236 | 0 | return false; |
237 | } | |
238 | } | |
239 | 13 | if (!thisSf.getType().equals(thatSf.getType())) |
240 | { | |
241 | 0 | return false; |
242 | } | |
243 | } | |
244 | 13 | if (!(thisSf.getBegin() == thatSf.getBegin() |
245 | && thisSf.getEnd() == thatSf.getEnd())) | |
246 | { | |
247 | 1 | return false; |
248 | } | |
249 | } | |
250 | 2 | return true; |
251 | ||
252 | } | |
253 | ||
254 | /** | |
255 | * map of positions in the associated annotation | |
256 | */ | |
257 | private Map<Integer, Annotation> sequenceMapping; | |
258 | ||
259 | /** | |
260 | * lower range for quantitative data | |
261 | */ | |
262 | public float graphMin; | |
263 | ||
264 | /** | |
265 | * Upper range for quantitative data | |
266 | */ | |
267 | public float graphMax; | |
268 | ||
269 | /** | |
270 | * Score associated with label and description. | |
271 | */ | |
272 | public double score = Double.NaN; | |
273 | ||
274 | /** | |
275 | * flag indicating if annotation has a score. | |
276 | */ | |
277 | public boolean hasScore = false; | |
278 | ||
279 | public GraphLine threshold; | |
280 | ||
281 | // Graphical hints and tips | |
282 | ||
283 | /** Can this row be edited by the user ? */ | |
284 | public boolean editable = false; | |
285 | ||
286 | /** Indicates if annotation has a graphical symbol track */ | |
287 | public boolean hasIcons; // | |
288 | ||
289 | /** Indicates if annotation has a text character label */ | |
290 | public boolean hasText; | |
291 | ||
292 | /** is the row visible */ | |
293 | public boolean visible = true; | |
294 | ||
295 | public int graphGroup = -1; | |
296 | ||
297 | /** Displayed height of row in pixels */ | |
298 | public int height = 0; | |
299 | ||
300 | public int graph = 0; | |
301 | ||
302 | public int graphHeight = 40; | |
303 | ||
304 | public boolean padGaps = false; | |
305 | ||
306 | public static final int NO_GRAPH = 0; | |
307 | ||
308 | public static final int BAR_GRAPH = 1; | |
309 | ||
310 | public static final int LINE_GRAPH = 2; | |
311 | ||
312 | public static final int CONTACT_MAP = 4; | |
313 | ||
314 | /** | |
315 | * property that when set to non-empty string disables display of column | |
316 | * groups defined on the contact matrix | |
317 | */ | |
318 | public static final String CONTACT_MAP_NOGROUPS = "CMNOGRPS"; | |
319 | ||
320 | public boolean belowAlignment = true; | |
321 | ||
322 | public SequenceGroup groupRef = null; | |
323 | ||
324 | /** | |
325 | * display every column label, even if there is a row of identical labels | |
326 | */ | |
327 | public boolean showAllColLabels = false; | |
328 | ||
329 | /** | |
330 | * scale the column label to fit within the alignment column. | |
331 | */ | |
332 | public boolean scaleColLabel = false; | |
333 | ||
334 | /** | |
335 | * centre the column labels relative to the alignment column | |
336 | */ | |
337 | public boolean centreColLabels = false; | |
338 | ||
339 | private boolean isrna; | |
340 | ||
341 | 213 | public static int getGraphValueFromString(String string) |
342 | { | |
343 | 213 | if (string.equalsIgnoreCase("BAR_GRAPH")) |
344 | { | |
345 | 23 | return BAR_GRAPH; |
346 | } | |
347 | 190 | else if (string.equalsIgnoreCase("LINE_GRAPH")) |
348 | { | |
349 | 162 | return LINE_GRAPH; |
350 | } | |
351 | else | |
352 | { | |
353 | 28 | return NO_GRAPH; |
354 | } | |
355 | } | |
356 | ||
357 | /** | |
358 | * Creates a new AlignmentAnnotation object. | |
359 | * | |
360 | * @param label | |
361 | * short label shown under sequence labels | |
362 | * @param description | |
363 | * text displayed on mouseover | |
364 | * @param annotations | |
365 | * set of positional annotation elements | |
366 | */ | |
367 | 34569 | public AlignmentAnnotation(String label, String description, |
368 | Annotation[] annotations) | |
369 | { | |
370 | 34569 | setAnnotationId(); |
371 | // always editable? | |
372 | 34569 | editable = true; |
373 | 34569 | this.label = label; |
374 | 34569 | this.description = description; |
375 | 34569 | this.annotations = annotations; |
376 | ||
377 | 34569 | validateRangeAndDisplay(); |
378 | } | |
379 | ||
380 | /** | |
381 | * Checks if annotation labels represent secondary structures | |
382 | * | |
383 | */ | |
384 | 5768 | void areLabelsSecondaryStructure() |
385 | { | |
386 | 5768 | boolean nonSSLabel = false; |
387 | 5768 | isrna = false; |
388 | 5768 | StringBuffer rnastring = new StringBuffer(); |
389 | ||
390 | 5768 | char firstChar = 0; |
391 | 616409 | for (int i = 0; i < annotations.length; i++) |
392 | { | |
393 | // DEBUG jalview.bin.Console.outPrintln(i + ": " + annotations[i]); | |
394 | 610640 | if (annotations[i] == null) |
395 | { | |
396 | 118110 | continue; |
397 | } | |
398 | 492531 | if (annotations[i].secondaryStructure == 'H' |
399 | || annotations[i].secondaryStructure == 'E') | |
400 | { | |
401 | // DEBUG jalview.bin.Console.outPrintln( "/H|E/ '" + | |
402 | // annotations[i].secondaryStructure + "'"); | |
403 | 51241 | hasIcons |= true; |
404 | } | |
405 | else | |
406 | // Check for RNA secondary structure | |
407 | { | |
408 | // DEBUG jalview.bin.Console.outPrintln( "/else/ '" + | |
409 | // annotations[i].secondaryStructure + "'"); | |
410 | // TODO: 2.8.2 should this ss symbol validation check be a function in | |
411 | // RNA/ResidueProperties ? | |
412 | // allow for DSSP extended code: | |
413 | // https://www.wikidoc.org/index.php/Secondary_structure#The_DSSP_code | |
414 | // GHITEBS as well as C and X (for missing?) | |
415 | 441290 | if (annotations[i].secondaryStructure == '(' |
416 | || annotations[i].secondaryStructure == '[' | |
417 | || annotations[i].secondaryStructure == '<' | |
418 | || annotations[i].secondaryStructure == '{' | |
419 | || annotations[i].secondaryStructure == 'A' | |
420 | // || annotations[i].secondaryStructure == 'B' | |
421 | // || annotations[i].secondaryStructure == 'C' | |
422 | || annotations[i].secondaryStructure == 'D' | |
423 | // || annotations[i].secondaryStructure == 'E' // ambiguous on | |
424 | // its own -- already checked above | |
425 | || annotations[i].secondaryStructure == 'F' | |
426 | // || annotations[i].secondaryStructure == 'G' | |
427 | // || annotations[i].secondaryStructure == 'H' // ambiguous on | |
428 | // its own -- already checked above | |
429 | // || annotations[i].secondaryStructure == 'I' | |
430 | || annotations[i].secondaryStructure == 'J' | |
431 | || annotations[i].secondaryStructure == 'K' | |
432 | || annotations[i].secondaryStructure == 'L' | |
433 | || annotations[i].secondaryStructure == 'M' | |
434 | || annotations[i].secondaryStructure == 'N' | |
435 | || annotations[i].secondaryStructure == 'O' | |
436 | || annotations[i].secondaryStructure == 'P' | |
437 | || annotations[i].secondaryStructure == 'Q' | |
438 | || annotations[i].secondaryStructure == 'R' | |
439 | // || annotations[i].secondaryStructure == 'S' | |
440 | // || annotations[i].secondaryStructure == 'T' | |
441 | || annotations[i].secondaryStructure == 'U' | |
442 | || annotations[i].secondaryStructure == 'V' | |
443 | || annotations[i].secondaryStructure == 'W' | |
444 | // || annotations[i].secondaryStructure == 'X' | |
445 | || annotations[i].secondaryStructure == 'Y' | |
446 | || annotations[i].secondaryStructure == 'Z') | |
447 | { | |
448 | 14422 | hasIcons |= true; |
449 | 14422 | isrna |= true; |
450 | } | |
451 | } | |
452 | ||
453 | // jalview.bin.Console.outPrintln("displaychar " + | |
454 | // annotations[i].displayCharacter); | |
455 | ||
456 | 492531 | if (annotations[i].displayCharacter == null |
457 | || annotations[i].displayCharacter.length() == 0) | |
458 | { | |
459 | 165977 | rnastring.append('.'); |
460 | 165977 | continue; |
461 | } | |
462 | 326554 | if (annotations[i].displayCharacter.length() == 1) |
463 | { | |
464 | 273214 | firstChar = annotations[i].displayCharacter.charAt(0); |
465 | // check to see if it looks like a sequence or is secondary structure | |
466 | // labelling. | |
467 | 273214 | if (annotations[i].secondaryStructure != ' ' && !hasIcons && |
468 | // Uncomment to only catch case where | |
469 | // displayCharacter==secondary | |
470 | // Structure | |
471 | // to correctly redisplay SS annotation imported from Stockholm, | |
472 | // exported to JalviewXML and read back in again. | |
473 | // && | |
474 | // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure | |
475 | firstChar != ' ' && firstChar != '$' && firstChar != 0xCE | |
476 | && firstChar != '(' && firstChar != '[' && firstChar != '<' | |
477 | && firstChar != '{' && firstChar != 'A' && firstChar != 'B' | |
478 | && firstChar != 'C' && firstChar != 'D' && firstChar != 'E' | |
479 | && firstChar != 'F' && firstChar != 'G' && firstChar != 'H' | |
480 | && firstChar != 'I' && firstChar != 'J' && firstChar != 'K' | |
481 | && firstChar != 'L' && firstChar != 'M' && firstChar != 'N' | |
482 | && firstChar != 'O' && firstChar != 'P' && firstChar != 'Q' | |
483 | && firstChar != 'R' && firstChar != 'S' && firstChar != 'T' | |
484 | && firstChar != 'U' && firstChar != 'V' && firstChar != 'W' | |
485 | && firstChar != 'X' && firstChar != 'Y' && firstChar != 'Z' | |
486 | && firstChar != '-' | |
487 | && firstChar < jalview.schemes.ResidueProperties.aaIndex.length) | |
488 | { | |
489 | 174 | if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO: |
490 | // parameterise | |
491 | // to | |
492 | // gap | |
493 | // symbol | |
494 | // number | |
495 | { | |
496 | 11 | nonSSLabel = true; |
497 | } | |
498 | } | |
499 | } | |
500 | else | |
501 | { | |
502 | 53340 | rnastring.append(annotations[i].displayCharacter.charAt(1)); |
503 | } | |
504 | ||
505 | 326554 | if (annotations[i].displayCharacter.length() > 0) |
506 | { | |
507 | 326554 | hasText = true; |
508 | } | |
509 | } | |
510 | ||
511 | 5768 | if (nonSSLabel) |
512 | { | |
513 | 1 | hasIcons = false; |
514 | 12 | for (int j = 0; j < annotations.length; j++) |
515 | { | |
516 | 11 | if (annotations[j] != null |
517 | && annotations[j].secondaryStructure != ' ') | |
518 | { | |
519 | 11 | annotations[j].displayCharacter = String |
520 | .valueOf(annotations[j].secondaryStructure); | |
521 | 11 | annotations[j].secondaryStructure = ' '; |
522 | } | |
523 | ||
524 | } | |
525 | } | |
526 | else | |
527 | { | |
528 | 5767 | if (isrna) |
529 | { | |
530 | 931 | _updateRnaSecStr(new AnnotCharSequence()); |
531 | } | |
532 | } | |
533 | } | |
534 | ||
535 | /** | |
536 | * flyweight access to positions in the alignment annotation row for RNA | |
537 | * processing | |
538 | * | |
539 | * @author jimp | |
540 | * | |
541 | */ | |
542 | private class AnnotCharSequence implements CharSequence | |
543 | { | |
544 | int offset = 0; | |
545 | ||
546 | int max = 0; | |
547 | ||
548 | 1357 | public AnnotCharSequence() |
549 | { | |
550 | 1357 | this(0, annotations.length); |
551 | } | |
552 | ||
553 | 1357 | AnnotCharSequence(int start, int end) |
554 | { | |
555 | 1357 | offset = start; |
556 | 1357 | max = end; |
557 | } | |
558 | ||
559 | 0 | @Override |
560 | public CharSequence subSequence(int start, int end) | |
561 | { | |
562 | 0 | return new AnnotCharSequence(offset + start, offset + end); |
563 | } | |
564 | ||
565 | 65811 | @Override |
566 | public int length() | |
567 | { | |
568 | 65811 | return max - offset; |
569 | } | |
570 | ||
571 | 64880 | @Override |
572 | public char charAt(int index) | |
573 | { | |
574 | 64880 | return ((index + offset < 0) || (index + offset) >= max |
575 | || annotations[index + offset] == null | |
576 | || (annotations[index + offset].secondaryStructure <= ' ') | |
577 | ? ' ' | |
578 | 27030 | : annotations[index + offset].displayCharacter == null |
579 | || annotations[index | |
580 | + offset].displayCharacter | |
581 | .length() == 0 | |
582 | ? annotations[index | |
583 | + offset].secondaryStructure | |
584 | : annotations[index | |
585 | + offset].displayCharacter | |
586 | .charAt(0)); | |
587 | } | |
588 | ||
589 | 426 | @Override |
590 | public String toString() | |
591 | { | |
592 | 426 | char[] string = new char[max - offset]; |
593 | 426 | int mx = annotations.length; |
594 | ||
595 | 31685 | for (int i = offset; i < mx; i++) |
596 | { | |
597 | 31259 | string[i] = (annotations[i] == null |
598 | || (annotations[i].secondaryStructure <= 32)) | |
599 | ? ' ' | |
600 | 13306 | : (annotations[i].displayCharacter == null |
601 | || annotations[i].displayCharacter | |
602 | .length() == 0 | |
603 | ? annotations[i].secondaryStructure | |
604 | : annotations[i].displayCharacter | |
605 | .charAt(0)); | |
606 | } | |
607 | 426 | return new String(string); |
608 | } | |
609 | }; | |
610 | ||
611 | private long _lastrnaannot = -1; | |
612 | ||
613 | 3411 | public String getRNAStruc() |
614 | { | |
615 | 3410 | if (isrna) |
616 | { | |
617 | 426 | String rnastruc = new AnnotCharSequence().toString(); |
618 | 426 | if (_lastrnaannot != rnastruc.hashCode()) |
619 | { | |
620 | // ensure rna structure contacts are up to date | |
621 | 419 | _lastrnaannot = rnastruc.hashCode(); |
622 | 419 | _updateRnaSecStr(rnastruc); |
623 | } | |
624 | 426 | return rnastruc; |
625 | } | |
626 | 2985 | return null; |
627 | } | |
628 | ||
629 | /** | |
630 | * Creates a new AlignmentAnnotation object. | |
631 | * | |
632 | * @param label | |
633 | * DOCUMENT ME! | |
634 | * @param description | |
635 | * DOCUMENT ME! | |
636 | * @param annotations | |
637 | * DOCUMENT ME! | |
638 | * @param min | |
639 | * DOCUMENT ME! | |
640 | * @param max | |
641 | * DOCUMENT ME! | |
642 | * @param winLength | |
643 | * DOCUMENT ME! | |
644 | */ | |
645 | 3157 | public AlignmentAnnotation(String label, String description, |
646 | Annotation[] annotations, float min, float max, int graphType) | |
647 | { | |
648 | 3157 | setAnnotationId(); |
649 | // graphs are not editable | |
650 | 3157 | editable = graphType == 0; |
651 | ||
652 | 3157 | this.label = label; |
653 | 3157 | this.description = description; |
654 | 3157 | this.annotations = annotations; |
655 | 3157 | graph = graphType; |
656 | 3157 | graphMin = min; |
657 | 3157 | graphMax = max; |
658 | 3157 | validateRangeAndDisplay(); |
659 | } | |
660 | ||
661 | /** | |
662 | * checks graphMin and graphMax, secondary structure symbols, sets graphType | |
663 | * appropriately, sets null labels to the empty string if appropriate. | |
664 | */ | |
665 | 39250 | public void validateRangeAndDisplay() |
666 | { | |
667 | ||
668 | 39250 | if (annotations == null) |
669 | { | |
670 | 33482 | visible = false; // try to prevent renderer from displaying. |
671 | 33482 | invalidrnastruc = -1; |
672 | 33482 | return; // this is a non-annotation row annotation - ie a sequence score. |
673 | } | |
674 | ||
675 | 5768 | int graphType = graph; |
676 | 5768 | float min = graphMin; |
677 | 5768 | float max = graphMax; |
678 | 5768 | boolean drawValues = true; |
679 | 5768 | _linecolour = null; |
680 | 5768 | if (min == max) |
681 | { | |
682 | 2772 | min = 999999999; |
683 | 509044 | for (int i = 0; i < annotations.length; i++) |
684 | { | |
685 | 506273 | if (annotations[i] == null) |
686 | { | |
687 | 109087 | continue; |
688 | } | |
689 | ||
690 | 397185 | if (drawValues && annotations[i].displayCharacter != null |
691 | && annotations[i].displayCharacter.length() > 1) | |
692 | { | |
693 | 1222 | drawValues = false; |
694 | } | |
695 | ||
696 | 397185 | if (annotations[i].value > max) |
697 | { | |
698 | 2475 | max = annotations[i].value; |
699 | } | |
700 | ||
701 | 397185 | if (annotations[i].value < min) |
702 | { | |
703 | 4964 | min = annotations[i].value; |
704 | } | |
705 | 397185 | if (_linecolour == null && annotations[i].colour != null) |
706 | { | |
707 | 767 | _linecolour = annotations[i].colour; |
708 | } | |
709 | } | |
710 | // ensure zero is origin for min/max ranges on only one side of zero | |
711 | 2771 | if (min > 0) |
712 | { | |
713 | 1332 | min = 0; |
714 | } | |
715 | else | |
716 | { | |
717 | 1439 | if (max < 0) |
718 | { | |
719 | 0 | max = 0; |
720 | } | |
721 | } | |
722 | } | |
723 | ||
724 | 5767 | graphMin = min; |
725 | 5767 | graphMax = max; |
726 | ||
727 | 5768 | areLabelsSecondaryStructure(); |
728 | ||
729 | 5768 | if (!drawValues && graphType != NO_GRAPH) |
730 | { | |
731 | 27176 | for (int i = 0; i < annotations.length; i++) |
732 | { | |
733 | 27004 | if (annotations[i] != null) |
734 | { | |
735 | 21912 | annotations[i].displayCharacter = ""; |
736 | } | |
737 | } | |
738 | } | |
739 | } | |
740 | ||
741 | /** | |
742 | * Copy constructor creates a new independent annotation row with the same | |
743 | * associated sequenceRef | |
744 | * | |
745 | * @param annotation | |
746 | */ | |
747 | 809 | public AlignmentAnnotation(AlignmentAnnotation annotation) |
748 | { | |
749 | 809 | setAnnotationId(); |
750 | 809 | this.label = new String(annotation.label); |
751 | 809 | if (annotation.description != null) |
752 | { | |
753 | 785 | this.description = new String(annotation.description); |
754 | } | |
755 | 809 | this.graphMin = annotation.graphMin; |
756 | 809 | this.graphMax = annotation.graphMax; |
757 | 809 | this.graph = annotation.graph; |
758 | 809 | this.graphHeight = annotation.graphHeight; |
759 | 809 | this.graphGroup = annotation.graphGroup; |
760 | 809 | this.groupRef = annotation.groupRef; |
761 | 809 | this.editable = annotation.editable; |
762 | 809 | this.autoCalculated = annotation.autoCalculated; |
763 | 809 | this.hasIcons = annotation.hasIcons; |
764 | 809 | this.hasText = annotation.hasText; |
765 | 809 | this.height = annotation.height; |
766 | 809 | this.label = annotation.label; |
767 | 809 | this.padGaps = annotation.padGaps; |
768 | 809 | this.visible = annotation.visible; |
769 | 809 | this.centreColLabels = annotation.centreColLabels; |
770 | 809 | this.scaleColLabel = annotation.scaleColLabel; |
771 | 809 | this.showAllColLabels = annotation.showAllColLabels; |
772 | 809 | this.calcId = annotation.calcId; |
773 | 809 | if (annotation.properties != null) |
774 | { | |
775 | 809 | properties = new HashMap<>(); |
776 | 809 | for (Map.Entry<String, String> val : annotation.properties.entrySet()) |
777 | { | |
778 | 16 | properties.put(val.getKey(), val.getValue()); |
779 | } | |
780 | } | |
781 | ? | if (this.hasScore = annotation.hasScore) |
782 | { | |
783 | 129 | this.score = annotation.score; |
784 | } | |
785 | 809 | if (annotation.threshold != null) |
786 | { | |
787 | 0 | threshold = new GraphLine(annotation.threshold); |
788 | } | |
789 | 809 | Annotation[] ann = annotation.annotations; |
790 | 809 | if (annotation.annotations != null) |
791 | { | |
792 | 806 | this.annotations = new Annotation[ann.length]; |
793 | 96370 | for (int i = 0; i < ann.length; i++) |
794 | { | |
795 | 95564 | if (ann[i] != null) |
796 | { | |
797 | 69722 | annotations[i] = new Annotation(ann[i]); |
798 | 69722 | if (_linecolour != null) |
799 | { | |
800 | 0 | _linecolour = annotations[i].colour; |
801 | } | |
802 | } | |
803 | } | |
804 | } | |
805 | 809 | if (annotation.sequenceRef != null) |
806 | { | |
807 | 623 | this.sequenceRef = annotation.sequenceRef; |
808 | 623 | if (annotation.sequenceMapping != null) |
809 | { | |
810 | 621 | Integer p = null; |
811 | 621 | sequenceMapping = new HashMap<>(); |
812 | 621 | Iterator<Integer> pos = annotation.sequenceMapping.keySet() |
813 | .iterator(); | |
814 | 49221 | while (pos.hasNext()) |
815 | { | |
816 | // could optimise this! | |
817 | 48600 | p = pos.next(); |
818 | 48600 | Annotation a = annotation.sequenceMapping.get(p); |
819 | 48600 | if (a == null) |
820 | { | |
821 | 0 | continue; |
822 | } | |
823 | 48600 | if (ann != null) |
824 | { | |
825 | 8313075 | for (int i = 0; i < ann.length; i++) |
826 | { | |
827 | 8264475 | if (ann[i] == a) |
828 | { | |
829 | 48558 | sequenceMapping.put(p, annotations[i]); |
830 | } | |
831 | } | |
832 | } | |
833 | } | |
834 | } | |
835 | else | |
836 | { | |
837 | 2 | this.sequenceMapping = null; |
838 | } | |
839 | ||
840 | } | |
841 | // TODO: check if we need to do this: JAL-952 | |
842 | // if (this.isrna=annotation.isrna) | |
843 | { | |
844 | // _rnasecstr=new SequenceFeature[annotation._rnasecstr]; | |
845 | } | |
846 | 809 | validateRangeAndDisplay(); // construct hashcodes, etc. |
847 | } | |
848 | ||
849 | /** | |
850 | * clip the annotation to the columns given by startRes and endRes (inclusive) | |
851 | * and prune any existing sequenceMapping to just those columns. | |
852 | * | |
853 | * @param startRes | |
854 | * @param endRes | |
855 | */ | |
856 | 108 | public void restrict(int startRes, int endRes) |
857 | { | |
858 | 108 | if (annotations == null) |
859 | { | |
860 | // non-positional | |
861 | 0 | return; |
862 | } | |
863 | 108 | if (startRes < 0) |
864 | { | |
865 | 0 | startRes = 0; |
866 | } | |
867 | 108 | if (startRes >= annotations.length) |
868 | { | |
869 | 0 | startRes = annotations.length - 1; |
870 | } | |
871 | 108 | if (endRes >= annotations.length) |
872 | { | |
873 | 100 | endRes = annotations.length - 1; |
874 | } | |
875 | 108 | if (annotations == null) |
876 | { | |
877 | 0 | return; |
878 | } | |
879 | 108 | Annotation[] temp = new Annotation[endRes - startRes + 1]; |
880 | 108 | if (startRes < annotations.length) |
881 | { | |
882 | 108 | System.arraycopy(annotations, startRes, temp, 0, |
883 | endRes - startRes + 1); | |
884 | } | |
885 | 108 | if (sequenceRef != null) |
886 | { | |
887 | // Clip the mapping, if it exists. | |
888 | 102 | int spos = sequenceRef.findPosition(startRes); |
889 | 102 | int epos = sequenceRef.findPosition(endRes); |
890 | 102 | if (sequenceMapping != null) |
891 | { | |
892 | 102 | Map<Integer, Annotation> newmapping = new HashMap<>(); |
893 | 102 | Iterator<Integer> e = sequenceMapping.keySet().iterator(); |
894 | 7896 | while (e.hasNext()) |
895 | { | |
896 | 7794 | Integer pos = e.next(); |
897 | 7794 | if (pos.intValue() >= spos && pos.intValue() <= epos) |
898 | { | |
899 | 7750 | newmapping.put(pos, sequenceMapping.get(pos)); |
900 | } | |
901 | } | |
902 | 102 | sequenceMapping.clear(); |
903 | 102 | sequenceMapping = newmapping; |
904 | } | |
905 | } | |
906 | 108 | annotations = temp; |
907 | } | |
908 | ||
909 | /** | |
910 | * set the annotation row to be at least length Annotations | |
911 | * | |
912 | * @param length | |
913 | * minimum number of columns required in the annotation row | |
914 | * @return false if the annotation row is greater than length | |
915 | */ | |
916 | 0 | public boolean padAnnotation(int length) |
917 | { | |
918 | 0 | if (annotations == null) |
919 | { | |
920 | 0 | return true; // annotation row is correct - null == not visible and |
921 | // undefined length | |
922 | } | |
923 | 0 | if (annotations.length < length) |
924 | { | |
925 | 0 | Annotation[] na = new Annotation[length]; |
926 | 0 | System.arraycopy(annotations, 0, na, 0, annotations.length); |
927 | 0 | annotations = na; |
928 | 0 | return true; |
929 | } | |
930 | 0 | return annotations.length > length; |
931 | ||
932 | } | |
933 | ||
934 | /** | |
935 | * DOCUMENT ME! | |
936 | * | |
937 | * @return DOCUMENT ME! | |
938 | */ | |
939 | 83 | @Override |
940 | public String toString() | |
941 | { | |
942 | 83 | if (annotations == null) |
943 | { | |
944 | 0 | return ""; |
945 | } | |
946 | 83 | StringBuilder buffer = new StringBuilder(256); |
947 | ||
948 | 192 | for (int i = 0; i < annotations.length; i++) |
949 | { | |
950 | 109 | if (annotations[i] != null) |
951 | { | |
952 | 109 | if (graph != 0) |
953 | { | |
954 | 25 | buffer.append(annotations[i].value); |
955 | } | |
956 | 84 | else if (hasIcons) |
957 | { | |
958 | 84 | buffer.append(annotations[i].secondaryStructure); |
959 | } | |
960 | else | |
961 | { | |
962 | 0 | buffer.append(annotations[i].displayCharacter); |
963 | } | |
964 | } | |
965 | ||
966 | 109 | buffer.append(", "); |
967 | } | |
968 | // TODO: remove disgusting hack for 'special' treatment of consensus line. | |
969 | 83 | if (label.indexOf("Consensus") == 0) |
970 | { | |
971 | 1 | buffer.append("\n"); |
972 | ||
973 | 6 | for (int i = 0; i < annotations.length; i++) |
974 | { | |
975 | 5 | if (annotations[i] != null) |
976 | { | |
977 | 5 | buffer.append(annotations[i].description); |
978 | } | |
979 | ||
980 | 5 | buffer.append(", "); |
981 | } | |
982 | } | |
983 | ||
984 | 83 | return buffer.toString(); |
985 | } | |
986 | ||
987 | 185 | public void setThreshold(GraphLine line) |
988 | { | |
989 | 185 | threshold = line; |
990 | } | |
991 | ||
992 | 630 | public GraphLine getThreshold() |
993 | { | |
994 | 630 | return threshold; |
995 | } | |
996 | ||
997 | /** | |
998 | * Attach the annotation to seqRef, starting from startRes position. If | |
999 | * alreadyMapped is true then the indices of the annotation[] array are | |
1000 | * sequence positions rather than alignment column positions. | |
1001 | * | |
1002 | * @param seqRef | |
1003 | * @param startRes | |
1004 | * @param alreadyMapped | |
1005 | */ | |
1006 | 1596 | public void createSequenceMapping(SequenceI seqRef, int startRes, |
1007 | boolean alreadyMapped) | |
1008 | { | |
1009 | ||
1010 | 1596 | if (seqRef == null) |
1011 | { | |
1012 | 0 | return; |
1013 | } | |
1014 | 1596 | sequenceRef = seqRef; |
1015 | 1596 | if (annotations == null) |
1016 | { | |
1017 | 103 | return; |
1018 | } | |
1019 | 1493 | sequenceMapping = new HashMap<>(); |
1020 | ||
1021 | 1493 | int seqPos; |
1022 | ||
1023 | 273018 | for (int i = 0; i < annotations.length; i++) |
1024 | { | |
1025 | 271525 | if (annotations[i] != null) |
1026 | { | |
1027 | 232679 | if (alreadyMapped) |
1028 | { | |
1029 | 150416 | seqPos = seqRef.findPosition(i); |
1030 | } | |
1031 | else | |
1032 | { | |
1033 | 82263 | seqPos = i + startRes; |
1034 | } | |
1035 | ||
1036 | 232679 | sequenceMapping.put(Integer.valueOf(seqPos), annotations[i]); |
1037 | } | |
1038 | } | |
1039 | ||
1040 | } | |
1041 | ||
1042 | /** | |
1043 | * When positional annotation and a sequence reference is present, clears and | |
1044 | * resizes the annotations array to the current alignment width, and adds | |
1045 | * annotation according to aligned positions of the sequenceRef given by | |
1046 | * sequenceMapping. | |
1047 | */ | |
1048 | 2102 | public void adjustForAlignment() |
1049 | { | |
1050 | 2102 | if (sequenceRef == null) |
1051 | { | |
1052 | 215 | return; |
1053 | } | |
1054 | ||
1055 | 1887 | if (annotations == null) |
1056 | { | |
1057 | 103 | return; |
1058 | } | |
1059 | ||
1060 | 1784 | int a = 0, aSize = sequenceRef.getLength(); |
1061 | ||
1062 | 1784 | if (aSize == 0) |
1063 | { | |
1064 | // Its been deleted | |
1065 | 1 | return; |
1066 | } | |
1067 | ||
1068 | 1783 | int position; |
1069 | 1783 | Annotation[] temp = new Annotation[aSize]; |
1070 | 1783 | Integer index; |
1071 | 1783 | if (sequenceMapping != null) |
1072 | { | |
1073 | 251236 | for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++) |
1074 | { | |
1075 | 249453 | index = Integer.valueOf(a); |
1076 | 249453 | Annotation annot = sequenceMapping.get(index); |
1077 | 249453 | if (annot != null) |
1078 | { | |
1079 | 193897 | position = sequenceRef.findIndex(a) - 1; |
1080 | ||
1081 | 193897 | temp[position] = annot; |
1082 | } | |
1083 | } | |
1084 | } | |
1085 | 1783 | annotations = temp; |
1086 | } | |
1087 | ||
1088 | /** | |
1089 | * remove any null entries in annotation row and return the number of non-null | |
1090 | * annotation elements. | |
1091 | * | |
1092 | * @return | |
1093 | */ | |
1094 | 0 | public int compactAnnotationArray() |
1095 | { | |
1096 | 0 | int i = 0, iSize = annotations.length; |
1097 | 0 | while (i < iSize) |
1098 | { | |
1099 | 0 | if (annotations[i] == null) |
1100 | { | |
1101 | 0 | if (i + 1 < iSize) |
1102 | { | |
1103 | 0 | System.arraycopy(annotations, i + 1, annotations, i, |
1104 | iSize - i - 1); | |
1105 | } | |
1106 | 0 | iSize--; |
1107 | } | |
1108 | else | |
1109 | { | |
1110 | 0 | i++; |
1111 | } | |
1112 | } | |
1113 | 0 | Annotation[] ann = annotations; |
1114 | 0 | annotations = new Annotation[i]; |
1115 | 0 | System.arraycopy(ann, 0, annotations, 0, i); |
1116 | 0 | ann = null; |
1117 | 0 | return iSize; |
1118 | } | |
1119 | ||
1120 | /** | |
1121 | * Associate this annotation with the aligned residues of a particular | |
1122 | * sequence. sequenceMapping will be updated in the following way: null | |
1123 | * sequenceI - existing mapping will be discarded but annotations left in | |
1124 | * mapped positions. valid sequenceI not equal to current sequenceRef: mapping | |
1125 | * is discarded and rebuilt assuming 1:1 correspondence TODO: overload with | |
1126 | * parameter to specify correspondence between current and new sequenceRef | |
1127 | * | |
1128 | * @param sequenceI | |
1129 | */ | |
1130 | 2626 | public void setSequenceRef(SequenceI sequenceI) |
1131 | { | |
1132 | 2626 | if (sequenceI != null) |
1133 | { | |
1134 | 2563 | if (sequenceRef != null) |
1135 | { | |
1136 | 1491 | boolean rIsDs = sequenceRef.getDatasetSequence() == null, |
1137 | tIsDs = sequenceI.getDatasetSequence() == null; | |
1138 | 1491 | if (sequenceRef != sequenceI |
1139 | && (rIsDs && !tIsDs | |
1140 | && sequenceRef != sequenceI.getDatasetSequence()) | |
1141 | && (!rIsDs && tIsDs | |
1142 | && sequenceRef.getDatasetSequence() != sequenceI) | |
1143 | && (!rIsDs && !tIsDs | |
1144 | && sequenceRef.getDatasetSequence() != sequenceI | |
1145 | .getDatasetSequence()) | |
1146 | && !sequenceRef.equals(sequenceI)) | |
1147 | { | |
1148 | // if sequenceRef isn't intersecting with sequenceI | |
1149 | // throw away old mapping and reconstruct. | |
1150 | 0 | sequenceRef = null; |
1151 | 0 | if (sequenceMapping != null) |
1152 | { | |
1153 | 0 | sequenceMapping = null; |
1154 | // compactAnnotationArray(); | |
1155 | } | |
1156 | 0 | createSequenceMapping(sequenceI, 1, true); |
1157 | 0 | adjustForAlignment(); |
1158 | } | |
1159 | else | |
1160 | { | |
1161 | // Mapping carried over | |
1162 | 1491 | sequenceRef = sequenceI; |
1163 | } | |
1164 | } | |
1165 | else | |
1166 | { | |
1167 | // No mapping exists | |
1168 | 1072 | createSequenceMapping(sequenceI, 1, true); |
1169 | 1072 | adjustForAlignment(); |
1170 | } | |
1171 | } | |
1172 | else | |
1173 | { | |
1174 | // throw away the mapping without compacting. | |
1175 | 63 | sequenceMapping = null; |
1176 | 63 | sequenceRef = null; |
1177 | } | |
1178 | } | |
1179 | ||
1180 | /** | |
1181 | * @return the score | |
1182 | */ | |
1183 | 156 | public double getScore() |
1184 | { | |
1185 | 156 | return score; |
1186 | } | |
1187 | ||
1188 | /** | |
1189 | * @param score | |
1190 | * the score to set | |
1191 | */ | |
1192 | 34591 | public void setScore(double score) |
1193 | { | |
1194 | 34591 | hasScore = true; |
1195 | 34591 | this.score = score; |
1196 | } | |
1197 | ||
1198 | /** | |
1199 | * | |
1200 | * @return true if annotation has an associated score | |
1201 | */ | |
1202 | 837 | public boolean hasScore() |
1203 | { | |
1204 | 837 | return hasScore || !Double.isNaN(score); |
1205 | } | |
1206 | ||
1207 | /** | |
1208 | * Score only annotation | |
1209 | * | |
1210 | * @param label | |
1211 | * @param description | |
1212 | * @param score | |
1213 | */ | |
1214 | 33442 | public AlignmentAnnotation(String label, String description, double score) |
1215 | { | |
1216 | 33442 | this(label, description, null); |
1217 | 33442 | setScore(score); |
1218 | } | |
1219 | ||
1220 | /** | |
1221 | * copy constructor with edit based on the hidden columns marked in colSel | |
1222 | * | |
1223 | * @param alignmentAnnotation | |
1224 | * @param colSel | |
1225 | */ | |
1226 | 0 | public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation, |
1227 | HiddenColumns hidden) | |
1228 | { | |
1229 | 0 | this(alignmentAnnotation); |
1230 | 0 | if (annotations == null) |
1231 | { | |
1232 | 0 | return; |
1233 | } | |
1234 | 0 | makeVisibleAnnotation(hidden); |
1235 | } | |
1236 | ||
1237 | 0 | public void setPadGaps(boolean padgaps, char gapchar) |
1238 | { | |
1239 | 0 | this.padGaps = padgaps; |
1240 | 0 | if (padgaps) |
1241 | { | |
1242 | 0 | hasText = true; |
1243 | 0 | for (int i = 0; i < annotations.length; i++) |
1244 | { | |
1245 | 0 | if (annotations[i] == null) |
1246 | { | |
1247 | 0 | annotations[i] = new Annotation(String.valueOf(gapchar), null, |
1248 | ' ', 0f, null); | |
1249 | } | |
1250 | 0 | else if (annotations[i].displayCharacter == null |
1251 | || annotations[i].displayCharacter.equals(" ")) | |
1252 | { | |
1253 | 0 | annotations[i].displayCharacter = String.valueOf(gapchar); |
1254 | } | |
1255 | } | |
1256 | } | |
1257 | } | |
1258 | ||
1259 | /** | |
1260 | * format description string for display | |
1261 | * | |
1262 | * @param seqname | |
1263 | * @return Get the annotation description string optionally prefixed by | |
1264 | * associated sequence name (if any) | |
1265 | */ | |
1266 | 10 | public String getDescription(boolean seqname) |
1267 | { | |
1268 | 10 | if (seqname && this.sequenceRef != null) |
1269 | { | |
1270 | 3 | int i = description.toLowerCase(Locale.ROOT).indexOf("<html>"); |
1271 | 3 | if (i > -1) |
1272 | { | |
1273 | // move the html tag to before the sequence reference. | |
1274 | 1 | return "<html>" + sequenceRef.getName() + " : " |
1275 | + description.substring(i + 6); | |
1276 | } | |
1277 | 2 | return sequenceRef.getName() + " : " + description; |
1278 | } | |
1279 | 7 | return description; |
1280 | } | |
1281 | ||
1282 | 232 | public boolean isValidStruc() |
1283 | { | |
1284 | 232 | return invalidrnastruc == -1; |
1285 | } | |
1286 | ||
1287 | 1146510 | public long getInvalidStrucPos() |
1288 | { | |
1289 | 1146512 | return invalidrnastruc; |
1290 | } | |
1291 | ||
1292 | /** | |
1293 | * machine readable ID string indicating what generated this annotation | |
1294 | */ | |
1295 | protected String calcId = ""; | |
1296 | ||
1297 | /** | |
1298 | * properties associated with the calcId | |
1299 | */ | |
1300 | protected Map<String, String> properties = new HashMap<>(); | |
1301 | ||
1302 | /** | |
1303 | * base colour for line graphs. If null, will be set automatically by | |
1304 | * searching the alignment annotation | |
1305 | */ | |
1306 | public java.awt.Color _linecolour; | |
1307 | ||
1308 | 12887 | public String getCalcId() |
1309 | { | |
1310 | 12887 | return calcId; |
1311 | } | |
1312 | ||
1313 | 1305 | public void setCalcId(String calcId) |
1314 | { | |
1315 | 1305 | this.calcId = calcId; |
1316 | } | |
1317 | ||
1318 | 13892 | public boolean isRNA() |
1319 | { | |
1320 | 13892 | return isrna; |
1321 | } | |
1322 | ||
1323 | /** | |
1324 | * transfer annotation to the given sequence using the given mapping from the | |
1325 | * current positions or an existing sequence mapping | |
1326 | * | |
1327 | * @param sq | |
1328 | * @param sp2sq | |
1329 | * map involving sq as To or From | |
1330 | */ | |
1331 | 183 | public void liftOver(SequenceI sq, Mapping sp2sq) |
1332 | { | |
1333 | 183 | if (sp2sq.getMappedWidth() != sp2sq.getWidth()) |
1334 | { | |
1335 | // TODO: employ getWord/MappedWord to transfer annotation between cDNA and | |
1336 | // Protein reference frames | |
1337 | 0 | throw new Error( |
1338 | "liftOver currently not implemented for transfer of annotation between different types of seqeunce"); | |
1339 | } | |
1340 | 183 | boolean mapIsTo = (sp2sq != null) |
1341 | ? (sp2sq.getTo() == sq | |
1342 | || sp2sq.getTo() == sq.getDatasetSequence()) | |
1343 | : false; | |
1344 | ||
1345 | // TODO build a better annotation element map and get rid of annotations[] | |
1346 | 183 | Map<Integer, Annotation> mapForsq = new HashMap<>(); |
1347 | 183 | if (sequenceMapping != null) |
1348 | { | |
1349 | 183 | if (sp2sq != null) |
1350 | { | |
1351 | 183 | for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet()) |
1352 | { | |
1353 | 16336 | Integer mpos = Integer |
1354 | 16336 | .valueOf(mapIsTo ? sp2sq.getMappedPosition(ie.getKey()) |
1355 | : sp2sq.getPosition(ie.getKey())); | |
1356 | 16336 | if (mpos >= sq.getStart() && mpos <= sq.getEnd()) |
1357 | { | |
1358 | 16328 | mapForsq.put(mpos, ie.getValue()); |
1359 | } | |
1360 | } | |
1361 | 183 | sequenceMapping = mapForsq; |
1362 | 183 | sequenceRef = sq; |
1363 | 183 | adjustForAlignment(); |
1364 | } | |
1365 | else | |
1366 | { | |
1367 | // trim positions | |
1368 | } | |
1369 | } | |
1370 | } | |
1371 | ||
1372 | /** | |
1373 | * like liftOver but more general. | |
1374 | * | |
1375 | * Takes an array of int pairs that will be used to update the internal | |
1376 | * sequenceMapping and so shuffle the annotated positions | |
1377 | * | |
1378 | * @param newref | |
1379 | * - new sequence reference for the annotation row - if null, | |
1380 | * sequenceRef is left unchanged | |
1381 | * @param mapping | |
1382 | * array of ints containing corresponding positions | |
1383 | * @param from | |
1384 | * - column for current coordinate system (-1 for index+1) | |
1385 | * @param to | |
1386 | * - column for destination coordinate system (-1 for index+1) | |
1387 | * @param idxoffset | |
1388 | * - offset added to index when referencing either coordinate system | |
1389 | * @note no checks are made as to whether from and/or to are sensible | |
1390 | * @note caller should add the remapped annotation to newref if they have not | |
1391 | * already | |
1392 | */ | |
1393 | 0 | public void remap(SequenceI newref, HashMap<Integer, int[]> mapping, |
1394 | int from, int to, int idxoffset) | |
1395 | { | |
1396 | 0 | if (mapping != null) |
1397 | { | |
1398 | 0 | Map<Integer, Annotation> old = sequenceMapping; |
1399 | 0 | Map<Integer, Annotation> remap = new HashMap<>(); |
1400 | 0 | int index = -1; |
1401 | 0 | for (int mp[] : mapping.values()) |
1402 | { | |
1403 | 0 | if (index++ < 0) |
1404 | { | |
1405 | 0 | continue; |
1406 | } | |
1407 | 0 | Annotation ann = null; |
1408 | 0 | if (from == -1) |
1409 | { | |
1410 | 0 | ann = sequenceMapping.get(Integer.valueOf(idxoffset + index)); |
1411 | } | |
1412 | else | |
1413 | { | |
1414 | 0 | if (mp != null && mp.length > from) |
1415 | { | |
1416 | 0 | ann = sequenceMapping.get(Integer.valueOf(mp[from])); |
1417 | } | |
1418 | } | |
1419 | 0 | if (ann != null) |
1420 | { | |
1421 | 0 | if (to == -1) |
1422 | { | |
1423 | 0 | remap.put(Integer.valueOf(idxoffset + index), ann); |
1424 | } | |
1425 | else | |
1426 | { | |
1427 | 0 | if (to > -1 && to < mp.length) |
1428 | { | |
1429 | 0 | remap.put(Integer.valueOf(mp[to]), ann); |
1430 | } | |
1431 | } | |
1432 | } | |
1433 | } | |
1434 | 0 | sequenceMapping = remap; |
1435 | 0 | old.clear(); |
1436 | 0 | if (newref != null) |
1437 | { | |
1438 | 0 | sequenceRef = newref; |
1439 | } | |
1440 | 0 | adjustForAlignment(); |
1441 | } | |
1442 | } | |
1443 | ||
1444 | 48555 | public String getProperty(String property) |
1445 | { | |
1446 | 48555 | if (properties == null) |
1447 | { | |
1448 | 0 | return null; |
1449 | } | |
1450 | 48555 | return properties.get(property); |
1451 | } | |
1452 | ||
1453 | 49 | public void setProperty(String property, String value) |
1454 | { | |
1455 | 49 | if (properties == null) |
1456 | { | |
1457 | 0 | properties = new HashMap<>(); |
1458 | } | |
1459 | 49 | properties.put(property, value); |
1460 | } | |
1461 | ||
1462 | 441 | public boolean hasProperties() |
1463 | { | |
1464 | 441 | return properties != null && properties.size() > 0; |
1465 | } | |
1466 | ||
1467 | 1 | public Collection<String> getProperties() |
1468 | { | |
1469 | 1 | if (properties == null) |
1470 | { | |
1471 | 0 | return Collections.emptyList(); |
1472 | } | |
1473 | 1 | return properties.keySet(); |
1474 | } | |
1475 | ||
1476 | /** | |
1477 | * Returns the Annotation for the given sequence position (base 1) if any, | |
1478 | * else null | |
1479 | * | |
1480 | * @param position | |
1481 | * @return | |
1482 | */ | |
1483 | 94597 | public Annotation getAnnotationForPosition(int position) |
1484 | { | |
1485 | 94597 | return sequenceMapping == null ? null : sequenceMapping.get(position); |
1486 | ||
1487 | } | |
1488 | ||
1489 | /** | |
1490 | * Set the id to "ann" followed by a counter that increments so as to be | |
1491 | * unique for the lifetime of the JVM | |
1492 | */ | |
1493 | 38535 | protected final void setAnnotationId() |
1494 | { | |
1495 | 38535 | this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId()); |
1496 | } | |
1497 | ||
1498 | /** | |
1499 | * Returns the match for the last unmatched opening RNA helix pair symbol | |
1500 | * preceding the given column, or '(' if nothing found to match. | |
1501 | * | |
1502 | * @param column | |
1503 | * @return | |
1504 | */ | |
1505 | 43 | public String getDefaultRnaHelixSymbol(int column) |
1506 | { | |
1507 | 43 | String result = "("; |
1508 | 43 | if (annotations == null) |
1509 | { | |
1510 | 1 | return result; |
1511 | } | |
1512 | ||
1513 | /* | |
1514 | * for each preceding column, if it contains an open bracket, | |
1515 | * count whether it is still unmatched at column, if so return its pair | |
1516 | * (likely faster than the fancy alternative using stacks) | |
1517 | */ | |
1518 | 150 | for (int col = column - 1; col >= 0; col--) |
1519 | { | |
1520 | 142 | Annotation annotation = annotations[col]; |
1521 | 142 | if (annotation == null) |
1522 | { | |
1523 | 66 | continue; |
1524 | } | |
1525 | 76 | String displayed = annotation.displayCharacter; |
1526 | 76 | if (displayed == null || displayed.length() != 1) |
1527 | { | |
1528 | 0 | continue; |
1529 | } | |
1530 | 76 | char symbol = displayed.charAt(0); |
1531 | 76 | if (!Rna.isOpeningParenthesis(symbol)) |
1532 | { | |
1533 | 24 | continue; |
1534 | } | |
1535 | ||
1536 | /* | |
1537 | * found an opening bracket symbol | |
1538 | * count (closing-opening) symbols of this type that follow it, | |
1539 | * up to and excluding the target column; if the count is less | |
1540 | * than 1, the opening bracket is unmatched, so return its match | |
1541 | */ | |
1542 | 52 | String closer = String |
1543 | .valueOf(Rna.getMatchingClosingParenthesis(symbol)); | |
1544 | 52 | String opener = String.valueOf(symbol); |
1545 | 52 | int count = 0; |
1546 | 256 | for (int j = col + 1; j < column; j++) |
1547 | { | |
1548 | 204 | if (annotations[j] != null) |
1549 | { | |
1550 | 90 | String s = annotations[j].displayCharacter; |
1551 | 90 | if (closer.equals(s)) |
1552 | { | |
1553 | 18 | count++; |
1554 | } | |
1555 | 72 | else if (opener.equals(s)) |
1556 | { | |
1557 | 0 | count--; |
1558 | } | |
1559 | } | |
1560 | } | |
1561 | 52 | if (count < 1) |
1562 | { | |
1563 | 34 | return closer; |
1564 | } | |
1565 | } | |
1566 | 8 | return result; |
1567 | } | |
1568 | ||
1569 | 38535 | protected static synchronized long nextId() |
1570 | { | |
1571 | 38535 | return counter++; |
1572 | } | |
1573 | ||
1574 | /** | |
1575 | * | |
1576 | * @return true for rows that have a range of values in their annotation set | |
1577 | */ | |
1578 | 5 | public boolean isQuantitative() |
1579 | { | |
1580 | 5 | return graphMin < graphMax; |
1581 | } | |
1582 | ||
1583 | /** | |
1584 | * delete any columns in alignmentAnnotation that are hidden (including | |
1585 | * sequence associated annotation). | |
1586 | * | |
1587 | * @param hiddenColumns | |
1588 | * the set of hidden columns | |
1589 | */ | |
1590 | 2 | public void makeVisibleAnnotation(HiddenColumns hiddenColumns) |
1591 | { | |
1592 | 2 | if (annotations != null) |
1593 | { | |
1594 | 1 | makeVisibleAnnotation(0, annotations.length, hiddenColumns); |
1595 | } | |
1596 | } | |
1597 | ||
1598 | /** | |
1599 | * delete any columns in alignmentAnnotation that are hidden (including | |
1600 | * sequence associated annotation). | |
1601 | * | |
1602 | * @param start | |
1603 | * remove any annotation to the right of this column | |
1604 | * @param end | |
1605 | * remove any annotation to the left of this column | |
1606 | * @param hiddenColumns | |
1607 | * the set of hidden columns | |
1608 | */ | |
1609 | 10 | public void makeVisibleAnnotation(int start, int end, |
1610 | HiddenColumns hiddenColumns) | |
1611 | { | |
1612 | 10 | if (annotations != null) |
1613 | { | |
1614 | 9 | if (hiddenColumns.hasHiddenColumns()) |
1615 | { | |
1616 | 3 | removeHiddenAnnotation(start, end, hiddenColumns); |
1617 | } | |
1618 | else | |
1619 | { | |
1620 | 6 | restrict(start, end); |
1621 | } | |
1622 | } | |
1623 | } | |
1624 | ||
1625 | /** | |
1626 | * The actual implementation of deleting hidden annotation columns | |
1627 | * | |
1628 | * @param start | |
1629 | * remove any annotation to the right of this column | |
1630 | * @param end | |
1631 | * remove any annotation to the left of this column | |
1632 | * @param hiddenColumns | |
1633 | * the set of hidden columns | |
1634 | */ | |
1635 | 3 | private void removeHiddenAnnotation(int start, int end, |
1636 | HiddenColumns hiddenColumns) | |
1637 | { | |
1638 | // mangle the alignmentAnnotation annotation array | |
1639 | 3 | ArrayList<Annotation[]> annels = new ArrayList<>(); |
1640 | 3 | Annotation[] els = null; |
1641 | ||
1642 | 3 | int w = 0; |
1643 | ||
1644 | 3 | Iterator<int[]> blocks = hiddenColumns.getVisContigsIterator(start, |
1645 | end + 1, false); | |
1646 | ||
1647 | 3 | int copylength; |
1648 | 3 | int annotationLength; |
1649 | 9 | while (blocks.hasNext()) |
1650 | { | |
1651 | 6 | int[] block = blocks.next(); |
1652 | 6 | annotationLength = block[1] - block[0] + 1; |
1653 | ||
1654 | 6 | if (blocks.hasNext()) |
1655 | { | |
1656 | // copy just the visible segment of the annotation row | |
1657 | 3 | copylength = annotationLength; |
1658 | } | |
1659 | else | |
1660 | { | |
1661 | 3 | if (annotationLength + block[0] <= annotations.length) |
1662 | { | |
1663 | // copy just the visible segment of the annotation row | |
1664 | 2 | copylength = annotationLength; |
1665 | } | |
1666 | else | |
1667 | { | |
1668 | // copy to the end of the annotation row | |
1669 | 1 | copylength = annotations.length - block[0]; |
1670 | } | |
1671 | } | |
1672 | ||
1673 | 6 | els = new Annotation[annotationLength]; |
1674 | 6 | annels.add(els); |
1675 | 6 | System.arraycopy(annotations, block[0], els, 0, copylength); |
1676 | 6 | w += annotationLength; |
1677 | } | |
1678 | ||
1679 | 3 | if (w != 0) |
1680 | { | |
1681 | 3 | annotations = new Annotation[w]; |
1682 | ||
1683 | 3 | w = 0; |
1684 | 3 | for (Annotation[] chnk : annels) |
1685 | { | |
1686 | 6 | System.arraycopy(chnk, 0, annotations, w, chnk.length); |
1687 | 6 | w += chnk.length; |
1688 | } | |
1689 | } | |
1690 | } | |
1691 | ||
1692 | 163 | public static Iterable<AlignmentAnnotation> findAnnotations( |
1693 | Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId, | |
1694 | String label) | |
1695 | { | |
1696 | ||
1697 | 163 | ArrayList<AlignmentAnnotation> aa = new ArrayList<>(); |
1698 | 163 | for (AlignmentAnnotation ann : list) |
1699 | { | |
1700 | 1129 | if ((calcId == null || (ann.getCalcId() != null |
1701 | && ann.getCalcId().equals(calcId))) | |
1702 | && (seq == null || (ann.sequenceRef != null | |
1703 | && ann.sequenceRef == seq)) | |
1704 | && (label == null | |
1705 | || (ann.label != null && ann.label.equals(label)))) | |
1706 | { | |
1707 | 182 | aa.add(ann); |
1708 | } | |
1709 | } | |
1710 | 163 | return aa; |
1711 | } | |
1712 | ||
1713 | /** | |
1714 | * Answer true if any annotation matches the calcId passed in (if not null). | |
1715 | * | |
1716 | * @param list | |
1717 | * annotation to search | |
1718 | * @param calcId | |
1719 | * @return | |
1720 | */ | |
1721 | 0 | public static boolean hasAnnotation(List<AlignmentAnnotation> list, |
1722 | String calcId) | |
1723 | { | |
1724 | ||
1725 | 0 | if (calcId != null && !"".equals(calcId)) |
1726 | { | |
1727 | 0 | for (AlignmentAnnotation a : list) |
1728 | { | |
1729 | 0 | if (a.getCalcId() == calcId) |
1730 | { | |
1731 | 0 | return true; |
1732 | } | |
1733 | } | |
1734 | } | |
1735 | 0 | return false; |
1736 | } | |
1737 | ||
1738 | 489 | public static Iterable<AlignmentAnnotation> findAnnotation( |
1739 | List<AlignmentAnnotation> list, String calcId) | |
1740 | { | |
1741 | ||
1742 | 489 | List<AlignmentAnnotation> aa = new ArrayList<>(); |
1743 | 489 | if (calcId == null) |
1744 | { | |
1745 | 1 | return aa; |
1746 | } | |
1747 | 488 | for (AlignmentAnnotation a : list) |
1748 | { | |
1749 | ||
1750 | 2570 | if (a.getCalcId() == calcId || (a.getCalcId() != null |
1751 | && calcId != null && a.getCalcId().equals(calcId))) | |
1752 | { | |
1753 | 509 | aa.add(a); |
1754 | } | |
1755 | } | |
1756 | 488 | return aa; |
1757 | } | |
1758 | ||
1759 | /** | |
1760 | * convenience method to check for the 'CONTACT_MAP_NOGROUPS' property for | |
1761 | * this alignment annotation row | |
1762 | * | |
1763 | * @return true if no CONTACT_MAP_NOGROUPS property is found, or it is set to | |
1764 | * "" | |
1765 | */ | |
1766 | 144 | public boolean isShowGroupsForContactMatrix() |
1767 | { | |
1768 | 144 | return getProperty(AlignmentAnnotation.CONTACT_MAP_NOGROUPS) == null |
1769 | || "".equals( | |
1770 | getProperty(AlignmentAnnotation.CONTACT_MAP_NOGROUPS)); | |
1771 | } | |
1772 | ||
1773 | /** | |
1774 | * set the 'CONTACT_MAP_NOGROUPS' property for this alignment annotation row | |
1775 | * | |
1776 | * @see isShowGroupsForContactMatrix | |
1777 | */ | |
1778 | 2 | public void setShowGroupsForContactMatrix(boolean showGroups) |
1779 | { | |
1780 | 2 | setProperty(AlignmentAnnotation.CONTACT_MAP_NOGROUPS, |
1781 | 2 | showGroups ? "" : "nogroups"); |
1782 | } | |
1783 | ||
1784 | 524072 | public long getNoOfSequencesIncluded() |
1785 | { | |
1786 | 524072 | return noOfSequencesIncluded; |
1787 | } | |
1788 | ||
1789 | 927 | public void setNoOfSequencesIncluded(long noOfSequencesIncluded) |
1790 | { | |
1791 | 927 | this.noOfSequencesIncluded = noOfSequencesIncluded; |
1792 | } | |
1793 | ||
1794 | } |