Estou trabalhando com o pointcloud 3D do Lidar. Os pontos são dados por uma matriz numpy que se parece com isso:
points = np.array([[61651921, 416326074, 39805], [61605255, 416360555, 41124], [61664810, 416313743, 39900], [61664837, 416313749, 39910], [61674456, 416316663, 39503], [61651933, 416326074, 39802], [61679969, 416318049, 39500], [61674494, 416316677, 39508], [61651908, 416326079, 39800], [61651908, 416326087, 39802], [61664845, 416313738, 39913], [61674480, 416316668, 39503], [61679996, 416318047, 39510], [61605290, 416360572, 41118], [61605270, 416360565, 41122], [61683939, 416313004, 41052], [61683936, 416313033, 41060], [61679976, 416318044, 39509], [61605279, 416360555, 41109], [61664837, 416313739, 39915], [61674487, 416316666, 39505], [61679961, 416318035, 39503], [61683943, 416313004, 41054], [61683930, 416313042, 41059]])
Gostaria de manter meus dados agrupados em cubos de tamanho, 50*50*50
para que cada cubo preserve algum índice hashable e índices numpy do meu points
conteúdo . Para obter a divisão, atribuo cubes = points \\ 50
quais saídas a:
cubes = np.array([[1233038, 8326521, 796], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233599, 8326360, 790], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233038, 8326521, 796], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1232105, 8327211, 822], [1232105, 8327211, 822], [1233678, 8326260, 821], [1233678, 8326260, 821], [1233599, 8326360, 790], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1233678, 8326260, 821], [1233678, 8326260, 821]])
Minha saída desejada é assim:
{(1232105, 8327211, 822): [1, 13, 14, 18]),
(1233038, 8326521, 796): [0, 5, 8, 9],
(1233296, 8326274, 798): [2, 3, 10, 19],
(1233489, 8326333, 790): [4, 7, 11, 20],
(1233599, 8326360, 790): [6, 12, 17, 21],
(1233678, 8326260, 821): [15, 16, 22, 23]}
Meu verdadeiro pointcloud contém até algumas centenas de milhões de pontos 3D. Qual é a maneira mais rápida de fazer esse tipo de agrupamento?
Eu tentei a maioria das várias soluções. Aqui está a comparação do consumo de tempo assumindo que o tamanho dos pontos é de cerca de 20 milhões e o tamanho de cubos distintos é de 1 milhão:
Pandas [tupla (elem) -> matriz np (dtype = int64)]
import pandas as pd
print(pd.DataFrame(cubes).groupby([0,1,2]).indices)
#takes 9sec
Defauldict [elem.tobytes () ou tupla -> lista]
#thanks @abc:
result = defaultdict(list)
for idx, elem in enumerate(cubes):
result[elem.tobytes()].append(idx) # takes 20.5sec
# result[elem[0], elem[1], elem[2]].append(idx) #takes 27sec
# result[tuple(elem)].append(idx) # takes 50sec
numpy_indexed [int -> matriz np]
# thanks @Eelco Hoogendoorn for his library
values = npi.group_by(cubes).split(np.arange(len(cubes)))
result = dict(enumerate(values))
# takes 9.8sec
Pandas + redução de dimensionalidade [int -> matriz np (dtype = int64)]
# thanks @Divakar for showing numexpr library:
import numexpr as ne
def dimensionality_reduction(cubes):
#cubes = cubes - np.min(cubes, axis=0) #in case some coords are negative
cubes = cubes.astype(np.int64)
s0, s1 = cubes[:,0].max()+1, cubes[:,1].max()+1
d = {'s0':s0,'s1':s1,'c0':cubes[:,0],'c1':cubes[:,1],'c2':cubes[:,2]}
c1D = ne.evaluate('c0+c1*s0+c2*s0*s1',d)
return c1D
cubes = dimensionality_reduction(cubes)
result = pd.DataFrame(cubes).groupby([0]).indices
# takes 2.5 seconds
É possível baixar o cubes.npz
arquivo aqui e usar um comando
cubes = np.load('cubes.npz')['array']
para verificar o tempo de desempenho.
numpy_indexed
apenas se aproxima disso também. Eu acho que está certo.pandas
Atualmente, uso nos meus processos de classificação.Respostas:
Número constante de índices por grupo
Abordagem # 1
Podemos executar
dimensionality-reduction
para reduzircubes
a uma matriz 1D. Isso se baseia em um mapeamento dos dados dos cubos fornecidos em uma grade n-dim para calcular os equivalentes de índice linear, discutidos em detalheshere
. Então, com base na singularidade desses índices lineares, podemos separar grupos únicos e seus índices correspondentes. Portanto, seguindo essas estratégias, teríamos uma solução, assim:Alternativa 1: se os valores inteiros em
cubes
forem muito grandes, convém fazer comdimensionality-reduction
que as dimensões com menor extensão sejam escolhidas como os eixos primários. Portanto, nesses casos, podemos modificar a etapa de redução para obterc1D
, assim:Abordagem # 2
Em seguida, podemos usar
Cython-powered kd-tree
a pesquisa rápida do vizinho mais próximo para obter os índices vizinhos mais próximos e, portanto, resolver nosso caso da seguinte forma:Caso genérico: número variável de índices por grupo
Vamos estender o método baseado em argsort com algumas divisões para obter a saída desejada, assim:
Usando versões 1D de grupos de
cubes
como chavesEstenderemos o método listado anteriormente com os grupos de
cubes
como chaves para simplificar o processo de criação de dicionário e também torná-lo eficiente, como:Em seguida, usaremos o
numba
pacote para iterar e chegar à saída final do dicionário hashable. Seguindo em frente, haveria duas soluções - uma que obtém as chaves e os valores separadamentenumba
e a chamada principal será compactada e convertida em dict, enquanto a outra criará umnumba-supported
tipo de dict e, portanto, nenhum trabalho extra exigido pela função de chamada principal .Assim, teríamos a primeira
numba
solução:E segunda
numba
solução como:Tempos com
cubes.npz
dados -Alternativa nº 1: podemos acelerar ainda mais com
numexpr
matrizes grandes para calcularc1D
, assim:Isso seria aplicável em todos os locais que exigirem
c1D
.fonte
dtypes
int32
eint64
number of indices per group would be a constant number
que reuni os comentários. Isso seria uma suposição segura? Além disso, você está testandocubes.npz
a saída de915791
?cubes.npz
apenas e foi983234
para as outras abordagens que sugeri.Approach #3
esse caso genérico de número variável de índices.Você pode apenas iterar e adicionar o índice de cada elemento à lista correspondente.
O tempo de execução pode ser aprimorado ainda mais usando tobytes () em vez de converter a chave em uma tupla.
fonte
res[tuple(elem)].append(idx)
levou 50 segundos contra sua edição,res[elem[0], elem[1], elem[2]].append(idx)
que levou 30 segundos.Você pode usar o Cython:
mas não o tornará mais rápido do que o Pandas faz, embora seja o mais rápido depois disso (e talvez o
numpy_index
solução baseada), e não venha com a penalidade de memória. Uma coleção do que foi proposto até agora está aqui .Na máquina do OP, o tempo de execução é de aproximadamente 12 segundos.
fonte