Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 68032ad

Browse files
authoredOct 14, 2024··
Merge branch 'main' into vchang/update-packaging-scripts
2 parents 0e24ba1 + ff33107 commit 68032ad

17 files changed

+221
-25
lines changed
 

‎applications/endoscopy_tool_tracking/python/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ if(BUILD_TESTING)
6161
DEPENDS endoscopy_tool_tracking_python_test
6262
PASS_REGULAR_EXPRESSION "Valid video output!"
6363
)
64-
6564
endif()
6665

6766
# Install application and dependencies into the install/ directory for packaging

‎applications/multiai_ultrasound/cpp/CMakeLists.txt

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,6 +27,7 @@ target_link_libraries(multiai_ultrasound
2727
PRIVATE
2828
holoscan::core
2929
holoscan::ops::aja
30+
holoscan::ops::video_stream_recorder
3031
holoscan::ops::video_stream_replayer
3132
holoscan::ops::format_converter
3233
holoscan::ops::holoviz
@@ -57,10 +58,20 @@ add_dependencies(multiai_ultrasound mgpu_multiai_ultrasound_yaml)
5758

5859
# Add testing
5960
if(BUILD_TESTING)
60-
# Configure the yaml file to only play 10 frames
61+
set(RECORDING_DIR ${CMAKE_CURRENT_BINARY_DIR}/recording_output)
62+
set(SOURCE_VIDEO_BASENAME cpp_multiai_ultrasound_output)
63+
set(VALIDATION_FRAMES_DIR ${CMAKE_SOURCE_DIR}/applications/multiai_ultrasound/testing/)
64+
65+
file(MAKE_DIRECTORY ${RECORDING_DIR})
66+
67+
# Configure the yaml file for testing
6168
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/multiai_ultrasound.yaml" CONFIG_FILE)
62-
string(REGEX REPLACE "source:[^\n]*" "source: replayer" CONFIG_FILE ${CONFIG_FILE})
6369
string(REPLACE "count: 0" "count: 10" CONFIG_FILE ${CONFIG_FILE})
70+
string(REGEX REPLACE "source:[^\n]*" "source: replayer" CONFIG_FILE ${CONFIG_FILE})
71+
string(REPLACE "record_type: \"none\"" "record_type: \"visualizer\"" CONFIG_FILE ${CONFIG_FILE})
72+
string(REPLACE "directory: \"/tmp\"" "directory: \"${RECORDING_DIR}\"" CONFIG_FILE ${CONFIG_FILE})
73+
string(REPLACE "basename: \"tensor\"" "basename: \"${SOURCE_VIDEO_BASENAME}\"" CONFIG_FILE ${CONFIG_FILE})
74+
6475
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/multiai_ultrasound_testing.yaml" ${CONFIG_FILE})
6576

6677
# Add test
@@ -71,4 +82,20 @@ if(BUILD_TESTING)
7182
set_tests_properties(multiai_ultrasound_cpp_test PROPERTIES
7283
PASS_REGULAR_EXPRESSION "Reach end of file or playback count reaches to the limit. Stop ticking."
7384
FAIL_REGULAR_EXPRESSION "[^a-z]Error;ERROR;Failed")
85+
86+
# Add a test to check the validity of the frames
87+
add_test(NAME multiai_ultrasound_cpp_render_test
88+
COMMAND python3 ${CMAKE_SOURCE_DIR}/utilities/video_validation.py
89+
--source_video_dir ${RECORDING_DIR}
90+
--source_video_basename ${SOURCE_VIDEO_BASENAME}
91+
--output_dir ${RECORDING_DIR}
92+
--validation_frames_dir ${VALIDATION_FRAMES_DIR}
93+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
94+
)
95+
96+
set_tests_properties(multiai_ultrasound_cpp_render_test PROPERTIES
97+
DEPENDS multiai_ultrasound_cpp_test
98+
PASS_REGULAR_EXPRESSION "Valid video output!"
99+
)
100+
74101
endif()

‎applications/multiai_ultrasound/cpp/main.cpp

+61-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
#include <holoscan/operators/holoviz/holoviz.hpp>
2424
#include <holoscan/operators/inference/inference.hpp>
2525
#include <holoscan/operators/inference_processor/inference_processor.hpp>
26+
#include <holoscan/operators/video_stream_recorder/video_stream_recorder.hpp>
2627
#include <holoscan/operators/video_stream_replayer/video_stream_replayer.hpp>
28+
2729
#include <holoscan/version_config.hpp>
2830

2931
#include <visualizer_icardio.hpp>
@@ -37,12 +39,25 @@ class App : public holoscan::Application {
3739
if (source == "aja") { is_aja_source_ = true; }
3840
}
3941

42+
enum class Record { NONE, INPUT, VISUALIZER };
43+
44+
void set_record(const std::string& record) {
45+
if (record == "input") {
46+
record_type_ = Record::INPUT;
47+
} else if (record == "visualizer") {
48+
record_type_ = Record::VISUALIZER;
49+
}
50+
}
51+
4052
void set_datapath(const std::string& path) { datapath = path; }
4153

4254
void compose() override {
4355
using namespace holoscan;
4456

4557
std::shared_ptr<Operator> source;
58+
std::shared_ptr<Operator> recorder;
59+
std::shared_ptr<Operator> recorder_format_converter;
60+
4661
const std::shared_ptr<CudaStreamPool> cuda_stream_pool =
4762
make_resource<CudaStreamPool>("cuda_stream");
4863

@@ -143,10 +158,34 @@ class App : public holoscan::Application {
143158
Arg("cuda_stream_pool") = cuda_stream_pool);
144159

145160
auto holoviz = make_operator<ops::HolovizOp>(
146-
"holoviz", from_config("holoviz"), Arg("cuda_stream_pool") = cuda_stream_pool);
161+
"holoviz", from_config("holoviz"),
162+
Arg("enable_render_buffer_output") = (record_type_ == Record::VISUALIZER),
163+
Arg("allocator") =
164+
make_resource<BlockMemoryPool>("visualizer_allocator",
165+
(int32_t)nvidia::gxf::MemoryStorageType::kDevice,
166+
// max from VisualizerICardioOp::tensor_to_shape_
167+
320 * 320 * 4 * sizeof(uint8_t),
168+
1 * 8),
169+
Arg("cuda_stream_pool") = cuda_stream_pool);
147170

148-
// Flow definition
149171

172+
// Add recording operators
173+
if (record_type_ != Record::NONE) {
174+
if (((record_type_ == Record::INPUT) && is_aja_source_) ||
175+
(record_type_ == Record::VISUALIZER)) {
176+
recorder_format_converter = make_operator<ops::FormatConverterOp>(
177+
"recorder_format_converter",
178+
from_config("recorder_format_converter"),
179+
Arg("pool") =
180+
make_resource<BlockMemoryPool>("pool",
181+
(int32_t)nvidia::gxf::MemoryStorageType::kDevice,
182+
320 * 320 * 4 * sizeof(uint8_t),
183+
1 * 8));
184+
}
185+
recorder = make_operator<ops::VideoStreamRecorderOp>("recorder", from_config("recorder"));
186+
}
187+
188+
// Flow definition
150189
const std::string source_port_name = is_aja_source_ ? "video_buffer_output" : "";
151190
add_flow(source, plax_cham_pre, {{source_port_name, ""}});
152191
add_flow(source, aortic_ste_pre, {{source_port_name, ""}});
@@ -168,10 +207,26 @@ class App : public holoscan::Application {
168207
add_flow(visualizer_icardio, holoviz, {{"keyarea_5", "receivers"}});
169208
add_flow(visualizer_icardio, holoviz, {{"lines", "receivers"}});
170209
add_flow(visualizer_icardio, holoviz, {{"logo", "receivers"}});
210+
211+
212+
if (record_type_ == Record::INPUT) {
213+
if (is_aja_source_) {
214+
add_flow(source, recorder_format_converter, {{source_port_name, "source_video"}});
215+
add_flow(recorder_format_converter, recorder);
216+
} else {
217+
add_flow(source, recorder);
218+
}
219+
} else if (record_type_ == Record::VISUALIZER) {
220+
add_flow(holoviz,
221+
recorder_format_converter,
222+
{{"render_buffer_output", "source_video"}});
223+
add_flow(recorder_format_converter, recorder);
224+
}
171225
}
172226

173227
private:
174228
bool is_aja_source_ = false;
229+
Record record_type_ = Record::NONE;
175230
std::string datapath = "data/multiai_ultrasound";
176231
};
177232

