vtk重连管线 分段颜色映射(RGBA图像生成)

解决的问题:1、在代码实现过程中往往需要重新连接管线,因为管线中的某些设置已经过时。2、对于整个图像使用一幅颜色映射,但是只显示其中某几段的颜色,方案设计。

直接上代码

// 构造函数中初始化管线
void tabHistogram::initVtkConnection()
{
    for(int i = 0; i < 3; i++) {
        image_slice[i] = vtkSmartPointer<vtkImageSlice>::New();
        image_sliceMapper[i] = vtkSmartPointer<vtkImageSliceMapper>::New();
        image_slice[i]->SetMapper(image_sliceMapper[i]);
        auto mainReslice = Singleton::getInstance()->curItem->m_ImageReslices[i];

        // 生成 RGBA 管线
//        std::vector<std::pair<double, double>> intervals = {};
//        auto rgbaPort = CreateRGBAIntervalSlicePipeline(mainReslice->GetOutputPort(), intervals, cavasLookup);

//        // 连接到 mapper
//        image_sliceMapper[i]->SetInputConnection(rgbaPort->GetOutputPort());

//        // slice property
//        vtkImageProperty* prop = image_slice[i]->GetProperty();
//        prop->SetOpacity(1.0); // RGBA alpha 已经处理
//        prop->UseLookupTableScalarRangeOff(); // RGBA 已经 bake,不用 LUT

//        Singleton::getInstance()->mainwindow->getRenderer(i)->AddViewProp(image_slice[i]);
    }
}

// 在管线中条件变化,比如分段区间变化时,重新构建管线
void tabHistogram::rebuildVtkConnection()
{
    for(int i = 0; i < 3; i++) {
        auto mainReslice = Singleton::getInstance()->curItem->m_ImageReslices[i];

        // 生成 RGBA 管线
        auto rgbaPort = CreateRGBAIntervalSlicePipeline(mainReslice->GetOutputPort(), m_intervals, cavasLookup);
        if(rgbaPort == nullptr) {
            image_sliceMapper[i]->RemoveAllInputs();
            Singleton::getInstance()->mainwindow->getRenderer(i)->RemoveViewProp(image_slice[i]);
            Singleton::getInstance()->mainwindow->updateRender(i);
            continue;
        }
        // 连接到 mapper
        image_sliceMapper[i]->SetInputConnection(rgbaPort->GetOutputPort());

        // slice property
        vtkImageProperty* prop = image_slice[i]->GetProperty();
        prop->SetOpacity(1.0); // RGBA alpha 已经处理
        prop->UseLookupTableScalarRangeOff(); // RGBA 已经 bake,不用 LUT

        Singleton::getInstance()->mainwindow->getRenderer(i)->AddViewProp(image_slice[i]);
        Singleton::getInstance()->mainwindow->updateRender(i);
    }
}
// 核心代码:生成RGBA图像,vtkImageAppendComponents就是做这个事情的,生成四通道数据,给vtkImageActor或者vtkImageSlice时会自动对四通道图像做RGBA显示。(Maptocolors RGB)(A)
// 由此引发的联想,以vtkImageActor为例,它是单通道图像所以没有办法颜色渲染,所以通过vtkMaptocolors生成三通道,就是我们平时做的颜色渲染。四通道则更进一步
// 输入:reslice 输出 image,区间列表,LUT
static vtkSmartPointer<vtkImageAppendComponents> CreateRGBAIntervalSlicePipeline(vtkAlgorithmOutput* inputPort,
    const std::vector<std::pair<double, double>>& intervals, vtkLookupTable* lut)
{
    if (!inputPort || intervals.empty())
        return nullptr;

    // ===============================
    // 1. LUT 映射 RGB
    // ===============================
    auto mapToColors = vtkSmartPointer<vtkImageMapToColors>::New();
    mapToColors->SetInputConnection(inputPort);
    mapToColors->SetLookupTable(lut);
    mapToColors->SetOutputFormatToRGB();
    mapToColors->PassAlphaToOutputOff();

    // ===============================
    // 2. 生成 Alpha Mask
    // ===============================
    std::vector<vtkSmartPointer<vtkImageThreshold>> masks;
    for (auto& interval : intervals)
    {
        auto mask = vtkSmartPointer<vtkImageThreshold>::New();
        mask->SetInputConnection(inputPort);
        mask->ThresholdBetween(interval.first, interval.second);
        mask->SetInValue(255);  // 区间内 alpha 255
        mask->SetOutValue(0);   // 区间外 alpha 0
        mask->SetOutputScalarTypeToUnsignedChar();
        masks.push_back(mask);
    }

    // 多个 mask 合并(取最大值)
    vtkSmartPointer<vtkImageAlgorithm> alphaMask = masks[0];
    for (size_t i = 1; i < masks.size(); ++i)
    {
        auto math = vtkSmartPointer<vtkImageMathematics>::New();
        math->SetOperationToMax();
        math->SetInputConnection(0, alphaMask->GetOutputPort());
        math->SetInputConnection(1, masks[i]->GetOutputPort());
        alphaMask = math;
    }

    // ===============================
    // 3. 合并 RGB + Alpha → RGBA
    // ===============================
    auto appendRGBA = vtkSmartPointer<vtkImageAppendComponents>::New();
    appendRGBA->AddInputConnection(mapToColors->GetOutputPort()); // RGB
    appendRGBA->AddInputConnection(alphaMask->GetOutputPort());   // Alpha

    return appendRGBA;
}

