Official implementation of JDEC from CVPR 2024: JDEC: JPEG Decoding via Enhanced Continuous Cosine Coefficients (arXiv, Project Page).
JDEC is a neural JPEG decoder that consumes JPEG DCT coefficients + quantization tables, not pixel-space images. It builds a continuous cosine representation and reconstructs RGB images with high PSNR and reduced artifacts.
- Key Ideas
- Architecture Overview (models/)
- Repository Structure
- Environment & Dependencies
- Installation
- Data Preparation
- Training Workflow
- Testing / Evaluation Workflow
- Project Workflow Deep Dive
- Configuration Reference
- Model & Dataset Registration
- Customization Guide
- How-to Recipes
- FAQ / Troubleshooting
- Known Limitations & Inconsistencies
- Potential Next Steps
- Acknowledgements
- BibTeX
- Inputs are JPEG-domain: JDEC uses compressed DCT coefficients and quantization maps, not RGB pixels.
- Continuous cosine basis: Features are mapped to a learnable cosine basis over block grids, enabling continuous-frequency reconstruction.
- Two-stage decoding:
- Encoder (SwinV2-based) embeds grouped DCT spectra.
- Decoder (MLP/1x1 conv) maps basis features to RGB.
Visual workflow (default training path):
JPEG DCT coeffs (Y, CbCr) + quant tables
│
▼
SwinV2 DCT encoder (swinv2_group_embedded)
│
▼
JDEC frequency/basis mixing + de-quantization
│
▼
MLP 1x1-conv decoder (mlp_1dconv) → RGB reconstruction
| File | Registry name(s) | What it is | Where/how it is used | Default? | Dependencies & interoperability |
|---|---|---|---|---|---|
models/JDEC.py |
IPEC-decoder_dctform-rgb-share-size4 (core JDEC model) |
Full JPEG-domain decoder. It consumes DCT coeffs + chroma + quant maps, builds cosine basis features, and produces RGB via a learned decoder. | Constructed via models.make inside training/eval when model.name points at the JDEC architecture. It calls a DCT encoder (encoder_spec) and a decoder (decoder_spec) to form the end-to-end JDEC pipeline. |
Yes (training config uses JDEC with SwinV2 encoder + MLP decoder). | Depends on an encoder (typically SwinV2) and a decoder (MLP/1x1 conv). The decoder is interchangeable as long as it matches in_dim/out_dim. Encoder/decoder selections are independent configuration knobs. |
models/swinirv2.py |
swinv2_group_embedded |
Swin Transformer V2 encoder adapted for DCT-domain inputs. It embeds grouped Y/CbCr blocks and outputs token features for JDEC. | Used as the default encoder in configs/train_JDEC.yaml and in test.py. The JDEC model instantiates it through encoder_spec. |
Yes (default encoder). | Uses DCT patch/subblock utilities from plainvit.py to combine/decompose 8×8 blocks. Interchangeable with any encoder that matches JDEC’s expected feature layout (encoder_spec/out_dim). |
models/mlp.py |
mlp, mlp_1dconv |
Lightweight decoders. mlp is a linear MLP on flattened tokens; mlp_1dconv is a 1×1 Conv MLP on feature maps. |
Used as the decoder head via decoder_spec inside JDEC. mlp_1dconv is the default in configs/train_JDEC.yaml. |
Yes (mlp_1dconv default). |
mlp and mlp_1dconv are interchangeable if the input tensor layout matches (flattened tokens vs. feature maps). Both map feature channels to RGB (out_dim=3). |
models/plainvit.py |
(no registry entry) | Patch embedding + positional encoding utilities and DCT subblock conversion helpers for ViT-like models. | Not directly instantiated via models.make, but its subblock utilities are imported and reused in swinirv2.py. It also supports building plain ViT variants for RGB/DCT if used elsewhere. |
No (utility + optional alternative). | Provides shared DCT block conversion logic (combine/decompose) used by SwinV2’s DCT patching. Functionality is analogous to DCT embedding in SwinV2; can be used to swap in a plain ViT encoder if wired into models.make. |
models/models.py |
(registry + factory) | Registry + make() factory for constructing models by name/spec. Handles saved state loading. |
Used by train.py and test.py to instantiate the JDEC model and its submodules from YAML or checkpoint specs. |
Yes (core construction path). | Defines the extension point for adding new architectures. JDEC depends on it to build encoder/decoder by registry name. |
models/__init__.py |
(module exports) | Registers model submodules so models.make sees all model classes. |
Imported at startup so that @register decorators execute and populate the registry. |
Yes (implicit). | Ensures all registered architectures are visible to the factory; without it, configs referencing registry names would fail. |
.
├── configs/ # YAML training configs
├── datasets/ # Dataset definitions + wrappers
├── dct_manip/ # Custom libjpeg handler (needs compilation)
├── models/ # JDEC model + encoder/decoder registries
├── utils/ # DCT ops, transforms, helpers
├── train.py # Training entrypoint
├── test.py # Evaluation entrypoint
├── requirements.txt # Pinned pip dependencies
└── environment.yaml # Conda environment (recommended)
Key modules:
- datasets/:
image_folder_paired.pyandwrappers_jpeg.pydefine JPEG-coefficient datasets and input/GT wrappers. - models/:
JDEC.py(core model),swinirv2.py(encoder),mlp.py(decoder). - utils_.py: training utilities, metrics (PSNR/PSNRB/SSIM), logging.
This project was developed on Ubuntu 20.04 with Python 3.6, PyTorch 1.10, and CUDA 11.3. See:
environment.yamlfor the full conda environment.requirements.txtfor a minimal pip list.
Major dependencies:
- PyTorch, torchvision
- timm, einops
- opencv-python, Pillow
- jpegio, dct-manip (JPEG coefficient I/O)
- tensorboardX, tqdm
conda env create --file environment.yaml
conda activate jdecdct_manip is a modified libjpeg handler required for DCT coefficient I/O:
- Open
dct_manip/setup.pyand update:include_dirsandlibrary_dirsextra_objects(path tolibjpeg.so)headers(path tojpeglib.h)
- Build and install:
cd dct_manip
pip install .The training/validation layout follows FBCNN with paired JPEGs at multiple qualities and PNG ground-truth.
Expected layout (quality-level folders):
jpeg_removal
├── train_paired
│ ├── train_10
│ │ ├── 0001.jpg
│ │ ├── 0002.jpg
│ │ └── ...
│ ├── train_20
│ │ ├── 0001.jpg
│ │ └── ...
│ ├── train_30
│ │ └── ...
│ ├── ...
│ └── train_100
│ └── ...
└── train
├── 0001.png
└── ...
Notes:
- Each
train_<quality>folder contains JPEG images (.jpg/.jpeg). GT images undertrain/are PNGs (.png). The loader reads all files in each folder, so keep only images there. - Filenames (basenames) must match across qualities and GT (e.g.,
train_paired/train_10/0001.jpg↔train/0001.png) so that sorted ordering pairs the correct inputs/targets.
Validation uses a similar structure (single fixed quality):
valid_paired
├── valid_10
│ ├── 0001.jpg
│ └── ...
└── valid
├── 0001.png
└── ...
Note: Training randomly samples from JPEG qualities
[10, 20, ..., 100]each iteration.
python train.py --config configs/train_JDEC.yaml --gpu 0-
Dataset setup
train-paired-imagesetreads JPEGs at multiple qualities + GT PNGs.JDEC-decoder_toimage_rgbwrapper:- Loads DCT coeffs + quant tables via
dct_manip.read_coefficients. - Crops aligned JPEG blocks and GT patches.
- Normalizes DCT values to
[-1, 1].
- Loads DCT coeffs + quant tables via
-
Model forward pass
- Encoder:
swinv2_group_embedded(SwinV2-based) - Decoder:
mlp_1dconv(1x1 conv MLP) - Loss: L1 between predicted RGB and GT RGB.
- Encoder:
-
Outputs
- Checkpoints saved in
./save/<config_name>/:epoch-last.pthevery epochepoch-<N>.pthforepoch_saveepoch-best.pthbased on validation PSNR
- Tensorboard logs in
./save/<config_name>/tensorboard/
- Checkpoints saved in
Set resume in YAML to the checkpoint:
resume: ./save/_train_JDEC/epoch-last.pthtest.py evaluates PSNR/PSNRB/SSIM on benchmark datasets (LIVE1, BSDS500, ICB).
python test.py- Set dataset and paths:
setname = 'LIVE1' data_path = './PATH_TO_LIVE1' model_path = './PATH_TO_MODEL'
- By default the script evaluates at JPEG quality
q=30.
- Prints averaged PSNR / PSNRB / SSIM.
- Saves decoded images under
./bin/<DATASET>/<QUALITY>/whensave=True.
This section follows the actual code paths to document invariants, data flow, and failure modes.
- JPEG I/O (DCT domain)
dct_manip.read_coefficientsloads each JPEG and returns:- Quantization tables (
q_y,q_cbcr) - Luma (Y) and chroma (CbCr) DCT coefficient blocks
- Quantization tables (
- Data wrapping
JDEC-decoder_toimage_rgb:- Randomly crops a block-aligned patch in DCT space and the corresponding RGB GT.
- Dequantizes DCT by multiplying coefficients with the quantization tables.
- Clamps coefficients to
[-1024, 1016], then normalizes to[-1, 1]. - Converts GT from BGR to RGB and shifts to
[-0.5, 0.5].
- Model forward
model(dct_y, dct_cbcr, q_map)predicts RGB in the same shifted range. - Loss / metrics
- Training:
L1(pred_rgb, gt_rgb) - Validation: PSNR on the normalized/shifted tensors.
- Training:
- Config loading:
train.pyreads YAML and setsCUDA_VISIBLE_DEVICES. - Dataset + wrapper:
train-paired-imagesetselects a random JPEG quality for each sample.JDEC-decoder_toimage_rgbperforms block-aligned cropping and normalization.
- DataLoader:
shuffle=Truefor train,num_workers=8,pin_memory=True.
- Forward pass:
- Inputs:
inp(Y DCT),chroma(CbCr DCT),dqt(quant tables). - Output: predicted RGB patch in shifted range.
- Inputs:
- Optimization:
- L1 loss; optimizer from
config.optimizer. - Optional
MultiStepLRscheduler.
- L1 loss; optimizer from
- Checkpointing:
epoch-last.pthevery epoch.epoch-<N>.ptheveryepoch_save.epoch-best.pthon best validation PSNR.
- Uses
valid-paired-dataset(fixed JPEG quality) with the same wrapper. - Computes PSNR with
utils_.calc_psnron normalized tensors. - Writes metrics to Tensorboard under
psnr/valid.
test.py is a standalone evaluation script designed for benchmark datasets.
- Dataset selection:
setnamechooses dataset and hard-coded path. - Image conditioning:
- Pads input via symmetric flips to a fixed size (
size = 112*10). - Writes a temporary JPEG (
./bin/temp_.jpg) with qualityq.
- Pads input via symmetric flips to a fixed size (
- DCT extraction:
- Reads DCT coefficients and quantization tables from the temp JPEG.
- Dequantizes, clamps, and normalizes to
[-1, 1].
- Inference:
- Runs model, shifts output by
+0.5, and crops to original size.
- Runs model, shifts output by
- Metrics + outputs:
- Calculates PSNR/PSNRB/SSIM vs. GT.
- Saves predicted PNGs if
save=True.
- Block alignment:
inp_sizeis in DCT block units (8x8), so images must be large enough for the sampled crop to be valid. - Range conventions:
- DCT coefficients are clamped to
[-1024, 1016]and normalized to[-1, 1]. - GT RGB patches are in
[0, 1]then shifted to[-0.5, 0.5].
- DCT coefficients are clamped to
- Data pairing: JPEGs at all qualities must align with GT PNG filenames.
- Path conventions:
test.pyconcatenates paths withdata_path + item, sodata_pathmust include a trailing/.
The training config (configs/train_JDEC.yaml) drives the full pipeline.
| Field | Purpose |
|---|---|
train_dataset |
Training dataset + wrapper + batch size |
val_dataset |
Validation dataset + wrapper + batch size |
model |
Model architecture spec + encoder/decoder |
optimizer |
Optimizer name + hyperparameters |
epoch_max |
Total training epochs |
multi_step_lr |
LR scheduler settings |
epoch_val |
Validation frequency (epochs) |
epoch_save |
Checkpoint frequency (epochs) |
resume |
Path to checkpoint to resume from |
train_dataset:
dataset:
name: train-paired-imageset
args:
root_path_inp: ./load/jpeg_removal/train_paired/train
root_path_gt: ./load/jpeg_removal/train
repeat: 5
cache: bin
wrapper:
name: JDEC-decoder_toimage_rgb
args:
inp_size: 14
batch_size: 16Key options
repeat: repeats the dataset (effective epoch length).cache:none | bin | in_memory(binary cache is recommended).inp_size: crop size in blocks (controls patch size).
model:
name: jdec
args:
encoder_spec:
name: swinv2_group_embedded
args:
use_subblock: True
emb_size: 256
num_heads: [8,8,8,8,8]
decoder_spec:
name: mlp_1dconv
args:
out_dim: 3
hidden_list: [512, 512, 512]
hidden_dim: 512Important: The model registry is keyed by @register(...) names. See Model & Dataset Registration.
The repo uses lightweight registries for extensibility:
- Registry:
models/models.py→modelsdict - Registration:
@register('<name>') - Factory:
models.make(spec)
Registered model names in code:
IPEC-decoder_dctform-rgb-share-size4→ JDEC core modelswinv2_group_embedded→ SwinV2 encodermlp/mlp_1dconv→ decoders
- Registry:
datasets/datasets.py - Registration:
@register('<name>') - Factory:
datasets.make(spec)
Registered dataset/wrapper names in code:
train-paired-imageset,valid-paired-datasetimage-folder-png,image-folder-embed-imageJDEC-decoder_toimage_rgb
# datasets/my_dataset.py
from datasets import register
from torch.utils.data import Dataset
@register('my-dataset')
class MyDataset(Dataset):
...Then update YAML:
dataset:
name: my-dataset
args: { ... }# models/my_model.py
from models import register
import torch.nn as nn
@register('my-model')
class MyModel(nn.Module):
...Then update YAML:
model:
name: my-model
args: { ... }JDEC is defined as:
encoder_spec: DCT encoder (SwinV2-based)decoder_spec: pixel-space decoder (MLP/Conv)
You can replace either spec with a registered model as long as input/output shapes are compatible.
- Prepare paired data with the
train_paired/train_<quality>andtrain/layout. - Build and install
dct_manip. - Update
configs/train_JDEC.yaml:train_dataset.dataset.args.root_path_inptrain_dataset.dataset.args.root_path_gtval_datasetpaths andinp_size
- Launch training:
python train.py --config configs/train_JDEC.yaml --gpu 0
- Edit
test.py:setname = 'LIVE1'(orBSDS500,ICB)data_path = './PATH_TO_LIVE1/'(must end with/)model_path = './save/<run>/epoch-best.pth'
- Run:
python test.py
- Increase speed / reduce memory: lower
train_dataset.batch_sizeorwrapper.args.inp_size. - Stability: ensure
inp_sizedoes not exceed the valid DCT crop area, or random cropping can fail.
Set cache in dataset args:
none: load from disk every time.bin: precompute.pklcaches (recommended for large datasets).in_memory: fastest but memory-heavy.
Q: “Model name jdec not found in registry.”
- The registry keys are derived from
@register(...)names. The core JDEC model is registered asIPEC-decoder_dctform-rgb-share-size4. If you see a missing key error, update the YAMLmodel.nameto match that key, or add a@register('jdec')alias in code.
Q: “dct_manip build fails.”
- Ensure libjpeg headers and shared objects are correctly referenced in
dct_manip/setup.py. You must provide validinclude_dirs,library_dirs, andextra_objectspaths.
Q: “CUDA out of memory.”
- Reduce
batch_size,inp_size, or encoder embedding sizes.
Q: “Validation PSNR is NaN/inf.”
- Verify input normalization in the wrapper and check for invalid coefficients (e.g., corrupted JPEGs).
Q: “Training finishes but tensorboard iterations look wrong.”
train.pyand validation use hard-coded dataset sizes for the iteration counters (used only for tensorboard x-axis). If your dataset is not size 3450 (train) or 10 (val), the iteration index will be off. This does not affect training, but plots may look compressed or stretched.
Q: “test.py can’t find images or errors with paths.”
data_pathis concatenated with filenames (noos.path.join), so it must end with a/.
Q: “test.py selects the wrong dataset path.”
- The script uses
isfor string comparison in dataset selection; change it to==if you modify the script and see unexpected behavior.
Q: “Cropping fails with a negative range error.”
- Ensure training images are large enough for the chosen
inp_sizeand that the image dimensions are multiples of 16 (because DCT blocks are 8x8 and chroma is subsampled).
Q: “Metrics don’t match the paper.”
- The validation loop computes PSNR on normalized tensors rather than on 8-bit RGB. For reproducibility vs. paper numbers, export predicted RGB to uint8 and re-evaluate externally.
- Hard-coded dataset sizes in training/validation for iteration counts (tensorboard only).
test.pypath concatenation relies on a trailing slash.test.pystring identity checks (is) can be brittle.- Single-quality evaluation:
test.pydefaults toq=30only. - Temporary JPEG file: evaluation rewrites
./bin/temp_.jpgfor each image.
- Reproducibility upgrades
- Add deterministic seeding and explicit RNG controls.
- Log DCT normalization ranges and quant tables per batch.
- Evaluation improvements
- Replace temporary JPEG file with in-memory encoding.
- Add multi-quality evaluation and aggregate curves (PSNR vs. quality).
- Model/algorithm extensions
- Add uncertainty-aware decoding for ambiguous high-frequency coefficients.
- Explore perceptual losses (LPIPS, DISTS) alongside L1.
- Engineering & scalability
- Replace hard-coded dataset sizes with
len(loader.dataset). - Add config-driven evaluation to eliminate code edits.
- Replace hard-coded dataset sizes with
- Dataset robustness
- Expand paired datasets with diverse cameras and chroma subsampling modes.
- Validate behavior under non-4:2:0 JPEGs.
This code is built on LIIF, LTE, SwinIR, and RGB No More. We thank the authors for sharing their codes.
@inproceedings{han2024jdec,
title={JDEC: JPEG Decoding via Enhanced Continuous Cosine Coefficients},
author={Han, Woo Kyoung and Im, Sunghoon and Kim, Jaedeok and Jin, Kyong Hwan},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
pages={2784--2793},
year={2024}
}