基于QT+DCMTK实现DICOM两点测距

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

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值