Mercurial > hg > orthanc-python
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CodeAnalysis/GenerateOrthancSDK.py Thu Jun 27 17:47:05 2024 +0200 @@ -0,0 +1,392 @@ +#!/usr/bin/env python3 + +## +## Python plugin for Orthanc +## Copyright (C) 2020-2023 Osimis S.A., Belgium +## Copyright (C) 2024-2024 Orthanc Team SRL, Belgium +## Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium +## +## This program is free software: you can redistribute it and/or +## modify it under the terms of the GNU Affero General Public License +## as published by the Free Software Foundation, either version 3 of +## the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Affero General Public License for more details. +## +## You should have received a copy of the GNU Affero General Public License +## along with this program. If not, see <http://www.gnu.org/licenses/>. +## + + +import argparse +import json +import os +import sys +import pystache + +ROOT = os.path.dirname(os.path.realpath(sys.argv[0])) + + +## +## Parse the command-line arguments +## + +parser = argparse.ArgumentParser(description = 'Generate Python code to wrap the Orthanc SDK.') +parser.add_argument('--model', + default = os.path.join(os.path.dirname(__file__), + '../Resources/Orthanc/Sdk-1.10.0/CodeModel.json'), + help = 'Input code model, as generated by the orthanc-java project') +parser.add_argument('--target', + default = os.path.join(os.path.dirname(__file__), + '../Sources/Autogenerated'), + help = 'Target folder') + +args = parser.parse_args() + + + +## +## Configuration of the custom primitives that are manually +## implemented (not autogenerated) +## + +CUSTOM_FUNCTIONS = [ + { + 'c_function' : 'OrthancPluginCreateMemoryBuffer', + 'args' : [ + { + 'name' : 'arg0', + 'sdk_type' : 'uint32_t', + } + ], + 'return_sdk_type' : 'OrthancPluginMemoryBuffer *', + } +] + + +CUSTOM_METHODS = { + 'OrthancPluginFindQuery' : [ + { + 'method_name' : 'GetFindQueryTagGroup', + 'implementation' : 'GetFindQueryTagGroup', + 'sdk_function' : 'OrthancPluginGetFindQueryTag', + }, + { + 'method_name' : 'GetFindQueryTagElement', + 'implementation' : 'GetFindQueryTagElement', + 'sdk_function' : 'OrthancPluginGetFindQueryTag', + }, + ], + + 'OrthancPluginWorklistAnswers' : [ + { + 'method_name' : 'WorklistAddAnswer', + 'implementation' : 'WorklistAddAnswer', + 'sdk_function' : 'OrthancPluginWorklistAddAnswer', + }, + ], + + 'OrthancPluginDicomInstance' : [ + { + 'method_name' : 'GetInstanceData', + 'implementation' : 'GetInstanceData', + 'sdk_function' : 'OrthancPluginGetInstanceData', + }, + ], + + 'OrthancPluginImage' : [ + { + 'method_name' : 'GetImageBuffer', + 'implementation' : 'GetImageBuffer', + 'sdk_function' : 'OrthancPluginGetImageBuffer', + } + ], +} + + + +TARGET = os.path.realpath(args.target) + + +partials = {} + +with open(os.path.join(ROOT, 'FunctionBody.mustache'), 'r') as f: + partials['function_body'] = f.read() + +renderer = pystache.Renderer( + escape = lambda u: u, # No escaping + partials = partials, +) + + + +with open(args.model, 'r') as f: + model = json.loads(f.read()) + + +def ToUpperCase(name): + s = '' + for i in range(len(name)): + if name[i].isupper(): + if len(s) == 0: + s += name[i] + elif name[i - 1].islower(): + s += '_' + name[i] + elif (i + 1 < len(name) and + name[i - 1].islower() and + name[i + 1].isupper()): + s += '_' + name[i] + else: + s += name[i] + else: + s += name[i].upper() + return s + + +def GetShortName(name): + if not name.startswith('OrthancPlugin'): + raise Exception() + else: + return name[len('OrthancPlugin'):] + + + +ORTHANC_TO_PYTHON_NUMERIC_TYPES = { + # https://docs.python.org/3/c-api/arg.html#numbers + # https://en.wikipedia.org/wiki/C_data_types + 'uint8_t' : { + 'type' : 'unsigned char', + 'format' : 'b', + }, + 'int32_t' : { + 'type' : 'long int', + 'format' : 'l', + }, + 'uint16_t' : { + 'type' : 'unsigned short', + 'format' : 'H', + }, + 'uint32_t' : { + 'type' : 'unsigned long', + 'format' : 'k', + }, + 'uint64_t' : { + 'type' : 'unsigned long long', + 'format' : 'K', + }, + 'float' : { + 'type' : 'float', + 'format' : 'f', + } + } + + +def FormatFunction(f): + answer = { + 'c_function' : f['c_function'], + 'short_name' : GetShortName(f['c_function']), + 'has_args' : len(f['args']) > 0, + 'count_args' : len(f['args']), + } + + tuple_format = '' + tuple_target = [] + call_args = [] + args = [] + + for arg in f['args']: + # https://docs.python.org/3/c-api/arg.html + if arg['sdk_type'] in [ 'const void *', 'const_void_pointer_with_size' ]: + args.append({ + 'name' : arg['name'], + 'python_type' : 'Py_buffer', + 'release' : 'PyBuffer_Release(&%s);' % arg['name'], + }) + tuple_format += 's*' + elif arg['sdk_type'] == 'const char *': + args.append({ + 'name' : arg['name'], + 'python_type' : 'const char*', + 'initialization' : ' = NULL', + }) + tuple_format += 's' + elif arg['sdk_type'] == 'enumeration': + args.append({ + 'name' : arg['name'], + 'python_type' : 'long int', + 'initialization' : ' = 0', + }) + tuple_format += 'l' + elif arg['sdk_type'] == 'const_object': + args.append({ + 'name' : arg['name'], + 'python_type' : 'PyObject*', + 'initialization' : ' = NULL', + 'check_object_type' : arg['sdk_class'], + }) + tuple_format += 'O' + elif arg['sdk_type'] in ORTHANC_TO_PYTHON_NUMERIC_TYPES: + t = ORTHANC_TO_PYTHON_NUMERIC_TYPES[arg['sdk_type']] + args.append({ + 'name' : arg['name'], + 'python_type' : t['type'], + 'initialization' : ' = 0', + }) + tuple_format += t['format'] + else: + print('Ignoring function with unsupported argument type: %s(), type = %s' % (f['c_function'], arg['sdk_type'])) + return None + + tuple_target.append('&' + arg['name']) + + if arg['sdk_type'] == 'const void *': + call_args.append(arg['name'] + '.buf') + elif arg['sdk_type'] == 'const_void_pointer_with_size': + call_args.append(arg['name'] + '.buf') + call_args.append(arg['name'] + '.len') + elif arg['sdk_type'] == 'enumeration': + call_args.append('static_cast<%s>(%s)' % (arg['sdk_enumeration'], arg['name'])) + elif arg['sdk_type'] == 'const_object': + call_args.append('%s == Py_None ? NULL : reinterpret_cast<sdk_%s_Object*>(%s)->object_' % ( + arg['name'], arg['sdk_class'], arg['name'])) + else: + call_args.append(arg['name']) + + answer['args'] = args + + if f['return_sdk_type'] == 'void': + answer['return_void'] = True + elif f['return_sdk_type'] in [ 'int32_t', 'uint32_t', 'int64_t' ]: + answer['return_long'] = True + elif f['return_sdk_type'] == 'OrthancPluginMemoryBuffer *': + answer['return_bytes'] = True + elif f['return_sdk_type'] == 'enumeration': + if f['return_sdk_enumeration'] == 'OrthancPluginErrorCode': + answer['return_error'] = True + else: + answer['return_enumeration'] = f['return_sdk_enumeration'] + elif f['return_sdk_type'] == 'char *': + answer['return_dynamic_string'] = True + elif f['return_sdk_type'] == 'const char *': + answer['return_static_string'] = True + elif f['return_sdk_type'] == 'object': + answer['return_object'] = f['return_sdk_class'] + else: + print('Ignoring function with unsupported return type: %s(), type = %s' % (f['c_function'], f['return_sdk_type'])) + return None + + answer['tuple_format'] = ', '.join([ '"' + tuple_format + '"' ] + tuple_target) + + if len(call_args) > 0: + answer['call_args'] = ', ' + ', '.join(call_args) + + return answer + + + +globalFunctions = [] + +for f in model['global_functions']: + g = FormatFunction(f) + if g != None: + globalFunctions.append(g) + +for f in CUSTOM_FUNCTIONS: + g = FormatFunction(f) + if g != None: + globalFunctions.append(g) + + +enumerations = [] + +with open(os.path.join(ROOT, 'Enumeration.mustache'), 'r') as f: + ENUMERATION_TEMPLATE = f.read() + +for e in model['enumerations']: + values = [] + for value in e['values']: + values.append({ + 'key' : ToUpperCase(value['key']), + 'value' : value['value'], + }) + + enumerations.append({ + 'name' : e['name'], + 'path' : 'sdk_%s.impl.h' % e['name'], + 'values' : values, + }) + + path = 'sdk_%s.impl.h' % e['name'] + + with open(os.path.join(TARGET, path), 'w') as f: + f.write(pystache.render(ENUMERATION_TEMPLATE, { + 'name' : e['name'], + 'short_name' : GetShortName(e['name']), + 'values' : values, + })) + + +classes = [] + +for c in model['classes']: + methods = [] + + for m in c['methods']: + g = FormatFunction(m) + if g != None: + g['self'] = ', self->object_' + methods.append(g) + + classes.append({ + 'class_name' : c['name'], + 'short_name' : GetShortName(c['name']), + 'methods' : methods, + }) + + if c['name'] in CUSTOM_METHODS: + classes[-1]['custom_methods'] = CUSTOM_METHODS[c['name']] + + if 'destructor' in c: + classes[-1]['destructor'] = c['destructor'] + + + + +with open(os.path.join(ROOT, 'Class.mustache'), 'r') as f: + with open(os.path.join(ROOT, 'ClassMethods.mustache'), 'r') as g: + classDefinition = f.read() + classMethods = g.read() + + for c in classes: + with open(os.path.join(TARGET, 'sdk_%s.impl.h' % c['class_name']), 'w') as h: + h.write(renderer.render(classDefinition, c)) + with open(os.path.join(TARGET, 'sdk_%s.methods.h' % c['class_name']), 'w') as h: + h.write(renderer.render(classMethods, c)) + + +sortedClasses = sorted(classes, key = lambda x: x['class_name']) +sortedEnumerations = sorted(enumerations, key = lambda x: x['name']) +sortedGlobalFunctions = sorted(globalFunctions, key = lambda x: x['c_function']) + +with open(os.path.join(ROOT, 'GlobalFunctions.mustache'), 'r') as f: + with open(os.path.join(TARGET, 'sdk_GlobalFunctions.impl.h'), 'w') as h: + h.write(renderer.render(f.read(), { + 'global_functions' : sortedGlobalFunctions, + })) + +with open(os.path.join(ROOT, 'sdk.cpp.mustache'), 'r') as f: + with open(os.path.join(TARGET, 'sdk.cpp'), 'w') as h: + h.write(renderer.render(f.read(), { + 'classes' : sortedClasses, + 'enumerations' : sortedEnumerations, + 'global_functions' : sortedGlobalFunctions, + })) + +with open(os.path.join(ROOT, 'sdk.h.mustache'), 'r') as f: + with open(os.path.join(TARGET, 'sdk.h'), 'w') as h: + h.write(renderer.render(f.read(), { + 'classes' : sortedClasses, + }))