uti712s 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
uti712s-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: uti712s
3
+ Version: 0.1.0
4
+ Summary: Extrae la matriz en °C de los BMP de la UNI-T UTi712S y muestra la temperatura de cada punto.
5
+ Author: Pablo Ventura
6
+ Project-URL: Homepage, https://github.com/pablogventura/uti712s
7
+ Project-URL: Repository, https://github.com/pablogventura/uti712s
8
+ Keywords: uti712s,uni-t,thermal,bmp
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Provides-Extra: png
15
+ Requires-Dist: pillow; extra == "png"
16
+
17
+ # UTi712S
18
+
19
+ Extrae la matriz térmica de los BMP que guarda la cámara UNI-T UTi712S, la convierte a grados Celsius con la misma escala que usa el software de la cámara, y permite consultar cada punto en un visor.
20
+
21
+ El archivo no trae la lectura del sensor. Trae la imagen de pantalla, de 8 bits, y tres temperaturas puntuales (mínima, máxima y central). La matriz en °C es la interpolación que arma `ImageFile.dll` a partir de esos datos. Sirve para reproducir lo que muestra el programa oficial. No mejora la medición.
22
+
23
+ ## La cámara
24
+
25
+ La UTi712S guarda las capturas en BMP. El sensor es de 120×90. La pantalla es de 320×240 y el archivo sale en 240×320, el mismo recuadro girado a vertical. El rango publicado por UNI-T es de -20 °C a 400 °C, con exactitud de ±2 °C o ±2 %, el que sea mayor. La emisividad de fábrica es 0,95.
26
+
27
+ Esas tres cifras (rango, exactitud, emisividad) describen los puntos que mide la cámara. No describen el byte de cada píxel.
28
+
29
+ ## Instalación
30
+
31
+ Hace falta Python 3.10 o posterior. Con [pipx](https://pipx.pypa.io/):
32
+
33
+ ```bash
34
+ pipx install git+https://github.com/pablogventura/uti712s.git
35
+ ```
36
+
37
+ Desde una copia local del repositorio:
38
+
39
+ ```bash
40
+ pipx install .
41
+ ```
42
+
43
+ Quedan dos comandos: `uti712s-extract` y `uti712s-view`.
44
+
45
+ Pillow es opcional. Sin él se escriben el JSON y los CSV, y no los PNG. El extra se llama `png`. En el entorno de pipx:
46
+
47
+ ```bash
48
+ pipx inject uti712s pillow
49
+ ```
50
+
51
+ El visor usa tkinter, que no se instala por pip. En Debian o Ubuntu: `sudo apt install python3-tk`. Tiene que estar en el mismo Python que usa pipx.
52
+
53
+ ## Uso
54
+
55
+ ```bash
56
+ uti712s-extract 'images/IMG 0001.bmp'
57
+ ```
58
+
59
+ Sin instalar, desde el repositorio:
60
+
61
+ ```bash
62
+ python3 uti712s_extract.py 'images/IMG 0001.bmp'
63
+ ```
64
+
65
+ Los archivos salen al lado del BMP:
66
+
67
+ | Archivo | Contenido |
68
+ |---|---|
69
+ | `IMG 0001_metadatos.json` | Temperaturas puntuales, anclas, modo y píxeles de control |
70
+ | `IMG 0001_intensidades.csv` | Matriz de 8 bits, una fila de imagen por línea, sin encabezado |
71
+ | `IMG 0001_celsius.csv` | La misma grilla en °C, ocho decimales |
72
+ | `IMG 0001_intensidades.png` | La matriz en gris, sin la interfaz de la cámara |
73
+ | `IMG 0001_termica_sin_indicadores.png` | La misma matriz coloreada con la paleta embebida |
74
+
75
+ La fila 0 es el borde de arriba. En modo 7, que es el de las capturas de `images/`, el orden es el del archivo.
76
+
77
+ ## Desde Python
78
+
79
+ `read_capture` lee el BMP y no escribe archivos. `celsius_at` calcula un punto con la escala de dos tramos. `extract` escribe el JSON, los CSV y, si Pillow está instalado, los PNG.
80
+
81
+ ```python
82
+ from pathlib import Path
83
+ from uti712s_extract import read_capture, extract
84
+
85
+ capture = read_capture(Path("images/IMG 0001.bmp"))
86
+ print(capture.celsius_at(119, 159))
87
+ extract(Path("images/IMG 0001.bmp"))
88
+ ```
89
+
90
+ ## Visor
91
+
92
+ Abre la escena térmica sin la interfaz de la cámara y muestra la temperatura estimada de cada píxel. Usa la paleta embebida y la misma escala que el CSV. Abrir un BMP no escribe archivos.
93
+
94
+ ```bash
95
+ uti712s-view
96
+ uti712s-view 'images/IMG 0001.bmp'
97
+ ```
98
+
99
+ Sin instalar, desde el repositorio: `python3 uti712s_view.py` y `python3 uti712s_view.py 'images/IMG 0001.bmp'`.
100
+
101
+ Archivo, Abrir, carga otro BMP. La rueda del mouse sobre la imagen cambia el zoom, de 1 a 6. Cada píxel de origen sigue siendo un bloque. La rueda sobre el panel desplaza el texto, si no entra en la ventana.
102
+
103
+ El panel tiene tres bloques.
104
+
105
+ **Bajo el mouse.** La temperatura en °C, con un decimal, del píxel que está bajo el puntero. También muestra la columna y la fila. La columna se cuenta desde la izquierda y la fila desde arriba. El brillo es el byte de 0 a 255 guardado en el archivo. La temperatura es la estimación en °C. Si el puntero no está sobre la imagen, el bloque pide pasar el mouse por ella.
106
+
107
+ **Punto fijado.** Un clic deja esa lectura marcada, aunque el mouse se mueva. Otro clic, o Escape, la suelta. El punto se dibuja con un cuadrado rosa, un poco más grande que el píxel.
108
+
109
+ **Mediciones de la cámara.** Los tres valores del pie, la emisividad y el modo.
110
+
111
+ - Mínima y máxima: el punto más frío y el más caliente que midió la cámara. No se marcan en la imagen. Esas coordenadas a veces caen fuera, y no siempre coinciden con el byte más frío o más caliente.
112
+ - Central: la lectura del centro. El cuadrado celeste queda en ese valor. Los demás píxeles se estiman a partir de estos tres.
113
+ - Emisividad: la que tenía configurada la cámara. En estas capturas es 0,95, el valor de fábrica.
114
+ - Modo 7: la foto no se gira. Arriba es arriba.
115
+
116
+ Hay dos marcas fijas. El cuadrado celeste es el píxel de la curva. En estas capturas está en la columna 119, fila 159, y su temperatura es la central. La cruz amarilla es el centro que dibuja el software, en la columna 120, fila 160. En modo 7 son píxeles distintos. Si el mouse cae en una, el panel lo dice.
117
+
118
+ ## Qué hay en el BMP
119
+
120
+ Las 32 capturas de `images/` miden 230995 bytes y comparten esta forma: BMP de 16 bits, 240×320, sin compresión, y un bloque extra de 77341 bytes.
121
+
122
+ El programa oficial no lee el ancho ni el alto del encabezado. Fija 240×320 y empieza la zona térmica en el offset `0x25839` (153657). Este script lee el tamaño en el encabezado y entra en `bfSize + 3`. En un BMP de 16 bits y 240×320, `bfSize` vale 153654 (54 bytes de encabezado más 153600 de píxeles), así que `bfSize + 3` cae en el mismo byte. Además exige que el bloque extra mida `3 + ancho*alto + 512 + 26` y que arranque con tres ceros.
123
+
124
+ ```text
125
+ 0 encabezado BMP y píxeles de 16 bits 153654 bytes
126
+ 153654 00 00 00 3 bytes
127
+ 153657 intensidades, 1 byte por píxel 240 * 320 = 76800
128
+ 230457 paleta RGB565, 256 colores 512
129
+ 230969 pie 26
130
+ 230995 fin
131
+ ```
132
+
133
+ El BMP visible es la pantalla: cruz, textos, batería, fecha y barra de color. La matriz de 8 bits es la escena térmica sin esa interfaz. El origen vertical coincide con la foto: arriba es arriba.
134
+
135
+ La paleta embebida no reproduce los píxeles del BMP. En estas capturas la coincidencia exacta es prácticamente nula. La pantalla lleva la interfaz encima y usa otro estirado de color. El PNG sin indicadores muestra la escena limpia, con la paleta del archivo, no una copia de la pantalla.
136
+
137
+ ## Paleta
138
+
139
+ Son 256 colores de 16 bits, little-endian, RGB565:
140
+
141
+ | Canal | Bits | Expansión que usa el software |
142
+ |---|---|---|
143
+ | Rojo | 15..11 | los 5 bits, desplazados 3 a la izquierda (máximo 248) |
144
+ | Verde | 10..5 | los 6 bits, desplazados 2 a la izquierda |
145
+ | Azul | 4..0 | los 5 bits, desplazados 3 a la izquierda |
146
+
147
+ El índice es el byte de intensidad, sin estirar al rango de la escena.
148
+
149
+ ## Pie de 26 bytes
150
+
151
+ Little-endian. Las temperaturas son enteros con signo, en décimas. Las coordenadas son enteros sin signo.
152
+
153
+ | Offset | Tipo | Campo |
154
+ |---|---|---|
155
+ | 0 | uint8 | Unidad. 0 es °C, 1 es °F |
156
+ | 1 | int16 | Temperatura máxima × 10 |
157
+ | 3 | int16 | Temperatura mínima × 10 |
158
+ | 5 | uint8 | Ancla caliente: intensidad que el software iguala a la máxima |
159
+ | 6 | uint8 | Ancla fría: intensidad que el software iguala a la mínima |
160
+ | 7 | int16 | Temperatura central × 10 |
161
+ | 9 | uint8 | Emisividad × 100 |
162
+ | 10 | uint8 | Modo de orientación |
163
+ | 11 | 3 bytes | El software no los lee. En estas capturas valen 0 |
164
+ | 14 | uint16 | X del mínimo |
165
+ | 16 | uint16 | Y del mínimo |
166
+ | 18 | uint16 | X del máximo |
167
+ | 20 | uint16 | Y del máximo |
168
+ | 22 | uint16 | X del centro guardado |
169
+ | 24 | uint16 | Y del centro guardado |
170
+
171
+ En las 32 capturas: unidad °C, emisividad 0,95, modo 7, ancla fría 0, ancla caliente igual al máximo de la matriz, centro guardado en (120, 160).
172
+
173
+ El byte 5 y el byte 6 juntos parecen un int16. No lo son. El software los lee por separado. Como el byte 6 es 0, ese int16 coincide con la intensidad máxima, y por eso un parseo anterior lo veía como "el máximo de la matriz" metido en un solo campo.
174
+
175
+ ## Modo de orientación
176
+
177
+ El modo es el byte 10. Cambia el orden de la matriz antes de convertir, y también cómo se interpretan las coordenadas de mínimo y máximo. El centro dibujado no sale del pie: el software lo fija según el modo.
178
+
179
+ | Modo | Matriz | Mínimo y máximo | Centro dibujado |
180
+ |---|---|---|---|
181
+ | 1 | `salida[fila, col] = origen[alto * (ancho - col - 1) + fila]` | `x' = ancho - y - 1`, `y' = x - 1` | `(alto/2 - 1, ancho/2 - 1)` = (159, 119) |
182
+ | 2 y 5 | el buffer al revés | `x' = ancho - x - 1`, `y' = alto - y - 1` | `(ancho/2 - 1, alto/2 - 1)` = (119, 159) |
183
+ | otro, incluido el 7 | se deja como está | se dejan como están | `(ancho/2, alto/2)` = (120, 160) |
184
+
185
+ Las 32 capturas son modo 7. Los modos 1, 2 y 5 están implementados como en el DLL, pero este conjunto no los trae.
186
+
187
+ Las coordenadas de mínimo y máximo pueden caer fuera del raster. En estas fotos aparece `x = 240` o `y = 321`. El software las guarda igual, sin recortarlas. A veces el marcador cae a unos pocos píxeles del extremo de intensidad, y a veces a decenas. La temperatura puntual y el byte más frío o más caliente no son el mismo dato: el byte ya pasó por el realce de contraste.
188
+
189
+ ## Escala a °C
190
+
191
+ El píxel de la curva no es la cruz. Después de reordenar, el software toma la intensidad `Ic` en `(ancho/2 - 1, alto/2 - 1)`. En 240×320 eso es (119, 159), un píxel arriba y a la izquierda de la cruz (120, 160).
192
+
193
+ Con las temperaturas todavía en décimas:
194
+
195
+ - si la intensidad `p` es mayor o igual que `Ic`, la recta une `(ancla caliente, máxima)` con `(Ic, central)`;
196
+ - si es menor, une `(Ic, central)` con `(ancla fría, mínima)`.
197
+
198
+ ```text
199
+ raw = p * (Tb - Ta) / (Ib - Ia) + (Ta * Ib - Tb * Ia) / (Ib - Ia)
200
+ °C = raw / 10
201
+ ```
202
+
203
+ `(Ia, Ta)` es el primer punto del tramo y `(Ib, Tb)` el segundo. Si el archivo está en Fahrenheit, este script pasa el resultado a Celsius con `(°F - 32) / 1.8`. El programa oficial, en ese caso, deja la primera matriz en la unidad del archivo y arma otra con `valor * 1.8 + 32`. En las capturas de `images/` la unidad es °C, así que el CSV coincide con la matriz del programa.
204
+
205
+ El píxel de la curva queda, por construcción, en la temperatura central. La cruz no. En `IMG 0001.bmp`:
206
+
207
+ | | Intensidad | °C |
208
+ |---|---|---|
209
+ | Curva (119, 159) | 17 | 13,3 |
210
+ | Cruz (120, 160) | 16 | 12,84705882 |
211
+ | Ancla fría | 0 | 5,6 |
212
+ | Píxel más frío de esa foto | 1 | 6,05294118 |
213
+ | Ancla caliente | 253 | 21,6 |
214
+
215
+ La mínima del pie es 5,6 °C y la máxima 21,6 °C. El píxel más frío no llega a 5,6 °C porque la ancla fría está en la intensidad 0 y ese píxel vale 1.
216
+
217
+ ## Lo que no se puede recuperar
218
+
219
+ El sensor entrega bastante más que 8 bits. Antes de guardar, la cámara realza el contraste y estira el resultado a 240×320. Muchas temperaturas distintas caen en el mismo byte, y la tabla de ese realce no viaja en el archivo. Con tres puntos medidos se puede clavar la curva en la mínima, la máxima y el centro. Cualquier otro píxel sigue siendo una estimación, y otra curva que pase por los mismos tres puntos no está mejor medida.
220
+
221
+ El error acompaña el rango de la escena. Con menos de 2 °C de diferencia, como en `IMG 0008.bmp` o `IMG 0031.bmp`, queda por debajo de 0,1 °C. En `IMG 0001.bmp` la cruz ya difiere 0,45 °C de la medición central. En una escena de cientos de grados la diferencia es de decenas de grados.
222
+
223
+ `IMG 0003.bmp` guarda una máxima de 431,7 °C y la pantalla muestra OL. `IMG 0004.bmp` guarda 429,6 °C, por encima del rango de -20 °C a 400 °C publicado para esta cámara. Esos máximos están en el archivo, y la cámara no los trata como una lectura normal.
224
+
225
+ Para un punto que importe, la lectura que vale es la del puntero en la cámara.
226
+
227
+ ## Software oficial y otros parseos
228
+
229
+ UNI-T publica el UTi712S PC Software (junio de 2022, un `Setup.exe` de Windows dentro del zip). La lógica de este formato está en `ImageFile.dll`, en `refreshParam` y en el pasaje de intensidad a temperatura. Este repositorio no incluye ese instalador.
230
+
231
+ El programa oficial hace esta misma escala de dos tramos. No tiene un plano radiométrico escondido.
232
+
233
+ [UNI-T Thermal Utilities](https://github.com/Santi-hr/UNI-T-Thermal-Utilities) documenta un BMP parecido, el de la UTi260B. No sirve para estos archivos: aquella cámara guarda BMP de 24 bits, sin los 3 bytes de relleno, y ordena primero el punto máximo. El autor describe el mismo límite en [temperature_issue.md](https://github.com/Santi-hr/UNI-T-Thermal-Utilities/blob/main/docs/temperature_issue.md): la imagen embebida ya viene realzada, y en el postproceso solo se pueden fiar la mínima, la máxima y el centro.
234
+
235
+ ## Muestras
236
+
237
+ `images/` tiene 32 BMP sacados de una UTi712S, de septiembre de 2020 a agosto de 2026. Todas entran en el formato de arriba, en modo 7 y en °C. Sirven para comprobar el parseo y la escala. No son una calibración.
@@ -0,0 +1,221 @@
1
+ # UTi712S
2
+
3
+ Extrae la matriz térmica de los BMP que guarda la cámara UNI-T UTi712S, la convierte a grados Celsius con la misma escala que usa el software de la cámara, y permite consultar cada punto en un visor.
4
+
5
+ El archivo no trae la lectura del sensor. Trae la imagen de pantalla, de 8 bits, y tres temperaturas puntuales (mínima, máxima y central). La matriz en °C es la interpolación que arma `ImageFile.dll` a partir de esos datos. Sirve para reproducir lo que muestra el programa oficial. No mejora la medición.
6
+
7
+ ## La cámara
8
+
9
+ La UTi712S guarda las capturas en BMP. El sensor es de 120×90. La pantalla es de 320×240 y el archivo sale en 240×320, el mismo recuadro girado a vertical. El rango publicado por UNI-T es de -20 °C a 400 °C, con exactitud de ±2 °C o ±2 %, el que sea mayor. La emisividad de fábrica es 0,95.
10
+
11
+ Esas tres cifras (rango, exactitud, emisividad) describen los puntos que mide la cámara. No describen el byte de cada píxel.
12
+
13
+ ## Instalación
14
+
15
+ Hace falta Python 3.10 o posterior. Con [pipx](https://pipx.pypa.io/):
16
+
17
+ ```bash
18
+ pipx install git+https://github.com/pablogventura/uti712s.git
19
+ ```
20
+
21
+ Desde una copia local del repositorio:
22
+
23
+ ```bash
24
+ pipx install .
25
+ ```
26
+
27
+ Quedan dos comandos: `uti712s-extract` y `uti712s-view`.
28
+
29
+ Pillow es opcional. Sin él se escriben el JSON y los CSV, y no los PNG. El extra se llama `png`. En el entorno de pipx:
30
+
31
+ ```bash
32
+ pipx inject uti712s pillow
33
+ ```
34
+
35
+ El visor usa tkinter, que no se instala por pip. En Debian o Ubuntu: `sudo apt install python3-tk`. Tiene que estar en el mismo Python que usa pipx.
36
+
37
+ ## Uso
38
+
39
+ ```bash
40
+ uti712s-extract 'images/IMG 0001.bmp'
41
+ ```
42
+
43
+ Sin instalar, desde el repositorio:
44
+
45
+ ```bash
46
+ python3 uti712s_extract.py 'images/IMG 0001.bmp'
47
+ ```
48
+
49
+ Los archivos salen al lado del BMP:
50
+
51
+ | Archivo | Contenido |
52
+ |---|---|
53
+ | `IMG 0001_metadatos.json` | Temperaturas puntuales, anclas, modo y píxeles de control |
54
+ | `IMG 0001_intensidades.csv` | Matriz de 8 bits, una fila de imagen por línea, sin encabezado |
55
+ | `IMG 0001_celsius.csv` | La misma grilla en °C, ocho decimales |
56
+ | `IMG 0001_intensidades.png` | La matriz en gris, sin la interfaz de la cámara |
57
+ | `IMG 0001_termica_sin_indicadores.png` | La misma matriz coloreada con la paleta embebida |
58
+
59
+ La fila 0 es el borde de arriba. En modo 7, que es el de las capturas de `images/`, el orden es el del archivo.
60
+
61
+ ## Desde Python
62
+
63
+ `read_capture` lee el BMP y no escribe archivos. `celsius_at` calcula un punto con la escala de dos tramos. `extract` escribe el JSON, los CSV y, si Pillow está instalado, los PNG.
64
+
65
+ ```python
66
+ from pathlib import Path
67
+ from uti712s_extract import read_capture, extract
68
+
69
+ capture = read_capture(Path("images/IMG 0001.bmp"))
70
+ print(capture.celsius_at(119, 159))
71
+ extract(Path("images/IMG 0001.bmp"))
72
+ ```
73
+
74
+ ## Visor
75
+
76
+ Abre la escena térmica sin la interfaz de la cámara y muestra la temperatura estimada de cada píxel. Usa la paleta embebida y la misma escala que el CSV. Abrir un BMP no escribe archivos.
77
+
78
+ ```bash
79
+ uti712s-view
80
+ uti712s-view 'images/IMG 0001.bmp'
81
+ ```
82
+
83
+ Sin instalar, desde el repositorio: `python3 uti712s_view.py` y `python3 uti712s_view.py 'images/IMG 0001.bmp'`.
84
+
85
+ Archivo, Abrir, carga otro BMP. La rueda del mouse sobre la imagen cambia el zoom, de 1 a 6. Cada píxel de origen sigue siendo un bloque. La rueda sobre el panel desplaza el texto, si no entra en la ventana.
86
+
87
+ El panel tiene tres bloques.
88
+
89
+ **Bajo el mouse.** La temperatura en °C, con un decimal, del píxel que está bajo el puntero. También muestra la columna y la fila. La columna se cuenta desde la izquierda y la fila desde arriba. El brillo es el byte de 0 a 255 guardado en el archivo. La temperatura es la estimación en °C. Si el puntero no está sobre la imagen, el bloque pide pasar el mouse por ella.
90
+
91
+ **Punto fijado.** Un clic deja esa lectura marcada, aunque el mouse se mueva. Otro clic, o Escape, la suelta. El punto se dibuja con un cuadrado rosa, un poco más grande que el píxel.
92
+
93
+ **Mediciones de la cámara.** Los tres valores del pie, la emisividad y el modo.
94
+
95
+ - Mínima y máxima: el punto más frío y el más caliente que midió la cámara. No se marcan en la imagen. Esas coordenadas a veces caen fuera, y no siempre coinciden con el byte más frío o más caliente.
96
+ - Central: la lectura del centro. El cuadrado celeste queda en ese valor. Los demás píxeles se estiman a partir de estos tres.
97
+ - Emisividad: la que tenía configurada la cámara. En estas capturas es 0,95, el valor de fábrica.
98
+ - Modo 7: la foto no se gira. Arriba es arriba.
99
+
100
+ Hay dos marcas fijas. El cuadrado celeste es el píxel de la curva. En estas capturas está en la columna 119, fila 159, y su temperatura es la central. La cruz amarilla es el centro que dibuja el software, en la columna 120, fila 160. En modo 7 son píxeles distintos. Si el mouse cae en una, el panel lo dice.
101
+
102
+ ## Qué hay en el BMP
103
+
104
+ Las 32 capturas de `images/` miden 230995 bytes y comparten esta forma: BMP de 16 bits, 240×320, sin compresión, y un bloque extra de 77341 bytes.
105
+
106
+ El programa oficial no lee el ancho ni el alto del encabezado. Fija 240×320 y empieza la zona térmica en el offset `0x25839` (153657). Este script lee el tamaño en el encabezado y entra en `bfSize + 3`. En un BMP de 16 bits y 240×320, `bfSize` vale 153654 (54 bytes de encabezado más 153600 de píxeles), así que `bfSize + 3` cae en el mismo byte. Además exige que el bloque extra mida `3 + ancho*alto + 512 + 26` y que arranque con tres ceros.
107
+
108
+ ```text
109
+ 0 encabezado BMP y píxeles de 16 bits 153654 bytes
110
+ 153654 00 00 00 3 bytes
111
+ 153657 intensidades, 1 byte por píxel 240 * 320 = 76800
112
+ 230457 paleta RGB565, 256 colores 512
113
+ 230969 pie 26
114
+ 230995 fin
115
+ ```
116
+
117
+ El BMP visible es la pantalla: cruz, textos, batería, fecha y barra de color. La matriz de 8 bits es la escena térmica sin esa interfaz. El origen vertical coincide con la foto: arriba es arriba.
118
+
119
+ La paleta embebida no reproduce los píxeles del BMP. En estas capturas la coincidencia exacta es prácticamente nula. La pantalla lleva la interfaz encima y usa otro estirado de color. El PNG sin indicadores muestra la escena limpia, con la paleta del archivo, no una copia de la pantalla.
120
+
121
+ ## Paleta
122
+
123
+ Son 256 colores de 16 bits, little-endian, RGB565:
124
+
125
+ | Canal | Bits | Expansión que usa el software |
126
+ |---|---|---|
127
+ | Rojo | 15..11 | los 5 bits, desplazados 3 a la izquierda (máximo 248) |
128
+ | Verde | 10..5 | los 6 bits, desplazados 2 a la izquierda |
129
+ | Azul | 4..0 | los 5 bits, desplazados 3 a la izquierda |
130
+
131
+ El índice es el byte de intensidad, sin estirar al rango de la escena.
132
+
133
+ ## Pie de 26 bytes
134
+
135
+ Little-endian. Las temperaturas son enteros con signo, en décimas. Las coordenadas son enteros sin signo.
136
+
137
+ | Offset | Tipo | Campo |
138
+ |---|---|---|
139
+ | 0 | uint8 | Unidad. 0 es °C, 1 es °F |
140
+ | 1 | int16 | Temperatura máxima × 10 |
141
+ | 3 | int16 | Temperatura mínima × 10 |
142
+ | 5 | uint8 | Ancla caliente: intensidad que el software iguala a la máxima |
143
+ | 6 | uint8 | Ancla fría: intensidad que el software iguala a la mínima |
144
+ | 7 | int16 | Temperatura central × 10 |
145
+ | 9 | uint8 | Emisividad × 100 |
146
+ | 10 | uint8 | Modo de orientación |
147
+ | 11 | 3 bytes | El software no los lee. En estas capturas valen 0 |
148
+ | 14 | uint16 | X del mínimo |
149
+ | 16 | uint16 | Y del mínimo |
150
+ | 18 | uint16 | X del máximo |
151
+ | 20 | uint16 | Y del máximo |
152
+ | 22 | uint16 | X del centro guardado |
153
+ | 24 | uint16 | Y del centro guardado |
154
+
155
+ En las 32 capturas: unidad °C, emisividad 0,95, modo 7, ancla fría 0, ancla caliente igual al máximo de la matriz, centro guardado en (120, 160).
156
+
157
+ El byte 5 y el byte 6 juntos parecen un int16. No lo son. El software los lee por separado. Como el byte 6 es 0, ese int16 coincide con la intensidad máxima, y por eso un parseo anterior lo veía como "el máximo de la matriz" metido en un solo campo.
158
+
159
+ ## Modo de orientación
160
+
161
+ El modo es el byte 10. Cambia el orden de la matriz antes de convertir, y también cómo se interpretan las coordenadas de mínimo y máximo. El centro dibujado no sale del pie: el software lo fija según el modo.
162
+
163
+ | Modo | Matriz | Mínimo y máximo | Centro dibujado |
164
+ |---|---|---|---|
165
+ | 1 | `salida[fila, col] = origen[alto * (ancho - col - 1) + fila]` | `x' = ancho - y - 1`, `y' = x - 1` | `(alto/2 - 1, ancho/2 - 1)` = (159, 119) |
166
+ | 2 y 5 | el buffer al revés | `x' = ancho - x - 1`, `y' = alto - y - 1` | `(ancho/2 - 1, alto/2 - 1)` = (119, 159) |
167
+ | otro, incluido el 7 | se deja como está | se dejan como están | `(ancho/2, alto/2)` = (120, 160) |
168
+
169
+ Las 32 capturas son modo 7. Los modos 1, 2 y 5 están implementados como en el DLL, pero este conjunto no los trae.
170
+
171
+ Las coordenadas de mínimo y máximo pueden caer fuera del raster. En estas fotos aparece `x = 240` o `y = 321`. El software las guarda igual, sin recortarlas. A veces el marcador cae a unos pocos píxeles del extremo de intensidad, y a veces a decenas. La temperatura puntual y el byte más frío o más caliente no son el mismo dato: el byte ya pasó por el realce de contraste.
172
+
173
+ ## Escala a °C
174
+
175
+ El píxel de la curva no es la cruz. Después de reordenar, el software toma la intensidad `Ic` en `(ancho/2 - 1, alto/2 - 1)`. En 240×320 eso es (119, 159), un píxel arriba y a la izquierda de la cruz (120, 160).
176
+
177
+ Con las temperaturas todavía en décimas:
178
+
179
+ - si la intensidad `p` es mayor o igual que `Ic`, la recta une `(ancla caliente, máxima)` con `(Ic, central)`;
180
+ - si es menor, une `(Ic, central)` con `(ancla fría, mínima)`.
181
+
182
+ ```text
183
+ raw = p * (Tb - Ta) / (Ib - Ia) + (Ta * Ib - Tb * Ia) / (Ib - Ia)
184
+ °C = raw / 10
185
+ ```
186
+
187
+ `(Ia, Ta)` es el primer punto del tramo y `(Ib, Tb)` el segundo. Si el archivo está en Fahrenheit, este script pasa el resultado a Celsius con `(°F - 32) / 1.8`. El programa oficial, en ese caso, deja la primera matriz en la unidad del archivo y arma otra con `valor * 1.8 + 32`. En las capturas de `images/` la unidad es °C, así que el CSV coincide con la matriz del programa.
188
+
189
+ El píxel de la curva queda, por construcción, en la temperatura central. La cruz no. En `IMG 0001.bmp`:
190
+
191
+ | | Intensidad | °C |
192
+ |---|---|---|
193
+ | Curva (119, 159) | 17 | 13,3 |
194
+ | Cruz (120, 160) | 16 | 12,84705882 |
195
+ | Ancla fría | 0 | 5,6 |
196
+ | Píxel más frío de esa foto | 1 | 6,05294118 |
197
+ | Ancla caliente | 253 | 21,6 |
198
+
199
+ La mínima del pie es 5,6 °C y la máxima 21,6 °C. El píxel más frío no llega a 5,6 °C porque la ancla fría está en la intensidad 0 y ese píxel vale 1.
200
+
201
+ ## Lo que no se puede recuperar
202
+
203
+ El sensor entrega bastante más que 8 bits. Antes de guardar, la cámara realza el contraste y estira el resultado a 240×320. Muchas temperaturas distintas caen en el mismo byte, y la tabla de ese realce no viaja en el archivo. Con tres puntos medidos se puede clavar la curva en la mínima, la máxima y el centro. Cualquier otro píxel sigue siendo una estimación, y otra curva que pase por los mismos tres puntos no está mejor medida.
204
+
205
+ El error acompaña el rango de la escena. Con menos de 2 °C de diferencia, como en `IMG 0008.bmp` o `IMG 0031.bmp`, queda por debajo de 0,1 °C. En `IMG 0001.bmp` la cruz ya difiere 0,45 °C de la medición central. En una escena de cientos de grados la diferencia es de decenas de grados.
206
+
207
+ `IMG 0003.bmp` guarda una máxima de 431,7 °C y la pantalla muestra OL. `IMG 0004.bmp` guarda 429,6 °C, por encima del rango de -20 °C a 400 °C publicado para esta cámara. Esos máximos están en el archivo, y la cámara no los trata como una lectura normal.
208
+
209
+ Para un punto que importe, la lectura que vale es la del puntero en la cámara.
210
+
211
+ ## Software oficial y otros parseos
212
+
213
+ UNI-T publica el UTi712S PC Software (junio de 2022, un `Setup.exe` de Windows dentro del zip). La lógica de este formato está en `ImageFile.dll`, en `refreshParam` y en el pasaje de intensidad a temperatura. Este repositorio no incluye ese instalador.
214
+
215
+ El programa oficial hace esta misma escala de dos tramos. No tiene un plano radiométrico escondido.
216
+
217
+ [UNI-T Thermal Utilities](https://github.com/Santi-hr/UNI-T-Thermal-Utilities) documenta un BMP parecido, el de la UTi260B. No sirve para estos archivos: aquella cámara guarda BMP de 24 bits, sin los 3 bytes de relleno, y ordena primero el punto máximo. El autor describe el mismo límite en [temperature_issue.md](https://github.com/Santi-hr/UNI-T-Thermal-Utilities/blob/main/docs/temperature_issue.md): la imagen embebida ya viene realzada, y en el postproceso solo se pueden fiar la mínima, la máxima y el centro.
218
+
219
+ ## Muestras
220
+
221
+ `images/` tiene 32 BMP sacados de una UTi712S, de septiembre de 2020 a agosto de 2026. Todas entran en el formato de arriba, en modo 7 y en °C. Sirven para comprobar el parseo y la escala. No son una calibración.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "uti712s"
7
+ version = "0.1.0"
8
+ description = "Extrae la matriz en °C de los BMP de la UNI-T UTi712S y muestra la temperatura de cada punto."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "Pablo Ventura" }]
12
+ keywords = ["uti712s", "uni-t", "thermal", "bmp"]
13
+ classifiers = [
14
+ "Environment :: Console",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ ]
18
+ dependencies = []
19
+
20
+ [project.optional-dependencies]
21
+ png = ["pillow"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/pablogventura/uti712s"
25
+ Repository = "https://github.com/pablogventura/uti712s"
26
+
27
+ [project.scripts]
28
+ uti712s-extract = "uti712s_extract:main"
29
+ uti712s-view = "uti712s_view:main"
30
+
31
+ [tool.setuptools]
32
+ packages = []
33
+ py-modules = ["uti712s_extract", "uti712s_view"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+