gen2.py 39.7 KB
Newer Older
1
#!/usr/bin/env python
2

3 4
from __future__ import print_function
import hdr_parser, sys, re, os
5 6
from string import Template

7 8 9 10 11
if sys.version_info[0] >= 3:
    from io import StringIO
else:
    from cStringIO import StringIO

Andrey Kamaev's avatar
Andrey Kamaev committed
12 13
ignored_arg_types = ["RNG*"]

14 15 16 17
gen_template_check_self = Template("""    $cname* _self_ = NULL;
    if(PyObject_TypeCheck(self, &pyopencv_${name}_Type))
        _self_ = ${amp}((pyopencv_${name}_t*)self)->v${get};
    if (_self_ == NULL)
18
        return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
19 20
""")

21 22 23 24
gen_template_check_self_algo = Template("""    $cname* _self_ = NULL;
    if(PyObject_TypeCheck(self, &pyopencv_${name}_Type))
        _self_ = dynamic_cast<$cname*>(${amp}((pyopencv_${name}_t*)self)->v.get());
    if (_self_ == NULL)
25 26 27
        return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
""")

28
gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new
29
        if(self) """)
30

31 32
gen_template_call_constructor = Template("""self->v.reset(new ${cname}${args})""")

33
gen_template_simple_call_constructor_prelude = Template("""if(self) """)
34

35
gen_template_simple_call_constructor = Template("""new (&(self->v)) ${cname}${args}""")
36 37 38 39 40 41 42

gen_template_parse_args = Template("""const char* keywords[] = { $kw_list, NULL };
    if( PyArg_ParseTupleAndKeywords(args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""")

gen_template_func_body = Template("""$code_decl
    $code_parse
    {
43
        ${code_prelude}ERRWRAP2($code_fcall);
44
        $code_ret;
45 46 47
    }
""")

48
py_major_version = sys.version_info[0]
49 50 51 52 53 54 55 56
if __name__ == "__main__":
    if len(sys.argv) > 3:
        if sys.argv[3] == 'PYTHON3':
            py_major_version = 3
        elif sys.argv[3] == 'PYTHON2':
            py_major_version = 2
        else:
            raise Exception('Incorrect argument: expected PYTHON2 or PYTHON3, received: ' + sys.argv[3])
57 58 59 60 61 62
if py_major_version >= 3:
    head_init_str = "PyVarObject_HEAD_INIT(&PyType_Type, 0)"
else:
    head_init_str = """PyObject_HEAD_INIT(&PyType_Type)
0,"""

63 64 65 66 67 68 69 70 71
gen_template_simple_type_decl = Template("""
struct pyopencv_${name}_t
{
    PyObject_HEAD
    ${cname} v;
};

static PyTypeObject pyopencv_${name}_Type =
{
72
    %s
73 74 75 76 77 78
    MODULESTR".$wname",
    sizeof(pyopencv_${name}_t),
};

static void pyopencv_${name}_dealloc(PyObject* self)
{
79
    ((pyopencv_${name}_t*)self)->v.${cname}::~${sname}();
80 81 82
    PyObject_Del(self);
}

83
template<> PyObject* pyopencv_from(const ${cname}& r)
84 85
{
    pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
86
    new (&m->v) ${cname}(r); //Copy constructor
87 88 89
    return (PyObject*)m;
}

90
template<> bool pyopencv_to(PyObject* src, ${cname}& dst, const char* name)
91
{
92
    if( src == NULL || src == Py_None )
93 94
        return true;
    if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type))
95
    {
96
        failmsg("Expected ${cname} for argument '%%s'", name);
97 98 99 100 101
        return false;
    }
    dst = ((pyopencv_${name}_t*)src)->v;
    return true;
}
102
""" % head_init_str)
103 104


105
gen_template_type_decl = Template("""
106 107 108
struct pyopencv_${name}_t
{
    PyObject_HEAD
109
    Ptr<${cname1}> v;
110 111
};

112 113
static PyTypeObject pyopencv_${name}_Type =
{
114
    %s
115 116 117 118
    MODULESTR".$wname",
    sizeof(pyopencv_${name}_t),
};

119 120
static void pyopencv_${name}_dealloc(PyObject* self)
{
121
    ((pyopencv_${name}_t*)self)->v.release();
122 123
    PyObject_Del(self);
}
124

125
template<> PyObject* pyopencv_from(const Ptr<${cname}>& r)
126 127
{
    pyopencv_${name}_t *m = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
128
    new (&(m->v)) Ptr<$cname1>(); // init Ptr with placement new
129 130 131 132
    m->v = r;
    return (PyObject*)m;
}

133
template<> bool pyopencv_to(PyObject* src, Ptr<${cname}>& dst, const char* name)
134 135 136 137 138
{
    if( src == NULL || src == Py_None )
        return true;
    if(!PyObject_TypeCheck(src, &pyopencv_${name}_Type))
    {
139
        failmsg("Expected ${cname} for argument '%%s'", name);
140 141
        return false;
    }
142
    dst = ((pyopencv_${name}_t*)src)->v.dynamicCast<${cname}>();
143 144 145
    return true;
}

