geopro/tests/spike/slice_alpha_probe.cpp

162 lines
6.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 实测vtkImagePlaneWidget 的纹理平面能否把 LUT 下溢透明(alpha=0)的 texel 渲染成透明。
// 构造一张图:左半真值(映射红)、右半哨兵 -1(下溢→透明)。纹理平面背后放一块不透明【黄】板,
// 背景【绿】。套用 buildLut(transparentBelowRange=true) 后离屏渲染、回读像素分类:
// 右半空白处出现【黄】 → texel alpha 透明【成功】(黄板透过来)
// 出现【蓝】 → 值被钳到最低色(透明没生效)
// 出现【黑】 → alpha 被丢
// 纯实测,不靠文档措辞猜。
#include <cstdio>
#include <vtkActor.h>
#include <vtkFloatArray.h>
#include <vtkImageData.h>
#include <vtkImageMapToColors.h>
#include <vtkImagePlaneWidget.h>
#include <vtkLookupTable.h>
#include <vtkNew.h>
#include <vtkPNGWriter.h>
#include <vtkPlaneSource.h>
#include <vtkPointData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkTrivialProducer.h>
#include <vtkUnsignedCharArray.h>
#include <vtkWindowToImageFilter.h>
#include <vtkCamera.h>
#include "ColorLutBuilder.hpp"
#include "model/ColorScale.hpp"
int main() {
using namespace geopro;
// 1) 构造体: 40x40x3, 左半 i<20 → 200(真值,映射红); 右半 → -1(哨兵,下溢透明)。
const int nx = 40, ny = 40, nz = 3;
vtkNew<vtkImageData> img;
img->SetDimensions(nx, ny, nz);
img->SetOrigin(0, 0, 0);
img->SetSpacing(1, 1, 1);
vtkNew<vtkFloatArray> sc;
sc->SetName("v");
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny * nz);
// 全值域核查:左侧 [0..nx-9] 列填 0→200 渐变(真数据),右侧 8 列填 -1(哨兵/白化)。
// 验证 (a) 颜色映射跨整个 [vmin,vmax] 是否正确;(b) 空值是否透明。
const int blankCols = 8;
const int gradCols = nx - blankCols;
for (int k = 0; k < nz; ++k)
for (int j = 0; j < ny; ++j)
for (int i = 0; i < nx; ++i) {
const vtkIdType id = (static_cast<vtkIdType>(k) * ny + j) * nx + i;
const float v = (i < gradCols)
? static_cast<float>(200.0 * i / (gradCols - 1)) // 0→200 渐变
: -1.0F; // 哨兵
sc->SetValue(id, v);
}
img->GetPointData()->SetScalars(sc);
// 2) 三色阶(全不透明): 0→蓝, 100→黄, 200→红。渐变应呈 蓝→黄→红;-1 应透明(露品红背板)。
core::ColorScale cs;
cs.addStop(0.0, core::Rgba{0, 0, 255, 255});
cs.addStop(100.0, core::Rgba{255, 255, 0, 255});
cs.addStop(200.0, core::Rgba{255, 0, 0, 255});
auto lut = render::buildLut(cs, 0.0, 200.0, 256, /*transparentBelowRange=*/true);
// 3) 渲染器 + 离屏窗口 + 交互器(widget 必需)。背景绿。
vtkNew<vtkRenderer> ren;
ren->SetBackground(0.15, 0.15, 0.15); // 深灰 = 背景
vtkNew<vtkRenderWindow> rw;
rw->SetOffScreenRendering(1);
rw->AddRenderer(ren);
rw->SetSize(400, 400);
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(rw);
// 4) 纹理平面【背后】放一块不透明黄板(z=0.5, 在切片 z=1 之下=远离上方相机)。
vtkNew<vtkPlaneSource> yp;
yp->SetOrigin(0, 0, 0.5);
yp->SetPoint1(nx - 1, 0, 0.5);
yp->SetPoint2(0, ny - 1, 0.5);
vtkNew<vtkPolyDataMapper> ym;
ym->SetInputConnection(yp->GetOutputPort());
vtkNew<vtkActor> ya;
ya->SetMapper(ym);
ya->GetProperty()->SetColor(1.0, 0.0, 1.0); // 品红 = 背板(空值透明处会露出它)
ya->GetProperty()->LightingOff();
ren->AddActor(ya);
// 5) 真 vtkImagePlaneWidget: Z 法向切片 1(z=1), 套用户 LUT, 最近邻避免边界混色。
vtkNew<vtkTrivialProducer> prod;
prod->SetOutput(img);
vtkNew<vtkImagePlaneWidget> w;
w->SetInteractor(iren);
w->SetInputConnection(prod->GetOutputPort());
w->SetPlaneOrientationToZAxes();
w->SetSliceIndex(1);
w->SetResliceInterpolateToNearestNeighbour();
w->TextureInterpolateOff();
w->SetLookupTable(lut);
// 钉死 window/level 到 [vmin,vmax]=[0,200],阻止 widget 按输入标量范围自动拉伸。
w->SetWindowLevel(200.0, 100.0);
w->On();
// 6) 顶视相机(沿 -Z 俯视), 平行投影框住平面。
ren->GetActiveCamera()->SetPosition(nx / 2.0, ny / 2.0, 100.0);
ren->GetActiveCamera()->SetFocalPoint(nx / 2.0, ny / 2.0, 1.0);
ren->GetActiveCamera()->SetViewUp(0, 1, 0);
ren->GetActiveCamera()->ParallelProjectionOn();
ren->ResetCamera();
rw->Render();
// 7) 回读像素, 分类计数。
vtkNew<vtkWindowToImageFilter> w2i;
w2i->SetInput(rw);
w2i->Update();
vtkImageData* out = w2i->GetOutput();
{
vtkNew<vtkPNGWriter> pw;
pw->SetFileName("D:/dev/spike_data/slice_alpha_probe.png");
pw->SetInputConnection(w2i->GetOutputPort());
pw->Write();
}
int dims[3];
out->GetDimensions(dims);
long red = 0, yellow = 0, green = 0, blue = 0, black = 0, other = 0;
for (int y = 0; y < dims[1]; ++y)
for (int x = 0; x < dims[0]; ++x) {
const auto* p = static_cast<unsigned char*>(out->GetScalarPointer(x, y, 0));
const int r = p[0], g = p[1], b = p[2];
if (r > 150 && g < 100 && b < 100) ++red;
else if (r > 150 && g > 150 && b < 100) ++yellow;
else if (r < 100 && g > 150 && b < 100) ++green;
else if (b > 150 && r < 100 && g < 100) ++blue;
else if (r < 60 && g < 60 && b < 60) ++black;
else ++other;
}
std::printf("PIXELS red(data)=%ld yellow(behind-shows=TRANSPARENT)=%ld "
"green(bg)=%ld blue(clamp-lowest)=%ld black(alpha-dropped)=%ld other=%ld\n",
red, yellow, green, blue, black, other);
// 8) 直接判读 colormap 对下溢值输出的 RGBA(隔离 LUT 正确性 vs 纹理渲染)。
if (auto* cm = w->GetColorMap()) {
cm->Update();
if (auto* co = cm->GetOutput()) {
int cd[3];
co->GetDimensions(cd);
const int comps = co->GetNumberOfScalarComponents();
// 右半某点(i=30,j=20,值=150) colormap 应产出 alpha=0。
const auto* px = static_cast<unsigned char*>(co->GetScalarPointer(30, 20, 0));
std::printf("COLORMAP comps=%d rightHalfTexel.RGBA=", comps);
for (int c = 0; c < comps; ++c) std::printf("%d ", px[c]);
std::printf("(期望 alpha=0 => LUT 正确产出透明)\n");
}
}
std::printf("VERDICT: 若 yellow 远多于 blue/black => widget 纹理【支持】texel alpha 透明; "
"若 blue 多 => 钳最低色; 若 black 多 => 丢 alpha\n");
return 0;
}