Some actions for adjusting pherogram displaying added.
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / molecular / AlignmentEditor.java
1 // $Id$
2 /**
3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.taxeditor.editor.molecular;
11
12
13 import info.bioinfweb.jphyloio.events.TokenSetType;
14 import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
15 import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
16 import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
17 import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
18 import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
19 import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
20 import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
21 import info.bioinfweb.libralign.editsettings.EditSettingsListener;
22 import info.bioinfweb.libralign.model.AlignmentModel;
23 import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
24 import info.bioinfweb.libralign.model.AlignmentModelUtils;
25 import info.bioinfweb.libralign.model.adapters.StringAdapter;
26 import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
27 import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
28 import info.bioinfweb.libralign.model.events.TokenChangeEvent;
29 import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
30 import info.bioinfweb.libralign.model.tokenset.BioJavaTokenSet;
31 import info.bioinfweb.libralign.model.tokenset.TokenSet;
32 import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
33 import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
34 import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
35 import info.bioinfweb.libralign.pherogram.model.ShiftChange;
36 import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
37 import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
38 import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.net.URI;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Iterator;
48 import java.util.Map;
49 import java.util.TreeMap;
50
51 import org.biojava.bio.chromatogram.ChromatogramFactory;
52 import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
53 import org.biojava3.core.sequence.compound.DNACompoundSet;
54 import org.biojava3.core.sequence.compound.NucleotideCompound;
55 import org.eclipse.core.runtime.IProgressMonitor;
56 import org.eclipse.swt.SWT;
57 import org.eclipse.swt.widgets.Composite;
58 import org.eclipse.ui.IActionBars;
59 import org.eclipse.ui.IEditorInput;
60 import org.eclipse.ui.IEditorPart;
61 import org.eclipse.ui.IEditorSite;
62 import org.eclipse.ui.PartInitException;
63 import org.eclipse.ui.PlatformUI;
64 import org.eclipse.ui.commands.ICommandService;
65 import org.eclipse.ui.part.EditorPart;
66
67 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
68 import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
69 import eu.etaxonomy.cdm.model.media.MediaUtils;
70 import eu.etaxonomy.cdm.model.molecular.Sequence;
71 import eu.etaxonomy.cdm.model.molecular.SequenceString;
72 import eu.etaxonomy.cdm.model.molecular.SingleRead;
73 import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
74 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleInsertOverwriteHandler;
75 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleLeftRightInsertionHandler;
76 import eu.etaxonomy.taxeditor.store.CdmStore;
77
78
79
80 /**
81 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
82 * a consensus sequence.
83 * <p>
84 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
85 *
86 * @author Ben Stöver
87 * @author pplitzner
88 * @date 04.08.2014
89 */
90 public class AlignmentEditor extends EditorPart {
91 public static final String ID = "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
92
93 public static final int READS_AREA_INDEX = 1;
94 public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
95 public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
96 public static final int PHEROGRAM_AREA_INDEX = 0;
97 public static final int CONSENSUS_DATA_AREA_INDEX = 0;
98 public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
99 public static final String CONSENSUS_NAME = "Consensus";
100
101
102 private final ConversationHolder conversationHolder;
103
104 private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
105 @Override
106 public <T> void afterTokenChange(TokenChangeEvent<T> e) {
107 setDirty();
108 }
109
110 @Override
111 public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
112 setDirty();
113 }
114
115 @Override
116 public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
117 setDirty();
118 }
119
120 @Override
121 public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
122 AlignmentModel<U> newProvider) { // Not expected.
123
124 setDirty();
125 }
126 };
127
128 private MultipleAlignmentsContainer alignmentsContainer = null;
129 private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
130 private boolean dirty = false;
131
132
133 public AlignmentEditor() {
134 super();
135 //conversationHolder = CdmStore.createConversation();
136 conversationHolder = null;
137 }
138
139
140 private void refreshToolbarElement(String id) {
141 ICommandService commandService =
142 (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
143 if (commandService != null) {
144 commandService.refreshElements(id, Collections.EMPTY_MAP);
145 }
146 }
147
148
149 private void registerEditSettingListener(MultipleAlignmentsContainer container) {
150 container.getEditSettings().addListener(new EditSettingsListener() {
151 @Override
152 public void workingModeChanged(EditSettingsChangeEvent e) {} // Currently nothing to do
153
154 @Override
155 public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
156 updateStatusBar();
157 refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
158 }
159
160 @Override
161 public void insertChanged(EditSettingsChangeEvent e) {
162 updateStatusBar();
163 refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
164 }
165 });
166 }
167
168
169 private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
170 AlignmentArea result = new AlignmentArea(container);
171 result.setAllowVerticalScrolling(false);
172 result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
173 return result;
174 }
175
176
177 private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
178 AlignmentArea result = new AlignmentArea(container);
179 result.setAllowVerticalScrolling(allowVerticalScrolling);
180
181 TokenSet<NucleotideCompound> tokenSet = new BioJavaTokenSet<NucleotideCompound>(
182 TokenSetType.DNA, new DNACompoundSet(), true); //TODO Should NUCLEOTIDE be used instead?
183 AlignmentModel<NucleotideCompound> provider = new PackedAlignmentModel<NucleotideCompound>(tokenSet);
184 result.setAlignmentModel(provider, false);
185 provider.getChangeListeners().add(DIRTY_LISTENER);
186 result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
187
188 return result;
189 }
190
191
192 private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
193 AlignmentArea labeledArea) {
194
195 AlignmentArea result = new AlignmentArea(container);
196 result.setAllowVerticalScrolling(false);
197 result.getDataAreas().getBottomAreas().add(
198 new ConsensusSequenceArea(result.getContentArea(), labeledArea));
199 return result;
200 }
201
202
203 private MultipleAlignmentsContainer getAlignmentsContainer() {
204 if (alignmentsContainer == null) {
205 alignmentsContainer = new MultipleAlignmentsContainer();
206
207 AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
208 AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
209 list.add(createIndexArea(alignmentsContainer, readsArea));
210 list.add(readsArea); // Make sure READS_AREA_INDEX is correct.
211 list.add(createEditableAlignmentArea(alignmentsContainer, false)); // Make sure COMSENSUS_AREA_INDEX is correct.
212 list.add(createConsensusHintArea(alignmentsContainer, readsArea));
213
214 registerEditSettingListener(alignmentsContainer);
215 }
216 return alignmentsContainer;
217 }
218
219
220 public AlignmentArea getReadsArea() {
221 return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
222 }
223
224
225 private AlignmentArea getEditableConsensusArea() {
226 return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
227 }
228
229
230 public PherogramArea getPherogramArea(int sequenceID) {
231 return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(
232 PHEROGRAM_AREA_INDEX);
233 }
234
235
236 private ConsensusSequenceArea getConsensusHintDataArea() {
237 return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
238 get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
239 get(CONSENSUS_DATA_AREA_INDEX);
240 }
241
242
243 private void createTestContents() {
244 // Just for testing:
245 try {
246 addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR430_JR-P01.ab1").toURI(), false);
247 //addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR444_JR-P05.ab1").toURI(), false);
248 addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/Test_qualityScore.scf").toURI(), false);
249
250 // Add test consensus sequence:
251 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
252 int id = consensusModel.addSequence(CONSENSUS_NAME);
253 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
254 tokens.add(consensusModel.getTokenSet().tokenByKeyChar('A'));
255 tokens.add(consensusModel.getTokenSet().tokenByKeyChar('C'));
256 tokens.add(consensusModel.getTokenSet().tokenByKeyChar('G'));
257 tokens.add(consensusModel.getTokenSet().tokenByKeyChar('T'));
258 consensusModel.insertTokensAt(id, 0, tokens);
259 }
260 catch (Exception e) {
261 throw new RuntimeException(e);
262 }
263 }
264
265
266 private void readCDMData(Sequence sequenceNode) {
267 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
268
269 // Add reads:
270 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
271 try {
272 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
273 int id = addRead(pherogramInfo.getPrimer().getLabel(), //TODO Should the sequence name contain other/additional/alternative data? Can the same string as in the derivative tree be used here?
274 MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri(),
275 singleReadAlignment.isReverseComplement(),
276 singleReadAlignment.getEditedSequence(),
277 singleReadAlignment.getFirstSeqPosition(),
278 singleReadAlignment.getLeftCutPosition(),
279 singleReadAlignment.getRightCutPosition(),
280 singleReadAlignment.getShifts());
281 cdmMap.put(id, singleReadAlignment);
282 }
283 catch (IOException e) {
284 e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
285 }
286 catch (UnsupportedChromatogramFormatException e) {
287 e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
288 }
289 }
290
291 // Set consensus sequence:
292 AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
293 int id = consensusProvider.addSequence(CONSENSUS_NAME);
294 consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
295 sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
296 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
297 }
298
299
300 /* (non-Javadoc)
301 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
302 */
303 @Override
304 public void createPartControl(Composite parent) {
305 getAlignmentsContainer().createSWTWidget(parent, SWT.NONE);
306 updateStatusBar();
307
308 if (getEditorInput() instanceof AlignmentEditorInput) {
309 if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
310 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
311 //re-load into the current session if it is already persisted in the DB
312 if(sequenceNode!=null && sequenceNode.getId()!=0){
313 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
314 }
315 readCDMData(sequenceNode);
316 }
317 else {
318 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
319 }
320 }
321 else {
322 throw new IllegalArgumentException("The editor input must have the type " +
323 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
324 }
325 }
326
327
328 private void updateStatusBar() {
329 IActionBars bars = getEditorSite().getActionBars();
330 bars.getStatusLineManager().setMessage("Edit mode: " +
331 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
332 "Insertion in pherogram: " +
333 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
334 }
335
336
337 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
338 SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[model.getShiftChangeCount()];
339 Iterator<ShiftChange> iterator = model.shiftChangeIterator();
340 int pos = 0;
341 while (iterator.hasNext()) {
342 ShiftChange shiftChange = iterator.next();
343 result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
344 }
345 return result;
346 }
347
348
349 /* (non-Javadoc)
350 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
351 */
352 @Override
353 public void doSave(IProgressMonitor monitor) {
354 if (getEditorInput() instanceof AlignmentEditorInput) {
355 String taskName = "Saving alignment";
356 monitor.beginTask(taskName, 3);
357
358 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
359 StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
360
361 // Write consensus sequence:
362 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
363 String newConsensusSequence = stringProvider.getSequence(
364 getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
365 if (consensusSequenceObj == null) {
366 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
367 }
368 else {
369 consensusSequenceObj.setString(newConsensusSequence);
370 }
371
372 // Write single reads:
373 stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
374 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
375 Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
376 while (iterator.hasNext()) {
377 int id = iterator.next();
378 SingleReadAlignment singleRead = cdmMap.get(id);
379 if (singleRead == null) {
380 //TODO Create new read object. => Shall it be allowed to add reads in the alignment editor which are not represented in the CDM tree before the alignment editor is saved?
381 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
382 }
383
384 singleRead.setEditedSequence(stringProvider.getSequence(id));
385
386 PherogramAreaModel model = getPherogramArea(id).getModel();
387 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
388 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
389 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
390 singleRead.setLeftCutPosition(model.getLeftCutPosition());
391 singleRead.setRightCutPosition(model.getRightCutPosition());
392 }
393
394 if (!conversationHolder.isBound()) {
395 conversationHolder.bind();
396 }
397 monitor.worked(1);
398
399 // Commit the conversation and start a new transaction immediately:
400 conversationHolder.commit(true);
401 monitor.worked(1);
402
403 dirty = false;
404 monitor.worked(1);
405 monitor.done();
406 firePropertyChange(PROP_DIRTY);
407 }
408 else {
409 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
410 }
411 }
412
413
414 /* (non-Javadoc)
415 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
416 */
417 @Override
418 public void doSaveAs() {}
419
420
421 /* (non-Javadoc)
422 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
423 */
424 @Override
425 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
426 setSite(site);
427 setInput(input);
428 }
429
430
431 /* (non-Javadoc)
432 * @see org.eclipse.ui.part.EditorPart#isDirty()
433 */
434 @Override
435 public boolean isDirty() {
436 return dirty;
437 }
438
439
440 private void setDirty() {
441 dirty = true;
442 firePropertyChange(IEditorPart.PROP_DIRTY);
443 }
444
445
446 /* (non-Javadoc)
447 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
448 */
449 @Override
450 public boolean isSaveAsAllowed() {
451 return false; // "Save as" not allowed.
452 }
453
454
455 @Override
456 public void setFocus() {
457 if(conversationHolder!=null){
458 conversationHolder.bind();
459 }
460 }
461
462
463 public boolean isInsertMode() {
464 return getAlignmentsContainer().getEditSettings().isInsert();
465 }
466
467
468 public boolean isInsertLeftInPherogram() {
469 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
470 }
471
472
473 public void toggleLeftRightInsertionInPherogram() {
474 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
475 }
476
477
478 public void toggleInsertOverwrite() {
479 getAlignmentsContainer().getEditSettings().toggleInsert();
480 }
481
482
483 private String cutPherogram(boolean left) {
484 SelectionModel selection = getReadsArea().getSelection();
485 if (selection.getCursorHeight() != 1) {
486 return "Cutting pherograms is only possible if exactly one row is selected.";
487 }
488 else {
489 PherogramArea pherogramArea =
490 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
491 if (pherogramArea == null) {
492 return "There is no pherogram attached to the current sequence.";
493 }
494 else {
495 if (left) {
496 if (pherogramArea.setLeftCutPositionBySelection()) {
497 return null;
498 }
499 else {
500 return "The left end of the selection lies outside the pherogram attached to this sequence.";
501 }
502 }
503 else {
504 if (pherogramArea.setRightCutPositionBySelection()) {
505 return null;
506 }
507 else {
508 return "The right end of the selection lies outside the pherogram attached to this sequence.";
509 }
510 }
511 }
512 }
513 }
514
515
516 public String cutPherogramLeft() {
517 return cutPherogram(true);
518 }
519
520
521 public String cutPherogramRight() {
522 return cutPherogram(false);
523 }
524
525
526 public void reverseComplementSelectedSequences() {
527 SelectionModel selection = getReadsArea().getSelection();
528 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
529 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
530 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
531 PherogramArea area = getPherogramArea(sequenceID);
532 PherogramAreaModel pherogramAlignmentModel = area.getModel();
533 AlignmentModelUtils.reverseComplement(model, sequenceID,
534 pherogramAlignmentModel.editableIndexByBaseCallIndex(
535 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
536 pherogramAlignmentModel.editableIndexByBaseCallIndex(
537 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
538 pherogramAlignmentModel.reverseComplement();
539 }
540 }
541
542
543 /**
544 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
545 * sequence is overwritte.
546 */
547 @SuppressWarnings("unchecked")
548 public <T> void createConsensusSequence() {
549 ConsensusSequenceArea area = getConsensusHintDataArea();
550 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
551 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
552 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
553
554 Collection<T> tokens = new ArrayList<T>(length);
555 for (int column = 0; column < length; column++) {
556 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
557 }
558
559 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
560 model.insertTokensAt(sequenceID, 0, tokens);
561 }
562
563
564 /**
565 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
566 * calculated from the single read sequences and extends the consensus sequence if necessary.
567 */
568 @SuppressWarnings("unchecked")
569 public <T> void updateConsensusSequence() {
570 ConsensusSequenceArea area = getConsensusHintDataArea();
571 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
572 TokenSet<T> tokenSet = model.getTokenSet();
573 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
574 int currentConsensusLength = model.getSequenceLength(sequenceID);
575 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
576
577 // Replace gaps by new information:
578 for (int column = 0; column < currentConsensusLength; column++) {
579 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
580 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
581 if (!tokenSet.isGapToken(newToken)) {
582 model.setTokenAt(sequenceID, column, newToken);
583 }
584 }
585 }
586
587 // Append additional tokens:
588 if (overallLength > currentConsensusLength) {
589 Collection<T> tokens = new ArrayList<T>(overallLength);
590 for (int column = currentConsensusLength; column < overallLength; column++) {
591 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
592 }
593 model.appendTokens(sequenceID, tokens);
594 }
595 }
596
597
598 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
599 PherogramProvider result;
600 InputStream stream = uri.toURL().openStream();
601 try {
602 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
603 }
604 finally {
605 stream.close();
606 }
607 return result;
608 }
609
610
611 private String newReadName() {
612 int index = 1;
613 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
614 != AlignmentModel.NO_SEQUENCE_FOUND) {
615
616 index++;
617 }
618 return DEFAULT_READ_NAME_PREFIX + index;
619 }
620
621
622 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
623 addRead(newReadName(), pherogramURI, reverseComplemented, null, 0, 0, 0, null); // Position values will be ignored, if shifts == null.
624 }
625
626
627 /**
628 * Adds a new sequence with attached phergram data area to the reads alignment.
629 * <p>
630 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
631 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
632 * and the base calls sequence are assumed.
633 *
634 * @param name the name of the new sequence
635 * @param pherogramURI the URI where the associated pherogram file is located
636 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
637 * be added, {@code false} otherwise.
638 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
639 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
640 * @return the sequence ID of the added read
641 * @throws IOException if an error occurred when trying to read the pherogram file
642 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
643 */
644 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
645 int firstSeqPos, int leftCutPos, int rightCutPos, SingleReadAlignment.Shift[] shifts)
646 throws IOException, UnsupportedChromatogramFormatException {
647
648 AlignmentModel provider = getReadsArea().getAlignmentModel();
649 PherogramProvider pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
650 if (reverseComplemented) {
651 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
652 }
653
654 // Create sequence:
655 provider.addSequence(name);
656 int id = provider.sequenceIDByName(name);
657
658 // Set edited sequence:
659 Collection<Object> tokens; // First save tokens in a collection to avoid GUI updated for each token.
660 if (editedSequence != null) {
661 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
662 }
663 else { // Copy base call sequence into alignment:
664 tokens = new ArrayList<Object>();
665 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
666 tokens.add(provider.getTokenSet().tokenByKeyChar(
667 pherogramProvider.getBaseCall(i).getUpperedBase().charAt(0)));
668 }
669 setDirty();
670 }
671 provider.insertTokensAt(id, 0, tokens);
672
673 // Create pherogram area:
674 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
675 new PherogramAreaModel(pherogramProvider));
676
677 // Set position properties and shifts:
678 if ((shifts != null) && (shifts.length > 0)) {
679 PherogramAreaModel model = pherogramArea.getModel();
680 model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
681 model.setRightCutPosition(rightCutPos);
682
683 for (int i = 0; i < shifts.length; i++) {
684 model.addShiftChange(shifts[i].position, shifts[i].shift);
685 }
686 setDirty();
687 }
688
689 // Add pherogram area to GUI:
690 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
691 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
692
693 // Save source URI:
694 return id;
695 }
696 }