gen_matlab.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env python
  2. import sys, re, os, time
  3. from string import Template
  4. from parse_tree import ParseTree, todict, constants
  5. from filters import *
  6. updated_files = []
  7. def update_file(fname, content):
  8. if fname in updated_files:
  9. print('ERROR(gen_matlab.py): attemption to write file multiple times: {}'.format(fname))
  10. return
  11. updated_files.append(fname)
  12. if os.path.exists(fname):
  13. with open(fname, 'rb') as f:
  14. old_content = f.read()
  15. if old_content == content:
  16. #print('Up-to-date: {}'.format(fname))
  17. return
  18. print('Updating: {}'.format(fname))
  19. else:
  20. print('Writing: {}'.format(fname))
  21. with open(fname, 'wb') as f:
  22. f.write(content)
  23. class MatlabWrapperGenerator(object):
  24. """
  25. MatlabWrapperGenerator is a class for generating Matlab mex sources from
  26. a set of C++ headers. MatlabWrapperGenerator objects can be default
  27. constructed. Given an instance, the gen() method performs the translation.
  28. """
  29. def gen(self, module_roots, modules, extras, output_dir):
  30. """
  31. Generate a set of Matlab mex source files by parsing exported symbols
  32. in a set of C++ headers. The headers can be input in one (or both) of
  33. two methods:
  34. 1. specify module_root and modules
  35. Given a path to the OpenCV module root and a list of module names,
  36. the headers to parse are implicitly constructed.
  37. 2. specifiy header locations explicitly in extras
  38. Each element in the list of extras must be of the form:
  39. 'namespace=/full/path/to/extra/header.hpp' where 'namespace' is
  40. the namespace in which the definitions should be added.
  41. The output_dir specifies the directory to write the generated sources
  42. to.
  43. """
  44. # dynamically import the parsers
  45. from jinja2 import Environment, FileSystemLoader
  46. import hdr_parser
  47. # parse each of the files and store in a dictionary
  48. # as a separate "namespace"
  49. parser = hdr_parser.CppHeaderParser()
  50. ns = dict((key, []) for key in modules)
  51. path_template = Template('${module}/include/opencv2/${module}.hpp')
  52. for module in modules:
  53. for module_root in module_roots:
  54. # construct a header path from the module root and a path template
  55. header = os.path.join(module_root, path_template.substitute(module=module))
  56. if os.path.isfile(header):
  57. break
  58. else:
  59. raise Exception('no header found for module %s!' % module)
  60. # parse the definitions
  61. ns[module] = parser.parse(header)
  62. for extra in extras:
  63. module = extra.split("=")[0]
  64. header = extra.split("=")[1]
  65. ns[module] = ns[module] + parser.parse(header) if module in ns else parser.parse(header)
  66. # cleanify the parser output
  67. parse_tree = ParseTree()
  68. parse_tree.build(ns)
  69. # setup the template engine
  70. template_dir = os.path.join(os.path.dirname(__file__), 'templates')
  71. jtemplate = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True)
  72. # add the custom filters
  73. jtemplate.filters['formatMatlabConstant'] = formatMatlabConstant
  74. jtemplate.filters['convertibleToInt'] = convertibleToInt
  75. jtemplate.filters['toUpperCamelCase'] = toUpperCamelCase
  76. jtemplate.filters['toLowerCamelCase'] = toLowerCamelCase
  77. jtemplate.filters['toUnderCase'] = toUnderCase
  78. jtemplate.filters['matlabURL'] = matlabURL
  79. jtemplate.filters['stripTags'] = stripTags
  80. jtemplate.filters['filename'] = filename
  81. jtemplate.filters['comment'] = comment
  82. jtemplate.filters['inputs'] = inputs
  83. jtemplate.filters['ninputs'] = ninputs
  84. jtemplate.filters['outputs'] = outputs
  85. jtemplate.filters['noutputs'] = noutputs
  86. jtemplate.filters['qualify'] = qualify
  87. jtemplate.filters['slugify'] = slugify
  88. jtemplate.filters['only'] = only
  89. jtemplate.filters['void'] = void
  90. jtemplate.filters['not'] = flip
  91. # load the templates
  92. tfunction = jtemplate.get_template('template_function_base.cpp')
  93. tclassm = jtemplate.get_template('template_class_base.m')
  94. tclassc = jtemplate.get_template('template_class_base.cpp')
  95. tconst = jtemplate.get_template('template_map_base.m')
  96. # create the build directory
  97. output_source_dir = output_dir+'/src'
  98. output_private_dir = output_source_dir+'/private'
  99. output_class_dir = output_dir+'/+cv'
  100. output_map_dir = output_dir+'/map'
  101. if not os.path.isdir(output_source_dir):
  102. os.makedirs(output_source_dir)
  103. if not os.path.isdir(output_private_dir):
  104. os.makedirs(output_private_dir)
  105. if not os.path.isdir(output_class_dir):
  106. os.makedirs(output_class_dir)
  107. if not os.path.isdir(output_map_dir):
  108. os.makedirs(output_map_dir)
  109. # populate templates
  110. for namespace in parse_tree.namespaces:
  111. # functions
  112. for method in namespace.methods:
  113. populated = tfunction.render(fun=method, time=time, includes=namespace.name)
  114. update_file(output_source_dir+'/'+method.name+'.cpp', populated.encode('utf-8'))
  115. # classes
  116. for clss in namespace.classes:
  117. # cpp converter
  118. populated = tclassc.render(clss=clss, time=time)
  119. update_file(output_private_dir+'/'+clss.name+'Bridge.cpp', populated.encode('utf-8'))
  120. # matlab classdef
  121. populated = tclassm.render(clss=clss, time=time)
  122. update_file(output_class_dir+'/'+clss.name+'.m', populated.encode('utf-8'))
  123. # create a global constants lookup table
  124. const = dict(constants(todict(parse_tree.namespaces)))
  125. populated = tconst.render(constants=const, time=time)
  126. update_file(output_dir+'/cv.m', populated.encode('utf-8'))
  127. if __name__ == "__main__":
  128. """
  129. Usage: python gen_matlab.py
  130. --hdrparser /path/to/hdr_parser/dir
  131. --moduleroot [ /path/to/opencv/modules /path/to/opencv_contrib/modules etc ]
  132. --modules [core imgproc objdetect etc]
  133. --extra namespace=/path/to/extra/header.hpp
  134. --outdir /path/to/output/generated/srcs
  135. gen_matlab.py is the main control script for generating matlab source
  136. files from given set of headers. Internally, gen_matlab:
  137. 1. constructs the headers to parse from the module root and list of modules
  138. 2. parses the headers using CppHeaderParser
  139. 3. refactors the definitions using ParseTree
  140. 4. populates the templates for classes, function, enums from the
  141. definitions
  142. gen_matlab.py requires the following inputs:
  143. --hdrparser the path to the header parser directory
  144. (opencv/modules/python/src2)
  145. --moduleroot (optional) paths to the opencv directories containing the modules
  146. --modules (optional - required if --moduleroot specified) the modules
  147. to produce bindings for. The path to the include directories
  148. as well as the namespaces are constructed from the modules
  149. and the moduleroot
  150. --extra extra headers explicitly defined to parse. This must be in
  151. the format "namepsace=/path/to/extra/header.hpp". For example,
  152. the core module requires the extra header:
  153. "core=/opencv/modules/core/include/opencv2/core/core/base.hpp"
  154. --outdir the output directory to put the generated matlab sources. In
  155. the OpenCV build this is "${CMAKE_CURRENT_BUILD_DIR}/src"
  156. """
  157. # parse the input options
  158. from argparse import ArgumentParser
  159. parser = ArgumentParser()
  160. parser.add_argument('--hdrparser')
  161. parser.add_argument('--moduleroot', nargs='*', default=[], required=False)
  162. parser.add_argument('--modules', nargs='*', default=[], required=False)
  163. parser.add_argument('--extra', nargs='*', default=[], required=False)
  164. parser.add_argument('--outdir')
  165. args = parser.parse_args()
  166. # add the hdr_parser module to the path
  167. sys.path.append(args.hdrparser)
  168. # create the generator
  169. mwg = MatlabWrapperGenerator()
  170. mwg.gen(args.moduleroot, args.modules, args.extra, args.outdir)