abbaab commited on
Commit
e557c83
·
verified ·
1 Parent(s): ad5d520

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. .gitattributes +4 -35
  3. .ipynb_checkpoints/DGFF_full-checkpoint.ipynb +0 -0
  4. .ipynb_checkpoints/DGFF_full-v2-checkpoint.ipynb +0 -0
  5. .ipynb_checkpoints/DGFF_full_v1-checkpoint.ipynb +0 -0
  6. .ipynb_checkpoints/LOL_load-checkpoint.ipynb +0 -0
  7. .ipynb_checkpoints/Untitled-checkpoint.ipynb +6 -0
  8. .ipynb_checkpoints/additional_revew_test-checkpoint.ipynb +6 -0
  9. .ipynb_checkpoints/confusion_matrix_normalized-checkpoint.png +3 -0
  10. .ipynb_checkpoints/dgff-checkpoint.py +367 -0
  11. .ipynb_checkpoints/dgff_results-checkpoint.png +3 -0
  12. .ipynb_checkpoints/dgff_v2-checkpoint.py +178 -0
  13. .ipynb_checkpoints/gate_maps-checkpoint.png +3 -0
  14. .ipynb_checkpoints/llen-checkpoint.py +62 -0
  15. .ipynb_checkpoints/lol_dataset-checkpoint.py +226 -0
  16. .ipynb_checkpoints/train-checkpoint.ipynb +6 -0
  17. .ipynb_checkpoints/val_batch0_pred-checkpoint.jpg +3 -0
  18. DGFF_full-v2.ipynb +0 -0
  19. DGFF_full.ipynb +3 -0
  20. DGFF_full_v1.ipynb +0 -0
  21. Dataset/README.md +40 -0
  22. Dataset/Thumbnails.png +3 -0
  23. Dataset/exdarkimg.gif +3 -0
  24. ExDark/.DS_Store +0 -0
  25. ExDark/Bicycle/2015_00001.png +3 -0
  26. ExDark/Bicycle/2015_00002.png +3 -0
  27. ExDark/Bicycle/2015_00003.png +3 -0
  28. ExDark/Bicycle/2015_00004.jpg +0 -0
  29. ExDark/Bicycle/2015_00005.jpg +0 -0
  30. ExDark/Bicycle/2015_00006.jpg +3 -0
  31. ExDark/Bicycle/2015_00007.jpg +0 -0
  32. ExDark/Bicycle/2015_00008.jpg +0 -0
  33. ExDark/Bicycle/2015_00009.jpg +3 -0
  34. ExDark/Bicycle/2015_00010.jpg +3 -0
  35. ExDark/Bicycle/2015_00011.jpg +0 -0
  36. ExDark/Bicycle/2015_00012.jpg +0 -0
  37. ExDark/Bicycle/2015_00013.jpg +0 -0
  38. ExDark/Bicycle/2015_00014.jpg +3 -0
  39. ExDark/Bicycle/2015_00015.jpg +3 -0
  40. ExDark/Bicycle/2015_00016.jpg +3 -0
  41. ExDark/Bicycle/2015_00017.jpg +0 -0
  42. ExDark/Bicycle/2015_00018.jpg +0 -0
  43. ExDark/Bicycle/2015_00019.jpg +3 -0
  44. ExDark/Bicycle/2015_00020.jpg +3 -0
  45. ExDark/Bicycle/2015_00021.jpg +0 -0
  46. ExDark/Bicycle/2015_00022.jpg +3 -0
  47. ExDark/Bicycle/2015_00023.jpg +3 -0
  48. ExDark/Bicycle/2015_00024.JPG +0 -0
  49. ExDark/Bicycle/2015_00025.jpg +3 -0
  50. ExDark/Bicycle/2015_00026.jpg +3 -0
.DS_Store ADDED
Binary file (28.7 kB). View file
 
.gitattributes CHANGED
@@ -1,35 +1,4 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.jpg filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
3
+ DGFF_full.ipynb filter=lfs diff=lfs merge=lfs -text
4
+ Dataset/exdarkimg.gif filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.ipynb_checkpoints/DGFF_full-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
.ipynb_checkpoints/DGFF_full-v2-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
.ipynb_checkpoints/DGFF_full_v1-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
.ipynb_checkpoints/LOL_load-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
.ipynb_checkpoints/Untitled-checkpoint.ipynb ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [],
3
+ "metadata": {},
4
+ "nbformat": 4,
5
+ "nbformat_minor": 5
6
+ }
.ipynb_checkpoints/additional_revew_test-checkpoint.ipynb ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [],
3
+ "metadata": {},
4
+ "nbformat": 4,
5
+ "nbformat_minor": 5
6
+ }
.ipynb_checkpoints/confusion_matrix_normalized-checkpoint.png ADDED

Git LFS Details

  • SHA256: cd687c66630cbfce70dc5f7996f343f4aefaf233318464f1f56f9f113c640dc0
  • Pointer size: 131 Bytes
  • Size of remote file: 292 kB
