From db299abbc53bfadba231183079cc9fc9613217a0 Mon Sep 17 00:00:00 2001 From: kradchen Date: Thu, 19 Jun 2025 10:43:21 +0800 Subject: [PATCH] feat: add round measure & update ellipse measure --- src/Icon/diameter.png | Bin 0 -> 9470 bytes src/QDicomViewer.qrc | 1 + src/src/Common/QGlobals.h | 1 + src/src/Rendering/Core/ControlPointActor.cpp | 5 + src/src/Rendering/Core/ControlPointActor.h | 2 + .../Measure/EllipseAnnotationActor.cpp | 490 ++++++++++++++---- .../Measure/EllipseAnnotationActor.h | 10 +- src/src/Rendering/Measure/MeasureFactory.cpp | 4 + .../Measure/OpenPolyAnnotationActor.cpp | 8 +- .../Measure/OpenPolyAnnotationActor.h | 2 + .../Measure/RoundAnnotationActor.cpp | 411 +++++++++++++++ .../Rendering/Measure/RoundAnnotationActor.h | 70 +++ src/src/UI/Widget/ToolBar/DefaultToolBar.cpp | 3 +- src/translations/zh_CN.ts | 24 + 14 files changed, 935 insertions(+), 96 deletions(-) create mode 100644 src/Icon/diameter.png create mode 100644 src/src/Rendering/Measure/RoundAnnotationActor.cpp create mode 100644 src/src/Rendering/Measure/RoundAnnotationActor.h diff --git a/src/Icon/diameter.png b/src/Icon/diameter.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc1f35f9f5c65504fc7cd8ed3917a585b9577b5 GIT binary patch literal 9470 zcmb7~2Uinc6UG+|5=10O6Y0HIr71;X5Q#{MNC~}5?;QjqCDcDisD>gCkP?s*q)1Vb zUZqJ#qzM9obOhe@BfL3hvnScTyLb1_o%zi(H%3=mgXYT3D*yn{Xg)-szin`#dBQCVy!Oib!j~>&34YiUZ2Z|>-z=Knt#nYY0$(%QJEiC3gU%GTD z%q2fqplJ!xy;Qvyr8m&f3gzZtf}0kR&fmM?pK2$$nUW;lO$LWC^0b8EOD-BmwC`rC z1|R%NFta>oqW8x5j5ztm5CO@ zRM!i8ql?WK3c5*`o2e3dGNPlj(|+sz{rft_F80%(6<1pXBaWg482%a|k6)ll5Vk!o zy;QH-zQU;F+-B-qVb)=Y84F|s=h@>63+PV1Ulb&~xIic<>YNF##2b;acF=&qkHA9p%^ zA(ei*xQU$2$=*-*%i%T!LU*nL2hZSQab^Yxy*(SZY9r@Citt_q0WregfD-)^wrGiK z?22mUfcol;9vo{E3G;Dj5TsmS`Ks2`osB*Ek(B)f9Si~fMg*D{+b%cp33eHX-c?fi zkY^;u5r;*@UdmurDO=CseMB^9vYGo3SZB=3s?j*l4FAw#oGEY;s_qedeqK(tBQw3njCPu4mYhKnrzD@`Ut+BtU>jt6Hq@ByYCboZ$C-n8fZAA;>O;H zE~E-zPV}V�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 aob&UdW&ArY8WOmm3D8v6MpVEpG5-VoI|bMP literal 0 HcmV?d00001 diff --git a/src/QDicomViewer.qrc b/src/QDicomViewer.qrc index b8a387b..f8bbca3 100644 --- a/src/QDicomViewer.qrc +++ b/src/QDicomViewer.qrc @@ -57,6 +57,7 @@ Icon/Reset.png Icon/Settings.png Icon/preset.png + Icon/diameter.png Icon/pq/pqBold24.png diff --git a/src/src/Common/QGlobals.h b/src/src/Common/QGlobals.h index fffa3b0..80e3efc 100644 --- a/src/src/Common/QGlobals.h +++ b/src/src/Common/QGlobals.h @@ -81,6 +81,7 @@ enum AnnotationActorType OpenPolygonAnn, ArrowAnn, EllipseAnn, + DiameterAnn, TextAnn, }; enum AnnotationDeleteType{ diff --git a/src/src/Rendering/Core/ControlPointActor.cpp b/src/src/Rendering/Core/ControlPointActor.cpp index d4784f6..b9419b8 100644 --- a/src/src/Rendering/Core/ControlPointActor.cpp +++ b/src/src/Rendering/Core/ControlPointActor.cpp @@ -70,6 +70,11 @@ void ControlPointActor::SetWorldPosition(double x, double y, double z) { BaseDataPoints->Reset(); BaseDataPoints->SetNumberOfPoints(1); BaseDataPoints->SetPoint(0, x, y, z); +} + +double *ControlPointActor::GetRenderPosition() +{ + return renderPoints->GetPoint(0); }; double *ControlPointActor::GetWorldPosition() { diff --git a/src/src/Rendering/Core/ControlPointActor.h b/src/src/Rendering/Core/ControlPointActor.h index 6ab9c2f..a5dd561 100644 --- a/src/src/Rendering/Core/ControlPointActor.h +++ b/src/src/Rendering/Core/ControlPointActor.h @@ -24,6 +24,8 @@ public: void SetWorldPosition(double x, double y, double z); + double *GetRenderPosition(); + void SetWorldPosition(double *pos) { SetWorldPosition(pos[0], pos[1], pos[2]); } diff --git a/src/src/Rendering/Measure/EllipseAnnotationActor.cpp b/src/src/Rendering/Measure/EllipseAnnotationActor.cpp index 5a8135b..09449c5 100644 --- a/src/src/Rendering/Measure/EllipseAnnotationActor.cpp +++ b/src/src/Rendering/Measure/EllipseAnnotationActor.cpp @@ -13,7 +13,21 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include + #include +#include + + +#include "omp.h" + +#include "Interaction/ActorDraggableInteractorStyle.h" #include "Rendering/Core/ControlPointActor.h" #include "Common/QGlobals.h" @@ -26,30 +40,30 @@ namespace { void EllipseAnnotationActor::BuildShape() { if (!BaseDataPoints->GetNumberOfPoints()) return; + QString stamp = QString("%1-%2").arg(BaseDataPoints->GetMTime()) + .arg(Renderer->GetActiveCamera()->GetMTime()); + if (mRenderTime == stamp) return; + QStringList stamps = stamp.split("-"); + QStringList renderTimes = mRenderTime.split("-"); + mRenderTime = stamp; RebuildRenderPoint(); - - double *p_lt = controlP_lt->GetWorldPosition(); - double *p_rb = controlP_rb->GetWorldPosition(); - if (!transforming) - { - double area = abs((p_lt[0] - p_rb[0]) * (p_lt[1] - p_rb[1])) * vtkMath::Pi() * 0.25; - bool unitFlag2 = area>1000; - QString num = QString::number(area, 'f', 2); - QString textValue = QString("%1: %2 %3") - .arg(mAreaName) - .arg(unitFlag2?area/100:area,0,'f',2).arg(unitFlag2?mUnitcm2:mUnitmm2); - vtkTextMapper::SafeDownCast(text->GetMapper())->SetInput(textValue.toUtf8().constData()); - + //base data change change message + if (stamps[0] != renderTimes[0]) + { double p_lt[3] = {0, 0, 0}; + double* disp_lt = controlP_lt->GetRenderPosition(); + MapScreenPointToWorld(disp_lt[0], disp_lt[1], this->Renderer, p_lt); + double p_rb[3] = {0, 0, 0}; + double* disp_rb = controlP_rb->GetRenderPosition(); + MapScreenPointToWorld(disp_rb[0], disp_rb[1], this->Renderer, p_rb); + analyzePixel(p_lt,p_rb); } double *rp = renderPoints->GetPoint(0); text->SetDisplayPosition(rp[0] + 10, rp[1] - 20); - vtkNew source; source->SetPoints(renderPoints); source->Update(); - renderData->DeepCopy(source->GetOutput()); if (Measure::Hidden) { @@ -83,8 +97,8 @@ EllipseAnnotationActor::EllipseAnnotationActor() { //BaseDataPoints->SetPoint(0, 0, 0, 0); //BaseDataPoints->SetPoint(1, 512, 512, 0); - renderPoints->SetNumberOfPoints(GRANULARITY + 1); + BaseDataPoints->Modified(); controlP_lt->AddObserver(DraggableActorEvents::DragEvent, this, &EllipseAnnotationActor::controlPointCb); controlP_rt->AddObserver(DraggableActorEvents::DragEvent, this, &EllipseAnnotationActor::controlPointCb); controlP_lb->AddObserver(DraggableActorEvents::DragEvent, this, &EllipseAnnotationActor::controlPointCb); @@ -95,10 +109,8 @@ EllipseAnnotationActor::EllipseAnnotationActor() { controlP_lb->SetcontrolledActor(this); controlP_rb->SetcontrolledActor(this); - //this->AddObserver(DraggableActorEvents::DragEvent, this, &EllipseAnnotationActor::selfDragCb); - text = vtkActor2D::New(); vtkNew textMapper; textMapper->SetInput("0"); @@ -147,72 +159,378 @@ void EllipseAnnotationActor::SetRenderer(vtkRenderer *ren) { } void EllipseAnnotationActor::controlPointCb(vtkObject *sender, unsigned long event, void *data) { - - + if (transforming) return; vtkPoints *pts = static_cast(data); double *pos = pts->GetPoint(0); - + double displayP[3] = {pos[0], pos[1], 0}; double result[3] = {0, 0, 0}; - MapScreenPointToWorld(pos[0], pos[1], this->Renderer, result); - - //BaseDataPoints->SetPoint(index, result); - + MapScreenPointToWorld(pos[0]+0.5, pos[1]+0.5, this->Renderer, result); + double * dp = Renderer->GetActiveCamera()->GetDirectionOfProjection(); + int nIndex[2] = {0,1}; + if (abs(dp[1])>0.0){ + nIndex[1] = 2; + } + if (abs(dp[0])>0.0){ + nIndex[0] = 1; + nIndex[1] = 2; + } double *p1; double *p2; + double displaypos[3]={0}; - if (sender == controlP_lt) { + if (sender == controlP_lt ) { p1 = result; p2 = controlP_rb->GetWorldPosition(); - - double p_rt[3] = {p2[0], p1[1], 0}; - double p_lb[3] = {p1[0], p2[1], 0}; + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayp_rb = displaypos; + double p_rt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayp_rb[0], displayP[1], Renderer,p_rt); + double p_lb[3] = {0, 0, 0}; + MapScreenPointToWorld( displayP[0], displayp_rb[1], Renderer,p_lb); controlP_rt->SetWorldPosition(p_rt); controlP_lb->SetWorldPosition(p_lb); - } else if (sender == controlP_rt) { + drawCircle(p1, p2); + } else if (sender == controlP_rt ) { p1 = result; p2 = controlP_lb->GetWorldPosition(); - - double p_lt[3] = {p2[0], p1[1], 0}; - double p_rb[3] = {p1[0], p2[1], 0}; + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayp_lb = displaypos; + double p_lt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayp_lb[0], displayP[1], Renderer,p_lt); + double p_rb[3] = {0, 0, 0}; + MapScreenPointToWorld( displayP[0], displayp_lb[1], Renderer,p_rb); controlP_lt->SetWorldPosition(p_lt); controlP_rb->SetWorldPosition(p_rb); + drawCircle(p_lt, p_rb); - } else if (sender == controlP_lb) { - p1 = result; - p2 = controlP_rt->GetWorldPosition(); - - double p_lt[3] = {p1[0], p2[1], 0}; - double p_rb[3] = {p2[0], p1[1], 0}; + } else if (sender == controlP_lb ) { + p2 = result; + p1 = controlP_rt->GetWorldPosition(); + Renderer->SetWorldPoint(p1); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayp_rt = displaypos; + double p_lt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayP[0],displayp_rt[1], Renderer,p_lt); + double p_rb[3] = {0, 0, 0}; + MapScreenPointToWorld( displayp_rt[0], displayP[1], Renderer,p_rb); controlP_lt->SetWorldPosition(p_lt); controlP_rb->SetWorldPosition(p_rb); - } else if (sender == controlP_rb) { - p1 = result; - p2 = controlP_lt->GetWorldPosition(); - - double p_rt[3] = {p1[0], p2[1], 0}; - double p_lb[3] = {p2[0], p1[1], 0}; + drawCircle(p_lt, p_rb); + } else if (sender == controlP_rb ) { + p2 = result; + p1 = controlP_lt->GetWorldPosition(); + Renderer->SetWorldPoint(p1); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayp_lt = displaypos; + double p_rt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayP[0], displayp_lt[1], Renderer,p_rt); + double p_lb[3] = {0, 0, 0}; + MapScreenPointToWorld(displayp_lt[0], displayP[1], Renderer,p_lb); controlP_rt->SetWorldPosition(p_rt); controlP_lb->SetWorldPosition(p_lb); + drawCircle(p1, p2); } else { + } - drawCircle(p1, p2); + } void EllipseAnnotationActor::drawCircle(double *p1, double *p2) { - int CenterX = (p1[0] + p2[0]) / 2.0; - int CenterY = (p1[1] + p2[1]) / 2.0; - double r1 = abs(p1[0] - p2[0]) / 2.0; - double r2 = abs(p1[1] - p2[1]) / 2.0; + double * dp = Renderer->GetActiveCamera()->GetDirectionOfProjection(); + + int nIndex[2] = {0,1}; + if (abs(dp[1])>0.0){ + nIndex[1] = 2; + } + if (abs(dp[0])>0.0){ + nIndex[0] = 1; + nIndex[1] = 2; + } + int CenterX = (p1[nIndex[0]] + p2[nIndex[0]] ) / 2.0; + int CenterY = (p1[nIndex[1]] + p2[nIndex[1]] ) / 2.0; + double r1 = round(abs(p1[nIndex[0]] - p2[nIndex[0]]) / 2.0*100.0)/100; + double r2 = round(abs(p1[nIndex[1]]- p2[nIndex[1]]) / 2.0*100.0)/100; double angle = 0; int id = 0; while (angle <= 2.0 * vtkMath::Pi() + (2.0 * vtkMath::Pi() / (GRANULARITY * 1.0))) { - BaseDataPoints->SetPoint(id, r1 * cos(angle) + CenterX, r2 * sin(angle) + CenterY, 0); + double p[3]={0}; + p[0] = p1[0]; + p[1] = p1[1]; + p[2] = p1[2]; + p[nIndex[0]] = r1 * cos(angle) + CenterX; + p[nIndex[1]] = r2 * sin(angle) + CenterY; + + BaseDataPoints->SetPoint(id, p); angle = angle + (2.0 * vtkMath::Pi() / (GRANULARITY * 1.0)); id++; } + BaseDataPoints->Modified(); +} + +template +void makeMessage(int count[4], double avg[4], double sq2[4], T max[4], T min[4], QString &message, double area, const QString& aAreaUnitName) +{ + long totalCount = count[0]; + double avgV = avg[0]; + double sq2V = sq2[0]; + bool hasValue = false; + for (size_t i = 1; i < 4; i++) + { + long lastCount = totalCount; + if (totalCount == 0) + { + totalCount = count[i]; + avgV = avg[i]; + sq2V = sq2[i]; + continue; + } + hasValue = true; + totalCount += count[i]; + double delta = avg[i] - avgV; + avgV = (lastCount * avgV + count[i] * avg[i]) / totalCount; + sq2V += sq2[i] + delta * delta * lastCount * count[i] / totalCount; + } + if (!hasValue) + { + double maxV = std::max({max[0], max[1], max[2], max[3]}); + double minV = std::min({min[0], min[1], min[2], min[3]}); + double sd = std::sqrt(sq2V / totalCount); + message = message + .arg(area) + .arg(0) + .arg(0) + .arg(0) + .arg(0) + .arg(0) + .arg(aAreaUnitName); + } + else{ + double maxV = std::max({max[0], max[1], max[2], max[3]}); + double minV = std::min({min[0], min[1], min[2], min[3]}); + double sd = std::sqrt(sq2V / totalCount); + message = message + .arg(area, 0, 'f', 0) + .arg(totalCount) + .arg(maxV, 0, 'f', 0) + .arg(minV, 0, 'f', 0) + .arg(avgV, 0, 'f', 0) + .arg(sd, 0, 'f', 0) + .arg(aAreaUnitName); + } +} + +template +void calcLoopContent(int count[4], T *row_pointer, size_t j, int pixelStride, T max[4], T min[4], double avg[4], double sq2[4]) +{ + count[omp_get_thread_num()]++; + T value = row_pointer[j * pixelStride]; + max[omp_get_thread_num()] = std::max(value, max[omp_get_thread_num()]); + min[omp_get_thread_num()] = std::min(value, min[omp_get_thread_num()]); + double lastavg = avg[omp_get_thread_num()]; + avg[omp_get_thread_num()] = (count[omp_get_thread_num()] - 1) * avg[omp_get_thread_num()] / ((double)count[omp_get_thread_num()]) + ((double)value) / ((double)count[omp_get_thread_num()]); + sq2[omp_get_thread_num()] = sq2[omp_get_thread_num()] + ((double)value - avg[omp_get_thread_num()]) * ((double)value - lastavg); +} + +template +void EllipseAnnotationActor::vtkValueCalcTemplate(vtkImageData *image, int sliceNumber, double *pt1, double *pt2, double *normals, QString &message) +{ + double spacings[3] ={0}; + int dims[3] = {0}; + image->GetSpacing(spacings); + image->GetDimensions(dims); + T *pointer = (T *)image->GetScalarPointer(); + if (!pointer) { + return; + } + int count[4] = {0, 0, 0, 0}; + T max[4] = {std::numeric_limits::min(),std::numeric_limits::min(),std::numeric_limits::min(),std::numeric_limits::min()}; + T min[4] = {std::numeric_limits::max(),std::numeric_limits::max(),std::numeric_limits::max(),std::numeric_limits::max()}; + double avg[4] = {0, 0, 0, 0}; + double sq2[4] = {0, 0, 0, 0}; + + if (abs(normals[2])>0.5) + { + int CenterX = (pt1[0] + pt2[0]) / 2.0; + int CenterY = (pt1[1] + pt2[1]) / 2.0; + + double r1= abs((pt1[0] - pt2[0]) / 2.0); + double r2= abs((pt1[1] - pt2[1]) / 2.0); + + double r2_1 = pow(r1, 2.0); + double r2_2 = pow(r2, 2.0); + long long sliceStride = dims[0] * dims[1]; + int pixelStride = 1; + int rowStride = dims[0]; + + pointer += sliceStride * (sliceNumber); + + double maxX = std::max({pt1[0], pt2[0]}); + double minX = std::min({pt1[0], pt2[0]}); + double maxY = std::max({pt1[1], pt2[1]}); + double minY = std::min({pt1[1], pt2[1]}); + double maxZ = 0.5; + double minZ = 0.5; + + #pragma omp parallel for num_threads(4) + for (int i = 0; i < dims[1]; i++) + { + T *row_pointer = pointer + i * rowStride; + double posY = i * spacings[1]; + if (posY < minY) + continue; + if (posY > maxY) + break; + for (size_t j = 0; j < dims[0]; j++) + { + double posX = j * spacings[0]; + if (posX < minX) + continue; + if (posX > maxX) + break; + if (pow((posX - CenterX), 2.0) / r2_1 + pow((posY - CenterY), 2.0) / r2_2 <= 1) + { + calcLoopContent(count, row_pointer, j, pixelStride, max, min, avg, sq2); + } + } + } + double area = vtkMath::Pi()*r1*r2; + + makeMessage(count, avg, sq2, max, min, message, area>1000?area/100:area,area>100?mUnitcm2:mUnitmm2); + } + else if (abs(normals[1])>0.5) + { + int CenterX = (pt1[0] + pt2[0]) / 2.0; + int CenterZ = (pt1[2] + pt2[2]) / 2.0; + + double r1= abs((pt1[0] - pt2[0]) / 2.0); + double r2= abs((pt1[2] - pt2[2]) / 2.0); + + double r2_1 = pow(r1, 2.0); + double r2_2 = pow(r2, 2.0); + + long long sliceStride = dims[0]; + int pixelStride = 1; + int rowStride = dims[0] * dims[1]; + pointer += sliceStride * (sliceNumber); + + double maxX = std::max({pt1[0], pt2[0]}); + double minX = std::min({pt1[0], pt2[0]}); + double maxY = 0.5; + double minY = 0.5; + double maxZ = std::max({pt1[2], pt2[2]}); + double minZ = std::min({pt1[2], pt2[2]}); + + #pragma omp parallel for num_threads(4) + for (int i = 0; i < dims[2]; i++) + { + T* row_pointer = pointer + i * rowStride; + double posZ = i * spacings[2]; + if (posZ < minZ) + continue; + if (posZ > maxZ) + break; + for (size_t j = 0; j < dims[0]; j++) + { + double posX = j * spacings[0]; + if (posX < minX) + continue; + if (posX > maxX) + break; + if (pow((posX - CenterX), 2.0) / r2_1 + pow((posZ - CenterZ), 2.0) / r2_2 <= 1) + { + calcLoopContent(count, row_pointer, j, pixelStride, max, min, avg, sq2); + } + } + } + double area = vtkMath::Pi()*r1*r2; + makeMessage(count, avg, sq2, max, min, message, area>1000?area/100:area,area>100?mUnitcm2:mUnitmm2); + } + else{ + int CenterY = (pt1[1] + pt2[1]) / 2.0; + int CenterZ = (pt1[2] + pt2[2]) / 2.0; + + double r1= abs((pt1[1] - pt2[1]) / 2.0); + double r2= abs((pt1[2] - pt2[2]) / 2.0); + + double r2_1 = pow((pt1[1] - pt2[1]) / 2.0,2.0); + double r2_2 = pow((pt1[2] - pt2[2]) / 2.0, 2.0); + long long sliceStride = 1; + int pixelStride = dims[0]; + int rowStride = dims[0]*dims[1]; + + pointer += sliceStride*(sliceNumber); + + double maxY = std::max({pt1[1], pt2[1]}); + double minY = std::min({pt1[1], pt2[1]}); + double maxZ = std::max({pt1[2], pt2[2]}); + double minZ = std::min({pt1[2], pt2[2]}); + + #pragma omp parallel for num_threads(4) + for (int i = 0; i < dims[2]; i++) + { + T *row_pointer = pointer + i * rowStride; + double posZ = i * spacings[2]; + if (posZ < minZ) + continue; + if (posZ > maxZ) + break; + for (size_t j = 0; j < dims[1]; j++) + { + double posY = j * spacings[1]; + if (posY < minY) + continue; + if (posY > maxY) + break; + if (pow((posY - CenterY), 2.0) / r2_1 + pow((posZ - CenterZ), 2.0) / r2_2 <= 1) + { + calcLoopContent(count, row_pointer, j, pixelStride, max, min, avg, sq2); + } + } + } + double area = vtkMath::Pi()*r1*r2; + makeMessage(count, avg, sq2, max, min, message, area>1000?area/100:area,area>100?mUnitcm2:mUnitmm2); + } +} + + +void EllipseAnnotationActor::analyzePixel(double *p1, double *p2) +{ + if (!Renderer) return; + auto renderWin = Renderer->GetRenderWindow(); + if (!renderWin)return; + auto interactor = renderWin->GetInteractor(); + if (!interactor)return; + auto style = interactor->GetInteractorStyle(); + if (!style) return; + auto castedStyle = ActorDraggableInteractorStyle::SafeDownCast(style); + if (!castedStyle) return; + + auto imageSlice = castedStyle->GetCurrentImageSlice(); + if (!imageSlice) return; + auto sliceMapper = vtkImageSliceMapper::SafeDownCast(imageSlice->GetMapper()); + if (!sliceMapper) return; + int sliceNumber = sliceMapper->GetSliceNumber(); + vtkImageData* data = imageSlice->GetMapper()->GetInput(); + if (!data) return; + double normals[3]={0}; + Renderer->GetActiveCamera()->GetDirectionOfProjection(normals); + QString message = QCoreApplication::translate("EllipseAnnotationActor","Area:%1 %7, Pixel:%2,\nMax:%3, Min:%4,\nAvg:%5, SD:%6"); + switch (data->GetScalarType()) + { + vtkTemplateMacro(vtkValueCalcTemplate(data, sliceNumber, p1, p2, normals, message)); + default: + break; + } + vtkTextMapper::SafeDownCast(text->GetMapper())->SetInput(message.toUtf8().constData()); } void EllipseAnnotationActor::selfDragCb(vtkObject *, unsigned long, void *data) { @@ -220,12 +538,12 @@ void EllipseAnnotationActor::selfDragCb(vtkObject *, unsigned long, void *data) } void EllipseAnnotationActor::controlPointsTransform(float x, float y) { - //no need to trigger renderpoint repaint DraggableActor::SafeDownCast(controlP_lt)->Transform(x, y); DraggableActor::SafeDownCast(controlP_rt)->Transform(x, y); DraggableActor::SafeDownCast(controlP_lb)->Transform(x, y); DraggableActor::SafeDownCast(controlP_rb)->Transform(x, y); + BaseDataPoints->Modified(); } void EllipseAnnotationActor::controlPointsApplyTransform() { @@ -242,34 +560,37 @@ void EllipseAnnotationActor::onMeasureMouseMove(vtkRenderWindowInteractor *iren) int y = iren->GetEventPosition()[1]; vtkRenderer *renderer = iren->FindPokedRenderer(x, y); if (!renderer) return; - renderer->SetDisplayPoint(x, y, 0.0); - renderer->DisplayToWorld(); - - double *p_lt = controlP_lt->GetWorldPosition(); - double *p_rb = renderer->GetWorldPoint(); - + double p_rb[3] = {0, 0, 0}; + double displayp_rb[3] = {x+0.5, y+0.5, 0}; + double* p_lt = controlP_lt->GetWorldPosition(); + MapScreenPointToWorld(x+0.5, y+0.5, renderer, p_rb); + renderer->SetWorldPoint(p_lt); + renderer->WorldToDisplay(); + double displayp_lt[3] ={0}; + renderer->GetDisplayPoint(displayp_lt); //if ctrl key is pressed ,draw a standard circle if (iren->GetControlKey()) { - double xlen = p_rb[0] - p_lt[0]; - double ylen = p_rb[1] - p_lt[1]; + double xlen = x - displayp_lt[0]; + double ylen = y - displayp_lt[1]; if (abs(ylen) < abs(xlen)) { - if (xlen > 0) { p_rb[0] = p_lt[0] + abs(ylen); } - if (xlen < 0) { p_rb[0] = p_lt[0] - abs(ylen); } + if (xlen > 0) { displayp_rb[0] = displayp_lt[0] + abs(ylen); } + if (xlen < 0) { displayp_rb[0] = displayp_lt[0] - abs(ylen); } } else { - if (ylen > 0) { p_rb[1] = p_lt[1] + abs(xlen); } - if (ylen < 0) { p_rb[1] = p_lt[1] - abs(xlen); } + if (ylen > 0) { displayp_rb[1] = displayp_lt[1] + abs(xlen); } + if (ylen < 0) { displayp_rb[1] = displayp_lt[1] - abs(xlen); } } - + MapScreenPointToWorld(displayp_rb[0], displayp_rb[1], renderer, p_rb); } - double p_rt[3] = {p_rb[0], p_lt[1], 0}; - double p_lb[3] = {p_lt[0], p_rb[1], 0}; + double p_rt[3] = {0}; + MapScreenPointToWorld(displayp_rb[0], displayp_lt[1], renderer, p_rt); + double p_lb[3] = {0}; + MapScreenPointToWorld(displayp_lt[0], displayp_rb[1], renderer, p_lb); controlP_rt->SetWorldPosition(p_rt); controlP_lb->SetWorldPosition(p_lb); controlP_rb->SetWorldPosition(p_rb); - drawCircle(p_lt, p_rb); //this->SetWorldPosition2(p); iren->Render(); @@ -278,20 +599,21 @@ void EllipseAnnotationActor::onMeasureMouseMove(vtkRenderWindowInteractor *iren) bool EllipseAnnotationActor::onMeasureLeftButtonDown(vtkRenderWindowInteractor *iren) { int x = iren->GetEventPosition()[0]; int y = iren->GetEventPosition()[1]; + vtkRenderer *renderer = iren->FindPokedRenderer(x, y); if (!renderer) return false; - renderer->SetDisplayPoint(x, y, 0.0); - renderer->DisplayToWorld(); - double *p = renderer->GetWorldPoint(); - controlP_lt->SetWorldPosition(p); + double result[3] = {0, 0, 0}; + MapScreenPointToWorld(x+0.5, y+0.5, renderer, result); + printf("lt:%d,%d,world lt:%f.2,%f.2,%f.2\r\n", x,y, result[0], result[1], result[2]); + controlP_lt->SetWorldPosition(result); controlP_lt->Highlight(0); - controlP_rt->SetWorldPosition(p); - controlP_lb->SetWorldPosition(p); - controlP_rb->SetWorldPosition(p); + controlP_rt->SetWorldPosition(result); + controlP_lb->SetWorldPosition(result); + controlP_rb->SetWorldPosition(result); for (int id = 0; id <= GRANULARITY; id++) { - BaseDataPoints->SetPoint(id, p[0], p[1], p[2]); + BaseDataPoints->SetPoint(id, result[0], result[1], result[2]); } this->SetRenderer(renderer); iren->Render(); @@ -312,19 +634,3 @@ void EllipseAnnotationActor::ApplyTransform() { this->controlPointsApplyTransform(); } -//void EllipseAnnotationActor::TransformOnly(float x, float y) { -// if (!tempStorePoints) { -// tempStorePoints = vtkPoints::New(); -// tempStorePoints->DeepCopy(renderPoints); -// transforming = true; -// } -// vtkNew trans; -// trans->Translate(x, y, 0); -// vtkNew filter; -// filter->SetTransform(trans); -// vtkNew poly; -// poly->SetPoints(tempStorePoints); -// filter->SetInputData(poly); -// filter->Update(); -// renderPoints->DeepCopy(filter->GetPolyDataOutput()->GetPoints()); -//} diff --git a/src/src/Rendering/Measure/EllipseAnnotationActor.h b/src/src/Rendering/Measure/EllipseAnnotationActor.h index 7e41f9f..f6d6da8 100644 --- a/src/src/Rendering/Measure/EllipseAnnotationActor.h +++ b/src/src/Rendering/Measure/EllipseAnnotationActor.h @@ -9,6 +9,8 @@ class vtkTextProperty; class ControlPointActor; +class vtkImageData; + class EllipseAnnotationActor : public DraggableActor, public Measure { public: //@{ @@ -56,11 +58,17 @@ private: void drawCircle(double *p1, double *p2); + void analyzePixel(double *p1, double *p2); + vtkTextProperty *textProperty; QString mUnitmm2 ; QString mUnitcm2 ; QString mAreaName; + + QString mRenderTime; + + template + void vtkValueCalcTemplate(vtkImageData *image, int sliceNumber, double *pt1, double *pt2, double *normals, QString &message); }; - #endif //OMEGAV_EllipseANNOTATIONACTOR_H diff --git a/src/src/Rendering/Measure/MeasureFactory.cpp b/src/src/Rendering/Measure/MeasureFactory.cpp index 6375e1f..4a6ec7f 100644 --- a/src/src/Rendering/Measure/MeasureFactory.cpp +++ b/src/src/Rendering/Measure/MeasureFactory.cpp @@ -7,6 +7,8 @@ #include "TextAnnotationActor.h" #include "ArrowAnnotationActor.h" #include "EllipseAnnotationActor.h" +#include "RoundAnnotationActor.h" + Measure *MeasureFactory::getMeasure(AnnotationActorType type) { switch (type) { @@ -25,6 +27,8 @@ Measure *MeasureFactory::getMeasure(AnnotationActorType type) { return ArrowAnnotationActor::New(); case AnnotationActorType::EllipseAnn: return EllipseAnnotationActor::New(); + case AnnotationActorType::DiameterAnn: + return RoundAnnotationActor::New(); case AnnotationActorType::TextAnn: return TextAnnotationActor::New(); default: diff --git a/src/src/Rendering/Measure/OpenPolyAnnotationActor.cpp b/src/src/Rendering/Measure/OpenPolyAnnotationActor.cpp index b6dccf5..11ff37b 100644 --- a/src/src/Rendering/Measure/OpenPolyAnnotationActor.cpp +++ b/src/src/Rendering/Measure/OpenPolyAnnotationActor.cpp @@ -192,7 +192,7 @@ void OpenPolyAnnotationActor::UpdatePerimeterAndAreaText() if(!Closed) { bool unitFlag = distance>100; - QString textValue = QString("%1: %2 %3").arg(mDistanceName) + QString textValue = QString("%1: %2%3").arg(mDistanceName) .arg(unitFlag?distance/10:distance,0,'f',2).arg(unitFlag?mUnitcm:mUnitmm); vtkTextMapper::SafeDownCast(text->GetMapper())->SetInput(textValue.toUtf8().constData()); text->SetVisibility(1); @@ -206,7 +206,7 @@ void OpenPolyAnnotationActor::UpdatePerimeterAndAreaText() bool unitFlag1 = distance>100; bool unitFlag2 = area>1000; - QString textValue = QString("%1: %2 %3, %4: %5 %6").arg(mDistanceName) + QString textValue = QString("%1: %2%3, %4: %5%6").arg(mDistanceName) .arg(unitFlag1?distance/10:distance,0,'f',2).arg(unitFlag1?mUnitcm:mUnitmm) .arg(mAreaName) .arg(unitFlag2?area/100:area,0,'f',2).arg(unitFlag2?mUnitcm2:mUnitmm2); @@ -220,3 +220,7 @@ void OpenPolyAnnotationActor::UpdatePerimeterAndAreaText() bool OpenPolyAnnotationActor::Valid() { return Closed == 0 ? BaseDataPoints->GetNumberOfPoints() > 1 : BaseDataPoints->GetNumberOfPoints() > 2; } + +void OpenPolyAnnotationActor::onTerminate(vtkRenderWindowInteractor *){ + +} diff --git a/src/src/Rendering/Measure/OpenPolyAnnotationActor.h b/src/src/Rendering/Measure/OpenPolyAnnotationActor.h index a960cab..fdaa261 100644 --- a/src/src/Rendering/Measure/OpenPolyAnnotationActor.h +++ b/src/src/Rendering/Measure/OpenPolyAnnotationActor.h @@ -38,6 +38,8 @@ public: bool onMeasureLeftButtonUp(vtkRenderWindowInteractor *) override; + void onTerminate(vtkRenderWindowInteractor *) override; + Measure *GetNextMeasure() override { auto m = OpenPolyAnnotationActor::New(); diff --git a/src/src/Rendering/Measure/RoundAnnotationActor.cpp b/src/src/Rendering/Measure/RoundAnnotationActor.cpp new file mode 100644 index 0000000..e404611 --- /dev/null +++ b/src/src/Rendering/Measure/RoundAnnotationActor.cpp @@ -0,0 +1,411 @@ + +#include "RoundAnnotationActor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include "omp.h" + +#include "Interaction/ActorDraggableInteractorStyle.h" +#include "Rendering/Core/ControlPointActor.h" +#include "Common/QGlobals.h" + + +vtkStandardNewMacro(RoundAnnotationActor) + +namespace { + const int GRANULARITY = 60; +} + +void RoundAnnotationActor::BuildShape() { + if (!BaseDataPoints->GetNumberOfPoints()) return; + QString stamp = QString("%1-%2").arg(BaseDataPoints->GetMTime()) + .arg(Renderer->GetActiveCamera()->GetMTime()); + if (mRenderTime == stamp) return; + QStringList stamps = stamp.split("-"); + QStringList renderTimes = mRenderTime.split("-"); + mRenderTime = stamp; + RebuildRenderPoint(); + //base data change change message + if (stamps[0] != renderTimes[0]) + { double p_lt[3] = {0, 0, 0}; + double* disp_lt = controlP_lt->GetRenderPosition(); + MapScreenPointToWorld(disp_lt[0], disp_lt[1], this->Renderer, p_lt); + double p_rb[3] = {0, 0, 0}; + double* disp_rb = controlP_rb->GetRenderPosition(); + MapScreenPointToWorld(disp_rb[0], disp_rb[1], this->Renderer, p_rb); + // analyzePixel(p_lt,p_rb); + } + double *rp = renderPoints->GetPoint(0); + + text->SetDisplayPosition(rp[0] + 10, rp[1] - 20); + + vtkNew source; + source->SetPoints(renderPoints); + source->Update(); + renderData->DeepCopy(source->GetOutput()); + + if (Measure::Hidden) { + this->Hide(); + controlP_rt->Hide(); + controlP_lb->Hide(); + controlP_rb->Hide(); + controlP_lt->Hide(); + + } else { + this->Show(); + controlP_rt->Show(); + controlP_lb->Show(); + controlP_rb->Show(); + controlP_lt->Show(); + } +} + +RoundAnnotationActor::RoundAnnotationActor() { + controlP_rt = ControlPointActor::New(); + controlP_lb = ControlPointActor::New(); + controlP_rb = ControlPointActor::New(); + controlP_lt = ControlPointActor::New(); + + controlP_lt->SetWorldPosition(0, 0, 0); + controlP_rt->SetWorldPosition(512, 0, 0); + controlP_lb->SetWorldPosition(0, 512, 0); + controlP_rb->SetWorldPosition(512, 512, 0); + + BaseDataPoints->SetNumberOfPoints(GRANULARITY + 1); + //BaseDataPoints->SetPoint(0, 0, 0, 0); + //BaseDataPoints->SetPoint(1, 512, 512, 0); + + renderPoints->SetNumberOfPoints(GRANULARITY + 1); + BaseDataPoints->Modified(); + controlP_lt->AddObserver(DraggableActorEvents::DragEvent, this, &RoundAnnotationActor::controlPointCb); + controlP_rt->AddObserver(DraggableActorEvents::DragEvent, this, &RoundAnnotationActor::controlPointCb); + controlP_lb->AddObserver(DraggableActorEvents::DragEvent, this, &RoundAnnotationActor::controlPointCb); + controlP_rb->AddObserver(DraggableActorEvents::DragEvent, this, &RoundAnnotationActor::controlPointCb); + + controlP_lt->SetcontrolledActor(this); + controlP_rt->SetcontrolledActor(this); + controlP_lb->SetcontrolledActor(this); + controlP_rb->SetcontrolledActor(this); + + //this->AddObserver(DraggableActorEvents::DragEvent, this, &RoundAnnotationActor::selfDragCb); + + text = vtkActor2D::New(); + vtkNew textMapper; + textMapper->SetInput("0"); + text->SetMapper(textMapper); + textProperty = textMapper->GetTextProperty(); + if (LanguageHelper::getLanguage() == ChineseSimple && QFile::exists(FONT_FILE_PATH)) + { + textProperty->SetFontFamily(VTK_FONT_FILE); + textProperty->SetFontFile(FONT_FILE_PATH); + } + else{ + textProperty->SetFontFamilyToArial(); + textProperty->SetBold(1); + } + textProperty->SetFontSize(16); + textProperty->SetColor(0.8, 0.8, 0.0); + textProperty->SetOpacity(0.75); + textProperty->SetFrame(false); + //textProperty->SetFrameColor(1.0,0.0,0.0); + textProperty->SetBackgroundColor(1.0, 0.0, 0.0); + textProperty->SetBackgroundOpacity(0.3); + + mUnitmm2 = QCoreApplication::translate("RoundAnnotationActor","mm²"); + mUnitcm2 = QCoreApplication::translate("RoundAnnotationActor","cm²"); + mAreaName = QCoreApplication::translate("RoundAnnotationActor", "Area"); + mUnitcm3 = QCoreApplication::translate("RoundAnnotationActor","cm³"); + mUnitmm3 = QCoreApplication::translate("RoundAnnotationActor","mm³"); + mVolumeName = QCoreApplication::translate("RoundAnnotationActor", "Volume"); +} + +RoundAnnotationActor::~RoundAnnotationActor() { + controlP_lt->Delete(); + controlP_rt->Delete(); + controlP_lb->Delete(); + controlP_rb->Delete(); + + controlP_lt = nullptr; + controlP_rt = nullptr; + controlP_lb = nullptr; + controlP_rb = nullptr; +} + +void RoundAnnotationActor::SetRenderer(vtkRenderer *ren) { + DraggableActor::SetRenderer(ren); + controlP_lt->SetRenderer(ren); + controlP_rt->SetRenderer(ren); + controlP_lb->SetRenderer(ren); + controlP_rb->SetRenderer(ren); +} + +void RoundAnnotationActor::controlPointCb(vtkObject *sender, unsigned long event, void *data) { + if (transforming) return; + vtkPoints *pts = static_cast(data); + double *pos = pts->GetPoint(0); + double displayP[3] = {pos[0], pos[1], 0}; + double result[3] = {0, 0, 0}; + MapScreenPointToWorld(pos[0]+0.5, pos[1]+0.5, this->Renderer, result); + double * dp = Renderer->GetActiveCamera()->GetDirectionOfProjection(); + + int nIndex[2] = {0,1}; + if (abs(dp[1])>0.0){ + nIndex[1] = 2; + } + if (abs(dp[0])>0.0){ + nIndex[0] = 1; + nIndex[1] = 2; + } + double *p1; + double *p2; + double displaypos[3]={0}; + + if (sender == controlP_lt ) { + p1 = result; + p2 = controlP_rb->GetWorldPosition(); + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayP2 = displaypos; + drawCircle(p1, p2); + double d = std::min({abs(displayP[0] - displayP2[0]), abs(displayP[1] - displayP2[1])}); + pos[0] = displayP[0]>displayP2[0]?displayP2[0]+d:displayP2[0]-d; + pos[1] = displayP[1]>displayP2[1]?displayP2[1]+d:displayP2[1]-d; + pts->SetPoint(0, pos); + double p_rt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayP2[0], pos[1], Renderer,p_rt); + double p_lb[3] = {0, 0, 0}; + MapScreenPointToWorld(pos[0], displayP2[1], Renderer,p_lb); + controlP_rt->SetWorldPosition(p_rt); + controlP_lb->SetWorldPosition(p_lb); + } else if (sender == controlP_rt ) { + p1 = result; + p2 = controlP_lb->GetWorldPosition(); + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayP2 = displaypos; + drawCircle(p1, p2); + double d = std::min({abs(displayP[0] - displayP2[0]), abs(displayP[1] - displayP2[1])}); + pos[0] = displayP[0]>displayP2[0]?displayP2[0]+d:displayP2[0]-d; + pos[1] = displayP[1]>displayP2[1]?displayP2[1]+d:displayP2[1]-d; + pts->SetPoint(0, pos); + double p_lt[3] = {0, 0, 0}; + MapScreenPointToWorld(displayP2[0], pos[1], Renderer,p_lt); + double p_rb[3] = {0, 0, 0}; + MapScreenPointToWorld( pos[0], displayP2[1], Renderer,p_rb); + controlP_lt->SetWorldPosition(p_lt); + controlP_rb->SetWorldPosition(p_rb); + } else if (sender == controlP_lb ) { + p1 = result; + p2 = controlP_rt->GetWorldPosition(); + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayP2 = displaypos; + drawCircle(p1, p2); + double d = std::min({abs(displayP[0] - displayP2[0]), abs(displayP[1] - displayP2[1])}); + pos[0] = displayP[0]>displayP2[0]?displayP2[0]+d:displayP2[0]-d; + pos[1] = displayP[1]>displayP2[1]?displayP2[1]+d:displayP2[1]-d; + pts->SetPoint(0, pos); + + double p_lt[3] = {0, 0, 0}; + MapScreenPointToWorld(pos[0],displayP2[1], Renderer,p_lt); + double p_rb[3] = {0, 0, 0}; + MapScreenPointToWorld( displayP2[0], pos[1], Renderer,p_rb); + controlP_lt->SetWorldPosition(p_lt); + controlP_rb->SetWorldPosition(p_rb); + } else if (sender == controlP_rb ) { + p1 = result; + p2 = controlP_lt->GetWorldPosition(); + Renderer->SetWorldPoint(p2); + Renderer->WorldToDisplay(); + Renderer->GetDisplayPoint(displaypos); + double *displayP2 = displaypos; + drawCircle(p1, p2); + double d = std::min({abs(displayP[0] - displayP2[0]), abs(displayP[1] - displayP2[1])}); + pos[0] = displayP[0]>displayP2[0]?displayP2[0]+d:displayP2[0]-d; + pos[1] = displayP[1]>displayP2[1]?displayP2[1]+d:displayP2[1]-d; + pts->SetPoint(0, pos); + double p_rt[3] = {0, 0, 0}; + MapScreenPointToWorld(pos[0], displayP2[1], Renderer,p_rt); + double p_lb[3] = {0, 0, 0}; + MapScreenPointToWorld(displayP2[0], pos[1], Renderer,p_lb); + controlP_rt->SetWorldPosition(p_rt); + controlP_lb->SetWorldPosition(p_lb); + } else { + + } + +} + + +void RoundAnnotationActor::drawCircle(double *p1, double *p2) { + double * dp = Renderer->GetActiveCamera()->GetDirectionOfProjection(); + + int nIndex[2] = {0,1}; + if (abs(dp[1])>0.0){ + nIndex[1] = 2; + } + if (abs(dp[0])>0.0){ + nIndex[0] = 1; + nIndex[1] = 2; + } + double r1 = round(abs(p1[nIndex[0]] - p2[nIndex[0]]) / 2.0*100.0)/100; + double r2 = round(abs(p1[nIndex[1]]- p2[nIndex[1]]) / 2.0*100.0)/100; + double r = r1r){ + CenterX = CenterX > p2[nIndex[0]]?(r+p2[nIndex[0]]):(p2[nIndex[0]]-r); + } + int CenterY = (p1[nIndex[1]] + p2[nIndex[1]] ) / 2.0; + if (abs(CenterY-p2[nIndex[1]])>r){ + CenterY = CenterY > p2[nIndex[1]]?(r+p2[nIndex[1]]):(p2[nIndex[1]]-r); + } + + double angle = 0; + int id = 0; + while (angle <= 2.0 * vtkMath::Pi() + (2.0 * vtkMath::Pi() / (GRANULARITY * 1.0))) { + double p[3]={0}; + p[0] = p1[0]; + p[1] = p1[1]; + p[2] = p1[2]; + p[nIndex[0]] = r * cos(angle) + CenterX; + p[nIndex[1]] = r * sin(angle) + CenterY; + + BaseDataPoints->SetPoint(id, p); + angle = angle + (2.0 * vtkMath::Pi() / (GRANULARITY * 1.0)); + id++; + } + BaseDataPoints->Modified(); + double area = vtkMath::Pi()*r*r; + double volume = area*3*r/4; + QString textValue = QString("%1: %2%3\r\n%4: %5%6").arg(mAreaName) + .arg(area>1000?area/100:area,0,'f',2).arg(area>1000?mUnitcm2:mUnitmm2) + .arg(mVolumeName) + .arg(volume>10000?volume/1000:volume,0,'f',2).arg(volume>10000?mUnitcm3:mUnitmm3); + vtkTextMapper::SafeDownCast(text->GetMapper())->SetInput(textValue.toUtf8().constData()); +} + +void RoundAnnotationActor::selfDragCb(vtkObject *, unsigned long, void *data) { +//control point drag realized by father +} + +void RoundAnnotationActor::controlPointsTransform(float x, float y) { + //no need to trigger renderpoint repaint + DraggableActor::SafeDownCast(controlP_lt)->Transform(x, y); + DraggableActor::SafeDownCast(controlP_rt)->Transform(x, y); + DraggableActor::SafeDownCast(controlP_lb)->Transform(x, y); + DraggableActor::SafeDownCast(controlP_rb)->Transform(x, y); + BaseDataPoints->Modified(); +} + +void RoundAnnotationActor::controlPointsApplyTransform() { + //restore base point + DraggableActor::SafeDownCast(controlP_lt)->ApplyTransform(); + DraggableActor::SafeDownCast(controlP_rt)->ApplyTransform(); + DraggableActor::SafeDownCast(controlP_lb)->ApplyTransform(); + DraggableActor::SafeDownCast(controlP_rb)->ApplyTransform(); +} + + +void RoundAnnotationActor::onMeasureMouseMove(vtkRenderWindowInteractor *iren) { + int x = iren->GetEventPosition()[0]; + int y = iren->GetEventPosition()[1]; + vtkRenderer *renderer = iren->FindPokedRenderer(x, y); + if (!renderer) return; + double p_rb[3] = {0, 0, 0}; + double displayp_rb[3] = {x+0.5, y+0.5, 0}; + double* p_lt = controlP_lt->GetWorldPosition(); + MapScreenPointToWorld(x+0.5, y+0.5, renderer, p_rb); + renderer->SetWorldPoint(p_lt); + renderer->WorldToDisplay(); + double displayp_lt[3] ={0}; + renderer->GetDisplayPoint(displayp_lt); + + double xlen = x - displayp_lt[0]; + double ylen = y - displayp_lt[1]; + if (abs(ylen) < abs(xlen)) { + if (xlen > 0) { displayp_rb[0] = displayp_lt[0] + abs(ylen); } + if (xlen < 0) { displayp_rb[0] = displayp_lt[0] - abs(ylen); } + } else { + if (ylen > 0) { displayp_rb[1] = displayp_lt[1] + abs(xlen); } + if (ylen < 0) { displayp_rb[1] = displayp_lt[1] - abs(xlen); } + } + MapScreenPointToWorld(displayp_rb[0], displayp_rb[1], renderer, p_rb); + + double p_rt[3] = {0}; + MapScreenPointToWorld(displayp_rb[0], displayp_lt[1], renderer, p_rt); + double p_lb[3] = {0}; + MapScreenPointToWorld(displayp_lt[0], displayp_rb[1], renderer, p_lb); + + controlP_rt->SetWorldPosition(p_rt); + controlP_lb->SetWorldPosition(p_lb); + controlP_rb->SetWorldPosition(p_rb); + + drawCircle(p_lt, p_rb); + //this->SetWorldPosition2(p); + iren->Render(); +} + +bool RoundAnnotationActor::onMeasureLeftButtonDown(vtkRenderWindowInteractor *iren) { + int x = iren->GetEventPosition()[0]; + int y = iren->GetEventPosition()[1]; + + vtkRenderer *renderer = iren->FindPokedRenderer(x, y); + if (!renderer) return false; + double result[3] = {0, 0, 0}; + MapScreenPointToWorld(x+0.5, y+0.5, renderer, result); + printf("lt:%d,%d,world lt:%f.2,%f.2,%f.2\r\n", x,y, result[0], result[1], result[2]); + controlP_lt->SetWorldPosition(result); + controlP_lt->Highlight(0); + + controlP_rt->SetWorldPosition(result); + controlP_lb->SetWorldPosition(result); + controlP_rb->SetWorldPosition(result); + + for (int id = 0; id <= GRANULARITY; id++) { + BaseDataPoints->SetPoint(id, result[0], result[1], result[2]); + } + this->SetRenderer(renderer); + iren->Render(); + return true; +} + +bool RoundAnnotationActor::onMeasureLeftButtonUp(vtkRenderWindowInteractor *iren) { + return false; +} + +void RoundAnnotationActor::Transform(float x, float y) { + DraggableActor::Transform(x, y); + this->controlPointsTransform(x, y); +} + +void RoundAnnotationActor::ApplyTransform() { + DraggableActor::ApplyTransform(); + this->controlPointsApplyTransform(); +} + diff --git a/src/src/Rendering/Measure/RoundAnnotationActor.h b/src/src/Rendering/Measure/RoundAnnotationActor.h new file mode 100644 index 0000000..e95e3e3 --- /dev/null +++ b/src/src/Rendering/Measure/RoundAnnotationActor.h @@ -0,0 +1,70 @@ +#ifndef OMEGAV_ROUNDANNOTATIONACTOR_H +#define OMEGAV_ROUNDANNOTATIONACTOR_H + +#include "Rendering/Core/DraggableActor.h" +#include "Rendering/Measure/Measure.h" + +class vtkTextProperty; + +class ControlPointActor; + +class RoundAnnotationActor : public DraggableActor, public Measure { +public: + //@{ + /** + * Standard methods for instances of this class. + */ + static RoundAnnotationActor *New(); + + vtkTypeMacro(RoundAnnotationActor, DraggableActor); + + //@} + void controlPointsTransform(float x, float y); + + void controlPointsApplyTransform(); + + void SetRenderer(vtkRenderer *ren) override; + + void BuildShape() override; + + void Transform(float x, float y) override; + + void ApplyTransform() override; + + bool onMeasureLeftButtonDown(vtkRenderWindowInteractor *) override; + + void onMeasureMouseMove(vtkRenderWindowInteractor *) override; + + bool onMeasureLeftButtonUp(vtkRenderWindowInteractor *) override; + + NextMeasureMacro(RoundAnnotationActor); +protected: + RoundAnnotationActor(); + + ~RoundAnnotationActor() override; + +private: + ControlPointActor *controlP_rt = nullptr; + ControlPointActor *controlP_lb = nullptr; + ControlPointActor *controlP_rb = nullptr; + ControlPointActor *controlP_lt = nullptr; + + void selfDragCb(vtkObject *, unsigned long event, void *data); + + void controlPointCb(vtkObject *sender, unsigned long event, void *data); + + void drawCircle(double *p1, double *p2); + + // void analyzePixel(double *p1, double *p2); + + vtkTextProperty *textProperty; + QString mUnitmm2 ; + QString mUnitcm2 ; + QString mUnitmm3 ; + QString mUnitcm3 ; + QString mAreaName; + QString mVolumeName; + QString mRenderTime; +}; + +#endif /* OMEGAV_ROUNDANNOTATIONACTOR_H */ diff --git a/src/src/UI/Widget/ToolBar/DefaultToolBar.cpp b/src/src/UI/Widget/ToolBar/DefaultToolBar.cpp index 7fd9fff..38b065e 100644 --- a/src/src/UI/Widget/ToolBar/DefaultToolBar.cpp +++ b/src/src/UI/Widget/ToolBar/DefaultToolBar.cpp @@ -17,7 +17,7 @@ namespace { const char *SYNC_MANUAL_URL = ":/InfiniteViewer/Icon/sync/sync_manual.png"; const char *SYNC_AUTO_URL = ":/InfiniteViewer/Icon/sync/sync_auto.png"; const char *SYNC_DIS_URL = ":/InfiniteViewer/Icon/sync/sync_dis.png"; - const int ACTION_COUNT = 7; + const int ACTION_COUNT = 8; const ActionProperty MEASURE_ACTIIONS[ACTION_COUNT] = { { ":/InfiniteViewer/Icon/distance.png", AnnotationActorType::RulerAnn}, { ":/InfiniteViewer/Icon/angle.png", AnnotationActorType::AngleAnn}, @@ -25,6 +25,7 @@ namespace { { ":/InfiniteViewer/Icon/polyline.png", AnnotationActorType::OpenPolygonAnn}, { ":/InfiniteViewer/Icon/arrow.png", AnnotationActorType::ArrowAnn}, { ":/InfiniteViewer/Icon/ellipse.png", AnnotationActorType::EllipseAnn}, + { ":/InfiniteViewer/Icon/diameter.png", AnnotationActorType::DiameterAnn}, { ":/InfiniteViewer/Icon/text.png", AnnotationActorType::TextAnn} }; } diff --git a/src/translations/zh_CN.ts b/src/translations/zh_CN.ts index 8af9618..15e0617 100644 --- a/src/translations/zh_CN.ts +++ b/src/translations/zh_CN.ts @@ -500,6 +500,15 @@ Area 面积 + + + Area:%1 %7, Pixel:%2, +Max:%3, Min:%4, +Avg:%5, SD:%6 + 面积:%1 %7, 像素:%2, +最大值:%3, 最小值:%4, +平均值:%5, 标准差:%6 + ExportDialog @@ -1037,6 +1046,21 @@ Area 面积 + + + cm³ + 立方厘米 + + + + mm³ + 立方毫米 + + + + Volume + 体积 + RulerAnnotationActor