TensorFlow Serving C++ API Documentation
json_tensor.h
1 /* Copyright 2018 Google Inc. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7  http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #ifndef TENSORFLOW_SERVING_UTIL_JSON_TENSOR_H_
17 #define TENSORFLOW_SERVING_UTIL_JSON_TENSOR_H_
18 
19 #include <functional>
20 #include <string>
21 
22 #include "absl/strings/string_view.h"
23 #include "tensorflow/core/framework/tensor.pb.h"
24 #include "tensorflow/core/lib/core/status.h"
25 #include "tensorflow/core/protobuf/meta_graph.pb.h"
26 #include "tensorflow_serving/apis/classification.pb.h"
27 #include "tensorflow_serving/apis/predict.pb.h"
28 #include "tensorflow_serving/apis/regression.pb.h"
29 
30 namespace tensorflow {
31 namespace serving {
32 
33 // Format of input tensors in predict request.
34 // See comments for FillPredictRequestFromJson() below for more details.
35 enum class JsonPredictRequestFormat {
36  kInvalid,
37  kRow,
38  kColumnar,
39 };
40 
41 // Fills PredictRequest proto from a JSON object.
42 //
43 // `json` string is parsed to create TensorProtos based on the type map returned
44 // via `get_tensorinfo_map` and added to `PredictRequest.inputs`. Both maps are
45 // keyed by the name/alias of the tensor as it appears in the TensorFlow graph,
46 // and must contain at-least one entry. The name to <type> (e.g. DT_FLOAT etc.)
47 // mapping is typically part of the graph metadata.
48 //
49 // Following fields of the request proto are filled in:
50 //
51 // `model_spec.signature_name` (string)
52 // `inputs` (map string -> tensors)
53 //
54 // The JSON object is expected to be formatted as follows:
55 //
56 // {
57 // "signature_name": <string>
58 // ("instances"|"inputs"): [ <value>|<(nested)list>|<object>, ... ]
59 // }
60 //
61 // The "signature_name" is *optional* (if not specified, default serving
62 // signature is used). The "instances" or "inputs" represents list of tensors
63 // (read further on the formatting of these tensors below). Any other keys in
64 // the top-level JSON object are ignored.
65 //
66 // "instances" is used to format input tensors in "row" format and "inputs"
67 // is used to format them in "columnar" format. The former is easy to read
68 // but requires all inputs to have same 0-th dimension whereas the latter
69 // can represent input tensors with varying sizes.
70 //
71 // === Notes on formatting of "instances" (row format) in the JSON ===
72 //
73 // The "instances" represents a list of tensors (all of same shape+type), and
74 // this function builds a stack of these tensors (represented as tensor). If the
75 // list has tensors of rank-R, the output tensor is of rank R+1.
76 //
77 // The "instances" (key) maps to list of tensors (value). Each element of the
78 // list is of same type (and nesting, in case listoflists).
79 //
80 // {
81 // "instances": [ <value>|<(nested)list>|<object>, ... ]
82 // }
83 //
84 // This formatting is similar to CMLE predict API:
85 // https://cloud.google.com/ml-engine/docs/v1/predict-request
86 //
87 // o When there is only one named input (tensorinfo_map has only one key),
88 // the list items are expected to be scalars (number/string) or lists of
89 // these primitive types.
90 //
91 // {
92 // "instances": [ "foo", "bar", "baz" ]
93 // }
94 //
95 // {
96 // "instances": [ [[1, 2]], [[3], [4]] ]
97 // }
98 //
99 // o For multiple named inputs (tensorinfo_map has >1 keys), each item is
100 // expected to be an object containing key(name)/value(tensor) pairs, one
101 // for each named input. Representing 2 instaces of a set of 3 named input
102 // tensors would look as follows:
103 //
104 // {
105 // "instances": [
106 // {
107 // "tag": ["foo"],
108 // "signal": [1, 2, 3, 4, 5],
109 // "sensor": [[1, 2], [3, 4]]
110 // },
111 // {
112 // "tag": ["bar"],
113 // "signal": [3, 4, 1, 2, 5],
114 // "sensor": [[4, 5], [6, 8]]
115 // }
116 // ]
117 // }
118 //
119 // o Default encoding of strings is UTF-8. To express binary data (like image
120 // bytes) use a JSON object '{ "b64": "<base64-encoded-data>" }' instead of
121 // a raw string. Following example shows list of 2 binary strings:
122 //
123 // {
124 // "instances": [ { "b64" : "aGVsbG8=" }, { "b64": "d29ybGQ=" } ]
125 // }
126 //
127 // === Notes on formatting of "inputs" (columnar format) in the JSON ===
128 //
129 // The value for "inputs" key is either a single input tensor or a map
130 // of input name to tensor values. Each input can have arbitrary shape
131 // and need not share the same 0-th dimension as required by the row
132 // format described above.
133 //
134 // This format is similar to the `inputs` field in request proto of
135 // gRPC Predict API. And compact compared to the row format
136 //
137 // A sample (compare this to "instances" above) is as follows:
138 // {
139 // "inputs": {
140 // "tag": ["foo", "bar"],
141 // "signal": [1, 2, 3, 4, 5, 3, 4, 1, 2, 4],
142 // "sensor": [[1, 2], [3, 4], [4, 5], [6, 8]]
143 // }
144 // }
145 tensorflow::Status FillPredictRequestFromJson(
146  const absl::string_view json,
147  const std::function<tensorflow::Status(
148  const string&, ::google::protobuf::Map<string, tensorflow::TensorInfo>*)>&
149  get_tensorinfo_map,
150  PredictRequest* request, JsonPredictRequestFormat* format);
151 
152 // Fills ClassificationRequest proto from a JSON object.
153 //
154 // `json` string is parsed to create `Example` protos and added to
155 // `ClassificationRequest.inputs`.
156 //
157 // Following fields of the request proto are filled in:
158 //
159 // `model_spec.signature_name` (string)
160 // `input` (list of example protos)
161 //
162 // The JSON object is expected to be formatted as follows:
163 //
164 // {
165 // "signature_name": <string>
166 //
167 // "context": {
168 // // Common (example) context shared by all examples.
169 // "<feature_name3>": <value>|<list>
170 // "<feature_name4>": <value>|<list>
171 // },
172 //
173 // "examples": [
174 // {
175 // // Example 1
176 // "<feature_name>": <value>|<list>
177 // "<feature_name2>": <value>|<list>
178 // },
179 // {
180 // // Example 2
181 // "<feature_name>": <value>|<list>
182 // "<feature_name2>": <value>|<list>
183 // },
184 // ]
185 // }
186 //
187 // The "signature_name" is *optional* (if not specified, default serving
188 // signature is used). "context" is also optional. "exampless" represents
189 // list of examples. Any other keys in the top-level JSON object are ignored.
190 tensorflow::Status FillClassificationRequestFromJson(
191  const absl::string_view json, ClassificationRequest* request);
192 
193 // Same as FillClassificationRequestFromJson() but fills a RegressionRequest.
194 // See comments above on how Example protos are expected to be formatted.
195 tensorflow::Status FillRegressionRequestFromJson(const absl::string_view json,
196  RegressionRequest* request);
197 
198 // Make JSON object from TensorProtos.
199 //
200 // `tensor_map` contains a map of name/alias tensor names (as it appears in the
201 // TensorFlow graph) to tensor protos. The output `json` string is JSON object
202 // containing all the tensors represented by these tensor protos. The
203 // composition of output JSON depends on `format` (read further for details).
204 //
205 // In case of error the contents of output `json` should not be used.
206 //
207 // Tensors containing binary data (e.g. image bytes) are base64 encoded in the
208 // JSON object. The name/alias for these tensors MUST have "_bytes" as suffix,
209 // to ensure JSON has correct (base64) encoding, otherwise the resulting JSON
210 // may not represent the output correctly and/or may not be parsable.
211 //
212 // Formatting when `format` is `kRow`:
213 //
214 // Tensors appear as list (with "batch" size elements) keyed by "predictions"
215 // in the JSON object:
216 //
217 // {
218 // "predictions": [ <value>|<(nested)list>|<object>, ...]
219 // }
220 //
221 // If `tensor_map` contains only one key/named tensor, the "predictions" key
222 // contains a array of <value> or <(nested)list> otherwise it is an array of
223 // JSON objects. See comments for FillPredictRequestFromJson() above for
224 // formatting details (and unit-test of examples).
225 //
226 // The first dimension in each of these tensors is assumed to be the "batch"
227 // size and it is expected that all tensors have the *same* batch size --
228 // otherwise we return an error.
229 //
230 // Note, this formatting is similar to CMLE predict API:
231 // https://cloud.google.com/ml-engine/docs/v1/predict-request#response-body
232 //
233 // Formatting when `format` is `kColumnar`
234 //
235 // Tensors appear as list (when there is only one named output) or as a JSON
236 // object, with one key-value pair for each named input, keyed by "outputs"
237 // in the JSON object.
238 //
239 // Unlike `kRow` format (see above) each named tensor can have different first
240 // dimension. This format is compact and matches closely with the response
241 // proto of the gRPC predict API.
242 //
243 // Only one named output:
244 // {
245 // "outputs": [ <value>|<(nested)list> ]
246 // }
247 //
248 // Multiple named output:
249 // {
250 // "outputs": {
251 // "named_output_foo": [ <value>|<(nested)list> ],
252 // "named_output_bar": [ <value>|<(nested)list> ],
253 // ...
254 // }
255 // }
256 //
257 // See comments for FillPredictRequestFromJson() above for formatting details
258 // (and unit-test of examples).
259 tensorflow::Status MakeJsonFromTensors(
260  const ::google::protobuf::Map<string, tensorflow::TensorProto>& tensor_map,
261  JsonPredictRequestFormat format, string* json);
262 
263 // Make JSON object from ClassificationResult proto.
264 //
265 // The output JSON object is formatted as follows:
266 //
267 // {
268 // "result": [
269 // // List of class label/score pairs for first Example (in request)
270 // [ [ <label1>, <score1> ], [ <label2>, <score2> ], ... ],
271 //
272 // // List of class label/score pairs for next Example (in request)
273 // [ [ <label1>, <score1> ], [ <label2>, <score2> ], ... ],
274 // ...
275 // ]
276 // }
277 //
278 // Note, all the results are keyed by "result" key in the JSON object, and
279 // they are ordered to match the input/request ordering of Examples. <label>
280 // is a string, and <score> is a float number. <label> can be missing from
281 // the results of graph evaluation, and these would appear as empty strings
282 // in above JSON.
283 tensorflow::Status MakeJsonFromClassificationResult(
284  const ClassificationResult& result, string* json);
285 
286 // Make JSON object from RegressionResult proto.
287 //
288 // The output JSON object is formatted as follows:
289 //
290 // {
291 // // One regression value for each example in the request in the same order.
292 // "result": [ <value1>, <value2>, <value3>, ...]
293 // }
294 //
295 // Note, all the results are keyed by "result" key in the JSON object.
296 // <value> is a float number.
297 tensorflow::Status MakeJsonFromRegressionResult(const RegressionResult& result,
298  string* json);
299 
300 // Make JSON object from Status.
301 //
302 // The output JSON object is formatted as follows:
303 //
304 // {
305 // "error": "<status error message>"
306 // }
307 //
308 // If `status` is OK then we do not append anything to output `json`.
309 void MakeJsonFromStatus(const tensorflow::Status& status, string* json);
310 
311 } // namespace serving
312 } // namespace tensorflow
313 
314 #endif // TENSORFLOW_SERVING_UTIL_JSON_TENSOR_H_