countmut 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {countmut-0.2.2/countmut.egg-info → countmut-0.2.3}/PKG-INFO +1 -1
  2. {countmut-0.2.2 → countmut-0.2.3}/backend/countmut_core.c +25 -145
  3. {countmut-0.2.2 → countmut-0.2.3}/backend/countmut_core.h +4 -6
  4. {countmut-0.2.2 → countmut-0.2.3}/backend/countmut_core_main.c +6 -0
  5. {countmut-0.2.2 → countmut-0.2.3}/backend/countmut_expr.c +22 -1
  6. countmut-0.2.3/countmut/_core/countmut_core +0 -0
  7. {countmut-0.2.2 → countmut-0.2.3/countmut.egg-info}/PKG-INFO +1 -1
  8. {countmut-0.2.2 → countmut-0.2.3}/pyproject.toml +1 -1
  9. countmut-0.2.2/countmut/_core/countmut_core +0 -0
  10. {countmut-0.2.2 → countmut-0.2.3}/LICENSE +0 -0
  11. {countmut-0.2.2 → countmut-0.2.3}/MANIFEST.in +0 -0
  12. {countmut-0.2.2 → countmut-0.2.3}/README.md +0 -0
  13. {countmut-0.2.2 → countmut-0.2.3}/backend/Makefile +0 -0
  14. {countmut-0.2.2 → countmut-0.2.3}/backend/bedidx.c +0 -0
  15. {countmut-0.2.2 → countmut-0.2.3}/backend/bgzf.c +0 -0
  16. {countmut-0.2.2 → countmut-0.2.3}/backend/bgzf.h +0 -0
  17. {countmut-0.2.2 → countmut-0.2.3}/backend/countmut_expr.h +0 -0
  18. {countmut-0.2.2 → countmut-0.2.3}/backend/faidx.c +0 -0
  19. {countmut-0.2.2 → countmut-0.2.3}/backend/faidx.h +0 -0
  20. {countmut-0.2.2 → countmut-0.2.3}/backend/hts.c +0 -0
  21. {countmut-0.2.2 → countmut-0.2.3}/backend/hts.h +0 -0
  22. {countmut-0.2.2 → countmut-0.2.3}/backend/ketopt.h +0 -0
  23. {countmut-0.2.2 → countmut-0.2.3}/backend/khash.h +0 -0
  24. {countmut-0.2.2 → countmut-0.2.3}/backend/kseq.h +0 -0
  25. {countmut-0.2.2 → countmut-0.2.3}/backend/ksort.h +0 -0
  26. {countmut-0.2.2 → countmut-0.2.3}/backend/kstdint.h +0 -0
  27. {countmut-0.2.2 → countmut-0.2.3}/backend/kstring.h +0 -0
  28. {countmut-0.2.2 → countmut-0.2.3}/backend/razf.c +0 -0
  29. {countmut-0.2.2 → countmut-0.2.3}/backend/razf.h +0 -0
  30. {countmut-0.2.2 → countmut-0.2.3}/backend/sam.c +0 -0
  31. {countmut-0.2.2 → countmut-0.2.3}/backend/sam.h +0 -0
  32. {countmut-0.2.2 → countmut-0.2.3}/countmut/__init__.py +0 -0
  33. {countmut-0.2.2 → countmut-0.2.3}/countmut/backend.py +0 -0
  34. {countmut-0.2.2 → countmut-0.2.3}/countmut/bam_tags.py +0 -0
  35. {countmut-0.2.2 → countmut-0.2.3}/countmut/cli.py +0 -0
  36. {countmut-0.2.2 → countmut-0.2.3}/countmut/core.py +0 -0
  37. {countmut-0.2.2 → countmut-0.2.3}/countmut/model.py +0 -0
  38. {countmut-0.2.2 → countmut-0.2.3}/countmut/utils.py +0 -0
  39. {countmut-0.2.2 → countmut-0.2.3}/countmut.egg-info/SOURCES.txt +0 -0
  40. {countmut-0.2.2 → countmut-0.2.3}/countmut.egg-info/dependency_links.txt +0 -0
  41. {countmut-0.2.2 → countmut-0.2.3}/countmut.egg-info/entry_points.txt +0 -0
  42. {countmut-0.2.2 → countmut-0.2.3}/countmut.egg-info/requires.txt +0 -0
  43. {countmut-0.2.2 → countmut-0.2.3}/countmut.egg-info/top_level.txt +0 -0
  44. {countmut-0.2.2 → countmut-0.2.3}/setup.cfg +0 -0
  45. {countmut-0.2.2 → countmut-0.2.3}/tests/test_cli.py +0 -0
  46. {countmut-0.2.2 → countmut-0.2.3}/tests/test_core.py +0 -0
  47. {countmut-0.2.2 → countmut-0.2.3}/tests/test_correctness.py +0 -0
  48. {countmut-0.2.2 → countmut-0.2.3}/tests/test_router.py +0 -0
  49. {countmut-0.2.2 → countmut-0.2.3}/tests/test_unified.py +0 -0
  50. {countmut-0.2.2 → countmut-0.2.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: countmut
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Unified ultra-fast strand-aware mutation counter (C backend + samtools-style -e/-p filters)
5
5
  Author-email: Ye Chang <yech1990@gmail.com>
6
6
  License-Expression: MIT
@@ -331,12 +331,7 @@ static void write_header(FILE *fp, const cm_config *cfg) {
331
331
  }
332
332
  return;
333
333
  }
