Repository: akikuno/DAJIN2 (language: python)

Issue:
Downsampling the sample reads to a maximum of 10,000 for clustering
### 📋 Description

I think it's fine to apply the same process to the Sample by downsampling the number of reads to 10,000, just like we are doing for the Control. Since clustering takes time, one option could be to first perform the clustering and then use supervised learning to assign each read to a category across all reads.


### 🔍 Environment

```markdown
- DAJIN2 version: 0.5.5
```


### 📎 Anything else?

_No response_


Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index da217598..9fc4ac19 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -11,6 +11,15 @@ body:
     validations:
       required: true
 
+  - type: input
+    id: dajin2_version
+    attributes:
+      label: '🔖 DAJIN2 Version'
+      description: 'Required: Specify the DAJIN2 version you are using. You can find this information in the `DAJIN2 --version` command.'
+      placeholder: 'e.g., 0.5.5'
+    validations:
+      required: true
+
   - type: textarea
     attributes:
       label: '💬 Current Behavior'
@@ -19,6 +28,7 @@ body:
     validations:
       required: false
 
+
   - type: textarea
     attributes:
       label: '🎯 Expected Behavior'
@@ -35,6 +45,7 @@ body:
     validations:
       required: false
 
+
   - type: textarea
     id: environment
     attributes:
@@ -42,17 +53,16 @@ body:
       description: |
           Optional: The environment information.
           Example:
-          - OS: WSL (Ubuntu 22.04)
-          - DAJIN2 version: x.x.x
-          - Python version: x.x.x
+          OS: WSL (Ubuntu 22.04)
+          Python version: 3.10.15
       value: |
-          - OS:
-          - DAJIN2 version:
-          - Python version:
+          OS:
+          Python version:
       render: markdown
     validations:
       required: false
 
+
   - type: textarea
     id: anything_else
     attributes:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 855e8916..3e12ad71 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -11,6 +11,15 @@ body:
     validations:
       required: true
 
+  - type: input
+    id: dajin2_version
+    attributes:
+      label: '🔖 DAJIN2 Version'
+      description: 'Required: Specify the DAJIN2 version you are using. You can find this information in the `DAJIN2 --version` command.'
+      placeholder: 'e.g., 0.5.5'
+    validations:
+      required: true
+
   - type: textarea
     id: environment
     attributes:
@@ -18,13 +27,11 @@ body:
       description: |
           Optional: The environment information.
           Example:
-          - OS: WSL (Ubuntu 22.04)
-          - DAJIN2 version: x.x.x
-          - Python version: x.x.x
+          OS: WSL (Ubuntu 22.04)
+          Python version: 3.10.15
       value: |
-          - OS:
-          - DAJIN2 version:
-          - Python version:
+          OS:
+          Python version:
       render: markdown
     validations:
       required: false
diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml
index 55474546..a7ab66f6 100644
--- a/.github/ISSUE_TEMPLATE/question.yml
+++ b/.github/ISSUE_TEMPLATE/question.yml
@@ -11,6 +11,15 @@ body:
     validations:
       required: true
 
+  - type: input
+    id: dajin2_version
+    attributes:
+      label: '🔖 DAJIN2 Version'
+      description: 'Required: Specify the DAJIN2 version you are using. You can find this information in the `DAJIN2 --version` command.'
+      placeholder: 'e.g., 0.5.5'
+    validations:
+      required: true
+
   - type: textarea
     id: environment
     attributes:
@@ -18,17 +27,16 @@ body:
       description: |
           Optional: The environment information.
           Example:
-          - OS: WSL (Ubuntu 22.04)
-          - DAJIN2 version: x.x.x
-          - Python version: x.x.x
+          OS: WSL (Ubuntu 22.04)
+          Python version: 3.10.15
       value: |
-          - OS:
-          - DAJIN2 version:
-          - Python version:
+          OS:
+          Python version:
       render: markdown
     validations:
       required: false
 
+
   - type: textarea
     id: anything_else
     attributes:
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
index e9d47c21..0accf657 100644
--- a/docs/RELEASE.md
+++ b/docs/RELEASE.md
@@ -12,23 +12,46 @@
 
 <!-- ############################################################# # -->
 
-# v0.5.5.1 (2024-10-28)
+# v0.5.6 (2024-MM-DD)
 
-## 📝 Documentation
+## 💥 Breaking
 
-+ Add `FAQ.md` and `FAQ_JP.md` to address the question: "Why is the read count of the Control sample lower in the output BAM file?". [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/b238d21fbb7cd3330a147bdde65b726278447649)]
++ Support for PacBio HiFi reads. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/69773342d3bf157f28db013d62796be43ae297e7)]
+
++ Add `preprocess.sequence_error_handler` to exclude Nanopore sequence errors from the analysis. Issue: #60 
+  + Initial commit [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/329493dbfe6c0d03a6f8aadaab88911900f35dbb)]
+  + Since most Nanopore sequencing errors occur due to read interruptions, `parse_midsv_from_csv` classifies entries as either Unknown or Other (M). [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/809c22be361b0566e46ea94b5dec37b8a4659244)]
+  + Instead of strategies like Cosine similarity or HDBSCAN, the Jaro-Winkler distance is explicitly used as a string similarity metric. Jaro-Winkler was chosen because Levenshtein would be too time-consuming. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/809c22be361b0566e46ea94b5dec37b8a4659244)]
+
++ Add `sr` presets to all execusions in `preprocess.mapping`. Issue: #55 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/682a20f3e71206fbd55369b5ff0dea799881aa67)]
+
++ Increase the sensitivity by lowering the mutation detection threshold from 0.5% to 0.1% to detect mutations around 0.75%. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/0e19752c1a4100d5a9121d54a563698642dc35c1)]
+
++ Use `AgglomerativeClustering` instead of Constrained KMeans because AgglomerativeClustering provides a more global clustering approach, and Constrained KMeans was not very useful due to the unreliability of its `min_cluster_size`. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/b81711e553edccaec4d6396cf940081163a18471)]
+
++ Output seqence error reads as `BAM/{name}/sequence_errors.bam`. Issue: #61 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/b2e717faa5ed17cdc18217d537cf49de6ca7c0b4)]
+
+## 🚀 Performance
+
++ Downsampling the sample reads to a maximum of 10,000. Issue: #58 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/33c2120b59c80afe7f76165b2410b8ffe51410bd)]
+
+## 🐛 Bug Fixes
+
++ Fix a bug where a element of dict with empty values was left behind after minor insertions were removed.  [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/dcd66324999fbf2054b1310b879f81baf0fa7a92)]
 
 ## 🔧 Maintenance
 
-+ Integrating insertion and inversion detection: Issue #31
-  + Add sv_handler [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/d994d845b0b8ed0fa8affed7992f1d95bf163073)]
++ With the end of security support for Python 3.8 in October 2024, we have updated DAJIN2 to support Python 3.9 or later. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/0967c463386a48639a614849b3d3e4453079c8b1)]
 
-  + Modify arguments of `is_insertion` to `is_sv` [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/f2d3dc4ca2dff60fc869fb1f5b6b08f54490b564)]
++ Replace typing.Generator to collections.abc.Iterator Since typing.Generator is deprecated. Issue: #53 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/f85964a4b8027b547b7b3e370b9e86ff8dda36be)]
 
-  + Remame `insertions_to_fasta.generate_insertions_fasta` to `insertion_detector.detect_insertions` because the function is not only for generating fasta files but also for generating csv tag. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/63c9d63bad627f529f272ea90c035e236f9dd1fb)]
++ Automatically retrieve version information using `importlib.metadata.version` Issue: #59 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/4cf75078b5e7f487b07650e934d63448bc3a328e)]
 
-+ Remove unused dependencies
-  + `networkx`: Issue #49 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/524186bdce9e28d6357378d0baeb45670d2e22ed)]
++ Move the FASTX IO processing to `utils.io`. Issue: #66 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/cca3db0b13ac53e082e2272f7ee7f593f905bd25)]
+
++ Add E2E tests in Github Actions. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/8fb93621ae7b9c4e867a68e9160c3295bfb0f872)]
+
+## ⛔️ Deprecated
 
 <!-- ############################################################# # -->
 
@@ -38,12 +61,34 @@
 
 # Past Releases
 
-<!--  ------------------------------------------------------------- -->
-
 <!-- <details>
-<summary> v0.5.3 (2024-07-16) </summary>
+<summary> v0.X.X (2024-MM-DD) </summary>
+
 </details> -->
 
+<details>
+<summary> v0.5.5 (2024-10-08) </summary>
+
+## 📝 Documentation
+
++ Add `FAQ.md` and `FAQ_JP.md` to address the question: "Why is the read count of the Control sample lower in the output BAM file?". [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/b238d21fbb7cd3330a147bdde65b726278447649)]
+
+## 🔧 Maintenance
+
++ Integrating insertion and inversion detection: Issue #31
+  + Add sv_handler [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/d994d845b0b8ed0fa8affed7992f1d95bf163073)]
+
+  + Modify arguments of `is_insertion` to `is_sv` [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/f2d3dc4ca2dff60fc869fb1f5b6b08f54490b564)]
+
+  + Remame `insertions_to_fasta.generate_insertions_fasta` to `insertion_detector.detect_insertions` because the function is not only for generating fasta files but also for generating csv tag. [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/63c9d63bad627f529f272ea90c035e236f9dd1fb)]
+
++ Remove unused dependencies
+  + `networkx`: Issue #49 [[Commit Detail](https://github.com/akikuno/DAJIN2/commit/524186bdce9e28d6357378d0baeb45670d2e22ed)]
+
+</details>
+
+
+
 <details>
 <summary> v0.5.4 (2024-07-23) </summary>
 
diff --git a/pyproject.toml b/pyproject.toml
index 26424c34..d17a10e3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
 
 [tool.poetry]
 name = "DAJIN2"
-version = "0.5.5.1"
+version = "0.5.6"
 description = "One-step genotyping tools for targeted long-read sequencing"
 authors = ["Akihiro Kuno <akuno@md.tsukuba.ac.jp>"]
 readme = "README.md"
@@ -27,7 +27,7 @@ include = [
 ]
 
 [tool.poetry.dependencies]
-python = "^3.8"
+python = "^3.9"
 numpy = ">=1.24.0"
 pandas = ">=1.3.0"
 scipy = ">=1.10.0"
