feat: add pseudocolor to image view
This commit is contained in:
@@ -53,6 +53,30 @@ namespace {
|
||||
if(idx == 1) return 0;
|
||||
return idx;
|
||||
}
|
||||
|
||||
void prepareTransferFunction(vtkDiscretizableColorTransferFunction *table, vtkImageProperty *prop,
|
||||
const std::vector<std::vector<double>> &vector) {
|
||||
|
||||
double min = prop->GetColorLevel() - prop->GetColorWindow() * 0.5;
|
||||
double max = prop->GetColorLevel() + prop->GetColorWindow() * 0.5;
|
||||
int size = table->GetSize();
|
||||
|
||||
table->RemoveAllPoints();
|
||||
for (int i = 0; i < size; i++) {
|
||||
table->AddRGBPoint(min + vector[i][0] * prop->GetColorWindow(),
|
||||
vector[i][1], vector[i][2], vector[i][3], vector[i][4], vector[i][5]);
|
||||
}
|
||||
table->DiscretizeOn();
|
||||
// vtkNew<vtkPiecewiseFunction> wise;
|
||||
// wise->AddPoint(min - 0.05, 0.0);
|
||||
// wise->AddPoint(min, table->GetAlpha());
|
||||
// wise->AddPoint(max, table->GetAlpha());
|
||||
// //wise->AddPoint(max + 0.05, 0.0);
|
||||
// wise->ClampingOn(); //Default is On
|
||||
// table->SetScalarOpacityFunction(wise);
|
||||
// table->EnableOpacityMappingOn();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vtkStandardNewMacro(DICOMImageViewer);
|
||||
@@ -65,8 +89,8 @@ DICOMImageViewer::DICOMImageViewer()
|
||||
FusionMapper(nullptr), Interactor(nullptr), InteractorStyle(nullptr), OpacityActor(nullptr),
|
||||
cornerAnnotation(FastCornerAnnotationActor::New()), bar(nullptr), ViewID(0),
|
||||
SliceIJK(-1), SlicePlane(-1), FirstRender(1), Slice(0), loadedMeasureSlice(0),
|
||||
currentPresetIndex(1), Fusion(false), firstFusion(true), FusionOpacity(0.5), list(nullptr),
|
||||
measureStore(MeasureStore::Instance()),
|
||||
currentPresetIndex(1), Fusion(false), firstFusion(true),
|
||||
FusionOpacity(0.5), list(nullptr), measureStore(MeasureStore::Instance()),
|
||||
rulerActive(false){
|
||||
this->ImageMapper->SliceAtFocalPointOn();
|
||||
this->ImageMapper->SliceFacesCameraOn();
|
||||
@@ -298,6 +322,8 @@ void DICOMImageViewer::InstallPipeline() {
|
||||
this, &DICOMImageViewer::RemoveMeasures);
|
||||
this->InteractorStyle->AddObserver(DraggableStyleEvents::SliceEvent, this,
|
||||
&DICOMImageViewer::ChangeSlice);
|
||||
this->InteractorStyle->AddObserver(vtkCommand::EventIds::WindowLevelEvent, this,
|
||||
&DICOMImageViewer::ChangeWindowLevel);
|
||||
|
||||
//for convert vtkEvent to Qt signal
|
||||
this->InteractorStyle->AddObserver(DraggableStyleEvents::SlicedEvent, this,
|
||||
@@ -683,6 +709,35 @@ void DICOMImageViewer::SetColorLevel(double s) {
|
||||
this->ImageActor->GetProperty()->SetColorLevel(s);
|
||||
}
|
||||
|
||||
void DICOMImageViewer::ChangeWindowLevel(vtkObject *, unsigned long eventid, void *calldata)
|
||||
{
|
||||
if (!vtkDiscretizableColorTransferFunction::SafeDownCast(ImageActor->GetProperty()->GetLookupTable())) return;
|
||||
auto values = ColorMapReader::DefaultPresets()->GetPresetValues(mColorPreset.data());
|
||||
vtkNew<vtkDiscretizableColorTransferFunction> table;
|
||||
for (int i = 0; i + 3 < values.size(); i += 4)
|
||||
{
|
||||
table->AddRGBPoint(values[i], values[i + 1], values[i + 2], values[i + 3]);
|
||||
}
|
||||
double *nancolor = ColorMapReader::DefaultPresets()->GetPresetNanColor(mColorPreset.data());
|
||||
if (nancolor)
|
||||
{
|
||||
table->SetNanColor(nancolor);
|
||||
}
|
||||
if (table)
|
||||
{
|
||||
int size = table->GetSize();
|
||||
fusion_tf_vector.clear();
|
||||
for (int i = 0; i < size; i++) {
|
||||
double v[6] = {0, 0, 0, 0, 0, 0};
|
||||
table->GetNodeValue(i, v);
|
||||
std::vector<double> nt = {v[0], v[1], v[2], v[3], v[4], v[5]};
|
||||
fusion_tf_vector.push_back(nt);
|
||||
}
|
||||
prepareTransferFunction(table, ImageActor->GetProperty(), fusion_tf_vector);
|
||||
}
|
||||
ImageActor->GetProperty()->SetLookupTable(table);
|
||||
}
|
||||
|
||||
void DICOMImageViewer::SetNegativeMode(bool negative) {
|
||||
if (negative) {
|
||||
double window = this->ImageActor->GetProperty()->GetColorWindow();
|
||||
@@ -703,7 +758,58 @@ void DICOMImageViewer::SetNegativeMode(bool negative) {
|
||||
}
|
||||
}
|
||||
|
||||
void DICOMImageViewer::SetLookupTable(vtkLookupTable *lut) {
|
||||
void DICOMImageViewer::SetLookupTable(const QString &aTableName)
|
||||
{
|
||||
if (aTableName == "gray")
|
||||
{
|
||||
mColorPreset = "gray";
|
||||
this->SetLookupTable(nullptr);
|
||||
}
|
||||
else if (aTableName == "neg")
|
||||
{
|
||||
mColorPreset = "grayneg";
|
||||
double window = this->ImageActor->GetProperty()->GetColorWindow();
|
||||
double level = this->ImageActor->GetProperty()->GetColorLevel();
|
||||
|
||||
vtkNew<vtkLookupTable> lut;
|
||||
lut->SetRange(level - 0.5 * window, level + 0.5 * window); // image intensity range
|
||||
lut->SetValueRange(1.0, 0.0); // from black to white
|
||||
lut->SetHueRange(0.0, 0.0);
|
||||
lut->SetSaturationRange(0.0, 0.0);
|
||||
//lookupTable->SetRampToLinear();
|
||||
lut->Build();
|
||||
this->SetLookupTable(lut);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aTableName == "jet")
|
||||
{
|
||||
mColorPreset = "Jet";
|
||||
auto values = ColorMapReader::DefaultPresets()->GetPresetValues(mColorPreset.data());
|
||||
vtkNew<vtkDiscretizableColorTransferFunction> table;
|
||||
for (int i = 0; i + 3 < values.size(); i += 4) {
|
||||
table->AddRGBPoint(values[i], values[i + 1], values[i + 2], values[i + 3]);
|
||||
}
|
||||
double *nancolor = ColorMapReader::DefaultPresets()->GetPresetNanColor(mColorPreset.data());
|
||||
if (nancolor) {
|
||||
table->SetNanColor(nancolor);
|
||||
}
|
||||
int size = table->GetSize();
|
||||
fusion_tf_vector.clear();
|
||||
for (int i = 0; i < size; i++) {
|
||||
double v[6] = {0, 0, 0, 0, 0, 0};
|
||||
table->GetNodeValue(i, v);
|
||||
std::vector<double> nt = {v[0], v[1], v[2], v[3], v[4], v[5]};
|
||||
fusion_tf_vector.push_back(nt);
|
||||
}
|
||||
prepareTransferFunction(table, ImageActor->GetProperty(), fusion_tf_vector);
|
||||
this->SetLookupTable(table);
|
||||
}
|
||||
}
|
||||
this->Render();
|
||||
}
|
||||
|
||||
void DICOMImageViewer::SetLookupTable(vtkScalarsToColors *lut) {
|
||||
this->ImageActor->GetProperty()->SetLookupTable(lut);
|
||||
}
|
||||
|
||||
@@ -840,28 +946,6 @@ void prepareLookTable(vtkLookupTable *table) {
|
||||
table->UseBelowRangeColorOn();
|
||||
}
|
||||
|
||||
void prepareTransferFunction(vtkDiscretizableColorTransferFunction *table, vtkImageProperty *prop,
|
||||
const std::vector<std::vector<double>> &vector) {
|
||||
|
||||
double min = prop->GetColorLevel() - prop->GetColorWindow() * 0.5;
|
||||
double max = prop->GetColorLevel() + prop->GetColorWindow() * 0.5;
|
||||
int size = table->GetSize();
|
||||
|
||||
table->RemoveAllPoints();
|
||||
for (int i = 0; i < size; i++) {
|
||||
table->AddRGBPoint(min + vector[i][0] * prop->GetColorWindow(),
|
||||
vector[i][1], vector[i][2], vector[i][3], vector[i][4], vector[i][5]);
|
||||
}
|
||||
vtkNew<vtkPiecewiseFunction> wise;
|
||||
wise->AddPoint(min - 0.05, 0.0);
|
||||
wise->AddPoint(min, table->GetAlpha());
|
||||
wise->AddPoint(max, table->GetAlpha());
|
||||
//wise->AddPoint(max + 0.05, 0.0);
|
||||
wise->ClampingOn(); //Default is On
|
||||
table->SetScalarOpacityFunction(wise);
|
||||
table->EnableOpacityMappingOn();
|
||||
}
|
||||
|
||||
void DICOMImageViewer::SetFusionColorTable(vtkScalarsToColors *table) {
|
||||
if (!FusionActor) return;
|
||||
PrepareFusionColorTable(table, true);
|
||||
|
||||
@@ -162,9 +162,13 @@ public:
|
||||
|
||||
virtual void SetColorLevel(double s);
|
||||
|
||||
void ChangeWindowLevel(vtkObject *, unsigned long eventid, void *calldata);
|
||||
|
||||
void SetNegativeMode(bool negative);
|
||||
|
||||
void SetLookupTable(vtkLookupTable *lut);
|
||||
void SetLookupTable(const QString& aTableName);
|
||||
|
||||
void SetLookupTable(vtkScalarsToColors *lut);
|
||||
//@}
|
||||
|
||||
//@{
|
||||
@@ -452,6 +456,7 @@ private:
|
||||
vtkNew<vtkMatrix4x4> ModelToWorldMatrix;
|
||||
|
||||
DicomCornerInfo m_cornerInfo;
|
||||
std::string mColorPreset;
|
||||
uint ViewID;
|
||||
int SliceIJK;
|
||||
int SlicePlane;
|
||||
|
||||
@@ -23,7 +23,7 @@ DicomImageView::DicomImageView(QWidget *parent)
|
||||
: QFrame(parent), mGLRenWin(vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New()), mImageViewer(nullptr),
|
||||
mSeries(nullptr), mScrollBar(new ClickableScrollBar(Qt::Orientation::Vertical, this)),
|
||||
mTitleBar(createMyTitleBar()), mGLWidget(new QVTKOpenGLNativeWidget), mVcrToolbar(nullptr), mVcrController(nullptr),
|
||||
mOverlayView(nullptr), mBaseView(nullptr), mIsCine(false), mIsNegative(false),
|
||||
mOverlayView(nullptr), mBaseView(nullptr), mTableName("gray"),mIsCine(false), mIsNegative(false),
|
||||
mIsOverlay(false), mIsSlotInited(false),mCurrentRAngle(0)
|
||||
{
|
||||
//main container
|
||||
@@ -176,6 +176,7 @@ void DicomImageView::loadSeries(SeriesImageSet *series) {
|
||||
.arg(time)
|
||||
.arg(series->GetProperty()->GetSeriesDescription()));
|
||||
mImageViewer->SetInputData(mSeries->GetData());
|
||||
setColorTable("gray");
|
||||
mIsFirstRenderAfterLoad = true;
|
||||
if(mSeries->GetOverlayData())
|
||||
{
|
||||
@@ -606,6 +607,19 @@ void DicomImageView::negativeWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
void DicomImageView::setColorTable(const QString &aTableName)
|
||||
{
|
||||
if (hasSeries()) {
|
||||
mImageViewer->SetLookupTable(aTableName);
|
||||
mTableName = aTableName;
|
||||
}
|
||||
}
|
||||
|
||||
QString DicomImageView::getColorTable()
|
||||
{
|
||||
return mTableName;
|
||||
}
|
||||
|
||||
void DicomImageView::hFlipImage() {
|
||||
if (hasSeries()) {
|
||||
int slice = mImageViewer->GetSlice();
|
||||
|
||||
@@ -162,6 +162,12 @@ public:
|
||||
//Negative
|
||||
void negativeWindow();
|
||||
|
||||
//colortable
|
||||
void setColorTable(const QString& aTableName);
|
||||
|
||||
QString getColorTable();
|
||||
|
||||
|
||||
//MPR
|
||||
void setReconPlane(int plane);
|
||||
|
||||
@@ -339,6 +345,8 @@ private:
|
||||
DicomImageView *mOverlayView;
|
||||
DicomImageView *mBaseView;
|
||||
|
||||
QString mTableName;
|
||||
|
||||
bool mIsCine;
|
||||
bool mIsNegative;
|
||||
bool mIsOverlay;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include <QButtonGroup>
|
||||
#include <QActionGroup>
|
||||
#include "Rendering/Measure/Measure.h"
|
||||
#include "UI/Widget/ImageView/DicomImageView.h"
|
||||
#include "UI/Manager/ImageViewManager.h"
|
||||
|
||||
#include "Common/QGlobals.h"
|
||||
#include "ExportToolButton.h"
|
||||
|
||||
@@ -70,6 +73,13 @@ void DefaultToolBar::setViewManager(ImageViewManager *aManager)
|
||||
mBtnSave->setImageViewManager(mImageViewManager);
|
||||
}
|
||||
|
||||
void DefaultToolBar::changePseudoColor(bool on)
|
||||
{
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
QString table = action->property("table").toString();
|
||||
emit setPseudoColor(table);
|
||||
}
|
||||
|
||||
QAction* DefaultToolBar::addButton(QToolButton* button, const char* objectName) {
|
||||
button->setObjectName(objectName);
|
||||
button->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
@@ -274,9 +284,27 @@ void DefaultToolBar::initModeButtons() {
|
||||
QMenu *m = new QMenu(this);
|
||||
m->addAction(tr("Custom window width and level"), this, &DefaultToolBar::customWindow);
|
||||
|
||||
auto action = m->addAction(tr("Negative"), this, &DefaultToolBar::negativeWindow);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(false);
|
||||
m->addSeparator();
|
||||
|
||||
mPseudocolorGroup = new QActionGroup(this);
|
||||
auto actionGray = m->addAction(tr("gray"), this, &DefaultToolBar::changePseudoColor);
|
||||
actionGray->setCheckable(true);
|
||||
actionGray->setChecked(true);
|
||||
actionGray->setActionGroup(mPseudocolorGroup);
|
||||
actionGray->setProperty("table","gray");
|
||||
|
||||
auto actionNeg = m->addAction(tr("Negative"), this, &DefaultToolBar::changePseudoColor);
|
||||
actionNeg->setCheckable(true);
|
||||
actionNeg->setChecked(false);
|
||||
actionNeg->setActionGroup(mPseudocolorGroup);
|
||||
actionNeg->setProperty("table","neg");
|
||||
|
||||
auto actionJet = m->addAction(tr("Jet"), this, &DefaultToolBar::changePseudoColor);
|
||||
actionJet->setCheckable(true);
|
||||
actionJet->setChecked(false);
|
||||
actionJet->setActionGroup(mPseudocolorGroup);
|
||||
actionJet->setProperty("table","jet");
|
||||
|
||||
|
||||
mBtnWindow->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
mBtnWindow->setMenu(m);
|
||||
@@ -488,7 +516,14 @@ void DefaultToolBar::initMPRButton(){
|
||||
void DefaultToolBar::resetNeedCheckFunctionButtons(){
|
||||
mBtnMPR->setEnabled(false);
|
||||
mBtnFusion->setEnabled(false);
|
||||
|
||||
for(auto var : mPseudocolorGroup->actions())
|
||||
{
|
||||
if (var->property("table").toString() == mImageViewManager->getCurrentView()->getColorTable())
|
||||
{
|
||||
var->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// SyncHelper::setSyncState(DIS_SYNC);
|
||||
// syncStateChanged();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
void resetNeedCheckFunctionButtons();
|
||||
void updateNeedCheckFunctionButtons(ViewFunctionState state);
|
||||
void setViewManager(ImageViewManager* aManager);
|
||||
|
||||
void changePseudoColor(bool on = false);
|
||||
signals:
|
||||
void openFile();
|
||||
void openFolder();
|
||||
@@ -29,7 +29,6 @@ signals:
|
||||
void showGrid(QToolButton* btn);
|
||||
void modeChanged(int mode);
|
||||
void customWindow();
|
||||
void negativeWindow();
|
||||
void fusion(bool on = false);
|
||||
void cine(bool on = false);
|
||||
void changeReconPlane(int plane);
|
||||
@@ -37,7 +36,7 @@ signals:
|
||||
void parentWindowStateChange(Qt::WindowState state);
|
||||
void parentWindowClose();
|
||||
void parentWindowLanguageChange();
|
||||
|
||||
void setPseudoColor(const QString& aTable);
|
||||
void transform(TransFormType type);
|
||||
void showMeta();
|
||||
void volumeRendering();
|
||||
@@ -101,6 +100,7 @@ private:
|
||||
QAction *mActionHidePatData;
|
||||
QAction* mSyncActions[3]={nullptr,nullptr,nullptr};
|
||||
QAction* mMPRActions[3]={nullptr,nullptr,nullptr};
|
||||
QActionGroup *mPseudocolorGroup;
|
||||
ImageViewManager* mImageViewManager;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QButtonGroup>
|
||||
#include <QToolButton>
|
||||
#include <QAction>
|
||||
|
||||
|
||||
#include "Common/Helper/OrientationHelper.h"
|
||||
@@ -140,12 +141,7 @@ void QDicomViewer::initViewOperation() {
|
||||
m_customwin->exec();
|
||||
});
|
||||
// negative window
|
||||
connect(ui->toolBar, &DefaultToolBar::negativeWindow, [=]() {
|
||||
DicomImageView *curV = ui->viewContainer->getViewManager()->getCurrentView();
|
||||
if (curV != nullptr && curV->hasSeries()) {
|
||||
curV->negativeWindow();
|
||||
}
|
||||
});
|
||||
connect(ui->toolBar, &DefaultToolBar::setPseudoColor, this, &QDicomViewer::changeColorTable);
|
||||
// fusion
|
||||
connect(ui->toolBar, &DefaultToolBar::fusion,
|
||||
ui->viewContainer->getViewManager(), &ImageViewManager::switchFusion);
|
||||
@@ -306,6 +302,14 @@ void QDicomViewer::openDICOMFromPACS(int err, std::string dirName) {
|
||||
}
|
||||
}
|
||||
|
||||
void QDicomViewer::changeColorTable(const QString& aTable)
|
||||
{
|
||||
DicomImageView *curV = ui->viewContainer->getViewManager()->getCurrentView();
|
||||
if (curV != nullptr && curV->hasSeries()) {
|
||||
curV->setColorTable(aTable);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: 覆盖逻辑和增加逻辑待补充
|
||||
void QDicomViewer::openDICOM(const std::string &dicomName, SeriesOpenMode openMode) {
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
public slots:
|
||||
void Slot_ToolbarVisibilityChanged(bool);
|
||||
void openDICOMFromPACS(int,std::string);
|
||||
void changeColorTable(const QString& aTable);
|
||||
private:
|
||||
Ui::QDicomViewerClass *ui;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user