pyopenrivercam 0.8.11__py3-none-any.whl → 0.9.0__py3-none-any.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyopenrivercam
3
- Version: 0.8.11
3
+ Version: 0.9.0
4
4
  Summary: pyorc: free and open-source image-based surface velocity and discharge.
5
5
  Author-email: Hessel Winsemius <winsemius@rainbowsensing.com>
6
6
  Requires-Python: >=3.9
@@ -21,23 +21,22 @@ Requires-Dist: click
21
21
  Requires-Dist: cython; platform_machine == 'armv7l'
22
22
  Requires-Dist: dask
23
23
  Requires-Dist: descartes
24
- Requires-Dist: ffpiv>=0.1.4
24
+ Requires-Dist: ffpiv>=0.2.0
25
25
  Requires-Dist: flox
26
26
  Requires-Dist: geojson
27
27
  Requires-Dist: geopandas
28
28
  Requires-Dist: matplotlib
29
29
  Requires-Dist: netCDF4
30
- Requires-Dist: numba
31
- Requires-Dist: numpy>=1.23, <2
30
+ Requires-Dist: numba>0.56
31
+ Requires-Dist: numpy>2
32
32
  Requires-Dist: opencv-python
33
- Requires-Dist: openpiv
34
33
  Requires-Dist: packaging; platform_machine == 'armv7l'
35
34
  Requires-Dist: pip
36
35
  Requires-Dist: pyproj
37
36
  Requires-Dist: pythran; platform_machine == 'armv7l'
38
37
  Requires-Dist: pyyaml
39
- Requires-Dist: rasterio<1.4.0
40
- Requires-Dist: scikit-image
38
+ Requires-Dist: rasterio<1.4.0; python_version <= '3.12'
39
+ Requires-Dist: rasterio; python_version > '3.12'
41
40
  Requires-Dist: scipy
42
41
  Requires-Dist: shapely
43
42
  Requires-Dist: tqdm
@@ -112,11 +111,15 @@ We are seeking funding for the following frequently requested functionalities:
112
111
 
113
112
  If you wish to fund this or other work on features, please contact us at info@rainbowsensing.com.
114
113
 
115
- > **_note:_** For instructions how to get Anaconda (with lots of pre-installed libraries) or Miniconda (light weight) installed, please go to https://docs.conda.io/projects/conda/en/latest/
114
+ > [!NOTE]
115
+ > For instructions how to get Anaconda (with lots of pre-installed libraries) or Miniconda (light weight) installed,
116
+ > please go to https://docs.conda.io/projects/conda/en/latest/
116
117
 
117
- > **_manual:_** Please go to https://localdevices.github.io/pyorc for the latest documentation
118
+ > [!TIP]
119
+ > Please go to https://localdevices.github.io/pyorc for the latest documentation
118
120
 
119
- > **_compatibility:_** At this moment **pyorc** works with any video compatible with OpenCV as long as it has proper metadata.
121
+ > [!IMPORTANT]
122
+ > At this moment **pyorc** works with any video compatible with OpenCV as long as it has proper metadata.
120
123
 
121
124
  ## Installation
122
125
  You need a python environment. We recommend using the Miniforge project. Download
@@ -158,6 +161,18 @@ pip install pyopenrivercam[extra]
158
161
  The `[extra]` section ensures that also geographical plotting is supported, which we recommend especially for the
159
162
  set up of a camera configuration with RTK-GPS measured control points.
160
163
 
164
+ > [!NOTE]
165
+ >
166
+ > Most of the heavy lifting is done while deriving cross-correlations for estimation of velocity vectors with Particle
167
+ > Image Velocimetry. You can speed up this process (x2) by installing `rocket-fft`. With `python <= 3.12` this
168
+ > is automatically included. With higher versions, you need, for the moment, to install it separately as follows:
169
+ >
170
+ > ```shell
171
+ > pip install git+https://github.com/localdevices/rocket-fft.git
172
+ > ```
173
+ >
174
+ > Once rocket-fft gets updated in PyPi you will no longer need this separate installation procedure.
175
+
161
176
  ### Upgrading from pypi with pip
