Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/zarr/zarr_v3_codec.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Zarr driver
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef ZARR_V3_CODEC_H
14
#define ZARR_V3_CODEC_H
15
16
#include "zarr.h"
17
18
struct VSIVirtualHandle;
19
20
/************************************************************************/
21
/*                          ZarrArrayMetadata                           */
22
/************************************************************************/
23
24
/** Array-related metadata needed for the good working of Zarr V3 codecs */
25
struct ZarrArrayMetadata
26
{
27
    /** Data type of the array */
28
    DtypeElt oElt{};
29
30
    /** Shape of a block/chunk */
31
    std::vector<size_t> anBlockSizes{};
32
33
    /** No data value of the array. Empty or abyNoData.size() == oElt.nNativeSize */
34
    std::vector<GByte> abyNoData{};
35
};
36
37
/************************************************************************/
38
/*                             ZarrV3Codec                              */
39
/************************************************************************/
40
41
/** Abstract class for a Zarr V3 codec */
42
class ZarrV3Codec CPL_NON_FINAL
43
{
44
  protected:
45
    const std::string m_osName;
46
    CPLJSONObject m_oConfiguration{};
47
    ZarrArrayMetadata m_oInputArrayMetadata{};
48
49
    ZarrV3Codec(const std::string &osName);
50
51
  public:
52
    virtual ~ZarrV3Codec();
53
54
    enum class IOType
55
    {
56
        BYTES,
57
        ARRAY
58
    };
59
60
    virtual IOType GetInputType() const = 0;
61
    virtual IOType GetOutputType() const = 0;
62
63
    virtual bool InitFromConfiguration(
64
        const std::string &osArrayName, const CPLJSONObject &configuration,
65
        const ZarrArrayMetadata &oInputArrayMetadata,
66
        ZarrArrayMetadata &oOutputArrayMetadata, bool bEmitWarnings) = 0;
67
68
    virtual std::unique_ptr<ZarrV3Codec> Clone() const = 0;
69
70
    virtual bool IsNoOp() const
71
0
    {
72
0
        return false;
73
0
    }
74
75
    virtual bool Encode(const ZarrByteVectorQuickResize &abySrc,
76
                        ZarrByteVectorQuickResize &abyDst) const = 0;
77
    virtual bool Decode(const ZarrByteVectorQuickResize &abySrc,
78
                        ZarrByteVectorQuickResize &abyDst) const = 0;
79
80
    /** Partial decoding.
81
     * anStartIdx[i]: coordinate in pixel, within the array of an outer chunk,
82
     * that is < m_oInputArrayMetadata.anBlockSizes[i]
83
     * anCount[i]: number of pixels to extract <= m_oInputArrayMetadata.anBlockSizes[i]
84
     */
85
    virtual bool DecodePartial(VSIVirtualHandle *poFile,
86
                               const ZarrByteVectorQuickResize &abySrc,
87
                               ZarrByteVectorQuickResize &abyDst,
88
                               std::vector<size_t> &anStartIdx,
89
                               std::vector<size_t> &anCount);
90
91
    const std::string &GetName() const
92
148
    {
93
148
        return m_osName;
94
148
    }
95
96
    const CPLJSONObject &GetConfiguration() const
97
0
    {
98
0
        return m_oConfiguration;
99
0
    }
100
101
    virtual std::vector<size_t>
102
    GetInnerMostBlockSize(const std::vector<size_t> &input) const
103
0
    {
104
0
        return input;
105
0
    }
106
107
    virtual void ChangeArrayShapeForward(std::vector<size_t> &anStartIdx,
108
                                         std::vector<size_t> &anCount)
109
0
    {
110
0
        (void)anStartIdx;
111
0
        (void)anCount;
112
0
    }
113
};
114
115
/************************************************************************/
116
/*                    ZarrV3CodecAbstractCompressor                     */
117
/************************************************************************/
118
119
class ZarrV3CodecAbstractCompressor CPL_NON_FINAL : public ZarrV3Codec
120
{
121
  protected:
122
    CPLStringList m_aosCompressorOptions{};
123
    const CPLCompressor *m_pDecompressor = nullptr;
124
    const CPLCompressor *m_pCompressor = nullptr;
125
126
    explicit ZarrV3CodecAbstractCompressor(const std::string &osName);
127
128
    ZarrV3CodecAbstractCompressor(const ZarrV3CodecAbstractCompressor &) =
129
        delete;
130
    ZarrV3CodecAbstractCompressor &
131
    operator=(const ZarrV3CodecAbstractCompressor &) = delete;
132
133
  public:
134
    IOType GetInputType() const override
135
0
    {
136
0
        return IOType::BYTES;
137
0
    }
138
139
    IOType GetOutputType() const override
140
0
    {
141
0
        return IOType::BYTES;
142
0
    }
143
144
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
145
                ZarrByteVectorQuickResize &abyDst) const override;
