Skip to content

Commit f847113

Browse files
committed
loosened evaluate function rules, added more descriptive filenames
1 parent 3df86bb commit f847113

File tree

4 files changed

+96
-33
lines changed

4 files changed

+96
-33
lines changed

gui/app.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,25 @@ def _tab_run():
672672
st.divider()
673673
if rc == 0:
674674
st.success("Pipeline finished successfully!")
675-
if FINAL_DIR.exists():
675+
mode = st.session_state.get("mode", "Singleplex")
676+
if mode == "Evaluate" and EVALUATE_DIR.exists():
677+
xlsx_files = sorted(EVALUATE_DIR.glob("**/*.xlsx"))
678+
if xlsx_files:
679+
import pandas as pd
680+
681+
st.subheader("Evaluation results preview")
682+
for xf in xlsx_files:
683+
try:
684+
df = pd.read_excel(xf, sheet_name="detail")
685+
st.write(f"**{xf.name}** — {len(df)} target alignments")
686+
st.dataframe(df.head(10), use_container_width=True)
687+
except Exception as exc:
688+
st.warning(f"Could not read {xf.name}: {exc}")
689+
st.caption("See the **Results** tab for full details and downloads.")
690+
else:
691+
st.warning("Pipeline completed but no evaluation reports were generated. "
692+
"Check the log for warnings.")
693+
elif FINAL_DIR.exists():
676694
csvs = sorted(FINAL_DIR.glob("*.csv"))
677695
if csvs:
678696
import pandas as pd
@@ -747,28 +765,41 @@ def _tab_results():
747765
st.subheader("Evaluation reports")
748766

749767
if EVALUATE_DIR.exists():
750-
reports = sorted(
751-
p for p in EVALUATE_DIR.iterdir()
752-
if p.is_dir()
753-
)
754768
xlsx_files = sorted(EVALUATE_DIR.glob("**/*.xlsx"))
755769
else:
756-
reports = []
757770
xlsx_files = []
758771

759-
if reports:
760-
st.write("Report directories: " + ", ".join(f"`{r.name}`" for r in reports))
761-
762772
if xlsx_files:
763-
for xf in xlsx_files:
773+
import pandas as pd
774+
775+
selected_xlsx = st.selectbox(
776+
"Select report to view",
777+
options=[xf.name for xf in xlsx_files],
778+
key="result_xlsx",
779+
)
780+
if selected_xlsx:
781+
xf = next(x for x in xlsx_files if x.name == selected_xlsx)
782+
try:
783+
# Show summary sheet
784+
df_summary = pd.read_excel(xf, sheet_name="summary", header=None)
785+
st.write("**Summary**")
786+
st.dataframe(df_summary, use_container_width=True, hide_index=True)
787+
788+
# Show detail sheet
789+
df_detail = pd.read_excel(xf, sheet_name="detail")
790+
st.write(f"**Detail** — {len(df_detail)} target alignments")
791+
st.dataframe(df_detail, use_container_width=True)
792+
except Exception as exc:
793+
st.error(f"Error reading {selected_xlsx}: {exc}")
794+
764795
st.download_button(
765-
f"Download {xf.name}",
796+
f"Download {selected_xlsx}",
766797
data=xf.read_bytes(),
767-
file_name=xf.name,
798+
file_name=selected_xlsx,
768799
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
769-
key=f"dl_{xf.name}",
800+
key=f"dl_{selected_xlsx}",
770801
)
771-
elif not reports:
802+
else:
772803
st.info("No evaluation reports found.")
773804

774805
st.divider()

src/qprimer_designer/commands/prepare_input.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def register(subparsers):
3232
parser.add_argument("--reftype", dest="reftype", required=True, choices=["on", "off"], help="on-target or off-target")
3333
parser.add_argument("--features", dest="pri_features", required=True, help="Primer features CSV")
3434
parser.add_argument("--prev", default="", help="Previous evaluation file (for off-target restriction)")
35+
parser.add_argument("--skip-length-filter", action="store_true", help="Skip amplicon length constraints")
3536
parser.set_defaults(func=run)
3637

3738