334
- if (cfg->out == CM_OUT_CONVERSION) {
335
- fputs("chrom\tpos\tstrand\tmotif\tu0\tu1\tu2\tm0\tm1\tm2", fp);
336
- if (cfg->save_rest) fputs("\to0\to1\to2", fp);
337
- fputs("\tmutation_rate", fp);
338
- fputc('\n', fp);
339
- } else if (cfg->out == CM_OUT_COMPOSITION) {
334
+ if (cfg->out == CM_OUT_COMPOSITION) {
340
335
  if (cfg->strandless) fputs("chrom\tpos\tref\tdepth\ta\tc\tg\tt\tn", fp);
341
336
  else fputs("chrom\tpos\tstrand\tref\tdepth\ta\tc\tg\tt\tn", fp);
342
337
  if (cfg->count_indels) fputs("\tins\tdel\tref_skip\tfail", fp);
@@ -372,14 +367,21 @@ static void emit_site(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, FILE *f
372
367
  w->motif_buf[mlen] = 0;
373
368
  motif = w->motif_buf;
374
369
  }
375
- /* ref_base/mut_base are legacy/unused (always 0) -> -1 (unset), so the
370
+ /* No ref/mut targets in the template path (unified counter): the
376
371
  * derived u/m/o fields stay off for template rows. */
377
- int refi = cfg->ref_base ? base_to_index((char)cfg->ref_base) : -1;
378
- int muti = cfg->mut_base ? base_to_index((char)cfg->mut_base) : -1;
372
+ int refi = -1, muti = -1;
379
373
  /* Per-strand rows: counts are this strand only, per category slot. */
380
374
  for (int s = 0; s < 2; ++s) {
381
375
  if (s == 0 && !emit_plus) continue;
382
376
  if (s == 1 && !emit_minus) continue;
377
+ /* target_base: emit only the strand whose reference base (on that
378
+ * strand) equals the target. For m6A (target A) this keeps the
379
+ * '+' strand A-site (genomic ref A) and the '-' strand A-site
380
+ * (genomic ref T), dropping the spurious complement-strand rows. */
381
+ if (cfg->target_base >= 0) {
382
+ char sref = (s == 0) ? ref_ch : rc_nt(ref_ch);
383
+ if (base_to_index((char)sref) != cfg->target_base) continue;
384
+ }
383
385
  int sdepth = site->ins[s] + site->del[s] + site->refskip[s] + site->fail[s];
384
386
  for (int c = 0; c < CM_CAT_MAX; ++c)
385
387
  for (int b = 0; b < 5; ++b) sdepth += site->cnt[s][c][b];
@@ -398,42 +400,7 @@ static void emit_site(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, FILE *f
398
400
  }
399
401
  return;
400
402
  }
401
- if (cfg->out == CM_OUT_CONVERSION) {
402
- int refi = base_to_index((char)cfg->ref_base), muti = base_to_index((char)cfg->mut_base);
403
- int mlen = cfg->pad * 2 + 1;
404
- int motif_ready = 0; /* build the reference-forward window once */
405
- for (int s = 0; s < 2; ++s) {
406
- if (s == 0 && !emit_plus) continue;
407
- if (s == 1 && !emit_minus) continue;
408
- int u0 = site->cnt[s][0][refi], m0 = site->cnt[s][0][muti];
409
- int u1 = site->cnt[s][1][refi], m1 = site->cnt[s][1][muti];
410
- int u2 = site->cnt[s][2][refi], m2 = site->cnt[s][2][muti];
411
- int ut = u0 + u1 + u2, mt = m0 + m1 + m2; /* total unconverted / converted */
412
- int o0 = site->cnt[s][0][0]+site->cnt[s][0][1]+site->cnt[s][0][2]+site->cnt[s][0][3]+site->cnt[s][0][4]-u0-m0;
413
- int o1 = site->cnt[s][1][0]+site->cnt[s][1][1]+site->cnt[s][1][2]+site->cnt[s][1][3]+site->cnt[s][1][4]-u1-m1;
414
- int o2 = site->cnt[s][2][0]+site->cnt[s][2][1]+site->cnt[s][2][2]+site->cnt[s][2][3]+site->cnt[s][2][4]-u2-m2;
415
- if (u1 + m1 + u2 + m2 <= 0) continue;
416
- if (!motif_ready) {
417
- /* Reference-forward window for BOTH strands (bases are counted
418
- * reference-forward), built at most once per site. */
419
- for (int k2 = (int)pos - cfg->pad; k2 < (int)pos + cfg->pad + 1; ++k2) {
420
- w->motif_buf[k2 - ((int)pos - cfg->pad)] =
421
- (k2 < 0 || k2 >= w->chr_len) ? 'N'
422
- : (char)toupper((unsigned char)w->chr_seq[k2]);
423
- }
424
- w->motif_buf[mlen] = 0;
425
- motif_ready = 1;
426
- }
427
- fprintf(fp, "%s\t%d\t%c\t%s\t%d\t%d\t%d\t%d\t%d\t%d",
428
- hdr->target_name[tid], (int)pos + 1, s ? '-' : '+', w->motif_buf,
429
- u0, u1, u2, m0, m1, m2);
430
- if (cfg->save_rest) fprintf(fp, "\t%d\t%d\t%d", o0, o1, o2);
431
- fprintf(fp, "\t%.4f",
432
- (ut + mt) ? (double)mt / (double)(ut + mt)
433
- : strtod("nan", (char **)NULL));
434
- fputc('\n', fp);
435
- }
436
- } else if (cfg->out == CM_OUT_COMPOSITION) {
403
+ if (cfg->out == CM_OUT_COMPOSITION) {
437
404
  if (!cfg->strandless) {
438
405
  for (int s = 0; s < 2; ++s) {
439
406
  if (s == 0 && !emit_plus) continue;
@@ -521,8 +488,7 @@ static int expr_pile_apply(cm_expr *x, const cm_config *cfg, worker_t *w,
521
488
  w->motif_buf[mlen] = 0;
522
489
  motif = w->motif_buf;
523
490
  }
524
- int refi = cfg->ref_base ? base_to_index((char)cfg->ref_base) : -1;
525
- int muti = cfg->mut_base ? base_to_index((char)cfg->mut_base) : -1;
491
+ int refi = -1, muti = -1; /* no ref/mut targets in the unified counter */
526
492
  return cm_expr_pile(x, chrom, pos, ref_ch, motif, cnt, ins, del, rs, fl,
527
493
  refi, muti);
528
494
  }
@@ -565,9 +531,6 @@ static void count_interval(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, FI
565
531
  if (n == 0) continue;
566
532
  const char ref_ch = w->chr_seq[pos]; /* pre-uppercased */
567
533
 
568
- if (cfg->out == CM_OUT_CONVERSION && cfg->ref_base && ref_ch != cfg->ref_base)
569
- continue;
570
-
571
534
  if (n > w->sel_cap) {
572
535
  w->sel = (int *)realloc(w->sel, n * sizeof(int));
573
536
  w->mapq_a = (int *)realloc(w->mapq_a, n * sizeof(int));
@@ -634,16 +597,11 @@ static void count_interval(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, FI
634
597
  * CIGAR order (left->right); complement the base into the
635
598
  * reference frame (parity with countmut 0.0.x + pysam pairs). */
636
599
  if (s == 1 && base_i < 4) base_i = 3 - base_i;
637
- int qual = (int)bam_get_qual(b)[p->qpos];
638
- if (cfg->out == CM_OUT_CONVERSION) {
639
- site.cnt[s][(qual >= cfg->min_baseq) ? 2 : 0][base_i]++;
640
- } else {
641
- /* router-assigned category slot (0..CM_CAT_MAX-1); g_a set in
642
- * the selection loop for every kept candidate. */
643
- int cat = w->g_a[i];
644
- if (cat < 0) cat = 0; /* defensive */
645
- site.cnt[s][cat][base_i]++;
646
- }
600
+ /* router-assigned category slot (0..CM_CAT_MAX-1); g_a set in
601
+ * the selection loop for every kept candidate. */
602
+ int cat = w->g_a[i];
603
+ if (cat < 0) cat = 0; /* defensive */
604
+ site.cnt[s][cat][base_i]++;
647
605
  }
648
606
 
649
607
  /* ---------- emit ---------- */
@@ -753,40 +711,6 @@ static int cmp_site_ord(const void *a, const void *b) {
753
711
  * jumps straight to the (sorted) target positions. Both funnel into
754
712
  * rw_add_base(), so dedup/quality decisions are byte-identical. */
755
713
 
756
- static uint32_t cigar_ref_len(const bam1_t *b) {
757
- const uint32_t *cig = bam_get_cigar(b);
758
- uint32_t r = 0;
759
- for (int i = 0; i < b->core.n_cigar; ++i) {
760
- int op = (int)bam_cigar_op_p(&cig[i]);
761
- if (op == 0 || op == 2 || op == 3 || op == 7 || op == 8)
762
- r += (uint32_t)bam_cigar_oplen_p(&cig[i]);
763
- }
764
- return r;
765
- }
766
- static int cigar_has_indels(const bam1_t *b) {
767
- const uint32_t *cig = bam_get_cigar(b);
768
- for (int i = 0; i < b->core.n_cigar; ++i) {
769
- int op = (int)bam_cigar_op_p(&cig[i]);
770
- if (op == 1 || op == 2 || op == 3) return 1;
771
- }
772
- return 0;
773
- }
774
- static uint32_t cigar_leading_softclips(const bam1_t *b) {
775
- const uint32_t *cig = bam_get_cigar(b);
776
- uint32_t sc = 0;
777
- for (int i = 0; i < b->core.n_cigar; ++i) {
778
- int op = (int)bam_cigar_op_p(&cig[i]);
779
- if (op == 4) sc += (uint32_t)bam_cigar_oplen_p(&cig[i]);
780
- else break; /* soft-clips only at the 5' end before the first M */
781
- }
782
- return sc;
783
- }
784
- static int tgt_lower_bound(const int *a, int n, int v) {
785
- int lo = 0, hi = n;
786
- while (lo < hi) { int mid = (lo + hi) >> 1; if (a[mid] < v) lo = mid + 1; else hi = mid; }
787
- return lo;
788
- }
789
-
790
714
  /* Add one matched base to the (pos,qname) dedup table. Applies the mutation
791
715
  * target gate (skipped when `already_target`), trim (is_internal), the -e
792
716
  * filter and the (mapq,read1,qual) preference. When `direct` is set the base
@@ -801,9 +725,6 @@ static rw_w *rw_add_base(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, int
801
725
  khash_t(posq) *h, rw_w *wins, int *wins_cap, int *wins_n) {
802
726
  uint32_t qlen = b->core.l_qseq;
803
727
  if (qpos >= qlen) return wins;
804
- if (!already_target && cfg->out == CM_OUT_CONVERSION && cfg->ref_base) {
805
- if (ref_pos >= w->chr_len || w->chr_seq[ref_pos] != cfg->ref_base) return wins;
806
- }
807
728
  if (s == 0) {
808
729
  if ((int)qpos < cfg->trim_fragment_start || (int)qlen - (int)qpos <= cfg->trim_fragment_end) return wins;
809
730
  } else {
@@ -831,8 +752,7 @@ static rw_w *rw_add_base(worker_t *w, const cm_config *cfg, bam_hdr_t *hdr, int
831
752
  if (s == 1 && base_i < 4) base_i = 3 - base_i;
832
753
  int qual = (int)bam_get_qual(b)[qpos];
833
754
  if (direct) {
834
- int cat = (cfg->out == CM_OUT_CONVERSION)
835
- ? ((qual >= cfg->min_baseq) ? 2 : 0) : rslot;
755
+ int cat = rslot;
836
756
  sm_get(sm, ref_pos)->cnt[s][cat][base_i]++;
837
757
  return wins;
838
758
  }
@@ -868,15 +788,6 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
868
788
 
869
789
  if (w->last_tid != tid) load_chr_seq(w, hdr, tid);
870
790
 
871
- /* sorted target positions (mutation mode) for the indel-free fast path */
872
- int *tgt = NULL; int tgt_n = 0;
873
- if (cfg->out == CM_OUT_CONVERSION && cfg->ref_base && end > beg) {
874
- tgt = (int *)malloc((size_t)(end - beg) * sizeof(int));
875
- for (int p = beg; p < end && p < w->chr_len; ++p)
876
- if (w->chr_seq[p] == (char)cfg->ref_base)
877
- tgt[tgt_n++] = p;
878
- }
879
-
880
791
  khash_t(posq) *h = kh_init(posq);
881
792
  khash_t(qn2id) *qnids = kh_init(qn2id);
882
793
  int qname_n = 0;
@@ -899,14 +810,8 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
899
810
  if (rcur < end && rcur + len > beg) {
900
811
  int64_t lo = rcur > beg ? rcur : beg;
901
812
  int64_t hi = rcur + len < end ? rcur + len : end;
902
- if (cfg->out == CM_OUT_CONVERSION && cfg->ref_base) {
903
- for (int64_t p = lo; p < hi; ++p)
904
- if ((int)p < w->chr_len && w->chr_seq[p] == cfg->ref_base)
905
- sm_get(&sm, p)->fail[s]++;
906
- } else {
907
- for (int64_t p = lo; p < hi; ++p)
908
- sm_get(&sm, p)->fail[s]++;
909
- }
813
+ for (int64_t p = lo; p < hi; ++p)
814
+ sm_get(&sm, p)->fail[s]++;
910
815
  }
911
816
  rcur += len;
912
817
  }
@@ -969,22 +874,7 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
969
874
  /* direct(ref_pos,qpos) = counts straight into the site (no dedup hash) */
970
875
  #define RW_DIRECT(_qpos) ((ovl == 0) || (ovl == 1 && ((int)(_qpos) < olo || (int)(_qpos) >= ohi)))
971
876
 
972
- if (tgt && !cigar_has_indels(b)) {
973
- /* fast path: indel-free read -> jump straight to target positions */
974
- int64_t r_start = b->core.pos;
975
- uint32_t sc = cigar_leading_softclips(b);
976
- int64_t r_end = r_start + (int64_t)cigar_ref_len(b);
977
- int64_t lo = r_start > beg ? r_start : beg;
978
- int64_t hi = r_end < end ? r_end : end;
979
- for (int ti = tgt_lower_bound(tgt, tgt_n, (int)lo); ti < tgt_n; ++ti) {
980
- int64_t ref_pos = tgt[ti];
981
- if (ref_pos >= hi) break;
982
- uint32_t qpos = (uint32_t)((ref_pos - r_start) + sc);
983
- wins = rw_add_base(w, cfg, hdr, tid, b, s, ref_pos, qpos,
984
- qid, 1, RW_DIRECT(qpos), &sm,
985
- h, wins, &wins_cap, &wins_n);
986
- }
987
- } else {
877
+ {
988
878
  uint32_t qcur = 0;
989
879
  int64_t rcur = b->core.pos;
990
880
  for (int i = 0; i < b->core.n_cigar; ++i) {
@@ -1008,15 +898,8 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
1008
898
  if (rcur < end && rcur + len > beg) {
1009
899
  int64_t lo2 = rcur > beg ? rcur : beg;
1010
900
  int64_t hi2 = rcur + len < end ? rcur + len : end;
1011
- if (cfg->out == CM_OUT_CONVERSION && cfg->ref_base) {
1012
- for (int64_t p = lo2; p < hi2; ++p)
1013
- if ((int)p < w->chr_len && w->chr_seq[p] == cfg->ref_base) {
1014
- if (is_del) sm_get(&sm, p)->del[s]++; else sm_get(&sm, p)->refskip[s]++;
1015
- }
1016
- } else {
1017
- for (int64_t p = lo2; p < hi2; ++p)
1018
- if (is_del) sm_get(&sm, p)->del[s]++; else sm_get(&sm, p)->refskip[s]++;
1019
- }
901
+ for (int64_t p = lo2; p < hi2; ++p)
902
+ if (is_del) sm_get(&sm, p)->del[s]++; else sm_get(&sm, p)->refskip[s]++;
1020
903
  }
1021
904
  rcur += len;
1022
905
  break;
@@ -1032,8 +915,7 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
1032
915
  if (!kh_exist(h, k)) continue;
1033
916
  int64_t pos = kh_key(h, k).pos;
1034
917
  const rw_w *win = &wins[kh_val(h, k)];
1035
- int cat = (cfg->out == CM_OUT_CONVERSION)
1036
- ? ((win->qual >= cfg->min_baseq) ? 2 : 0) : win->slot;
918
+ int cat = win->slot;
1037
919
  site_t *st = sm_get(&sm, pos);
1038
920
  st->cnt[win->strand][cat][win->base]++;
1039
921
  }
@@ -1048,7 +930,6 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
1048
930
  int64_t pos = ord[i].pos;
1049
931
  if (pos < 0 || pos >= w->chr_len) continue;
1050
932
  char ref_ch = w->chr_seq[pos]; /* pre-uppercased */
1051
- if (cfg->out == CM_OUT_CONVERSION && cfg->ref_base && ref_ch != cfg->ref_base) continue;
1052
933
  if (w->inc_bed && !bed_overlap(w->inc_bed, hdr->target_name[tid], (int)pos, (int)pos + 1)) continue;
1053
934
  if (w->exc_bed && bed_overlap(w->exc_bed, hdr->target_name[tid], (int)pos, (int)pos + 1)) continue;
1054
935
  /* -p site filter */
@@ -1066,7 +947,6 @@ static void count_interval_readwalk(worker_t *w, const cm_config *cfg, bam_hdr_t
1066
947
  kh_destroy(posq, h);
1067
948
  free(wins);
1068
949
  free(ord);
1069
- free(tgt);
1070
950
  sm_free(&sm);
1071
951
  bam_destroy1(b);
1072
952
  if (aux.itr) bam_itr_destroy(aux.itr);
@@ -42,7 +42,6 @@ typedef struct {
42
42
  #define CM_STRAND_FORWARD 1
43
43
  #define CM_STRAND_REVERSE 2
44
44
 
45
- #define CM_OUT_CONVERSION 0 /* legacy/unused: u/m conversion view (dead) */
46
45
  #define CM_OUT_COMPOSITION 1 /* output format: per-base composition (default) */
47
46
  #define CM_OUT_ALLELE 2 /* output format: ref/alt / VCF */
48
47
 
@@ -50,12 +49,11 @@ typedef struct {
50
49
  int32_t out; /* CM_OUT_* (output format) */
51
50
  int32_t engine; /* CM_ENGINE_* */
52
51
  int32_t vcf; /* allele output: emit VCF */
53
- int ref_base; /* legacy/unused: always 0 (no --ref-base flag) */
54
- int mut_base;
55
- int ref_base2; /* legacy/unused: always 0 */
56
- int mut_base2;
52
+ int target_base; /* strand-aware reference base to emit (-1 = no filter).
53
+ When set, only the strand whose reference base
54
+ equals this base is emitted (m6A A-site on either
55
+ strand). */
57
56
  int pad; /* {motif} reference window: 2*pad+1 bases (--motif-pad) */
58
- int save_rest; /* legacy/unused */
59
57
  const char *output_expr; /* -o output-row template (overrides the built-in format) */
60
58
  const char *fmt_header; /* header line for a custom output template ("" = none) */
61
59
  int min_mapq;
@@ -168,6 +168,7 @@ int main(int argc, char **argv) {
168
168
  {"pile-expr", required_argument, 0, 2003},
169
169
  {"output-expr", required_argument, 0, 2004},
170
170
  {"fmt-header", required_argument, 0, 2005},
171
+ {"target-base", required_argument, 0, 2006},
171
172
  {"verbose", no_argument, 0, 'V'},
172
173
  {"help", no_argument, 0, 'h'},
173
174
  {0, 0, 0, 0},
@@ -180,6 +181,11 @@ int main(int argc, char **argv) {
180
181
  case 2003: cfg.pile_expr = optarg; break;
181
182
  case 2004: cfg.output_expr = optarg; break;
182
183
  case 2005: cfg.fmt_header = optarg; break;
184
+ case 2006: {
185
+ char tb = (char)toupper((unsigned char)optarg[0]);
186
+ cfg.target_base = (tb == 'A') ? 0 : (tb == 'C') ? 1 : (tb == 'G') ? 2 : (tb == 'T') ? 3 : -1;
187
+ break;
188
+ }
183
189
  case 'V': cfg.verbose = 1; break;
184
190
  case 'f': fa = optarg; break;
185
191
  case 'o': out = optarg; break;
@@ -473,7 +473,7 @@ static int compile_chunk(lua_State *L, const char *expr, const char *what,
473
473
  if (need_qpos) *need_qpos = has_token(src, "qpos") || has_token(src, "QPOS");
474
474
  if (need_bq) *need_bq = has_token(src, "bq") || has_token(src, "BQ") || has_token(src, "baseq");
475
475
  if (need_base) *need_base = has_token(src, "base");
476
- if (need_ref) *need_ref = has_token(src, "ref");
476
+ if (need_ref) *need_ref = has_token(src, "ref") || has_token(src, "base") || has_token(src, "BASE");
477
477
  if (need_dist) *need_dist = has_token(src, "dist5") || has_token(src, "dist3")
478
478
  || has_token(src, "DIST5") || has_token(src, "DIST3")
479
479
  || has_token(src, "distance_from_5prime")
@@ -897,11 +897,32 @@ static void pile_set_all(lua_State *L, cm_expr *x, const char *chrom, int64_t po
897
897
  char refstr[2] = { ref_ch ? ref_ch : 'N', 0 };
898
898
  const char *mot = motif ? motif : "";
899
899
 
900
+ /* Strand-aware m6A reference base. For the site-level (-p) namespace the
901
+ * site is a potential A-site if the genomic reference is A (the '+'
902
+ * strand A-site) or T (the '-' strand A-site). For a per-strand row
903
+ * (strand_s == 0/1) `base` is the reference base ON THAT STRAND, so a
904
+ * filter `base == 'A'` keeps A-sites on both strands automatically. */
905
+ char basestr[2] = { 'N', 0 };
906
+ if (strand_s == 0) { /* '+' strand: reference base = genomic */
907
+ basestr[0] = ref_ch ? ref_ch : 'N';
908
+ } else if (strand_s == 1) { /* '-' strand: reference base = complement */
909
+ static const char comp[256] = { ['A']='T', ['T']='A', ['C']='G', ['G']='C' };
910
+ basestr[0] = ref_ch ? comp[(unsigned char)ref_ch] : 'N';
911
+ if (basestr[0] == 0) basestr[0] = 'N';
912
+ } else { /* site-level: A-site on either strand */
913
+ if (ref_ch == 'A' || ref_ch == 'T')
914
+ basestr[0] = 'A';
915
+ else if (ref_ch)
916
+ basestr[0] = ref_ch;
917
+ }
918
+
900
919
  p_int(x, "pos", (lua_Integer)pos);
901
920
  p_int(x, "POS", (lua_Integer)pos);
902
921
  p_str(x, "ref", refstr);
903
922
  p_str(x, "ref_base", refstr);
904
923
  p_str(x, "REF", refstr);
924
+ p_str(x, "base", basestr);
925
+ p_str(x, "BASE", basestr);
905
926
  p_int(x, "depth", depth);
906
927
  p_int(x, "DEPTH", depth);
907
928
  p_str(x, "chrom", chrom ? chrom : "");
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: countmut
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Unified ultra-fast strand-aware mutation counter (C backend + samtools-style -e/-p filters)
5
5
  Author-email: Ye Chang <yech1990@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "countmut"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Unified ultra-fast strand-aware mutation counter (C backend + samtools-style -e/-p filters)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes