Trouble when alternating scalar colors

I’m currently working on an React application using vtk.js for 3D visualization. For the 3D models i am using .vtp files which share a similar structure:

  • both contain values for magnitude 1, and magnitude 2
  • difference between magnitude 1 and magnitude 2 is basically the color pallete which is assigned to each.

The issue is: I have buttons on my React application in order to alternate the colors display between Magnitude 1 and Magnitude 2, and when I change from Magnitude 1 to Magnitude 2 it changes the colors but the scalar values for the model doesn’t which results in having Magnitude 2 color values but not obtaining the expected behavior. The only way to avoid this to happen has been importing a second .vtp which only contains Magnitude 2

An example:

This is Magnitude 1:
Pasted image 20240604144049

Colors are displayed as expected on this one

In the other hand, here is Magnitude 2 after I click the button event:

Colors are not displayed as expected.

Finally, this is after I isolate Magnitude 2 inside my .vtp (Magnitude 1 doesn’t exist on this one)

This should be the expected behavior for the click event in my button, instead of the colors in screenshot number 2.

Here’s my current code using the .vtp which stores both Magnitude 1 and 2:

import React, { useEffect, useRef, useState } from 'react';

import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';

import vtkXMLPolyDataReader from '@kitware/vtk.js/IO/XML/XMLPolyDataReader';

import vtkSTLReader from '@kitware/vtk.js/IO/Geometry/STLReader';

import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';

import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';

import vtkInteractorStyleTrackballCamera from '@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera';

import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';

import '@kitware/vtk.js/Rendering/Profiles/Geometry';

import vtkCellPicker from '@kitware/vtk.js/Rendering/Core/CellPicker';

import { Oval } from 'react-loader-spinner';

import './Spinner.css';

import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';

  

const Spinner = () => (

  <div className="spinner">

    <Oval

      height={80}

      width={80}

      color="green"

      ariaLabel="loading"

      secondaryColor="lightgrey"

      strokeWidth={2}

      strokeWidthSecondary={2}

    />

  </div>

);

  

const App = () => {

  

  const reader = vtkXMLPolyDataReader.newInstance();

  const containerRef = useRef(null);

  const [actor, setActor] = React.useState(null);

  const [isLoading, setIsLoading] = useState(true);

  const [stlActor, setStlActor] = useState(null);

  const [pickedValue, setPickedValue] = useState(null);

  

  const cHandleSTL = () => {

    if(actor) {

      const mapper = actor.getMapper();

      const lookupTable = vtkColorTransferFunction.newInstance();

      lookupTable.addRGBPoint(0.0, 139 / 255, 0 / 255, 0 / 255); // chocolate

      actor.getProperty().setOpacity(0.3)

      mapper.setLookupTable(lookupTable);

      global.fullScreenRenderer.getRenderWindow().render();

    }

  };

  

  const cHandleMagnitude2 = () => {

    const mapper = vtkMapper.newInstance();

  

      const pointData = actor.getMapper().getInputData().getPointData();

      const scalars = pointData.getArrayByName('Magnitude2');

  
  

      if (!scalars) {

        console.error('No se encontraron scalars con el nombre "Magnitude2".');

        return;

      }

  

      const scalarRange = scalars.getRange();

      console.log(`Rango de escalares Magnitude2: ${scalarRange}`);

  

      mapper.setScalarRange(scalarRange);

      mapper.setColorByArrayName('Magnitude2');

      mapper.setScalarModeToUsePointData();

  

      const lookupTable = vtkColorTransferFunction.newInstance();

      lookupTable.addRGBPoint(137, 0.55, 0.0, 0.0); // Chocolate

      lookupTable.addRGBPoint(60, 0.55, 0.0, 0.0); // Chocolate

      lookupTable.addRGBPoint(20, 0.82, 0.41, 0.12); // Dark Red

      lookupTable.addRGBPoint(15, 1.0, 0.84, 0.0); // Gold

      lookupTable.addRGBPoint(14, 0.63, 0.79, 0.95); // Cornflower Blue

      lookupTable.addRGBPoint(13, 0.63, 0.79, 0.95); // Cornflower Blue

      lookupTable.addRGBPoint(12, 0.63, 0.79, 0.95); // Cornflower Blue

      lookupTable.addRGBPoint(10, 0.39, 0.58, 0.93); // Cornflower Blue

      lookupTable.addRGBPoint(8, 0.39, 0.58, 0.93); // Cornflower Blue

      lookupTable.addRGBPoint(6, 0.39, 0.58, 0.93); // Cornflower Blue

      lookupTable.addRGBPoint(4, 0.39, 0.58, 0.93); // Cornflower Blue

      lookupTable.addRGBPoint(3, 0.0, 0.0, 0.5); // Cornflower Blue    

      lookupTable.addRGBPoint(2, 0.0, 0.0, 0.5); // Cornflower Blue

      lookupTable.addRGBPoint(0, 0.0, 0.0, 0.5); // Navy

  

      lookupTable.addRGBPoint(-1, 0.5, 0.5, 0.5); // Cornflower Blue

  

      mapper.setLookupTable(lookupTable);

      mapper.setColorModeToMapScalars();

  

      actor.getProperty().setOpacity(1.0);

      actor.getProperty().setInterpolationToPhong();

      global.fullScreenRenderer.getRenderWindow().render();

  };

  

  const cHandleMagnitude1 = () => {

    console.log('Magnitude1')

    if (actor) {

  

      const pointData = actor.getMapper().getInputData().getPointData();

      const scalars = pointData.getArrayByName('Magnitude1_Corrected'); // Asegúrate de que el nombre del array sea correcto

      if (!scalars) {

        console.error('No se encontraron scalars con el nombre "Magnitude1".');

        return;

      }

  

      const scalarRange = scalars.getRange();

      console.log(`Rango de escalares Magnitude1: ${scalarRange}`);

  

      const mapper = actor.getMapper();

      mapper.setScalarRange(scalarRange);

      mapper.setColorByArrayName('Magnitude1_Corrected');

      mapper.setScalarModeToUsePointData();

      const lookupTable = vtkColorTransferFunction.newInstance();

      lookupTable.addRGBPoint(0.6, 0.9, 178 / 255, 34 / 255);

      lookupTable.addRGBPoint(0.7, 1.0, 0.0, 0.0);

      lookupTable.addRGBPoint(0.75, 1.0, 0.5, 0.0);

      lookupTable.addRGBPoint(0.8, 1.0, 1.0, 0.0);

      lookupTable.addRGBPoint(0.9, 127 / 255, 255 / 255, 212 / 255);

      lookupTable.addRGBPoint(0.95, 30 / 255, 144 / 255, 255 / 255);

      lookupTable.addRGBPoint(1.0, 65 / 255, 105 / 255, 225 / 255);

      lookupTable.addRGBPoint(9.0, 0.5, 0.5, 0.5);

  

      mapper.setLookupTable(lookupTable);

      mapper.setColorModeToMapScalars();

      actor.getProperty().setOpacity(1.0);

      global.fullScreenRenderer.getRenderWindow().render();

    }

  };

  

  useEffect(() => {

    const fileName = 'Paciente_Magnitude2.vtp';

  

    const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ rootContainer: containerRef.current });

    const renderer = fullScreenRenderer.getRenderer();

    const renderWindow = fullScreenRenderer.getRenderWindow();

    const interactor = fullScreenRenderer.getInteractor();

  

    interactor.setInteractorStyle(vtkInteractorStyleTrackballCamera.newInstance());

  
  

    reader.setUrl(`${fileName}`).then(() => {

      reader.loadData().then(() => {

        const polydata = reader.getOutputData(0);

        if (!polydata) {

          console.error('No se pudieron cargar los datos del archivo VTP.');

          return;

        }

  

        const numberOfPoints = polydata.getPoints().getNumberOfPoints();

        const numberOfCells = polydata.getNumberOfCells();

        console.log(`Número de puntos: ${numberOfPoints}`);

        console.log(`Número de celdas: ${numberOfCells}`);

  

        const pointData = polydata.getPointData();

        const scalars = pointData.getArrayByName('Magnitude1_Corrected');

        const Magnitude2 = pointData.getArrayByName('Magnitude1_Corrected');

        const Magnitude1 = pointData.getArrayByName('Magnitude1_Corrected');

        if (!scalars) {

          console.error('No se encontraron scalars con el nombre "Magnitude1_Corrected".');

          return;

        }

  

        const scalarRange = scalars.getRange();

        const Magnitude2Range = Magnitude2.getRange();

        const Magnitude1Range = Magnitude1.getRange();

        console.log(`Rango de escalares: ${scalarRange}`);

        console.log(Magnitude2Range);

        console.log(Magnitude2.getData());

  

        const mapper = vtkMapper.newInstance();

        mapper.setInputData(polydata);

        mapper.setScalarRange(scalarRange);

        mapper.setColorByArrayName('Magnitude1_Corrected');

        mapper.setScalarModeToUsePointData();

  

        const lookupTable = vtkColorTransferFunction.newInstance();

        lookupTable.addRGBPoint(137, 0.5, 0.5, 0.5); // Gray

  

        lookupTable.addRGBPoint(scalarRange[1], 0.5, 0.5, 0.5);

  

        mapper.setLookupTable(lookupTable);

        mapper.setColorModeToMapScalars();

  

        const actor = vtkActor.newInstance();

        actor.setMapper(mapper);

  

        renderer.addActor(actor);

  

        const bounds = actor.getBounds();

        console.log('Bounds:', bounds);

        renderer.resetCamera(bounds);

        renderWindow.render();

  

        const picker = vtkCellPicker.newInstance();

        picker.setPickFromList(1);

        picker.setTolerance(0);

        picker.initializePickList();

        picker.addPickList(actor);

  

renderWindow.getInteractor().onLeftButtonPress((callData) => {

  if (renderer !== callData.pokedRenderer) {

    return;

  }

  

  const pos = callData.position;

  const point = [pos.x, pos.y, 0.0];

  console.log(`Pick at: ${point}`);

  picker.pick(point, renderer);

  

  if (picker.getActors().length === 0) {

    const pickedPoint = picker.getPickPosition();

    console.log(`No cells picked, default: ${pickedPoint}`);

  } else {

    const pickedCellId = picker.getCellId();

    console.log('Picked cell: ', pickedCellId);

  

    const pickedPoints = picker.getPickedPositions();

    pickedPoints.forEach((pickedPoint, i) => {

      pickedPoint = pickedPoint.map(coord => coord / 1000);

      console.log(`Picked (divided): ${pickedPoint}`);

    });

  

    const Magnitude1Array = polydata.getPointData().getArrayByName('Magnitude1_Corrected');

    const Magnitude2Array = polydata.getPointData().getArrayByName('Magnitude2');

  

    const findClosestPointId = (points, pickedPoint) => {

      let minDist = Infinity;

      let closestPointId = -1;

  

      for (let i = 0; i < points.length / 3; i++) {

        const dx = points[i * 3] - pickedPoint[0];

        const dy = points[i * 3 + 1] - pickedPoint[1];

        const dz = points[i * 3 + 2] - pickedPoint[2];

        const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);

  

        if (dist < minDist) {

          minDist = dist;

          closestPointId = i;

        }

      }

  

      return closestPointId;

    };

  

    const processArrayData = (array, arrayName, elementId) => {

      if (array) {

        const points = polydata.getPoints().getData();

        const closestPointId = findClosestPointId(points, pickedPoints[0]);

  

        if (closestPointId !== -1) {

          const data = array.getData();

          const cellValue = data[closestPointId];

          console.log(`Valor de ${arrayName}: ${cellValue}`);

          document.getElementById(elementId).innerHTML = `${arrayName}: ${cellValue}`;

          setPickedValue(cellValue);

        } else {

          console.error(`No se encontró el punto más cercano para ${arrayName}.`);

          setPickedValue('No disponible');

        }

      } else {

        console.error(`No se encontraron datos de ${arrayName}.`);

        setPickedValue('No disponible');

      }

    };

  

    processArrayData(Magnitude2Array, 'Magnitude2', 'Magnitude2');

    processArrayData(Magnitude1Array, 'Magnitude1_Corrected', 'Magnitude1');

  }

  

          renderWindow.render();

        });

  

        setActor(actor);

        setIsLoading(false); // Deja de mostrar el spinner después de cargar

      }).catch((error) => {

        console.error('Error al cargar los datos del archivo VTP:', error);

        setIsLoading(false); // Deja de mostrar el spinner si hay un error

      });

    }).catch((error) => {

      console.error('Error al cargar el archivo VTP:', error);

      setIsLoading(false); // Deja de mostrar el spinner si hay un error

    });

  

    global.reader = reader;

    global.fullScreenRenderer = fullScreenRenderer;

  

    // Configurar el límite de tiempo para ocultar el spinner después de 10 segundos

    const timeoutId = setTimeout(() => {

      setIsLoading(false);

    }, 10000);

  

    // Limpiar el timeout si el componente se desmonta antes de que se complete

    return () => clearTimeout(timeoutId);

  }, []);

  

  return (

  <div>

    {isLoading && <Spinner />}

    <div style={{ position: 'absolute', top: '10px', left: '10px', zIndex: 1 }}>

      <button onClick={cHandleMagnitude1}>Magnitude1</button>

      <button onClick={cHandleMagnitude2}>Magnitude2</button>

      <button onClick={cHandleSTL}>STL</button>

      <button onClick={loadSTLModel}>Load STL</button>

      <br/>

      <div>

        <h2 id='Magnitude1'>Magnitude1:</h2>

        <h2 id='Magnitude2'>Magnitude2: </h2>

      </div>

    </div>

    <div ref={containerRef} style={{ width: '100vw', height: '90vh', zIndex: 0 }}></div>

    <div id='logo' style={{ position: 'absolute', bottom: '10px', left: '10px', zIndex: 1 }}>

    </div>

  </div>

  

  );

};

  

export default App;

so far I have tried:

  • resetting the mapper and the actor data in order to clean them and add the new scalar
  • as I said above, to use a .vtp with Magnitude 2 only

The value stored in the point is accurate, since I have a method which obtains the values for Magnitude 1 and 2 for each point of the .vtp, and when the color is correct the numbers are just accurate for that point of the 3d model.

The goal is to get both palletes of Magnitude 1 and Magnitude 2 without having to use two different .vtp files.