# Avoiding numpy with PythonCall

**URL:** <https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505>\
**Category:** General Usage\
**Tags:** question, pythoncall\
**Created:** [January 25, 2023, 12:31pm UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505 "2023-01-25T12:31:44Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [January 25, 2023, 12:31pm UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/1 "2023-01-25T12:31:44Z")

</div>

I’m trying to avoid using `numpy` in the following MWE.

The main goal here is to construct the object-points argument, `objectPoints`, needed in OpenCV’s camera calibration [function](https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga3207604e4b1a1758aa66acb6ed5aa65d), `calibrateCamera`.

The following creates the correct `objectPoints` object:

```julia
using PythonCall

using CondaPkg
CondaPkg.add("numpy")

np = pyimport("numpy")

nc = (2, 3)
n = 4

objp = np.zeros((1, *(nc...), 3), np.float32)
objectPoints = PyList(fill(objp, n))

```

Is there a way, and if so how, to create a _similar_ `objectPoints` without using `numpy`? My main test for if the alternative is similar or not is if `calibrateCamera` accepts it or not…

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [January 25, 2023, 9:36pm UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/2 "2023-01-25T21:36:32Z")

</div>

When a Julia array is passed to Python with PythonCall, it is given the numpy array interface. This means that you can pass Julia arrays to most functions which accept numpy arrays.

So replacing `np.zeros` with `zeros` should work:

```julia
objp = zeros(Float32, (1, *(nc...), 3))
objectPoints = pylist(fill(objp, n))

```

Furthermore, the conversion to a Python list might be unnecessary:

```julia
objectPoints = fill(objp, n)

```

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [January 26, 2023, 9:24am UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/3 "2023-01-26T09:24:58Z")

</div>

Thanks for the suggestion!

However, here is a more complete MWE that illustrates a problem with your suggestion.

The following works:

```julia
using PythonCall

using CondaPkg
CondaPkg.add.(["numpy", "opencv"])

np = pyimport("numpy")
cv2 = pyimport("cv2")

nc = (5, 8) # number of corners in the calibration checkerboard
n = 9 # number of images of the checkerboard
sz = (10, 10) # image dimensions

objp = np.zeros((1, *(nc...), 3), np.float32)
# the following just populates the object points with real-world coordinates
for (i, v) in enumerate(repeat(1:nc[1], outer = nc[2]))
    objp[0][i - 1][0] = v - 1
end
for (i, v) in enumerate(repeat(1:nc[2], inner = nc[1]))
    objp[0][i - 1][1] = v - 1
end
objectPoints = fill(objp, n)

imgp = np.zeros((*(nc...), 1, 2), np.float32)
# the following just populates the image points with random pixel coordinates
for i in 1:*(nc...)
    imgp[i - 1] = rand()
end
imagePoints = fill(imgp, n)

# the following call works
cv2.calibrateCamera(objectPoints, imagePoints, sz, nothing, nothing)

```

But if I replace `objp` with your suggestion:

```julia
objp = zeros(Float32, (1, *(nc...), 3))
# and fill it with the same real-world coordinates
objp[1, :, 1] .= repeat(0:nc[1] - 1, outer = nc[2])
objp[1, :, 2] .= repeat(0:nc[2] - 1, inner = nc[1])

```

the call to `cv2.calibrateCamera` fails with:

```julia
julia> cv2.calibrateCamera(objectPoints, imagePoints, sz, nothing, nothing)
ERROR: Python: error: OpenCV(4.7.0) :-1: error: (-5:Bad argument) in function 'calibrateCamera'
> Overload resolution failed:
> - Can't parse 'objectPoints'. Sequence item with index 0 has a wrong type
> - Can't parse 'objectPoints'. Sequence item with index 0 has a wrong type

Python stacktrace: none
Stacktrace:
 [1] pythrow()
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/err.jl:94
 [2] errcheck
   @ ~/.julia/packages/PythonCall/3GRYN/src/err.jl:10 [inlined]
 [3] pycallargs(f::Py, args::Py)
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/abstract/object.jl:210
 [4] pycall(::Py, ::Vector{Array{Float32, 3}}, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/abstract/object.jl:228
 [5] pycall(::Py, ::Vector{Array{Float32, 3}}, ::Vararg{Any})
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/abstract/object.jl:218
 [6] (::Py)(::Vector{Array{Float32, 3}}, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/Py.jl:352
 [7] (::Py)(::Vector{Array{Float32, 3}}, ::Vararg{Any})
   @ PythonCall ~/.julia/packages/PythonCall/3GRYN/src/Py.jl:352
 [8] top-level scope
   @ REPL[103]:1

```

To me, `Can't parse 'objectPoints'. Sequence item with index 0 has a wrong type` seems to suggest that the type of the elements in `objectPoints` is wrong.

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [January 26, 2023, 4:53pm UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/4 "2023-01-26T16:53:02Z")

</div>

It looks like OpenCV only accepts actual numpy arrays, and not array-like objects: [opencv/cv2\_convert.cpp at 8bd17163c79231a4afbbdf978864cd2d0c6b1f67 · opencv/opencv · GitHub](https://github.com/opencv/opencv/blob/8bd17163c79231a4afbbdf978864cd2d0c6b1f67/modules/python/src2/cv2_convert.cpp#L75)

You could make a PR to have it accept anything satisfying the array interface.

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [January 27, 2023, 8:01am UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/5 "2023-01-27T08:01:57Z")

</div>

Ah, I see. OK, I think I’ll live with `numpy` then. Changing `opencv` just so I won’t need to use `numpy` with `PythonCall` seems a bit extreme (unless there are other legitimate reasons to change that type check?).

Thanks for the detective work though!

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [January 27, 2023, 8:29am UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/6 "2023-01-27T08:29:45Z")

</div>

No problem. You can nevertheless do more in Julia - you can construct `objp` as an ordinary Julia array and convert it to numpy afterwards.

Stuff like this is a bit of a gripe of mine. Python is supposed to be “duck typed” meaning you rely on interfaces (e.g. does it iterate, can it convert to array) than checking for specific types, which is what OpenCV does here.

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [April 6, 2023, 3:06pm UTC](https://discourse.julialang.org/t/avoiding-numpy-with-pythoncall/93505/7 "2023-04-06T15:06:18Z")

</div>

I managed to do exactly what you suggested. The ugly part is that I have these conversion functions that convert julia things into python things just because of those type-checks. It’s fine. Thanks again!
