/*========================================================================= medInria Copyright (c) INRIA 2013 - 2018. All rights reserved. See LICENSE.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =========================================================================*/ #include "vtkImageViewCornerAnnotation.h" #include "vtkImageView2D.h" #include "medVtkImageInfo.h" #include #include #include #include #include #include #include #include #include #include #include vtkStandardNewMacro(vtkImageViewCornerAnnotation) vtkImageViewCornerAnnotation::vtkImageViewCornerAnnotation() { this->ImageView = nullptr; this->view2d = nullptr; } vtkImageViewCornerAnnotation::~vtkImageViewCornerAnnotation() = default; void vtkImageViewCornerAnnotation::SetImageView(vtkImageView* arg) { this->ImageView = arg; } void vtkImageViewCornerAnnotation::TextReplace(vtkImageActor *imageActor, vtkImageMapToWindowLevelColors */*wl*/) { int slice = 0, slice_max = 0; double window = 0, level = 0; long int windowi = 0, leveli = 0; vtkImageData *ia_input = nullptr; medVtkImageInfo *wl_input = nullptr; vtkCamera *cam = nullptr; bool input_type_is_float = 0; int size_x = -1, size_y = -1; double spacing_x = 0.0, spacing_y = 0.0; double pos[3] = {0.0, 0.0, 0.0}; double pos_x = 0.0, pos_y = 0.0, pos_z = 0.0; int coord[3] = {0, 0, 0}; int coord_x = 0, coord_y = 0; double value = 0.0, zoom = 100.0; if (!view2d) { this->view2d = vtkImageView2D::SafeDownCast (this->ImageView); } if (this->ImageView) { this->ImageView->GetCursorPosition (pos); this->ImageView->GetImageCoordinatesFromWorldCoordinates (pos, coord); value = this->ImageView->GetValueAtPosition(pos); zoom = this->ImageView->GetZoom()*100.0; window = this->ImageView->GetColorWindow(); window *= this->LevelScale; level = this->ImageView->GetColorLevel(); level = level * this->LevelScale + this->LevelShift; windowi = static_cast(window); leveli = static_cast(level); wl_input = this->ImageView->GetMedVtkImageInfo(); cam = this->ImageView->GetRenderer() ? this->ImageView->GetRenderer()->GetActiveCamera() : nullptr; if (wl_input && wl_input->initialized && cam) { double *viewup = cam->GetViewUp(); double *normal = cam->GetViewPlaneNormal(); double rightvector[3]; vtkMath::Cross (normal, viewup, rightvector); // naively the X and Y axes of the current view // correspond to the rightvector and the viewup respectively. // But in fact we have to put those vectors back in the image // coordinates and see to which xyz image axis they correspond. double Xaxis[4] = {0, 0, 0, 0}; double Yaxis[4] = {0, 0, 0, 0}; for (unsigned int i=0; i<3; i++) { Xaxis[i] = rightvector[i]; Yaxis[i] = viewup[i]; } this->ImageView->GetInvertOrientationMatrix()->MultiplyPoint (Xaxis, Xaxis); this->ImageView->GetInvertOrientationMatrix()->MultiplyPoint (Yaxis, Yaxis); double dotX = 0; double dotY = 0; int idX, idY; idX = idY = 0; for (unsigned int i=0; i<3; i++) { if (dotX <= std::fabs (Xaxis[i])) { dotX = std::fabs (Xaxis[i]); idX = i; } if (dotY <= std::fabs (Yaxis[i])) { dotY = std::fabs (Yaxis[i]); idY = i; } } int sliceOrientation = 0; if (view2d) { sliceOrientation = view2d->GetSliceOrientation(); } input_type_is_float = (wl_input->scalarType == VTK_FLOAT || wl_input->scalarType == VTK_DOUBLE); size_x = wl_input->dimensions[idX]; size_y = wl_input->dimensions[idY]; spacing_x = wl_input->spacing[idX]; spacing_y = wl_input->spacing[idY]; pos_x = pos[idX]; pos_y = pos[idY]; pos_z = pos[sliceOrientation]; coord_x = coord[idX]; coord_y = coord[idY]; } } if (view2d && imageActor) { // Current slice and Slice Max int min = 0, max = 0; view2d->GetSliceRange(min, max); slice = view2d->GetSlice() - min +1; slice_max = max - min + 1; // Slice Orientation & Position int sliceOrientation = view2d->GetSliceOrientation(); coord[sliceOrientation]=slice-1; double position[3]={0.0, 0.0, 0.0}; this->ImageView->GetWorldCoordinatesFromImageCoordinates(coord, position); pos_z = position[sliceOrientation]; ia_input = imageActor->GetInput(); if (!wl_input && ia_input) { input_type_is_float = (ia_input->GetScalarType() == VTK_FLOAT || ia_input->GetScalarType() == VTK_DOUBLE); } } int isKnownAnnotationStyle = -1; if (view2d) { isKnownAnnotationStyle = static_cast(view2d->GetAnnotationStyle2D()); } // Search for tokens, replace and then assign to TextMappers QString replaceText = ""; QString textQ = ""; if (imageActor && this->ShowSliceAndImage && this->ImageView) { //---- osSW if (this->CornerText[0] && strlen(this->CornerText[0])) { textQ = this->CornerText[0]; // Zoom replaceText = " Zoom: " + QString::number(zoom); textQ.replace("", replaceText); // Slice current and max replaceText = " Slice: " + QString::number(slice) + "/" + QString::number(slice_max); textQ.replace("", replaceText); if (isKnownAnnotationStyle == -1 || isKnownAnnotationStyle == vtkImageView2D::AnnotationStyle1) { // Location replaceText = " Location: " + QString::number(pos_z) + " mm"; textQ.replace("", replaceText); } this->TextMapper[0]->SetInput(textQ.toStdString().c_str()); } //---- osSE // No South Est change, it's done in vtkImageView2D::SetAnnotationsFromOrientation() if (this->CornerText[1] && strlen(this->CornerText[1])) { textQ = this->CornerText[1]; this->TextMapper[1]->SetInput(textQ.toStdString().c_str()); } //---- osNW if (this->CornerText[2] && strlen(this->CornerText[2])) { textQ = this->CornerText[2]; // AnnotationStyle1 if (isKnownAnnotationStyle == -1 || isKnownAnnotationStyle == vtkImageView2D::AnnotationStyle1) { // Tag from VTK // Image replaceText = " Image: " + QString::number(slice); textQ.replace("", replaceText); // From this app // Image size x/y replaceText = " Image size: " + QString::number(size_x) + " x " + QString::number(size_y); textQ.replace("", replaceText); // Voxel size x/y replaceText = " Voxel size: " + QString::number(spacing_x) + QString(" mm") + " x " + QString::number(spacing_y) + QString(" mm"); textQ.replace("", replaceText); // Coordinates x/y replaceText = " X: " + QString::number(coord_x) + QString(" px Y: ") + QString::number(coord_y) + QString(" px") + QString(" Value: ") + QString::number(value); textQ.replace("", replaceText); // Position x/y replaceText = " X: " + QString::number(pos_x) + QString(" mm Y: ") + QString::number(pos_y) + QString(" mm"); textQ.replace("", replaceText); // WW/WL if (input_type_is_float) { replaceText = " WW/WL: " + QString::number(window) + QString("/") + QString::number(level); } else { replaceText = " WW/WL: " + QString::number(windowi) + QString("/") + QString::number(leveli); } textQ.replace("", replaceText); } // AnnotationStyle2 if (isKnownAnnotationStyle == -1 || isKnownAnnotationStyle == vtkImageView2D::AnnotationStyle2) { // Image size x/y, almost equal to for AnnotationStyle1 replaceText = " image size: " + QString::number(size_x) + " x " + QString::number(size_y); textQ.replace("", replaceText); // Spacing x/y, almost equal to for AnnotationStyle1 replaceText = " spacing: " + QString::number(spacing_x) + " x " + QString::number(spacing_y) + QString(" mm"); textQ.replace("", replaceText); // Location x/y/z replaceText = " xyz: " + QString::number(pos[0]) + "," + QString::number(pos[1]) + "," + QString::number(pos[2]); textQ.replace("", replaceText); // Value replaceText = " value: " + QString::number(value); textQ.replace("", replaceText); } this->TextMapper[2]->SetInput(textQ.toStdString().c_str()); } //---- osNE if (this->CornerText[3] && strlen(this->CornerText[3])) { textQ = this->CornerText[3]; // AnnotationStyle1 if (isKnownAnnotationStyle == -1 || isKnownAnnotationStyle == vtkImageView2D::AnnotationStyle1) { // Patient Name textQ.replace("", this->ImageView->GetPatientName()); // Study Name textQ.replace("", this->ImageView->GetStudyName()); // Series Name textQ.replace("", this->ImageView->GetSeriesName()); // Slice: current and max for AnnotationStyle2 replaceText = " Slice: " + QString::number(slice) + "/" + QString::number(slice_max); textQ.replace("", replaceText); } // AnnotationStyle2 if (isKnownAnnotationStyle == -1 || isKnownAnnotationStyle == vtkImageView2D::AnnotationStyle2) { // Window if (input_type_is_float) { replaceText = QString::number(window); } else { replaceText = QString::number(windowi); } textQ.replace("", replaceText); // Level if (input_type_is_float) { replaceText = QString::number(level); } else { replaceText = QString::number(leveli); } textQ.replace("", replaceText); } this->TextMapper[3]->SetInput(textQ.toStdString().c_str()); } } else { this->TextMapper[0]->SetInput(""); this->TextMapper[1]->SetInput(""); this->TextMapper[2]->SetInput(""); this->TextMapper[3]->SetInput(""); } } namespace { // Ported from old vtkTextMapper implementation int GetNumberOfLines(const char *str) { if (str == nullptr || *str == '\0') { return 0; } int result = 1; while (str != nullptr) { if ((str = strstr(str, "\n")) != nullptr) { result++; str++; // Skip '\n' } } return result; } } //---------------------------------------------------------------------------- int vtkImageViewCornerAnnotation::RenderOpaqueGeometry(vtkViewport *viewport) { int fontSize; int i; // Check to see whether we have to rebuild everything // If the viewport has changed we may - or may not need // to rebuild, it depends on if the projected coords chage int viewport_size_has_changed = 0; if (viewport->GetMTime() > this->BuildTime || (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime)) { int *vSize = viewport->GetSize(); if (this->LastSize[0] != vSize[0] || this->LastSize[1] != vSize[1]) { viewport_size_has_changed = 1; } } // Is there an image actor ? vtkImageMapToWindowLevelColors *wl = this->WindowLevel; vtkImageActor *ia = nullptr; if (this->ImageActor) { ia = this->ImageActor; } else { vtkPropCollection *pc = viewport->GetViewProps(); int numProps = pc->GetNumberOfItems(); for (i = 0; i < numProps; i++) { ia = vtkImageActor::SafeDownCast(pc->GetItemAsObject(i)); if (ia) { break; } } } vtkImageView *iv = this->ImageView; int tprop_has_changed = (this->TextProperty && this->TextProperty->GetMTime() > this->BuildTime); // Check to see whether we have to rebuild everything if (viewport_size_has_changed || tprop_has_changed || (this->GetMTime() > this->BuildTime) || (ia && (ia != this->LastImageActor || ia->GetMTime() > this->BuildTime)) || (iv && iv->GetMTime() > this->BuildTime) || (wl && wl->GetMTime() > this->BuildTime)) { int *vSize = viewport->GetSize(); vtkDebugMacro(<<"Rebuilding text"); // Replace text this->TextReplace(ia, wl); // Get the viewport size in display coordinates this->LastSize[0] = vSize[0]; this->LastSize[1] = vSize[1]; // Only adjust size then the text changes due to non w/l slice reasons if (viewport_size_has_changed || tprop_has_changed || this->GetMTime() > this->BuildTime) { // Rebuid text props. // Perform shallow copy here since each individual corner has a // different aligment/size but they share the other this->TextProperty // attributes. fontSize = this->TextMapper[0]->GetTextProperty()->GetFontSize(); if (tprop_has_changed) { vtkTextProperty *tprop = this->TextMapper[0]->GetTextProperty(); tprop->ShallowCopy(this->TextProperty); tprop->SetFontSize(fontSize); tprop = this->TextMapper[1]->GetTextProperty(); tprop->ShallowCopy(this->TextProperty); tprop->SetFontSize(fontSize); tprop = this->TextMapper[2]->GetTextProperty(); tprop->ShallowCopy(this->TextProperty); tprop->SetFontSize(fontSize); tprop = this->TextMapper[3]->GetTextProperty(); tprop->ShallowCopy(this->TextProperty); tprop->SetFontSize(fontSize); this->SetTextActorsJustification(); } // Update all the composing objects to find the best size for the font // use the last size as a first guess /* +--------+ |2 3| | | | | |0 1| +--------+ */ int tempi[8]; int allZeros = 1; for (i = 0; i < 4; i++) { this->TextMapper[i]->GetSize(viewport, tempi + i * 2); if (tempi[2*i] > 0 || tempi[2*i+1] > 0) { allZeros = 0; } } if (allZeros) { return 0; } int height_02 = tempi[1] + tempi[5]; int height_13 = tempi[3] + tempi[7]; int width_01 = tempi[0] + tempi[2]; int width_23 = tempi[4] + tempi[6]; int max_width = (width_01 > width_23) ? width_01 : width_23; int num_lines_02 = GetNumberOfLines(this->TextMapper[0]->GetInput()) + GetNumberOfLines(this->TextMapper[2]->GetInput()); int num_lines_13 = GetNumberOfLines(this->TextMapper[1]->GetInput()) + GetNumberOfLines(this->TextMapper[3]->GetInput()); int line_max_02 = (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_02 ? num_lines_02 : 1); int line_max_13 = (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_13 ? num_lines_13 : 1); // Target size is to use 90% of x and y int tSize[2]; tSize[0] = (int)(0.9*vSize[0]); tSize[1] = (int)(0.9*vSize[1]); // While the size is too small increase it while (height_02 < tSize[1] && height_13 < tSize[1] && max_width < tSize[0] && height_02 < line_max_02 && height_13 < line_max_13 && fontSize < 100) { fontSize++; for (i = 0; i < 4; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); this->TextMapper[i]->GetSize(viewport, tempi + i * 2); } height_02 = tempi[1] + tempi[5]; height_13 = tempi[3] + tempi[7]; width_01 = tempi[0] + tempi[2]; width_23 = tempi[4] + tempi[6]; max_width = (width_01 > width_23) ? width_01 : width_23; } // While the size is too large decrease it while ((height_02 > tSize[1] || height_13 > tSize[1] || max_width > tSize[0] || height_02 > line_max_02 || height_13 > line_max_13) && fontSize > 0) { fontSize--; for (i = 0; i < 4; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); this->TextMapper[i]->GetSize(viewport, tempi + i * 2); } height_02 = tempi[1] + tempi[5]; height_13 = tempi[3] + tempi[7]; width_01 = tempi[0] + tempi[2]; width_23 = tempi[4] + tempi[6]; max_width = (width_01 > width_23) ? width_01 : width_23; } fontSize = static_cast(pow((double)fontSize, NonlinearFontScaleFactor)*LinearFontScaleFactor); if (fontSize > this->MaximumFontSize) { fontSize = this->MaximumFontSize; } this->FontSize = fontSize; for (i = 0; i < 4; i++) { this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize); this->TextMapper[i]->GetTextProperty()->ShadowOn(); } // Now set the position of the TextActors this->SetTextActorsPosition(vSize); for (i = 0; i < 4; i++) { this->TextActor[i]->SetProperty(this->GetProperty()); } } this->BuildTime.Modified(); this->LastImageActor = ia; } // Everything is built, just have to render if (this->FontSize >= this->MinimumFontSize) { for (i = 0; i < 4; i++) { this->TextActor[i]->RenderOpaqueGeometry(viewport); } } return 1; }