feat: add pseudocolor to image view

This commit is contained in:
kradchen
2025-06-19 21:14:31 +08:00
parent 516c79f83d
commit 5925e13ed0
8 changed files with 191 additions and 40 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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) {

View File

@@ -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;