Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Sections commonly used: Features, Bug fixes, Other changes.

### Bug fixes

- `--soloBarcodeMate 1` no longer runs without clipping. The flag says the
barcode lives inside mate 1, and nothing else says how many bases that is,
so STAR refuses the run unless the mate is clipped
(`ParametersSolo.cpp:145-150`). Without the check the CB+UMI prefix was
aligned as if it were cDNA (28 bases of it for 10x v3) and the run reported
nothing. Closes #227.
- Read names are cut at `--readNameSeparator` (default `/`), as STAR does. A
read named `foo/1` was previously emitted as `foo/1` where STAR emits `foo`.

Expand Down
89 changes: 83 additions & 6 deletions src/params/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1872,6 +1872,22 @@ impl Parameters {
"--soloBarcodeMate 1 is only supported with --soloType CB_UMI_Simple",
));
}
// The barcode region of that mate is not cDNA, and nothing
// else says how many bases it spans, so STAR refuses the
// run unless the mate is clipped
// (`ParametersSolo.cpp:145-150`). Without this the CB+UMI
// prefix is aligned as if it were sequence: 28 bases of it
// for 10x v3, with no error anywhere in the run.
let mate = 0; // --soloBarcodeMate 1 is mate 1
if params.clip5p(mate) == 0 && params.clip3p(mate) == 0 {
return Err(command.error(
ErrorKind::InvalidValue,
"--soloBarcodeMate 1 puts the barcode inside mate 1, which requires \
clipping the barcode off that mate\n\
SOLUTION: clip it from 5' and/or 3' with --clip5pNbases and/or \
--clip3pNbases; give a value per mate, using 0 for no clipping",
));
}
}
other => {
return Err(command.error(
Expand Down Expand Up @@ -2367,8 +2383,8 @@ mod tests {

#[test]
fn solo_barcode_mate_validation() {
let with_mate = |mate: &str| {
try_parse(&[
let with_mate_and_clip = |mate: &str, clip: Option<(&str, &str)>| {
let mut args = vec![
"--readFilesIn",
"R1.fq",
"R2.fq",
Expand All @@ -2384,18 +2400,79 @@ mod tests {
"Gene",
"--soloBarcodeMate",
mate,
])
];
if let Some((flag, value)) = clip {
args.push(flag);
args.push(value);
args.push("0");
}
try_parse(&args)
};
// Mate 1 (5' paired-end) is accepted; the helper reports it.
let with_mate = |mate: &str| with_mate_and_clip(mate, Some(("--clip5pNbases", "28")));

// Mate 1 (5' paired-end) with the barcode clipped off: accepted, and
// the helper reports it.
let p = with_mate("1").unwrap();
assert!(p.solo_barcode_on_mate1());
// Mate 0 (default) is the standard SE-solo path.
assert!(!with_mate("0").unwrap().solo_barcode_on_mate1());
// Mate 0 (default) is the standard SE-solo path; no clip is needed
// because the barcode has its own read.
assert!(
!with_mate_and_clip("0", None)
.unwrap()
.solo_barcode_on_mate1()
);
// Mate 2 is rejected with a clear message.
let err = with_mate("2").unwrap_err().to_string();
assert!(err.contains("soloBarcodeMate"), "unexpected error: {err}");
}

/// STAR refuses `--soloBarcodeMate 1` unless the barcode is clipped off
/// that mate (`ParametersSolo.cpp:145-150`): the barcode region is not
/// cDNA, and nothing else says how long it is. Without the check the CB+UMI
/// prefix is aligned as sequence and the run reports nothing.
#[test]
fn solo_barcode_mate_1_requires_clipping_that_mate() {
let with_clip = |extra: &[&str]| {
let mut args = vec![
"--readFilesIn",
"R1.fq",
"R2.fq",
"--soloType",
"CB_UMI_Simple",
"--soloCBwhitelist",
"None",
"--soloCBmatchWLtype",
"Exact",
"--sjdbGTFfile",
"g.gtf",
"--soloFeatures",
"Gene",
"--soloBarcodeMate",
"1",
];
args.extend_from_slice(extra);
try_parse(&args)
};

// No clip at all: refused, and the message says what to do.
let err = with_clip(&[]).unwrap_err().to_string();
assert!(
err.contains("clip5pNbases"),
"the error should name the fix: {err}"
);

// A clip on mate 2 only leaves mate 1's barcode in place, so it is
// still refused.
assert!(with_clip(&["--clip5pNbases", "0", "28"]).is_err());

// 5' clip on mate 1: accepted.
assert!(with_clip(&["--clip5pNbases", "28", "0"]).is_ok());
// 3' clip on mate 1: also accepted, for a barcode at the other end.
assert!(with_clip(&["--clip3pNbases", "28", "0"]).is_ok());
// A single value applies to both mates, which covers mate 1.
assert!(with_clip(&["--clip5pNbases", "28"]).is_ok());
}

#[test]
fn scoring_overrides() {
let p = try_parse(&[
Expand Down
Loading