Coverage Report

Created: 2026-02-26 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bag/api/bag_vrnode.cpp
Line
Count
Source
1
2
#include "bag_hdfhelper.h"
3
#include "bag_private.h"
4
#include "bag_vrnode.h"
5
#include "bag_vrnodedescriptor.h"
6
7
#include <array>
8
#include <cstring>  //memset
9
#include <memory>
10
#include <H5Cpp.h>
11
12
13
namespace BAG {
14
15
namespace {
16
17
//! Create an HDF5 CompType for the variable resolution node.
18
/*!
19
\return
20
    An HDF5 CompType for the variable resolution node.
21
*/
22
::H5::CompType makeDataType()
23
0
{
24
0
    const ::H5::CompType memDataType{sizeof(BagVRNodeItem)};
25
26
0
    memDataType.insertMember("hyp_strength",
27
0
        HOFFSET(BagVRNodeItem, hyp_strength), ::H5::PredType::NATIVE_FLOAT);
28
0
    memDataType.insertMember("num_hypotheses",
29
0
        HOFFSET(BagVRNodeItem, num_hypotheses), ::H5::PredType::NATIVE_UINT32);
30
0
    memDataType.insertMember("n_samples", HOFFSET(BagVRNodeItem, n_samples),
31
0
        ::H5::PredType::NATIVE_UINT32);
32
33
0
    return memDataType;
34
0
}
35
36
//! Read an attribute from an HDF5 DataSet.
37
/*!
38
\param h5file
39
    The HDF5 file.
40
\param name
41
    The name of the attribute.
42
43
\return
44
    The value in the specified attribute.
45
*/
46
template<typename T>
47
T readAttribute(
48
    const ::H5::H5File& h5file,
49
    const char* const name)
50
0
{
51
0
    const auto h5DataSet = h5file.openDataSet(VR_NODE_PATH);
52
0
    const auto attribute = h5DataSet.openAttribute(name);
53
54
0
    T value{};
55
0
    attribute.read(attribute.getDataType(), &value);
56
57
0
    return value;
58
0
}
Unexecuted instantiation: bag_vrnode.cpp:float BAG::(anonymous namespace)::readAttribute<float>(H5::H5File const&, char const*)
Unexecuted instantiation: bag_vrnode.cpp:unsigned int BAG::(anonymous namespace)::readAttribute<unsigned int>(H5::H5File const&, char const*)
59
60
}  // namespace
61
62
//! Constructor.
63
/*!
64
\param dataset
65
    The BAG Dataset that this layer belongs to.
66
\param descriptor
67
    The descriptor of this layer.
68
\param pH5dataSet
69
    The HDF5 DataSet this class wraps.
70
*/
71
VRNode::VRNode(
72
    Dataset& dataset,
73
    VRNodeDescriptor& descriptor,
74
    std::unique_ptr<::H5::DataSet, DeleteH5dataSet> pH5dataSet)
75
0
    : Layer(dataset, descriptor)
76
0
    , m_pH5dataSet(std::move(pH5dataSet))
77
0
{
78
0
}
79
80
//! Retrieve the layer's descriptor. Note: this shadows BAG::Layer.getDescriptor()
81
/*!
82
\return
83
    The layer's descriptor.
84
    Will never be nullptr.
85
*/
86
std::shared_ptr<VRNodeDescriptor> VRNode::getDescriptor() & noexcept
87
0
{
88
0
    return std::dynamic_pointer_cast<VRNodeDescriptor>(Layer::getDescriptor());
89
0
}
90
91
//! Retrieve the layer's descriptor. Note: this shadows BAG::Layer.getDescriptor()
92
/*!
93
\return
94
    The layer's descriptor.
95
    Will never be nullptr.
96
*/
97
0
std::shared_ptr<const VRNodeDescriptor> VRNode::getDescriptor() const & noexcept {
98
0
    return std::dynamic_pointer_cast<const VRNodeDescriptor>(Layer::getDescriptor());
99
0
}
100
101
//! Create a variable resolution node.
102
/*!
103
\param dataset
104
    The BAG Dataset that this layer belongs to.
105
\param chunkSize
106
    The chunk size in the HDF5 DataSet.
107
\param compressionLevel
108
    The compression level in the HDF5 DataSet.
109
110
\return
111
    The new variable resolution node.
112
*/
113
std::shared_ptr<VRNode> VRNode::create(
114
    Dataset& dataset,
115
    uint64_t chunkSize,
116
    int compressionLevel)
117
0
{
118
0
    auto descriptor = VRNodeDescriptor::create(dataset, chunkSize,
119
0
        compressionLevel);
120
121
0
    auto h5dataSet = VRNode::createH5dataSet(dataset, *descriptor);
122
123
0
    return std::make_shared<VRNode>(dataset,
124
0
        *descriptor, std::move(h5dataSet));
125
0
}
126
127
//! Open an existing variable resolution node.
128
/*!
129
\param dataset
130
    The BAG Dataset that this layer belongs to.
131
\param descriptor
132
    The descriptor of this layer.
133
134
\return
135
    The specified variable resolution node.
136
*/
137
std::shared_ptr<VRNode> VRNode::open(
138
    Dataset& dataset,
139
    VRNodeDescriptor& descriptor)
140
0
{
141
0
    auto& h5file = dataset.getH5file();
142
143
    // Read the attribute values from the file and set in the descriptor.
144
    // min/max hyp strength
145
0
    const auto minHypStrength = readAttribute<float>(h5file,
146
0
        VR_NODE_MIN_HYP_STRENGTH);
147
0
    const auto maxHypStrength = readAttribute<float>(h5file,
148
0
        VR_NODE_MAX_HYP_STRENGTH);
149
150
0
    descriptor.setMinMaxHypStrength(minHypStrength, maxHypStrength);
151
152
    // min/max num hypotheses
153
0
    const auto minNumHypotheses = readAttribute<uint32_t>(h5file,
154
0
        VR_NODE_MIN_NUM_HYPOTHESES);
155
0
    const auto maxNumHypotheses = readAttribute<uint32_t>(h5file,
156
0
        VR_NODE_MAX_NUM_HYPOTHESES);
157
158
0
    descriptor.setMinMaxNumHypotheses(minNumHypotheses, maxNumHypotheses);
159
160
    // min/max n samples
161
0
    const auto minNSamples = readAttribute<uint32_t>(h5file,
162
0
        VR_NODE_MIN_N_SAMPLES);
163
0
    const auto maxNSamples = readAttribute<uint32_t>(h5file,
164
0
        VR_NODE_MAX_N_SAMPLES);
165
166
0
    descriptor.setMinMaxNSamples(minNSamples, maxNSamples);
167
168
0
    auto h5dataSet = std::unique_ptr<::H5::DataSet, DeleteH5dataSet>(
169
0
        new ::H5::DataSet{h5file.openDataSet(VR_NODE_PATH)},
170
0
            DeleteH5dataSet{});
171
172
    // We need to know the dimensions of the array on file so that we can update the
173
    // descriptor for the layer.
174
0
    hsize_t dims[2];
175
0
    int ndims = h5dataSet->getSpace().getSimpleExtentDims(dims, nullptr);
176
0
    if (ndims != 2) {
177
        // Should be 1D according to BAG spec, but some implementations use a 2D array,
178
        // so for compatibility's sake, use 2D.
179
0
        throw InvalidVRRefinementDimensions{};
180
0
    }
181
0
    descriptor.setDims(dims[0], dims[1]);
182
0
    return std::make_unique<VRNode>(dataset, descriptor, std::move(h5dataSet));
183
0
}
184
185
186
//! Create the HDF5 DataSet.
187
/*!
188
\param dataset
189
    The BAG Dataset that this layer belongs to.
190
\param descriptor
191
    The descriptor of this layer.
192
193
\return
194
    The new HDF5 DataSet.
195
*/
196
std::unique_ptr<::H5::DataSet, DeleteH5dataSet>
197
VRNode::createH5dataSet(
198
    const Dataset& dataset,
199
    const VRNodeDescriptor& descriptor)
200
0
{
201
0
    std::array<hsize_t, kRank> fileDims{0, 0};
202
0
    const std::array<hsize_t, kRank> kMaxFileDims{H5S_UNLIMITED, H5S_UNLIMITED};
203
0
    const ::H5::DataSpace h5fileDataSpace{kRank, fileDims.data(), kMaxFileDims.data()};
204
205
    // Create the creation property list.
206
0
    const ::H5::DSetCreatPropList h5createPropList{};
207
208
    // Use chunk size and compression level from the descriptor.
209
0
    const hsize_t chunkSize = descriptor.getChunkSize();
210
0
    const auto compressionLevel = descriptor.getCompressionLevel();
211
0
    if (chunkSize > 0)
212
0
    {
213
0
        const std::array<hsize_t, kRank> chunkDims{chunkSize, chunkSize};
214
0
        h5createPropList.setChunk(kRank, chunkDims.data());
215
216
0
        if (compressionLevel > 0 && compressionLevel <= kMaxCompressionLevel)
217
0
            h5createPropList.setDeflate(compressionLevel);
218
0
    }
219
0
    else if (compressionLevel > 0)
220
0
        throw CompressionNeedsChunkingSet{};
221
0
    else
222
0
        throw LayerRequiresChunkingSet{};
223
224
0
    h5createPropList.setFillTime(H5D_FILL_TIME_ALLOC);
225
226
0
    const auto memDataType = makeDataType();
227
228
    // Create the DataSet using the above.
229
0
    const auto& h5file = dataset.getH5file();
230
231
0
    const auto h5dataSet = h5file.createDataSet(VR_NODE_PATH,
232
0
        memDataType, h5fileDataSpace, h5createPropList);
233
234
    // Create attributes.
235
0
    createAttributes(h5dataSet, ::H5::PredType::NATIVE_FLOAT,
236
0
        {VR_NODE_MIN_HYP_STRENGTH, VR_NODE_MAX_HYP_STRENGTH});
237
238
0
    createAttributes(h5dataSet, ::H5::PredType::NATIVE_UINT32,
239
0
        {VR_NODE_MIN_NUM_HYPOTHESES, VR_NODE_MAX_NUM_HYPOTHESES,
240
0
        VR_NODE_MIN_N_SAMPLES, VR_NODE_MAX_N_SAMPLES});
241
242
    // Set initial min/max values.
243
0
    writeAttribute(h5dataSet, ::H5::PredType::NATIVE_FLOAT,
244
0
        std::numeric_limits<float>::max(), VR_NODE_MIN_HYP_STRENGTH);
245
0
    writeAttribute(h5dataSet, ::H5::PredType::NATIVE_FLOAT,
246
0
        std::numeric_limits<float>::lowest(), VR_NODE_MAX_HYP_STRENGTH);
247
248
0
    BAG::writeAttributes(h5dataSet, ::H5::PredType::NATIVE_UINT32,
249
0
        std::numeric_limits<uint32_t>::max(), {VR_NODE_MIN_NUM_HYPOTHESES,
250
0
        VR_NODE_MIN_N_SAMPLES});
251
0
    BAG::writeAttributes(h5dataSet, ::H5::PredType::NATIVE_UINT32,
252
0
        std::numeric_limits<uint32_t>::lowest(), {VR_NODE_MAX_NUM_HYPOTHESES,
253
0
        VR_NODE_MAX_N_SAMPLES});
254
255
0
    return std::unique_ptr<::H5::DataSet, DeleteH5dataSet>(
256
0
        new ::H5::DataSet{h5dataSet}, DeleteH5dataSet{});
257
0
}
258
259
//! \copydoc Layer::read
260
//! The rowStart and rowEnd are ignored since the data is 1 dimensional.
261
UInt8Array VRNode::readProxy(
262
    uint32_t /*rowStart*/,
263
    uint32_t columnStart,
264
    uint32_t /*rowEnd*/,
265
    uint32_t columnEnd) const
266
0
{
267
0
    auto pDescriptor = std::dynamic_pointer_cast<const VRNodeDescriptor>(
268
0
        this->getDescriptor());
269
0
    if (!pDescriptor)
270
0
        throw InvalidLayerDescriptor{};
271
272
    // Query the file for the specified rows and columns.
273
0
    const hsize_t columns = (columnEnd - columnStart) + 1;
274
0
    const hsize_t offset = columnStart;
275
276
0
    const std::array<hsize_t, kRank> sizes{1, columns};
277
0
    const std::array<hsize_t, kRank> offsets{0, offset};
278
279
0
    const auto h5fileDataSpace = m_pH5dataSet->getSpace();
280
0
    h5fileDataSpace.selectHyperslab(H5S_SELECT_SET, sizes.data(), offsets.data());
281
282
0
    const auto bufferSize = pDescriptor->getReadBufferSize(1, columns);
283
0
    UInt8Array buffer{bufferSize};
284
285
0
    const ::H5::DataSpace memDataSpace{kRank, sizes.data(), sizes.data()};
286
287
0
    const auto memDataType = makeDataType();
288
289
0
    m_pH5dataSet->read(buffer.data(), memDataType, memDataSpace, h5fileDataSpace);
290
291
0
    return buffer;
292
0
}
293
294
//! \copydoc Layer::writeAttributes
295
void VRNode::writeAttributesProxy() const
296
0
{
297
0
    auto pDescriptor = std::dynamic_pointer_cast<const VRNodeDescriptor>(
298
0
        this->getDescriptor());
299
0
    if (!pDescriptor)
300
0
        throw InvalidLayerDescriptor{};
301
302
    // Write the attributes from the layer descriptor.
303
    // min/max hyp strength
304
0
    const auto minMaxHypStrength = pDescriptor->getMinMaxHypStrength();
305
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_FLOAT,
306
0
        std::get<0>(minMaxHypStrength), VR_NODE_MIN_HYP_STRENGTH);
307
308
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_FLOAT,
309
0
        std::get<1>(minMaxHypStrength), VR_NODE_MAX_HYP_STRENGTH);