.ipynb_checkpoints/dgff-checkpoint.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Detection-Guided Feature Feedback (DGFF) Module
3
+ ================================================
4
+ The DGFF module is the core architectural contribution of this thesis.
5
+
6
+ During training it:
7
+ 1. Hooks into the YOLOv8 backbone to extract intermediate feature maps
8
+ at P4 (stride-16) and P5 (stride-32) — the semantically richest layers.
9
+ 2. Projects each feature map to match the channel depth of the corresponding
10
+ LLEN decoder stage via lightweight 1×1 convolutional adapters.
11
+ 3. Returns the projected tensors so LLEN.forward() can inject them via
12
+ element-wise addition at decoder stages D4 and D3.
13
+
14
+ At inference the DGFF module is NOT used — LLEN runs alone with no overhead.
15
+
16
+ YOLOv8n backbone channel dimensions (verified for 640×640 input):
17
+ P4: (B, 256, H/16, W/16) stride-16, semantically rich
18
+ P5: (B, 512, H/32, W/32) stride-32, most semantic
19
+
20
+ LLEN decoder injection targets:
21
+ dec4 output: (B, 128, H/8, W/8) ← receives projected P5
22
+ dec3 output: (B, 64, H/4, W/4) ← receives projected P4
23
+
24
+ Data flow during training:
25
+ low_img
26
+ │
27
+ ▼
28
+ LLEN encoder → bottleneck
29
+ │ YOLOv8 backbone
30
+ │ enhanced_img ──────────────────────► P4 (256ch)
31
+ │ ▲ ► P5 (512ch)
32
+ │ │ │
33
+ │ LLEN head DGFF adapters
34
+ │ ▲ P4→64ch, P5→128ch
35
+ │ │ │
36
+ └── dec1 ◄ dec2 ◄ dec3 ◄──────── dgff_p4 │
37
+ dec4 ◄──────────────── dgff_p5
38
+ (element-wise +)
39
+
40
+ Usage:
41
+ from dgff import DGFFModule
42
+ dgff = DGFFModule()
43
+
44
+ # During joint training forward pass:
45
+ enhanced = llen(low_img) # first pass: no feedback
46
+ dgff_p4, dgff_p5 = dgff(enhanced) # extract + project features
47
+ enhanced = llen(low_img, dgff_p5, dgff_p4) # second pass: with feedback
48
+ loss = detection_loss + l1_loss(enhanced, high_img)
49
+ loss.backward()
50
+ """
51
+
52
+ import torch
53
+ import torch.nn as nn
54
+ import torch.nn.functional as F
55
+ from ultralytics import YOLO
56
+
57
+
58
+ # ─────────────────────────────────────────────────────────────
59
+ # Adapter — lightweight 1×1 projection
60
+ # ─────────────────────────────────────────────────────────────
61
+
62
+ class FeatureAdapter(nn.Module):
63
+ """
64
+ Projects a YOLOv8 feature map to the channel depth expected by LLEN.
65
+
66
+ 1×1 Conv keeps spatial size unchanged and adds negligible parameter cost.
67
+ BN + ReLU stabilise training when detection gradients flow back.
68
+
69
+ Args:
70
+ in_ch : channels from YOLOv8 backbone (P4=256, P5=512)
71
+ out_ch : channels expected by LLEN decoder (dec3=64, dec4=128)
72
+ """
73
+ def __init__(self, in_ch: int, out_ch: int):
74
+ super().__init__()
75
+ self.adapt = nn.Sequential(
76
+ nn.Conv2d(in_ch, out_ch, kernel_size=1, bias=False),
77
+ nn.BatchNorm2d(out_ch),
78
+ nn.ReLU(inplace=True),
79
+ )
80
+
81
+ def forward(self, x):
82
+ return self.adapt(x)
83
+
84
+
85
+ # ─────────────────────────────────────────────────────────────
86
+ # DGFF Module
87
+ # ─────────────────────────────────────────────────────────────
88
+
89
+ class DGFFModule(nn.Module):
90
+ """
91
+ Detection-Guided Feature Feedback module.
92
+
93
+ Wraps a frozen-or-trainable YOLOv8 backbone and attaches forward hooks
94
+ to capture P4 and P5 feature maps. Lightweight adapters project them to
95
+ match the LLEN decoder channel depths.
96
+
97
+ Args:
98
+ yolo_weights : YOLOv8 model weights string (e.g. 'yolov8n.pt')
99
+ Downloads automatically on first use.
100
+ llen_base_ch : base channel count of the LLEN (default 32).
101
+ dec4 expects llen_base_ch*4, dec3 expects llen_base_ch*2.
102
+ freeze_backbone : if True, YOLOv8 backbone weights are frozen and
103
+ only the adapter layers are trained in this module.
104
+ Set False to fine-tune backbone end-to-end.
105
+
106
+ YOLOv8n P-layer channels (hard-coded for yolov8n):
107
+ P4 → 128 channels (layer 6 C2f output)
108
+ P5 → 256 channels (layer 9 C2f output)
109
+ If you switch to yolov8s/m/l/x, update P4_CH and P5_CH accordingly:
110
+ yolov8s: P4=256, P5=512
111
+ yolov8m: P4=384, P5=576
112
+ yolov8l: P4=512, P5=512
113
+ yolov8x: P4=640, P5=640
114
+ """
115
+
116
+ # YOLOv8n backbone output channels at P4 and P5
117
+ # Verified by probing layer outputs on 640×640 input:
118
+ # layer 6 (C2f) → 128ch = P4
119
+ # layer 9 (C2f) → 256ch = P5
120
+ P4_CH = 128
121
+ P5_CH = 256
122
+
123
+ # YOLOv8 layer indices where P4 and P5 are produced
124
+ # (these are the C2f output layers in the YOLOv8n backbone)
125
+ P4_LAYER_IDX = 6 # stride-16 feature map
126
+ P5_LAYER_IDX = 9 # stride-32 feature map
127
+
128
+ def __init__(
129
+ self,
130
+ yolo_weights: str = 'yolov8n.pt',
131
+ llen_base_ch: int = 32,
132
+ freeze_backbone: bool = True,
133
+ ):
134
+ super().__init__()
135
+
136
+ # ── Load YOLOv8 ──────────────────────────────────────
137
+ yolo = YOLO(yolo_weights)
138
+ self.backbone = yolo.model.model # nn.Sequential of backbone layers
139
+
140
+ if freeze_backbone:
141
+ for p in self.backbone.parameters():
142
+ p.requires_grad_(False)
143
+
144
+ # ── Verify actual backbone channel depths ────────────
145
+ # Run a tiny probe so we catch channel mismatches at init time
146
+ # rather than deep inside a training loop.
147
+ with torch.no_grad():
148
+ _probe = torch.zeros(1, 3, 64, 64)
149
+ _out = _probe
150
+ for _i, _layer in enumerate(list(self.backbone)[:self.P5_LAYER_IDX + 1]):
151
+ _out = _layer(_out)
152
+ if _i == self.P4_LAYER_IDX:
153
+ _actual_p4 = _out.shape[1]
154
+ if _i == self.P5_LAYER_IDX:
155
+ _actual_p5 = _out.shape[1]
156
+ if _actual_p4 != self.P4_CH or _actual_p5 != self.P5_CH:
157
+ raise ValueError(
158
+ f"YOLOv8 backbone channel mismatch!\n"
159
+ f" P4: expected {self.P4_CH}, got {_actual_p4}\n"
160
+ f" P5: expected {self.P5_CH}, got {_actual_p5}\n"
161
+ f"Update P4_CH and P5_CH class attributes to match."
162
+ )
163
+
164
+ # ── Hook storage ─────────────────────────────────────
165
+ self._p4_feat = None
166
+ self._p5_feat = None
167
+ self._hooks = []
168
+ self._register_hooks()
169
+
170
+ # ── Adapter layers ───────────────────────────────────
171
+ # Project YOLOv8 channels → LLEN decoder channels
172
+ dec4_ch = llen_base_ch * 4 # 128 (dec4 out_ch)
173
+ dec3_ch = llen_base_ch * 2 # 64 (dec3 out_ch)
174
+
175
+ self.adapter_p5 = FeatureAdapter(self.P5_CH, dec4_ch) # 256 → 128
176
+ self.adapter_p4 = FeatureAdapter(self.P4_CH, dec3_ch) # 128 → 64
177
+
178
+ # ----------------------------------------------------------
179
+ def _register_hooks(self):
180
+ """Attach forward hooks to the P4 and P5 backbone layers."""
181
+
182
+ def make_hook(name):
183
+ def hook(module, input, output):
184
+ if name == 'p4':
185
+ self._p4_feat = output
186
+ else:
187
+ self._p5_feat = output
188
+ return hook
189
+
190
+ layer_list = list(self.backbone)
191
+ h4 = layer_list[self.P4_LAYER_IDX].register_forward_hook(make_hook('p4'))
192
+ h5 = layer_list[self.P5_LAYER_IDX].register_forward_hook(make_hook('p5'))
193
+ self._hooks = [h4, h5]
194
+
195
+ # ----------------------------------------------------------
196
+ def remove_hooks(self):
197
+ """Call this if you want to detach hooks (e.g. at inference)."""
198
+ for h in self._hooks:
199
+ h.remove()
200
+ self._hooks = []
201
+
202
+ # ----------------------------------------------------------
203
+ def forward(self, enhanced_img: torch.Tensor):
204
+ """
205
+ Run the enhanced image through the YOLOv8 backbone to extract
206
+ P4 and P5 features, then project them via adapter layers.
207
+
208
+ Args:
209
+ enhanced_img : (B, 3, H, W) enhanced output from LLEN, [0,1]
210
+ Will be resized to 640×640 for YOLOv8 if needed.
211
+
212
+ Returns:
213
+ dgff_p4 : (B, llen_base_ch*2, H/4, W/4) — for LLEN dec3
214
+ dgff_p5 : (B, llen_base_ch*4, H/8, W/8) — for LLEN dec4
215
+ """
216
+ B, C, H, W = enhanced_img.shape
217
+
218
+ # YOLOv8 expects 640×640 input; resize if necessary
219
+ if H != 640 or W != 640:
220
+ x = F.interpolate(
221
+ enhanced_img, size=(640, 640),
222
+ mode='bilinear', align_corners=False
223
+ )
224
+ else:
225
+ x = enhanced_img
226
+
227
+ # Run backbone layer-by-layer up to P5_LAYER_IDX.
228
+ # We cannot call self.backbone(x) directly because layers after
229
+ # the pure backbone use Concat ops that expect list inputs — they
230
+ # crash when called sequentially. The pure backbone layers 0-9
231
+ # are all simple Conv/C2f/SPPF blocks that accept a single tensor.
232
+ # YOLOv8 also saves certain intermediate outputs for skip routes;
233
+ # we replicate that here by keeping a `saved` dict keyed by index.
234
+ layer_list = list(self.backbone)
235
+ saved = {} # stores outputs of layers that feed into later Concat ops
236
+ # Layer save indices for YOLOv8n backbone (layers 4 and 6 are reused)
237
+ SAVE_LAYERS = {4, 6}
238
+ out = x
239
+ for i, layer in enumerate(layer_list[:self.P5_LAYER_IDX + 1]):
240
+ out = layer(out)
241
+ if i in SAVE_LAYERS:
242
+ saved[i] = out
243
+ # hooks already captured p4 (layer 6) and p5 (layer 9) via register_forward_hook
244
+
245
+ p4_raw = self._p4_feat # (B, 128, 40, 40) for 640×640 input
246
+ p5_raw = self._p5_feat # (B, 256, 20, 20) for 640×640 input
247
+
248
+ if p4_raw is None or p5_raw is None:
249
+ raise RuntimeError(
250
+ "DGFF hooks did not capture features. "
251
+ "Check P4_LAYER_IDX and P5_LAYER_IDX match your YOLOv8 version."
252
+ )
253
+
254
+ # Project to LLEN decoder channel depths
255
+ dgff_p4_proj = self.adapter_p4(p4_raw) # (B, 64, 40, 40) 128→64
256
+ dgff_p5_proj = self.adapter_p5(p5_raw) # (B, 128, 20, 20) 256→128
257
+
258
+ # Resize projected features to match LLEN decoder spatial sizes
259
+ # LLEN decoder spatial sizes depend on input resolution (H, W)
260
+ # dec4 operates at H/8, W/8 — matches P5 after resize
261
+ # dec3 operates at H/4, W/4 — matches P4 after resize
262
+ dgff_p5_out = F.interpolate(
263
+ dgff_p5_proj, size=(H // 8, W // 8),
264
+ mode='bilinear', align_corners=False
265
+ )
266
+ dgff_p4_out = F.interpolate(
267
+ dgff_p4_proj, size=(H // 4, W // 4),
268
+ mode='bilinear', align_corners=False
269
+ )
270
+
271
+ return dgff_p4_out, dgff_p5_out
272
+
273
+ # ----------------------------------------------------------
274
+ def count_parameters(self):
275
+ total = sum(p.numel() for p in self.parameters())
276
+ trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
277
+ return total, trainable
278
+
279
+
280
+ # ─────────────────────────────────────────────────────────────
281
+ # Smoke Test
282
+ # ─────────────────────────────────────────────────────────────
283
+
284
+ if __name__ == '__main__':
285
+ import sys
286
+ from pathlib import Path
287
+
288
+ print("=" * 55)
289
+ print(" DGFF Module Smoke Test")
290
+ print("=" * 55)
291
+
292
+ device = (
293
+ torch.device('mps') if torch.backends.mps.is_available() else
294
+ torch.device('cuda') if torch.cuda.is_available() else
295
+ torch.device('cpu')
296
+ )
297
+ print(f" Device : {device}")
298
+
299
+ # ── Load DGFF ────────────────────────────────────────────
300
+ print(" Loading YOLOv8n backbone ...")
301
+ dgff = DGFFModule(
302
+ yolo_weights = 'yolov8n.pt',
303
+ llen_base_ch = 32,
304
+ freeze_backbone = True,
305
+ ).to(device)
306
+
307
+ total, trainable = dgff.count_parameters()
308
+ print(f" Total parameters : {total:,}")
309
+ print(f" Trainable (adapters): {trainable:,}")
310
+
311
+ # ── Forward pass test ────────────────────────────────────
312
+ dummy_enhanced = torch.rand(2, 3, 256, 256).to(device)
313
+
314
+ print(f"\n Input : {tuple(dummy_enhanced.shape)}")
315
+ dgff_p4, dgff_p5 = dgff(dummy_enhanced)
316
+ print(f" dgff_p4 (→ dec3) : {tuple(dgff_p4.shape)}") # expect (2, 64, 64, 64)
317
+ print(f" dgff_p5 (→ dec4) : {tuple(dgff_p5.shape)}") # expect (2, 128, 32, 32)
318
+
319
+ assert dgff_p4.shape == (2, 64, 64, 64), f"P4 shape wrong: {dgff_p4.shape}"
320
+ assert dgff_p5.shape == (2, 128, 32, 32), f"P5 shape wrong: {dgff_p5.shape}"
321
+ print(" Shape assertions passed ✓")
322
+
323
+ # ── Full pipeline test: LLEN + DGFF ─────────────────────
324
+ print("\n Testing LLEN + DGFF combined forward pass ...")
325
+ sys.path.insert(0, str(Path(__file__).parent if '__file__' in dir() else Path('.')))
326
+ from llen import LLEN
327
+
328
+ llen = LLEN(base_ch=32).to(device)
329
+
330
+ # Simulate the two-pass training forward:
331
+ # Pass 1: LLEN enhances without feedback (cold start)
332
+ enhanced = llen(dummy_enhanced)
333
+ print(f" Pass 1 enhanced : {tuple(enhanced.shape)}")
334
+
335
+ # Pass 2: DGFF extracts features from enhanced image
336
+ dgff_p4, dgff_p5 = dgff(enhanced)
337
+
338
+ # Pass 3: LLEN re-enhances with detection feedback injected
339
+ enhanced_guided = llen(dummy_enhanced, dgff_p5=dgff_p5, dgff_p4=dgff_p4)
340
+ print(f" Pass 2 guided : {tuple(enhanced_guided.shape)}")
341
+
342
+ assert enhanced_guided.shape == dummy_enhanced.shape
343
+ print(" Combined pipeline ✓")
344
+
345
+ # ── Gradient flow test ─��─────────────────────────────────
346
+ print("\n Testing gradient flow through DGFF adapters ...")
347
+ import torch.nn.functional as F_test
348
+
349
+ llen.train()
350
+ dgff.train()
351
+
352
+ enhanced = llen(dummy_enhanced)
353
+ dgff_p4, dgff_p5 = dgff(enhanced)
354
+ enhanced_guided = llen(dummy_enhanced, dgff_p5=dgff_p5, dgff_p4=dgff_p4)
355
+
356
+ dummy_target = torch.rand_like(enhanced_guided)
357
+ loss = F_test.l1_loss(enhanced_guided, dummy_target)
358
+ loss.backward()
359
+
360
+ # Check adapter grads received signal
361
+ for name, param in dgff.adapter_p5.named_parameters():
362
+ assert param.grad is not None, f"adapter_p5.{name} has no gradient!"
363
+ for name, param in dgff.adapter_p4.named_parameters():
364
+ assert param.grad is not None, f"adapter_p4.{name} has no gradient!"
365
+
366
+ print(" Adapter gradients flowing ✓")
367
+ print("\n All DGFF tests passed ✓")
.ipynb_checkpoints/dgff_results-checkpoint.png ADDED

Git LFS Details

  • SHA256: d5b4cb93da13bfda96f82f8722b1ff65204e0f62094e5935459d762b76fcb85d
  • Pointer size: 132 Bytes
  • Size of remote file: 6.66 MB
.ipynb_checkpoints/dgff_v2-checkpoint.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ dgff_v2.py — DGFF Module with Channel Gating
3
+ =============================================
4
+ Improvement over v1: 1×1 adapter + sigmoid gate replaces plain
5
+ element-wise addition. The gate learns which detector channels
6
+ to incorporate per-spatial-location, making the feedback selective
7
+ and more theoretically principled.
8
+
9
+ Change from v1:
10
+ v1: x_decoder = x_decoder + adapter(f_det)
11
+ v2: gate = sigmoid(gate_conv(f_det))
12
+ x_decoder = x_decoder + gate * adapter(f_det)
13
+
14
+ The gate suppresses irrelevant detector features (e.g. background
15
+ texture channels that don't help enhancement) while amplifying
16
+ object-boundary and semantic channels.
17
+ """
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+ from ultralytics import YOLO
22
+
23
+
24
+ class GatedAdapter(nn.Module):
25
+ """
26
+ Projects YOLOv8 backbone features into LLEN decoder space
27
+ with a learned sigmoid gate.
28
+
29
+ Args:
30
+ in_ch : YOLOv8 feature channels (256 for P4, 512 for P5 on yolov8n)
31
+ out_ch : LLEN decoder channels at the injection point
32
+ """
33
+ def __init__(self, in_ch: int, out_ch: int):
34
+ super().__init__()
35
+ # Value path: projects detector features to decoder space
36
+ self.proj = nn.Sequential(
37
+ nn.Conv2d(in_ch, out_ch, kernel_size=1, bias=False),
38
+ nn.BatchNorm2d(out_ch),
39
+ nn.ReLU(inplace=True),
40
+ )
41
+ # Gate path: learns a channel-wise attention mask in [0, 1]
42
+ self.gate = nn.Sequential(
43
+ nn.Conv2d(in_ch, out_ch, kernel_size=1, bias=False),
44
+ nn.Sigmoid(),
45
+ )
46
+
47
+ def forward(self, f_det, target_size):
48
+ """
49
+ Args:
50
+ f_det : (B, in_ch, H', W') raw backbone feature
51
+ target_size : (H, W) spatial size of the decoder stage
52
+
53
+ Returns:
54
+ (B, out_ch, H, W) gated feature feedback
55
+ """
56
+ # Resize to match decoder spatial resolution
57
+ f_det = nn.functional.interpolate(
58
+ f_det, size=target_size, mode='bilinear', align_corners=False
59
+ )
60
+ return self.gate(f_det) * self.proj(f_det) # element-wise gated projection
61
+
62
+
63
+ class DGFFModule(nn.Module):
64
+ """
65
+ Detection-Guided Feature Feedback module.
66
+
67
+ Wraps a frozen YOLOv8 backbone and two GatedAdapters that project
68
+ P4 and P5 backbone features into the LLEN decoder spatial dimensions.
69
+
70
+ Args:
71
+ yolo_weights : path to YOLOv8 .pt file
72
+ llen_base_ch : base channel width of the LLEN (default 32)
73
+ freeze_backbone : if True, backbone weights are not updated
74
+ """
75
+
76
+ # YOLOv8n backbone channel counts at P4 and P5
77
+ # These depend on the variant (n/s/m/l/x). yolov8n:
78
+ YOLO_P4_CH = 256
79
+ YOLO_P5_CH = 512
80
+
81
+ def __init__(
82
+ self,
83
+ yolo_weights: str = 'yolov8n.pt',
84
+ llen_base_ch: int = 32,
85
+ freeze_backbone: bool = True,
86
+ ):
87
+ super().__init__()
88
+
89
+ # ── Load YOLOv8 backbone ──────────────────────────────
90
+ yolo = YOLO(yolo_weights)
91
+ # Extract the backbone (model.model.model[:10] for YOLOv8)
92
+ # This gives us the CSP + C2f feature extraction up to P5
93
+ self.backbone = yolo.model.model[:10]
94
+
95
+ if freeze_backbone:
96
+ for p in self.backbone.parameters():
97
+ p.requires_grad_(False)
98
+
99
+ # ── Gated adapters ───────────────────────────────────
100
+ c = llen_base_ch
101
+ # P5 feedback → decoder stage D4 (out_ch = c*4 = 128)
102
+ self.adapter_p5 = GatedAdapter(self.YOLO_P5_CH, c * 4)
103
+ # P4 feedback → decoder stage D3 (out_ch = c*2 = 64)
104
+ self.adapter_p4 = GatedAdapter(self.YOLO_P4_CH, c * 2)
105
+
106
+ self._llen_base_ch = c
107
+
108
+ # ----------------------------------------------------------
109
+ def forward(self, enhanced_img):
110
+ """
111
+ Args:
112
+ enhanced_img : (B, 3, H, W) enhanced image from LLEN Pass 1
113
+
114
+ Returns:
115
+ (p4_feedback, p5_feedback) — tensors ready to inject into LLEN decoder
116
+ """
117
+ # Run backbone — collect intermediate feature maps
118
+ x = enhanced_img
119
+ p4_raw = None
120
+ p5_raw = None
121
+
122
+ for i, layer in enumerate(self.backbone):
123
+ x = layer(x)
124
+ # YOLOv8n: P4 at layer 6, P5 at layer 9 (backbone output)
125
+ if i == 6:
126
+ p4_raw = x
127
+ if i == 9:
128
+ p5_raw = x
129
+
130
+ if p4_raw is None or p5_raw is None:
131
+ raise RuntimeError(
132
+ "Could not extract P4/P5 from backbone. "
133
+ "Check layer indices for your YOLOv8 variant."
134
+ )
135
+
136
+ c = self._llen_base_ch
137
+ H_input, W_input = enhanced_img.shape[-2:]
138
+
139
+ # Compute decoder target sizes (matching LLEN decoder stages)
140
+ p5_size = (H_input // 8, W_input // 8) # D4 spatial dim
141
+ p4_size = (H_input // 4, W_input // 4) # D3 spatial dim
142
+
143
+ p5_feedback = self.adapter_p5(p5_raw, p5_size) # (B, c*4, H/8, W/8)
144
+ p4_feedback = self.adapter_p4(p4_raw, p4_size) # (B, c*2, H/4, W/4)
145
+
146
+ return p4_feedback, p5_feedback
147
+
148
+ # ----------------------------------------------------------
149
+ def count_parameters(self):
150
+ total = sum(p.numel() for p in self.parameters())
151
+ trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
152
+ return total, trainable
153
+
154
+
155
+ # ─────────────────────────────────────────────────────────────
156
+ # Smoke test
157
+ # ─────────────────────────────────────────────────────────────
158
+
159
+ if __name__ == '__main__':
160
+ import sys
161
+
162
+ print("DGFFv2 Smoke Test")
163
+ device = torch.device('cpu')
164
+
165
+ try:
166
+ dgff = DGFFModule(yolo_weights='yolov8n.pt', llen_base_ch=32).to(device)
167
+ except Exception as e:
168
+ print(f"[SKIP] Could not load YOLOv8: {e}")
169
+ sys.exit(0)
170
+
171
+ total, trainable = dgff.count_parameters()
172
+ print(f" Params total / trainable: {total:,} / {trainable:,}")
173
+
174
+ dummy = torch.rand(2, 3, 256, 256)
175
+ p4, p5 = dgff(dummy)
176
+ print(f" P4 feedback shape: {tuple(p4.shape)} (expected: 2, 64, 64, 64)")
177
+ print(f" P5 feedback shape: {tuple(p5.shape)} (expected: 2, 128, 32, 32)")
178
+ print(" Smoke test passed ✓")
.ipynb_checkpoints/gate_maps-checkpoint.png ADDED

Git LFS Details

  • SHA256: 4cc3de8adb531708725999bc915167b323bd542cad9ece1630a2c6202df5ccd7
  • Pointer size: 132 Bytes
  • Size of remote file: 2.08 MB
.ipynb_checkpoints/llen-checkpoint.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from ultralytics import YOLO
5
+
6
+ # ── PASTE THIS into your DGFF smoke-test cell, replacing the import block ──
7
+ #
8
+ # The problem: your notebook has all code in cells, so there is no llen.py
9
+ # file on disk to import. LLEN is already defined in a previous cell though,
10
+ # so we just skip the import entirely and use it directly.
11
+ #
12
+ # BEFORE (broken):
13
+ # sys.path.insert(0, str(Path(__file__).parent if '__file__' in dir() else Path('.')))
14
+ # from llen import LLEN
15
+ #
16
+ # AFTER (fixed) — replace those two lines with:
17
+
18
+ # Check if LLEN is already defined in this notebook session
19
+ if 'LLEN' not in dir():
20
+ # Only needed if running dgff.py as a standalone script
21
+ import sys
22
+ sys.path.insert(0, '.') # assumes llen.py is in the same folder as notebook
23
+ from llen import LLEN
24
+
25
+ # Everything below stays exactly the same
26
+ llen_model = LLEN(base_ch=32).to(device)
27
+
28
+ # Pass 1: LLEN enhances without feedback
29
+ enhanced = llen_model(dummy_enhanced)
30
+ print(f" Pass 1 enhanced : {tuple(enhanced.shape)}")
31
+
32
+ # Pass 2: DGFF extracts features from enhanced image
33
+ dgff_p4, dgff_p5 = dgff(enhanced)
34
+
35
+ # Pass 3: LLEN re-enhances with detection feedback injected
36
+ enhanced_guided = llen_model(dummy_enhanced, dgff_p5=dgff_p5, dgff_p4=dgff_p4)
37
+ print(f" Pass 2 guided : {tuple(enhanced_guided.shape)}")
38
+
39
+ assert enhanced_guided.shape == dummy_enhanced.shape
40
+ print(" Combined pipeline ✓")
41
+
42
+ # ── Gradient flow test ──────────────────────────────────────
43
+ print("\n Testing gradient flow through DGFF adapters ...")
44
+
45
+ llen_model.train()
46
+ dgff.train()
47
+
48
+ enhanced = llen_model(dummy_enhanced)
49
+ dgff_p4, dgff_p5 = dgff(enhanced)
50
+ enhanced_guided = llen_model(dummy_enhanced, dgff_p5=dgff_p5, dgff_p4=dgff_p4)
51
+
52
+ dummy_target = torch.rand_like(enhanced_guided)
53
+ loss = torch.nn.functional.l1_loss(enhanced_guided, dummy_target)
54
+ loss.backward()
55
+
56
+ for name, param in dgff.adapter_p5.named_parameters():
57
+ assert param.grad is not None, f"adapter_p5.{name} has no gradient!"
58
+ for name, param in dgff.adapter_p4.named_parameters():
59
+ assert param.grad is not None, f"adapter_p4.{name} has no gradient!"
60
+
61
+ print(" Adapter gradients flowing ✓")
62
+ print("\n All DGFF tests passed ✓")
.ipynb_checkpoints/lol_dataset-checkpoint.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+
6
+ # ─────────────────────────────────────────────────────────────
7
+ # 1. Core Dataset Class
8
+ # ─────────────────────────────────────────────────────────────
9
+
10
+ class LOLDataset(Dataset):
11
+ """
12
+ Loads paired (low-light, normal-light) images from the LOL dataset.
13
+
14
+ Args:
15
+ root : path to the LOL root folder
16
+ split : 'train' → uses our485/
17
+ 'val' → uses eval15/
18
+ crop_size : if not None, randomly crops both images to this size
19
+ during training (e.g. 256 or 384).
20
+ Set to None to use full images (600×400 for LOL).
21
+ augment : apply random horizontal flip during training
22
+ """
23
+
24
+ SPLIT_DIRS = {
25
+ 'train': 'our485',
26
+ 'val': 'eval15',
27
+ }
28
+
29
+ def __init__(
30
+ self,
31
+ root: str,
32
+ split: str = 'train',
33
+ crop_size: int | None = 256,
34
+ augment: bool = True,
35
+ ):
36
+ super().__init__()
37
+
38
+ assert split in self.SPLIT_DIRS, f"split must be 'train' or 'val', got '{split}'"
39
+
40
+ self.split = split
41
+ self.crop_size = crop_size
42
+ self.augment = augment and (split == 'train')
43
+
44
+ split_dir = Path(root) / self.SPLIT_DIRS[split]
45
+ self.low_dir = split_dir / 'low'
46
+ self.high_dir = split_dir / 'high'
47
+
48
+ self._verify_dirs()
49
+
50
+ # Collect filenames — sorted so low/high pairs always align
51
+ # Filter to image files only (excludes .DS_Store and other macOS metadata)
52
+ _IMG_EXTS = {'.png', '.jpg', '.jpeg', '.bmp', '.tif', '.tiff'}
53
+ self.filenames = sorted(
54
+ f for f in os.listdir(self.low_dir)
55
+ if Path(f).suffix.lower() in _IMG_EXTS
56
+ )
57
+
58
+ # Sanity check: every low image has a matching high image
59
+ missing = [f for f in self.filenames if not (self.high_dir / f).exists()]
60
+ if missing:
61
+ raise FileNotFoundError(
62
+ f"No matching high-light image for: {missing[:5]} ..."
63
+ )
64
+
65
+ # Simple normalisation: PIL image [0,255] → tensor [0,1]
66
+ self.to_tensor = T.ToTensor()
67
+
68
+ # ----------------------------------------------------------
69
+ def _verify_dirs(self):
70
+ for d in (self.low_dir, self.high_dir):
71
+ if not d.is_dir():
72
+ raise FileNotFoundError(
73
+ f"Directory not found: {d}\n"
74
+ f"Make sure your LOL dataset is at the correct path."
75
+ )
76
+
77
+ # ----------------------------------------------------------
78
+ def __len__(self):
79
+ return len(self.filenames)
80
+
81
+ # ----------------------------------------------------------
82
+ def __getitem__(self, idx):
83
+ fname = self.filenames[idx]
84
+
85
+ # Load images as RGB PIL
86
+ low_img = Image.open(self.low_dir / fname).convert('RGB')
87
+ high_img = Image.open(self.high_dir / fname).convert('RGB')
88
+
89
+ # ── Random crop (same crop for both images) ──
90
+ if self.crop_size is not None:
91
+ low_img, high_img = self._paired_random_crop(low_img, high_img)
92
+
93
+ # ── Random horizontal flip ──
94
+ if self.augment and random.random() > 0.5:
95
+ low_img = TF.hflip(low_img)
96
+ high_img = TF.hflip(high_img)
97
+
98
+ # ── PIL → float tensor [0, 1] ──
99
+ low_tensor = self.to_tensor(low_img) # shape: (3, H, W)
100
+ high_tensor = self.to_tensor(high_img) # shape: (3, H, W)
101
+
102
+ return low_tensor, high_tensor, fname
103
+
104
+ # ----------------------------------------------------------
105
+ def _paired_random_crop(self, low_pil, high_pil):
106
+ """Apply the SAME random crop to both images."""
107
+ w, h = low_pil.size # PIL gives (width, height)
108
+ crop = self.crop_size
109
+
110
+ if w < crop or h < crop:
111
+ # If image is smaller than desired crop, just resize both
112
+ low_pil = TF.resize(low_pil, [crop, crop])
113
+ high_pil = TF.resize(high_pil, [crop, crop])
114
+ return low_pil, high_pil
115
+
116
+ # Random top-left corner
117
+ top = random.randint(0, h - crop)
118
+ left = random.randint(0, w - crop)
119
+
120
+ low_pil = TF.crop(low_pil, top, left, crop, crop)
121
+ high_pil = TF.crop(high_pil, top, left, crop, crop)
122
+ return low_pil, high_pil
123
+
124
+
125
+ # ─────────────────────────────────────────────────────────────
126
+ # 2. Convenience Factory Function
127
+ # ──────────────────────────────────────────────��──────────────
128
+
129
+ def get_lol_dataloaders(
130
+ root: str,
131
+ batch_size: int = 8,
132
+ crop_size: int | None = 256,
133
+ num_workers: int = 0, # 0 = safe for Jupyter notebooks on Mac/Windows
134
+ pin_memory: bool = False, # False = safe for MPS (Apple Silicon) and CPU
135
+ ) -> tuple[DataLoader, DataLoader]:
136
+ """
137
+ Build train and validation DataLoaders for the LOL dataset.
138
+
139
+ Args:
140
+ root : path to the LOL root folder (contains our485/ and eval15/)
141
+ batch_size : number of image pairs per batch
142
+ crop_size : random crop size during training. None = full image.
143
+ num_workers : parallel workers for data loading
144
+ pin_memory : speeds up CPU→GPU transfer (set True when using CUDA)
145
+
146
+ Returns:
147
+ (train_loader, val_loader)
148
+ """
149
+ train_set = LOLDataset(root=root, split='train', crop_size=crop_size, augment=True)
150
+ val_set = LOLDataset(root=root, split='val', crop_size=None, augment=False)
151
+
152
+ train_loader = DataLoader(
153
+ train_set,
154
+ batch_size = batch_size,
155
+ shuffle = True, # shuffle every epoch
156
+ num_workers = num_workers,
157
+ pin_memory = pin_memory,
158
+ drop_last = True, # keeps batch size consistent
159
+ )
160
+
161
+ val_loader = DataLoader(
162
+ val_set,
163
+ batch_size = 1, # validate one image at a time (full resolution)
164
+ shuffle = False,
165
+ num_workers = num_workers,
166
+ pin_memory = pin_memory,
167
+ drop_last = False,
168
+ )
169
+
170
+ return train_loader, val_loader
171
+
172
+
173
+ # ─────────────────────────────────────────────────────────────
174
+ # 3. Quick Sanity Check (run this file directly to test)
175
+ # ─────────────────────────────────────────────────────────────
176
+
177
+ if __name__ == '__main__':
178
+ import sys
179
+ import matplotlib.pyplot as plt
180
+
181
+ # ── Point this at your LOL folder ──
182
+ LOL_ROOT = './LOL' # <-- change this if your path is different
183
+
184
+ print(f"Loading LOL dataset from: {LOL_ROOT}")
185
+
186
+ try:
187
+ train_loader, val_loader = get_lol_dataloaders(
188
+ root = LOL_ROOT,
189
+ batch_size = 4,
190
+ crop_size = 256,
191
+ num_workers = 0, # always 0 when running directly as __main__
192
+ pin_memory = False, # False for MPS (Apple Silicon) and CPU
193
+ )
194
+ except FileNotFoundError as e:
195
+ print(f"\n[ERROR] {e}")
196
+ sys.exit(1)
197
+
198
+ print(f" Train batches : {len(train_loader)} "
199
+ f"({len(train_loader.dataset)} image pairs)")
200
+ print(f" Val batches : {len(val_loader)} "
201
+ f"({len(val_loader.dataset)} image pairs)")
202
+
203
+ # ── Grab one batch and print tensor info ──
204
+ low_batch, high_batch, fnames = next(iter(train_loader))
205
+
206
+ print(f"\nBatch shapes : low={tuple(low_batch.shape)} "
207
+ f"high={tuple(high_batch.shape)}")
208
+ print(f"Value range : [{low_batch.min():.3f}, {low_batch.max():.3f}]")
209
+ print(f"Filenames : {list(fnames)}")
210
+
211
+ # ── Visualise the first pair ──
212
+ fig, axes = plt.subplots(1, 2, figsize=(10, 4))
213
+ # Convert tensor (C,H,W) → numpy (H,W,C) for matplotlib
214
+ axes[0].imshow(low_batch[0].permute(1, 2, 0).numpy())
215
+ axes[0].set_title(f'Low-light input\n{fnames[0]}')
216
+ axes[0].axis('off')
217
+
218
+ axes[1].imshow(high_batch[0].permute(1, 2, 0).numpy())
219
+ axes[1].set_title(f'Normal-light reference\n{fnames[0]}')
220
+ axes[1].axis('off')
221
+
222
+ plt.tight_layout()
223
+ plt.savefig('lol_sample.png', dpi=120)
224
+ print("\nSaved sample visualisation → lol_sample.png")
225
+ plt.show()
226
+
.ipynb_checkpoints/train-checkpoint.ipynb ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [],
3
+ "metadata": {},
4
+ "nbformat": 4,
5
+ "nbformat_minor": 5
6
+ }
.ipynb_checkpoints/val_batch0_pred-checkpoint.jpg ADDED

Git LFS Details

  • SHA256: 13c33171b5736a5f3f8b7a942dea4c0bd33bdc68bd7708fbe036e583da09b40a
  • Pointer size: 131 Bytes
  • Size of remote file: 362 kB
DGFF_full-v2.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
DGFF_full.ipynb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd1fa7b3b356e59e7defe3ed590d3e456a6764df8553ee537768707b1a72bf8a
3
+ size 56188001
DGFF_full_v1.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
Dataset/README.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Description
2
+
3
+ The ExDark dataset can be downloaded at [this https URL](https://drive.google.com/file/d/1BHmPgu8EsHoFDDkMGLVoXIlCth2dW6Yx/view?usp=sharing) (size = 1.5Gb).
4
+
5
+ ![exdark](Thumbnails.png)
6
+
7
+ ## Dataset Information
8
+
9
+ The images are kept in individual class folders following the image class labels:
10
+ 1. Bicycle - 652 images
11
+ 2. Boat - 679 images
12
+ 3. Bottle - 547 images
13
+ 4. Bus - 527 images
14
+ 5. Car - 638 images
15
+ 6. Cat - 735 images
16
+ 7. Chair - 648 images
17
+ 8. Cup - 519 images
18
+ 9. Dog - 801 images
19
+ 10. Motorbike - 503 images
20
+ 11. People - 609 images
21
+ 12. Table - 505 images
22
+
23
+ Total : 7,363 images
24
+
25
+ ![demo](exdarkimg.gif)
26
+
27
+ For the experiments in our paper that involves training, the data are split as follows:
28
+
29
+ (a) Training - 3,000 images (250 images per class)
30
+
31
+ (b) Validation - 1,800 images (150 images per class)
32
+
33
+ (c) Testing - 2,563 images
34
+
35
+ * See [annotation](https://github.com/cs-chan/Exclusively-Dark-Image-Dataset/tree/master/Groundtruth) for further details.
36
+
37
+ Note:
38
+ 1. The ExDark dataset can be used only for non-commercial research purpose.
39
+
40
+ 2. For commercial purpose usage, please contact Dr. Chee Seng Chan at `cs.chan at um.edu.my`
Dataset/Thumbnails.png ADDED

Git LFS Details

  • SHA256: fb1a9d2e211b515d755c184d67d5a97cf6d3dd9e242056bb218894e7c933470e
  • Pointer size: 133 Bytes
  • Size of remote file: 23.2 MB
Dataset/exdarkimg.gif ADDED

Git LFS Details

  • SHA256: a3b8f88c2830856a3bfe7a9bc198819d8d20d48437380272b63a8c897cc31c70
  • Pointer size: 131 Bytes
  • Size of remote file: 902 kB
ExDark/.DS_Store ADDED
Binary file (10.2 kB). View file
 
ExDark/Bicycle/2015_00001.png ADDED

Git LFS Details

  • SHA256: b81919b5154a602c19bab61780c2928238b3f3930ece1f38d2c96967034922e7
  • Pointer size: 131 Bytes
  • Size of remote file: 310 kB
ExDark/Bicycle/2015_00002.png ADDED

Git LFS Details

  • SHA256: 089bf7abdc03b7eeabe8d8822094b280c690a071ea6ee55c3a5182e9194341a5
  • Pointer size: 131 Bytes
  • Size of remote file: 414 kB
ExDark/Bicycle/2015_00003.png ADDED

Git LFS Details

  • SHA256: 6f6b4a8d29b50d2d87e814a57e2de4fe9f1f94df4cc803119f39baec532b53d0
  • Pointer size: 131 Bytes
  • Size of remote file: 303 kB
ExDark/Bicycle/2015_00004.jpg ADDED
ExDark/Bicycle/2015_00005.jpg ADDED
ExDark/Bicycle/2015_00006.jpg ADDED

Git LFS Details

  • SHA256: 96f0a5093d762513ac4cb67a2e9f8c4c0b006b565a72ef81a1a1335c28f0a0c6
  • Pointer size: 131 Bytes
  • Size of remote file: 106 kB
ExDark/Bicycle/2015_00007.jpg ADDED
ExDark/Bicycle/2015_00008.jpg ADDED
ExDark/Bicycle/2015_00009.jpg ADDED

Git LFS Details

  • SHA256: 904a5656500ba7f127ca193dad1b6396924067949d6e7580ce514c2460cfc6c1
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
ExDark/Bicycle/2015_00010.jpg ADDED

Git LFS Details

  • SHA256: ed9fbd861fbad15d31f511a8a03e196ada1997bc98e0c2cc1bcdd229058d024a
  • Pointer size: 131 Bytes
  • Size of remote file: 135 kB
ExDark/Bicycle/2015_00011.jpg ADDED
ExDark/Bicycle/2015_00012.jpg ADDED
ExDark/Bicycle/2015_00013.jpg ADDED
ExDark/Bicycle/2015_00014.jpg ADDED

Git LFS Details

  • SHA256: a7597548cf7c8a92d770caa00191a5be98825e48e2b0bd08457cb7fc67a94d49
  • Pointer size: 131 Bytes
  • Size of remote file: 181 kB
ExDark/Bicycle/2015_00015.jpg ADDED

Git LFS Details

  • SHA256: 33b8fde6ae87e119c1a13e1e75a3a64e7618d15fd7a93e30dd5083a9f22eecc3
  • Pointer size: 131 Bytes
  • Size of remote file: 271 kB
ExDark/Bicycle/2015_00016.jpg ADDED

Git LFS Details

  • SHA256: 6d8086e4f12ebb588492da2aa9c738f2272d574034d42aaf7c226485d9653213
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
ExDark/Bicycle/2015_00017.jpg ADDED
ExDark/Bicycle/2015_00018.jpg ADDED
ExDark/Bicycle/2015_00019.jpg ADDED

Git LFS Details

  • SHA256: ce179fba5746f6bfc50118f652eb1103c45fd8395b84b716f42e4d1dee4a96b5
  • Pointer size: 131 Bytes
  • Size of remote file: 104 kB
ExDark/Bicycle/2015_00020.jpg ADDED

Git LFS Details

  • SHA256: 3e9adccf0a86969d73ff9730a61c31abcccf993df6c32958bdbdfcadb8de6f7f
  • Pointer size: 131 Bytes
  • Size of remote file: 132 kB
ExDark/Bicycle/2015_00021.jpg ADDED
ExDark/Bicycle/2015_00022.jpg ADDED

Git LFS Details

  • SHA256: c49d81db55dc6cf974596418f0a7393c3e10a455aa1e8225925985d9f08d35ec
  • Pointer size: 131 Bytes
  • Size of remote file: 149 kB
ExDark/Bicycle/2015_00023.jpg ADDED

Git LFS Details

  • SHA256: 612e5d0561a249e22bd00d22b58bdd3835e663f5b8bcd5ae7e84a29e953af15a
  • Pointer size: 131 Bytes
  • Size of remote file: 115 kB
ExDark/Bicycle/2015_00024.JPG ADDED
ExDark/Bicycle/2015_00025.jpg ADDED

Git LFS Details

  • SHA256: 30caac9fe9d5bcf72d02137a51c75e8dedce0648b75d118b370a1dafedad7e06
  • Pointer size: 131 Bytes
  • Size of remote file: 180 kB
ExDark/Bicycle/2015_00026.jpg ADDED

Git LFS Details

  • SHA256: 58ab5719ab615f4bfb7c0a12b196f589e75f6e6952f2c91a48d77556bb909218
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB