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: 138 156 88.5 %
Date: 2026-03-05 10:33:42 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        2665 : bool ZarrV3CodecSequence::InitFromJson(const CPLJSONObject &oCodecs,
      34             :                                        ZarrArrayMetadata &oOutputArrayMetadata)
      35             : {
      36        2665 :     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        5330 :     auto oCodecsArray = oCodecs.ToArray();
      42             : 
      43        5330 :     ZarrArrayMetadata oInputArrayMetadata = m_oInputArrayMetadata;
      44        2665 :     ZarrV3Codec::IOType eLastType = ZarrV3Codec::IOType::ARRAY;
      45        5330 :     std::string osLastCodec;
      46             : 
      47             :     const auto InsertImplicitEndianCodecIfNeeded =
      48        8087 :         [this, &oInputArrayMetadata, &eLastType, &osLastCodec]()
      49             :     {
      50        4041 :         CPL_IGNORE_RET_VAL(this);
      51        4041 :         if (eLastType == ZarrV3Codec::IOType::ARRAY &&
      52           1 :             oInputArrayMetadata.oElt.nativeSize > 1)
      53             :         {
      54           1 :             CPLError(CE_Warning, CPLE_AppDefined,
      55             :                      "'bytes' codec missing. Assuming little-endian storage, "
      56             :                      "but such tolerance may be removed in future versions");
      57           2 :             auto poEndianCodec = std::make_unique<ZarrV3CodecBytes>();
      58           2 :             ZarrArrayMetadata oTmpOutputArrayMetadata;
      59           2 :             poEndianCodec->InitFromConfiguration(
      60           2 :                 ZarrV3CodecBytes::GetConfiguration(true), oInputArrayMetadata,
      61             :                 oTmpOutputArrayMetadata, /* bEmitWarnings = */ true);
      62           1 :             oInputArrayMetadata = std::move(oTmpOutputArrayMetadata);
      63           1 :             eLastType = poEndianCodec->GetOutputType();
      64           1 :             osLastCodec = poEndianCodec->GetName();
      65             :             if constexpr (!CPL_IS_LSB)
      66             :             {
      67             :                 // Insert a little endian codec if we are on a big endian target
      68             :                 m_apoCodecs.emplace_back(std::move(poEndianCodec));
      69             :             }
      70             :         }
      71        4041 :     };
      72             : 
      73        2665 :     bool bShardingFound = false;
      74        5330 :     std::vector<size_t> anBlockSizesBeforeSharding;
      75        6788 :     for (const auto &oCodec : oCodecsArray)
      76             :     {
      77        4143 :         if (oCodec.GetType() != CPLJSONObject::Type::Object)
      78             :         {
      79           0 :             CPLError(CE_Failure, CPLE_AppDefined, "codecs[] is not an object");
      80          20 :             return false;
      81             :         }
      82        8286 :         const auto osName = oCodec["name"].ToString();
      83           0 :         std::unique_ptr<ZarrV3Codec> poCodec;
      84        4143 :         if (osName == ZarrV3CodecGZip::NAME)
      85          59 :             poCodec = std::make_unique<ZarrV3CodecGZip>();
      86        4084 :         else if (osName == ZarrV3CodecBlosc::NAME)
      87           4 :             poCodec = std::make_unique<ZarrV3CodecBlosc>();
      88        4080 :         else if (osName == ZarrV3CodecZstd::NAME)
      89         524 :             poCodec = std::make_unique<ZarrV3CodecZstd>();
      90        5138 :         else if (osName == ZarrV3CodecBytes::NAME ||
      91        1582 :                  osName == "endian" /* endian is the old name */)
      92        1974 :             poCodec = std::make_unique<ZarrV3CodecBytes>();
      93        1582 :         else if (osName == ZarrV3CodecTranspose::NAME)
      94          83 :             poCodec = std::make_unique<ZarrV3CodecTranspose>();
      95        1499 :         else if (osName == ZarrV3CodecCRC32C::NAME)
      96         809 :             poCodec = std::make_unique<ZarrV3CodecCRC32C>();
      97         690 :         else if (osName == ZarrV3CodecShardingIndexed::NAME)
      98             :         {
      99         688 :             bShardingFound = true;
     100         688 :             poCodec = std::make_unique<ZarrV3CodecShardingIndexed>();
     101             :         }
     102             :         else
     103             :         {
     104           2 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported codec: %s",
     105             :                      osName.c_str());
     106           2 :             return false;
     107             :         }
     108             : 
     109        4141 :         if (poCodec->GetInputType() == ZarrV3Codec::IOType::ARRAY)
     110             :         {
     111        2745 :             if (eLastType == ZarrV3Codec::IOType::BYTES)
     112             :             {
     113           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     114             :                          "Cannot chain codec %s with %s",
     115           0 :                          poCodec->GetName().c_str(), osLastCodec.c_str());
     116           0 :                 return false;
     117             :             }
     118             :         }
     119             :         else
     120             :         {
     121        1396 :             InsertImplicitEndianCodecIfNeeded();
     122             :         }
     123             : 
     124        4141 :         ZarrArrayMetadata oStepOutputArrayMetadata;
     125        4141 :         if (osName == ZarrV3CodecShardingIndexed::NAME)
     126             :         {
     127         688 :             anBlockSizesBeforeSharding = oInputArrayMetadata.anBlockSizes;
     128             :         }
     129        8282 :         if (!poCodec->InitFromConfiguration(oCodec["configuration"],
     130             :                                             oInputArrayMetadata,
     131             :                                             oStepOutputArrayMetadata,
     132        4141 :                                             /* bEmitWarnings = */ true))
     133             :         {
     134          18 :             return false;
     135             :         }
     136        4123 :         oInputArrayMetadata = std::move(oStepOutputArrayMetadata);
     137        4123 :         eLastType = poCodec->GetOutputType();
     138        4123 :         osLastCodec = poCodec->GetName();
     139             : 
     140        4123 :         if (!poCodec->IsNoOp())
     141        2202 :             m_apoCodecs.emplace_back(std::move(poCodec));
     142             :     }
     143             : 
     144        2645 :     if (bShardingFound)
     145             :     {
     146         670 :         m_bPartialDecodingPossible =
     147         670 :             (m_apoCodecs.back()->GetName() == ZarrV3CodecShardingIndexed::NAME);
     148         670 :         if (!m_bPartialDecodingPossible)
     149             :         {
     150         138 :             m_bPartialDecodingPossible = false;
     151             :             // This is not an implementation limitation, but the result of a
     152             :             // badly thought dataset. Zarr-Python also emits a similar warning.
     153         138 :             CPLError(
     154             :                 CE_Warning, CPLE_AppDefined,
     155             :                 "Sharding codec found, but not in last position. Consequently "
     156             :                 "partial shard decoding will not be possible");
     157             :             oInputArrayMetadata.anBlockSizes =
     158         138 :                 std::move(anBlockSizesBeforeSharding);
     159             :         }
     160             :     }
     161             : 
     162        2645 :     InsertImplicitEndianCodecIfNeeded();
     163             : 
     164        2645 :     m_oCodecArray = oCodecs.Clone();
     165        2645 :     oOutputArrayMetadata = std::move(oInputArrayMetadata);
     166        2645 :     return true;
     167             : }
     168             : 
     169             : /************************************************************************/
     170             : /*                  ZarrV3CodecBytes::AllocateBuffer()                  */
     171             : /************************************************************************/
     172             : 
     173       44396 : bool ZarrV3CodecSequence::AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer,
     174             :                                          size_t nEltCount)
     175             : {
     176       44396 :     if (!m_apoCodecs.empty())
     177             :     {
     178       33019 :         const size_t nRawSize =
     179       33019 :             nEltCount * m_oInputArrayMetadata.oElt.nativeSize;
     180             :         // Grow the temporary buffer a bit beyond the uncompressed size
     181       33019 :         const size_t nMaxSize = nRawSize + nRawSize / 3 + 64;
     182             :         try
     183             :         {
     184       33019 :             m_abyTmp.resize(nMaxSize);
     185             :         }
     186           0 :         catch (const std::exception &e)
     187             :         {
     188           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     189           0 :             return false;
     190             :         }
     191       33019 :         m_abyTmp.resize(nRawSize);
     192             : 
     193             :         // Grow the input/output buffer too if we have several steps
     194       33019 :         if (m_apoCodecs.size() >= 2 && abyBuffer.capacity() < nMaxSize)
     195             :         {
     196         219 :             const size_t nSize = abyBuffer.size();
     197             :             try
     198             :             {
     199         219 :                 abyBuffer.resize(nMaxSize);
     200             :             }
     201           0 :             catch (const std::exception &e)
     202             :             {
     203           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     204           0 :                 return false;
     205             :             }
     206         219 :             abyBuffer.resize(nSize);
     207             :         }
     208             :     }
     209       44396 :     return true;
     210             : }
     211             : 
     212             : /************************************************************************/
     213             : /*                    ZarrV3CodecSequence::Encode()                     */
     214             : /************************************************************************/
     215             : 
     216       15232 : bool ZarrV3CodecSequence::Encode(ZarrByteVectorQuickResize &abyBuffer)
     217             : {
     218       15232 :     if (!AllocateBuffer(abyBuffer,
     219       15232 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     220           0 :         return false;
     221       24973 :     for (const auto &poCodec : m_apoCodecs)
     222             :     {
     223        9741 :         if (!poCodec->Encode(abyBuffer, m_abyTmp))
     224           0 :             return false;
     225        9741 :         std::swap(abyBuffer, m_abyTmp);
     226             :     }
     227       15232 :     return true;
     228             : }
     229             : 
     230             : /************************************************************************/
     231             : /*                    ZarrV3CodecSequence::Decode()                     */
     232             : /************************************************************************/
     233             : 
     234       28137 : bool ZarrV3CodecSequence::Decode(ZarrByteVectorQuickResize &abyBuffer)
     235             : {
     236       28137 :     if (!AllocateBuffer(abyBuffer,
     237       28137 :                         MultiplyElements(m_oInputArrayMetadata.anBlockSizes)))
     238           0 :         return false;
     239       49724 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     240             :     {
     241       22480 :         const auto &poCodec = *iter;
     242       22480 :         if (!poCodec->Decode(abyBuffer, m_abyTmp))
     243         893 :             return false;
     244       21587 :         std::swap(abyBuffer, m_abyTmp);
     245             :     }
     246       27244 :     return true;
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                 ZarrV3CodecSequence::DecodePartial()                 */
     251             : /************************************************************************/
     252             : 
     253        1027 : bool ZarrV3CodecSequence::DecodePartial(VSIVirtualHandle *poFile,
     254             :                                         ZarrByteVectorQuickResize &abyBuffer,
     255             :                                         const std::vector<size_t> &anStartIdxIn,
     256             :                                         const std::vector<size_t> &anCountIn)
     257             : {
     258        1027 :     CPLAssert(anStartIdxIn.size() == m_oInputArrayMetadata.anBlockSizes.size());
     259        1027 :     CPLAssert(anStartIdxIn.size() == anCountIn.size());
     260             : 
     261        1027 :     if (!AllocateBuffer(abyBuffer, MultiplyElements(anCountIn)))
     262           0 :         return false;
     263             : 
     264             :     // anStartIdxIn and anCountIn are expressed in the shape *before* encoding
     265             :     // We need to apply the potential transpositions before submitting them
     266             :     // to the decoder of the Array->Bytes decoder
     267        2054 :     std::vector<size_t> anStartIdx(anStartIdxIn);
     268        2054 :     std::vector<size_t> anCount(anCountIn);
     269        2079 :     for (auto &poCodec : m_apoCodecs)
     270             :     {
     271        1052 :         poCodec->ChangeArrayShapeForward(anStartIdx, anCount);
     272             :     }
     273             : 
     274        1723 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     275             :     {
     276        1052 :         const auto &poCodec = *iter;
     277             : 
     278        2104 :         if (!poCodec->DecodePartial(poFile, abyBuffer, m_abyTmp, anStartIdx,
     279        1052 :                                     anCount))
     280         356 :             return false;
     281         696 :         std::swap(abyBuffer, m_abyTmp);
     282             :     }
     283         671 :     return true;
     284             : }
     285             : 
     286             : /************************************************************************/
     287             : /*              ZarrV3CodecSequence::BatchDecodePartial()               */
     288             : /************************************************************************/
     289             : 
     290        4031 : bool ZarrV3CodecSequence::BatchDecodePartial(
     291             :     VSIVirtualHandle *poFile, const char *pszFilename,
     292             :     const std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
     293             :         &anRequests,
     294             :     std::vector<ZarrByteVectorQuickResize> &aResults)
     295             : {
     296             :     // Only batch-decode when sharding is the sole codec. If other codecs
     297             :     // (e.g. transpose) precede it, indices and output need codec-specific
     298             :     // transformations that BatchDecodePartial does not handle.
     299        4031 :     if (m_apoCodecs.size() == 1)
     300             :     {
     301        4023 :         auto *poSharding = dynamic_cast<ZarrV3CodecShardingIndexed *>(
     302        8046 :             m_apoCodecs.back().get());
     303        4023 :         if (poSharding)
     304             :         {
     305        4023 :             return poSharding->BatchDecodePartial(poFile, pszFilename,
     306        4023 :                                                   anRequests, aResults);
     307             :         }
     308             :     }
     309             : 
     310             :     // Fallback: sequential DecodePartial for non-sharding codec chains
     311           8 :     aResults.resize(anRequests.size());
     312          32 :     for (size_t i = 0; i < anRequests.size(); ++i)
     313             :     {
     314          24 :         if (!DecodePartial(poFile, aResults[i], anRequests[i].first,
     315          24 :                            anRequests[i].second))
     316           0 :             return false;
     317             :     }
     318           8 :     return true;
     319             : }
     320             : 
     321             : /************************************************************************/
     322             : /*             ZarrV3CodecSequence::GetInnerMostBlockSize()             */
     323             : /************************************************************************/
     324             : 
     325        1281 : std::vector<size_t> ZarrV3CodecSequence::GetInnerMostBlockSize(
     326             :     const std::vector<size_t> &anOuterBlockSize) const
     327             : {
     328        1281 :     auto chunkSize = anOuterBlockSize;
     329        2286 :     for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
     330             :     {
     331        1005 :         const auto &poCodec = *iter;
     332        1478 :         if (m_bPartialDecodingPossible ||
     333         473 :             poCodec->GetName() != ZarrV3CodecShardingIndexed::NAME)
     334             :         {
     335         867 :             chunkSize = poCodec->GetInnerMostBlockSize(chunkSize);
     336             :         }
     337             :     }
     338        1281 :     return chunkSize;
     339             : }

Generated by: LCOV version 1.14