action.find_next = Find next
action.file = File
action.view = View
+action.annotations = Annotations
action.change_params = Change Parameters
action.apply = Apply
action.apply_threshold_all_groups = Apply threshold to all groups
label.sort_by_score = Sort by Score
label.sort_by_density = Sort by Density
label.sequence_sort_by_density = Sequence sort by Density
+label.sort_annotations_by_sequence = Sort annotations by sequence order
+label.sort_annotations_by_type = Sort annotations by type
label.reveal = Reveal
label.hide_columns = Hide Columns
label.load_jalview_annotations = Load Jalview Annotations or Features File
package jalview.analysis;
import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
import jalview.renderer.AnnotationRenderer;
* @param anns
* @return
*/
- public static List<AlignmentAnnotation> asList(
- AlignmentAnnotation[] anns)
+ public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
{
// TODO use AlignmentAnnotationI instead when it exists
return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
*/
package jalview.analysis;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+
import java.util.ArrayList;
import java.util.List;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.AlignmentI;
-
/**
* grab bag of useful alignment manipulation operations Expect these to be
* refactored elsewhere at some point.
newAl.setDataset(core.getDataset());
return newAl;
}
+
+ /**
+ * Returns the index (zero-based position) of a sequence in an alignment, or
+ * -1 if not found.
+ *
+ * @param al
+ * @param seq
+ * @return
+ */
+ public static int getSequenceIndex(AlignmentI al, SequenceI seq)
+ {
+ int result = -1;
+ int pos = 0;
+ for (SequenceI alSeq : al.getSequences())
+ {
+ if (alSeq == seq)
+ {
+ result = pos;
+ break;
+ }
+ pos++;
+ }
+ return result;
+ }
}
--- /dev/null
+package jalview.analysis;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * A helper class to sort all annotations associated with an alignment in
+ * various ways.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class AnnotationSorter
+{
+
+ private final AlignmentI alignment;
+
+ public AnnotationSorter(AlignmentI alignmentI)
+ {
+ this.alignment = alignmentI;
+ }
+
+ /**
+ * Default comparator sorts as follows by annotation type within sequence
+ * order:
+ * <ul>
+ * <li>annotations with a reference to a sequence in the alignment are sorted
+ * on sequence ordering</li>
+ * <li>other annotations go 'at the end', with their mutual order unchanged</li>
+ * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
+ * </ul>
+ */
+ private final Comparator<? super AlignmentAnnotation> bySequenceAndType = new Comparator<AlignmentAnnotation>()
+ {
+ @Override
+ public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null && o2 == null)
+ {
+ return 0;
+ }
+ if (o1 == null)
+ {
+ return -1;
+ }
+ if (o2 == null)
+ {
+ return 1;
+ }
+
+ /*
+ * Ignore label (keep existing ordering) for
+ * Conservation/Quality/Consensus etc
+ */
+ if (o1.sequenceRef == null && o2.sequenceRef == null)
+ {
+ return 0;
+ }
+ int sequenceOrder = compareSequences(o1, o2);
+ return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
+ }
+ };
+
+ /**
+ * This comparator sorts as follows by sequence order within annotation type
+ * <ul>
+ * <li>annotations with a reference to a sequence in the alignment are sorted
+ * on label (non-case-sensitive)</li>
+ * <li>other annotations go 'at the end', with their mutual order unchanged</li>
+ * <li>within the same label, sort by order of the related sequences</li>
+ * </ul>
+ */
+ private final Comparator<? super AlignmentAnnotation> byTypeAndSequence = new Comparator<AlignmentAnnotation>()
+ {
+ @Override
+ public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null && o2 == null)
+ {
+ return 0;
+ }
+ if (o1 == null)
+ {
+ return -1;
+ }
+ if (o2 == null)
+ {
+ return 1;
+ }
+
+ /*
+ * Ignore label (keep existing ordering) for
+ * Conservation/Quality/Consensus etc
+ */
+ if (o1.sequenceRef == null && o2.sequenceRef == null)
+ {
+ return 0;
+ }
+
+ /*
+ * Sort non-sequence-related after sequence-related.
+ */
+ if (o1.sequenceRef == null)
+ {
+ return 1;
+ }
+ if (o2.sequenceRef == null)
+ {
+ return -1;
+ }
+ int labelOrder = compareLabels(o1, o2);
+ return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
+ }
+ };
+
+ /**
+ * Sort by annotation type (label), within sequence order.
+ * Non-sequence-related annotations sort to the end.
+ *
+ * @param alignmentAnnotations
+ */
+ public void sortBySequenceAndType(
+ AlignmentAnnotation[] alignmentAnnotations)
+ {
+ if (alignmentAnnotations != null)
+ {
+ synchronized (alignmentAnnotations)
+ {
+ Arrays.sort(alignmentAnnotations, bySequenceAndType);
+ }
+ }
+ }
+
+ /**
+ * Sort by sequence order within annotation type (label). Non-sequence-related
+ * annotations sort to the end.
+ *
+ * @param alignmentAnnotations
+ */
+ public void sortByTypeAndSequence(
+ AlignmentAnnotation[] alignmentAnnotations)
+ {
+ if (alignmentAnnotations != null)
+ {
+ synchronized (alignmentAnnotations)
+ {
+ Arrays.sort(alignmentAnnotations, byTypeAndSequence);
+ }
+ }
+ }
+
+ /**
+ * Non-case-sensitive comparison of annotation labels. Returns zero if either
+ * argument is null.
+ *
+ * @param o1
+ * @param o2
+ * @return
+ */
+ private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null || o2 == null)
+ {
+ return 0;
+ }
+ String label1 = o1.label;
+ String label2 = o2.label;
+ if (label1 == null && label2 == null)
+ {
+ return 0;
+ }
+ if (label1 == null)
+ {
+ return -1;
+ }
+ if (label2 == null)
+ {
+ return 1;
+ }
+ return label1.toUpperCase().compareTo(label2.toUpperCase());
+ }
+
+ /**
+ * Comparison based on position of associated sequence (if any) in the
+ * alignment. Returns zero if either argument is null.
+ *
+ * @param o1
+ * @param o2
+ * @return
+ */
+ private int compareSequences(AlignmentAnnotation o1,
+ AlignmentAnnotation o2)
+ {
+ SequenceI seq1 = o1.sequenceRef;
+ SequenceI seq2 = o2.sequenceRef;
+ if (seq1 == null && seq2 == null)
+ {
+ return 0;
+ }
+ if (seq1 == null)
+ {
+ return 1;
+ }
+ if (seq2 == null)
+ {
+ return -1;
+ }
+ // get sequence index - but note -1 means 'at end' so needs special handling
+ int index1 = AlignmentUtils.getSequenceIndex(alignment, seq1);
+ int index2 = AlignmentUtils.getSequenceIndex(alignment, seq2);
+ if (index1 == index2)
+ {
+ return 0;
+ }
+ if (index1 == -1)
+ {
+ return -1;
+ }
+ if (index2 == -1)
+ {
+ return 1;
+ }
+ return Integer.compare(index1, index2);
+ }
+}
import jalview.analysis.AAFrequency;
import jalview.analysis.AlignmentSorter;
import jalview.analysis.AlignmentUtils;
+import jalview.analysis.AnnotationSorter;
import jalview.analysis.Conservation;
import jalview.analysis.CrossRef;
import jalview.analysis.NJTree;
.getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt
.getKeyCode() <= KeyEvent.VK_NUMPAD9))
&& Character.isDigit(evt.getKeyChar()))
+ {
alignPanel.seqPanel.numberPressed(evt.getKeyChar());
+ }
switch (evt.getKeyCode())
{
case KeyEvent.VK_DOWN:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
moveSelectedSequences(false);
+ }
if (viewport.cursorMode)
+ {
alignPanel.seqPanel.moveCursor(0, 1);
+ }
break;
case KeyEvent.VK_UP:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
moveSelectedSequences(true);
+ }
if (viewport.cursorMode)
+ {
alignPanel.seqPanel.moveCursor(0, -1);
+ }
break;
case KeyEvent.VK_LEFT:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
slideSequences(false, alignPanel.seqPanel.getKeyboardNo1());
+ }
else
+ {
alignPanel.seqPanel.moveCursor(-1, 0);
+ }
break;
case KeyEvent.VK_RIGHT:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
slideSequences(true, alignPanel.seqPanel.getKeyboardNo1());
+ }
else
+ {
alignPanel.seqPanel.moveCursor(1, 0);
+ }
break;
case KeyEvent.VK_SPACE:
{
case KeyEvent.VK_LEFT:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
viewport.firePropertyChange("alignment", null, viewport
.getAlignment().getSequences());
+ }
break;
case KeyEvent.VK_RIGHT:
if (evt.isAltDown() || !viewport.cursorMode)
+ {
viewport.firePropertyChange("alignment", null, viewport
.getAlignment().getSequences());
+ }
break;
}
}
protected void undoMenuItem_actionPerformed(ActionEvent e)
{
if (viewport.historyList.empty())
+ {
return;
+ }
CommandI command = (CommandI) viewport.historyList.pop();
viewport.redoList.push(command);
command.undoCommand(getViewAlignments());
for (int i = 0; i < viewport.getAlignment().getHeight(); i++)
{
if (!sg.contains(viewport.getAlignment().getSequenceAt(i)))
+ {
invertGroup.add(viewport.getAlignment().getSequenceAt(i));
+ }
}
SequenceI[] seqs1 = sg.toArray(new SequenceI[0]);
SequenceI[] seqs2 = new SequenceI[invertGroup.size()];
for (int i = 0; i < invertGroup.size(); i++)
+ {
seqs2[i] = (SequenceI) invertGroup.elementAt(i);
+ }
SlideSequencesCommand ssc;
if (right)
+ {
ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1,
size, viewport.getGapCharacter());
+ }
else
+ {
ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2,
size, viewport.getGapCharacter());
+ }
int groupAdjustment = 0;
if (ssc.getGapsInsertedBegin() && right)
{
if (viewport.cursorMode)
+ {
alignPanel.seqPanel.moveCursor(size, 0);
+ }
else
+ {
groupAdjustment = size;
+ }
}
else if (!ssc.getGapsInsertedBegin() && !right)
{
if (viewport.cursorMode)
+ {
alignPanel.seqPanel.moveCursor(-size, 0);
+ }
else
+ {
groupAdjustment = -size;
+ }
}
if (groupAdjustment != 0)
}
if (!appendHistoryItem)
+ {
addHistoryItem(ssc);
+ }
repaint();
}
{
AlignmentAnnotation sann[] = sequences[i].getAnnotation();
if (sann == null)
+ {
continue;
+ }
for (int avnum = 0; avnum < alview.length; avnum++)
{
if (alview[avnum] != alignment)
if (ds.getSequences() == null
|| !ds.getSequences().contains(
sprods[s].getDatasetSequence()))
+ {
ds.addSequence(sprods[s].getDatasetSequence());
+ }
sprods[s].updatePDBIds();
}
Alignment al = new Alignment(sprods);
}
this.alignPanel.paintAlignment(true);
}
+
+ @Override
+ protected void sortAnnotationsByType_actionPerformed()
+ {
+ AnnotationSorter sorter = new AnnotationSorter(
+ this.alignPanel.getAlignment());
+ sorter.sortByTypeAndSequence(this.alignPanel.getAlignment()
+ .getAlignmentAnnotation());
+ alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
+ }
+
+ @Override
+ protected void sortAnnotationsBySequence_actionPerformed()
+ {
+ AnnotationSorter sorter = new AnnotationSorter(
+ this.alignPanel.getAlignment());
+ sorter.sortBySequenceAndType(this.alignPanel.getAlignment()
+ .getAlignmentAnnotation());
+ alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
+ }
}
class PrintThread extends Thread
import jalview.analysis.AAFrequency;
import jalview.analysis.AlignmentAnnotationUtils;
+import jalview.analysis.AnnotationSorter;
import jalview.analysis.Conservation;
import jalview.commands.ChangeCaseCommand;
import jalview.commands.EditCommand;
* Add annotations at the top of the annotation, in the same order as their
* related sequences.
*/
- int insertPosition = 0;
for (SequenceI seq : candidates.keySet())
{
for (AlignmentAnnotation ann : candidates.get(seq))
// adjust for gaps
copyAnn.adjustForAlignment();
// add to the alignment and set visible
- this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++);
+ this.ap.getAlignment().addAnnotation(copyAnn);
copyAnn.visible = true;
}
}
+ // TODO: save annotation sort order on AlignViewport
+ // do sorting from AlignmentPanel.updateAnnotation()
+ new AnnotationSorter(this.ap.getAlignment())
+ .sortBySequenceAndType(this.ap.getAlignment()
+ .getAlignmentAnnotation());
refresh();
}
protected JMenu viewMenu = new JMenu();
+ protected JMenu annotationsMenu = new JMenu();
+
protected JMenu colourMenu = new JMenu();
protected JMenu calculateMenu = new JMenu();
protected JMenuItem hideAllAnnotations = new JMenuItem();
+ protected JMenuItem sortAnnBySequence = new JMenuItem();
+
+ protected JMenuItem sortAnnByType = new JMenuItem();
+
protected JCheckBoxMenuItem hiddenMarkers = new JCheckBoxMenuItem();
JMenuItem invertColSel = new JMenuItem();
});
editMenu.setText(MessageManager.getString("action.edit"));
viewMenu.setText(MessageManager.getString("action.view"));
+ annotationsMenu.setText(MessageManager.getString("action.annotations"));
colourMenu.setText(MessageManager.getString("action.colour"));
calculateMenu.setText(MessageManager.getString("action.calculate"));
webService.setText(MessageManager.getString("action.web_service"));
hideAllAnnotations_actionPerformed();
}
});
+ sortAnnBySequence.setText(MessageManager
+ .getString("label.sort_annotations_by_sequence"));
+ sortAnnBySequence.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ sortAnnotationsBySequence_actionPerformed();
+ }
+ });
+ sortAnnByType.setText(MessageManager
+ .getString("label.sort_annotations_by_type"));
+ sortAnnByType.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ sortAnnotationsByType_actionPerformed();
+ }
+ });
colourTextMenuItem.setText(MessageManager
.getString("label.colour_text"));
colourTextMenuItem
alignFrameMenuBar.add(editMenu);
alignFrameMenuBar.add(selectMenu);
alignFrameMenuBar.add(viewMenu);
+ alignFrameMenuBar.add(annotationsMenu);
alignFrameMenuBar.add(formatMenu);
alignFrameMenuBar.add(colourMenu);
alignFrameMenuBar.add(calculateMenu);
viewMenu.add(hideMenu);
viewMenu.addSeparator();
viewMenu.add(followHighlightMenuItem);
- viewMenu.add(annotationPanelMenuItem);
- viewMenu.add(showAllAnnotations);
- viewMenu.add(hideAllAnnotations);
+ annotationsMenu.add(annotationPanelMenuItem);
+ annotationsMenu.add(showAllAnnotations);
+ annotationsMenu.add(hideAllAnnotations);
+ annotationsMenu.add(sortAnnBySequence);
+ annotationsMenu.add(sortAnnByType);
autoAnnMenu.add(applyAutoAnnotationSettings);
autoAnnMenu.add(showConsensusHistogram);
autoAnnMenu.add(showSequenceLogo);
autoAnnMenu.addSeparator();
autoAnnMenu.add(showGroupConservation);
autoAnnMenu.add(showGroupConsensus);
- viewMenu.add(autoAnnMenu);
+ annotationsMenu.add(autoAnnMenu);
viewMenu.addSeparator();
viewMenu.add(showSeqFeatures);
// viewMenu.add(showSeqFeaturesHeight);
}
/**
+ * Action on clicking sort annotations by type.
+ */
+ protected void sortAnnotationsByType_actionPerformed()
+ {
+ }
+
+ /**
+ * Action on clicking sort annotations by sequence
+ */
+ protected void sortAnnotationsBySequence_actionPerformed()
+ {
+ }
+
+ /**
* Action on clicking Show all annotations.
*/
protected void showAllAnnotations_actionPerformed()
--- /dev/null
+package jalview.analysis;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AnnotationSorterTest
+{
+ private static final int NUM_SEQS = 6;
+
+ private static final int NUM_ANNS = 7;
+
+ private static final String SS = "secondary structure";
+
+ AlignmentAnnotation[] anns = new AlignmentAnnotation[0];
+
+ Alignment al = null;
+
+ /*
+ * Set up 6 sequences and 7 annotations.
+ */
+ @Before
+ public void setUp()
+ {
+ al = buildAlignment(NUM_SEQS);
+ anns = buildAnnotations(NUM_ANNS);
+ }
+
+ /**
+ * Construct an array of numAnns annotations
+ *
+ * @param numAnns
+ *
+ * @return
+ */
+ protected AlignmentAnnotation[] buildAnnotations(int numAnns)
+ {
+ List<AlignmentAnnotation> annlist = new ArrayList<AlignmentAnnotation>();
+ for (int i = 0; i < numAnns; i++)
+ {
+ AlignmentAnnotation ann = new AlignmentAnnotation(SS + i, "", 0);
+ annlist.add(ann);
+ }
+ return annlist.toArray(anns);
+ }
+
+ /**
+ * Make an alignment with numSeqs sequences in it.
+ *
+ * @param numSeqs
+ *
+ * @return
+ */
+ private Alignment buildAlignment(int numSeqs)
+ {
+ SequenceI[] seqs = new Sequence[numSeqs];
+ for (int i = 0; i < numSeqs; i++)
+ {
+ seqs[i] = new Sequence("Sequence" + i, "axrdkfp");
+ }
+ return new Alignment(seqs);
+ }
+
+ /**
+ * Test sorting by annotation type (label) within sequence order, including
+ * <ul>
+ * <li>annotations with no sequence reference - sort to end keeping mutual
+ * ordering</li>
+ * <li>annotations with sequence ref = sort in sequence order</li>
+ * <li>multiple annotations for same sequence ref - sort by label
+ * non-case-specific</li>
+ * <li>annotations with reference to sequence not in alignment - treat like no
+ * sequence ref</li>
+ * </ul>
+ */
+ @Test
+ public void testSortBySequenceAndType()
+ {
+ // @formatter:off
+ anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
+ anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
+ anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
+ anns[3].sequenceRef = null; anns[3].label = "Quality";
+ anns[4].sequenceRef = null; anns[4].label = "Consensus";
+ anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5";
+ anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP";
+ // @formatter:on
+
+ AnnotationSorter testee = new AnnotationSorter(al);
+ testee.sortBySequenceAndType(anns);
+ assertEquals("label5", anns[0].label); // for sequence 0
+ assertEquals("label0", anns[1].label); // for sequence 1
+ assertEquals("iron", anns[2].label); // sequence 3 /iron
+ assertEquals("IRP", anns[3].label); // sequence 3/IRP
+ assertEquals("structure", anns[4].label); // sequence 3/structure
+ assertEquals("Quality", anns[5].label); // non-sequence annotations
+ assertEquals("Consensus", anns[6].label); // retain ordering
+ }
+
+ /**
+ * Test sorting by annotation type (label) within sequence order, including
+ * <ul>
+ * <li>annotations with no sequence reference - sort to end keeping mutual
+ * ordering</li>
+ * <li>annotations with sequence ref = sort in sequence order</li>
+ * <li>multiple annotations for same sequence ref - sort by label
+ * non-case-specific</li>
+ * <li>annotations with reference to sequence not in alignment - treat like no
+ * sequence ref</li>
+ * </ul>
+ */
+ @Test
+ public void testSortByTypeAndSequence()
+ {
+ // @formatter:off
+ anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
+ anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
+ anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
+ anns[3].sequenceRef = null; anns[3].label = "Quality";
+ anns[4].sequenceRef = null; anns[4].label = "Consensus";
+ anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON";
+ anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure";
+ // @formatter:on
+
+ AnnotationSorter testee = new AnnotationSorter(al);
+ testee.sortByTypeAndSequence(anns);
+ assertEquals("IRON", anns[0].label); // IRON / sequence 0
+ assertEquals("iron", anns[1].label); // iron / sequence 3
+ assertEquals("label0", anns[2].label); // label0 / sequence 1
+ assertEquals("Structure", anns[3].label); // Structure / sequence 2
+ assertEquals("structure", anns[4].label); // structure / sequence 3
+ assertEquals("Quality", anns[5].label); // non-sequence annotations
+ assertEquals("Consensus", anns[6].label); // retain ordering
+ }
+
+ @Test
+ public void testSortBySequenceAndType_timing()
+ {
+ final long targetTime = 300; // ms
+ final int numSeqs = 10000;
+ final int numAnns = 20000;
+ al = buildAlignment(numSeqs);
+ anns = buildAnnotations(numAnns);
+
+ /*
+ * Set the annotations in random order with respect to the sequences
+ */
+ Random r = new Random();
+ final SequenceI[] sequences = al.getSequencesArray();
+ for (int i = 0; i < anns.length; i++)
+ {
+ SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)];
+ anns[i].sequenceRef = randomSequenceRef;
+ }
+ long startTime = System.currentTimeMillis();
+ AnnotationSorter testee = new AnnotationSorter(al);
+ testee.sortByTypeAndSequence(anns);
+ long endTime = System.currentTimeMillis();
+ final long elapsed = endTime - startTime;
+ System.out.println("Timing test for " + numSeqs + " sequences and "
+ + numAnns + " annotations took " + elapsed + "ms");
+ assertTrue("Sort took more than " + targetTime + "ms",
+ elapsed <= targetTime);
+ }
+}