php网站上线,汕头建站网站模板,百度云盘登录入口,网站建设合同的性质一、TBB的应用
在前面分析了TBB框架的各种基本知识和相关的基础应用。这些基础的应用很容易通过学习文档或相关的代码来较为轻松的掌握。为了能够更好的理解TBB框架的优势#xff0c;这里从一个开源的应用程序来分析一下TBB在其中的更高一层的抽象应用#xff0c;以方便开发…一、TBB的应用
在前面分析了TBB框架的各种基本知识和相关的基础应用。这些基础的应用很容易通过学习文档或相关的代码来较为轻松的掌握。为了能够更好的理解TBB框架的优势这里从一个开源的应用程序来分析一下TBB在其中的更高一层的抽象应用以方便开发者能够更好的理解和深入掌握TBB框架。也从设计角度为开发者提供了一个TBB应用的方向从而能够进一步将TBB框架从基础的技术应用上升到架构设计。
二、Supra项目的介绍
SUPRA: Open Source Software Defined Ultrasound Processing for Real-Time Applications。它是一个开源的超声实时应用软件主要是在医学领域的超声数据的图像重建和输出同时允许在图像数据的处理过程中对其进行完善和修改。它支持CPU和GPU两种模式支持2D和3D图像的格式。 SUPRA的整体的框架基础是在TBB的框架基础上进行设计的它在TBB的任务、节点等抽象的层次上又进行了一次抽象的封装让其更接近于人们的认知形态。
三、整体架构分析
SUPRA的架构分析将整体略过上层应用的部分因为这块与UI和实际业务强相关与今天要分析的TBB没有什么关系。主要谈一下其库的内容设计。SUPRA将整体的设计划分成了几层 1、TBB节点的抽象层 SUPRA在TBB现有节点的基础上再次抽象。实现了输入、输出和算法等节点。但是其这种抽象与TBB本身的输入、输出完全不同其设计的节点完全是纯逻辑上的意义。看一下它的代码定义
class AbstractNode {
protected:typedef tbb::flow::function_nodestd::shared_ptrRecordObject, std::shared_ptrRecordObject, tbb::flow::rejectingNodeTypeDiscarding;typedef tbb::flow::function_nodestd::shared_ptrRecordObject, std::shared_ptrRecordObject, tbb::flow::queueingNodeTypeQueueing;typedef tbb::flow::function_nodestd::shared_ptrRecordObject, tbb::flow::continue_msg, tbb::flow::rejectingNodeTypeOneSidedDiscarding;typedef tbb::flow::function_nodestd::shared_ptrRecordObject, tbb::flow::continue_msg, tbb::flow::queueingNodeTypeOneSidedQueueing;public:/// Base constructor for all nodesAbstractNode(const std::string nodeID, bool queueing) : m_nodeID(nodeID), m_queueing(queueing) {m_configurationDictionary.setValueRangeDictionary(m_valueRangeDictionary);}virtual ~AbstractNode() {}virtual size_t getNumInputs() 0;virtual size_t getNumOutputs() 0;/// Returns a pointer to the input port with the given numbervirtual tbb::flow::graph_node *getInput(size_t index) { return nullptr; }/// Returns a pointer to the output port with the given numbervirtual tbb::flow::graph_node *getOutput(size_t index) { return nullptr; }const ValueRangeDictionary *getValueRangeDictionary() { return m_valueRangeDictionary; }const ConfigurationDictionary *getConfigurationDictionary() { return m_configurationDictionary; }const std::string getNodeID() { return m_nodeID; }template typename ValueType bool changeConfig(const std::string configKey, const ValueType newValue) {if (m_valueRangeDictionary.hasKey(configKey) m_valueRangeDictionary.isInRange(configKey, newValue)) {LOG(INFO) Parameter: m_nodeID . configKey newValue;m_configurationDictionary.set(configKey, newValue);configurationEntryChanged(configKey);return true;}return false;}void changeConfig(const ConfigurationDictionary newConfig) {configurationDictionaryChanged(newConfig);// validate the configuration entriesConfigurationDictionary validConfig newConfig;validConfig.setValueRangeDictionary(m_valueRangeDictionary);validConfig.checkEntriesAndLog(m_nodeID);// store all valid entriesm_configurationDictionary validConfig;configurationChanged();}std::string getTimingInfo() { return m_callFrequency.getTimingInfo(); }protected:/// The collection of node parametersConfigurationDictionary m_configurationDictionary;/// The definition of parameters and their respective rangesValueRangeDictionary m_valueRangeDictionary;CallFrequency m_callFrequency;bool m_queueing;protected:virtual void configurationEntryChanged(const std::string configKey) {}virtual void configurationChanged() {}virtual void configurationDictionaryChanged(const ConfigurationDictionary newConfig) {}private:std::string m_nodeID;
};
class AbstractInput : public AbstractNode {
public:/// Base constructor for the input node. Initializes its output ports.AbstractInput(tbb::flow::graph graph, const std::string nodeID, size_t numPorts): AbstractNode(nodeID, false), m_numOutputs(numPorts) {m_pOutputNodes.resize(m_numOutputs);for (size_t i 0; i m_numOutputs; i) {m_pOutputNodes[i] std::unique_ptrtbb::flow::broadcast_nodestd::shared_ptrRecordObject(new tbb::flow::broadcast_nodestd::shared_ptrRecordObject(graph));}}~AbstractInput() { waitForFinish(); }void waitForFinish() {if (m_pInputDeviceThread m_pInputDeviceThread-joinable()) {m_pInputDeviceThread-join();}}void detachThread() { this-m_pInputDeviceThread-detach(); }void start() {setRunning(true);m_pInputDeviceThread std::make_sharedstd::thread(std::thread([this]() { this-startAcquisition(); }));}/// Set the state of the input node, if newState is false, the node is stoppedvirtual bool setRunning(bool newState) {bool oldState m_running;m_running newState;if (!m_running) {stopAcquisition();}return (oldState || newState) !(oldState oldState);}/// Returns whether the node is runningbool getRunning() { return m_running; }/// returns the output port with the given indextemplate size_t index tbb::flow::broadcast_nodestd::shared_ptrRecordObject getOutputNode() {return *std::getindex(m_pOutputNodes);}virtual size_t getNumInputs() { return 0; }/// returns the number of output ports of this nodevirtual size_t getNumOutputs() { return m_pOutputNodes.size(); }/// returns a pointer to the output port with the given indexvirtual tbb::flow::graph_node *getOutput(size_t index) {if (index m_pOutputNodes.size()) {return m_pOutputNodes[index].get();}return nullptr;}protected:/// The nodes output. An implementing node calls this method when it has a/// dataset to send into the graph.template size_t index bool addData(std::shared_ptrRecordObject data) {return m_pOutputNodes[index]-try_put(data);}double getTimerFrequency() { return m_timer.getFrequency(); }void setUpTimer(double frequency) { m_timer.setFrequency(frequency); }void timerLoop() {bool shouldContinue true;while (shouldContinue) {shouldContinue timerCallback();if (shouldContinue) {m_timer.sleepUntilNextSlot();}}}private:std::vectorstd::unique_ptrtbb::flow::broadcast_nodestd::shared_ptrRecordObject m_pOutputNodes;SingleThreadTimer m_timer;std::shared_ptrstd::thread m_pInputDeviceThread;std::atomic_bool m_running;protected:std::mutex m_mutex;const size_t m_numOutputs;// Functions to be overwritten
public:virtual void initializeDevice() {}virtual bool ready() { return false; }virtual std::vectorsize_t getImageOutputPorts() 0;virtual std::vectorsize_t getTrackingOutputPorts() 0;virtual void freeze() 0;virtual void unfreeze() 0;protected:/// The entry point for the implementing input node/// This method is called in a separate thread once the node is started.virtual void startAcquisition() 0;virtual void stopAcquisition() {}virtual bool timerCallback() { return false; }
};其它节点的代码就不拷贝上来了。从上面的代码可以看到其节点的设计几乎等同于应用的逻辑表达了也就是说抽象的层次更高了。
2、TBB节点抽象后的动态管理层 动态管理层分为几部分首先是节点的性质、参数以太连接状态等由一个XML配置文件来实现。当然它也支持在代码中完全动态的调整其次节点的管理注册由一个专门的工厂类的实现包括节点的创建和连接等最后它实现了对节点重点参数的动态修改实现了一套相关的XML自动映射机制。 这里暂时只关注一下工厂类的处理
std::shared_ptrAbstractOutput InterfaceFactory::createOutputDevice(shared_ptrtbb::flow::graph pG,const std::string nodeID, std::string deviceType,bool queueing) {std::shared_ptrAbstractOutput retVal std::shared_ptrAbstractOutput(nullptr);if (deviceType OpenIGTLinkClientOutputDevice) {retVal std::make_sharedOpenIGTLinkClientOutputDevice(*pG, nodeID, queueing);}if (deviceType DatasCacheOutputDevice) {retVal std::make_sharedDatasCacheOutputDevice(*pG, nodeID, queueing);}LOG_IF(ERROR, !((bool)retVal)) Error creating output device. Requested type deviceType is unknown. Did you activate the corresponding module in the build of the library?;LOG_IF(INFO, (bool)retVal) Created output device deviceType with ID nodeID ;return retVal;
}在后面会对这一块进行较详细的分析。 3、数据处理层 在每个功能节点都会有类似下面的writeData来连接抽象节点与TBB节点之间的联系来处理数据 AbstractOutput(tbb::flow::graph graph, const std::string nodeID, bool queueing) : AbstractNode(nodeID, queueing) {if (queueing) {m_inputNode std::unique_ptrNodeTypeOneSidedQueueing(new NodeTypeOneSidedQueueing(graph, 1, [this](const std::shared_ptrRecordObject inMessage) {if (this-m_running) {writeData(inMessage);}}));} else {m_inputNode std::unique_ptrNodeTypeOneSidedDiscarding(new NodeTypeOneSidedDiscarding(graph, 1, [this](const std::shared_ptrRecordObject inMessage) {if (this-m_running) {writeData(inMessage);}}));}}这样就非常巧妙的把二者整合到一想非常值得借鉴。
4、算法处理层
其算法处理层就是在节点中拿到数据后对相关数据进行处理比如各种图像的处理、数据的压缩等等 shared_ptrRecordObject BeamformingMVNode::checkTypeAndBeamform(shared_ptrRecordObject inObj){unique_lockmutex l(m_mutex);shared_ptrUSImage pImageRF nullptr;if (inObj-getType() TypeUSRawData){shared_ptrconst USRawData pRawData dynamic_pointer_castconst USRawData(inObj);if (pRawData){if (pRawData-getImageProperties()-getImageState() USImageProperties::RawDelayed){m_callFrequency.measure();switch (pRawData-getDataType()){case TypeInt16:pImageRF beamformTemplatedint16_t(pRawData);break;case TypeFloat:pImageRF beamformTemplatedfloat(pRawData);break;default:logging::log_error(BeamformingMVNode: Input rawdata type is not supported.);break;}m_callFrequency.measureEnd();if (m_lastSeenImageProperties ! pImageRF-getImageProperties()){updateImageProperties(pImageRF-getImageProperties());}pImageRF-setImageProperties(m_editedImageProperties);}else {logging::log_error(BeamformingMVNode: Cannot beamform undelayed RawData. Apply RawDelayNode first);}}else {logging::log_error(BeamformingMVNode: could not cast object to USRawData type, is it in supported ElementType?);}}return pImageRF;}这个函数checkTypeAndBeamform(类似于writedata函数)内部调用了 template typename RawDataTypestd::shared_ptrUSImage BeamformingMVNode::beamformTemplated(shared_ptrconst USRawData rawData){shared_ptrUSImage pImageRF nullptr;cudaSafeCall(cudaDeviceSynchronize());cublasSafeCall(cublasSetStream(m_cublasH, rawData-getDataRawDataType()-getStream()));switch (m_outputType){case supra::TypeInt16:pImageRF RxBeamformerMV::performRxBeamformingRawDataType, int16_t(rawData, m_subArraySize, m_temporalSmoothing, m_cublasH, m_subArrayScalingPower, m_computeMeans);break;case supra::TypeFloat:pImageRF RxBeamformerMV::performRxBeamformingRawDataType, float(rawData, m_subArraySize, m_temporalSmoothing, m_cublasH, m_subArrayScalingPower, m_computeMeans);break;default:logging::log_error(BeamformingMVNode: Output image type not supported:);break;}cudaSafeCall(cudaDeviceSynchronize());return pImageRF;}这其实就进入了算法的处理。后面复杂的算法处理就不拷贝上来了有兴趣可以自己看看。 5、输入输出层 这个也非常值得借鉴数据的输入和经过TBB算法处理后的数据需要传递给相关的各方此处SUPRA也提供了很好的范例
//输入bool UltrasoundInterfaceRawDataMock::timerCallback() {if (!m_frozen){double timestamp getCurrentTime();m_callFrequency.measure();shared_ptrUSRawData pRawData std::make_sharedUSRawData(m_protoRawData-getNumScanlines(),m_protoRawData-getNumElements(),m_protoRawData-getElementLayout(),m_protoRawData-getNumReceivedChannels(),m_protoRawData-getNumSamples(),m_protoRawData-getSamplingFrequency(),m_pMockData,m_protoRawData-getRxBeamformerParameters(),m_protoRawData-getImageProperties(),getCurrentTime(),getCurrentTime());addData0(pRawData);if (!m_singleImage){if (m_lastFrame){setRunning(false);}else{readNextFrame();}}m_callFrequency.measureEnd();}return getRunning();}输入节点通过读取MOCK的文件数据来复现实际的图像和相关的数据。再看一下输出
//输出void OpenIGTLinkOutputDevice::writeData(std::shared_ptrRecordObject data){if (m_isReady getRunning() m_isConnected){m_callFrequency.measure();sendMessage(data);m_callFrequency.measureEnd();}}//最终调用template typename Tvoid OpenIGTLinkOutputDevice::sendImageMessageTemplated(shared_ptrconst USImage imageData){static_assert(std::is_sameT, uint8_t::value ||std::is_sameT, int16_t::value ||std::is_sameT, float::value,Image only implemented for uchar, short and float at the moment);auto properties imageData-getImageProperties();if (properties-getImageType() USImageProperties::BMode ||properties-getImageType() USImageProperties::Doppler){double resolution properties-getImageResolution();vec3s imageSize imageData-getSize();igtl::ImageMessage::Pointer pImageMsg igtl::ImageMessage::New();pImageMsg-SetDimensions((int)imageSize.x, (int)imageSize.y, (int)imageSize.z);pImageMsg-SetSpacing(resolution, resolution, resolution);if (is_sameT, uint8_t::value){pImageMsg-SetScalarTypeToUint8();}if (is_sameT, int16_t::value){pImageMsg-SetScalarTypeToInt16();}if (is_sameT, float::value){pImageMsg-SetScalarType(igtl::ImageMessage::TYPE_FLOAT32);}pImageMsg-SetEndian(igtl::ImageMessage::ENDIAN_LITTLE);igtl::Matrix4x4 m;igtl::IdentityMatrix(m);m[0][0] -1;m[1][1] -1;pImageMsg-SetMatrix(m);pImageMsg-SetNumComponents(1);pImageMsg-SetDeviceName(m_streamName.c_str());pImageMsg-AllocateScalars();igtl::TimeStamp::Pointer pTimestamp igtl::TimeStamp::New();double timestampSeconds;double timestampFrac modf(imageData-getSyncTimestamp(), timestampSeconds);pTimestamp-SetTime((uint32_t)timestampSeconds, (uint32_t)(timestampFrac*1e9));pImageMsg-SetTimeStamp(pTimestamp);auto imageContainer imageData-getDataT();if (!imageContainer-isHost()){imageContainer make_sharedContainerT (LocationHost, *imageContainer);}size_t numElements imageSize.x * imageSize.y * imageSize.z;memcpy(pImageMsg-GetScalarPointer(), imageContainer-get(), numElements * sizeof(T));pImageMsg-Pack();int sendResult m_clientConnection-Send(pImageMsg-GetPackPointer(), pImageMsg-GetPackSize());if (sendResult 0) //when it could not be sent{m_isConnected false;log_info(IGTL: Lost connection. Waiting for next connection.);waitAsyncForConnection();}}}这个输出节点提供的是医疗上常用的IGTL通信模块来做为输出节点的最终通信方式。
四、总结
之所以从SUPRA框架入手最主要的就是其在设计上有机的整合了TBB框架将业务逻辑更好的与TBB框架的设计再次抽象在整体流程实现的过程中实现了业务逻辑与底层技术的动态组合。是一个非常值得借鉴的设计。