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
99 changes: 98 additions & 1 deletion src/pmotools/pmo_builder/mhap_table_to_pmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ def mhap_table_to_pmo(
masking_delim: str = ",",
microhaplotype_name_col: str | None = None,
pseudocigar_col: str | None = None,
pseudocigar_chrom_col: str | None = None,
pseudocigar_start_col: str | None = None,
pseudocigar_end_col: str | None = None,
pseudocigar_ref_seq_col: str | None = None,
pseudocigar_strand_col: str | None = None,
pseudocigar_genome_id: int | None = None,
pseudocigar_generation_description_col: str | None = None,
quality_col: str | None = None,
additional_representative_mhap_cols: list | None = None,
additional_mhap_detected_cols: list | None = None,
Expand Down Expand Up @@ -73,6 +80,20 @@ def mhap_table_to_pmo(
:type microhaplotype_name_col: str, optional
:param pseudocigar_col: the name of the column containing a pseudocigar for the microhaplotype
:type pseudocigar_col: str, optional
:param pseudocigar_chrom_col: column for the pseudocigar ref_loc chromosome; defaults to chrom_col
:type pseudocigar_chrom_col: str, optional
:param pseudocigar_start_col: column for the pseudocigar ref_loc start (required if pseudocigar_col is set)
:type pseudocigar_start_col: str, optional
:param pseudocigar_end_col: column for the pseudocigar ref_loc end (required if pseudocigar_col is set)
:type pseudocigar_end_col: str, optional
:param pseudocigar_ref_seq_col: optional column for the pseudocigar ref_loc reference sequence
:type pseudocigar_ref_seq_col: str, optional
:param pseudocigar_strand_col: optional column for the pseudocigar ref_loc strand
:type pseudocigar_strand_col: str, optional
:param pseudocigar_genome_id: genome id for the pseudocigar ref_loc; defaults to genome_id
:type pseudocigar_genome_id: int, optional
:param pseudocigar_generation_description_col: optional column describing how the pseudocigar was generated
:type pseudocigar_generation_description_col: str, optional
:param quality_col: the name of the column containing the ANSI FASTQ per-base quality score for this sequence
:type quality_col: str, optional
:param additional_representative_mhap_cols: additional columns to add to the representative microhaplotypes table
Expand Down Expand Up @@ -100,6 +121,13 @@ def mhap_table_to_pmo(
masking_delim=masking_delim,
microhaplotype_name_col=microhaplotype_name_col,
pseudocigar_col=pseudocigar_col,
pseudocigar_chrom_col=pseudocigar_chrom_col,
pseudocigar_start_col=pseudocigar_start_col,
pseudocigar_end_col=pseudocigar_end_col,
pseudocigar_ref_seq_col=pseudocigar_ref_seq_col,
pseudocigar_strand_col=pseudocigar_strand_col,
pseudocigar_genome_id=pseudocigar_genome_id,
pseudocigar_generation_description_col=pseudocigar_generation_description_col,
quality_col=quality_col,
additional_representative_mhap_cols=additional_representative_mhap_cols,
)
Expand Down Expand Up @@ -160,6 +188,13 @@ def create_representative_microhaplotype_dict(
masking_delim: str = ",",
microhaplotype_name_col: str | None = None,
pseudocigar_col: str | None = None,
pseudocigar_chrom_col: str | None = None,
pseudocigar_start_col: str | None = None,
pseudocigar_end_col: str | None = None,
pseudocigar_ref_seq_col: str | None = None,
pseudocigar_strand_col: str | None = None,
pseudocigar_genome_id: int | None = None,
pseudocigar_generation_description_col: str | None = None,
quality_col: str | None = None,
additional_representative_mhap_cols: list[str] | None = None,
):
Expand Down Expand Up @@ -198,6 +233,20 @@ def create_representative_microhaplotype_dict(
:type microhaplotype_name_col: str, optional
:param pseudocigar_col: the name of the column containing a pseudocigar for the microhaplotype
:type pseudocigar_col: str, optional
:param pseudocigar_chrom_col: column for the pseudocigar ref_loc chromosome; defaults to chrom_col
:type pseudocigar_chrom_col: str, optional
:param pseudocigar_start_col: column for the pseudocigar ref_loc start (required if pseudocigar_col is set)
:type pseudocigar_start_col: str, optional
:param pseudocigar_end_col: column for the pseudocigar ref_loc end (required if pseudocigar_col is set)
:type pseudocigar_end_col: str, optional
:param pseudocigar_ref_seq_col: optional column for the pseudocigar ref_loc reference sequence
:type pseudocigar_ref_seq_col: str, optional
:param pseudocigar_strand_col: optional column for the pseudocigar ref_loc strand
:type pseudocigar_strand_col: str, optional
:param pseudocigar_genome_id: genome id for the pseudocigar ref_loc; defaults to genome_id
:type pseudocigar_genome_id: int, optional
:param pseudocigar_generation_description_col: optional column describing how the pseudocigar was generated
:type pseudocigar_generation_description_col: str, optional
:param quality_col: the name of the column containing the ANSI FASTQ per-base quality score for this sequence
:type quality_col: str, optional
:param additional_representative_mhap_cols: additional columns to add to the representative microhaplotypes table
Expand All @@ -211,6 +260,21 @@ def create_representative_microhaplotype_dict(
microhaplotype_table, additional_representative_mhap_cols
)

# the pseudocigar ref_loc shares the chromosome / genome with the
# microhaplotype location by default, but each may use its own column
ps_chrom_col = pseudocigar_chrom_col if pseudocigar_chrom_col else chrom_col
ps_genome_id = (
pseudocigar_genome_id if pseudocigar_genome_id is not None else genome_id
)
if pseudocigar_col and not (
ps_chrom_col and pseudocigar_start_col and pseudocigar_end_col
):
raise ValueError(
"pseudocigar_col is set, so a Pseudocigar ref_loc must be "
"constructable: set a chromosome (pseudocigar_chrom_col or "
"chrom_col), pseudocigar_start_col, and pseudocigar_end_col."
)

def get_if_present(row, col):
return row[col] if col and pd.notna(row[col]) else None

Expand Down Expand Up @@ -263,6 +327,12 @@ def warn_if_duplicated_seqs(df, target_col, seq_col):
alt_annotations_col,
microhaplotype_name_col,
pseudocigar_col,
ps_chrom_col,
pseudocigar_start_col,
pseudocigar_end_col,
pseudocigar_ref_seq_col,
pseudocigar_strand_col,
pseudocigar_generation_description_col,
quality_col,
]
masking_cols = [
Expand Down Expand Up @@ -319,7 +389,34 @@ def warn_if_duplicated_seqs(df, target_col, seq_col):
if val := get_if_present(row, microhaplotype_name_col):
mhap["microhaplotype_name"] = val
if val := get_if_present(row, pseudocigar_col):
mhap["pseudo_cigar"] = val
if not (
pd.notna(row[ps_chrom_col])
and pd.notna(row[pseudocigar_start_col])
and pd.notna(row[pseudocigar_end_col])
):
raise ValueError(
f"pseudocigar present for target {target}, seq "
f"{row[seq_col]} but its ref_loc chrom/start/end "
"is missing"
)
ref_loc = {
"genome_id": ps_genome_id,
"chrom": row[ps_chrom_col],
"start": row[pseudocigar_start_col],
"end": row[pseudocigar_end_col],
}
if pseudocigar_ref_seq_col and pd.notna(row[pseudocigar_ref_seq_col]):
ref_loc["ref_seq"] = row[pseudocigar_ref_seq_col]
if pseudocigar_strand_col and pd.notna(row[pseudocigar_strand_col]):
ref_loc["strand"] = row[pseudocigar_strand_col]
pseudocigar = {"pseudocigar_seq": val, "ref_loc": ref_loc}
if pseudocigar_generation_description_col and pd.notna(
row[pseudocigar_generation_description_col]
):
pseudocigar["pseudocigar_generation_description"] = row[
pseudocigar_generation_description_col
]
mhap["pseudocigar"] = pseudocigar
if val := get_if_present(row, quality_col):
mhap["quality"] = val
if additional_representative_mhap_cols:
Expand Down
71 changes: 34 additions & 37 deletions tests/test_pmo_builder/test_mhap_table_to_pmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,52 +510,49 @@ def test_create_representative_microhaplotype_dict_with_ad_cols(self):
mhap_table_ad_cols = self.small_mhap_table.copy()
mhap_table_ad_cols["adcol1"] = "this"
mhap_table_ad_cols["adcol2"] = "that"
mhap_table_ad_cols["cig"] = "cigs"

rep_dict_with_ad_cols = copy.deepcopy(self.small_representative_dict)
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][0]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][0]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][0][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][1]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][1]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][1][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][2]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][2]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][0]["microhaplotypes"][2][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][0]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][0]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][0][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][1]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][1]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][1]["microhaplotypes"][1][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][0]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][0]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][0][
"pseudo_cigar"
] = "cigs"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][1]["adcol1"] = "this"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][1]["adcol2"] = "that"
rep_dict_with_ad_cols["targets"][2]["microhaplotypes"][1][
"pseudo_cigar"
] = "cigs"
for target in rep_dict_with_ad_cols["targets"]:
for mhap in target["microhaplotypes"]:
mhap["adcol1"] = "this"
mhap["adcol2"] = "that"

