Add C-Store protocol.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#include "dicomviewerhelper.h"
|
||||
#include <qDebug>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#include <QJsonArray>
|
||||
|
||||
|
||||
struct host {
|
||||
struct host
|
||||
{
|
||||
QString name;
|
||||
QString ae;
|
||||
QString ip;
|
||||
|
||||
@@ -49,7 +49,6 @@ bool GetWorker::initDcmSCU()
|
||||
|
||||
void GetWorker::getBySeriesUID(const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& aDicomType)
|
||||
{
|
||||
qDebug(aDicomType.toLatin1());
|
||||
if (!initDcmSCU())
|
||||
{
|
||||
mScu->abortAssociation();
|
||||
|
||||
89
src/src/PACS/Network/StoreWorker.cpp
Normal file
89
src/src/PACS/Network/StoreWorker.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "StoreWorker.h"
|
||||
#include "dcmtk/dcmnet/scu.h"
|
||||
|
||||
StoreWorker::StoreWorker(QObject* aParent)
|
||||
: QObject(aParent)
|
||||
{
|
||||
}
|
||||
|
||||
bool StoreWorker::initDcmSCU()
|
||||
{
|
||||
mScu = new DcmSCU();
|
||||
mScu->setAETitle(mOurTitle);
|
||||
mScu->setPeerHostName(mPeerIP);
|
||||
mScu->setPeerPort(OFstatic_cast(Uint16, mPeerPort));
|
||||
mScu->setPeerAETitle(mPeerTitle);
|
||||
OFList<OFString> syntaxes;
|
||||
syntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
|
||||
mScu->addPresentationContext(UID_CTImageStorage, syntaxes);
|
||||
mScu->addPresentationContext(UID_MRImageStorage, syntaxes);
|
||||
OFCondition cond = mScu->initNetwork();
|
||||
if (cond.bad())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cond = mScu->negotiateAssociation();
|
||||
if (cond.bad())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StoreWorker::setPacsInfo(const std::string& aPeerIP, unsigned short aPeerPort, const std::string& aPeerTitle, const std::string& aOurTitle)
|
||||
{
|
||||
mPeerIP = aPeerIP;
|
||||
mPeerPort = aPeerPort;
|
||||
mPeerTitle = aPeerTitle;
|
||||
mOurTitle = aOurTitle;
|
||||
}
|
||||
|
||||
void StoreWorker::cancel()
|
||||
{
|
||||
mCancelFlag = true;
|
||||
}
|
||||
|
||||
void StoreWorker::store(const QStringList& aFilePathList, const QString& aDicomType)
|
||||
{
|
||||
if (!initDcmSCU())
|
||||
{
|
||||
mScu->abortAssociation();
|
||||
emit notifyStoreDone(0);
|
||||
emit notifyStoreFinished();
|
||||
delete mScu;
|
||||
mScu = nullptr;
|
||||
return;
|
||||
}
|
||||
T_ASC_PresentationContextID pcid;
|
||||
Uint16 response;
|
||||
DcmDataset dataset;
|
||||
for(int i = 0;i < aFilePathList.size(); ++i)
|
||||
{
|
||||
if(mCancelFlag)
|
||||
{
|
||||
mCancelFlag = false;
|
||||
break;
|
||||
}
|
||||
if(aDicomType.toLower() == "mr")
|
||||
{
|
||||
pcid = mScu->findPresentationContextID(UID_MRImageStorage,"");
|
||||
}
|
||||
else
|
||||
{
|
||||
pcid = mScu->findPresentationContextID(UID_CTImageStorage,"");
|
||||
}
|
||||
OFCondition cond = mScu->sendSTORERequest(pcid,aFilePathList.at(i).toStdString(), &dataset, response);
|
||||
if (cond.bad() || cond != EC_Normal || response != 0)
|
||||
{
|
||||
mScu->closeAssociation(DCMSCU_ABORT_ASSOCIATION);
|
||||
emit notifyStoreDone(0);
|
||||
emit notifyStoreFinished();
|
||||
return;
|
||||
}
|
||||
emit notifyStoreDone(i+1);
|
||||
}
|
||||
mScu->releaseAssociation();
|
||||
delete mScu;
|
||||
mScu = nullptr;
|
||||
emit notifyStoreFinished();
|
||||
}
|
||||
35
src/src/PACS/Network/StoreWorker.h
Normal file
35
src/src/PACS/Network/StoreWorker.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef STOREWORKER_H
|
||||
#define STOREWORKER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class DcmSCU;
|
||||
|
||||
class StoreWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StoreWorker(QObject* aParent);
|
||||
void setPacsInfo(const std::string& aPeerIP, unsigned short aPeerPort, const std::string& aPeerTitle, const std::string& aOurTitle);
|
||||
void cancel();
|
||||
Q_INVOKABLE void store(const QStringList& aFilePathList, const QString& aDicomType);
|
||||
|
||||
signals:
|
||||
void notifyStoreDone(int aIndex); //0 failed,1,2,3.. sucessed
|
||||
void notifyStoreFinished();
|
||||
|
||||
private:
|
||||
bool initDcmSCU();
|
||||
|
||||
private:
|
||||
DcmSCU* mScu;
|
||||
QString mStoreFilePath;
|
||||
std::string mPeerTitle;
|
||||
std::string mPeerIP;
|
||||
std::string mOurTitle;
|
||||
unsigned short mPeerPort;
|
||||
unsigned short mOurPort;
|
||||
bool mCancelFlag = false;
|
||||
};
|
||||
|
||||
#endif // STOREWORKER_H
|
||||
57
src/src/PACS/Widget/StoreDialog.cpp
Normal file
57
src/src/PACS/Widget/StoreDialog.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "StoreDialog.h"
|
||||
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QSpacerItem>
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString SENDING_IMAGES_TO_PACS = "Sending images to PACS...";
|
||||
QString makeText(int aTotalNum, int aCurrentNum)
|
||||
{
|
||||
return SENDING_IMAGES_TO_PACS + "[" + QString::number(aCurrentNum) + "/" + QString::number(aTotalNum) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
StoreDialog::StoreDialog(QWidget* aParent)
|
||||
: QDialog(aParent)
|
||||
, mFileSize(0)
|
||||
, mProgressBar(new QProgressBar(this))
|
||||
, mText(new QLabel(this))
|
||||
, mCancelButton(nullptr)
|
||||
{
|
||||
setFixedSize(QSize(380,105));
|
||||
initWidget();
|
||||
}
|
||||
|
||||
void StoreDialog::initWidget()
|
||||
{
|
||||
mProgressBar->setTextVisible(false);
|
||||
QVBoxLayout* vLayout = new QVBoxLayout(this);
|
||||
vLayout->addWidget(mProgressBar);
|
||||
vLayout->addWidget(mText);
|
||||
QWidget* buttonContainer = new QWidget(this);
|
||||
vLayout->addWidget(buttonContainer);
|
||||
QHBoxLayout* hLayout = new QHBoxLayout(buttonContainer);
|
||||
hLayout->addSpacerItem(new QSpacerItem(280,105));
|
||||
mCancelButton = new QPushButton(tr("Cancel"),buttonContainer);
|
||||
mCancelButton->setFixedHeight(23);
|
||||
hLayout->addWidget(mCancelButton);
|
||||
connect(mCancelButton,&QPushButton::clicked,this,&QDialog::reject);
|
||||
}
|
||||
|
||||
void StoreDialog::setFileSize(int aFileSize)
|
||||
{
|
||||
mFileSize = aFileSize;
|
||||
mText->setText(makeText(mFileSize, 0));
|
||||
}
|
||||
|
||||
void StoreDialog::setStoreProgress(int aNum)
|
||||
{
|
||||
mProgressBar->setValue((aNum*100)/mFileSize);
|
||||
mText->setText(makeText(mFileSize, aNum));
|
||||
}
|
||||
|
||||
27
src/src/PACS/Widget/StoreDialog.h
Normal file
27
src/src/PACS/Widget/StoreDialog.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef STOREDIALOG_H
|
||||
#define STOREDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QProgressBar;
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
|
||||
class StoreDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
StoreDialog(QWidget* aParent = nullptr);
|
||||
void setStoreProgress(int aNum);
|
||||
void setFileSize(int aFileSize);
|
||||
|
||||
private:
|
||||
void initWidget();
|
||||
|
||||
private:
|
||||
int mFileSize;
|
||||
QProgressBar* mProgressBar;
|
||||
QLabel* mText;
|
||||
QPushButton* mCancelButton;
|
||||
};
|
||||
|
||||
#endif // STOREDIALOG_H
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <QButtonGroup>
|
||||
#include <QActionGroup>
|
||||
#include "Rendering/Measure/Measure.h"
|
||||
|
||||
#include "Common/QGlobals.h"
|
||||
#include "ExportToolButton.h"
|
||||
|
||||
typedef tuple<const char *, const char *, int> ActionProperty;
|
||||
namespace {
|
||||
const char *SYNC_MANUAL_URL = ":/InfiniteViewer/Icon/sync/sync_manual.png";
|
||||
@@ -30,7 +31,7 @@ namespace {
|
||||
DefaultToolBar::DefaultToolBar(QWidget *parent) : QToolBar(parent)
|
||||
, mBtnFile(new QToolButton(this))
|
||||
, mBtnImport(new QToolButton(this))
|
||||
, mBtnSave(new QToolButton(this))
|
||||
, mBtnSave(new ExportToolButton(this))
|
||||
, mBtnGrid(new QToolButton(this))
|
||||
, mBtnSync(new QToolButton(this))
|
||||
, mBtnAnonymize(new QToolButton(this))
|
||||
@@ -52,7 +53,8 @@ DefaultToolBar::DefaultToolBar(QWidget *parent) : QToolBar(parent)
|
||||
, mActionMinimize(nullptr)
|
||||
, mActionMaximize(nullptr)
|
||||
, mActionClose(nullptr)
|
||||
, mActionFullScreen(nullptr) {
|
||||
, mActionFullScreen(nullptr)
|
||||
{
|
||||
//init icons
|
||||
mManualIcon.addFile(SYNC_MANUAL_URL);
|
||||
mAutoIcon.addFile(SYNC_AUTO_URL);
|
||||
@@ -64,6 +66,12 @@ DefaultToolBar::~DefaultToolBar() {
|
||||
|
||||
}
|
||||
|
||||
void DefaultToolBar::setViewManager(ImageViewManager *aManager)
|
||||
{
|
||||
mImageViewManager = aManager;
|
||||
mBtnSave->setImageViewManager(mImageViewManager);
|
||||
}
|
||||
|
||||
QAction* DefaultToolBar::addButton(QToolButton* button, const char* objectName) {
|
||||
button->setObjectName(objectName);
|
||||
button->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
@@ -164,6 +172,7 @@ void DefaultToolBar::initImportButton() {
|
||||
}
|
||||
|
||||
void DefaultToolBar::initExportButton() {
|
||||
mBtnSave->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
mBtnSave->setToolTip(QString("Export images"));
|
||||
connect(mBtnSave, &QToolButton::clicked, this, &DefaultToolBar::save);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "UI/Manager/ImageViewStateCheckWorker.h"
|
||||
|
||||
class QButtonGroup;
|
||||
class ExportToolButton;
|
||||
|
||||
class DefaultToolBar : public QToolBar {
|
||||
Q_OBJECT
|
||||
@@ -21,6 +22,7 @@ public:
|
||||
|
||||
void resetNeedCheckFunctionButtons();
|
||||
void updateNeedCheckFunctionButtons(ViewFunctionState state);
|
||||
void setViewManager(ImageViewManager* aManager);
|
||||
|
||||
signals:
|
||||
void openFile();
|
||||
@@ -70,7 +72,7 @@ private:
|
||||
QIcon mDisIcon;
|
||||
QToolButton *mBtnFile;
|
||||
QToolButton *mBtnImport;
|
||||
QToolButton *mBtnSave;
|
||||
ExportToolButton *mBtnSave;
|
||||
QToolButton *mBtnGrid;
|
||||
QToolButton *mBtnSync;
|
||||
QToolButton *mBtnAnonymize;
|
||||
@@ -99,6 +101,7 @@ private:
|
||||
QAction *mActionHideMeasure;
|
||||
QAction *mActionHidePatData;
|
||||
QAction* mSyncActions[3]={nullptr,nullptr,nullptr};
|
||||
ImageViewManager* mImageViewManager;
|
||||
};
|
||||
|
||||
|
||||
|
||||
179
src/src/UI/Widget/ToolBar/ExportToolButton.cpp
Normal file
179
src/src/UI/Widget/ToolBar/ExportToolButton.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "ExportToolButton.h"
|
||||
|
||||
#include "Common/ImageSetStore.h"
|
||||
#include "PACS/Common/dicomviewerhelper.h"
|
||||
#include "PACS/Network/StoreWorker.h"
|
||||
#include "PACS/Widget/StoreDialog.h"
|
||||
#include "UI/Manager/ImageViewManager.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
namespace
|
||||
{
|
||||
void createFilePathListFromAllOpenedStudies(QStringList& aResult,ExtendMedicalImageProperties* aProperty)
|
||||
{
|
||||
if (aProperty == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<std::pair<std::string,long>> fileNames = aProperty->GetFileNames();
|
||||
auto itr1 = fileNames.begin();
|
||||
for(; itr1 != fileNames.end();++itr1)
|
||||
{
|
||||
aResult.push_back(itr1->first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void createFilePathListFromCurrentStudy(QStringList& aResult,ExtendMedicalImageProperties* aProperty, const QString& aCurrentStudyUid)
|
||||
{
|
||||
if (aProperty == nullptr || aCurrentStudyUid != aProperty->GetStudyUID())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<std::pair<std::string,long>> fileNames = aProperty->GetFileNames();
|
||||
auto itr1 = fileNames.begin();
|
||||
for(; itr1 != fileNames.end();++itr1)
|
||||
{
|
||||
aResult.push_back(itr1->first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void createFilePathListFromCurrentSeries(QStringList& aResult,ExtendMedicalImageProperties* aProperty, const QString& aCurrentSeriesUid)
|
||||
{
|
||||
if (aProperty == nullptr || aCurrentSeriesUid != aProperty->GetSeriesUID())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<std::pair<std::string,long>> fileNames = aProperty->GetFileNames();
|
||||
auto itr1 = fileNames.begin();
|
||||
for(; itr1 != fileNames.end();++itr1)
|
||||
{
|
||||
aResult.push_back(itr1->first.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExportToolButton::ExportToolButton(QWidget* aParent)
|
||||
: QToolButton(aParent)
|
||||
, mImageViewManager(nullptr)
|
||||
, mStoreDialog(nullptr)
|
||||
, mStoreWorker(nullptr)
|
||||
, mThread(nullptr)
|
||||
{
|
||||
initSendToPacsMenu();
|
||||
}
|
||||
|
||||
void ExportToolButton::initSendToPacsMenu()
|
||||
{
|
||||
QMenu* saveMenu = new QMenu(this);
|
||||
QMenu* sendPacs = new QMenu(tr("Send to PACS"), this);
|
||||
QMenu* allOpenedStudies = new QMenu(tr("All opened studies"), sendPacs);
|
||||
QMenu* currentStudy = new QMenu(tr("Current study"), sendPacs);
|
||||
QMenu* currentSeries = new QMenu(tr("Current series"), sendPacs);
|
||||
QList<host> hosts;
|
||||
DicomViewerHelper::pacsInfo(hosts);
|
||||
foreach(auto h,hosts)
|
||||
{
|
||||
QString text = h.name + " [" + h.ae + "@" + h.ip + ":" + h.port + "]";
|
||||
auto sendToPacs = [h,this]()
|
||||
{
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
if (action == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<ExtendMedicalImageProperties*> openedFile = ImageSetStore::GetInstance()->getProperties();
|
||||
if (mImageViewManager == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DicomImageView* imageView = mImageViewManager->getCurrentView();
|
||||
if(imageView == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SeriesImageSet* imageSet = imageView->getSeriesInstance();
|
||||
if(imageSet == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto itr = openedFile.begin();
|
||||
QStringList filePathList;
|
||||
for(;itr != openedFile.end();++itr)
|
||||
{
|
||||
std::vector<std::pair<std::string,long>> fileNames = (*itr)->GetFileNames();
|
||||
if(action->data().toString() == "All opened studies")
|
||||
{
|
||||
createFilePathListFromAllOpenedStudies(filePathList, (*itr));
|
||||
}
|
||||
else if(action->data().toString() == "Current study")
|
||||
{
|
||||
createFilePathListFromCurrentStudy(filePathList, (*itr), imageSet->getStudyUID());
|
||||
}
|
||||
else if(action->data().toString() == "Current series")
|
||||
{
|
||||
createFilePathListFromCurrentSeries(filePathList, (*itr), imageSet->getSeriesName());
|
||||
}
|
||||
}
|
||||
if(filePathList.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (mThread == nullptr)
|
||||
{
|
||||
mThread = new QThread(this);
|
||||
}
|
||||
if (mStoreWorker == nullptr)
|
||||
{
|
||||
mStoreWorker = QSharedPointer<StoreWorker>(new StoreWorker(nullptr));
|
||||
mStoreWorker->moveToThread(mThread);
|
||||
connect(mStoreWorker.data(),&StoreWorker::notifyStoreFinished,mThread,&QThread::quit);
|
||||
}
|
||||
|
||||
if(mStoreDialog == nullptr)
|
||||
{
|
||||
mStoreDialog = new StoreDialog(this);
|
||||
connect(mStoreDialog,&QDialog::rejected,this,[this]()
|
||||
{
|
||||
mStoreWorker->cancel();
|
||||
});
|
||||
connect(mStoreWorker.data(),&StoreWorker::notifyStoreDone,mStoreDialog,&StoreDialog::setStoreProgress);
|
||||
connect(mStoreWorker.data(),&StoreWorker::notifyStoreFinished,mStoreDialog,&StoreDialog::accept);
|
||||
}
|
||||
mThread->start();
|
||||
mStoreWorker->setPacsInfo(h.ip.toStdString(), static_cast<unsigned short>(h.port.toShort()),
|
||||
h.ae.toStdString(), DicomViewerHelper::ourTitle().toStdString());
|
||||
QMetaObject::invokeMethod(mStoreWorker.data(), "store", Qt::QueuedConnection, Q_ARG(QStringList, filePathList), Q_ARG(QString, ""));
|
||||
mStoreDialog->setFileSize(filePathList.size());
|
||||
mStoreDialog->exec();
|
||||
};
|
||||
QAction* allAction = new QAction(text,allOpenedStudies);
|
||||
allAction->setData("All opened studies");
|
||||
connect(allAction,&QAction::triggered,this,sendToPacs);
|
||||
allOpenedStudies->addAction(allAction);
|
||||
|
||||
QAction* studyAction = new QAction(text,currentStudy);
|
||||
studyAction->setData("Current study");
|
||||
connect(studyAction,&QAction::triggered,this,sendToPacs);
|
||||
currentStudy->addAction(studyAction);
|
||||
|
||||
QAction* seriesAction = new QAction(text,currentSeries);
|
||||
seriesAction->setData("Current series");
|
||||
connect(seriesAction,&QAction::triggered,this,sendToPacs);
|
||||
currentSeries->addAction(seriesAction);
|
||||
}
|
||||
saveMenu->addMenu(sendPacs);
|
||||
sendPacs->addMenu(allOpenedStudies);
|
||||
sendPacs->addMenu(currentStudy);
|
||||
sendPacs->addMenu(currentSeries);
|
||||
|
||||
setPopupMode(QToolButton::MenuButtonPopup);
|
||||
setMenu(saveMenu);
|
||||
}
|
||||
|
||||
void ExportToolButton::setImageViewManager(ImageViewManager* aImageViewManager)
|
||||
{
|
||||
mImageViewManager = aImageViewManager;
|
||||
}
|
||||
28
src/src/UI/Widget/ToolBar/ExportToolButton.h
Normal file
28
src/src/UI/Widget/ToolBar/ExportToolButton.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef EXPORTTOOLBUTTON_H
|
||||
#define EXPORTTOOLBUTTON_H
|
||||
|
||||
#include <QToolButton>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class ImageViewManager;
|
||||
class StoreDialog;
|
||||
class StoreWorker;
|
||||
class QThread;
|
||||
|
||||
class ExportToolButton : public QToolButton
|
||||
{
|
||||
public:
|
||||
ExportToolButton(QWidget* aParent);
|
||||
void setImageViewManager(ImageViewManager* aImageViewManager);
|
||||
|
||||
private:
|
||||
void initSendToPacsMenu();
|
||||
|
||||
private:
|
||||
ImageViewManager* mImageViewManager;
|
||||
StoreDialog* mStoreDialog;
|
||||
QSharedPointer<StoreWorker> mStoreWorker;
|
||||
QThread* mThread;
|
||||
};
|
||||
|
||||
#endif // EXPORTTOOLBUTTON_H
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "QDicomViewer.h"
|
||||
#include "QDicomViewer.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
@@ -23,6 +23,7 @@ QDicomViewer::QDicomViewer(QWidget *parent) : QMainWindow(parent),
|
||||
m_import(nullptr) {
|
||||
ui->setupUi(this);
|
||||
this->statusBar()->showMessage(tr("Ready"));
|
||||
ui->toolBar->setViewManager(ui->viewContainer->getViewManager());
|
||||
QWidget::setWindowTitle(Project_NAME);
|
||||
|
||||
this->Initial();
|
||||
|
||||
Reference in New Issue
Block a user