1

I am trying to make the Hadamard product of a 3-D with a 2-D array. The 2-D array shares the shape of the first two axes of the 3-D array and should be moved along the 2 axis (thus, the 3rd) for the multiplications, meaning: make Hadamard product with slice 0, then slice 1, and so on (cf. image, schematic).

The original 3-D array is an opencv image, thus has a shape of f.e. (1080, 1920, 3). The 2-D-array is one slice of this image, thus has a shape of (1080, 1920)

Is there a way to do this without loops or specifying each slice on its own? Or are loops the way to go here?

What works is:

    img_new = np.zeros(shape=img.shape[:2])
    img_new[0] = (img[:, :, 1] * img[:, :, 0])[0]
    img_new[1] = (img[:, :, 2] * img[:, :, 0])[1]

However, I would prefer not to have this calculation 2 times in the code.

I have tried:

    img_new = np.multiply(img_cvt[:, :, 1:3], img[:, :, 0])

Although this works when using a 2-D and a 1-D array

>>> a = np.array(((1,2),(3,4)))
>>> b = np.array((5,8))
>>> np.multiply(a,b)
array([[ 5, 16],
       [15, 32]])

It gives a broadcasting error in the 3-D/2-D case:

ValueError: operands could not be broadcast together with shapes (1080,1920,2) (1080,1920)

Same applies to np.apply_along_axis:

img_new = np.apply_along_axis(np.multiply, 2, img[:, :, 1:3], img[:, :, 0])

Which yields the following:

ValueError: operands could not be broadcast together with shapes (2,) (1080,1920)

But I guess this could not work because it is designed for 1d functions...

Desired array multiplication

lcnittl
  • 233
  • 1
  • 14

2 Answers2

2

Have a look at how broadcasting works. Essentially you can append an axis to perform element wise operations, for example this works

import numpy as np

a = np.random.rand(10, 3)
b = np.random.rand(10, 3, 2)

c = b * a[..., np.newaxis] # make a shape 10 x 3 x 1
cvanelteren
  • 1,633
  • 9
  • 16
  • Pretty neat, looks like I missed some basic functionality here! Thx – lcnittl Dec 24 '18 at 00:19
  • To use a slice of b to multiply with every slice of b, one need to do `np.multiply(b,b[:, :, 1, np.newaxis])`? – lcnittl Dec 24 '18 at 00:23
  • To multiple a subset of slices you can use the index methods outlined in the link above. Your specific example would need this; b * b[:, :, [1]], using the multiply function is equivalent if sliced as shown. Edit: the * here is not a dot product, if that is causing the confusion. – cvanelteren Dec 24 '18 at 00:29
  • Both seem work, but the bracket in bracket method looks nicer then having the `np.newaxis` after splice definition. – lcnittl Dec 24 '18 at 00:33
  • Not confused about the `*`, was wondering about slicing and adding a new axis in the same step. But your answer clarified that already. (Although I cannot find the bracket in bracket method in the link provided) – lcnittl Dec 24 '18 at 00:37
  • 1
    My apologies. You are correct the method with brackets is not explicitly mentioned, but implied through the requirements for broadcasting. I should have been clearer. Another recommended read is [indexing](https://docs.scipy.org/doc/numpy-1.15.0/user/basics.indexing.html) in numpy which shows different ways to do this. – cvanelteren Dec 24 '18 at 01:17
0

You can use the np.expand_dims() function for this.

Create a tuple with the missing dimensions using np.arange(), then just add them. (I assume you want to add at the end - you can always use transpose to achieve that):

c = np.array([
        [[1, 2, 3, 4],[5, 6, 7, 8]],
        [[.1, .2, .3, .4], [.5, .6, .7, .8]]
    ])
d = np.array([.5, .6])

cdim = len(c.shape)
ddim = len(d.shape)
newdims = tuple(np.arange(start=ddim, stop=cdim))
dx = np.expand_dims(d, newdims)

c + dx
c * dx

Obviously you can do all of that in one line - the variables are just for clarity:

def match_dim(b, A):
    "Adds dimensions of length 1 to b to make it dimension compliant with A and returns the expanded structure"
    return np.expand_dims(b, tuple(np.arange(start=len(b.shape), stop=len(A.shape))))
theletz
  • 1,713
  • 2
  • 16
  • 22
Tom O
  • 1
  • 2