actual = create_representative_microhaplotype_dict(
mhap_table_ad_cols,
additional_representative_mhap_cols=["adcol1", "adcol2"],
pseudocigar_col="cig",
)
self.assertEqual(actual, rep_dict_with_ad_cols)

def test_create_representative_microhaplotype_dict_with_pseudocigar(self):
tbl = self.small_mhap_table.copy()
tbl["chrom"] = "chr1"
tbl["start"] = 100
tbl["end"] = 150
tbl["cig"] = "8M"
actual = create_representative_microhaplotype_dict(
tbl,
chrom_col="chrom",
start_col="start",
end_col="end",
pseudocigar_col="cig",
pseudocigar_start_col="start",
pseudocigar_end_col="end",
)
first = actual["targets"][0]["microhaplotypes"][0]
# pseudocigar is a Pseudocigar object with a GenomicLocation ref_loc;
# the chromosome is reused from chrom_col and genome_id defaults to 0
self.assertEqual(first["pseudocigar"]["pseudocigar_seq"], "8M")
self.assertEqual(
first["pseudocigar"]["ref_loc"],
{"genome_id": 0, "chrom": "chr1", "start": 100, "end": 150},
)

def test_pseudocigar_without_ref_loc_columns_raises(self):
tbl = self.small_mhap_table.copy()
tbl["cig"] = "8M"
with self.assertRaises(ValueError):
create_representative_microhaplotype_dict(tbl, pseudocigar_col="cig")