162
177
 
163
178
  Did you read about a new version and you want to upgrade? Simply activate your virtual environment, type
@@ -175,6 +190,8 @@ If you use `mamba` as a package manager, then the steps are the same, except for
175
190
  ```shell
176
191
  mamba install pyopenrivercam
177
192
  ```
193
+ The version installed may not have the latest underlying libraries and therefore may be slower than the latest PyPi
194
+ version. We therefore recommend using `pip` for installation (see above).
178
195
 
179
196
  ### Installation from latest code base
180
197
 
@@ -0,0 +1,33 @@
1
+ pyorc/__init__.py,sha256=yPY8lllCcJb3rKv4-9O6b393ZSX86dAiOSRqAbEBmaQ,523
2
+ pyorc/const.py,sha256=Ia0KRkm-E1lJk4NxQVPDIfN38EBB7BKvxmwIHJrGPUY,2597
3
+ pyorc/cv.py,sha256=t2ZR4eyGbiwlIaGHysOheWdaDQuqpWLKjcTiAUzWAR0,50261
4
+ pyorc/helpers.py,sha256=jed0YyywnpvsZS-8mcA7Lfzn9np1MTlmVLE_PDn2QY0,30454
5
+ pyorc/plot_helpers.py,sha256=gLKslspsF_Z4jib5jkBv2wRjKnHTbuRFgkp_PCmv-uU,1803
6
+ pyorc/project.py,sha256=CGKfICkQEpFRmh_ZeDEfbQ-wefJt7teWJd6B5IPF038,7747
7
+ pyorc/pyorc.sh,sha256=-xOSUNnMAwVbdNkjKNKMZMaBljWsGLhadG-j0DNlJP4,5
8
+ pyorc/sample_data.py,sha256=_yxtjhHc1sjHXZJRWQgBNOOn0Qqs2A5CavyFOQX5p8U,3241
9
+ pyorc/api/__init__.py,sha256=k2OQQH4NrtXTuVm23d0g_SX6H5DhnKC9_kDyzJ4dWdk,428
10
+ pyorc/api/cameraconfig.py,sha256=NP9F7LhPO3aO6FRWkrGl6XpX8O3K59zfTtaYR3Kujqw,65419
11
+ pyorc/api/cross_section.py,sha256=MH0AEw5K1Kc1ClZeQRBUYkShZYVk41fshLn6GzCZAas,65212
12
+ pyorc/api/frames.py,sha256=BnglhmHdbKlIip5tym3x-aICOpQRmm853109A7JkWk8,23189
13
+ pyorc/api/mask.py,sha256=A3TRMqi30L4N491C4FoYY0zvV1GwQ1U31OEkCJp_Nzc,16698
14
+ pyorc/api/orcbase.py,sha256=C23QTKOyxHUafyJsq_t7xn_BzAEvf4DDfzlYAopons8,4189
15
+ pyorc/api/plot.py,sha256=MxIEIS8l46bUaca0GtMazx8-k2_TbfQLrPCPAjuWos8,31082
16
+ pyorc/api/transect.py,sha256=wENKWt0u0lHtT0lPYv47faHf_iAN9Mmeev-vwWjnz6E,13382
17
+ pyorc/api/velocimetry.py,sha256=bfU_XPbUbrdBI2XGprzh_3YADbGHfy4OuS1oBlbLEEI,12047
18
+ pyorc/api/video.py,sha256=lGD6bcV6Uu2u3zuGF_m3KxX2Cyp9k-YHUiXA42TOE3E,22458
19
+ pyorc/cli/__init__.py,sha256=A7hOQV26vIccPnDc8L2KqoJOSpMpf2PiMOXS18pAsWg,32
20
+ pyorc/cli/cli_elements.py,sha256=zX9wv9-1KWC_E3cInGMm3g9jh4uXmT2NqooAMhhXR9s,22165
21
+ pyorc/cli/cli_utils.py,sha256=S7qOO4bintxXDSUl26u3Ujqu4JHb_TNhw5d6psyDrFo,15085
22
+ pyorc/cli/log.py,sha256=Vg8GznmrEPqijfW6wv4OCl8R00Ld_fVt-ULTitaDijY,2824
23
+ pyorc/cli/main.py,sha256=qhAZkUuAViCpHh9c19tpcpbs_xoZJkYHhOsEXJBFXfM,12742
24
+ pyorc/service/__init__.py,sha256=vPrzFlZ4e_GjnibwW6-k8KDz3b7WpgmGcwSDk0mr13Y,55
25
+ pyorc/service/camera_config.py,sha256=OsRLpe5jd-lu6HT4Vx5wEg554CMS-IKz-q62ir4VbPo,2375
26
+ pyorc/service/velocimetry.py,sha256=bPI1OdN_fi0gZES08mb7yqCS_4I-lKSZ2JvWSGTRD1E,34434
27
+ pyorc/velocimetry/__init__.py,sha256=5oShoMocCalcCZuIsBqlZlqQuKJgDDBUvXQIo-uqFPA,88
28
+ pyorc/velocimetry/ffpiv.py,sha256=92XDgzCW4mEZ5ow82zV0APOhfDc1OVftBjKqYdw1zzc,17494
29
+ pyopenrivercam-0.9.0.dist-info/entry_points.txt,sha256=Cv_WI2Y6QLnPiNCXGli0gS4WAOAeMoprha1rAR3vdRE,44
30
+ pyopenrivercam-0.9.0.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
31
+ pyopenrivercam-0.9.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
32
+ pyopenrivercam-0.9.0.dist-info/METADATA,sha256=d2rvFtUmIHAhMBruxLKJ8qEukxEl3y4fZWzAbeeNZ0w,12383
33
+ pyopenrivercam-0.9.0.dist-info/RECORD,,
pyorc/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """pyorc: free and open-source image-based surface velocity and discharge."""
2
2
 
