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,
+        }))