Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,30 @@ tsalign align -p pair.fa -o alignment.toml -c sample_tsa_config --memory-limit 1
tsalign align -p pair.fa -o alignment-no-ts.toml -c sample_tsa_config --memory-limit 1000000000 --no-ts
```

Then, create the visualisation as follows:
Then, create the visualisation as follows (`-t` is for plain text):

```bash
tsalign show -i alignment.toml -n alignment-no-ts.toml
tsalign show -i alignment.toml -n alignment-no-ts.toml -t
```

If you want more than just a simple command-line visualisation, use the parameter `-s visualisation.svg` to render the visualisation as SVG.
Note that `-t` supports only simple cases of template switches, and may crash for more complicated cases.
For a more detailed and robust visualisation, use the parameter `-s visualisation.svg` to render the visualisation as SVG.
Since SVGs are not always well supported, you can also use the switch `-p` to render the visualisation also as PNG.
Note that the `-p` switch requires setting the `-s` parameter.

```bash
tsalign show -i alignment.toml -n alignment-no-ts.toml -ps visualisation.svg
```

There are some switches to add more detail to the SVG and PNG visualisation.

* `-a` adds arrows for the jumps of the alignments
* `-c` renders more of the complements of the input sequences

```bash
tsalign show -i alignment.toml -n alignment-no-ts.toml -ps visualisation.svg -ac
```

### Setting the alignment range

When searching for template switches, researchers are often interested in specific mutation hotspots that are only tens of characters long, while the 2-3-alignment of the template switch may also align outside of the mutation hotspot.
Expand Down
21 changes: 17 additions & 4 deletions lib_tsshow/src/plain_text/mutlipair_alignment_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ impl<SequenceName: Eq + Ord, CharacterData>
do_lowercasing: bool,
invert_alignment: bool,
) -> AlignmentRenderResult {
let alignment = alignment.into_iter();
let mut reference_gaps = Vec::new();

let (reference_sequence_name, mut translated_reference_sequence) = self
Expand All @@ -223,6 +224,14 @@ impl<SequenceName: Eq + Ord, CharacterData>
translated_reference_sequence.len()
);
trace!("translated_reference_sequence_offset: {rendered_sequence_offset}");
trace!(
"alignment_length: ({}, {})",
alignment.size_hint().0,
alignment
.size_hint()
.1
.map_or(String::from("unknown"), |len| len.to_string())
);
Comment thread
sebschmi marked this conversation as resolved.

let (query_sequence_name, mut translated_query_sequence) =
self.sequences.remove_entry(query_sequence_name).unwrap();
Expand All @@ -233,22 +242,22 @@ impl<SequenceName: Eq + Ord, CharacterData>
let mut query_offset_column = None;
let mut query_limit_column = None;

for alignment_type in alignment.into_iter().map(|alignment_type| {
for alignment_type in alignment.map(|alignment_type| {
if invert_alignment {
alignment_type.inverted()
} else {
alignment_type
}
}) {
//trace!("alignment_type: {alignment_type}");
trace!("alignment_type: {alignment_type}");

while matches!(
translated_reference_sequence
.get(index)
.map(Character::kind),
Some(CharacterKind::Blank)
) {
//trace!("Skipping blank");
trace!("Skipping blank");
translated_query_sequence.push(Character::new_blank(blank_data_generator()));
index += 1;
}
Expand Down Expand Up @@ -348,7 +357,11 @@ impl<SequenceName: Eq + Ord, CharacterData>
}
}

assert!(index <= translated_reference_sequence.len());
assert!(
index <= translated_reference_sequence.len(),
"index: {index}, translated_reference_sequence.len(): {}",
translated_reference_sequence.len(),
);
}

assert!(extension.next().is_none());
Expand Down
2 changes: 1 addition & 1 deletion lib_tsshow/src/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub fn create_ts_svg(
else {
return Err(Error::AlignmentHasNoTarget);
};
debug!("Alignment: {alignment:?}");
debug!("Alignment: {}", alignment.cigar());

let reference = &statistics.sequences.reference;
let query = &statistics.sequences.query;
Expand Down
29 changes: 24 additions & 5 deletions lib_tsshow/src/ts_arrangement/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,43 @@ impl TsInnerArrangement {
);

for ts in template_switches {
trace!("source_inner: {:?}", ts.inner);
trace!(
"source_inner: {:?}",
ts.inner
.iter()
.map(|c| format!("{}", c.source_column()))
.collect::<Vec<_>>()
);
trace!("inner_alignment: {:?}", ts.inner_alignment.cigar());

let (mut sp2_secondary, mut sp3_secondary) = match ts.secondary {
TemplateSwitchSecondary::Reference => (
source_arrangement
.try_reference_source_to_arrangement_column(ts.sp2_secondary)
.unwrap_or_else(|| source_arrangement.reference().len().into()),
.unwrap_or_else(|| {
trace!("SP2 is at reference end");
source_arrangement.reference().len().into()
}),
source_arrangement
.try_reference_source_to_arrangement_column(ts.sp3_secondary)
.unwrap_or_else(|| source_arrangement.reference().len().into()),
.unwrap_or_else(|| {
trace!("SP3 is at reference end");
source_arrangement.reference().len().into()
}),
),
TemplateSwitchSecondary::Query => (
source_arrangement
.try_query_source_to_arrangement_column(ts.sp2_secondary)
.unwrap_or_else(|| source_arrangement.query().len().into()),
.unwrap_or_else(|| {
trace!("SP2 is at query end");
source_arrangement.query().len().into()
}),
source_arrangement
.try_query_source_to_arrangement_column(ts.sp3_secondary)
.unwrap_or_else(|| source_arrangement.query().len().into()),
.unwrap_or_else(|| {
trace!("SP3 is at query end");
source_arrangement.query().len().into()
}),
),
};
let forward = sp2_secondary < sp3_secondary;
Expand Down
68 changes: 54 additions & 14 deletions lib_tsshow/src/ts_arrangement/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use lib_tsalign::a_star_aligner::{
AlignmentType, TemplateSwitchDirection, TemplateSwitchPrimary, TemplateSwitchSecondary,
},
};
use log::trace;
use log::{debug, trace};
use tagged_vec::TaggedVec;

use super::{
Expand Down Expand Up @@ -98,6 +98,12 @@ impl TsSourceArrangement {
debug_assert_eq!(current_reference_index, current_query_index);

while let Some(alignment_type) = alignment.next() {
trace!(
"Source alignment type: {alignment_type}; R/Q indices: {current_reference_index}/{current_query_index} {}/{}",
result.reference_arrangement_to_source_column(current_reference_index),
result.query_arrangement_to_source_column(current_query_index),
);

match alignment_type {
AlignmentType::PrimaryInsertion | AlignmentType::PrimaryFlankInsertion => {
result.reference.insert(
Expand Down Expand Up @@ -213,25 +219,37 @@ impl TsSourceArrangement {
current_reference_index: &mut ArrangementColumn,
current_query_index: &mut ArrangementColumn,
) -> TemplateSwitch {
debug!("Aligning template switch #{ts_index}");
let sp1_reference =
self.reference_arrangement_to_arrangement_char_column(*current_reference_index);
let sp1_query = self.query_arrangement_to_arrangement_char_column(*current_query_index);
let sp2_secondary = match ts_secondary {
TemplateSwitchSecondary::Reference => {
let source_current_reference_index =
self.reference_arrangement_to_source_column(*current_reference_index);
let adjusted_source_current_reference_index =
source_current_reference_index
.checked_sub(self.count_reference_copy_chars_before_next_real_char(
*current_reference_index,
))
.unwrap();
Comment thread
sebschmi marked this conversation as resolved.
trace!(
"current_reference_index: {current_reference_index} -> {source_current_reference_index}"
"current_reference_index: {current_reference_index} -> {source_current_reference_index} -> {adjusted_source_current_reference_index}"
);
source_current_reference_index
adjusted_source_current_reference_index
Comment thread
sebschmi marked this conversation as resolved.
}
TemplateSwitchSecondary::Query => {
let source_current_query_index =
self.query_arrangement_to_source_column(*current_query_index);
let adjusted_source_current_query_index = source_current_query_index
.checked_sub(
self.count_query_copy_chars_before_next_real_char(*current_query_index),
)
.unwrap();
Comment thread
sebschmi marked this conversation as resolved.
trace!(
"current_query_index: {current_query_index} -> {source_current_query_index}"
"current_query_index: {current_query_index} -> {source_current_query_index} -> {adjusted_source_current_query_index}"
);
source_current_query_index
adjusted_source_current_query_index
Comment thread
sebschmi marked this conversation as resolved.
}
} + first_offset;
trace!("first_offset: {first_offset}");
Expand All @@ -242,33 +260,34 @@ impl TsSourceArrangement {
let mut inner_alignment = Alignment::new();

let anti_primary_gap = loop {
match alignment.next() {
Some(AlignmentType::TemplateSwitchExit { anti_primary_gap }) => {
let alignment_type = alignment.next().unwrap_or_else(|| unreachable!());
trace!("Secondary alignment type: {alignment_type}");

match alignment_type {
AlignmentType::TemplateSwitchExit { anti_primary_gap } => {
break anti_primary_gap;
}
Some(alignment_type @ AlignmentType::SecondaryDeletion) => {
AlignmentType::SecondaryDeletion => {
match ts_direction {
TemplateSwitchDirection::Forward => sp3_secondary += 1,
TemplateSwitchDirection::Reverse => sp3_secondary -= 1,
}
inner_alignment.push(alignment_type);
}
Some(
alignment_type @ (AlignmentType::SecondarySubstitution
| AlignmentType::SecondaryMatch),
) => {

AlignmentType::SecondarySubstitution | AlignmentType::SecondaryMatch => {
match ts_direction {
TemplateSwitchDirection::Forward => sp3_secondary += 1,
TemplateSwitchDirection::Reverse => sp3_secondary -= 1,
}
primary_inner_length += 1;
inner_alignment.push(alignment_type);
}
Some(alignment_type @ AlignmentType::SecondaryInsertion) => {
AlignmentType::SecondaryInsertion => {
primary_inner_length += 1;
inner_alignment.push(alignment_type);
}
Some(AlignmentType::SecondaryRoot) => { /* Do nothing */ }
AlignmentType::SecondaryRoot => { /* Do nothing */ }
_ => unreachable!(),
}
};
Expand Down Expand Up @@ -690,6 +709,26 @@ impl TsSourceArrangement {
.nth(column.primitive())
.unwrap()
}

fn count_reference_copy_chars_before_next_real_char(&self, offset: ArrangementColumn) -> usize {
Self::count_copy_chars_before_next_real_char(&self.reference, offset)
}

fn count_query_copy_chars_before_next_real_char(&self, offset: ArrangementColumn) -> usize {
Self::count_copy_chars_before_next_real_char(&self.query, offset)
}

fn count_copy_chars_before_next_real_char(
sequence: &TaggedVec<ArrangementColumn, SourceChar>,
offset: ArrangementColumn,
) -> usize {
sequence
.iter_values()
.skip(offset.into())
.take_while(|c| !c.is_source_char())
.filter(|c| c.is_char() && c.is_copy())
.count()
}
}

impl SourceChar {
Expand Down Expand Up @@ -808,6 +847,7 @@ impl Char for SourceChar {
matches!(self, Self::Blank)
}

/// Returns true if this is a source char and not a copy of a source char.
fn is_source_char(&self) -> bool {
matches!(
self,
Expand Down
4 changes: 2 additions & 2 deletions test_files/LINC00271_92.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env bash

cargo build
cargo build --release

if [ ! -f test_files/LINC00271_92.toml ]; then
cargo build --release
target/release/tsalign align -p test_files/LINC00271_92.fa -o test_files/LINC00271_92.toml --alignment-method a-star-template-switch --skip-characters 'N-' --alphabet dna -c test_files/config/bench --ts-node-ord-strategy anti-diagonal --ts-min-length-strategy preprocess-price -k 0 --max-chaining-successors 0 --max-exact-cost-function-cost 0 --chaining-closed-list special --chaining-open-list linear-heap --rq-ranges R196..227Q196..202
target/release/tsalign align -p test_files/LINC00271_92.fa -o test_files/LINC00271_92.no_ts.toml --alignment-method a-star-template-switch --skip-characters 'N-' --alphabet dna -c test_files/config/bench --ts-node-ord-strategy anti-diagonal --ts-min-length-strategy preprocess-price -k 0 --max-chaining-successors 0 --max-exact-cost-function-cost 0 --chaining-closed-list special --chaining-open-list linear-heap --rq-ranges R196..227Q196..202 --no-ts
fi

target/debug/tsalign show -i test_files/LINC00271_92.toml -n test_files/LINC00271_92.no_ts.toml -ps test_files/LINC00271_92.svg
target/debug/tsalign show -i test_files/LINC00271_92.toml -n test_files/LINC00271_92.no_ts.toml -ps test_files/LINC00271_92.svg --log-level trace
14 changes: 14 additions & 0 deletions test_files/twin_show_repeated_chars.fa
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
>Reference
AAAAAAAAAAAA
TCTCGG
TTTTCCTTTT
TCTC
GGGGCCGGGG
CCCCCCCCCCCC
>Query
AAAAAGGAAAAA
TCTCGG



CCCCCGGCCCCC
17 changes: 14 additions & 3 deletions tsalign/src/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ use std::{
path::PathBuf,
};

use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use clap::Parser;
use lib_tsshow::{
plain_text::show_template_switches,
svg::{SvgConfig, create_error_svg, create_ts_svg},
svg_to_png,
};
use log::{LevelFilter, info, warn};
use log::{LevelFilter, error, info, warn};
use simplelog::{ColorChoice, TermLogger, TerminalMode};

#[derive(Parser)]
Expand Down Expand Up @@ -40,6 +40,10 @@ pub struct Cli {
#[clap(long, short = 'p')]
png: bool,

/// Output the template switches in plain text format to stdout.
#[clap(long, short = 't')]
plain_text: bool,

/// Always render the SVG (and optionally PNG), even if an error occurs. In the case of an error, the SVG simply contains the error message.
#[clap(long, short = 'r')]
render_always: bool,
Expand All @@ -62,6 +66,11 @@ pub fn cli(cli: Cli) -> Result<()> {
)
.unwrap();

if cli.svg.is_none() && !cli.plain_text {
error!("Neither --svg nor --plain-text is set. Nothing to do.");
bail!("Neither --svg nor --plain-text is set. Nothing to do.");
}

info!("Reading tsalign output toml file {:?}", cli.input);
let mut buffer = String::new();
File::open(cli.input)
Expand All @@ -81,7 +90,9 @@ pub fn cli(cli: Cli) -> Result<()> {
toml::from_str(&buffer).unwrap_or_else(|error| panic!("Error parsing input file: {error}"))
});

show_template_switches(stdout(), &result, &no_ts_result)?;
if cli.plain_text {
Comment thread
sebschmi marked this conversation as resolved.
show_template_switches(stdout(), &result, &no_ts_result)?;
}

if let Some(svg_out_path) = cli.svg.as_ref() {
let mut svg = Vec::new();
Expand Down
Loading