3
- __version__ = "0.8.11"
3
+ __version__ = "0.9.0"
4
4
 
5
5
  from .api import CameraConfig, CrossSection, Frames, Transect, Velocimetry, Video, get_camera_config, load_camera_config # noqa
6
6
  from .project import * # noqa
@@ -278,7 +278,7 @@ class CrossSection:
278
278
  Parameters
279
279
  ----------
280
280
  h : float
281
- water level [m]
281
+ water level [m].
282
282
  sz : bool, optional
283
283
  If set, return water level line in y-z projection, by default False.
284
284
  extend_by : float, optional
@@ -613,7 +613,9 @@ class CrossSection:
613
613
  raise ValueError("Amount of water line crossings must be 2 for a planar surface estimate.")
614
614
  return geometry.Polygon(list(wls[0].coords) + list(wls[1].coords[::-1]))
615
615
 
616
- def get_wetted_surface_sz(self, h: float, perimeter: bool = False) -> Union[geometry.MultiPolygon, geometry.MultiLineString]:
616
+ def get_wetted_surface_sz(
617
+ self, h: float, perimeter: bool = False
618
+ ) -> Union[geometry.MultiPolygon, geometry.MultiLineString]:
617
619
  """Retrieve a wetted surface or perimeter perpendicular to flow direction (SZ) for a water level.
618
620
 
619
621
  This returns a `geometry.MultiPolygon` when a surface is requested (`perimeter=False`), and
@@ -1097,6 +1099,182 @@ class CrossSection:
1097
1099
  )
1098
1100
  return ax
1099
1101
 
1102
+ def _preprocess_level_range(
1103
+ self,
1104
+ min_h: Optional[float] = None,
1105
+ max_h: Optional[float] = None,
1106
+ min_z: Optional[float] = None,
1107
+ max_z: Optional[float] = None,
1108
+ ):
1109
+ """Set minimum and maximum z-levels (if not provided) based on the camera config."""
1110
+ if min_z is None:
1111
+ if min_h is not None:
1112
+ min_z = self.camera_config.h_to_z(min_h)
1113
+ min_z = np.maximum(min_z, self.z.min())
1114
+ if max_z is None:
1115
+ if max_h is not None:
1116
+ max_z = self.camera_config.h_to_z(max_h)
1117
+ max_z = np.minimum(max_z, self.z.max())
1118
+ if min_z and max_z:
1119
+ # check for z_min < z_max
1120
+ if min_z > max_z:
1121
+ raise ValueError("Minimum water level is higher than maximum water level.")
1122
+ return min_z, max_z
1123
+
1124
+ def _preprocess_l_range(self, l_min: float, l_max: float, ds_max=0.5, dz_max=0.02) -> List[float]:
1125
+ """Generate a list of evaluation points between l_min and l_max for water level detection.
1126
+
1127
+ Controls on evaluation are that vertically (using `self.z`), points are at least `dz_max` meters apart and
1128
+ horizontally (using `self.s`), points are included if the distance between them exceeds `dz_min` meters.
1129
+
1130
+ Parameters
1131
+ ----------
1132
+ l_min: float
1133
+ Minimum l value for evaluation.
1134
+ l_max: float
1135
+ Maximum l value for evaluation.
1136
+ ds_max : float, optional
1137
+ maximum step size between evaluation points in horizontal direction, by default 0.5.
1138
+ dz_max : float, optional
1139
+ maximum step size between evaluation points in vertical direction, by default 0.02.
1140
+
1141
+ Returns
1142
+ -------
1143
+ Tuple[List[float], List[float]]
1144
+ lists of l-coordinates and associated z-coordinates for evaluation.
1145
+
1146
+ """
1147
+ # # Initial list of evaluation points
1148
+ # l_range = []
1149
+ # z_range = []
1150
+ # Start with the first point within the range
1151
+ current_l = l_min
1152
+ last_z = None
1153
+ last_s = None
1154
+
1155
+ # Add all points from self.l that lie within [l_min, l_max]
1156
+ valid_l_indices = (self.l >= l_min) & (self.l <= l_max) # Logical filter
1157
+ l_range = list(self.l[valid_l_indices])
1158
+ z_range = list(self.z[valid_l_indices])
1159
+
1160
+ # Iterate over the self.l values by interpolating within the range
1161
+ while current_l <= l_max:
1162
+ # Interpolate x, y, z, and s using the available properties
1163
+ # x = self.interp_x(current_l)
1164
+ # y = self.interp_y(current_l)
1165
+ z = self.interp_z(current_l)
1166
+ s = self.interp_s_from_l(current_l)
1167
+
1168
+ # Append the point if it satisfies the required criteria
1169
+ if last_z is None or last_s is None or abs(z - last_z) >= dz_max or abs(s - last_s) >= ds_max:
1170
+ l_range.append(current_l)
1171
+ z_range.append(z)
1172
+ last_z = z
1173
+ last_s = s
1174
+
1175
+ # Increment l
1176
+ current_l += 0.01 # Increment in steps small enough to meet any constraint
1177
+
1178
+ # add the final l
1179
+ if current_l > l_max:
1180
+ l_range.append(l_max)
1181
+ z_range.append(self.interp_z(l_max))
1182
+
1183
+ # sort l_range and z_range by incremental l_range
1184
+ sorted_indices = np.argsort(l_range)
1185
+ l_range = np.array(l_range)[sorted_indices]
1186
+ z_range = np.array(z_range)[sorted_indices]
1187
+
1188
+ return l_range, z_range
1189
+
1190
+ def _water_level_score_range(
1191
+ self,
1192
+ img: np.ndarray,
1193
+ bank: BANK_OPTIONS = "far",
1194
+ bin_size: int = 5,
1195
+ length: float = 2.0,
1196
+ padding: float = 0.5,
1197
+ offset: float = 0.0,
1198
+ ds_max: Optional[float] = 0.5,
1199
+ dz_max: Optional[float] = 0.02,
1200
+ min_h: Optional[float] = None,
1201
+ max_h: Optional[float] = None,
1202
+ min_z: Optional[float] = None,
1203
+ max_z: Optional[float] = None,
1204
+ ) -> float:
1205
+ """Evaluate a range of scores for water level detection.
1206
+
1207
+ This computes the histogram score for a range of l values and returns all scores for further evaluation.
1208
+ The histogram score is a measure of dissimilarity (low means very dissimilar) of pixel intensity distributions
1209
+ left and right of hypothesized water lines. The evaluation points along the cross section (l-values) are
1210
+ determined by two parameters max_ds (maximum step in horizontal direction) and max_dz (maximum step in vertical
1211
+ direction).
1212
+
1213
+ Parameters
1214
+ ----------
1215
+ img : np.ndarray
1216
+ image (uint8) used to estimate water level from.
1217
+ bank: Literal["far", "near", "both"], optional
1218
+ select from which bank to detect the water level. Use this if camera is positioned in a way that only
1219
+ one shore is clearly distinguishable and not obscured. Typically you will use "far" if the camera is
1220
+ positioned on one bank, aimed perpendicular to the flow. Use "near" if not the full cross section is
1221
+ visible, but only the part nearest the camera. And leave empty when both banks are clearly visible and
1222
+ approximately the same in distance (e.g. middle of a bridge). If not provided, the bank is detected based
1223
+ on the best estimate from both banks.
1224
+ bin_size : int, optional
1225
+ Size of bins for histogram calculation of the provided image intensities, default 5.
1226
+ length : float, optional
1227
+ length of the waterline [m], by default 2.0
1228
+ padding : float, optional
1229
+ amount of distance [m] to extend the polygon beyond the waterline, by default 0.5. Two polygons are drawn
1230
+ left and right of hypothesized water line at -padding and +padding.
1231
+ offset : float, optional
1232
+ perpendicular offset of the waterline from the cross-section [m], by default 0.0
1233
+ ds_max : float, optional
1234
+ maximum step size between evaluation points in horizontal direction, by default 0.5.
1235
+ dz_max : float, optional
1236
+ maximum step size between evaluation points in vertical direction, by default 0.02.
1237
+ min_h : float, optional
1238
+ minimum water level to try detection [m]. If not provided, the minimum water level is taken from the
1239
+ cross section.
1240
+ max_h : float, optional
1241
+ maximum water level to try detection [m]. If not provided, the maximum water level is taken from the
1242
+ cross section.
1243
+ min_z : float, optional
1244
+ same as min_h but using z-coordinates instead of local datum, min_z overrules min_h
1245
+ max_z : float, optional
1246
+ same as max_z but using z-coordinates instead of local datum, max_z overrules max_h
1247
+
1248
+ """
1249
+ l_min, l_max = self.get_line_of_interest(bank=bank)
1250
+ min_z, max_z = self._preprocess_level_range(min_h, max_h, min_z, max_z)
1251
+ l_range, z_range = self._preprocess_l_range(l_min=l_min, l_max=l_max, ds_max=ds_max, dz_max=dz_max)
1252
+ if len(img.shape) == 3:
1253
+ # flatten image first if it his a time dimension
1254
+ img = img.mean(axis=2)
1255
+ assert (
1256
+ img.shape[0] == self.camera_config.height
1257
+ ), f"Image height {img.shape[0]} is not the same as camera_config height {self.camera_config.height}"
1258
+ assert (
1259
+ img.shape[1] == self.camera_config.width
1260
+ ), f"Image width {img.shape[1]} is not the same as camera_config width {self.camera_config.width}"
1261
+
1262
+ # gridded results
1263
+ results = [
1264
+ self.get_histogram_score(
1265
+ x=[l],
1266
+ img=img,
1267
+ bin_size=bin_size,
1268
+ offset=offset,
1269
+ padding=padding,
1270
+ length=length,
1271
+ min_z=min_z,
1272
+ max_z=max_z,
1273
+ )
1274
+ for l in l_range
1275
+ ]
1276
+ return l_range, z_range, results
1277
+
1100
1278
  def detect_water_level(
1101
1279
  self,
1102
1280
  img: np.ndarray,
@@ -1110,11 +1288,11 @@ class CrossSection:
1110
1288
  min_z: Optional[float] = None,
1111
1289
  max_z: Optional[float] = None,
1112
1290
  ) -> float:
1113
- """Detect water level optically from provided image.
1291
+ """Detect water level from provided image through optimization.
1114
1292
 
1115
1293
  Water level detection is done by first detecting the water line along the cross-section by comparisons
1116
1294
  of distribution functions left and right of hypothesized water lines, and then looking up the water level
1117
- associated with the water line location.
1295
+ associated with the water line location. A differential evolution optimization is used to find the optimum.
1118
1296
 
1119
1297
  Parameters
1120
1298
  ----------
@@ -1147,19 +1325,14 @@ class CrossSection:
1147
1325
  max_z : float, optional
1148
1326
  same as max_z but using z-coordinates instead of local datum, max_z overrules max_h
1149
1327
 
1150
- """
1151
- if min_z is None:
1152
- if min_h is not None:
1153
- min_z = self.camera_config.h_to_z(min_h)
1154
- min_z = np.maximum(min_z, self.z.min())
1155
- if max_z is None:
1156
- if max_h is not None:
1157
- max_z = self.camera_config.h_to_z(max_h)
1158
- max_z = np.minimum(max_z, self.z.max())
1159
- if min_z and max_z:
1160
- if min_z > max_z:
1161
- raise ValueError("Minimum water level is higher than maximum water level.")
1328
+ Returns
1329
+ -------
1330
+ float
1331
+ Most likely water level, according to optimization and scoring (most distinct intensity PDF)
1162
1332
 
