comparison CodeAnalysis/GenerateOrthancSDK.py @ 172:8382c7dea471 java-code-model

created CodeAnalysis/GenerateOrthancSDK.py
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 27 Jun 2024 17:47:05 +0200
parents
children e9be3c9294d4
comparison
equal deleted inserted replaced
171:c8de83fe7faa 172:8382c7dea471
1 #!/usr/bin/env python3
2
3 ##
4 ## Python plugin for Orthanc
5 ## Copyright (C) 2020-2023 Osimis S.A., Belgium
6 ## Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
7 ## Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
8 ##
9 ## This program is free software: you can redistribute it and/or
10 ## modify it under the terms of the GNU Affero General Public License
11 ## as published by the Free Software Foundation, either version 3 of
12 ## the License, or (at your option) any later version.
13 ##
14 ## This program is distributed in the hope that it will be useful, but
15 ## WITHOUT ANY WARRANTY; without even the implied warranty of
16 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 ## Affero General Public License for more details.
18 ##
19 ## You should have received a copy of the GNU Affero General Public License
20 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##
22
23
24 import argparse
25 import json
26 import os
27 import sys
28 import pystache
29
30 ROOT = os.path.dirname(os.path.realpath(sys.argv[0]))
31
32
33 ##
34 ## Parse the command-line arguments
35 ##
36
37 parser = argparse.ArgumentParser(description = 'Generate Python code to wrap the Orthanc SDK.')
38 parser.add_argument('--model',
39 default = os.path.join(os.path.dirname(__file__),
40 '../Resources/Orthanc/Sdk-1.10.0/CodeModel.json'),
41 help = 'Input code model, as generated by the orthanc-java project')
42 parser.add_argument('--target',
43 default = os.path.join(os.path.dirname(__file__),
44 '../Sources/Autogenerated'),
45 help = 'Target folder')
46
47 args = parser.parse_args()
48
49
50
51 ##
52 ## Configuration of the custom primitives that are manually
53 ## implemented (not autogenerated)
54 ##
55
56 CUSTOM_FUNCTIONS = [
57 {
58 'c_function' : 'OrthancPluginCreateMemoryBuffer',
59 'args' : [
60 {
61 'name' : 'arg0',
62 'sdk_type' : 'uint32_t',
63 }
64 ],
65 'return_sdk_type' : 'OrthancPluginMemoryBuffer *',
66 }
67 ]
68
69
70 CUSTOM_METHODS = {
71 'OrthancPluginFindQuery' : [
72 {
73 'method_name' : 'GetFindQueryTagGroup',
74 'implementation' : 'GetFindQueryTagGroup',
75 'sdk_function' : 'OrthancPluginGetFindQueryTag',
76 },
77 {
78 'method_name' : 'GetFindQueryTagElement',
79 'implementation' : 'GetFindQueryTagElement',
80 'sdk_function' : 'OrthancPluginGetFindQueryTag',
81 },
82 ],
83
84 'OrthancPluginWorklistAnswers' : [
85 {
86 'method_name' : 'WorklistAddAnswer',
87 'implementation' : 'WorklistAddAnswer',
88 'sdk_function' : 'OrthancPluginWorklistAddAnswer',
89 },
90 ],
91
92 'OrthancPluginDicomInstance' : [
93 {
94 'method_name' : 'GetInstanceData',
95 'implementation' : 'GetInstanceData',
96 'sdk_function' : 'OrthancPluginGetInstanceData',
97 },
98 ],
99
100 'OrthancPluginImage' : [
101 {
102 'method_name' : 'GetImageBuffer',
103 'implementation' : 'GetImageBuffer',
104 'sdk_function' : 'OrthancPluginGetImageBuffer',
105 }
106 ],
107 }
108
109
110
111 TARGET = os.path.realpath(args.target)
112
113
114 partials = {}
115
116 with open(os.path.join(ROOT, 'FunctionBody.mustache'), 'r') as f:
117 partials['function_body'] = f.read()
118
119 renderer = pystache.Renderer(
120 escape = lambda u: u, # No escaping
121 partials = partials,
122 )
123
124
125
126 with open(args.model, 'r') as f:
127 model = json.loads(f.read())
128
129
130 def ToUpperCase(name):
131 s = ''
132 for i in range(len(name)):
133 if name[i].isupper():
134 if len(s) == 0:
135 s += name[i]
136 elif name[i - 1].islower():
137 s += '_' + name[i]
138 elif (i + 1 < len(name) and
139 name[i - 1].islower() and
140 name[i + 1].isupper()):
141 s += '_' + name[i]
142 else:
143 s += name[i]
144 else:
145 s += name[i].upper()
146 return s
147
148
149 def GetShortName(name):
150 if not name.startswith('OrthancPlugin'):
151 raise Exception()
152 else:
153 return name[len('OrthancPlugin'):]
154
155
156
157 ORTHANC_TO_PYTHON_NUMERIC_TYPES = {
158 # https://docs.python.org/3/c-api/arg.html#numbers
159 # https://en.wikipedia.org/wiki/C_data_types
160 'uint8_t' : {
161 'type' : 'unsigned char',
162 'format' : 'b',
163 },
164 'int32_t' : {
165 'type' : 'long int',
166 'format' : 'l',
167 },
168 'uint16_t' : {
169 'type' : 'unsigned short',
170 'format' : 'H',
171 },
172 'uint32_t' : {
173 'type' : 'unsigned long',
174 'format' : 'k',
175 },
176 'uint64_t' : {
177 'type' : 'unsigned long long',
178 'format' : 'K',
179 },
180 'float' : {
181 'type' : 'float',
182 'format' : 'f',
183 }
184 }
185
186
187 def FormatFunction(f):
188 answer = {
189 'c_function' : f['c_function'],
190 'short_name' : GetShortName(f['c_function']),
191 'has_args' : len(f['args']) > 0,
192 'count_args' : len(f['args']),
193 }
194
195 tuple_format = ''
196 tuple_target = []
197 call_args = []
198 args = []
199
200 for arg in f['args']:
201 # https://docs.python.org/3/c-api/arg.html
202 if arg['sdk_type'] in [ 'const void *', 'const_void_pointer_with_size' ]:
203 args.append({
204 'name' : arg['name'],
205 'python_type' : 'Py_buffer',
206 'release' : 'PyBuffer_Release(&%s);' % arg['name'],
207 })
208 tuple_format += 's*'
209 elif arg['sdk_type'] == 'const char *':
210 args.append({
211 'name' : arg['name'],
212 'python_type' : 'const char*',
213 'initialization' : ' = NULL',
214 })
215 tuple_format += 's'
216 elif arg['sdk_type'] == 'enumeration':
217 args.append({
218 'name' : arg['name'],
219 'python_type' : 'long int',
220 'initialization' : ' = 0',
221 })
222 tuple_format += 'l'
223 elif arg['sdk_type'] == 'const_object':
224 args.append({
225 'name' : arg['name'],
226 'python_type' : 'PyObject*',
227 'initialization' : ' = NULL',
228 'check_object_type' : arg['sdk_class'],
229 })
230 tuple_format += 'O'
231 elif arg['sdk_type'] in ORTHANC_TO_PYTHON_NUMERIC_TYPES:
232 t = ORTHANC_TO_PYTHON_NUMERIC_TYPES[arg['sdk_type']]
233 args.append({
234 'name' : arg['name'],
235 'python_type' : t['type'],
236 'initialization' : ' = 0',
237 })
238 tuple_format += t['format']
239 else:
240 print('Ignoring function with unsupported argument type: %s(), type = %s' % (f['c_function'], arg['sdk_type']))
241 return None
242
243 tuple_target.append('&' + arg['name'])
244
245 if arg['sdk_type'] == 'const void *':
246 call_args.append(arg['name'] + '.buf')
247 elif arg['sdk_type'] == 'const_void_pointer_with_size':
248 call_args.append(arg['name'] + '.buf')
249 call_args.append(arg['name'] + '.len')
250 elif arg['sdk_type'] == 'enumeration':
251 call_args.append('static_cast<%s>(%s)' % (arg['sdk_enumeration'], arg['name']))
252 elif arg['sdk_type'] == 'const_object':
253 call_args.append('%s == Py_None ? NULL : reinterpret_cast<sdk_%s_Object*>(%s)->object_' % (
254 arg['name'], arg['sdk_class'], arg['name']))
255 else:
256 call_args.append(arg['name'])
257
258 answer['args'] = args
259
260 if f['return_sdk_type'] == 'void':
261 answer['return_void'] = True
262 elif f['return_sdk_type'] in [ 'int32_t', 'uint32_t', 'int64_t' ]:
263 answer['return_long'] = True
264 elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *':
265 answer['return_bytes'] = True
266 elif f['return_sdk_type'] == 'enumeration':
267 if f['return_sdk_enumeration'] == 'OrthancPluginErrorCode':
268 answer['return_error'] = True
269 else:
270 answer['return_enumeration'] = f['return_sdk_enumeration']
271 elif f['return_sdk_type'] == 'char *':
272 answer['return_dynamic_string'] = True
273 elif f['return_sdk_type'] == 'const char *':
274 answer['return_static_string'] = True
275 elif f['return_sdk_type'] == 'object':
276 answer['return_object'] = f['return_sdk_class']
277 else:
278 print('Ignoring function with unsupported return type: %s(), type = %s' % (f['c_function'], f['return_sdk_type']))
279 return None
280
281 answer['tuple_format'] = ', '.join([ '"' + tuple_format + '"' ] + tuple_target)
282
283 if len(call_args) > 0:
284 answer['call_args'] = ', ' + ', '.join(call_args)
285
286 return answer
287
288
289
290 globalFunctions = []
291
292 for f in model['global_functions']:
293 g = FormatFunction(f)
294 if g != None:
295 globalFunctions.append(g)
296
297 for f in CUSTOM_FUNCTIONS:
298 g = FormatFunction(f)
299 if g != None:
300 globalFunctions.append(g)
301
302
303 enumerations = []
304
305 with open(os.path.join(ROOT, 'Enumeration.mustache'), 'r') as f:
306 ENUMERATION_TEMPLATE = f.read()
307
308 for e in model['enumerations']:
309 values = []
310 for value in e['values']:
311 values.append({
312 'key' : ToUpperCase(value['key']),
313 'value' : value['value'],
314 })
315
316 enumerations.append({
317 'name' : e['name'],
318 'path' : 'sdk_%s.impl.h' % e['name'],
319 'values' : values,
320 })
321
322 path = 'sdk_%s.impl.h' % e['name']
323
324 with open(os.path.join(TARGET, path), 'w') as f:
325 f.write(pystache.render(ENUMERATION_TEMPLATE, {
326 'name' : e['name'],
327 'short_name' : GetShortName(e['name']),
328 'values' : values,
329 }))
330
331
332 classes = []
333
334 for c in model['classes']:
335 methods = []
336
337 for m in c['methods']:
338 g = FormatFunction(m)
339 if g != None:
340 g['self'] = ', self->object_'
341 methods.append(g)
342
343 classes.append({
344 'class_name' : c['name'],
345 'short_name' : GetShortName(c['name']),
346 'methods' : methods,
347 })
348
349 if c['name'] in CUSTOM_METHODS:
350 classes[-1]['custom_methods'] = CUSTOM_METHODS[c['name']]
351
352 if 'destructor' in c:
353 classes[-1]['destructor'] = c['destructor']
354
355
356
357
358 with open(os.path.join(ROOT, 'Class.mustache'), 'r') as f:
359 with open(os.path.join(ROOT, 'ClassMethods.mustache'), 'r') as g:
360 classDefinition = f.read()
361 classMethods = g.read()
362
363 for c in classes:
364 with open(os.path.join(TARGET, 'sdk_%s.impl.h' % c['class_name']), 'w') as h:
365 h.write(renderer.render(classDefinition, c))
366 with open(os.path.join(TARGET, 'sdk_%s.methods.h' % c['class_name']), 'w') as h:
367 h.write(renderer.render(classMethods, c))
368
369
370 sortedClasses = sorted(classes, key = lambda x: x['class_name'])
371 sortedEnumerations = sorted(enumerations, key = lambda x: x['name'])
372 sortedGlobalFunctions = sorted(globalFunctions, key = lambda x: x['c_function'])
373
374 with open(os.path.join(ROOT, 'GlobalFunctions.mustache'), 'r') as f:
375 with open(os.path.join(TARGET, 'sdk_GlobalFunctions.impl.h'), 'w') as h:
376 h.write(renderer.render(f.read(), {
377 'global_functions' : sortedGlobalFunctions,
378 }))
379
380 with open(os.path.join(ROOT, 'sdk.cpp.mustache'), 'r') as f:
381 with open(os.path.join(TARGET, 'sdk.cpp'), 'w') as h:
382 h.write(renderer.render(f.read(), {
383 'classes' : sortedClasses,
384 'enumerations' : sortedEnumerations,
385 'global_functions' : sortedGlobalFunctions,
386 }))
387
388 with open(os.path.join(ROOT, 'sdk.h.mustache'), 'r') as f:
389 with open(os.path.join(TARGET, 'sdk.h'), 'w') as h:
390 h.write(renderer.render(f.read(), {
391 'classes' : sortedClasses,
392 }))