strkit 0.11.8__tar.gz → 0.12.0a1__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 (62) hide show
  1. {strkit-0.11.8/strkit.egg-info → strkit-0.12.0a1}/PKG-INFO +1 -1
  2. strkit-0.12.0a1/strkit/VERSION +1 -0
  3. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/constants.py +0 -13
  4. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/entry.py +4 -7
  5. strkit-0.12.0a1/strkit/mi/repeathmm.py +92 -0
  6. strkit-0.12.0a1/strkit/mi/straglr.py +98 -0
  7. strkit-0.12.0a1/strkit/mi/tandem_genotypes.py +88 -0
  8. {strkit-0.11.8 → strkit-0.12.0a1/strkit.egg-info}/PKG-INFO +1 -1
  9. strkit-0.11.8/strkit/VERSION +0 -1
  10. strkit-0.11.8/strkit/mi/repeathmm.py +0 -192
  11. strkit-0.11.8/strkit/mi/straglr.py +0 -201
  12. strkit-0.11.8/strkit/mi/tandem_genotypes.py +0 -172
  13. {strkit-0.11.8 → strkit-0.12.0a1}/LICENSE +0 -0
  14. {strkit-0.11.8 → strkit-0.12.0a1}/MANIFEST.in +0 -0
  15. {strkit-0.11.8 → strkit-0.12.0a1}/README.md +0 -0
  16. {strkit-0.11.8 → strkit-0.12.0a1}/pyproject.toml +0 -0
  17. {strkit-0.11.8 → strkit-0.12.0a1}/setup.cfg +0 -0
  18. {strkit-0.11.8 → strkit-0.12.0a1}/setup.py +0 -0
  19. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/__init__.py +0 -0
  20. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/__init__.py +0 -0
  21. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/align_matrix.py +0 -0
  22. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/allele.py +0 -0
  23. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/call_locus.py +0 -0
  24. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/call_sample.py +0 -0
  25. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/cigar.py +0 -0
  26. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/non_daemonic_pool.py +0 -0
  27. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/realign.py +0 -0
  28. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/repeats.py +0 -0
  29. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/snvs.py +0 -0
  30. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/types.py +0 -0
  31. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/call/utils.py +0 -0
  32. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/catalog/__init__.py +0 -0
  33. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/catalog/combine.py +0 -0
  34. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/__init__.py +0 -0
  35. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/_bed_4.py +0 -0
  36. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/converter.py +0 -0
  37. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/expansionhunter.py +0 -0
  38. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/gangstr.py +0 -0
  39. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/hipstr.py +0 -0
  40. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/straglr.py +0 -0
  41. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/convert/tandem_genotypes.py +0 -0
  42. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/exceptions.py +0 -0
  43. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/json.py +0 -0
  44. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/logger.py +0 -0
  45. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/__init__.py +0 -0
  46. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/base.py +0 -0
  47. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/expansionhunter.py +0 -0
  48. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/gangstr.py +0 -0
  49. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/result.py +0 -0
  50. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/strkit.py +0 -0
  51. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/mi/vcf_utils.py +0 -0
  52. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/utils.py +0 -0
  53. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/viz/__init__.py +0 -0
  54. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/viz/server.py +0 -0
  55. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/viz/static/logo.png +0 -0
  56. {strkit-0.11.8 → strkit-0.12.0a1}/strkit/viz/templates/browser.html +0 -0
  57. {strkit-0.11.8 → strkit-0.12.0a1}/strkit.egg-info/SOURCES.txt +0 -0
  58. {strkit-0.11.8 → strkit-0.12.0a1}/strkit.egg-info/dependency_links.txt +0 -0
  59. {strkit-0.11.8 → strkit-0.12.0a1}/strkit.egg-info/entry_points.txt +0 -0
  60. {strkit-0.11.8 → strkit-0.12.0a1}/strkit.egg-info/requires.txt +0 -0
  61. {strkit-0.11.8 → strkit-0.12.0a1}/strkit.egg-info/top_level.txt +0 -0
  62. {strkit-0.11.8 → strkit-0.12.0a1}/tests/test_caller_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strkit
3
- Version: 0.11.8
3
+ Version: 0.12.0a1
4
4
  Summary: A toolkit for analyzing variation in short(ish) tandem repeats.
5
5
  Home-page: https://github.com/davidlougheed/strkit
6
6
  Author: David Lougheed