@@ -71,6 +71,7 @@ select = [
     "UP", # pyupgrade
 ]
 ignore = [
+    "E402", # module level import not at top of file
     "E501", # line too long, handled by black
     "B008", # do not perform function calls in argument defaults
     "C901", # too complex
@@ -96,6 +97,6 @@ split-on-trailing-comma = true
 [tool.ruff.format]
 quote-style = "double"
 
-
-[tool.ruff.lint.pyupgrade]
-keep-runtime-typing = true # Settings for Python 3.8 compatibility
+[tool.mypy]
+ignore_missing_imports = true
+check_untyped_defs = true
diff --git a/src/DAJIN2/core/clustering/clustering.py b/src/DAJIN2/core/clustering/clustering.py
index c179a455..6c12bc22 100644
--- a/src/DAJIN2/core/clustering/clustering.py
+++ b/src/DAJIN2/core/clustering/clustering.py
@@ -6,10 +6,9 @@
 
 import numpy as np
 from scipy.sparse import csr_matrix, spmatrix
-from sklearn import metrics
-from sklearn.cluster import BisectingKMeans
+from sklearn.cluster import AgglomerativeClustering, KMeans
+from sklearn.metrics import silhouette_score
 
-from DAJIN2.core.clustering.constrained_kmeans import ConstrainedKMeans
 from DAJIN2.core.clustering.score_handler import subset_scores
 from DAJIN2.core.clustering.strand_bias_handler import remove_biased_clusters
 from DAJIN2.utils import config, io
@@ -23,47 +22,31 @@
 
 
 def count_number_of_clusters(labels_control: list[int], coverage_control: int) -> int:
-    """If there is less than 1% lead within a cluster, they are considered clustering errors and are not counted"""
+    # If there is less than 1% lead within a cluster, they are considered clustering errors and are not counted
     return sum(1 for control_reads in Counter(labels_control).values() if control_reads / coverage_control > 0.01)
 
 
-def optimize_labels(X: spmatrix, coverage_sample: int, coverage_control: int, min_cluster_size: int) -> list[int]:
-    labels_previous = [1 for _ in range(coverage_sample)]
-    positive_silhouette_previous = 1
+def optimize_labels(X: spmatrix, coverage_sample: int, coverage_control: int) -> list[int]:
+    labels_best = [1 for _ in range(coverage_sample)]
+    prev_silhouette_score = -1
 
-    for i in range(2, coverage_sample):
+    for n_clusters in range(2, coverage_sample):
         np.random.seed(seed=1)
-        labels_all = (
-            ConstrainedKMeans(n_clusters=i, min_cluster_size=min_cluster_size, random_state=1).fit_predict(X).tolist()
-        )
+        labels_all = AgglomerativeClustering(n_clusters=n_clusters).fit_predict(X).tolist()
 
         labels_sample = labels_all[:coverage_sample]
         labels_control = labels_all[coverage_sample:]
 
-        min_cluster_size_ = min(Counter(labels_sample).values())
-
         num_labels_control = count_number_of_clusters(labels_control, coverage_control)
 
-        silhouette_scores = metrics.silhouette_samples(X, labels_all, metric="euclidean")
-        positive_silhouette_current = sum(silhouette_scores > 0.25)
-        silhouette_ratio = positive_silhouette_current / positive_silhouette_previous
-
-        # print(
-        #     i,
-        #     min_cluster_size,
-        #     Counter(labels_control),
-        #     Counter(labels_sample),
-        #     silhouette_ratio,
-        #     positive_silhouette_current,
-        #     positive_silhouette_previous,
-        # )  # ! DEBUG
-
-        if min_cluster_size_ < min_cluster_size - 1 or num_labels_control >= 2 or silhouette_ratio < 0.95:
+        current_silhouette_score = silhouette_score(X, labels_all)
+
+        if prev_silhouette_score > current_silhouette_score or num_labels_control > 1:
             break
 
-        labels_previous = labels_sample
-        positive_silhouette_previous = max(positive_silhouette_previous, positive_silhouette_current)
-    return labels_previous
+        labels_best = labels_sample
+        prev_silhouette_score = current_silhouette_score
+    return labels_best
 
 
 def get_label_most_common(labels: list[int]) -> int:
@@ -71,19 +54,15 @@ def get_label_most_common(labels: list[int]) -> int:
 
 
 def return_labels(
-    path_score_sample: Path,
-    path_score_control: Path,
-    path_sample: Path,
-    strand_bias_in_control: bool,
-    min_cluster_size: int,
+    path_score_sample: Path, path_score_control: Path, path_sample: Path, strand_bias_in_control: bool
 ) -> list[int]:
     np.random.seed(seed=1)
 
     score_control = list(io.read_jsonl(path_score_control))
     X_control = csr_matrix(score_control)
 
-    """Subset to 1000 reads of controls in the most common cluster to remove outliers and reduce computation time"""
-    labels_control = BisectingKMeans(n_clusters=2, random_state=1).fit_predict(X_control)
+    # Subset to 1000 reads of controls in the most common cluster to remove outliers and reduce computation time
+    labels_control = KMeans(n_clusters=2, random_state=1).fit_predict(X_control)
     label_most_common = get_label_most_common(labels_control)
     scores_control_subset = subset_scores(labels_control, io.read_jsonl(path_score_control), label_most_common, 1000)
 
@@ -92,9 +71,9 @@ def return_labels(
 
     coverage_sample = io.count_newlines(path_score_sample)
     coverage_control = len(scores_control_subset)
-    labels = optimize_labels(X, coverage_sample, coverage_control, min_cluster_size)
+    labels = optimize_labels(X, coverage_sample, coverage_control)
 
-    """Re-allocate clusters with strand bias to clusters without strand bias"""
+    # Re-allocate clusters with strand bias to clusters without strand bias
     if strand_bias_in_control is False:
         labels = remove_biased_clusters(path_sample, path_score_sample, labels)
 
diff --git a/src/DAJIN2/core/clustering/kmer_generator.py b/src/DAJIN2/core/clustering/kmer_generator.py
index c9d41476..2006f024 100644
--- a/src/DAJIN2/core/clustering/kmer_generator.py
+++ b/src/DAJIN2/core/clustering/kmer_generator.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
+from collections.abc import Iterator
 from pathlib import Path
-from typing import Generator
 
 from DAJIN2.utils import io
 
@@ -34,7 +34,7 @@ def annotate_cs_tag(cssplit: str, mutation: set[str]) -> str:
 
 def generate_mutation_kmers(
     path_sample: Path | str, mutation_loci: list[set[str]], compress_ins: bool = True
-) -> Generator[list[str]]:
+) -> Iterator[list[str]]:
     midsv_sample = io.read_jsonl(path_sample)
     for cssplit in (cs["CSSPLIT"].split(",") for cs in midsv_sample):
         mutation_kmers = ["@,@,@"]
diff --git a/src/DAJIN2/core/clustering/label_extractor.py b/src/DAJIN2/core/clustering/label_extractor.py
index d74bcdea..9f0bc27c 100644
--- a/src/DAJIN2/core/clustering/label_extractor.py
+++ b/src/DAJIN2/core/clustering/label_extractor.py
@@ -22,7 +22,7 @@ def extract_labels(classif_sample, TEMPDIR, SAMPLE_NAME, CONTROL_NAME) -> list[d
     for allele, group in groupby(classif_sample, key=lambda x: x["ALLELE"]):
         # Cache data to temporary files
 
-        # FOr Insertion/Inversion allele
+        # For Insertion/Inversion allele
         path_control = Path(TEMPDIR, CONTROL_NAME, "midsv", allele, f"{SAMPLE_NAME}.jsonl")
         if not path_control.exists():
             path_control = Path(TEMPDIR, CONTROL_NAME, "midsv", allele, f"{CONTROL_NAME}.jsonl")
@@ -58,7 +58,7 @@ def extract_labels(classif_sample, TEMPDIR, SAMPLE_NAME, CONTROL_NAME) -> list[d
         io.write_jsonl(data=scores_control, file_path=path_score_control)
 
         # Extract labels
-        labels = return_labels(path_score_sample, path_score_control, path_sample, strand_bias, min_cluster_size)
+        labels = return_labels(path_score_sample, path_score_control, path_sample, strand_bias)
         labels_result += relabel_with_consective_order(labels, start=max_label)
 
         # Remove temporary files
diff --git a/src/DAJIN2/core/clustering/score_handler.py b/src/DAJIN2/core/clustering/score_handler.py
index 6e0db9ee..b6a80d47 100644
--- a/src/DAJIN2/core/clustering/score_handler.py
+++ b/src/DAJIN2/core/clustering/score_handler.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
 from collections import Counter
+from collections.abc import Iterator
 from itertools import groupby
-from typing import Generator
 
 from DAJIN2.core.clustering.kmer_generator import generate_mutation_kmers
 
@@ -12,7 +12,7 @@ def subset_scores(labels: list[int], scores: list[int], label_most: int, length:
     return subset[:length]
 
 
-def call_count(cssplits: Generator[list[str]]) -> list[dict[str, int]]:
+def call_count(cssplits: Iterator[list[str]]) -> list[dict[str, int]]:
     return [dict(Counter(cssplit)) for cssplit in zip(*cssplits)]
 
 
@@ -133,7 +133,7 @@ def make_score(
 ###############################################################################
 
 
-def annotate_score(path_sample, mutation_score, mutation_loci, is_control=False) -> Generator[list[float]]:
+def annotate_score(path_sample, mutation_score, mutation_loci, is_control=False) -> Iterator[list[float]]:
     for cssplit_kmer in generate_mutation_kmers(path_sample, mutation_loci):
         score = [0 for _ in range(len(cssplit_kmer))]
         for i, (cs_kmer, mut_score) in enumerate(zip(cssplit_kmer, mutation_score)):
diff --git a/src/DAJIN2/core/clustering/strand_bias_handler.py b/src/DAJIN2/core/clustering/strand_bias_handler.py
index e057f3f9..e3fb4afa 100644
--- a/src/DAJIN2/core/clustering/strand_bias_handler.py
+++ b/src/DAJIN2/core/clustering/strand_bias_handler.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
 from collections import defaultdict
+from collections.abc import Iterator
 from pathlib import Path
-from typing import Generator
 
 from sklearn.tree import DecisionTreeClassifier
 
@@ -42,7 +42,7 @@ def is_strand_bias(path_control: Path) -> bool:
 ###############################################################################
 
 
-def count_strand(labels: list[int], samples: Generator[dict[str, str]]) -> tuple[dict[str, int], dict[str, int]]:
+def count_strand(labels: list[int], samples: Iterator[dict[str, str]]) -> tuple[dict[str, int], dict[str, int]]:
     """Count the occurrences of each strand type by label."""
     positive_strand_counts_by_labels = defaultdict(int)
     total_counts_by_labels = defaultdict(int)
diff --git a/src/DAJIN2/core/consensus/clust_formatter.py b/src/DAJIN2/core/consensus/clust_formatter.py
index 47187cd4..b5e04e48 100644
--- a/src/DAJIN2/core/consensus/clust_formatter.py
+++ b/src/DAJIN2/core/consensus/clust_formatter.py
@@ -14,8 +14,16 @@ def remove_minor_alleles(clust_sample: list[dict]) -> list[dict]:
         counts[c["LABEL"]] += 1
 
     minor_labels = {label for label, count in counts.items() if count < min_sample_size}
+    clust_sample_removed = [c for c in clust_sample if c["LABEL"] not in minor_labels]
 
-    return [c for c in clust_sample if c["LABEL"] not in minor_labels]
+    # Make the remaining clusters account for 100%.
+    percents = {c["PERCENT"] for c in clust_sample_removed}
+    percents_100 = {p: round(p * 100 / sum(percents), 3) for p in percents}
+
+    for c in clust_sample_removed:
+        c["PERCENT"] = percents_100[c["PERCENT"]]
+
+    return clust_sample_removed
 
 
 def downsample_by_label(clust_sample: list[dict], num: int = 1000) -> list[dict]:
diff --git a/src/DAJIN2/core/core.py b/src/DAJIN2/core/core.py
index 3b079fd3..9b147508 100644
--- a/src/DAJIN2/core/core.py
+++ b/src/DAJIN2/core/core.py
@@ -20,7 +20,7 @@ def execute_control(arguments: dict):
     logger.info(f"{arguments['control']} is now processing...")
 
     ###########################################################
-    # Preprocess
+    # Setup arguments
     ###########################################################
     ARGS: FormattedInputs = preprocess.format_inputs(arguments)
     preprocess.create_temporal_directories(ARGS.tempdir, ARGS.control_name, is_control=True)
@@ -37,30 +37,54 @@ def execute_control(arguments: dict):
     logger.info(f"Preprocess {arguments['control']}...")
 
     ###########################################################
-    # Merge fastq files
+    # Concatenate fastq files
+    ###########################################################
+
+    fastx_handler.save_inputs_as_single_fastq(ARGS, is_control=True)
+
+    path_fastq = Path(ARGS.tempdir, ARGS.control_name, "fastq", f"{ARGS.control_name}.fastq.gz")
+    fastx_handler.overwrite_with_downsampled_fastq(path_fastq, num_reads=20_000)
+
+    ###########################################################
+    # Export fasta files as single-FASTA format
     ###########################################################
+    fastx_handler.export_fasta_files(ARGS, is_control=True)
+
+    ###########################################################
+    # Separate fastq files by sequence error
+    ###########################################################
+    paths_fasta = Path(ARGS.tempdir, ARGS.control_name, "fasta").glob("control.fasta")
+    preprocess.generate_sam(ARGS, paths_fasta, is_control=True, is_sv=False)
+    preprocess.generate_midsv(ARGS, is_control=True, is_sv=False)
+    # ============================================================
+    # Detect sequence error reads
+    # ============================================================
+    preprocess.detect_sequence_error_reads(ARGS, is_control=True)
+    preprocess.split_fastq_by_sequence_error(ARGS, is_control=True)
 
-    fastx_handler.save_inputs_as_single_fastx(ARGS.tempdir, ARGS.path_control, ARGS.control_name)
+    # ============================================================
     # Save subsetted fastq if the read number is too large (> 10,000 reads)
-    fastx_handler.save_subsetted_fastx(
-        Path(ARGS.tempdir, ARGS.control_name, "fastq", f"{ARGS.control_name}.fastq.gz"), num_reads=10_000
+    # ============================================================
+    path_fastq = Path(ARGS.tempdir, ARGS.control_name, "fastq", f"{ARGS.control_name}.fastq.gz")
+    path_fastq_with_error = Path(
+        ARGS.tempdir, ARGS.control_name, "fastq", f"{ARGS.control_name}_sequence_error.fastq.gz"
     )
+    fastx_handler.overwrite_with_downsampled_fastq(path_fastq, num_reads=10_000)
+    fastx_handler.overwrite_with_downsampled_fastq(path_fastq_with_error, num_reads=10_000)
+
     ###########################################################
-    # Mapping
+    # Mapping filtered control reads
     ###########################################################
 
-    # Export fasta files as single-FASTA format
-    fastx_handler.export_fasta_files(ARGS.tempdir, ARGS.fasta_alleles, ARGS.control_name)
-
     # ============================================================
     # Mapping using mappy
     # ============================================================
     paths_fasta = Path(ARGS.tempdir, ARGS.control_name, "fasta").glob("*.fasta")
     preprocess.generate_sam(ARGS, paths_fasta, is_control=True, is_sv=False)
 
-    ###########################################################
+    # ============================================================
     # MIDSV conversion
-    ###########################################################
+    # ============================================================
     preprocess.generate_midsv(ARGS, is_control=True, is_sv=False)
 
     ###########################################################
@@ -73,7 +97,7 @@ def execute_control(arguments: dict):
     ###########################################################
     logger.info(f"Output BAM files of {arguments['control']}...")
     report.bam_exporter.export_to_bam(
-        ARGS.tempdir, ARGS.control_name, ARGS.genome_coordinates, ARGS.threads, ARGS.uuid, is_control=True
+        ARGS.tempdir, ARGS.control_name, ARGS.genome_coordinates, ARGS.threads, is_control=True
     )
     ###########################################################
     # Finish call
@@ -98,7 +122,11 @@ def execute_sample(arguments: dict):
     # Merge fastq files
     # ============================================================
 
-    fastx_handler.save_inputs_as_single_fastx(ARGS.tempdir, ARGS.path_sample, ARGS.sample_name)
+    fastx_handler.save_inputs_as_single_fastq(ARGS, is_control=False)
+
+    # Save subsetted fastq if the read number is too large (> 10,000 reads)
+    path_fastq = Path(ARGS.tempdir, ARGS.sample_name, "fastq", f"{ARGS.sample_name}.fastq.gz")
+    fastx_handler.overwrite_with_downsampled_fastq(path_fastq, num_reads=10_000)
 
     # ============================================================
     # Mapping with mappy
@@ -116,6 +144,13 @@ def execute_sample(arguments: dict):
 
     preprocess.generate_midsv(ARGS, is_control=False, is_sv=False)
 
+    ###########################################################
+    # Remove sequence error reads from MIDSV files
+    ###########################################################
+    preprocess.detect_sequence_error_reads(ARGS, is_control=False)
+    preprocess.split_fastq_by_sequence_error(ARGS, is_control=False)
+    preprocess.replace_midsv_without_sequence_errors(ARGS)
+
     # ============================================================
     # Extract mutation loci
     # ============================================================
@@ -130,8 +165,11 @@ def execute_sample(arguments: dict):
     }
 
     preprocess.detect_insertions(ARGS.tempdir, ARGS.sample_name, ARGS.control_name, ARGS.fasta_alleles)
+    # preprocess.detect_inversions(ARGS.tempdir, ARGS.sample_name, ARGS.control_name, ARGS.fasta_alleles) #TODO
 
     paths_sv_fasta = {str(p) for p in Path(ARGS.tempdir, ARGS.sample_name, "fasta").glob("insertion*.fasta")}
+    # paths_sv_fasta |= {str(p) for p in Path(ARGS.tempdir, ARGS.sample_name, "fasta").glob("inversion*.fasta")}
+
     paths_sv_fasta -= paths_predefined_fasta
 
     if paths_sv_fasta:
@@ -222,7 +260,7 @@ def execute_sample(arguments: dict):
     report.mutation_exporter.export_to_csv(ARGS.tempdir, ARGS.sample_name, ARGS.genome_coordinates, cons_percentage)
     # BAM
     report.bam_exporter.export_to_bam(
-        ARGS.tempdir, ARGS.sample_name, ARGS.genome_coordinates, ARGS.threads, ARGS.uuid, RESULT_SAMPLE
+        ARGS.tempdir, ARGS.sample_name, ARGS.genome_coordinates, ARGS.threads, RESULT_SAMPLE
     )
     for path_bam_igvjs in Path(ARGS.tempdir, "cache", ".igvjs").glob(f"{ARGS.control_name}_control.bam*"):
         shutil.copy(path_bam_igvjs, Path(ARGS.tempdir, "report", ".igvjs", ARGS.sample_name))
diff --git a/src/DAJIN2/core/preprocess/__init__.py b/src/DAJIN2/core/preprocess/__init__.py
index 97e03d78..01bea7bf 100644
--- a/src/DAJIN2/core/preprocess/__init__.py
+++ b/src/DAJIN2/core/preprocess/__init__.py
@@ -8,3 +8,8 @@
 from DAJIN2.core.preprocess.mapping import generate_sam
 from DAJIN2.core.preprocess.midsv_caller import generate_midsv
 from DAJIN2.core.preprocess.mutation_extractor import cache_mutation_loci
+from DAJIN2.core.preprocess.sequence_error_handler import (
+    detect_sequence_error_reads,
+    replace_midsv_without_sequence_errors,
+    split_fastq_by_sequence_error,
+)
diff --git a/src/DAJIN2/core/preprocess/insertion_detector.py b/src/DAJIN2/core/preprocess/insertion_detector.py
index 4a7177a4..62daf08c 100644
--- a/src/DAJIN2/core/preprocess/insertion_detector.py
+++ b/src/DAJIN2/core/preprocess/insertion_detector.py
@@ -3,9 +3,9 @@
 import random
 import uuid
 from collections import Counter, defaultdict
+from collections.abc import Iterator
 from itertools import groupby
 from pathlib import Path
-from typing import Generator
 
 import cstag
 import numpy as np
@@ -43,7 +43,7 @@ def clustering_insertions(cssplits_insertion: list[str]) -> list[int]:
 ###########################################################
 
 
-def extract_all_insertions(midsv_sample: Generator, mutation_loci: list[set[str]]) -> dict[int, list[str]]:
+def extract_all_insertions(midsv_sample: Iterator, mutation_loci: list[set[str]]) -> dict[int, list[str]]:
     """To extract insertion sequences of **10 base pairs or more** at each index."""
     insertion_index_sequences_control = defaultdict(list)
     for m_sample in midsv_sample:
@@ -172,7 +172,7 @@ def remove_minor_groups(
         for seq in to_delete:
             del ins[seq]
 
-    return insertions_merged
+    return {key: value for key, value in insertions_merged.items() if value}
 
 
 def merge_similar_insertions(
@@ -341,7 +341,7 @@ def get_cstag_position(sam_insertions: list[str]) -> tuple[list[str], list[int]]
 
 def mapping_insertion(
     TEMPDIR: Path, SAMPLE_NAME: str, cs_transposed: list[str], consensus_length: int, uuid4: str
-) -> Generator[str]:
+) -> Iterator[str]:
     # Temporarily cache the reference sequence
     cs_insertion = next(cs for cs in cs_transposed if cs != "N" and len(cs) == consensus_length)
     ref_seq = cstag.to_sequence(convert_cssplits_to_cstag([cs_insertion]))
@@ -537,8 +537,8 @@ def detect_insertions(TEMPDIR, SAMPLE_NAME, CONTROL_NAME, FASTA_ALLELES) -> None
 
     # Update labels
     cstag_insertions_update = {key: cstag_insertions[key] for key in fasta_insertions_unique.keys()}
-    cstag_insertions_update = add_unique_allele_keys(cstag_insertions_update, FASTA_ALLELES, "insertion")
-    fasta_insertions_update = add_unique_allele_keys(fasta_insertions_unique, FASTA_ALLELES, "insertion")
+    cstag_insertions_update = add_unique_allele_keys(cstag_insertions_update, FASTA_ALLELES, key="insertion")
+    fasta_insertions_update = add_unique_allele_keys(fasta_insertions_unique, FASTA_ALLELES, key="insertion")
 
     save_cstag(TEMPDIR, SAMPLE_NAME, cstag_insertions_update)
     save_fasta(TEMPDIR, SAMPLE_NAME, fasta_insertions_update)
diff --git a/src/DAJIN2/core/preprocess/inversion_detector.py b/src/DAJIN2/core/preprocess/inversion_detector.py
index b99039de..3af0071e 100644
--- a/src/DAJIN2/core/preprocess/inversion_detector.py
+++ b/src/DAJIN2/core/preprocess/inversion_detector.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
+from collections.abc import Iterator
 from itertools import groupby
 from pathlib import Path
-from typing import Generator
 
 import numpy as np
 from rapidfuzz import process
@@ -23,7 +23,7 @@
 ###########################################################
 
 
-def extract_inversions(midsv_sample: Generator[list[str]]) -> list[dict[int, int, str, str]]:
+def extract_inversions(midsv_sample: Iterator[list[str]]) -> list[dict[int, int, str, str]]:
     """Extract sequence of inversions and their start and end indices."""
     inversions = []
     for m_sample in midsv_sample:
@@ -56,7 +56,7 @@ def _convert_sequences_to_distances(sequences: list[str]) -> list[float]:
     return distances
 
 
-def _convert_to_scores(start_end: Generator[int, int], distances: list[float]) -> list[int, int, float]:
+def _convert_to_scores(start_end: Iterator[int, int], distances: list[float]) -> list[int, int, float]:
     scores = []
     for (start, end), dist in zip(start_end, distances):
         scores.append([start, end, dist])
diff --git a/src/DAJIN2/core/preprocess/mapping.py b/src/DAJIN2/core/preprocess/mapping.py
index f440bec7..41dc0c02 100644
--- a/src/DAJIN2/core/preprocess/mapping.py
+++ b/src/DAJIN2/core/preprocess/mapping.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
+from collections.abc import Iterator
 from pathlib import Path
-from typing import Generator
 
 import cstag
 import mappy
@@ -16,7 +16,7 @@ def to_sam(
     threads: int = 1,
     options: dict = None,
     cslong: bool = True,
-) -> Generator[str]:
+) -> Iterator[str]:
     """Align sequences using mappy and Convert PAF to SAM.
 
     Args:
@@ -116,10 +116,7 @@ def generate_sam(
 
     for path_fasta in paths_fasta:
         name_fasta = Path(path_fasta).stem
-        len_sequence = len(Path(path_fasta).read_text().split("\n")[1])
-        presets = ["map-ont", "splice"]
-        if len_sequence < 500:
-            presets += ["sr"]
+        presets = ["map-ont", "map-hifi", "sr"]
 
         path_sam_directory = Path(ARGS.tempdir, name, "sam", name_fasta)
         path_sam_directory.mkdir(parents=True, exist_ok=True)
diff --git a/src/DAJIN2/core/preprocess/midsv_caller.py b/src/DAJIN2/core/preprocess/midsv_caller.py
index 064fd16d..c154ebdb 100644
--- a/src/DAJIN2/core/preprocess/midsv_caller.py
+++ b/src/DAJIN2/core/preprocess/midsv_caller.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
 from collections import Counter, defaultdict
+from collections.abc import Iterator
 from pathlib import Path
-from typing import Generator
 
 import midsv
 
@@ -40,6 +40,7 @@ def extract_preset_and_cigar_by_qname(path_sam_files: list[Path]) -> dict[dict[s
 
 
 def extract_best_preset(preset_cigar_by_qname: dict[str, dict[str, str]]) -> dict[str, str]:
+    """Select the preset with the longest alignment length."""
     best_preset = defaultdict(str)
     for qname in preset_cigar_by_qname:
         preset_cigar = preset_cigar_by_qname[qname]
@@ -71,7 +72,7 @@ def custom_key(key: str, alignment_lengths=alignment_lengths) -> tuple[int, bool
 
 def extract_best_alignment_length_from_sam(
     path_sam_files: list[Path], best_preset: dict[str, str]
-) -> Generator[list[str]]:
+) -> Iterator[list[str]]:
     flag_header = False
     for path in path_sam_files:
         preset = path.stem
@@ -87,11 +88,11 @@ def extract_best_alignment_length_from_sam(
         flag_header = True
 
 
-def transform_to_midsv_format(sam: Generator[list[str]]) -> Generator[list[dict]]:
+def transform_to_midsv_format(sam: Iterator[list[str]]) -> Iterator[list[dict]]:
     yield from midsv.transform(sam, midsv=False, cssplit=True, qscore=False, keep={"FLAG"})
 
 
-def replace_internal_n_to_d(midsv_sample: Generator[list[dict]], sequence: str) -> Generator[list[dict]]:
+def replace_internal_n_to_d(midsv_sample: Iterator[list[dict]], sequence: str) -> Iterator[list[dict]]:
     """
     Replace internal 'N's with 'D' in a given sequence.
     This function modifies the 'CSSPLIT' field in the input sample. It identifies
@@ -120,7 +121,7 @@ def is_reverse_strand(flag: int) -> bool:
     return bool(flag & 16)
 
 
-def convert_flag_to_strand(midsv_sample: Generator[list[dict]]) -> Generator[list[dict]]:
+def convert_flag_to_strand(midsv_sample: Iterator[list[dict]]) -> Iterator[list[dict]]:
     """Convert FLAG to STRAND (+ or -)"""
     for samp in midsv_sample:
         samp["STRAND"] = "-" if is_reverse_strand(int(samp["FLAG"])) else "+"
@@ -128,8 +129,8 @@ def convert_flag_to_strand(midsv_sample: Generator[list[dict]]) -> Generator[lis
         yield samp
 
 
-def filter_samples_by_n_proportion(midsv_sample: Generator[dict], threshold: int = 95) -> Generator[list[dict]]:
-    """Filters out the samples from the input generator where the proportion of 'N' in the 'CSSPLIT' field is 95% or higher."""
+def filter_samples_by_n_proportion(midsv_sample: Iterator[dict], threshold: int = 95) -> Iterator[list[dict]]:
+    """Filters out the samples from the input Iterator where the proportion of 'N' in the 'CSSPLIT' field is 95% or higher."""
     for samp in midsv_sample:
         cssplits = samp.get("CSSPLIT", "").split(",")
         count = Counter(cssplits)
@@ -185,7 +186,7 @@ def convert_consecutive_indels_to_match(cssplit: str) -> str:
     return ",".join(cssplit_reversed[::-1])
 
 
-def convert_consecutive_indels(midsv_sample: Generator) -> Generator[list[dict]]:
+def convert_consecutive_indels(midsv_sample: Iterator) -> Iterator[list[dict]]:
     """
     Due to alignment errors, there can be instances where a true match is mistakenly replaced with "insertion following a deletion".
     For example, although it should be "=C,=T", it gets replaced by "-C,+C|=T". In such cases, a process is performed to revert it back to "=C,=T".
@@ -204,6 +205,9 @@ def generate_midsv(ARGS, is_control: bool = False, is_sv: bool = False) -> None:
     name = ARGS.control_name if is_control else ARGS.sample_name
 
     for allele, sequence in ARGS.fasta_alleles.items():
+        if not Path(ARGS.tempdir, name, "sam", allele).exists():
+            continue
+
         path_midsv_directory = Path(ARGS.tempdir, name, "midsv", allele)
         path_midsv_directory.mkdir(parents=True, exist_ok=True)
 
@@ -212,13 +216,13 @@ def generate_midsv(ARGS, is_control: bool = False, is_sv: bool = False) -> None:
 
         if is_control and is_sv:
             """
-            Set the destination for midsv as `barcode01/midsv/insertion1_barcode02.json` when control is barcode01, sample is barcode02, and the allele is insertion1.
+            Set the destination for midsv as `barcode01/midsv/insertion1_barcode02.jsonl` when control is barcode01, sample is barcode02, and the allele is insertion1.
             """
             path_sam_files = list(Path(ARGS.tempdir, name, "sam", allele).glob(f"{ARGS.sample_name}_*.sam"))
             path_midsv_output = Path(ARGS.tempdir, name, "midsv", allele, f"{ARGS.sample_name}.jsonl")
         else:
             """
-            Set the destination for midsv as `barcode02/midsv/insertion1.json` when the sample is barcode02 and the allele is insertion1.
+            Set the destination for midsv as `barcode02/midsv/insertion1.jsonl` when the sample is barcode02 and the allele is insertion1.
             """
             path_sam_files = list(Path(ARGS.tempdir, name, "sam", allele).glob("*.sam"))
             path_midsv_output = Path(ARGS.tempdir, name, "midsv", allele, f"{name}.jsonl")
diff --git a/src/DAJIN2/core/preprocess/mutation_extractor.py b/src/DAJIN2/core/preprocess/mutation_extractor.py
index dd5ca241..ef1040ee 100644
--- a/src/DAJIN2/core/preprocess/mutation_extractor.py
+++ b/src/DAJIN2/core/preprocess/mutation_extractor.py
@@ -3,8 +3,8 @@
 import bisect
 import re
 from collections import defaultdict
+from collections.abc import Iterator
 from pathlib import Path
-from typing import Generator
 
 from DAJIN2.utils import config
 
@@ -22,7 +22,7 @@
 from DAJIN2.utils import io
 
 
-def count_indels(midsv_sample: Generator[dict], sequence: str) -> dict[str, list[int]]:
+def count_indels(midsv_sample: Iterator[dict], sequence: str) -> dict[str, list[int]]:
     len_sequence = len(sequence)
     count = {"=": [0] * len_sequence, "+": [0] * len_sequence, "-": [0] * len_sequence, "*": [0] * len_sequence}
     for samp in midsv_sample:
@@ -315,7 +315,7 @@ def extract_mutation_loci(
     is_consensus: bool = False,
 ) -> list[set[str]]:
     if thresholds is None:
-        thresholds = {"*": 0.5, "-": 0.5, "+": 0.5}
+        thresholds = {"*": 0.1, "-": 0.1, "+": 0.1}
     indels_normalized_sample = io.load_pickle(path_indels_normalized_sample)
 
     # Extract candidate mutation loci
diff --git a/src/DAJIN2/core/preprocess/sequence_error_handler.py b/src/DAJIN2/core/preprocess/sequence_error_handler.py
new file mode 100644
index 00000000..e4fdacde
--- /dev/null
+++ b/src/DAJIN2/core/preprocess/sequence_error_handler.py
@@ -0,0 +1,174 @@
+from __future__ import annotations
+
+import gzip
+import random
+from pathlib import Path
+
+import numpy as np
+from rapidfuzz.distance import JaroWinkler
+from rapidfuzz.process import cdist
+from scipy.sparse import hstack
+from sklearn.cluster import KMeans
+from sklearn.feature_extraction.text import TfidfVectorizer
+
+from DAJIN2.utils import io
+from DAJIN2.utils.fastx_handler import read_fastq
+
+###############################################################################
+# Detect sequence errors
+###############################################################################
+
+
+def convert_nm_tag(csv_tags: list[list[str]]) -> str:
+    """Create a tag with N for bases that reads are truncated and M for bases that are mapped"""
+    return "".join(["N" if tag.startswith("N") or tag.startswith("n") else "M" for tag in csv_tags])
+
+
+def detect_sequence_error_reads_in_control(ARGS) -> None:
+    # Convert CSV strings to MIDSV tags
+    midsv_control = io.read_jsonl(
+        Path(ARGS.tempdir, ARGS.control_name, "midsv", "control", f"{ARGS.control_name}.jsonl")
+    )
+    nm_tags, qnames = zip(*[(convert_nm_tag(m["CSSPLIT"].split(",")), m["QNAME"]) for m in midsv_control])
+
+    # Vectorize the MIDSV tags using TF-IDF with character-level N-grams
+    vectorizer = TfidfVectorizer(analyzer="char", ngram_range=(100, 100))
+    X = vectorizer.fit_transform(nm_tags)
+    # Add a feature for the number of matches in the X
+    match_counts = np.array([tag.count("M") for tag in nm_tags], dtype=int)
+    X = hstack([X, match_counts.reshape(-1, 1)])
+
+    # Apply KMeans clustering for binary classification based on the similarity of MIDSV.
+    kmeans = KMeans(n_clusters=2, random_state=1)
+    labels = kmeans.fit_predict(X)
+
+    # Initialize lists for match counts of each label
+    match_counts = {0: [], 1: []}
+
+    # Count occurrences of "M" for each label
+    for midsv_tag, label in zip(nm_tags, labels):
+        match_counts[label].append(midsv_tag.count("M"))
+
+    # Determine which label corresponds to the sequences with fewer matches and treat them as sequence errors.
+    error_label = 0 if np.median(match_counts[0]) < np.median(match_counts[1]) else 1
+
+    # Collect QNAMEs of sequences with or without sequence errors
+    qnames_with_sequence_error = [qname for qname, label in zip(qnames, labels) if label == error_label]
+    qnames_without_sequence_error = [qname for qname, label in zip(qnames, labels) if label != error_label]
+
+    error_fraction = len(qnames_with_sequence_error) / len(qnames)
+
+    # Output QNAMEs of sequences with sequence errors and the fraction of sequence errors
+    Path(ARGS.tempdir, ARGS.control_name, "sequence_error").mkdir(parents=True, exist_ok=True)
+    path_qnames_with_sequence_error = Path(
+        ARGS.tempdir, ARGS.control_name, "sequence_error", "qnames_with_sequence_error.txt"
+    )
+    path_qnames_with_sequence_error.write_text("\n".join(qnames_with_sequence_error) + "\n")
+    path_qnames_without_sequence_error = Path(
+        ARGS.tempdir, ARGS.control_name, "sequence_error", "qnames_without_sequence_error.txt"
+    )
+    path_qnames_without_sequence_error.write_text("\n".join(qnames_without_sequence_error) + "\n")
+    path_error_fraction = Path(ARGS.tempdir, ARGS.control_name, "sequence_error", "error_fraction.txt")
+    path_error_fraction.write_text(str(error_fraction) + "\n")
+
+
+def detect_sequence_error_reads_in_sample(ARGS) -> None:
+    path_qnames_without_sequence_error = Path(
+        ARGS.tempdir, ARGS.control_name, "sequence_error", "qnames_with_sequence_error.txt"
+    )
+    qnames_with_sequence_error_control = set(path_qnames_without_sequence_error.read_text().splitlines())
+
+    midsv_control = io.read_jsonl(
+        Path(ARGS.tempdir, ARGS.control_name, "midsv", "control", f"{ARGS.control_name}.jsonl")
+    )
+    midsv_errors = (m for m in midsv_control if m["QNAME"] in qnames_with_sequence_error_control)
+    nm_tags_error = [convert_nm_tag(m["CSSPLIT"].split(",")) for m in midsv_errors]
+
+    # ランダムに100本のエラー配列を取得
+    random.seed(1)
+    nm_tags_error = random.sample(nm_tags_error, min(len(nm_tags_error), 100))
+
+    path_midsv_sample = Path(ARGS.tempdir, ARGS.sample_name, "midsv", "control", f"{ARGS.sample_name}.jsonl")
+    midsv_sample = io.read_jsonl(path_midsv_sample)
+    nm_tags_sample = [convert_nm_tag(m["CSSPLIT"].split(",")) for m in midsv_sample]
+
+    similarity_scores = cdist(nm_tags_sample, nm_tags_error, scorer=JaroWinkler.normalized_similarity)
+    most_similar_scores = np.max(similarity_scores, axis=1)
+
+    midsv_sample = io.read_jsonl(path_midsv_sample)
+    qnames_without_sequence_error = [m["QNAME"] for m, score in zip(midsv_sample, most_similar_scores) if score < 0.99]
+    # Output QNAMEs of sequences with sequence errors and the fraction of sequence errors
+    Path(ARGS.tempdir, ARGS.sample_name, "sequence_error").mkdir(parents=True, exist_ok=True)
+    path_qnames_without_sequence_error = Path(
+        ARGS.tempdir, ARGS.sample_name, "sequence_error", "qnames_without_sequence_error.txt"
+    )
+    path_qnames_without_sequence_error.write_text("\n".join(qnames_without_sequence_error) + "\n")
+
+
+def detect_sequence_error_reads(ARGS, is_control: bool = False) -> None:
+    if is_control:
+        detect_sequence_error_reads_in_control(ARGS)
+    else:
+        detect_sequence_error_reads_in_sample(ARGS)
+
+
+###############################################################################
+# Split FASTQ by sequence error
+###############################################################################
+
+
+def split_fastq_by_sequence_error(ARGS, is_control: bool = False) -> None:
+    if is_control:
+        NAME = ARGS.control_name
+    else:
+        NAME = ARGS.sample_name
+
+    path_qnames_without_sequence_error = Path(
+        ARGS.tempdir, NAME, "sequence_error", "qnames_without_sequence_error.txt"
+    )
+    qnames_without_error = set(path_qnames_without_sequence_error.read_text().splitlines())
+
+    path_fastq = Path(ARGS.tempdir, NAME, "fastq", f"{NAME}.fastq.gz")
+    path_fastq_error = Path(ARGS.tempdir, NAME, "fastq", f"{NAME}_sequence_error.fastq.gz")
+
+    fastq: list[dict] = read_fastq(path_fastq)
+
+    # -----------------------------------------------------
+    # Split FASTQ by sequence error
+    # -----------------------------------------------------
+    fastq_passed = []
+    fastq_error = []
+    for fastq_record in fastq:
+        qname = fastq_record["identifier"].split()[0][1:]
+        if qname in qnames_without_error:
+            fastq_passed.append(fastq_record)
+        else:
+            fastq_error.append(fastq_record)
+
+    # -----------------------------------------------------
+    # Output FASTQ files
+    # -----------------------------------------------------
+    with gzip.open(path_fastq, "wt") as f:
+        for read in fastq_passed:
+            f.write(f"{read['identifier']}\n{read['sequence']}\n{read['separator']}\n{read['quality']}\n")
+
+    with gzip.open(path_fastq_error, "wt") as f:
+        for read in fastq_error:
+            f.write(f"{read['identifier']}\n{read['sequence']}\n{read['separator']}\n{read['quality']}\n")
+
+
+###############################################################################
+# Replace MIDSV files without sequence errors
+###############################################################################
+
+
+def replace_midsv_without_sequence_errors(ARGS) -> None:
+    path_qnames_without_sequence_error = Path(
+        ARGS.tempdir, ARGS.sample_name, "sequence_error", "qnames_without_sequence_error.txt"
+    )
+    qnames_without_sequence_error = set(path_qnames_without_sequence_error.read_text().splitlines())
+
+    for path_midsv in Path(ARGS.tempdir, ARGS.sample_name, "midsv").glob(f"*/{ARGS.sample_name}.jsonl"):
+        midsv_sample = io.read_jsonl(path_midsv)
+        midsv_sample_filtered = [m for m in midsv_sample if m["QNAME"] in qnames_without_sequence_error]
+        io.write_jsonl(midsv_sample_filtered, path_midsv)
diff --git a/src/DAJIN2/core/report/bam_exporter.py b/src/DAJIN2/core/report/bam_exporter.py
index 78959003..adaa0d35 100644
--- a/src/DAJIN2/core/report/bam_exporter.py
+++ b/src/DAJIN2/core/report/bam_exporter.py
@@ -1,11 +1,13 @@
 from __future__ import annotations
 
+import uuid
 from collections import defaultdict
 from itertools import groupby
 from pathlib import Path
 
 import pysam
 
+from DAJIN2.core.preprocess.mapping import to_sam
 from DAJIN2.utils import io, sam_handler
 
 
@@ -18,40 +20,43 @@ def recalculate_sam_coodinates_to_reference(sam: list[list[str]], GENOME_COODINA
             continue
         s[1] = f'SN:{GENOME_COODINATES["chrom"]}'
         s[2] = f'LN:{GENOME_COODINATES["chrom_size"]}'
+
     for s in sam_contents:
         s[2] = GENOME_COODINATES["chrom"]
+
     if GENOME_COODINATES["strand"] == "-":
         sam_contents = sam_handler.revcomp_sam(sam_contents, GENOME_COODINATES["end"])
     else:
         for s in sam_contents:
             s[3] = str(int(s[3]) + GENOME_COODINATES["start"] - 1)
+
     return sam_headers + sam_contents
 
 
 def convert_pos_to_one_indexed(sam_lines: list[list[str]]) -> list[list[str]]:
     """Convert SAM POS from 0-indexed to 1-indexed"""
 
-    def convert_line(line: list[str]) -> list[str]:
+    def convert_index(line: list[str]) -> list[str]:
         if not line[0].startswith("@") and line[3] == "0":
             line[3] = "1"
         return line
 
-    return [convert_line(line) for line in sam_lines]
+    return [convert_index(line) for line in sam_lines]
 
 
-def group_by_name(sam_contents: list[str], clust_sample: list[dict]) -> dict[list]:
+def group_by_allele_name(sam_contents: list[str], result_sample: list[dict]) -> dict[list]:
     """Group alignments in map-ont.sam by allele name (NAME)"""
     sam_contents.sort()
-    clust_sample_sorted = sorted(clust_sample, key=lambda x: x["QNAME"])
+    result_sample_sorted = sorted(result_sample, key=lambda x: x["QNAME"])
 
-    qnames: set[str] = {c["QNAME"] for c in clust_sample_sorted}
+    qnames: set[str] = {c["QNAME"] for c in result_sample_sorted}
 
     sam_groups = defaultdict(list)
     idx_sam_contents = 0
-    idx_clust_sample = 0
-    while idx_sam_contents < len(sam_contents) and idx_clust_sample < len(clust_sample_sorted):
+    idx_result_sample = 0
+    while idx_sam_contents < len(sam_contents) and idx_result_sample < len(result_sample_sorted):
         alignments_sam = sam_contents[idx_sam_contents][:-1]  # Discard CS tags to reduce file size
-        alignments_clsut_sample = clust_sample_sorted[idx_clust_sample]
+        alignments_clsut_sample = result_sample_sorted[idx_result_sample]
         qname_sam = alignments_sam[0]
 
         if qname_sam not in qnames:
@@ -63,7 +68,7 @@ def group_by_name(sam_contents: list[str], clust_sample: list[dict]) -> dict[lis
             sam_groups[key].append(alignments_sam)
             idx_sam_contents += 1
         else:
-            idx_clust_sample += 1
+            idx_result_sample += 1
 
     return dict(sam_groups)
 
@@ -73,9 +78,9 @@ def group_by_name(sam_contents: list[str], clust_sample: list[dict]) -> dict[lis
 ###############################################################################
 
 
-def subset_qnames(RESULT_SAMPLE, readnum: int = 100) -> dict[set[str]]:
+def subset_qnames(result_sample: list[dict], readnum: int = 100) -> dict[set[str]]:
     qnames_by_name = defaultdict(set)
-    for name, group in groupby(RESULT_SAMPLE, key=lambda x: x["NAME"]):
+    for name, group in groupby(result_sample, key=lambda x: x["NAME"]):
         group = list(group)
         qnames = [res["QNAME"] for res in group[:readnum]]
         qnames_by_name[name] = set(qnames)
@@ -91,14 +96,27 @@ def subset_reads(sam_content: list[str], qnames: set[str]) -> list[str]:
 ###############################################################################
 
 
-def write_sam_to_bam(sam: list[list[str]], path_sam: str | Path, path_bam: str | Path, threads: int = 1) -> None:
+def split_headers_and_contents(sam: list) -> tuple:
+    """Split SAM headers and contents."""
+    sam_headers = [s for s in sam if s[0].startswith("@")]
+    sam_contents = [s for s in sam if not s[0].startswith("@")]
+    return sam_headers, sam_contents
+
+
+def write_sam_to_bam(sam: list[list[str]], path_bam: str | Path, threads: int = 1) -> None:
+    # Format SAM
     formatted_sam = "\n".join("\t".join(s) for s in sam)
+    # Write SAM to a temporary file
+    path_sam = Path(path_bam.parent, f"temp_{uuid.uuid4()}.sam")
     Path(path_sam).write_text(formatted_sam + "\n")
+    # Convert SAM to BAM
     pysam.sort("-@", f"{threads}", "-o", str(path_bam), str(path_sam))
     pysam.index("-@", f"{threads}", str(path_bam))
+    # Remove temporary file
+    path_sam.unlink()
 
 
-def update_sam(sam: list, GENOME_COODINATES: dict = None) -> list:
+def update_genome_coodinates(sam: list, GENOME_COODINATES: dict = None) -> list:
     if GENOME_COODINATES is None:
         GENOME_COODINATES = {}
 
@@ -110,45 +128,50 @@ def update_sam(sam: list, GENOME_COODINATES: dict = None) -> list:
         return convert_pos_to_one_indexed(sam_records)
 
 
-def export_to_bam(TEMPDIR, NAME, GENOME_COODINATES, THREADS, UUID, RESULT_SAMPLE=None, is_control=False) -> None:
+def export_sequence_error_to_bam(TEMPDIR, NAME, GENOME_COODINATES, THREADS) -> None:
+    path_fastq = Path(TEMPDIR, NAME, "fastq", f"{NAME}_sequence_error.fastq.gz")
+    path_fasta = Path(TEMPDIR, NAME, "fasta", "control.fasta")
+    preset = "map-ont"
+
+    sam_records = [record.split("\t") for record in to_sam(path_fasta, path_fastq, preset=preset, threads=THREADS)]
+    sam_updated = update_genome_coodinates(sam_records, GENOME_COODINATES)
+
+    # Output SAM and BAM
+    path_bam_output = Path(TEMPDIR, "report", "BAM", NAME, f"{NAME}_sequence_error.bam")
+    write_sam_to_bam(sam_updated, path_bam_output, THREADS)
+
+
+def export_to_bam(TEMPDIR, NAME, GENOME_COODINATES, THREADS, RESULT_SAMPLE=None, is_control=False) -> None:
     path_sam_input = Path(TEMPDIR, NAME, "sam", "control", "map-ont.sam")
-    if not path_sam_input.exists():  # In the case of short-read.
-        path_sam_input = Path(TEMPDIR, NAME, "sam", "control", "sr.sam")
 
     sam_records = list(io.read_sam(path_sam_input))
-
-    # Update sam
-    sam_updated = update_sam(sam_records, GENOME_COODINATES)
+    sam_updated = update_genome_coodinates(sam_records, GENOME_COODINATES)
 
     # Output SAM and BAM
-    path_sam_output = Path(TEMPDIR, "report", "BAM", f"temp_{UUID}_{NAME}_control.sam")
     path_bam_output = Path(TEMPDIR, "report", "BAM", NAME, f"{NAME}.bam")
-    write_sam_to_bam(sam_updated, path_sam_output, path_bam_output, THREADS)
+    write_sam_to_bam(sam_updated, path_bam_output, THREADS)
 
     # Prepare SAM headers and contents
-    sam_headers = [s for s in sam_updated if s[0].startswith("@")]
-    sam_contents = [s for s in sam_updated if not s[0].startswith("@")]
+    sam_headers, sam_contents = split_headers_and_contents(sam_updated)
+
     if is_control:
-        qnames_100reads: set[str] = set(list({s[0] for s in sam_contents[:10000]})[:100])  # subset 100 reads
+        # subset 100 reads with uqniue qnames for igv.js
+        qnames_100reads: set[str] = set(list({s[0] for s in sam_contents[:10000]})[:100])
         sam_subset = [s for s in sam_updated if s[0] in qnames_100reads]
-        path_sam_output = Path(TEMPDIR, "report", "BAM", f"temp_{UUID}_{NAME}_control_cache.sam")
         path_bam_output = Path(TEMPDIR, "cache", ".igvjs", NAME, "control.bam")
-        write_sam_to_bam(sam_headers + sam_subset, path_sam_output, path_bam_output, THREADS)
+        write_sam_to_bam(sam_headers + sam_subset, path_bam_output, THREADS)
     else:
-        sam_groups = group_by_name(sam_contents, RESULT_SAMPLE)
+        sam_groups = group_by_allele_name(sam_contents, RESULT_SAMPLE)
         qnames_by_name = subset_qnames(RESULT_SAMPLE)
         # Output SAM and BAM
         for name, sam_content in sam_groups.items():
             # BAM
-            path_sam_output = Path(TEMPDIR, "report", "BAM", f"temp_{UUID}_{name}.sam")
             path_bam_output = Path(TEMPDIR, "report", "BAM", NAME, f"{NAME}_{name}.bam")
-            write_sam_to_bam(sam_headers + sam_content, path_sam_output, path_bam_output, THREADS)
+            write_sam_to_bam(sam_headers + sam_content, path_bam_output, THREADS)
             # igvjs
             sam_subset = subset_reads(sam_content, qnames_by_name[name])
-            path_sam_output = Path(TEMPDIR, "report", "BAM", f"temp_{UUID}_{name}_subset.sam")
             path_bam_output = Path(TEMPDIR, "report", ".igvjs", NAME, f"{name}.bam")
-            write_sam_to_bam(sam_headers + sam_subset, path_sam_output, path_bam_output, THREADS)
+            write_sam_to_bam(sam_headers + sam_subset, path_bam_output, THREADS)
 
-    # Remove temporary files
-    sam_temp = Path(TEMPDIR, "report", "BAM").glob(f"temp_{UUID}*.sam")
-    [s.unlink() for s in sam_temp]
+    # Export sequence error to BAM
+    export_sequence_error_to_bam(TEMPDIR, NAME, GENOME_COODINATES, THREADS)
diff --git a/src/DAJIN2/main.py b/src/DAJIN2/main.py
index fc2b0b5d..6f9a27f0 100644
--- a/src/DAJIN2/main.py
+++ b/src/DAJIN2/main.py
@@ -8,8 +8,8 @@
 os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
 os.environ["NUMEXPR_NUM_THREADS"] = "1"
 
-
 import argparse
+import importlib.metadata
 import logging
 import shutil
 import sys
@@ -21,6 +21,11 @@
 from DAJIN2.core import core
 from DAJIN2.utils import config, input_validator, io, multiprocess, report_generator
 
+try:
+    DAJIN_VERSION = importlib.metadata.version("DAJIN2")
+except importlib.metadata.PackageNotFoundError:
+    DAJIN_VERSION = "counld not be retrieved"
+
 
 def generate_report(name: str, logger: logging.Logger) -> None:
     report_generator.report(name)
@@ -36,7 +41,7 @@ def execute_single_mode(arguments: dict[str]):
     # Set logging to export log to stderr and file
     path_logfile = config.get_logfile()
     logger = config.set_logging(path_logfile)
-    logger.info(f"\N{RUNNER} Start running DAJIN2 version {config.DAJIN_VERSION}")
+    logger.info(f"\N{RUNNER} Start running DAJIN2 version {DAJIN_VERSION}")
     logger.info(f"\N{PERSONAL COMPUTER} {' '.join(sys.argv)}")
 
     # Validate input files
@@ -146,7 +151,7 @@ def execute_batch_mode(arguments: dict[str]):
         config.reset_logging()
         path_logfile = config.get_logfile()
         logger = config.set_logging(path_logfile)
-        logger.info(f"\N{RUNNER} Start running DAJIN2 version {config.DAJIN_VERSION}")
+        logger.info(f"\N{RUNNER} Start running DAJIN2 version {DAJIN_VERSION}")
         logger.info(f"\N{PERSONAL COMPUTER} {' '.join(sys.argv)}")
 
         # Run DAJIN2
@@ -175,7 +180,7 @@ def execute():
         "-g", "--genome", type=str, default="", help="Reference genome ID (e.g hg38, mm39) [default: '']"
     )
     parser.add_argument("-t", "--threads", type=int, default=1, help="Number of threads [default: 1]")
-    parser.add_argument("-v", "--version", action="version", version=f"DAJIN2 version {config.DAJIN_VERSION}")
+    parser.add_argument("-v", "--version", action="version", version=f"DAJIN2 version {DAJIN_VERSION}")
     parser.add_argument("-d", "--debug", action="store_true", help=argparse.SUPPRESS)
 
     ###############################################################################
diff --git a/src/DAJIN2/utils/config.py b/src/DAJIN2/utils/config.py
index 2c51206a..d09c6bb3 100644
--- a/src/DAJIN2/utils/config.py
+++ b/src/DAJIN2/utils/config.py
@@ -8,7 +8,6 @@
 
 from sklearn.exceptions import ConvergenceWarning
 
-DAJIN_VERSION = "0.5.5.1"
 DAJIN_RESULTS_DIR = Path("DAJIN_Results")
 TEMP_ROOT_DIR = Path(DAJIN_RESULTS_DIR, ".tempdir")
 
diff --git a/src/DAJIN2/utils/fastx_handler.py b/src/DAJIN2/utils/fastx_handler.py
index 41763be5..420fbb5c 100644
--- a/src/DAJIN2/utils/fastx_handler.py
+++ b/src/DAJIN2/utils/fastx_handler.py
@@ -4,11 +4,14 @@
 import random
 import re
 import uuid
+from collections.abc import Iterator
+from itertools import islice
 from pathlib import Path
 
 import mappy
 import pysam
-from DAJIN2.utils.io import sanitize_name
+
+from DAJIN2.utils.io import detect_fastx_format, is_gzip_file, read_fasta, read_fastq, sanitize_name, write_fastq
 
 #################################################
 # Extract filename
@@ -35,12 +38,18 @@ def dictionize_allele(path_fasta: str | Path) -> dict[str, str]:
 #################################################
 
 
-def export_fasta_files(TEMPDIR: Path, FASTA_ALLELES: dict, NAME: str) -> None:
+def export_fasta_files(ARGS, is_control: bool = False) -> None:
     """Save multiple FASTAs in separate single-FASTA format files."""
-    for identifier, sequence in FASTA_ALLELES.items():
+    tempdir = ARGS.tempdir
+    if is_control:
+        sample_name = ARGS.control_name
+    else:
+        sample_name = ARGS.sample_name
+
+    for identifier, sequence in ARGS.fasta_alleles.items():
         identifier = sanitize_name(identifier)
         contents = "\n".join([">" + identifier, sequence]) + "\n"
-        path_output_fasta = Path(TEMPDIR, NAME, "fasta", f"{identifier}.fasta")
+        path_output_fasta = Path(tempdir, sample_name, "fasta", f"{identifier}.fasta")
         path_output_fasta.write_text(contents)
 
 
@@ -54,13 +63,16 @@ def extract_extention(path_file: str | Path) -> str:
     return "".join(suffixes)
 
 
-def is_gzip_file(path_file: str | Path) -> bool:
-    """Check if a file is a GZip compressed file."""
-    try:
-        with Path(path_file).open("rb") as f:
-            return f.read(2) == b"\x1f\x8b"
-    except OSError:
-        return False
+def convert_bam_to_fastq(path_bam: str | Path, path_fastq: str | Path, quality_score: str = "I"):
+    """Convert a BAM file to a gzipped FASTQ file."""
+    with pysam.AlignmentFile(str(path_bam), "rb") as bam_file, gzip.open(path_fastq, "wt") as fastq_file:
+        for read in bam_file:
+            if read.is_unmapped:
+                continue
+            if read.qual is None:  # FASTA format
+                read.qual = quality_score * len(read.query_sequence)
+            fastq_entry = f"@{read.query_name}\n{read.query_sequence}\n+\n{read.qual}\n"
+            fastq_file.write(fastq_entry)
 
 
 def save_bam_files_to_single_fastq(TEMPDIR: Path, path_bam_files: list[Path], sample_name: str) -> None:
@@ -69,72 +81,88 @@ def save_bam_files_to_single_fastq(TEMPDIR: Path, path_bam_files: list[Path], sa
     pysam.merge("-f", "-o", str(path_concatenated_bam), *path_bam_files)
 
     path_concatenated_fastq = Path(TEMPDIR, sample_name, "fastq", f"{sample_name}.fastq.gz")
-    _ = pysam.fastq("-0", str(path_concatenated_fastq), str(path_concatenated_bam))
+    convert_bam_to_fastq(path_concatenated_fastq, str(path_concatenated_bam))
 
     path_concatenated_bam.unlink()
 
 
-def save_fastx_files_to_single_fastq(TEMPDIR: Path, path_input_files: list[Path], sample_name: str) -> None:
-    """Merge gzip and non-gzip files into a single gzip file."""
-    with gzip.open(Path(TEMPDIR, sample_name, "fastq", f"{sample_name}.fastq.gz"), "wb") as merged_file:
-        for path_file in path_input_files:
-            if is_gzip_file(path_file):
-                with gzip.open(path_file, "rb") as f:
-                    merged_file.write(f.read())
+def merge_fastx_files_to_single_fastx(TEMPDIR: Path, path_input_files: list[Path], sample_name: str) -> None:
+    """Merge gzip and non-gzip FASTX files into a single gzip FASTX file."""
+    path_concatenated_fastx = Path(TEMPDIR, sample_name, "fastq", f"{sample_name}.fastx.gz")
+    with gzip.open(path_concatenated_fastx, "wt") as output_file:
+        for path_input_file in path_input_files:
+            if is_gzip_file(path_input_file):
+                with gzip.open(path_input_file, "rt") as input_file:
+                    for line in input_file:
+                        output_file.write(line)
             else:
-                with open(path_file) as f:
-                    merged_file.write(f.read().encode())
+                with open(path_input_file) as input_file:
+                    for line in input_file:
+                        output_file.write(line)
+
+
+def convert_fasta_to_fastq(path_fasta: str | Path, path_fastq: str | Path, quality_score: str = "I"):
+    """Convert a FASTA file to a gzipped FASTQ file with a default quality score."""
+    fasta: Iterator[dict[str, str]] = read_fasta(path_fasta)
+    with gzip.open(path_fastq, "wt") as fastq_file:
+        for record in fasta:
+            identifier = record["identifier"]
+            if record["identifier"].startswith(">"):
+                identifier = f'@{record["identifier"][1:]}'
+            sequence = record["sequence"]
+            separator = "+"
+            quality = quality_score * len(sequence)
+            fastq_file.write(f"{identifier}\n{sequence}\n{separator}\n{quality}\n")
+
+
+def save_fastx_files_to_single_fastq(TEMPDIR: Path, path_input_files: list[Path], sample_name: str) -> None:
+    merge_fastx_files_to_single_fastx(TEMPDIR, path_input_files, sample_name)
+    path_concatenated_fastx = Path(TEMPDIR, sample_name, "fastq", f"{sample_name}.fastx.gz")
+    path_concatenated_fastq = Path(TEMPDIR, sample_name, "fastq", f"{sample_name}.fastq.gz")
+    if detect_fastx_format == "FASTA":
+        convert_fasta_to_fastq(path_concatenated_fastx, path_concatenated_fastq)
+        path_concatenated_fastx.unlink()
+    else:
+        path_concatenated_fastx.rename(path_concatenated_fastq)
+    path_concatenated_fastx.unlink(missing_ok=True)
 
 
-def save_inputs_as_single_fastx(TEMPDIR: Path, path_directory: Path, sample_name: str) -> None:
+def save_inputs_as_single_fastq(ARGS, is_control: bool = False) -> None:
+    tempdir = ARGS.tempdir
+    if is_control:
+        path_directory = ARGS.path_control
+        sample_name = ARGS.control_name
+    else:
+        path_directory = ARGS.path_sample
+        sample_name = ARGS.sample_name
+
     file_suffix = {".fa", ".fq", ".fasta", ".fastq", ".fa.gz", ".fq.gz", ".fasta.gz", ".fastq.gz", ".bam"}
     path_input_files = [path for path in path_directory.iterdir() if extract_extention(path) in file_suffix]
     if all(extract_extention(path) == ".bam" for path in path_directory.iterdir()):
-        save_bam_files_to_single_fastq(TEMPDIR, path_input_files, sample_name)
+        save_bam_files_to_single_fastq(tempdir, path_input_files, sample_name)
     else:
-        save_fastx_files_to_single_fastq(TEMPDIR, path_input_files, sample_name)
+        save_fastx_files_to_single_fastq(tempdir, path_input_files, sample_name)
 
 
 #################################################
-# save_subsetted_fastx
+# overwrite_with_downsampled_fastq
 #################################################
 
 
-def read_lines(path_file) -> list[str]:
-    if is_gzip_file(path_file):
-        with gzip.open(path_file, "rt") as f:
-            return [line.strip() for line in f]
-    else:
-        with open(path_file) as f:
-            return [line.strip() for line in f]
-
-
-def parse_fastq(fastq) -> list[dict]:
-    fastq_parsed = []
-    iterator = iter(fastq)
-    try:
-        while True:
-            header = next(iterator).strip()
-            sequence = next(iterator).strip()
-            annotate = next(iterator).strip()
-            quality = next(iterator).strip()
-            fastq_parsed.append({"header": header, "sequence": sequence, "annotate": annotate, "quality": quality})
-    except StopIteration:
-        pass
-    return fastq_parsed
-
-
-def save_subsetted_fastx(path_fastq: str | Path, num_reads: int = 10_000) -> None:
-    """If the number of control reads is too high, it unnecessarily slows down the computation speed. Therefore, we perform random sampling to reduce the number of reads to below 10,000."""
+def is_iterator_length_below_limits(iterator: Iterator, num_limits: int):
+    return sum(1 for _ in islice(iterator, num_limits + 1)) <= num_limits
 
-    fastq: list[str] = read_lines(path_fastq)
-    if len(fastq) // 4 <= 10_000:
-        return None
 
-    fastq: list[dict] = parse_fastq(fastq)
+def overwrite_with_downsampled_fastq(path_fastq: str | Path, num_reads=10_000) -> None:
+    """If the number of control reads is too high, it unnecessarily slows down the computation speed. Therefore, we perform random sampling to reduce the number of reads to below 10,000."""
 
     random.seed(1)
-    fastq_subset = random.sample(fastq, num_reads)
-    with gzip.open(path_fastq, "wt") as f:
-        for read in fastq_subset:
-            f.write(f"{read['header']}\n{read['sequence']}\n{read['annotate']}\n{read['quality']}\n")
+
+    num_limits = num_reads * 4
+
+    if is_iterator_length_below_limits(read_fastq(path_fastq), num_limits):
+        return None
+
+    reads = list(read_fastq(path_fastq))
+    sampled_reads = random.sample(reads, num_reads)
+    write_fastq(sampled_reads, path_fastq, is_gzip=True)
diff --git a/src/DAJIN2/utils/io.py b/src/DAJIN2/utils/io.py
index 42ce91dd..bde76b69 100644
--- a/src/DAJIN2/utils/io.py
+++ b/src/DAJIN2/utils/io.py
@@ -1,13 +1,14 @@
 from __future__ import annotations
 
 import csv
+import gzip
 import hashlib
 import json
 import pickle
 import re
+from collections.abc import Iterator
 from io import BufferedReader
 from pathlib import Path
-from typing import Generator
 
 import wslPath
 from openpyxl import Workbook, load_workbook
@@ -17,12 +18,20 @@
 ###########################################################
 
 
-def read_sam(path_of_sam: str | Path) -> Generator[list]:
+# =========================================================
+# SAM/BAM
+# =========================================================
+def read_sam(path_of_sam: str | Path) -> Iterator[list]:
     with open(path_of_sam) as f:
         for line in f:
             yield line.strip().split("\t")
 
 
+# =========================================================
+# Pickle
+# =========================================================
+
+
 def load_pickle(file_path: Path):
     with open(file_path, "rb") as f:
         return pickle.load(f)
@@ -33,19 +42,64 @@ def save_pickle(data: object, file_path: Path) -> None:
         pickle.dump(data, f)
 
 
-def read_jsonl(file_path: str | Path) -> Generator[dict]:
+# =========================================================
+# JSONL
+# =========================================================
+
+
+def read_jsonl(file_path: str | Path) -> Iterator[dict]:
     with open(file_path) as f:
         for line in f:
             yield json.loads(line)
 
 
-def write_jsonl(data: list[dict], file_path: str | Path) -> None:
+def write_jsonl(data: list[dict] | Iterator[dict], file_path: str | Path) -> None:
     with open(file_path, "w") as f:
         for d in data:
             json_str = json.dumps(d)
             f.write(json_str + "\n")
 
 
+# =========================================================
+# Table (Excel, CSV)
+# =========================================================
+
+
+def read_xlsx(file_path: str | Path) -> list[dict[str, str]]:
+    """Load data from an Excel file."""
+    wb = load_workbook(filename=file_path)
+    ws = wb.active
+
+    headers = list(next(ws.iter_rows(min_row=1, max_row=1, values_only=True)))
+
+    records = []
+    for row in ws.iter_rows(min_row=2, values_only=True):
+        if all(element is None for element in row):  # Skip rows with all None values
+            continue
+        row_data = {headers[i]: (row[i] if i < len(row) else None) for i in range(len(headers))}
+        records.append(row_data)
+
+    return records
+
+
+def read_csv(file_path: str | Path) -> list[dict[str, str]]:
+    """Load data from a CSV file."""
+    with open(file_path) as csvfile:
+        header = [field.strip() for field in next(csv.reader(csvfile))]
+
+        records = []
+        for row in csv.reader(csvfile):
+            if not row:  # Skip empty rows
+                continue
+            if all(element is None for element in row):  # Skip rows with all None values
+                continue
+            row_trimmed = [field.strip() for field in row]
+            row_data = dict(zip(header, row_trimmed))
+            records.append(row_data)
+
+        return records
+
+
 def write_xlsx(data: list[dict[str, str]], file_path: str | Path) -> None:
     # Create a workbook and a worksheet
     wb = Workbook()
@@ -76,6 +130,84 @@ def write_xlsx(data: list[dict[str, str]], file_path: str | Path) -> None:
     wb.save(file_path)
 
 
+# =========================================================
+# FASTA / FASTQ
+# =========================================================
+
+
+def is_gzip_file(path_file: str | Path) -> bool:
+    """Check if a file is a GZip compressed file."""
+    try:
+        with Path(path_file).open("rb") as f:
+            return f.read(2) == b"\x1f\x8b"
+    except OSError:
+        return False
+
+
+def read_lines(path_fastx: str | Path) -> Iterator[str]:
+    if is_gzip_file(path_fastx):
+        with gzip.open(path_fastx, "rt") as f:
+            yield from [line.strip() for line in f]
+    else:
+        with open(path_fastx) as f:
+            yield from [line.strip() for line in f]
+
+
+def detect_fastx_format(path_fastx: str | Path) -> str:
+    fastx_iterator = read_lines(path_fastx)
+    lines = [next(fastx_iterator).strip() for _ in range(4)]
+
+    if lines[0].startswith(">"):
+        return "FASTA"
+    elif lines[0].startswith("@") and lines[2].startswith("+"):
+        return "FASTQ"
+    raise ValueError("File format is unknown. The input does not appear to be in FASTA or FASTQ format.")
+
+
+def read_fastq(path_fastx: str | Path) -> Iterator[dict]:
+    """Read a FASTQ file and yield each record as a dictionary."""
+    iterator = read_lines(path_fastx)
+    try:
+        while True:
+            identifier = next(iterator).strip()
+            sequence = next(iterator).strip()
+            separator = next(iterator).strip()
+            quality = next(iterator).strip()
+            yield {"identifier": identifier, "sequence": sequence, "separator": separator, "quality": quality}
+    except StopIteration:
+        return
+
+
+def read_fasta(path_fasta: str | Path) -> Iterator[dict]:
+    """Read a FASTA file and yield each record as a dictionary."""
+    iterator = read_lines(path_fasta)
+    try:
+        while True:
+            identifier = next(iterator).strip()
+            sequence = next(iterator).strip()
+            yield {"identifier": identifier, "sequence": sequence}
+    except StopIteration:
+        return
+
+
+def write_fasta(data: list[dict] | Iterator[dict], file_path: str | Path, is_gzip: bool = True) -> None:
+    open_func = gzip.open if is_gzip else open
+    mode = "wt" if is_gzip else "w"
+
+    with open_func(file_path, mode) as f:
+        for record in data:
+            f.write(f"{record['identifier']}\n{record['sequence']}\n")
+
+
+def write_fastq(data: list[dict] | Iterator[dict], file_path: str | Path, is_gzip: bool = True) -> None:
+    open_func = gzip.open if is_gzip else open
+    mode = "wt" if is_gzip else "w"
+
+    with open_func(file_path, mode) as f:
+        for record in data:
+            f.write(f"{record['identifier']}\n{record['sequence']}\n{record['separator']}\n{record['quality']}\n")
+
+
 ###########################################################
 # Load batch file
 ###########################################################
@@ -92,41 +224,6 @@ def determine_file_type(file_path: str) -> str | None:
         raise ValueError("The provided file must be either an Excel or CSV file.")
 
 
-def read_xlsx(file_path: str | Path) -> list[dict[str, str]]:
-    """Load data from an Excel file."""
-    wb = load_workbook(filename=file_path)
-    ws = wb.active
-
-    headers = list(next(ws.iter_rows(min_row=1, max_row=1, values_only=True)))
-
-    records = []
-    for row in ws.iter_rows(min_row=2, values_only=True):
-        if all(element is None for element in row):  # Skip rows with all None values
-            continue
-        row_data = {headers[i]: (row[i] if i < len(row) else None) for i in range(len(headers))}
-        records.append(row_data)
-
-    return records
-
-
-def read_csv(file_path: str | Path) -> list[dict[str, str]]:
-    """Load data from a CSV file."""
-    with open(file_path) as csvfile:
-        header = [field.strip() for field in next(csv.reader(csvfile))]
-
-        records = []
-        for row in csv.reader(csvfile):
-            if not row:  # Skip empty rows
-                continue
-            if all(element is None for element in row):  # Skip rows with all None values
-                continue
-            row_trimmed = [field.strip() for field in row]
-            row_data = dict(zip(header, row_trimmed))
-            records.append(row_data)
-
-        return records
-
-
 def load_batchfile(batchfile_path: str) -> list[dict[str, str]]:
     """Load data from either an Excel or CSV file."""
     file_type = determine_file_type(batchfile_path)
@@ -142,8 +239,8 @@ def load_batchfile(batchfile_path: str) -> list[dict[str, str]]:
 
 
 def count_newlines(filepath: str | Path) -> int:
-    def read_in_chunks(file: BufferedReader, chunk_size: int = 2**16) -> Generator[bytes, None, None]:
-        """Get a generator that reads a file in chunks and yields each chunk."""
+    def read_in_chunks(file: BufferedReader, chunk_size: int = 2**16) -> Iterator[bytes, None, None]:
+        """Get a Iterator that reads a file in chunks and yields each chunk."""
         while True:
             chunk = file.read(chunk_size)
             if not chunk:
diff --git a/src/DAJIN2/utils/multiprocess.py b/src/DAJIN2/utils/multiprocess.py
index 0a2aecf7..8ef532c2 100644
--- a/src/DAJIN2/utils/multiprocess.py
+++ b/src/DAJIN2/utils/multiprocess.py
@@ -4,10 +4,9 @@
 import logging
 import sys
 import traceback
-from collections.abc import Callable
+from collections.abc import Callable, Iterator
 from itertools import islice
 from multiprocessing import Process, Queue
-from typing import Generator
 
 
 def get_error_message_prefix(arg: dict) -> str:
@@ -15,7 +14,7 @@ def get_error_message_prefix(arg: dict) -> str:
     return f"An unexpected error occurred at {arg['sample']}"
 
 
-def generate_chunks(iterable, chunk_size: int = 1) -> Generator[tuple]:
+def generate_chunks(iterable, chunk_size: int = 1) -> Iterator[tuple]:
     """Yield successive n-sized chunks from iterable."""
     iterator = iter(iterable)
     while True:
diff --git a/src/DAJIN2/utils/report_generator.py b/src/DAJIN2/utils/report_generator.py
index 20ea6da2..fd93dc88 100644
--- a/src/DAJIN2/utils/report_generator.py
+++ b/src/DAJIN2/utils/report_generator.py
@@ -4,6 +4,7 @@
 from pathlib import Path
 
 import plotly.express as px
+
 from DAJIN2.utils import io
 from DAJIN2.utils.config import DAJIN_RESULTS_DIR, TEMP_ROOT_DIR
 


Test patch:
diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml
deleted file mode 100644
index 19ac50b1..00000000
--- a/.github/workflows/cli-tests.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: cli-tests
-
-on:
-  push:
-  schedule:
-    - cron: "0 2 * * 1" # The task runs at 2 a.m. every Monday.
-
-jobs:
-  build:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      max-parallel: 1
-      matrix:
-        os: [ubuntu-latest]
-        python-version: ['3.10']
-    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
-
-    defaults:
-      run:
-        shell: bash -l {0}
-
-    steps:
-      - uses: actions/checkout@v4
-      - uses: conda-incubator/setup-miniconda@v3
-        with:
-          python-version: ${{ matrix.python-version }}
-          miniconda-version: "latest"
-          activate-environment: test-env
-          channels: bioconda, conda-forge, defaults, anaconda
-
-      - name: Setup conda environment on ubuntu-latest🐧
-        if: runner.os == 'Linux'
-        run: |
-          conda create -y -n env-dajin2 python=${{ matrix.python-version }}
-
-      - name: Install dependencies
-        run: |
-          conda activate env-dajin2
-
-          # ==========================================
-          # Use pip because conda does not support pyproject.toml.
-          # ==========================================
-          pip install -U pip pytest
-          pip install -e .
-
-      ##########################################
-      # Execute tests
-      ##########################################
-
-      - name: Run tests version
-        run: |
-          conda activate env-dajin2
-          sh tests/test_version.sh
diff --git a/.github/workflows/e2e_cables2_flox.yml b/.github/workflows/e2e_cables2_flox.yml
new file mode 100644
index 00000000..b5697dc5
--- /dev/null
+++ b/.github/workflows/e2e_cables2_flox.yml
@@ -0,0 +1,51 @@
+name: E2E test for Cables2 flox
+
+on:
+  push:
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      max-parallel: 2
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        python-version: ["3.13"]
+    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Miniconda
+        uses: conda-incubator/setup-miniconda@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+          miniconda-version: "latest"
+          activate-environment: test-env
+          channels: bioconda, conda-forge, defaults
+
+      - name: Create and activate conda environment
+        run: |
+          conda create -y -n env-dajin2 python=${{ matrix.python-version }} pip pytest
+          conda activate env-dajin2
+
+      - name: Install DAJIN2 and dependencies
+        run: |
+          pip install -e .
+
+      - name: Download and prepare example dataset
+        run: |
+          if [ ! -d example_flox ]; then
+            curl -LJO https://github.com/akikuno/DAJIN2/raw/main/examples/example_flox.tar.gz
+            tar -xf example_flox.tar.gz
+          fi
+
+      - name: Execute DAJIN2 batch test
+        env:
+          PYTHONPATH: src
+        run: |
+          time DAJIN2 batch --file example_flox/batch.xlsx --threads 4 --debug || exit 1
diff --git a/.github/workflows/e2e_stx2_deletion.yml b/.github/workflows/e2e_stx2_deletion.yml
new file mode 100644
index 00000000..20d68ee6
--- /dev/null
+++ b/.github/workflows/e2e_stx2_deletion.yml
@@ -0,0 +1,58 @@
+name: E2E test for Stx2 deletion
+
+on:
+  push:
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      max-parallel: 2
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        python-version: ["3.13"]
+    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Miniconda
+        uses: conda-incubator/setup-miniconda@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+          miniconda-version: "latest"
+          activate-environment: test-env
+          channels: bioconda, conda-forge, defaults
+
+      - name: Create and activate conda environment
+        run: |
+          conda create -y -n env-dajin2 python=${{ matrix.python-version }} pip pytest
+          conda activate env-dajin2
+
+      - name: Install DAJIN2 and dependencies
+        run: |
+          pip install -e .  # Install DAJIN2 in editable mode
+
+      - name: Download example dataset
+        run: |
+          if [ ! -d example_single ]; then
+            curl -LJO https://github.com/akikuno/DAJIN2/raw/main/examples/example_single.tar.gz
+            tar -xf example_single.tar.gz
+          fi
+
+      - name: Run DAJIN2 with parameters
+        env:
+          PYTHONPATH: src
+        run: |
+          time DAJIN2 \
+              --control example_single/control \
+              --sample example_single/sample \
+              --allele example_single/stx2_deletion.fa \
+              --name stx2_deletion \
+              --genome mm39 \
+              --threads 4 \
+              --debug || exit 1
diff --git a/.github/workflows/e2e_tyr_substitution.yml b/.github/workflows/e2e_tyr_substitution.yml
new file mode 100644
index 00000000..dba32c1d
--- /dev/null
+++ b/.github/workflows/e2e_tyr_substitution.yml
@@ -0,0 +1,53 @@
+name: E2E test for Tyr substitution
+
+on:
+  push:
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      max-parallel: 2
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        python-version: ["3.13"]
+    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Miniconda
+        uses: conda-incubator/setup-miniconda@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+          miniconda-version: "latest"
+          activate-environment: test-env
+          channels: bioconda, conda-forge, defaults
+
+      - name: Create and activate conda environment
+        run: |
+          conda create -y -n env-dajin2 python=${{ matrix.python-version }} pip pytest
+          conda activate env-dajin2
+
+      - name: Install DAJIN2 and dependencies
+        run: |
+          pip install -e .  # Install DAJIN2 in editable mode
+
+      - name: Download and prepare example dataset
+        run: |
+          if [ ! -d example_batch ]; then
+            curl -LJO https://github.com/akikuno/DAJIN2/raw/main/examples/example_batch.tar.gz
+            tar -xf example_batch.tar.gz
+          fi
+
+      - name: Modify batch file and execute DAJIN2 batch test
+        env:
+          PYTHONPATH: src
+        run: |
+          TODAY=$(date -u "+%Y%m%d")
+          sed "s|tyr_c230gt|tyr_c230gt_${TODAY}|" example_batch/batch.csv > /tmp/tmp_batch.csv
+          time DAJIN2 batch --file /tmp/tmp_batch.csv --threads 4 --debug || exit 1
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 8a5f28af..41311b4c 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -1,48 +1,43 @@
 name: Pytest for Ubuntu / macOS latest
 
 on:
-  push:
-  schedule:
-    - cron: "0 2 * * 1" # The task runs at 2 a.m. every Monday.
+    push:
+    schedule:
+        - cron: "0 2 * * 1" # The task runs at 2 a.m. every Monday.
 
 jobs:
-  build:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      max-parallel: 10
-      matrix:
-        os: [ubuntu-latest, macos-latest]
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
-    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
-
-    defaults:
-      run:
-        shell: bash -l {0}
-
-    steps:
-      - uses: actions/checkout@v4
-      - uses: conda-incubator/setup-miniconda@v3
-        with:
-          python-version: ${{ matrix.python-version }}
-          miniconda-version: "latest"
-          activate-environment: test-env
-          channels: bioconda, conda-forge, defaults, anaconda
-
-      - name: Setup conda environment
-        run: |
-          conda create -y -n env-dajin2 python=${{ matrix.python-version }} pip pytest
-
-      - name: Install weasyprint on macos-latest🍎
-        if: runner.os == 'macOS'
-        run: |
-          conda install -n env-dajin2 weasyprint
-
-      - name: Execute tests
-        env:
-          PYTHONPATH: src
-        run: |
-          conda activate env-dajin2
-
-          # Use pip because conda does not support pyproject.toml.
-          pip install -e .
-          pytest tests/ -p no:warnings -vv
+    build:
+        runs-on: ${{ matrix.os }}
+        strategy:
+            max-parallel: 10
+            matrix:
+                os: [ubuntu-latest, macos-latest]
+                python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+        name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+
+        defaults:
+            run:
+                shell: bash -l {0}
+
+        steps:
+            - uses: actions/checkout@v4
+            - uses: conda-incubator/setup-miniconda@v3
+              with:
+                  python-version: ${{ matrix.python-version }}
+                  miniconda-version: "latest"
+                  activate-environment: test-env
+                  channels: bioconda, conda-forge, defaults
+
+            - name: Setup conda environment
+              run: |
+                  conda create -y -n env-dajin2 python=${{ matrix.python-version }} pip pytest
+
+            - name: Execute tests
+              env:
+                  PYTHONPATH: src
+              run: |
+                  conda activate env-dajin2
+
+                  # Use pip because conda does not support pyproject.toml.
+                  pip install -e .
+                  pytest tests/ -p no:warnings -vv
diff --git a/tests/data/preprocess/get_index_mapping/deletion/fasta/control.fasta b/tests/data/preprocess/get_index_mapping/deletion/fasta/control.fasta
deleted file mode 100644
index 179d8051..00000000
--- a/tests/data/preprocess/get_index_mapping/deletion/fasta/control.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->control
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGAGCGCAACCACTTTATGTCTGAGTCAGACTCATTACAGTGATTGGGTGATTAAAAATACCCGGCACACCCCATCTGTACACACAATATACTTACACAAGTGCACACACATGCAAATGAACCAGAGCAACATAGAGACATGTGGTTACACACACACACACACAAACTCAGCATATGAATCCATAAAAACAAACACGTAAGGCCCATGTTCAGTCACATACCTATATGCATATTTGTGCATGATTTTGTTTTTGTGTGTTTTTCTGTATGCATGTGCCTATGATTTTGGTATGCATGTACACATAAAACATGTTTGTGATTGTCCATCCACCTGTCTGTCATTGTCCACAAGTGCGGACAGTGTCATAAGCATTCCTTATGATGGCTGGACTTTAGAGGACAATTTCCAGAGGTCACCTATTCTTTAGTTTTGTGGGGAAAGTTGAGGCCATCCCTGAGACCAATGTCGTATCTTGGTGACTCTGTCCTGCTTCCTTCCAGCTATTGAGCAGAGCTGTGATCAGGACGAGAATGGGAACCGAACTTCAGTGGATCTGCGGATACGAAGGACCCAGGTTGGCCTTCCAGGCTCAGTTAAAGCGATGTGGAAGAGCAGCATTATGCTTTGTCCAGACTCAGAGCAATTTCAGTTTTTATTTTTTTATTGTGCTGGAAGTTGAGGCCAGTATCCTGCACATGCTCAGCAAGGGCTCTGTCACCAAGCCACAGCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/deletion/fasta/deletion.fasta b/tests/data/preprocess/get_index_mapping/deletion/fasta/deletion.fasta
deleted file mode 100644
index 6a5dc7a6..00000000
--- a/tests/data/preprocess/get_index_mapping/deletion/fasta/deletion.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->deletion
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/insertion/fasta/control.fasta b/tests/data/preprocess/get_index_mapping/insertion/fasta/control.fasta
deleted file mode 100644
index c076c288..00000000
--- a/tests/data/preprocess/get_index_mapping/insertion/fasta/control.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->control
-GGCAAGAACGACCTGCTTTTTACCTTACAGAACCAAGCATGCATCTGGATCACCAAGACACAAAGGCCTGAAGAAAACCCACTTCATCAAGAACATGAGGCAGTATGACACAAAGAACAGCAGGTGAGTGAGTGTAGGGCCAGGCCCGGGAAGGCCACTGTGCTCCCTGTACTGTAGGATCAGGCCGTGTCTCTGGGGGCCAAAAATATGAGACCCAGGCCCTGCCCCTGTTGGCAGCAGCCCCTTCTCCATCACCCGTTGTCCCCTGACCCTCTATATTGTTGAACACTGGCTAGCATAGCCAGGCCAGCAAGCCACCACTGTATAATCCCAGGGAAGAGCCACCAGCTGGACACAAATCTTAAGAAAAAGAGACTAGGCACCACTGTATAATCCCAGGGAAGGGCCAGCAGCTGGACACAGATCTTAAGAAAAAGAGACTAGGGAAAGCAAAAATGCCTCCCCCCTCCCCAAATGTGCCCAGGATAGAAGGTGCGACCATATTTGGGGCACATATACAGGATAAGGCATGGCTATACTTGTCAGCACTAAGAGTTCCATTGGGGACACACAGATGGAGATGAGGTATATTGGGGGACATATGGGAGGCTTAACTCAAACTAGTATTTGGGTCCCCAGAACAGTGCTAGGCTAGAATCTGGGCAGCTGCATATGCCTGCACTGGTGCAAACACACTACCTGTCACTCTGATCTCAGTCCCTTCCCTGCTGTAGGTCACCGTGATAAGTATCCCCAGGAACTGTTGCTGCAGGACCTGCAAATCCTAAGTCCTGAGACCACAGGGTGGCCTATGGTTATAGCTTTGAGCATTCAGGGGCCCCAGTCCTAAGCCCCCCTAGATAGCTTAGTTCTGTATTATATCTAGGTGTTCTCTATTAGCAGTAAACCTTGTGTTTGCCTGTACTCTGTAGCCTGTTGCCAATGAACTTGGCAGCTTCCTGTGCCCTCTCTTTGTGCGAATGAAGCTGCCCTTTCCTGGGTAACCTGTGCTCCTGGGAGCAGGGAGCTTCCTAAGCAGATCTAGGGAATCTGGCCCAAGTGCTAAGATGCCAGACCTGGCTTCCGGAATCCAAGTCCCCTTGGTGGCTCTGGCAGTTTCCCATCACCCAAATGTAGACAGGACAGAGTTCCTGCCCTGCTCTGACCTCGGCCTCCCACAGGATTGTACTCATCTGTGCCAAGCGGTCCCTGTGTGCGGCCTTCTCAGTCCTGCCCTATGGAGAAGGCCTACGGATCAGGTAAGAAACTGCCCATGCTGCGAACACGGGTAAGCCCTGGTCCTGCTGAGCACAATCAGTGTGGCCTGCTCTTGAGTGTAGGCTGGGGCCTTAGACTCATGGGGCCAGCATATCGTCTCCCTCCACAGCCCAGGAGTTTGCATTCCAGGTTCTTATCCCCGGAGCCACGGGTTCTGTCCAGACTATCTATATGTGTGTGGGATGTGTGTGTAGATGGATGGATAGATAGATAGGCAGGCTACATATAGGTTGGCTATATATAGGTTAGCTATATACAGGTATGTATCAGTAGGCTATATATAGGTTGACTGAGATATTCCTGAACCTCTCTCAAATAAGCTCCTAAAACCTTGTGACCAGCCCAGGCAATTGTGTCCTGAGTATAATTCAGGGAAGAAGGAAACAGCTGTTGGGGCGGGGCAGTGTTGTGTGTGACCCCACAGAACAATAGTCGGCACAGAAAAACCTCCTGTCCAGAGTGGGAGATAGCCAGTTCTCAGGATCCCAGGCCAGCTGCTGAACTGTGTGACTACAGGTGAAGGGACTGAGGCACCTGGGAAGCGAATTTAAGCCAAAGTCTGGTAAGTTTTCAGGGATGTTCCAGAGTGGTGGCTCCCAGGTGCTATGACAGATGACATCTATCCTGTCATCCACAAGGCAGGAGTTAAGATACAGGCTGCCCTCAGGCTAAACCTTCTGTCATGTACCTCCAAGGTCATGCCCGCCCCCTTCTCCTGTTTTGAGGACTTTGTGGCAGGAGCACTCTGTGTGGGTGGATTCCCGGTGGGCCGAGCACCCACCCACTGACCACAGACAGAGGGACTACAGCCCTCCCTCTCATATACATTGCAGTGACCTGAGGGTGGACAGCCAGAAGCAGAGGCACCCATCCGGCGGCGTTTCTGTTTCTTCTGAGATGGTCTTTGAATTGGAAGGCGTTGAGCTGGGAGCAGATGGAAAGGCAAGAGCCCTGTGGCCAGCCATTGGCCAGCAGAGTTAGGCAGACAGGTGCTGCACATTAAGGCACAGTATTGGGAGCAAGCCACACCCCGTTACACCTGTCACTAGCTGGCTCATGCCACCCAGAGAAAGTGCCTAAGAGTACACCATACACAGCCACTGCTAGCTGTGGGTAACCCAACTGCTCCCCTGGGTTCTCCACATAGAGCCTTGCTTCTGGACCAGTAAGTAGGCCAAGGGTCTTCCAAGACAGAGACTAGCCTGGCCACAGACCACAGATTAGGCATGGAAGCTAGTGCTCTGACCTTGAAAGTACCCACTCCGACTGGGTAGCAGCATGGCTTCAGGCTCCTCACTGGAGGGCAGAGGGAAGCAGAACTTGGTTGTACTTTCTGGCCTGGGGAGCAGAACTGAGCTAATGTGATTGCTTTTCCAGGTCGTGTCTTATGCAAAGTTCCTGTACCCTACTAATGCTCTGGTTATACACAAGAATGACAGCCATGGC
diff --git a/tests/data/preprocess/get_index_mapping/insertion/fasta/flox.fasta b/tests/data/preprocess/get_index_mapping/insertion/fasta/flox.fasta
deleted file mode 100644
index 459ceaae..00000000
--- a/tests/data/preprocess/get_index_mapping/insertion/fasta/flox.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->flox
-GGCAAGAACGACCTGCTTTTTACCTTACAGAACCAAGCATGCATCTGGATCACCAAGACACAAAGGCCTGAAGAAAACCCACTTCATCAAGAACATGAGGCAGTATGACACAAAGAACAGCAGGTGAGTGAGTGTAGGGCCAGGCCCGGGAAGGCCACTGTGCTCCCTGTACTGTAGGATCAGGCCGTGTCTCTGGGGGCCAAAAATATGAGACCCAGGCCCTGCCCCTGTTGGCAGCAGCCCCTTCTCCATCACCCGTTGTCCCCTGACCCTCTATATTGTTGAACACTGGCTAGCATAGCCAGGCCAGCAAGCCACCACTGTATAATCCCAGGGAAGAGCCACCAGCTGGACACAAATCTTAAGAAAAAGAGACTAGGCACCACTGTATAATCCCAGGGAAGGGCCAGCAGCTGGACACAGATCTTAAGAAAAAGAGACTAGGGAAAGCAAAAATGCCTCCCCCCTCCCCAAATGTGCCCAGGATAGAAGGTGCGACCATATTTGGGGCACATATACAGGATAAGGCATGGCTATACTTGTCAGCACTAAGAGTTCCATTGGGGACACACAGATGGAGATGAGGTATATTGGGGGACATATGGGAGGCTTAACTCAAACTAGTATTTGGGTCCCCAGAACAGTGCTAGGCTAGAATCTGGGCAGCTGCATATGCCTGCACTGGTGCAAACACACTACCTGTCACTCTGATCTCAGTCCCTTCCCTGCTGTAGGTCACCGTGATAAGTATCCCCAGGAACTGTTGCTGCAGGACCTGCAAATCCTAAGTCCTGAGACCACAGGGTGGCCTATGGTTATAGCTTTGAGCATTCAGGGGCCCCAGTCCTAAGCCCCCCTAGATAGCTTAGTTCTGTATTATATCTAGGTGTTCTCTATTAGCAGTAAACCTTGTGTTTGCCTGTACTCTGTAGCCTGTTGCCAATGAACTTGGCAGCTTCCTGTGCCCTCTCTTTGTGCGAATGAAGCTGCCCTTTCCTGGGTAACCTGTGCTCCTGGGAGCAGGGAGCTTCCTAAGCAGATCTAGGGAATCTGGCCCAAGTGCTAAGATGCCAGACCTGGCTTCCGGAATCCAAGTCCCCTTGGTGGCTCTGGCAGTTTCCCATCACCCAAATGTAGACAGGACAGAGTTCCTGCCCTGCTCTGACCTCGGCCTCCCACAGGATTGTACTCATCTGTGCCAAGCGGTCCCTGTGTGCGGCCTTCTCAGTCCTGCCCTATGGAGAAGGCCTACGGATCAGGTAAGAAACTGCCCATGCTGCGAACACGGGTAAGCCCTGGTCCTGCTGAGCACAATCAGTGTGGCCTGCTCTTGAGTGTAGGCTGGGGCCTTAGACTCATGGGGCCAGCATATCGTCTCCCTCCACAGCCCAGGAGTTTGCATTCCAGGTTCTTATCCCCGGAGCCACGGGTTCTGTCCAGACTATCTATATGTGTGTGGGATGTGTGTGTAGATGGATGGATAGATAGATAGGCAGGCTACATATAGGTTGGCTATATATAGGTTAGCTATATACAGGTATGTATCAGTAGGCTATATATAGGTTGACTGAGATATTCCTGAACCTCTCTCAAATAAGCTCCTAAAACCTTGTGACCAGCCCAGGCAATTGTGTCCTGAGTATAATTCAGGGAAGAAGGAAACAGCTGTTGGGGCGGGGCAGTGTTGTGTGTGACCCCACAGAACAATAGTCGGCACAGAAAAACCTCCTGTCaaacATAACTTCGTATAGCATACATTATACGAAGTTATggcgcgccCAGAGTGGGAGATAGCCAGTTCTCAGGATCCCAGGCCAGCTGCTGAACTGTGTGACTACAGGTGAAGGGACTGAGGCACCTGGGAAGCGAATTTAAGCCAAAGTCTGGTAAGTTTTCAGGGATGTTCCAGAGTGGTGGCTCCCAGGTGCTATGACAGATGACATCTATCCTGTCATCCACAAGGCAGGAGTTAAGATACAGGCTGCCCTCAGGCTAAACCTTCTGTCATGTACCTCCAAGGTCATGCCCGCCCCCTTCTCCTGTTTTGAGGACTTTGTGGCAGGAGCACTCTGTGTGGGTGGATTCCCGGTGGGCCGAGCACCCACCCACTGACCACAGACAGAGGGACTACAGCCCTCCCTCTCATATACATTGCAGTGACCTGAGGGTGGACAGCCAGAAGCAGAGGCACCCATCCGGCGGCGTTTCTGTTTCTTCTGAGATGGTCTTTGAATTGGAAGGCGTTGAGCTGGGAGCAGATGGAAAGGCAAGAGCCCTGTGGCCAGCCATTGGCCAGCAGAGTTAGGCAGACAGGTGCTGCACATTAAGGCACAGTATTGGGAGCAAGCCACACCCCGTTACACCTGTCACTAGCTGGCTCATGCCACCCAGAGAAAGTGCCTAAGAGTACACCATACACAGCCACTGcgcgccaattcgatatcaagctATAACTTCGTATAGCATACATTATACGAAGTTATgcggccCTAGCTGTGGGTAACCCAACTGCTCCCCTGGGTTCTCCACATAGAGCCTTGCTTCTGGACCAGTAAGTAGGCCAAGGGTCTTCCAAGACAGAGACTAGCCTGGCCACAGACCACAGATTAGGCATGGAAGCTAGTGCTCTGACCTTGAAAGTACCCACTCCGACTGGGTAGCAGCATGGCTTCAGGCTCCTCACTGGAGGGCAGAGGGAAGCAGAACTTGGTTGTACTTTCTGGCCTGGGGAGCAGAACTGAGCTAATGTGATTGCTTTTCCAGGTCGTGTCTTATGCAAAGTTCCTGTACCCTACTAATGCTCTGGTTATACACAAGAATGACAGCCATGGC
diff --git a/tests/data/preprocess/get_index_mapping/inversion/fasta/control.fasta b/tests/data/preprocess/get_index_mapping/inversion/fasta/control.fasta
deleted file mode 100644
index 179d8051..00000000
--- a/tests/data/preprocess/get_index_mapping/inversion/fasta/control.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->control
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGAGCGCAACCACTTTATGTCTGAGTCAGACTCATTACAGTGATTGGGTGATTAAAAATACCCGGCACACCCCATCTGTACACACAATATACTTACACAAGTGCACACACATGCAAATGAACCAGAGCAACATAGAGACATGTGGTTACACACACACACACACAAACTCAGCATATGAATCCATAAAAACAAACACGTAAGGCCCATGTTCAGTCACATACCTATATGCATATTTGTGCATGATTTTGTTTTTGTGTGTTTTTCTGTATGCATGTGCCTATGATTTTGGTATGCATGTACACATAAAACATGTTTGTGATTGTCCATCCACCTGTCTGTCATTGTCCACAAGTGCGGACAGTGTCATAAGCATTCCTTATGATGGCTGGACTTTAGAGGACAATTTCCAGAGGTCACCTATTCTTTAGTTTTGTGGGGAAAGTTGAGGCCATCCCTGAGACCAATGTCGTATCTTGGTGACTCTGTCCTGCTTCCTTCCAGCTATTGAGCAGAGCTGTGATCAGGACGAGAATGGGAACCGAACTTCAGTGGATCTGCGGATACGAAGGACCCAGGTTGGCCTTCCAGGCTCAGTTAAAGCGATGTGGAAGAGCAGCATTATGCTTTGTCCAGACTCAGAGCAATTTCAGTTTTTATTTTTTTATTGTGCTGGAAGTTGAGGCCAGTATCCTGCACATGCTCAGCAAGGGCTCTGTCACCAAGCCACAGCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/inversion/fasta/inversion.fasta b/tests/data/preprocess/get_index_mapping/inversion/fasta/inversion.fasta
deleted file mode 100644
index 79a06180..00000000
--- a/tests/data/preprocess/get_index_mapping/inversion/fasta/inversion.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->inversion
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGCTGTGGCTTGGTGACAGAGCCCTTGCTGAGCATGTGCAGGATACTGGCCTCAACTTCCAGCACAATAAAAAAATAAAAACTGAAATTGCTCTGAGTCTGGACAAAGCATAATGCTGCTCTTCCACATCGCTTTAACTGAGCCTGGAAGGCCAACCTGGGTCCTTCGTATCCGCAGATCCACTGAAGTTCGGTTCCCATTCTCGTCCTGATCACAGCTCTGCTCAATAGCTGGAAGGAAGCAGGACAGAGTCACCAAGATACGACATTGGTCTCAGGGATGGCCTCAACTTTCCCCACAAAACTAAAGAATAGGTGACCTCTGGAAATTGTCCTCTAAAGTCCAGCCATCATAAGGAATGCTTATGACACTGTCCGCACTTGTGGACAATGACAGACAGGTGGATGGACAATCACAAACATGTTTTATGTGTACATGCATACCAAAATCATAGGCACATGCATACAGAAAAACACACAAAAACAAAATCATGCACAAATATGCATATAGGTATGTGACTGAACATGGGCCTTACGTGTTTGTTTTTATGGATTCATATGCTGAGTTTGTGTGTGTGTGTGTGTAACCACATGTCTCTATGTTGCTCTGGTTCATTTGCATGTGTGTGCACTTGTGTAAGTATATTGTGTGTACAGATGGGGTGTGCCGGGTATTTTTAATCACCCAATCACTGTAATGAGTCTGACTCAGACATAAAGTGGTTGCGCTCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/stx2/fasta/control.fasta b/tests/data/preprocess/get_index_mapping/stx2/fasta/control.fasta
deleted file mode 100644
index 179d8051..00000000
--- a/tests/data/preprocess/get_index_mapping/stx2/fasta/control.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->control
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGAGCGCAACCACTTTATGTCTGAGTCAGACTCATTACAGTGATTGGGTGATTAAAAATACCCGGCACACCCCATCTGTACACACAATATACTTACACAAGTGCACACACATGCAAATGAACCAGAGCAACATAGAGACATGTGGTTACACACACACACACACAAACTCAGCATATGAATCCATAAAAACAAACACGTAAGGCCCATGTTCAGTCACATACCTATATGCATATTTGTGCATGATTTTGTTTTTGTGTGTTTTTCTGTATGCATGTGCCTATGATTTTGGTATGCATGTACACATAAAACATGTTTGTGATTGTCCATCCACCTGTCTGTCATTGTCCACAAGTGCGGACAGTGTCATAAGCATTCCTTATGATGGCTGGACTTTAGAGGACAATTTCCAGAGGTCACCTATTCTTTAGTTTTGTGGGGAAAGTTGAGGCCATCCCTGAGACCAATGTCGTATCTTGGTGACTCTGTCCTGCTTCCTTCCAGCTATTGAGCAGAGCTGTGATCAGGACGAGAATGGGAACCGAACTTCAGTGGATCTGCGGATACGAAGGACCCAGGTTGGCCTTCCAGGCTCAGTTAAAGCGATGTGGAAGAGCAGCATTATGCTTTGTCCAGACTCAGAGCAATTTCAGTTTTTATTTTTTTATTGTGCTGGAAGTTGAGGCCAGTATCCTGCACATGCTCAGCAAGGGCTCTGTCACCAAGCCACAGCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/stx2/fasta/deletion.fasta b/tests/data/preprocess/get_index_mapping/stx2/fasta/deletion.fasta
deleted file mode 100644
index 6a5dc7a6..00000000
--- a/tests/data/preprocess/get_index_mapping/stx2/fasta/deletion.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->deletion
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/stx2/fasta/inversion.fasta b/tests/data/preprocess/get_index_mapping/stx2/fasta/inversion.fasta
deleted file mode 100644
index 79a06180..00000000
--- a/tests/data/preprocess/get_index_mapping/stx2/fasta/inversion.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->inversion
-GCTCCAGGGTGTCTCATAGTGTTTGAAGGCTCCTAAATTGCCCAGTGTTCAGCTGGGGAAAGACCATCAGCTAGGCAGGATCCAAAGGATAATGAGTGTGCCCCATGGGACTCTGGCTTAGCCGCAGCTCTACCCTAAGCCCACAGTTGAGGGGTAGTGGATCTTGTGTTTGAGGACATTAACAACAGGCTGATTGGGAAAGTAGTGGTTGCTTGATGGAGTTGGGCTAGCGATGGAGGTGAGTGAGTCTGGAGGCCAGTTGTGTGCCGCATACTAACAGAGGAGTAAGCACCAGCTAGATTTTCATTTCCTTAGGATATCTTTCTAAATCTCAGCTCTTTTTAGAAATGTTAAATGCCTTTGTTTAGAAACTTTGAATGTTGGCTGCCACTTACATTAAAAAAAAAAAAAAAAAAAACCTGGAAACTAAGACAGTGTCTGGTCACTCCCACACATCAGTTACTGTGGAGCAGCTTGCTCACCTGTGCTCACTCCACTGCCCTCAAGGAGCTAATCTGCCTGTCTGCTGGCTGCATATTCTGCTTCCTCCTGGGGAGTCTCATAGGGTCTCTGAGATCACACTGTTTCTGGTCACACTGGGGTTTTGTAAGGCCTCCCTTACATCTGCCAGGTTCCAGTTTGCTTCACCTCATTGGGTGTTTTAATGACATCACATCAGATGGCAAGAAAGACTCTGGGTGGAGTCCCCCAGAATCTCCATAGATGCCATAGAGGTATAGACATGGCAGTGGACTGCTGCTGTCAGTTAGCTGCAGCCAGTGACCTTTTACCCCGAGAATGGCTGGGGCACCAAAGTTGGCTCTGAAGGTCTAGAGCCAGGTCTCCAGGAGGGTGACATGCGTGGTATGTCTGATGGAGCAACAGGGACCAGGAAGACAGCGACAGCAGAAGGGACTTCTGGCAGCTTCTTCCTGGGTGTGGTTGCATGTACTCTGTGTCCCAGTGAAGGTTAGCATCAGAGATCTGATTGGGGTGAGCCTGCAGTGCTGGCTCACCCCAGCTGTGTCTGGTAACACAGTTCGGGGTCTACCAGGAGCTGGCCCGGACACGGCACTGGTGGCTGGGGAGGTGAACTTGGACAGGCACTGAGAAAAGAAGGCTGGTTGCTCCTTCTCTGTGCTTGGCCTGCATGCCTGTATCTGTAGTGTAAAGTGGTCCATCTCACATGCTTCATTTCCCCCTTTTAAAGAAATAAAAGAAGAGCTGGAGGACCTGAACAAAGAGATCAAGAAAACTGCTAACAGGATCCGGGGCAAGCTGAAGTGTAAGTTTGCCTTCTTGGGTGCTGGTGGGGTGTGGAAGTTGGTACCTTTTTCCTTAGAGTTTTATTATTATATTTCCCAAAATGAAAATTGAGACTGCTTCTAAGTAGGTTGGTTATCTGTAATCCCGTGCTATATCTCATTGTGACTCAGTAGGTGACATTGGGCAGGTGTTTGAGAAAGTGGGGATAGCTTTCCTAGTGACTCGCCAAGCTCTCAGGCGGGCCTCTTAGCTAGAATGTTCTTTCCAGCCCCTAGAAGCTCAGCTGAGATCATGGTCCTCTGTGTTCAGGATGACGCCTTGGCTGGGTTGAGGGTTGTGGCTTTTTGCACAGTGCTTAAACAGAAGTTGCTTTCCTTGATCTGTGAGGAAGACATATGTTTAACTTTTTTATTTTTTTCTGAGACAGGGTTTCTCTGTGTAGCCCTGGCTGTCCTAGAACTCACTTTGTAGACCAAGTTGGCCTCGAACTCAGAAATCCGCCTGCCTCTGCCTCCCGAGTGCTGGGATTAAAGGCATGCTCCACCACTGCCCAGCTGACATGTGTTTTAACTTACAGAGACACATCAAAAGGAATAAATCTCATCTCCGCACATACCCTCTCCTCAGGAACAGTGACTCCCAGGTAGCAGGCACTCTGCCCAGATCCTAGTAGGCTCCTGGCACTCCTCACTGGGCTCAAGGCAGACCTTAGAGAGTGTGCACCTTAAAAACAGCGTTGTTCCCAAGCTGTGGCTTGGTGACAGAGCCCTTGCTGAGCATGTGCAGGATACTGGCCTCAACTTCCAGCACAATAAAAAAATAAAAACTGAAATTGCTCTGAGTCTGGACAAAGCATAATGCTGCTCTTCCACATCGCTTTAACTGAGCCTGGAAGGCCAACCTGGGTCCTTCGTATCCGCAGATCCACTGAAGTTCGGTTCCCATTCTCGTCCTGATCACAGCTCTGCTCAATAGCTGGAAGGAAGCAGGACAGAGTCACCAAGATACGACATTGGTCTCAGGGATGGCCTCAACTTTCCCCACAAAACTAAAGAATAGGTGACCTCTGGAAATTGTCCTCTAAAGTCCAGCCATCATAAGGAATGCTTATGACACTGTCCGCACTTGTGGACAATGACAGACAGGTGGATGGACAATCACAAACATGTTTTATGTGTACATGCATACCAAAATCATAGGCACATGCATACAGAAAAACACACAAAAACAAAATCATGCACAAATATGCATATAGGTATGTGACTGAACATGGGCCTTACGTGTTTGTTTTTATGGATTCATATGCTGAGTTTGTGTGTGTGTGTGTGTAACCACATGTCTCTATGTTGCTCTGGTTCATTTGCATGTGTGTGCACTTGTGTAAGTATATTGTGTGTACAGATGGGGTGTGCCGGGTATTTTTAATCACCCAATCACTGTAATGAGTCTGACTCAGACATAAAGTGGTTGCGCTCCGAGCATCACATCTGCCCTTATGTAATGTCTGTCTGCAGGGGGTGGGGGTGGGGCTGGGGGGCTGCATCTCTTCCATGTGAAGAGATGACTCAGCGACTCGGCTACCTATATGCTCTGTGCCTCAGAGTAGAGGACTAGCCAGACCACCTAGATGGTAGTGGTTTGGTTTTAATCAGTTTTTTTTTTTAAAGATTTATTTATTTATTTTATGTATATGTGTACACGGTCGCTGTCTTCAGACACACCAAAAGAGGGCATCAGATCCCATTACAGATGGTTGTGAGCCACCATGTGGTTTGCTGGGAATTGAACTCAGGTCCTCTGGAACAGCAGTCCATGCTCTTAACCACTGAGCCATCTCTCTAGCTGGGTTTTCAACCTTCCTAATGCTGTGACCCTTTAATATATAGTTTCTCATGTTGTAATGACCCCAATCATAATATTATCTTCATTGCTACTTTATAACTGTAACTTTGCTTGTTATGAATTGTAATATAAATGTCTGACATGCAGGATATCTGCTATTTGACCCCAAAGTGGTTGTTACCAAAAGCCTTAACTATTAGCCTTAACTATTCAGGGTTTATGGTTGAAACTGGGATGCTCAACCCTGTTTGACCAAGCCTGCCAGCTTAGGGCAGGCAGTCCACGTGCCTGCCAGCTGGATTCAACCTGTGATAACGCCCTCCCTCTAGAGGTCAGCTGGCTGCTCCGGGCTCCAACTTATTTGTTGTTGTTATTGGTGATGGTGGTATTTTGTTTTGTTGAGCCAGAATCTTACTCTGTATCCCTGGCTGGCCTGGAGTGAGCTTGTTACATAGACCAGGTCGACCTTGAACTCACAGAGATTTATTTCCCTCTCCCTCTGTATTGGAATTGAAGGCATGCGCCACCGTCCTGGTGGCCCAGGTCATGCACCTCCCATTGCCGCTCTTTCTCTTACTTTCTTCTGGGATGGAGTCATGGTTGGGGCTTCTTAGTCGTCAGTTTTACCTCTTTCAAAGGAAGGGGTTGCCCCCACATAGTAACACGGTATTCTCCGTGTTCTAGCACTCGGTGCTGTCACGGAAGTTTGTGGACGTCATGACAGAATACAATGAAGCGCAGATCCTGTTCCGGGAGCGAAGCAAAGGCCGCATCCAGCGCCAGCTGGAGATCAGTGAGTAGGGCGCATGCGGGAGACGTGCTTCAGGACCCATCAGATGGCAAGCGCCCCACTTCTAACTGCAGAGTAAACCGAAAGGCCCTTTGCTGCAGCCTGTGCCTAGAGTCAGGCAGATGTGTTATGTGTCGTAAGCTTCAGAGTTCTGAGTGCGGGGCTGTAGTGGCTTGCGATTGCTGGTGCTGCTATTTCACGCATCCCGAAATCTTGTGGTCCTGATTTCTTAAGGCATTTTCGTGTCCTGCAGCTGCTCCTACCTGGCTGGGAGAGGATCATCCTCATGTGCCTCCTTCAGCTTAACTCCCTCTGGCTAGAAGCCCTGTGGGACCCGAGCATAGCTGCTCCTCTGTAACACCAGGGTAGGGTTGTGACACATTGCATCACATTTCTGCCACATTACTGTGT
diff --git a/tests/data/preprocess/get_index_mapping/substitution/fasta/albino.fasta b/tests/data/preprocess/get_index_mapping/substitution/fasta/albino.fasta
deleted file mode 100644
index a8bd355f..00000000
--- a/tests/data/preprocess/get_index_mapping/substitution/fasta/albino.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->albino
-TGCATTGAAGCAGTTCACCAAAATAACAAAGTAACAAAGTAAGATATCTTTGGAATAATCAATTCAAGATAATCAAGGAAAAATGAGAGGCAACTATTTTAGACTGATTACTTTTATAAAATAAATAAGCTCAGCTTAGCCAGATATAAGCAATATTCTGAGTTCTGAAGAAAAATTTTTGACAAAATGAGTTCTATAAATGTTATTGTCTACTTATGATCTCTAAATACAACAGGCTTGTATTCAGAATCTAGATGTTTCATGACCTTTATTCATAAGAGATGATGTATTCTTGATACTACTTCTCATTTGCAAATTCCAATTATTATTAATTTCATATCAATTAGAATAATATATCTTCCTTCAATTTAGTTACCTCACTATGGGCTATGTACAAACTCCAAGAAAAAGTTAGTCATGTGCTTTGCAGAAGATAAAAGCTTAGTGTAAAACAGGCTGAGAGTATTTGATGTAAGAAGGGGAGTGGTTATATAGGTCTTAGCCAAAACATGTGATAGTCACTCCAGGGGTTGCTGGAAAAGAAGTCTGTGACACTCATTAACCTATTGGTGCAGATTTTGTATGATCTAAAGGAGAAAATGTTCTTGGCTGTTTTGTATTGCCTTCTGTGGAGTTTCCAGATCTCTGATGGCCATTTTCCTCGAGCCTGTGCCTCCTCTAAGAACTTGTTGGCAAAAGAATGCTGCCCACCATGGATGGGTGATGGGAGTCCCTGCGGCCAGCTTTCAGGCAGAGGTTCCTGCCAGGATATCCTTCTGTCCAGTGCACCATCTGGACCTCAGTTCCCCTTCAAAGGGGTGGATGACCTTGAGTCCTGGCCCTCTGTGTTTTATAATAGGACCTGCCAGTGCTCAGGCAACTTCATGGGTTTCAACTGCGGAAACTGTAAGTTTGGATTTGGGGGCCCAAATTGTACAGAGAAGCGAGTCTTGATTAGAAGAAACATTTTTGATTTGAGTGTCTCCGAAAAGAATAAGTTCTTTTCTTACCTCACTTTAGCAAAACATACTATCAGCTCAGTCTATGTCATCCCCACAGGCACCTATGGCCAAATGAACAATGGGTCAACACCCATGTTTAATGATATCAACATCTACGACCTCTTTGTATGGATGCATTACTATGTGTCAAGGGACACACTGCTTGGGGGCTCTGAAATATGGAGGGACATTGATTTTGCCCATGAAGCACCAGGGTTTCTGCCTTGGCACAGACTTTTCTTGTTATTGTGGGAACAAGAAATTCGAGAACTAACTGGGGATGAGAACTTCACTGTTCCATACTGGGATTGGAGAGATGCAGAAAACTGTGACATTTGCACAGATGAGTACTTGGGAGGTCGTCACCCTGAAAATCCTAACTTACTCAGCCCAGCATCCTTCTTCTCCTCCTGGCAGGTAAGATGCACTATATAGAGAGAGTTGCAAAGACTGGTACTTCAGCAGCCACATTTTCATGCTCTGTGAGCATCTCTGATAATATCTCAGGGCAGAAAATGTGCCTTACTAACAGATGTTAATGCTTCTTGATTTCTTTTTCTCTTTTGAGAACTCTTCAAAGTTGTTATTAAACAAATATCTATGTGCTTATTTGTCTTAATATCTAACAGCTTAGTTAGATTTCTAAGCTGCTATAAACAAGGACTGATTGGTTCACCACTGTATTGTTAGCACCTCCTATGGTATCTGGAATAAACAGTAACTCAGTTATTTAAGAATGGATGAGAAACCAGATTATCTTAGTTCATGTTTCTGAGTAATATTAAAATTAATATTAACAGTAAAATCCATAAGTATGCTACTTTAAAATATAAATCTCTGGCCAAAACCAAGACTTATTATTCAGGATCTTCAAGAGAAAGTGCTGAGATAATTCACTAAGTATCAGAGATGACCTTTATTACATGATTGCCTGATAGAAAAAATGATTACACACACACAAAAAAATCTTCAGTTGCTTAAATTTTAAACGTTGCTGACTCTCAAACAGTTAAGTAATAAAAGAGTTAAAGCCTGCTGTGTATTTAGAATATGTGAATACCTATTGAAAGAATTTATTGTACAATTAATATAAACAGACTTCTATTTTACAGTCATAAGATACTACTTAATTTGTTAAAAATTATTTTTTGATAGCATTGTTGGTAAATAGCAAAGGTGATATTTGCTAATGATTACAAGGGCTGTCTGGCTAACTTACGTTATGTTCAGGGAGAAGACAGTCCTTTTTAAGGAATGGGCACTTTCTAACTTTTTTTCTCTAGGATGGAGAAAAATTAGCCTTCTTCCTACTTTAAAAATGTTAGACATAGAATTAAGGGATTGTTATTTTGAGATTAAATTTTCTTTTCTCCTATTATTTTTCCTCATTCTGGAATGGAAGCAAAAGATGAAGAAAGAAATATATGTTAAATTGTTTTCCTTTAAATGAACACAAATGTGAAATATGTTTTTCTGCCTATCTTGTAAAATTTTCTATTGCAACTATTCTGATTACCAGTTCAAATGGGGAAAAAAGAACATAGGCTACCCCACACTTGAAATTTTGAAATATGAATGTCCTCTGTCTCTGCTGGTCTAACACTTCCAAAATGGAAACCTTTAAAGGGCCACTGTAAATTACAGCTGCTAATTCCTGGTGCCAATGGTGATAAGTGTTTACTAAACCTAGTGAGTACTTTATAGCATGGGGCTCTGCTGCGAAGTAACATTGCTGTATATTTTCAGTCATTCTACCTTAATTCATGAACTGCAAAACTCTCATCTAGCTTTTTACTTCTCTAGCTATTGCTTTAAGTTCTATCAGGCTCAGGTGTGGAATTCTC
diff --git a/tests/data/preprocess/get_index_mapping/substitution/fasta/control.fasta b/tests/data/preprocess/get_index_mapping/substitution/fasta/control.fasta
deleted file mode 100644
index f1663c71..00000000
--- a/tests/data/preprocess/get_index_mapping/substitution/fasta/control.fasta
+++ /dev/null
@@ -1,2 +0,0 @@
->control
-TGCATTGAAGCAGTTCACCAAAATAACAAAGTAACAAAGTAAGATATCTTTGGAATAATCAATTCAAGATAATCAAGGAAAAATGAGAGGCAACTATTTTAGACTGATTACTTTTATAAAATAAATAAGCTCAGCTTAGCCAGATATAAGCAATATTCTGAGTTCTGAAGAAAAATTTTTGACAAAATGAGTTCTATAAATGTTATTGTCTACTTATGATCTCTAAATACAACAGGCTTGTATTCAGAATCTAGATGTTTCATGACCTTTATTCATAAGAGATGATGTATTCTTGATACTACTTCTCATTTGCAAATTCCAATTATTATTAATTTCATATCAATTAGAATAATATATCTTCCTTCAATTTAGTTACCTCACTATGGGCTATGTACAAACTCCAAGAAAAAGTTAGTCATGTGCTTTGCAGAAGATAAAAGCTTAGTGTAAAACAGGCTGAGAGTATTTGATGTAAGAAGGGGAGTGGTTATATAGGTCTTAGCCAAAACATGTGATAGTCACTCCAGGGGTTGCTGGAAAAGAAGTCTGTGACACTCATTAACCTATTGGTGCAGATTTTGTATGATCTAAAGGAGAAAATGTTCTTGGCTGTTTTGTATTGCCTTCTGTGGAGTTTCCAGATCTCTGATGGCCATTTTCCTCGAGCCTGTGCCTCCTCTAAGAACTTGTTGGCAAAAGAATGCTGCCCACCATGGATGGGTGATGGGAGTCCCTGCGGCCAGCTTTCAGGCAGAGGTTCCTGCCAGGATATCCTTCTGTCCAGTGCACCATCTGGACCTCAGTTCCCCTTCAAAGGGGTGGATGACCGTGAGTCCTGGCCCTCTGTGTTTTATAATAGGACCTGCCAGTGCTCAGGCAACTTCATGGGTTTCAACTGCGGAAACTGTAAGTTTGGATTTGGGGGCCCAAATTGTACAGAGAAGCGAGTCTTGATTAGAAGAAACATTTTTGATTTGAGTGTCTCCGAAAAGAATAAGTTCTTTTCTTACCTCACTTTAGCAAAACATACTATCAGCTCAGTCTATGTCATCCCCACAGGCACCTATGGCCAAATGAACAATGGGTCAACACCCATGTTTAATGATATCAACATCTACGACCTCTTTGTATGGATGCATTACTATGTGTCAAGGGACACACTGCTTGGGGGCTCTGAAATATGGAGGGACATTGATTTTGCCCATGAAGCACCAGGGTTTCTGCCTTGGCACAGACTTTTCTTGTTATTGTGGGAACAAGAAATTCGAGAACTAACTGGGGATGAGAACTTCACTGTTCCATACTGGGATTGGAGAGATGCAGAAAACTGTGACATTTGCACAGATGAGTACTTGGGAGGTCGTCACCCTGAAAATCCTAACTTACTCAGCCCAGCATCCTTCTTCTCCTCCTGGCAGGTAAGATGCACTATATAGAGAGAGTTGCAAAGACTGGTACTTCAGCAGCCACATTTTCATGCTCTGTGAGCATCTCTGATAATATCTCAGGGCAGAAAATGTGCCTTACTAACAGATGTTAATGCTTCTTGATTTCTTTTTCTCTTTTGAGAACTCTTCAAAGTTGTTATTAAACAAATATCTATGTGCTTATTTGTCTTAATATCTAACAGCTTAGTTAGATTTCTAAGCTGCTATAAACAAGGACTGATTGGTTCACCACTGTATTGTTAGCACCTCCTATGGTATCTGGAATAAACAGTAACTCAGTTATTTAAGAATGGATGAGAAACCAGATTATCTTAGTTCATGTTTCTGAGTAATATTAAAATTAATATTAACAGTAAAATCCATAAGTATGCTACTTTAAAATATAAATCTCTGGCCAAAACCAAGACTTATTATTCAGGATCTTCAAGAGAAAGTGCTGAGATAATTCACTAAGTATCAGAGATGACCTTTATTACATGATTGCCTGATAGAAAAAATGATTACACACACACAAAAAAATCTTCAGTTGCTTAAATTTTAAACGTTGCTGACTCTCAAACAGTTAAGTAATAAAAGAGTTAAAGCCTGCTGTGTATTTAGAATATGTGAATACCTATTGAAAGAATTTATTGTACAATTAATATAAACAGACTTCTATTTTACAGTCATAAGATACTACTTAATTTGTTAAAAATTATTTTTTGATAGCATTGTTGGTAAATAGCAAAGGTGATATTTGCTAATGATTACAAGGGCTGTCTGGCTAACTTACGTTATGTTCAGGGAGAAGACAGTCCTTTTTAAGGAATGGGCACTTTCTAACTTTTTTTCTCTAGGATGGAGAAAAATTAGCCTTCTTCCTACTTTAAAAATGTTAGACATAGAATTAAGGGATTGTTATTTTGAGATTAAATTTTCTTTTCTCCTATTATTTTTCCTCATTCTGGAATGGAAGCAAAAGATGAAGAAAGAAATATATGTTAAATTGTTTTCCTTTAAATGAACACAAATGTGAAATATGTTTTTCTGCCTATCTTGTAAAATTTTCTATTGCAACTATTCTGATTACCAGTTCAAATGGGGAAAAAAGAACATAGGCTACCCCACACTTGAAATTTTGAAATATGAATGTCCTCTGTCTCTGCTGGTCTAACACTTCCAAAATGGAAACCTTTAAAGGGCCACTGTAAATTACAGCTGCTAATTCCTGGTGCCAATGGTGATAAGTGTTTACTAAACCTAGTGAGTACTTTATAGCATGGGGCTCTGCTGCGAAGTAACATTGCTGTATATTTTCAGTCATTCTACCTTAATTCATGAACTGCAAAACTCTCATCTAGCTTTTTACTTCTCTAGCTATTGCTTTAAGTTCTATCAGGCTCAGGTGTGGAATTCTC
diff --git a/tests/data/utils/fastx_handler/test_fasta.bam b/tests/data/utils/fastx_handler/test_fasta.bam
new file mode 100644
index 00000000..3a897506
Binary files /dev/null and b/tests/data/utils/fastx_handler/test_fasta.bam differ
diff --git a/tests/data/utils/fastx_handler/test_fasta.sam b/tests/data/utils/fastx_handler/test_fasta.sam
new file mode 100644
index 00000000..b887af06
--- /dev/null
+++ b/tests/data/utils/fastx_handler/test_fasta.sam
@@ -0,0 +1,2 @@
+@SQ	SN:ref	LN:45
+r001	99	ref	7	30	8M2I4M1D3M	=	37	39	AAAAAAAAAAAAAAAAA	*
diff --git a/tests/data/utils/fastx_handler/test_fastq.bam b/tests/data/utils/fastx_handler/test_fastq.bam
new file mode 100644
index 00000000..699416cf
Binary files /dev/null and b/tests/data/utils/fastx_handler/test_fastq.bam differ
diff --git a/tests/data/utils/fastx_handler/test_fastq.sam b/tests/data/utils/fastx_handler/test_fastq.sam
new file mode 100644
index 00000000..9d45f4e8
--- /dev/null
+++ b/tests/data/utils/fastx_handler/test_fastq.sam
@@ -0,0 +1,2 @@
+@SQ	SN:ref	LN:45
+r001	99	ref	7	30	8M2I4M1D3M	=	37	39	TTAGATAAAGGATACTG	IIIIIIIIIIIIIIIII
diff --git a/tests/data/utils/io/test.fasta b/tests/data/utils/io/test.fasta
new file mode 100644
index 00000000..4cf04d40
--- /dev/null
+++ b/tests/data/utils/io/test.fasta
@@ -0,0 +1,2 @@
+>test
+ACGT
diff --git a/tests/data/utils/io/test.fasta.gz b/tests/data/utils/io/test.fasta.gz
new file mode 100644
index 00000000..ad4b015b
Binary files /dev/null and b/tests/data/utils/io/test.fasta.gz differ
diff --git a/tests/src/classification/test_classification.py b/tests/src/classification/test_classification.py
index edf1b47e..4b9a90b9 100644
--- a/tests/src/classification/test_classification.py
+++ b/tests/src/classification/test_classification.py
@@ -1,4 +1,5 @@
 import pytest
+
 from DAJIN2.core.classification import classifier
 
 
diff --git a/tests/src/consensus/test_name_handler.py b/tests/src/consensus/test_name_handler.py
index 84c0a562..3e2db17b 100644
--- a/tests/src/consensus/test_name_handler.py
+++ b/tests/src/consensus/test_name_handler.py
@@ -2,6 +2,7 @@
 from typing import NamedTuple
 
 import pytest
+
 from DAJIN2.core.consensus.name_handler import (
     _detect_sv,
     _determine_suffix,
diff --git a/tests/src/preprocess/test_midsv_caller.py b/tests/src/preprocess/test_midsv_caller.py
index 56d44fbf..6734d7bd 100644
--- a/tests/src/preprocess/test_midsv_caller.py
+++ b/tests/src/preprocess/test_midsv_caller.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pytest
+
 from DAJIN2.core.preprocess.mapping import to_sam
 from DAJIN2.core.preprocess.midsv_caller import (
     convert_consecutive_indels_to_match,
diff --git a/tests/src/report/test_bam_exporter.py b/tests/src/report/test_bam_exporter.py
index fbbbaf47..e69de29b 100644
--- a/tests/src/report/test_bam_exporter.py
+++ b/tests/src/report/test_bam_exporter.py
@@ -1,38 +0,0 @@
-from __future__ import annotations
-
-from pathlib import Path
-from unittest.mock import patch
-
-import pytest
-
-from src.DAJIN2.core.report import bam_exporter
-
-###############################################################################
-# Output
-###############################################################################
-
-
-@pytest.fixture
-def mock_pysam_sort_and_index():
-    with patch("pysam.sort") as mock_sort:
-        with patch("pysam.index") as mock_index:
-            yield mock_sort, mock_index
-
-
-def test_write_sam_to_bam(mock_pysam_sort_and_index):
-    mock_sort, mock_index = mock_pysam_sort_and_index
-    sam_data = [["r1", "seq1", "+", "chr1", "1000"], ["r2", "seq2", "-", "chr2", "2000"]]
-    path_sam = "test.sam"
-    path_bam = "test.bam"
-    bam_exporter.write_sam_to_bam(sam_data, path_sam, path_bam)
-
-    mock_sort.assert_called_once()
-    mock_index.assert_called_once()
-
-    with open(path_sam) as f:
-        content = f.read()
-        expected_content = "r1\tseq1\t+\tchr1\t1000\nr2\tseq2\t-\tchr2\t2000\n"
-        assert content == expected_content
-
-    # テスト後のクリーンアップ
-    Path(path_sam).unlink()
diff --git a/tests/src/utils/test_cssplits_handler.py b/tests/src/utils/test_cssplits_handler.py
index 97783e05..b20876dc 100644
--- a/tests/src/utils/test_cssplits_handler.py
+++ b/tests/src/utils/test_cssplits_handler.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import pytest
+
 from DAJIN2.utils import cssplits_handler
 
 ###########################################################
diff --git a/tests/src/utils/test_fastx_handler.py b/tests/src/utils/test_fastx_handler.py
index a867e170..3589035f 100644
--- a/tests/src/utils/test_fastx_handler.py
+++ b/tests/src/utils/test_fastx_handler.py
@@ -1,6 +1,9 @@
 from __future__ import annotations
 
+import gzip
+
 import pytest
+
 from DAJIN2.utils import fastx_handler
 
 ########################################################################
@@ -53,3 +56,33 @@ def test_dictionize_allele_empty():
     with pytest.raises(ValueError) as e:
         fastx_handler.dictionize_allele(path_fasta)
     assert str(e.value) == "Provided name is empty or consists only of whitespace"
+
+
+########################################################################
+# convert_fasta_to_fastq
+########################################################################
+
+
+@pytest.mark.parametrize(
+    "fasta_content, expected_fastq_content",
+    [
+        (
+            ">seq1\nAGCTTAGCTAG\n>seq2\nCGATCGTAGC\n",
+            "@seq1\nAGCTTAGCTAG\n+\nIIIIIIIIIII\n@seq2\nCGATCGTAGC\n+\nIIIIIIIIII\n",
+        ),
+    ],
+)
+def test_convert_fasta_to_fastq(tmp_path, fasta_content, expected_fastq_content):
+    fasta_path = tmp_path / "test.fasta"
+    fastq_path = tmp_path / "test.fastq.gz"
+
+    # Write test FASTA content
+    fasta_path.write_text(fasta_content)
+
+    # Run conversion function
+    fastx_handler.convert_fasta_to_fastq(fasta_path, fastq_path)
+
+    # Read and check if gzipped FASTQ output matches expected output
+    with gzip.open(fastq_path, "rt") as f:
+        result = f.read()
+    assert result == expected_fastq_content
diff --git a/tests/src/utils/test_input_validator.py b/tests/src/utils/test_input_validator.py
index edf789ae..906e7b63 100644
--- a/tests/src/utils/test_input_validator.py
+++ b/tests/src/utils/test_input_validator.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pytest
+
 from DAJIN2.utils import input_validator
 
 ###############################################################################
diff --git a/tests/src/utils/test_io.py b/tests/src/utils/test_io.py
index 0b348bf4..ffca4889 100644
--- a/tests/src/utils/test_io.py
+++ b/tests/src/utils/test_io.py
@@ -83,6 +83,22 @@ def test_write_jsonl():
     os.remove(test_filename)
 
 
+########################################################################
+# is_gip_file
+########################################################################
+
+
+@pytest.mark.parametrize(
+    "path_file, expected",
+    [
+        ("tests/data/utils/io/test.fasta.gz", True),
+        ("tests/data/utils/io/test.fasta", False),
+    ],
+)
+def test_is_gzip_file(path_file, expected):
+    assert io.is_gzip_file(path_file) == expected
+
+
 # Utility function to create a temporary file and write some data into it
 def create_temp_file(tmp_path, filename, content):
     file_path = tmp_path / filename
diff --git a/tests/src/utils/test_sam_handler.py b/tests/src/utils/test_sam_handler.py
index f24d5673..c73b5838 100644
--- a/tests/src/utils/test_sam_handler.py
+++ b/tests/src/utils/test_sam_handler.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import midsv
+
 from DAJIN2.utils.sam_handler import (
     calculate_alignment_length,
     is_header,
diff --git a/tests/test_version.sh b/tests/test_version.sh
deleted file mode 100644
index 527b4afd..00000000
--- a/tests/test_version.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-PIP_VERSION=$(echo $(DAJIN2 --version) | awk '{print $NF}')
-UTIL_VERSION=$(grep "DAJIN_VERSION" src/DAJIN2/utils/config.py | awk '{print $NF}' | tr -d '"')
-
-if [ -z "$PIP_VERSION" ]; then
-    echo "DAJIN2 is not installed"
-    exit 1
-fi
-
-if [ -z "$UTIL_VERSION" ]; then
-    echo "src/DAJIN2/utils/config.py is not found"
-    exit 1
-fi
-
-if [ "$PIP_VERSION" != "$UTIL_VERSION" ]; then
-    echo "Version mismatch: $PIP_VERSION != $UTIL_VERSION"
-    exit 1
-fi