LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_codec_sequence.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 143 161 88.8 %
Date: 2026-03-26 23:25:44 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver, ZarrV3CodecSequence class
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr_v3_codec.h"
      14             : 
      15             : /************************************************************************/
      16             : /*                     ZarrV3CodecSequence::Clone()                     */
      17             : /************************************************************************/
      18             : 
      19          96 : std::unique_ptr<ZarrV3CodecSequence> ZarrV3CodecSequence::Clone() const
      20             : {
      21          96 :     auto poClone = std::make_unique<ZarrV3CodecSequence>(m_oInputArrayMetadata);
      22         164 :     for (const auto &poCodec : m_apoCodecs)
      23          68 :         poClone->m_apoCodecs.emplace_back(poCodec->Clone());
      24          96 :     poClone->m_oCodecArray = m_oCodecArray.Clone();
      25          96 :     poClone->m_bPartialDecodingPossible = m_bPartialDecodingPossible;
      26          96 :     return poClone;
      27             : }
      28             : 
      29             : /************************************************************************/
      30             : /*                 ZarrV3CodecSequence::InitFromJson()                  */
      31             : /************************************************************************/
      32             : 
      33        2678 : bool ZarrV3CodecSequence::InitFromJson(const CPLJSONObject &oCodecs,
      34             :                                        ZarrArrayMetadata &oOutputArrayMetadata)
      35             : {
      36        2678 :     if (oCodecs.GetType() != CPLJSONObject::Type::Array)
      37             :     {
      38           0 :         CPLError(CE_Failure, CPLE_AppDefined, "codecs is not an array");
      39           0 :         return false;
      40             :     }
      41        5356 :     auto oCodecsArray = oCodecs.ToArray();
      42             : 
      43        5356 :     ZarrArrayMetadata oInputArrayMetadata = m_oInputArrayMetadata;
      44        2678 :     ZarrV3Codec::IOType eLastType = ZarrV3Codec::IOType::ARRAY;
      45        5356 :     std::string osLastCodec;
      46             : 
      47             :     const auto InsertImplicitEndianCodecIfNeeded =
      48        8121 :         [this, &oInputArrayMetadata, &eLastType, &osLastCodec]()
      49             :     {
      50        4057 :         CPL_IGNORE_RET_VAL(this);
      51        4057 :         if (eLastType == ZarrV3Codec::IOType::ARRAY &&
      52           1 :             oInputArrayMetadata.oElt.nativeSize > 1 &&
      53           1 :             oInputArrayMetadata.oElt.nativeType !=
      54           1 :                 DtypeElt::NativeType::STRING_ASCII &&
      55           1 :             oInputArrayMetadata.oElt.nativeType !=
      56             :                 DtypeElt::NativeType::STRING_UNICODE)
      57             :         {
      58           1 :             CPLError(CE_Warning, CPLE_AppDefined,
      59             :                      "'bytes' codec missing. Assuming little-endian storage, "
      60             :                      "but such tolerance may be removed in future versions");
      61           2 :             auto poEndianCodec = std::make_unique<ZarrV3CodecBytes>();
      62           2 :             ZarrArrayMetadata oTmpOutputArrayMetadata;
      63           2 :             poEndianCodec->InitFromConfiguration(
      64           2 :                 ZarrV3CodecBytes::GetConfiguration(true), oInputArrayMetadata,
      65             :                 oTmpOutputArrayMetadata, /* bEmitWarnings = */ true);
      66           1 :             oInputArrayMetadata = std::move(oTmpOutputArrayMetadata);
      67           1 :             eLastType = poEndianCodec->GetOutputType();
      68           1 :             osLastCodec = poEndianCodec->GetName();
      69             :             if constexpr (!CPL_IS_LSB)
      70             :             {
      71             :                 // Insert a little endian codec if we are on a big endian target
      72             :                 m_apoCodecs.emplace_back(std::move(poEndianCodec));
      73             :             }
      74             :         }
      75        4057 :     };
      76             : 
      77        2678 :     bool bShardingFound = false;
      78        5356 :     std::vector<size_t> anBlockSizesBeforeSharding;
      79        6817 :     for (const auto &oCodec : oCodecsArray)
      80             :     {
      81        4159 :         if (oCodec.GetType() != CPLJSONObject::Type::Object)
      82             :         {
      83           0 :             CPLError(CE_Failure, CPLE_AppDefined, "codecs[] is not an object");
      84          20 :             return false;
      85             :         }
      86        8318 :         const auto osName = oCodec["name"].ToString();
      87           0 :         std::unique_ptr<ZarrV3Codec> poCodec;
      88        4159 :         if (osName == ZarrV3CodecGZip::NAME)
      89          59 :             poCodec = std::make_unique<ZarrV3CodecGZip>();
      90        4100 :         else if (osName == ZarrV3CodecBlosc::NAME)
      91           4 :             poCodec = std::make_unique<ZarrV3CodecBlosc>();
      92        4096 :         else if (osName == ZarrV3CodecZstd::NAME)
      93         527 :             poCodec = std::make_unique<ZarrV3CodecZstd>();
      94        5165 :         else if (osName == ZarrV3CodecBytes::NAME ||
      95        1596 :                  osName == "endian" /* endian is the old name */)
      96        1973 :             poCodec = std::make_unique<ZarrV3CodecBytes>();
      97        1596 :         else if (osName == ZarrV3CodecTranspose::NAME)
      98          83 :             poCodec = std::make_unique<ZarrV3CodecTranspose>();
      99        1513 :         else if (osName == ZarrV3CodecCRC32C::NAME)
     100         809 :             poCodec = std::make_unique<ZarrV3CodecCRC32C>();
     101         704 :         else if (osName == ZarrV3CodecVLenUTF8::NAME)
     102          14 :             poCodec = std::make_unique<ZarrV3CodecVLenUTF8>();
     103         690 :         else if (osName == ZarrV3CodecShardingIndexed::NAME)
     104             :         {
     105         688 :             bShardingFound = true;
     106         688 :             poCodec = std::make_unique<ZarrV3CodecShardingIndexed>();
     107             :         }
     108             :         else
     109             :         {
     110           2 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported codec: %s",
     111             :                      osName.c_str());
     112           2 :             return false;
     113             :         }
     114             : 
     115        4157 :         if (poCodec->GetInputType() == ZarrV3Codec::IOType::ARRAY)
     116             :         {
     117        2758 :             if (eLastType == ZarrV3Codec::IOType::BYTES)
     118             :             {
     119           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     120             :                          "Cannot chain codec %s with %s",
     121           0 :                          poCodec->GetName().c_str(), osLastCodec.c_str());
     122           0 :                 return false;
     123             :             }
     124             :         }
     125             :         else
     126             :         {
     127        1399 :             InsertImplicitEndianCodecIfNeeded();
     128             :         }
     129             : 
     130        4157 :         ZarrArrayMetadata oStepOutputArrayMetadata;
     131        4157 :         if (osName == ZarrV3CodecShardingIndexed::NAME)
     132             :         {
     133         688 :             anBlockSizesBeforeSharding = oInputArrayMetadata.anBlockSizes;
     134             :         }
     135        8314 :         if (!poCodec->InitFromConfiguration(oCodec["configuration"],
     136             :                                             oInputArrayMetadata,
     137             :                                             oStepOutputArrayMetadata,
     138        4157 :                                             /* bEmitWarnings = */ true))
     139             :         {
     140          18 :             return false;
     141             :         }
     142        4139 :         oInputArrayMetadata = std::move(oStepOutputArrayMetadata);
     143        4139 :         eLastType = poCodec->GetOutputType();
     144        4139 :         osLastCodec = poCodec->GetName();
     145             : 
     146        4139 :         if (!poCodec->IsNoOp())
     147        2219 :             m_apoCodecs.emplace_back(std::move(poCodec));
     148             :     }
     149             : 
     150        2658 :     if (bShardingFound)
     151             :     {
     152         670 :         m_bPartialDecodingPossible =
     153         670 :             (m_apoCodecs.back()->GetName() == ZarrV3CodecShardingIndexed::NAME);
     154         670 :         if (!m_bPartialDecodingPossible)
     155             :         {
     156         138 :             m_bPartialDecodingPossible = false;
     157             :             // This is not an implementation limitation, but the result of a
     158             :             // badly thought dataset. Zarr-Python also emits a similar warning.
     159         138 :             CPLError(
     160             :                 CE_Warning, CPLE_AppDefined,
     161             :                 "Sharding codec found, but not in last position. Consequently "
     162             :                 "partial shard decoding will not be possible");
     163             :             oInputArrayMetadata.anBlockSizes =
     164         138 :                 std::move(anBlockSizesBeforeSharding);
     165             :         }
     166             :     }
     167             : 
     168        2658 :     InsertImplicitEndianCodecIfNeeded();
     169             : 
     170        2658 :     m_oCodecArray = oCodecs.Clone();
     171        2658 :     oOutputArrayMetadata = std::move(oInputArrayMetadata);
     172        2658 :     return true;
     173             : }
     174             : 
     175             : /************************************************************************/
     176             : /*                  ZarrV3CodecBytes::AllocateBuffer()                  */
     177             : /************************************************************************/
     178             : 
     179       44406 : bool ZarrV3CodecSequence::AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer,
     180             :                                          size_t nEltCount)
     181             : {
     182       44406 :     if (!m_apoCodecs.empty())
     183             :     {
     184       33029 :         const size_t nRawSize =
     185       33029 :             nEltCount * m_oInputArrayMetadata.oElt.nativeSize;
     186             :         // Grow the temporary buffer a bit beyond the uncompressed size
     187       33029 :         const size_t nMaxSize = nRawSize + nRawSize / 3 + 64;
     188             :         try
     189             :         {
     190       33029 :             m_abyTmp.resize(nMaxSize);
     191             :         }
     192           0 :         catch (const std::exception &e)
     193             :         {
     194           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     195           0 :             return false;
     196             :         }
     197       33029 :         m_abyTmp.resize(nRawSize);
     198             : 
     199             :         // Grow the input/output buffer too if we have several steps
     200       33029 :         if (m_apoCodecs.size() >= 2 && abyBuffer.capacity() < nMaxSize)
     201             :         {
     202         222 :             const size_t nSize = abyBuffer.size();
     203             :             try
     204             :             {
     205         222 :                 abyBuffer.resize(nMaxSize);
     206             :             }
     207           0 :             catch (const std::exception &e)
     208             :             {
     209           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     210           0 :                 return false;
     211             :             }
     212         222 :             abyBuffer.resize(nSize);
     213             :         }
     214             :     }
     215       44406 :     return true;
     216             : }
     217             : 
     218             : /************************************************************************/
     219             : /*                    ZarrV3CodecSequence::Encode()                     */
     220             : /************************************************************************/
     221             : 
     222       15236 : bool ZarrV3CodecSequence::Encode(ZarrByteVectorQuickResize &abyBuffer)
     223             : {
     224       15236 :     if (!AllocateBuffer(abyBuffer,
     225       15236 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     226           0 :         return false;
     227       24982 :     for (const auto &poCodec : m_apoCodecs)
     228             :     {
     229        9746 :         if (!poCodec->Encode(abyBuffer, m_abyTmp))
     230           0 :             return false;
     231        9746 :         std::swap(abyBuffer, m_abyTmp);
     232             :     }
     233       15236 :     return true;
     234             : }
     235             : 
     236             : /************************************************************************/
     237             : /*                    ZarrV3CodecSequence::Decode()                     */
     238             : /************************************************************************/
     239             : 
     240       28143 : bool ZarrV3CodecSequence::Decode(ZarrByteVectorQuickResize &abyBuffer)
     241             : {
     242       28143 :     if (!AllocateBuffer(abyBuffer,
     243       28143 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     244           0 :         return false;
     245       49738 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     246             :     {
     247       22488 :         const auto &poCodec = *iter;
     248       22488 :         if (!poCodec->Decode(abyBuffer, m_abyTmp))
     249         893 :             return false;
     250       21595 :         std::swap(abyBuffer, m_abyTmp);
     251             :     }
     252       27250 :     return true;
     253             : }
     254             : 
     255             : /************************************************************************/
     256             : /*                 ZarrV3CodecSequence::DecodePartial()                 */
     257             : /************************************************************************/
     258             : 
     259        1027 : bool ZarrV3CodecSequence::DecodePartial(VSIVirtualHandle *poFile,
     260             :                                         ZarrByteVectorQuickResize &abyBuffer,
     261             :                                         const std::vector<size_t> &anStartIdxIn,
     262             :                                         const std::vector<size_t> &anCountIn)
     263             : {
     264        1027 :     CPLAssert(anStartIdxIn.size() == m_oInputArrayMetadata.anBlockSizes.size());
     265        1027 :     CPLAssert(anStartIdxIn.size() == anCountIn.size());
     266             : 
     267        1027 :     if (!AllocateBuffer(abyBuffer, MultiplyElements(anCountIn)))
     268           0 :         return false;
     269             : 
     270             :     // anStartIdxIn and anCountIn are expressed in the shape *before* encoding
     271             :     // We need to apply the potential transpositions before submitting them
     272             :     // to the decoder of the Array->Bytes decoder
     273        2054 :     std::vector<size_t> anStartIdx(anStartIdxIn);
     274        2054 :     std::vector<size_t> anCount(anCountIn);
     275        2079 :     for (auto &poCodec : m_apoCodecs)
     276             :     {
     277        1052 :         poCodec->ChangeArrayShapeForward(anStartIdx, anCount);
     278             :     }
     279             : 
     280        1723 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     281             :     {
     282        1052 :         const auto &poCodec = *iter;
     283             : 
     284        2104 :         if (!poCodec->DecodePartial(poFile, abyBuffer, m_abyTmp, anStartIdx,
     285        1052 :                                     anCount))
     286         356 :             return false;
     287         696 :         std::swap(abyBuffer, m_abyTmp);
     288             :     }
     289         671 :     return true;
     290             : }
     291             : 
     292             : /************************************************************************/
     293             : /*              ZarrV3CodecSequence::BatchDecodePartial()               */
     294             : /************************************************************************/
     295             : 
     296        4031 : bool ZarrV3CodecSequence::BatchDecodePartial(
     297             :     VSIVirtualHandle *poFile, const char *pszFilename,
     298             :     const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
     299             :         &anRequests,
     300             :     std::vector<ZarrByteVectorQuickResize> &aResults)
     301             : {
     302             :     // Only batch-decode when sharding is the sole codec. If other codecs
     303             :     // (e.g. transpose) precede it, indices and output need codec-specific
     304             :     // transformations that BatchDecodePartial does not handle.
     305        4031 :     if (m_apoCodecs.size() == 1)
     306             :     {
     307        4023 :         auto *poSharding = dynamic_cast<ZarrV3CodecShardingIndexed *>(
     308        8046 :             m_apoCodecs.back().get());
     309        4023 :         if (poSharding)
     310             :         {
     311        4023 :             return poSharding->BatchDecodePartial(poFile, pszFilename,
     312        4023 :                                                   anRequests, aResults);
     313             :         }
     314             :     }
     315             : 
     316             :     // Fallback: sequential DecodePartial for non-sharding codec chains
     317           8 :     aResults.resize(anRequests.size());
     318          32 :     for (size_t i = 0; i < anRequests.size(); ++i)
     319             :     {
     320          24 :         if (!DecodePartial(poFile, aResults[i], anRequests[i].first,
     321          24 :                            anRequests[i].second))
     322           0 :             return false;
     323             :     }
     324           8 :     return true;
     325             : }
     326             : 
     327             : /************************************************************************/
     328             : /*             ZarrV3CodecSequence::GetInnerMostBlockSize()             */
     329             : /************************************************************************/
     330             : 
     331        1294 : std::vector<size_t> ZarrV3CodecSequence::GetInnerMostBlockSize(
     332             :     const std::vector<size_t> &anOuterBlockSize) const
     333             : {
     334        1294 :     auto chunkSize = anOuterBlockSize;
     335        2316 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     336             :     {
     337        1022 :         const auto &poCodec = *iter;
     338        1512 :         if (m_bPartialDecodingPossible ||
     339         490 :             poCodec->GetName() != ZarrV3CodecShardingIndexed::NAME)
     340             :         {
     341         884 :             chunkSize = poCodec->GetInnerMostBlockSize(chunkSize);
     342             :         }
     343             :     }
     344        1294 :     return chunkSize;
     345             : }

Generated by: LCOV version 1.14