void tabHistogram::on_Bn_showPeak_clicked()
{
    QList<QTableWidgetItem*> items = ui->tableWidget->selectedItems();
    if(items.size() > 0) {
        int row = items[0]->row();
        if(ui->tableWidget->item(row, STU)->text() == tr("未显示")) {
            double pos = ui->tableWidget->item(row, POS)->text().toDouble();
            double wid = ui->tableWidget->item(row, WID)->text().toDouble();
            ui->tableWidget->item(row, STU)->setText(tr("已显示"));
            ui->Bn_showPeak->setText(tr("取消显示峰"));
        } else {
            double pos = ui->tableWidget->item(row, POS)->text().toDouble();
            double wid = ui->tableWidget->item(row, WID)->text().toDouble();
            ui->tableWidget->item(row, STU)->setText(tr("未显示"));
            ui->Bn_showPeak->setText(tr("显示峰"));
        }
    }

    std::vector<Interval> intervals;
    for(int i = 0; i < ui->tableWidget->rowCount(); i++) {
        if(ui->tableWidget->item(i, STU)->text() == tr("已显示")) {
            double pos = ui->tableWidget->item(i, POS)->text().toDouble();
            double wid = ui->tableWidget->item(i, WID)->text().toDouble();
            intervals.push_back({pos - wid / 2, pos + wid / 2});
        }
    }
    mergeIntervals(intervals);
    m_intervals = intervals;

    // 重建二维渲染管线
    rebuildVtkConnection();

    // 建立三维渲染
    auto item = Singleton::getInstance()->curItem;
    auto m_colorTransferFunction = item->m_colorTransferFunction;
    auto m_opacityTransferFunction = item->m_opacityTransferFunction;
    auto m_volumeProperty = item->m_volumeProperty;
    auto m_volumeMapper = item->m_volumeMapper;
    Double2 range;
    item->m_data->GetScalarRange(range.data());
    double huMin = range[0];
    double huMax = range[1];

    m_colorTransferFunction->RemoveAllPoints();
    int n = cavasLookup->GetNumberOfTableValues();
    for (int i = 0; i < n; ++i) {
        double rgb[4];
        cavasLookup->GetTableValue(i, rgb);
        double value = huMin + (huMax - huMin) * i / (n-1);
        m_colorTransferFunction->AddRGBPoint(value, rgb[0], rgb[1], rgb[2]);
    }

    m_opacityTransferFunction->RemoveAllPoints();
    // 先把整个范围设为透明
    m_opacityTransferFunction->AddPoint(cavasLookup->GetRange()[0], 0.0);
    m_opacityTransferFunction->AddPoint(cavasLookup->GetRange()[1], 0.0);

    // 遍历 intervals,把对应区间 alpha 设置为 1
    for (auto& interval : m_intervals)
    {
        double start = interval.first;
        double end   = interval.second;

        // 区间前一点保持透明
        m_opacityTransferFunction->AddPoint(start - 0.001, 0.0);
        // 区间内显示
        m_opacityTransferFunction->AddPoint(start, 1.0);
        m_opacityTransferFunction->AddPoint(end, 1.0);
        // 区间后一点透明
        m_opacityTransferFunction->AddPoint(end + 0.001, 0.0);
    }
    Singleton::getInstance()->mainwindow->updateRender(3);
}

在初次渲染的时候,如果init管线时就立刻进行渲染,会产生一些红色斑块。这个gpt解释是脏数据。实测,只要加个延时显示就行了。现在init中没有渲染操作也没有这个操作的必要

Table of Contents