146
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
147
                ZarrByteVectorQuickResize &abyDst) const override;
148
};
149
150
/************************************************************************/
151
/*                           ZarrV3CodecGZip                            */
152
/************************************************************************/
153
154
// Implements https://zarr-specs.readthedocs.io/en/latest/v3/codecs/gzip/v1.0.html
155
class ZarrV3CodecGZip final : public ZarrV3CodecAbstractCompressor
156
{
157
  public:
158
    static constexpr const char *NAME = "gzip";
159
160
    ZarrV3CodecGZip();
161
162
    static CPLJSONObject GetConfiguration(int nLevel);
163
164
    bool InitFromConfiguration(const std::string &osArrayName,
165
                               const CPLJSONObject &configuration,
166
                               const ZarrArrayMetadata &oInputArrayMetadata,
167
                               ZarrArrayMetadata &oOutputArrayMetadata,
168
                               bool bEmitWarnings) override;
169
170
    std::unique_ptr<ZarrV3Codec> Clone() const override;
171
};
172
173
/************************************************************************/
174
/*                           ZarrV3CodecBlosc                           */
175
/************************************************************************/
176
177
// Implements https://zarr-specs.readthedocs.io/en/latest/v3/codecs/blosc/v1.0.html
178
class ZarrV3CodecBlosc final : public ZarrV3CodecAbstractCompressor
179
{
180
  public:
181
    static constexpr const char *NAME = "blosc";
182
183
    ZarrV3CodecBlosc();
184
185
    static CPLJSONObject GetConfiguration(const char *cname, int clevel,
186
                                          const char *shuffle, int typesize,
187
                                          int blocksize);
188
189
    bool InitFromConfiguration(const std::string &osArrayName,
190
                               const CPLJSONObject &configuration,
191
                               const ZarrArrayMetadata &oInputArrayMetadata,
192
                               ZarrArrayMetadata &oOutputArrayMetadata,
193
                               bool bEmitWarnings) override;
194
195
    std::unique_ptr<ZarrV3Codec> Clone() const override;
196
};
197
198
/************************************************************************/
199
/*                           ZarrV3CodecZstd                            */
200
/************************************************************************/
201
202
// Implements https://github.com/zarr-developers/zarr-specs/pull/256
203
class ZarrV3CodecZstd final : public ZarrV3CodecAbstractCompressor
204
{
205
  public:
206
    static constexpr const char *NAME = "zstd";
207
208
    ZarrV3CodecZstd();
209
210
    static CPLJSONObject GetConfiguration(int level, bool checksum);
211
212
    bool InitFromConfiguration(const std::string &osArrayName,
213
                               const CPLJSONObject &configuration,
214
                               const ZarrArrayMetadata &oInputArrayMetadata,
215
                               ZarrArrayMetadata &oOutputArrayMetadata,
216
                               bool bEmitWarnings) override;
217
218
    std::unique_ptr<ZarrV3Codec> Clone() const override;
219
};
220
221
/************************************************************************/
222
/*                           ZarrV3CodecBytes                           */
223
/************************************************************************/
224
225
// Implements https://zarr-specs.readthedocs.io/en/latest/v3/codecs/bytes/v1.0.html
226
class ZarrV3CodecBytes final : public ZarrV3Codec
227
{
228
    bool m_bLittle = true;
229
230
  public:
231
    static constexpr const char *NAME = "bytes";
232
233
    ZarrV3CodecBytes();
234
235
    IOType GetInputType() const override
236
148
    {
237
148
        return IOType::ARRAY;
238
148
    }
239
240
    IOType GetOutputType() const override
241
148
    {
242
148
        return IOType::BYTES;
243
148
    }
244
245
    static CPLJSONObject GetConfiguration(bool bLittle);
246
247
    bool InitFromConfiguration(const std::string &osArrayName,
248
                               const CPLJSONObject &configuration,
249
                               const ZarrArrayMetadata &oInputArrayMetadata,
250
                               ZarrArrayMetadata &oOutputArrayMetadata,
251
                               bool bEmitWarnings) override;
252
253
    bool IsLittle() const
254
0
    {
255
0
        return m_bLittle;
256
0
    }
257
258
    bool IsNoOp() const override
259
148
    {
260
        // Byte-oriented string types have no endianness concept
261
148
        if (m_oInputArrayMetadata.oElt.nativeType ==
262
148
            DtypeElt::NativeType::STRING_ASCII)
263
0
            return true;
264
        if constexpr (CPL_IS_LSB)
265
148
            return m_oInputArrayMetadata.oElt.nativeSize == 1 || m_bLittle;
266
        else
267
            return m_oInputArrayMetadata.oElt.nativeSize == 1 || !m_bLittle;
268
148
    }
269
270
    std::unique_ptr<ZarrV3Codec> Clone() const override;
271
272
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
273
                ZarrByteVectorQuickResize &abyDst) const override;
