nmariotto commited on
Commit
a0b8bc0
·
verified ·
1 Parent(s): 2368ccc

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +3 -3
  2. padding_patch.py +130 -130
app.py CHANGED
@@ -72,9 +72,9 @@ MODEL_INFO = {
72
  def _padding_efetivo():
73
  """Proves the padding reached LetterBox instead of assuming it did.
74
 
75
- The D12 incident in this project was exactly an assumed patch that never
76
- reached the code that mattered, and 25 training runs were lost to it. Here the
77
- cost of being wrong is smaller and the check is one line, so it is checked.
78
  """
79
  import ultralytics.data.augment as A
80
  return getattr(A.LetterBox(new_shape=(640, 640)), "padding_value", None)
 
72
  def _padding_efetivo():
73
  """Proves the padding reached LetterBox instead of assuming it did.
74
 
75
+ A patch that is assumed to have applied, and has not, produces plausible
76
+ numbers rather than an error which is the hardest kind of defect to notice.
77
+ The check costs one line, so it is made rather than assumed.
78
  """
79
  import ultralytics.data.augment as A
80
  return getattr(A.LetterBox(new_shape=(640, 640)), "padding_value", None)
padding_patch.py CHANGED
@@ -1,130 +1,130 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- padding_patch.py — Controla EXPLICITAMENTE a cor de preenchimento (padding) do
4
- Ultralytics, para a ablation black-edge vs white-edge do R1.2.
5
-
6
- Por que e necessario: o Ultralytics 8.4.x codifica 114 (cinza) em TRES lugares
7
- independentes, nenhum exposto no cfg de treino:
8
- 1. Mosaic._mosaic4/_mosaic9 -> np.full(..., 114) [treino]
9
- 2. RandomPerspective -> borderValue=(114,114,114) [treino]
10
- 3. LetterBox -> padding_value=114 [val/inferencia]
11
-
12
- Patchear so o LetterBox contaminaria a ablation (o treino seguiria cinza).
13
- Este modulo intercepta os tres de forma auditavel e verificavel.
14
-
15
- Uso:
16
- import padding_patch; padding_patch.apply("black") # 0
17
- import padding_patch; padding_patch.apply("white") # 255
18
- import padding_patch; padding_patch.apply("gray") # 114 (default upstream)
19
- """
20
- import os
21
-
22
- import numpy as np
23
-
24
- VALUES = {"black": 0, "white": 255, "gray": 114}
25
- ENV = "WHST_PADDING_MODE"
26
- _APPLIED = None
27
-
28
-
29
- def worker_init(worker_id: int) -> None:
30
- """worker_init_fn que REAPLICA o patch dentro de cada worker process.
31
-
32
- ISTO E O CORACAO DA CORRECAO — sem isto a ablation nao acontece.
33
-
34
- No Windows o DataLoader usa SPAWN: cada worker e um processo novo, que nao
35
- herda monkeypatch nenhum do pai. Diagnosticado em 2026-07-27: os workers
36
- reportavam `np.full(114) -> 114` enquanto o pai reportava 255, e as 100
37
- epocas de treino de black e white sairam com loss BIT-IDENTICA.
38
-
39
- O LetterBox escapava do problema por acidente — o padding_value vira atributo
40
- de instancia no __init__ (executado no pai) e viaja no pickle do dataset. Por
41
- isso a VALIDACAO diferia e o TREINO nao: exatamente o padrao observado.
42
-
43
- Esta funcao e passada como `worker_init_fn`. Ela e picklada POR REFERENCIA
44
- (`padding_patch.worker_init`), o que obriga o worker a importar este modulo —
45
- e entao o patch e aplicado la dentro, antes do primeiro batch. O modo vem da
46
- variavel de ambiente, que processos filhos herdam.
47
- """
48
- modo = os.environ.get(ENV)
49
- if modo in VALUES:
50
- apply(modo, _em_worker=True)
51
- # preserva o comportamento original do Ultralytics
52
- try:
53
- import random
54
-
55
- import torch
56
- s = torch.initial_seed() % 2 ** 32
57
- np.random.seed(s)
58
- random.seed(s)
59
- except Exception:
60
- pass
61
-
62
-
63
- def apply(mode: str, _em_worker: bool = False):
64
- """Aplica o valor de padding globalmente. Retorna o valor numerico usado."""
65
- global _APPLIED
66
- if mode not in VALUES:
67
- raise ValueError(f"modo invalido: {mode} (use {list(VALUES)})")
68
- fill = VALUES[mode]
69
- os.environ[ENV] = mode # herdado pelos processos filhos (spawn)
70
-
71
- import ultralytics.data.augment as A
72
-
73
- # ---- 1) LetterBox: forca o default de padding_value ----
74
- if not hasattr(A.LetterBox, "_orig_init"):
75
- A.LetterBox._orig_init = A.LetterBox.__init__
76
-
77
- def _lb_init(self, *args, **kw):
78
- kw["padding_value"] = fill
79
- return A.LetterBox._orig_init(self, *args, **kw)
80
-
81
- A.LetterBox.__init__ = _lb_init
82
-
83
- # ---- 2) RandomPerspective: borderValue do warp ----
84
- cv2 = A.cv2
85
- if not hasattr(cv2, "_orig_warpAffine"):
86
- cv2._orig_warpAffine = cv2.warpAffine
87
- cv2._orig_warpPerspective = cv2.warpPerspective
88
-
89
- # guard POR VALOR (==114), simetrico ao _full: so intercepta o preenchimento
90
- # cinza da imagem em RandomPerspective. NAO toca o borderValue=255 usado por
91
- # apply_semantic para a mascara semantica (latente neste pipeline, mas blinda).
92
- def _wa(src, M, dsize, *a, **kw):
93
- if kw.get("borderValue") == (114, 114, 114):
94
- kw["borderValue"] = (fill,) * 3
95
- return cv2._orig_warpAffine(src, M, dsize, *a, **kw)
96
-
97
- def _wp(src, M, dsize, *a, **kw):
98
- if kw.get("borderValue") == (114, 114, 114):
99
- kw["borderValue"] = (fill,) * 3
100
- return cv2._orig_warpPerspective(src, M, dsize, *a, **kw)
101
-
102
- cv2.warpAffine = _wa
103
- cv2.warpPerspective = _wp
104
-
105
- # ---- 3) Mosaic: canvas np.full(..., 114) ----
106
- if not hasattr(A.np, "_orig_full"):
107
- A.np._orig_full = np.full
108
-
109
- def _full(shape, fill_value, *a, **kw):
110
- # so intercepta o canvas cinza do mosaico; nao mexe em outros np.full
111
- if isinstance(fill_value, (int, np.integer)) and int(fill_value) == 114:
112
- fill_value = fill
113
- return A.np._orig_full(shape, fill_value, *a, **kw)
114
-
115
- A.np.full = _full
116
-
117
- # ---- 4) worker_init_fn: leva o patch para dentro dos workers ----
118
- # Sem isto, os itens 2 e 3 nao existem no processo que de fato monta o batch.
119
- if not _em_worker:
120
- import ultralytics.data.build as B
121
- if getattr(B, "_padding_worker_init", None) is not worker_init:
122
- B.seed_worker = worker_init
123
- B._padding_worker_init = worker_init
124
-
125
- _APPLIED = mode
126
- return fill
127
-
128
-
129
- def applied():
130
- return _APPLIED
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ padding_patch.py — Controla EXPLICITAMENTE a cor de preenchimento (padding) do
4
+ Ultralytics, para a ablation black-edge vs white-edge.
5
+
6
+ Por que e necessario: o Ultralytics 8.4.x codifica 114 (cinza) em TRES lugares
7
+ independentes, nenhum exposto no cfg de treino:
8
+ 1. Mosaic._mosaic4/_mosaic9 -> np.full(..., 114) [treino]
9
+ 2. RandomPerspective -> borderValue=(114,114,114) [treino]
10
+ 3. LetterBox -> padding_value=114 [val/inferencia]
11
+
12
+ Patchear so o LetterBox contaminaria a ablation (o treino seguiria cinza).
13
+ Este modulo intercepta os tres de forma auditavel e verificavel.
14
+
15
+ Uso:
16
+ import padding_patch; padding_patch.apply("black") # 0
17
+ import padding_patch; padding_patch.apply("white") # 255
18
+ import padding_patch; padding_patch.apply("gray") # 114 (default upstream)
19
+ """
20
+ import os
21
+
22
+ import numpy as np
23
+
24
+ VALUES = {"black": 0, "white": 255, "gray": 114}
25
+ ENV = "WHST_PADDING_MODE"
26
+ _APPLIED = None
27
+
28
+
29
+ def worker_init(worker_id: int) -> None:
30
+ """worker_init_fn que REAPLICA o patch dentro de cada worker process.
31
+
32
+ ISTO E O CORACAO DA CORRECAO — sem isto a ablation nao acontece.
33
+
34
+ No Windows o DataLoader usa SPAWN: cada worker e um processo novo, que nao
35
+ herda monkeypatch nenhum do pai. Diagnosticado em 2026-07-27: os workers
36
+ reportavam `np.full(114) -> 114` enquanto o pai reportava 255, e as 100
37
+ epocas de treino de black e white sairam com loss BIT-IDENTICA.
38
+
39
+ O LetterBox escapava do problema por acidente — o padding_value vira atributo
40
+ de instancia no __init__ (executado no pai) e viaja no pickle do dataset. Por
41
+ isso a VALIDACAO diferia e o TREINO nao: exatamente o padrao observado.
42
+
43
+ Esta funcao e passada como `worker_init_fn`. Ela e picklada POR REFERENCIA
44
+ (`padding_patch.worker_init`), o que obriga o worker a importar este modulo —
45
+ e entao o patch e aplicado la dentro, antes do primeiro batch. O modo vem da
46
+ variavel de ambiente, que processos filhos herdam.
47
+ """
48
+ modo = os.environ.get(ENV)
49
+ if modo in VALUES:
50
+ apply(modo, _em_worker=True)
51
+ # preserva o comportamento original do Ultralytics
52
+ try:
53
+ import random
54
+
55
+ import torch
56
+ s = torch.initial_seed() % 2 ** 32
57
+ np.random.seed(s)
58
+ random.seed(s)
59
+ except Exception:
60
+ pass
61
+
62
+
63
+ def apply(mode: str, _em_worker: bool = False):
64
+ """Aplica o valor de padding globalmente. Retorna o valor numerico usado."""
65
+ global _APPLIED
66
+ if mode not in VALUES:
67
+ raise ValueError(f"modo invalido: {mode} (use {list(VALUES)})")
68
+ fill = VALUES[mode]
69
+ os.environ[ENV] = mode # herdado pelos processos filhos (spawn)
70
+
71
+ import ultralytics.data.augment as A
72
+
73
+ # ---- 1) LetterBox: forca o default de padding_value ----
74
+ if not hasattr(A.LetterBox, "_orig_init"):
75
+ A.LetterBox._orig_init = A.LetterBox.__init__
76
+
77
+ def _lb_init(self, *args, **kw):
78
+ kw["padding_value"] = fill
79
+ return A.LetterBox._orig_init(self, *args, **kw)
80
+
81
+ A.LetterBox.__init__ = _lb_init
82
+
83
+ # ---- 2) RandomPerspective: borderValue do warp ----
84
+ cv2 = A.cv2
85
+ if not hasattr(cv2, "_orig_warpAffine"):
86
+ cv2._orig_warpAffine = cv2.warpAffine
87
+ cv2._orig_warpPerspective = cv2.warpPerspective
88
+
89
+ # guard POR VALOR (==114), simetrico ao _full: so intercepta o preenchimento
90
+ # cinza da imagem em RandomPerspective. NAO toca o borderValue=255 usado por
91
+ # apply_semantic para a mascara semantica (latente neste pipeline, mas blinda).
92
+ def _wa(src, M, dsize, *a, **kw):
93
+ if kw.get("borderValue") == (114, 114, 114):
94
+ kw["borderValue"] = (fill,) * 3
95
+ return cv2._orig_warpAffine(src, M, dsize, *a, **kw)
96
+
97
+ def _wp(src, M, dsize, *a, **kw):
98
+ if kw.get("borderValue") == (114, 114, 114):
99
+ kw["borderValue"] = (fill,) * 3
100
+ return cv2._orig_warpPerspective(src, M, dsize, *a, **kw)
101
+
102
+ cv2.warpAffine = _wa
103
+ cv2.warpPerspective = _wp
104
+
105
+ # ---- 3) Mosaic: canvas np.full(..., 114) ----
106
+ if not hasattr(A.np, "_orig_full"):
107
+ A.np._orig_full = np.full
108
+
109
+ def _full(shape, fill_value, *a, **kw):
110
+ # so intercepta o canvas cinza do mosaico; nao mexe em outros np.full
111
+ if isinstance(fill_value, (int, np.integer)) and int(fill_value) == 114:
112
+ fill_value = fill
113
+ return A.np._orig_full(shape, fill_value, *a, **kw)
114
+
115
+ A.np.full = _full
116
+
117
+ # ---- 4) worker_init_fn: leva o patch para dentro dos workers ----
118
+ # Sem isto, os itens 2 e 3 nao existem no processo que de fato monta o batch.
119
+ if not _em_worker:
120
+ import ultralytics.data.build as B
121
+ if getattr(B, "_padding_worker_init", None) is not worker_init:
122
+ B.seed_worker = worker_init
123
+ B._padding_worker_init = worker_init
124
+
125
+ _APPLIED = mode
126
+ return fill
127
+
128
+
129
+ def applied():
130
+ return _APPLIED