插件算法管理以及任务队列机制
插件算法管理以及任务队列机制
插件管理
插件管理者执行构造函数的时候,加载所有插件实例,每一个插件实例在构造的时候加载它的插件,外部使用者通过插件管理者的QMap获得想要的插件,可以通过得到插件的具体算法。
抽象插件基类
抽象插件基类,给出基本的接口。
#ifndef ABSTRACTPLUGIN_H
#define ABSTRACTPLUGIN_H
class AbstractPlugin {
public:
AbstractPlugin() = default;
virtual ~AbstractPlugin() = default;
virtual bool start(const char*) = 0;
virtual bool stop() = 0;
virtual double progress() = 0;
};
#endif // ABSTRACTPLUGIN_H
插件导入导出规范
利用了工厂设计模式,注册插件的时候导出具体插件类,加载的时候也可以得到具体插件类。
#define PLUGIN(classType, pluginName, pluginVersion) \
extern "C" { \
__attribute__ ((visibility ("default"))) AbstractPlugin* createPlugin() { \
return new classType(); \
} \
__attribute__ ((visibility ("default"))) Plugin exports = { \
pluginName, \
pluginVersion, \
createPlugin, \
}; \
}
#endif //C3PTEMPLATE_PLUGIN_H
attribute((visibility(“default”)))
__attribute__((visibility("default"))) //默认,设置为:default之后就可以让外面的类看见了。
__attribute__((visibility("hideen"))) //隐藏
visibility用于设置动态链接库中函数的可见性,将变量或函数设置为hidden,则该符号仅在本so中可见,在其他库中则不可见。
g++在编译时,可用参数-fvisibility指定所有符号的可见性(不加此参数时默认外部可见,参考man g++中-fvisibility部分);若需要对特定函数的可见性进行设置,需在代码中使用attribute设置visibility属性。
编写大型程序时,可用-fvisibility=hidden设置符号默认隐藏,针对特定变量和函数,在代码中使用attribute ((visibility(“default”)))另该符号外部可见,这种方法可用有效避免so之间的符号冲突。
FDK插件算法编译命令:
nvcc --compiler-options "-Bsymbolic -fvisibility=hidden -fPIC " -shared -O3 -std=c++11 -Xcompiler -fopenmp *.cpp *.cu -o libFdkCuda.so
插件实例
插件实例负责加载、运行和监督插件的运行进度。
#include "PluginInstance.h"
#include <thread>
#include "tool/macroTools.h"
#include "tool/Log.h"
PluginInstance::PluginInstance(QString path) {
mPluginPath = path;
reload();
}
PluginInstance::~PluginInstance() {
SAFE_DELETE(mPlugin);
mLib.close();
if (mPluginExport != nullptr) {
// PluginExport has been deleted by mLib.
Log::exec("PluginInstance::~PluginInstance() mPluginExport is not nullptr!");
mPluginExport = nullptr;
}
}
bool PluginInstance::reload() {
auto str = mPluginPath.toStdString();
auto path = str.c_str();
mLib.close();
if (!mLib.open(path)) {
Log::exec(QString("PluginInstance::loadPlugin() Open %1 failed!").arg(path));
return false;
}
if (!mLib.symbol("exports", (void**)(&mPluginExport))) {
Log::exec(QString("Library::symbol() Symbol %1 failed!").arg(path));
return false;
}
mPluginName = mPluginExport->mPluginName;
Log::exec(QString("PluginInstance::loadPlugin() Plugin Name: %1.").arg(mPluginExport->mPluginName));
SAFE_DELETE(mPlugin);
mPlugin = mPluginExport->mCreateFunc();
if (mPlugin == nullptr) {
Log::exec("PluginInstance::loadPlugin() Plugin Instance is nullptr!");
}
Log::exec("PluginInstance::loadPlugin() Plugin Instancing succeed.");
return true;
}
bool PluginInstance::join(QString iniPath) {
if (!runnable()) {
return false;
}
mIniPath = iniPath;
std::thread(starter, this).join();
std::thread(progresser, this).join();
return true;
}
bool PluginInstance::detach(QString iniPath) {
if (!runnable()) {
return false;
}
mIniPath = iniPath;
std::thread(starter, this).detach();
std::thread(progresser, this).detach();
return true;
}
double PluginInstance::progress() const {
return mProgress.progress();
}
bool PluginInstance::runnable() {
if (mPlugin == nullptr) {
Log::exec("PluginInstance::runnable() failed! mpAlgorithm is nullptr.");
return false;
} else if (mIsRunning) {
Log::exec("PluginInstance::runnable() failed! Algorithm is still running.");
return false;
}
return true;
}
QString PluginInstance::pluginName() const {
return mPluginName;
}
void PluginInstance::starter(PluginInstance *self) {
Log::exec("PluginInstance::starter() starting starter thread...");
self->mIsRunning = true;
self->mPlugin->start(self->mIniPath.toStdString().c_str());
}
void PluginInstance::progresser(PluginInstance *self) {
Log::exec("PluginInstance::progresser() starting progress thread...");
while (self->mPlugin != nullptr && self->mPlugin->progress() <= 1.0) {
if (self->mProgress.setProgress(self->mPlugin->progress())) {
emit self->sigProgress(self->mProgress.progress());
}
}
self->mProgress.finish();
self->mIsRunning = false;
self->reload();
emit self->sigProgress(self->mProgress.progress());
emit self->sigFinished();
}
插件管理者
主要负责初始化的时候加载所有插件实例,提供对外的接口,让外部可以得到想要执行的插件。
#include "PluginManager.h"
#include <QDir>
#include <QStringList>
#include "metainfo/SystemMetaInfo.h"
#include "tool/macroTools.h"
#include "tool/Log.h"
PluginManager *PluginManager::instance() {
static PluginManager pm;
return ±
}
PluginInstance *PluginManager::getPluginIns(QString pluginName) {
if (mPluginList.end() == mPluginList.find(pluginName)) {
Log::exec(QString("PluginManager::getPluginIns() Plugin name %1 not found!").arg(pluginName));
return nullptr;
}
return mPluginList[pluginName];
}
bool PluginManager::isNameDuplicated(QString name) {
if (mPluginList.end() != mPluginList.find(name)) {
Log::exec(QString("PluginManager::isNameDuplicated() Plugin name %1 already exist!").arg(name));
return true;
}
return false;
}
PluginManager::PluginManager() {
QString pluginPath{QString("%1/").arg(SYSTEM_META_CONFIG->getQStringValue("Plugin", "path"))};
QDir dir(pluginPath);
if (dir.isEmpty()) {
Log::exec(QString("PluginManager::PluginManager() No plugin found in %1!").arg(pluginPath));
}
QStringList list{dir.entryList(QDir::Files)};
for (QFileInfo item : list) {
QStringList nameSplit{item.fileName().split(".")};
if (nameSplit.back() == "so" || nameSplit.back() == "dll") {
QString pluginFilePath{pluginPath + nameSplit.first()};
if (!isNameDuplicated(nameSplit.first())) {
mPluginList[nameSplit.first()] = new PluginInstance(pluginFilePath);
}
}
}
}
PluginManager::~PluginManager() {
for (auto& item : mPluginList) {
SAFE_DELETE(item);
}
}
算法管理
算法管理与插件管理十分类似,只不过算法管理直接内嵌在平台中,而插件算法是独立于平台之外,加载到平台中。
抽象算法基类
#ifndef ABSTRACTALGORITHM_H
#define ABSTRACTALGORITHM_H
class AbstractAlgorithm {
public:
AbstractAlgorithm() = default;
virtual ~AbstractAlgorithm() = default;
virtual bool start(const char*) = 0; // .ini path
virtual bool stop() = 0;
virtual double progress() = 0;
};
#endif // ABSTRACTALGORITHM_H
算法Map与具体算法注册规范
#define BASE_CLASS AbstractAlgorithm
#define ALGORITHM_MAP AlgorithmMap<BASE_CLASS>::instance()
#define DEFINE_CLASS(className, funcName) \
className(QString) {\
ALGORITHM_MAP->regist(#funcName, className::funcName);\
}\
className(){}\
static className className##_;\
static BASE_CLASS* funcName() {\
return new className;\
}
#define REGIST_CLASS(className) \
className className::className##_(#className);
template<class T>
class AlgorithmMap {
public:
typedef T*(*FUNCTION)(void);
void regist(QString funcName, FUNCTION func) {
mFunctionMap[funcName] = func;
}
T* get(const QString funcName) {
if (mFunctionMap.end() != mFunctionMap.find(funcName)) {
return mFunctionMap[funcName]();
} else {
return nullptr;
}
}
static AlgorithmMap<T>* instance() {
static AlgorithmMap<T> algoMap;
return &algoMap;
}
QMap<QString, FUNCTION>& funcMap() const {
return mFunctionMap;
}
private:
QMap<QString, FUNCTION> mFunctionMap;
};
#endif // ALGORITHMMAPPER_H
先定义,后注册,注册时候会加载到具体算法Map中
PetCoin
头文件
#ifndef ALGORITHMPETCOIN_H
#define ALGORITHMPETCOIN_H
#include <fstream>
#include "../AlgorithmMap.h"
#include "AlgoPetCoinPara.h"
#include "CoinStruct.h"
#include "CoinTimeRecorder.h"
#include "../PetScan/DataFrameV2.h"
#include "../Calibration/position/PositionMap.h"
#include "../Calibration/energy/EnergyMap.h"
class AlgorithmPetCoin : public AbstractAlgorithm {
public:
DEFINE_CLASS(AlgorithmPetCoin, PetCoin);
virtual ~AlgorithmPetCoin();
bool start(const char *path) override;
bool stop() override;
double progress() override;
double coinRate();
private:
bool convertDataFrameToSingles(DataFrameV2& src, SinglesStruct& dst);
bool coinEnergy(double energy) const; // 能量符合,只有在能量窗内的singles才能被选择出来 TODO: 前置能量符合
void coinTime(SinglesStruct& single); // 时间符合,只有在时间窗内的singles才能被选择出来
int coinTimeDelayWindow(SinglesStruct& single1, SinglesStruct& single2); //0: in the DW; 1: before the DW; -1: after the DW.
bool coinPosition(SinglesStruct& single1, SinglesStruct& single2);
AlgoPetCoinPara mPetCoinPara{};
QVector<QVector<int>> mGroupCsv{};
QVector<int> mSinglesNumList{};
QVector<int> mBdmFileSizeList{};
std::vector<std::ifstream> mBdmFileList{};
SinglesStruct** mSingles;
SinglesStruct* mSortedSingles{nullptr}; // 合并后1路有序singles数组
int mSortedIndex{0}; // mSortedSingles的指针
QVector<int> mCurSingleIndexList;
unsigned long long mTotalSinglesNum{0};
QVector<CoinStruct> mCoinList;
CoinTimeRecorder mCoinTimeRecorder; // 一个用于时间符合的singles记录器
int mTotalCoinNum{0};
PositionMap* mPositionMap{nullptr};
EnergyMap* mEnergyMap{nullptr};
double mProgress{0.0};
double mCoinRate{0.0};
int mMinSectorDiff{0};
int mDelayedWindow{0};
};
#endif // ALGORITHMPETCOIN_H
源文件
#include "AlgorithmPetCoin.h"
#include <cmath>
#include <cfloat>
#include <thread>
#include <vector>
#include "PetCoinToMich.h"
#include "tool/IniConfig.h"
#include "tool/Log.h"
#include "tool/TimerClock.h"
#include "tool/CsvTool.h"
#include "tool/macroTools.h"
#include "setting/Setting.h"
#include "metainfo/SystemMetaInfo.h"
#include "metainfo/CaliMetaInfo.h"
REGIST_CLASS(AlgorithmPetCoin);
AlgorithmPetCoin::~AlgorithmPetCoin() {
SAFE_DELETE(mPositionMap);
SAFE_DELETE(mEnergyMap);
}
bool AlgorithmPetCoin::start(const char *path) {
// 1. init para
IniConfig coinIni(path);
coinIni.setSegmentName("CoincidenceInfo");
mPetCoinPara.petPath = coinIni.getQStringValue("petPath");
mPetCoinPara.coinPath = coinIni.getQStringValue("coinPath");
// mPetCoinPara.positionPath = coinIni.getQStringValue("positionPath");
// mPetCoinPara.energyPath = coinIni.getQStringValue("energyPath");
mPetCoinPara.positionSize = SETTING->mPetSetting.petScannerSetting.positionSize;
// mPetCoinPara.crystalSize = SETTING->mPetSetting.petScannerSetting.crystalNumY;
mPetCoinPara.crystalNumX = SETTING->mPetSetting.petScannerSetting.crystalNumX;
mPetCoinPara.crystalNumY = SETTING->mPetSetting.petScannerSetting.crystalNumY;
mPetCoinPara.crystalNumZ = SETTING->mPetSetting.petScannerSetting.crystalNumZ;
mPetCoinPara.blockNumX = SETTING->mPetSetting.petScannerSetting.blockNumX;
mPetCoinPara.blockNumY = SETTING->mPetSetting.petScannerSetting.blockNumY;
mPetCoinPara.blockNumZ = SETTING->mPetSetting.petScannerSetting.blockNumZ;
mPetCoinPara.moduleNumX = SETTING->mPetSetting.petScannerSetting.moduleNumX;
mPetCoinPara.moduleNumY = SETTING->mPetSetting.petScannerSetting.moduleNumY;
mPetCoinPara.moduleNumZ = SETTING->mPetSetting.petScannerSetting.moduleNumZ;
mPetCoinPara.bdmNum = SETTING->mPetSetting.petScannerSetting.bdmNum;
mPetCoinPara.duNum = SETTING->mPetSetting.petScannerSetting.duNum;
mPetCoinPara.timingWindow = coinIni.getIntValue("timingWindow");
mPetCoinPara.energyWindowStart = coinIni.getIntValue("energyWindowStart");
mPetCoinPara.energyWindowEnd = coinIni.getIntValue("energyWindowEnd");
mPetCoinPara.coinType = coinIni.getQStringValue("coinType");
mPetCoinPara.bedIndex = coinIni.getIntValue("bedIndex");
mPetCoinPara.isFastCoin = false;
CALI_META_CONFIG->setSegmentName(SETTING->mPetSetting.curPetDevice);
SYSTEM_META_CONFIG->setSegmentName("NamePreset");
QString posTablePath = QString("%1/%2").arg(CALI_META_CONFIG->getQStringValue("positionPath"))
.arg(SYSTEM_META_CONFIG->getQStringValue("positionTable"));
QString energyCorrFactorPath = QString("%1/%2").arg(CALI_META_CONFIG->getQStringValue("energyPath"))
.arg(SYSTEM_META_CONFIG->getQStringValue("energyCorrFactor"));
mPositionMap = new PositionMap;
mEnergyMap = new EnergyMap;
mPositionMap->ReadPositionTable(posTablePath);
mEnergyMap->readEnergyCorrFactor(energyCorrFactorPath);
int lBdmNum{mPetCoinPara.bdmNum};
mBdmFileSizeList.resize(lBdmNum);
mSinglesNumList.resize(lBdmNum);
mBdmFileList.resize(lBdmNum);
for (int bdmIndex = 0;bdmIndex < lBdmNum;++bdmIndex) {
QString lBdmFilePath{QString("%1/%2").arg(mPetCoinPara.petPath)
.arg(SYSTEM_META_CONFIG->getQStringValue("Data", "petScanFile").arg(bdmIndex))};
mBdmFileList[bdmIndex].open(lBdmFilePath.toStdString());
if (!mBdmFileList[bdmIndex].is_open()) {
Log::exec(QString("AlgorithmPetCoin::start() Open file %1 failed!").arg(lBdmFilePath));
return false;
}
mBdmFileList[bdmIndex].seekg(0, std::ios_base::end);
mBdmFileSizeList[bdmIndex] = mBdmFileList[bdmIndex].tellg();
mBdmFileList[bdmIndex].seekg(0, std::ios_base::beg);
mSinglesNumList[bdmIndex] = mBdmFileSizeList[bdmIndex] / sizeof(DataFrameV2);
mTotalSinglesNum += mSinglesNumList[bdmIndex];
}
Log::exec(QString("AlgorithmPetCoin::start() Total singles num = %1").arg(mTotalSinglesNum));
Log::exec(QString("AlgorithmPetCoin::start() %1 MB memory needed.").arg(double(mTotalSinglesNum) * sizeof(DataFrameV2) / 1024 / 1024));
QString lGroupCsvPath{QString("%1/%2").arg(mPetCoinPara.petPath).arg(SYSTEM_META_CONFIG->getQStringValue("NamePreset", "petGroupFile"))};
mGroupCsv = CsvTool::readCsvInt(lGroupCsvPath);
if (mGroupCsv.size() != lBdmNum) {
Log::exec("AlgorithmPetCoin::start() The Group CSV file is illegal!");
return false;
}
mSingles = new SinglesStruct*[lBdmNum];
mSinglesNumList.resize(lBdmNum);
int groupSize{mGroupCsv[0].size()};
double progressFactor{1.0 / groupSize * 1.0};
QString lCoinFilePath{QString("%1/%2").arg(mPetCoinPara.coinPath)
.arg(SYSTEM_META_CONFIG->getQStringValue("Data", "petCoinFile").arg(mPetCoinPara.bedIndex))};
QString lSinglesFilePath{QString("%1/%2").arg(mPetCoinPara.coinPath)
.arg(QString("Bed%1-singles.dat").arg(mPetCoinPara.bedIndex))};
for (int groupIndex = 0;groupIndex < groupSize; ++groupIndex) {
// 2. Data Frame to Singles
mTotalSinglesNum = 0;
mSortedIndex = 0;
mTotalCoinNum = 0;
std::vector<std::thread> lConvertTasks;
for (int bdmIndex = 0;bdmIndex < lBdmNum;++bdmIndex) {
// mTotalSinglesNum += mGroupCsv[bdmIndex][groupIndex];
lConvertTasks.emplace_back([&, bdmIndex] {
mSinglesNumList[bdmIndex] = mGroupCsv[bdmIndex][groupIndex];
auto* lDataFrameArray = new DataFrameV2[mSinglesNumList[bdmIndex]]; // input
mSingles[bdmIndex] = new SinglesStruct[mSinglesNumList[bdmIndex]]; // output
mBdmFileList[bdmIndex].read((char*)lDataFrameArray, mSinglesNumList[bdmIndex] * sizeof(DataFrameV2));
// Log::exec(QString("[BDM %1] mBdmFileList[bdmIndex].tellg() = %2").arg(bdmIndex).arg(mBdmFileList[bdmIndex].tellg()));
unsigned actualSinglesIndex{0};
for (int i = 0;i < mSinglesNumList[bdmIndex];++i) {
if (convertDataFrameToSingles(lDataFrameArray[i], mSingles[bdmIndex][actualSinglesIndex])) {
++actualSinglesIndex;
}
}
SAFE_DELETE_ARRAY(lDataFrameArray);
mSinglesNumList[bdmIndex] = actualSinglesIndex;
// qsort
if (!mPetCoinPara.isFastCoin) {
qsort(mSingles[bdmIndex], mSinglesNumList[bdmIndex], sizeof(SinglesStruct), [](const void *a, const void *b) {
auto *aa = (SinglesStruct *) a;
auto *bb = (SinglesStruct *) b;
if (aa->timevalue > bb->timevalue) return 1;
else if (aa->timevalue < bb->timevalue) return -1;
else return 0;
});
}
// check errors
int error{0};
for (int i = 0;i < mSinglesNumList[bdmIndex] - 1;++i) {
if (mSingles[bdmIndex][i].timevalue > mSingles[bdmIndex][i+1].timevalue) {
++error;
}
}
if (error) {
Log::exec(QString("[BDM %1] Coin unordered count = %2").arg(bdmIndex).arg(error));
}
// // * save Nsingles files
// QString lNSinglesFilePath{QString("%1/%2").arg(mPetCoinPara.coinPath)
// .arg(QString("Bed%1-singles%2.dat").arg(mPetCoinPara.bedIndex).arg(bdmIndex))};
// std::ofstream lNSinglesFile(lNSinglesFilePath.toStdString(), std::ios::binary | std::ios::app);
// if (!lNSinglesFile.is_open()) {
// return false;
// }
// lNSinglesFile.write((char*)mSingles[bdmIndex], mSinglesNumList[bdmIndex] * sizeof(SinglesStruct));
});
}
for (auto& item : lConvertTasks) {
item.join();
}
for (int bdmIndex = 0;bdmIndex < lBdmNum;++bdmIndex) {
mTotalSinglesNum += mSinglesNumList[bdmIndex];
}
mProgress += 0.3 * progressFactor;
// 3. Singles merging.
mSortedSingles = new SinglesStruct[mTotalSinglesNum];
unsigned* lLoserTree{new unsigned[lBdmNum + 1]};
SinglesStruct* lLoserTreeNodes{new SinglesStruct[lBdmNum + 1]};
mCurSingleIndexList.clear();
mCurSingleIndexList.resize(lBdmNum);
for (int i = 0; i < lBdmNum; ++i) {
mCurSingleIndexList[i] = 0;
if (mSinglesNumList[i] == 0) {
lLoserTreeNodes[i].timevalue = DBL_MAX; // Default maximum
} else {
lLoserTreeNodes[i] = mSingles[i][0];
}
++mCurSingleIndexList[i];
}
auto funcAdjust = [&](int n, int s) {
int t, temp;
for (t = (s + n) / 2; t > 0; t = t / 2) {
if (lLoserTreeNodes[s].timevalue > lLoserTreeNodes[lLoserTree[t]].timevalue) {
temp = s;
s = lLoserTree[t];
lLoserTree[t] = temp;
}
}
lLoserTree[0] = s;
};
lLoserTreeNodes[lBdmNum].timevalue = -1.0;
for (int i = 0; i < lBdmNum; ++i) {
lLoserTree[i] = lBdmNum;
}
for (int i = lBdmNum - 1; i >= 0; --i) {
funcAdjust(lBdmNum, i);
}
int winner;
while (lLoserTreeNodes[lLoserTree[0]].timevalue != DBL_MAX) {
winner = lLoserTree[0];
mSortedSingles[mSortedIndex] = lLoserTreeNodes[winner];
++mSortedIndex;
if (mCurSingleIndexList[winner] >= mSinglesNumList[winner]) {
lLoserTreeNodes[winner].timevalue = DBL_MAX;
} else {
lLoserTreeNodes[winner] = mSingles[winner][mCurSingleIndexList[winner]];
++mCurSingleIndexList[winner];
}
funcAdjust(lBdmNum, winner);
}
SAFE_DELETE_ARRAY(lLoserTree);
SAFE_DELETE_ARRAY(lLoserTreeNodes);
for (int i = 0;i < lBdmNum;++i) {
SAFE_DELETE_ARRAY(mSingles[i]);
}
mProgress += 0.3 * progressFactor;
// // 3.5* Save sorted singles to file
// std::ofstream lSortedSinglesFile(lSinglesFilePath.toStdString(), std::ios::binary | std::ios::app);
// if (!lSortedSinglesFile.is_open()) {
// return false;
// }
// lSortedSinglesFile.write((char*)mSortedSingles, mTotalSinglesNum * sizeof(SinglesStruct));
// 4. Sorted Singles to coins
if(mPetCoinPara.coinType == "Delayed Window"){
mMinSectorDiff = coinIni.getIntValue("minSectorDifference");
mDelayedWindow = coinIni.getIntValue("delayedTime");
for(mSortedIndex = 0; mSortedIndex < mTotalSinglesNum - 1; ++mSortedIndex){
for(int nextIndex = mSortedIndex + 1; nextIndex < mTotalSinglesNum; ++nextIndex){
int coinTimeStatus = coinTimeDelayWindow(mSortedSingles[mSortedIndex], mSortedSingles[nextIndex]);
if(coinTimeStatus == 0){ //in the delayed window
if(coinPosition(mSortedSingles[mSortedIndex], mSortedSingles[nextIndex])){
mCoinTimeRecorder.first() = mSortedSingles[mSortedIndex];
mCoinTimeRecorder.second() = mSortedSingles[nextIndex];
mCoinList.append(mCoinTimeRecorder.data);
++mTotalCoinNum;
mCoinRate = 2.0 * mTotalCoinNum / (mSortedIndex+1);
}
} else if(coinTimeStatus == -1){// after delayed window
break;
} // befor delayed window -> j++.
}// end for time window
}
}else{
for (mSortedIndex = 0; mSortedIndex < mTotalSinglesNum; ++mSortedIndex) {
coinTime(mSortedSingles[mSortedIndex]);
}
}
SAFE_DELETE_ARRAY(mSortedSingles);
mProgress += 0.3 * progressFactor;
// 5. Save coin list
std::ofstream lCoinFile(lCoinFilePath.toStdString(), std::ios::binary | std::ios::app);
if (!lCoinFile.is_open()) {
Log::exec(QString("AlgorithmPetCoin::start() Open %1 failed!").arg(lCoinFilePath));
return false;
}
lCoinFile.write((char*)mCoinList.data(), mTotalCoinNum * sizeof(CoinStruct));
mCoinList.clear();
lCoinFile.close();
mProgress += 0.1 * progressFactor;
}
SAFE_DELETE_ARRAY(mSingles);
// 6. Coin To Mich
QString lMichFilePath{QString("%1/%2").arg(mPetCoinPara.coinPath)
.arg(SYSTEM_META_CONFIG->getQStringValue("Data", "petMichFile").arg(mPetCoinPara.bedIndex))};
PetCoinToMich::coin2mich(lCoinFilePath, lMichFilePath);
mProgress = 2.0;
mCoinRate = 0.0;
return true;
}
bool AlgorithmPetCoin::convertDataFrameToSingles(DataFrameV2 &src, SinglesStruct &dst) {
/* Position, Energy, Time corrections included */
unsigned m_nChannelNum = mPetCoinPara.bdmNum;
// unsigned moduleNumX = mCoinPetPara.m_nModuleNumX;
unsigned moduleNumY = mPetCoinPara.moduleNumY;
// unsigned moduleNumZ = mCoinPetPara.m_nModuleNumZ;
// unsigned blockNumX = mCoinPetPara.m_nBlockNumX;
unsigned blockNumY = mPetCoinPara.blockNumY;
unsigned blockNumZ = mPetCoinPara.blockNumZ;
// unsigned crystalNumX = mCoinPetPara.m_nCrystalNumX;
unsigned crystalNumY = mPetCoinPara.crystalNumY;
unsigned crystalNumZ = mPetCoinPara.crystalNumZ;
unsigned positionSize = mPetCoinPara.positionSize;
/* Temporary structure to provide BDM and DU info */
TempSinglesStruct temp;
temp.globalBDMIndex = src.nBDM;
temp.localDUIndex = src.nHeadAndDU & (0x0F);
/* Time convertion, from unsigned char[8] to double */
unsigned long long nTimeTemp;
nTimeTemp = src.nTime[0];
for (int i = 1;i <= 7;++i) {
nTimeTemp <<= 8;
nTimeTemp |= src.nTime[i];
}
temp.timevalue = (double)nTimeTemp;
/* Position correction */
unsigned originCrystalIndex = mPositionMap->GetPositionTable(temp.globalBDMIndex, temp.localDUIndex)[src.X + src.Y * positionSize];
unsigned localX = originCrystalIndex % crystalNumZ;
unsigned localY = originCrystalIndex / crystalNumY;
temp.localCrystalIndex = localX + (crystalNumY - 1 - localY) * crystalNumZ;
/* Time correction */
/* Energy convertion, from unsigned char[2] to float */
unsigned nEnergyTemp;
nEnergyTemp = (src.Energy[0] << 8 | src.Energy[1]);
temp.energy = (float)nEnergyTemp;
unsigned nCrystalIdInRing = temp.globalBDMIndex % (m_nChannelNum * moduleNumY) * blockNumY * crystalNumY + temp.localDUIndex / blockNumZ * crystalNumY + temp.localCrystalIndex / crystalNumZ;
unsigned nRingId = temp.globalBDMIndex / (m_nChannelNum * moduleNumY) * blockNumZ * crystalNumZ + temp.localDUIndex % blockNumZ * crystalNumZ + temp.localCrystalIndex % crystalNumZ;
unsigned nCrystalNumOneRing = crystalNumY * blockNumY * m_nChannelNum;
dst.globalCrystalIndex = nCrystalIdInRing + nRingId * nCrystalNumOneRing;
/* Energy correction */
dst.energy = temp.energy * mEnergyMap->getEnergyCorrFactor(temp.globalBDMIndex, temp.localDUIndex, temp.localCrystalIndex)[int(floor(temp.energy / 10))];
dst.timevalue = temp.timevalue;
return coinEnergy(dst.energy);
}
bool AlgorithmPetCoin::coinEnergy(double energy) const {
bool isValid = energy >= mPetCoinPara.energyWindowStart && energy <= mPetCoinPara.energyWindowEnd;
return isValid;
}
void AlgorithmPetCoin::coinTime(SinglesStruct &single) {
// Kill All 符合方式: 当且仅当时间窗内有两个单事件时记录一组符合事件对
if (single.timevalue - mCoinTimeRecorder.first().timevalue > mPetCoinPara.timingWindow) {
if (2 == mCoinTimeRecorder.index) {
// 如果全局晶体编号不一致,就保存
if (mCoinTimeRecorder.first().globalCrystalIndex != mCoinTimeRecorder.second().globalCrystalIndex) {
mCoinList.append(mCoinTimeRecorder.data);
++mTotalCoinNum;
mCoinRate = 2.0 * mTotalCoinNum / (mSortedIndex+1);
}
}
mCoinTimeRecorder.first() = single;
mCoinTimeRecorder.index = 1;
} else {
mCoinTimeRecorder.second() = single;
++mCoinTimeRecorder.index;
}
}
int AlgorithmPetCoin::coinTimeDelayWindow(SinglesStruct &single1, SinglesStruct &single2)
{
double timeDiff = single2.timevalue - (single1.timevalue + mDelayedWindow);
if( timeDiff < 0 )
return 1; // before the DW
else if( timeDiff < mPetCoinPara.timingWindow)
return 0; // in the DW
else
return -1; // after the DW
}
bool AlgorithmPetCoin::coinPosition(SinglesStruct &single1, SinglesStruct &single2)
{
unsigned panelNum = SETTING->mPetSetting.petScannerSetting.panelNum;
unsigned nCrystalNumOneRing = SETTING->mPetSetting.petScannerSetting.getCrystalNumOneRing();
unsigned nCrystalNumYInModule = SETTING->mPetSetting.petScannerSetting.getCrystalNumYInModule();
int sector1 = single1.globalCrystalIndex % nCrystalNumOneRing / (nCrystalNumYInModule);
int sector2 = single2.globalCrystalIndex % nCrystalNumOneRing / (nCrystalNumYInModule);
if ((sector1 - sector2 + panelNum) % panelNum >= mMinSectorDiff)
return true;
else
return false;
}
bool AlgorithmPetCoin::stop() {
return false;
}
double AlgorithmPetCoin::progress() {
return mProgress;
}
double AlgorithmPetCoin::coinRate() {
return mCoinRate;
}
算法实例
头文件
#ifndef ALGORITHMINSTANCE_H
#define ALGORITHMINSTANCE_H
#include "AbstractAlgorithm.h"
#include <memory>
#include <QObject>
#include <QString>
class AlgorithmInstance : public QObject {
Q_OBJECT
public:
AlgorithmInstance(QString algoName);
virtual ~AlgorithmInstance();
void reload();
virtual bool join(QString iniPath) = 0; // run algorithm in current thread
virtual bool detach(QString iniPath) = 0; // run algorithm in detach thread
double progress() const;
signals:
void sigProgress(double);
void sigFinished();
protected:
bool runnable();
QString mAlgoName;
QString mIniPath;
AbstractAlgorithm* mpAlgorithm{nullptr};
double mProgress{0.0};
bool mIsRunning{false};
//private:
// static void starter(AlgorithmInstance* self);
// static void progresser(AlgorithmInstance* self);
};
#endif // ALGORITHMINSTANCE_H
源文件
#include "AlgorithmInstance.h"
#include <thread>
#include "tool/macroTools.h"
#include "tool/Log.h"
#include "AlgorithmMap.h"
AlgorithmInstance::AlgorithmInstance(QString algoName) {
mAlgoName = algoName;
reload();
}
AlgorithmInstance::~AlgorithmInstance() {
SAFE_DELETE(mpAlgorithm);
}
void AlgorithmInstance::reload() {
SAFE_DELETE(mpAlgorithm);
mpAlgorithm = ALGORITHM_MAP->get(mAlgoName);
}
//bool AlgorithmInstance::join(QString iniPath) {
// if (!runnable()) {
// return false;
// }
// mIniPath = iniPath;
// std::thread(starter, this).join();
// std::thread(progresser, this).join();
// return true;
//}
//bool AlgorithmInstance::detach(QString iniPath) {
// if (!runnable()) {
// return false;
// }
// mIniPath = iniPath;
// std::thread(starter, this).detach();
// std::thread(progresser, this).detach();
// return true;
//}
double AlgorithmInstance::progress() const {
return mProgress;
}
bool AlgorithmInstance::runnable() {
if (mpAlgorithm == nullptr) {
Log::exec("AlgorithmInstance::runnable() failed! mpAlgorithm is nullptr.");
return false;
} else if (mIsRunning) {
Log::exec("AlgorithmInstance::runnable() failed! Algorithm is still running.");
return false;
}
return true;
}
//void AlgorithmInstance::starter(AlgorithmInstance* self) {
// Log::exec("AlgorithmInstance::starter() starting starter thread...");
// self->mIsRunning = true;
// self->mpAlgorithm->start(self->mIniPath.toStdString().c_str());
//}
//void AlgorithmInstance::progresser(AlgorithmInstance* self) {
// Log::exec("AlgorithmInstance::progresser() starting progress thread...");
// double preProgress = 0.0;
// double curProgress = 0.0;
// while (self->mpAlgorithm != nullptr && self->mpAlgorithm->progress() <= 1.0) {
// curProgress = self->mpAlgorithm->progress();
// if (curProgress - preProgress > 0.01 || curProgress - preProgress < -0.01) {
// preProgress = curProgress;
// self->mProgress = curProgress;
// emit self->sigProgress(self->mProgress);
// }
// }
// self->mProgress = 2.0;
// self->mIsRunning = false;
// self->reload();
// emit self->sigProgress(self->mProgress);
// emit self->sigFinished();
//}
每一个插件实例中存放了具体需要的算法,获取的算法的时候直接通过算法管理者的Map得到算法实例即可。
算法管理者
#ifndef ALGORITHMMANAGER_H
#define ALGORITHMMANAGER_H
#include <QMap>
#include "AlgorithmInstance.h"
#define ALGORITHM_MANAGER AlgorithmManager::instance()
class AlgorithmManager {
public:
static AlgorithmManager* instance();
AlgorithmInstance* getAlgoIns(QString algoName);
template<class T>
T* getAlgoIns(QString algoName) {
return (T*)mAlgorithmList[algoName];
}
private:
AlgorithmManager();
virtual ~AlgorithmManager();
QMap<QString, AlgorithmInstance*> mAlgorithmList;
};
#endif // ALGORITHMMANAGER_H
//cpp
#include "AlgorithmManager.h"
#include "tool/macroTools.h"
#include "algorithm/PetScan/AlgoInsPetScan.h"
#include "algorithm/PetCoin/AlgoInsPetCoin.h"
AlgorithmManager *AlgorithmManager::instance() {
static AlgorithmManager am;
return &am;
}
AlgorithmInstance *AlgorithmManager::getAlgoIns(QString algoName) {
return mAlgorithmList[algoName];
}
AlgorithmManager::AlgorithmManager() {
mAlgorithmList["PetScan"] = (AlgorithmInstance*)(new AlgoInsPetScan("PetScan"));
mAlgorithmList["PetCoin"] = (AlgorithmInstance*)(new AlgoInsPetCoin("PetCoin"));
}
AlgorithmManager::~AlgorithmManager() {
for (auto& item : mAlgorithmList) {
SAFE_DELETE(item);
}
}
管理者构造的时候就把具体实例加载进来。
外部只需要通过Map获得算法实例即可。
任务队列机制
抽象任务
#ifndef ABSTRACTTASK_H
#define ABSTRACTTASK_H
#include <QObject>
#include <QMetaType>
class AbstractTask : public QObject {
Q_OBJECT
public:
AbstractTask() {
qRegisterMetaType<TaskStatus>("TaskStatus");
}
virtual ~AbstractTask() = default;
enum TaskStatus {
RUNNING = 0,
PAUSED,
WAITING,
STOPPED,
FINISHED,
ERRORED,
STOPPING,
PAUSING,
RESUMING,
STATUS_NUM
};
TaskStatus status() const {return mStatus;}
virtual bool start() = 0;
void setErrored() {mStatus = ERRORED;}
signals:
void sigStarted();
void sigPaused();
void sigResumed();
void sigFinished(TaskStatus nStatus);
void sigProgress(double fValue);
void sigTimePassed(unsigned int nTime);
protected:
TaskStatus mStatus;
};
#endif // ABSTRACTTASK_H
具体任务
具体的任务会有对应的插件实例或者算法实例。
重建任务:
#ifndef RECONTASKPET_H
#define RECONTASKPET_H
#include "plugin/PluginManager.h"
#include "../AbstractTask.h"
class ReconTaskPet : public AbstractTask {
Q_OBJECT
public:
ReconTaskPet(QString strIniPath, int nBedIndex, int nBedNum);
virtual ~ReconTaskPet() = default;
bool start() override;
private slots:
void onPetReconFinished();
private:
PluginInstance* mpPlugPetRecon;
QString mIniPath;
int mBedIndex;
int mBedNum;
};
#endif // RECONTASKPET_H
//cpp
#include "ReconTaskPet.h"
#include "tool/Log.h"
#include "metainfo/SystemMetaInfo.h"
ReconTaskPet::ReconTaskPet(QString strIniPath, int nBedIndex, int nBedNum) :
mpPlugPetRecon{PLUGIN_MANAGER->getPluginIns(SYSTEM_META_CONFIG->getQStringValue("Plugin", "osemName"))},
mIniPath{strIniPath}, mBedIndex{nBedIndex}, mBedNum{nBedNum} {}
bool ReconTaskPet::start() {
emit sigStarted();
mStatus = RUNNING;
connect(mpPlugPetRecon, &PluginInstance::sigProgress, this, &ReconTaskPet::sigProgress);
connect(mpPlugPetRecon, &PluginInstance::sigFinished, this, &ReconTaskPet::onPetReconFinished);
return mpPlugPetRecon->detach(mIniPath);
}
void ReconTaskPet::onPetReconFinished() {
disconnect(mpPlugPetRecon, &PluginInstance::sigProgress, this, &ReconTaskPet::sigProgress);
disconnect(mpPlugPetRecon, &PluginInstance::sigFinished, this, &ReconTaskPet::onPetReconFinished);
Log::exec(QString("[%1 / %2] ReconTaskPet::onPetReconFinished() PET reconstruct task finished!").arg(mBedIndex).arg(mBedNum));
emit sigFinished(FINISHED);
}
符合任务:
#ifndef COINTASK_H
#define COINTASK_H
#include "algorithm/AlgorithmManager.h"
#include "algorithm/PetCoin/AlgoInsPetCoin.h"
#include "../AbstractTask.h"
#include "../TaskType.h"
class CoinTask : public AbstractTask {
Q_OBJECT
public:
CoinTask(QString strIniPath, int nBedIndex, int nBedNum);
virtual ~CoinTask() = default;
bool start() override;
private slots:
void onPetCoinFinished();
private:
AlgoInsPetCoin* mpAlgoPetCoin;
QString mIniPath;
int mBedIndex;
int mBedNum;
signals:
void sigCoinRate(double);
};
#endif // COINTASK_H
//cpp
#include "CoinTask.h"
#include "tool/Log.h"
CoinTask::CoinTask(QString strIniPath, int nBedIndex, int nBedNum) :
mpAlgoPetCoin{ALGORITHM_MANAGER->getAlgoIns<AlgoInsPetCoin>("PetCoin")},
mIniPath{strIniPath},
mBedIndex{nBedIndex},
mBedNum{nBedNum} {
}
bool CoinTask::start() {
emit sigStarted();
mStatus = RUNNING;
connect(mpAlgoPetCoin, &AlgorithmInstance::sigProgress, this, &CoinTask::sigProgress);
connect(mpAlgoPetCoin, &AlgorithmInstance::sigFinished, this, &CoinTask::onPetCoinFinished);
connect(mpAlgoPetCoin, &AlgoInsPetCoin::sigCoinRate, this, &CoinTask::sigCoinRate);
return mpAlgoPetCoin->detach(mIniPath);
}
void CoinTask::onPetCoinFinished() {
disconnect(mpAlgoPetCoin, &AlgorithmInstance::sigProgress, this, &CoinTask::sigProgress);
disconnect(mpAlgoPetCoin, &AlgorithmInstance::sigFinished, this, &CoinTask::onPetCoinFinished);
disconnect(mpAlgoPetCoin, &AlgoInsPetCoin::sigCoinRate, this, &CoinTask::sigCoinRate);
QString msg{QString("[%1 / %2] CoinTask::onPetCoinFinished() PET coincidence task finished!").arg(mBedIndex).arg(mBedNum)};
Log::exec(msg);
emit sigFinished(FINISHED);
}
任务管理者
#ifndef TASKMANAGER_H
#define TASKMANAGER_H
#include <QObject>
#include "TaskType.h"
#include "TaskQueue.h"
#define TASK_MANAGER TaskManager::instance()
class TaskManager : public QObject {
Q_OBJECT
public:
static TaskManager* instance();
void start(TaskType nType);
bool isRunning(TaskType nType);
bool isEmpty(TaskType nType);
AbstractTask* getCurTask(TaskType nType);
TaskQueue* getQueue(TaskType nType);
int leftTaskNum(TaskType nType);
bool appendTask(AbstractTask* pTask, TaskType nType);
void removeTask(AbstractTask* pTask, TaskType nType);
QVector<AbstractTask*>* getWaitTaskList(TaskType nType);
QVector<AbstractTask*>* getFinishedTaskList(TaskType nType);
private:
TaskManager();
virtual ~TaskManager();
TaskQueue* mpQueue[TASK_TYPE_NUM];
};
#endif // TASKMANAGER_H
#include "TaskManager.h"
#include "tool/macroTools.h"
TaskManager* TaskManager::instance() {
static TaskManager tm;
return &tm;
}
void TaskManager::start(TaskType nType) {
mpQueue[nType]->start();
}
bool TaskManager::isRunning(TaskType nType) {
return mpQueue[nType]->isTaskRunning();
}
bool TaskManager::isEmpty(TaskType nType) {
return mpQueue[nType]->leftTask() == 0;
}
AbstractTask *TaskManager::getCurTask(TaskType nType) {
return mpQueue[nType]->getCurTask();
}
TaskQueue *TaskManager::getQueue(TaskType nType) {
return mpQueue[nType];
}
int TaskManager::leftTaskNum(TaskType nType) {
return mpQueue[nType]->leftTask();
}
bool TaskManager::appendTask(AbstractTask *pTask, TaskType nType) {
return mpQueue[nType]->appendTask(pTask);
}
void TaskManager::removeTask(AbstractTask *pTask, TaskType nType) {
mpQueue[nType]->removeTask(pTask);
}
QVector<AbstractTask*>* TaskManager::getWaitTaskList(TaskType nType) {
return mpQueue[nType]->getWaitingTaskList();
}
QVector<AbstractTask *> *TaskManager::getFinishedTaskList(TaskType nType) {
return mpQueue[nType]->getFinishedTaskList();
}
TaskManager::TaskManager() {
for (int i = 0;i < TASK_TYPE_NUM;++i) {
mpQueue[i] = new TaskQueue;
mpQueue[i]->SetAutoRemoveFinished();
}
}
TaskManager::~TaskManager() {
for (int i = 0;i < TASK_TYPE_NUM;++i) {
SAFE_DELETE(mpQueue[i]);
}
}
//cpp
有多个任务队列,每一种任务都是一个单独的队列,每个队列互相独立运行。
主要负责添加、移除、运行任务。
任务队列
#ifndef TASKQUEUE_H
#define TASKQUEUE_H
#include <QObject>
#include "AbstractTask.h"
class TaskQueue : public QObject {
Q_OBJECT
public:
TaskQueue(bool bIsAuto = false, QObject *parent = nullptr);
virtual ~TaskQueue() = default;
void SetAutoStart(bool bIsAuto = true);
void SetAutoRemoveFinished(bool bIsAuto = true);
bool isTaskRunning() {return mIsTaskRunning;}
bool isAutoRunning() {return mIsAutoRunning;}
void start();
bool appendTask(AbstractTask* pTask);
bool removeTask(AbstractTask* pTask);
void removeAllFinishedTask();
void removeAllWaitingTask();
AbstractTask* getCurTask() {return mpCurTask;}
QVector<AbstractTask*>* getWaitingTaskList() {return &mTaskQueueReady;}
QVector<AbstractTask*>* getFinishedTaskList() {return &mTaskQueueFinished;}
int leftTask() {return mTaskQueueReady.size();}
int finishedTask() {return mTaskQueueFinished.size();}
signals:
void sigReadyTaskCount(int nCount);
void sigQueueFinished(bool bSuccess = true);
void sigTaskFinished(AbstractTask::TaskStatus nStatus);
void sigProgress(double dValue);
void sigTimePassed(unsigned nTime);
private slots:
void onTaskFinished(AbstractTask::TaskStatus nStatus);
bool onStartNext();
private:
template <typename ForwardIterator>
void deleteAllTask(ForwardIterator begin, ForwardIterator end);
QVector<AbstractTask*> mTaskQueueReady;
QVector<AbstractTask*> mTaskQueueFinished;
AbstractTask* mpCurTask;
bool mIsTaskRunning;
bool mIsAutoRunning;
bool mIsAutoRemove;
bool mIsStopQueue;
bool mLastSuccess;
};
#endif // TASKQUEUE_H
//cpp
#include "TaskQueue.h"
#include "tool/macroTools.h"
#include "tool/Log.h"
TaskQueue::TaskQueue(bool bIsAuto, QObject *parent)
: QObject(parent)
, mpCurTask{nullptr}
, mIsTaskRunning{false}
, mIsAutoRunning{bIsAuto}
, mIsAutoRemove{false}
, mIsStopQueue{false}
, mLastSuccess{true} {}
void TaskQueue::SetAutoStart(bool bIsAuto) {
mIsAutoRunning = bIsAuto;
if(!mIsTaskRunning && mIsAutoRunning) {
onStartNext();
}
}
void TaskQueue::SetAutoRemoveFinished(bool bIsAuto) {
mIsAutoRemove = bIsAuto;
}
void TaskQueue::start() {
if (!mIsTaskRunning) {
onStartNext();
}
}
bool TaskQueue::appendTask(AbstractTask *pTask) {
mTaskQueueReady.append(pTask); //添加到任务队列
emit sigReadyTaskCount(leftTask());
connect(pTask, &AbstractTask::sigFinished, this, &TaskQueue::onTaskFinished);
connect(pTask, &AbstractTask::sigProgress, this, &TaskQueue::sigProgress);
connect(pTask, &AbstractTask::sigTimePassed, this, &TaskQueue::sigTimePassed);
if (!mIsTaskRunning && mIsAutoRunning) {
return onStartNext();
}
return true;
}
bool TaskQueue::removeTask(AbstractTask* pTask) {
bool result{true};
switch (pTask->status()) {
case AbstractTask::RUNNING :
case AbstractTask::STOPPING :
case AbstractTask::PAUSING :
case AbstractTask::PAUSED : {
result = false;
} break;
case AbstractTask::WAITING : {
mTaskQueueReady.removeAll(pTask);
emit sigReadyTaskCount(leftTask());
SAFE_DELETE(pTask);
} break;
case AbstractTask::STOPPED :
case AbstractTask::ERRORED :
case AbstractTask::FINISHED : {
mTaskQueueReady.removeAll(pTask);
SAFE_DELETE(pTask);
} break;
default: break;
}
return result;
}
void TaskQueue::removeAllFinishedTask() {
deleteAllTask(mTaskQueueFinished.begin(), mTaskQueueFinished.end());
mTaskQueueFinished.clear();
}
void TaskQueue::removeAllWaitingTask() {
deleteAllTask(mTaskQueueReady.begin(), mTaskQueueReady.end());
mTaskQueueReady.clear();
}
void TaskQueue::onTaskFinished(AbstractTask::TaskStatus nStatus) {
if (mpCurTask) {
mLastSuccess = (mpCurTask->status() == AbstractTask::FINISHED);
mTaskQueueFinished.append(mpCurTask);
}
mIsTaskRunning = false;
emit sigTaskFinished(nStatus);
onStartNext();
}
bool TaskQueue::onStartNext() {
bool result{true};
if (!mTaskQueueReady.isEmpty()) {
mpCurTask = mTaskQueueReady.takeFirst();
// const auto status = mpCurTask->status();
mIsTaskRunning = true;
emit sigReadyTaskCount(leftTask());
result = mpCurTask->start();
if (!result) {
Log::exec("TaskQueue::onStartNext() mpCurTask->start() errored!");
mpCurTask->setErrored();
}
} else {
if (mIsStopQueue) {
mIsStopQueue = false;
removeAllFinishedTask();
}
if (mIsAutoRemove) {
removeAllFinishedTask();
}
mIsTaskRunning = false;
mpCurTask = nullptr;
emit sigQueueFinished(mLastSuccess);
}
return result;
}
template<typename ForwardIterator>
void TaskQueue::deleteAllTask(ForwardIterator begin, ForwardIterator end) {
while (begin != end) {
SAFE_DELETE(*begin);
++begin;
}
}