| from __future__ import annotations |
|
|
| import random |
|
|
| import pandas as pd |
|
|
| from deepgenopix.pixelizer import DNAPixelizer |
| from deepgenopix.variant import call_cna, pixel_to_12mer |
| from deepgenopix.variant.decoder import ( |
| base_at_intra_offset, |
| base_position_to_pixel_offsets, |
| offsets_for_strip, |
| strip_to_sequence, |
| vote_base_at_position, |
| ) |
|
|
|
|
| def _det_seq(length: int, seed: int) -> str: |
| rng = random.Random(seed) |
| return "".join(rng.choice("ACGT") for _ in range(length)) |
|
|
|
|
| def test_variant_decoder_round_trip_random_12mer(): |
| sequence = _det_seq(12, seed=1) |
| pixelizer = DNAPixelizer() |
|
|
| tensor, n_pixels = pixelizer.string_to_tensor(sequence) |
|
|
| assert n_pixels == 1 |
| assert pixel_to_12mer(tensor[:, 0]) == sequence |
|
|
|
|
| def test_call_cna_returns_expected_calls(): |
| tumor = pd.Series({"a": 30, "b": 10, "c": 10}) |
| normal = pd.Series({"a": 10, "b": 10, "c": 30}) |
|
|
| calls = call_cna(tumor, normal) |
|
|
| by_locus = dict(zip(calls["locus"], calls["call"], strict=False)) |
| assert by_locus["a"] == "amp" |
| assert by_locus["c"] == "del" |
|
|
|
|
| def test_strip_to_sequence_recovers_at_stride_12(): |
| sequence = _det_seq(96, seed=2) |
| pixelizer = DNAPixelizer() |
| tensor, n_pixels = pixelizer.string_to_tensor(sequence) |
|
|
| decoded, disagreement = strip_to_sequence(tensor, pixel_stride_bp=12, sequence_length=len(sequence)) |
| assert decoded == sequence |
| |
| assert all(d == 0 for d in disagreement) |
| assert n_pixels == len(sequence) // 12 |
|
|
|
|
| def test_strip_to_sequence_recovers_at_walk_stride_9(): |
| sequence = _det_seq(99, seed=3) |
| pixelizer = DNAPixelizer(pixel_stride_bp=9) |
| tensor, _ = pixelizer.string_to_tensor(sequence) |
|
|
| decoded, disagreement = strip_to_sequence(tensor, pixel_stride_bp=9, sequence_length=len(sequence)) |
| assert decoded == sequence |
| |
| |
| assert max(disagreement) == 0 |
|
|
|
|
| def test_strip_to_sequence_recovers_at_stride_1(): |
| sequence = _det_seq(36, seed=4) |
| pixelizer = DNAPixelizer(pixel_stride_bp=1) |
| tensor, _ = pixelizer.string_to_tensor(sequence) |
|
|
| decoded, disagreement = strip_to_sequence(tensor, pixel_stride_bp=1, sequence_length=len(sequence)) |
| assert decoded == sequence |
| |
| |
| interior_disagreement = disagreement[12:-12] |
| assert all(d == 0 for d in interior_disagreement) |
|
|
|
|
| def test_offsets_for_strip_matches_pixelizer(): |
| sequence = _det_seq(73, seed=5) |
| pixelizer = DNAPixelizer(pixel_stride_bp=9) |
| tensor, n_pixels = pixelizer.string_to_tensor(sequence) |
|
|
| offsets = offsets_for_strip(n_pixels, pixel_stride_bp=9, sequence_length=len(sequence)) |
| assert len(offsets) == n_pixels |
| |
| assert offsets[-1] == len(sequence) - 12 |
|
|
|
|
| def test_base_position_to_pixel_offsets_covers_every_base(): |
| sequence = _det_seq(45, seed=6) |
| pixelizer = DNAPixelizer(pixel_stride_bp=6) |
| _, n_pixels = pixelizer.string_to_tensor(sequence) |
| offsets = offsets_for_strip(n_pixels, pixel_stride_bp=6, sequence_length=len(sequence)) |
|
|
| for base_position in range(len(sequence)): |
| covers = base_position_to_pixel_offsets(offsets, base_position) |
| assert covers, f"position {base_position} has no covering pixel" |
| for pixel_idx, intra in covers: |
| assert 0 <= intra < 12 |
| assert offsets[pixel_idx] + intra == base_position |
|
|
|
|
| def test_disagreement_signals_corrupted_pixel(): |
| sequence = _det_seq(36, seed=7) |
| pixelizer = DNAPixelizer(pixel_stride_bp=6) |
| tensor, n_pixels = pixelizer.string_to_tensor(sequence) |
| offsets = offsets_for_strip(n_pixels, pixel_stride_bp=6, sequence_length=len(sequence)) |
|
|
| |
| |
| target_pixel = 1 |
| original_base = base_at_intra_offset(tensor[:, target_pixel], 0) |
| flipped = "ACGT"[("ACGT".index(original_base) + 1) % 4] |
| |
| packed = ( |
| (int(round(float(tensor[0, target_pixel].item()) * 255)) << 16) |
| | (int(round(float(tensor[1, target_pixel].item()) * 255)) << 8) |
| | int(round(float(tensor[2, target_pixel].item()) * 255)) |
| ) |
| mask = 0x3 << (2 * 11) |
| packed = (packed & ~mask) | ("ACGT".index(flipped) << (2 * 11)) |
| tensor[0, target_pixel] = ((packed >> 16) & 0xFF) / 255.0 |
| tensor[1, target_pixel] = ((packed >> 8) & 0xFF) / 255.0 |
| tensor[2, target_pixel] = (packed & 0xFF) / 255.0 |
|
|
| _, disagreement = strip_to_sequence(tensor, pixel_stride_bp=6, sequence_length=len(sequence)) |
| affected_position = offsets[target_pixel] + 0 |
| assert disagreement[affected_position] > 0 |
|
|