274
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
275
                ZarrByteVectorQuickResize &abyDst) const override;
276
};
277
278
/************************************************************************/
279
/*                         ZarrV3CodecVLenUTF8                          */
280
/************************************************************************/
281
282
/** Implements the vlen-utf8 array-to-bytes codec for variable-length
283
 *  UTF-8 strings (zarr-extensions).
284
 *
285
 *  Binary format (little-endian):
286
 *    [u32 item_count] [u32 len_0][bytes_0] [u32 len_1][bytes_1] ...
287
 *
288
 *  Decode produces a flat buffer of nElements * nativeSize bytes where
289
 *  each slot is a null-padded string. Read-only for now.
290
 */
291
class ZarrV3CodecVLenUTF8 final : public ZarrV3Codec
292
{
293
  public:
294
    static constexpr const char *NAME = "vlen-utf8";
295
296
    ZarrV3CodecVLenUTF8();
297
298
    IOType GetInputType() const override
299
0
    {
300
0
        return IOType::ARRAY;
301
0
    }
302
303
    IOType GetOutputType() const override
304
0
    {
305
0
        return IOType::BYTES;
306
0
    }
307
308
    bool InitFromConfiguration(const std::string &osArrayName,
309
                               const CPLJSONObject &configuration,
310
                               const ZarrArrayMetadata &oInputArrayMetadata,
311
                               ZarrArrayMetadata &oOutputArrayMetadata,
312
                               bool bEmitWarnings) override;
313
314
    std::unique_ptr<ZarrV3Codec> Clone() const override;
315
316
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
317
                ZarrByteVectorQuickResize &abyDst) const override;
318
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
319
                ZarrByteVectorQuickResize &abyDst) const override;