146
""" % head_init_str)
147 148

gen_template_map_type_cvt = Template("""
149
template<> bool pyopencv_to(PyObject* src, ${cname}& dst, const char* name);
150
""")
151

152 153 154 155 156 157 158 159 160 161
gen_template_set_prop_from_map = Template("""
    if( PyMapping_HasKeyString(src, (char*)"$propname") )
    {
        tmp = PyMapping_GetItemString(src, (char*)"$propname");
        ok = tmp && pyopencv_to(tmp, dst.$propname);
        Py_DECREF(tmp);
        if(!ok) return false;
    }""")

gen_template_type_impl = Template("""
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
static PyObject* pyopencv_${name}_repr(PyObject* self)
{
    char str[1000];
    sprintf(str, "<$wname %p>", self);
    return PyString_FromString(str);
}

${getset_code}

static PyGetSetDef pyopencv_${name}_getseters[] =
{${getset_inits}
    {NULL}  /* Sentinel */
};

${methods_code}

static PyMethodDef pyopencv_${name}_methods[] =
{
${methods_inits}
    {NULL,          NULL}
};

static void pyopencv_${name}_specials(void)
{
    pyopencv_${name}_Type.tp_base = ${baseptr};
    pyopencv_${name}_Type.tp_dealloc = pyopencv_${name}_dealloc;
    pyopencv_${name}_Type.tp_repr = pyopencv_${name}_repr;
    pyopencv_${name}_Type.tp_getset = pyopencv_${name}_getseters;
190
    pyopencv_${name}_Type.tp_init = (initproc)${constructor};
191 192 193 194
    pyopencv_${name}_Type.tp_methods = pyopencv_${name}_methods;${extra_specials}
}
""")

195

196 197 198
gen_template_get_prop = Template("""
static PyObject* pyopencv_${name}_get_${member}(pyopencv_${name}_t* p, void *closure)
{
199
    return pyopencv_from(p->v${access}${member});
200 201 202
}
""")

203 204 205
gen_template_get_prop_algo = Template("""
static PyObject* pyopencv_${name}_get_${member}(pyopencv_${name}_t* p, void *closure)
{
206 207 208 209
    $cname* _self_ = dynamic_cast<$cname*>(p->v.get());
    if (_self_ == NULL)
        return failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
    return pyopencv_from(_self_${access}${member});
210 211 212
}
""")

213 214 215 216 217 218 219 220
gen_template_set_prop = Template("""
static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
{
    if (value == NULL)
    {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
        return -1;
    }
221
    return pyopencv_to(value, p->v${access}${member}) ? 0 : -1;
222 223 224
}
""")

225 226 227 228 229 230 231 232
gen_template_set_prop_algo = Template("""
static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value, void *closure)
{
    if (value == NULL)
    {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute");
        return -1;
    }
233 234 235 236 237 238 239
    $cname* _self_ = dynamic_cast<$cname*>(p->v.get());
    if (_self_ == NULL)
    {
        failmsgp("Incorrect type of object (must be '${name}' or its derivative)");
        return -1;
    }
    return pyopencv_to(value, _self_${access}${member}) ? 0 : -1;
240 241 242 243
}
""")


244 245 246 247 248 249 250
gen_template_prop_init = Template("""
    {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, NULL, (char*)"${member}", NULL},""")

gen_template_rw_prop_init = Template("""
    {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, (setter)pyopencv_${name}_set_${member}, (char*)"${member}", NULL},""")

simple_argtype_mapping = {
251
    "bool": ("bool", "b", "0"),
252
    "size_t": ("size_t", "I", "0"),
253 254 255
    "int": ("int", "i", "0"),
    "float": ("float", "f", "0.f"),
    "double": ("double", "d", "0"),
256
    "c_string": ("char*", "s", '(char*)""')
257 258
}

259 260 261
def normalize_class_name(name):
    return re.sub(r"^cv\.", "", name).replace(".", "_")

262 263
class ClassProp(object):
    def __init__(self, decl):
264
        self.tp = decl[0].replace("*", "_ptr")
265 266 267 268 269 270 271 272
        self.name = decl[1]
        self.readonly = True
        if "/RW" in decl[3]:
            self.readonly = False

class ClassInfo(object):
    def __init__(self, name, decl=None):
        self.cname = name.replace(".", "::")
273
        self.name = self.wname = normalize_class_name(name)
274
        self.sname = name[name.rfind('.') + 1:]
275
        self.ismap = False
276
        self.issimple = False
277
        self.isalgorithm = False
278 279 280
        self.methods = {}
        self.props = []
        self.consts = {}
281
        self.base = None
282
        self.constructor = None
283
        customname = False
284

285
        if decl:
286 287
            bases = decl[1].split()[1:]
            if len(bases) > 1:
288
                print("Note: Class %s has more than 1 base class (not supported by Python C extensions)" % (self.name,))
289
                print("      Bases: ", " ".join(bases))
290
                print("      Only the first base class will be used")
291
                #return sys.exit(-1)
292 293 294 295 296 297 298 299
            elif len(bases) == 1:
                self.base = bases[0].strip(",")
                if self.base.startswith("cv::"):
                    self.base = self.base[4:]
                if self.base == "Algorithm":
                    self.isalgorithm = True
                self.base = self.base.replace("::", "_")

300 301 302 303 304 305
            for m in decl[2]:
                if m.startswith("="):
                    self.wname = m[1:]
                    customname = True
                elif m == "/Map":
                    self.ismap = True
306 307
                elif m == "/Simple":
                    self.issimple = True
308
            self.props = [ClassProp(p) for p in decl[3]]
309

310 311
        if not customname and self.wname.startswith("Cv"):
            self.wname = self.wname[2:]
312

313 314
    def gen_map_code(self, codegen):
        all_classes = codegen.classes
315
        code = "static bool pyopencv_to(PyObject* src, %s& dst, const char* name)\n{\n    PyObject* tmp;\n    bool ok;\n" % (self.cname)
316
        code += "".join([gen_template_set_prop_from_map.substitute(propname=p.name,proptype=p.tp) for p in self.props])
317 318
        if self.base:
            code += "\n    return pyopencv_to(src, (%s&)dst, name);\n}\n" % all_classes[self.base].cname
319 320
        else:
            code += "\n    return true;\n}\n"
321
        return code
322

323 324
    def gen_code(self, codegen):
        all_classes = codegen.classes
325
        if self.ismap:
326
            return self.gen_map_code(codegen)
327

328 329
        getset_code = StringIO()
        getset_inits = StringIO()
330

331 332
        sorted_props = [(p.name, p) for p in self.props]
        sorted_props.sort()
333

334 335 336
        access_op = "->"
        if self.issimple:
            access_op = "."
337

338
        for pname, p in sorted_props:
339 340 341 342
            if self.isalgorithm:
                getset_code.write(gen_template_get_prop_algo.substitute(name=self.name, cname=self.cname, member=pname, membertype=p.tp, access=access_op))
            else:
                getset_code.write(gen_template_get_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op))
343
            if p.readonly:
344
                getset_inits.write(gen_template_prop_init.substitute(name=self.name, member=pname))
345
            else:
346 347 348 349
                if self.isalgorithm:
                    getset_code.write(gen_template_set_prop_algo.substitute(name=self.name, cname=self.cname, member=pname, membertype=p.tp, access=access_op))
                else:
                    getset_code.write(gen_template_set_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op))
350
                getset_inits.write(gen_template_rw_prop_init.substitute(name=self.name, member=pname))
351

352 353
        methods_code = StringIO()
        methods_inits = StringIO()
354

355
        sorted_methods = list(self.methods.items())
356
        sorted_methods.sort()
357

358
        if self.constructor is not None:
359
            methods_code.write(self.constructor.gen_code(codegen))
360

361
        for mname, m in sorted_methods:
362
            methods_code.write(m.gen_code(codegen))
363
            methods_inits.write(m.get_tab_entry())
364

365
        baseptr = "NULL"
366 367
        if self.base and self.base in all_classes:
            baseptr = "&pyopencv_" + all_classes[self.base].name + "_Type"
368

369 370 371 372
        constructor_name = "0"
        if self.constructor is not None:
            constructor_name = self.constructor.get_wrapper_name()

373 374 375
        code = gen_template_type_impl.substitute(name=self.name, wname=self.wname, cname=self.cname,
            getset_code=getset_code.getvalue(), getset_inits=getset_inits.getvalue(),
            methods_code=methods_code.getvalue(), methods_inits=methods_inits.getvalue(),
376
            baseptr=baseptr, constructor=constructor_name, extra_specials="")
377

378
        return code
379 380


381 382 383 384 385 386
def handle_ptr(tp):
    if tp.startswith('Ptr_'):
        tp = 'Ptr<' + "::".join(tp.split('_')[1:]) + '>'
    return tp


387 388
class ArgInfo(object):
    def __init__(self, arg_tuple):
389
        self.tp = handle_ptr(arg_tuple[0])
390 391 392 393 394 395 396
        self.name = arg_tuple[1]
        self.defval = arg_tuple[2]
        self.isarray = False
        self.arraylen = 0
        self.arraycvt = None
        self.inputarg = True
        self.outputarg = False
397
        self.returnarg = False
398 399 400 401
        for m in arg_tuple[3]:
            if m == "/O":
                self.inputarg = False
                self.outputarg = True
402
                self.returnarg = True
403 404 405
            elif m == "/IO":
                self.inputarg = True
                self.outputarg = True
406
                self.returnarg = True
407 408 409 410 411 412 413 414
            elif m.startswith("/A"):
                self.isarray = True
                self.arraylen = m[2:].strip()
            elif m.startswith("/CA"):
                self.isarray = True
                self.arraycvt = m[2:].strip()
        self.py_inputarg = False
        self.py_outputarg = False
415

416
    def isbig(self):
417 418
        return self.tp == "Mat" or self.tp == "vector_Mat"\
               or self.tp == "UMat" or self.tp == "vector_UMat" # or self.tp.startswith("vector")
419

420 421 422
    def crepr(self):
        return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg)

423 424

class FuncVariant(object):
425 426 427
    def __init__(self, classname, name, decl, isconstructor):
        self.classname = classname
        self.name = self.wname = name
428
        self.isconstructor = isconstructor
429

lewisjb's avatar
lewisjb committed
430 431 432
        self.docstring = decl[5]

        self.rettype = decl[4] or handle_ptr(decl[1])
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
        if self.rettype == "void":
            self.rettype = ""
        self.args = []
        self.array_counters = {}
        for a in decl[3]:
            ainfo = ArgInfo(a)
            if ainfo.isarray and not ainfo.arraycvt:
                c = ainfo.arraylen
                c_arrlist = self.array_counters.get(c, [])
                if c_arrlist:
                    c_arrlist.append(ainfo.name)
                else:
                    self.array_counters[c] = [ainfo.name]
            self.args.append(ainfo)
        self.init_pyproto()
448

449 450 451 452
    def init_pyproto(self):
        # string representation of argument list, with '[', ']' symbols denoting optional arguments, e.g.
        # "src1, src2[, dst[, mask]]" for cv.add
        argstr = ""
453

454 455 456 457 458 459
        # list of all input arguments of the Python function, with the argument numbers:
        #    [("src1", 0), ("src2", 1), ("dst", 2), ("mask", 3)]
        # we keep an argument number to find the respective argument quickly, because
        # some of the arguments of C function may not present in the Python function (such as array counters)
        # or even go in a different order ("heavy" output parameters of the C function
        # become the first optional input parameters of the Python function, and thus they are placed right after
460
        # non-optional input parameters)
461
        arglist = []
462

463 464 465
        # the list of "heavy" output parameters. Heavy parameters are the parameters
        # that can be expensive to allocate each time, such as vectors and matrices (see isbig).
        outarr_list = []
466

467 468
        # the list of output parameters. Also includes input/output parameters.
        outlist = []
469

470
        firstoptarg = 1000000
471 472 473 474 475
        argno = -1
        for a in self.args:
            argno += 1
            if a.name in self.array_counters:
                continue
Andrey Kamaev's avatar
Andrey Kamaev committed
476 477
            if a.tp in ignored_arg_types:
                continue
478
            if a.returnarg:
479
                outlist.append((a.name, argno))
480
            if (not a.inputarg) and a.isbig():
481
                outarr_list.append((a.name, argno))
482
                continue
483
            if not a.inputarg:
484 485 486 487
                continue
            if not a.defval:
                arglist.append((a.name, argno))
            else:
488
                firstoptarg = min(firstoptarg, len(arglist))
489 490 491 492 493 494
                # if there are some array output parameters before the first default parameter, they
                # are added as optional parameters before the first optional parameter
                if outarr_list:
                    arglist += outarr_list
                    outarr_list = []
                arglist.append((a.name, argno))
495

496
        if outarr_list:
497
            firstoptarg = min(firstoptarg, len(arglist))
498
            arglist += outarr_list
499
        firstoptarg = min(firstoptarg, len(arglist))
500

501 502 503 504 505 506 507 508 509 510
        noptargs = len(arglist) - firstoptarg
        argnamelist = [aname for aname, argno in arglist]
        argstr = ", ".join(argnamelist[:firstoptarg])
        argstr = "[, ".join([argstr] + argnamelist[firstoptarg:])
        argstr += "]" * noptargs
        if self.rettype:
            outlist = [("retval", -1)] + outlist
        elif self.isconstructor:
            assert outlist == []
            outlist = [("self", -1)]
511 512 513 514 515 516
        if self.isconstructor:
            classname = self.classname
            if classname.startswith("Cv"):
                classname=classname[2:]
            outstr = "<%s object>" % (classname,)
        elif outlist:
517 518 519
            outstr = ", ".join([o[0] for o in outlist])
        else:
            outstr = "None"
520

521 522
        self.py_arg_str = argstr
        self.py_return_str = outstr
lewisjb's avatar
lewisjb committed
523
        self.py_prototype = "%s(%s) -> %s" % (self.wname, argstr, outstr)
524 525 526 527 528 529 530 531 532 533 534
        self.py_noptargs = noptargs
        self.py_arglist = arglist
        for aname, argno in arglist:
            self.args[argno].py_inputarg = True
        for aname, argno in outlist:
            if argno >= 0:
                self.args[argno].py_outputarg = True
        self.py_outlist = outlist


class FuncInfo(object):
535
    def __init__(self, classname, name, cname, isconstructor, namespace, isclassmethod):
536 537 538 539
        self.classname = classname
        self.name = name
        self.cname = cname
        self.isconstructor = isconstructor
540
        self.namespace = namespace
541
        self.isclassmethod = isclassmethod
542
        self.variants = []
543

544
    def add_variant(self, decl):
545
        self.variants.append(FuncVariant(self.classname, self.name, decl, self.isconstructor))
546

547
    def get_wrapper_name(self):
548
        name = self.name
549
        if self.classname:
550 551 552
            classname = self.classname + "_"
            if "[" in name:
                name = "getelem"
553
        else:
554
            classname = ""
555 556 557 558

        if self.isclassmethod:
            name += "_cls"

559
        return "pyopencv_" + self.namespace.replace('.','_') + '_' + classname + name
560

561
    def get_wrapper_prototype(self, codegen):
562
        full_fname = self.get_wrapper_name()
563 564
        if self.isconstructor:
            return "static int {fn_name}(pyopencv_{type_name}_t* self, PyObject* args, PyObject* kw)".format(
565
                    fn_name=full_fname, type_name=codegen.classes[self.classname].name)
566 567

        if self.classname:
568 569 570 571
            self_arg = "self"
        else:
            self_arg = ""
        return "static PyObject* %s(PyObject* %s, PyObject* args, PyObject* kw)" % (full_fname, self_arg)
572

573
    def get_tab_entry(self):
lewisjb's avatar
lewisjb committed
574
        prototype_list = []
575
        docstring_list = []
lewisjb's avatar
lewisjb committed
576

577 578
        have_empty_constructor = False
        for v in self.variants:
lewisjb's avatar
lewisjb committed
579
            s = v.py_prototype
580 581
            if (not v.py_arglist) and self.isconstructor:
                have_empty_constructor = True
lewisjb's avatar
lewisjb committed
582 583 584 585
            if s not in prototype_list:
                prototype_list.append(s)
                docstring_list.append(v.docstring)

586 587 588 589 590
        # if there are just 2 constructors: default one and some other,
        # we simplify the notation.
        # Instead of ClassName(args ...) -> object or ClassName() -> object
        # we write ClassName([args ...]) -> object
        if have_empty_constructor and len(self.variants) == 2:
591
            idx = self.variants[1].py_arglist != []
lewisjb's avatar
lewisjb committed
592
            s = self.variants[idx].py_prototype
593 594
            p1 = s.find("(")
            p2 = s.rfind(")")
lewisjb's avatar
lewisjb committed
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
            prototype_list = [s[:p1+1] + "[" + s[p1+1:p2] + "]" + s[p2:]]

        # The final docstring will be: Each prototype, followed by
        # their relevant doxygen comment
        full_docstring = ""
        for prototype, body in zip(prototype_list, docstring_list):
            full_docstring += Template("$prototype\n$docstring\n\n\n\n").substitute(
                prototype=prototype,
                docstring='\n'.join(
                    ['.   ' + line
                     for line in body.split('\n')]
                )
            )

        # Escape backslashes, newlines, and double quotes
        full_docstring = full_docstring.strip().replace("\\", "\\\\").replace('\n', '\\n').replace("\"", "\\\"")
        # Convert unicode chars to xml representation, but keep as string instead of bytes
        full_docstring = full_docstring.encode('ascii', errors='xmlcharrefreplace').decode()
613

614 615 616 617 618
        flags = ["METH_VARARGS", "METH_KEYWORDS"]
        if self.isclassmethod:
            flags.append("METH_CLASS")

        return Template('    {"$py_funcname", (PyCFunction)$wrap_funcname, $flags, "$py_docstring"},\n'
619
                        ).substitute(py_funcname = self.variants[0].wname, wrap_funcname=self.get_wrapper_name(),
620
                                     flags = " | ".join(flags), py_docstring = full_docstring)
621

622 623 624
    def gen_code(self, codegen):
        all_classes = codegen.classes
        proto = self.get_wrapper_prototype(codegen)
625
        code = "%s\n{\n" % (proto,)
626
        code += "    using namespace %s;\n\n" % self.namespace.replace('.', '::')
627 628 629 630 631 632 633 634 635

        selfinfo = ClassInfo("")
        ismethod = self.classname != "" and not self.isconstructor
        # full name is needed for error diagnostic in PyArg_ParseTupleAndKeywords
        fullname = self.name

        if self.classname:
            selfinfo = all_classes[self.classname]
            if not self.isconstructor:
636
                amp = "&" if selfinfo.issimple else ""
637 638 639
                if self.isclassmethod:
                    pass
                elif selfinfo.isalgorithm:
640 641
                    code += gen_template_check_self_algo.substitute(name=selfinfo.name, cname=selfinfo.cname, amp=amp)
                else:
642 643
                    get = "" if selfinfo.issimple else ".get()"
                    code += gen_template_check_self.substitute(name=selfinfo.name, cname=selfinfo.cname, amp=amp, get=get)
644 645 646 647 648 649 650 651 652
                fullname = selfinfo.wname + "." + fullname

        all_code_variants = []
        declno = -1
        for v in self.variants:
            code_decl = ""
            code_ret = ""
            code_cvt_list = []

653
            code_args = "("
654 655 656 657 658 659 660 661
            all_cargs = []
            parse_arglist = []

            # declare all the C function arguments,
            # add necessary conversions from Python objects to code_cvt_list,
            # form the function/method call,
            # for the list of type mappings
            for a in v.args:
Andrey Kamaev's avatar
Andrey Kamaev committed
662 663 664 665 666
                if a.tp in ignored_arg_types:
                    defval = a.defval
                    if not defval and a.tp.endswith("*"):
                        defval = 0
                    assert defval
667 668 669
                    if not code_args.endswith("("):
                        code_args += ", "
                    code_args += defval
Andrey Kamaev's avatar
Andrey Kamaev committed
670 671
                    all_cargs.append([[None, ""], ""])
                    continue
672 673 674 675 676 677 678 679 680 681
                tp1 = tp = a.tp
                amp = ""
                defval0 = ""
                if tp.endswith("*"):
                    tp = tp1 = tp[:-1]
                    amp = "&"
                    if tp.endswith("*"):
                        defval0 = "0"
                        tp1 = tp.replace("*", "_ptr")
                if tp1.endswith("*"):
682
                    print("Error: type with star: a.tp=%s, tp=%s, tp1=%s" % (a.tp, tp, tp1))
683 684
                    sys.exit(-1)

685
                amapping = simple_argtype_mapping.get(tp, (tp, "O", defval0))
686
                parse_name = a.name
687 688 689
                if a.py_inputarg:
                    if amapping[1] == "O":
                        code_decl += "    PyObject* pyobj_%s = NULL;\n" % (a.name,)
690
                        parse_name = "pyobj_" + a.name
691 692 693 694
                        if a.tp == 'char':
                            code_cvt_list.append("convert_to_char(pyobj_%s, &%s, %s)"% (a.name, a.name, a.crepr()))
                        else:
                            code_cvt_list.append("pyopencv_to(pyobj_%s, %s, %s)" % (a.name, a.name, a.crepr()))
695

696
                all_cargs.append([amapping, parse_name])
697

698 699
                defval = a.defval
                if not defval:
700
                    defval = amapping[2]
701 702 703 704
                else:
                    if "UMat" in tp:
                        if "Mat" in defval and "UMat" not in defval:
                            defval = defval.replace("Mat", "UMat")
705 706 707
                # "tp arg = tp();" is equivalent to "tp arg;" in the case of complex types
                if defval == tp + "()" and amapping[1] == "O":
                    defval = ""
708 709
                if a.outputarg and not a.inputarg:
                    defval = ""
710 711 712 713 714
                if defval:
                    code_decl += "    %s %s=%s;\n" % (amapping[0], a.name, defval)
                else:
                    code_decl += "    %s %s;\n" % (amapping[0], a.name)

715 716 717 718 719 720 721 722 723 724 725 726 727
                if not code_args.endswith("("):
                    code_args += ", "
                code_args += amp + a.name

            code_args += ")"

            if self.isconstructor:
                if selfinfo.issimple:
                    templ_prelude = gen_template_simple_call_constructor_prelude
                    templ = gen_template_simple_call_constructor
                else:
                    templ_prelude = gen_template_call_constructor_prelude
                    templ = gen_template_call_constructor
728

729 730 731 732 733 734 735 736
                code_prelude = templ_prelude.substitute(name=selfinfo.name, cname=selfinfo.cname)
                code_fcall = templ.substitute(name=selfinfo.name, cname=selfinfo.cname, args=code_args)
            else:
                code_prelude = ""
                code_fcall = ""
                if v.rettype:
                    code_decl += "    " + v.rettype + " retval;\n"
                    code_fcall += "retval = "
737
                if ismethod and not self.isclassmethod:
738 739 740 741
                    code_fcall += "_self_->" + self.cname
                else:
                    code_fcall += self.cname
                code_fcall += code_args
742 743 744 745 746 747 748 749 750 751 752

            if code_cvt_list:
                code_cvt_list = [""] + code_cvt_list

            # add info about return value, if any, to all_cargs. if there non-void return value,
            # it is encoded in v.py_outlist as ("retval", -1) pair.
            # As [-1] in Python accesses the last element of a list, we automatically handle the return value by
            # adding the necessary info to the end of all_cargs list.
            if v.rettype:
                tp = v.rettype
                tp1 = tp.replace("*", "_ptr")
753
                amapping = simple_argtype_mapping.get(tp, (tp, "O", "0"))
754 755
                all_cargs.append(amapping)

756
            if v.args and v.py_arglist:
757
                # form the format spec for PyArg_ParseTupleAndKeywords
758
                fmtspec = "".join([all_cargs[argno][0][1] for aname, argno in v.py_arglist])
759 760 761 762 763 764 765 766 767 768 769
                if v.py_noptargs > 0:
                    fmtspec = fmtspec[:-v.py_noptargs] + "|" + fmtspec[-v.py_noptargs:]
                fmtspec += ":" + fullname

                # form the argument parse code that:
                #   - declares the list of keyword parameters
                #   - calls PyArg_ParseTupleAndKeywords
                #   - converts complex arguments from PyObject's to native OpenCV types
                code_parse = gen_template_parse_args.substitute(
                    kw_list = ", ".join(['"' + aname + '"' for aname, argno in v.py_arglist]),
                    fmtspec = fmtspec,
770
                    parse_arglist = ", ".join(["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]),
771 772
                    code_cvt = " &&\n        ".join(code_cvt_list))
            else:
773
                code_parse = "if(PyObject_Size(args) == 0 && (kw == NULL || PyObject_Size(kw) == 0))"
774 775

            if len(v.py_outlist) == 0:
776
                code_ret = "Py_RETURN_NONE"
777 778
            elif len(v.py_outlist) == 1:
                if self.isconstructor:
779
                    code_ret = "return 0"
780 781
                else:
                    aname, argno = v.py_outlist[0]
782
                    code_ret = "return pyopencv_from(%s)" % (aname,)
783 784 785 786 787
            else:
                # ther is more than 1 return parameter; form the tuple out of them
                fmtspec = "N"*len(v.py_outlist)
                backcvt_arg_list = []
                for aname, argno in v.py_outlist:
788
                    amapping = all_cargs[argno][0]
789
                    backcvt_arg_list.append("%s(%s)" % (amapping[2], aname))
790
                code_ret = "return Py_BuildValue(\"(%s)\", %s)" % \
791
                    (fmtspec, ", ".join(["pyopencv_from(" + aname + ")" for aname, argno in v.py_outlist]))
792 793

            all_code_variants.append(gen_template_func_body.substitute(code_decl=code_decl,
794
                code_parse=code_parse, code_prelude=code_prelude, code_fcall=code_fcall, code_ret=code_ret))
795 796 797 798 799 800 801

        if len(all_code_variants)==1:
            # if the function/method has only 1 signature, then just put it
            code += all_code_variants[0]
        else:
            # try to execute each signature
            code += "    PyErr_Clear();\n\n".join(["    {\n" + v + "    }\n" for v in all_code_variants])
802 803 804 805 806

        def_ret = "NULL"
        if self.isconstructor:
            def_ret = "-1"
        code += "\n    return %s;\n}\n\n" % def_ret
807 808 809 810 811 812 813 814 815 816 817 818 819 820

        cname = self.cname
        if self.classname:
            classinfo = all_classes[self.classname]
            cname = classinfo.cname + '::' + cname
        py_signatures = codegen.py_signatures.setdefault(cname, [])
        for v in self.variants:
            s = dict(name=v.name, arg=v.py_arg_str, ret=v.py_return_str)
            for old in py_signatures:
                if s == old:
                    break
            else:
                py_signatures.append(s)

821 822 823
        return code


824 825 826 827 828 829
class Namespace(object):
    def __init__(self):
        self.funcs = {}
        self.consts = {}


830 831 832
class PythonWrapperGenerator(object):
    def __init__(self):
        self.clear()
833

834 835
    def clear(self):
        self.classes = {}
836
        self.namespaces = {}
837
        self.consts = {}
838
        self.code_include = StringIO()
839 840 841
        self.code_types = StringIO()
        self.code_funcs = StringIO()
        self.code_type_reg = StringIO()
842
        self.code_ns_reg = StringIO()
843
        self.code_type_publish = StringIO()
844
        self.py_signatures = dict()
845
        self.class_idx = 0
846 847 848

    def add_class(self, stype, name, decl):
        classinfo = ClassInfo(name, decl)
849 850
        classinfo.decl_idx = self.class_idx
        self.class_idx += 1
851

852 853 854
        if classinfo.name in self.classes:
            print("Generator error: class %s (cname=%s) already exists" \
                % (classinfo.name, classinfo.cname))
855
            sys.exit(-1)
856
        self.classes[classinfo.name] = classinfo
857

858 859 860 861 862 863 864
    def split_decl_name(self, name):
        chunks = name.split('.')
        namespace = chunks[:-1]
        classes = []
        while namespace and '.'.join(namespace) not in self.parser.namespaces:
            classes.insert(0, namespace.pop())
        return namespace, classes, chunks[-1]
865

866 867 868 869 870 871 872 873

    def add_const(self, name, decl):
        cname = name.replace('.','::')
        namespace, classes, name = self.split_decl_name(name)
        namespace = '.'.join(namespace)
        name = '_'.join(classes+[name])
        ns = self.namespaces.setdefault(namespace, Namespace())
        if name in ns.consts:
874
            print("Generator error: constant %s (cname=%s) already exists" \
875
                % (name, cname))
876
            sys.exit(-1)
877
        ns.consts[name] = cname
878 879

    def add_func(self, decl):
880 881 882
        namespace, classes, barename = self.split_decl_name(decl[0])
        cname = "::".join(namespace+classes+[barename])
        name = barename
883 884 885 886 887 888 889
        classname = ''
        bareclassname = ''
        if classes:
            classname = normalize_class_name('.'.join(namespace+classes))
            bareclassname = classes[-1]
        namespace = '.'.join(namespace)

890
        isconstructor = name == bareclassname
891 892 893 894 895 896
        isclassmethod = False
        for m in decl[2]:
            if m == "/S":
                isclassmethod = True
            elif m.startswith("="):
                name = m[1:]
897
        if isconstructor:
898
            name = "_".join(classes[:-1]+[name])
899

900 901
        if isclassmethod:
            # Add it as a method to the class
902
            func_map = self.classes[classname].methods
903 904 905 906 907
            func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, isclassmethod))
            func.add_variant(decl)

            # Add it as global function
            g_name = "_".join(classes+[name])
908
            func_map = self.namespaces.setdefault(namespace, Namespace()).funcs
909 910 911 912 913 914 915 916 917 918 919
            func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace, False))
            func.add_variant(decl)
        else:
            if classname and not isconstructor:
                cname = barename
                func_map = self.classes[classname].methods
            else:
                func_map = self.namespaces.setdefault(namespace, Namespace()).funcs

            func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, isclassmethod))
            func.add_variant(decl)
920

921 922
        if classname and isconstructor:
            self.classes[classname].constructor = func
923 924


925
    def gen_namespace(self, ns_name):
926
        ns = self.namespaces[ns_name]
927
        wname = normalize_class_name(ns_name)
928

929
        self.code_ns_reg.write('static PyMethodDef methods_%s[] = {\n'%wname)
930
        for name, func in sorted(ns.funcs.items()):
931 932
            if func.isconstructor:
                continue
933 934 935
            self.code_ns_reg.write(func.get_tab_entry())
        self.code_ns_reg.write('    {NULL, NULL}\n};\n\n')

936 937
        self.code_ns_reg.write('static ConstDef consts_%s[] = {\n'%wname)
        for name, cname in sorted(ns.consts.items()):
Alexander Mordvintsev's avatar
Alexander Mordvintsev committed
938 939 940 941 942
            self.code_ns_reg.write('    {"%s", %s},\n'%(name, cname))
            compat_name = re.sub(r"([a-z])([A-Z])", r"\1_\2", name).upper()
            if name != compat_name:
                self.code_ns_reg.write('    {"%s", %s},\n'%(compat_name, cname))
        self.code_ns_reg.write('    {NULL, 0}\n};\n\n')
943

944 945
    def gen_namespaces_reg(self):
        self.code_ns_reg.write('static void init_submodules(PyObject * root) \n{\n')
946
        for ns_name in sorted(self.namespaces):
947 948 949
            if ns_name.split('.')[0] == 'cv':
                wname = normalize_class_name(ns_name)
                self.code_ns_reg.write('  init_submodule(root, MODULESTR"%s", methods_%s, consts_%s);\n' % (ns_name[2:], wname, wname))
950 951 952
        self.code_ns_reg.write('};\n')


953
    def save(self, path, name, buf):
954 955 956 957 958 959 960
        with open(path + "/" + name, "wt") as f:
            f.write(buf.getvalue())

    def save_json(self, path, name, value):
        import json
        with open(path + "/" + name, "wt") as f:
            json.dump(value, f)
961

962
    def gen(self, srcfiles, output_path):
963
        self.clear()
964
        self.parser = hdr_parser.CppHeaderParser(generate_umat_decls=True)
965

966 967
        # step 1: scan the headers and build more descriptive maps of classes, consts, functions
        for hdr in srcfiles:
968
            decls = self.parser.parse(hdr)
969 970
            if len(decls) == 0:
                continue
971
            self.code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) )
972 973 974 975 976 977 978 979 980 981 982 983 984 985
            for decl in decls:
                name = decl[0]
                if name.startswith("struct") or name.startswith("class"):
                    # class/struct
                    p = name.find(" ")
                    stype = name[:p]
                    name = name[p+1:].strip()
                    self.add_class(stype, name, decl)
                elif name.startswith("const"):
                    # constant
                    self.add_const(name.replace("const ", "").strip(), decl)
                else:
                    # function
                    self.add_func(decl)
986

987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
        # step 1.5 check if all base classes exist
        for name, classinfo in self.classes.items():
            if classinfo.base:
                chunks = classinfo.base.split('_')
                base = '_'.join(chunks)
                while base not in self.classes and len(chunks)>1:
                    del chunks[-2]
                    base = '_'.join(chunks)
                if base not in self.classes:
                    print("Generator error: unable to resolve base %s for %s"
                        % (classinfo.base, classinfo.name))
                    sys.exit(-1)
                classinfo.base = base
                classinfo.isalgorithm |= self.classes[base].isalgorithm
                self.classes[name] = classinfo

1003
        # step 2: generate code for the classes and their methods
1004
        classlist = list(self.classes.items())
1005
        classlist.sort()
1006 1007 1008 1009 1010 1011 1012 1013
        for name, classinfo in classlist:
            if classinfo.ismap:
                self.code_types.write(gen_template_map_type_cvt.substitute(name=name, cname=classinfo.cname))
            else:
                if classinfo.issimple:
                    templ = gen_template_simple_type_decl
                else:
                    templ = gen_template_type_decl
1014
                self.code_types.write(templ.substitute(name=name, wname=classinfo.wname, cname=classinfo.cname, sname=classinfo.sname,
1015
                                      cname1=("cv::Algorithm" if classinfo.isalgorithm else classinfo.cname)))
1016

1017 1018 1019 1020
        # register classes in the same order as they have been declared.
        # this way, base classes will be registered in Python before their derivatives.
        classlist1 = [(classinfo.decl_idx, name, classinfo) for name, classinfo in classlist]
        classlist1.sort()
1021

1022
        for decl_idx, name, classinfo in classlist1:
1023
            code = classinfo.gen_code(self)
1024 1025 1026
            self.code_types.write(code)
            if not classinfo.ismap:
                self.code_type_reg.write("MKTYPE2(%s);\n" % (classinfo.name,) )
1027
                self.code_type_publish.write("PUBLISH_OBJECT(\"{name}\", pyopencv_{name}_Type);\n".format(name=classinfo.name))
1028

1029
        # step 3: generate the code for all the global functions
1030
        for ns_name, ns in sorted(self.namespaces.items()):
1031 1032
            if ns_name.split('.')[0] != 'cv':
                continue
1033
            for name, func in sorted(ns.funcs.items()):
1034 1035
                if func.isconstructor:
                    continue
1036
                code = func.gen_code(self)
1037
                self.code_funcs.write(code)
1038
            self.gen_namespace(ns_name)
1039
        self.gen_namespaces_reg()
1040

1041
        # step 4: generate the code for constants
1042
        constlist = list(self.consts.items())
1043 1044 1045
        constlist.sort()
        for name, constinfo in constlist:
            self.gen_const_reg(constinfo)
1046

1047
        # That's it. Now save all the files
1048
        self.save(output_path, "pyopencv_generated_include.h", self.code_include)
1049 1050 1051
        self.save(output_path, "pyopencv_generated_funcs.h", self.code_funcs)
        self.save(output_path, "pyopencv_generated_types.h", self.code_types)
        self.save(output_path, "pyopencv_generated_type_reg.h", self.code_type_reg)
1052
        self.save(output_path, "pyopencv_generated_ns_reg.h", self.code_ns_reg)
1053
        self.save(output_path, "pyopencv_generated_type_publish.h", self.code_type_publish)
1054
        self.save_json(output_path, "pyopencv_signatures.json", self.py_signatures)
1055 1056

if __name__ == "__main__":
1057 1058
    srcfiles = hdr_parser.opencv_hdr_list
    dstdir = "/Users/vp/tmp"
1059
    if len(sys.argv) > 1:
1060
        dstdir = sys.argv[1]
1061
    if len(sys.argv) > 2:
1062
        srcfiles = [f.strip() for f in open(sys.argv[2], 'r').readlines()]
1063 1064
    generator = PythonWrapperGenerator()
    generator.gen(srcfiles, dstdir)