diff --git a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp index 8975ee7a81..77f7671fcc 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp @@ -1,899 +1,907 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ // std dependencies #include #include #include // mitk dependencies #include "mitkUSDiPhASDevice.h" #include "mitkUSDiPhASImageSource.h" #include #include "mitkUSDiPhASBModeImageFilter.h" #include "ITKUltrasound/itkBModeImageFilter.h" #include "mitkImageCast.h" #include "mitkITKImageImport.h" // itk dependencies #include "itkImage.h" #include "itkResampleImageFilter.h" #include "itkCastImageFilter.h" #include "itkCropImageFilter.h" #include "itkRescaleIntensityImageFilter.h" #include "itkIntensityWindowingImageFilter.h" #include #include "itkMultiplyImageFilter.h" mitk::USDiPhASImageSource::USDiPhASImageSource(mitk::USDiPhASDevice* device) : m_Device(device), m_StartTime(((float)std::clock()) / CLOCKS_PER_SEC), m_UseGUIOutPut(false), m_DataType(DataType::Image_uChar), m_GUIOutput(nullptr), m_UseBModeFilter(false), m_CurrentlyRecording(false), m_DataTypeModified(true), m_DataTypeNext(DataType::Image_uChar), m_CurrentImageTimestamp(0), m_PyroConnected(false), m_VerticalSpacing(0), m_UseBModeFilterModified(false), m_UseBModeFilterNext(false), m_ScatteringCoefficientModified(false), m_CompensateForScatteringModified(false), m_VerticalSpacingModified(false), m_ScatteringCoefficient(15), m_CompensateForScattering(false), m_CompensateEnergy(false), m_CompensateEnergyNext(false), m_CompensateEnergyModified(false) { m_BufferSize = 100; m_ImageTimestampBuffer.insert(m_ImageTimestampBuffer.begin(), m_BufferSize, 0); m_LastWrittenImage = m_BufferSize - 1; m_ImageBuffer.insert(m_ImageBuffer.begin(), m_BufferSize, std::pair(nullptr, nullptr)); us::ModuleResource resourceFile; std::string name; m_FluenceCompOriginal.insert(m_FluenceCompOriginal.begin(), 5, Image::New()); for (int i = 5; i <= 25; ++i) { name = "c:\\HomogeneousScatteringAssumptions\\Scattering" + std::to_string(i) + ".nrrd"; m_FluenceCompOriginal.push_back(mitk::IOUtil::LoadImage(name)); } m_FluenceCompResized.insert(m_FluenceCompResized.begin(), 26, Image::New()); m_FluenceCompResizedItk.insert(m_FluenceCompResizedItk.begin(), 26, itk::Image::New()); } mitk::USDiPhASImageSource::~USDiPhASImageSource() { // close the pyro MITK_INFO("Pyro Debug") << "StopDataAcquisition: " << m_Pyro->StopDataAcquisition(); MITK_INFO("Pyro Debug") << "CloseConnection: " << m_Pyro->CloseConnection(); m_PyroConnected = false; m_Pyro = nullptr; } void mitk::USDiPhASImageSource::CheckModifiedVariables() { // modify all settings that have been changed here, so we don't get multithreading issues if (m_DataTypeModified) { SetDataType(m_DataTypeNext); m_DataTypeModified = false; UpdateImageGeometry(); } if (m_UseBModeFilterModified) { SetUseBModeFilter(m_UseBModeFilterNext); m_UseBModeFilterModified = false; } if (m_VerticalSpacingModified) { m_VerticalSpacing = m_VerticalSpacingNext; m_VerticalSpacingModified = false; } if (m_ScatteringCoefficientModified) { m_ScatteringCoefficient = m_ScatteringCoefficientNext; m_ScatteringCoefficientModified = false; } if (m_CompensateForScatteringModified) { m_CompensateForScattering = m_CompensateForScatteringNext; m_CompensateForScatteringModified = false; } if (m_CompensateEnergyModified) { m_CompensateEnergy = m_CompensateEnergyNext; m_CompensateEnergyModified = false; } } void mitk::USDiPhASImageSource::ResizeFluenceImage(mitk::Vector3D spacing, unsigned int* dimensions) { auto curResizeImage = ApplyResampling(m_FluenceCompOriginal.at(m_ScatteringCoefficient), spacing, dimensions); unsigned int imageSize = dimensions[0] * dimensions[1]; double* rawOutputData = new double[imageSize]; double* rawScatteringData = (double*)curResizeImage->GetData(); + unsigned int sizeRawScatteringData = curResizeImage->GetDimension(0) * curResizeImage->GetDimension(1); //everything above 1.5mm is still inside the transducer; therefore the fluence compensation image has to be positioned a little lower float upperCutoffmm = 1.5; unsigned int lowerBound = std::round(upperCutoffmm / spacing[1])*dimensions[0]; - unsigned int upperBound = lowerBound + imageSize; + unsigned int upperBound = lowerBound + sizeRawScatteringData; for (unsigned int i = 0; i < lowerBound && i < imageSize; ++i) { rawOutputData[i] = 0; // everything than cannot be compensated shall be treated as garbage, here the upper 0.15mm } for (unsigned int i = lowerBound; i < upperBound && i < imageSize; ++i) { rawOutputData[i] = 1 / rawScatteringData[i - lowerBound]; } for (unsigned int i = upperBound; i < imageSize; ++i) { rawOutputData[i] = 0; // everything than cannot be compensated shall be treated as garbage } unsigned int dim[] = { dimensions[0], dimensions[1], 1 }; curResizeImage->Initialize(mitk::MakeScalarPixelType(), 3, dim); curResizeImage->SetSpacing(spacing); - curResizeImage->SetImportSlice(rawOutputData, 0, 0, 0, mitk::Image::ManageMemory); + curResizeImage->SetSlice(rawOutputData); mitk::CastToItkImage(curResizeImage, m_FluenceCompResizedItk.at(m_ScatteringCoefficient)); m_FluenceCompResized.at(m_ScatteringCoefficient) = mitk::GrabItkImageMemory(m_FluenceCompResizedItk.at(m_ScatteringCoefficient)); MITK_INFO << "Resized a fluence image."; + + delete[] rawOutputData; } void mitk::USDiPhASImageSource::GetNextRawImage(std::vector& imageVector) { CheckModifiedVariables(); if (imageVector.size() != 2) { imageVector.resize(2); } // make sure image is nullptr mitk::Image::Pointer imageUS = nullptr; mitk::Image::Pointer imagePA = nullptr; float ImageEnergyValue = 0; for (int i = 100; i > 0 && ImageEnergyValue <= 0; --i) { if (m_ImageTimestampBuffer[(m_LastWrittenImage + i) % 100] != 0) { ImageEnergyValue = m_Pyro->GetClosestEnergyInmJ(m_ImageTimestampBuffer[(m_LastWrittenImage + i) % 100]); if (ImageEnergyValue > 0) { imagePA = m_ImageBuffer[(m_LastWrittenImage + i) % 100].first; imageUS = m_ImageBuffer[(m_LastWrittenImage + i) % 100].second; } } } // if we did not get any usable Energy value, compensate using this default value if (imagePA == nullptr || imageUS == nullptr) { imagePA = m_ImageBuffer[m_LastWrittenImage].first; imageUS = m_ImageBuffer[m_LastWrittenImage].second; ImageEnergyValue = 1; if (imagePA == nullptr || imageUS == nullptr) return; } // do image processing before displaying it if(imagePA.IsNotNull() && m_DataType == DataType::Beamformed_Short) { itkFloatImageType::Pointer itkImage; - mitk::CastToItkImage(imagePA, itkImage); imagePA = mitk::GrabItkImageMemory(itkImage); //thereby using float images + imagePA = CutOffTop(imagePA, 165); if (m_CompensateEnergy) imagePA = MultiplyImage(imagePA, 1 / ImageEnergyValue); // TODO: add the correct prefactor here if (m_UseBModeFilter) imagePA = ApplyBmodeFilter(imagePA, false); if (m_VerticalSpacing) imagePA = ResampleOutputVertical(imagePA, m_VerticalSpacing); if (m_CompensateForScattering) { auto curResizeImage = m_FluenceCompResized.at(m_ScatteringCoefficient); bool doResampling = imagePA->GetDimension(0) != curResizeImage->GetDimension(0) || imagePA->GetDimension(1) != curResizeImage->GetDimension(1) || imagePA->GetGeometry()->GetSpacing()[0] != curResizeImage->GetGeometry()->GetSpacing()[0] || imagePA->GetGeometry()->GetSpacing()[1] != curResizeImage->GetGeometry()->GetSpacing()[1]; if (doResampling) ResizeFluenceImage(imagePA->GetGeometry()->GetSpacing(), imagePA->GetDimensions()); imagePA = ApplyScatteringCompensation(imagePA, m_ScatteringCoefficient); } } if(imageUS.IsNotNull() && m_DataType == DataType::Beamformed_Short) { itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(imageUS, itkImage); imageUS = mitk::GrabItkImageMemory(itkImage); //thereby using float images imageUS = CutOffTop(imageUS, 165); if (m_UseBModeFilter) imageUS = ApplyBmodeFilter(imageUS, true); // the US Images get a logarithmic filter if (m_VerticalSpacing) imageUS = ResampleOutputVertical(imageUS, m_VerticalSpacing); - } + } imageVector[0] = imagePA; imageVector[1] = imageUS; } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyBmodeFilter(mitk::Image::Pointer image, bool useLogFilter) { // we use this seperate ApplyBmodeFilter Method for processing of two-dimensional images // the image needs to be of floating point type for the envelope filter to work; the casting is done automatically by the CastToItkImage typedef itk::BModeImageFilter < itkFloatImageType, itkFloatImageType > BModeFilterType; BModeFilterType::Pointer bModeFilter = BModeFilterType::New(); // LogFilter typedef itk::PhotoacousticBModeImageFilter < itkFloatImageType, itkFloatImageType > PhotoacousticBModeImageFilter; PhotoacousticBModeImageFilter::Pointer photoacousticBModeFilter = PhotoacousticBModeImageFilter::New(); // No LogFilter itkFloatImageType::Pointer itkImage; itkFloatImageType::Pointer bmode; mitk::CastToItkImage(image, itkImage); if (useLogFilter) { bModeFilter->SetInput(itkImage); bModeFilter->SetDirection(1); bmode = bModeFilter->GetOutput(); } else { photoacousticBModeFilter->SetInput(itkImage); photoacousticBModeFilter->SetDirection(1); bmode = photoacousticBModeFilter->GetOutput(); } return mitk::GrabItkImageMemory(bmode); } mitk::Image::Pointer mitk::USDiPhASImageSource::CutOffTop(mitk::Image::Pointer image, int cutOffSize) { typedef itk::CropImageFilter < itkFloatImageType, itkFloatImageType > CutImageFilter; itkFloatImageType::SizeType cropSize; itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(image, itkImage); cropSize[0] = 0; if(itkImage->GetLargestPossibleRegion().GetSize()[1] == 2048) cropSize[1] = cutOffSize; else cropSize[1] = 0; cropSize[2] = 0; CutImageFilter::Pointer cutOffFilter = CutImageFilter::New(); cutOffFilter->SetInput(itkImage); cutOffFilter->SetLowerBoundaryCropSize(cropSize); cutOffFilter->UpdateLargestPossibleRegion(); return mitk::GrabItkImageMemory(cutOffFilter->GetOutput()); } mitk::Image::Pointer mitk::USDiPhASImageSource::ResampleOutputVertical(mitk::Image::Pointer image, float verticalSpacing) { typedef itk::ResampleImageFilter < itkFloatImageType, itkFloatImageType > ResampleImageFilter; ResampleImageFilter::Pointer resampleImageFilter = ResampleImageFilter::New(); itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(image, itkImage); itkFloatImageType::SpacingType outputSpacing; itkFloatImageType::SizeType inputSize = itkImage->GetLargestPossibleRegion().GetSize(); itkFloatImageType::SizeType outputSize = inputSize; outputSpacing[0] = itkImage->GetSpacing()[0] * (static_cast(inputSize[0]) / static_cast(outputSize[0])); outputSpacing[1] = verticalSpacing; outputSpacing[2] = itkImage->GetSpacing()[2]; outputSize[1] = inputSize[1] * itkImage->GetSpacing()[1] / outputSpacing[1]; typedef itk::IdentityTransform TransformType; resampleImageFilter->SetInput(itkImage); resampleImageFilter->SetSize(outputSize); resampleImageFilter->SetOutputSpacing(outputSpacing); resampleImageFilter->SetTransform(TransformType::New()); resampleImageFilter->UpdateLargestPossibleRegion(); return mitk::GrabItkImageMemory(resampleImageFilter->GetOutput()); } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyScatteringCompensation(mitk::Image::Pointer inputImage, int scattering) { typedef itk::MultiplyImageFilter MultiplyImageFilterType; itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); MultiplyImageFilterType::Pointer multiplyFilter = MultiplyImageFilterType::New(); multiplyFilter->SetInput1(itkImage); multiplyFilter->SetInput2(m_FluenceCompResizedItk.at(m_ScatteringCoefficient)); return mitk::GrabItkImageMemory(multiplyFilter->GetOutput()); } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyResampling(mitk::Image::Pointer inputImage, mitk::Vector3D outputSpacing, unsigned int outputSize[3]) { typedef itk::ResampleImageFilter < itkFloatImageType, itkFloatImageType > ResampleImageFilter; ResampleImageFilter::Pointer resampleImageFilter = ResampleImageFilter::New(); itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); itkFloatImageType::SpacingType outputSpacingItk; itkFloatImageType::SizeType inputSizeItk = itkImage->GetLargestPossibleRegion().GetSize(); itkFloatImageType::SizeType outputSizeItk = inputSizeItk; itkFloatImageType::SpacingType inputSpacing = itkImage->GetSpacing(); outputSizeItk[0] = outputSize[0]; outputSizeItk[1] = 10 * (inputSpacing[1] * inputSizeItk[1]) / (outputSpacing[1]); outputSizeItk[2] = 1; outputSpacingItk[0] = 0.996 * inputSpacing[0] * (static_cast(inputSizeItk[0]) / static_cast(outputSizeItk[0])); // TODO: find out why the spacing is not correct, so we need that factor; ?!?! outputSpacingItk[1] = inputSpacing[1] * (static_cast(inputSizeItk[1]) / static_cast(outputSizeItk[1])); outputSpacingItk[2] = outputSpacing[2]; typedef itk::IdentityTransform TransformType; resampleImageFilter->SetInput(itkImage); resampleImageFilter->SetSize(outputSizeItk); resampleImageFilter->SetOutputSpacing(outputSpacingItk); resampleImageFilter->SetTransform(TransformType::New()); resampleImageFilter->UpdateLargestPossibleRegion(); return mitk::GrabItkImageMemory(resampleImageFilter->GetOutput()); } mitk::Image::Pointer mitk::USDiPhASImageSource::MultiplyImage(mitk::Image::Pointer inputImage, double value) { typedef itk::MultiplyImageFilter MultiplyImageFilterType; itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); MultiplyImageFilterType::Pointer multiplyFilter = MultiplyImageFilterType::New(); multiplyFilter->SetInput1(itkImage); multiplyFilter->SetConstant(value); return mitk::GrabItkImageMemory(multiplyFilter->GetOutput()); } void mitk::USDiPhASImageSource::ImageDataCallback( short* rfDataChannelData, int& channelDataChannelsPerDataset, int& channelDataSamplesPerChannel, int& channelDataTotalDatasets, short* rfDataArrayBeamformed, int& beamformedLines, int& beamformedSamples, int& beamformedTotalDatasets, unsigned char* imageData, int& imageWidth, int& imageHeight, int& imageBytesPerPixel, int& imageSetsTotal, double& timeStamp) { if (m_DataTypeModified) return; if (!m_PyroConnected) { m_Pyro = mitk::OphirPyro::New(); MITK_INFO << "[Pyro Debug] OpenConnection: " << m_Pyro->OpenConnection(); MITK_INFO << "[Pyro Debug] StartDataAcquisition: " << m_Pyro->StartDataAcquisition(); m_PyroConnected = true; } bool writeImage = ((m_DataType == DataType::Image_uChar) && (imageData != nullptr)) || ((m_DataType == DataType::Beamformed_Short) && (rfDataArrayBeamformed != nullptr)); if (writeImage) { - unsigned int imageDimensions[3]; //get the timestamp we will save later on m_CurrentImageTimestamp = std::chrono::high_resolution_clock::now().time_since_epoch().count(); // create new images and initialize them mitk::Image::Pointer imageUS = mitk::Image::New(); mitk::Image::Pointer imagePA = mitk::Image::New(); - + switch (m_DataType) { case DataType::Image_uChar: { + unsigned int imageDimensions[3]; imageDimensions[0] = imageWidth; imageDimensions[1] = imageHeight; - imageDimensions[2] = imageSetsTotal; - imageUS->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); + imageDimensions[2] = 1; imagePA->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); - imageUS->GetGeometry()->SetSpacing(m_ImageSpacing); - imageUS->GetGeometry()->Modified(); + imageUS->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); + imagePA->SetSpacing(m_ImageSpacing); + imageUS->SetSpacing(m_ImageSpacing); break; } case DataType::Beamformed_Short: { + unsigned int imageDimensions[3]; imageDimensions[0] = beamformedLines; imageDimensions[1] = beamformedSamples; - imageDimensions[2] = beamformedTotalDatasets; + imageDimensions[2] = 1; + imagePA->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); imageUS->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); - imagePA->Initialize(mitk::MakeScalarPixelType(), 3, imageDimensions); - imagePA->GetGeometry()->SetSpacing(m_ImageSpacing); - imagePA->GetGeometry()->Modified(); + imagePA->SetSpacing(m_ImageSpacing); + imageUS->SetSpacing(m_ImageSpacing); break; } } - + // write the given buffer into the image switch (m_DataType) { case DataType::Image_uChar: { if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) { imagePA->SetSlice(imageData); imageUS->SetVolume(&imageData[imageHeight*imageWidth]); } else { imageUS->SetVolume(imageData); } break; } case DataType::Beamformed_Short: { short* flipme = new short[beamformedLines*beamformedSamples*beamformedTotalDatasets]; int pixelsPerImage = beamformedLines*beamformedSamples; for (unsigned char currentSet = 0; currentSet < beamformedTotalDatasets; currentSet++) { for (unsigned short sample = 0; sample < beamformedSamples; sample++) { for (unsigned short line = 0; line < beamformedLines; line++) { flipme[sample*beamformedLines + line + pixelsPerImage*currentSet] = rfDataArrayBeamformed[line*beamformedSamples + sample + pixelsPerImage*currentSet]; } - } // the beamformed pa image is flipped by 90 degrees; we need to flip it manually + } } if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) { - imagePA->SetImportVolume(flipme, 0, 0, mitk::Image::ManageMemory); - imageUS->SetImportVolume(&flipme[beamformedLines*beamformedSamples], 0, 0, mitk::Image::ManageMemory); + imagePA->SetImportVolume(flipme, 0, 0, mitk::Image::RtlCopyMemory); + imageUS->SetImportVolume(&(flipme[beamformedLines*beamformedSamples]), 0, 0, mitk::Image::RtlCopyMemory); } else { - imageUS->SetImportVolume(flipme, 0, 0, mitk::Image::ManageMemory); + imageUS->SetImportVolume(flipme, 0, 0, mitk::Image::RtlCopyMemory); } break; } } - + if (m_SavingSettings.saveRaw && m_CurrentlyRecording && rfDataChannelData != nullptr) { - unsigned int dim[3]; - dim[0] = channelDataChannelsPerDataset; - dim[1] = channelDataSamplesPerChannel; - dim[2] = 1; short offset = m_Device->GetScanMode().accumulation * 2048; short* noOffset = new short[channelDataChannelsPerDataset*channelDataSamplesPerChannel*channelDataTotalDatasets]; for (unsigned char set = 0; set < channelDataTotalDatasets; ++set) { for (unsigned short sam = 0; sam < channelDataSamplesPerChannel; ++sam) { for (unsigned short chan = 0; chan < channelDataChannelsPerDataset; ++chan) { noOffset[set*channelDataSamplesPerChannel*channelDataChannelsPerDataset + sam * channelDataChannelsPerDataset + chan] = rfDataChannelData[set*channelDataSamplesPerChannel*channelDataChannelsPerDataset + sam * channelDataChannelsPerDataset + chan] - offset; // this offset in the raw Images is given by the API... } } } mitk::Image::Pointer rawImageUS = mitk::Image::New(); mitk::Image::Pointer rawImagePA = mitk::Image::New(); + unsigned int dim[3]; + dim[0] = channelDataChannelsPerDataset; + dim[1] = channelDataSamplesPerChannel; + dim[2] = 1; + mitk::Vector3D rawSpacing; rawSpacing[0] = m_Device->GetScanMode().transducerPitchMeter * 1000; // save in mm rawSpacing[1] = m_Device->GetScanMode().receivePhaseLengthSeconds / channelDataSamplesPerChannel * 1000000; // save in us rawSpacing[2] = 1; - rawImageUS->GetGeometry()->SetSpacing(rawSpacing); - rawImageUS->GetGeometry()->Modified(); - rawImagePA->GetGeometry()->SetSpacing(rawSpacing); - rawImagePA->GetGeometry()->Modified(); + rawImagePA->Initialize(mitk::MakeScalarPixelType(), 3, dim); + dim[2] = channelDataTotalDatasets - 1; + rawImageUS->Initialize(mitk::MakeScalarPixelType(), 3, dim); + + rawImagePA->SetSpacing(rawSpacing); + rawImageUS->SetSpacing(rawSpacing); if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) { - rawImagePA->SetImportVolume(noOffset, 0, 0, mitk::Image::ManageMemory); - rawImageUS->SetImportVolume(&noOffset[channelDataChannelsPerDataset*channelDataSamplesPerChannel], 0, 0, mitk::Image::ManageMemory); + rawImagePA->SetImportVolume(noOffset, 0, 0, mitk::Image::RtlCopyMemory); + rawImageUS->SetImportVolume(&noOffset[channelDataChannelsPerDataset*channelDataSamplesPerChannel], 0, 0, mitk::Image::RtlCopyMemory); } else { - rawImageUS->SetImportVolume(noOffset, 0, 0, mitk::Image::ManageMemory); + rawImageUS->SetImportVolume(noOffset, 0, 0, mitk::Image::RtlCopyMemory); } // save the raw images when recording m_RawRecordedImages.push_back(std::pair(rawImagePA, rawImageUS)); } if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) { itk::Index<3> pixel = { { (itk::Index<3>::IndexValueType)(imagePA->GetDimension(0) / 2), (itk::Index<3>::IndexValueType)(22.0 / 532.0*m_Device->GetScanMode().reconstructionSamplesPerLine), 0 } }; //22/532*2048 = 84 if (!m_Pyro->IsSyncDelaySet() && (imagePA->GetPixelValueByIndex(pixel) < -30)) // #MagicNumber { MITK_INFO << "Setting SyncDelay now"; m_Pyro->SetSyncDelay(m_CurrentImageTimestamp); } } m_ImageTimestampBuffer[(m_LastWrittenImage + 1) % m_BufferSize] = m_CurrentImageTimestamp; + m_ImageBuffer[(m_LastWrittenImage + 1) % m_BufferSize] = std::pair(imagePA, imageUS); m_LastWrittenImage = (m_LastWrittenImage + 1) % m_BufferSize; // if the user decides to start recording, we feed the vector the generated images if (m_CurrentlyRecording) { m_RecordedImages.push_back(std::pair(imagePA, imageUS)); m_ImageTimestampRecord.push_back(m_CurrentImageTimestamp); // save timestamps for each PA image! } } } void mitk::USDiPhASImageSource::UpdateImageGeometry() { MITK_INFO << "Retreaving Image Geometry Information for Spacing..."; float& recordTime = m_Device->GetScanMode().receivePhaseLengthSeconds; int& speedOfSound = m_Device->GetScanMode().averageSpeedOfSound; float& pitch = m_Device->GetScanMode().reconstructedLinePitchMmOrAngleDegree; int& reconstructionLines = m_Device->GetScanMode().reconstructionLines; switch (m_DataType) { case DataType::Image_uChar : { int& imageWidth = m_Device->GetScanMode().imageWidth; int& imageHeight = m_Device->GetScanMode().imageHeight; m_ImageSpacing[0] = pitch * reconstructionLines / imageWidth; m_ImageSpacing[1] = recordTime * speedOfSound / 2 * 1000 / imageHeight; break; } case DataType::Beamformed_Short : { int& imageWidth = reconstructionLines; int& imageHeight = m_Device->GetScanMode().reconstructionSamplesPerLine; m_ImageSpacing[0] = pitch; m_ImageSpacing[1] = recordTime * speedOfSound / 2 * 1000 / imageHeight; break; } } m_ImageSpacing[2] = 1; MITK_INFO << "Retreaving Image Geometry Information for Spacing " << m_ImageSpacing[0] << " ... " << m_ImageSpacing[1] << " ... " << m_ImageSpacing[2] << " ...[DONE]"; } void mitk::USDiPhASImageSource::ModifyDataType(DataType dataT) { m_DataTypeModified = true; m_DataTypeNext = dataT; } void mitk::USDiPhASImageSource::ModifyUseBModeFilter(bool isSet) { m_UseBModeFilterModified = true; m_UseBModeFilterNext = isSet; } void mitk::USDiPhASImageSource::ModifyScatteringCoefficient(int coeff) { m_ScatteringCoefficientNext = coeff; m_ScatteringCoefficientModified = true; } void mitk::USDiPhASImageSource::ModifyCompensateForScattering(bool useIt) { m_CompensateForScatteringNext = useIt; m_CompensateForScatteringModified = true; } void mitk::USDiPhASImageSource::ModifyEnergyCompensation(bool compensate) { m_CompensateEnergyNext = compensate; m_CompensateEnergyModified = true; } void mitk::USDiPhASImageSource::SetDataType(DataType dataT) { if (dataT != m_DataType) { m_DataType = dataT; MITK_INFO << "Setting new DataType..." << dataT; switch (m_DataType) { case DataType::Image_uChar : MITK_INFO << "height: " << m_Device->GetScanMode().imageHeight << " width: " << m_Device->GetScanMode().imageWidth; break; case DataType::Beamformed_Short : MITK_INFO << "samples: " << m_Device->GetScanMode().reconstructionSamplesPerLine << " lines: " << m_Device->GetScanMode().reconstructionLines; break; } } } void mitk::USDiPhASImageSource::SetGUIOutput(std::function out) { USDiPhASImageSource::m_GUIOutput = out; m_StartTime = ((float)std::clock()) / CLOCKS_PER_SEC; //wait till the callback is available again m_UseGUIOutPut = false; } void mitk::USDiPhASImageSource::SetUseBModeFilter(bool isSet) { m_UseBModeFilter = isSet; } void mitk::USDiPhASImageSource::SetVerticalSpacing(float mm) { m_VerticalSpacingNext = mm; m_VerticalSpacingModified = true; } void mitk::USDiPhASImageSource::SetSavingSettings(SavingSettings settings) { m_SavingSettings = settings; } // this is just a little function to set the filenames below right inline void replaceAll(std::string& str, const std::string& from, const std::string& to) { if (from.empty()) return; size_t start_pos = 0; while ((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' } } void mitk::USDiPhASImageSource::SetRecordingStatus(bool record) { // start the recording process if (record) { m_RecordedImages.clear(); m_RawRecordedImages.clear(); // we make sure there are no leftovers m_ImageTimestampRecord.clear(); // also for the timestamps m_PixelValues.clear(); // aaaand for the pixel values if (m_SavingSettings.saveRaw) { m_Device->GetScanMode().transferChannelData = true; m_Device->UpdateScanmode(); // set the raw Data to be transfered } // tell the callback to start recording images m_CurrentlyRecording = true; } // save images, end recording, and clean up else { m_CurrentlyRecording = false; m_Device->GetScanMode().transferChannelData = false; // make sure raw Channel Data is not transferred anymore! m_Device->UpdateScanmode(); // get the time and date, put them into a nice string and create a folder for the images time_t time = std::time(nullptr); time_t* timeptr = &time; std::string currentDate = std::ctime(timeptr); replaceAll(currentDate, ":", "-"); currentDate.pop_back(); //std::string MakeFolder = "mkdir \"c:/DiPhASImageData/" + currentDate + "\""; //system(MakeFolder.c_str()); // initialize file paths and the images Image::Pointer PAImage = Image::New(); Image::Pointer USImage = Image::New(); std::string pathPA = "c:\\ImageData\\" + currentDate + "-" + "PAbeamformed" + ".nrrd"; std::string pathUS = "c:\\ImageData\\" + currentDate + "-" + "USImages" + ".nrrd"; std::string pathTS = "c:\\ImageData\\" + currentDate + "-" + "ts" + ".csv"; std::string pathS = "c:\\ImageData\\" + currentDate + "-" + "Settings" + ".txt"; // idon't forget the raw Images (if chosen to be saved) Image::Pointer PAImageRaw = Image::New(); Image::Pointer USImageRaw = Image::New(); std::string pathPARaw = "c:\\ImageData\\" + currentDate + "-" + "PAraw" + ".nrrd"; std::string pathUSRaw = "c:\\ImageData\\" + currentDate + "-" + "USImagesRaw" + ".nrrd"; if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) // save a PAImage if we used interleaved mode { // first, save the data, so the pyro does not aquire more unneccessary timestamps m_Pyro->SaveData(); // now order the images and save them // the beamformed ones... if (m_SavingSettings.saveBeamformed) { OrderImagesInterleaved(PAImage, USImage, m_RecordedImages, false); mitk::IOUtil::Save(USImage, pathUS); mitk::IOUtil::Save(PAImage, pathPA); } // ...and the raw images if (m_SavingSettings.saveRaw) { OrderImagesInterleaved(PAImageRaw, USImageRaw, m_RawRecordedImages, true); // mitk::IOUtil::Save(USImageRaw, pathUSRaw); mitk::IOUtil::Save(PAImageRaw, pathPARaw); } // read the pixelvalues of the enveloped images at this position if (m_RecordedImages.at(0).first != nullptr && m_RecordedImages.at(0).first.IsNotNull()) { itk::Index<3> pixel = { { (itk::Index<3>::IndexValueType)(m_RecordedImages.at(0).first->GetDimension(0) / 2), (itk::Index<3>::IndexValueType)(22.0 / 532.0*m_Device->GetScanMode().reconstructionSamplesPerLine), 0 } }; //22/532*2048 = 84 GetPixelValues(pixel, m_PixelValues); // write the Pixelvalues to m_PixelValues } // save the timestamps! ofstream timestampFile; timestampFile.open(pathTS); timestampFile << ",timestamp,pixelvalue"; // write the header for (int index = 0; index < m_ImageTimestampRecord.size(); ++index) { timestampFile << "\n" << index << "," << m_ImageTimestampRecord.at(index) << "," << m_PixelValues.at(index); } timestampFile.close(); //save the settings! ofstream settingsFile; settingsFile.open(pathS); auto& sM = m_Device->GetScanMode(); settingsFile << "[General Parameters]\n"; settingsFile << "Scan Depth [mm] = " << sM.receivePhaseLengthSeconds * sM.averageSpeedOfSound / 2 * 1000 << "\n"; settingsFile << "Speed of Sound [m/s] = " << sM.averageSpeedOfSound << "\n"; settingsFile << "Excitation Frequency [MHz] = " << sM.transducerFrequencyHz/1000000 << "\n"; settingsFile << "Voltage [V] = " << sM.voltageV << "\n"; settingsFile << "TGC min = " << (int)sM.tgcdB[0] << " max = " << (int)sM.tgcdB[7] << "\n"; settingsFile << "[Beamforming Parameters]\n"; settingsFile << "Reconstructed Lines = " << sM.reconstructionLines << "\n"; settingsFile << "Samples per Line = " << sM.reconstructionSamplesPerLine << "\n"; settingsFile.close(); } else if (m_Device->GetScanMode().beamformingAlgorithm == (int)Beamforming::PlaneWaveCompound) // save no PAImage if we used US only mode { OrderImagesUltrasound(USImage, m_RecordedImages); mitk::IOUtil::Save(USImage, pathUS); } m_PixelValues.clear(); m_RawRecordedImages.clear(); // clean up the pixel values m_RecordedImages.clear(); // clean up the images m_ImageTimestampRecord.clear(); // clean up the timestamps } } void mitk::USDiPhASImageSource::GetPixelValues(itk::Index<3> pixel, std::vector& values) { unsigned int events = 2; for (int index = 0; index < m_RecordedImages.size(); index += events) // omit sound images { Image::Pointer image = m_RecordedImages.at(index).first; if (image != nullptr && image.IsNotNull()) { image = ApplyBmodeFilter(image); values.push_back(image.GetPointer()->GetPixelValueByIndex(pixel)); } } } void mitk::USDiPhASImageSource::OrderImagesInterleaved(Image::Pointer PAImage, Image::Pointer USImage, std::vector> recordedList, bool raw) { unsigned int width = 32; unsigned int height = 32; unsigned int events = m_Device->GetScanMode().transmitEventsCount; if (!raw) events = 1; // the beamformed image array contains only the resulting image of multiple events if (raw) { width = recordedList.at(0).first->GetDimension(0); height = recordedList.at(0).first->GetDimension(1); } else if (m_DataType == DataType::Beamformed_Short) { width = m_Device->GetScanMode().reconstructionLines; height = m_Device->GetScanMode().reconstructionSamplesPerLine; } else if (m_DataType == DataType::Image_uChar) { width = m_Device->GetScanMode().imageWidth; height = m_Device->GetScanMode().imageHeight; } unsigned int dimLaser[] = { width, height, (unsigned int)recordedList.size()}; unsigned int dimSound[] = { width, height, (unsigned int)(recordedList.size() * events)}; PAImage->Initialize(recordedList.back().first->GetPixelType(), 3, dimLaser); PAImage->SetSpacing(recordedList.back().first->GetGeometry()->GetSpacing()); USImage->Initialize(recordedList.back().first->GetPixelType(), 3, dimSound); USImage->SetSpacing(recordedList.back().first->GetGeometry()->GetSpacing()); for (int index = 0; index < recordedList.size(); ++index) { mitk::ImageReadAccessor inputReadAccessorPA(recordedList.at(index).first); PAImage->SetSlice(inputReadAccessorPA.GetData(), index); for (unsigned int i = 0; i < events; ++i) { mitk::ImageReadAccessor inputReadAccessorUS(recordedList.at(index).second, recordedList.at(index).second->GetSliceData(i)); USImage->SetSlice(inputReadAccessorUS.GetData(), index + i); } } } void mitk::USDiPhASImageSource::OrderImagesUltrasound(Image::Pointer USImage, std::vector> recordedList) { unsigned int width = 32; unsigned int height = 32; unsigned int events = m_Device->GetScanMode().transmitEventsCount; if (m_DataType == DataType::Beamformed_Short) { width = (unsigned int)m_Device->GetScanMode().reconstructionLines; height = (unsigned int)m_Device->GetScanMode().reconstructionSamplesPerLine; } else if (m_DataType == DataType::Image_uChar) { width = (unsigned int)m_Device->GetScanMode().imageWidth; height = (unsigned int)m_Device->GetScanMode().imageHeight; } unsigned int dimSound[] = { width, height, (unsigned int)(recordedList.size() * events) }; USImage->Initialize(recordedList.back().second->GetPixelType(), 3, dimSound); USImage->SetSpacing(recordedList.back().second->GetGeometry()->GetSpacing()); for (int index = 0; index < recordedList.size(); ++index) { for (unsigned int i = 0; i < events; ++i) { mitk::ImageReadAccessor inputReadAccessorUS(recordedList.at(index).second, recordedList.at(index).second->GetSliceData(i)); USImage->SetSlice(inputReadAccessorUS.GetData(), index + i); } } } \ No newline at end of file