320
};
321
322
/************************************************************************/
323
/*                         ZarrV3CodecTranspose                         */
324
/************************************************************************/
325
326
// Implements https://zarr-specs.readthedocs.io/en/latest/v3/codecs/transpose/v1.0.html
327
class ZarrV3CodecTranspose final : public ZarrV3Codec
328
{
329
    // m_anOrder is such that dest_shape[i] = source_shape[m_anOrder[i]]
330
    // where source_shape[] is the size of the array before the Encode() operation
331
    // and dest_shape[] its size after.
332
    // m_anOrder[] describes a bijection of [0,N-1] to [0,N-1]
333
    std::vector<int> m_anOrder{};
334
335
    // m_anReverseOrder is such that m_anReverseOrder[m_anOrder[i]] = i
336
    std::vector<int> m_anReverseOrder{};
337
338
    bool Transpose(const ZarrByteVectorQuickResize &abySrc,
339
                   ZarrByteVectorQuickResize &abyDst, bool bEncodeDirection,
340
                   const std::vector<size_t> &anForwardBlockSizes) const;
341
342
  public:
343
    static constexpr const char *NAME = "transpose";
344
345
    ZarrV3CodecTranspose();
346
347
    IOType GetInputType() const override
348
0
    {
349
0
        return IOType::ARRAY;
350
0
    }
351
352
    IOType GetOutputType() const override
353
0
    {
354
0
        return IOType::ARRAY;
355
0
    }
356
357
    static CPLJSONObject GetConfiguration(const std::vector<int> &anOrder);
358
359
    bool InitFromConfiguration(const std::string &osArrayName,
360
                               const CPLJSONObject &configuration,
361
                               const ZarrArrayMetadata &oInputArrayMetadata,
362
                               ZarrArrayMetadata &oOutputArrayMetadata,
363
                               bool bEmitWarnings) override;
364
365
    const std::vector<int> &GetOrder() const
366
0
    {
367
0
        return m_anOrder;
368
0
    }
369
370
    bool IsNoOp() const override;
371
372
    std::unique_ptr<ZarrV3Codec> Clone() const override;
373
374
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
375
                ZarrByteVectorQuickResize &abyDst) const override;
376
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
377
                ZarrByteVectorQuickResize &abyDst) const override;
378
379
    bool DecodePartial(VSIVirtualHandle *poFile,
380
                       const ZarrByteVectorQuickResize &abySrc,
381
                       ZarrByteVectorQuickResize &abyDst,
382
                       std::vector<size_t> &anStartIdx,
383
                       std::vector<size_t> &anCount) override;
384
385
    std::vector<size_t>
386
    GetInnerMostBlockSize(const std::vector<size_t> &input) const override;
387
388
    template <class T>
389
    inline void Reorder1DForward(std::vector<T> &vector) const
390
0
    {
391
0
        std::vector<T> res;
392
0
        for (int idx : m_anOrder)
393
0
            res.push_back(vector[idx]);
394
0
        vector = std::move(res);
395
0
    }
396
397
    template <class T>
398
    inline void Reorder1DInverse(std::vector<T> &vector) const
399
0
    {
400
0
        std::vector<T> res;
401
0
        for (int idx : m_anReverseOrder)
402
0
            res.push_back(vector[idx]);
403
0
        vector = std::move(res);
404
0
    }
405
406
    void ChangeArrayShapeForward(std::vector<size_t> &anStartIdx,
407
                                 std::vector<size_t> &anCount) override
408
0
    {
409
0
        Reorder1DForward(anStartIdx);
410
0
        Reorder1DForward(anCount);
411
0
    }
412
};
413
414
/************************************************************************/
415
/*                          ZarrV3CodecCRC32C                           */
416
/************************************************************************/
417
418
// Implements https://zarr-specs.readthedocs.io/en/latest/v3/codecs/crc32c/index.html
419
class ZarrV3CodecCRC32C final : public ZarrV3Codec
420
{
421
    bool m_bCheckCRC = true;
422
423
  public:
424
    static constexpr const char *NAME = "crc32c";
425
426
    ZarrV3CodecCRC32C();
427
428
    IOType GetInputType() const override
429
0
    {
430
0
        return IOType::BYTES;
431
0
    }
432
433
    IOType GetOutputType() const override
434
0
    {
435
0
        return IOType::BYTES;
436
0
    }
437
438
    bool InitFromConfiguration(const std::string &osArrayName,
439
                               const CPLJSONObject &configuration,
440
                               const ZarrArrayMetadata &oInputArrayMetadata,
441
                               ZarrArrayMetadata &oOutputArrayMetadata,
442
                               bool bEmitWarnings) override;
443
444
    std::unique_ptr<ZarrV3Codec> Clone() const override;
445
446
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
447
                ZarrByteVectorQuickResize &abyDst) const override;
