简介
lqOpenPcapReaction用于打开pcap点云流文件。
打开pcap文件过程:
第一步:点击打开文件按钮
第二步:选择pcap文件
第三步:选择传感器配置文件,可以选择高级配置进行更多参数的配置。
第二步中打开的对话框是lqOpenPcapReaction类中的成员函数onTriggered中触发的。该函数是Qt槽函数,通过 lqOpenPcapReaction对应的QAction类的triggered信号触发。该对话框是pqFileDialog类型。
同时在onTriggered函数中调用了lqOpenPcapReaction的静态函数createSourceFromFile(QString fileName),该函数中打开了第三步中的传感器配置对话框(vvCalibrationDialog),用于加载传感器的配置文件,同时调用了lqOpenPcapReaction的静态函数createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)。
lqOpenPcapReaction的基类是pqReaction。其绑定了一个QAction实例,当该QAction实例发出triggered信号时,调用onTriggered槽函数,该信号槽的绑定在pqReaction的构造函数中实现。因此lqOpenPcapReaction只需要实现虚函数onTriggered槽函数即可。
头文件
lqOpenPcapReaction头文件如下:
#ifndef LQOPENPCAPREACTION_H
#define LQOPENPCAPREACTION_H
#include "applicationui_export.h"
#include "pqReaction.h"
#include "vvCalibrationDialog.h"
#include <vtkObject.h>
/**
* @ingroup Reactions
* Reaction to open a pcap
*/
class APPLICATIONUI_EXPORT lqOpenPcapReaction : public pqReaction
{
Q_OBJECT
typedef pqReaction Superclass;
public:
lqOpenPcapReaction(QAction* action);
static void createSourceFromFile(QString fileName);
static void createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog);
protected:
/// Called when the action is triggered.
void onTriggered() override;
static void onProgressEvent(vtkObject* caller, unsigned long, void*);
private:
class vtkObserver;
Q_DISABLE_COPY(lqOpenPcapReaction)
};
#endif // LQOPENPCAPREACTION_H
源文件
lqOpenPcapReaction继承自pqReaction,在pqReaction的构造函数中会自动将onTriggered与QAction的triggered信号进行连接。lqOpenPcapReaction的构造函数中调用的其基类(pqReaction)的构造函数。
lqOpenPcapReaction::lqOpenPcapReaction(QAction* action)
: Superclass(action)
{
}
当lqOpenPcapReaction对应的QAction的triggered信号触发时,调用onTriggered槽函数,该槽函数主要打开了一个选择已存在点云流(pcap)文件的对话框,该对话框类型是pqFileDialog。然后调用了lqOpenPcapReaction中的静态函数createSourceFromFile(fileName)。代码如下:
void lqOpenPcapReaction::onTriggered()
{
// Get the pcap filename
// pqSettings是对QSetting的扩展,其支持保存以及恢复窗口以及对话框的几何设置。
pqSettings* settings = pqApplicationCore::instance()->settings();
const char* defaultDir_path = "LidarPlugin/OpenData/DefaultDir";
QString defaultDir = settings->value(defaultDir_path, QDir::homePath()).toString();
// 打开对话框,进行文件选额,对话框模式为打开已有文件。pqFileDialog也提供打开远程文件功能。
pqFileDialog dial(pqActiveObjects::instance().activeServer(),
pqCoreUtilities::mainWidget(),
tr("Open LiDAR File"),
defaultDir,
tr("Wireshark Capture (*.pcap)"));
dial.setObjectName("LidarFileOpenDialog");
dial.setFileMode(pqFileDialog::ExistingFile);
if (dial.exec() == QDialog::Rejected)
{
return;
}
// 获取选中的文件列表中第一个文件的名称。
QString fileName = dial.getSelectedFiles().at(0);
QFileInfo fileInfo(fileName); //QFileInfo用于获取文件信息。
// 保存当前文件所在的文件夹路径,用于下次打开时的默认路径
settings->setValue(defaultDir_path, fileInfo.absolutePath());
// 该函数中打开了传感器配置对话框。
lqOpenPcapReaction::createSourceFromFile(fileName);
}
createSourceFromFile该函数在该类中有2个重载,在本次调用中会再次调用其另一个重载createSourceFromFile(fileName, dialog)。其中dialog的类型是vvCalibrationDialog,用于设置传感器的配置参数,包括解译器的类型,解译器的配置文件,雷达位姿,是否开启GPS等。其界面在简介中的第三步中有展示。
createSourceFromFile(QString)代码如下:
void lqOpenPcapReaction::createSourceFromFile(QString fileName)
{
// Launch the calibration Dialog before creating the Source to allow to cancel the action
// (with the "cancel" button in the dialog)
vvCalibrationDialog dialog(lqLidarViewManager::instance()->getMainWindow(), false);
// DisplayDialogOnActiveWindow(dialog);
if (dialog.exec())
{
lqOpenPcapReaction::createSourceFromFile(fileName, dialog);
}
}
在createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)中,完成了数据读取器以及雷达数据帧的追踪滤波器的创建,并将其加载到界面上(渲染管线浏览器以及属性配置器)。其使用了创建者模式(pqObjectBuilder)进行读取器和滤波器的创建。功能调用使用了代理模式(vtkSMProxy)来完成对应功能的实现。目前读取器(雷达数据解译器)支持HESAI和VELODYNE雷达。
createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)代码如下:
void lqOpenPcapReaction::createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)
{
// 获取当前激活的服务
pqServer* server = pqActiveObjects::instance().activeServer();
// pqObjectBuilder是一个不严格的创建者设计模式
// 它用于创建和销毁复杂对象,如视图,显示器,数据源等。
// 通过pqApplicationCore单例模式可以获取pqObjectBuilder实例。
pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();
// 在vtkSMParaViewPipelineController的基础上添加了初始化合适的渲染方式的功能。
// vtkSMParaViewPipelineController用于控制ParaView的逻辑控制
vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;
// PQ抽象的一般视图模型,用于作为视图的基类。其子类可以指定不同方式的渲染方式,比如三维渲染视图,直方图。
pqView* view = pqActiveObjects::instance().activeView();
// Create a progress bar so the user see that LidarView is running
// 选定pcap文件以及配置文件后的打开文件的进度条
QProgressDialog progress("Reading pcap", "", 0, 0, lqLidarViewManager::getMainWindow());
progress.setCancelButton(nullptr);
progress.setModal(true);
progress.show();
// vtkProcessModule是ParaView进程的进程初始化和会话管理核心。
vtkProcessModule* pm = vtkProcessModule::GetProcessModule();
// vtkSession的子类,vtkPVSession扩展了vtkSession,用于对ParaView seesion添加API
// vtkPVSession添加ParaView规范的APIs到vtkSession。即这些APIs用于在数据服务,渲染服务以及客户端之间通信。
// vtkPVSession是抽象类
vtkPVSession* session = vtkPVSession::SafeDownCast(pm->GetSession());
if (!session)
{
return;
}
// vtkPVProgressHandler处理进度信息。
vtkSmartPointer<vtkPVProgressHandler> handler = session->GetProgressHandler();
handler->PrepareProgress();
double interval = handler->GetProgressInterval();
handler->SetProgressInterval(0.05);
vtkNew<vtkObserver> obs;
unsigned long tag = handler->AddObserver(vtkCommand::ProgressEvent, obs);
// Remove all Streams (and every filter depending on them) from pipeline Browser
// Thanks to the lqSensorListWidget,
// if a LidarStream is delete, it will automatically delete its PositionOrientationStream.
// So we just have to delete all lidarStream.
RemoveAllProxyTypeFromPipelineBrowser<vtkLidarStream>();
if (!dialog.isEnableMultiSensors())
{
// We remove all lidarReader (and every filter depending on them) in the pipeline
// Thanks to the lqSensorListWidget,
// if a LidarReader is delete, it will automatically delete its PositionOrientationReader.
// So we just have to delete all lidarReader.
RemoveAllProxyTypeFromPipelineBrowser<vtkLidarReader>();
}
// Create the lidar Reader
// We have to use pqObjectBuilder::createSource to add the created source to the pipeline
// The source will be created immediately so the signal "sourceAdded" of the pqServerManagerModel
// is send during "create source".
// To get the pqPipelineSource modified with the new property, you have to connect to the signal
// "dataUpdated" of the pqServerManagerModel
// 在server上创建名称为"sources"的数据源,该数据源的服务管理群组为"LidarReader"。
pqPipelineSource* lidarSource = builder->createSource("sources", "LidarReader", server);
lidarSource->setModifiedState(pqProxy::UNMODIFIED);
vtkSMPropertyHelper(lidarSource->getProxy(), "FileName").Set(fileName.toStdString().c_str());
lidarSource->getProxy()->UpdateProperty("FileName"); // 将文件的全路径更新到服务器
// For Hesai, enable advanced arrays by default
vtkSMProxy* interpProxy =
vtkSMPropertyHelper(lidarSource->getProxy(), "PacketInterpreter").GetAsProxy();
vtkSmartPointer<vtkLidarPacketInterpreter> interp =
vtkLidarPacketInterpreter::SafeDownCast(interpProxy->GetClientSideObject());
unsigned int found = interp->GetSensorInformation().find("Hesai");
if (found != std::string::npos)
{
vtkSMPropertyHelper(interpProxy, "EnableAdvancedArrays").Set(1);
interpProxy->UpdateProperty("PacketInterpreter");
lidarSource->getProxy()->UpdateProperty("PacketInterpreter");
}
QString lidarName = lidarSource->getSMName();
pqPipelineSource* posOrSource = nullptr;
QString posOrName = "";
// Update lidarSource and posOrSource
// If the GPS interpretation is asked, the posOrsource will be created in the
// lqUpdateCalibrationReaction because it has to manage it if the user enable interpreting GPS
// packet after the first instantiation
lqUpdateCalibrationReaction::UpdateCalibration(lidarSource, posOrSource, dialog);
// vtkSMProxy* lidarProxy = lidarSource->getProxy();
if (posOrSource)
{
posOrSource->updatePipeline();
posOrName = posOrSource->getSMName();
controller->Show(posOrSource->getSourceProxy(), 0, view->getViewProxy());
}
// Create the trailing Frame filter on the output of the LidarReader
QMap<QString, QList<pqOutputPort*>> namedInputs;
QList<pqOutputPort*> inputs;
inputs.push_back(lidarSource->getOutputPort(0));
namedInputs["Input"] = inputs;
pqPipelineSource* trailingFrameFilter =
builder->createFilter("filters", "TrailingFrame", namedInputs, server);
trailingFrameFilter->setModifiedState(pqProxy::UNMODIFIED);
// 在GUI上的Pipline Browser上显示LidarReader1以及TrailingFrame1
// Set the trailing frame associated to the sensor Widget
lqSensorListWidget* listSensor = lqSensorListWidget::instance();
listSensor->setSourceToDisplayToLidarSourceWidget(lidarSource, trailingFrameFilter);
// Update applogic to be able to use function only define in applogic.
lqLidarViewManager::instance()->runPython(
QString("lv.UpdateApplogicReader('%1', '%2')\n").arg(lidarName, posOrName));
// Show the trailing frame
controller->Show(trailingFrameFilter->getSourceProxy(), 0, view->getViewProxy());
pqActiveObjects::instance().setActiveSource(trailingFrameFilter);
pqApplicationCore::instance()->render();
// WIP Workaround Seek to first Frame To prevent displaying half frame from begining
pqAnimationScene* animScene =
pqPVApplicationCore::instance()->animationManager()->getActiveScene();
if (animScene)
{
QList<double> timesteps = animScene->getTimeSteps();
if (!timesteps.empty())
{
animScene->setAnimationTime(timesteps.first());
}
}
// Remove the handler so the user can interact with LidarView again (pushing any button)
handler->RemoveObserver(tag);
handler->LocalCleanupPendingProgress();
handler->SetProgressInterval(interval);
progress.close();
}
当加载雷达数据时,会显示一个进度信息,该进度信息是由vtk的观察者/命令模式进行控制,QProgressDialog进行展示。因此在该源文件的开头定义了基类为vgtCommand的类vtkObserver,代码如下:
class lqOpenPcapReaction::vtkObserver : public vtkCommand
{
public:
static vtkObserver* New()
{
vtkObserver* obs = new vtkObserver();
return obs;
}
void Execute(vtkObject*, unsigned long eventId, void*) override
{
if (eventId == vtkCommand::ProgressEvent)
{
QApplication::instance()->processEvents();
}
}
};
在源代码实现中,大量运用了创建者模式(pqObjectBuilder)和代理模式(vtkSMProxy)。
vtkSMProxy:
服务器上VTK对象的代理。
vtkSMProxy使用代理模式管理在服务器上创建的VTK对象。被管理对象通过属性进行操作。
由vtkSMProxy创建和管理的对象类型由VTKClassName变量决定。通过从代理获取所需的属性、更改其值并使用UpdateVTKObjects()更新服务器来管理该对象。
代理可以是复合的。代理管理器可以添加子代理。这对用户来说是透明的,用户可以看到所有属性,就好像它们属于根代理一样。
一个代理有一个iVar的连接ID。该连接ID表示一个存在该代理的连接。虽然,连接ID能够让一个客户端连接多个服务端。但在ParaView中,因为ParaView只存在一个服务端,因此该ID不重要。连接ID必须在创建代理时马上进行设置,在这之后改变连接ID的行为是危险的。
* Once a proxy has been defined, it can be listed in another secondary group
* \code
* <ProxyGroup name="new_group">
* < Proxy group = "group" name ="proxyname" />
* </ProxyGroup>
* \endcode
*
* When defining a proxy in the XML configuration file,
* to derive the property interface from another proxy definition,
* we can use attributes "base_proxygroup" and "base_proxyname" which
* identify the proxy group and proxy name of another proxy. Base interfaces
* can be defined recursively, however care must be taken to avoid cycles.
*
* There are several special XML features available for subproxies.
* \li 1) It is possible to share properties among subproxies.
* eg.
* \code
* <Proxy name="Display" class="Alpha">
* <SubProxy>
* <Proxy name="Mapper" class="vtkPolyDataMapper">
* <InputProperty name="Input" ...>
* ...
* </InputProperty>
* <IntVectorProperty name="ScalarVisibility" ...>
* ...
* </IntVectorProperty>
* ...
* </Proxy>
* </SubProxy>
* <SubProxy>
* <Proxy name="Mapper2" class="vtkPolyDataMapper">
* <InputProperty name="Input" ...>
* ...
* </InputProperty>
* <IntVectorProperty name="ScalarVisibility" ...>
* ...
* </IntVectorProperty>
* ...
* </Proxy>
* <ShareProperties subproxy="Mapper">
* <Exception name="Input" />
* </ShareProperties>
* </SubProxy>
* </Proxy>
* \endcode
* Thus, subproxies Mapper and Mapper2 share the properties that are
* common to both; except those listed as exceptions using the "Exception"
* tag.
*
* \li 2) It is possible for a subproxy to use proxy definition defined elsewhere
* by identifying the interface with attributes "proxygroup" and "proxyname".
* eg.
* \code
* <SubProxy>
* <Proxy name="Mapper" proxygroup="mappers" proxyname="PolyDataMapper" />
* </SubProxy>
* \endcode
*
* \li 3) It is possible to scope the properties exposed by a subproxy and expose
* only a fixed set of properties to be accessible from outside. Also,
* while exposing the property, it can be exposed with a different name.
* eg.
* \code
* <Proxy name="Alpha" ....>
* ....
* <SubProxy>
* <Proxy name="Mapper" proxygroup="mappers" proxyname="PolyDataMapper" />
* <ExposedProperties>
* <Property name="LookupTable" exposed_name="MapperLookupTable" />
* </ExposedProperties>
* </SubProxy>
* </Proxy>
* \endcode
* Here, for the proxy Alpha, the property with the name LookupTable from its
* subproxy "Mapper" can be obtained by calling GetProperty("MapperLookupTable")
* on an instance of the proxy Alpha. "exposed_name" attribute is optional, if
* not specified, then the "name" is used as the exposed property name.
* Properties that are not exposed are treated as
* non-saveable and non-animateable (see vtkSMProperty for details).
* Exposed property restrictions only work when
* using the GetProperty on the container proxy (in this case Alpha) or
* using the PropertyIterator obtained from the container proxy. If one
* is to some how obtain a pointer to the subproxy and call GetProperty on
* it (or get a PropertyIterator for the subproxy), the properties exposed
* by the container class are no longer applicable.
* If two exposed properties are exposed with the same name, then a Warning is
* flagged -- only one of the two exposed properties will get exposed.
updateCalibration:读取配置信息。其中调用了lqUpdateCalibrationReaction::setReaderCalibration中选择了雷达数据解译器的具体实现。其代码如下:
// Applicatoin/Ui/lqUpdateCalibrationReaction.cxx > setReaderCalibration
if (interpreter == vvCalibration::Plugin::HESAI)
{
// Set General Packet Interpreter based on SDK as default
defaultProxy =
proxyListDomain->FindProxy("LidarPacketInterpreter", "HesaiGeneralPacketInterpreter");
// Set Custom Interpreter for Pandar128 if name implies it
if ((calibrationFile.contains("Pandar128", Qt::CaseInsensitive)))
{
defaultProxy =
proxyListDomain->FindProxy("LidarPacketInterpreter", "HesaiPacketInterpreter");
}
}
else if (interpreter == vvCalibration::Plugin::VELODYNE)
{
// Set Meta Interpreter as Default
defaultProxy =
proxyListDomain->FindProxy("LidarPacketInterpreter", "VelodyneMetaPacketInterpreter");
}
else
{
qCritical() << "Unknown Interpreter Type";
return;
}