File size: 10,086 Bytes
6ee6c94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c80a892
6ee6c94
 
 
 
 
 
 
 
c80a892
 
6ee6c94
feb1aa2
 
021b860
 
6ee6c94
 
 
 
 
 
 
c80a892
 
6ee6c94
 
c80a892
6ee6c94
 
c80a892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ee6c94
 
 
 
 
c80a892
6ee6c94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c80a892
6ee6c94
c80a892
6ee6c94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea42def
 
 
6ee6c94
ea42def
6ee6c94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b09ba1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ee6c94
 
 
 
 
b09ba1f
 
6ee6c94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
---
license: cc-by-nc-4.0
tags:
  - diffusion
  - ddpm
  - 3d
  - volumetric
  - monai
  - generative
  - synthetic-data
  - atmospheric-science
---

# 3D Convective-Cloud Denoising Diffusion Probabilistic Model

An unconditional **3D Denoising Diffusion Probabilistic Model (DDPM)** that
generates synthetic **64Γ—64Γ—64 convective-cloud cells** (vertical-velocity
volumes). Roughly 190M parameters.

**What it is for:** producing synthetic 3D cloud cells to augment scarce
atmospheric-simulation data β€” for example as additional training data for 3D
cloud segmentation.

**What it is not:** this is not a weather-forecasting model and not a physical
simulation. It samples volumes resembling its training distribution; it does not
solve atmospheric dynamics.

