68 template <
typename stream_type,
69 typename seq_legal_alph_type,
70 typename ref_seqs_type,
71 typename ref_ids_type,
72 typename stream_pos_type,
75 typename ref_seq_type,
77 typename ref_offset_type,
83 typename tag_dict_type,
84 typename e_value_type,
85 typename bit_score_type>
88 ref_seqs_type & ref_seqs,
90 stream_pos_type & position_buffer,
94 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
97 cigar_type & cigar_vector,
101 tag_dict_type & tag_dict,
102 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
103 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score));
105 template <
typename stream_type,
106 typename header_type,
109 typename ref_seq_type,
110 typename ref_id_type,
114 typename tag_dict_type>
117 [[maybe_unused]] header_type && header,
118 [[maybe_unused]] seq_type &&
seq,
119 [[maybe_unused]] qual_type &&
qual,
120 [[maybe_unused]] id_type &&
id,
121 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
122 [[maybe_unused]] ref_id_type &&
ref_id,
124 [[maybe_unused]] cigar_type && cigar_vector,
126 [[maybe_unused]] uint8_t
const mapq,
127 [[maybe_unused]] mate_type &&
mate,
128 [[maybe_unused]] tag_dict_type && tag_dict,
129 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
130 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score));
133 template <
typename stream_t,
typename header_type>
170 ret[
static_cast<index_t
>(
'I')] = 1;
171 ret[
static_cast<index_t
>(
'D')] = 2;
172 ret[
static_cast<index_t
>(
'N')] = 3;
173 ret[
static_cast<index_t
>(
'S')] = 4;
174 ret[
static_cast<index_t
>(
'H')] = 5;
175 ret[
static_cast<index_t
>(
'P')] = 6;
176 ret[
static_cast<index_t
>(
'=')] = 7;
177 ret[
static_cast<index_t
>(
'X')] = 8;
183 static uint16_t
reg2bin(int32_t beg, int32_t end)
noexcept
186 if (beg >> 14 == end >> 14)
187 return ((1 << 15) - 1) / 7 + (beg >> 14);
188 if (beg >> 17 == end >> 17)
189 return ((1 << 12) - 1) / 7 + (beg >> 17);
190 if (beg >> 20 == end >> 20)
191 return ((1 << 9) - 1) / 7 + (beg >> 20);
192 if (beg >> 23 == end >> 23)
193 return ((1 << 6) - 1) / 7 + (beg >> 23);
194 if (beg >> 26 == end >> 26)
195 return ((1 << 3) - 1) / 7 + (beg >> 26);
183 static uint16_t
reg2bin(int32_t beg, int32_t end)
noexcept {
…}
205 template <
typename stream_view_type, std::
integral number_type>
212 template <std::
integral number_type>
223 template <
typename stream_view_type>
229 template <
typename value_type>
232 value_type
const & SEQAN3_DOXYGEN_ONLY(value));
242template <
typename stream_type,
243 typename seq_legal_alph_type,
244 typename ref_seqs_type,
245 typename ref_ids_type,
246 typename stream_pos_type,
249 typename ref_seq_type,
250 typename ref_id_type,
251 typename ref_offset_type,
257 typename tag_dict_type,
258 typename e_value_type,
259 typename bit_score_type>
263 ref_seqs_type & ref_seqs,
265 stream_pos_type & position_buffer,
269 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
272 cigar_type & cigar_vector,
276 tag_dict_type & tag_dict,
277 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
278 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score))
280 static_assert(detail::decays_to_ignore_v<ref_offset_type>
281 || detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
282 "The ref_offset must be a specialisation of std::optional.");
284 static_assert(detail::decays_to_ignore_v<mapq_type> || std::same_as<mapq_type, uint8_t>,
285 "The type of field::mapq must be uint8_t.");
287 static_assert(detail::decays_to_ignore_v<flag_type> || std::same_as<flag_type, sam_flag>,
288 "The type of field::flag must be seqan3::sam_flag.");
312 for (int32_t ref_idx = 0; ref_idx < n_ref; ++ref_idx)
324 if constexpr (detail::decays_to_ignore_v<ref_seqs_type>)
329 auto & reference_ids = header.
ref_ids();
335 header.
ref_dict.emplace(reference_ids.back(), reference_ids.size() - 1);
346 +
"' found in BAM file header (header.ref_ids():",
350 else if (id_it->second != ref_idx)
356 " does not correspond to the position ",
358 " in the header (header.ref_ids():",
362 else if (std::get<0>(header.
ref_id_info[id_it->second]) != l_ref)
364 throw format_error{
"Provided reference has unequal length as specified in the header."};
376 position_buffer = stream.tellg();
384 if (core.
refID >=
static_cast<int32_t
>(header.
ref_ids().size()) || core.
refID < -1)
388 "' is not in range of ",
389 "header.ref_ids(), which has size ",
393 else if (core.
refID > -1)
399 mapq =
static_cast<uint8_t
>(core.
mapq);
404 if constexpr (!detail::decays_to_ignore_v<mate_type>)
418 size_t considered_bytes{0};
420 if constexpr (!detail::decays_to_ignore_v<id_type>)
427 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
434 if constexpr (!detail::decays_to_ignore_v<seq_type>)
436 size_t const number_of_bytes = (core.
l_seq + 1) / 2;
443 using alph_t = std::ranges::range_value_t<
decltype(
seq)>;
444 constexpr auto from_dna16 = detail::convert_through_char_representation<dna16sam, alph_t>;
447 for (
size_t i = 0, j = 0; i < number_of_bytes; ++i, j += 2)
457 considered_bytes += (core.
l_seq + 1) / 2;
461 if constexpr (!detail::decays_to_ignore_v<qual_type>)
466 for (int32_t i = 0; i < core.
l_seq; ++i)
467 qual[i] =
assign_char_to(
static_cast<char>(qual_str[i] + 33), std::ranges::range_value_t<qual_type>{});
470 considered_bytes += core.
l_seq;
474 if constexpr (!detail::decays_to_ignore_v<tag_dict_type>)
479 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
486 if (core.
l_seq != 0 && sc_front == core.
l_seq)
488 if constexpr (detail::decays_to_ignore_v<tag_dict_type> | detail::decays_to_ignore_v<seq_type>)
493 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
494 "stored in the optional field CG. You need to read in the field::tags and "
495 "field::seq in order to access this information.")};
499 auto it = tag_dict.
find(
"CG"_tag);
501 if (it == tag_dict.end())
505 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
506 "stored in the optional field CG but this tag is not present in the given ",
517template <
typename stream_type,
518 typename header_type,
521 typename ref_seq_type,
522 typename ref_id_type,
526 typename tag_dict_type>
529 [[maybe_unused]] header_type && header,
530 [[maybe_unused]] seq_type &&
seq,
531 [[maybe_unused]] qual_type &&
qual,
532 [[maybe_unused]] id_type &&
id,
533 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
534 [[maybe_unused]] ref_id_type &&
ref_id,
536 [[maybe_unused]] cigar_type && cigar_vector,
538 [[maybe_unused]] uint8_t
const mapq,
539 [[maybe_unused]] mate_type &&
mate,
540 [[maybe_unused]] tag_dict_type && tag_dict,
541 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
542 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score))
548 "The seq object must be a std::ranges::forward_range over "
549 "letters that model seqan3::alphabet.");
552 "The id object must be a std::ranges::forward_range over "
553 "letters that model seqan3::alphabet.");
556 "The ref_seq object must be a std::ranges::forward_range "
557 "over letters that model seqan3::alphabet.");
559 if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
561 static_assert((std::ranges::forward_range<ref_id_type> || std::integral<std::remove_reference_t<ref_id_type>>
562 || detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>,
std::optional>),
563 "The ref_id object must be a std::ranges::forward_range "
564 "over letters that model seqan3::alphabet or an integral or a std::optional<integral>.");
568 "The qual object must be a std::ranges::forward_range "
569 "over letters that model seqan3::alphabet.");
572 "The mate object must be a std::tuple of size 3 with "
573 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
574 "2) a std::integral or std::optional<std::integral>, and "
575 "3) a std::integral.");
578 ((std::ranges::forward_range<decltype(std::get<0>(
mate))>
581 && (std::integral<std::remove_cvref_t<decltype(std::get<1>(
mate))>>
583 && std::integral<std::remove_cvref_t<decltype(std::get<2>(
mate))>>),
584 "The mate object must be a std::tuple of size 3 with "
585 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
586 "2) a std::integral or std::optional<std::integral>, and "
587 "3) a std::integral.");
590 "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
592 if constexpr (detail::decays_to_ignore_v<header_type>)
594 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
595 "You can either construct the output file with reference names and reference length "
596 "information and the header will be created for you, or you can access the `header` member "
622 int32_t ref_length{};
625 if (!std::ranges::empty(cigar_vector))
627 int32_t dummy_seq_length{};
628 for (
auto & [count, operation] : cigar_vector)
632 if (cigar_vector.size() >= (1 << 16))
635 cigar_vector.resize(2);
636 cigar_vector[0] =
cigar{
static_cast<uint32_t
>(std::ranges::distance(
seq)),
'S'_cigar_operation};
637 cigar_vector[1] =
cigar{
static_cast<uint32_t
>(ref_length),
'N'_cigar_operation};
647 uint8_t read_name_size = std::min<uint8_t>(std::ranges::distance(
id), 254) + 1;
648 read_name_size +=
static_cast<uint8_t
>(read_name_size == 1);
656 static_cast<uint16_t
>(cigar_vector.size()),
658 static_cast<int32_t
>(std::ranges::distance(
seq)),
660 get<1>(
mate).value_or(-1),
663 auto check_and_assign_id_to = [&header]([[maybe_unused]]
auto & id_source, [[maybe_unused]]
auto & id_target)
667 if constexpr (!detail::decays_to_ignore_v<id_t>)
669 if constexpr (std::integral<id_t>)
671 id_target = id_source;
673 else if constexpr (detail::is_type_specialisation_of_v<id_t, std::optional>)
675 id_target = id_source.value_or(-1);
679 if (!std::ranges::empty(id_source))
683 if constexpr (std::ranges::contiguous_range<
decltype(id_source)>
684 && std::ranges::sized_range<
decltype(id_source)>
685 && std::ranges::borrowed_range<
decltype(id_source)>)
688 std::span{std::ranges::data(id_source), std::ranges::size(id_source)});
696 "The ref_id type is not convertible to the reference id information stored in the "
697 "reference dictionary of the header object.");
699 id_it = header.
ref_dict.find(id_source);
707 "not be found in BAM header ref_dict: ",
712 id_target = id_it->second;
719 check_and_assign_id_to(
ref_id, core.refID);
722 check_and_assign_id_to(get<0>(
mate), core.next_refID);
725 core.block_size =
sizeof(core) - 4 + core.l_read_name + core.n_cigar_op * 4
727 (core.l_seq + 1) / 2 +
729 tag_dict_binary_str.
size();
733 if (std::ranges::empty(
id))
740 for (
auto [cigar_count, op] : cigar_vector)
742 cigar_count = cigar_count << 4;
748 using alph_t = std::ranges::range_value_t<seq_type>;
749 constexpr auto to_dna16 = detail::convert_through_char_representation<alph_t, dna16sam>;
752 for (int32_t sidx = 0; sidx < ((core.l_seq & 1) ? core.l_seq - 1 : core.l_seq); ++sidx, ++sit)
757 stream_it =
static_cast<char>(compressed_chr);
761 stream_it =
static_cast<char>(
to_rank(to_dna16[
to_rank(*sit)]) << 4);
764 if (std::ranges::empty(
qual))
771 if (std::ranges::distance(
qual) != core.l_seq)
774 ". Got quality with size ",
775 std::ranges::distance(
qual),
779 | std::views::transform(
782 return static_cast<char>(
to_rank(chr));
788 stream << tag_dict_binary_str;
793template <
typename stream_t,
typename header_type>
796 if constexpr (detail::decays_to_ignore_v<header_type>)
798 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
799 "You can either construct the output file with reference names and reference length "
800 "information and the header will be created for you, or you can access the `header` member "
812#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
813 int32_t
const l_text{
static_cast<int32_t
>(os.
str().size())};
815 int32_t
const l_text{
static_cast<int32_t
>(os.view().size())};
819#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
820 auto header_view = os.
str();
822 auto header_view = os.view();
826 assert(header.ref_ids().size() < (1ull << 32));
827 int32_t
const n_ref{
static_cast<int32_t
>(header.ref_ids().size())};
830 for (int32_t ridx = 0; ridx < n_ref; ++ridx)
832 assert(header.ref_ids()[ridx].size() + 1 < (1ull << 32));
833 int32_t
const l_name{
static_cast<int32_t
>(header.ref_ids()[ridx].size()) + 1};
839 std::ranges::copy_n(
reinterpret_cast<char *
>(&get<0>(header.ref_id_info[ridx])), 4, stream_it);
862template <
typename value_type>
865 value_type
const & SEQAN3_DOXYGEN_ONLY(value))
867 auto it = str.
begin();
870 int32_t
const vector_size = [&]()
878 int32_t bytes_left{vector_size};
881 tmp_vector.
reserve(vector_size);
885 while (bytes_left > 0)
887 if constexpr (std::integral<value_type>)
889 else if constexpr (std::same_as<value_type, float>)
892 static_assert(std::is_same_v<value_type, void>,
"format_bam::read_sam_dict_vector: unsupported value_type");
899 variant = std::move(tmp_vector);
927 auto it = tag_str.
begin();
930 auto parse_integer_into_target = [&]<std::integral int_t>(uint16_t
const tag, int_t)
934 target[tag] =
static_cast<int32_t
>(tmp);
939 auto parse_array_into_target = [&]<
arithmetic array_value_t>(uint16_t
const tag, array_value_t)
942 it +=
sizeof(int32_t) +
sizeof(array_value_t) * count;
946 auto parse_tag = [&]()
948 uint16_t tag =
static_cast<uint16_t
>(*it) << 8;
950 tag |=
static_cast<uint16_t
>(*it);
955 while (it != tag_str.
end())
957 uint16_t
const tag = parse_tag();
959 char const type_id{*it};
973 parse_integer_into_target(tag, int8_t{});
978 parse_integer_into_target(tag, uint8_t{});
983 parse_integer_into_target(tag, int16_t{});
988 parse_integer_into_target(tag, uint16_t{});
993 parse_integer_into_target(tag, int32_t{});
998 parse_integer_into_target(tag, uint32_t{});
1006 it +=
sizeof(float);
1011 std::string const v{
static_cast<char const *
>(it)};
1013 target[tag] = std::move(v);
1022 uint8_t dummy_byte{};
1024 if (str.
size() % 2 != 0)
1025 throw format_error{
"[CORRUPTED BAM FILE] Hexadecimal tag must have even number of digits."};
1029 for (
auto hex_begin = str.
begin(), hex_end = str.
begin() + 2; hex_begin != str.
end();
1030 hex_begin += 2, hex_end += 2)
1034 if (res.ec == std::errc::invalid_argument)
1036 +
std::string(hex_begin, hex_end) +
"' could not be cast into type uint8_t."};
1038 if (res.ec == std::errc::result_out_of_range)
1040 +
"' into type uint8_t would cause an overflow."};
1045 target[tag] = std::move(tmp_vector);
1047 it += str.
size() + 1;
1053 char array_value_type_id = *it;
1056 switch (array_value_type_id)
1059 parse_array_into_target(tag, int8_t{});
1062 parse_array_into_target(tag, uint8_t{});
1065 parse_array_into_target(tag, int16_t{});
1068 parse_array_into_target(tag, uint16_t{});
1071 parse_array_into_target(tag, int32_t{});
1074 parse_array_into_target(tag, uint32_t{});
1077 parse_array_into_target(tag,
float{});
1081 "must be one of [cCsSiIf] but '",
1082 array_value_type_id,
1089 "SAM tag must be one of [A,i,Z,H,B,f] but '",
1105 cigar_operation_mapping{
'M',
'I',
'D',
'N',
'S',
'H',
'P',
'=',
'X',
'*',
'*',
'*',
'*',
'*',
'*',
'*'};
1107 constexpr uint32_t cigar_operation_mask = 0x0f;
1110 char operation{
'\0'};
1112 uint32_t operation_and_count{};
1114 assert(cigar_str.
size() % 4 == 0);
1116 for (
auto it = cigar_str.
begin(); it != cigar_str.
end(); it +=
sizeof(operation_and_count))
1118 std::memcpy(&operation_and_count, it,
sizeof(operation_and_count));
1119 operation = cigar_operation_mapping[operation_and_count & cigar_operation_mask];
1120 count = operation_and_count >> 4;
1125 return cigar_vector;
1135 auto stream_variant_fn = [&result](
auto && arg)
1140 if constexpr (std::same_as<T, int32_t>)
1143 size_t const absolute_arg = std::abs(arg);
1145 bool const negative = arg < 0;
1146 n = n * n + 2 * negative;
1152 result[result.size() - 1] =
'C';
1153 result.append(
reinterpret_cast<char const *
>(&arg), 1);
1158 result[result.size() - 1] =
'S';
1159 result.append(
reinterpret_cast<char const *
>(&arg), 2);
1164 result[result.size() - 1] =
'c';
1165 int8_t tmp =
static_cast<int8_t
>(arg);
1166 result.append(
reinterpret_cast<char const *
>(&tmp), 1);
1171 result[result.size() - 1] =
's';
1172 int16_t tmp =
static_cast<int16_t
>(arg);
1173 result.append(
reinterpret_cast<char const *
>(&tmp), 2);
1178 result.append(
reinterpret_cast<char const *
>(&arg), 4);
1183 else if constexpr (std::same_as<T, std::string>)
1185 result.append(
reinterpret_cast<char const *
>(arg.data()), arg.size() + 1 );
1187 else if constexpr (!std::ranges::range<T>)
1189 result.append(
reinterpret_cast<char const *
>(&arg),
sizeof(arg));
1193 int32_t sz{
static_cast<int32_t
>(arg.size())};
1194 result.append(
reinterpret_cast<char *
>(&sz), 4);
1195 result.append(
reinterpret_cast<char const *
>(arg.data()),
1196 arg.size() *
sizeof(std::ranges::range_value_t<T>));
1200 for (
auto & [tag, variant] : tag_dict)
1202 result.push_back(
static_cast<char>(tag / 256));
1203 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:184
The seqan3::cigar semialphabet pairs a counter with a seqan3::cigar::operation letter.
Definition alphabet/cigar/cigar.hpp:57
Functionally the same as std::istreambuf_iterator, but faster.
Definition fast_istreambuf_iterator.hpp:37
Functionally the same as std::ostreambuf_iterator, but offers writing a range more efficiently.
Definition fast_ostreambuf_iterator.hpp:38
A 16 letter DNA alphabet, containing all IUPAC symbols minus the gap and plus an equality sign ('=').
Definition dna16sam.hpp:45
The actual implementation of seqan3::cigar::operation for documentation purposes only.
Definition cigar_operation.hpp:45
The SAM tag dictionary class that stores all optional SAM fields.
Definition sam_tag_dictionary.hpp:327
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:517
constexpr auto assign_char_strictly_to
Assign a character to an alphabet object, throw if the character is not valid.
Definition alphabet/concept.hpp:721
constexpr auto to_rank
Return the rank representation of a (semi-)alphabet object.
Definition alphabet/concept.hpp:152
sam_flag
An enum flag that describes the properties of an aligned read (given as a SAM record).
Definition sam_flag.hpp:73
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:118
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:42
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:48
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:39
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:87
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:587
constexpr auto istreambuf
A view factory that returns a view over the stream buffer of an input stream.
Definition istreambuf_view.hpp:104
@ 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:60
constexpr auto repeat_n
A view factory that repeats a given value n times.
Definition repeat_n.hpp:88
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:26
The main SeqAn3 namespace.
Definition aligned_sequence_concept.hpp:26
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:23
Provides seqan3::views::take_exactly and seqan3::views::take_exactly_or_throw.
Provides seqan3::debug_stream and related types.