448
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
449
                ZarrByteVectorQuickResize &abyDst) const override;
450
};
451
452
/************************************************************************/
453
/*                      ZarrV3CodecShardingIndexed                      */
454
/************************************************************************/
455
456
class ZarrV3CodecSequence;
457
458
// https://zarr-specs.readthedocs.io/en/latest/v3/codecs/sharding-indexed/index.html
459
class ZarrV3CodecShardingIndexed final : public ZarrV3Codec
460
{
461
    std::unique_ptr<ZarrV3CodecSequence> m_poCodecSequence{};
462
    std::unique_ptr<ZarrV3CodecSequence> m_poIndexCodecSequence{};
463
    bool m_bIndexLocationAtEnd = true;
464
    bool m_bIndexHasCRC32 = false;
465
    std::vector<size_t> m_anInnerBlockSize{};
466
467
    struct Location
468
    {
469
        uint64_t nOffset;
470
        uint64_t nSize;
471
    };
472
473
  public:
474
    static constexpr const char *NAME = "sharding_indexed";
475
476
    ZarrV3CodecShardingIndexed();
477
478
    IOType GetInputType() const override
479
0
    {
480
0
        return IOType::ARRAY;
481
0
    }
482
483
    IOType GetOutputType() const override
484
0
    {
485
0
        return IOType::BYTES;
486
0
    }
487
488
    bool InitFromConfiguration(const std::string &osArrayName,
489
                               const CPLJSONObject &configuration,
490
                               const ZarrArrayMetadata &oInputArrayMetadata,
491
                               ZarrArrayMetadata &oOutputArrayMetadata,
492
                               bool bEmitWarnings) override;
493
494
    std::unique_ptr<ZarrV3Codec> Clone() const override;
495
496
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
497
                ZarrByteVectorQuickResize &abyDst) const override;
498
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
499
                ZarrByteVectorQuickResize &abyDst) const override;
500
501
    bool DecodePartial(VSIVirtualHandle *poFile,
502
                       const ZarrByteVectorQuickResize &abySrc,
503
                       ZarrByteVectorQuickResize &abyDst,
504
                       std::vector<size_t> &anStartIdx,
505
                       std::vector<size_t> &anCount) override;
506
507
    /** Batch-read multiple inner chunks from the same shard via two
508
     *  ReadMultiRange() passes (index entries, then data), then decode.
509
     *  pszFilename is used as a cache key for the shard index; pass nullptr
510
     *  to bypass the cache.
511
     */
512
    bool BatchDecodePartial(
513
        VSIVirtualHandle *poFile, const char *pszFilename,
514
        const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
515
            &anRequests,
516
        std::vector<ZarrByteVectorQuickResize> &aResults);
517
518
    std::vector<size_t>
519
    GetInnerMostBlockSize(const std::vector<size_t> &input) const override;
520
};
521
522
/************************************************************************/
523
/*                         ZarrV3CodecSequence                          */
524
/************************************************************************/
525
526
class ZarrV3CodecSequence
527
{
528
    const ZarrArrayMetadata m_oInputArrayMetadata;
529
    std::vector<std::unique_ptr<ZarrV3Codec>> m_apoCodecs{};
530
    CPLJSONObject m_oCodecArray{};
531
    ZarrByteVectorQuickResize m_abyTmp{};
532
    bool m_bPartialDecodingPossible = false;
533
534
    bool AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer, size_t nEltCount);
535
536
  public:
537
    explicit ZarrV3CodecSequence(const ZarrArrayMetadata &oInputArrayMetadata)
