sarapy 1.3.0__tar.gz → 2.0.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.
- {sarapy-1.3.0/sarapy.egg-info → sarapy-2.0.0}/PKG-INFO +20 -1
- {sarapy-1.3.0 → sarapy-2.0.0}/README.md +19 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/pyproject.toml +1 -1
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/dataProcessing/OpsProcessor.py +68 -114
- sarapy-2.0.0/sarapy/dataProcessing/TLMSensorDataProcessor.py +90 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/dataProcessing/TimeSeriesProcessor.py +19 -1
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/mlProcessors/FertilizerTransformer.py +16 -29
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/mlProcessors/PlantinClassifier.py +3 -3
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/mlProcessors/PlantinFMCreator.py +39 -59
- sarapy-2.0.0/sarapy/preprocessing/TransformInputData.py +140 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/preprocessing/TransformToOutputData.py +4 -17
- sarapy-2.0.0/sarapy/stats/__init__.py +1 -0
- sarapy-2.0.0/sarapy/stats/stats.py +258 -0
- sarapy-2.0.0/sarapy/utils/plotting.py +96 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/version.py +1 -1
- {sarapy-1.3.0 → sarapy-2.0.0/sarapy.egg-info}/PKG-INFO +20 -1
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy.egg-info/SOURCES.txt +4 -1
- sarapy-1.3.0/sarapy/dataProcessing/TLMSensorDataProcessor.py +0 -781
- sarapy-1.3.0/sarapy/preprocessing/TransformInputData.py +0 -110
- {sarapy-1.3.0 → sarapy-2.0.0}/LICENCE +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/__init__.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/dataProcessing/GeoProcessor.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/dataProcessing/__init__.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/mlProcessors/FertilizerFMCreator.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/mlProcessors/__init__.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/preprocessing/DistancesImputer.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/preprocessing/FertilizerImputer.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/preprocessing/__init__.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/utils/__init__.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/utils/amg_decoder.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/utils/amg_ppk.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy/utils/getRawOperations.py +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy.egg-info/dependency_links.txt +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy.egg-info/requires.txt +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/sarapy.egg-info/top_level.txt +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/setup.cfg +0 -0
- {sarapy-1.3.0 → sarapy-2.0.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sarapy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Home-page: https://github.com/lucasbaldezzari/sarapy
|
|
5
5
|
Author: Lucas Baldezzari
|
|
6
6
|
Author-email: Lucas Baldezzari <lmbaldezzari@gmail.com>
|
|
@@ -19,6 +19,25 @@ Requires-Dist: geopy
|
|
|
19
19
|
|
|
20
20
|
Library for processing SARAPICO project metadata of _AMG SA_.
|
|
21
21
|
|
|
22
|
+
#### Version 2.0.0
|
|
23
|
+
|
|
24
|
+
- Se realizan cambios en TransformInputData para que convierta los datos de telemetría a la forma de lista de diccionarios, como un JSON, para que sea más fácil de usar y entender. Además, a futuro, se espera que los datos de la electrónica se puedan enviar en este formato.
|
|
25
|
+
- Se realizan cambios en TLMSensorDataProcessor para que pueda recibir los datos de telemetría como una lista de diccionarios. La clase permite obtener los datos como un numpy array, o indexar por filas y columnas de forma similar a un DataFrame de pandas (de hecho se usa pandas para esto).
|
|
26
|
+
- Se generan cambios en PlantinFMCreator para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
|
|
27
|
+
- Se generan cambios en FertilizerTransformer para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
|
|
28
|
+
- Se generan cambios en OpsProcessor para poder trabajar con el formato nuevo de datos.
|
|
29
|
+
|
|
30
|
+
Los detalles de los cambios son comentados en el issue #37.
|
|
31
|
+
|
|
32
|
+
#### Version 1.4.0b1
|
|
33
|
+
|
|
34
|
+
- Se empieza a trabajar sobre librería estadística para poder detectar sensores saturados.
|
|
35
|
+
- Se empieza a trabajar sobre actualización para trabajar con los datos en la nueva modalidad luego de la migración.
|
|
36
|
+
|
|
37
|
+
#### Version 1.3.1
|
|
38
|
+
|
|
39
|
+
- Se modifica PlantinClassifier para tomar el umbral máximo de dst_pt en 4 y no en 7.
|
|
40
|
+
|
|
22
41
|
#### Version 1.3.0
|
|
23
42
|
|
|
24
43
|
- Se agrega funcionalidad para estimar la cantidad de gramos de fertilizante en base al nivel de distorsión de fertilizante arrojado por la electrónica. Se implementa mlProcessor.FertlizerFMCreator y mlProcessor.FertilizerTransformer. Además se modifican las clases OpsProcessor y TransformToOutputData para poder usar estas nuevas clases.
|
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Library for processing SARAPICO project metadata of _AMG SA_.
|
|
4
4
|
|
|
5
|
+
#### Version 2.0.0
|
|
6
|
+
|
|
7
|
+
- Se realizan cambios en TransformInputData para que convierta los datos de telemetría a la forma de lista de diccionarios, como un JSON, para que sea más fácil de usar y entender. Además, a futuro, se espera que los datos de la electrónica se puedan enviar en este formato.
|
|
8
|
+
- Se realizan cambios en TLMSensorDataProcessor para que pueda recibir los datos de telemetría como una lista de diccionarios. La clase permite obtener los datos como un numpy array, o indexar por filas y columnas de forma similar a un DataFrame de pandas (de hecho se usa pandas para esto).
|
|
9
|
+
- Se generan cambios en PlantinFMCreator para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
|
|
10
|
+
- Se generan cambios en FertilizerTransformer para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
|
|
11
|
+
- Se generan cambios en OpsProcessor para poder trabajar con el formato nuevo de datos.
|
|
12
|
+
|
|
13
|
+
Los detalles de los cambios son comentados en el issue #37.
|
|
14
|
+
|
|
15
|
+
#### Version 1.4.0b1
|
|
16
|
+
|
|
17
|
+
- Se empieza a trabajar sobre librería estadística para poder detectar sensores saturados.
|
|
18
|
+
- Se empieza a trabajar sobre actualización para trabajar con los datos en la nueva modalidad luego de la migración.
|
|
19
|
+
|
|
20
|
+
#### Version 1.3.1
|
|
21
|
+
|
|
22
|
+
- Se modifica PlantinClassifier para tomar el umbral máximo de dst_pt en 4 y no en 7.
|
|
23
|
+
|
|
5
24
|
#### Version 1.3.0
|
|
6
25
|
|
|
7
26
|
- Se agrega funcionalidad para estimar la cantidad de gramos de fertilizante en base al nivel de distorsión de fertilizante arrojado por la electrónica. Se implementa mlProcessor.FertlizerFMCreator y mlProcessor.FertilizerTransformer. Además se modifican las clases OpsProcessor y TransformToOutputData para poder usar estas nuevas clases.
|
|
@@ -5,6 +5,12 @@ from sarapy.mlProcessors import PlantinFMCreator
|
|
|
5
5
|
from sarapy.mlProcessors import PlantinClassifier
|
|
6
6
|
from sarapy.preprocessing import TransformInputData, TransformToOutputData
|
|
7
7
|
from sarapy.mlProcessors import FertilizerFMCreator, FertilizerTransformer
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
##nivel de logging en warning para evitar mensajes de advertencia de sklearn
|
|
11
|
+
logging.basicConfig(level=logging.WARNING,
|
|
12
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
13
|
+
datefmt='%Y-%m-%d %H:%M:%S')
|
|
8
14
|
|
|
9
15
|
class OpsProcessor():
|
|
10
16
|
"""Clase para procesar las operaciones de los operarios. La información se toma de la base de datos
|
|
@@ -68,23 +74,7 @@ class OpsProcessor():
|
|
|
68
74
|
Se retorna un array con las clasificaciones concatenadas, manteniendo el orden de las operaciones por operario.
|
|
69
75
|
|
|
70
76
|
Args:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Ejemplo:
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
"id_db_h":1, #int
|
|
77
|
-
"ID_NPDP":"XXAA123", #string
|
|
78
|
-
"FR": 1, #int
|
|
79
|
-
"TLM_NPDP": b'\xfc\x01\t\t\x00\x00\x00\x98', #bytes
|
|
80
|
-
"date_oprc":datetime.datetime(2024, 2, 16, 21, 2, 2, tzinfo=tzutc()),#datetime
|
|
81
|
-
"Latitud":-32.145564789, #float
|
|
82
|
-
"Longitud":-55.145564789, #float
|
|
83
|
-
"Precision": 1000,
|
|
84
|
-
"id_db_dw": 1 #int
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
- kwargs: Diccionario con los argumentos necesarios para la clasificación. Se utiliza para pasar argumentos a los métodos de clasificación.
|
|
77
|
+
¡¡¡¡¡¡¡DOCUMENTAR!!!!!!!!!!!!!!!
|
|
88
78
|
|
|
89
79
|
Returns:
|
|
90
80
|
Lista de diccionarios con las clasificaciones. Cada diccionario tiene la forma
|
|
@@ -93,21 +83,22 @@ class OpsProcessor():
|
|
|
93
83
|
|
|
94
84
|
##chqueo que newSample no esté vacío
|
|
95
85
|
if len(data) != 0:
|
|
96
|
-
newSample = self.transformInputData.
|
|
86
|
+
newSample = self.transformInputData.transform(data)
|
|
97
87
|
#Si tenemos nuevas operaciones, actualizamos el diccionario de operaciones
|
|
98
88
|
self.updateOperationsDict(newSample) #actualizamos diccionario interno de la clase
|
|
99
89
|
pl_clas = self.classifyForPlantin(**kwargs) #clasificamos las operaciones para plantín
|
|
100
90
|
|
|
101
91
|
#estimamos los gramos de fertilizante
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
ft_grams = self._fertilizer_transformer.transform(newSample)
|
|
93
|
+
logging.debug(f"Fertilizer grams shape: {ft_grams.shape}")
|
|
104
94
|
id_db_h_nums, id_db_dw_nums = self.getActualOperationsNumbers() #obtenemos los números de operaciones desde el diccionario de operaciones
|
|
105
|
-
|
|
95
|
+
logging.debug(f"ID_DB_H shape: {id_db_h_nums.shape}, ID_DB_DW shape: {id_db_dw_nums.shape}")
|
|
96
|
+
date_oprc = pd.DataFrame(newSample)["date_oprc"].values.reshape(-1, 1) ##extraigo las fechas de operación de la muestra
|
|
97
|
+
|
|
106
98
|
return self.transformToOutputData.fit_transform(np.column_stack((id_db_h_nums,
|
|
107
99
|
id_db_dw_nums,
|
|
108
100
|
pl_clas,
|
|
109
|
-
ft_grams
|
|
110
|
-
date_oprc)))
|
|
101
|
+
ft_grams)))
|
|
111
102
|
else:
|
|
112
103
|
self.resetAllNewSamplesValues()
|
|
113
104
|
return None
|
|
@@ -116,33 +107,20 @@ class OpsProcessor():
|
|
|
116
107
|
"""Actualiza el diccionario de operaciones.
|
|
117
108
|
|
|
118
109
|
Args:
|
|
119
|
-
- newSample: lista con los datos
|
|
120
|
-
|
|
121
|
-
- 0: id_db_h
|
|
122
|
-
- 1: ID_NPDP
|
|
123
|
-
- 2: TLM_NPDP
|
|
124
|
-
- 3: date_oprc
|
|
125
|
-
- 4: latitud
|
|
126
|
-
- 5: longitud
|
|
127
|
-
- 6: Precision
|
|
128
|
-
- 7: FR
|
|
129
|
-
- 8: id_db_dw
|
|
130
|
-
|
|
131
|
-
Returns:
|
|
132
|
-
- None
|
|
133
|
-
NOTA: PENSAR SI SE DEVUELVE ALGO COMO UN TRUE O FALSE PARA SABER SI SE ACTUALIZÓ O NO EL DICCIONARIO
|
|
134
|
-
DE MANERA CORRECTA O HUBO ALGÚN PROBLEMA Y ASÍ VER QUÉ HACER EN EL MAIN
|
|
110
|
+
- newSample: lista de diccionarios con los datos de las operaciones.
|
|
111
|
+
|
|
135
112
|
"""
|
|
136
|
-
|
|
137
|
-
ID_NPDPs_newOperations = np.unique(
|
|
138
|
-
|
|
113
|
+
nodos_recibidos = np.array([row["ID_NPDP"] for row in newSample]) ##nodos recibidos en la muestra
|
|
114
|
+
ID_NPDPs_newOperations = np.unique(nodos_recibidos) ##identificadores de operarios con nuevas operaciones en la muestra
|
|
115
|
+
logging.debug(f"Received nodes: {ID_NPDPs_newOperations}")
|
|
116
|
+
|
|
139
117
|
##chqueo si estos ID_NPDPs ya están en el diccionario, sino los agrego
|
|
140
118
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
141
119
|
if ID_NPDP not in self._operationsDict:
|
|
142
120
|
#El diccionario contiene la siguiente información:
|
|
143
|
-
#sample_ops:
|
|
144
|
-
#last_oprc:
|
|
145
|
-
#first_day_op_classified: booleano para saber si es la primera operación del día fue clasificada
|
|
121
|
+
#sample_ops: lista de diccionarios con los datos de las operaciones.
|
|
122
|
+
#last_oprc: diccionario con la última operación registrada.
|
|
123
|
+
#first_day_op_classified: booleano para saber si es la primera operación del día que fue clasificada
|
|
146
124
|
self._operationsDict[ID_NPDP] = {"sample_ops": None,
|
|
147
125
|
"last_oprc": None,
|
|
148
126
|
"first_day_op_classified": False,
|
|
@@ -152,18 +130,18 @@ class OpsProcessor():
|
|
|
152
130
|
|
|
153
131
|
##actualizo el diccionario con las operaciones nuevas para aquellos operarios que correspondan
|
|
154
132
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
155
|
-
sample_ops = newSample
|
|
156
|
-
id_db_h =
|
|
157
|
-
id_db_dw =
|
|
133
|
+
sample_ops = newSample
|
|
134
|
+
id_db_h = np.array([row["id_db_h"] for row in newSample]) ##extraigo los id_db_h de la muestra
|
|
135
|
+
id_db_dw = np.array([row["id_db_dw"] for row in newSample])
|
|
158
136
|
##actualizo el diccionario
|
|
159
137
|
self._operationsDict[ID_NPDP]["sample_ops"] = sample_ops
|
|
160
|
-
self._operationsDict[ID_NPDP]["id_db_h"] = id_db_h
|
|
161
|
-
self._operationsDict[ID_NPDP]["id_db_dw"] = id_db_dw
|
|
138
|
+
self._operationsDict[ID_NPDP]["id_db_h"] = np.astype(id_db_h, str) ##convierto a int
|
|
139
|
+
self._operationsDict[ID_NPDP]["id_db_dw"] = np.astype(id_db_dw, str) ##convierto a int
|
|
162
140
|
##chequeo si tenemos última operación, si es así, asignamos dicha operación en la primera fila de sample_ops
|
|
163
141
|
last_op = self._operationsDict[ID_NPDP]["last_oprc"]
|
|
164
142
|
###si last_op es not None y last_op no está vacía, entonces concatenamos last_op con sample_ops
|
|
165
|
-
if last_op is not None and last_op
|
|
166
|
-
self._operationsDict[ID_NPDP]["sample_ops"]
|
|
143
|
+
if last_op is not None and len(last_op) != 0:
|
|
144
|
+
self._operationsDict[ID_NPDP]["sample_ops"] += last_op ##concatenamos la última operación con las nuevas operaciones
|
|
167
145
|
|
|
168
146
|
self.updateNewSamplesValues(ID_NPDPs_newOperations) #actualizo el estado de 'new_sample' en el diccionario de operaciones
|
|
169
147
|
self.updateLastOperations(ID_NPDPs_newOperations) #actualizo la última operación de una muestra de operaciones en el diccionario de operaciones
|
|
@@ -193,25 +171,27 @@ class OpsProcessor():
|
|
|
193
171
|
plantinClassifications = None
|
|
194
172
|
|
|
195
173
|
##me quedo con los ID_NPDPs que tengan _operationsDict[ID_NPDP]["new_sample"] iguales a True
|
|
196
|
-
ops_with_new_sample = [ID_NPDP for ID_NPDP in self.
|
|
174
|
+
ops_with_new_sample = [ID_NPDP for ID_NPDP in self._operationsDict.keys() if self.operationsDict[ID_NPDP]["new_sample"]]
|
|
197
175
|
|
|
198
176
|
for ID_NPDP in ops_with_new_sample:#self.operationsDict.keys():
|
|
199
177
|
##clasificamos las operaciones para plantín
|
|
200
|
-
operations = self.
|
|
178
|
+
operations = self._operationsDict[ID_NPDP]["sample_ops"]
|
|
179
|
+
logging.debug(f"Número de operaciones para el nodo {ID_NPDP}: {len(operations)}")
|
|
201
180
|
features, dst_pt, inest_pt = self.plantinFMCreator.fit_transform(operations)
|
|
181
|
+
logging.debug(f"Features shape for {ID_NPDP}: {features.shape}")
|
|
202
182
|
classified_ops = self._plantin_classifier.classify(features, dst_pt, inest_pt, **classify_kwargs)
|
|
183
|
+
logging.debug(f"Classified operations shape for {ID_NPDP}: {classified_ops.shape}")
|
|
203
184
|
|
|
204
185
|
##chequeo si first_day_op_classified es True, si es así, no se considera la primera fila de las classified_ops
|
|
205
|
-
if self.
|
|
186
|
+
if self._operationsDict[ID_NPDP]["first_day_op_classified"]:
|
|
206
187
|
classified_ops = classified_ops[1:]
|
|
207
188
|
|
|
208
189
|
##actualizo las operaciones que hayan sido hardcodeadas luego de despertar y/o reiniciar la electrónica
|
|
209
|
-
classified_ops = self.
|
|
190
|
+
classified_ops = self.updateAfterAwake(classified_ops)
|
|
210
191
|
|
|
211
|
-
# plantinClassifications = np.vstack((plantinClassifications, classified_ops)) if plantinClassifications is not None else classified_ops
|
|
212
192
|
plantinClassifications = np.concatenate((plantinClassifications, classified_ops)) if plantinClassifications is not None else classified_ops
|
|
213
193
|
|
|
214
|
-
self.
|
|
194
|
+
self._operationsDict[ID_NPDP]["first_day_op_classified"] = True
|
|
215
195
|
|
|
216
196
|
return plantinClassifications
|
|
217
197
|
|
|
@@ -219,18 +199,7 @@ class OpsProcessor():
|
|
|
219
199
|
"""Método para actualizar la última operación de una muestra de operaciones en el diccionario de operaciones
|
|
220
200
|
|
|
221
201
|
Args:
|
|
222
|
-
- newSample: lista con los datos
|
|
223
|
-
La forma de cada dato dentro de la lista newSample es (n,6). Las columnas de newSample son,
|
|
224
|
-
|
|
225
|
-
- 0: id_db_h
|
|
226
|
-
- 1: ID_NPDP
|
|
227
|
-
- 2: TLM_NPDP
|
|
228
|
-
- 3: date_oprc
|
|
229
|
-
- 4: latitud
|
|
230
|
-
- 5: longitud
|
|
231
|
-
- 6: Precision
|
|
232
|
-
- 7: FR
|
|
233
|
-
- 8: id_db_dw
|
|
202
|
+
- newSample: lista de diccionarios con los datos de las operaciones.
|
|
234
203
|
"""
|
|
235
204
|
|
|
236
205
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
@@ -245,8 +214,9 @@ class OpsProcessor():
|
|
|
245
214
|
|
|
246
215
|
##recorro el diccionario de operaciones y actualizo el estado de 'new_sample' a
|
|
247
216
|
##True para los ID_NPDPs que tienen nuevas operaciones y a False para los que no tienen nuevas operaciones
|
|
248
|
-
for ID_NPDP in self.
|
|
217
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
249
218
|
if ID_NPDP in ID_NPDPs_newOperations:
|
|
219
|
+
logging.debug(f"Actualizando 'new_sample' para nodo: {ID_NPDP}")
|
|
250
220
|
self._operationsDict[ID_NPDP]["new_sample"] = True
|
|
251
221
|
else:
|
|
252
222
|
self._operationsDict[ID_NPDP]["new_sample"] = False
|
|
@@ -255,19 +225,20 @@ class OpsProcessor():
|
|
|
255
225
|
"""Método para resetar todos los valores de new_sample en el diccionario de operaciones.
|
|
256
226
|
"""
|
|
257
227
|
|
|
258
|
-
for ID_NPDP in self.
|
|
228
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
259
229
|
self._operationsDict[ID_NPDP]["new_sample"] = False
|
|
260
230
|
|
|
261
231
|
def getActualOperationsNumbers(self):
|
|
262
232
|
"""Método para obtener los números de operaciones desde el diccionario de operaciones para aquellos operarios que
|
|
263
233
|
tienen nuevas operaciones en la muestra."""
|
|
264
|
-
|
|
234
|
+
|
|
265
235
|
id_db_h_list = np.array([])
|
|
266
236
|
id_db_dw_list = np.array([])
|
|
267
|
-
for ID_NPDP in self.
|
|
268
|
-
if self.
|
|
269
|
-
|
|
270
|
-
|
|
237
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
238
|
+
if self._operationsDict[ID_NPDP]["new_sample"]:
|
|
239
|
+
logging.debug(f"Obteniendo números de operaciones para el ID_NPDP: {ID_NPDP}")
|
|
240
|
+
id_db_h_list = np.append(id_db_h_list, self._operationsDict[ID_NPDP]["id_db_h"].flatten())
|
|
241
|
+
id_db_dw_list = np.append(id_db_dw_list, self._operationsDict[ID_NPDP]["id_db_dw"].flatten())
|
|
271
242
|
|
|
272
243
|
return id_db_h_list.astype(int), id_db_dw_list.astype(int)
|
|
273
244
|
|
|
@@ -275,7 +246,7 @@ class OpsProcessor():
|
|
|
275
246
|
"""Método para actualizar el indicador de si es la primera operación del día para cada operario en el diccionario de operaciones.
|
|
276
247
|
"""
|
|
277
248
|
|
|
278
|
-
for ID_NPDP in self.
|
|
249
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
279
250
|
self._operationsDict[ID_NPDP]["first_day_op_classified"] = False
|
|
280
251
|
|
|
281
252
|
def cleanSamplesOperations(self):
|
|
@@ -294,10 +265,10 @@ class OpsProcessor():
|
|
|
294
265
|
- 6: Precision
|
|
295
266
|
"""
|
|
296
267
|
|
|
297
|
-
for ID_NPDP in self.
|
|
268
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
298
269
|
self._operationsDict[ID_NPDP]["sample_ops"] = None
|
|
299
270
|
|
|
300
|
-
def
|
|
271
|
+
def updateAfterAwake(self, classified_ops):
|
|
301
272
|
"""
|
|
302
273
|
Función para actualizar las operaciones que hayan sido hardcodeadas luego de despertar y/o reiniciar la electrónica.
|
|
303
274
|
|
|
@@ -310,8 +281,8 @@ class OpsProcessor():
|
|
|
310
281
|
- classified_ops: np.array con las operaciones clasificadas.
|
|
311
282
|
"""
|
|
312
283
|
|
|
313
|
-
##me quedo con los índices donde
|
|
314
|
-
mask = self.plantinFMCreator.
|
|
284
|
+
##me quedo con los índices donde N_MODE es igual a 1
|
|
285
|
+
mask = self.plantinFMCreator.tlmDataProcessor["N_MODE",:]==1
|
|
315
286
|
classified_ops[mask] = 0 ##hardcodeo las operaciones que hayan sido clasificadas como 1
|
|
316
287
|
return classified_ops
|
|
317
288
|
|
|
@@ -321,37 +292,20 @@ class OpsProcessor():
|
|
|
321
292
|
|
|
322
293
|
|
|
323
294
|
if __name__ == "__main__":
|
|
324
|
-
#cargo archivo examples\volcado_17112023_NODE_processed.csv
|
|
325
295
|
import pandas as pd
|
|
326
|
-
import
|
|
327
|
-
import
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
import time
|
|
340
|
-
start_time = time.time()
|
|
341
|
-
op = OpsProcessor.OpsProcessor(classifier_file='modelos\\pipeline_rf.pkl', imputeDistances = False,
|
|
342
|
-
regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
|
|
343
|
-
classifications = op.processOperations(raw_ops, update_samePlace=True, useRatioStats=True)
|
|
344
|
-
end_time = time.time()
|
|
345
|
-
execution_time = end_time - start_time
|
|
346
|
-
print("Execution time:", execution_time, "seconds")
|
|
296
|
+
import json
|
|
297
|
+
import logging
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
|
|
301
|
+
with open(historical_data_path, 'r') as file:
|
|
302
|
+
samples = json.load(file)
|
|
303
|
+
|
|
304
|
+
samples1 = samples[:100]
|
|
305
|
+
samples2 = samples[100:200]
|
|
306
|
+
|
|
307
|
+
op = OpsProcessor(classifier_file='modelos\\pipeline_rf.pkl', imputeDistances = False,
|
|
308
|
+
regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
|
|
347
309
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
tag_seedling = df["tag_seedling"].values
|
|
351
|
-
print(tag_seedling.mean())
|
|
352
|
-
print(df["tag_seedling"].shape)
|
|
353
|
-
|
|
354
|
-
##datos de fertilizante
|
|
355
|
-
tag_fertilizer = df["tag_fertilizer"].values
|
|
356
|
-
print(tag_fertilizer[1500:1560])
|
|
357
|
-
print(tag_fertilizer.mean())
|
|
310
|
+
op.processOperations(samples[:2])
|
|
311
|
+
# op.processOperations(samples2)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
###Documentación en https://github.com/lucasbaldezzari/sarapy/blob/main/docs/Docs.md
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
class TLMSensorDataProcessor():
|
|
6
|
+
"""- Autor: BALDEZZARI Lucas
|
|
7
|
+
|
|
8
|
+
Estar clase sirve para entegar los datos de telemetría como un array de numpy. La idea es
|
|
9
|
+
poder acceder a los datos a través de indexación, ejemplos:
|
|
10
|
+
tlm_data[["INST_PT","INST_FT"],:] en este caso se obtienen los datos de las columnas INST_PT e INST_FT
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, data:list):
|
|
15
|
+
"""Constructor de la clase MetadataManager
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
- data: Es una lista de diccionarios (como un JSON) con los datos de telemetría.
|
|
19
|
+
"""
|
|
20
|
+
#convierto a un DataFrame de pandas
|
|
21
|
+
self.data = pd.DataFrame(data)
|
|
22
|
+
self.keys = self.data.columns.tolist()
|
|
23
|
+
|
|
24
|
+
def getData(self):
|
|
25
|
+
"""Devuelve los datos de telemetría como un numpy array.
|
|
26
|
+
|
|
27
|
+
Los datos se retornan como un array de numpy, donde cada fila es una operación y cada columna es un sensor.
|
|
28
|
+
Es decir, los datos son de la forma (n_operaciones, n_columnas).
|
|
29
|
+
"""
|
|
30
|
+
return self.data.values
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, key):
|
|
33
|
+
"""
|
|
34
|
+
Permite indexar el objeto como un array o DataFrame.
|
|
35
|
+
|
|
36
|
+
Ejemplos:
|
|
37
|
+
obj[["col1", "col2"], :] -> columnas col1 y col2, todas las filas
|
|
38
|
+
obj[:, :100] -> todas las columnas, primeras 100 filas
|
|
39
|
+
obj[:] -> todo
|
|
40
|
+
obj[["col1"], :50] -> columna col1, primeras 50 filas
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(key, tuple): ##reviso si es una tupla
|
|
43
|
+
##se supone que key es una tupla de la forma (cols, rows)
|
|
44
|
+
if len(key) != 2:
|
|
45
|
+
raise ValueError("La clave debe ser una tupla de la forma (cols, rows)")
|
|
46
|
+
cols, rows = key
|
|
47
|
+
|
|
48
|
+
# Columnas
|
|
49
|
+
if isinstance(cols, list) or isinstance(cols, str):
|
|
50
|
+
selected_cols = cols
|
|
51
|
+
elif cols == slice(None): # equivalente a ":"
|
|
52
|
+
selected_cols = self.data.columns
|
|
53
|
+
else:
|
|
54
|
+
raise TypeError(f"Tipo de indexado de columnas no soportado: {type(cols)}")
|
|
55
|
+
|
|
56
|
+
# Filas
|
|
57
|
+
if isinstance(rows, int) or isinstance(rows, slice):
|
|
58
|
+
#retornamos de la forma (n_operaciones, n_columnas)
|
|
59
|
+
return self.data.loc[rows, selected_cols].values
|
|
60
|
+
else:
|
|
61
|
+
raise TypeError(f"Tipo de indexado de filas no soportado: {type(rows)}")
|
|
62
|
+
|
|
63
|
+
elif isinstance(key, slice):
|
|
64
|
+
# Caso para todas las filas y columnas
|
|
65
|
+
# Retornamos de la forma (n_operaciones, n_columnas)
|
|
66
|
+
return self.data.iloc[key].values
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
raise TypeError("La indexación debe ser una tupla (cols, rows) o un slice ':'")
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
import pandas as pd
|
|
73
|
+
import json
|
|
74
|
+
from sarapy.preprocessing import TransformInputData
|
|
75
|
+
|
|
76
|
+
historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
|
|
77
|
+
with open(historical_data_path, 'r') as file:
|
|
78
|
+
historical_data = json.load(file)
|
|
79
|
+
|
|
80
|
+
inputData_transformer = TransformInputData.TransformInputData()
|
|
81
|
+
data = inputData_transformer.transform(historical_data)
|
|
82
|
+
|
|
83
|
+
tlm_processor = TLMSensorDataProcessor(data=data)
|
|
84
|
+
print(tlm_processor.data.head())
|
|
85
|
+
tlm_processor[:, :10].shape
|
|
86
|
+
|
|
87
|
+
tlm_processor[["id_db_dw", "id_db_h"], :5]#.shape
|
|
88
|
+
tlm_processor.keys
|
|
89
|
+
tlm_processor["longitud",:]
|
|
90
|
+
|
|
@@ -54,7 +54,14 @@ class TimeSeriesProcessor(BaseEstimator, TransformerMixin):
|
|
|
54
54
|
def transform(self, X: np.array):
|
|
55
55
|
"""Genera un array con los tiempos de operación, caminata, pico abierto y ratio_dCdP.
|
|
56
56
|
Args:
|
|
57
|
-
- X es un array de strings de forma (n, 2) donde la primera columna es el tiempo
|
|
57
|
+
- X es un array de strings de forma (n, 2) donde la primera columna es el tiempo
|
|
58
|
+
y la segunda columna es el tiempo de pico abierto (en segundos).
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
- Un array de numpy de forma (n, 4) donde la primera columna es
|
|
62
|
+
el tiempo de operación, la segunda columna es el tiempo de caminata,
|
|
63
|
+
la tercera columna es el tiempo de pico abierto y la cuarta columna es
|
|
64
|
+
el ratio entre el tiempo de caminata y el tiempo de pico abierto.
|
|
58
65
|
"""
|
|
59
66
|
|
|
60
67
|
if not self.is_fitted:
|
|
@@ -66,6 +73,17 @@ class TimeSeriesProcessor(BaseEstimator, TransformerMixin):
|
|
|
66
73
|
self._ratio_dCdP.reshape(-1,1))).round(2)
|
|
67
74
|
|
|
68
75
|
def fit_transform(self, X: np.array, y=None):
|
|
76
|
+
"""Genera un array con los tiempos de operación, caminata, pico abierto y ratio_dCdP.
|
|
77
|
+
Args:
|
|
78
|
+
- X es un array de strings de forma (n, 2) donde la primera columna es el tiempo
|
|
79
|
+
y la segunda columna es el tiempo de pico abierto (en segundos).
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
- Un array de numpy de forma (n, 4) donde la primera columna es
|
|
83
|
+
el tiempo de operación, la segunda columna es el tiempo de caminata,
|
|
84
|
+
la tercera columna es el tiempo de pico abierto y la cuarta columna es
|
|
85
|
+
el ratio entre el tiempo de caminata y el tiempo de pico abierto.
|
|
86
|
+
"""
|
|
69
87
|
self.fit(X)
|
|
70
88
|
return self.transform(X)
|
|
71
89
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pickle
|
|
2
|
+
from sarapy.dataProcessing import TLMSensorDataProcessor
|
|
2
3
|
|
|
3
4
|
class FertilizerTransformer:
|
|
4
5
|
"""
|
|
@@ -31,51 +32,37 @@ class FertilizerTransformer:
|
|
|
31
32
|
self.fertilizer_grams = None ##cuando no se ha transformado ningún dato, se inicializa en None
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
def transform(self,
|
|
35
|
+
def transform(self, data):
|
|
35
36
|
"""Transforma los datos de distorsión de fertilizante a gramos.
|
|
36
37
|
|
|
37
38
|
Params:
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
Ejemplo: [12. 1. 12. 0. 0. 0. 0. 0. 0. 12.]
|
|
39
|
+
- data: Es una lista de diccionarios (como un JSON) con los datos de telemetría.
|
|
41
40
|
|
|
42
41
|
Returns:
|
|
43
42
|
- 0: Array con los valores de distorsión de fertilizante transformados a gramos.
|
|
44
43
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
tlmDataProcessor = TLMSensorDataProcessor.TLMSensorDataProcessor(data)
|
|
45
|
+
X = tlmDataProcessor["SC_FT",:]
|
|
46
|
+
X_poly = self._poly_features.fit_transform(X.reshape(-1, 1))
|
|
47
47
|
self.fertilizer_grams = self._regresor.predict(X_poly)
|
|
48
48
|
|
|
49
49
|
##retorno con shape (n,)
|
|
50
50
|
return self.fertilizer_grams.reshape(-1,)
|
|
51
51
|
|
|
52
52
|
if __name__ == "__main__":
|
|
53
|
-
import os
|
|
54
53
|
import pandas as pd
|
|
55
|
-
import
|
|
54
|
+
import json
|
|
56
55
|
from sarapy.preprocessing import TransformInputData
|
|
57
|
-
from sarapy.mlProcessors import PlantinFMCreator
|
|
58
|
-
import sarapy.utils.getRawOperations as getRawOperations
|
|
59
|
-
tindata = TransformInputData.TransformInputData()
|
|
60
|
-
|
|
61
|
-
##cargo los archivos examples\2024-09-04\UPM001N\data.json y examples\2024-09-04\UPM001N\historical-data.json
|
|
62
|
-
data_path = os.path.join(os.getcwd(), "examples\\2024-09-04\\UPM001N\\data.json")
|
|
63
|
-
historical_data_path = os.path.join(os.getcwd(), "examples\\2024-09-04\\UPM001N\\historical-data.json")
|
|
64
|
-
raw_data = pd.read_json(data_path, orient="records").to_dict(orient="records")
|
|
65
|
-
raw_data2 = pd.read_json(historical_data_path, orient="records").to_dict(orient="records")
|
|
66
|
-
|
|
67
|
-
raw_ops = np.array(getRawOperations.getRawOperations(raw_data, raw_data2))
|
|
68
|
-
X = tindata.fit_transform(raw_ops) #transforma los datos de operaciones a un array de numpy
|
|
69
|
-
|
|
70
|
-
from sarapy.mlProcessors import FertilizerFMCreator
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
dst_ft = dst_ft.astype(int)
|
|
57
|
+
historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
|
|
58
|
+
with open(historical_data_path, 'r') as file:
|
|
59
|
+
historical_data = json.load(file)
|
|
76
60
|
|
|
77
|
-
|
|
61
|
+
##cargo en un diccionario sarapy\preprocessing\telemetriaDataPosition.json
|
|
62
|
+
data_positions = json.load(open("sarapy/preprocessing/telemetriaDataPosition.json", 'r'))
|
|
63
|
+
transform_input_data = TransformInputData.TransformInputData()
|
|
64
|
+
transformed_data = transform_input_data.transform(historical_data)
|
|
78
65
|
|
|
79
|
-
fertransformer = FertilizerTransformer
|
|
80
|
-
gramos = fertransformer.transform(
|
|
66
|
+
fertransformer = FertilizerTransformer(regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
|
|
67
|
+
gramos = fertransformer.transform(transformed_data)
|
|
81
68
|
print(gramos[:10])
|
|
@@ -129,12 +129,12 @@ class PlantinClassifier(BaseEstimator, TransformerMixin):
|
|
|
129
129
|
Función para actualizar las etiquetas de las operaciones que tengan distorsiones de plantín.
|
|
130
130
|
"""
|
|
131
131
|
new_labels = labels.copy()
|
|
132
|
+
|
|
132
133
|
##filtro si dst_pt es menor a 7 y si inest_pt es 0
|
|
133
|
-
new_labels[(dst_pt <
|
|
134
|
+
new_labels[(dst_pt < 4) & (inest_pt == 0)] = 0
|
|
134
135
|
|
|
135
|
-
##si inest_pt es y umbral_proba es menor a umbra_proba, entonces la operación es 0
|
|
136
|
+
##si inest_pt 1 es y umbral_proba es menor a umbra_proba, entonces la operación es 0
|
|
136
137
|
new_labels[(inest_pt == 1) & (self.probas[:,1] < umbral_proba)] = 0
|
|
137
|
-
# new_labels[(inest_pt == 1) & (self.probas[:,1] >= umbral_proba)] = 1
|
|
138
138
|
|
|
139
139
|
return new_labels
|
|
140
140
|
|