27
27
"""
28
28
29
29
import logging
30
- from numpy import ones , where , empty , array , concatenate , ma , zeros
30
+ from numpy import where , empty , array , concatenate , ma , zeros , unique , round
31
31
from scipy .ndimage import minimum_filter
32
- from scipy .ndimage import binary_erosion
33
32
from matplotlib .figure import Figure
34
33
from .tools import index_from_nearest_path , index_from_nearest_path_with_pt_in_bbox
35
34
@@ -39,7 +38,7 @@ class Amplitude(object):
39
38
Class to calculate *amplitude* and counts of *local maxima/minima*
40
39
within a closed region of a sea level anomaly field.
41
40
"""
42
-
41
+ EPSILON = 1e-8
43
42
__slots__ = (
44
43
'h_0' ,
45
44
'grid_extract' ,
@@ -48,7 +47,6 @@ class Amplitude(object):
48
47
'contour' ,
49
48
'interval' ,
50
49
'amplitude' ,
51
- 'local_extrema_inds' ,
52
50
'mle' ,
53
51
)
54
52
@@ -73,7 +71,6 @@ def __init__(self, contour, contour_height, data, interval):
73
71
self .sla = data [contour .pixels_index ]
74
72
# Amplitude which will be provide
75
73
self .amplitude = 0
76
- self .local_extrema_inds = None
77
74
# Maximum local extrema accepted
78
75
self .mle = 1
79
76
@@ -98,54 +95,96 @@ def all_pixels_below_h0(self, level):
98
95
Check CSS11 criterion 1: The SSH values of all of the pixels
99
96
are below a given SSH threshold for cyclonic eddies.
100
97
"""
101
- if (self .sla > self .h_0 ).any () or (hasattr (self .sla , 'mask' ) and self .sla .mask .any ()):
98
+ # In some case pixel value must be very near of contour bounds
99
+ if ((self .sla - self .h_0 ) > self .EPSILON ).any () or (hasattr (self .sla , 'mask' ) and self .sla .mask .any ()):
102
100
return False
103
101
else :
104
- self ._set_local_extrema (1 )
105
- lmi_i , lmi_j = where (self .local_extrema_inds )
106
- m = self .mask [lmi_i , lmi_j ]
107
- lmi_i , lmi_j = lmi_i [m ], lmi_j [m ]
108
- nb_real_extrema = ((level - self .grid_extract [lmi_i , lmi_j ]) >= 2 * self .interval ).sum ()
109
- if nb_real_extrema > self .mle :
110
- return False
111
- amp_min = level - (2 * self .interval )
112
- index = self .grid_extract [lmi_i , lmi_j ].argmin ()
113
- jmin_ , imin_ = lmi_j [index ], lmi_i [index ]
114
- if self .grid_extract [imin_ , jmin_ ] <= amp_min :
115
- self ._set_cyc_amplitude ()
102
+ # All local extrema index on th box
103
+ lmi_i , lmi_j = self ._set_local_extrema (1 )
116
104
slice_x , slice_y = self .contour .bbox_slice
117
- return imin_ + slice_x .start , jmin_ + slice_y .start
105
+ if len (lmi_i ) == 1 :
106
+ i , j = lmi_i [0 ] + slice_x .start , lmi_j [0 ] + slice_y .start
107
+ else :
108
+ # Verify if several extrema are seriously below contour
109
+ nb_real_extrema = ((level - self .grid_extract [lmi_i , lmi_j ]) >= 2 * self .interval ).sum ()
110
+ if nb_real_extrema > self .mle :
111
+ return False
112
+ index = self .grid_extract [lmi_i , lmi_j ].argmin ()
113
+ i , j = lmi_i [index ] + slice_x .start , lmi_j [index ] + slice_y .start
114
+ self ._set_cyc_amplitude ()
115
+ return i , j
118
116
119
117
def all_pixels_above_h0 (self , level ):
120
118
"""
121
119
Check CSS11 criterion 1: The SSH values of all of the pixels
122
120
are above a given SSH threshold for anticyclonic eddies.
123
121
"""
124
- if (self .sla < self .h_0 ).any () or (hasattr (self .sla , 'mask' ) and self .sla .mask .any ()):
122
+ # In some case pixel value must be very near of contour bounds
123
+ if ((self .sla - self .h_0 ) < - self .EPSILON ).any () or (hasattr (self .sla , 'mask' ) and self .sla .mask .any ()):
125
124
# i.e.,with self.amplitude == 0
126
125
return False
127
126
else :
128
- self ._set_local_extrema (- 1 )
129
- lmi_i , lmi_j = where (self .local_extrema_inds )
130
- m = self .mask [lmi_i , lmi_j ]
131
- lmi_i , lmi_j = lmi_i [m ], lmi_j [m ]
132
- nb_real_extrema = ((self .grid_extract [lmi_i , lmi_j ] - level ) >= 2 * self .interval ).sum ()
133
- if nb_real_extrema > self .mle :
134
- return False
135
- amp_min = level + (2 * self .interval )
136
- index = self .grid_extract [lmi_i , lmi_j ].argmax ()
137
- jmin_ , imin_ = lmi_j [index ], lmi_i [index ]
138
- if self .grid_extract [imin_ , jmin_ ] >= amp_min :
139
- self ._set_cyc_amplitude ()
127
+
128
+ # All local extrema index on th box
129
+ lmi_i , lmi_j = self ._set_local_extrema (- 1 )
140
130
slice_x , slice_y = self .contour .bbox_slice
141
- return imin_ + slice_x .start , jmin_ + slice_y .start
131
+ if len (lmi_i ) == 1 :
132
+ i , j = lmi_i [0 ] + slice_x .start , lmi_j [0 ] + slice_y .start
133
+ else :
134
+ # Verify if several extrema are seriously above contour
135
+ nb_real_extrema = ((self .grid_extract [lmi_i , lmi_j ] - level ) >= 2 * self .interval ).sum ()
136
+ if nb_real_extrema > self .mle :
137
+ return False
138
+ index = self .grid_extract [lmi_i , lmi_j ].argmax ()
139
+ i , j = lmi_i [index ] + slice_x .start , lmi_j [index ] + slice_y .start
140
+ self ._set_cyc_amplitude ()
141
+ return i , j
142
142
143
143
def _set_local_extrema (self , sign ):
144
144
"""
145
145
Set count of local SLA maxima/minima within eddy
146
146
"""
147
- # mask of minima
148
- self .local_extrema_inds = self .detect_local_minima (self .grid_extract * sign )
147
+ # index of minima on whole grid extract
148
+ i_x , i_y = self .detect_local_minima (self .grid_extract * sign )
149
+ # Only index in contour
150
+ m = self .mask [i_x , i_y ]
151
+ i_x , i_y = i_x [m ], i_y [m ]
152
+ # Verify if some extramum is contigus
153
+ nb_extrema = len (i_x )
154
+ if nb_extrema > 1 :
155
+ # Group
156
+ nb_group = 1
157
+ gr = zeros (nb_extrema , dtype = 'u2' )
158
+ for i1 , (i , j ) in enumerate (zip (i_x [:- 1 ], i_y [:- 1 ])):
159
+ for i2 , (k , l ) in enumerate (zip (i_x [i1 + 1 :], i_y [i1 + 1 :])):
160
+ if (abs (i - k ) + abs (j - l )) == 1 :
161
+ i2_ = i2 + i1 + 1
162
+ if gr [i1 ] == 0 and gr [i2_ ] == 0 :
163
+ # Nobody was link with a know group
164
+ gr [i1 ] = nb_group
165
+ gr [i2_ ] = nb_group
166
+ nb_group += 1
167
+ elif gr [i1 ] == 0 and gr [i2_ ] != 0 :
168
+ # i2 is link not i1
169
+ gr [i1 ] = gr [i2_ ]
170
+ elif gr [i2_ ] == 0 and gr [i1 ] != 0 :
171
+ # i1 is link not i2
172
+ gr [i2_ ] = gr [i1 ]
173
+ else :
174
+ # there already linked in two different group
175
+ # we replace group from i1 with group from i2
176
+ gr [gr == gr [i1 ]] = gr [i2_ ]
177
+ m = gr != 0
178
+ grs = unique (gr [m ])
179
+ # all non grouped extremum
180
+ i_x_new , i_y_new = list (i_x [~ m ]), list (i_y [~ m ])
181
+ for gr_ in grs :
182
+ m = gr_ == gr
183
+ # Choose barycentre of group
184
+ i_x_new .append (round (i_x [m ].mean (axis = 0 )).astype ('i2' ))
185
+ i_y_new .append (round (i_y [m ].mean (axis = 0 )).astype ('i2' ))
186
+ return i_x_new , i_y_new
187
+ return i_x , i_y
149
188
150
189
@staticmethod
151
190
def detect_local_minima (grid ):
@@ -155,20 +194,13 @@ def detect_local_minima(grid):
155
194
the pixel's value is the neighborhood maximum, 0 otherwise)
156
195
http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710
157
196
"""
158
- # Equivalent
159
- neighborhood = ones ((3 , 3 ), dtype = 'bool' )
197
+ # To don't perturbate filter
160
198
if hasattr (grid , 'mask' ):
161
199
grid [grid .mask ] = 2e10
162
200
# Get local mimima
163
- detected_minima = minimum_filter (
164
- grid , footprint = neighborhood ) == grid
165
- background = (grid == 0 )
166
- # Aims ?
167
- eroded_background = binary_erosion (
168
- background , structure = neighborhood , border_value = 1 )
169
- detected_minima ^= eroded_background
170
- # mask of minima
171
- return detected_minima
201
+ detected_minima = minimum_filter (grid , size = 3 ) == grid
202
+ # index of minima
203
+ return where (detected_minima )
172
204
173
205
174
206
class Contours (object ):
0 commit comments