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*50
para que cada cubo conserve algún índice hashable e índices numpy de mi points
que contiene . Para dividir, asigno cubes = points \\ 50
las 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.npz
archivo aquí y usar un comando
cubes = np.load('cubes.npz')['array']
para verificar el tiempo de rendimiento.
numpy_indexed
solo se acerca a él también. Supongo que es correcto. Utilizopandas
para mis procesos de clasificación actualmente.Respuestas:
Número constante de índices por grupo
Enfoque n. ° 1
Podemos realizar
dimensionality-reduction
para reducircubes
a 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
cubes
son demasiado grandes, es posible que deseemos hacerdimensionality-reduction
que 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-tree
bú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
cubes
como clavesAmpliaremos el método enumerado anteriormente con los grupos de
cubes
claves para simplificar el proceso de creación de diccionarios y también hacerlo eficiente con él, de esta manera:A continuación, utilizaremos el
numba
paquete 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 separadonumba
y la llamada principal se comprimirá y convertirá a dict, mientras que la otra creará unnumba-supported
tipo 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
numba
solución:Y segunda
numba
solución como:Tiempos con
cubes.npz
datos -Alternativa n. ° 1: Podemos lograr una mayor velocidad con computadoras
numexpr
grandes para calcularc1D
, así:Esto sería aplicable en todos los lugares que lo requieran
c1D
.fuente
dtypes
int32
yint64
number of indices per group would be a constant number
que reuní los comentarios. ¿Sería una suposición segura? Además, ¿estás probandocubes.npz
la salida de915791
?cubes.npz
y fue983234
para los otros enfoques que sugerí.Approach #3
ese 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_index
solució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