@patch(
"pmotools.pmo_builder.mhap_table_to_pmo.create_representative_microhaplotype_dict"
)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_pmo_builder/test_schema_validation_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ def test_full_1_0_0_toy_pmo_validates_against_schema():
umis_col="umis",
microhaplotype_name_col="microhap_name",
pseudocigar_col="pseudocigar",
pseudocigar_start_col="start",
pseudocigar_end_col="end",
quality_col="quality",
masking_seq_start_col="mask_start",
masking_seq_segment_size_col="mask_segment",
Expand Down Expand Up @@ -467,6 +469,9 @@ def test_slimer_1_1_0_toy_pmo_validates_against_schema():
"umis": [10],
"microhap_name": ["mh1"],
"pseudocigar": ["8M"],
"pseudocigar_chrom": ["chr1"],
"pseudocigar_start": [120],
"pseudocigar_end": [127],
"quality": ["ABCD"],
"mask_start": ["1"],
"mask_segment": ["2"],
Expand All @@ -480,6 +485,9 @@ def test_slimer_1_1_0_toy_pmo_validates_against_schema():
umis_col="umis",
microhaplotype_name_col="microhap_name",
pseudocigar_col="pseudocigar",
pseudocigar_chrom_col="pseudocigar_chrom",
pseudocigar_start_col="pseudocigar_start",
pseudocigar_end_col="pseudocigar_end",
quality_col="quality",
masking_seq_start_col="mask_start",
masking_seq_segment_size_col="mask_segment",
Expand Down
Loading