In this short course, we use the simple numcodecs [1] API for compression. In numcodecs, compressors are called Codecs and only have two important methods: codec.encode(data) and codec.decode(encoded). numcodecs works great for the simple case of compressing and decompressing in-memory numpy arrays in one go, though it can also be used with chunked data.
This product includes software produced by UChicago Argonne, LLC under Contract No. DE-AC02-06CH11357 with the Department of Energy.
import numcodecs
import numpy as npConfiguration¶
Each codec is a class that inherits from numcodecs.abc.Codec. Codecs are configured upon instantiation using keyword arguments. For example, here we create an instance of the lossless Zlib compressor:
codec = numcodecs.Zlib(level=6)
codecZlib(level=6)We can obtain the full configuration of the codec in JSON format using codec.get_config():
config = codec.get_config()
config{'id': 'zlib', 'level': 6}And then use numcodecs.get_codec(config) to recreate the codec from its configuration:
numcodecs.get_codec(config)Zlib(level=6)Compression¶
We use the codec.encode(data) function to encode the data:
data = np.linspace(-10, 10, 41)
dataarray([-10. , -9.5, -9. , -8.5, -8. , -7.5, -7. , -6.5, -6. ,
-5.5, -5. , -4.5, -4. , -3.5, -3. , -2.5, -2. , -1.5,
-1. , -0.5, 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. ,
3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5,
8. , 8.5, 9. , 9.5, 10. ])encoded = codec.encode(data)
encodedb'x\x9c5\xcc;\x0e@\x00\x14D\xd1W(\x14\n\x11\x11\x11\x11\xbf}\xb0s\x96b\tJ\xa5\xe0\xb8\xcd)&\x99\x88\xa7y\x7f\x89\x89#\x07\xf6\xec\xd8\xb2a\xcd\x8a%\x0b\xe6\xcc\x982a\xf0\xda>O\x1e\xfc;\x16;/\xc6\xea\x8f)3\xe6,X\xb2b\xcd\x86-;\xf6\x1c8r\xe2\xbc\xde>E\x1c\x7f'Decompression¶
We use the codec.decode(encoded) function to decode the data:
decoded = codec.decode(encoded)
decodedb'\x00\x00\x00\x00\x00\x00$\xc0\x00\x00\x00\x00\x00\x00#\xc0\x00\x00\x00\x00\x00\x00"\xc0\x00\x00\x00\x00\x00\x00!\xc0\x00\x00\x00\x00\x00\x00 \xc0\x00\x00\x00\x00\x00\x00\x1e\xc0\x00\x00\x00\x00\x00\x00\x1c\xc0\x00\x00\x00\x00\x00\x00\x1a\xc0\x00\x00\x00\x00\x00\x00\x18\xc0\x00\x00\x00\x00\x00\x00\x16\xc0\x00\x00\x00\x00\x00\x00\x14\xc0\x00\x00\x00\x00\x00\x00\x12\xc0\x00\x00\x00\x00\x00\x00\x10\xc0\x00\x00\x00\x00\x00\x00\x0c\xc0\x00\x00\x00\x00\x00\x00\x08\xc0\x00\x00\x00\x00\x00\x00\x04\xc0\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\xf8\xbf\x00\x00\x00\x00\x00\x00\xf0\xbf\x00\x00\x00\x00\x00\x00\xe0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf8?\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x04@\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x0c@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x12@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x16@\x00\x00\x00\x00\x00\x00\x18@\x00\x00\x00\x00\x00\x00\x1a@\x00\x00\x00\x00\x00\x00\x1c@\x00\x00\x00\x00\x00\x00\x1e@\x00\x00\x00\x00\x00\x00 @\x00\x00\x00\x00\x00\x00!@\x00\x00\x00\x00\x00\x00"@\x00\x00\x00\x00\x00\x00#@\x00\x00\x00\x00\x00\x00$@'Some codecs, like Zlib, need to know the type and shape of the original data to correctly decompress it. For these cases, it is also possible to decode directly into an existing array using codec.decode(encoded, out=decoded):
decoded = codec.decode(encoded, out=np.empty_like(data))
decodedarray([-10. , -9.5, -9. , -8.5, -8. , -7.5, -7. , -6.5, -6. ,
-5.5, -5. , -4.5, -4. , -3.5, -3. , -2.5, -2. , -1.5,
-1. , -0.5, 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. ,
3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5,
8. , 8.5, 9. , 9.5, 10. ])Building up more complex compressors¶
It is often useful to combine several codecs, e.g. to use one to transform the data and one to compress it. Meta-compressors are such flexible combinators, and some are provided in the numcodecs-combinators [2] package:
from numcodecs_combinators.stack import CodecStackCodecStack(a, b, c) combines codecs a, b, and c into a linear stack. During encoding, the data is first encoded by a, then b, then c. During decoding, the encoded data is first decoded by c, then by b, then a. The CodecStack supports an arbitrary number of sub-codecs.
The CodecStack also provides two useful helper methods:
CodecStack.encode_decode(data)encodes and then decodes the data array in one go.CodecStack.encode_decode_data_array(da)encodes and then decodes anxarray.DataArrayand preserves all metadata. If the data is chunked, the sub-codecs are applied independently to each data chunk.
from numcodecs_combinators.framed import FramedCodecStackThe FramedCodecStack is a specialised CodecStack that remembers the type and shape of the data during encoding, encodes it alongside the encoded data, and then uses this information during decoding to provide the out parameter to each codec.decode. The FramedCodecStack is thus useful when trying to stack codecs that require this information to decode correctly, or to ensure that any codec produces its encoded data as a byte string.
from numcodecs_combinators.best import PickBestCodecFinally, the PickBestCodec(a, b, c) combinator tries to encode the data with codecs a, b, or c, chooses the one with the greatest reduction in byte size, and outputs its encoded data alongside the index of the codec that was picked. During decoding, the picked sub-codec is used to decode the data. The PickBestCodec supports an arbitrary number of sub-codecs.
Portable scientific compressors with numcodecs-wasm¶
In this short course, we utilize the numcodecs-wasm [3] project, which provides several scientific compressors in a numcodecs-compatible form. The compressors are compiled to WebAssembly to ensure that compression and decompression can be performed reproducibly across any CPU machine [4]. Each compressor is published as its own Python package on PyPi, e.g. numcodecs-wasm-sz3 for the SZ3 compressor.
The complete list of currently supported compressors is:
numcodecs_wasm_bit_round: bit roundingnumcodecs_wasm_ebcc: EBCCnumcodecs_wasm_fourier_network: fourier feature neural networknumcodecs_wasm_jpeg2000: JPEG 2000numcodecs_wasm_linear_quantize: linear quantizationnumcodecs_wasm_pco: pcodecnumcodecs_wasm_pressio: Pressio (work-in-progress)numcodecs_wasm_qpet_sperr: QPET-SPERRnumcodecs_wasm_random_projection: random projectionnumcodecs_wasm_reinterpret: binary reinterpretnumcodecs_wasm_round: roundingnumcodecs_wasm_sperr: SPERRnumcodecs_wasm_stochastic_rounding: stochastic roundingnumcodecs_wasm_swizzle_reshape: array axis swizzle and reshapenumcodecs_wasm_sz3: SZ3numcodecs_wasm_tthresh: Tthreshnumcodecs_wasm_uniform_noise: add uniform noisenumcodecs_wasm_zfp: ZFP-ROUNDnumcodecs_wasm_zfp_classic: ZFPnumcodecs_wasm_zlib: Zlibnumcodecs_wasm_zstd: Zstdandard
While numcodecs-wasm provides reproducibility and makes it easy to quickly test out different compressors, it has reduced performance as compressors are run single-threaded in a WebAssembly engine and not as native code. Furthermore, the codecs run inside a sandboxed 32 bit memory space and can typically only compress arrays up to 1-2 GB in size. To compress larger data with numcodecs-wasm, please chunk the data using xarray and use CodecStack.encode_decode_data_array(da).
Reichelt, T., Tyree, J., Klöwer, M., Dueben, P., Lawrence, B. N., Baker, A. H., Faghih-Naini, S., Hoefler, T., & Stier, P. (2026). ClimateBenchPress (v1.0): A Benchmark for Lossy Compression of Climate Data. EGUsphere [Preprint]. Available from: Reichelt et al. (2026).
- Reichelt, T., Tyree, J., Klöwer, M., Dueben, P., Lawrence, B. N., Baker, A. H., Faghih-Naini, S., Hoefler, T., & Stier, P. (2026). ClimateBenchPress (v1.0): A Benchmark for Lossy Compression of Climate Data. 10.5194/egusphere-2026-60