1 /************************************************************************/
2 /* */
3 /* Copyright 2009 by Ullrich Koethe */
4 /* */
5 /* This file is part of the VIGRA computer vision library. */
6 /* The VIGRA Website is */
7 /* http://hci.iwr.uni-heidelberg.de/vigra/ */
8 /* Please direct questions, bug reports, and contributions to */
9 /* ullrich.koethe@iwr.uni-heidelberg.de or */
10 /* vigra@informatik.uni-hamburg.de */
11 /* */
12 /* Permission is hereby granted, free of charge, to any person */
13 /* obtaining a copy of this software and associated documentation */
14 /* files (the "Software"), to deal in the Software without */
15 /* restriction, including without limitation the rights to use, */
16 /* copy, modify, merge, publish, distribute, sublicense, and/or */
17 /* sell copies of the Software, and to permit persons to whom the */
18 /* Software is furnished to do so, subject to the following */
19 /* conditions: */
20 /* */
21 /* The above copyright notice and this permission notice shall be */
22 /* included in all copies or substantial portions of the */
23 /* Software. */
24 /* */
25 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
26 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
27 /* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
28 /* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
29 /* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
30 /* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
31 /* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
32 /* OTHER DEALINGS IN THE SOFTWARE. */
33 /* */
34 /************************************************************************/
35
36 #define PY_ARRAY_UNIQUE_SYMBOL vigranumpyanalysis_PyArray_API
37 #define NO_IMPORT_ARRAY
38
39 #include <vigra/numpy_array.hxx>
40 #include <vigra/numpy_array_converters.hxx>
41 #include <vigra/edgedetection.hxx>
42 #include <vigra/multi_convolution.hxx>
43 #include <vigra/tensorutilities.hxx>
44
45 namespace python = boost::python;
46
47 namespace vigra
48 {
49
Edgel__getitem__(Edgel const & e,unsigned int i)50 double Edgel__getitem__(Edgel const & e, unsigned int i)
51 {
52 if(i > 1)
53 {
54 PyErr_SetString(PyExc_IndexError,
55 "Edgel.__getitem__(): index out of bounds.");
56 python::throw_error_already_set();
57 }
58 return i == 0 ? e.x : e.y;
59 }
60
Edgel__setitem__(Edgel & e,unsigned int i,double v)61 void Edgel__setitem__(Edgel & e, unsigned int i, double v)
62 {
63 if(i > 1)
64 {
65 PyErr_SetString(PyExc_IndexError,
66 "Edgel.__setitem__(): index out of bounds.");
67 python::throw_error_already_set();
68 }
69 if(i==0)
70 e.x = Edgel::value_type(v);
71 else
72 e.y = Edgel::value_type(v);
73 }
74
Edgel__len__(Edgel const &)75 unsigned int Edgel__len__(Edgel const &)
76 {
77 return 2;
78 }
79
Edgel__repr__(Edgel const & e)80 PyObject * Edgel__repr__(Edgel const & e)
81 {
82 std::stringstream s;
83 s << std::setprecision(14)
84 << "Edgel(x=" << e.x << ", y=" << e.y << ", strength=" << e.strength << ", angle=" << e.orientation << ")";
85 return pythonFromData(s.str().c_str());
86 }
87
88 template < class PixelType>
89 python::list
pythonFindEdgelsFromGrad(NumpyArray<2,TinyVector<PixelType,2>> grad,double threshold)90 pythonFindEdgelsFromGrad(NumpyArray<2, TinyVector<PixelType, 2> > grad,
91 double threshold)
92 {
93 std::vector<Edgel> edgels;
94 {
95 PyAllowThreads _pythread;
96 cannyEdgelList(srcImageRange(grad), edgels);
97 }
98
99 python::list pyEdgels;
100 for(unsigned int i = 0; i < edgels.size(); ++i)
101 {
102 if(edgels[i].strength >= threshold)
103 pyEdgels.append(edgels[i]);
104 }
105 return pyEdgels;
106 }
107
108 template < class PixelType>
109 python::list
pythonFindEdgels(NumpyArray<2,Singleband<PixelType>> image,double scale,double threshold)110 pythonFindEdgels(NumpyArray<2, Singleband<PixelType> > image,
111 double scale, double threshold)
112 {
113 std::vector<Edgel> edgels;
114 {
115 PyAllowThreads _pythread;
116 cannyEdgelList(srcImageRange(image), edgels, scale);
117 }
118
119 python::list pyEdgels;
120 for(unsigned int i = 0; i < edgels.size(); ++i)
121 {
122 if(edgels[i].strength >= threshold)
123 pyEdgels.append(edgels[i]);
124 }
125 return pyEdgels;
126 }
127
128 template < class PixelType>
129 python::list
pythonFindEdgels3x3FromGrad(NumpyArray<2,TinyVector<PixelType,2>> grad,double threshold)130 pythonFindEdgels3x3FromGrad(NumpyArray<2, TinyVector<PixelType, 2> > grad,
131 double threshold)
132 {
133 std::vector<Edgel> edgels;
134 {
135 PyAllowThreads _pythread;
136 cannyEdgelList3x3(srcImageRange(grad), edgels);
137 }
138
139 python::list pyEdgels;
140 for(unsigned int i = 0; i < edgels.size(); ++i)
141 {
142 if(edgels[i].strength >= threshold)
143 pyEdgels.append(edgels[i]);
144 }
145 return pyEdgels;
146 }
147
148 template < class PixelType>
149 python::list
pythonFindEdgels3x3(NumpyArray<2,Singleband<PixelType>> image,double scale,double threshold)150 pythonFindEdgels3x3(NumpyArray<2, Singleband<PixelType> > image,
151 double scale, double threshold)
152 {
153 std::vector<Edgel> edgels;
154 {
155 PyAllowThreads _pythread;
156 cannyEdgelList3x3(srcImageRange(image), edgels, scale);
157 }
158 python::list pyEdgels;
159 for(unsigned int i = 0; i < edgels.size(); ++i)
160 {
161 if(edgels[i].strength >= threshold)
162 pyEdgels.append(edgels[i]);
163 }
164 return pyEdgels;
165 }
166
167 template < class SrcPixelType, typename DestPixelType >
168 NumpyAnyArray
pythonCannyEdgeImage(NumpyArray<2,Singleband<SrcPixelType>> image,double scale,double threshold,DestPixelType edgeMarker,NumpyArray<2,Singleband<DestPixelType>> res=python::object ())169 pythonCannyEdgeImage(NumpyArray<2, Singleband<SrcPixelType> > image,
170 double scale, double threshold, DestPixelType edgeMarker,
171 NumpyArray<2, Singleband<DestPixelType> > res = python::object())
172 {
173 std::string description("Canny edges, scale=");
174 description += asString(scale) + ", threshold=" + asString(threshold);
175
176 res.reshapeIfEmpty(image.taggedShape().setChannelDescription(description),
177 "cannyEdgeImage(): Output array has wrong shape.");
178
179 {
180 PyAllowThreads _pythread;
181 cannyEdgeImage(srcImageRange(image), destImage(res),
182 scale, threshold, edgeMarker);
183 }
184
185 return res;
186 }
187
188 template < class SrcPixelType, typename DestPixelType >
189 NumpyAnyArray
pythonCannyEdgeImageColor(NumpyArray<2,RGBValue<SrcPixelType>> image,double scale,double threshold,DestPixelType edgeMarker,NumpyArray<2,Singleband<DestPixelType>> res=python::object ())190 pythonCannyEdgeImageColor(NumpyArray<2, RGBValue<SrcPixelType> > image,
191 double scale, double threshold, DestPixelType edgeMarker,
192 NumpyArray<2, Singleband<DestPixelType> > res = python::object())
193 {
194 std::string description("Canny edges, scale=");
195 description += asString(scale) + ", threshold=" + asString(threshold);
196
197 res.reshapeIfEmpty(image.taggedShape().setChannelDescription(description),
198 "cannyEdgeImage(): Output array has wrong shape.");
199
200 {
201 PyAllowThreads _pythread;
202 MultiArray<2, TinyVector<float, 2>> gradient(image.shape());
203 MultiArray<2, TinyVector<float, 3>> tmp(image.shape()),
204 gradient_tensor(image.shape());
205 for(int k=0; k<3; ++k)
206 {
207 gaussianGradientMultiArray(image.bindElementChannel(k), gradient, scale);
208 vectorToTensor(gradient, tmp);
209 gradient_tensor += tmp;
210 }
211 tensorEigenRepresentation(gradient_tensor, tmp);
212 transformMultiArray(tmp, gradient, [](TinyVector<float, 3> const & v) {
213 return TinyVector<float, 2>(std::cos(v[2])*sqrt(v[0]), std::sin(v[2])*sqrt(v[0]));
214 });
215 cannyEdgeImageFromGradWithThinning(gradient, res,
216 threshold, edgeMarker, false);
217 }
218
219 return res;
220 }
221
222 template < class SrcPixelType, typename DestPixelType >
223 NumpyAnyArray
pythonCannyEdgeImageWithThinning(NumpyArray<2,Singleband<SrcPixelType>> image,double scale,double threshold,DestPixelType edgeMarker,bool addBorder=true,NumpyArray<2,Singleband<DestPixelType>> res=python::object ())224 pythonCannyEdgeImageWithThinning(NumpyArray<2, Singleband<SrcPixelType> > image,
225 double scale, double threshold,
226 DestPixelType edgeMarker, bool addBorder = true,
227 NumpyArray<2, Singleband<DestPixelType> > res = python::object())
228 {
229 std::string description("Canny edges with thinning, scale=");
230 description += asString(scale) + ", threshold=" + asString(threshold);
231
232 res.reshapeIfEmpty(image.taggedShape().setChannelDescription(description),
233 "cannyEdgeImageWithThinning(): Output array has wrong shape.");
234
235 {
236 PyAllowThreads _pythread;
237 cannyEdgeImageWithThinning(srcImageRange(image), destImage(res),
238 scale, threshold, edgeMarker, addBorder);
239 }
240
241 return res;
242 }
243
244 template < class SrcPixelType, typename DestPixelType >
245 NumpyAnyArray
pythonShenCastanEdgeImage(NumpyArray<2,Singleband<SrcPixelType>> image,double scale,double threshold,DestPixelType edgeMarker,NumpyArray<2,Singleband<DestPixelType>> res=python::object ())246 pythonShenCastanEdgeImage(NumpyArray<2, Singleband<SrcPixelType> > image,
247 double scale, double threshold, DestPixelType edgeMarker,
248 NumpyArray<2, Singleband<DestPixelType> > res = python::object())
249 {
250 std::string description("Shen/Castan edges, scale=");
251 description += asString(scale) + ", threshold=" + asString(threshold);
252
253 res.reshapeIfEmpty(image.taggedShape().setChannelDescription(description),
254 "shenCastanEdgeImage(): Output array has wrong shape.");
255
256 {
257 PyAllowThreads _pythread;
258 differenceOfExponentialEdgeImage(srcImageRange(image), destImage(res),
259 scale, threshold, edgeMarker);
260 }
261
262 return res;
263 }
264
265 template < class SrcPixelType, typename DestPixelType >
266 NumpyAnyArray
pythonShenCastanCrackEdgeImage(NumpyArray<2,Singleband<SrcPixelType>> image,double scale,double threshold,DestPixelType edgeMarker,NumpyArray<2,Singleband<DestPixelType>> res=python::object ())267 pythonShenCastanCrackEdgeImage(NumpyArray<2, Singleband<SrcPixelType> > image,
268 double scale, double threshold, DestPixelType edgeMarker,
269 NumpyArray<2, Singleband<DestPixelType> > res = python::object())
270 {
271 std::string description("Shen/Castan crack edges, scale=");
272 description += asString(scale) + ", threshold=" + asString(threshold);
273
274 MultiArrayShape<2>::type newShape = 2*image.shape() - MultiArrayShape<2>::type(1,1);
275 res.reshapeIfEmpty(image.taggedShape().resize(newShape).setChannelDescription(description),
276 "shenCastanCrackEdgeImage(): Output array has wrong shape. Needs to be (w,h)*2 - 1.");
277
278 {
279 PyAllowThreads _pythread;
280 differenceOfExponentialCrackEdgeImage(srcImageRange(image), destImage(res),
281 scale, threshold, edgeMarker);
282 }
283
284 return res;
285 }
286
287 template < class PixelType>
288 NumpyAnyArray
pythonRemoveShortEdges(NumpyArray<2,Singleband<PixelType>> image,int minEdgeLength,PixelType nonEdgeMarker,NumpyArray<2,Singleband<PixelType>> res=python::object ())289 pythonRemoveShortEdges(NumpyArray<2, Singleband<PixelType> > image,
290 int minEdgeLength, PixelType nonEdgeMarker,
291 NumpyArray<2, Singleband<PixelType> > res = python::object())
292 {
293 res.reshapeIfEmpty(image.taggedShape(),
294 "removeShortEdges(): Output array has wrong shape.");
295
296 {
297 PyAllowThreads _pythread;
298 copyImage(srcImageRange(image), destImage(res));
299 removeShortEdges(destImageRange(res), minEdgeLength, nonEdgeMarker);
300 }
301
302 return res;
303 }
304
305 template < class PixelType >
306 NumpyAnyArray
pythonBeautifyCrackEdgeImage(NumpyArray<2,Singleband<PixelType>> image,PixelType edgeMarker,PixelType backgroundMarker,NumpyArray<2,Singleband<PixelType>> res=python::object ())307 pythonBeautifyCrackEdgeImage(NumpyArray<2, Singleband<PixelType> > image,
308 PixelType edgeMarker,
309 PixelType backgroundMarker,
310 NumpyArray<2, Singleband<PixelType> > res = python::object())
311 {
312 res.reshapeIfEmpty(image.taggedShape(),
313 "beautifyCrackEdgeImage(): Output array has wrong shape.");
314
315 {
316 PyAllowThreads _pythread;
317 copyImage(srcImageRange(image), destImage(res));
318 beautifyCrackEdgeImage(destImageRange(res), edgeMarker, backgroundMarker);
319 }
320
321 return res;
322 }
323
324 template < class PixelType >
325 NumpyAnyArray
pythonCloseGapsInCrackEdgeImage(NumpyArray<2,Singleband<PixelType>> image,PixelType edgeMarker,NumpyArray<2,Singleband<PixelType>> res=python::object ())326 pythonCloseGapsInCrackEdgeImage(NumpyArray<2, Singleband<PixelType> > image,
327 PixelType edgeMarker,
328 NumpyArray<2, Singleband<PixelType> > res = python::object())
329 {
330 res.reshapeIfEmpty(image.taggedShape(),
331 "closeGapsInCrackEdgeImage(): Output array has wrong shape.");
332
333 {
334 PyAllowThreads _pythread;
335 copyImage(srcImageRange(image), destImage(res));
336 closeGapsInCrackEdgeImage(destImageRange(res), edgeMarker);
337 }
338
339 return res;
340 }
341
342 template < class PixelType >
343 NumpyAnyArray
pythonRegionImageToCrackEdgeImage(NumpyArray<2,Singleband<PixelType>> image,PixelType edgeLabel=0,NumpyArray<2,Singleband<PixelType>> res=python::object ())344 pythonRegionImageToCrackEdgeImage(NumpyArray<2, Singleband<PixelType> > image,
345 PixelType edgeLabel = 0,
346 NumpyArray<2, Singleband<PixelType> > res = python::object())
347 {
348 MultiArrayShape<2>::type newShape = 2*image.shape() - MultiArrayShape<2>::type(1,1);
349 res.reshapeIfEmpty(image.taggedShape().resize(newShape),
350 "regionImageToCrackEdgeImage(): Output array has wrong shape. Needs to be (w,h)*2 - 1.");
351
352 {
353 PyAllowThreads _pythread;
354 regionImageToCrackEdgeImage(srcImageRange(image), destImage(res), edgeLabel);
355 }
356 return res;
357 }
358
359 template < class PixelType >
360 NumpyAnyArray
pythonRegionImageToEdgeImage(NumpyArray<2,Singleband<PixelType>> image,PixelType edgeLabel=1,NumpyArray<2,Singleband<PixelType>> res=python::object ())361 pythonRegionImageToEdgeImage(NumpyArray<2, Singleband<PixelType> > image,
362 PixelType edgeLabel = 1,
363 NumpyArray<2, Singleband<PixelType> > res = python::object())
364 {
365 res.reshapeIfEmpty(image.taggedShape(),
366 "regionImageToEdgeImage2D(): Output array has wrong shape.");
367
368 {
369 PyAllowThreads _pythread;
370 regionImageToEdgeImage(srcImageRange(image), destImage(res), edgeLabel);
371 }
372 return res;
373 }
374
375
defineEdgedetection()376 void defineEdgedetection()
377 {
378 using namespace python;
379
380 docstring_options doc_options(true, true, false);
381
382 class_<Edgel> edgel("Edgel", "Represent an Edgel at a particular subpixel position (x, y), having "
383 "given 'strength' and 'orientation'.\n\n"
384 "For details, see Edgel_ in the vigra C++ documentation.\n",
385 init<>("Standard constructor::\n\n Edgel()\n\n"));
386 edgel
387 .def(init<float, float, float, float>(args("x", "y", "strength", "orientation"),
388 "Constructor::\n\n Edgel(x, y, strength, orientation)\n\n"))
389 .def_readwrite("x", &Edgel::x, "The edgel's x position.")
390 .def_readwrite("y", &Edgel::y, "The edgel's y position.")
391 .def_readwrite("strength", &Edgel::strength, "The edgel's strength.")
392 .def_readwrite("orientation", &Edgel::orientation, "The edgel's orientation.")
393 .def("__getitem__", &Edgel__getitem__)
394 .def("__setitem__", &Edgel__setitem__)
395 .def("__repr__", &Edgel__repr__)
396 .def("__len__", &Edgel__len__)
397 ;
398
399 def("cannyEdgelList",
400 registerConverters(&pythonFindEdgelsFromGrad<float>),
401 args("gradient", "threshold"),
402 "Return a list of :class:`Edgel` objects whose strength is at least 'threshold'.\n\n"
403 "The function comes in two forms::\n\n"
404 " cannyEdgelList(gradient, threshold) -> list\n"
405 " cannyEdgelList(image, scale, threshold) -> list\n\n"
406 "The first form expects a gradient image (i.e. with two channels) to compute "
407 "edgels, whereas the second form expects a scalar image and computes the "
408 "gradient internally at 'scale'.\n\n"
409 "For details see cannyEdgelList_ in the vigra C++ documentation.\n");
410
411 def("cannyEdgelList",
412 registerConverters(&pythonFindEdgels<float>),
413 args("image", "scale", "threshold"),
414 "Compute edgels of a 2D scalar image, given the filter scale.\n");
415
416 def("cannyEdgelList3x3",
417 registerConverters(&pythonFindEdgels3x3FromGrad<float>),
418 args("gradient", "threshold"),
419 "Return a list of :class:`Edgel` objects whose strength is at least 'threshold'.\n\n"
420 "The function comes in two forms::\n\n"
421 " cannyEdgelList3x3(gradient, threshold) -> list\n"
422 " cannyEdgelList3x3(image, scale, threshold) -> list\n\n"
423 "The first form expects a gradient image (i.e. with two channels) to compute "
424 "edgels, whereas the second form expects a scalar image and computes the "
425 "gradient internally at 'scale'. The results are slightly better than "
426 "those of :func:`cannyEdgelList`.\n\n"
427 "For details see cannyEdgelList3x3_ in the vigra C++ documentation.\n");
428
429 def("cannyEdgelList3x3",
430 registerConverters(&pythonFindEdgels3x3<float>),
431 args("image", "scale", "threshold"),
432 "Compute edgels of a 2D scalar image, given the filter scale.\n");
433
434 def("cannyEdgeImage",
435 registerConverters(&pythonCannyEdgeImage<float, UInt8>),
436 (arg("image"), arg("scale"), arg("threshold"), arg("edgeMarker"),arg("out")=python::object()),
437 "Detect and mark edges in an edge image using Canny's algorithm.\n\n"
438 "For details see cannyEdgeImage_ in the vigra C++ documentation.\n");
439
440 def("cannyEdgeImage",
441 registerConverters(&pythonCannyEdgeImageColor<float, UInt8>),
442 (arg("image"), arg("scale"), arg("threshold"), arg("edgeMarker"),arg("out")=python::object()),
443 "Detect and mark edges in an edge image using Canny's algorithm.\n\n"
444 "For details see cannyEdgeImage_ in the vigra C++ documentation.\n");
445
446 def("cannyEdgeImageWithThinning",
447 registerConverters(&pythonCannyEdgeImageWithThinning<float, UInt8>),
448 (arg("image"), arg("scale"), arg("threshold"), arg("edgeMarker"),
449 arg("addBorder")=true,arg("out")=python::object()),
450 "Detect and mark edges in an edge image using Canny's algorithm.\n\n"
451 "For details see cannyEdgeImageWithThinning_ in the vigra C++ documentation.\n");
452
453 def("shenCastanEdgeImage",
454 registerConverters(&pythonShenCastanEdgeImage<float, UInt8>),
455 (arg("image"), arg("scale"), arg("threshold"), arg("edgeMarker"),arg("out")=python::object()),
456 "Detect and mark edges in an edge image using the Shen/Castan zero-crossing detector.\n\n"
457 "For details see differenceOfExponentialEdgeImage_ in the vigra C++ documentation.\n");
458
459 def("shenCastanCrackEdgeImage",
460 registerConverters(&pythonShenCastanCrackEdgeImage<float, UInt8>),
461 (arg("image"), arg("scale"), arg("threshold"), arg("edgeMarker"),arg("out")=python::object()),
462 "Detect and mark edges in a crack edge image using the Shen/Castan zero-crossing detector.\n\n"
463 "For details see differenceOfExponentialCrackEdgeImage_ in the vigra C++ documentation.\n");
464
465 def("removeShortEdges",
466 registerConverters(&pythonRemoveShortEdges<UInt8>),
467 (arg("image"), arg("minEdgeLength"), arg("nonEdgeMarker"),arg("out")=python::object()),
468 "Remove short edges from an edge image.\n\n"
469 "For details see removeShortEdges_ in the vigra C++ documentation.\n");
470
471 def("beautifyCrackEdgeImage",
472 registerConverters(&pythonBeautifyCrackEdgeImage<UInt8>),
473 (arg("image"), arg("edgeMarker"), arg("backgroundMarker"),arg("out")=python::object()),
474 "Beautify crack edge image for visualization.\n\n"
475 "For details see beautifyCrackEdgeImage_ in the vigra C++ documentation.\n");
476
477 def("closeGapsInCrackEdgeImage",
478 registerConverters(&pythonCloseGapsInCrackEdgeImage<UInt8>),
479 (arg("image"), arg("edgeMarker"),arg("out")=python::object()),
480 "Close one-pixel wide gaps in a cell grid edge image.\n\n"
481 "For details see closeGapsInCrackEdgeImage_ in the vigra C++ documentation.\n");
482
483 def("regionImageToEdgeImage",
484 registerConverters(&pythonRegionImageToEdgeImage<npy_uint32>),
485 (arg("image"),
486 arg("edgeLabel") = 1,
487 arg("out")=python::object()),
488 "Transform a labeled uint32 image into an edge image.\n\n"
489 "For details see regionImageToEdgeImage_ in the vigra C++ documentation.\n");
490
491 def("regionImageToEdgeImage",
492 registerConverters(&pythonRegionImageToEdgeImage<npy_uint64>),
493 (arg("image"),
494 arg("edgeLabel") = 1,
495 arg("out")=python::object()),
496 "Likewise for a uint64 image.\n");
497
498 def("regionImageToCrackEdgeImage",
499 registerConverters(&pythonRegionImageToCrackEdgeImage<npy_uint32>),
500 (arg("image"),
501 arg("edgeLabel") = 0,
502 arg("out")=python::object()),
503 "Transform a labeled uint32 image into a crack edge image. \n\n"
504 "For details see regionImageToCrackEdgeImage_ in the vigra C++ documentation.\n");
505
506 def("regionImageToCrackEdgeImage",
507 registerConverters(&pythonRegionImageToCrackEdgeImage<npy_uint64>),
508 (arg("image"),
509 arg("edgeLabel") = 0,
510 arg("out")=python::object()),
511 "Likewise for a uint64 image.\n");
512
513 }
514
515 } // namespace vigra
516