@@ -232,8 +232,7 @@ def local_norm_median_val(
232232 u : np .ndarray ,
233233 v : np .ndarray ,
234234 ε : float ,
235- u_threshold : float ,
236- v_threshold : float ,
235+ threshold : float ,
237236 size : int = 1
238237 )-> np .ndarray :
239238 """This function is adapted from OpenPIV's implementation of
@@ -245,6 +244,8 @@ def local_norm_median_val(
245244 article J. Westerweel, F. Scarano, "Universal outlier detection for PIV data",
246245 Experiments in fluids, 39(6), p.1096-1100, 2005.
247246 For the list of parameters, see the referenced article, equation 2 on p.1097.
247+ The current function implements equation 2 from the referenced article in a
248+ manner shown in the MATLAB script at the end of the article, on p.1100.
248249
249250 This validation method tests for the spatial consistency of the data.
250251 Vectors are classified as outliers and replaced with Nan (Not a Number) if
@@ -267,11 +268,8 @@ def local_norm_median_val(
267268 ε : float
268269 minimum normalization level (see the referenced article, eqn.2)
269270
270- u_threshold : float
271- the threshold value for component u
272-
273- v_threshold : float
274- the threshold value for component v
271+ threshold : float
272+ the threshold to determine whether the vector is valid or not
275273
276274 size: int
277275 the representative size of the kernel of the median filter, the
@@ -286,6 +284,65 @@ def local_norm_median_val(
286284 a boolean array; true elements corresponds to outliers
287285
288286 """
287+ if np .ma .is_masked (u ):
288+ masked_u = np .where (~ u .mask , u .data , np .nan )
289+ masked_v = np .where (~ v .mask , v .data , np .nan )
290+ else :
291+ masked_u = u
292+ masked_v = v
293+
294+ um = generic_filter (masked_u ,
295+ np .nanmedian ,
296+ mode = 'constant' ,
297+ cval = np .nan ,
298+ size = (2 * size + 1 , 2 * size + 1 )
299+ )
300+ vm = generic_filter (masked_v ,
301+ np .nanmedian ,
302+ mode = 'constant' ,
303+ cval = np .nan ,
304+ size = (2 * size + 1 , 2 * size + 1 )
305+ )
306+
307+ def rfunc (x ):
308+ """
309+ Implementation of r from the cited article (see the description of
310+ the function above). x is the array within the filtering kernel.
311+ I.e., every element of x is a velocity vector ui or vi.
312+ This function must return a scalar: https://stackoverflow.com/a/14060024/10073233
313+ """
314+ # copied from here: https://stackoverflow.com/a/60166608/10073233
315+ np .put (x , x .size // 2 , np .nan ) # put NaN in the middle to avoid using
316+ # the middle in the calculations
317+ xm = np .nanmedian (x ) # Um for the current filtering window
318+ rm = np .nanmedian (np .abs (np .subtract (x ,xm ))) # median of |ui-um| or |vi-vm|
319+ return rm
320+
321+ rm_u = generic_filter (masked_u ,
322+ rfunc ,
323+ mode = 'constant' ,
324+ cval = np .nan ,
325+ size = (2 * size + 1 , 2 * size + 1 )
326+ )
327+ rm_v = generic_filter (masked_v ,
328+ rfunc ,
329+ mode = 'constant' ,
330+ cval = np .nan ,
331+ size = (2 * size + 1 , 2 * size + 1 )
332+ )
333+
334+ r0ast_u = np .divide (np .abs (np .subtract (masked_u ,um )), np .add (rm_u ,ε )) # r0ast stands for r_0^* -
335+ # see formula 2 in the
336+ # referenced article
337+ # (see description of the function)
338+ r0ast_v = np .divide (np .abs (np .subtract (masked_v ,vm )), np .add (rm_v ,ε )) # r0ast stands for r_0^* -
339+ # see formula 2 in the
340+ # referenced article
341+ # (see description of the function)
342+
343+ ind = (np .sqrt (np .add (np .square (r0ast_u ),np .square (r0ast_v )))) > threshold
344+
345+ return ind
289346
290347
291348def typical_validation (
0 commit comments