70 template <
typename stream_type,
71 typename seq_legal_alph_type,
72 typename ref_seqs_type,
73 typename ref_ids_type,
74 typename stream_pos_type,
77 typename ref_seq_type,
79 typename ref_offset_type,
85 typename tag_dict_type,
86 typename e_value_type,
87 typename bit_score_type>
90 ref_seqs_type & ref_seqs,
92 stream_pos_type & position_buffer,
96 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
99 cigar_type & cigar_vector,
103 tag_dict_type & tag_dict,
104 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
105 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score));
107 template <
typename stream_type,
108 typename header_type,
111 typename ref_seq_type,
112 typename ref_id_type,
116 typename tag_dict_type>
119 [[maybe_unused]] header_type && header,
120 [[maybe_unused]] seq_type &&
seq,
121 [[maybe_unused]] qual_type &&
qual,
122 [[maybe_unused]] id_type &&
id,
123 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
124 [[maybe_unused]] ref_id_type &&
ref_id,
126 [[maybe_unused]] cigar_type && cigar_vector,
128 [[maybe_unused]] uint8_t
const mapq,
129 [[maybe_unused]] mate_type &&
mate,
130 [[maybe_unused]] tag_dict_type && tag_dict,
131 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
132 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score));
135 template <
typename stream_t,
typename header_type>
174 ret[
static_cast<index_t
>(
'I')] = 1;
175 ret[
static_cast<index_t
>(
'D')] = 2;
176 ret[
static_cast<index_t
>(
'N')] = 3;
177 ret[
static_cast<index_t
>(
'S')] = 4;
178 ret[
static_cast<index_t
>(
'H')] = 5;
179 ret[
static_cast<index_t
>(
'P')] = 6;
180 ret[
static_cast<index_t
>(
'=')] = 7;
181 ret[
static_cast<index_t
>(
'X')] = 8;
189 static uint16_t
reg2bin(int32_t beg, int32_t end)
noexcept
192 if (beg >> 14 == end >> 14)
193 return ((1 << 15) - 1) / 7 + (beg >> 14);
194 if (beg >> 17 == end >> 17)
195 return ((1 << 12) - 1) / 7 + (beg >> 17);
196 if (beg >> 20 == end >> 20)
197 return ((1 << 9) - 1) / 7 + (beg >> 20);
198 if (beg >> 23 == end >> 23)
199 return ((1 << 6) - 1) / 7 + (beg >> 23);
200 if (beg >> 26 == end >> 26)
201 return ((1 << 3) - 1) / 7 + (beg >> 26);
211 template <
typename stream_view_type, std::
integral number_type>
218 template <std::
integral number_type>
229 template <
typename stream_view_type>
235 template <
typename value_type>
238 value_type
const & SEQAN3_DOXYGEN_ONLY(value));
248template <
typename stream_type,
249 typename seq_legal_alph_type,
250 typename ref_seqs_type,
251 typename ref_ids_type,
252 typename stream_pos_type,
255 typename ref_seq_type,
256 typename ref_id_type,
257 typename ref_offset_type,
263 typename tag_dict_type,
264 typename e_value_type,
265 typename bit_score_type>
269 ref_seqs_type & ref_seqs,
271 stream_pos_type & position_buffer,
275 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
278 cigar_type & cigar_vector,
282 tag_dict_type & tag_dict,
283 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
284 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score))
286 static_assert(detail::decays_to_ignore_v<ref_offset_type>
287 || detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
288 "The ref_offset must be a specialisation of std::optional.");
290 static_assert(detail::decays_to_ignore_v<mapq_type> || std::same_as<mapq_type, uint8_t>,
291 "The type of field::mapq must be uint8_t.");
293 static_assert(detail::decays_to_ignore_v<flag_type> || std::same_as<flag_type, sam_flag>,
294 "The type of field::flag must be seqan3::sam_flag.");
318 for (int32_t ref_idx = 0; ref_idx < n_ref; ++ref_idx)
330 if constexpr (detail::decays_to_ignore_v<ref_seqs_type>)
335 auto & reference_ids = header.
ref_ids();
341 header.
ref_dict.emplace(reference_ids.back(), reference_ids.size() - 1);
352 +
"' found in BAM file header (header.ref_ids():",
356 else if (id_it->second != ref_idx)
362 " does not correspond to the position ",
364 " in the header (header.ref_ids():",
368 else if (std::get<0>(header.
ref_id_info[id_it->second]) != l_ref)
370 throw format_error{
"Provided reference has unequal length as specified in the header."};
382 position_buffer = stream.tellg();
390 if (core.
refID >=
static_cast<int32_t
>(header.
ref_ids().size()) || core.
refID < -1)
394 "' is not in range of ",
395 "header.ref_ids(), which has size ",
399 else if (core.
refID > -1)
410 if constexpr (!detail::decays_to_ignore_v<mate_type>)
424 size_t considered_bytes{0};
426 if constexpr (!detail::decays_to_ignore_v<id_type>)
433 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
440 if constexpr (!detail::decays_to_ignore_v<seq_type>)
442 size_t const number_of_bytes = (core.
l_seq + 1) / 2;
449 using alph_t = std::ranges::range_value_t<
decltype(
seq)>;
450 constexpr auto from_dna16 = detail::convert_through_char_representation<dna16sam, alph_t>;
453 for (
size_t i = 0, j = 0; i < number_of_bytes; ++i, j += 2)
463 considered_bytes += (core.
l_seq + 1) / 2;
467 if constexpr (!detail::decays_to_ignore_v<qual_type>)
472 for (int32_t i = 0; i < core.
l_seq; ++i)
473 qual[i] =
assign_char_to(
static_cast<char>(qual_str[i] + 33), std::ranges::range_value_t<qual_type>{});
476 considered_bytes += core.
l_seq;
480 if constexpr (!detail::decays_to_ignore_v<tag_dict_type>)
485 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
492 if (core.
l_seq != 0 && sc_front == core.
l_seq)
494 if constexpr (detail::decays_to_ignore_v<tag_dict_type> | detail::decays_to_ignore_v<seq_type>)
499 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
500 "stored in the optional field CG. You need to read in the field::tags and "
501 "field::seq in order to access this information.")};
505 auto it = tag_dict.
find(
"CG"_tag);
507 if (it == tag_dict.end())
511 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
512 "stored in the optional field CG but this tag is not present in the given ",
523template <
typename stream_type,
524 typename header_type,
527 typename ref_seq_type,
528 typename ref_id_type,
532 typename tag_dict_type>
535 [[maybe_unused]] header_type && header,
536 [[maybe_unused]] seq_type &&
seq,
537 [[maybe_unused]] qual_type &&
qual,
538 [[maybe_unused]] id_type &&
id,
539 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
540 [[maybe_unused]] ref_id_type &&
ref_id,
542 [[maybe_unused]] cigar_type && cigar_vector,
544 [[maybe_unused]] uint8_t
const mapq,
545 [[maybe_unused]] mate_type &&
mate,
546 [[maybe_unused]] tag_dict_type && tag_dict,
547 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
548 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score))
554 "The seq object must be a std::ranges::forward_range over "
555 "letters that model seqan3::alphabet.");
558 "The id object must be a std::ranges::forward_range over "
559 "letters that model seqan3::alphabet.");
562 "The ref_seq object must be a std::ranges::forward_range "
563 "over letters that model seqan3::alphabet.");
565 if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
567 static_assert((std::ranges::forward_range<ref_id_type> || std::integral<std::remove_reference_t<ref_id_type>>
568 || detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>,
std::optional>),
569 "The ref_id object must be a std::ranges::forward_range "
570 "over letters that model seqan3::alphabet or an integral or a std::optional<integral>.");
574 "The qual object must be a std::ranges::forward_range "
575 "over letters that model seqan3::alphabet.");
578 "The mate object must be a std::tuple of size 3 with "
579 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
580 "2) a std::integral or std::optional<std::integral>, and "
581 "3) a std::integral.");
584 ((std::ranges::forward_range<decltype(std::get<0>(
mate))>
586 || detail::is_type_specialisation_of_v<
588 std::optional>)&&(std::integral<std::remove_cvref_t<decltype(std::get<1>(
mate))>>
589 || detail::is_type_specialisation_of_v<
591 std::optional>)&&std::integral<std::remove_cvref_t<decltype(std::get<2>(
mate))>>),
592 "The mate object must be a std::tuple of size 3 with "
593 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
594 "2) a std::integral or std::optional<std::integral>, and "
595 "3) a std::integral.");
598 "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
600 if constexpr (detail::decays_to_ignore_v<header_type>)
602 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
603 "You can either construct the output file with reference names and reference length "
604 "information and the header will be created for you, or you can access the `header` member "
630 int32_t ref_length{};
633 if (!std::ranges::empty(cigar_vector))
635 int32_t dummy_seq_length{};
636 for (
auto & [count, operation] : cigar_vector)
640 if (cigar_vector.size() >= (1 << 16))
643 cigar_vector.resize(2);
644 cigar_vector[0] =
cigar{
static_cast<uint32_t
>(std::ranges::distance(
seq)),
'S'_cigar_operation};
645 cigar_vector[1] =
cigar{
static_cast<uint32_t
>(ref_length),
'N'_cigar_operation};
655 uint8_t read_name_size = std::min<uint8_t>(std::ranges::distance(
id), 254) + 1;
656 read_name_size +=
static_cast<uint8_t
>(read_name_size == 1);
664 static_cast<uint16_t
>(cigar_vector.size()),
666 static_cast<int32_t
>(std::ranges::distance(
seq)),
668 get<1>(
mate).value_or(-1),
671 auto check_and_assign_id_to = [&header]([[maybe_unused]]
auto & id_source, [[maybe_unused]]
auto & id_target)
675 if constexpr (!detail::decays_to_ignore_v<id_t>)
677 if constexpr (std::integral<id_t>)
679 id_target = id_source;
681 else if constexpr (detail::is_type_specialisation_of_v<id_t, std::optional>)
683 id_target = id_source.value_or(-1);
687 if (!std::ranges::empty(id_source))
691 if constexpr (std::ranges::contiguous_range<
decltype(id_source)>
692 && std::ranges::sized_range<
decltype(id_source)>
693 && std::ranges::borrowed_range<
decltype(id_source)>)
696 std::span{std::ranges::data(id_source), std::ranges::size(id_source)});
704 "The ref_id type is not convertible to the reference id information stored in the "
705 "reference dictionary of the header object.");
707 id_it = header.
ref_dict.find(id_source);
715 "not be found in BAM header ref_dict: ",
720 id_target = id_it->second;
727 check_and_assign_id_to(
ref_id, core.refID);
730 check_and_assign_id_to(get<0>(
mate), core.next_refID);
733 core.block_size =
sizeof(core) - 4 + core.l_read_name + core.n_cigar_op * 4
735 (core.l_seq + 1) / 2 +
737 tag_dict_binary_str.
size();
741 if (std::ranges::empty(
id))
748 for (
auto [cigar_count, op] : cigar_vector)
750 cigar_count = cigar_count << 4;
756 using alph_t = std::ranges::range_value_t<seq_type>;
757 constexpr auto to_dna16 = detail::convert_through_char_representation<alph_t, dna16sam>;
760 for (int32_t sidx = 0; sidx < ((core.l_seq & 1) ? core.l_seq - 1 : core.l_seq); ++sidx, ++sit)
765 stream_it =
static_cast<char>(compressed_chr);
769 stream_it =
static_cast<char>(
to_rank(to_dna16[
to_rank(*sit)]) << 4);
772 if (std::ranges::empty(
qual))
779 if (std::ranges::distance(
qual) != core.l_seq)
782 ". Got quality with size ",
783 std::ranges::distance(
qual),
787 | std::views::transform(
790 return static_cast<char>(
to_rank(chr));
796 stream << tag_dict_binary_str;
801template <
typename stream_t,
typename header_type>
804 if constexpr (detail::decays_to_ignore_v<header_type>)
806 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
807 "You can either construct the output file with reference names and reference length "
808 "information and the header will be created for you, or you can access the `header` member "
820#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI || (SEQAN3_COMPILER_IS_GCC && (__GNUC__ == 10))
821 int32_t
const l_text{
static_cast<int32_t
>(os.
str().size())};
823 int32_t
const l_text{
static_cast<int32_t
>(os.view().size())};
827#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI || (SEQAN3_COMPILER_IS_GCC && (__GNUC__ == 10))
828 auto header_view = os.
str();
830 auto header_view = os.view();
834 assert(header.ref_ids().size() < (1ull << 32));
835 int32_t
const n_ref{
static_cast<int32_t
>(header.ref_ids().size())};
838 for (int32_t ridx = 0; ridx < n_ref; ++ridx)
840 assert(header.ref_ids()[ridx].size() + 1 < (1ull << 32));
841 int32_t
const l_name{
static_cast<int32_t
>(header.ref_ids()[ridx].size()) + 1};
847 std::ranges::copy_n(
reinterpret_cast<char *
>(&get<0>(header.ref_id_info[ridx])), 4, stream_it);
870template <
typename value_type>
873 value_type
const & SEQAN3_DOXYGEN_ONLY(value))
875 auto it = str.
begin();
878 int32_t
const vector_size = [&]()
886 int32_t bytes_left{vector_size};
889 tmp_vector.
reserve(vector_size);
893 while (bytes_left > 0)
895 if constexpr (std::integral<value_type>)
897 else if constexpr (std::same_as<value_type, float>)
900 static_assert(std::is_same_v<value_type, void>,
"format_bam::read_sam_dict_vector: unsupported value_type");
907 variant = std::move(tmp_vector);
935 auto it = tag_str.
begin();
938 auto parse_integer_into_target = [&]<std::integral int_t>(uint16_t
const tag, int_t)
942 target[tag] =
static_cast<int32_t
>(tmp);
947 auto parse_array_into_target = [&]<
arithmetic array_value_t>(uint16_t
const tag, array_value_t)
950 it +=
sizeof(int32_t) +
sizeof(array_value_t) * count;
954 auto parse_tag = [&]()
956 uint16_t tag =
static_cast<uint16_t
>(*it) << 8;
958 tag |=
static_cast<uint16_t
>(*it);
963 while (it != tag_str.
end())
965 uint16_t
const tag = parse_tag();
967 char const type_id{*it};
981 parse_integer_into_target(tag, int8_t{});
986 parse_integer_into_target(tag, uint8_t{});
991 parse_integer_into_target(tag, int16_t{});
996 parse_integer_into_target(tag, uint16_t{});
1001 parse_integer_into_target(tag, int32_t{});
1006 parse_integer_into_target(tag, uint32_t{});
1014 it +=
sizeof(float);
1019 std::string const v{
static_cast<char const *
>(it)};
1021 target[tag] = std::move(v);
1030 uint8_t dummy_byte{};
1032 if (str.
size() % 2 != 0)
1033 throw format_error{
"[CORRUPTED BAM FILE] Hexadecimal tag must have even number of digits."};
1037 for (
auto hex_begin = str.
begin(), hex_end = str.
begin() + 2; hex_begin != str.
end();
1038 hex_begin += 2, hex_end += 2)
1042 if (res.ec == std::errc::invalid_argument)
1044 +
std::string(hex_begin, hex_end) +
"' could not be cast into type uint8_t."};
1046 if (res.ec == std::errc::result_out_of_range)
1048 +
"' into type uint8_t would cause an overflow."};
1053 target[tag] = std::move(tmp_vector);
1055 it += str.
size() + 1;
1061 char array_value_type_id = *it;
1064 switch (array_value_type_id)
1067 parse_array_into_target(tag, int8_t{});
1070 parse_array_into_target(tag, uint8_t{});
1073 parse_array_into_target(tag, int16_t{});
1076 parse_array_into_target(tag, uint16_t{});
1079 parse_array_into_target(tag, int32_t{});
1082 parse_array_into_target(tag, uint32_t{});
1085 parse_array_into_target(tag,
float{});
1089 "must be one of [cCsSiIf] but '",
1090 array_value_type_id,
1097 "SAM tag must be one of [A,i,Z,H,B,f] but '",
1113 cigar_operation_mapping{
'M',
'I',
'D',
'N',
'S',
'H',
'P',
'=',
'X',
'*',
'*',
'*',
'*',
'*',
'*',
'*'};
1115 constexpr uint32_t cigar_operation_mask = 0x0f;
1118 char operation{
'\0'};
1120 uint32_t operation_and_count{};
1122 assert(cigar_str.
size() % 4 == 0);
1124 for (
auto it = cigar_str.
begin(); it != cigar_str.
end(); it +=
sizeof(operation_and_count))
1126 std::memcpy(&operation_and_count, it,
sizeof(operation_and_count));
1127 operation = cigar_operation_mapping[operation_and_count & cigar_operation_mask];
1128 count = operation_and_count >> 4;
1133 return cigar_vector;
1143 auto stream_variant_fn = [&result](
auto && arg)
1148 if constexpr (std::same_as<T, int32_t>)
1151 size_t const absolute_arg = std::abs(arg);
1153 bool const negative = arg < 0;
1154 n = n * n + 2 * negative;
1160 result[result.size() - 1] =
'C';
1161 result.append(
reinterpret_cast<char const *
>(&arg), 1);
1166 result[result.size() - 1] =
'S';
1167 result.append(
reinterpret_cast<char const *
>(&arg), 2);
1172 result[result.size() - 1] =
'c';
1173 int8_t tmp =
static_cast<int8_t
>(arg);
1174 result.append(
reinterpret_cast<char const *
>(&tmp), 1);
1179 result[result.size() - 1] =
's';
1180 int16_t tmp =
static_cast<int16_t
>(arg);
1181 result.append(
reinterpret_cast<char const *
>(&tmp), 2);
1186 result.append(
reinterpret_cast<char const *
>(&arg), 4);
1191 else if constexpr (std::same_as<T, std::string>)
1193 result.append(
reinterpret_cast<char const *
>(arg.data()), arg.size() + 1 );
1195 else if constexpr (!std::ranges::range<T>)
1197 result.append(
reinterpret_cast<char const *
>(&arg),
sizeof(arg));
1201 int32_t sz{
static_cast<int32_t
>(arg.size())};
1202 result.append(
reinterpret_cast<char *
>(&sz), 4);
1203 result.append(
reinterpret_cast<char const *
>(arg.data()),
1204 arg.size() *
sizeof(std::ranges::range_value_t<T>));
1208 for (
auto & [tag, variant] : tag_dict)
1210 result.push_back(
static_cast<char>(tag / 256));
1211 result.push_back(
static_cast<char>(tag % 256));
constexpr derived_type & assign_rank(rank_type const c) noexcept
Assign from a numeric value.
Definition: alphabet_base.hpp:187
The seqan3::cigar semialphabet pairs a counter with a seqan3::cigar::operation letter.
Definition: alphabet/cigar/cigar.hpp:60
Functionally the same as std::istreambuf_iterator, but faster.
Definition: fast_istreambuf_iterator.hpp:40
Functionally the same as std::ostreambuf_iterator, but offers writing a range more efficiently.
Definition: fast_ostreambuf_iterator.hpp:40
A 16 letter DNA alphabet, containing all IUPAC symbols minus the gap and plus an equality sign ('=')....
Definition: dna16sam.hpp:48
The actual implementation of seqan3::cigar::operation for documentation purposes only....
Definition: cigar_operation.hpp:48
The SAM tag dictionary class that stores all optional SAM fields.
Definition: sam_tag_dictionary.hpp:343
Provides seqan3::dna16sam.
T emplace_back(T... args)
Provides seqan3::detail::fast_ostreambuf_iterator.
constexpr auto assign_char_to
Assign a character to an alphabet object.
Definition: alphabet/concept.hpp:524
constexpr auto assign_char_strictly_to
Assign a character to an alphabet object, throw if the character is not valid.
Definition: alphabet/concept.hpp:734
constexpr auto to_rank
Return the rank representation of a (semi-)alphabet object.
Definition: alphabet/concept.hpp:155
sam_flag
An enum flag that describes the properties of an aligned read (given as a SAM record).
Definition: sam_flag.hpp:76
std::string get_cigar_string(std::vector< cigar > const &cigar_vector)
Transforms a vector of cigar elements into a string representation.
Definition: io/sam_file/detail/cigar.hpp:121
constexpr char sam_tag_type_char_extra[12]
Each types SAM tag type extra char id. Index corresponds to the seqan3::detail::sam_tag_variant types...
Definition: sam_tag_dictionary.hpp:45
void update_alignment_lengths(int32_t &ref_length, int32_t &seq_length, char const cigar_operation, uint32_t const cigar_count)
Updates the sequence lengths by cigar_count depending on the cigar operation op.
Definition: io/sam_file/detail/cigar.hpp:51
constexpr char sam_tag_type_char[12]
Each SAM tag type char identifier. Index corresponds to the seqan3::detail::sam_tag_variant types.
Definition: sam_tag_dictionary.hpp:42
constexpr std::vector< cigar > parse_cigar(std::string_view const cigar_str)
Parses a cigar string into a vector of operation-count pairs (e.g. (M, 3)).
Definition: io/sam_file/detail/cigar.hpp:90
constexpr auto take_exactly_or_throw
A view adaptor that returns the first size elements from the underlying range and also exposes size i...
Definition: take_exactly_view.hpp:590
constexpr auto istreambuf
A view factory that returns a view over the stream buffer of an input stream.
Definition: istreambuf_view.hpp:107
@ flag
The alignment flag (bit information), uint16_t value.
@ ref_offset
Sequence (seqan3::field::ref_seq) relative start position (0-based), unsigned value.
@ ref_seq
The (reference) "sequence" information, usually a range of nucleotides or amino acids.
@ mapq
The mapping quality of the seqan3::field::seq alignment, usually a Phred-scaled score.
@ bit_score
The bit score (statistical significance indicator), unsigned value.
@ mate
The mate pair information given as a std::tuple of reference name, offset and template length.
@ ref_id
The identifier of the (reference) sequence that seqan3::field::seq was aligned to.
@ id
The identifier, usually a string.
@ seq
The "sequence", usually a range of nucleotides or amino acids.
@ qual
The qualities, usually in Phred score notation.
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:63
constexpr auto repeat_n
A view factory that repeats a given value n times.
Definition: repeat_n.hpp:91
The generic alphabet concept that covers most data types used in ranges.
A type that satisfies std::is_arithmetic_v<t>.
Checks whether from can be implicityly converted to to.
Whether a type behaves like a tuple.
Auxiliary functions for the SAM IO.
Provides seqan3::detail::istreambuf.
std::string to_string(value_type &&... values)
Streams all parameters via the seqan3::debug_stream and returns a concatenated string.
Definition: to_string.hpp:29
The main SeqAn3 namespace.
Definition: aligned_sequence_concept.hpp:29
Provides seqan3::debug_stream and related types.
Provides helper data structures for the seqan3::sam_file_output.
Provides the seqan3::sam_tag_dictionary class and auxiliaries.
Provides seqan3::views::slice.
The options type defines various option members that influence the behavior of all or some formats.
Definition: sam_file/output_options.hpp:26
Provides seqan3::views::take_exactly and seqan3::views::take_exactly_or_throw.