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: 144 165 87.3 %
Date: 2026-06-19 21:24:00 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          88 : std::unique_ptr<ZarrV3CodecSequence> ZarrV3CodecSequence::Clone() const
      20             : {
      21          88 :     auto poClone = std::make_unique<ZarrV3CodecSequence>(m_oInputArrayMetadata);
      22         156 :     for (const auto &poCodec : m_apoCodecs)
      23          68 :         poClone->m_apoCodecs.emplace_back(poCodec->Clone());
      24          88 :     poClone->m_oCodecArray = m_oCodecArray.Clone();
      25          88 :     poClone->m_bPartialDecodingPossible = m_bPartialDecodingPossible;
      26          88 :     return poClone;
      27             : }
      28             : 
      29             : /************************************************************************/
      30             : /*                 ZarrV3CodecSequence::InitFromJson()                  */
      31             : /************************************************************************/
      32             : 
      33        2792 : bool ZarrV3CodecSequence::InitFromJson(const std::string &osArrayName,
      34             :                                        const CPLJSONObject &oCodecs,
      35             :                                        ZarrArrayMetadata &oOutputArrayMetadata)
      36             : {
      37        2792 :     if (oCodecs.GetType() != CPLJSONObject::Type::Array)
      38             :     {
      39           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: codecs is not an array",
      40             :                  osArrayName.c_str());
      41           0 :         return false;
      42             :     }
      43        5584 :     auto oCodecsArray = oCodecs.ToArray();
      44             : 
      45        5584 :     ZarrArrayMetadata oInputArrayMetadata = m_oInputArrayMetadata;
      46        2792 :     ZarrV3Codec::IOType eLastType = ZarrV3Codec::IOType::ARRAY;
      47        5584 :     std::string osLastCodec;
      48             : 
      49             :     const auto InsertImplicitEndianCodecIfNeeded =
      50        8453 :         [this, &oInputArrayMetadata, &eLastType, &osLastCodec]()
      51             :     {
      52        4223 :         CPL_IGNORE_RET_VAL(this);
      53        4223 :         if (eLastType == ZarrV3Codec::IOType::ARRAY &&
      54           1 :             oInputArrayMetadata.oElt.nativeSize > 1 &&
      55           1 :             oInputArrayMetadata.oElt.nativeType !=
      56           1 :                 DtypeElt::NativeType::STRING_ASCII &&
      57           1 :             oInputArrayMetadata.oElt.nativeType !=
      58             :                 DtypeElt::NativeType::STRING_UNICODE)
      59             :         {
      60           1 :             CPLError(CE_Warning, CPLE_AppDefined,
      61             :                      "'bytes' codec missing. Assuming little-endian storage, "
      62             :                      "but such tolerance may be removed in future versions");
      63           2 :             auto poEndianCodec = std::make_unique<ZarrV3CodecBytes>();
      64           2 :             ZarrArrayMetadata oTmpOutputArrayMetadata;
      65           2 :             poEndianCodec->InitFromConfiguration(
      66           2 :                 std::string(), ZarrV3CodecBytes::GetConfiguration(true),
      67             :                 oInputArrayMetadata, oTmpOutputArrayMetadata,
      68             :                 /* bEmitWarnings = */ true);
      69           1 :             oInputArrayMetadata = std::move(oTmpOutputArrayMetadata);
      70           1 :             eLastType = poEndianCodec->GetOutputType();
      71           1 :             osLastCodec = poEndianCodec->GetName();
      72             :             if constexpr (!CPL_IS_LSB)
      73             :             {
      74             :                 // Insert a little endian codec if we are on a big endian target
      75             :                 m_apoCodecs.emplace_back(std::move(poEndianCodec));
      76             :             }
      77             :         }
      78        4223 :     };
      79             : 
      80        2792 :     bool bShardingFound = false;
      81        5584 :     std::vector<size_t> anBlockSizesBeforeSharding;
      82        7097 :     for (const auto &oCodec : oCodecsArray)
      83             :     {
      84        4325 :         if (oCodec.GetType() != CPLJSONObject::Type::Object)
      85             :         {
      86           0 :             CPLError(CE_Failure, CPLE_AppDefined, "codecs[] is not an object");
      87          20 :             return false;
      88             :         }
      89        8650 :         const auto osName = oCodec["name"].ToString();
      90           0 :         std::unique_ptr<ZarrV3Codec> poCodec;
      91        4325 :         if (osName == ZarrV3CodecGZip::NAME)
      92          59 :             poCodec = std::make_unique<ZarrV3CodecGZip>();
      93        4266 :         else if (osName == ZarrV3CodecBlosc::NAME)
      94          28 :             poCodec = std::make_unique<ZarrV3CodecBlosc>();
      95        4238 :         else if (osName == ZarrV3CodecZstd::NAME)
      96         533 :             poCodec = std::make_unique<ZarrV3CodecZstd>();
      97        5345 :         else if (osName == ZarrV3CodecBytes::NAME ||
      98        1640 :                  osName == "endian" /* endian is the old name */)
      99        2065 :             poCodec = std::make_unique<ZarrV3CodecBytes>();
     100        1640 :         else if (osName == ZarrV3CodecTranspose::NAME)
     101          83 :             poCodec = std::make_unique<ZarrV3CodecTranspose>();
     102        1557 :         else if (osName == ZarrV3CodecCRC32C::NAME)
     103         831 :             poCodec = std::make_unique<ZarrV3CodecCRC32C>();
     104         726 :         else if (osName == ZarrV3CodecVLenUTF8::NAME)
     105          14 :             poCodec = std::make_unique<ZarrV3CodecVLenUTF8>();
     106         712 :         else if (osName == ZarrV3CodecShardingIndexed::NAME)
     107             :         {
     108         710 :             bShardingFound = true;
     109         710 :             poCodec = std::make_unique<ZarrV3CodecShardingIndexed>();
     110             :         }
     111           2 :         else if (osName == ZarrV3CodecPcodec::NAME)
     112             :         {
     113             : #ifdef HAVE_PCODEC
     114             :             constexpr bool bHasPcodec = true;
     115             : #else
     116           0 :             constexpr bool bHasPcodec = false;
     117             : #endif
     118             :             if constexpr (bHasPcodec)
     119             :             {
     120             :                 poCodec = std::make_unique<ZarrV3CodecPcodec>();
     121             :             }
     122             :             else
     123             :             {
     124           0 :                 CPLError(
     125             :                     CE_Failure, CPLE_NotSupported,
     126             :                     "%s: Codec '%s' is supported by GDAL, but not in this "
     127             :                     "particular build. Requires building GDAL with "
     128             :                     "-DGDAL_USE_PCODEC=ON and with a Rust toolchain available",
     129             :                     osArrayName.c_str(), osName.c_str());
     130           0 :                 return false;
     131             :             }
     132             :         }
     133             :         else
     134             :         {
     135           2 :             CPLError(CE_Failure, CPLE_NotSupported, "%s: Unsupported codec: %s",
     136             :                      osArrayName.c_str(), osName.c_str());
     137           2 :             return false;
     138             :         }
     139             : 
     140        4323 :         if (poCodec->GetInputType() == ZarrV3Codec::IOType::ARRAY)
     141             :         {
     142        2872 :             if (eLastType == ZarrV3Codec::IOType::BYTES)
     143             :             {
     144           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     145             :                          "Cannot chain codec %s with %s",
     146           0 :                          poCodec->GetName().c_str(), osLastCodec.c_str());
     147           0 :                 return false;
     148             :             }
     149             :         }
     150             :         else
     151             :         {
     152        1451 :             InsertImplicitEndianCodecIfNeeded();
     153             :         }
     154             : 
     155        4323 :         ZarrArrayMetadata oStepOutputArrayMetadata;
     156        4323 :         if (osName == ZarrV3CodecShardingIndexed::NAME)
     157             :         {
     158         710 :             anBlockSizesBeforeSharding = oInputArrayMetadata.anBlockSizes;
     159             :         }
     160        8646 :         if (!poCodec->InitFromConfiguration(
     161             :                 osArrayName, oCodec["configuration"], oInputArrayMetadata,
     162             :                 oStepOutputArrayMetadata,
     163        4323 :                 /* bEmitWarnings = */ true))
     164             :         {
     165          18 :             return false;
     166             :         }
     167        4305 :         oInputArrayMetadata = std::move(oStepOutputArrayMetadata);
     168        4305 :         eLastType = poCodec->GetOutputType();
     169        4305 :         osLastCodec = poCodec->GetName();
     170             : 
     171        4305 :         if (!poCodec->IsNoOp())
     172        2293 :             m_apoCodecs.emplace_back(std::move(poCodec));
     173             :     }
     174             : 
     175        2772 :     if (bShardingFound)
     176             :     {
     177         692 :         m_bPartialDecodingPossible =
     178         692 :             (m_apoCodecs.back()->GetName() == ZarrV3CodecShardingIndexed::NAME);
     179         692 :         if (!m_bPartialDecodingPossible)
     180             :         {
     181         139 :             m_bPartialDecodingPossible = false;
     182             :             // This is not an implementation limitation, but the result of a
     183             :             // badly thought dataset. Zarr-Python also emits a similar warning.
     184         139 :             CPLError(
     185             :                 CE_Warning, CPLE_AppDefined,
     186             :                 "Sharding codec found, but not in last position. Consequently "
     187             :                 "partial shard decoding will not be possible");
     188             :             oInputArrayMetadata.anBlockSizes =
     189         139 :                 std::move(anBlockSizesBeforeSharding);
     190             :         }
     191             :     }
     192             : 
     193        2772 :     InsertImplicitEndianCodecIfNeeded();
     194             : 
     195        2772 :     m_oCodecArray = oCodecs.Clone();
     196        2772 :     oOutputArrayMetadata = std::move(oInputArrayMetadata);
     197        2772 :     return true;
     198             : }
     199             : 
     200             : /************************************************************************/
     201             : /*                  ZarrV3CodecBytes::AllocateBuffer()                  */
     202             : /************************************************************************/
     203             : 
     204       44465 : bool ZarrV3CodecSequence::AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer,
     205             :                                          size_t nEltCount)
     206             : {
     207       44465 :     if (!m_apoCodecs.empty())
     208             :     {
     209       33032 :         const size_t nRawSize =
     210       33032 :             nEltCount * m_oInputArrayMetadata.oElt.nativeSize;
     211             :         // Grow the temporary buffer a bit beyond the uncompressed size
     212       33032 :         const size_t nMaxSize = nRawSize + nRawSize / 3 + 64;
     213             :         try
     214             :         {
     215       33032 :             m_abyTmp.resize(nMaxSize);
     216             :         }
     217           0 :         catch (const std::exception &e)
     218             :         {
     219           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     220           0 :             return false;
     221             :         }
     222       33032 :         m_abyTmp.resize(nRawSize);
     223             : 
     224             :         // Grow the input/output buffer too if we have several steps
     225       33032 :         if (m_apoCodecs.size() >= 2 && abyBuffer.capacity() < nMaxSize)
     226             :         {
     227         223 :             const size_t nSize = abyBuffer.size();
     228             :             try
     229             :             {
     230         223 :                 abyBuffer.resize(nMaxSize);
     231             :             }
     232           0 :             catch (const std::exception &e)
     233             :             {
     234           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     235           0 :                 return false;
     236             :             }
     237         223 :             abyBuffer.resize(nSize);
     238             :         }
     239             :     }
     240       44465 :     return true;
     241             : }
     242             : 
     243             : /************************************************************************/
     244             : /*                    ZarrV3CodecSequence::Encode()                     */
     245             : /************************************************************************/
     246             : 
     247       15239 : bool ZarrV3CodecSequence::Encode(ZarrByteVectorQuickResize &abyBuffer)
     248             : {
     249       15239 :     if (!AllocateBuffer(abyBuffer,
     250       15239 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     251           0 :         return false;
     252       24985 :     for (const auto &poCodec : m_apoCodecs)
     253             :     {
     254        9746 :         if (!poCodec->Encode(abyBuffer, m_abyTmp))
     255           0 :             return false;
     256        9746 :         std::swap(abyBuffer, m_abyTmp);
     257             :     }
     258       15239 :     return true;
     259             : }
     260             : 
     261             : /************************************************************************/
     262             : /*                    ZarrV3CodecSequence::Decode()                     */
     263             : /************************************************************************/
     264             : 
     265       28198 : bool ZarrV3CodecSequence::Decode(ZarrByteVectorQuickResize &abyBuffer)
     266             : {
     267       28198 :     if (!AllocateBuffer(abyBuffer,
     268       28198 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     269           0 :         return false;
     270       49795 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     271             :     {
     272       22491 :         const auto &poCodec = *iter;
     273       22491 :         if (!poCodec->Decode(abyBuffer, m_abyTmp))
     274         894 :             return false;
     275       21597 :         std::swap(abyBuffer, m_abyTmp);
     276             :     }
     277       27304 :     return true;
     278             : }
     279             : 
     280             : /************************************************************************/
     281             : /*                 ZarrV3CodecSequence::DecodePartial()                 */
     282             : /************************************************************************/
     283             : 
     284        1028 : bool ZarrV3CodecSequence::DecodePartial(VSIVirtualHandle *poFile,
     285             :                                         ZarrByteVectorQuickResize &abyBuffer,
     286             :                                         const std::vector<size_t> &anStartIdxIn,
     287             :                                         const std::vector<size_t> &anCountIn)
     288             : {
     289        1028 :     CPLAssert(anStartIdxIn.size() == m_oInputArrayMetadata.anBlockSizes.size());
     290        1028 :     CPLAssert(anStartIdxIn.size() == anCountIn.size());
     291             : 
     292        1028 :     if (!AllocateBuffer(abyBuffer, MultiplyElements(anCountIn)))
     293           0 :         return false;
     294             : 
     295             :     // anStartIdxIn and anCountIn are expressed in the shape *before* encoding
     296             :     // We need to apply the potential transpositions before submitting them
     297             :     // to the decoder of the Array->Bytes decoder
     298        2056 :     std::vector<size_t> anStartIdx(anStartIdxIn);
     299        2056 :     std::vector<size_t> anCount(anCountIn);
     300        2081 :     for (auto &poCodec : m_apoCodecs)
     301             :     {
     302        1053 :         poCodec->ChangeArrayShapeForward(anStartIdx, anCount);
     303             :     }
     304             : 
     305        1725 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     306             :     {
     307        1053 :         const auto &poCodec = *iter;
     308             : 
     309        2106 :         if (!poCodec->DecodePartial(poFile, abyBuffer, m_abyTmp, anStartIdx,
     310        1053 :                                     anCount))
     311         356 :             return false;
     312         697 :         std::swap(abyBuffer, m_abyTmp);
     313             :     }
     314         672 :     return true;
     315             : }
     316             : 
     317             : /************************************************************************/
     318             : /*              ZarrV3CodecSequence::BatchDecodePartial()               */
     319             : /************************************************************************/
     320             : 
     321        4031 : bool ZarrV3CodecSequence::BatchDecodePartial(
     322             :     VSIVirtualHandle *poFile, const char *pszFilename,
     323             :     const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
     324             :         &anRequests,
     325             :     std::vector<ZarrByteVectorQuickResize> &aResults)
     326             : {
     327             :     // Only batch-decode when sharding is the sole codec. If other codecs
     328             :     // (e.g. transpose) precede it, indices and output need codec-specific
     329             :     // transformations that BatchDecodePartial does not handle.
     330        4031 :     if (m_apoCodecs.size() == 1)
     331             :     {
     332        4023 :         auto *poSharding = dynamic_cast<ZarrV3CodecShardingIndexed *>(
     333        8046 :             m_apoCodecs.back().get());
     334        4023 :         if (poSharding)
     335             :         {
     336        4023 :             return poSharding->BatchDecodePartial(poFile, pszFilename,
     337        4023 :                                                   anRequests, aResults);
     338             :         }
     339             :     }
     340             : 
     341             :     // Fallback: sequential DecodePartial for non-sharding codec chains
     342           8 :     aResults.resize(anRequests.size());
     343          32 :     for (size_t i = 0; i < anRequests.size(); ++i)
     344             :     {
     345          24 :         if (!DecodePartial(poFile, aResults[i], anRequests[i].first,
     346          24 :                            anRequests[i].second))
     347           0 :             return false;
     348             :     }
     349           8 :     return true;
     350             : }
     351             : 
     352             : /************************************************************************/
     353             : /*             ZarrV3CodecSequence::GetInnerMostBlockSize()             */
     354             : /************************************************************************/
     355             : 
     356        1364 : std::vector<size_t> ZarrV3CodecSequence::GetInnerMostBlockSize(
     357             :     const std::vector<size_t> &anOuterBlockSize) const
     358             : {
     359        1364 :     auto chunkSize = anOuterBlockSize;
     360        2418 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     361             :     {
     362        1054 :         const auto &poCodec = *iter;
     363        1555 :         if (m_bPartialDecodingPossible ||
     364         501 :             poCodec->GetName() != ZarrV3CodecShardingIndexed::NAME)
     365             :         {
     366         915 :             chunkSize = poCodec->GetInnerMostBlockSize(chunkSize);
     367             :         }
     368             :     }
     369        1364 :     return chunkSize;
     370             : }

Generated by: LCOV version 1.14