@@ -47,10 +48,16 @@ def _ensure_list(x):
4748
def run(args):
4849
"""Run the prepare-input command."""
4950
params = parse_params(args.param_file)
50-
min_amp_len = int(params.get("AMPLEN_MIN", 60))
51-
max_amp_len = int(params.get("AMPLEN_MAX", 200))
52-
min_off_len = int(params.get("OFFLEN_MIN", 60))
53-
max_off_len = int(params.get("OFFLEN_MAX", 2000))
51+
if args.skip_length_filter:
52+
min_amp_len = 0
53+
max_amp_len = float('inf')
54+
min_off_len = 0
55+
max_off_len = float('inf')
56+
else:
57+
min_amp_len = int(params.get("AMPLEN_MIN", 60))
58+
max_amp_len = int(params.get("AMPLEN_MAX", 200))
59+
min_off_len = int(params.get("OFFLEN_MIN", 60))
60+
max_off_len = int(params.get("OFFLEN_MAX", 2000))
5461
num_select = int(params.get("NUM_TOP_SENSITIVITY", 100))
5562

5663
print(f"Preparing ML input from {args.mapped}...")
@@ -108,7 +115,14 @@ def run(args):
108115

109116
drop_cols = ['orientation', 'forrev']
110117

111-
if args.reftype == 'on':
118+
skip = args.skip_length_filter
119+
120+
if skip:
121+
fors = maptbl[maptbl['forrev'] == 'f'].drop(columns=drop_cols)
122+
revs = maptbl[maptbl['forrev'] == 'r'].drop(columns=drop_cols)
123+
minl, maxl = 0, float('inf')
124+
lfunc = max
125+
elif args.reftype == 'on':
112126
fors = maptbl[(maptbl['orientation'] == 0) & (maptbl['forrev'] == 'f')].drop(columns=drop_cols)
113127
revs = maptbl[(maptbl['orientation'] == 16) & (maptbl['forrev'] == 'r')].drop(columns=drop_cols)
114128
minl, maxl = min_amp_len, max_amp_len
@@ -164,7 +178,7 @@ def run(args):
164178
# Use the reverse primer's actual length from features
165179
r_len = int(revs.loc[r_id, 'len'])
166180
ampseq = tarseqs[t_f][st_f - 1: st_r + r_len]
167-
if minl <= len(ampseq) <= maxl:
181+
if len(ampseq) > 0 and minl <= len(ampseq) <= maxl:
168182
targets_by_r[r_id].append(t_f)
169183
starts_by_r[r_id].append(st_f)
170184
amplens_by_r[r_id].add(len(ampseq))

workflows/Snakefile.example

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
############################################
66

77
# SINGLEPLEX REQUIRED: user must fill these for singleplex, ignored if not multiplex
8-
TARGETS = ['TEST']
8+
TARGETS = ['H3']
99
CROSS = []
1010

1111
# shared option for multiplex and singleplex
@@ -146,7 +146,10 @@ if EVALUATE:
146146

147147
PSET_NAME = PSET_PATH.stem
148148
else:
149-
PSET_NAME = "ps2eval"
149+
# Build a readable name from primer sequences
150+
f_tag = (FOR[:5] + FOR[-5:]).upper() if len(FOR) >= 10 else FOR.upper()
151+
r_tag = (REV[:5] + REV[-5:]).upper() if len(REV) >= 10 else REV.upper()
152+
PSET_NAME = f"eval_f_{f_tag}_r_{r_tag}"
150153

151154
############################################
152155
# Final targets
@@ -381,8 +384,13 @@ rule prepare_pset_fasta:
381384
comp = str.maketrans("ACGTacgt", "TGCAtgca")
382385
rev_seq_rc = rev_seq.translate(comp)[::-1]
383386

387+
# Use a readable primer pair ID from the sequences
388+
f_tag = (for_seq[:5] + for_seq[-5:]).upper() if len(for_seq) >= 10 else for_seq.upper()
389+
r_tag = (rev_seq[:5] + rev_seq[-5:]).upper() if len(rev_seq) >= 10 else rev_seq.upper()
390+
pair_id = f"f_{f_tag}_r_{r_tag}"
391+
384392
with open(out, "w") as f:
385-
f.write(f">1_for\n{for_seq}\n>1_rev\n{rev_seq_rc}\n")
393+
f.write(f">{pair_id}_for\n{for_seq}\n>{pair_id}_rev\n{rev_seq_rc}\n")
386394

387395
ids = check_ids(out)
388396

