Numpy: Funções indices e meshgrid

As funções indices e meshgrid são extremamente úteis na geração de imagens sintéticas e o seu aprendizado permite também entender as vantagens de programação matricial, evitando-se a varredura seqüencial da imagem muito usual na programação na linguagem C.

Operador indices em pequenos exemplos numéricos

A função indices recebe como parâmetros uma tupla com as dimensões (H,W) das matrizes a serem criadas. No exemplo a seguir, estamos gerando matrizes de 5 linhas e 10 colunas. Esta função retorna uma tupla de duas matrizes que podem ser obtidas fazendo suas atribuições como no exemplo a seguir onde criamos as matrizes r e c, ambas de tamanho (5,10), isto é, 5 linhas e 10 colunas:


In [1]:
import numpy as np
r,c = np.indices( (5, 10) )
print('r=\n', r)
print('c=\n', c)


r=
 [[0 0 0 0 0 0 0 0 0 0]
 [1 1 1 1 1 1 1 1 1 1]
 [2 2 2 2 2 2 2 2 2 2]
 [3 3 3 3 3 3 3 3 3 3]
 [4 4 4 4 4 4 4 4 4 4]]
c=
 [[0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]]

Note que a matriz r é uma matriz onde cada elemento é a sua coordenada linha e a matriz c é uma matriz onde cada elemento é a sua coordenada coluna. Desta forma, qualquer operação matricial feita com r e c, na realidade você está processando as coordenadas da matriz. Assim, é possível gerar diversas imagens sintéticas a partir de uma função de suas coordenadas.

Como o NumPy processa as matrizes diretamente, sem a necessidade de fazer um for explícito, a notação do programa fica bem simples e a eficiência também. O único inconveniente é o uso da memória para se calcular as matrizes de índices r e c. Iremos ver mais à frente que isto pode ser minimizado.

Por exemplo seja a função que seja a soma de suas coordenadas $f(r,c) = r + c$:


In [2]:
f = r + c
print('f=\n', f)


f=
 [[ 0  1  2  3  4  5  6  7  8  9]
 [ 1  2  3  4  5  6  7  8  9 10]
 [ 2  3  4  5  6  7  8  9 10 11]
 [ 3  4  5  6  7  8  9 10 11 12]
 [ 4  5  6  7  8  9 10 11 12 13]]

Ou ainda a função diferença entre a coordenada linha e coluna $f(r,c) = r - c$:


In [3]:
f = r - c
print('f=\n', f)


f=
 [[ 0 -1 -2 -3 -4 -5 -6 -7 -8 -9]
 [ 1  0 -1 -2 -3 -4 -5 -6 -7 -8]
 [ 2  1  0 -1 -2 -3 -4 -5 -6 -7]
 [ 3  2  1  0 -1 -2 -3 -4 -5 -6]
 [ 4  3  2  1  0 -1 -2 -3 -4 -5]]

Ou ainda a função $f(r,c) = (r + c) \% 2$ onde % é operador módulo. Esta função retorna 1 se a soma das coordenadas for ímpar e 0 caso contrário. É uma imagem no estilo de um tabuleiro de xadrez de valores 0 e 1:


In [4]:
f = (r + c) % 2
print('f=\n', f)


f=
 [[0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]]

Ou ainda a função de uma reta $ f(r,c) = (r = \frac{1}{2}c)$:


In [5]:
f = (r == c//2)
print('f=\n', f)
print('f=\n',f.astype(np.int))


f=
 [[ True  True False False False False False False False False]
 [False False  True  True False False False False False False]
 [False False False False  True  True False False False False]
 [False False False False False False  True  True False False]
 [False False False False False False False False  True  True]]
f=
 [[1 1 0 0 0 0 0 0 0 0]
 [0 0 1 1 0 0 0 0 0 0]
 [0 0 0 0 1 1 0 0 0 0]
 [0 0 0 0 0 0 1 1 0 0]
 [0 0 0 0 0 0 0 0 1 1]]

Ou ainda a função parabólica dada pela soma do quadrado de suas coordenadas $$ f(r,c) = r^2 + c^2 $$:


In [6]:
f = r**2 + c**2
print('f=\n', f)


f=
 [[ 0  1  4  9 16 25 36 49 64 81]
 [ 1  2  5 10 17 26 37 50 65 82]
 [ 4  5  8 13 20 29 40 53 68 85]
 [ 9 10 13 18 25 34 45 58 73 90]
 [16 17 20 25 32 41 52 65 80 97]]

Ou ainda a função do círculo de raio 4, com centro em (0,0) $f(r,c) = (r^2 + c^2 < 4^2)$:


In [7]:
f = ((r**2 + c**2) < 4**2)
print('f=\n', f * 1)


f=
 [[1 1 1 1 0 0 0 0 0 0]
 [1 1 1 1 0 0 0 0 0 0]
 [1 1 1 1 0 0 0 0 0 0]
 [1 1 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]

Operador indices em exemplo de imagens sintéticas

Vejamos os exemplos acima, porém gerados em imagens. A diferença será no tamanho da matriz, iremos utilizar matriz (200,300), e a forma de visualizá-la através do adshow, ao invés de imprimir os valores como fizemos acima. Como muitas vezes o resultado das funções poderão estar fora da faixa 0-255 admitida pelo adshow, iremos sempre normalizar os valores finais da imagem calculada para a faixa 0-255 utilizando a função ia636:ianormalize ianormalize da toolbox ia636.

Gerando as coordenadas utilizando indices:

Observe que o parâmetro de indices é uma tupla. Verifique o número de parêntesis utilizados:


In [8]:
import numpy as np
import sys,os
ia898path = os.path.abspath('../../')
if ia898path not in sys.path:
    sys.path.append(ia898path)
import ia898.src as ia
nb = ia.nbshow(3)

In [9]:
r,c = np.indices( (200, 300) )
rn = ia.normalize(r)
cn = ia.normalize(c)
nb.nbshow(rn,'rn:linhas')
nb.nbshow(cn,'cn:colunas',flush=True)


rn:linhas
cn:colunas

Soma

Função soma: $f(r,c) = r + c$:


In [10]:
f = r + c
ia.adshow(ia.normalize(f),'r + c')


r + c

Subtração

Função subtração $f(r,c) = r - c$:


In [11]:
f = r - c
ia.adshow(ia.normalize(f),'r - c')


r - c

In [12]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(f,cmap='gray')
plt.colorbar()


Out[12]:
<matplotlib.colorbar.Colorbar at 0x114e9ce10>

Xadrez

Função xadrez $f(r,c) = \frac{(r + c)}{8} \% 2$. Aqui foi feita a divisão por 8 para que o tamanho das casas do xadrez fique 8 x 8, caso contrário é muito difícil de visualizar o efeito xadrez pois a imagem possui muitos pixels.:


In [13]:
f = (r//8 + c//8) % 2
ia.adshow(ia.normalize(f),'(r//8 + c//8) % 2')


(r//8 + c//8) % 2

Reta

Ou ainda a função de uma reta $f(r,c) = (r = \frac{1}{2} c)$:


In [14]:
f = (r == c//2)
ia.adshow(f,'r == c//2')


r == c//2

Parábola

Função parabólica: $f(r,c) = r^2 + c^2$:


In [15]:
f = r**2 + c**2
ia.adshow(ia.normalize(f),'r**2 + c**2')


r**2 + c**2

Círculo

Função do círculo de raio 190, $f(r,c) = (r^2 + c^2 < 190^2)$:


In [16]:
f = (r**2 + c**2 < 190**2)
ia.adshow(ia.normalize(f),'r**2 + c**2) < 190**2')


r**2 + c**2) < 190**2

Meshgrid

A função meshgrid é semelhante à função indices visto anteriormente, porém, enquanto indices gera as coordenadas inteiras não negativas a partir de um shape(H,W), o meshgrid gera os valores das matrizes a partir de dois vetores de valores reais quaisquer, um para as linhas e outro para as colunas.

Veja a seguir um pequeno exemplo numérico. Para que o meshgrid fique compatível com a nossa convenção de (linhas,colunas), deve-se usar o parâmetro indexing='ij'.


In [17]:
import numpy as np
r, c = np.meshgrid( np.array([-1.5, -1.0, -0.5, 0.0, 0.5]), 
                    np.array([ -20,  -10,    0,  10,  20, 30]), indexing='ij')
print('r=\n',r)
print('c=\n',c)


r=
 [[-1.5 -1.5 -1.5 -1.5 -1.5 -1.5]
 [-1.  -1.  -1.  -1.  -1.  -1. ]
 [-0.5 -0.5 -0.5 -0.5 -0.5 -0.5]
 [ 0.   0.   0.   0.   0.   0. ]
 [ 0.5  0.5  0.5  0.5  0.5  0.5]]
c=
 [[-20 -10   0  10  20  30]
 [-20 -10   0  10  20  30]
 [-20 -10   0  10  20  30]
 [-20 -10   0  10  20  30]
 [-20 -10   0  10  20  30]]

Gerando os vetores com linspace

A função linspace gera vetor em ponto flutuante recebendo os parâmetro de valor inicial, valor final e número de pontos do vetor. Desta forma ele é bastante usado para gerar os parâmetro para o meshgrid.

Repetindo os mesmos valores do exemplo anterior, porém usando linspace. Observe que o primeiro vetor possui 5 pontos, começando com valor -1.5 e o valor final é 0.5 (inclusive). O segundo vetor possui 6 pontos, começando de -20 até 30:


In [18]:
rows = np.linspace(-1.5, 0.5, 5)
cols = np.linspace(-20, 30, 6)

print('rows:', rows)
print('cols:', cols)


rows: [-1.5 -1.  -0.5  0.   0.5]
cols: [-20. -10.   0.  10.  20.  30.]

Usando os dois vetores gerados pelo linspace no meshgrid:


In [19]:
r, c = np.meshgrid(rows, cols, indexing='ij')
print('r = \n', r)
print('c = \n', c)


r = 
 [[-1.5 -1.5 -1.5 -1.5 -1.5 -1.5]
 [-1.  -1.  -1.  -1.  -1.  -1. ]
 [-0.5 -0.5 -0.5 -0.5 -0.5 -0.5]
 [ 0.   0.   0.   0.   0.   0. ]
 [ 0.5  0.5  0.5  0.5  0.5  0.5]]
c = 
 [[-20. -10.   0.  10.  20.  30.]
 [-20. -10.   0.  10.  20.  30.]
 [-20. -10.   0.  10.  20.  30.]
 [-20. -10.   0.  10.  20.  30.]
 [-20. -10.   0.  10.  20.  30.]]

Podemos agora gerar uma matriz ou imagem que seja função destes valores. Por exemplo ser o produto deles:


In [20]:
f = r * c
print('f=\n', f)


f=
 [[ 30.  15.  -0. -15. -30. -45.]
 [ 20.  10.  -0. -10. -20. -30.]
 [ 10.   5.  -0.  -5. -10. -15.]
 [ -0.  -0.   0.   0.   0.   0.]
 [-10.  -5.   0.   5.  10.  15.]]

Exemplo na geração da imagem sinc com meshgrid

Neste exemplo, geramos a imagem da função $ sinc(r,c)$ em duas dimensões, nos intervalos na vertical, de -5 a 5 e na horizontal de -6 a 6. A função sinc é uma função trigonométrica que pode ser utilizada para filtragens.

A equação é dada por:

$$ sinc(r,c) = \frac{\sin(r^2 + c^2)}{r^2 + c^2}, \text{para\ } -5 \leq r \leq 5, -6 \leq c \leq 6 $$

Na origem, tanto r como c são zeros, resultando uma divisão por zero. Entretanto pela teoria dos limites, $\frac{sin(x)}{x}$ é igual a 1 quando $x$ é igual a zero. Uma forma de se obter isto em ponto flutuante é somar tanto no numerador como no denominador um epsilon, que é a menor valor em ponto flutuante. Epsilon pode ser obtido pela função np.spacing.


In [29]:
e = np.spacing(1) # epsilon to avoid 0/0
rows = np.linspace(-5.0, 5.0, 150) # coordenadas das linhas
cols = np.linspace(-6.0, 6.0, 180) # coordenadas das colunas
r, c = np.meshgrid(rows, cols, indexing='ij') # Grid de coordenadas estilo numpy
z = np.sin(r**2 + c**2 + e) / (r**2 + c**2 + e) # epsilon is added to avoid 0/0
ia.adshow(ia.normalize(z),'Função sinc: sen(r² + c²)/(r²+c²) em duas dimensões')


Função sinc: sen(r² + c²)/(r²+c²) em duas dimensões

In [30]:
plt.imshow(z)
plt.colorbar()


Out[30]:
<matplotlib.colorbar.Colorbar at 0x11502bc50>

Exemplo na geração da imagem sinc com indices

Outra forma de gerar a mesma imagem, usando a função indices é processar os indices de modo a gerar os mesmos valores relativos à grade de espaçamento regular acima, conforme ilustrado abaixo:


In [32]:
n_rows = len(rows)
n_cols = len(cols)
r,c = np.indices((n_rows,n_cols))
r = -5. + 10.*r.astype(float)/(n_rows-1)
c = -6. + 12.*c.astype(float)/(n_cols-1)
zi = np.sin(r**2 + c**2 + e) / (r**2 + c**2 + e) # epsilon is addes to avoid 0/0
ia.adshow(ia.normalize(zi),'Função sinc: sin(r² + c²)/(r²+c²) em duas dimensões')


Função sinc: sin(r² + c²)/(r²+c²) em duas dimensões

Verificando que as duas funções são iguais:


In [33]:
print('Máxima diferença entre z e zi?', abs(z - zi).max())


Máxima diferença entre z e zi? 1.22124532709e-15

In [34]:
np.exp(-1/2.)


Out[34]:
0.60653065971263342

Documentação Oficial Numpy

Para usuários avançados

Na realidade a função indices retorna um único array n-dimensional com uma dimensão a mais que o indicado pelo shape usado como parâmetro. Assim, quando é feito r,c = np.indices((rows,cols)), r é atribuído para o elemento 0 e c é atribuído para o elemento 1 do ndarray. No caso do meshgrid, ele retorna tantos arrays quanto forem o número de vetores passados como parâmetro para meshgrid.

Exemplos com Imagem

  • ia636:iacircle Uso do meshgrid na função iacircle
  • ia636:iarectangle Uso meshgrid na função iarectangle
  • ia636:iacos Uso de indices na função iacos - cossenóide
  • ia636:ialog Uso de indices na função ialog - Laplaciano da gaussiana