**Project:** [3D Convective-Cloud Generation collection](https://huggingface.co/collections/HoqueMahmudul/3d-convective-cloud-generation-ddpm-190m-params-6a6ba230c89356ff617d481a)

**Code:** [3d-convective-cloud-modeling](https://github.com/HoqueMahmudul/3d-convective-cloud-modeling) β€” DDPM training and sampling scripts.

> **Status:** research artifact from work with an associated manuscript **in
> preparation**. Methodology, training details, and evaluation are deliberately
> not described here and will appear in the publication.

## Requirements

This is a **MONAI** model, not a `transformers` model. You need
**`monai-generative`** (which provides the `generative` package) to build the
network before loading the weights:

```bash
pip install monai-generative==0.2.3 monai==1.5.2 torch safetensors huggingface_hub
```

A `requirements.txt` with the verified-working versions is included in this repo:

```bash
pip install -r <(curl -sL https://huggingface.co/HoqueMahmudul/convective-cloud-denoising-diffusion-probabilistic-model-3d/raw/main/requirements.txt)
```

> [!IMPORTANT]
> **Use `monai-generative`, not MONAI core.** MONAI core (>= 1.4) ships its own
> `DiffusionModelUNet`, `DDPMScheduler` and `DiffusionInferer`, and they look
> like drop-in replacements. **They are not weight-compatible with this
> checkpoint.**
>
> | | `monai-generative` 0.2.3 | MONAI core 1.5 |
> |---|---|---|
> | Attention keys | `...attentions.0.to_q.weight` | `...attentions.0.`**`attn`**`.to_q.weight` |
> | Constructor arg | `num_channels=[...]` | `channels=[...]` |
> | Strict load here | works | **52 missing / 52 unexpected keys** |
>
> Both classes have the *identical* parameter count (189,815,297), so if you hit
> the key error and "fix" it with `strict=False`, you will get a model with **52
> randomly-initialised attention tensors**. It will run and produce
> plausible-looking output, and it will be silently wrong.
>
> **Never load these weights with `strict=False`.** If a strict load fails, the
> environment is wrong β€” fix the environment.

## Files

| File | What it is |
|---|---|
| `best_model.safetensors` | The model weights (a plain PyTorch `state_dict`) |
| `requirements.txt` | Verified-working dependency versions |

## How to load and generate a sample

The configuration below is **required** β€” the weights are a bare `state_dict`, so
the network must be constructed exactly this way before loading.

```python
import torch, numpy as np
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
from generative.networks.nets import DiffusionModelUNet
from generative.networks.schedulers import DDPMScheduler
from generative.inferers import DiffusionInferer

REPO = "HoqueMahmudul/convective-cloud-denoising-diffusion-probabilistic-model-3d"
device = "cuda"

# 1. build the network with exactly this configuration
model = DiffusionModelUNet(
    spatial_dims=3, in_channels=1, out_channels=1,
    num_channels=[256, 256, 512],
    attention_levels=[False, False, True],
    num_head_channels=[0, 0, 512],
    num_res_blocks=2,
)

# 2. load the weights -- strict=True is deliberate; see the warning above
weights = hf_hub_download(REPO, "best_model.safetensors")
model.load_state_dict(load_file(weights), strict=True)
model.to(device).eval()

# 3. the sampler must use these settings
scheduler = DDPMScheduler(
    num_train_timesteps=1000, schedule="scaled_linear_beta",
    beta_start=0.0005, beta_end=0.0195,
)
scheduler.set_timesteps(1000)
inferer = DiffusionInferer(scheduler)

# 4. sample one volume (1000 reverse steps -- use a GPU)
noise = torch.randn((1, 1, 64, 64, 64), device=device)
with torch.no_grad(), torch.amp.autocast("cuda"):
    x = inferer.sample(input_noise=noise, diffusion_model=model, scheduler=scheduler)

# 5. convert the [0,1] output back to raw units
#    clip the LOWER bound at 1, not 0: the model's background output is very
#    slightly negative, which would otherwise denormalise to 0 and leave you
#    with a background value the format does not use.
blob = x[0, 0].cpu().numpy()
blob = np.clip(blob * (3100 - 1) + 1, 1, 3100).astype(np.float32)

# 6. enforce the value gap the data format requires (see below)
gap = (blob > 1) & (blob < 300)
blob[gap] = np.where(blob[gap] < 150, 1, 300)

blob = np.round(blob).astype(np.uint16)   # 64^3 synthetic cloud cell
mask = blob >= 300                        # binary cloud mask
```

## Understanding the output

You need these conventions to interpret a generated volume:

| Property | Value |
|---|---|
| Shape / dtype | `64 Γ— 64 Γ— 64`, `uint16` |
| Background | `1` |
| Cloud voxels | `300` – `3022` (vertical velocity, scaled units) |
| Value gap | nothing lies strictly between `1` and `300` |
| Voxel size | 0.5 km isotropic (so 20 voxels = 10 km) |
| Normalisation | `(raw - 1) / (3100 - 1)`, clipped to `[0, 1]` |

The model operates in the normalised `[0, 1]` space, so step 5 above is required
to return to raw units, and step 6 restores the value gap that real volumes have.

## Post-processing β€” extracting a single cloud cell

Steps 5 and 6 of the snippet give you a valid volume, but a raw sample often
contains several disconnected pieces (or none). To get a usable single cloud
cell you need to threshold, find connected components, keep the largest, and
blank the rest. Requires `scipy`:

```python
import numpy as np
from scipy import ndimage

def extract_cloud(blob, min_voxels=6, max_voxels=None):
    """Keep only the largest 26-connected cloud component; blank everything else.

    Returns (cleaned, mask, size).
      cleaned : uint16 volume containing at most one cloud component
      mask    : boolean cloud mask for `cleaned`
      size    : voxel count of the kept component; 0 means "discard this sample"
    """
    mask = blob >= 300
    if not mask.any():
        return blob.copy(), mask, 0                  # empty sample

    # 26-connectivity: faces, edges AND corners count as connected
    labels, n = ndimage.label(mask, structure=np.ones((3, 3, 3)))
    sizes = ndimage.sum(mask, labels, range(1, n + 1))
    keep = int(np.argmax(sizes)) + 1
    largest = int(sizes.max())

    cleaned = blob.copy()
    cleaned[labels != keep] = 1                      # fragments -> background

    if largest < min_voxels:
        return cleaned, cleaned >= 300, 0            # noise only -> discard
    if max_voxels is not None and largest > max_voxels:
        return cleaned, cleaned >= 300, largest      # implausibly large -> review
    return cleaned, cleaned >= 300, largest


cleaned, mask, size = extract_cloud(blob)
if size == 0:
    print("empty or noise-only sample -- generate another")
else:
    print(f"single cloud cell, {size} voxels "
          f"({size * 0.5**3:.1f} km^3 at 0.5 km voxels)")
```

`min_voxels` rejects noise-only samples and `max_voxels` (optional) flags blobs
that fill an implausible share of the volume. Both are yours to choose β€” pick
them from the size range your application needs. Expect to discard a large
fraction of samples; see the note below.

## Practical notes before you use this

- **Sampling is variable and low-yield.** Only a minority of raw samples are
  usable single cloud cells; the rest come out empty, as noise fragments, or
  occasionally as a blob filling the whole volume. **Plan on generating a surplus
  and filtering** with `extract_cloud()` above, discarding samples where it
  returns `size == 0`. This is the intended workflow, not a sign of a defect.
- **The size distribution is skewed.** Generated cells tend to be smaller and
  weaker than those in the source data, so a raw synthetic set is not a
  distribution-matched substitute for real data.
- **One source dataset.** Behaviour outside that simulation regime is unknown.
- **Cost.** Each sample takes 1000 reverse diffusion steps. A GPU is strongly
  recommended.

## Intended use and limitations

Intended for **research and educational use** β€” synthetic data augmentation and
generative-modelling experiments on volumetric atmospheric data.

No perceptual or physical-fidelity metrics are published for this model, so **no
fidelity or accuracy claims are made**. Do not use it for operational
forecasting, hazard assessment, or any decision-making application.

## Data and credit

Trained on convective-cloud cells extracted from atmospheric-simulation output.
**The dataset is not included or redistributed here.**

The underlying simulations come from **Dr. Xiaowen Li's lab at Morgan State
University**; please credit that lab as the source of the simulation data if you
use this model. The diffusion model in this repository was developed by
Mahmudul Hoque.

## License

**CC BY-NC 4.0** β€” [Creative Commons Attribution-NonCommercial 4.0
International](https://creativecommons.org/licenses/by-nc/4.0/).

You may use, share, and adapt these weights for **non-commercial** purposes with
attribution. Commercial use is not permitted. Please also credit the source of
the simulation data as described above.

## References

- Ho, Jain & Abbeel, *Denoising Diffusion Probabilistic Models*, NeurIPS 2020
  ([arXiv:2006.11239](https://arxiv.org/abs/2006.11239))
- [MONAI GenerativeModels](https://github.com/Project-MONAI/GenerativeModels)

## Contact

Mahmudul Hoque, Morgan State University β€” <mahoq1@morgan.edu>