@@ -593,16 +601,17 @@ rule prepare_input:
593601
output:
594602
"inputs/{virus}.{target}.input"
595603
params:
596-
ref_type=lambda wc: "on" if wc.virus == wc.target else "off",
604+
ref_type=lambda wc: "on" if (wc.virus == wc.target or EVALUATE) else "off",
597605
prev_arg=lambda wc: (
598606
f"--prev outputs/{wc.virus}.{wc.virus}.eval"
599607
if wc.virus != wc.target and not EVALUATE else ""
600-
)
608+
),
609+
skip_len=lambda wc: "--skip-length-filter" if EVALUATE else ""
601610
shell:
602611
"qprimer prepare-input "
603612
"--in {input.mapped} {params.prev_arg} --out {output} "
604613
"--ref {input.ref} --reftype {params.ref_type} "
605-
"--features {input.features} --params {PARAMS}"
614+
"--features {input.features} --params {PARAMS} {params.skip_len}"
606615

607616

608617
rule evaluate:
@@ -619,7 +628,7 @@ rule evaluate:
619628
resources:
620629
gpu=1
621630
params:
622-
ref_type=lambda wc: "on" if wc.virus == wc.target else "off"
631+
ref_type=lambda wc: "on" if (wc.virus == wc.target or EVALUATE) else "off"
623632
shell:
624633
"qprimer evaluate "
625634
"--in {input.inp} --out {output} "

workflows/Snakefile.template

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ if EVALUATE:
146146

147147
PSET_NAME = PSET_PATH.stem
148148
else:
149-
PSET_NAME = "ps2eval"
149+
# Build a readable name from primer sequences
150+
f_tag = (FOR[:5] + FOR[-5:]).upper() if len(FOR) >= 10 else FOR.upper()
151+
r_tag = (REV[:5] + REV[-5:]).upper() if len(REV) >= 10 else REV.upper()
152+
PSET_NAME = f"eval_f_{f_tag}_r_{r_tag}"
150153

151154
############################################
152155
# Final targets
@@ -381,8 +384,13 @@ rule prepare_pset_fasta:
381384
comp = str.maketrans("ACGTacgt", "TGCAtgca")
382385
rev_seq_rc = rev_seq.translate(comp)[::-1]
383386

387+
# Use a readable primer pair ID from the sequences
388+
f_tag = (for_seq[:5] + for_seq[-5:]).upper() if len(for_seq) >= 10 else for_seq.upper()
389+
r_tag = (rev_seq[:5] + rev_seq[-5:]).upper() if len(rev_seq) >= 10 else rev_seq.upper()
390+
pair_id = f"f_{f_tag}_r_{r_tag}"
391+
384392
with open(out, "w") as f:
385-
f.write(f">1_for\n{for_seq}\n>1_rev\n{rev_seq_rc}\n")
393+
f.write(f">{pair_id}_for\n{for_seq}\n>{pair_id}_rev\n{rev_seq_rc}\n")
386394

387395
ids = check_ids(out)
388396

@@ -593,16 +601,17 @@ rule prepare_input:
593601
output:
594602
"inputs/{virus}.{target}.input"
595603
params:
596-
ref_type=lambda wc: "on" if wc.virus == wc.target else "off",
604+
ref_type=lambda wc: "on" if (wc.virus == wc.target or EVALUATE) else "off",
597605
prev_arg=lambda wc: (
598606
f"--prev outputs/{wc.virus}.{wc.virus}.eval"
599607
if wc.virus != wc.target and not EVALUATE else ""
600-
)
608+
),
609+
skip_len=lambda wc: "--skip-length-filter" if EVALUATE else ""
601610
shell:
602611
"qprimer prepare-input "
603612
"--in {input.mapped} {params.prev_arg} --out {output} "
604613
"--ref {input.ref} --reftype {params.ref_type} "
605-
"--features {input.features} --params {PARAMS}"
614+
"--features {input.features} --params {PARAMS} {params.skip_len}"
606615

607616

608617
rule evaluate:
@@ -619,7 +628,7 @@ rule evaluate:
619628
resources:
620629
gpu=1
621630
params:
622-
ref_type=lambda wc: "on" if wc.virus == wc.target else "off"
631+
ref_type=lambda wc: "on" if (wc.virus == wc.target or EVALUATE) else "off"
623632
shell:
624633
"qprimer evaluate "
625634
"--in {input.inp} --out {output} "

0 commit comments

Comments
 (0)