Visualización de Nubes de Puntos por Categoría y Aceleración de Renderizado

Implementación de Filtros por Categoría

La visualización selectiva por categorías se logra mediante cuadros de selección. Cada checkobx modifica una máscara booleana que contorla la visibilidad de los puntos:

check_box.stateChanged.connect(
    functools.partial(self.actualizar_visibilidad_puntos)
)

Lógica de procesamiento de eventos:

def actualizar_visibilidad_puntos(self):
    if self.widgetOpenGL.modo_visualizacion == MODO.CATEGORIA:
        filtro = np.ones_like(self.widgetOpenGL.categorias, dtype=bool)
        
        for idx in range(self.listaEtiquetas.count()):
            elemento = self.listaEtiquetas.item(idx)
            widget = self.listaEtiquetas.itemWidget(elemento)
            selector = widget.findChild(QCheckBox, 'selector_categoria')
            
            if not selector.isChecked():
                filtro[self.widgetOpenGL.categorias == idx] = False
                self.widgetOpenGL.estado_categorias[idx] = False
            else:
                self.widgetOpenGL.estado_categorias[idx] = True
        
        self.widgetOpenGL.filtro_activo = filtro
        self.widgetOpenGL.vertices_actuales = self.widgetOpenGL.nube.xyz[filtro]
        self.widgetOpenGL.colores_actuales = self.widgetOpenGL.colores_categoria[filtro]
    
    self.widgetOpenGL.inicializar_buffers()
    self.widgetOpenGL.update()

Optimización de Renderizado con OpenGL

Para mejorar el rendimiento, se utilizan Vertex Buffer Objects (VBO) y Vertex Array Objects (VAO):

def inicializar_buffers(self):
    self.vao_puntos = glGenVertexArrays(1)
    identificadores_vbo = glGenBuffers(2)
    
    # Buffer de vértices
    glBindBuffer(GL_ARRAY_BUFFER, identificadores_vbo[0])
    glBufferData(GL_ARRAY_BUFFER, self.vertices_actuales.nbytes, 
                 self.vertices_actuales, GL_STATIC_DRAW)
    
    # Buffer de colores
    glBindBuffer(GL_ARRAY_BUFFER, identificadores_vbo[1])
    glBufferData(GL_ARRAY_BUFFER, self.colores_actuales.nbytes,
                 self.colores_actuales, GL_STATIC_DRAW)
    
    # Configuración de atributos
    glBindVertexArray(self.vao_puntos)
    glBindBuffer(GL_ARRAY_BUFFER, identificadores_vbo[0])
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(0)
    
    glBindBuffer(GL_ARRAY_BUFFER, identificadores_vbo[1])
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(1)
    
    glBindVertexArray(0)

Selección de Puntos en 3D

Al hacer doble clic, se calcula el punto más cercano al rayo de seleción:

def evento_doble_clic(self, evento):
    x, y = evento.pos().x(), self.height() - evento.pos().y()
    punto = self.seleccionar_punto(x, y)
    
    if punto.size:
        self.transformacion.setTranslation(-punto[0], -punto[1], -punto[2])
    self.update()

def seleccionar_punto(self, x, y):
    origen = QVector3D(x, y, 0).unproject(
        self.camara.matriz() * self.transformacion.matriz(),
        self.proyeccion, 
        QRect(0, 0, self.width(), self.height())
    )
    
    destino = QVector3D(x, y, 1).unproject(
        self.camara.matriz() * self.transformacion.matriz(),
        self.proyeccion,
        QRect(0, 0, self.width(), self.height())
    )
    
    direccion = destino - origen
    direccion.normalize()
    
    # Cálculo de distancias
    t = np.dot(self.vertices_actuales - origen, direccion) / direccion.lengthSquared()
    proyecciones = origen + t * direccion
    distancias = np.linalg.norm(self.vertices_actuales - proyecciones, axis=1)
    
    radio = self.radio_seleccion * self.escala_actual
    candidatos = distancias < radio
    
    if not np.any(candidatos):
        return np.array([])
    
    puntos_cercanos = self.vertices_actuales[candidatos]
    idx = np.argmin(np.dot(puntos_cercanos, direccion))
    return puntos_cercanos[idx]

Etiquetas: PyQt OpenGL Visualización3D NubeDePuntos OptimizaciónRenderizado

Publicado el 7-24 18:14