@@ -214,6 +269,10 @@ int main(int argc, char** argv) {
214269

215270
auto source = app->from_config("source").as<std::string>();
216271
app->set_source(source);
272+
273+
auto record_type = app->from_config("record_type").as<std::string>();
274+
app->set_record(record_type);
275+
217276
if (data_path != "") app->set_datapath(data_path);
218277
app->run();
219278

‎applications/multiai_ultrasound/cpp/multiai_ultrasound.yaml

+11-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# limitations under the License.
1616
---
1717
source: "replayer" # or "aja"
18-
do_record: false # or 'true' if you want to record input video stream.
1918

2019
replayer:
2120
basename: "icardio_input1"
@@ -24,6 +23,9 @@ replayer:
2423
realtime: true # default: true
2524
count: 0 # default: 0 (no frame count restriction)
2625

26+
record_type: "none" # or "input" if you want to record input video stream, or "visualizer" if you want
27+
# to record the visualizer output.
28+
2729
aja: # AJASourceOp
2830
width: 1920
2931
height: 1080
@@ -140,3 +142,11 @@ holoviz:
140142
width: 320
141143
height: 320
142144
use_exclusive_display: false
145+
146+
recorder_format_converter:
147+
in_dtype: "rgba8888"
148+
out_dtype: "rgb888"
149+
150+
recorder:
151+
directory: "/tmp"
152+
basename: "tensor"
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,50 @@
1616
# Add testing
1717
if(BUILD_TESTING)
1818
# To get the environment path
19-
find_package(holoscan 0.6 REQUIRED CONFIG
20-
PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
19+
find_package(holoscan 1.0 REQUIRED CONFIG PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
2120

22-
# Configure the yaml file to only play 10 frames
21+
set(RECORDING_DIR ${CMAKE_CURRENT_BINARY_DIR}/recording_output)
22+
set(SOURCE_VIDEO_BASENAME python_multiai_ultrasound_output)
23+
set(VALIDATION_FRAMES_DIR ${CMAKE_SOURCE_DIR}/applications/multiai_ultrasound/testing/)
24+
25+
file(MAKE_DIRECTORY ${RECORDING_DIR})
26+
27+
# Configure the yaml file for testing
2328
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/multiai_ultrasound.yaml" CONFIG_FILE)
24-
string(REGEX REPLACE "source:[^\n]*" "source: replayer" CONFIG_FILE ${CONFIG_FILE})
2529
string(REPLACE "count: 0" "count: 10" CONFIG_FILE ${CONFIG_FILE})
26-
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/multiai_ultrasound_testing.yaml ${CONFIG_FILE})
30+
string(REGEX REPLACE "source:[^\n]*" "source: replayer" CONFIG_FILE ${CONFIG_FILE})
31+
string(REPLACE "directory: \"/tmp\"" "directory: \"${RECORDING_DIR}\"" CONFIG_FILE ${CONFIG_FILE})
32+
string(REPLACE "basename: \"tensor\"" "basename: \"${SOURCE_VIDEO_BASENAME}\"" CONFIG_FILE ${CONFIG_FILE})
33+
34+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/multiai_ultrasound_testing.yaml" ${CONFIG_FILE})
2735

2836
# Add test
2937
add_test(NAME multiai_ultrasound_python_test
30-
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/multiai_ultrasound.py
31-
--config ${CMAKE_CURRENT_BINARY_DIR}/multiai_ultrasound_testing.yaml
32-
--data "${HOLOHUB_DATA_DIR}/multiai_ultrasound"
33-
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
38+
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/multiai_ultrasound.py
39+
--config ${CMAKE_CURRENT_BINARY_DIR}/multiai_ultrasound_testing.yaml
40+
--data "${HOLOHUB_DATA_DIR}/multiai_ultrasound"
41+
--record_type visualizer
42+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
3443

3544
set_property(TEST multiai_ultrasound_python_test PROPERTY ENVIRONMENT
36-
"PYTHONPATH=${GXF_LIB_DIR}/../python/lib:${CMAKE_BINARY_DIR}/python/lib")
45+
"PYTHONPATH=${GXF_LIB_DIR}/../python/lib:${CMAKE_BINARY_DIR}/python/lib")
46+
47+
set_tests_properties(multiai_ultrasound_python_test PROPERTIES
48+
PASS_REGULAR_EXPRESSION "Reach end of file or playback count reaches to the limit. Stop ticking.;"
49+
FAIL_REGULAR_EXPRESSION "[^a-z]Error;ERROR;Failed")
50+
51+
# Add a test to check the validity of the frames
52+
add_test(NAME multiai_ultrasound_python_render_test
53+
COMMAND python3 ${CMAKE_SOURCE_DIR}/utilities/video_validation.py
54+
--source_video_dir ${RECORDING_DIR}
55+
--source_video_basename ${SOURCE_VIDEO_BASENAME}
56+
--output_dir ${RECORDING_DIR}
57+
--validation_frames_dir ${VALIDATION_FRAMES_DIR}
58+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
59+
)
3760

38-
set_tests_properties(multiai_ultrasound_python_test
39-
PROPERTIES PASS_REGULAR_EXPRESSION "Reach end of file or playback count reaches to the limit. Stop ticking.;"
40-
FAIL_REGULAR_EXPRESSION "[^a-z]Error;ERROR;Failed")
61+
set_tests_properties(multiai_ultrasound_python_render_test PROPERTIES
62+
DEPENDS multiai_ultrasound_python_test
63+
PASS_REGULAR_EXPRESSION "Valid video output!"
64+
)
4165
endif()

‎applications/multiai_ultrasound/python/multiai_ultrasound.py

+73-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,7 @@
2424
HolovizOp,
2525
InferenceOp,
2626
InferenceProcessorOp,
27+
VideoStreamRecorderOp,
2728
VideoStreamReplayerOp,
2829
)
2930
from holoscan.resources import BlockMemoryPool, CudaStreamPool, MemoryStorageType
@@ -33,7 +34,7 @@
3334

3435

3536
class MultiAIICardio(Application):
36-
def __init__(self, data, source="replayer"):
37+
def __init__(self, data, source="replayer", record_type=None):
3738
super().__init__()
3839

3940
if data == "none":
@@ -48,12 +49,19 @@ def __init__(self, data, source="replayer"):
4849
raise ValueError(f"unsupported source: {source}. Please use 'replayer' or 'aja'.")
4950
self.source = source
5051

52+
self.record_type = record_type
53+
if record_type is not None:
54+
if record_type not in ("input", "visualizer"):
55+
raise ValueError("record_type must be either ('input' or 'visualizer')")
56+
5157
self.sample_data_path = data
5258

5359
def compose(self):
5460
cuda_stream_pool = CudaStreamPool(self, name="cuda_stream")
5561

62+
record_type = self.record_type
5663
is_aja = self.source.lower() == "aja"
64+
5765
SourceClass = AJASourceOp if is_aja else VideoStreamReplayerOp
5866
source_kwargs = self.kwargs(self.source)
5967
if self.source == "replayer":
@@ -183,8 +191,39 @@ def compose(self):
183191
cuda_stream_pool=cuda_stream_pool,
184192
**visualizer_kwargs,
185193
)
194+
195+
source_pool_kwargs = dict(
196+
storage_type=MemoryStorageType.DEVICE,
197+
block_size=block_size,
198+
num_blocks=1,
199+
)
200+
201+
if record_type is not None:
202+
if ((record_type == "input") and (self.source != "replayer")) or (
203+
record_type == "visualizer"
204+
):
205+
recorder_format_converter = FormatConverterOp(
206+
self,
207+
name="recorder_format_converter",
208+
pool=BlockMemoryPool(self, name="pool", **source_pool_kwargs),
209+
**self.kwargs("recorder_format_converter"),
210+
)
211+
recorder = VideoStreamRecorderOp(
212+
name="recorder", fragment=self, **self.kwargs("recorder")
213+
)
214+
215+
if (record_type == "visualizer") and (self.source == "replayer"):
216+
visualizer_allocator = BlockMemoryPool(self, name="allocator", **source_pool_kwargs)
217+
else:
218+
visualizer_allocator = None
219+
186220
holoviz = HolovizOp(
187-
self, name="holoviz", cuda_stream_pool=cuda_stream_pool, **self.kwargs("holoviz")
221+
self,
222+
name="holoviz",
223+
cuda_stream_pool=cuda_stream_pool,
224+
enable_render_buffer_output=record_type == "visualizer",
225+
allocator=visualizer_allocator,
226+
**self.kwargs("holoviz"),
188227
)
189228

