Skip to content

Commit 5cc7204

Browse files
authored
Fix ups for PDAL 2.0 (#30)
* Checkpoint. * Fix tests. * Update for new name of MemoryViewReader. * clean up to catch up with PDAL 2.0 * add back logging test * shut off log test * bump version to 2.2.0 in preparation for release
1 parent 5ca8b4f commit 5cc7204

File tree

11 files changed

+492
-301
lines changed

11 files changed

+492
-301
lines changed

VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.7
1+
2.2.0

pdal/PyArray.cpp

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/******************************************************************************
2+
* Copyright (c) 2019, Hobu Inc. ([email protected])
3+
*
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following
8+
* conditions are met:
9+
*
10+
* * Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in
14+
* the documentation and/or other materials provided
15+
* with the distribution.
16+
* * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
17+
* names of its contributors may be used to endorse or promote
18+
* products derived from this software without specific prior
19+
* written permission.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28+
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29+
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31+
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32+
* OF SUCH DAMAGE.
33+
****************************************************************************/
34+
35+
#include "PyArray.hpp"
36+
#include <pdal/io/MemoryViewReader.hpp>
37+
38+
#include <numpy/arrayobject.h>
39+
40+
namespace pdal
41+
{
42+
namespace python
43+
{
44+
45+
namespace
46+
{
47+
48+
Dimension::Type pdalType(int t)
49+
{
50+
using namespace Dimension;
51+
52+
switch (t)
53+
{
54+
case NPY_FLOAT32:
55+
return Type::Float;
56+
case NPY_FLOAT64:
57+
return Type::Double;
58+
case NPY_INT8:
59+
return Type::Signed8;
60+
case NPY_INT16:
61+
return Type::Signed16;
62+
case NPY_INT32:
63+
return Type::Signed32;
64+
case NPY_INT64:
65+
return Type::Signed64;
66+
case NPY_UINT8:
67+
return Type::Unsigned8;
68+
case NPY_UINT16:
69+
return Type::Unsigned16;
70+
case NPY_UINT32:
71+
return Type::Unsigned32;
72+
case NPY_UINT64:
73+
return Type::Unsigned64;
74+
default:
75+
return Type::None;
76+
}
77+
assert(0);
78+
79+
return Type::None;
80+
}
81+
82+
std::string toString(PyObject *pname)
83+
{
84+
PyObject* r = PyObject_Str(pname);
85+
if (!r)
86+
throw pdal_error("couldn't make string representation value");
87+
Py_ssize_t size;
88+
return std::string(PyUnicode_AsUTF8AndSize(r, &size));
89+
}
90+
91+
} // unnamed namespace
92+
93+
Array::Array() : m_array(nullptr)
94+
{
95+
if (_import_array() < 0)
96+
throw pdal_error("Could not import numpy.core.multiarray.");
97+
}
98+
99+
Array::Array(PyArrayObject* array) : m_array(array), m_rowMajor(true)
100+
{
101+
if (_import_array() < 0)
102+
throw pdal_error("Could not import numpy.core.multiarray.");
103+
104+
Py_XINCREF(array);
105+
106+
PyArray_Descr *dtype = PyArray_DTYPE(m_array);
107+
npy_intp ndims = PyArray_NDIM(m_array);
108+
npy_intp *shape = PyArray_SHAPE(m_array);
109+
int numFields = (dtype->fields == Py_None) ?
110+
0 :
111+
static_cast<int>(PyDict_Size(dtype->fields));
112+
113+
int xyz = 0;
114+
if (numFields == 0)
115+
{
116+
if (ndims != 3)
117+
throw pdal_error("Array without fields must have 3 dimensions.");
118+
m_fields.push_back({"Intensity", pdalType(dtype->type_num), 0});
119+
}
120+
else
121+
{
122+
PyObject *names_dict = dtype->fields;
123+
PyObject *names = PyDict_Keys(names_dict);
124+
PyObject *values = PyDict_Values(names_dict);
125+
if (!names || !values)
126+
throw pdal_error("Bad field specification in numpy array.");
127+
128+
for (int i = 0; i < numFields; ++i)
129+
{
130+
std::string name = toString(PyList_GetItem(names, i));
131+
if (name == "X")
132+
xyz |= 1;
133+
else if (name == "Y")
134+
xyz |= 2;
135+
else if (name == "Z")
136+
xyz |= 4;
137+
PyObject *tup = PyList_GetItem(values, i);
138+
139+
// Get offset.
140+
size_t offset = PyLong_AsLong(PySequence_Fast_GET_ITEM(tup, 1));
141+
142+
// Get type.
143+
PyArray_Descr *descriptor =
144+
(PyArray_Descr *)PySequence_Fast_GET_ITEM(tup, 0);
145+
Dimension::Type type = pdalType(descriptor->type_num);
146+
if (type == Dimension::Type::None)
147+
throw pdal_error("Incompatible type for field '" + name + "'.");
148+
149+
m_fields.push_back({name, type, offset});
150+
}
151+
152+
if (xyz != 0 && xyz != 7)
153+
throw pdal_error("Array fields must contain all or none "
154+
"of X, Y and Z");
155+
if (xyz == 0 && ndims != 3)
156+
throw pdal_error("Array without named X/Y/Z fields "
157+
"must have three dimensions.");
158+
}
159+
if (xyz == 0)
160+
m_shape = { (size_t)shape[0], (size_t)shape[1], (size_t)shape[2] };
161+
m_rowMajor = !(PyArray_FLAGS(m_array) & NPY_ARRAY_F_CONTIGUOUS);
162+
}
163+
164+
Array::~Array()
165+
{
166+
if (m_array)
167+
Py_XDECREF((PyObject *)m_array);
168+
}
169+
170+
171+
void Array::update(PointViewPtr view)
172+
{
173+
if (m_array)
174+
Py_XDECREF((PyObject *)m_array);
175+
m_array = nullptr; // Just in case of an exception.
176+
177+
Dimension::IdList dims = view->dims();
178+
npy_intp size = view->size();
179+
180+
PyObject *dtype_dict = (PyObject*)buildNumpyDescription(view);
181+
if (!dtype_dict)
182+
throw pdal_error("Unable to build numpy dtype "
183+
"description dictionary");
184+
185+
PyArray_Descr *dtype = nullptr;
186+
if (PyArray_DescrConverter(dtype_dict, &dtype) == NPY_FAIL)
187+
throw pdal_error("Unable to build numpy dtype");
188+
Py_XDECREF(dtype_dict);
189+
190+
// This is a 1 x size array.
191+
m_array = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, dtype,
192+
1, &size, 0, nullptr, NPY_ARRAY_CARRAY, nullptr);
193+
194+
// copy the data
195+
DimTypeList types = view->dimTypes();
196+
for (PointId idx = 0; idx < view->size(); idx++)
197+
{
198+
char *p = (char *)PyArray_GETPTR1(m_array, idx);
199+
view->getPackedPoint(types, idx, p);
200+
}
201+
}
202+
203+
204+
//ABELL - Who's responsible for incrementing the ref count?
205+
PyArrayObject *Array::getPythonArray() const
206+
{
207+
return m_array;
208+
}
209+
210+
PyObject* Array::buildNumpyDescription(PointViewPtr view) const
211+
{
212+
// Build up a numpy dtype dictionary
213+
//
214+
// {'formats': ['f8', 'f8', 'f8', 'u2', 'u1', 'u1', 'u1', 'u1', 'u1',
215+
// 'f4', 'u1', 'u2', 'f8', 'u2', 'u2', 'u2'],
216+
// 'names': ['X', 'Y', 'Z', 'Intensity', 'ReturnNumber',
217+
// 'NumberOfReturns', 'ScanDirectionFlag', 'EdgeOfFlightLine',
218+
// 'Classification', 'ScanAngleRank', 'UserData',
219+
// 'PointSourceId', 'GpsTime', 'Red', 'Green', 'Blue']}
220+
//
221+
222+
Dimension::IdList dims = view->dims();
223+
224+
PyObject* dict = PyDict_New();
225+
PyObject* sizes = PyList_New(dims.size());
226+
PyObject* formats = PyList_New(dims.size());
227+
PyObject* titles = PyList_New(dims.size());
228+
229+
for (size_t i = 0; i < dims.size(); ++i)
230+
{
231+
Dimension::Id id = dims[i];
232+
Dimension::Type t = view->dimType(id);
233+
npy_intp stride = view->dimSize(id);
234+
235+
std::string name = view->dimName(id);
236+
237+
std::string kind("i");
238+
Dimension::BaseType b = Dimension::base(t);
239+
if (b == Dimension::BaseType::Unsigned)
240+
kind = "u";
241+
else if (b == Dimension::BaseType::Signed)
242+
kind = "i";
243+
else if (b == Dimension::BaseType::Floating)
244+
kind = "f";
245+
else
246+
throw pdal_error("Unable to map kind '" + kind +
247+
"' to PDAL dimension type");
248+
249+
std::stringstream oss;
250+
oss << kind << stride;
251+
PyObject* pySize = PyLong_FromLong(stride);
252+
PyObject* pyTitle = PyUnicode_FromString(name.c_str());
253+
PyObject* pyFormat = PyUnicode_FromString(oss.str().c_str());
254+
255+
PyList_SetItem(sizes, i, pySize);
256+
PyList_SetItem(titles, i, pyTitle);
257+
PyList_SetItem(formats, i, pyFormat);
258+
}
259+
260+
PyDict_SetItemString(dict, "names", titles);
261+
PyDict_SetItemString(dict, "formats", formats);
262+
263+
return dict;
264+
}
265+
266+
bool Array::rowMajor() const
267+
{
268+
return m_rowMajor;
269+
}
270+
271+
Array::Shape Array::shape() const
272+
{
273+
return m_shape;
274+
}
275+
276+
const Array::Fields& Array::fields() const
277+
{
278+
return m_fields;
279+
}
280+
281+
ArrayIter& Array::iterator()
282+
{
283+
ArrayIter *it = new ArrayIter(*this);
284+
m_iterators.push_back(std::unique_ptr<ArrayIter>(it));
285+
return *it;
286+
}
287+
288+
ArrayIter::ArrayIter(Array& array)
289+
{
290+
m_iter = NpyIter_New(array.getPythonArray(),
291+
NPY_ITER_EXTERNAL_LOOP | NPY_ITER_READONLY | NPY_ITER_REFS_OK,
292+
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
293+
if (!m_iter)
294+
throw pdal_error("Unable to create numpy iterator.");
295+
296+
char *itererr;
297+
m_iterNext = NpyIter_GetIterNext(m_iter, &itererr);
298+
if (!m_iterNext)
299+
{
300+
NpyIter_Deallocate(m_iter);
301+
throw pdal_error(std::string("Unable to create numpy iterator: ") +
302+
itererr);
303+
}
304+
m_data = NpyIter_GetDataPtrArray(m_iter);
305+
m_stride = NpyIter_GetInnerStrideArray(m_iter);
306+
m_size = NpyIter_GetInnerLoopSizePtr(m_iter);
307+
m_done = false;
308+
}
309+
310+
ArrayIter::~ArrayIter()
311+
{
312+
NpyIter_Deallocate(m_iter);
313+
}
314+
315+
ArrayIter& ArrayIter::operator++()
316+
{
317+
if (m_done)
318+
return *this;
319+
320+
if (--(*m_size))
321+
*m_data += *m_stride;
322+
else if (!m_iterNext(m_iter))
323+
m_done = true;
324+
return *this;
325+
}
326+
327+
ArrayIter::operator bool () const
328+
{
329+
return !m_done;
330+
}
331+
332+
char * ArrayIter::operator * () const
333+
{
334+
return *m_data;
335+
}
336+
337+
} // namespace python
338+
} // namespace pdal
339+

0 commit comments

Comments
 (0)