538
148
        : m_oInputArrayMetadata(oInputArrayMetadata)
539
148
    {
540
148
    }
541
542
    // This method is not thread safe due to cloning a JSON object
543
    std::unique_ptr<ZarrV3CodecSequence> Clone() const;
544
545
    bool InitFromJson(const std::string &osArrayName,
546
                      const CPLJSONObject &oCodecs,
547
                      ZarrArrayMetadata &oOutputArrayMetadata);
548
549
    const CPLJSONObject &GetJSon() const
550
207
    {
551
207
        return m_oCodecArray;
552
207
    }
553
554
    const std::vector<std::unique_ptr<ZarrV3Codec>> &GetCodecs() const
555
0
    {
556
0
        return m_apoCodecs;
557
0
    }
558
559
    bool SupportsPartialDecoding() const
560
34.6k
    {
561
34.6k
        return m_bPartialDecodingPossible;
562
34.6k
    }
563
564
    bool Encode(ZarrByteVectorQuickResize &abyBuffer);
565
    bool Decode(ZarrByteVectorQuickResize &abyBuffer);
566
567
    /** Partial decoding.
568
     * anStartIdx[i]: coordinate in pixel, within the array of an outer chunk,
569
     * that is < m_oInputArrayMetadata.anBlockSizes[i]
570
     * anCount[i]: number of pixels to extract <= m_oInputArrayMetadata.anBlockSizes[i]
571
     */
572
    bool DecodePartial(VSIVirtualHandle *poFile,
573
                       ZarrByteVectorQuickResize &abyBuffer,
574
                       const std::vector<size_t> &anStartIdx,
575
                       const std::vector<size_t> &anCount);
576
577
    /** Batch-read multiple inner chunks via ReadMultiRange().
578
     *  Delegates to the sharding codec if present, otherwise falls back
579
     *  to sequential DecodePartial() calls.
580
     *  pszFilename is forwarded to the sharding codec for index caching.
581
     */
582
    bool BatchDecodePartial(
583
        VSIVirtualHandle *poFile, const char *pszFilename,
584
        const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
585
            &anRequests,
586
        std::vector<ZarrByteVectorQuickResize> &aResults);
587
588
    std::vector<size_t>
589
    GetInnerMostBlockSize(const std::vector<size_t> &anOuterBlockSize) const;
590
};
591
592
/************************************************************************/
593
/*                          ZarrV3CodecPcodec                           */
594
/************************************************************************/
595
596
/** Non-standard "pcodec" (a.k.a. "pco") from https://github.com/pcodec/pcodec
597
 *
598
 * Also see https://numcodecs.readthedocs.io/en/stable/compression/pcodec.html
599
 */
600
class ZarrV3CodecPcodec /* final */ : public ZarrV3Codec
601
{
602
  public:
603
    static constexpr const char *NAME = "numcodecs.pcodec";
604
605
    explicit ZarrV3CodecPcodec();
606
    ~ZarrV3CodecPcodec() override;
607
608
    IOType GetInputType() const override
609
0
    {
610
0
        return IOType::ARRAY;
611
0
    }
612
613
    IOType GetOutputType() const override
614
0
    {
615
0
        return IOType::BYTES;
616
0
    }
617
618
    bool InitFromConfiguration(const std::string &osArrayName,
619
                               const CPLJSONObject &configuration,
620
                               const ZarrArrayMetadata &oInputArrayMetadata,
621
                               ZarrArrayMetadata &oOutputArrayMetadata,
622
                               bool bEmitWarnings) override;
623
624
    std::unique_ptr<ZarrV3Codec> Clone() const override;
625
626
    bool Decode(const ZarrByteVectorQuickResize &abySrc,
627
                ZarrByteVectorQuickResize &abyDst) const override;
628
629
    bool Encode(const ZarrByteVectorQuickResize &abySrc,
630
                ZarrByteVectorQuickResize &abyDst) const override;
631
};
632
633
#endif