1333
+ """
1334
+ l_min, l_max = self.get_line_of_interest(bank=bank)
1335
+ min_z, max_z = self._preprocess_level_range(min_h, max_h, min_z, max_z)
1163
1336
  if len(img.shape) == 3:
1164
1337
  # flatten image first if it his a time dimension
1165
1338
  img = img.mean(axis=2)
@@ -1169,9 +1342,7 @@ class CrossSection:
1169
1342
  assert (
1170
1343
  img.shape[1] == self.camera_config.width
1171
1344
  ), f"Image width {img.shape[1]} is not the same as camera_config width {self.camera_config.width}"
1172
- # determine the relevant start point if only one is used
1173
- # import pdb;pdb.set_trace()
1174
- l_min, l_max = self.get_line_of_interest(bank=bank)
1345
+
1175
1346
  opt = differential_evolution(
1176
1347
  self.get_histogram_score,
1177
1348
  popsize=50,
@@ -1190,3 +1361,93 @@ class CrossSection:
1190
1361
  stacklevel=2,
1191
1362
  )
1192
1363
  return h
1364
+
1365
+ def detect_water_level_s2n(
1366
+ self,
1367
+ img: np.ndarray,
1368
+ bank: BANK_OPTIONS = "far",
1369
+ bin_size: int = 5,
1370
+ length: float = 2.0,
1371
+ padding: float = 0.5,
1372
+ offset: float = 0.0,
1373
+ ds_max: Optional[float] = 0.5,
1374
+ dz_max: Optional[float] = 0.02,
1375
+ min_h: Optional[float] = None,
1376
+ max_h: Optional[float] = None,
1377
+ min_z: Optional[float] = None,
1378
+ max_z: Optional[float] = None,
1379
+ ) -> float:
1380
+ """Detect water level optically from provided image, through evaluation of a vector of locations.
1381
+
1382
+ Water level detection is done by first detecting the water line along the cross-section by comparisons
1383
+ of distribution functions left and right of hypothesized water lines, and then looking up the water level
1384
+ associated with the water line location. Because a full vector of results is evaluated, the signal to noise
1385
+ ratio can be evaluated and used to understand the quality of the water level detection. Two parameters are used
1386
+ to control the spacing between l (location) coordinates: `ds_max` controls the maximum step size in horizontal
1387
+ direction, `dz_max` controls the maximum step size in vertical direction.
1388
+
1389
+ Parameters
1390
+ ----------
1391
+ img : np.ndarray
1392
+ image (uint8) used to estimate water level from.
1393
+ bank: Literal["far", "near", "both"], optional
1394
+ select from which bank to detect the water level. Use this if camera is positioned in a way that only
1395
+ one shore is clearly distinguishable and not obscured. Typically you will use "far" if the camera is
1396
+ positioned on one bank, aimed perpendicular to the flow. Use "near" if not the full cross section is
1397
+ visible, but only the part nearest the camera. And leave empty when both banks are clearly visible and
1398
+ approximately the same in distance (e.g. middle of a bridge). If not provided, the bank is detected based
1399
+ on the best estimate from both banks.
1400
+ bin_size : int, optional
1401
+ Size of bins for histogram calculation of the provided image intensities, default 5.
1402
+ length : float, optional
1403
+ length of the waterline [m], by default 2.0.
1404
+ padding : float, optional
1405
+ amount of distance [m] to extend the polygon beyond the waterline, by default 0.5. Two polygons are drawn
1406
+ left and right of hypothesized water line at -padding and +padding.
1407
+ offset : float, optional
1408
+ perpendicular offset of the waterline from the cross-section [m], by default 0.0.
1409
+ ds_max : float, optional
1410
+ maximum step size between evaluation points in horizontal direction, by default 0.5.
1411
+ dz_max : float, optional
1412
+ maximum step size between evaluation points in vertical direction, by default 0.02.
1413
+ min_h : float, optional
1414
+ minimum water level to try detection [m]. If not provided, the minimum water level is taken from the
1415
+ cross section.
1416
+ max_h : float, optional
1417
+ maximum water level to try detection [m]. If not provided, the maximum water level is taken from the
1418
+ cross section.
1419
+ min_z : float, optional
1420
+ same as min_h but using z-coordinates instead of local datum, min_z overrules min_h.
1421
+ max_z : float, optional
1422
+ same as max_z but using z-coordinates instead of local datum, max_z overrules max_h.
1423
+
1424
+ Returns
1425
+ -------
1426
+ float
1427
+ Most likely water level, according to optimization and scoring (most distinct intensity PDF).
1428
+ float
1429
+ Signal to noise ratio, calculated as the mean of all computed scores divided by the optimum (lowest) score
1430
+ found in the entire l-range.
1431
+
1432
+ """
1433
+ l_range, z_range, results = self._water_level_score_range(
1434
+ img=img,
1435
+ bank=bank,
1436
+ bin_size=bin_size,
1437
+ length=length,
1438
+ padding=padding,
1439
+ offset=offset,
1440
+ ds_max=ds_max,
1441
+ dz_max=dz_max,
1442
+ min_h=min_h,
1443
+ max_h=max_h,
1444
+ min_z=min_z,
1445
+ max_z=max_z,
1446
+ )
1447
+ # find optimum (minimum score)
1448
+ idx = np.argmin(results)
1449
+ s2n = np.mean(results) / results[idx]
1450
+ z = z_range[idx]
1451
+ h = self.camera_config.z_to_h(z)
1452
+ # warning if the optimum is on the edge of the search space for l
1453
+ return h, s2n
pyorc/api/frames.py CHANGED
@@ -12,7 +12,7 @@ from matplotlib.animation import FuncAnimation
12
12
  from tqdm import tqdm
13
13
 
14
14
  from pyorc import const, cv, helpers, project
15
- from pyorc.velocimetry import ffpiv, openpiv
15
+ from pyorc.velocimetry import ffpiv
16
16
 
17
17
  from .orcbase import ORCBase
18
18
  from .plot import _frames_plot
@@ -124,14 +124,12 @@ class Frames(ORCBase):
124
124
  overlap : (int, int), optional
125
125
  amount of overlap between interrogation windows in pixels (y, x)
126
126
  engine : str, optional
127
- select the compute engine, can be "openpiv" (default), "numba", or "numpy". "numba" will give the fastest
128
- performance but is still experimental. It can boost performance by almost an order of magnitude compared
129
- to openpiv or numpy. both "numba" and "numpy" use the FF-PIV library as back-end.
127
+ select the compute engine, can be "numba" (default), or "numpy". "numba" will give the fastest
128
+ performance. It can boost performance by almost an order of magnitude compared
129
+ to numpy. both "numba" and "numpy" use the FF-PIV library as back-end.
130
130
  ensemble_corr : bool, optional
131
- only used with `engine="numba"` or `engine="numpy"`.
132
131
  If True, performs PIV by first averaging cross-correlations across all frames and then deriving velocities.
133
132
  If False, computes velocities for each frame pair separately. Default is True.
134
-
135
133
  **kwargs : dict
136
134
  keyword arguments to pass to the piv engine. For "numba" and "numpy" the argument `chunks` can be provided
137
135
  with an integer defining in how many batches of work the total velocimetry problem should be subdivided.
@@ -143,7 +141,6 @@ class Frames(ORCBase):
143
141
 
144
142
  See Also
145
143
  --------
146
- OpenPIV project: https://github.com/OpenPIV/openpiv-python
147
144
  FF-PIV project: https://github.com/localdevices/ffpiv
148
145
 
149
146
  """
@@ -167,43 +164,19 @@ class Frames(ORCBase):
167
164
  # get all required coordinates for the PIV result
168
165
  coords, mesh_coords = self.get_piv_coords(window_size, search_area_size, overlap)
169
166
  # provide kwargs for OpenPIV analysis
170
- if engine == "openpiv":
171
- # thresholds are not used.
172
-
173
- import warnings
174
-
175
- warnings.warn(
176
- '"openpiv" is deprecated, please use "numba" or "numpy" as engine',
177
- DeprecationWarning,
178
- stacklevel=2,
179
- )
180
- # Remove threshold parameters from kwargs
181
- kwargs.pop("corr_min", None)
182
- kwargs.pop("s2n_min", None)
183
- kwargs.pop("count_min", None)
184
- kwargs = {
185
- **kwargs,
186
- "search_area_size": search_area_size[0],
187
- "window_size": window_size[0],
188
- "overlap": overlap[0],
189
- "res_x": camera_config.resolution,
190
- "res_y": camera_config.resolution,
191
- }
192
- ds = openpiv.get_openpiv(self._obj, coords["y"], coords["x"], dt, **kwargs)
193
- elif engine in ["numba", "numpy"]:
194
- kwargs = {
195
- **kwargs,
196
- "search_area_size": search_area_size,
197
- "window_size": window_size,
198
- "overlap": overlap,
199
- "res_x": camera_config.resolution,
200
- "res_y": camera_config.resolution,
201
- }
202
- ds = ffpiv.get_ffpiv(
203
- self._obj, coords["y"], coords["x"], dt, engine=engine, ensemble_corr=ensemble_corr, **kwargs
204
- )
205
- else:
167
+ if engine not in ["numba", "numpy"]:
206
168
  raise ValueError(f"Selected PIV engine {engine} does not exist.")
169
+ kwargs = {
170
+ **kwargs,
171
+ "search_area_size": search_area_size,
172
+ "window_size": window_size,
173
+ "overlap": overlap,
174
+ "res_x": camera_config.resolution,
175
+ "res_y": camera_config.resolution,
176
+ }
177
+ ds = ffpiv.get_ffpiv(
178
+ self._obj, coords["y"], coords["x"], dt, engine=engine, ensemble_corr=ensemble_corr, **kwargs
179
+ )
207
180
  # add all 2D-coordinates
208
181
  ds = ds.velocimetry.add_xy_coords(mesh_coords, coords, {**const.PERSPECTIVE_ATTRS, **const.GEOGRAPHICAL_ATTRS})
209
182
  # ensure all metadata is transferred
@@ -359,7 +332,7 @@ class Frames(ORCBase):
359
332
  keep_attrs=True,
360
333
  )
361
334
 
362
- def minmax(self, min=-np.Inf, max=np.Inf):
335
+ def minmax(self, min=-np.inf, max=np.inf):
363
336
  """Minimum / maximum intensity filter.
364
337
 
365
338
  All pixels will be thresholded to a minimum and maximum value.