takane
Validators for ArtifactDB file formats
Loading...
Searching...
No Matches
multi_sample_dataset.hpp
Go to the documentation of this file.
1#ifndef TAKANE_MULTI_SAMPLE_DATASET_HPP
2#define TAKANE_MULTI_SAMPLE_DATASET_HPP
3
4#include "H5Cpp.h"
5#include "ritsuko/ritsuko.hpp"
6#include "sanisizer/sanisizer.hpp"
7
8#include <filesystem>
9#include <string>
10#include <cstdint>
11#include <stdexcept>
12#include <exception>
13#include <cstddef>
14
15#include "utils_public.hpp"
16#include "utils_other.hpp"
17#include "utils_summarized_experiment.hpp"
18
24namespace takane {
25
29void validate(const std::filesystem::path&, const ObjectMetadata&, const Options&);
30std::size_t height(const std::filesystem::path&, const ObjectMetadata&, const Options&);
31bool satisfies_interface(const std::string&, const std::string&, const Options&);
41inline void validate_multi_sample_dataset(const std::filesystem::path& path, const ObjectMetadata& metadata, const Options& options) {
42 const std::string type_name = "multi_sample_dataset"; // use a separate variable to avoid dangling reference warnings from GCC.
43
44 try {
45 const auto& dmap = extract_json_object(metadata.other, type_name);
46 try {
47 const std::string& vstring = extract_json_version_string(dmap);
48 auto version = ritsuko::parse_version_string(vstring.c_str(), vstring.size(), /* skip_patch = */ true);
49 if (version.major != 1) {
50 throw std::runtime_error("unsupported version string '" + vstring + "'");
51 }
52 } catch (...) {
53 std::throw_with_nested(std::runtime_error("failed to read 'version'"));
54 }
55 } catch (...) {
56 std::throw_with_nested(std::runtime_error("failed to read '" + type_name + "' in object metadata"));
57 }
58
59 // Sample data should exist.
60 std::size_t num_samples;
61 try {
62 auto sd_path = path / "sample_data";
63 auto sdmeta = read_object_metadata(sd_path);
64 if (!satisfies_interface(sdmeta.type, "DATA_FRAME", options)) {
65 throw std::runtime_error("object in 'sample_data' should satisfy the 'DATA_FRAME' interface");
66 }
67 validate(sd_path, sdmeta, options);
68 num_samples = height(sd_path, sdmeta, options);
69 } catch (...) {
70 std::throw_with_nested(std::runtime_error("failed to validate 'sample_data'"));
71 }
72
73 // Checking the experiments.
74 std::vector<size_t> num_columns;
75 auto edir = path / "experiments";
76 if (std::filesystem::exists(edir)) {
77 try {
78 const auto num_experiments = [&]{
79 try {
80 return extract_summarized_experiment_names(edir / "names.json").size();
81 } catch (...) {
82 std::throw_with_nested(std::runtime_error("failed to read 'names.json'"));
83 }
84 }();
85
86 num_columns.reserve(num_experiments);
87 for (I<decltype(num_experiments)> e = 0; e < num_experiments; ++e) {
88 auto ename = ritsuko::itoa(e);
89 auto epath = edir / ename;
90 try {
91 auto emeta = read_object_metadata(epath);
92 if (!satisfies_interface(emeta.type, "SUMMARIZED_EXPERIMENT", options)) {
93 throw std::runtime_error("object should satisfy the 'SUMMARIZED_EXPERIMENT' interface");
94 }
95 validate(epath, emeta, options);
96 auto dims = dimensions(epath, emeta, options);
97 num_columns.push_back(dims[1]);
98 } catch (...) {
99 std::throw_with_nested(std::runtime_error("failed to validate experiment " + ename));
100 }
101 }
102
103 const auto num_dir_obj = count_directory_entries(edir);
104 if (!sanisizer::is_equal(num_dir_obj - 1, num_experiments)) { // -1 to account for the names.json file itself.
105 throw std::runtime_error("more objects than expected inside the subdirectory");
106 }
107 } catch (...) {
108 std::throw_with_nested(std::runtime_error("failed to validate 'experiments'"));
109 }
110 }
111
112 // Checking the sample map, if it exists or if there are any experiments.
113 const auto mappath = path / "sample_map.h5";
114 if (!num_columns.empty() || std::filesystem::exists(mappath)) {
115 try {
116 H5::H5File handle(mappath, H5F_ACC_RDONLY);
117 auto ghandle = handle.openGroup(type_name);
118
119 const auto num_experiments = num_columns.size();
120 for (I<decltype(num_experiments)> e = 0; e < num_experiments; ++e) {
121 auto ename = ritsuko::itoa(e);
122 try {
123 auto dhandle = ghandle.openDataSet(ename);
124 if (ritsuko::hdf5::exceeds_integer_limit(dhandle, 64, false)) {
125 throw std::runtime_error("expected a datatype that fits into a 64-bit unsigned integer");
126 }
127
128 auto dspace = dhandle.getSpace();
129 if (dspace.getSimpleExtentNdims() != 1) {
130 throw std::runtime_error("expected a 1-dimensional dataset");
131 }
132 hsize_t len;
133 dspace.getSimpleExtentDims(&len);
134 if (!sanisizer::is_equal(len, num_columns[e])) {
135 throw std::runtime_error("dataset extent should equal the number of columns of 'experiments/" + ename + "'");
136 }
137
138 ritsuko::hdf5::Stream1dNumericDataset<std::uint64_t> stream(
139 &dhandle,
140 len,
141 [&]{
142 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
143 opt.contiguous_chunk_size = options.hdf5_buffer_size;
144 return opt;
145 }()
146 );
147 iterate_stream<std::uint64_t>(
148 stream,
149 [&](hsize_t, std::uint64_t val) -> void {
150 if (sanisizer::is_greater_than_or_equal(val, num_samples)) {
151 throw std::runtime_error("entries should be less than the number of samples");
152 }
153 }
154 );
155 } catch (...) {
156 std::throw_with_nested(std::runtime_error("failed to validate mapping for experiment " + ename));
157 }
158 }
159
160 if (!sanisizer::is_equal(num_columns.size(), ghandle.getNumObjs())) {
161 throw std::runtime_error("more objects present in the 'multi_sample_dataset' group than expected");
162 }
163 } catch (...) {
164 std::throw_with_nested(std::runtime_error("failed to validate the sample mapping in 'sample_map.h5'"));
165 }
166 }
167
168 validate_metadata(path, "other_data", options);
169}
170
171}
172
173#endif
takane validation functions.
Definition _derived_from.hpp:15
bool satisfies_interface(const std::string &type, const std::string &interface, const Options &options)
Definition _satisfies_interface.hpp:68
ObjectMetadata read_object_metadata(const std::filesystem::path &path)
Definition utils_public.hpp:77
std::vector< std::size_t > dimensions(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition _dimensions.hpp:63
void validate_multi_sample_dataset(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition multi_sample_dataset.hpp:41
std::size_t height(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition _height.hpp:83
void validate(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition _validate.hpp:107
Object metadata, including the type and other fields.
Definition utils_public.hpp:29
std::unordered_map< std::string, std::shared_ptr< millijson::Base > > other
Definition utils_public.hpp:38
Validation options.
Definition utils_public.hpp:99
hsize_t hdf5_buffer_size
Definition utils_public.hpp:109
Exported utilities.