O tipo ndarray, ou apenas array é um arranjo de itens homogêneos de dimensionalidade N, indexados por uma tupla de N inteiros. Existem 3 informações essenciais associadas ao ndarray: o tipo dos dados, suas dimensões e seus dados em si. A propriedade dtype permite saber o tipo de dados enquanto que shape é uma tupla que indica o tamanho de cada dimensão do arranjo. O acesso aos dados em si deve ser feito por indexação, por fatiamento ou pela variável em si.
Existem várias maneiras de criar uma variável do tipo ndarray. Por exemplo, é possível criar uma a partir de uma lista (1D) ou lista de listas usando a função array. O tipo de matriz resultante é deduzida a partir do tipo de elementos nas sequências.
Veja a seguir um vetor de inteiros com 5 elementos. Note que o vetor é uma linha com 5 colunas. Observe também que o shape é uma tupla de um único elemento (veja a vírgula que aparece por ser uma tupla).
In [65]:
import numpy as np
a = np.array( [2,3,4,-1,-2] )
print('Dimensões: a.shape=', a.shape )
print('Tipo dos elementos: a.dtype=', a.dtype )
print('Imprimindo o array completo:\n a=',a )
Veja a seguir uma matriz bidimensional de dados ponto flutuante de 2 linhas e 3 colunas. Observe que a tupla do shape aumenta para a esquerda,
isto é, se eu tiver um vetor de 3 elementos, o seu shape é (3,) e se uma nova dimensão for adicionada, por exemplo 2 linhas e 3 colunas, o
shape passa a ser (3,2). O que antes shape[0] no vetor unidimensional era colunas, já na matriz bidimensional shape[0] passou a ser o número
de linhas.
Assim o último elemento da tupla do shape
indica o número de colunas, a penúltima o número de linhas. Assim se quisermos sempre buscar
o número de colunas, independentemente do número de dimensões, shape[-1] informa sempre o número de colunas, shape[-2], o número de linhas.
In [66]:
b = np.array( [ [1.5, 2.3, 5.2],
[4.2, 5.6, 4.4] ] )
print('Um array bidimensional, dimensões:(b.shape=', b.shape )
print('Tipo dos elementos: b.dtype', b.dtype )
print('Número de colunas:', b.shape[-1] )
print('Número de linhas:', b.shape[-2] )
print('Elementos, b=\n', b )
É possível criar arrays de zeros, uns ou valores não inicializados usando as funções zeros, ones ou empty. As dimensões do array são obrigatórias e é dado por uma tupla e o tipo dos elementos é opcional, sendo que o default é tipo float.
O código a seguir cria 3 arrays. O primeiro possui 2 linhas e 4 colunas. O segundo tem 3 dimensões: 3 fatias (ou imagens) onde cada uma tem 2 linhas e 5 colunas. Por último, é criado uma matriz booleana (True e False) de valores não inicializados de 2 linhas e 3 colunas. A vantagem do empty é que ele é mais rápido que o zeros ou ones. No caso abaixo, os valores mostrados na matrix criada pelo empty são aleatórios.
In [67]:
d = np.zeros( (2,4) )
print('Array de 0s: \n', d )
d = np.ones( (3,2,5), dtype='int16' )
print('\n\nArray de 1s: \n', d )
d = np.empty( (2,3), 'bool' )
print('Array não inicializado (vazio):\n', d )
Note que o Numpy permite arrays n-dimensionais. Em imagens em níveis de cinza iremos trabalhar com matrizes bidimensionais mas se a imagem for colorida, iremos representá-la em 3 canais, R, G e B, representados na estrutura com 3 dimensões. Se for um vídeo, isto é, uma sequência de imagens, teremos que adicionar mais uma dimensão. Se for uma tomografia, também podemos representar em 3 dimensões: largura, altura e profundidade.
As funções arange e linspace geram um vetor sequencial. Eles diferem apenas nos parâmetros. Enquanto o arange gera uma sequência a partir dos valores inicial (includente e opcional), final( excludente) e passo (opcional), linspace gera uma sequência com valores inicial e final e número de elementos. Note as diferenças nos exemplos a seguir:
In [68]:
print('np.arange( 10) = ', np.arange(10) )
print('np.arange( 3, 8) = ', np.arange(3,8) )
print('np.arange( 0, 2, 0.5) = ', np.arange(0, 2, 0.5) )
print('np.linspace( 0, 2, 5 ) = ', np.linspace( 0, 2, 5 ) )
Veja que no último caso, usando o linspace, a sequência começa em 0 e termina em 2 e deve possuir 5 elementos. Veja que para isto o passo a ser utilizado será 0.5, calculado automaticamente. Já no exemplo anterior, a sequência começa em 0 e deve terminar antes de 2 e o passo é 0.5.
Um recurso importante do numpy é o fatiamento no qual é possível acessar um subconjunto do array de diversas formas. O fatiamento define os índices onde o array será acessado definindo o ponto inicial, final e o passo entre eles, nesta ordem: [inicial:final:passo].
In [69]:
a = np.arange(20) # a é um vetor de dimensão 20
print('a = \n', a )
Para a realização do fatiamento são utilizados 3 parâmetros, colocados no local do índice do array. Os 3 parâmetros são separados por dois pontos ":". Todos os 3 parâmetros podem ser opcionais que ocorrem quando o valor inicial é 0, o valor final é o tamanho do array e o passo é 1. Lembrar que a ordem deles é: [inicial:final:passo]. Se o passo for 1 fica: [inicial:final]. Se o início for 0 fica: [:final] e se o final for o último fica: [inicio:] e se forem todos [:].
O fatiamento é feito começando pelo primeiro valor, adicionando-se o passo até antes do último valor. Três aspectos são extremamente importantes de serem lembrados: O índice inicial começa em zero, o índice final nunca é atingido, o último índice utilizado é sempre o imediatamente anterior e o Numpy admite índices negativos, que é uma indexação do último (-1) até o primeiro elemento (-W).
Os exemplos a seguir ajudam a fixar estes conceitos.
O código abaixo acessa os elementos ímpares começando de 1 até 14:
In [70]:
a = np.arange(20)
print('Resultado da operação a[1:15:2]' )
print(a[1:15:2] )
In [71]:
a = np.arange(20)
print('Resultado da operação a[1:-1:2]' )
print(a[1:-1:2] )
print('Note que o fatiamento termina antes do último elemento (-1)' )
In [72]:
a = np.arange(20)
print('Resultado da operação a[-3:2:-1]' )
print(a[-3:2:-1] )
print( 'Note que o fatiamento retorna o array invertido' )
print( 'Antepenúltimo até o terceiro elemento com step = -1' )
É possível realizar o fatiamento utilizando os 3 parâmetros explícitos ( o limite inferior, limite superior e o step), ou podemos suprimir algum desses parâmetros. Nestes casos a função toma o valor defaut: limite inferior = primeiro elemento, limite superior = último elemento e step = 1.
Proposta inicial | Equivalente |
---|---|
a[0:len(a):1] | a[:] |
a[0:10:1] | a[:10] |
a[0:10:2] | a[:10:2] |
a[2:len(a):1] | a[2::] |
a[2:len(a):2] | a[2::2] |
Quando o índice do limite inferior é omitido, é subentendido que é 0:
In [73]:
a = np.arange(20)
print('Resultado da operação a[:15:2]' )
print(a[:15:2] )
print('Note que o fatiamento inicia do primeiro elemento' )
print('Primeiro elemento até antes do 15o com passo duplo' )
In [74]:
a = np.arange(20)
print('Resultado da operação a[1::2]' )
print(a[1::2] )
print('Note que o fatiamento termina último elemento' )
print('Primeiro elemento até o último com passo duplo' )
In [75]:
a = np.arange(20)
print('Resultado da operação a[1:15]' )
print(a[1:15] )
print('Note que o fatiamento tem step unitário' )
print('Primeiro elemento até antes do 15o com passo um' )
In [76]:
a = np.arange(20)
print('Resultado da operação a[:]' )
print(a[:] )
print('Todos os elementos com passo unitário' )
In [77]:
a = np.arange(20) # a é um vetor unidimensional de 20 elementos
print('a = \n', a )
a = a.reshape(4,5) # a agora é um array 4x5 (4 linhas por 5 colunas)
print('a.reshape(4,5) = \n', a )
In [78]:
print('A segunda linha do array: \n', a[1,:] ) # A segunda linha é o índice 1
print(' A primeira coluna do array: \n', a[:,0] ) # A primeira coluna corresponde ao índice 0
In [79]:
print('Acessando as linhas do array de 2 em 2 começando pelo índice 0: \n', a[0::2,:] )
print(' Acessando as linhas e colunas do array de 2 em 2 começando \
pela linha 0 e coluna 1: \n', a[0::2,1::2] )
In [80]:
b = a[-1:-3:-1,:]
print('Acesso as duas últimas linhas do array em ordem reversa, \
b = a[-1:-3:-1,:] = \n',a[-1:-3:-1,:] )
print('Acesso elemento na última linha e coluna do array, a[-1,-1] =', a[-1,-1] )
c = a[::-1,:]
print('Invertendo a ordem das linhas do array: c = a[::-1,:] = \n', a[::-1,:] )
O ndarray foi projetado para acesso otimizado a uma grande quantidade de dados. Neste sentido, os conceitos descritos a seguir sobre as três formas de cópias entre variáveis ditas sem cópia, cópia rasa (shallow) e cópia profunda (deep) são fundamentais para uma codificação eficiente. Podemos dizer que um ndarray possui o cabeçalho que contém dados pelas informações sobre o tipo do elemento, a dimensionalidade (shape) e passo ou deslocamento para o próximo elemento (strides) e os dados raster em si. A tabela a seguir mostra a situação do cabeçalho e dos dados nos três tipos de cópias.
Tipo | Cabeçalho:Type,Shape,Strides | Dados raster | Exemplo |
---|---|---|---|
Sem cópia, apenas ref | apontador original | apontador original | a = b |
Cópia rasa | novo | apontador original | b=a.reshape |
slicing, a.T | |||
Cópia profunda | novo | novo | a = b.copy() |
No caso abaixo, usaremos o comando normal de igual como atribuição do array a para o array b. Verifica-se que tanto o shape como os dados de b são os mesmos de a. Tudo se passa como b fosse apenas um apontador para a. Qualquer modificação em b é refletida em a.
In [81]:
a = np.arange(6)
b = a
print("a =\n",a )
print("b =\n",b )
b.shape = (2,3) # mudança no shape de b,
print("\na shape =",a.shape ) # altera o shape de a
b[0,0] = -1 # mudança no conteúdo de b
print("a =\n",a ) # altera o conteudo de a
print("\nid de a = ",id(a) ) # id é um identificador único de objeto
print("id de b = ",id(b) ) # a e b possuem o mesmo id
print('np.may_share_memory(a,b):',np.may_share_memory(a,b) )
Observe que mesmo no retorno de uma função, a cópia explícita pode não acontecer. Veja o exemplo a seguir de uma função que apenas retorna a variável de entrada:
In [82]:
def cc(a):
return a
b = cc(a)
print("id de a = ",id(a) )
print("id de b = ",id(b) )
print('np.may_share_memory(a,b):',np.may_share_memory(a,b) )
A cópia rasa é muito útil e extensivamente utilizada. É usada quando se quer indexar o array original através da mudança de dimensionalidade ou do refatiamento, porém sem a necessidade de realizar uma cópia dos dados raster. Desta forma consegue-se uma otimização no acesso ao array n-dimensional. Existem várias formas onde a cópia rasa acontece, sendo as principais: 1) no caso do reshape onde o número de elementos do ndarray é o mesmo, porém sua dimensionalidade é alterada; 2) no caso de fatiamento onde um subarray é indexado; 3) no caso de transposição do array; 4) no caso de linearização do raster através do ravel(). entre outros.
O exemplo a seguir mostra inicialmente a criação de um vetor unidimensional sequencial sendo "visto" de forma bidimensional ou tridimensional.
In [83]:
a = np.arange(30)
print("a =\n", a )
b = a.reshape( (5, 6))
print("b =\n", b )
b[:, 0] = -1
print("a =\n", a )
c = a.reshape( (2, 3, 5) )
print("c =\n", c )
print('c.base is a:',c.base is a )
print('np.may_share_memory(a,c):',np.may_share_memory(a,c) )
O exemplo a seguir mostra a cópia rasa no uso de fatiamento. No exemplo, todos os elementos de linhas e colunas pares são modificados para 1. CUIDADO: quando é feita a atribuição de b = 1., é importante que b seja referenciado como ndarray na forma b[:,:], caso contrário, se fizermos b = 1., uma nova variável é criada.
In [84]:
a = np.zeros( (5, 6))
print('%s %s %s %s %s' % (type(a), np.shape(a), a.dtype, a.min(), a.max()) )
b = a[::2,::2]
print('%s %s %s %s %s' % (type(b), np.shape(b), b.dtype, b.min(), b.max()) )
b[:,:] = 1.
print('b=\n', b )
print('a=\n', a )
print('b.base is a:',b.base is a )
print('np.may_share_memory(a,b):',np.may_share_memory(a,b) )
Este outro exemplo é uma forma atraente de processar uma coluna de uma matriz bidimensional, porém é preciso CUIDADO, pois o uso de b deve ser com b[:] se for atribuído um novo valor para ele, caso contrário, se fizermos b = arange(5), uma nova variável é criada.
In [85]:
a = np.arange(25).reshape((5,5))
print('a=\n',a )
b = a[:,0]
print('b=',b )
b[:] = np.arange(5)
print('b=',b )
print('a=\n',a )
In [86]:
a = np.arange(24).reshape((4,6))
print('a:\n',a )
print('a.T:\n',a.T )
print('np.may_share_memory(a,a.T):',np.may_share_memory(a,a.T) )
In [87]:
a = np.arange(24).reshape((4,6))
print('a:\n',a )
av = a.ravel()
print('av.shape:',av.shape )
print('av:\n',av )
print('np.may_share_memory(a,av):',np.may_share_memory(a,av) )
In [88]:
b = a.copy()
c = np.array(a, copy=True)
print("id de a = ",id(a) )
print("id de b = ",id(b) )
print("id de c = ",id(c) )
Uma das principais vantagens da estrutura ndarray é sua habilidade de processamento matricial.
Assim, para se multiplicar todos os elementos de um array por um escalar basta escrever a 5 por
exemplo. Para se fazer qualquer operação lógica ou aritmética entre arrays, basta escrever a
In [89]:
a = np.arange(20).reshape(5,4)
b = 2 * np.ones((5,4))
c = np.arange(12,0,-1).reshape(4,3)
print('a=\n', a )
print('b=\n', b )
print('c=\n', c )
In [90]:
b5 = 5 * b
print('b5=\n', b5 )
In [91]:
amb = a + b
print('amb=\n', amb )
A transposta de uma matriz, troca os eixos das coordenadas. O elemento que estava na posição (r,c) vai agora estar na posição (c,r). O shape da matriz resultante ficará portanto com os valores trocados. A operação de transposição é feita através de cópia rasa, portanto é uma operação muito eficiente e deve ser utilizada sempre que possível.
Veja o exemplo a seguir:
In [92]:
at = a.T
print('a.shape=',a.shape )
print('a.T.shape=',a.T.shape )
print('a=\n', a )
print('at=\n', at )
A multiplicação de matrizes é feita através do operador dot. Para que a multiplicação seja possível é importante que o número de colunas do primeiro ndarray seja igual ao número de linhas do segundo. As dimensões do resultado será o número de linhas do primeiro ndarray pelo número de colunas do segundo ndarray. Confira:
In [93]:
ac = a.dot(c)
print('a.shape:',a.shape )
print('c.shape:',c.shape )
print('a=\n',a )
print('c=\n',c )
print('ac=\n', ac )
print('ac.shape:',ac.shape )
As funções do numpy linspace e arange tem o mesmo objetivo: gerar numpy.arrays linearmente espaçados em um intervalo indicado como parâmetro.
A diferença primordial entre essas funções é como será realizada a divisão no intervalo especificado. Na função linspace essa divisão é feita através da definição do intervalo fechado [inicio,fim], isto é, contém o início e o fim, e da quantidade de elementos que o numpy.array final terá. O passo portanto é calculado como (fim - inicio)/(n - 1). Dessa forma, se queremos gerar um numpy.array entre 0 e 1 com 10 elementos, utilizaremos o linspace da seguinte forma
In [94]:
# gera um numpy.array de 10 elementos, linearmente espaçados entre 0 a 1
print(np.linspace(0, 1.0, num=10).round(2) )
Já na função arange, define-se o intervalo semi-aberto [inicio,fim) e o passo que será dado entre um elemento e outro. Dessa forma, para gerar um numpy.array entre 0 e 1 com 10 elementos, temos que calcular o passo (0.1) e passar esse passo como parâmetro.
In [95]:
# gera um numpy.array linearmente espaçados entre 0 a 1 com passo 0.1
print(np.arange(0, 1.0, 0.1) )
Confirme que a principal diferença entre os dois que pode ser verificada nos exemplos acima é que no linspace o limite superior da distribuição é inclusivo (intervalo fechado), enquanto no arange isso não ocorre (intervalo semi-aberto).
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.
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 [96]:
r,c = np.indices( (5, 10) )
print('r=\n', r )
print('c=\n', c )
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 [97]:
f = r + c
print('f=\n', f )
Ou ainda a função diferença entre a coordenada linha e coluna $f(r,c) = r - c$:
In [98]:
f = r - c
print('f=\n', f )
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 [99]:
f = (r + c) % 2
print('f=\n', f )
Ou ainda a função de uma reta $f(r,c) = (r = \frac{1}{2}c)$:
In [100]:
f = (r == c//2)
print('f=\n', f )
Ou ainda a função parabólica dada pela soma do quadrado de suas coordenadas $f(r,c) = r^2 + c^2$:
In [101]:
f = r**2 + c**2
print('f=\n', f )
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 [102]:
f = ((r**2 + c**2) < 4**2)
print('f=\n', f * 1 )
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. Gerando as coordenadas utilizando indices:
Observe que o parâmetro de indices é uma tupla. Verifique o número de parêntesis utilizados:
In [103]:
# Diretiva para mostrar gráficos inline no notebook
%matplotlib inline
import matplotlib.pylab as plt
r,c = np.indices( (200, 300) )
plt.subplot(121)
plt.imshow(r,cmap = 'gray')
plt.title("linhas")
plt.axis('off')
plt.subplot(122)
plt.imshow(c,cmap = 'gray')
plt.axis('off')
plt.title("colunas");
In [104]:
f = r + c
plt.imshow(f,cmap = 'gray')
plt.title("r+c")
plt.axis("off")
Out[104]:
In [105]:
f = r - c
plt.imshow(f,cmap = 'gray')
plt.title("r-c")
plt.axis("off")
Out[105]:
In [106]:
f = (r//8 + c//8) % 2
plt.imshow(f,cmap = 'gray')
plt.title("(r+c)%2")
plt.axis("off")
Out[106]:
In [107]:
f = (r == c//2)
plt.imshow(f,cmap = 'gray')
plt.title('r == c//2')
plt.axis("off")
Out[107]:
In [108]:
f = r**2 + c**2
plt.imshow(f,cmap = 'gray')
plt.title('r**2 + c**2')
plt.axis("off")
Out[108]:
In [109]:
f = (((r-100)**2 + (c-100)**2) < 19**2)
plt.imshow(f,cmap = 'gray')
plt.title('((r-100)**2 + (c-100)**2) < 19**2')
plt.axis("off")
Out[109]:
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 [110]:
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 )
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 [111]:
rows = np.linspace(-1.5, 0.5, 5)
cols = np.linspace(-20, 30, 6)
print('rows:', rows )
print('cols:', cols )
Usando os dois vetores gerados pelo linspace no meshgrid:
In [112]:
r, c = np.meshgrid(rows, cols, indexing='ij')
print('r = \n', r )
print('c = \n', c )
Podemos agora gerar uma matriz ou imagem que seja função destes valores. Por exemplo ser o produto deles:
In [113]:
f = r * c
print('f=\n', f )
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 [114]:
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
plt.imshow(z,cmap = 'gray')
plt.title('Função sinc: sen(r² + c²)/(r²+c²) em duas dimensões')
plt.axis("off")
Out[114]:
In [115]:
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
plt.imshow(zi,cmap = 'gray')
plt.title('Função sinc: sin(r² + c²)/(r²+c²) em duas dimensões')
plt.axis("off")
Out[115]:
Verificando que as duas funções são iguais:
In [116]:
print('Máxima diferença entre z e zi?', abs(z - zi).max() )
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.
Uma função importante da biblioteca numpy é a tile, que gera repetições do array passado com parâmetro. A quantidade de repetições é dada pelo parâmetro reps
In [117]:
a = np.array([0, 1, 2])
print('a = \n', a )
print()
print('Resultado da operação np.tile(a,2): \n',np.tile(a,2) )
In [118]:
a = np.array([0, 1, 2])
print('a = \n', a )
print()
print('Resultado da operação np.tile(a,(2,1)): \n',np.tile(a,(2,1)) )
In [119]:
a = np.arange(4).reshape(2,2)
print('a = \n', a )
print()
print('Resultado da operação np.tile(a,2): \n',np.tile(a,2) )
In [120]:
a = np.arange(4).reshape(2,2)
print('a = \n', a )
print()
print('Resultado da operação np.tile(a,(3,1)): \n',np.tile(a,(3,1)) )
In [121]:
a = np.arange(4).reshape(2,2)
print('a = \n', a )
print()
print('Resultado da operação np.tile(a,(2,2)): \n',np.tile(a,(2,2)) )
In [122]:
a = np.array([[0,1],[2,3]])
print('a = \n', a )
print()
print('np.resize(a,(1,7)) = \n', np.resize(a,(1,7)) )
print()
print('np.resize(a,(2,5)) = \n', np.resize(a,(2,5)) )
A função clip substitui os valores de um array que estejam abaixo de um limiar mínimo ou que estejam acima de um limiar máximo, por esses limiares mínimo e máximo, respectivamente. Esta função é especialmente útil em processamento de imagens para evitar que os índices ultrapassem os limites das imagens.
In [123]:
a = np.array([11,1,2,3,4,5,12,-3,-4,7,4])
print('a = ',a )
print('np.clip(a,0,10) = ', np.clip(a,0,10) )
In [124]:
a = np.arange(10).astype(np.int)
print('a=',a )
print('np.clip(a,2.5,7.5)=',np.clip(a,2.5,7.5) )
In [125]:
A = np.exp(np.linspace(0.1,10,32)).reshape(4,8)/3000.
print('A: \n', A )
É possível diminuir o número de casas decimais e suprimir a notação exponencial utilizando a função set_printoption do numpy:
In [126]:
np.set_printoptions(suppress=True, precision=3)
print('A: \n', A )
In [127]:
A = np.random.rand(5,10) > 0.5
print('A = \n', A )
Para facilitar a visualização destes arrays, é possível converter os valores para inteiros utilizando o método astype(int):
In [128]:
print ('A = \n', A.astype(int))
In [ ]: