QT与DCMTK版本
QT使用版本为5.9.8,编译器使用vs2017。DCMTK使用版本为3.6.7。
读取dicom文件
使用DCMTK读取dicom文件,并解析tag信息。
#ifndef __DICOMPROCESSOR_H__
#define __DICOMPROCESSOR_H__
#include "IDicom.h"
#include "dcmtk/dcmdata/dctk.h"
struct DCPatientInfo;
struct DCStudyInfo;
struct DCSeriesInfo;
struct DCImageInfo;
struct DCPrivateData;
class DicomProcessor : public IDicom
{
public:
DicomProcessor();
~DicomProcessor();
bool Read(const std::string& filePath);
void Reset();
inline std::shared_ptr<DCPatientInfo> PatientInfo()
{
return m_patient;
}
inline std::shared_ptr<DCStudyInfo> StudyInfo()
{
return m_study;
}
inline std::shared_ptr<DCSeriesInfo> SeriesInfo()
{
return m_series;
}
inline std::shared_ptr<DCImageInfo> ImageInfo()
{
return m_image;
}
inline std::list<DCPrivateData> &PrivateDataInfo()
{
return m_privateData;
}
void MetaInfoTags();
void DataSetTags();
inline void SetPatientInfo(const DCPatientInfo &info)
{
if(nullptr != m_patient)
{
m_patient->PatientID = info.PatientID;
m_patient->PatientsBirthDate = info.PatientsBirthDate;
m_patient->PatientsName = info.PatientsName;
m_patient->PatientsSex =info.PatientsSex;
m_patient->PatientsWeight = info.PatientsWeight;
m_patient->OtherPatientNames = info.OtherPatientNames;
m_patient->PatientComments = info.PatientComments;
}
}
inline void SetStudyInfo(const DCStudyInfo &info)
{
if(nullptr != m_study)
{
m_study->StudyID = info.StudyID;
m_study->StudyInstanceUID = info.StudyInstanceUID;
m_study->AccessionNumber = info.AccessionNumber;
m_study->StudyDateTime = info.StudyDateTime;
m_study->StudyDescription = info.StudyDescription;
}
}
inline void SetSeriesInfo(const DCSeriesInfo &info)
{
if(nullptr != m_series)
{
m_series->BodyPartExamined = info.BodyPartExamined;
m_series->PerformingPhysicianName = info.PerformingPhysicianName;
m_series->ProtocolName = info.ProtocolName;
m_series->SeriesDateTime = info.SeriesDateTime;
m_series->SeriesInstanceUID = info.SeriesInstanceUID;
m_series->SeriesNumber = info.SeriesNumber;
}
}
inline void SetImageInfo(const DCImageInfo &info, uchar* imageData, uint dataSize)
{
if(nullptr != m_image)
{
m_image->BitsAllocated = info.BitsAllocated;
m_image->BitsStored = info.BitsStored;
m_image->Columns = info.Columns;
m_image->HighBit = info.HighBit;
m_image->PhotometricInterpretation = info.PhotometricInterpretation;
m_image->PixelRepresentation = info.PixelRepresentation;
m_image->Rows = info.Rows;
m_image->SamplesPerPixel = info.SamplesPerPixel;
m_image->PixelSpacing = info.PixelSpacing;
m_image->NumberOfFrames = info.NumberOfFrames;
m_image->InstanceNumber = info.InstanceNumber;
FreePixelData();
m_image->PixelSize = dataSize;
m_image->PixelData = new uchar[m_image->PixelSize];
memcpy(m_image->PixelData, imageData, m_image->PixelSize);
}
}
inline void SetPrivateData(const std::list<DCPrivateData> &data)
{
m_privateData = data;
}
private:
void GetPatient();
void GetStudy();
void GetSeries();
void GetImage();
void GetPrivateData();
void FillPatient();
void FillStudy();
void FillSeries();
void FillImage();
void FillPrivateData();
void FillBaseInfo();
void GetInfo(DcmTagKey tagKey, std::string &info);
void GetInfo(DcmTagKey tagKey, unsigned int &info);
void GetDateTimeInfo(DcmTagKey tagDateKey, DcmTagKey tagTimeKey, std::chrono::system_clock::time_point &info);
void FillInfo(const DcmTagKey& tagKey, const std::string &info);
void RegisterPrivateTags(DCPrivateData data);
void FreePixelData();
void AddDicomTags();
void AddDicomTag(DcmDataDictionary &dict, const DcmTagKey &tagKey, const DcmEVR &vr);
private:
DcmFileFormat m_fileformat;
std::shared_ptr<DCPatientInfo> m_patient;
std::shared_ptr<DCStudyInfo> m_study;
std::shared_ptr<DCSeriesInfo> m_series;
std::shared_ptr<DCImageInfo> m_image;
std::list<DCPrivateData> m_privateData;
};
#endif // __DICOMPROCESSOR_H__
上述代码中定义了一些dicom文件的读取方法,dicom标签的解析方法等。
#include "dicomProcessor.h"
#include "dcmtk/config/osconfig.h"
#include "dcmtkStruct.h"
#include "dicomUtils.h"
#include "dcmtk/dcmjpeg/djdecode.h"
DicomProcessor::DicomProcessor()
{
Reset();
}
DicomProcessor::~DicomProcessor()
{
FreePixelData();
}
bool DicomProcessor::Read(const std::string &filePath)
{
OFCondition status = m_fileformat.loadFile(filePath.c_str());
if (!status.good())
{
std::cout << "Read dimcom file error: " << status.text() << ",file: " << filePath << std::endl;
return false;
}
try
{
std::cout << "get patient" << std::endl;
GetPatient();
std::cout << "get study" << std::endl;
GetStudy();
std::cout << "get series" <<std::endl;
//GetSeries();
std::cout << "get image" << std::endl;
GetImage();
std::cout << "get private data" << std::endl;
GetPrivateData();
}
catch (...)
{
std::cout << "Get dimcom info error!" << std::endl;
FreePixelData();
return false;
}
std::cout << "Read dimcom file succeed!" << std::endl;
return true;
}
void DicomProcessor::Reset()
{
//添加标签
AddDicomTags();
m_patient.reset(new DCPatientInfo());
m_study.reset(new DCStudyInfo());
m_series.reset(new DCSeriesInfo());
m_image.reset(new DCImageInfo());
m_privateData.clear();
FreePixelData();
}
void DicomProcessor::MetaInfoTags()
{
std::cout << "Meta Tag-----------------------------Meta Tag" << std::endl;
DcmObject* item = m_fileformat.getMetaInfo()->nextInContainer(NULL);
while (item)
{
DcmVR valueVR(item->getVR());
DcmTag tag(item->getTag());
std::cout << item->getTag().toString().c_str() << " " << valueVR.getVRName() << " " << tag.getTagName() << std::endl;
item = m_fileformat.getMetaInfo()->nextInContainer(item);
}
}
void DicomProcessor::DataSetTags()
{
std::cout << "DataSet Tag------------------------------DataSet Tag" << std::endl;
DcmObject* item = m_fileformat.getDataset()->nextInContainer(NULL);
while (item)
{
DcmVR valueVR(item->getVR());
DcmTag tag(item->getTag());
std::cout << item->getTag().toString().c_str() << " " << valueVR.getVRName() << " " << tag.getTagName() << std::endl;
item = m_fileformat.getDataset()->nextInContainer(item);
}
}
void DicomProcessor::GetPatient()
{
GetInfo(DCM_PatientID, m_patient->PatientID);
GetInfo(DCM_PatientName, m_patient->PatientsName);
}
void DicomProcessor::GetStudy()
{
GetInfo(DCM_StudyInstanceUID, m_study->StudyInstanceUID);
if (m_study->StudyInstanceUID.empty())
{
std::cout << "Get Tag DCM_StudyInstanceUID Error!" << std::endl;
}
}
void DicomProcessor::GetSeries()
{
GetDateTimeInfo(DCM_SeriesDate, DCM_SeriesTime, m_series->SeriesDateTime);
GetInfo(DCM_BodyPartExamined, m_series->BodyPartExamined);
}
void DicomProcessor::GetImage()
{
GetInfo(DCM_SamplesPerPixel, m_image->SamplesPerPixel);
GetInfo(DCM_PhotometricInterpretation, m_image->PhotometricInterpretation);
GetInfo(DCM_Rows, m_image->Rows);
GetInfo(DCM_Columns, m_image->Columns);
GetInfo(DCM_BitsAllocated, m_image->BitsAllocated);
GetInfo(DCM_BitsStored, m_image->BitsStored);
GetInfo(DCM_HighBit, m_image->HighBit);
GetInfo(DCM_PixelRepresentation, m_image->PixelRepresentation);
DcmElement* elePixSpacing = NULL;
OFCondition status = m_fileformat.getDataset()->findAndGetElement(DCM_PixelSpacing, elePixSpacing);
if (!status.good())
{
std::cout << "get pixel spacing element error: " << status.text() << std::endl;
}
else
{
OFString pixelSpacing = nullptr;
status = elePixSpacing->getOFStringArray(pixelSpacing);
if (!status.good())
{
std::cout << "get pixel spacing data error: " << status.text() << std::endl;
}
else
{
m_image->PixelSpacing = pixelSpacing.c_str();
}
}
uint pixelByteCount = (m_image->BitsAllocated <= 8) ? 1 : 2;
ulong dataLength = m_image->Rows * m_image->Columns * pixelByteCount * m_image->SamplesPerPixel;
std::cout << "dataLength: "<< dataLength <<std::endl;
DcmElement* element = NULL;
status = m_fileformat.getDataset()->findAndGetElement(DCM_PixelData, element);
if (!status.good())
{
std::cout<< "Get pixel data element error:" << status.text() << std::endl;
}
else
{
//std::cout << "Pixel data element's length:" << element->getLength() << std::endl;
unsigned short* pData = NULL;
status = element->getUint16Array(pData);
if (!status.good())
{
std::cout << "Get pixel data array error:" << status.text() << std::endl;
return;
}
FreePixelData();
m_image->PixelData = new uint8_t[dataLength];
memcpy(m_image->PixelData, pData, dataLength);
m_image->PixelSize = dataLength;
}
}
void DicomProcessor::GetPrivateData()
{
DcmTagKey tagKey;
OFString value;
DcmElement* element = NULL;
for (auto &data : m_privateData)
{
tagKey.set((Uint16)data.GroupTag, (Uint16)data.ElementTag);
value.clear();
element = NULL;
OFCondition status = m_fileformat.getDataset()->findAndGetElement(tagKey, element);
if (!status.good())
{
std::ios::fmtflags fmt(std::cout.flags());
std::cout << "Get private Tag(" << std::hex << data.GroupTag << "," << data.ElementTag << ") error:" << status.text() << std::endl;
std::cout.flags(fmt);
}
else
{
status = element->getOFString(value, 0);
if (!status.good())
{
std::ios::fmtflags fmt(std::cout.flags());
std::cout << "Get private Tag(" << std::hex << data.GroupTag << "," << data.ElementTag << ") error:" << status.text() << std::endl;
std::cout.flags(fmt);
}
else
{
data.Value = std::string(value.c_str());
DcmVR valueVR(element->getVR());
data.VR = std::string(valueVR.getVRName());
}
}
}
}
void DicomProcessor::FillPatient()
{
FillInfo(DCM_PatientID, m_patient->PatientID);
FillInfo(DCM_PatientName, m_patient->PatientsName);
}
void DicomProcessor::FillStudy()
{
std::cout << "study instanceUID is "<<m_study->StudyInstanceUID<<std::endl;
if (m_study->StudyInstanceUID.empty())
{
std::cout << "Study instanceUID is empty!" << std::endl;
}
FillInfo(DCM_StudyInstanceUID, m_study->StudyInstanceUID);
FillInfo(DCM_StudyDate, DicomUtils::DatetoString(m_study->StudyDateTime));
FillInfo(DCM_StudyTime, DicomUtils::TimetoString(m_study->StudyDateTime));
FillInfo(DCM_StudyID, m_study->StudyID);
FillInfo(DCM_AccessionNumber, m_study->AccessionNumber);
}
void DicomProcessor::FillSeries()
{
FillInfo(DCM_SeriesInstanceUID, m_series->SeriesInstanceUID);
FillInfo(DCM_SeriesDate, DicomUtils::DatetoString(m_series->SeriesDateTime));
FillInfo(DCM_SeriesTime, DicomUtils::TimetoString(m_series->SeriesDateTime));
FillInfo(DCM_SeriesNumber, m_series->SeriesNumber);
FillInfo(DCM_ProtocolName, m_series->ProtocolName);
}
void DicomProcessor::FillImage()
{
FillInfo(DCM_SamplesPerPixel, std::to_string(m_image->SamplesPerPixel));
if (m_image->PhotometricInterpretation.empty())
{
std::cout << "No PhotometricInterpretation Info in Fill image Pixel" << std::endl;
}
FillInfo(DCM_PhotometricInterpretation, m_image->PhotometricInterpretation);
FillInfo(DCM_Rows, std::to_string(m_image->Rows));
FillInfo(DCM_Columns, std::to_string(m_image->Columns));
FillInfo(DCM_BitsAllocated, std::to_string(m_image->BitsAllocated));
FillInfo(DCM_BitsStored, std::to_string(m_image->BitsStored));
FillInfo(DCM_HighBit, std::to_string(m_image->HighBit));
FillInfo(DCM_PixelRepresentation, std::to_string(m_image->PixelRepresentation));
FillInfo(DCM_LossyImageCompression, "00");
FillInfo(DCM_NumberOfFrames, std::to_string(m_image->NumberOfFrames));
FillInfo(DCM_PixelSpacing, m_image->PixelSpacing);
FillInfo(DCM_InstanceNumber, std::to_string(m_image->InstanceNumber));
FillInfo(DCM_MediaStorageSOPInstanceUID, DicomUtils::GenerateUniqueID(UID_Image));
if (m_image->PixelData != NULL)
{
OFCondition status = m_fileformat.getDataset()->putAndInsertUint8Array(DCM_PixelData, m_image->PixelData, m_image->PixelSize);
if (!status.good())
{
std::cout << "Fill pixel data error:" << status.text() << std::endl;
}
}
}
void DicomProcessor::FillPrivateData()
{
for (auto &data : m_privateData)
{
if (!data.VR.empty())
{
RegisterPrivateTags(data);
OFCondition status = m_fileformat.getDataset()->putAndInsertString(DcmTag((Uint16)data.GroupTag, (Uint16)data.ElementTag, PRIVATE_GROUP_NAME), data.Value.c_str());
if (!status.good())
{
std::cout << "Fill private Tag(" << data.GroupTag << "," << data.ElementTag << ") error:" << status.text();
}
}
}
m_privateData.clear();
}
void DicomProcessor::FillBaseInfo()
{
FillInfo(DCM_Modality, "US");
FillInfo(DCM_SpecificCharacterSet, "ISO_IR 192");
FillInfo(DCM_PlanarConfiguration, "0");
}
void DicomProcessor::GetInfo(DcmTagKey tagKey, std::string &info)
{
OFString ofData;
OFCondition status = m_fileformat.getDataset()->findAndGetOFString(tagKey, ofData);
if (!status.good())
{
std::cout << "Get Tag(" << tagKey.toString().c_str() << ") error: " << status.text() << std::endl;
}
info = ofData.c_str();
}
void DicomProcessor::GetInfo(DcmTagKey tagKey, unsigned int &info)
{
std::string strInfo;
GetInfo(tagKey, strInfo);
try
{
info = std::atoi(strInfo.c_str());
}
catch (...)
{
std::cout << "Get Tag(" << tagKey.toString().c_str() << ") error(String to UInt)!" << std::endl;
}
}
void DicomProcessor::GetDateTimeInfo(DcmTagKey tagDateKey, DcmTagKey tagTimeKey, std::chrono::system_clock::time_point &info)
{
std::string date;
std::string time;
GetInfo(tagDateKey, date);
GetInfo(tagTimeKey, time);
info = DicomUtils::StringtoDateTime(date, time);
}
void DicomProcessor::FillInfo(const DcmTagKey &tagKey, const std::string &info)
{
OFCondition status = m_fileformat.getDataset()->putAndInsertString(tagKey, info.c_str());
if (!status.good())
{
std::cout << "Fill Tag(" << tagKey.toString().c_str() << ") error: " << status.text() << std::endl;
}
}
void DicomProcessor::RegisterPrivateTags(DCPrivateData data)
{
DcmDataDictionary &dict = dcmDataDict.wrlock();
if ((data.VR.compare("OB") == 0) || (data.VR.compare("FL") == 0) || (data.VR.compare("FD") == 0))
{
dict.addEntry(new DcmDictEntry((Uint16)data.GroupTag, (Uint16)data.ElementTag, EVR_UT, data.Name.c_str(), 1, 1, "private", OFTrue, PRIVATE_GROUP_NAME));
}
else
{
dict.addEntry(new DcmDictEntry((Uint16)data.GroupTag, (Uint16)data.ElementTag, DcmVR(data.VR.c_str()), data.Name.c_str(), 1, 1, "private", OFTrue, PRIVATE_GROUP_NAME));
}
dcmDataDict.wrunlock();
}
void DicomProcessor::FreePixelData()
{
if (m_image->PixelData != NULL)
{
//std::cout << "Free pixel data of Dicom Handler" << std::endl;
free(m_image->PixelData);
m_image->PixelData = NULL;
}
}
void DicomProcessor::AddDicomTags()
{
DcmDataDictionary &dict = dcmDataDict.wrlock();
AddDicomTag(dict, DCM_MediaStorageSOPClassUID, EVR_UI);
AddDicomTag(dict, DCM_MediaStorageSOPInstanceUID, EVR_UI);
AddDicomTag(dict, DCM_ImplementationClassUID, EVR_UI);
AddDicomTag(dict, DCM_ImplementationVersionName, EVR_CS);
AddDicomTag(dict, DCM_FileMetaInformationVersion, EVR_SH);
AddDicomTag(dict, DCM_FileMetaInformationGroupLength, EVR_IS);
AddDicomTag(dict, DCM_SpecificCharacterSet, EVR_CS);
AddDicomTag(dict, DCM_Manufacturer, EVR_LO);
AddDicomTag(dict, DCM_TransducerType, EVR_CS);
AddDicomTag(dict, DCM_AccessionNumber, EVR_SH);
AddDicomTag(dict, DCM_SpecificCharacterSet, EVR_CS);
AddDicomTag(dict, DCM_ImageType, EVR_CS);
AddDicomTag(dict, DCM_SOPClassUID, EVR_UI);
AddDicomTag(dict, DCM_SOPInstanceUID, EVR_UI);
AddDicomTag(dict, DCM_Modality, EVR_CS);
AddDicomTag(dict, DCM_StageName, EVR_SH);
AddDicomTag(dict, DCM_PatientID, EVR_LO);
AddDicomTag(dict, DCM_PatientName, EVR_PN);
AddDicomTag(dict, DCM_PatientBirthDate, EVR_DA);
AddDicomTag(dict, DCM_PatientSex, EVR_CS);
AddDicomTag(dict, DCM_RETIRED_OtherPatientIDs, EVR_LO);
AddDicomTag(dict, DCM_PatientOrientation, EVR_CS);
AddDicomTag(dict, DCM_StudyInstanceUID, EVR_UI);
AddDicomTag(dict, DCM_StudyDate, EVR_DA);
AddDicomTag(dict, DCM_StudyTime, EVR_TM);
AddDicomTag(dict, DCM_StudyID, EVR_SH);
AddDicomTag(dict, DCM_SeriesNumber, EVR_IS);
AddDicomTag(dict, DCM_InstanceNumber, EVR_IS);
AddDicomTag(dict, DCM_Laterality, EVR_CS);
AddDicomTag(dict, DCM_StudyDescription, EVR_LO);
AddDicomTag(dict, DCM_SeriesInstanceUID, EVR_UI);
AddDicomTag(dict, DCM_SeriesDate, EVR_DA);
AddDicomTag(dict, DCM_SeriesTime, EVR_TM);
AddDicomTag(dict, DCM_BodyPartExamined, EVR_SH);
AddDicomTag(dict, DCM_ProtocolName, EVR_LO);
AddDicomTag(dict, DCM_SoftwareVersions, EVR_LO);
AddDicomTag(dict, DCM_SequenceOfUltrasoundRegions, EVR_SQ);
AddDicomTag(dict, DCM_TransducerType, EVR_CS);
AddDicomTag(dict, DCM_SamplesPerPixel, EVR_US);
AddDicomTag(dict, DCM_SamplesPerPixelUsed, EVR_US);
AddDicomTag(dict, DCM_PhotometricInterpretation, EVR_CS);
AddDicomTag(dict, DCM_PlanarConfiguration, EVR_US);
AddDicomTag(dict, DCM_Rows, EVR_US);
AddDicomTag(dict, DCM_Columns, EVR_US);
AddDicomTag(dict, DCM_PixelSpacing, EVR_DS);
AddDicomTag(dict, DCM_BitsAllocated, EVR_US);
AddDicomTag(dict, DCM_BitsStored, EVR_US);
AddDicomTag(dict, DCM_HighBit, EVR_US);
AddDicomTag(dict, DCM_PixelRepresentation, EVR_US);
AddDicomTag(dict, DCM_PixelData, EVR_OB);
AddDicomTag(dict, DCM_LossyImageCompression, EVR_CS);
AddDicomTag(dict, DCM_NumberOfFrames, EVR_IS);
AddDicomTag(dict, DCM_PlanarConfiguration, EVR_US);
dcmDataDict.wrunlock();
}
void DicomProcessor::AddDicomTag(DcmDataDictionary &dict, const DcmTagKey &tagKey, const DcmEVR &vr)
{
dict.addEntry(new DcmDictEntry(tagKey.getGroup(), tagKey.getElement(), vr, nullptr, 1, 1, nullptr, OFTrue, nullptr));
}
上述代码中是读取方法与标签解析方法的实现。
#ifndef __DCMTKSTRUCT_H__
#define __DCMTKSTRUCT_H__
#include <string>
#include <chrono>
#define PRIVATE_GROUP_NAME "Private Group"
struct DCPatientInfo
{
std::string PatientsName;
std::string PatientID;
std::chrono::system_clock::time_point PatientsBirthDate;
std::string PatientsSex;
std::string OtherPatientNames;
std::string PatientsWeight;
std::string PatientComments;
};
struct DCStudyInfo
{
std::string StudyInstanceUID;
std::string StudyID;
std::chrono::system_clock::time_point StudyDateTime;
std::string AccessionNumber;
std::string StudyDescription;
};
struct DCSeriesInfo
{
std::string SeriesInstanceUID;
std::string SeriesNumber;
std::chrono::system_clock::time_point SeriesDateTime;
std::string PerformingPhysicianName;
std::string ProtocolName;
std::string BodyPartExamined;
};
struct DCImageInfo
{
unsigned int SamplesPerPixel;
std::string PhotometricInterpretation;
unsigned int Rows;
unsigned int Columns;
unsigned int BitsAllocated;
unsigned int BitsStored;
unsigned int HighBit;
unsigned int PixelRepresentation;
unsigned char* PixelData;
unsigned int PixelSize;
std::string PixelSpacing;
unsigned int NumberOfFrames;
unsigned int InstanceNumber;
};
struct DCPrivateData
{
uint16_t GroupTag;
uint16_t ElementTag;
std::string Name;
std::string VR;
std::string Value;
};
enum DC_UID_Type
{
UID_Study = 1,
UID_Series = 2,
UID_Image = 3,
UID_Other = 4,
};
enum DC_DataVR_Type
{
DataVRType_CS = 1,
DataVRType_SH,
DataVRType_LO,
DataVRType_ST,
DataVRType_LT,
DataVRType_UT,
DataVRType_AE,
DataVRType_PN,
DataVRType_UI,
DataVRType_DA,
DataVRType_TM,
DataVRType_DT,
DataVRType_AS,
DataVRType_IS,
DataVRType_DS,
DataVRType_SS,
DataVRType_US,
DataVRType_SL,
DataVRType_UL,
DataVRType_AT,
DataVRType_FL,
DataVRType_FD,
DataVRType_OB,
DataVRType_OW,
DataVRType_OF,
DataVRType_SQ,
DataVRType_UN
};
#endif // __DCMTKSTRUCT_H__
该代码块中定义了一些常用标签的数据结构。
QGraphicsView中绘制线段
class CustomGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
enum OperateType
{
OperateTypeNone = 0,
OperateTypeCycle = 1,
OperateTypeRectangle = 2,
OperateTypePolygon = 3,
OperateTypeDistance = 4
};
public:
explicit CustomGraphicsView (QWidget *parent = nullptr);
~CustomGraphicsView ();
/**
* @brief 设置图片
* @param id-图片唯一ID
* @param image-图片数据
*/
void setImage(int id, const QImage &image, int realWidth, int realHeight);
/**
* @brief 设置操作类型
* @param image-图片数据
*/
void setOperateType(int type);
/**
* @brief 删除图元
* @param itemType-图元类型
* @param id-图元唯一ID
*/
bool deleteItem(int itemType, int id);
/**
* @brief 清除图元
*/
bool clearItem();
/**
* @brief 设置像素间距离
* @param ps-像素间距离
*/
void setPixelSpacing(const QString &ps);
protected:
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
private slots:
void slotLineResize(int itemId, int index, int dx, int dy);
private:
template<class T>
void moveItem(T* item, const QPointF &prePos, const QPointF &posEnd);
void drawLine(const QPoint &posEnd);
void calcDistance(const CustomLineItem *lineItem);
void drawDistanceText(const QPointF &pos, const QString &text);
void updateLineEndPoint(const QPointF &posEnd);
void createLineEditItem();
void clearLineEditItem();
private:
std::unique_ptr<QGraphicsScene> _scene; //场景
QGraphicsPixmapItem* _pixmapItem; //图片图元
int _curItemId; //当前图元ID
int _id = 0; //图片唯一ID
int _operateType = 0; //操作类型
bool _mousePressed = false; //鼠标是否点击
QPointF _mousePrePos; //鼠标点击位置
QMap<int, CustomTextItem*> _mapTextItem; //文本图元列表
CustomTextItem* _curTextItem; //当前文本图元
QPointF _textPrePos; //文本图元点击位置
std::unique_ptr<CustomROIMenu> _itemMenu; //右键图元菜单
QMap<int, CustomLineItem*> m_mapLineItem; //线段图元列表
CustomLineItem *m_curLineItem = nullptr; //当前线段图元
QString m_pixelSpacing; //像素距离
int m_imageRealWidth = 0; //图片实际宽度
int m_imageRealHeight = 0; //图片实际高度
bool m_mouseLinePressed = false; //线段是否点击
QLineF m_linePrePos; //线段点击时顶点坐标
LineItemEditMgr *m_lineEditItem = nullptr; //线段编辑图元管理类
};
template<class T>
inline void ImageSequence::moveItem(T * item, const QPointF &prePos, const QPointF & posEnd)
{
if (nullptr == item)
{
return;
}
//计算偏移量
QPointF offset = posEnd - _mousePrePos;
QPointF newPos = prePos + offset;
//更新图元位置
item->setPos(newPos);
update();
}
#endif // __IMAGESEQUENCE_H__
#include "ROIProcess/CustomGraphicsView.h"
#include "ROIProcess/customTextItem.h"
#include "ROIProcess/RoiCalcFunction.h"
#include "ROIProcess/customROIMenu.h"
#include "ROIProcess/customLineItem.h"
#include "ROIProcess/lineItemEditMgr.h"
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QDebug>
#include <QMouseEvent>
#include <QGraphicsLineItem>
#include <thread>
#define TEXT_ITEM_POS_OFFSET QPointF(0, 20)
#define EDIT_ITEM_SIZE QSize(15, 15)
CustomGraphicsView::CustomGraphicsView(QWidget *parent) :
QGraphicsView(parent),
_scene(nullptr),
_pixmapItem(nullptr),
_curItemId(0),
_curTextItem(nullptr),
_itemMenu(nullptr),
_polygonLineItem(nullptr)
{
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //设置滚动条不显示
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setMouseTracking(true);
setRenderHint(QPainter::Antialiasing);
_scene = std::make_unique<QGraphicsScene>(this);
_scene->setBackgroundBrush(QBrush("#2C2F35"));
_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
this->setScene(_scene.get());
_pixmapItem = new QGraphicsPixmapItem();
_scene->addItem(_pixmapItem);
initStyle();
}
CustomGraphicsView::~CustomGraphicsView()
{
qDeleteAll(_mapTextItem);
if (nullptr != _pixmapItem)
{
delete _pixmapItem;
_pixmapItem = nullptr;
}
if (nullptr != m_lineEditItem)
{
delete m_lineEditItem;
m_lineEditItem = nullptr;
}
}
void CustomGraphicsView::setImage(int id, const QImage & image, int realWidth, int realHeight)
{
_id = id;
m_imageRealWidth = realWidth;
m_imageRealHeight = realHeight;
if (nullptr != _pixmapItem)
{
QPixmap pixmap = QPixmap::fromImage(image);
if (pixmap.width() < this->width() || pixmap.height() < this->height())
{
pixmap = pixmap.scaled(this->size());
}
_pixmapItem->setPixmap(pixmap);
}
}
void CustomGraphicsView::setOperateType(int type)
{
_curTextItem = nullptr;
m_curLineItem = nullptr;
_operateType = type;
}
bool CustomGraphicsView::deleteItem(int itemType, int id)
{
if (OperateTypeDistance == itemType)
{
//查找线段
auto it = m_mapLineItem.find(id);
if (it != m_mapLineItem.end())
{
CustomLineItem *item = it.value();
delete item;
item = nullptr;
m_mapLineItem.erase(it);
}
if (nullptr != m_curLineItem && id == m_curLineItem->getId()) //清空当前处理图元指针
{
m_curLineItem = nullptr;
}
//删除线段编辑图元
clearLineEditItem();
}
//删除对应文本
auto itText = _mapTextItem.find(id);
if (itText != _mapTextItem.end())
{
CustomTextItem *item = itText.value();
delete item;
item = nullptr;
_mapTextItem.erase(itText);
if (nullptr != _curTextItem && _curTextItem->id() == id)
{
_curTextItem = nullptr;
}
}
return true;
}
bool CustomGraphicsView::clearItem()
{
//清除线段编辑图元
clearLineEditItem();
//清除文本
for (auto it = _mapTextItem.begin(); it != _mapTextItem.end();)
{
CustomTextItem *item = it.value();
delete item;
item = nullptr;
it = _mapTextItem.erase(it);
}
_curTextItem = nullptr;
return true;
}
void CustomGraphicsView::setPixelSpacing(const QString & ps)
{
m_pixelSpacing = ps;
}
void CustomGraphicsView::mousePressEvent(QMouseEvent * e)
{
if (_operateType != OperateTypeNone && e->button() == Qt::LeftButton)
{
_mousePrePos = e->pos();
_mousePressed = true;
return;
}
m_curLineItem = dynamic_cast<CustomLineItem*>(scene()->itemAt(e->pos(), QTransform()));
if (nullptr != m_curLineItem)
{
if (e->button() == Qt::RightButton)
{
//右击弹出菜单
showMenu(e->pos(), OperateTypeDistance, m_curLineItem->getId());
}
else if (e->button() == Qt::LeftButton)
{
//左键开始编辑或移动
_mousePrePos = e->pos();
m_linePrePos = m_curLineItem->line();
m_mouseLinePressed = true;
initTextItemPos(m_curLineItem->getId());
//新建编辑图元
createLineEditItem();
}
}
else
{
CustomEllipseEditItem *itemTmp = dynamic_cast<CustomEllipseEditItem*>(scene()->itemAt(e->pos(), QTransform()));
if (nullptr == itemTmp)
{
clearLineEditItem(); //清除线段编辑图元
}
}
QGraphicsView::mousePressEvent(e);
}
void CustomGraphicsView::mouseMoveEvent(QMouseEvent * e)
{
if (_mousePressed)
{
switch (_operateType)
{
case OperateTypeDistance:
drawLine(e->pos());
break;
default:
break;
}
}
//移动线段
if (m_mouseLinePressed && nullptr != m_curLineItem)
{
updateLineEndPoint(e->pos());
moveItem<CustomTextItem>(_curTextItem, _textPrePos, e->pos());
if (nullptr != m_lineEditItem) //移动编辑点
{
QVector<QPointF> pos = { m_curLineItem->line().p1(), m_curLineItem->line().p2() };
m_lineEditItem->moveItem(pos);
m_lineEditItem->setItemFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
}
}
QGraphicsView::mouseMoveEvent(e);
}
void CustomGraphicsView::mouseReleaseEvent(QMouseEvent * e)
{
if (OperateTypeDistance == _operateType || m_mouseLinePressed)
{
_operateType = OperateTypeNone;
m_mouseLinePressed = false;
}
_mousePressed = false;
QGraphicsView::mouseReleaseEvent(e);
}
void CustomGraphicsView::slotLineResize(int itemId, int index, int dx, int dy)
{
auto it = m_mapLineItem.find(itemId);
if (it != m_mapLineItem.end())
{
QLineF line = it.value()->line();
if (0 == index)
{
line.setP1(line.p1() + QPointF(dx, dy));
}
else
{
line.setP2(line.p2() + QPointF(dx, dy));
}
it.value()->setLine(line);
//重计算距离
calcDistance(it.value());
}
}
void CustomGraphicsView::initTextItemPos(int id)
{
auto it = _mapTextItem.find(id);
if (it != _mapTextItem.end())
{
//确认当前移动文本图元
_curTextItem = it.value();
//确认当前文本图元的起始位置
_textPrePos = it.value()->pos();
}
}
void CustomGraphicsView::showMenu(const QPointF & pos, int itemType, int itemId)
{
if (nullptr == _itemMenu)
{
_itemMenu = std::make_unique<CustomROIMenu>(this);
_itemMenu->setObjectName("itemMenu");
connect(_itemMenu.get(), &CustomROIMenu::signalDelete, this, [=](int itemType, int itemId) {
deleteItem(itemType, itemId);
});
}
if (nullptr != _itemMenu)
{
qDebug() << pos;
QPoint pos1 = mapToGlobal(QPoint(pos.x(), pos.y()));
qDebug() << pos1;
_itemMenu->setItemId(itemId);
_itemMenu->setItemType(itemType);
_itemMenu->popup(pos1);
}
}
void CustomGraphicsView::initStyle()
{
this->setStyleSheet("QMenu#itemMenu{ \
background: rgb(65, 67, 73); \
color: white; \
} \
QMenu#itemMenu::item:selected{ \
background: #00A1E0; \
color: white; \
}");
}
void CustomGraphicsView::drawLine(const QPoint & posEnd)
{
if (nullptr == m_curLineItem)
{
m_curLineItem = new CustomLineItem;
m_curLineItem->setCursor(Qt::PointingHandCursor);
m_curLineItem->setPen(QPen(QColor("#FF0000"), 3)); //设置边框颜色与大小
_curItemId += 1;
m_curLineItem->setId(_curItemId);
if (nullptr != _scene)
{
_scene->addItem(m_curLineItem);
}
m_mapLineItem[_curItemId] = m_curLineItem;
}
if (nullptr != m_curLineItem)
{
m_curLineItem->setLine(_mousePrePos.x(), _mousePrePos.y(), posEnd.x(), posEnd.y());
calcDistance(m_curLineItem);
}
}
void CustomGraphicsView::calcDistance(const CustomLineItem * lineItem)
{
if (nullptr != lineItem && nullptr != _pixmapItem)
{
double distance = 0;
ROICalcFunction rf;
//qDebug() << "pixmap width: " << _pixmapItem->pixmap().width() << " height: " << _pixmapItem->pixmap().height();
//qDebug() << "real width: " << m_imageRealWidth << " height: " << m_imageRealHeight;
//计算缩放前坐标
QPointF pos1 = QPointF(lineItem->line().p1().x() * m_imageRealWidth / _pixmapItem->pixmap().width()
, lineItem->line().p1().y() * m_imageRealHeight / _pixmapItem->pixmap().height());
QPointF pos2 = QPointF(lineItem->line().p2().x() * m_imageRealWidth / _pixmapItem->pixmap().width()
, lineItem->line().p2().y() * m_imageRealHeight / _pixmapItem->pixmap().height());
//qDebug() << "p1: " << lineItem->line().p1() << " " << pos1;
//qDebug() << "p2: " << lineItem->line().p2() << " " << pos2;
rf.calcDistance(pos1, pos2, m_pixelSpacing, distance);
//qDebug() << "distance = " << distance;
QPointF textPos = lineItem->line().p2().x() > lineItem->line().p1().x() ? lineItem->line().p2() : lineItem->line().p1();
drawDistanceText(textPos - QPointF(5, 0), QString("%1mm").arg(QString::number(distance, 'f', 2)));
}
}
void CustomGraphicsView::drawDistanceText(const QPointF & pos, const QString & text)
{
if (nullptr == _curTextItem)
{
_curTextItem = new CustomTextItem(_pixmapItem);
}
if (nullptr != _curTextItem)
{
//设置文本图元字体颜色
_curTextItem->setDefaultTextColor(Qt::yellow);
//设置文本位置
_curTextItem->setPos(pos);
//设置文本
_curTextItem->setPlainText(text);
_curTextItem->setId(_curItemId);
_mapTextItem[_curItemId] = _curTextItem;
}
}
void CustomGraphicsView::updateLineEndPoint(const QPointF & posEnd)
{
//计算偏移量
QPointF offset = posEnd - _mousePrePos;
//更新顶点坐标
if (nullptr != m_curLineItem)
{
QLineF line = m_linePrePos;
line.setP1(line.p1() + offset);
line.setP2(line.p2() + offset);
m_curLineItem->setLine(line);
}
}
void CustomGraphicsView::createLineEditItem()
{
if (nullptr == m_lineEditItem)
{
m_lineEditItem = new LineItemEditMgr(_scene.get());
connect(m_lineEditItem, &LineItemEditMgr::signalItemResize, this, &ImageSequence::slotLineResize);
}
if (nullptr != m_lineEditItem && nullptr != m_curLineItem)
{
QVector<QPointF> pos = {m_curLineItem->line().p1(), m_curLineItem->line().p2()};
m_lineEditItem->createItem(m_curLineItem->getId(), EDIT_ITEM_SIZE, pos);
}
}
void CustomGraphicsView::clearLineEditItem()
{
if (nullptr != m_lineEditItem)
{
m_lineEditItem->clearItem();
}
}
上述代码中实现了在QGraphicsView中绘制线段,绘制文本,以及移动线段及文本。
两点间测距
#ifndef __ROICALCFUNCTION_H__
#define __ROICALCFUNCTION_H__
#include <opencv2/opencv.hpp>
#include <QVector>
#include <algorithm>
class QImage;
class QRect;
class QPoint;
struct CoefficientParams;
class ROICalcFunction
{
public:
ROICalcFunction();
~ROICalcFunction();
/**
* @brief 计算两点间距离
* @param posStart-起始点
* @param posEnd-截止点
* @param pixelSpacing-像素间距离
* @param distance-返回计算后距离
*/
void calcDistance(const QPointF &posStart, const QPointF &posEnd, const QString &pixelSpacing, double &distance);
};
#endif // __ROICALCFUNCTION_H__
#include "ROIProcess/RoiCalcFunction.h"
#include <QImage>
#include <QRect>
#include "qmath.h"
#include <QDebug>
using namespace cv;
ROICalcFunction::ROICalcFunction()
{
}
ROICalcFunction::~ROICalcFunction()
{
}
void ROICalcFunction::calcDistance(const QPointF & posStart, const QPointF & posEnd, const QString & pixelSpacing, double & distance)
{
double pixelSpacingX = 1.0;
double pixelSpacingY = 1.0;
QStringList sl = pixelSpacing.split('\\');
if (sl.size() > 1)
{
pixelSpacingX = sl.at(0).toDouble();
pixelSpacingY = sl.at(1).toDouble();
}
qDebug() <<"spacing: " <<pixelSpacing <<" pixelSpacingX: " << pixelSpacingX << " Y: " << pixelSpacingY;
double xDistance = (fabs(posEnd.x() - posStart.x())) * pixelSpacingX;
double yDistance = (fabs(posEnd.y() - posStart.y())) * pixelSpacingY;
distance = sqrt(pow(xDistance, 2) + pow(yDistance, 2));
}
上述代码中,利用了从dicom文件读取的pixelSpacing值,计算两点坐标的差值再乘以像素间隔,算出最终x轴与y轴长度,最后利用勾股定理计算出三角形斜边长度,即为最后两点间距离。
编辑线段长度
#ifndef __LINEITEMEDITMGR_H__
#define __LINEITEMEDITMGR_H__
#include <QObject>
#include <QVector>
#include <QGraphicsEllipseItem>
class QGraphicsScene;
class CustomEllipseEditItem;
class LineItemEditMgr : public QObject
{
Q_OBJECT
public:
explicit LineItemEditMgr(QGraphicsScene *scene, QObject *parent = nullptr);
~LineItemEditMgr();
/**
* @brief 创建线段编辑图元
* @param itemId-线段图元ID
* @param size-编辑图元尺寸
* @param vecPos-线段端点坐标
*/
void createItem(int itemId, const QSize& size, const QVector<QPointF> &vecPos);
/**
* @brief 清除编辑图元
*/
void clearItem();
/**
* @brief 移动线段编辑图元
* @param vecPos-线段端点坐标
*/
void moveItem(const QVector<QPointF> &vecPos);
/**
* @brief 设置图元移动属性
* @param flag-属性类型
* @param enabled-是否开启
*/
void setItemFlag(QGraphicsItem::GraphicsItemFlag flag, bool enabled);
Q_SIGNALS:
void signalItemResize(int id, int index, int dx, int dy);
private slots:
void slotItemMoved(int itemType, const QPointF &newPos);
private:
QGraphicsScene *m_scene;
int m_itemId;
QVector<CustomEllipseEditItem*> m_vecEditItem;
};
#endif // __LINEITEMEDITMGR_H__
#include "ROIProcess/lineItemEditMgr.h"
#include "ROIProcess/ellipseItemEditMgr.h"
#include <QGraphicsScene>
#include <QCursor>
#include <QDebug>
LineItemEditMgr::LineItemEditMgr(QGraphicsScene *scene, QObject *parent) :
QObject(parent),
m_scene(scene)
{
}
LineItemEditMgr::~LineItemEditMgr()
{
clearItem();
m_scene = nullptr;
}
void LineItemEditMgr::createItem(int itemId, const QSize & size, const QVector<QPointF>& vecPos)
{
clearItem();
m_itemId = itemId;
for (int i = 0; i < vecPos.size(); i++)
{
if (m_vecEditItem.size() < i + 1)
{
CustomEllipseEditItem *pItem = new CustomEllipseEditItem;
pItem->setBrush(QBrush(Qt::red));
pItem->setPen(Qt::NoPen);
pItem->setFlag(QGraphicsItem::ItemIsMovable);
pItem->setCursor(Qt::PointingHandCursor);
pItem->setItemType(i);
connect(pItem, &CustomEllipseEditItem::signalItemMovedWithType, this, &LineItemEditMgr::slotItemMoved);
if (nullptr != m_scene)
{
m_scene->addItem(pItem);
}
m_vecEditItem.push_back(pItem);
}
if (m_vecEditItem.size() > i)
{
m_vecEditItem[i]->setPos(vecPos.at(i) - QPointF(size.width(), size.height()) / 2);
m_vecEditItem[i]->setRect(0, 0, size.width(), size.height());
}
}
}
void LineItemEditMgr::clearItem()
{
auto it = m_vecEditItem.begin();
while (it != m_vecEditItem.end())
{
CustomEllipseEditItem *pItem = *it;
if (nullptr != pItem)
{
delete pItem;
pItem = nullptr;
}
it = m_vecEditItem.erase(it);
}
m_vecEditItem.clear();
}
void LineItemEditMgr::moveItem(const QVector<QPointF>& vecPos)
{
for (int i = 0; i < vecPos.size(); i++)
{
if (m_vecEditItem.size() > i)
{
m_vecEditItem[i]->setPos(vecPos.at(i) - QPointF(m_vecEditItem.at(i)->rect().width(), m_vecEditItem.at(i)->rect().height()) / 2);
}
}
}
void LineItemEditMgr::setItemFlag(QGraphicsItem::GraphicsItemFlag flag, bool enabled)
{
for (int i = 0; i < m_vecEditItem.size(); i++)
{
m_vecEditItem.at(i)->setFlag(flag, enabled);
}
}
void LineItemEditMgr::slotItemMoved(int itemType, const QPointF &newPos)
{
if (newPos == QPointF(0, 0))
{
return;
}
if (itemType >= m_vecEditItem.size())
{
return;
}
int dx = newPos.x() - m_vecEditItem[itemType]->pos().x();
int dy = newPos.y() - m_vecEditItem[itemType]->pos().y();
emit signalItemResize(m_itemId, itemType, dx, dy);
}
上述代码,先缓存了线段的端点坐标值,再在端点处绘制了两个可移动的圆点,通过计算圆点的移动距离,得出相应的x,y轴偏移量。在自定义的CustomGraphicsView的slotLineResize槽函数中,通过原线段的坐标值与偏移量相加实现线段的大小编辑。
线段长度变化之后,调用ROICalcFunction类中的calcDistance函数,重新计算两点间的距离。
调用实例
#include "customGraphicsView.h"
#include "utils/dcmtk/IDicom.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
const QString dicomFile = ".\\testDicom.dcm"
IDicom *dicomProcessor = new DicomProcessor;
QImage image;
QString pixelSpacing;
if(nullptr != dicomProcessor )
{
dicomProcessor->Read(dicomFile);
if(nullptr != dicomProcessor->ImageInfo())
{
image = QImage(reinterpret_cast<unsigned char*>(dicomProcessor->ImageInfo()->PixelData), dicomProcessor->ImageInfo()->Columns, dicomProcessor->ImageInfo()->Rows, QImage::Format_RGB888); //最后一个参数根据dicom文件中具体的图片数据类型进行选择,我这里是RGB图片,所以用的是Format_RGB888
pixelSpacing = QString::fromStdString(dicomProcessor->ImageInfo()->PixelSpacing);
}
}
customGraphicsView graphicsView;
graphicsView->setFixedSize(500, 400);
if(!image.isNull())
{
const int width = image.width();
const int height = image.height();
image = image.scaled(_imageSeq->width(), _imageSeq->height());
graphicsView->setImage(0, image, width , height);
graphicsView->setPixelSpacing(pixelSpacing);
}
graphicsView.show();
return a.exec();
}
功能展示
dicom_distance