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