mannnon commited on
Commit
50ee7ed
·
verified ·
1 Parent(s): 570a542

Create zebris_extractor.py

Browse files
Files changed (1) hide show
  1. zebris_extractor.py +255 -0
zebris_extractor.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+
6
+ STANDARD_COLUMNS = [
7
+ "Nom",
8
+ "Date",
9
+ "Poids (kg)",
10
+ "Vitesse (km/h)",
11
+ "Cadence (pas/min)",
12
+ "Contact (%)",
13
+ "Flight (%)",
14
+ "Force talon G (N)",
15
+ "Force talon D (N)",
16
+ "Force avant-pied G (N)",
17
+ "Force avant-pied D (N)",
18
+ "Pression talon G (N/cm²)",
19
+ "Pression talon D (N/cm²)",
20
+ "COP G (mm)",
21
+ "COP D (mm)",
22
+ "Rotation G (°)",
23
+ "Rotation D (°)",
24
+ "Transition G (s)",
25
+ "Transition D (s)",
26
+ "Longueur foulée (cm)",
27
+ "Largeur pas (cm)",
28
+ ]
29
+
30
+
31
+ def _norm(text):
32
+ return (
33
+ str(text)
34
+ .strip()
35
+ .lower()
36
+ .replace("é", "e")
37
+ .replace("è", "e")
38
+ .replace("ê", "e")
39
+ .replace("à", "a")
40
+ .replace("ù", "u")
41
+ .replace("ç", "c")
42
+ .replace("²", "2")
43
+ .replace("°", "")
44
+ )
45
+
46
+
47
+ COLUMN_CANDIDATES = {
48
+ "Nom": [
49
+ ("last name", "first name"),
50
+ ("nom", "prenom"),
51
+ ("last name", "first name "),
52
+ ],
53
+ "Date": [
54
+ "date",
55
+ "recording date",
56
+ "enregistrement",
57
+ ],
58
+ "Poids (kg)": [
59
+ "weight (kg)",
60
+ "poids (kg)",
61
+ "weight",
62
+ "poids",
63
+ ],
64
+ "Vitesse (km/h)": [
65
+ "speed (km/h)",
66
+ "vitesse (km/h)",
67
+ "speed",
68
+ "vitesse",
69
+ ],
70
+ "Cadence (pas/min)": [
71
+ "cadence (steps/min)",
72
+ "cadence (pas/min)",
73
+ "cadence",
74
+ "cadence, pass/min",
75
+ ],
76
+ "Contact (%)": [
77
+ "total contact (%)",
78
+ "contact (%)",
79
+ "total contact",
80
+ ],
81
+ "Flight (%)": [
82
+ "total flight (%)",
83
+ "flight (%)",
84
+ "total flight",
85
+ ],
86
+ "Force talon G (N)": [
87
+ "heel (three zones) left force max (n)",
88
+ "mean force heel left (n)",
89
+ "heel left force max (n)",
90
+ "heel left (n)",
91
+ ],
92
+ "Force talon D (N)": [
93
+ "heel (three zones) right force max (n)",
94
+ "mean force heel right (n)",
95
+ "heel right force max (n)",
96
+ "heel right (n)",
97
+ ],
98
+ "Force avant-pied G (N)": [
99
+ "forefoot (three zones) left force max (n)",
100
+ "mean force forefoot left (n)",
101
+ "forefoot left force max (n)",
102
+ "forefoot left (n)",
103
+ ],
104
+ "Force avant-pied D (N)": [
105
+ "forefoot (three zones) right force max (n)",
106
+ "mean force forefoot right (n)",
107
+ "forefoot right force max (n)",
108
+ "forefoot right (n)",
109
+ ],
110
+ "Pression talon G (N/cm²)": [
111
+ "heel (three zones) left pressure max (n/cm2)",
112
+ "mean pressure heel left (n/cm2)",
113
+ "heel left pressure max (n/cm2)",
114
+ ],
115
+ "Pression talon D (N/cm²)": [
116
+ "heel (three zones) right pressure max (n/cm2)",
117
+ "mean pressure heel right (n/cm2)",
118
+ "heel right pressure max (n/cm2)",
119
+ ],
120
+ "COP G (mm)": [
121
+ "cop length left (mm)",
122
+ "cop parameters running left length (mm)",
123
+ "cop left (mm)",
124
+ ],
125
+ "COP D (mm)": [
126
+ "cop length right (mm)",
127
+ "cop parameters running right length (mm)",
128
+ "cop right (mm)",
129
+ ],
130
+ "Rotation G (°)": [
131
+ "foot rotation left (deg)",
132
+ "foot rotation left",
133
+ "rotation left",
134
+ ],
135
+ "Rotation D (°)": [
136
+ "foot rotation right (deg)",
137
+ "foot rotation right",
138
+ "rotation right",
139
+ ],
140
+ "Transition G (s)": [
141
+ "heel to forefoot transition left (s)",
142
+ "transition left (s)",
143
+ "instant du passage du talon vers l'avant-pied gauche (s)",
144
+ ],
145
+ "Transition D (s)": [
146
+ "heel to forefoot transition right (s)",
147
+ "transition right (s)",
148
+ "instant du passage du talon vers l'avant-pied droite (s)",
149
+ ],
150
+ "Longueur foulée (cm)": [
151
+ "stride length (cm)",
152
+ "longueur de la foulee (cm)",
153
+ "longueur de la foulee",
154
+ ],
155
+ "Largeur pas (cm)": [
156
+ "step width (cm)",
157
+ "largeur du pas (cm)",
158
+ "largeur du pas",
159
+ ],
160
+ }
161
+
162
+
163
+ def _to_numeric(series):
164
+ return pd.to_numeric(
165
+ series.astype(str).str.replace(",", ".", regex=False),
166
+ errors="coerce"
167
+ )
168
+
169
+
170
+ def _read_csv_flex(uploaded_file):
171
+ raw = uploaded_file.read()
172
+ uploaded_file.seek(0)
173
+
174
+ for encoding in ["utf-8-sig", "utf-8", "latin1", "cp1252"]:
175
+ for sep in [";", ",", "\t"]:
176
+ try:
177
+ txt = raw.decode(encoding)
178
+ df = pd.read_csv(io.StringIO(txt), sep=sep)
179
+ if df.shape[1] > 1:
180
+ return df
181
+ except Exception:
182
+ pass
183
+
184
+ # dernier essai naïf
185
+ return pd.read_csv(uploaded_file)
186
+
187
+
188
+ def _find_column(df, candidates):
189
+ normalized_cols = {_norm(c): c for c in df.columns}
190
+ for cand in candidates:
191
+ key = _norm(cand)
192
+ if key in normalized_cols:
193
+ return normalized_cols[key]
194
+ return None
195
+
196
+
197
+ def extract_zebris_csv(uploaded_file):
198
+ df = _read_csv_flex(uploaded_file)
199
+
200
+ out = pd.DataFrame(index=df.index)
201
+ mapping_debug = {}
202
+ manquantes = []
203
+
204
+ # Nom
205
+ nom_done = False
206
+ for cand_pair in COLUMN_CANDIDATES["Nom"]:
207
+ if isinstance(cand_pair, tuple):
208
+ c1 = _find_column(df, [cand_pair[0]])
209
+ c2 = _find_column(df, [cand_pair[1]])
210
+ if c1 and c2:
211
+ out["Nom"] = df[c1].astype(str).str.strip() + " " + df[c2].astype(str).str.strip()
212
+ mapping_debug["Nom"] = [c1, c2]
213
+ nom_done = True
214
+ break
215
+
216
+ if not nom_done:
217
+ c = _find_column(df, ["nom"])
218
+ if c:
219
+ out["Nom"] = df[c].astype(str)
220
+ mapping_debug["Nom"] = c
221
+ else:
222
+ out["Nom"] = "Inconnu"
223
+ manquantes.append("Nom")
224
+
225
+ # Autres colonnes
226
+ for target in STANDARD_COLUMNS:
227
+ if target == "Nom":
228
+ continue
229
+
230
+ candidates = COLUMN_CANDIDATES.get(target, [])
231
+ col = _find_column(df, candidates)
232
+
233
+ if col is None:
234
+ out[target] = np.nan
235
+ manquantes.append(target)
236
+ else:
237
+ mapping_debug[target] = col
238
+ if target == "Date":
239
+ out[target] = df[col]
240
+ else:
241
+ out[target] = _to_numeric(df[col])
242
+
243
+ # Garde lignes avec allure
244
+ if "Vitesse (km/h)" in out.columns:
245
+ out = out[out["Vitesse (km/h)"].notna()].copy()
246
+
247
+ out = out.reindex(columns=STANDARD_COLUMNS).reset_index(drop=True)
248
+
249
+ debug = {
250
+ "mapping": mapping_debug,
251
+ "manquantes": manquantes,
252
+ "colonnes_csv": list(df.columns),
253
+ }
254
+
255
+ return out, debug