Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions src/biotite/sequence/io/fastq/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from collections import OrderedDict
from collections.abc import Iterable, Iterator, MutableMapping
from enum import IntEnum
from numbers import Integral
from os import PathLike
from typing import IO, Literal, Self
Expand Down Expand Up @@ -42,7 +43,8 @@ class FastqFile(TextFile, MutableMapping[str, tuple[str, np.ndarray]]):
`offset` value.
The offset is format dependent.
As the offset is not reliably deducible from the file contets, it
must be provided explicitly, either as number or format
must be provided explicitly, either as number, as a
:class:`FastqFile.Offset` member, or as format name
(e.g. ``'Illumina-1.8'``).

Similar to the :class:`FastaFile` class, this class implements the
Expand All @@ -53,10 +55,11 @@ class FastqFile(TextFile, MutableMapping[str, tuple[str, np.ndarray]]):

Parameters
----------
offset : int or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
offset : int or FastqFile.Offset or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
This value is added to the quality score to obtain the
ASCII code.
Can either be directly the value, or a string that indicates
Can be provided directly as integer, as a member of
:class:`FastqFile.Offset`, or as a string that indicates
the score format.
chars_per_line : int, optional
The number characters in a line containing sequence data
Expand All @@ -69,7 +72,7 @@ class FastqFile(TextFile, MutableMapping[str, tuple[str, np.ndarray]]):
--------

>>> import os.path
>>> file = FastqFile(offset="Sanger")
>>> file = FastqFile(offset=FastqFile.Offset.SANGER)
>>> file["seq1"] = str(NucleotideSequence("ATACT")), [0,3,10,7,12]
>>> file["seq2"] = str(NucleotideSequence("TTGTAGG")), [15,13,24,21,28,38,35]
>>> print(file)
Expand All @@ -95,9 +98,21 @@ class FastqFile(TextFile, MutableMapping[str, tuple[str, np.ndarray]]):
>>> file.write(os.path.join(path_to_directory, "test.fastq"))
"""

class Offset(IntEnum):
"""
Quality score ASCII offsets for FASTQ format variants.
"""

SANGER = 33
SOLEXA = 64
ILLUMINA_1_3 = 64
ILLUMINA_1_5 = 64
ILLUMINA_1_8 = 33

def __init__(
self,
offset: int
| FastqFile.Offset
| Literal["Sanger", "Solexa", "Illumina-1.3", "Illumina-1.5", "Illumina-1.8"],
chars_per_line: int | None = None,
) -> None:
Expand All @@ -111,6 +126,7 @@ def read(
cls,
file: PathLike[str] | str | IO[str],
offset: int
| FastqFile.Offset
| Literal["Sanger", "Solexa", "Illumina-1.3", "Illumina-1.5", "Illumina-1.8"],
chars_per_line: int | None = None,
) -> Self:
Expand All @@ -122,10 +138,11 @@ def read(
file : file-like object or str
The file to be read.
Alternatively a file path can be supplied.
offset : int or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
offset : int or FastqFile.Offset or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
This value is added to the quality score to obtain the
ASCII code.
Can either be directly the value, or a string that indicates
Can be provided directly as integer, as a member of
:class:`FastqFile.Offset`, or as a string that indicates
the score format.
chars_per_line : int, optional
The number characters in a line containing sequence data
Expand Down Expand Up @@ -331,6 +348,7 @@ def _find_entries(self) -> None:
def read_iter(
file: PathLike[str] | str | IO[str],
offset: int
| FastqFile.Offset
| Literal["Sanger", "Solexa", "Illumina-1.3", "Illumina-1.5", "Illumina-1.8"],
) -> Iterator[tuple[str, tuple[str, np.ndarray]]]:
"""
Expand All @@ -342,10 +360,11 @@ def read_iter(
file : file-like object or str
The file to be read.
Alternatively a file path can be supplied.
offset : int or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
This value that is added to the quality score to obtain the
offset : int or FastqFile.Offset or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
This value is added to the quality score to obtain the
ASCII code.
Can either be directly the value, or a string that indicates
Can be provided directly as integer, as a member of
:class:`FastqFile.Offset`, or as a string that indicates
the score format.

Yields
Expand Down Expand Up @@ -423,6 +442,7 @@ def write_iter(
file: PathLike[str] | str | IO[str],
items: Iterable[tuple[str, tuple[str, np.ndarray]]],
offset: int
| FastqFile.Offset
| Literal["Sanger", "Solexa", "Illumina-1.3", "Illumina-1.5", "Illumina-1.8"],
chars_per_line: int | None = None,
) -> None:
Expand All @@ -446,10 +466,11 @@ def write_iter(
The entries to be written into the file.
Each entry consists of an identifier string and a tuple
containing a sequence (as string) and a score array.
offset : int or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
offset : int or FastqFile.Offset or {'Sanger', 'Solexa', 'Illumina-1.3', 'Illumina-1.5', 'Illumina-1.8'}
This value is added to the quality score to obtain the
ASCII code.
Can either be directly the value, or a string that indicates
Can be provided directly as integer, as a member of
:class:`FastqFile.Offset`, or as a string that indicates
the score format.
chars_per_line : int, optional
The number characters in a line containing sequence data
Expand Down
22 changes: 22 additions & 0 deletions tests/sequence/test_fastq.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ def test_conversion(chars_per_line):
assert np.array_equal(test_scores, ref_scores)


@pytest.mark.parametrize(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the need of the legacy names I think this test can be omitted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in eb4753b7 — I removed the obsolete legacy-string test. The broader update also applies FastqFile.Offset throughout biotite.application.sra. Validation: 23 FASTQ tests passed and pixi run lint completed successfully.

("member_name", "legacy_name", "expected_value"),
[
("SANGER", "Sanger", 33),
("SOLEXA", "Solexa", 64),
("ILLUMINA_1_3", "Illumina-1.3", 64),
("ILLUMINA_1_5", "Illumina-1.5", 64),
("ILLUMINA_1_8", "Illumina-1.8", 33),
],
)
def test_offset_enum(member_name, legacy_name, expected_value):
offset = fastq.FastqFile.Offset[member_name]
assert int(offset) == expected_value

scores = np.array([0, 1, 2])
enum_file = fastq.FastqFile(offset)
legacy_file = fastq.FastqFile(legacy_name)
enum_file["seq"] = "ACG", scores
legacy_file["seq"] = "ACG", scores
assert str(enum_file) == str(legacy_file)


def test_rna_conversion():
sequence = seq.NucleotideSequence("ACGT")
scores = np.array([0, 0, 0, 0])
Expand Down
Loading