<!-- DO NOT EDIT. -->
<!-- THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. -->
<!-- TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: -->
<!-- "auto_examples/optimization/demo_multispectral_demosaicing.py" -->
<!-- LINE NUMBERS ARE GIVEN BELOW. -->

#### NOTE
New to DeepInverse? Get started with the basics with the
[5 minute quickstart tutorial](https://deepinv.org/auto_examples/basics/demo_quickstart.html.md#sphx-glr-auto-examples-basics-demo-quickstart-py)..

<a id="sphx-glr-auto-examples-optimization-demo-multispectral-demosaicing-py"></a>

# Multispectral demosaicing from raw sensor data

This example reconstructs a full-resolution multispectral image from raw snapshot
mosaiced measurements using various reconstruction algorithms.

Snapshot multispectral cameras cover the sensor with a mosaic of spectral filters (multispectral filter array),
so that each pixel records only one band. Demosaicing recovers every band at the full sensor resolution.

Demosaicing is a critical part of any image signal processing (ISP) pipeline, and using better algorithms results in higher quality images for downstream tasks.

We use raw oral-tissue data from the MODID dataset <sup>[1](#footcite-chand2024modid)</sup> acquired in-vivo for screening of oral diseases such as oral squamous cell carcinoma.
See [original data source](https://datadryad.org/dataset/doi:10.5061/dryad.nvx0k6dxw).
The data is unprocessed from an imec CMV2K-SSM4x4-VIS CMOS sensor covering 16 bands from 460 to 600 nm before any corrections.
The sensor uses a sequential 4x4 MSFA i.e. 16 bands.

<!-- GENERATED FROM PYTHON SOURCE LINES 18-25 -->
```Python
import torch
import deepinv as dinv

device = dinv.utils.get_device()
```

```none
Selected GPU 0 with 20523.4375 MiB free memory
```

<!-- GENERATED FROM PYTHON SOURCE LINES 27-32 -->

## Load and mosaic the raw data

We take sample 2 from MODID, hosted on the DeepInverse HuggingFace repository.
You can explore further samples yourself from the [original data source](https://datadryad.org/dataset/doi:10.5061/dryad.nvx0k6dxw) (licensed CC0 1.0).
The file is a raw stream which we reshape to the pixel array of shape 1088x2048 and crop a 768x768 region of interest.

<!-- GENERATED FROM PYTHON SOURCE LINES 32-45 -->
```Python
H, W = 1088, 2048
TILE = 4
CROP = 768
r0 = ((H - CROP) // 2 // TILE) * TILE
c0 = ((W - CROP) // 2 // TILE) * TILE

raw = torch.frombuffer(
    bytearray(dinv.utils.load_url(dinv.utils.get_image_url("modid.raw")).getvalue()),
    dtype=torch.float32,
).reshape(H, W)
raw = raw[r0 : r0 + CROP, c0 : c0 + CROP].to(device)  # 1, H, W where H=W=768
```

<!-- GENERATED FROM PYTHON SOURCE LINES 46-48 -->

Unravel the mosaic into a 16-band image of shape (1, 16, 768, 768) and build the binary MSFA mask, which is 1 where a band is sampled and 0 elsewhere.
Demosaicing is simply inpainting under this mask (see [`deepinv.physics.Demosaicing`](https://deepinv.org/api/stubs/deepinv.physics.Demosaicing.html.md#deepinv.physics.Demosaicing)).

<!-- GENERATED FROM PYTHON SOURCE LINES 48-61 -->
```Python
y = torch.zeros(1, 16, *raw.shape[-2:], device=device)
mask = torch.zeros_like(y)
for i in range(4):
    for j in range(4):
        c = i * 4 + j
        y[:, c, i::4, j::4] = raw[i::4, j::4]
        mask[:, c, i::4, j::4] = 1

y = y / y.max()  # normalise

physics = dinv.physics.Inpainting(img_size=mask.shape[1:], mask=mask, device=device)
```

<!-- GENERATED FROM PYTHON SOURCE LINES 62-66 -->

## Reconstruct with classical interpolation

As a classical baseline we fill the unsampled pixels of each band by Gaussian interpolation.
This is a standard reconstruction used in many ISP pipelines.

<!-- GENERATED FROM PYTHON SOURCE LINES 66-86 -->
```Python
class GaussianInterpolation(dinv.models.Reconstructor):
    def forward(self, y, physics, **kwargs):
        f = dinv.physics.functional.gaussian_blur(sigma=(1.5, 1.5), device=y.device)
        return torch.where(
            physics.mask > 0,
            y,
            dinv.physics.functional.conv2d(y, f, padding="replicate")
            / (
                dinv.physics.functional.conv2d(physics.mask, f, padding="replicate")
                + 1e-8
            ),
        )


model_classic = GaussianInterpolation().to(device)
with torch.no_grad():
    x_classic = model_classic(y, physics)
```

<!-- GENERATED FROM PYTHON SOURCE LINES 87-97 -->

## Reconstruct with total variation prior

We frame image reconstruction as an optimization problem and use the total variation prior to regularise the problem.
We use a Proximal Gradient Descent (PGD) algorithm to solve the inverse problem.
We use the classical interpolated reconstruction as a warm initialisation to speed up the optimization.

#### NOTE
We run 100 iterations on GPU, or 10 on CPU, which might not run to convergence. Increase it for better results.

<!-- GENERATED FROM PYTHON SOURCE LINES 97-113 -->
```Python
model = dinv.optim.PGD(
    prior=dinv.optim.TVPrior(n_it_max=20),  # set larger on GPU
    data_fidelity=dinv.optim.L2(),
    stepsize=1.0,
    lambda_reg=0.01,
    max_iter=10 if dinv.utils.devices_equal(device, "cpu") else 100,
    custom_init=lambda y, physics: x_classic,
    verbose=True,
    show_progress_bar=True,
).to(device)

with torch.no_grad():
    x_tv = model(y, physics)
```

```none
  0%|          | 0/100 [00:00<?, ?it/s]
  2%|▏         | 2/100 [00:00<00:09, 10.51it/s]
  4%|▍         | 4/100 [00:00<00:09, 10.57it/s]
  6%|▌         | 6/100 [00:00<00:08, 10.59it/s]
  8%|▊         | 8/100 [00:00<00:08, 10.60it/s]
 10%|█         | 10/100 [00:00<00:08, 10.60it/s]
 12%|█▏        | 12/100 [00:01<00:08, 10.60it/s]
 14%|█▍        | 14/100 [00:01<00:08, 10.61it/s]
 16%|█▌        | 16/100 [00:01<00:07, 10.61it/s]
 18%|█▊        | 18/100 [00:01<00:07, 10.61it/s]
 20%|██        | 20/100 [00:01<00:07, 10.61it/s]
 22%|██▏       | 22/100 [00:02<00:07, 10.61it/s]
 24%|██▍       | 24/100 [00:02<00:07, 10.61it/s]
 26%|██▌       | 26/100 [00:02<00:06, 10.61it/s]
 28%|██▊       | 28/100 [00:02<00:06, 10.61it/s]
 30%|███       | 30/100 [00:02<00:06, 10.61it/s]
 32%|███▏      | 32/100 [00:03<00:06, 10.61it/s]
 34%|███▍      | 34/100 [00:03<00:06, 10.61it/s]
 36%|███▌      | 36/100 [00:03<00:06, 10.61it/s]
 38%|███▊      | 38/100 [00:03<00:05, 10.61it/s]
 40%|████      | 40/100 [00:03<00:05, 10.61it/s]
 42%|████▏     | 42/100 [00:03<00:05, 10.61it/s]
 44%|████▍     | 44/100 [00:04<00:05, 10.61it/s]
 46%|████▌     | 46/100 [00:04<00:05, 10.61it/s]
 48%|████▊     | 48/100 [00:04<00:04, 10.61it/s]
 50%|█████     | 50/100 [00:04<00:04, 10.61it/s]
 52%|█████▏    | 52/100 [00:04<00:04, 10.61it/s]
 54%|█████▍    | 54/100 [00:05<00:04, 10.61it/s]
 56%|█████▌    | 56/100 [00:05<00:04, 10.61it/s]
 58%|█████▊    | 58/100 [00:05<00:03, 10.61it/s]
 60%|██████    | 60/100 [00:05<00:03, 10.61it/s]
 62%|██████▏   | 62/100 [00:05<00:03, 10.61it/s]
 64%|██████▍   | 64/100 [00:06<00:03, 10.61it/s]
 66%|██████▌   | 66/100 [00:06<00:03, 10.61it/s]
 68%|██████▊   | 68/100 [00:06<00:03, 10.61it/s]
 70%|███████   | 70/100 [00:06<00:02, 10.60it/s]
 72%|███████▏  | 72/100 [00:06<00:02, 10.61it/s]
 74%|███████▍  | 74/100 [00:06<00:02, 10.61it/s]
 76%|███████▌  | 76/100 [00:07<00:02, 10.61it/s]
 78%|███████▊  | 78/100 [00:07<00:02, 10.61it/s]
 80%|████████  | 80/100 [00:07<00:01, 10.61it/s]
 82%|████████▏ | 82/100 [00:07<00:01, 10.61it/s]
 84%|████████▍ | 84/100 [00:07<00:01, 10.61it/s]
 86%|████████▌ | 86/100 [00:08<00:01, 10.61it/s]
 88%|████████▊ | 88/100 [00:08<00:01, 10.61it/s]
 90%|█████████ | 90/100 [00:08<00:00, 10.61it/s]
 92%|█████████▏| 92/100 [00:08<00:00, 10.61it/s]
 94%|█████████▍| 94/100 [00:08<00:00, 10.61it/s]
 96%|█████████▌| 96/100 [00:09<00:00, 10.61it/s]
 98%|█████████▊| 98/100 [00:09<00:00, 10.61it/s]
100%|██████████| 100/100 [00:09<00:00, 10.61it/s]
100%|██████████| 100/100 [00:09<00:00, 10.61it/s]
```

<!-- GENERATED FROM PYTHON SOURCE LINES 114-119 -->

## Visualise the results

We plot the three bands closest to RGB: the full reconstruction has 16 bands.
Interpolation shows chromatic errors at bright spots, an artefact of interpolating across bands.
The TV reconstruction displays classic piecewise constant artifacts.

<!-- GENERATED FROM PYTHON SOURCE LINES 119-132 -->
```Python
dinv.utils.plot(
    {
        "Raw": y[:, [3, 11, 12]],
        "Interpolated": x_classic[:, [3, 11, 12]],
        "TV": x_tv[:, [3, 11, 12]],
    },
    plot_inset=True,
    inset_loc=(0.0, 0.6),
    extract_loc=(0.56, 0.13),
    figsize=(9, 3),
)
```

<!-- GENERATED FROM PYTHON SOURCE LINES 133-151 -->

## What’s next?

We didn’t show any deep learning reconstruction methods in this example to keep the example lightweight.
You can try out other types of reconstruction algorithms listed in the [user guide](https://deepinv.org/user_guide/reconstruction/introduction.html.md#reconstructors).
For example, you could try applying the Deep Image Prior <sup>[2](#footcite-ulyanov2018deep)</sup><sup>[3](#footcite-park2020joint)</sup>:

```default
model = dinv.models.DeepImagePrior(
    dinv.models.ConvDecoder(img_size=mask.shape[1:]),
    img_size=(256, 4, 4),
    iterations=1000,
).to(device)
```

Instead of training models from scratch, you could also fine-tune a foundation model such as [`deepinv.models.RAM`](https://deepinv.org/api/stubs/deepinv.models.RAM.html.md#deepinv.models.RAM)
without ground truth as done in Wang and Davies<sup>[4](#footcite-wang2026perspective)</sup>.

<!-- GENERATED FROM PYTHON SOURCE LINES 153-156 -->
* **References:**

* <a id='footcite-chand2024modid'>**[1]**</a> Sneha Chand, Karthik Namasivayam, Janak Dave, S. P. Preejith, Sadaksharam Jayachandran, and Mohanasankar Sivaprakasam. In-vivo non-contact multispectral oral disease image dataset with segmentation. *Scientific Data*, 11(1):1298, 2024.
* <a id='footcite-ulyanov2018deep'>**[2]**</a> Dmitry Ulyanov, Andrea Vedaldi, and Victor Lempitsky. Deep image prior. In *Proceedings of the IEEE conference on computer vision and pattern recognition*, 9446–9454. 2018.
* <a id='footcite-park2020joint'>**[3]**</a> Yunjin Park, Sukho Lee, Byeongseon Jeong, and Jungho Yoon. Joint demosaicing and denoising based on a variational deep image prior neural network. *Sensors*, 20(10):2970, 2020.
* <a id='footcite-wang2026perspective'>**[4]**</a> Andrew Wang and Mike Davies. Perspective-equivariant fine-tuning for multispectral demosaicing without ground truth. In *Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR) Workshops*. 2026.

**Total running time of the script:** (0 minutes 10.347 seconds)

🏷 Tags: Demosaicing

<a id="sphx-glr-download-auto-examples-optimization-demo-multispectral-demosaicing-py"></a>

[`Download Jupyter notebook: demo_multispectral_demosaicing.ipynb`](https://deepinv.org/auto_examples/optimization/_downloads/581dc4af808afbc3cb58287fa77d1691/demo_multispectral_demosaicing.ipynb)

[`Download Python source code: demo_multispectral_demosaicing.py`](https://deepinv.org/auto_examples/optimization/_downloads/d36460d745f8cf08f53b2aa3c7fef747/demo_multispectral_demosaicing.py)

[`Download zipped: demo_multispectral_demosaicing.zip`](https://deepinv.org/auto_examples/optimization/_downloads/1b8a7f0546f1ab29ffade052d1aca11d/demo_multispectral_demosaicing.zip)

[Gallery generated by Sphinx-Gallery](https://sphinx-gallery.github.io)