310
311
    // min/max num hypotheses
312
0
    const auto minMaxNumHypotheses = pDescriptor->getMinMaxNumHypotheses();
313
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_UINT32,
314
0
        std::get<0>(minMaxNumHypotheses), VR_NODE_MIN_NUM_HYPOTHESES);
315
316
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_UINT32,
317
0
        std::get<1>(minMaxNumHypotheses), VR_NODE_MAX_NUM_HYPOTHESES);
318
319
    // min/max n samples
320
0
    const auto minMaxNSamples = pDescriptor->getMinMaxNSamples();
321
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_UINT32,
322
0
        std::get<0>(minMaxNSamples), VR_NODE_MIN_N_SAMPLES);
323
324
0
    writeAttribute(*m_pH5dataSet, ::H5::PredType::NATIVE_UINT32,
325
0
        std::get<1>(minMaxNSamples), VR_NODE_MAX_N_SAMPLES);
326
0
}
327
328
//! \copydoc Layer::write
329
void VRNode::writeProxy(
330
    uint32_t rowStart,
331
    uint32_t columnStart,
332
    uint32_t rowEnd,
333
    uint32_t columnEnd,
334
    const uint8_t* buffer)
335
0
{
336
0
    auto pDescriptor = std::dynamic_pointer_cast<VRNodeDescriptor>(
337
0
        this->getDescriptor());
338
0
    if (!pDescriptor)
339
0
        throw InvalidLayerDescriptor{};
340
341
0
    const auto rows = (rowEnd - rowStart) + 1;
342
0
    const auto columns = (columnEnd - columnStart) + 1;
343
0
    const std::array<hsize_t, kRank> count{rows, columns};
344
0
    const std::array<hsize_t, kRank> offset{rowStart, columnStart};
345
0
    const ::H5::DataSpace memDataSpace{kRank, count.data(), count.data()};
346
347
0
    ::H5::DataSpace fileDataSpace = m_pH5dataSet->getSpace();
348
349
    // Expand the file data space if needed.
350
0
    std::array<hsize_t, kRank> fileDims{};
351
0
    std::array<hsize_t, kRank> maxFileDims{};
352
353
0
    const int numDims = fileDataSpace.getSimpleExtentDims(fileDims.data(),
354
0
                                                          maxFileDims.data());
355
0
    if (numDims != kRank) {
356
0
        throw InvalidVRRefinementDimensions{};
357
0
    }
358
359
0
    if ((fileDims[0] < (rowEnd + 1)) ||
360
0
        (fileDims[1] < (columnEnd + 1)))
361
0
    {
362
0
        const std::array<hsize_t, kRank> newDims{
363
0
                std::max<hsize_t>(fileDims[0], rowEnd + 1),
364
0
                std::max<hsize_t>(fileDims[1], columnEnd + 1)};
365
0
        m_pH5dataSet->extend(newDims.data());
366
367
0
        fileDataSpace = m_pH5dataSet->getSpace();
368
369
        // Update the dataset's dimensions.
370
0
        if (this->getDataset().expired())
371
0
            throw DatasetNotFound{};
372
373
        // So that the read() call checks correctly against the size of the array, rather
374
        // than the dimensions of the mandatory layer, we need to keep track of the size
375
        // of the layer in the layer-specific descriptor.
376
0
        pDescriptor->setDims(newDims[0], newDims[1]);
377
0
    }
378
379
0
    fileDataSpace.selectHyperslab(H5S_SELECT_SET, count.data(), offset.data());
380
381
0
    const auto memDataType = makeDataType();
382
383
0
    m_pH5dataSet->write(buffer, memDataType, memDataSpace, fileDataSpace);
384
385
    // Update min/max attributes
386
    // Get the current min/max from descriptor.
387
0
    float minHypStr = 0.f, maxHypStr = 0.f;
388
0
    std::tie(minHypStr, maxHypStr) = pDescriptor->getMinMaxHypStrength();
389
390
0
    uint32_t minNumHyp = 0, maxNumHyp = 0;
391
0
    std::tie(minNumHyp, maxNumHyp) = pDescriptor->getMinMaxNumHypotheses();
392
393
0
    uint32_t minNSamples = 0, maxNSamples = 0;
394
0
    std::tie(minNSamples, maxNSamples) = pDescriptor->getMinMaxNSamples();
395
396
    // Update the min/max from new data.
397
0
    const auto* items = reinterpret_cast<const BagVRNodeItem*>(buffer);
398
399
0
    auto* item = items;
400
0
    const auto end = items + columns;
401
402
0
    for (; item != end; ++item)
403
0
    {
404
0
        minHypStr = item->hyp_strength < minHypStr ? item->hyp_strength : minHypStr;
405
0
        maxHypStr = item->hyp_strength > maxHypStr ? item->hyp_strength : maxHypStr;
406
407
0
        minNumHyp = item->num_hypotheses < minNumHyp ? item->num_hypotheses : minNumHyp;
408
0
        maxNumHyp = item->num_hypotheses > maxNumHyp ? item->num_hypotheses : maxNumHyp;
409
410
0
        minNSamples = item->n_samples < minNSamples ? item->n_samples : minNSamples;
411
0
        maxNSamples = item->n_samples > maxNSamples ? item->n_samples : maxNSamples;
412
0
    }
413
414
0
    pDescriptor->setMinMaxHypStrength(minHypStr, maxHypStr);
415
0
    pDescriptor->setMinMaxNumHypotheses(minNumHyp, maxNumHyp);
416
0
    pDescriptor->setMinMaxNSamples(minNSamples, maxNSamples);
417
0
}
418
419
}   //namespace BAG
420