Skip to content

Commit 313da51

Browse files
Support Clara v3.0 Inference API for py/cpp client (#65)
* Support Clara v3.0 Inference API for py/cpp client * add missing aiaa-inference.cpp * fixed comments Co-authored-by: Sachidanand Alle <salle@nvidia.com>
1 parent 6bddd5a commit 313da51

12 files changed

Lines changed: 271 additions & 24 deletions

File tree

cpp-client/include/nvidia/aiaa/client.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ class AIAA_CLIENT_API Client {
173173
int deepgrow(const Model &model, const PointSet &foregroundPointSet, const PointSet &backgroundPointSet, const std::string &inputImageFile,
174174
const std::string &outputImageFile, const std::string &sessionId = "") const;
175175

176+
/*!
177+
@brief This API is used to run generic inference on input image
178+
@param[in] model Model to be used
179+
@param[in] params Json String which will be an input for AIAA to run the model inference
180+
@param[in] inputImageFile Input image filename which will be sent to AIAA for inference action
181+
@param[in] outputImageFile Output image file where Result mask is stored
182+
@param[in] sessionId If *session_id* is not empty then *inputImageFile* will be ignored
183+
184+
@retval JSON string representing the output response from AIAA
185+
186+
@throw nvidia.aiaa.error.101 in case of connect error
187+
@throw nvidia.aiaa.error.103 if case of ITK error related to image processing
188+
*/
189+
std::string inference(const Model &model, const std::string &params, const std::string &inputImageFile, const std::string &outputImageFile,
190+
const std::string &sessionId = "") const;
176191
/*!
177192
@brief 3D binary mask to polygon representation conversion
178193
@param[in] pointRatio Point Ratio

cpp-client/include/nvidia/aiaa/exception.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ namespace aiaa {
5151
nvidia.aiaa.error.103 | Failed to process ITK Operations.
5252
nvidia.aiaa.error.104 | Invalid Arguments.
5353
nvidia.aiaa.error.105 | AIAA Session Timeout.
54-
nvidia.aiaa.error.106 | System/Unknown Error.
54+
nvidia.aiaa.error.106 | AIAA Response Error.
55+
nvidia.aiaa.error.107 | System/Unknown Error.
5556
*/
5657

5758
class exception : public std::exception {
@@ -62,13 +63,14 @@ class exception : public std::exception {
6263
RESPONSE_PARSE_ERROR = 102, /// Failed to parse AIAA Server Response
6364
ITK_PROCESS_ERROR = 103, /// Failed to process ITK Operations
6465
INVALID_ARGS_ERROR = 104, /// Invalid Arguments
65-
AIAA_SESSION_TIMEOUT = 105, /// AIAA Session Timeout
66-
SYSTEM_ERROR = 106, /// System/Unknown Error
66+
AIAA_SESSION_TIMEOUT = 105, /// AIAA Session Timeout,
67+
AIAA_RESPONSE_ERROR = 106, /// AIAA Response Error
68+
SYSTEM_ERROR = 107, /// System/Unknown Error
6769
};
6870

6971
/// Message String for each enum type
70-
const std::string messages[6] = { "Failed to communicate to AIAA Server", "Failed to parse AIAA Server Response",
71-
"Failed to process ITK Operations", "Invalid Arguments", "AIAA Session Timeout", "System/Unknown Error" };
72+
const std::string messages[7] = { "Failed to communicate to AIAA Server", "Failed to parse AIAA Server Response",
73+
"Failed to process ITK Operations", "Invalid Arguments", "AIAA Session Timeout", "AIAA Response Error", "System/Unknown Error" };
7274

7375
/// returns the explanatory string
7476
const char* what() const noexcept override {

cpp-client/src/client.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const std::string EP_MODELS = "/v1/models";
4343
const std::string EP_DEXTRA_3D = "/v1/dextr3d";
4444
const std::string EP_DEEPGROW = "/v1/deepgrow";
4545
const std::string EP_SEGMENTATION = "/v1/segmentation";
46+
const std::string EP_INFERENCE = "/v1/inference";
4647
const std::string EP_MASK_TO_POLYGON = "/v1/mask2polygon";
4748
const std::string EP_FIX_POLYGON = "/v1/fixpolygon";
4849
const std::string EP_SESSION = "/session/";
@@ -223,6 +224,32 @@ int Client::deepgrow(const Model &model, const PointSet &foregroundPointSet, con
223224
return 0;
224225
}
225226

227+
std::string Client::inference(const Model &model, const std::string &params, const std::string &inputImageFile, const std::string &outputImageFile,
228+
const std::string &sessionId) const {
229+
if (model.name.empty()) {
230+
AIAA_LOG_WARN("Selected model is EMPTY");
231+
throw exception(exception::INVALID_ARGS_ERROR, "Model is EMPTY");
232+
}
233+
234+
AIAA_LOG_DEBUG("Model: " << model.toJson());
235+
AIAA_LOG_DEBUG("Params: " << params);
236+
AIAA_LOG_DEBUG("InputImageFile: " << inputImageFile);
237+
AIAA_LOG_DEBUG("OutputImageFile: " << outputImageFile);
238+
AIAA_LOG_DEBUG("SessionId: " << sessionId);
239+
240+
std::string m = CurlUtils::encode(model.name);
241+
std::string uri = serverUri + EP_INFERENCE + "?model=" + m;
242+
243+
std::string inputImage = inputImageFile;
244+
if (!sessionId.empty()) {
245+
uri += "&session_id=" + CurlUtils::encode(sessionId);
246+
inputImage = "";
247+
}
248+
249+
std::string paramsStr = params.empty() ? "{}" : params;
250+
return CurlUtils::doMethod("POST", uri, paramsStr, inputImage, outputImageFile, timeoutInSec);
251+
}
252+
226253
PolygonsList Client::maskToPolygon(int pointRatio, const std::string &inputImageFile) const {
227254
std::string uri = serverUri + EP_MASK_TO_POLYGON;
228255
std::string paramStr = "{\"more_points\":" + Utils::lexical_cast<std::string>(pointRatio) + "}";

cpp-client/src/curlutils.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,21 @@ std::string CurlUtils::doMethod(const std::string &method, const std::string &ur
187187
std::istream &is = session.receiveResponse(res);
188188
AIAA_LOG_DEBUG("Status: " << res.getStatus() << "; Reason: " << res.getReason() << "; Content-type: " << res.getContentType());
189189

190+
std::stringstream response;
191+
Poco::StreamCopier::copyStream(is, response);
192+
190193
if (res.getStatus() == 440) {
191-
throw exception(exception::AIAA_SESSION_TIMEOUT, res.getReason().c_str());
194+
throw exception(exception::AIAA_SESSION_TIMEOUT, response.str().c_str());
192195
}
193196
if (res.getStatus() != 200) {
194-
throw exception(exception::AIAA_SERVER_ERROR, res.getReason().c_str());
197+
AIAA_LOG_INFO("Response: " << response.str());
198+
throw exception(exception::AIAA_RESPONSE_ERROR, (res.getReason() + " => " + response.str()).c_str());
195199
}
196200

197-
std::stringstream response;
198-
Poco::StreamCopier::copyStream(is, response);
199-
200-
if (res.getContentType().find("multipart/form-data") == std::string::npos) {
201-
AIAA_LOG_INFO("Expected Multipart Response but received: " << res.getContentType());
201+
if (res.getContentType().find("multipart") == std::string::npos) {
202+
if (!resultFileName.empty()) {
203+
AIAA_LOG_INFO("Expected Multipart Response but received: " << res.getContentType());
204+
}
202205
AIAA_LOG_DEBUG("Received response from server: \n" << response.str());
203206

204207
textReponse = response.str();

cpp-client/tools/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ target_link_libraries(nvidiaAIAASession NvidiaAIAAClient ${CMAKE_DL_LIBS})
7070
add_executable(nvidiaAIAADeepgrow aiaa/aiaa-deepgrow.cpp)
7171
target_link_libraries(nvidiaAIAADeepgrow NvidiaAIAAClient ${CMAKE_DL_LIBS})
7272

73+
add_executable(nvidiaAIAAInference aiaa/aiaa-inference.cpp)
74+
target_link_libraries(nvidiaAIAAInference NvidiaAIAAClient ${CMAKE_DL_LIBS})
75+
7376
# Install Targets
7477
install(TARGETS DicomToNifti DESTINATION bin)
7578
install(TARGETS NiftiToDicom DESTINATION bin)
@@ -82,3 +85,4 @@ install(TARGETS nvidiaAIAAMaskPolygon DESTINATION bin)
8285
install(TARGETS nvidiaAIAASampling3D DESTINATION bin)
8386
install(TARGETS nvidiaAIAASession DESTINATION bin)
8487
install(TARGETS nvidiaAIAADeepgrow DESTINATION bin)
88+
install(TARGETS nvidiaAIAAInference DESTINATION bin)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* * Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of NVIDIA CORPORATION nor the names of its
13+
* contributors may be used to endorse or promote products derived
14+
* from this software without specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
17+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24+
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
29+
#include <nvidia/aiaa/client.h>
30+
#include <nvidia/aiaa/utils.h>
31+
32+
#include "../commonutils.h"
33+
#include <chrono>
34+
35+
int main(int argc, char **argv) {
36+
if (argc < 2 || cmdOptionExists(argv, argv + argc, "-h")) {
37+
std::cout << "Usage:: <COMMAND> <OPTIONS>\n"
38+
" |-h (Help) Print this information |\n"
39+
" |-server Server URI {default: http://0.0.0.0:5000} |\n"
40+
" *|-model Model Name [either -label or -model is required] |\n"
41+
" |-params Input Params (JSON) |\n"
42+
" *|-image Input Image File |\n"
43+
" *|-session Session ID |\n"
44+
" |-output Output Image File |\n"
45+
" |-timeout Timeout In Seconds {default: 60} |\n"
46+
" |-ts Print API Latency |\n";
47+
return 0;
48+
}
49+
50+
std::string serverUri = getCmdOption(argv, argv + argc, "-server", "http://0.0.0.0:5000");
51+
std::string model = getCmdOption(argv, argv + argc, "-model");
52+
53+
std::string params = getCmdOption(argv, argv + argc, "-params");
54+
std::string inputImageFile = getCmdOption(argv, argv + argc, "-image");
55+
std::string sessionId = getCmdOption(argv, argv + argc, "-session");
56+
std::string outputImageFile = getCmdOption(argv, argv + argc, "-output");
57+
58+
int timeout = nvidia::aiaa::Utils::lexical_cast<int>(getCmdOption(argv, argv + argc, "-timeout", "60"));
59+
bool printTs = cmdOptionExists(argv, argv + argc, "-ts") ? true : false;
60+
61+
if (model.empty()) {
62+
std::cerr << "Model is required\n";
63+
return -1;
64+
}
65+
if (inputImageFile.empty() && sessionId.empty()) {
66+
std::cerr << "Input Image file is missing (Either session-id or input image should be provided)\n";
67+
return -1;
68+
}
69+
70+
try {
71+
nvidia::aiaa::Client client(serverUri, timeout);
72+
73+
nvidia::aiaa::Model m;
74+
m = client.model(model);
75+
76+
if (m.name.empty()) {
77+
std::cerr << "Couldn't find a model for name: " << model << "\n";
78+
return -1;
79+
}
80+
81+
auto begin = std::chrono::high_resolution_clock::now();
82+
std::string resultJson = client.inference(m, params, inputImageFile, outputImageFile, sessionId);
83+
std::cout << "Result (JSON): " << resultJson << std::endl;
84+
85+
auto end = std::chrono::high_resolution_clock::now();
86+
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
87+
88+
if (printTs) {
89+
std::cout << "API Latency (in milli sec): " << ms << std::endl;
90+
}
91+
return 0;
92+
} catch (nvidia::aiaa::exception &e) {
93+
std::cerr << "nvidia::aiaa::exception => nvidia.aiaa.error." << e.id << "; description: " << e.name() << "; reason: " << e.what() << std::endl;
94+
}
95+
return -1;
96+
}

docs/tools.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,37 @@ Example
225225
-fpoints [[283,204,105]] \
226226
-output tmp_out.nii.gz
227227
228+
Inference
229+
------------
230+
231+
Provides implementation for ``nvidia::aiaa::Client::inference()`` API.
232+
For more details refer `aiaa-inference.cpp <https://github.com/NVIDIA/ai-assisted-annotation-client/blob/master/cpp-client/tools/aiaa/aiaa-inference.cpp>`_
233+
234+
Following are the options available
235+
236+
.. csv-table::
237+
:header: Option,Description,Default,Example
238+
:widths: auto
239+
240+
-h,Prints the help information,,
241+
-server,Server URI for AIAA Server,,-server http://0.0.0.0:5000
242+
-model,Model Name,,-model clara_xray_classification_chest_amp
243+
-params,Params JSON,,-params {}
244+
-image,Input image filename where image,,-image input.png
245+
-output,File name to store output image result from AIAA server,,-output output.png
246+
-session,Session ID instead of -image option,,-session "9ad970be-530e-11ea-84e3-0242ac110007"
247+
248+
Example
249+
250+
.. code-block:: bash
251+
252+
nvidiaAIAAInference \
253+
-server http://0.0.0.0:5000 \
254+
-model clara_xray_classification_chest_amp \
255+
-image AC07112011207153620_v2.png \
256+
-params {}
257+
258+
228259
Mask To Polygon
229260
------------------
230261

py-client/aas_tests.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@
5757
"image_in": "test-data/image.nii.gz",
5858
"image_out": "test-data/output/deepgrow_spleen.nii.gz"
5959
},
60+
{
61+
"name": "test_inference",
62+
"disabled": false,
63+
"api": "inference",
64+
"model": "clara_xray_classification_chest_amp",
65+
"params": {},
66+
"image_in": "test-data/AC07112011207153620_v2.png"
67+
},
6068
{
6169
"name": "test_mask2polygon_spleen",
6270
"disabled": false,

0 commit comments

Comments
 (0)