업데이트 된 object - place 간 연결 수정 (가장 가까운 place를 찾아 연결)2d places를 도출void FrontendModule::updateMesh(const ReconstructionOutput& input) {
  {  // start timing scope
    ScopedTimer timer("frontend/mesh_archive", input.timestamp_ns, true, 1, false);
    VLOG(5) << "[Hydra Frontend] Clearing " << input.archived_blocks.size()
            << " blocks from mesh";
    if (!input.archived_blocks.empty()) {
      mesh_compression_->clearArchivedBlocks(input.archived_blocks);
    }
  }  // end timing scope
  // TODO(nathan) prune archived blocks from input?
  const auto& input_mesh = input.map().getMeshLayer();
  {
    ScopedTimer timer("frontend/mesh_compression", input.timestamp_ns, true, 1, false);
    mesh_remapping_ = std::make_shared<kimera_pgmo::HashedIndexMapping>();
    auto mesh = getActiveMesh(input_mesh, input.archived_blocks);
    VLOG(5) << "[Hydra Frontend] Updating mesh with " << mesh->numBlocks() << " blocks";
    auto interface = PgmoMeshLayerInterface(*mesh);
    last_mesh_update_ =
        mesh_compression_->update(interface, input.timestamp_ns, mesh_remapping_.get());
  }  // end timing scope
  {  // start timing scope
    // TODO(nathan) we should probably have a mutex before modifying the mesh, but
    // nothing else uses it at the moment
    ScopedTimer timer("frontend/mesh_update", input.timestamp_ns, true, 1, false);
    last_mesh_update_->updateMesh(*dsg_->graph->mesh());
    invalidateMeshEdges(*last_mesh_update_);
  }  // end timing scope
  ScopedTimer timer(
      "frontend/launch_postmesh_callbacks", input.timestamp_ns, true, 1, false);
  launchCallbacks(post_mesh_callbacks_, input);
}
void FrontendModule::updateObjects(const ReconstructionOutput& input) {
  if (!last_mesh_update_) {
    LOG(ERROR) << "Cannot detect objects without valid mesh";
    return;
  }
  const auto clusters =
      segmenter_->detect(input.timestamp_ns, *last_mesh_update_, std::nullopt);
  {  // start dsg critical section
    std::unique_lock<std::mutex> lock(dsg_->mutex);
    segmenter_->updateGraph(input.timestamp_ns,
                            clusters,
                            last_mesh_update_->getTotalArchivedVertices(),
                            *dsg_->graph);
    addPlaceObjectEdges(input.timestamp_ns);
  }  // end dsg critical section
}
불필요한 메쉬 블록을 삭제 + 압축된 메쉬 데이터를 생성input.archived_blocks는 이전 프레임에서 사용되지 않거나 불필요하게 된 메쉬 블록을 의미mesh_compression_ 객체를 사용해 삭제clearArchivedBlocks() 함수로 처리됨const auto& input_mesh = input.map().getMeshLayer();input_mesh)로 설정mesh_remapping_)std::make_shared<kimera_pgmo::HashedIndexMapping>()로 리매핑 정보를 저장할 객체를 생성getActiveMesh(input_mesh, input.archived_blocks) auto interface = PgmoMeshLayerInterface(*mesh);last_mesh_update_ = mesh_compression_->update(interface, input.timestamp_ns, mesh_remapping_.get());mesh_compression_->update 구현 부분을 찾아서 공부last_mesh_update_에 저장된 압축된 메쉬 업데이트 정보를 실제로 그래프(dsg_)에 반영last_mesh_update_->updateMesh(*dsg_->graph->mesh())는 업데이트된 메쉬 데이터를 그래프에 적용하는 작업last_mesh_update_->updateMesh 에 대해 찾아서 공부해보기invalidateMeshEdges()는 변경된 메쉬에 연결된 엣지(edge)들을 무효화하여 유효하지 않은 연결을 제거ScopedTimer timer("frontend/launch_postmesh_callbacks", input.timestamp_ns, true, 1, false);launchCallbacks(post_mesh_callbacks_, input)FrontendModule::updateObjects, FrontendModule::updatePlaces2dvoid FrontendModule::updateObjects(const ReconstructionOutput& input) {
  if (!last_mesh_update_) {
    LOG(ERROR) << "Cannot detect objects without valid mesh";
    return;
  }
  const auto clusters =
      segmenter_->detect(input.timestamp_ns, *last_mesh_update_, std::nullopt);
  {  // start dsg critical section
    std::unique_lock<std::mutex> lock(dsg_->mutex);
    segmenter_->updateGraph(input.timestamp_ns,
                            clusters,
                            last_mesh_update_->getTotalArchivedVertices(),
                            *dsg_->graph);
    addPlaceObjectEdges(input.timestamp_ns);
  }  // end dsg critical section
}클러스터링을 통한 객체 탐지
const auto clusters =
    segmenter_->detect(input.timestamp_ns, *last_mesh_update_, std::nullopt);segmenter_ 객체는 메쉬를 기반으로 객체를 클러스터링detect() 함수는 클러스터링된 객체 정보(clusters)를 반환DSG(Graph) 업데이트 (Critical Section)
{  // start dsg critical section
  std::unique_lock<std::mutex> lock(dsg_->mutex);
  segmenter_->updateGraph(input.timestamp_ns,
                          clusters,
                          last_mesh_update_->getTotalArchivedVertices(),
                          *dsg_->graph);
  addPlaceObjectEdges(input.timestamp_ns);
}  // end dsg critical sectionsegmenter_->updateGraph():last_mesh_update_->getTotalArchivedVertices()는 메쉬의 아카이브된 전체 버텍스 수를 반환addPlaceObjectEdges(input.timestamp_ns) 함수를 호출하여 객체와 장소 노드 간의 연결을 추가segmenter_에서 활성화된 노드들을 반환하는 getActiveNodes() 함수로부터 가져옵니다. 이 함수는 현재 메쉬에서 인식된 활성 객체들의 ID를 반환합니다.for (const auto& object_id : segmenter_->getActiveNodes()) {node->attributes().position)를 기준으로 가장 가까운 장소 노드를 탐색places_nn_finder_는 K-최근접 이웃(KNN) 알고리즘을 사용해, 주어진 객체 위치에서 가장 가까운 장소 노드를 찾습니다. find 함수는 탐색된 장소 노드에 대해 콜백 함수를 실행합니다. places_nn_finder_->find(
    node->attributes().position, 1, false, [&](NodeId place_id, size_t, double) {
      dsg_->graph->insertParentEdge(place_id, object_id);
    });insertParentEdge(place_id, object_id) 함수는 place_id를 부모로, object_id를 자식으로 설정하여, 그래프에 부모-자식 관계를 정의합니다.dsg_->graph->insertParentEdge(place_id, object_id);장소 탐지 (surface_places_->detect)
NodeIdSet active_nodes;
{  // start timing scope
  ScopedTimer timer("frontend/places_2d", input.timestamp_ns, true, 1, false);
  surface_places_->detect(input, *last_mesh_update_, *dsg_->graph);surface_places_->detect() 함수는 현재 메쉬 데이터(last_mesh_update_)와 DSG를 기반으로 2D 장소를 탐지DSG 업데이트 (Critical Section)
{  // start graph critical section
  std::unique_lock<std::mutex> graph_lock(dsg_->mutex);
  surface_places_->updateGraph(input.timestamp_ns, input, *dsg_->graph);archivePlaces2d(active_nodes);
previous_active_places_2d_ = active_nodes;archivePlaces2d(active_nodes) 함수는 활성 상태에서 벗어난 장소를 아카이브 상태로 변경하는 작업을 수행active_places)과 이전 활성 장소 집합(previous_active_places_2d_)을 비교previous_active_places_2d_에 현재 활성 노드 목록을 저장하여 다음 업데이트에서 비교할 수 있도록 합니다.