pyfaceau 1.0.3__cp313-cp313-win_amd64.whl
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.
- pyfaceau/__init__.py +19 -0
- pyfaceau/alignment/__init__.py +0 -0
- pyfaceau/alignment/calc_params.py +671 -0
- pyfaceau/alignment/face_aligner.py +352 -0
- pyfaceau/alignment/numba_calcparams_accelerator.py +244 -0
- pyfaceau/cython_histogram_median.cp313-win_amd64.pyd +0 -0
- pyfaceau/cython_rotation_update.cp313-win_amd64.pyd +0 -0
- pyfaceau/detectors/__init__.py +0 -0
- pyfaceau/detectors/pfld.py +128 -0
- pyfaceau/detectors/retinaface.py +352 -0
- pyfaceau/download_weights.py +134 -0
- pyfaceau/features/__init__.py +0 -0
- pyfaceau/features/histogram_median_tracker.py +335 -0
- pyfaceau/features/pdm.py +269 -0
- pyfaceau/features/triangulation.py +64 -0
- pyfaceau/parallel_pipeline.py +462 -0
- pyfaceau/pipeline.py +1083 -0
- pyfaceau/prediction/__init__.py +0 -0
- pyfaceau/prediction/au_predictor.py +434 -0
- pyfaceau/prediction/batched_au_predictor.py +269 -0
- pyfaceau/prediction/model_parser.py +337 -0
- pyfaceau/prediction/running_median.py +318 -0
- pyfaceau/prediction/running_median_fallback.py +200 -0
- pyfaceau/processor.py +270 -0
- pyfaceau/refinement/__init__.py +12 -0
- pyfaceau/refinement/svr_patch_expert.py +361 -0
- pyfaceau/refinement/targeted_refiner.py +362 -0
- pyfaceau/utils/__init__.py +0 -0
- pyfaceau/utils/cython_extensions/cython_histogram_median.c +35391 -0
- pyfaceau/utils/cython_extensions/cython_histogram_median.pyx +316 -0
- pyfaceau/utils/cython_extensions/cython_rotation_update.c +32262 -0
- pyfaceau/utils/cython_extensions/cython_rotation_update.pyx +211 -0
- pyfaceau/utils/cython_extensions/setup.py +47 -0
- pyfaceau-1.0.3.data/scripts/pyfaceau_gui.py +302 -0
- pyfaceau-1.0.3.dist-info/METADATA +466 -0
- pyfaceau-1.0.3.dist-info/RECORD +40 -0
- pyfaceau-1.0.3.dist-info/WHEEL +5 -0
- pyfaceau-1.0.3.dist-info/entry_points.txt +3 -0
- pyfaceau-1.0.3.dist-info/licenses/LICENSE +40 -0
- pyfaceau-1.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
# cython: boundscheck=False
|
|
3
|
+
# cython: wraparound=False
|
|
4
|
+
# cython: cdivision=True
|
|
5
|
+
"""
|
|
6
|
+
Cython-optimized Histogram-Based Running Median Tracker
|
|
7
|
+
|
|
8
|
+
High-performance implementation of OpenFace 2.2's running median algorithm.
|
|
9
|
+
|
|
10
|
+
Performance improvements:
|
|
11
|
+
- Histogram update: ~10-20x faster (4464 features × 200 bins per frame)
|
|
12
|
+
- Median computation: ~15-30x faster (nested loops with early termination)
|
|
13
|
+
- Memory layout: C-contiguous for cache efficiency
|
|
14
|
+
|
|
15
|
+
Expected speedup: 10-20x for running median operations (major bottleneck!)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
cimport numpy as cnp
|
|
20
|
+
from libc.math cimport floor
|
|
21
|
+
cimport cython
|
|
22
|
+
|
|
23
|
+
# Initialize NumPy C API
|
|
24
|
+
cnp.import_array()
|
|
25
|
+
|
|
26
|
+
# Type definitions
|
|
27
|
+
ctypedef cnp.float32_t FLOAT32
|
|
28
|
+
ctypedef cnp.float64_t FLOAT64
|
|
29
|
+
ctypedef cnp.int32_t INT32
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@cython.boundscheck(False)
|
|
33
|
+
@cython.wraparound(False)
|
|
34
|
+
@cython.cdivision(True)
|
|
35
|
+
cdef void update_histogram_c(float[:] features,
|
|
36
|
+
int[:, :] histogram,
|
|
37
|
+
int feature_dim,
|
|
38
|
+
int num_bins,
|
|
39
|
+
float min_val,
|
|
40
|
+
float bin_width,
|
|
41
|
+
float length) nogil:
|
|
42
|
+
"""
|
|
43
|
+
Update histogram with new feature values (C implementation)
|
|
44
|
+
|
|
45
|
+
This is the critical tight loop - called every 2nd frame on 4702 features.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
features: Input feature vector (feature_dim,)
|
|
49
|
+
histogram: Histogram array (feature_dim, num_bins) - modified in-place
|
|
50
|
+
feature_dim: Number of features
|
|
51
|
+
num_bins: Number of histogram bins
|
|
52
|
+
min_val: Minimum value for histogram range
|
|
53
|
+
bin_width: Width of each bin
|
|
54
|
+
length: Total range (max_val - min_val)
|
|
55
|
+
"""
|
|
56
|
+
cdef int i, bin_idx
|
|
57
|
+
cdef float converted
|
|
58
|
+
|
|
59
|
+
for i in range(feature_dim):
|
|
60
|
+
# Convert feature value to bin index
|
|
61
|
+
# Formula: (value - min_val) * num_bins / length
|
|
62
|
+
converted = (features[i] - min_val) * num_bins / length
|
|
63
|
+
|
|
64
|
+
# Clamp to [0, num_bins-1]
|
|
65
|
+
if converted < 0.0:
|
|
66
|
+
bin_idx = 0
|
|
67
|
+
elif converted >= <float>(num_bins - 1):
|
|
68
|
+
bin_idx = num_bins - 1
|
|
69
|
+
else:
|
|
70
|
+
bin_idx = <int>converted # Truncation (matches C++ cast)
|
|
71
|
+
|
|
72
|
+
# Increment histogram bin
|
|
73
|
+
histogram[i, bin_idx] += 1
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@cython.boundscheck(False)
|
|
77
|
+
@cython.wraparound(False)
|
|
78
|
+
@cython.cdivision(True)
|
|
79
|
+
cdef void compute_median_c(int[:, :] histogram,
|
|
80
|
+
double[:] median,
|
|
81
|
+
int feature_dim,
|
|
82
|
+
int num_bins,
|
|
83
|
+
int hist_count,
|
|
84
|
+
float min_val,
|
|
85
|
+
float bin_width) nogil:
|
|
86
|
+
"""
|
|
87
|
+
Compute median from histogram using cumulative sum (C implementation)
|
|
88
|
+
|
|
89
|
+
This is performance-critical: nested loops with early termination.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
histogram: Histogram array (feature_dim, num_bins)
|
|
93
|
+
median: Output median vector (feature_dim,) - modified in-place
|
|
94
|
+
feature_dim: Number of features
|
|
95
|
+
num_bins: Number of histogram bins
|
|
96
|
+
hist_count: Total number of histogram updates
|
|
97
|
+
min_val: Minimum value for histogram range
|
|
98
|
+
bin_width: Width of each bin
|
|
99
|
+
"""
|
|
100
|
+
cdef int i, j, cutoff_point, cumulative_sum
|
|
101
|
+
|
|
102
|
+
cutoff_point = (hist_count + 1) / 2 # Integer division
|
|
103
|
+
|
|
104
|
+
for i in range(feature_dim):
|
|
105
|
+
cumulative_sum = 0
|
|
106
|
+
|
|
107
|
+
for j in range(num_bins):
|
|
108
|
+
cumulative_sum += histogram[i, j]
|
|
109
|
+
|
|
110
|
+
if cumulative_sum >= cutoff_point:
|
|
111
|
+
# Convert bin index back to value
|
|
112
|
+
# Formula: min_val + bin_idx * bin_width + 0.5 * bin_width
|
|
113
|
+
median[i] = min_val + <double>j * bin_width + 0.5 * bin_width
|
|
114
|
+
break # Early termination (critical for performance!)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
cdef class HistogramMedianTrackerCython:
|
|
118
|
+
"""
|
|
119
|
+
Cython-optimized histogram-based running median tracker
|
|
120
|
+
|
|
121
|
+
Direct replacement for Python HistogramBasedMedianTracker with
|
|
122
|
+
10-20x performance improvement.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# C-level attributes (no Python overhead)
|
|
126
|
+
cdef int feature_dim
|
|
127
|
+
cdef int num_bins
|
|
128
|
+
cdef int hist_count
|
|
129
|
+
cdef float min_val
|
|
130
|
+
cdef float max_val
|
|
131
|
+
cdef float length
|
|
132
|
+
cdef float bin_width
|
|
133
|
+
|
|
134
|
+
# NumPy arrays (typed memoryviews for C-level access)
|
|
135
|
+
cdef cnp.ndarray histogram_array
|
|
136
|
+
cdef cnp.ndarray median_array
|
|
137
|
+
|
|
138
|
+
def __init__(self, int feature_dim, int num_bins=200,
|
|
139
|
+
float min_val=-3.0, float max_val=5.0):
|
|
140
|
+
"""
|
|
141
|
+
Initialize histogram-based median tracker
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
feature_dim: Dimensionality of feature vectors
|
|
145
|
+
num_bins: Number of histogram bins (default: 200)
|
|
146
|
+
min_val: Minimum value for histogram range
|
|
147
|
+
max_val: Maximum value for histogram range
|
|
148
|
+
"""
|
|
149
|
+
self.feature_dim = feature_dim
|
|
150
|
+
self.num_bins = num_bins
|
|
151
|
+
self.min_val = min_val
|
|
152
|
+
self.max_val = max_val
|
|
153
|
+
self.hist_count = 0
|
|
154
|
+
|
|
155
|
+
# Precompute constants
|
|
156
|
+
self.length = max_val - min_val
|
|
157
|
+
self.bin_width = self.length / num_bins
|
|
158
|
+
|
|
159
|
+
# Allocate arrays (C-contiguous for cache efficiency)
|
|
160
|
+
self.histogram_array = np.zeros((feature_dim, num_bins), dtype=np.int32, order='C')
|
|
161
|
+
self.median_array = np.zeros(feature_dim, dtype=np.float64, order='C')
|
|
162
|
+
|
|
163
|
+
def update(self, cnp.ndarray[FLOAT32, ndim=1] features, bint update_histogram=True):
|
|
164
|
+
"""
|
|
165
|
+
Update tracker with new feature vector
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
features: Feature vector (1D float32 array)
|
|
169
|
+
update_histogram: Whether to update histogram (every 2nd frame in OF2.2)
|
|
170
|
+
"""
|
|
171
|
+
# Declare all cdef variables at the beginning
|
|
172
|
+
cdef int[:, :] hist_view
|
|
173
|
+
cdef float[:] feat_view
|
|
174
|
+
cdef double[:] median_view
|
|
175
|
+
|
|
176
|
+
if features.shape[0] != self.feature_dim:
|
|
177
|
+
raise ValueError(f"Expected {self.feature_dim} features, got {features.shape[0]}")
|
|
178
|
+
|
|
179
|
+
if update_histogram:
|
|
180
|
+
# Update histogram using C function
|
|
181
|
+
hist_view = self.histogram_array
|
|
182
|
+
feat_view = features
|
|
183
|
+
|
|
184
|
+
with nogil:
|
|
185
|
+
update_histogram_c(feat_view, hist_view, self.feature_dim,
|
|
186
|
+
self.num_bins, self.min_val, self.bin_width,
|
|
187
|
+
self.length)
|
|
188
|
+
|
|
189
|
+
self.hist_count += 1
|
|
190
|
+
|
|
191
|
+
# Compute median
|
|
192
|
+
if self.hist_count == 0:
|
|
193
|
+
# Frame 0: use features directly
|
|
194
|
+
self.median_array[:] = features
|
|
195
|
+
elif self.hist_count == 1:
|
|
196
|
+
# Frame 1: still use features directly (matches C++)
|
|
197
|
+
self.median_array[:] = features
|
|
198
|
+
else:
|
|
199
|
+
# Frame 2+: compute from histogram
|
|
200
|
+
hist_view = self.histogram_array
|
|
201
|
+
median_view = self.median_array
|
|
202
|
+
|
|
203
|
+
with nogil:
|
|
204
|
+
compute_median_c(hist_view, median_view, self.feature_dim,
|
|
205
|
+
self.num_bins, self.hist_count,
|
|
206
|
+
self.min_val, self.bin_width)
|
|
207
|
+
|
|
208
|
+
def get_median(self):
|
|
209
|
+
"""Get current running median"""
|
|
210
|
+
return self.median_array.copy()
|
|
211
|
+
|
|
212
|
+
def reset(self):
|
|
213
|
+
"""Reset tracker"""
|
|
214
|
+
self.histogram_array.fill(0)
|
|
215
|
+
self.median_array.fill(0.0)
|
|
216
|
+
self.hist_count = 0
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def count(self):
|
|
220
|
+
"""Get histogram update count"""
|
|
221
|
+
return self.hist_count
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
cdef class DualHistogramMedianTrackerCython:
|
|
225
|
+
"""
|
|
226
|
+
Cython-optimized dual histogram median tracker
|
|
227
|
+
|
|
228
|
+
Manages separate trackers for HOG and geometric features.
|
|
229
|
+
Drop-in replacement for Python DualHistogramMedianTracker.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
cdef HistogramMedianTrackerCython hog_tracker
|
|
233
|
+
cdef HistogramMedianTrackerCython geom_tracker
|
|
234
|
+
cdef int hog_dim
|
|
235
|
+
cdef int geom_dim
|
|
236
|
+
|
|
237
|
+
def __init__(self,
|
|
238
|
+
int hog_dim=4464,
|
|
239
|
+
int geom_dim=238,
|
|
240
|
+
int hog_bins=200,
|
|
241
|
+
float hog_min=-3.0,
|
|
242
|
+
float hog_max=5.0,
|
|
243
|
+
int geom_bins=200,
|
|
244
|
+
float geom_min=-3.0,
|
|
245
|
+
float geom_max=5.0):
|
|
246
|
+
"""
|
|
247
|
+
Initialize dual histogram median tracker
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
hog_dim: HOG feature dimensionality
|
|
251
|
+
geom_dim: Geometric feature dimensionality
|
|
252
|
+
hog_bins, hog_min, hog_max: HOG histogram parameters
|
|
253
|
+
geom_bins, geom_min, geom_max: Geometric histogram parameters
|
|
254
|
+
"""
|
|
255
|
+
self.hog_dim = hog_dim
|
|
256
|
+
self.geom_dim = geom_dim
|
|
257
|
+
|
|
258
|
+
self.hog_tracker = HistogramMedianTrackerCython(
|
|
259
|
+
hog_dim, hog_bins, hog_min, hog_max
|
|
260
|
+
)
|
|
261
|
+
self.geom_tracker = HistogramMedianTrackerCython(
|
|
262
|
+
geom_dim, geom_bins, geom_min, geom_max
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def update(self,
|
|
266
|
+
cnp.ndarray[FLOAT32, ndim=1] hog_features,
|
|
267
|
+
cnp.ndarray[FLOAT32, ndim=1] geom_features,
|
|
268
|
+
bint update_histogram=True):
|
|
269
|
+
"""
|
|
270
|
+
Update both trackers
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
hog_features: HOG feature vector (hog_dim,)
|
|
274
|
+
geom_features: Geometric feature vector (geom_dim,)
|
|
275
|
+
update_histogram: Whether to update histograms
|
|
276
|
+
"""
|
|
277
|
+
self.hog_tracker.update(hog_features, update_histogram)
|
|
278
|
+
|
|
279
|
+
# CRITICAL: OpenFace clamps HOG median to >= 0 after update
|
|
280
|
+
# (FaceAnalyser.cpp line 405: this->hog_desc_median.setTo(0, this->hog_desc_median < 0);)
|
|
281
|
+
cdef double[:] hog_median_view = self.hog_tracker.median_array
|
|
282
|
+
cdef int i
|
|
283
|
+
for i in range(self.hog_dim):
|
|
284
|
+
if hog_median_view[i] < 0.0:
|
|
285
|
+
hog_median_view[i] = 0.0
|
|
286
|
+
|
|
287
|
+
self.geom_tracker.update(geom_features, update_histogram)
|
|
288
|
+
|
|
289
|
+
def get_combined_median(self):
|
|
290
|
+
"""
|
|
291
|
+
Get concatenated [HOG_median, geom_median]
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Combined median vector (hog_dim + geom_dim,)
|
|
295
|
+
"""
|
|
296
|
+
hog_median = self.hog_tracker.get_median()
|
|
297
|
+
geom_median = self.geom_tracker.get_median()
|
|
298
|
+
return np.concatenate([hog_median, geom_median])
|
|
299
|
+
|
|
300
|
+
def get_hog_median(self):
|
|
301
|
+
"""Get HOG running median"""
|
|
302
|
+
return self.hog_tracker.get_median()
|
|
303
|
+
|
|
304
|
+
def get_geom_median(self):
|
|
305
|
+
"""Get geometric running median"""
|
|
306
|
+
return self.geom_tracker.get_median()
|
|
307
|
+
|
|
308
|
+
def reset(self):
|
|
309
|
+
"""Reset both trackers"""
|
|
310
|
+
self.hog_tracker.reset()
|
|
311
|
+
self.geom_tracker.reset()
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def count(self):
|
|
315
|
+
"""Get histogram update count"""
|
|
316
|
+
return self.hog_tracker.count
|