@@ -0,0 +1 @@
1
+ 0.12.0a1
@@ -3,11 +3,8 @@ __all__ = [
3
3
  "CALLER_HIPSTR",
4
4
  "CALLER_GANGSTR",
5
5
  "CALLER_REPEATHMM",
6
- "CALLER_REPEATHMM_RECALL",
7
6
  "CALLER_STRAGLR",
8
- "CALLER_STRAGLR_RECALL",
9
7
  "CALLER_TANDEM_GENOTYPES",
10
- "CALLER_TANDEM_GENOTYPES_RECALL",
11
8
  "CALL_SUPPORTED_CALLERS",
12
9
 
13
10
  "M_CHROMOSOME_NAMES",
@@ -24,13 +21,10 @@ CALLER_EXPANSIONHUNTER = "expansionhunter"
24
21
  CALLER_HIPSTR = "hipstr"
25
22
  CALLER_GANGSTR = "gangstr"
26
23
  CALLER_REPEATHMM = "repeathmm"
27
- CALLER_REPEATHMM_RECALL = "repeathmm-recall"
28
24
  CALLER_STRAGLR = "straglr"
29
- CALLER_STRAGLR_RECALL = "straglr-recall"
30
25
  CALLER_STRKIT = "strkit"
31
26
  CALLER_STRKIT_JSON = "strkit-json"
32
27
  CALLER_TANDEM_GENOTYPES = "tandem-genotypes"
33
- CALLER_TANDEM_GENOTYPES_RECALL = "tandem-genotypes-recall"
34
28
 
35
29
  CALL_SUPPORTED_CALLERS = (
36
30
  CALLER_REPEATHMM,
@@ -57,16 +51,9 @@ CHROMOSOMES = (
57
51
  MI_CALLERS = (
58
52
  CALLER_EXPANSIONHUNTER,
59
53
  CALLER_GANGSTR,
60
- "generic-vcf",
61
- "hipstr",
62
- "lobstr",
63
- "pacmonstr",
64
54
  CALLER_REPEATHMM,
65
- CALLER_REPEATHMM_RECALL,
66
55
  CALLER_STRAGLR,
67
- CALLER_STRAGLR_RECALL,
68
56
  CALLER_STRKIT,
69
57
  CALLER_STRKIT_JSON,
70
58
  CALLER_TANDEM_GENOTYPES,
71
- CALLER_TANDEM_GENOTYPES_RECALL,
72
59
  )
@@ -364,28 +364,25 @@ def _exec_mi(p_args) -> None:
364
364
  from strkit.mi.base import BaseCalculator
365
365
  from strkit.mi.expansionhunter import ExpansionHunterCalculator
366
366
  from strkit.mi.gangstr import GangSTRCalculator
367
- from strkit.mi.repeathmm import RepeatHMMCalculator, RepeatHMMReCallCalculator
368
- from strkit.mi.straglr import StraglrCalculator, StraglrReCallCalculator
367
+ from strkit.mi.repeathmm import RepeatHMMCalculator
368
+ from strkit.mi.straglr import StraglrCalculator
369
369
  from strkit.mi.strkit import StrKitCalculator, StrKitJSONCalculator
370
- from strkit.mi.tandem_genotypes import TandemGenotypesCalculator, TandemGenotypesReCallCalculator
370
+ from strkit.mi.tandem_genotypes import TandemGenotypesCalculator
371
371
 
372
372
  calc_classes: dict[str, Type[BaseCalculator]] = {
373
373
  c.CALLER_EXPANSIONHUNTER: ExpansionHunterCalculator,
374
374
  c.CALLER_GANGSTR: GangSTRCalculator,
375
375
  c.CALLER_REPEATHMM: RepeatHMMCalculator,
376
- c.CALLER_REPEATHMM_RECALL: RepeatHMMReCallCalculator,
377
376
  c.CALLER_STRAGLR: StraglrCalculator,
378
- c.CALLER_STRAGLR_RECALL: StraglrReCallCalculator,
379
377
  c.CALLER_STRKIT: StrKitCalculator,
380
378
  c.CALLER_STRKIT_JSON: StrKitJSONCalculator,
381
379
  c.CALLER_TANDEM_GENOTYPES: TandemGenotypesCalculator,
382
- c.CALLER_TANDEM_GENOTYPES_RECALL: TandemGenotypesReCallCalculator,
383
380
  }
384
381
 
385
382
  caller = p_args.caller.lower()
386
383
 
387
384
  trf_bed_file = getattr(p_args, "trf_bed") or None
388
- if trf_bed_file is None and caller in (c.CALLER_STRAGLR, c.CALLER_STRAGLR_RECALL):
385
+ if trf_bed_file is None and caller == c.CALLER_STRAGLR:
389
386
  raise ParamError("Using `strkit mi` with Straglr requires that the --trf-bed flag is used.")
390
387
 
391
388
  exclude_bed_file = p_args.exclude_loci_bed or None
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from .base import BaseCalculator
4
+ from .result import MIContigResult, MILocusData
5
+ from ..utils import int_tuple
6
+
7
+ __all__ = [
8
+ "RepeatHMMCalculator",
9
+ ]
10
+
11
+
12
+ class RepeatHMMCalculator(BaseCalculator):
13
+ @staticmethod
14
+ def get_contigs_from_fh(fh) -> set:
15
+ return {ls[0] for ls in (line.split(":") for line in fh)}
16
+
17
+ @staticmethod
18
+ def make_calls_dict(ph, contig):
19
+ return {
20
+ tuple(k.split(":")): int_tuple(v.split("/"))
21
+ for k, v in (pv.split() for pv in ph)
22
+ if k.split(":")[0] == contig
23
+ }
24
+
25
+ def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
26
+ with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
27
+ open(self._child_call_file, "r") as cvf:
28
+
29
+ mc = self.get_contigs_from_fh(mvf)
30
+ fc = self.get_contigs_from_fh(fvf)
31
+ cc = self.get_contigs_from_fh(cvf)
32
+
33
+ return mc, fc, cc
34
+
35
+ def calculate_contig(self, contig: str) -> MIContigResult:
36
+ cr = MIContigResult()
37
+
38
+ with open(self._mother_call_file) as mh:
39
+ mother_calls = self.make_calls_dict(mh, contig)
40
+
41
+ with open(self._father_call_file) as fh:
42
+ father_calls = self.make_calls_dict(fh, contig)
43
+
44
+ with open(self._child_call_file) as ch:
45
+ for cv in ch:
46
+ locus_data, call = cv.strip().split(" ")
47
+ lookup = tuple(locus_data.split(":"))
48
+
49
+ if lookup[0] != contig:
50
+ continue
51
+
52
+ bed_k = lookup[:3]
53
+
54
+ # Check to make sure call is present in TRF BED file, if it is specified
55
+ if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
56
+ continue
57
+
58
+ if self.should_exclude_locus(bed_k):
59
+ continue
60
+
61
+ locus_start: int = int(lookup[1])
62
+ locus_end: int = int(lookup[2])
63
+
64
+ cr.seen_locus(contig, locus_start, locus_end)
65
+
66
+ # Check to make sure call is present in all trio individuals
67
+ if lookup not in mother_calls or lookup not in father_calls:
68
+ continue
69
+
70
+ c_gt = int_tuple(call.split("/"))
71
+ m_gt = mother_calls[lookup]
72
+ f_gt = father_calls[lookup]
73
+
74
+ # Failed calls from RepeatHMM seem to be represented as 0/0, so skip this
75
+ # TODO… Need to decide if we actually want to include these?
76
+ # or at least somehow record them
77
+ if (0, 0) in (c_gt, m_gt, f_gt):
78
+ continue
79
+
80
+ # TODO: Include ref copies... should be in file somewhere?
81
+ cr.append(MILocusData(
82
+ lookup[0],
83
+ locus_start,
84
+ locus_end,
85
+ lookup[3],
86
+
87
+ child_gt=int_tuple(call.split("/")),
88
+ mother_gt=mother_calls[lookup],
89
+ father_gt=father_calls[lookup],
90
+ ))
91
+
92
+ return cr
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from .base import BaseCalculator
6
+ from .result import MILocusData, MIContigResult
7
+
8
+ __all__ = [
9
+ "StraglrCalculator",
10
+ ]
11
+
12
+
13
+ class StraglrCalculator(BaseCalculator):
14
+ @staticmethod
15
+ def get_contigs_from_fh(fh) -> set:
16
+ return {ls[0] for ls in (line.split("\t") for line in fh if not line.startswith("#"))}
17
+
18
+ def make_calls_dict(self, ph, contig, cr: Optional[MIContigResult] = None):
19
+ # For reference, dicts are ordered in Python 3.7+ (guaranteed)
20
+
21
+ calls = {}
22
+
23
+ for pv in ph:
24
+ if pv.startswith("#"):
25
+ continue
26
+
27
+ line = pv.strip().split("\t")
28
+
29
+ if line[0] != contig:
30
+ continue
31
+
32
+ locus = tuple(line[:3])
33
+
34
+ orig_motif = self._loci_dict.get(locus)
35
+ orig_motif = orig_motif[-1] if orig_motif else None
36
+
37
+ if not orig_motif:
38
+ continue
39
+
40
+ if self.should_exclude_locus(locus):
41
+ continue
42
+
43
+ if cr:
44
+ cr.seen_locus(locus[0], int(locus[1]), int(locus[2]))
45
+
46
+ # Transform the genotypes into something that is consistent across individuals,
47
+ # using the file with the list of loci.
48
+ gt_fact = len(line[3]) / len(orig_motif)
49
+
50
+ gt = tuple(float(g.split("(")[0]) * gt_fact for g in line[4].split(";"))
51
+ if len(gt) == 1: # If it's homozygous, expand it out to length 2
52
+ gt = gt + gt
53
+
54
+ calls[locus + (orig_motif,)] = gt
55
+
56
+ return calls
57
+
58
+ def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
59
+ with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
60
+ open(self._child_call_file, "r") as cvf:
61
+
62
+ mc = self.get_contigs_from_fh(mvf)
63
+ fc = self.get_contigs_from_fh(fvf)
64
+ cc = self.get_contigs_from_fh(cvf)
65
+
66
+ return mc, fc, cc
67
+
68
+ def calculate_contig(self, contig: str):
69
+ cr = MIContigResult()
70
+
71
+ with open(self._mother_call_file, "r") as mh:
72
+ mother_calls = self.make_calls_dict(mh, contig)
73
+
74
+ with open(self._father_call_file, "r") as fh:
75
+ father_calls = self.make_calls_dict(fh, contig)
76
+
77
+ with open(self._child_call_file, "r") as ch:
78
+ child_calls = self.make_calls_dict(ch, contig, cr)
79
+
80
+ for locus_data, c_gt in child_calls.items():
81
+ # Check to make sure call is present in all trio individuals
82
+ if locus_data not in mother_calls or locus_data not in father_calls:
83
+ continue
84
+
85
+ cr.append(MILocusData(
86
+ contig=locus_data[0],
87
+ start=int(locus_data[1]),
88
+ end=int(locus_data[2]),
89
+ motif=locus_data[3],
90
+
91
+ child_gt=c_gt,
92
+ mother_gt=mother_calls[locus_data],
93
+ father_gt=father_calls[locus_data],
94
+
95
+ decimal=True,
96
+ ))
97
+
98
+ return cr
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from .base import BaseCalculator
4
+ from .result import MIContigResult, MILocusData
5
+ from ..utils import int_tuple
6
+
7
+ __all__ = [
8
+ "TandemGenotypesCalculator",
9
+ ]
10
+
11
+
12
+ class TandemGenotypesCalculator(BaseCalculator):
13
+ @staticmethod
14
+ def get_contigs_from_fh(fh) -> set[str]:
15
+ return {ls[0] for ls in (line.split("\t") for line in fh if not line.startswith("#"))}
16
+
17
+ @staticmethod
18
+ def make_calls_dict(ph, contig):
19
+ return {
20
+ tuple(line[:4]): int_tuple(line[6:8])
21
+ for line in (pv.strip().split("\t") for pv in ph if not pv.startswith("#"))
22
+ if line[0] == contig and "." not in line[6:8]
23
+ }
24
+
25
+ def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
26
+ with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
27
+ open(self._child_call_file, "r") as cvf:
28
+
29
+ mc = self.get_contigs_from_fh(mvf)
30
+ fc = self.get_contigs_from_fh(fvf)
31
+ cc = self.get_contigs_from_fh(cvf)
32
+
33
+ return mc, fc, cc
34
+
35
+ def calculate_contig(self, contig: str) -> MIContigResult:
36
+ cr = MIContigResult()
37
+
38
+ with open(self._mother_call_file) as mh:
39
+ mother_calls = self.make_calls_dict(mh, contig)
40
+
41
+ with open(self._father_call_file) as fh:
42
+ father_calls = self.make_calls_dict(fh, contig)
43
+
44
+ with open(self._child_call_file) as ch:
45
+ for cv in ch:
46
+ locus_data = cv.strip().split("\t")
47
+ lookup = tuple(locus_data[:4])
48
+
49
+ if locus_data[0] != contig:
50
+ continue
51
+
52
+ bed_k = lookup[:3]
53
+
54
+ # Check to make sure call is present in TRF BED file, if it is specified
55
+ if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
56
+ continue
57
+
58
+ # noinspection PyTypeChecker
59
+ if self.should_exclude_locus(bed_k):
60
+ continue
61
+
62
+ locus_start = int(lookup[1])
63
+ locus_end = int(lookup[2])
64
+
65
+ cr.seen_locus(contig, locus_start, locus_end)
66
+
67
+ # Check to make sure call is present in all trio individuals
68
+ if lookup not in mother_calls or lookup not in father_calls:
69
+ continue
70
+
71
+ child_calls = locus_data[6:8]
72
+
73
+ if "." in child_calls:
74
+ # Failed call
75
+ continue
76
+
77
+ cr.append(MILocusData(
78
+ contig=contig,
79
+ start=locus_start,
80
+ end=locus_end,
81
+ motif=lookup[3],
82
+
83
+ child_gt=int_tuple(child_calls),
84
+ mother_gt=mother_calls[lookup],
85
+ father_gt=father_calls[lookup],
86
+ ))
87
+
88
+ return cr
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strkit
3
- Version: 0.11.8
3
+ Version: 0.12.0a1
4
4
  Summary: A toolkit for analyzing variation in short(ish) tandem repeats.
5
5
  Home-page: https://github.com/davidlougheed/strkit
6
6
  Author: David Lougheed
@@ -1 +0,0 @@
1
- 0.11.8
@@ -1,192 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from .base import BaseCalculator
4
- from .result import MIContigResult, MILocusData
5
- from ..utils import int_tuple, parse_cis
6
-
7
- __all__ = [
8
- "RepeatHMMCalculator",
9
- "RepeatHMMReCallCalculator",
10
- ]
11
-
12
-
13
- class RepeatHMMCalculator(BaseCalculator):
14
- @staticmethod
15
- def get_contigs_from_fh(fh) -> set:
16
- return {ls[0] for ls in (line.split(":") for line in fh)}
17
-
18
- @staticmethod
19
- def make_calls_dict(ph, contig):
20
- return {
21
- tuple(k.split(":")): int_tuple(v.split("/"))
22
- for k, v in (pv.split() for pv in ph)
23
- if k.split(":")[0] == contig
24
- }
25
-
26
- def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
27
- with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
28
- open(self._child_call_file, "r") as cvf:
29
-
30
- mc = self.get_contigs_from_fh(mvf)
31
- fc = self.get_contigs_from_fh(fvf)
32
- cc = self.get_contigs_from_fh(cvf)
33
-
34
- return mc, fc, cc
35
-
36
- def calculate_contig(self, contig: str) -> MIContigResult:
37
- cr = MIContigResult()
38
-
39
- with open(self._mother_call_file) as mh:
40
- mother_calls = self.make_calls_dict(mh, contig)
41
-
42
- with open(self._father_call_file) as fh:
43
- father_calls = self.make_calls_dict(fh, contig)
44
-
45
- with open(self._child_call_file) as ch:
46
- for cv in ch:
47
- locus_data, call = cv.strip().split(" ")
48
- lookup = tuple(locus_data.split(":"))
49
-
50
- if lookup[0] != contig:
51
- continue
52
-
53
- bed_k = lookup[:3]
54
-
55
- # Check to make sure call is present in TRF BED file, if it is specified
56
- if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
57
- continue
58
-
59
- if self.should_exclude_locus(bed_k):
60
- continue
61
-
62
- locus_start: int = int(lookup[1])
63
- locus_end: int = int(lookup[2])
64
-
65
- cr.seen_locus(contig, locus_start, locus_end)
66
-
67
- # Check to make sure call is present in all trio individuals
68
- if lookup not in mother_calls or lookup not in father_calls:
69
- continue
70
-
71
- c_gt = int_tuple(call.split("/"))
72
- m_gt = mother_calls[lookup]
73
- f_gt = father_calls[lookup]
74
-
75
- # Failed calls from RepeatHMM seem to be represented as 0/0, so skip this
76
- # TODO… Need to decide if we actually want to include these?
77
- # or at least somehow record them
78
- if (0, 0) in (c_gt, m_gt, f_gt):
79
- continue
80
-
81
- # TODO: Include ref copies... should be in file somewhere?
82
- cr.append(MILocusData(
83
- lookup[0],
84
- locus_start,
85
- locus_end,
86
- lookup[3],
87
-
88
- child_gt=int_tuple(call.split("/")),
89
- mother_gt=mother_calls[lookup],
90
- father_gt=father_calls[lookup],
91
- ))
92
-
93
- return cr
94
-
95
-
96
- class RepeatHMMReCallCalculator(RepeatHMMCalculator):
97
- @staticmethod
98
- def make_calls_dict(ph, contig):
99
- return {
100
- tuple(v[0].split(":")): (
101
- int_tuple(v[1:3]),
102
- parse_cis(v[3:5], commas=True),
103
- parse_cis(v[5:7], commas=True),
104
- )
105
- for v in (pv.split("\t") for pv in ph)
106
- if v[0].split(":")[0] == contig and "." not in v[1:3]
107
- }
108
-
109
- # TODO: Deduplicate with above
110
- def calculate_contig(self, contig: str) -> MIContigResult:
111
- cr = MIContigResult(includes_95_ci=True, includes_99_ci=True)
112
-
113
- with open(self._mother_call_file) as mh:
114
- mother_calls = self.make_calls_dict(mh, contig)
115
-
116
- with open(self._father_call_file) as fh:
117
- father_calls = self.make_calls_dict(fh, contig)
118
-
119
- with open(self._child_call_file) as ch:
120
- for cv in ch:
121
- locus_data = cv.strip().split("\t")
122
- lookup = tuple(locus_data[0].split(":"))
123
-
124
- if lookup[0] != contig:
125
- continue
126
-
127
- bed_k = lookup[:3]
128
-
129
- # Check to make sure call is present in TRF BED file, if it is specified
130
- if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
131
- continue
132
-
133
- # noinspection PyTypeChecker
134
- if self.should_exclude_locus(bed_k):
135
- continue
136
-
137
- locus_start: int = int(lookup[1])
138
- locus_end: int = int(lookup[2])
139
-
140
- cr.seen_locus(contig, locus_start, locus_end)
141
-
142
- # Check to make sure call is present in all trio individuals
143
- if lookup not in mother_calls or lookup not in father_calls:
144
- continue
145
-
146
- # TODO: What will failed calls look like here? Do we also check for 0?
147
-
148
- m_gt, m_gt_95_ci, m_gt_99_ci = mother_calls[lookup]
149
- f_gt, f_gt_95_ci, f_gt_99_ci = father_calls[lookup]
150
-
151
- # print(m_gt, m_gt_95_ci, m_gt_99_ci)
152
-
153
- calls = locus_data[1:3]
154
-
155
- if "." in calls:
156
- # Failed call
157
- continue
158
-
159
- c_gt = int_tuple(calls)
160
- c_gt_95_ci = parse_cis(locus_data[3:5], commas=True)
161
- c_gt_99_ci = parse_cis(locus_data[5:7], commas=True)
162
-
163
- if (0, 0) in (c_gt, m_gt, f_gt): # TODO
164
- # Failed call
165
- continue
166
-
167
- if self._debug: # TODO: Real logging
168
- print(f"c_gt={c_gt} c_gt_95_ci={c_gt_95_ci}")
169
- print(f"m_gt={m_gt} m_gt_95_ci={m_gt_95_ci}")
170
- print(f"f_gt={f_gt} f_gt_95_ci={f_gt_95_ci}")
171
-
172
- # TODO: Put ref # here, since we have it with the detail thing
173
- cr.append(MILocusData(
174
- lookup[0],
175
- int(lookup[1]),
176
- int(lookup[2]),
177
- lookup[3],
178
-
179
- child_gt=int_tuple(calls),
180
- mother_gt=m_gt,
181
- father_gt=f_gt,
182
-
183
- child_gt_95_ci=c_gt_95_ci,
184
- mother_gt_95_ci=m_gt_95_ci,
185
- father_gt_95_ci=f_gt_95_ci,
186
-
187
- child_gt_99_ci=c_gt_99_ci,
188
- mother_gt_99_ci=m_gt_99_ci,
189
- father_gt_99_ci=f_gt_99_ci,
190
- ))
191
-
192
- return cr
@@ -1,201 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Optional
4
-
5
- from .base import BaseCalculator
6
- from .result import MILocusData, MIContigResult
7
-
8
- __all__ = [
9
- "StraglrCalculator",
10
- "StraglrReCallCalculator",
11
- ]
12
-
13
-
14
- class StraglrCalculator(BaseCalculator):
15
- @staticmethod
16
- def get_contigs_from_fh(fh) -> set:
17
- return {ls[0] for ls in (line.split("\t") for line in fh if not line.startswith("#"))}
18
-
19
- def make_calls_dict(self, ph, contig, cr: Optional[MIContigResult] = None):
20
- # For reference, dicts are ordered in Python 3.7+ (guaranteed)
21
-
22
- calls = {}
23
-
24
- for pv in ph:
25
- if pv.startswith("#"):
26
- continue
27
-
28
- line = pv.strip().split("\t")
29
-
30
- if line[0] != contig:
31
- continue
32
-
33
- locus = tuple(line[:3])
34
-
35
- orig_motif = self._loci_dict.get(locus)
36
- orig_motif = orig_motif[-1] if orig_motif else None
37
-
38
- if not orig_motif:
39
- continue
40
-
41
- if self.should_exclude_locus(locus):
42
- continue
43
-
44
- if cr:
45
- cr.seen_locus(locus[0], int(locus[1]), int(locus[2]))
46
-
47
- # Transform the genotypes into something that is consistent across individuals,
48
- # using the file with the list of loci.
49
- gt_fact = len(line[3]) / len(orig_motif)
50
-
51
- gt = tuple(float(g.split("(")[0]) * gt_fact for g in line[4].split(";"))
52
- if len(gt) == 1: # If it's homozygous, expand it out to length 2
53
- gt = gt + gt
54
-
55
- calls[locus + (orig_motif,)] = gt
56
-
57
- return calls
58
-
59
- def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
60
- with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
61
- open(self._child_call_file, "r") as cvf:
62
-
63
- mc = self.get_contigs_from_fh(mvf)
64
- fc = self.get_contigs_from_fh(fvf)
65
- cc = self.get_contigs_from_fh(cvf)
66
-
67
- return mc, fc, cc
68
-
69
- def calculate_contig(self, contig: str):
70
- cr = MIContigResult()
71
-
72
- with open(self._mother_call_file, "r") as mh:
73
- mother_calls = self.make_calls_dict(mh, contig)
74
-
75
- with open(self._father_call_file, "r") as fh:
76
- father_calls = self.make_calls_dict(fh, contig)
77
-
78
- with open(self._child_call_file, "r") as ch:
79
- child_calls = self.make_calls_dict(ch, contig, cr)
80
-
81
- for locus_data, c_gt in child_calls.items():
82
- # Check to make sure call is present in all trio individuals
83
- if locus_data not in mother_calls or locus_data not in father_calls:
84
- continue
85
-
86
- cr.append(MILocusData(
87
- contig=locus_data[0],
88
- start=int(locus_data[1]),
89
- end=int(locus_data[2]),
90
- motif=locus_data[3],
91
-
92
- child_gt=c_gt,
93
- mother_gt=mother_calls[locus_data],
94
- father_gt=father_calls[locus_data],
95
-
96
- decimal=True,
97
- ))
98
-
99
- return cr
100
-
101
-
102
- class StraglrReCallCalculator(BaseCalculator):
103
- @staticmethod
104
- def get_contigs_from_fh(fh) -> set:
105
- return {ls[0] for ls in (line.split("\t") for line in fh if not line.startswith("#"))}
106
-
107
- def make_calls_dict(self, ph, contig, cr: Optional[MIContigResult] = None):
108
- # For reference, dicts are ordered in Python 3.7+ (guaranteed)
109
-
110
- calls = {}
111
-
112
- for pv in ph:
113
- if pv.startswith("#"):
114
- continue
115
-
116
- line = pv.strip().split("\t")
117
-
118
- if line[0] != contig:
119
- continue
120
-
121
- locus = tuple(line[:3])
122
- orig_motif = self._loci_dict.get(locus)
123
- orig_motif = orig_motif[-1] if orig_motif else None
124
- if not orig_motif:
125
- continue
126
-
127
- if self.should_exclude_locus(locus):
128
- continue
129
-
130
- if cr:
131
- cr.seen_locus(contig, int(locus[1]), int(locus[2]))
132
-
133
- if "." in line[6:8]:
134
- continue
135
-
136
- # Transform the genotypes into something that is consistent across individuals,
137
- # using the file with the list of loci.
138
- # Round it to the nearest first decimal place, since that is what Straglr calls.
139
-
140
- gt_fact = len(line[3]) / len(orig_motif)
141
-
142
- def _to_tenth(x: str):
143
- return round(float(x) * gt_fact * 10) / 10
144
-
145
- gt = tuple(map(_to_tenth, line[6:8]))
146
- gt_95_ci = tuple(tuple(map(_to_tenth, ci.split(","))) for ci in line[8:10])
147
- gt_99_ci = tuple(tuple(map(_to_tenth, ci.split(","))) for ci in line[10:12])
148
-
149
- calls[locus + (orig_motif,)] = (gt, gt_95_ci, gt_99_ci)
150
-
151
- return calls
152
-
153
- def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
154
- with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
155
- open(self._child_call_file, "r") as cvf:
156
-
157
- mc = self.get_contigs_from_fh(mvf)
158
- fc = self.get_contigs_from_fh(fvf)
159
- cc = self.get_contigs_from_fh(cvf)
160
-
161
- return mc, fc, cc
162
-
163
- def calculate_contig(self, contig: str):
164
- cr = MIContigResult(includes_95_ci=True, includes_99_ci=True)
165
-
166
- with open(self._mother_call_file) as mh:
167
- mother_calls = self.make_calls_dict(mh, contig)
168
-
169
- with open(self._father_call_file) as fh:
170
- father_calls = self.make_calls_dict(fh, contig)
171
-
172
- with open(self._child_call_file) as ch:
173
- child_calls = self.make_calls_dict(ch, contig, cr)
174
-
175
- for locus_data, c_gt_and_cis in child_calls.items():
176
- # Check to make sure call is present in all trio individuals
177
- if locus_data not in mother_calls or locus_data not in father_calls:
178
- continue
179
-
180
- locus_start = int(locus_data[1])
181
- locus_end = int(locus_data[2])
182
-
183
- c_gt, c_gt_95_ci, c_gt_99_ci = c_gt_and_cis
184
-
185
- m_gt, m_gt_95_ci, m_gt_99_ci = mother_calls[locus_data]
186
- f_gt, f_gt_95_ci, f_gt_99_ci = father_calls[locus_data]
187
-
188
- cr.append(MILocusData(
189
- contig=locus_data[0],
190
- start=locus_start,
191
- end=locus_end,
192
- motif=locus_data[3],
193
-
194
- child_gt=c_gt, mother_gt=m_gt, father_gt=f_gt,
195
- child_gt_95_ci=c_gt_95_ci, mother_gt_95_ci=m_gt_95_ci, father_gt_95_ci=f_gt_95_ci,
196
- child_gt_99_ci=c_gt_99_ci, mother_gt_99_ci=m_gt_99_ci, father_gt_99_ci=f_gt_99_ci,
197
-
198
- decimal=True,
199
- ))
200
-
201
- return cr
@@ -1,172 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from .base import BaseCalculator
4
- from .result import MIContigResult, MILocusData
5
- from ..utils import int_tuple, parse_cis
6
-
7
- __all__ = [
8
- "TandemGenotypesCalculator",
9
- "TandemGenotypesReCallCalculator",
10
- ]
11
-
12
-
13
- class TandemGenotypesCalculator(BaseCalculator):
14
- @staticmethod
15
- def get_contigs_from_fh(fh) -> set[str]:
16
- return {ls[0] for ls in (line.split("\t") for line in fh if not line.startswith("#"))}
17
-
18
- @staticmethod
19
- def make_calls_dict(ph, contig):
20
- return {
21
- tuple(line[:4]): int_tuple(line[6:8]) if "." not in line[6:8] else (None, None)
22
- for line in (pv.strip().split("\t") for pv in ph if not pv.startswith("#"))
23
- if line[0] == contig
24
- }
25
-
26
- def _get_sample_contigs(self, include_sex_chromosomes: bool = False) -> tuple[set, set, set]:
27
- with open(self._mother_call_file, "r") as mvf, open(self._father_call_file, "r") as fvf, \
28
- open(self._child_call_file, "r") as cvf:
29
-
30
- mc = self.get_contigs_from_fh(mvf)
31
- fc = self.get_contigs_from_fh(fvf)
32
- cc = self.get_contigs_from_fh(cvf)
33
-
34
- return mc, fc, cc
35
-
36
- def calculate_contig(self, contig: str) -> MIContigResult:
37
- cr = MIContigResult()
38
-
39
- with open(self._mother_call_file) as mh:
40
- mother_calls = self.make_calls_dict(mh, contig)
41
-
42
- with open(self._father_call_file) as fh:
43
- father_calls = self.make_calls_dict(fh, contig)
44
-
45
- with open(self._child_call_file) as ch:
46
- for cv in ch:
47
- locus_data = cv.strip().split("\t")
48
- lookup = tuple(locus_data[:4])
49
-
50
- if locus_data[0] != contig:
51
- continue
52
-
53
- bed_k = lookup[:3]
54
-
55
- # Check to make sure call is present in TRF BED file, if it is specified
56
- if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
57
- continue
58
-
59
- # noinspection PyTypeChecker
60
- if self.should_exclude_locus(bed_k):
61
- continue
62
-
63
- locus_start = int(lookup[1])
64
- locus_end = int(lookup[2])
65
-
66
- cr.seen_locus(contig, locus_start, locus_end)
67
-
68
- # Check to make sure call is present in all trio individuals
69
- if lookup not in mother_calls or lookup not in father_calls:
70
- continue
71
-
72
- child_calls = locus_data[6:8]
73
-
74
- if "." in child_calls:
75
- # Failed call
76
- continue
77
-
78
- cr.append(MILocusData(
79
- contig=contig,
80
- start=locus_start,
81
- end=locus_end,
82
- motif=lookup[3],
83
-
84
- child_gt=int_tuple(child_calls),
85
- mother_gt=mother_calls[lookup],
86
- father_gt=father_calls[lookup],
87
- ))
88
-
89
- return cr
90
-
91
-
92
- class TandemGenotypesReCallCalculator(TandemGenotypesCalculator):
93
- @staticmethod
94
- def make_calls_dict(ph, contig):
95
- return {
96
- # Use relative indices because we may have the original calls lurking
97
- tuple(line[:4]): (
98
- int_tuple(line[-6:-4]),
99
- parse_cis(line[-4:-2], commas=True),
100
- parse_cis(line[-2:], commas=True),
101
- )
102
- for line in (pv.strip().split("\t") for pv in ph)
103
- if line[0] == contig and "." not in line[-6:-4]
104
- }
105
-
106
- def calculate_contig(self, contig: str) -> MIContigResult:
107
- cr = MIContigResult(includes_95_ci=True, includes_99_ci=True)
108
-
109
- with open(self._mother_call_file) as mh:
110
- mother_calls = self.make_calls_dict(mh, contig)
111
-
112
- with open(self._father_call_file) as fh:
113
- father_calls = self.make_calls_dict(fh, contig)
114
-
115
- with open(self._child_call_file) as ch:
116
- for cv in ch:
117
- locus_data = cv.strip().split("\t")
118
- lookup = tuple(locus_data[:4])
119
-
120
- if locus_data[0] != contig:
121
- continue
122
-
123
- bed_k = lookup[:3]
124
-
125
- # Check to make sure call is present in TRF BED file, if it is specified
126
- if self._loci_file and self._loci_dict and bed_k not in self._loci_dict:
127
- continue
128
-
129
- # noinspection PyTypeChecker
130
- if self.should_exclude_locus(bed_k):
131
- continue
132
-
133
- locus_start = int(lookup[1])
134
- locus_end = int(lookup[2])
135
-
136
- cr.seen_locus(contig, locus_start, locus_end)
137
-
138
- # Check to make sure call is present in all trio individuals
139
- if lookup not in mother_calls or lookup not in father_calls:
140
- continue
141
-
142
- m_gt, m_gt_95_ci, m_gt_99_ci = mother_calls[lookup]
143
- f_gt, f_gt_95_ci, f_gt_99_ci = father_calls[lookup]
144
-
145
- calls = locus_data[-6:-4]
146
-
147
- if "." in calls:
148
- # Failed call
149
- continue
150
-
151
- cr.append(MILocusData(
152
- contig=lookup[0],
153
- start=int(lookup[1]),
154
- end=int(lookup[2]),
155
- motif=lookup[3],
156
-
157
- child_gt=int_tuple(calls),
158
- mother_gt=m_gt,
159
- father_gt=f_gt,
160
-
161
- child_gt_95_ci=parse_cis(locus_data[-4:-2], commas=True),
162
- mother_gt_95_ci=m_gt_95_ci,
163
- father_gt_95_ci=f_gt_95_ci,
164
-
165
- child_gt_99_ci=parse_cis(locus_data[-2:], commas=True),
166
- mother_gt_99_ci=m_gt_99_ci,
167
- father_gt_99_ci=f_gt_99_ci,
168
-
169
- # TODO: ref count
170
- ))
171
-
172
- return cr
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