diff --git a/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp b/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp index cd45b91917..720c5cf19c 100644 --- a/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp +++ b/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp @@ -1,284 +1,310 @@ /*=================================================================== 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. ===================================================================*/ #include "mitkOphirPyro.h" #include #include #include #include mitk::OphirPyro::OphirPyro() : m_CurrentWavelength(0), m_DeviceHandle(0), m_Connected(false), m_Streaming(false), m_SerialNumber(nullptr), m_GetDataThread(), m_ImagePyroDelay(0), m_EnergyMultiplicator(60000) { m_PulseEnergy.clear(); m_PulseTime.clear(); m_PulseStatus.clear(); m_TimeStamps.clear(); } mitk::OphirPyro::~OphirPyro() { if (m_Connected) + { this->CloseConnection(); + if (m_GetDataThread.joinable()) + { + m_GetDataThread.join(); + MITK_INFO << "[OphirPyro Debug] joined data thread"; + } + } MITK_INFO << "[OphirPyro Debug] destroying that Pyro"; /* cleanup thread */ - if (m_GetDataThread.joinable()) - { - m_GetDataThread.join(); - MITK_INFO << "[OphirPyro Debug] joined data thread"; - } + } bool mitk::OphirPyro::StartDataAcquisition() { if (ophirAPI.StartStream(m_DeviceHandle)) { m_Streaming = true; m_GetDataThread = std::thread(&mitk::OphirPyro::GetDataFromSensorThread, this); } - return m_Streaming; } // 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::OphirPyro::SaveCsvData() { // 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:/DiPhASTimeStamps/" + currentDate + "\""; system(MakeFolder.c_str()); std::string pathTS = "c:\\DiPhASTimeStamps\\" + currentDate + " Timestamps" + ".csv"; std::ofstream timestampFile; timestampFile.open(pathTS); timestampFile << ",timestamp,PulseEnergy,PulseTime"; for (int index = 0; index < m_TimeStamps.size(); ++index) { timestampFile << "\n" << index << "," << m_TimeStamps.at(index) << ","<< m_PulseEnergy.at(index) << "," << (long)m_PulseTime.at(index); } timestampFile.close(); } bool mitk::OphirPyro::StopDataAcquisition() { if (ophirAPI.StopStream(m_DeviceHandle)) m_Streaming = false; SaveCsvData(); MITK_INFO << "[OphirPyro Debug] m_Streaming = "<< m_Streaming; - + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (m_GetDataThread.joinable()) + { + m_GetDataThread.join(); + } return !m_Streaming; } unsigned int mitk::OphirPyro::GetDataFromSensor() { if (m_Streaming) { std::vector newEnergy; std::vector newTimestamp; std::vector newStatus; unsigned int noPackages = 0; try { noPackages = ophirAPI.GetData(m_DeviceHandle, &newEnergy, &newTimestamp, &newStatus); if (noPackages > 0) { m_PulseEnergy.insert(m_PulseEnergy.end(), newEnergy.begin(), newEnergy.end()); for (int i=0; iGetDataFromSensor(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } return; } double mitk::OphirPyro::LookupCurrentPulseEnergy() { if (m_Connected && !m_PulseEnergy.empty()) { MITK_INFO << m_PulseEnergy.size(); return m_PulseEnergy.back(); } return 0; } double mitk::OphirPyro::GetClosestEnergyInmJ(long long ImageTimeStamp, double interval) { - if (!(m_PulseTime.size() > 0)) + if (m_PulseTime.size() == 0) return 0; - long long searchTime = (ImageTimeStamp/1000000) - m_ImagePyroDelay; // conversion from ns to ms - int foundIndex = 0; - long long shortestDifference = 5*interval; + long long searchTime = (ImageTimeStamp/1000000) + m_ImagePyroDelay; // conversion from ns to ms + + //MITK_INFO << "searchTime = " << searchTime; + int foundIndex = -1; + long long shortestDifference = 250*interval; // search the list for a fitting energy value time for (int index = 0; index < m_PulseTime.size();++index) { - if ((m_PulseTime[index] - searchTime) < shortestDifference) + long long newDifference = abs(((int)m_PulseTime[index]) - searchTime); + MITK_INFO << "newDifference[" << index << "] = " << newDifference; + if (newDifference < shortestDifference) { - shortestDifference = m_PulseTime[index] - searchTime; + shortestDifference = newDifference; foundIndex = index; + //MITK_INFO << "foundIndex = " << foundIndex; + } } if (abs(shortestDifference) < interval) { // delete all elements before the one found m_PulseEnergy.erase(m_PulseEnergy.begin(), m_PulseEnergy.begin() + foundIndex); m_PulseTime.erase(m_PulseTime.begin(), m_PulseTime.begin() + foundIndex); m_PulseStatus.erase(m_PulseStatus.begin(), m_PulseStatus.begin() + foundIndex); // multipy with m_EnergyMultiplicator, because the Pyro gives just a fraction of the actual Laser Energy return (GetNextPulseEnergy()*m_EnergyMultiplicator); + MITK_INFO << "found " << shortestDifference; + } - MITK_INFO << "No matching energy value for image found in interval of " << interval << "ms."; + MITK_INFO << "No matching energy value for image found in interval of " << interval << "ms. sd: " << shortestDifference; return -1; } -double mitk::OphirPyro::GetNextEnergyInmj(long long ImageTimeStamp, double interval) +double mitk::OphirPyro::GetNextEnergyInmJ(long long ImageTimeStamp, double interval) { if (m_Connected && !(m_PulseTime.size() > 0)) return 0; long long searchTime = (ImageTimeStamp / 1000000) - m_ImagePyroDelay; // conversion from ns to ms if (abs(searchTime - m_PulseTime.front()) < interval) { return (GetNextPulseEnergy()*m_EnergyMultiplicator); // multipy with m_EnergyMultiplicator, because the Pyro gives just a fraction of the actual Laser Energy } MITK_INFO << "Image aquisition and energy measurement ran out of sync"; return -1; } void mitk::OphirPyro::SetSyncDelay(long long FirstImageTimeStamp) { + while (!m_PulseTime.size()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } m_ImagePyroDelay = (FirstImageTimeStamp / 1000000) - m_PulseTime.at(0); + + MITK_INFO << "m_ImagePyroDelay = " << m_ImagePyroDelay; + return; +} + +bool mitk::OphirPyro::IsSyncDelaySet() +{ + return (m_ImagePyroDelay != 0); } double mitk::OphirPyro::GetNextPulseEnergy() { if (m_Connected && m_PulseEnergy.size()>=1) { double out = m_PulseEnergy.front(); m_PulseEnergy.erase(m_PulseEnergy.begin()); m_PulseTime.erase(m_PulseTime.begin()); m_PulseStatus.erase(m_PulseStatus.begin()); return out; } return 0; } double mitk::OphirPyro::LookupCurrentPulseEnergy(double* timestamp, int* status) { if (m_Connected) { *timestamp = m_PulseTime.back(); *status = m_PulseStatus.back(); return m_PulseEnergy.back(); } return 0; } double mitk::OphirPyro::GetNextPulseEnergy(double* timestamp, int* status) { if (m_Connected) { double out = m_PulseEnergy.front(); *timestamp = m_PulseTime.front(); *status = m_PulseStatus.front(); m_PulseEnergy.erase(m_PulseEnergy.begin()); m_PulseTime.erase(m_PulseTime.begin()); m_PulseStatus.erase(m_PulseStatus.begin()); return out; } return 0; } bool mitk::OphirPyro::OpenConnection() { if (!m_Connected) { char* m_SerialNumber = ophirAPI.ScanUSB(); if (m_SerialNumber != 0) { m_DeviceHandle = ophirAPI.OpenDevice(m_SerialNumber); if (m_DeviceHandle != 0) { m_Connected = true; return true; } } } return false; } bool mitk::OphirPyro::CloseConnection() { if (m_Connected) { bool closed = ophirAPI.CloseDevice(m_DeviceHandle); if (closed) m_DeviceHandle = 0; m_Connected = !closed; return closed; } return false; } \ No newline at end of file diff --git a/Modules/PhotoacousticsHardware/mitkOphirPyro.h b/Modules/PhotoacousticsHardware/mitkOphirPyro.h index f4c71380f9..dd906408c0 100644 --- a/Modules/PhotoacousticsHardware/mitkOphirPyro.h +++ b/Modules/PhotoacousticsHardware/mitkOphirPyro.h @@ -1,85 +1,87 @@ /*=================================================================== 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. ===================================================================*/ #ifndef MITKOPHIRPYRO_H_HEADER_INCLUDED #define MITKOPHIRPYRO_H_HEADER_INCLUDED #include "itkObject.h" #include "mitkCommon.h" #include "vector" #include "MitkPhotoacousticsHardwareExports.h" #include "OphirPyroWrapper.h" #include #include #include #include #include #include #include #include #include namespace mitk { class MITKPHOTOACOUSTICSHARDWARE_EXPORT OphirPyro : public itk::Object { public: mitkClassMacroItkParent(mitk::OphirPyro, itk::Object); itkFactorylessNewMacro(Self); virtual bool OpenConnection(); virtual bool CloseConnection(); virtual bool StartDataAcquisition(); virtual bool StopDataAcquisition(); unsigned int GetDataFromSensor(); void GetDataFromSensorThread(); virtual double LookupCurrentPulseEnergy(); virtual double GetNextPulseEnergy(); virtual double LookupCurrentPulseEnergy(double* timestamp, int* status); virtual double GetNextPulseEnergy(double* timestamp, int* status); virtual double GetClosestEnergyInmJ(long long ImageTimeStamp, double interval=20); virtual double GetNextEnergyInmJ(long long ImageTimeStamp, double interval = 20); virtual void SetSyncDelay(long long FirstImageTimeStamp); + virtual bool IsSyncDelaySet(); + protected: OphirPyro(); virtual ~OphirPyro(); void SaveCsvData(); OphirPyroWrapper ophirAPI; char* m_SerialNumber; int m_DeviceHandle; bool m_Connected; bool m_Streaming; std::vector m_PulseEnergy; std::vector m_PulseTime; std::vector m_PulseStatus; std::vector m_TimeStamps; double m_CurrentWavelength; double m_CurrentEnergyRange; long long m_ImagePyroDelay; float m_EnergyMultiplicator; std::thread m_GetDataThread; }; } // namespace mitk #endif /* MITKOPHIRPYRO_H_HEADER_INCLUDED */ diff --git a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp index 7a2ac22b0c..8f3d79215c 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp @@ -1,530 +1,528 @@ /*=================================================================== 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 // mitk dependencies #include "mitkUSDiPhASDevice.h" #include "mitkUSDiPhASImageSource.h" #include #include "mitkUSDiPhASBModeImageFilter.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 mitk::USDiPhASImageSource::USDiPhASImageSource(mitk::USDiPhASDevice* device) : m_Image(mitk::Image::New()), m_device(device), startTime(((float)std::clock()) / CLOCKS_PER_SEC), useGUIOutPut(false), m_DataType(DataType::Image_uChar), m_GUIOutput(nullptr), useBModeFilter(false), currentlyRecording(false), m_DataTypeModified(true), m_DataTypeNext(DataType::Image_uChar), m_UseBModeFilterModified(false), m_UseBModeFilterNext(false), - m_currenttime(0), + m_CurrentImageTimestamp(0), m_PyroConnected(false) { m_ImageMutex = itk::FastMutexLock::New(); m_BufferSize = 100; m_LastWrittenImage = m_BufferSize - 1; m_ImageBuffer.insert(m_ImageBuffer.begin(), m_BufferSize, nullptr); } -mitk::USDiPhASImageSource::~USDiPhASImageSource( ) +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::GetNextRawImage( mitk::Image::Pointer& image) { // we get this image pointer from the USDevice and write into it the data we got from the DiPhAS API if (m_DataTypeModified) { SetDataType(m_DataTypeNext); m_DataTypeModified = false; } if (m_UseBModeFilterModified) { SetUseBModeFilter(m_UseBModeFilterNext); m_UseBModeFilterModified = false; } image = &(*m_ImageBuffer[m_LastWrittenImage]); - + if (image != nullptr) { // do image processing before displaying it if (useBModeFilter && m_DataType == DataType::Beamformed_Short) { // apply BmodeFilter to the image image = ApplyBmodeFilter(image); } } } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyBmodeFilter2d(mitk::Image::Pointer inputImage) { // we use this seperate ApplyBmodeFilter Method for processing of images that are to be saved later on (see SetRecordingStatus(bool)). // 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::Image< float, 2 > itkInputImageType; typedef itk::Image< short, 2 > itkOutputImageType; typedef itk::PhotoacousticBModeImageFilter < itkInputImageType, itkOutputImageType > PhotoacousticBModeImageFilter; PhotoacousticBModeImageFilter::Pointer photoacousticBModeFilter = PhotoacousticBModeImageFilter::New(); itkInputImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); photoacousticBModeFilter->SetInput(itkImage); photoacousticBModeFilter->SetDirection(1); return mitk::GrabItkImageMemory(photoacousticBModeFilter->GetOutput()); } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyBmodeFilter(mitk::Image::Pointer inputImage) { //MITK_INFO << "Applying BMode Filter"; // 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::Image< float, 3 > itkFloatImageType; typedef itk::PhotoacousticBModeImageFilter < itkFloatImageType, itkFloatImageType > PhotoacousticBModeImageFilter; PhotoacousticBModeImageFilter::Pointer photoacousticBModeFilter = PhotoacousticBModeImageFilter::New(); typedef itk::ResampleImageFilter < itkFloatImageType, itkFloatImageType > ResampleImageFilter; ResampleImageFilter::Pointer resampleImageFilter = ResampleImageFilter::New(); itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); photoacousticBModeFilter->SetInput(itkImage); photoacousticBModeFilter->SetDirection(1); itkFloatImageType::Pointer bmode = photoacousticBModeFilter->GetOutput(); itkFloatImageType::SpacingType outputSpacing; itkFloatImageType::SizeType inputSize = itkImage->GetLargestPossibleRegion().GetSize(); itkFloatImageType::SizeType outputSize = inputSize; outputSize[0] = 256; outputSpacing[0] = itkImage->GetSpacing()[0] * (static_cast(inputSize[0]) / static_cast(outputSize[0])); outputSpacing[1] = outputSpacing[0] / 2; outputSpacing[2] = 0.6; outputSize[1] = inputSize[1] * itkImage->GetSpacing()[1] / outputSpacing[1]; typedef itk::IdentityTransform TransformType; resampleImageFilter->SetInput(bmode); resampleImageFilter->SetSize(outputSize); resampleImageFilter->SetOutputSpacing(outputSpacing); resampleImageFilter->SetTransform(TransformType::New()); resampleImageFilter->UpdateLargestPossibleRegion(); return mitk::GrabItkImageMemory(resampleImageFilter->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_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)) && !m_Image.IsNull(); if (writeImage) { - //get the timestamp we might later on save - m_currenttime = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + //get the timestamp we might save later on + m_CurrentImageTimestamp = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + m_ImageTimestamps.push_back(m_CurrentImageTimestamp); + if (m_ImageTimestamps.size()) + { + float cf = m_Pyro->GetClosestEnergyInmJ(m_CurrentImageTimestamp); + } // create a new image and initialize it mitk::Image::Pointer image = mitk::Image::New(); switch (m_DataType) { case DataType::Image_uChar: { m_ImageDimensions[0] = imageWidth; m_ImageDimensions[1] = imageHeight; m_ImageDimensions[2] = imageSetsTotal; image->Initialize(mitk::MakeScalarPixelType(), 3, m_ImageDimensions); break; } case DataType::Beamformed_Short: { m_ImageDimensions[0] = beamformedLines; m_ImageDimensions[1] = beamformedSamples; m_ImageDimensions[2] = beamformedTotalDatasets; image->Initialize(mitk::MakeScalarPixelType(), 3, m_ImageDimensions); break; } } image->GetGeometry()->SetSpacing(m_ImageSpacing); image->GetGeometry()->Modified(); // write the given buffer into the image switch (m_DataType) { case DataType::Image_uChar: { for (int i = 0; i < imageSetsTotal; i++) { image->SetSlice(&imageData[i*imageHeight*imageWidth], i); } break; } case DataType::Beamformed_Short: { short* flipme = new short[beamformedLines*beamformedSamples*beamformedTotalDatasets]; int pixelsPerImage = beamformedLines*beamformedSamples; for (int currentSet = 0; currentSet < beamformedTotalDatasets; currentSet++) { for (int sample = 0; sample < beamformedSamples; sample++) { for (int 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 } for (int i = 0; i < beamformedTotalDatasets; i++) { image->SetSlice(&flipme[i*beamformedLines*beamformedSamples], i); // set every image to a different slice } delete[] flipme; break; } } + itk::Index<3> pixel = { { (image->GetDimension(0) / 2), 84, 0 } }; //22/532*2048 + if (!m_Pyro->IsSyncDelaySet() &&(image->GetPixelValueByIndex(pixel) < -30)) // #MagicNumber + { + MITK_INFO << "Setting SyncDelay"; + m_Pyro->SetSyncDelay(m_CurrentImageTimestamp); + } + // if the user decides to start recording, we feed the vector the generated images if (currentlyRecording) { for (int index = 0; index < image->GetDimension(2); ++index) { if (image->IsSliceSet(index)) { m_recordedImages.push_back(Image::New()); m_recordedImages.back()->Initialize(image->GetPixelType(), 2, image->GetDimensions()); m_recordedImages.back()->SetGeometry(image->GetGeometry()); mitk::ImageReadAccessor inputReadAccessor(image, image->GetSliceData(index)); m_recordedImages.back()->SetSlice(inputReadAccessor.GetData()); } } // save timestamps for each laser image! - m_timestamps.push_back(m_currenttime); } // [sic!] Thomas: This kills everything, so we commented it out and now it doesnt kill anything.. // if (!useGUIOutPut && m_GUIOutput) { // // Need to do this because the program initializes the GUI twice // // this is probably a bug in UltrasoundSupport, if it's fixed the timing becomes unneccesary // float timePassed = ((float)std::clock()) / CLOCKS_PER_SEC - startTime; // if (timePassed > 10) // { // useGUIOutPut = true; // } // } // if (useGUIOutPut) { // // pass some beamformer state infos to the GUI // getSystemInfo(&BeamformerInfos); // std::ostringstream s; // s << "state info: PRF:" << BeamformerInfos.systemPRF << "Hz, datarate: " << BeamformerInfos.dataTransferRateMBit << "MBit/s"; // m_GUIOutput(QString::fromStdString(s.str())); // } m_ImageBuffer[(m_LastWrittenImage + 1) % m_BufferSize] = image; m_LastWrittenImage = (m_LastWrittenImage + 1) % m_BufferSize; } } 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] = 0.6; 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::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; startTime = ((float)std::clock()) / CLOCKS_PER_SEC; //wait till the callback is available again useGUIOutPut = false; } void mitk::USDiPhASImageSource::SetUseBModeFilter(bool isSet) { useBModeFilter = isSet; } // 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(); // we make sure there are no leftovers - m_timestamps.clear(); // also for the timestamps + m_ImageTimestamps.clear(); // also for the timestamps m_pixelValues.clear(); // aaaand for the pixel values - // start the pyro data acquisition - try - { - 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; - } - else - { - m_PyroConnected = false; - } - } - catch (...) { - MITK_INFO << "While trying to connect to the Pyro an exception was caught (this almost always happens on the first try after reboot - try again!)"; - m_PyroConnected = false; - } - // tell the callback to start recording images currentlyRecording = true; } // save images, end recording, and clean up else { currentlyRecording = false; - // 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; - // 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:\\DiPhASImageData\\" + currentDate + "\\" + "PAImages" + ".nrrd"; std::string pathUS = "c:\\DiPhASImageData\\" + currentDate + "\\" + "USImages" + ".nrrd"; std::string pathTS = "c:\\DiPhASImageData\\" + currentDate + "\\" + "TimestampsImages" + ".csv"; // order the images and save them if (m_device->GetScanMode().beamformingAlgorithm == (int)Beamforming::Interleaved_OA_US) // save a PAImage if we used interleaved mode { bool saveImageData = false; if (saveImageData) { OrderImagesInterleaved(PAImage, USImage); mitk::IOUtil::Save(USImage, pathUS); mitk::IOUtil::Save(PAImage, pathPA); } // read the pixelvalues of the enveloped images at this position itk::Index<3> pixel = { { m_recordedImages.at(1)->GetDimension(0) / 2, 84, 0 } }; //22/532*2048 GetPixelValues(pixel); // save the timestamps! ofstream timestampFile; timestampFile.open(pathTS); timestampFile << ",timestamp,pixelvalue"; // write the header - for (int index = 0; index < m_timestamps.size(); ++index) + for (int index = 0; index < m_ImageTimestamps.size(); ++index) { - timestampFile << "\n" << index << "," << m_timestamps.at(index) << "," << m_pixelValues.at(index); + timestampFile << "\n" << index << "," << m_ImageTimestamps.at(index) << "," << m_pixelValues.at(index); } timestampFile.close(); } else if (m_device->GetScanMode().beamformingAlgorithm == (int)Beamforming::PlaneWaveCompound) // save no PAImage if we used US only mode { OrderImagesUltrasound(USImage); mitk::IOUtil::Save(USImage, pathUS); } m_pixelValues.clear(); // clean up the pixel values m_recordedImages.clear(); // clean up the images - m_timestamps.clear(); // clean up the timestamps + m_ImageTimestamps.clear(); // clean up the timestamps } } void mitk::USDiPhASImageSource::GetPixelValues(itk::Index<3> pixel) { unsigned int events = m_device->GetScanMode().transmitEventsCount + 1; // the PA event is not included in the transmitEvents, so we add 1 here for (int index = 0; index < m_recordedImages.size(); index += events) // omit sound images { m_recordedImages.at(index) = ApplyBmodeFilter2d(m_recordedImages.at(index)); m_pixelValues.push_back(m_recordedImages.at(index).GetPointer()->GetPixelValueByIndex(pixel)); } } void mitk::USDiPhASImageSource::OrderImagesInterleaved(Image::Pointer PAImage, Image::Pointer USImage) { unsigned int width = 32; unsigned int height = 32; unsigned int events = m_device->GetScanMode().transmitEventsCount + 1; // the PA event is not included in the transmitEvents, so we add 1 here 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, m_recordedImages.size() / events}; unsigned int dimSound[] = { width, height, m_recordedImages.size() / events * (events-1)}; PAImage->Initialize(m_recordedImages.back()->GetPixelType(), 3, dimLaser); PAImage->SetGeometry(m_recordedImages.back()->GetGeometry()); USImage->Initialize(m_recordedImages.back()->GetPixelType(), 3, dimSound); USImage->SetGeometry(m_recordedImages.back()->GetGeometry()); for (int index = 0; index < m_recordedImages.size(); ++index) { mitk::ImageReadAccessor inputReadAccessor(m_recordedImages.at(index)); if (index % events == 0) { PAImage->SetSlice(inputReadAccessor.GetData(), index / events); } else { USImage->SetSlice(inputReadAccessor.GetData(), ((index - (index % events)) / events) + (index % events)-1); } } } void mitk::USDiPhASImageSource::OrderImagesUltrasound(Image::Pointer SoundImage) { unsigned int width = 32; unsigned int height = 32; unsigned int events = m_device->GetScanMode().transmitEventsCount; 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 dimSound[] = { width, height, m_recordedImages.size()}; SoundImage->Initialize(m_recordedImages.back()->GetPixelType(), 3, dimSound); SoundImage->SetGeometry(m_recordedImages.back()->GetGeometry()); for (int index = 0; index < m_recordedImages.size(); ++index) { mitk::ImageReadAccessor inputReadAccessor(m_recordedImages.at(index)); SoundImage->SetSlice(inputReadAccessor.GetData(), index); } } \ No newline at end of file diff --git a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h index bb8f0dc019..1b03bd33e3 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h @@ -1,161 +1,162 @@ /*=================================================================== 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. ===================================================================*/ #ifndef MITKUSDiPhASImageSource_H_HEADER_INCLUDED_ #define MITKUSDiPhASImageSource_H_HEADER_INCLUDED_ #include "mitkUSImageSource.h" #include "mitkUSDiPhASCustomControls.h" #include "Framework.IBMT.US.CWrapper.h" #include "mitkImageReadAccessor.h" #include "itkFastMutexLock.h" #include #include #include #include #include #include #include #include namespace mitk { class USDiPhASDevice; /** * \brief Implementation of mitk::USImageSource for DiPhAS API devices. * The method mitk::USImageSource::GetNextRawImage() is implemented for * getting images from the DiPhAS API. * * The image data is given to this class from the DiPhAS API by calling * a callback method that writes the image data to an mitk::image */ class USDiPhASImageSource : public USImageSource { public: mitkClassMacro(USDiPhASImageSource, USImageSource); mitkNewMacro1Param(Self, mitk::USDiPhASDevice*); itkCloneMacro(Self); typedef mitk::USDiPhASDeviceCustomControls::DataType DataType; /** * Implementation of the superclass method. Returns the pointer * to the mitk::Image filled by DiPhAS API callback. */ virtual void GetNextRawImage( mitk::Image::Pointer& ); /** * The API calls this function to pass the image data to the * user; here the m_Image is updated */ 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& imagePixelFormat, int& imageSetsTotal, double& timeStamp); void SetGUIOutput(std::function out); /** This starts or ends the recording session*/ void SetRecordingStatus(bool record); void ModifyDataType(DataType DataT); void ModifyUseBModeFilter(bool isSet); /** * Sets the spacing used in the image based on the informations of the ScanMode in USDiPhAS Device */ void UpdateImageGeometry(); protected: void SetDataType(DataType DataT); void SetUseBModeFilter(bool isSet); USDiPhASImageSource(mitk::USDiPhASDevice* device); virtual ~USDiPhASImageSource( ); /** This vector holds all the images we record, if recording is set to active. */ std::vector m_recordedImages; - std::vector m_timestamps; - std::vector m_pixelValues; - long long m_currenttime; + std::vector m_ImageTimestamps; + std::vector m_pixelValues; + long long m_CurrentImageTimestamp; bool currentlyRecording; mitk::OphirPyro::Pointer m_Pyro; bool m_PyroConnected; std::vector m_ImageBuffer; int m_LastWrittenImage; int m_BufferSize; unsigned int m_ImageDimensions[3]; mitk::Vector3D m_ImageSpacing; mitk::Image::Pointer ApplyBmodeFilter(mitk::Image::Pointer inputImage); mitk::Image::Pointer ApplyBmodeFilter2d(mitk::Image::Pointer inputImage); void OrderImagesInterleaved(Image::Pointer LaserImage, Image::Pointer SoundImage); void OrderImagesUltrasound(Image::Pointer SoundImage); void GetPixelValues(itk::Index<3> pixel); + float GetPixelValue(itk::Index<3> pixel); /** This image holds the image to be displayed right now*/ mitk::Image::Pointer m_Image; mitk::USDiPhASDevice* m_device; /** This is a callback to pass text data to the GUI. */ std::function m_GUIOutput; /** * Variables for management of current state. */ float startTime; bool useGUIOutPut; BeamformerStateInfoNative BeamformerInfos; bool useBModeFilter; bool m_DataTypeModified; DataType m_DataTypeNext; bool m_UseBModeFilterModified; bool m_UseBModeFilterNext; DataType m_DataType; itk::FastMutexLock::Pointer m_ImageMutex; }; } // namespace mitk #endif // MITKUSDiPhASImageSource_H