122 const std::string type_name =
"genomic_ranges";
125 const auto& type_meta = extract_json_object(metadata.
other, type_name);
127 const auto& vstring = extract_json_version_string(type_meta);
128 auto version = ritsuko::parse_version_string(vstring.c_str(), vstring.size(),
true);
129 if (version.major != 1) {
130 throw std::runtime_error(
"unsupported version string '" + vstring +
"'");
133 std::throw_with_nested(std::runtime_error(
"failed to validate 'version'"));
136 std::throw_with_nested(std::runtime_error(
"failed to read '" + type_name +
"' in the object metadata"));
140 std::optional<SequenceLimits> limits;
142 limits = find_sequence_limits(path /
"sequence_information", options);
144 std::throw_with_nested(std::runtime_error(
"failed to read the 'sequence_information' object"));
147 const auto num_sequences = limits->length.size();
152 H5::H5File handle(path /
"ranges.h5", H5F_ACC_RDONLY);
153 auto ghandle = handle.openGroup(type_name);
155 H5::DataSet id_handle;
157 id_handle = ghandle.openDataSet(
"sequence");
158 if (ritsuko::hdf5::exceeds_integer_limit(id_handle, 64,
false)) {
159 throw std::runtime_error(
"expected a datatype that fits into a 64-bit unsigned integer");
162 auto id_space = id_handle.getSpace();
163 if (id_space.getSimpleExtentNdims() != 1) {
164 throw std::runtime_error(
"expected a 1-dimensional dataset");
166 id_space.getSimpleExtentDims(&num_ranges);
168 std::throw_with_nested(std::runtime_error(
"failed to validate 'sequence'"));
171 H5::DataSet start_handle;
173 start_handle = ghandle.openDataSet(
"start");
174 if (ritsuko::hdf5::exceeds_integer_limit(start_handle, 64,
true)) {
175 throw std::runtime_error(
"expected a datatype that fits into a 64-bit signed integer");
178 auto start_space = start_handle.getSpace();
179 if (start_space.getSimpleExtentNdims() != 1) {
180 throw std::runtime_error(
"expected a 1-dimensional dataset");
183 start_space.getSimpleExtentDims(&num_start);
184 if (num_start != num_ranges) {
185 throw std::runtime_error(
"dataset extent should be the same as that of 'sequence'");
188 std::throw_with_nested(std::runtime_error(
"failed to validate 'start'"));
191 H5::DataSet width_handle;
193 width_handle = ghandle.openDataSet(
"width");
194 if (ritsuko::hdf5::exceeds_integer_limit(width_handle, 64,
false)) {
195 throw std::runtime_error(
"expected 'width' to have a datatype that fits into a 64-bit unsigned integer");
198 auto width_space = width_handle.getSpace();
199 if (width_space.getSimpleExtentNdims() != 1) {
200 throw std::runtime_error(
"expected a 1-dimensional dataset");
203 width_space.getSimpleExtentDims(&num_width);
204 if (num_width != num_ranges) {
205 throw std::runtime_error(
"dataset extent should be the same as that of 'sequence'");
208 std::throw_with_nested(std::runtime_error(
"failed to validate 'width'"));
211 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
213 StreamIterator<std::uint64_t> id_stream(ritsuko::hdf5::Stream1dNumericDataset<std::uint64_t>(&id_handle, num_ranges, opt));
214 StreamIterator<std::int64_t> start_stream(ritsuko::hdf5::Stream1dNumericDataset<std::int64_t>(&start_handle, num_ranges, opt));
215 StreamIterator<std::uint64_t> width_stream(ritsuko::hdf5::Stream1dNumericDataset<std::uint64_t>(&width_handle, num_ranges, opt));
217 for (I<
decltype(num_ranges)> i = 0; i < num_ranges; ++i) {
218 const auto id = id_stream.next();
219 if (sanisizer::is_greater_than_or_equal(
id, num_sequences)) {
220 throw std::runtime_error(
"'sequence' must be less than the number of sequences in 'sequence_information'");
223 const auto start = start_stream.next();
224 const auto width = width_stream.next();
227 const auto& circular = limits->circular[id];
228 if (circular.has_value() && !(*circular)) {
230 throw std::runtime_error(
"non-positive 'start' position for a non-circular sequence");
233 const auto& length = limits->length[id];
234 if (length.has_value()) {
235 if (sanisizer::is_greater_than(start, *length)) {
236 throw std::runtime_error(
"'start' position exceeds sequence length for a non-circular sequence");
241 if (sanisizer::is_less_than(*length - (start - 1), width)) {
242 throw std::runtime_error(
"end position exceeds sequence length for a non-circular sequence");
247 if (start_plus_width_exceeds_int64(start, width)) {
248 throw std::runtime_error(
"sum of 'start' and 'width' is beyond the range of a 64-bit signed integer");
253 auto strand_handle = ghandle.openDataSet(
"strand");
254 if (ritsuko::hdf5::exceeds_integer_limit(strand_handle, 32,
true)) {
255 throw std::runtime_error(
"expected a datatype that fits into a 32-bit signed integer");
258 auto strand_space = strand_handle.getSpace();
259 if (strand_space.getSimpleExtentNdims() != 1) {
260 throw std::runtime_error(
"expected a 1-dimensional dataset");
263 strand_space.getSimpleExtentDims(&nstrand);
264 if (nstrand != num_ranges) {
265 throw std::runtime_error(
"dataset extent should be the same as that of 'sequence'");
268 ritsuko::hdf5::Stream1dNumericDataset<std::int32_t> strand_stream(&strand_handle, num_ranges, opt);
269 iterate_stream<std::int32_t>(
271 [&](hsize_t, std::int32_t x) ->
void {
272 if (x < -1 || x > 1) {
273 throw std::runtime_error(
"entries should be one of 0, -1, or 1");
278 std::throw_with_nested(std::runtime_error(
"failed to validate 'strand'"));
283 std::throw_with_nested(std::runtime_error(
"failed to validate '" + type_name +
"' in 'ranges.h5'"));
286 validate_mcols(path,
"range_annotations", num_ranges, options);
287 validate_metadata(path,
"other_annotations", options);