190229
# connect the input to the resizer and each pre-processor
@@ -219,6 +258,25 @@ def compose(self):
219258
for src in visualizer_inputs:
220259
self.add_flow(visualizer_icardio, holoviz, {(src, "receivers")})
221260

261+
# Flow for the recorder
262+
if record_type == "input":
263+
if self.source != "replayer":
264+
self.add_flow(
265+
source,
266+
recorder_format_converter,
267+
{("video_buffer_output", "source_video")},
268+
)
269+
self.add_flow(recorder_format_converter, recorder)
270+
else:
271+
self.add_flow(source, recorder)
272+
elif record_type == "visualizer":
273+
self.add_flow(
274+
holoviz,
275+
recorder_format_converter,
276+
{("render_buffer_output", "source_video")},
277+
)
278+
self.add_flow(recorder_format_converter, recorder)
279+
222280

223281
if __name__ == "__main__":
224282
parser = ArgumentParser(description="Multi-AI demo application.")
@@ -232,6 +290,13 @@ def compose(self):
232290
"capture card as the source (default: %(default)s)."
233291
),
234292
)
293+
parser.add_argument(
294+
"-r",
295+
"--record_type",
296+
choices=["none", "input", "visualizer"],
297+
default="none",
298+
help="The video stream to record (default: %(default)s).",
299+
)
235300
parser.add_argument(
236301
"-c",
237302
"--config",
@@ -246,11 +311,15 @@ def compose(self):
246311
)
247312
args = parser.parse_args()
248313

314+
record_type = args.record_type
315+
if record_type == "none":
316+
record_type = None
317+
249318
if args.config == "none":
250319
config_file = os.path.join(os.path.dirname(__file__), "multiai_ultrasound.yaml")
251320
else:
252321
config_file = args.config
253322

254-
app = MultiAIICardio(source=args.source, data=args.data)
323+
app = MultiAIICardio(record_type=record_type, source=args.source, data=args.data)
255324
app.config(config_file)
256325
app.run()

‎applications/multiai_ultrasound/python/multiai_ultrasound.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,11 @@ holoviz:
140140
width: 320
141141
height: 320
142142
use_exclusive_display: false
143+
144+
recorder_format_converter:
145+
in_dtype: "rgba8888"
146+
out_dtype: "rgb888"
147+
148+
recorder:
149+
directory: "/tmp"
150+
basename: "tensor"
63.4 KB
Loading
63.5 KB
Loading
63.3 KB
Loading
63.3 KB
Loading
63.3 KB
Loading
63.7 KB
Loading
63.6 KB
Loading
62.6 KB
Loading
Loading
62.4 KB
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.