vigra - vigra/numpy_array.hxx Source File

[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

numpy_array.hxx |

1 /************************************************************************/
2 /* */
3 /* Copyright 2009 by Ullrich Koethe and Hans Meine */
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 #ifndef VIGRA_NUMPY_ARRAY_HXX
37 #define VIGRA_NUMPY_ARRAY_HXX
38
39 #ifndef NPY_NO_DEPRECATED_API
40 # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
41 #endif
42
43 #include <Python.h>
44 #include <string>
45 #include <iostream>
46 #include <numpy/arrayobject.h>
47 #include "multi_array.hxx"
48 #include "array_vector.hxx"
49 #include "python_utility.hxx"
50 #include "numpy_array_traits.hxx"
51 #include "numpy_array_taggedshape.hxx"
52
53 // NumPy function called by NumPy's import_array() macro (and our import_vigranumpy() below)
54 int _import_array();
55
56 namespace vigra {
57
58 static inline void import_vigranumpy()
59 {
60 // roughly equivalent to import_array():
61 if(_import_array() < 0)
62 pythonToCppException(0);
63
64 // Import vigra to activate the numpy array converters, but ensure that
65 // cyclic imports (from within vigra itself) are avoided.
66 char const * load_vigra =
67 "import sys\n"
68 "if not sys.modules.has_key('vigra.vigranumpycore'):\n"
69 " import vigra\n";
70 pythonToCppException(PyRun_SimpleString(load_vigra) == 0);
71 }
72
73 /********************************************************/
74 /* */
75 /* MultibandVectorAccessor */
76 /* */
77 /********************************************************/
78
79 template <class T>
80 class MultibandVectorAccessor
81 {
82 MultiArrayIndex size_, stride_;
83
84 public:
85 MultibandVectorAccessor(MultiArrayIndex size, MultiArrayIndex stride)
86 : size_(size),
87 stride_(stride)
88 {}
89
90
91 typedef Multiband<T> value_type;
92
93 /** the vector's value_type
94 */
95 typedef T component_type;
96
97 typedef VectorElementAccessor<MultibandVectorAccessor<T> > ElementAccessor;
98
99 /** Read the component data at given vector index
100 at given iterator position
101 */
102 template <class ITERATOR>
103 component_type const & getComponent(ITERATOR const & i, int idx) const
104 {
105 return *(&*i+idx*stride_);
106 }
107
108 /** Set the component data at given vector index
109 at given iterator position. The type <TT>V</TT> of the passed
110 in <TT>value</TT> is automatically converted to <TT>component_type</TT>.
111 In case of a conversion floating point -> integral this includes rounding and clipping.
112 */
113 template <class V, class ITERATOR>
114 void setComponent(V const & value, ITERATOR const & i, int idx) const
115 {
116 *(&*i+idx*stride_) = detail::RequiresExplicitCast<component_type>::cast(value);
117 }
118
119 /** Read the component data at given vector index
120 at an offset of given iterator position
121 */
122 template <class ITERATOR, class DIFFERENCE>
123 component_type const & getComponent(ITERATOR const & i, DIFFERENCE const & diff, int idx) const
124 {
125 return *(&i[diff]+idx*stride_);
126 }
127
128 /** Set the component data at given vector index
129 at an offset of given iterator position. The type <TT>V</TT> of the passed
130 in <TT>value</TT> is automatically converted to <TT>component_type</TT>.
131 In case of a conversion floating point -> integral this includes rounding and clipping.
132 */
133 template <class V, class ITERATOR, class DIFFERENCE>
134 void
135 setComponent(V const & value, ITERATOR const & i, DIFFERENCE const & diff, int idx) const
136 {
137 *(&i[diff]+idx*stride_) = detail::RequiresExplicitCast<component_type>::cast(value);
138 }
139
140 template <class U>
141 MultiArrayIndex size(U) const
142 {
143 return size_;
144 }
145 };
146
147 /********************************************************/
148
149 template <class TYPECODE> // pseudo-template to avoid inline expansion of the function
150 // will always be NPY_TYPES
151 PyObject *
152 constructArray(TaggedShape tagged_shape, TYPECODE typeCode, bool init,
153 python_ptr arraytype = python_ptr());
154
155 /********************************************************/
156
157 template <class Shape>
158 void numpyParseSlicing(Shape const & shape, PyObject * idx, Shape & start, Shape & stop)
159 {
160 int N = shape.size();
161 for(int k=0; k<N; ++k)
162 {
163 start[k] = 0;
164 stop[k] = shape[k];
165 }
166
167 python_ptr index(idx);
168 if(!PySequence_Check(index))
169 {
170 index = python_ptr(PyTuple_Pack(1, index.ptr()), python_ptr::new_nonzero_reference);
171 }
172 int lindex = PyTuple_Size(index);
173 int kindex = 0;
174 for(; kindex<lindex; ++kindex)
175 {
176 if(PyTuple_GET_ITEM((PyTupleObject *)index.ptr(), kindex) == Py_Ellipsis)
177 break;
178 }
179 if(kindex == lindex && lindex < N)
180 {
181 python_ptr ellipsis = python_ptr(PyTuple_Pack(1, Py_Ellipsis), python_ptr::new_nonzero_reference);
182 index = python_ptr(PySequence_Concat(index, ellipsis), python_ptr::new_nonzero_reference);
183 ++lindex;
184 }
185 kindex = 0;
186 for(int k=0; k < N; ++k)
187 {
188 PyObject * item = PyTuple_GET_ITEM((PyTupleObject *)index.ptr(), kindex);
189 if(PyInt_Check(item))
190 {
191 MultiArrayIndex i = PyInt_AsLong(item);
192 start[k] = i;
193 if(start[k] < 0)
194 start[k] += shape[k];
195 stop[k] = start[k];
196 ++kindex;
197 }
198 else if(PySlice_Check(item))
199 {
200 Py_ssize_t sstart, sstop, step;
201 if(PySlice_GetIndices((PySliceObject *)item, shape[k], &sstart, &sstop, &step) != 0)
202 pythonToCppException(0);
203 vigra_precondition(step == 1,
204 "numpyParseSlicing(): only unit steps are supported.");
205 start[k] = sstart;
206 stop[k] = sstop;
207 ++kindex;
208 }
209 else if(item == Py_Ellipsis)
210 {
211 if(lindex == N)
212 ++kindex;
213 else
214 ++lindex;
215 }
216 else
217 {
218 vigra_precondition(false,
219 "numpyParseSlicing(): unsupported index object.");
220 }
221 }
222 }
223
224
225 /********************************************************/
226 /* */
227 /* NumpyAnyArray */
228 /* */
229 /********************************************************/
230
231 /** Wrapper class for a Python array.
232
233 This class stores a reference-counted pointer to an Python numpy array object,
234 i.e. an object where <tt>PyArray_Check(object)</tt> returns true (in Python, the
235 object is then a subclass of <tt>numpy.ndarray</tt>). This class is mainly used
236 as a smart pointer to these arrays, but some basic access and conversion functions
237 are also provided.
238
239 <b>\#include</b> <vigra/numpy_array.hxx><br>
240 Namespace: vigra
241 */
242 class NumpyAnyArray
243 {
244 protected:
245 python_ptr pyArray_;
246
247 public:
248
249 /// difference type
250 typedef ArrayVector<npy_intp> difference_type;
251
252 static python_ptr getArrayTypeObject()
253 {
254 return detail::getArrayTypeObject();
255 }
256
257 static std::string defaultOrder(std::string defaultValue = "C")
258 {
259 return detail::defaultOrder(defaultValue);
260 }
261
262 static python_ptr defaultAxistags(int ndim, std::string order = "")
263 {
264 return detail::defaultAxistags(ndim, order);
265 }
266
267 static python_ptr emptyAxistags(int ndim)
268 {
269 return detail::emptyAxistags(ndim);
270 }
271
272 /**
273 Construct from a Python object. If \a obj is NULL, or is not a subclass
274 of numpy.ndarray, the resulting NumpyAnyArray will have no data (i.e.
275 hasData() returns false). Otherwise, it creates a new reference to the array
276 \a obj, unless \a createCopy is true, where a new array is created by calling
277 the C-equivalent of obj->copy().
278 */
279 explicit NumpyAnyArray(PyObject * obj = 0, bool createCopy = false, PyTypeObject * type = 0)
280 {
281 if(obj == 0)
282 return;
283 vigra_precondition(type == 0 || PyType_IsSubtype(type, &PyArray_Type),
284 "NumpyAnyArray(obj, createCopy, type): type must be numpy.ndarray or a subclass thereof.");
285 if(createCopy)
286 makeCopy(obj, type);
287 else
288 vigra_precondition(makeReference(obj, type), "NumpyAnyArray(obj): obj isn't a numpy array.");
289 }
290
291 /**
292 Copy constructor. By default, it creates a new reference to the array
293 \a other. When \a createCopy is true, a new array is created by calling
294 the C-equivalent of other.copy().
295 */
296 NumpyAnyArray(NumpyAnyArray const & other, bool createCopy = false, PyTypeObject * type = 0)
297 {
298 if(!other.hasData())
299 return;
300 vigra_precondition(type == 0 || PyType_IsSubtype(type, &PyArray_Type),
301 "NumpyAnyArray(obj, createCopy, type): type must be numpy.ndarray or a subclass thereof.");
302 if(createCopy)
303 makeCopy(other.pyObject(), type);
304 else
305 makeReference(other.pyObject(), type);
306 }
307
308 // auto-generated destructor is ok
309
310 /**
311 * Assignment operator. If this is already a view with data
312 * (i.e. hasData() is true) and the shapes match, the RHS
313 * array contents are copied via the C-equivalent of
314 * 'self[...] = other[...]'. If the shapes don't matched,
315 * broadcasting is tried on the trailing (i.e. channel)
316 * dimension.
317 * If the LHS is an empty view, assignment is identical to
318 * makeReference(other.pyObject()).
319 */
320 NumpyAnyArray & operator=(NumpyAnyArray const & other)
321 {
322 if(hasData())
323 {
324 vigra_precondition(other.hasData(),
325 "NumpyArray::operator=(): Cannot assign from empty array.");
326
327 python_ptr arraytype = getArrayTypeObject();
328 python_ptr f(PyString_FromString("_copyValuesImpl"), python_ptr::keep_count);
329 if(PyObject_HasAttr(arraytype, f))
330 {
331 python_ptr res(PyObject_CallMethodObjArgs(arraytype, f.get(),
332 pyArray_.get(), other.pyArray_.get(), NULL),
333 python_ptr::keep_count);
334 vigra_postcondition(res.get() != 0,
335 "NumpyArray::operator=(): VigraArray._copyValuesImpl() failed.");
336 }
337 else
338 {
339 PyArrayObject * sarray = (PyArrayObject *)pyArray_.get();
340 PyArrayObject * tarray = (PyArrayObject *)other.pyArray_.get();
341
342 if(PyArray_CopyInto(tarray, sarray) == -1)
343 pythonToCppException(0);
344 }
345 }
346 else
347 {
348 pyArray_ = other.pyArray_;
349 }
350 return *this;
351 }
352
353 /**
354 Returns the number of dimensions of this array, or 0 if
355 hasData() is false.
356 */
357 MultiArrayIndex ndim() const
358 {
359 if(hasData())
360 return PyArray_NDIM(pyArray());
361 return 0;
362 }
363
364 /**
365 Returns the number of spatial dimensions of this array, or 0 if
366 hasData() is false. If the enclosed Python array does not define
367 the attribute spatialDimensions, ndim() is returned.
368 */
369 MultiArrayIndex spatialDimensions() const
370 {
371 if(!hasData())
372 return 0;
373 return pythonGetAttr(pyObject(), "spatialDimensions", ndim());
374 }
375
376 bool hasChannelAxis() const
377 {
378 if(!hasData())
379 return false;
380 return channelIndex() == ndim();
381 }
382
383 MultiArrayIndex channelIndex() const
384 {
385 if(!hasData())
386 return 0;
387 return pythonGetAttr(pyObject(), "channelIndex", ndim());
388 }
389
390 MultiArrayIndex innerNonchannelIndex() const
391 {
392 if(!hasData())
393 return 0;
394 return pythonGetAttr(pyObject(), "innerNonchannelIndex", ndim());
395 }
396
397 /**
398 Returns the shape of this array. The size of
399 the returned shape equals ndim().
400 */
401 difference_type shape() const
402 {
403 if(hasData())
404 return difference_type(PyArray_DIMS(pyArray()), PyArray_DIMS(pyArray()) + ndim());
405 return difference_type();
406 }
407
408 /** Compute the ordering of the strides of this array.
409 The result is describes the current permutation of the axes relative
410 to an ascending stride order.
411 */
412 difference_type strideOrdering() const
413 {
414 if(!hasData())
415 return difference_type();
416 MultiArrayIndex N = ndim();
417 difference_type stride(PyArray_STRIDES(pyArray()), PyArray_STRIDES(pyArray()) + N),
418 permutation(N);
419 for(MultiArrayIndex k=0; k<N; ++k)
420 permutation[k] = k;
421 for(MultiArrayIndex k=0; k<N-1; ++k)
422 {
423 MultiArrayIndex smallest = k;
424 for(MultiArrayIndex j=k+1; j<N; ++j)
425 {
426 if(stride[j] < stride[smallest])
427 smallest = j;
428 }
429 if(smallest != k)
430 {
431 std::swap(stride[k], stride[smallest]);
432 std::swap(permutation[k], permutation[smallest]);
433 }
434 }
435 difference_type ordering(N);
436 for(MultiArrayIndex k=0; k<N; ++k)
437 ordering[permutation[k]] = k;
438 return ordering;
439 }
440
441 // /**
442 // Returns the the permutation that will transpose this array into
443 // canonical ordering (currently: F-order). The size of
444 // the returned permutation equals ndim().
445 // */
446 // difference_type permutationToNormalOrder() const
447 // {
448 // if(!hasData())
449 // return difference_type();
450
451 // // difference_type res(detail::getAxisPermutationImpl(pyArray_,
452 // // "permutationToNormalOrder", true));
453 // difference_type res;
454 // detail::getAxisPermutationImpl(res, pyArray_, "permutationToNormalOrder", true);
455 // if(res.size() == 0)
456 // {
457 // res.resize(ndim());
458 // linearSequence(res.begin(), res.end(), ndim()-1, MultiArrayIndex(-1));
459 // }
460 // return res;
461 // }
462
463 /**
464 Returns the value type of the elements in this array, or -1
465 when hasData() is false.
466 */
467 int dtype() const
468 {
469 if(hasData())
470 return PyArray_DESCR(pyArray())->type_num;
471 return -1;
472 }
473
474 /**
475 Constructs a slicing from the given shape objects and calls '__getitem__'.
476 */
477 template <class Shape>
478 NumpyAnyArray
479 getitem(Shape start, Shape stop) const
480 {
481 unsigned int size = ndim();
482 vigra_precondition(start.size() == size && stop.size() == size,
483 "NumpyAnyArray::getitem(): shape has wrong dimension.");
484
485 difference_type s(this->shape());
486
487 python_ptr index(PyTuple_New(size), python_ptr::new_nonzero_reference);
488 for(unsigned int k=0; k<size; ++k)
489 {
490 if(start[k] < 0)
491 start[k] += s[k];
492 if(stop[k] < 0)
493 stop[k] += s[k];
494 vigra_precondition(0 <= start[k] && start[k] <= stop[k] && stop[k] <= s[k],
495 "NumpyAnyArray::getitem(): slice out of bounds.");
496 PyObject * item = 0;
497 if(start[k] == stop[k])
498 {
499 item = PyInt_FromLong(start[k]);
500 }
501 else
502 {
503 python_ptr s0(PyInt_FromLong(start[k]), python_ptr::new_nonzero_reference);
504 python_ptr s1(PyInt_FromLong(stop[k]), python_ptr::new_nonzero_reference);
505 item = PySlice_New(s0, s1, 0);
506 }
507 pythonToCppException(item);
508 PyTuple_SET_ITEM((PyTupleObject *)index.ptr(), k, item); // steals reference to item
509 }
510
511 python_ptr func(PyString_FromString("__getitem__"), python_ptr::new_nonzero_reference);
512 python_ptr res(PyObject_CallMethodObjArgs(pyObject(), func.ptr(), index.ptr(), NULL),
513 python_ptr::new_nonzero_reference);
514 return NumpyAnyArray(res.ptr());
515 }
516
517
518 /**
519 * Return the AxisTags of this array or a NULL pointer when the attribute
520 'axistags' is missing in the Python object or this array has no data.
521 */
522 python_ptr axistags() const
523 {
524 python_ptr axistags;
525 if(pyObject())
526 {
527 python_ptr key(PyString_FromString("axistags"), python_ptr::keep_count);
528 axistags.reset(PyObject_GetAttr(pyObject(), key), python_ptr::keep_count);
529 if(!axistags)
530 PyErr_Clear();
531 }
532 return axistags;
533 }
534
535 /**
536 * Return a borrowed reference to the internal PyArrayObject.
537 */
538 PyArrayObject * pyArray() const
539 {
540 return (PyArrayObject *)pyArray_.get();
541 }
542
543 /**
544 * Return a borrowed reference to the internal PyArrayObject
545 * (see pyArray()), cast to PyObject for your convenience.
546 */
547 PyObject * pyObject() const
548 {
549 return pyArray_.get();
550 }
551
552 /**
553 Reset the NumpyAnyArray to the given object. If \a obj is a numpy array object,
554 a new reference to that array is created, and the function returns
555 true. Otherwise, it returns false and the NumpyAnyArray remains unchanged.
556 If \a type is given, the new reference will be a view with that type, provided
557 that \a type is a numpy ndarray or a subclass thereof. Otherwise, an
558 exception is thrown.
559 */
560 bool makeReference(PyObject * obj, PyTypeObject * type = 0)
561 {
562 if(obj == 0 || !PyArray_Check(obj))
563 return false;
564 if(type != 0)
565 {
566 vigra_precondition(PyType_IsSubtype(type, &PyArray_Type) != 0,
567 "NumpyAnyArray::makeReference(obj, type): type must be numpy.ndarray or a subclass thereof.");
568 obj = PyArray_View((PyArrayObject*)obj, 0, type);
569 pythonToCppException(obj);
570 }
571 pyArray_.reset(obj);
572 return true;
573 }
574
575 /**
576 Create a copy of the given array object. If \a obj is a numpy array object,
577 a copy is created via the C-equivalent of 'obj->copy()'. If
578 this call fails, or obj was not an array, an exception is thrown
579 and the NumpyAnyArray remains unchanged.
580 */
581 void makeCopy(PyObject * obj, PyTypeObject * type = 0)
582 {
583 vigra_precondition(obj && PyArray_Check(obj),
584 "NumpyAnyArray::makeCopy(obj): obj is not an array.");
585 vigra_precondition(type == 0 || PyType_IsSubtype(type, &PyArray_Type),
586 "NumpyAnyArray::makeCopy(obj, type): type must be numpy.ndarray or a subclass thereof.");
587 python_ptr array(PyArray_NewCopy((PyArrayObject*)obj, NPY_ANYORDER), python_ptr::keep_count);
588 pythonToCppException(array);
589 makeReference(array, type);
590 }
591
592 /**
593 Check whether this NumpyAnyArray actually points to a Python array.
594 */
595 bool hasData() const
596 {
597 return pyArray_ != 0;
598 }
599 };
600
601 /********************************************************/
602 /* */
603 /* constructArray */
604 /* */
605 /********************************************************/
606
607 namespace detail {
608
609 inline bool
610 nontrivialPermutation(ArrayVector<npy_intp> const & p)
611 {
612 for(unsigned int k=0; k<p.size(); ++k)
613 if(p[k] != k)
614 return true;
615 return false;
616 }
617
618 } // namespace detail
619
620 template <class TYPECODE> // pseudo-template to avoid inline expansion of the function
621 // will always be NPY_TYPES
622 PyObject *
623 constructArray(TaggedShape tagged_shape, TYPECODE typeCode, bool init, python_ptr arraytype)
624 {
625 ArrayVector<npy_intp> shape = finalizeTaggedShape(tagged_shape);
626 PyAxisTags axistags(tagged_shape.axistags);
627
628 int ndim = (int)shape.size();
629 ArrayVector<npy_intp> inverse_permutation;
630 int order = 1; // Fortran order
631
632 if(axistags)
633 {
634 if(!arraytype)
635 arraytype = NumpyAnyArray::getArrayTypeObject();
636
637 inverse_permutation = axistags.permutationFromNormalOrder();
638 vigra_precondition(ndim == (int)inverse_permutation.size(),
639 "axistags.permutationFromNormalOrder(): permutation has wrong size.");
640 }
641 else
642 {
643 arraytype = python_ptr((PyObject*)&PyArray_Type);
644 order = 0; // C order
645 }
646
647 // std::cerr << "constructArray: " << shape << "\n" << inverse_permutation << "\n";
648
649 python_ptr array(PyArray_New((PyTypeObject *)arraytype.get(), ndim, shape.begin(),
650 typeCode, 0, 0, 0, order, 0),
651 python_ptr::keep_count);
652 pythonToCppException(array);
653
654 if(detail::nontrivialPermutation(inverse_permutation))
655 {
656 PyArray_Dims permute = { inverse_permutation.begin(), ndim };
657 array = python_ptr(PyArray_Transpose((PyArrayObject*)array.get(), &permute),
658 python_ptr::keep_count);
659 pythonToCppException(array);
660 }
661
662 if(arraytype != (PyObject*)&PyArray_Type && axistags)
663 pythonToCppException(PyObject_SetAttrString(array, "axistags", axistags.axistags) != -1);
664
665 if(init)
666 PyArray_FILLWBYTE((PyArrayObject *)array.get(), 0);
667
668 return array.release();
669 }
670
671 // FIXME: reimplement in terms of TaggedShape?
672 template <class TINY_VECTOR>
673 inline
674 python_ptr constructNumpyArrayFromData(
675 TINY_VECTOR const & shape, npy_intp *strides,
676 NPY_TYPES typeCode, void *data)
677 {
678 ArrayVector<npy_intp> pyShape(shape.begin(), shape.end());
679
680 #ifndef NPY_ARRAY_WRITEABLE
681 # define NPY_ARRAY_WRITEABLE NPY_WRITEABLE // old API compatibility
682 #endif
683
684 python_ptr array(PyArray_New(&PyArray_Type, shape.size(), pyShape.begin(),
685 typeCode, strides, data, 0, NPY_ARRAY_WRITEABLE, 0),
686 python_ptr::keep_count);
687 pythonToCppException(array);
688
689 return array;
690 }
691
692 /********************************************************/
693 /* */
694 /* NumpyArray */
695 /* */
696 /********************************************************/
697
698 /** Provide the MultiArrayView interface for a Python array.
699
700 This class inherits from both \ref vigra::MultiArrayView and \ref vigra::NumpyAnyArray
701 in order to support easy and save application of VIGRA functions to Python arrays.
702
703 <b>\#include</b> <vigra/numpy_array.hxx><br>
704 Namespace: vigra
705 */
706 template <unsigned int N, class T, class Stride = StridedArrayTag>
707 class NumpyArray
708 : public MultiArrayView<N, typename NumpyArrayTraits<N, T, Stride>::value_type, Stride>,
709 public NumpyAnyArray
710 {
711 public:
712 typedef NumpyArrayTraits<N, T, Stride> ArrayTraits;
713 typedef typename ArrayTraits::dtype dtype;
714 typedef T pseudo_value_type;
715
716 static NPY_TYPES const typeCode = ArrayTraits::typeCode;
717
718 /** the view type associated with this array.
719 */
720 typedef MultiArrayView<N, typename ArrayTraits::value_type, Stride> view_type;
721
722 enum { actual_dimension = view_type::actual_dimension };
723
724 /** the array's value type
725 */
726 typedef typename view_type::value_type value_type;
727
728 /** pointer type
729 */
730 typedef typename view_type::pointer pointer;
731
732 /** const pointer type
733 */
734 typedef typename view_type::const_pointer const_pointer;
735
736 /** reference type (result of operator[])
737 */
738 typedef typename view_type::reference reference;
739
740 /** const reference type (result of operator[] const)
741 */
742 typedef typename view_type::const_reference const_reference;
743
744 /** size type
745 */
746 typedef typename view_type::size_type size_type;
747
748 /** difference type (used for multi-dimensional offsets and indices)
749 */
750 typedef typename view_type::difference_type difference_type;
751
752 /** difference and index type for a single dimension
753 */
754 typedef typename view_type::difference_type_1 difference_type_1;
755
756 /** type of an array specifying an axis permutation
757 */
758 typedef typename NumpyAnyArray::difference_type permutation_type;
759
760 /** traverser type
761 */
762 typedef typename view_type::traverser traverser;
763
764 /** traverser type to const data
765 */
766 typedef typename view_type::const_traverser const_traverser;
767
768 /** sequential (random access) iterator type
769 */
770 typedef typename view_type::iterator iterator;
771
772 /** sequential (random access) const iterator type
773 */
774 typedef typename view_type::const_iterator const_iterator;
775
776 using view_type::shape; // resolve ambiguity of multiple inheritance
777 using view_type::hasData; // resolve ambiguity of multiple inheritance
778 using view_type::strideOrdering; // resolve ambiguity of multiple inheritance
779
780 protected:
781
782 // this function assumes that pyArray_ has already been set, and compatibility been checked
783 void setupArrayView();
784
785 static python_ptr init(difference_type const & shape, bool init = true,
786 std::string const & order = "")
787 {
788 vigra_precondition(order == "" || order == "C" || order == "F" ||
789 order == "V" || order == "A",
790 "NumpyArray.init(): order must be in ['C', 'F', 'V', 'A', ''].");
791 return python_ptr(constructArray(ArrayTraits::taggedShape(shape, order), typeCode, init),
792 python_ptr::keep_count);
793 }
794
795 public:
796
797 using view_type::init;
798
799 /**
800 * Construct from a given PyObject pointer. When the given
801 * python object is NULL, the internal python array will be
802 * NULL and hasData() will return false.
803 *
804 * Otherwise, the function attempts to create a
805 * new reference to the given Python object, unless
806 * copying is forced by setting \a createCopy to true.
807 * If either of this fails, the function throws an exception.
808 * This will not happen if isReferenceCompatible(obj) (in case
809 * of creating a new reference) or isCopyCompatible(obj)
810 * (in case of copying) have returned true beforehand.
811 */
812 explicit NumpyArray(PyObject *obj = 0, bool createCopy = false)
813 {
814 if(obj == 0)
815 return;
816 if(createCopy)
817 makeCopy(obj);
818 else
819 vigra_precondition(makeReference(obj),
820 "NumpyArray(obj): Cannot construct from incompatible array.");
821 }
822
823 /**
824 * Copy constructor; does not copy the memory, but creates a
825 * new reference to the same underlying python object, unless
826 * a copy is forced by setting \a createCopy to true.
827 * (If the source object has no data, this one will have
828 * no data, too.)
829 */
830 NumpyArray(const NumpyArray &other, bool createCopy = false)
831 : view_type(),
832 NumpyAnyArray()
833 {
834 if(!other.hasData())
835 return;
836 if(createCopy)
837 makeCopy(other.pyObject());
838 else
839 makeReferenceUnchecked(other.pyObject());
840 }
841
842 /**
843 * Allocate new memory and copy data from a MultiArrayView.
844 */
845 template <class U, class S>
846 explicit NumpyArray(const MultiArrayView<N, U, S> &other)
847 {
848 if(!other.hasData())
849 return;
850 vigra_postcondition(makeReference(init(other.shape(), false)),
851 "NumpyArray(MultiArrayView): Python constructor did not produce a compatible array.");
852 view_type::operator=(other);
853 }
854
855 /**
856 * Construct a new array object, allocating an internal python
857 * ndarray of the given shape in the given order (default: VIGRA order), initialized
858 * with zeros.
859 *
860 * An exception is thrown when construction fails.
861 */
862 explicit NumpyArray(difference_type const & shape, std::string const & order = "")
863 {
864 vigra_postcondition(makeReference(init(shape, true, order)),
865 "NumpyArray(shape): Python constructor did not produce a compatible array.");
866 }
867
868 /**
869 * Construct a new array object, allocating an internal python
870 * ndarray according to the given tagged shape, initialized with zeros.
871 *
872 * An exception is thrown when construction fails.
873 */
874 explicit NumpyArray(TaggedShape const & tagged_shape)
875 {
876 reshapeIfEmpty(tagged_shape,
877 "NumpyArray(tagged_shape): Python constructor did not produce a compatible array.");
878 }
879
880 /**
881 * Constructor from NumpyAnyArray.
882 * Equivalent to NumpyArray(other.pyObject())
883 */
884 explicit NumpyArray(const NumpyAnyArray &other, bool createCopy = false)
885 {
886 if(!other.hasData())
887 return;
888 if(createCopy)
889 makeCopy(other.pyObject());
890 else
891 vigra_precondition(makeReference(other.pyObject()), //, false),
892 "NumpyArray(NumpyAnyArray): Cannot construct from incompatible or empty array.");
893 }
894
895 /**
896 * Assignment operator. If this is already a view with data
897 * (i.e. hasData() is true) and the shapes match, the RHS
898 * array contents are copied. If this is an empty view,
899 * assignment is identical to makeReferenceUnchecked(other.pyObject()).
900 * See MultiArrayView::operator= for further information on
901 * semantics.
902 */
903 NumpyArray &operator=(const NumpyArray &other)
904 {
905 if(hasData())
906 view_type::operator=(other);
907 else
908 makeReferenceUnchecked(other.pyObject());
909 return *this;
910 }
911
912 /**
913 * Assignment operator. If this is already a view with data
914 * (i.e. hasData() is true) and the shapes match, the RHS
915 * array contents are copied. If this is an empty view,
916 * assignment is identical to makeReferenceUnchecked(other.pyObject()).
917 * See MultiArrayView::operator= for further information on
918 * semantics.
919 */
920 template <class U, class S>
921 NumpyArray &operator=(const NumpyArray<N, U, S> &other)
922 {
923 if(hasData())
924 {
925 vigra_precondition(shape() == other.shape(),
926 "NumpyArray::operator=(): shape mismatch.");
927 view_type::operator=(other);
928 }
929 else if(other.hasData())
930 {
931 NumpyArray copy;
932 copy.reshapeIfEmpty(other.taggedShape(),
933 "NumpyArray::operator=(): reshape failed unexpectedly.");
934 copy = other;
935 makeReferenceUnchecked(copy.pyObject());
936 }
937 return *this;
938 }
939
940 /**
941 * Assignment operator. If this is already a view with data
942 * (i.e. hasData() is true) and the shapes match, the RHS
943 * array contents are copied. If this is an empty view,
944 * a new buffer with the RHS shape is allocated before copying.
945 */
946 template <class U, class S>
947 NumpyArray &operator=(const MultiArrayView<N, U, S> &other)
948 {
949 if(hasData())
950 {
951 vigra_precondition(shape() == other.shape(),
952 "NumpyArray::operator=(): shape mismatch.");
953 view_type::operator=(other);
954 }
955 else if(other.hasData())
956 {
957 NumpyArray copy;
958 copy.reshapeIfEmpty(other.shape(),
959 "NumpyArray::operator=(): reshape failed unexpectedly.");
960 copy = other;
961 makeReferenceUnchecked(copy.pyObject());
962 }
963 return *this;
964 }
965
966 /**
967 * Assignment operator. If this is already a view with data
968 * (i.e. hasData() is true) and the shapes match, the RHS
969 * array contents are copied.
970 * If this is an empty view, assignment is identical to
971 * makeReference(other.pyObject()).
972 * Otherwise, an exception is thrown.
973 */
974 NumpyArray &operator=(const NumpyAnyArray &other)
975 {
976 if(hasData())
977 {
978 NumpyAnyArray::operator=(other);
979 }
980 else if(isReferenceCompatible(other.pyObject()))
981 {
982 makeReferenceUnchecked(other.pyObject());
983 }
984 else
985 {
986 vigra_precondition(false,
987 "NumpyArray::operator=(): Cannot assign from incompatible array.");
988 }
989 return *this;
990 }
991
992 /**
993 Permute the entries of the given array \a data exactly like the axes of this NumpyArray
994 were permuted upon conversion from numpy.
995 */
996 template <class U>
997 ArrayVector<U>
998 permuteLikewise(ArrayVector<U> const & data) const
999 {
1000 vigra_precondition(hasData(),
1001 "NumpyArray::permuteLikewise(): array has no data.");
1002
1003 ArrayVector<U> res(data.size());
1004 ArrayTraits::permuteLikewise(this->pyArray_, data, res);
1005 return res;
1006 }
1007
1008 /**
1009 Permute the entries of the given array \a data exactly like the axes of this NumpyArray
1010 were permuted upon conversion from numpy.
1011 */
1012 template <class U, int K>
1013 TinyVector<U, K>
1014 permuteLikewise(TinyVector<U, K> const & data) const
1015 {
1016 vigra_precondition(hasData(),
1017 "NumpyArray::permuteLikewise(): array has no data.");
1018
1019 TinyVector<U, K> res;
1020 ArrayTraits::permuteLikewise(this->pyArray_, data, res);
1021 return res;
1022 }
1023
1024 /**
1025 Get the permutation of the axes of this NumpyArray
1026 that was performed upon conversion from numpy.
1027 */
1028 template <int K>
1029 TinyVector<npy_intp, K>
1030 permuteLikewise() const
1031 {
1032 vigra_precondition(hasData(),
1033 "NumpyArray::permuteLikewise(): array has no data.");
1034
1035 TinyVector<npy_intp, K> data, res;
1036 linearSequence(data.begin(), data.end());
1037 ArrayTraits::permuteLikewise(this->pyArray_, data, res);
1038 return res;
1039 }
1040
1041 /**
1042 * Test whether a given python object is a numpy array that can be
1043 * converted (copied) into an array compatible to this NumpyArray type.
1044 * This means that the array's shape conforms to the requirements of
1045 * makeCopy().
1046 */
1047 static bool isCopyCompatible(PyObject *obj)
1048 {
1049 #if VIGRA_CONVERTER_DEBUG
1050 std::cerr << "class " << typeid(NumpyArray).name() << " got " << obj->ob_type->tp_name << "\n";
1051 std::cerr << "using traits " << typeid(ArrayTraits).name() << "\n";
1052 std::cerr<<"isArray: "<< ArrayTraits::isArray(obj)<<std::endl;
1053 std::cerr<<"isShapeCompatible: "<< ArrayTraits::isShapeCompatible((PyArrayObject *)obj)<<std::endl;
1054 #endif
1055
1056 return ArrayTraits::isArray(obj) &&
1057 ArrayTraits::isShapeCompatible((PyArrayObject *)obj);
1058 }
1059
1060 /**
1061 * Test whether a given python object is a numpy array with a
1062 * compatible dtype and the correct shape and strides, so that it
1063 * can be referenced as a view by this NumpyArray type (i.e.
1064 * it conforms to the requirements of makeReference()).
1065 */
1066 static bool isReferenceCompatible(PyObject *obj)
1067 {
1068 return ArrayTraits::isArray(obj) &&
1069 ArrayTraits::isPropertyCompatible((PyArrayObject *)obj);
1070 }
1071
1072 /**
1073 * Deprecated, use isReferenceCompatible(obj) instead.
1074 */
1075 static bool isStrictlyCompatible(PyObject *obj)
1076 {
1077 return isReferenceCompatible(obj);
1078 }
1079
1080 /**
1081 * Create a vector representing the standard stride ordering of a NumpyArray.
1082 * That is, we get a vector representing the range [0,...,N-1], which
1083 * denotes the stride ordering for Fortran order.
1084 */
1085 static difference_type standardStrideOrdering()
1086 {
1087 difference_type strideOrdering;
1088 for(unsigned int k=0; k<N; ++k)
1089 strideOrdering[k] = k;
1090 return strideOrdering;
1091 }
1092
1093 /**
1094 * Set up a view to the given object without checking compatibility.
1095 * This function must not be used unless isReferenceCompatible(obj) returned
1096 * true on the given object (otherwise, a crash is likely).
1097 */
1098 void makeReferenceUnchecked(PyObject *obj)
1099 {
1100 NumpyAnyArray::makeReference(obj);
1101 setupArrayView();
1102 }
1103
1104 /**
1105 * Try to set up a view referencing the given PyObject.
1106 * Returns false if the python object is not a compatible
1107 * numpy array (see isReferenceCompatible()).
1108 *
1109 * The second parameter ('strict') is deprecated and will be ignored.
1110 */
1111 bool makeReference(PyObject *obj, bool /* strict */ = false)
1112 {
1113 if(!isReferenceCompatible(obj))
1114 return false;
1115 makeReferenceUnchecked(obj);
1116 return true;
1117 }
1118
1119 /**
1120 * Try to set up a view referencing the same data as the given
1121 * NumpyAnyArray. This overloaded variant simply calls
1122 * makeReference() on array.pyObject(). The parameter \a strict
1123 * is deprecated and will be ignored.
1124 */
1125 bool makeReference(const NumpyAnyArray &array, bool strict = false)
1126 {
1127 return makeReference(array.pyObject(), strict);
1128 }
1129
1130 /**
1131 * Set up an unsafe reference to the given MultiArrayView.
1132 * ATTENTION: This creates a numpy.ndarray that points to the
1133 * same data, but does not own it, so it must be ensured by
1134 * other means that the memory does not get freed before the
1135 * end of the ndarray's lifetime! (One elegant way would be
1136 * to set the 'base' attribute of the resulting ndarray to a
1137 * python object which directly or indirectly holds the memory
1138 * of the given MultiArrayView.)
1139 */
1140 void makeUnsafeReference(const view_type &multiArrayView)
1141 {
1142 vigra_precondition(!hasData(),
1143 "makeUnsafeReference(): cannot replace existing view with given buffer");
1144
1145 // construct an ndarray that points to our data (taking strides into account):
1146 python_ptr array(ArrayTraits::unsafeConstructorFromData(multiArrayView.shape(),
1147 multiArrayView.data(), multiArrayView.stride()));
1148
1149 view_type::operator=(multiArrayView);
1150 pyArray_ = array;
1151 }
1152
1153 /**
1154 Try to create a copy of the given PyObject.
1155 Raises an exception when obj is not a compatible array
1156 (see isCopyCompatible() or isReferenceCompatible(), according to the
1157 parameter \a strict) or the Python constructor call failed.
1158 */
1159 void makeCopy(PyObject *obj, bool strict = false)
1160 {
1161 #if VIGRA_CONVERTER_DEBUG
1162 int ndim = PyArray_NDIM((PyArrayObject *)obj);
1163 npy_intp * s = PyArray_DIMS((PyArrayObject *)obj);
1164 std::cerr << "makeCopy: " << ndim << " " << ArrayVectorView<npy_intp>(ndim, s) <<
1165 ", strides " << ArrayVectorView<npy_intp>(ndim, PyArray_STRIDES((PyArrayObject *)obj)) << "\n";
1166 std::cerr << "for " << typeid(*this).name() << "\n";
1167 #endif
1168 vigra_precondition(strict ? isReferenceCompatible(obj) : isCopyCompatible(obj),
1169 "NumpyArray::makeCopy(obj): Cannot copy an incompatible array.");
1170
1171 NumpyAnyArray copy(obj, true);
1172 makeReferenceUnchecked(copy.pyObject());
1173 }
1174
1175 /**
1176 Allocate new memory with the given shape and initialize with zeros.<br>
1177 If a stride ordering is given, the resulting array will have this stride
1178 ordering, when it is compatible with the array's memory layout (unstrided
1179 arrays only permit the standard ascending stride ordering).
1180
1181 <em>Note:</em> this operation invalidates dependent objects
1182 (MultiArrayViews and iterators)
1183 */
1184 void reshape(difference_type const & shape)
1185 {
1186 vigra_postcondition(makeReference(init(shape)),
1187 "NumpyArray.reshape(shape): Python constructor did not produce a compatible array.");
1188 }
1189
1190 /**
1191 When this array has no data, allocate new memory with the given \a shape and
1192 initialize with zeros. Otherwise, check if the new shape matches the old shape
1193 and throw a precondition exception with the given \a message if not.
1194 */
1195 void reshapeIfEmpty(difference_type const & shape, std::string message = "")
1196 {
1197 // FIXME: is this really a good replacement?
1198 // reshapeIfEmpty(shape, standardStrideOrdering(), message);
1199 reshapeIfEmpty(TaggedShape(shape), message);
1200 }
1201
1202 /**
1203 When this array has no data, allocate new memory with the given \a shape and
1204 initialize with zeros. Otherwise, check if the new shape matches the old shape
1205 and throw a precondition exception with the given \a message if not.
1206 */
1207 void reshapeIfEmpty(TaggedShape tagged_shape, std::string message = "")
1208 {
1209 ArrayTraits::finalizeTaggedShape(tagged_shape);
1210
1211 if(hasData())
1212 {
1213 vigra_precondition(tagged_shape.compatible(taggedShape()), message.c_str());
1214 }
1215 else
1216 {
1217 python_ptr array(constructArray(tagged_shape, typeCode, true),
1218 python_ptr::keep_count);
1219 vigra_postcondition(makeReference(NumpyAnyArray(array.get())),
1220 "NumpyArray.reshapeIfEmpty(): Python constructor did not produce a compatible array.");
1221 }
1222 }
1223
1224 TaggedShape taggedShape() const
1225 {
1226 return ArrayTraits::taggedShape(this->shape(), PyAxisTags(this->axistags(), true));
1227 }
1228 };
1229
1230 // this function assumes that pyArray_ has already been set, and compatibility been checked
1231 template <unsigned int N, class T, class Stride>
1232 void NumpyArray<N, T, Stride>::setupArrayView()
1233 {
1234 if(NumpyAnyArray::hasData())
1235 {
1236 permutation_type permute;
1237 ArrayTraits::permutationToSetupOrder(this->pyArray_, permute);
1238
1239 vigra_precondition(abs((int)permute.size() - actual_dimension) <= 1,
1240 "NumpyArray::setupArrayView(): got array of incompatible shape (should never happen).");
1241
1242 applyPermutation(permute.begin(), permute.end(),
1243 PyArray_DIMS(pyArray()), this->m_shape.begin());
1244 applyPermutation(permute.begin(), permute.end(),
1245 PyArray_STRIDES(pyArray()), this->m_stride.begin());
1246
1247 if((int)permute.size() == actual_dimension - 1)
1248 {
1249 this->m_shape[actual_dimension-1] = 1;
1250 this->m_stride[actual_dimension-1] = sizeof(value_type);
1251 }
1252
1253 this->m_stride /= sizeof(value_type);
1254 this->m_ptr = reinterpret_cast<pointer>(PyArray_DATA(pyArray()));
1255 vigra_precondition(this->checkInnerStride(Stride()),
1256 "NumpyArray<..., UnstridedArrayTag>::setupArrayView(): First dimension of given array is not unstrided (should never happen).");
1257
1258 }
1259 else
1260 {
1261 this->m_ptr = 0;
1262 }
1263 }
1264
1265
1266 typedef NumpyArray<2, float > NumpyFArray2;
1267 typedef NumpyArray<3, float > NumpyFArray3;
1268 typedef NumpyArray<4, float > NumpyFArray4;
1269 typedef NumpyArray<2, Singleband<float> > NumpyFImage;
1270 typedef NumpyArray<3, Singleband<float> > NumpyFVolume;
1271 typedef NumpyArray<2, RGBValue<float> > NumpyFRGBImage;
1272 typedef NumpyArray<3, RGBValue<float> > NumpyFRGBVolume;
1273 typedef NumpyArray<3, Multiband<float> > NumpyFMultibandImage;
1274 typedef NumpyArray<4, Multiband<float> > NumpyFMultibandVolume;
1275
1276 /********************************************************/
1277 /* */
1278 /* NumpyArray Multiband Argument Object Factories */
1279 /* */
1280 /********************************************************/
1281
1282 template <class PixelType, class Stride>
1283 inline triple<ConstStridedImageIterator<PixelType>,
1284 ConstStridedImageIterator<PixelType>,
1285 MultibandVectorAccessor<PixelType> >
1286 srcImageRange(NumpyArray<3, Multiband<PixelType>, Stride> const & img)
1287 {
1288 ConstStridedImageIterator<PixelType>
1289 ul(img.data(), 1, img.stride(0), img.stride(1));
1290 return triple<ConstStridedImageIterator<PixelType>,
1291 ConstStridedImageIterator<PixelType>,
1292 MultibandVectorAccessor<PixelType> >
1293 (ul, ul + Size2D(img.shape(0), img.shape(1)), MultibandVectorAccessor<PixelType>(img.shape(2), img.stride(2)));
1294 }
1295
1296 template <class PixelType, class Stride>
1297 inline pair< ConstStridedImageIterator<PixelType>,
1298 MultibandVectorAccessor<PixelType> >
1299 srcImage(NumpyArray<3, Multiband<PixelType>, Stride> const & img)
1300 {
1301 ConstStridedImageIterator<PixelType>
1302 ul(img.data(), 1, img.stride(0), img.stride(1));
1303 return pair<ConstStridedImageIterator<PixelType>, MultibandVectorAccessor<PixelType> >
1304 (ul, MultibandVectorAccessor<PixelType>(img.shape(2), img.stride(2)));
1305 }
1306
1307 template <class PixelType, class Stride>
1308 inline triple< StridedImageIterator<PixelType>,
1309 StridedImageIterator<PixelType>,
1310 MultibandVectorAccessor<PixelType> >
1311 destImageRange(NumpyArray<3, Multiband<PixelType>, Stride> & img)
1312 {
1313 StridedImageIterator<PixelType>
1314 ul(img.data(), 1, img.stride(0), img.stride(1));
1315 return triple<StridedImageIterator<PixelType>,
1316 StridedImageIterator<PixelType>,
1317 MultibandVectorAccessor<PixelType> >
1318 (ul, ul + Size2D(img.shape(0), img.shape(1)),
1319 MultibandVectorAccessor<PixelType>(img.shape(2), img.stride(2)));
1320 }
1321
1322 template <class PixelType, class Stride>
1323 inline pair< StridedImageIterator<PixelType>,
1324 MultibandVectorAccessor<PixelType> >
1325 destImage(NumpyArray<3, Multiband<PixelType>, Stride> & img)
1326 {
1327 StridedImageIterator<PixelType>
1328 ul(img.data(), 1, img.stride(0), img.stride(1));
1329 return pair<StridedImageIterator<PixelType>, MultibandVectorAccessor<PixelType> >
1330 (ul, MultibandVectorAccessor<PixelType>(img.shape(2), img.stride(2)));
1331 }
1332
1333 template <class PixelType, class Stride>
1334 inline pair< ConstStridedImageIterator<PixelType>,
1335 MultibandVectorAccessor<PixelType> >
1336 maskImage(NumpyArray<3, Multiband<PixelType>, Stride> const & img)
1337 {
1338 ConstStridedImageIterator<PixelType>
1339 ul(img.data(), 1, img.stride(0), img.stride(1));
1340 return pair<ConstStridedImageIterator<PixelType>, MultibandVectorAccessor<PixelType> >
1341 (ul, MultibandVectorAccessor<PixelType>(img.shape(2), img.stride(2)));
1342 }
1343
1344 } // namespace vigra
1345
1346 #endif // VIGRA_NUMPY_ARRAY_HXX

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de) Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany | html generated using doxygen and Python vigra 1.10.0 (Thu Jan 8 2015)