Skip to content

Commit

Permalink
Merge pull request #265 from stevengj/reverse_dims
Browse files Browse the repository at this point in the history
PyReverseDims(::StridedArray) constructor
  • Loading branch information
stevengj committed Apr 22, 2016
2 parents c5daa0c + 54dc0ae commit 6e42b1d
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 14 deletions.
14 changes: 13 additions & 1 deletion README.md
Expand Up @@ -211,7 +211,19 @@ in Python (assuming that the object is callable in Python). In Julia
`pycall` form is still useful in Julia 0.4 if you want to specify the
return type.

#### PyArray
#### Arrays and PyArray

##### From Julia to Python

Assuming you have NumPy installed (true by default if you use Conda),
then a Julia `a::Array` of NumPy-compatible elements is converted
by `PyObject(a)` into a NumPy wrapper for the *same data*, i.e. without
copying the data. Julia arrays are stored in [column-major order](https://en.wikipedia.org/wiki/Row-major_order),
and since NumPy supports column-major arrays this is not a problem.

However, the *default* ordering of NumPy arrays created in *Python* is row-major, and some Python packages will throw an error if you try to pass them column-major NumPy arrays. To deal with this, you can use `PyReverseDims(a)` to pass a Julia array as a row-major NumPy array with the dimensions *reversed*. For example, if `a` is a 3x4x5 Julia array, then `PyReverseDims(a)` passes it as a 5x4x3 NumPy row-major array (without making a copy of the underlying data).

##### From Python to Julia

Multidimensional NumPy arrays (`ndarray`) are supported and can be
converted to the native Julia `Array` type, which makes a copy of the data.
Expand Down
2 changes: 1 addition & 1 deletion src/PyCall.jl
Expand Up @@ -2,7 +2,7 @@ __precompile__()

module PyCall

export pycall, pyimport, pybuiltin, PyObject,
export pycall, pyimport, pybuiltin, PyObject, PyReverseDims,
PyPtr, pyincref, pydecref, pyversion, PyArray, PyArray_Info,
pyerr_check, pyerr_clear, pytype_query, PyAny, @pyimport, PyDict,
pyisinstance, pywrap, pytypeof, pyeval, PyVector, pystring,
Expand Down
50 changes: 38 additions & 12 deletions src/numpy.jl
Expand Up @@ -175,22 +175,48 @@ const npy_typestrs = Dict( "b1"=>Bool,
#########################################################################
# no-copy conversion of Julia arrays to NumPy arrays.

# Julia arrays are in column-major order, but in some cases it is useful
# to pass them to Python as row-major arrays simply by reversing the
# dimensions. For example, although NumPy works with both row-major and
# column-major data, some Python libraries like OpenCV seem to require
# row-major data (the default in NumPy). In such cases, use PyReverseDims(array)
function NpyArray{T<:NPY_TYPES}(a::StridedArray{T}, revdims::Bool)
@npyinitialize
size_a = revdims ? reverse(size(a)) : size(a)
strides_a = revdims ? reverse(strides(a)) : strides(a)
p = @pycheck ccall(npy_api[:PyArray_New], PyPtr,
(PyPtr,Cint,Ptr{Int},Cint, Ptr{Int},Ptr{T}, Cint,Cint,PyPtr),
npy_api[:PyArray_Type],
ndims(a), Int[size_a...], npy_type(T),
Int[strides_a...] * sizeof(eltype(a)), a, sizeof(eltype(a)),
NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE,
C_NULL)
return PyObject(p, a)
end

function PyObject{T<:NPY_TYPES}(a::StridedArray{T})
try
@npyinitialize
p = @pycheck ccall(npy_api[:PyArray_New], PyPtr,
(PyPtr,Cint,Ptr{Int},Cint, Ptr{Int},Ptr{T}, Cint,Cint,PyPtr),
npy_api[:PyArray_Type],
ndims(a), Int[size(a)...], npy_type(T),
Int[strides(a)...] * sizeof(eltype(a)), a, sizeof(eltype(a)),
NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE,
C_NULL)
return PyObject(p, a)
catch e
return NpyArray(a, false)
catch
array2py(a) # fallback to non-NumPy version
end
end

function PyReverseDims{T<:NPY_TYPES}(a::StridedArray{T})
return NpyArray(a, true)
end

"""
PyReverseDims(array)
Passes a Julia `array` to Python as a NumPy row-major array
(rather than Julia's native column-major order) with the
dimensions reversed (e.g. a 2×3×4 Julia array is passed as
a 4×3×2 NumPy row-major array). This is useful for Python
libraries that expect row-major data.
"""
PyReverseDims(a::AbstractArray)

#########################################################################
# Extract shape and other information about a NumPy array. We need
# to call the Python interface to do this, since the equivalent information
Expand Down Expand Up @@ -267,9 +293,9 @@ c_contiguous(i::PyArray_Info) = f_contiguous(i.T, flipdim(i.sz,1), flipdim(i.st,
This converts an `ndarray` object `o` to a PyArray.
This implements a nocopy wrapper to a NumPy array (currently of only numeric types only).
This implements a nocopy wrapper to a NumPy array (currently of only numeric types only).
If you are using `pycall` and the function returns an `ndarray`, you can use `PyArray` as the return type to directly receive a `PyArray`.
If you are using `pycall` and the function returns an `ndarray`, you can use `PyArray` as the return type to directly receive a `PyArray`.
"""
type PyArray{T,N} <: AbstractArray{T,N}
o::PyObject
Expand Down
3 changes: 3 additions & 0 deletions test/runtests.jl
Expand Up @@ -54,6 +54,9 @@ testkw(x; y=0) = x + 2*y

if PyCall.npy_initialized
@test PyArray(PyObject([1. 2 3;4 5 6])) == [1. 2 3;4 5 6]
let A = rand(Int, 2,3,4)
@test convert(PyAny, PyReverseDims(A)) == permutedims(A, [3,2,1])
end
end
@test PyVector(PyObject([1,3.2,"hello",true])) == [1,3.2,"hello",true]
@test PyDict(PyObject(Dict(1 => "hello", 2 => "goodbye"))) == Dict(1 => "hello", 2 => "goodbye")
Expand Down

0 comments on commit 6e42b1d

Please sign in to comment.