From 0b0e9aeeffcc851e11e62c51b941973fd423cfe5 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Thu, 18 Jul 2024 18:43:39 +0200 Subject: [PATCH] Fix typos, CI and README --- examples/python-dataflow/dataflow.yml | 6 +- examples/python-dataflow/dataflow_dynamic.yml | 6 +- examples/python-dataflow/dataflow_yolo.yml | 10 ++-- node-hub/opencv-plot/README.md | 48 +++++++-------- node-hub/opencv-plot/main.py | 60 +++++++++---------- node-hub/opencv-video-capture/README.md | 4 +- node-hub/opencv-video-capture/main.py | 6 +- node-hub/ultralytics-yolo/README.md | 6 +- node-hub/ultralytics-yolo/main.py | 2 +- 9 files changed, 73 insertions(+), 75 deletions(-) diff --git a/examples/python-dataflow/dataflow.yml b/examples/python-dataflow/dataflow.yml index fe0e6733..e6a743ab 100644 --- a/examples/python-dataflow/dataflow.yml +++ b/examples/python-dataflow/dataflow.yml @@ -3,7 +3,8 @@ nodes: build: pip install ../../node-hub/opencv-video-capture path: opencv-video-capture inputs: - tick: plot/tick + tick: dora/timer/millis/16 + stop: plot/end outputs: - image - text @@ -17,7 +18,6 @@ nodes: path: opencv-plot inputs: image: camera/image - tick: dora/timer/millis/16 # this node display a window, so it's better to deflect the timer, so when the window is closed, the ticks are not sent anymore in the graph text: camera/text outputs: - - tick \ No newline at end of file + - end \ No newline at end of file diff --git a/examples/python-dataflow/dataflow_dynamic.yml b/examples/python-dataflow/dataflow_dynamic.yml index 7b948d5f..4253e2d8 100644 --- a/examples/python-dataflow/dataflow_dynamic.yml +++ b/examples/python-dataflow/dataflow_dynamic.yml @@ -3,7 +3,8 @@ nodes: build: pip install ../../node-hub/opencv-video-capture path: opencv-video-capture inputs: - tick: plot/tick + tick: dora/timer/millis/16 + stop: plot/end outputs: - image env: @@ -16,6 +17,5 @@ nodes: path: dynamic inputs: image: camera/image - tick: dora/timer/millis/16 # this node display a window, so it's better to deflect the timer, so when the window is closed, the ticks are not sent anymore in the graph outputs: - - tick \ No newline at end of file + - end \ No newline at end of file diff --git a/examples/python-dataflow/dataflow_yolo.yml b/examples/python-dataflow/dataflow_yolo.yml index 1b4f75d3..e5f8ca48 100644 --- a/examples/python-dataflow/dataflow_yolo.yml +++ b/examples/python-dataflow/dataflow_yolo.yml @@ -3,7 +3,8 @@ nodes: build: pip install ../../node-hub/opencv-video-capture path: opencv-video-capture inputs: - tick: plot/tick + tick: dora/timer/millis/16 + stop: plot/end outputs: - image env: @@ -21,7 +22,7 @@ nodes: outputs: - bbox env: - MODEL: yolov5n.pt + MODEL: yolov8n.pt - id: plot build: pip install ../../node-hub/opencv-plot @@ -31,8 +32,5 @@ nodes: source: camera/image queue_size: 1 bbox: object-detection/bbox - tick: - source: dora/timer/millis/16 # this node display a window, so it's better to deflect the timer, so when the window is closed, the ticks are not sent anymore in the graph - queue_size: 1 outputs: - - tick \ No newline at end of file + - end \ No newline at end of file diff --git a/node-hub/opencv-plot/README.md b/node-hub/opencv-plot/README.md index 95017cbb..8f6cdef1 100644 --- a/node-hub/opencv-plot/README.md +++ b/node-hub/opencv-plot/README.md @@ -13,12 +13,8 @@ This node is used to plot a text and a list of bbox on a base image (ideal for o # bbox: Arrow array of bbox # text: Arrow array of size 1 containing the text to be plotted - tick: - source: dora/timer/millis/16 # this node display a window, so it's better to deflect the timer, so when the window is closed, the ticks are not sent anymore in the graph - queue_size: 1 - outputs: - - tick + - end env: PLOT_WIDTH: 640 # optional, default is image input width @@ -26,13 +22,11 @@ This node is used to plot a text and a list of bbox on a base image (ideal for o ``` # Inputs -- -- `tick`: empty Arrow array to trigger the capture - `image`: Arrow array containing the base image ```python -image = { +image: { "width": np.uint32, "height": np.uint32, "channels": np.uint8, @@ -50,29 +44,33 @@ decoded_image = { ``` +- `bbox`: an arrow array containing the bounding boxes, confidence scores, and class names of the detected objects + +```Python + +bbox: { + "bbox": np.array, # flattened array of bounding boxes + "conf": np.array, # flat array of confidence scores + "names": np.array, # flat array of class names +} + +encoded_bbox = pa.array([bbox]) + +decoded_bbox = { + "bbox": encoded_bbox[0]["bbox"].values.to_numpy().reshape(-1, 3), + "conf": encoded_bbox[0]["conf"].values.to_numpy(), + "names": encoded_bbox[0]["names"].values.to_numpy(zero_copy_only=False), +} +``` + - `text`: Arrow array containing the text to be plotted ```python -text = { - "text": str, - "font_scale": np.float32, - "color": (np.uint8, np.uint8, np.uint8), - "thickness": np.uint32, - "position": (np.uint32, np.uint32) -} +text: str encoded_text = pa.array([text]) -decoded_text = { - "text": encoded_text[0]["text"].as_py(), - "font_scale": np.float32(encoded_text[0]["font_scale"].as_py()), - "color": (np.uint8(encoded_text[0]["color"].as_py()[0]), - np.uint8(encoded_text[0]["color"].as_py()[1]), - np.uint8(encoded_text[0]["color"].as_py()[2])), - "thickness": np.uint32(encoded_text[0]["thickness"].as_py()), - "position": (np.uint32(encoded_text[0]["position"].as_py()[0]), - np.uint32(encoded_text[0]["position"].as_py()[1])) -} +decoded_text = encoded_text[0].as_py() ``` ## License diff --git a/node-hub/opencv-plot/main.py b/node-hub/opencv-plot/main.py index 1a65cd5d..0648c3fd 100644 --- a/node-hub/opencv-plot/main.py +++ b/node-hub/opencv-plot/main.py @@ -7,6 +7,8 @@ import pyarrow as pa from dora import Node +RUNNER_CI = True if os.getenv("CI", False) == "true" else False + class Plot: frame: np.array = np.array([]) @@ -23,7 +25,7 @@ class Plot: height: np.uint32 = None -def plot_frame(plot, ci_enabled): +def plot_frame(plot): for bbox in zip(plot.bboxes["bbox"], plot.bboxes["conf"], plot.bboxes["names"]): [ [min_x, min_y, max_x, max_y], @@ -63,15 +65,10 @@ def plot_frame(plot, ci_enabled): if plot.width is not None and plot.height is not None: plot.frame = cv2.resize(plot.frame, (plot.width, plot.height)) - if not ci_enabled: + if not RUNNER_CI: if len(plot.frame.shape) >= 3: cv2.imshow("Dora Node: opencv-plot", plot.frame) - if cv2.waitKey(1) & 0xFF == ord('q'): - return True - - return False - def main(): # Handle dynamic nodes, ask for the name of the node in the dataflow, and the same values as the ENV variables. @@ -96,11 +93,6 @@ def main(): if isinstance(plot_height, str) and plot_height.isnumeric(): plot_height = int(plot_height) - # check if the code is running in a CI environment (e.g. GitHub Actions) (parse to bool) - ci_enabled = os.getenv("CI", False) - if ci_enabled == "true": - ci_enabled = True - node = Node(args.name) # provide the name to connect to the dataflow if dynamic node plot = Plot() @@ -115,17 +107,7 @@ def main(): if event_type == "INPUT": event_id = event["id"] - if event_id == "tick": - if ci_enabled: - break - - node.send_output( - "tick", - pa.array([]), - event["metadata"] - ) - - elif event_id == "image": + if event_id == "image": arrow_image = event["value"][0] image = { "width": np.uint32(arrow_image["width"].as_py()), @@ -136,29 +118,45 @@ def main(): plot.frame = np.reshape(image["data"], (image["height"], image["width"], image["channels"])) - if plot_frame(plot, ci_enabled): - break + plot_frame(plot) + if not RUNNER_CI: + if cv2.waitKey(1) & 0xFF == ord("q"): + break + else: + break # break the loop for CI elif event_id == "bbox": arrow_bbox = event["value"][0] plot.bboxes = { "bbox": arrow_bbox["bbox"].values.to_numpy().reshape(-1, 4), "conf": arrow_bbox["conf"].values.to_numpy(), - "names": arrow_bbox["names"].values.to_pylist(), + "names": arrow_bbox["names"].values.to_numpy(zero_copy_only=False), } - if plot_frame(plot, ci_enabled): - break - + plot_frame(plot) + if not RUNNER_CI: + if cv2.waitKey(1) & 0xFF == ord("q"): + break + else: + break # break the loop for CI elif event_id == "text": plot.text = event["value"][0].as_py() - if plot_frame(plot, ci_enabled): - break + plot_frame(plot) + if not RUNNER_CI: + if cv2.waitKey(1) & 0xFF == ord("q"): + break + else: + break # break the loop for CI elif event_type == "ERROR": raise Exception(event["error"]) + node.send_output( + "end", + pa.array([0], type=pa.uint8()) + ) + if __name__ == "__main__": main() diff --git a/node-hub/opencv-video-capture/README.md b/node-hub/opencv-video-capture/README.md index f4f10cdd..9aa2dbb8 100644 --- a/node-hub/opencv-video-capture/README.md +++ b/node-hub/opencv-video-capture/README.md @@ -10,7 +10,7 @@ This node is used to capture video from a camera using OpenCV. path: opencv-video-capture inputs: tick: dora/timer/millis/16 # try to capture at 60fps - + # stop: some stop signal from another node outputs: - image: # the captured image @@ -31,7 +31,7 @@ This node is used to capture video from a camera using OpenCV. ```Python -image = { +image: { "width": np.uint32, "height": np.uint32, "channels": np.uint8, diff --git a/node-hub/opencv-video-capture/main.py b/node-hub/opencv-video-capture/main.py index 0b2d22f5..85a8e2c8 100644 --- a/node-hub/opencv-video-capture/main.py +++ b/node-hub/opencv-video-capture/main.py @@ -58,7 +58,7 @@ def main(): frame = np.zeros((480, 640, 3), dtype=np.uint8) cv2.putText( frame, - f'Error: Could not read frame from camera at path {video_capture_path}.', + f'Error: no frame for camera at path {video_capture_path}.', (int(30), int(30)), cv2.FONT_HERSHEY_SIMPLEX, 0.50, @@ -84,6 +84,10 @@ def main(): event["metadata"] ) + if event_id == "stop": + video_capture.release() + break + elif event_type == "ERROR": raise Exception(event["error"]) diff --git a/node-hub/ultralytics-yolo/README.md b/node-hub/ultralytics-yolo/README.md index 5a66c832..8a7bc8f2 100644 --- a/node-hub/ultralytics-yolo/README.md +++ b/node-hub/ultralytics-yolo/README.md @@ -22,7 +22,7 @@ This node is used to detect objects in images using YOLOv8. - `image`: Arrow array containing the base image ```python -image = { +image: { "width": np.uint32, "height": np.uint32, "channels": np.uint8, @@ -46,7 +46,7 @@ decoded_image = { ```Python -bbox = { +bbox: { "bbox": np.array, # flattened array of bounding boxes "conf": np.array, # flat array of confidence scores "names": np.array, # flat array of class names @@ -57,7 +57,7 @@ encoded_bbox = pa.array([bbox]) decoded_bbox = { "bbox": encoded_bbox[0]["bbox"].values.to_numpy().reshape(-1, 3), "conf": encoded_bbox[0]["conf"].values.to_numpy(), - "names": encoded_bbox[0]["names"].values.to_pylist(), + "names": encoded_bbox[0]["names"].values.to_numpy(zero_copy_only=False), } ``` diff --git a/node-hub/ultralytics-yolo/main.py b/node-hub/ultralytics-yolo/main.py index 2bd3ce00..fa3e48ba 100644 --- a/node-hub/ultralytics-yolo/main.py +++ b/node-hub/ultralytics-yolo/main.py @@ -41,7 +41,7 @@ def main(): "data": arrow_image["data"].values.to_numpy().astype(np.uint8) } - frame = image["data"].reshape((image["height"], image["width"], 3)) + frame = image["data"].reshape((image["height"], image["width"], image["channels"])) frame = frame[:, :, ::-1] # OpenCV image (BGR to RGB) results = model(frame, verbose=False) # includes NMS