Estoy trabajando con 3D pointcloud de Lidar. Los puntos están dados por una matriz numpy que se ve así:
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]])
Me gustaría mantener mis datos agrupados en cubos de tamaño 50*50*50para que cada cubo conserve algún índice hashable e índices numpy de mi pointsque contiene . Para dividir, asigno cubes = points \\ 50las salidas 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]])
Mi salida deseada se ve así:
{(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]}
Mi nube de puntos real contiene hasta unos pocos cientos de millones de puntos 3D. ¿Cuál es la forma más rápida de hacer este tipo de agrupación?
He probado la mayoría de varias soluciones. Aquí hay una comparación del consumo de tiempo asumiendo que el tamaño de los puntos es de alrededor de 20 millones y el tamaño de los cubos distintos es de alrededor de 1 millón:
Pandas [tupla (elem) -> np.array (dtype = int64)]
import pandas as pd
print(pd.DataFrame(cubes).groupby([0,1,2]).indices)
#takes 9sec
Por defecto [elem.tobytes () o 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 -> np.array]
# thanks @Eelco Hoogendoorn for his library
values = npi.group_by(cubes).split(np.arange(len(cubes)))
result = dict(enumerate(values))
# takes 9.8sec
Pandas + reducción de dimensionalidad [int -> np.array (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
Es posible descargar el cubes.npzarchivo aquí y usar un comando
cubes = np.load('cubes.npz')['array']
para verificar el tiempo de rendimiento.

numpy_indexedsolo se acerca a él también. Supongo que es correcto. Utilizopandaspara mis procesos de clasificación actualmente.Respuestas:
Número constante de índices por grupo
Enfoque n. ° 1
Podemos realizar
dimensionality-reductionpara reducircubesa una matriz 1D. Esto se basa en un mapeo de los datos de cubos dados en una cuadrícula n-dim para calcular los equivalentes de índice lineal, discutidos en detallehere. Luego, en función de la unicidad de esos índices lineales, podemos segregar grupos únicos y sus índices correspondientes. Por lo tanto, siguiendo esas estrategias, tendríamos una solución, así:Alternativa n. ° 1: si los valores enteros en
cubesson demasiado grandes, es posible que deseemos hacerdimensionality-reductionque las dimensiones con menor extensión se elijan como ejes primarios. Por lo tanto, para esos casos, podemos modificar el paso de reducción para obtenerc1D, así:Enfoque n. ° 2
A continuación, podemos usar la
Cython-powered kd-treebúsqueda rápida del vecino más cercano para obtener los índices vecinos más cercanos y, por lo tanto, resolver nuestro caso así:Caso genérico: número variable de índices por grupo
Extendiremos el método basado en argsort con un poco de división para obtener el resultado deseado, así:
Usando versiones 1D de grupos de
cubescomo clavesAmpliaremos el método enumerado anteriormente con los grupos de
cubesclaves para simplificar el proceso de creación de diccionarios y también hacerlo eficiente con él, de esta manera:A continuación, utilizaremos el
numbapaquete para iterar y llegar a la salida final del diccionario hashable. Para ello, habría dos soluciones: una que obtiene las claves y los valores por separadonumbay la llamada principal se comprimirá y convertirá a dict, mientras que la otra creará unnumba-supportedtipo de dict y, por lo tanto, no se requiere trabajo adicional por parte de la función de llamada principal .Por lo tanto, tendríamos la primera
numbasolución:Y segunda
numbasolución como:Tiempos con
cubes.npzdatos -Alternativa n. ° 1: Podemos lograr una mayor velocidad con computadoras
numexprgrandes para calcularc1D, así:Esto sería aplicable en todos los lugares que lo requieran
c1D.fuente
dtypesint32yint64number of indices per group would be a constant numberque reuní los comentarios. ¿Sería una suposición segura? Además, ¿estás probandocubes.npzla salida de915791?cubes.npzy fue983234para los otros enfoques que sugerí.Approach #3ese caso genérico de número variable de índices.Puede iterar y agregar el índice de cada elemento a la lista correspondiente.
El tiempo de ejecución se puede mejorar aún más mediante el uso de tobytes () en lugar de convertir la clave en una tupla.
fuente
res[tuple(elem)].append(idx)tardó 50 segundos en comparación con su edición,res[elem[0], elem[1], elem[2]].append(idx)que tardó 30 segundos.Puedes usar Cython:
pero no lo hará más rápido que lo que hace Pandas, aunque es el más rápido después de eso (y tal vez la
numpy_indexsolución basada), y no viene con la penalización de la memoria. Una colección de lo que se ha propuesto hasta ahora está aquí .En la máquina de OP que debería acercarse a ~ 12 segundos de tiempo de ejecución.
fuente