pyopenrivercam 0.8.5__py3-none-any.whl → 0.8.7__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.
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.7.dist-info}/METADATA +4 -4
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.7.dist-info}/RECORD +15 -15
- pyorc/__init__.py +1 -1
- pyorc/api/cameraconfig.py +161 -69
- pyorc/api/cross_section.py +59 -9
- pyorc/api/frames.py +19 -58
- pyorc/api/plot.py +75 -27
- pyorc/cli/cli_elements.py +11 -2
- pyorc/cli/cli_utils.py +70 -10
- pyorc/cli/main.py +29 -3
- pyorc/cv.py +187 -49
- pyorc/service/velocimetry.py +236 -174
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.7.dist-info}/WHEEL +0 -0
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.7.dist-info}/entry_points.txt +0 -0
- {pyopenrivercam-0.8.5.dist-info → pyopenrivercam-0.8.7.dist-info}/licenses/LICENSE +0 -0
pyorc/api/plot.py
CHANGED
|
@@ -6,8 +6,7 @@ import functools
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
import matplotlib.ticker as mticker
|
|
8
8
|
import numpy as np
|
|
9
|
-
from matplotlib import patheffects
|
|
10
|
-
from matplotlib.collections import QuadMesh
|
|
9
|
+
from matplotlib import colors, patheffects
|
|
11
10
|
|
|
12
11
|
from pyorc import helpers
|
|
13
12
|
|
|
@@ -132,7 +131,7 @@ def _base_plot(plot_func):
|
|
|
132
131
|
|
|
133
132
|
# check if dataset is a transect or not
|
|
134
133
|
is_transect = True if "points" in ref._obj.dims else False
|
|
135
|
-
|
|
134
|
+
kwargs = set_default_kwargs(kwargs, method=plot_func.__name__, mode=mode)
|
|
136
135
|
assert mode in ["local", "geographical", "camera"], 'Mode must be "local", "geographical" or "camera"'
|
|
137
136
|
if mode == "local":
|
|
138
137
|
x = ref._obj["x"].values
|
|
@@ -279,23 +278,22 @@ def _frames_plot(ref, ax=None, mode="local", **kwargs):
|
|
|
279
278
|
x = "xp"
|
|
280
279
|
y = "yp"
|
|
281
280
|
assert all(v in ref._obj.coords for v in [x, y]), f'required coordinates "{x}" and/or "{y}" are not available'
|
|
282
|
-
if
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ref._obj[
|
|
289
|
-
ref._obj.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# array has dims
|
|
296
|
-
QuadMesh.set_array(primitive, None)
|
|
297
|
-
else:
|
|
281
|
+
if x == "x":
|
|
282
|
+
# use a simple imshow, much faster
|
|
283
|
+
dx = abs(float(ref._obj[x][1] - ref._obj[x][0])) # x grid cell size
|
|
284
|
+
dy = abs(float(ref._obj[y][1] - ref._obj[y][0])) # y grid cell size
|
|
285
|
+
# Calculate extent with half grid cell expansion
|
|
286
|
+
extent = [
|
|
287
|
+
ref._obj[x].min().item() - 0.5 * dx,
|
|
288
|
+
ref._obj[x].max().item() + 0.5 * dx,
|
|
289
|
+
ref._obj[y].min().item() - 0.5 * dy,
|
|
290
|
+
ref._obj[y].max().item() + 0.5 * dy,
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
if x != "x":
|
|
298
294
|
primitive = ax.pcolormesh(ref._obj[x], ref._obj[y], ref._obj, **kwargs)
|
|
295
|
+
else:
|
|
296
|
+
primitive = ax.imshow(ref._obj, origin="upper", extent=extent, aspect="auto", **kwargs)
|
|
299
297
|
# fix axis limits to min and max of extent of frames
|
|
300
298
|
if mode == "geographical":
|
|
301
299
|
ax.set_extent(
|
|
@@ -371,7 +369,8 @@ class _Transect_PlotMethods:
|
|
|
371
369
|
_u = self._obj[v_eff] * np.sin(self._obj[v_dir])
|
|
372
370
|
_v = self._obj[v_eff] * np.cos(self._obj[v_dir])
|
|
373
371
|
s = np.abs(self._obj[v_eff].values)
|
|
374
|
-
x_moved, y_moved = x + _u * dt, y + _v * dt
|
|
372
|
+
# x_moved, y_moved = x + _u * dt, y + _v * dt
|
|
373
|
+
x_moved, y_moved = x + _u, y + _v
|
|
375
374
|
# transform to real-world
|
|
376
375
|
cols_moved, rows_moved = x_moved / camera_config.resolution, y_moved / camera_config.resolution
|
|
377
376
|
rows_moved = camera_config.shape[0] - rows_moved
|
|
@@ -496,9 +495,11 @@ class _Velocimetry_PlotMethods:
|
|
|
496
495
|
"""
|
|
497
496
|
# select lon and lat variables as coordinates
|
|
498
497
|
velocimetry = self._obj.velocimetry
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
498
|
+
v_x = self._obj["v_x"].values
|
|
499
|
+
v_y = self._obj["v_y"].values
|
|
500
|
+
u = v_x / (2 * 1e5) # 1e5 is aobut the amount of meters per degree
|
|
501
|
+
v = -v_y / (2 * 1e5)
|
|
502
|
+
s = (v_x**2 + v_y**2) ** 0.5
|
|
502
503
|
aff = velocimetry.camera_config.transform
|
|
503
504
|
theta = np.arctan2(aff.d, aff.a)
|
|
504
505
|
# rotate velocity vectors along angle theta to match the requested projection. this only changes values
|
|
@@ -519,9 +520,14 @@ class _Velocimetry_PlotMethods:
|
|
|
519
520
|
scalar velocity
|
|
520
521
|
|
|
521
522
|
"""
|
|
522
|
-
u
|
|
523
|
-
|
|
524
|
-
|
|
523
|
+
# u and v should be scaled to a nice proportionality for local plots. This is typically about 2x smaller
|
|
524
|
+
# than the m/s values
|
|
525
|
+
v_x = self._obj["v_x"].values
|
|
526
|
+
v_y = self._obj["v_y"].values
|
|
527
|
+
|
|
528
|
+
u = v_x / 2
|
|
529
|
+
v = -v_y / 2
|
|
530
|
+
s = (v_x**2 + v_y**2) ** 0.5
|
|
525
531
|
return u, v, s
|
|
526
532
|
|
|
527
533
|
def get_uv_camera(self, dt=0.1):
|
|
@@ -552,7 +558,8 @@ class _Velocimetry_PlotMethods:
|
|
|
552
558
|
yi = np.flipud(yi)
|
|
553
559
|
|
|
554
560
|
# follow the velocity vector over a short distance (dt*velocity)
|
|
555
|
-
x_moved, y_moved = xi + self._obj["v_x"] * dt, yi + self._obj["v_y"] * dt
|
|
561
|
+
# x_moved, y_moved = xi + self._obj["v_x"] * dt, yi + self._obj["v_y"] * dt
|
|
562
|
+
x_moved, y_moved = xi + self._obj["v_x"] / 2, yi + self._obj["v_y"] / 2
|
|
556
563
|
# transform to real-world
|
|
557
564
|
cols_moved, rows_moved = x_moved / camera_config.resolution, y_moved / camera_config.resolution
|
|
558
565
|
xs_moved, ys_moved = helpers.get_xs_ys(cols_moved, rows_moved, camera_config.transform)
|
|
@@ -578,10 +585,51 @@ class _Velocimetry_PlotMethods:
|
|
|
578
585
|
return u, v, s
|
|
579
586
|
|
|
580
587
|
|
|
588
|
+
def set_default_kwargs(kwargs, method="quiver", mode="local"):
|
|
589
|
+
"""Set color mapping default kwargs if no vmin and/or vmax is supplied."""
|
|
590
|
+
if mode == "local":
|
|
591
|
+
# width scale is in cm
|
|
592
|
+
width_scale = 0.02
|
|
593
|
+
elif mode == "geographical":
|
|
594
|
+
# widths are in degrees
|
|
595
|
+
width_scale = 0.00000025
|
|
596
|
+
elif mode == "camera":
|
|
597
|
+
# widths in pixels
|
|
598
|
+
width_scale = 3.0
|
|
599
|
+
else:
|
|
600
|
+
raise ValueError("mode must be one of 'local', 'geographical' or 'camera'")
|
|
601
|
+
if "cmap" not in kwargs:
|
|
602
|
+
kwargs["cmap"] = "rainbow" # the famous rainbow colormap!
|
|
603
|
+
if "vmin" not in kwargs and "vmax" not in kwargs and "norm" not in kwargs:
|
|
604
|
+
# set a normalization array
|
|
605
|
+
norm = [0, 0.05, 0.1, 0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
|
|
606
|
+
kwargs["norm"] = colors.BoundaryNorm(norm, ncolors=256, extend="max")
|
|
607
|
+
if method == "quiver":
|
|
608
|
+
if "scale" not in kwargs:
|
|
609
|
+
kwargs["scale"] = 1 # larger quiver arrows
|
|
610
|
+
if "units" not in kwargs:
|
|
611
|
+
kwargs["units"] = "xy"
|
|
612
|
+
# for width, it will matter a lot what mode is used, width should be a dimensionless number
|
|
613
|
+
if "width" not in kwargs:
|
|
614
|
+
kwargs["width"] = width_scale
|
|
615
|
+
else:
|
|
616
|
+
kwargs["width"] *= width_scale # multiply
|
|
617
|
+
|
|
618
|
+
return kwargs
|
|
619
|
+
|
|
620
|
+
|
|
581
621
|
@_base_plot
|
|
582
622
|
def quiver(_, x, y, u, v, s=None, ax=None, **kwargs):
|
|
583
623
|
"""Create quiver plot from velocimetry results on new or existing axes.
|
|
584
624
|
|
|
625
|
+
Note that the `width` parameter is always in a unitless scale, with `1` providing a nice-looking default value
|
|
626
|
+
for all plot modes. The default value is usually very nice. If you do want to change:
|
|
627
|
+
|
|
628
|
+
The `scale` parameter defaults to 1, providing a nice looking arrow length. With a smaller (larger) value,
|
|
629
|
+
quivers will become longer (shorter).
|
|
630
|
+
|
|
631
|
+
The `width` parameter is in a unitless scale, with `1` providing a nice-looking default value.
|
|
632
|
+
|
|
585
633
|
Wraps :py:func:`matplotlib:matplotlib.pyplot.quiver`.
|
|
586
634
|
"""
|
|
587
635
|
if "color" in kwargs:
|
pyorc/cli/cli_elements.py
CHANGED
|
@@ -360,7 +360,7 @@ class AoiSelect(BaseSelect):
|
|
|
360
360
|
class GcpSelect(BaseSelect):
|
|
361
361
|
"""Selector tool to provide source GCP coordinates to pyOpenRiverCam."""
|
|
362
362
|
|
|
363
|
-
def __init__(self, img, dst, crs=None, lens_position=None, logger=logging):
|
|
363
|
+
def __init__(self, img, dst, crs=None, camera_matrix=None, dist_coeffs=None, lens_position=None, logger=logging):
|
|
364
364
|
"""Set up interactive GCP selection plot."""
|
|
365
365
|
super(GcpSelect, self).__init__(img, dst, crs=crs, logger=logger)
|
|
366
366
|
# make empty plot
|
|
@@ -389,6 +389,8 @@ class GcpSelect(BaseSelect):
|
|
|
389
389
|
self.ax_geo.legend()
|
|
390
390
|
self.ax.legend()
|
|
391
391
|
self.lens_position = lens_position
|
|
392
|
+
self.camera_matrix_input = camera_matrix
|
|
393
|
+
self.dist_coeffs_input = dist_coeffs
|
|
392
394
|
# add dst coords in the intended CRS
|
|
393
395
|
if crs is not None and use_cartopy:
|
|
394
396
|
self.dst_crs = helpers.xyz_transform(self.dst, 4326, crs)
|
|
@@ -407,7 +409,14 @@ class GcpSelect(BaseSelect):
|
|
|
407
409
|
self.title.set_text("Fitting pose and camera parameters...")
|
|
408
410
|
self.ax.figure.canvas.draw()
|
|
409
411
|
src_fit, dst_fit, camera_matrix, dist_coeffs, rvec, tvec, err = cli_utils.get_gcps_optimized_fit(
|
|
410
|
-
self.src,
|
|
412
|
+
self.src,
|
|
413
|
+
self.dst_crs,
|
|
414
|
+
self.height,
|
|
415
|
+
self.width,
|
|
416
|
+
c=2.0,
|
|
417
|
+
camera_matrix=self.camera_matrix_input,
|
|
418
|
+
dist_coeffs=self.dist_coeffs_input,
|
|
419
|
+
lens_position=self.lens_position,
|
|
411
420
|
)
|
|
412
421
|
self.p_fit.set_data(*list(zip(*src_fit)))
|
|
413
422
|
self.camera_matrix = camera_matrix
|
pyorc/cli/cli_utils.py
CHANGED
|
@@ -4,6 +4,7 @@ import hashlib
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
from typing import Optional
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import cv2
|
|
@@ -57,15 +58,39 @@ def get_corners_interactive(
|
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def get_gcps_interactive(
|
|
60
|
-
fn,
|
|
61
|
+
fn,
|
|
62
|
+
dst,
|
|
63
|
+
crs=None,
|
|
64
|
+
crs_gcps=None,
|
|
65
|
+
frame_sample=0,
|
|
66
|
+
focal_length=None,
|
|
67
|
+
k1=None,
|
|
68
|
+
k2=None,
|
|
69
|
+
lens_position=None,
|
|
70
|
+
rotation=None,
|
|
71
|
+
logger=logging,
|
|
61
72
|
):
|
|
62
73
|
"""Select GCP points in interactive display using first selected video frame."""
|
|
63
74
|
vid = Video(fn, start_frame=frame_sample, end_frame=frame_sample + 1, rotation=rotation)
|
|
64
75
|
# get first frame
|
|
65
76
|
frame = vid.get_frame(0, method="rgb")
|
|
77
|
+
# construct camera matrix and distortion coefficients
|
|
78
|
+
# parse items
|
|
79
|
+
camera_matrix, dist_coeffs = parse_lens_params(
|
|
80
|
+
height=frame.shape[0], width=frame.shape[1], focal_length=focal_length, k1=k1, k2=k2
|
|
81
|
+
)
|
|
82
|
+
|
|
66
83
|
if crs_gcps is not None:
|
|
67
84
|
dst = helpers.xyz_transform(dst, crs_from=crs_gcps, crs_to=4326)
|
|
68
|
-
selector = GcpSelect(
|
|
85
|
+
selector = GcpSelect(
|
|
86
|
+
frame,
|
|
87
|
+
dst,
|
|
88
|
+
crs=crs,
|
|
89
|
+
camera_matrix=camera_matrix,
|
|
90
|
+
dist_coeffs=dist_coeffs,
|
|
91
|
+
lens_position=lens_position,
|
|
92
|
+
logger=logger,
|
|
93
|
+
)
|
|
69
94
|
plt.show(block=True)
|
|
70
95
|
return selector.src, selector.camera_matrix, selector.dist_coeffs
|
|
71
96
|
|
|
@@ -90,21 +115,16 @@ def get_file_hash(fn):
|
|
|
90
115
|
return hash256
|
|
91
116
|
|
|
92
117
|
|
|
93
|
-
def get_gcps_optimized_fit(src, dst, height, width, c=2.0, lens_position=None):
|
|
118
|
+
def get_gcps_optimized_fit(src, dst, height, width, c=2.0, camera_matrix=None, dist_coeffs=None, lens_position=None):
|
|
94
119
|
"""Fit intrinsic and extrinsic parameters on provided set of src and dst points."""
|
|
95
120
|
# optimize cam matrix and dist coeffs with provided control points
|
|
96
121
|
if np.array(dst).shape == (4, 2):
|
|
97
122
|
_dst = np.c_[np.array(dst), np.zeros(4)]
|
|
98
123
|
else:
|
|
99
124
|
_dst = np.array(dst)
|
|
125
|
+
print(camera_matrix)
|
|
100
126
|
camera_matrix, dist_coeffs, err = cv.optimize_intrinsic(
|
|
101
|
-
src,
|
|
102
|
-
_dst,
|
|
103
|
-
height,
|
|
104
|
-
width,
|
|
105
|
-
c=c,
|
|
106
|
-
lens_position=lens_position,
|
|
107
|
-
# dist_coeffs=cv.DIST_COEFFS
|
|
127
|
+
src, _dst, height, width, c=c, lens_position=lens_position, camera_matrix=camera_matrix, dist_coeffs=dist_coeffs
|
|
108
128
|
)
|
|
109
129
|
# once optimized, solve the perspective, and estimate the GCP locations with the perspective rot/trans
|
|
110
130
|
coord_mean = np.array(_dst).mean(axis=0)
|
|
@@ -116,7 +136,12 @@ def get_gcps_optimized_fit(src, dst, height, width, c=2.0, lens_position=None):
|
|
|
116
136
|
src_est, jacobian = cv2.projectPoints(_dst, rvec, tvec, camera_matrix, np.array(dist_coeffs))
|
|
117
137
|
src_est = np.array([list(point[0]) for point in src_est])
|
|
118
138
|
dst_est = cv.unproject_points(_src, _dst[:, -1], rvec, tvec, camera_matrix, dist_coeffs)
|
|
139
|
+
# add mean coordinates to estimated locations
|
|
119
140
|
dst_est = np.array(dst_est)[:, 0 : len(coord_mean)] + coord_mean
|
|
141
|
+
# also reverse rvec and tvec to real-world (rw), add mean coordinate, and reverse again.
|
|
142
|
+
rvec_cam, tvec_cam = cv.pose_world_to_camera(rvec, tvec)
|
|
143
|
+
tvec_cam += coord_mean
|
|
144
|
+
rvec, tvec = cv.pose_world_to_camera(rvec_cam, tvec_cam)
|
|
120
145
|
return src_est, dst_est, camera_matrix, dist_coeffs, rvec, tvec, err
|
|
121
146
|
|
|
122
147
|
|
|
@@ -152,6 +177,29 @@ def parse_corners(ctx, param, value):
|
|
|
152
177
|
return [[int(x), int(y)] for x, y in corners]
|
|
153
178
|
|
|
154
179
|
|
|
180
|
+
def parse_lens_params(
|
|
181
|
+
height: int,
|
|
182
|
+
width: int,
|
|
183
|
+
focal_length: Optional[float] = None,
|
|
184
|
+
k1: Optional[float] = None,
|
|
185
|
+
k2: Optional[float] = None,
|
|
186
|
+
):
|
|
187
|
+
"""Parse lens parameters to camera matrix and distortion coefficients vector."""
|
|
188
|
+
if focal_length is not None:
|
|
189
|
+
camera_matrix = cv.get_cam_mtx(height, width, c=2.0, focal_length=focal_length)
|
|
190
|
+
else:
|
|
191
|
+
camera_matrix = None
|
|
192
|
+
if k1 is not None or k2 is not None:
|
|
193
|
+
dist_coeffs = cv.DIST_COEFFS.copy()
|
|
194
|
+
if k1 is not None:
|
|
195
|
+
dist_coeffs[0][0] = k1
|
|
196
|
+
if k2 is not None:
|
|
197
|
+
dist_coeffs[1][0] = k2
|
|
198
|
+
else:
|
|
199
|
+
dist_coeffs = None
|
|
200
|
+
return camera_matrix, dist_coeffs
|
|
201
|
+
|
|
202
|
+
|
|
155
203
|
def validate_file(ctx, param, value):
|
|
156
204
|
"""Validate existence of file."""
|
|
157
205
|
if value is not None:
|
|
@@ -283,6 +331,18 @@ def read_shape_as_gdf(fn=None, geojson=None, gdf=None):
|
|
|
283
331
|
else:
|
|
284
332
|
gdf = gpd.read_file(fn)
|
|
285
333
|
crs = gdf.crs if hasattr(gdf, "crs") else None
|
|
334
|
+
# also read raw json, and check if crs attribute exists
|
|
335
|
+
if isinstance(fn, str):
|
|
336
|
+
with open(fn, "r") as f:
|
|
337
|
+
raw_json = json.load(f)
|
|
338
|
+
else:
|
|
339
|
+
# apparently a file object was provided
|
|
340
|
+
fn.seek(0)
|
|
341
|
+
raw_json = json.load(fn)
|
|
342
|
+
if "crs" not in raw_json:
|
|
343
|
+
# override the crs
|
|
344
|
+
crs = None
|
|
345
|
+
gdf = gdf.set_crs(None, allow_override=True)
|
|
286
346
|
# check if all geometries are points
|
|
287
347
|
assert all([isinstance(geom, Point) for geom in gdf.geometry]), (
|
|
288
348
|
"shapefile may only contain geometries of type " '"Point"'
|
pyorc/cli/main.py
CHANGED
|
@@ -99,6 +99,17 @@ def cli(ctx, info, license):
|
|
|
99
99
|
help="Coordinate reference system in which destination GCP points (--dst) are measured",
|
|
100
100
|
)
|
|
101
101
|
@click.option("--resolution", type=float, help="Target resolution [m] for ortho-projection.")
|
|
102
|
+
@click.option("--focal_length", type=float, help="Focal length [pix] of lens.")
|
|
103
|
+
@click.option(
|
|
104
|
+
"--k1",
|
|
105
|
+
type=float,
|
|
106
|
+
help="First lens radial distortion coefficient k1 [-]. See also https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html",
|
|
107
|
+
)
|
|
108
|
+
@click.option(
|
|
109
|
+
"--k2",
|
|
110
|
+
type=float,
|
|
111
|
+
help="Second lens radial distortion coefficient k2 [-]. See also https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html",
|
|
112
|
+
)
|
|
102
113
|
@click.option(
|
|
103
114
|
"--window_size", type=int, help="Target window size [px] for interrogation window for Particle Image Velocimetry"
|
|
104
115
|
)
|
|
@@ -148,6 +159,9 @@ def camera_config(
|
|
|
148
159
|
z_0: Optional[float],
|
|
149
160
|
h_ref: Optional[float],
|
|
150
161
|
crs_gcps: Optional[Union[str, int]],
|
|
162
|
+
focal_length: Optional[float],
|
|
163
|
+
k1: Optional[float],
|
|
164
|
+
k2: Optional[float],
|
|
151
165
|
resolution: Optional[float],
|
|
152
166
|
window_size: Optional[int],
|
|
153
167
|
lens_position: Optional[List[float]],
|
|
@@ -207,6 +221,9 @@ def camera_config(
|
|
|
207
221
|
crs=crs,
|
|
208
222
|
crs_gcps=crs_gcps,
|
|
209
223
|
frame_sample=frame_sample,
|
|
224
|
+
focal_length=focal_length,
|
|
225
|
+
k1=k1,
|
|
226
|
+
k2=k2,
|
|
210
227
|
lens_position=lens_position,
|
|
211
228
|
rotation=rotation,
|
|
212
229
|
logger=logger,
|
|
@@ -295,9 +312,17 @@ def camera_config(
|
|
|
295
312
|
@click.option(
|
|
296
313
|
"--cross",
|
|
297
314
|
type=click.Path(exists=True, resolve_path=True, dir_okay=False, file_okay=True),
|
|
315
|
+
help="Cross section file (*.geojson). This will be used for discharge estimation if the `transect` "
|
|
316
|
+
" section is provided in your recipe.",
|
|
317
|
+
callback=cli_utils.parse_cross_section_gdf,
|
|
318
|
+
required=False,
|
|
319
|
+
)
|
|
320
|
+
@click.option(
|
|
321
|
+
"--cross_wl",
|
|
322
|
+
type=click.Path(exists=True, resolve_path=True, dir_okay=False, file_okay=True),
|
|
298
323
|
help="Cross section file (*.geojson). If you provide this, you may add water level retrieval settings to the"
|
|
299
|
-
" recipe section
|
|
300
|
-
" https://localdevices.github.io/pyorc/user-guide/
|
|
324
|
+
" recipe in the section `water_level`. For more information see"
|
|
325
|
+
" [PyORC docs](https://localdevices.github.io/pyorc/user-guide/cross_section/index.html)",
|
|
301
326
|
callback=cli_utils.parse_cross_section_gdf,
|
|
302
327
|
required=False,
|
|
303
328
|
)
|
|
@@ -316,7 +341,7 @@ def camera_config(
|
|
|
316
341
|
)
|
|
317
342
|
@verbose_opt
|
|
318
343
|
@click.pass_context
|
|
319
|
-
def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross, update, lowmem, verbose):
|
|
344
|
+
def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross, cross_wl, update, lowmem, verbose):
|
|
320
345
|
"""CLI subcommand for velocimetry."""
|
|
321
346
|
log_level = max(10, 20 - 10 * verbose)
|
|
322
347
|
logger = log.setuplog("velocimetry", os.path.abspath("pyorc.log"), append=False, log_level=log_level)
|
|
@@ -328,6 +353,7 @@ def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross
|
|
|
328
353
|
cameraconfig=cameraconfig,
|
|
329
354
|
h_a=h_a,
|
|
330
355
|
cross=cross,
|
|
356
|
+
cross_wl=cross_wl,
|
|
331
357
|
prefix=prefix,
|
|
332
358
|
output=output,
|
|
333
359
|
update=update,
|