diff --git a/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp b/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp index 85da10fa80..1d5280b178 100644 --- a/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp +++ b/Modules/PhotoacousticsHardware/mitkOphirPyro.cpp @@ -1,313 +1,316 @@ /*=================================================================== 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 */ } 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"; int currentSize = m_TimeStamps.size(); for (int index = 0; index < currentSize; ++index) { - timestampFile << "\n" << index << "," << m_TimeStamps.at(index) << ","<< m_PulseEnergy.at(index) << "," << (long)m_PulseTime.at(index); + timestampFile << "\n" << index << "," << m_TimeStamps.at(index) << ","<< m_PulseEnergySaved.at(index) << "," << (long)m_PulseTimeSaved.at(index); } timestampFile.close(); } void mitk::OphirPyro::SaveData() { SaveCsvData(); } 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) return 0; 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) { long long newDifference = abs(((int)m_PulseTime[index]) - searchTime); //MITK_INFO << "newDifference[" << index << "] = " << newDifference; if (newDifference < shortestDifference) { 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 << "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) { 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 de3fb7f8ab..b1d9f886d6 100644 --- a/Modules/PhotoacousticsHardware/mitkOphirPyro.h +++ b/Modules/PhotoacousticsHardware/mitkOphirPyro.h @@ -1,88 +1,94 @@ /*=================================================================== 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(); void SaveData(); 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; + + std::vector m_PulseEnergySaved; + std::vector m_PulseTimeSaved; + std::vector m_PulseStatusSaved; + std::vector m_TimeStampsSaved; + 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/mitkUSDiPhASDevice.cpp b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASDevice.cpp index 597dacb8d8..86c8a179f1 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASDevice.cpp +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASDevice.cpp @@ -1,303 +1,303 @@ /*=================================================================== 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 "mitkUSDiPhASDevice.h" #include "mitkUSDiPhASCustomControls.h" mitk::USDiPhASDevice::USDiPhASDevice(std::string manufacturer, std::string model) : mitk::USDevice(manufacturer, model), m_ControlsProbes(mitk::USDiPhASProbesControls::New(this)), m_ImageSource(mitk::USDiPhASImageSource::New(this)), m_ControlInterfaceCustom(mitk::USDiPhASCustomControls::New(this)), m_IsRunning(false), m_BurstHalfwaveClockCount(7), m_Interleaved(true) { SetNumberOfOutputs(1); SetNthOutput(0, this->MakeOutput(0)); } mitk::USDiPhASDevice::~USDiPhASDevice() { } //Gets std::string mitk::USDiPhASDevice::GetDeviceClass() { return "org.mitk.modules.us.USDiPhASDevice"; } mitk::USControlInterfaceProbes::Pointer mitk::USDiPhASDevice::GetControlInterfaceProbes() { return m_ControlsProbes.GetPointer(); }; mitk::USAbstractControlInterface::Pointer mitk::USDiPhASDevice::GetControlInterfaceCustom() { return m_ControlInterfaceCustom.GetPointer(); } mitk::USImageSource::Pointer mitk::USDiPhASDevice::GetUSImageSource() { return m_ImageSource.GetPointer(); } ScanModeNative& mitk::USDiPhASDevice::GetScanMode() { return m_ScanMode; } // Setup and Cleanup bool mitk::USDiPhASDevice::OnInitialization() { return true; } //---------------------------------------------------------------------------------------------------------------------------- /* ugly wrapper stuff - find better solution so it isn't necessary to create a global pointer to USDiPhASDevice... * passing a lambda function would be nicer - sadly something goes wrong when passing the adress of a lambda function: * the API produces access violations. Passing the Lambda function itself would be preferable, but that's not possible */ mitk::USDiPhASDevice* w_device; mitk::USDiPhASImageSource* w_ISource; void WrapperMessageCallback(const char* message) { w_device->MessageCallback(message); } void WrapperImageDataCallback( short* rfDataChannelData, int channelDatalinesPerDataset, 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) { w_ISource->ImageDataCallback( rfDataChannelData, channelDatalinesPerDataset, channelDatalinesPerDataset, channelDataTotalDatasets, rfDataArrayBeamformed, beamformedLines, beamformedSamples, beamformedTotalDatasets, imageData, imageWidth, imageHeight, imagePixelFormat, imageSetsTotal, timeStamp); } //---------------------------------------------------------------------------------------------------------------------------- bool mitk::USDiPhASDevice::OnConnection() { w_device = this; w_ISource = m_ImageSource; // Need those pointers for the forwarders to call member functions; createBeamformer expects non-member function pointers. createBeamformer((StringMessageCallback)&WrapperMessageCallback, (NewDataCallback)&WrapperImageDataCallback); InitializeScanMode(); initBeamformer(); //start the hardware connection m_ImageSource->UpdateImageGeometry(); //make sure the image geometry is initialized! // pass the new scanmode to the device: setupScan(this->m_ScanMode); return true; } bool mitk::USDiPhASDevice::OnDisconnection() { //close the beamformer so hardware is disconnected closeBeamformer(); return true; } bool mitk::USDiPhASDevice::OnActivation() { // probe controls are available now m_ControlsProbes->SetIsActive(true); if (m_ControlsProbes->GetProbesCount() < 1) { MITK_WARN("USDevice")("USDiPhASDevice") << "No probe found."; return false; } m_ControlsProbes->SelectProbe(0); // toggle the beamformer of the API if(!m_IsRunning) m_IsRunning=toggleFreeze(); return true; } bool mitk::USDiPhASDevice::OnDeactivation() { if(m_IsRunning) m_IsRunning=toggleFreeze(); return true; } void mitk::USDiPhASDevice::OnFreeze(bool freeze) { if(m_IsRunning==freeze) m_IsRunning=toggleFreeze(); // toggleFreeze() returns true if it starts running the beamformer, otherwise false } void mitk::USDiPhASDevice::UpdateScanmode() { OnFreeze(true); - + SetInterleaved(m_Interleaved); // update the beamforming parameters... UpdateTransmitEvents(); if (!(dynamic_cast(this->m_ControlInterfaceCustom.GetPointer())->GetSilentUpdate())) { setupScan(this->m_ScanMode); m_ImageSource->UpdateImageGeometry(); } OnFreeze(false); } void mitk::USDiPhASDevice::UpdateTransmitEvents() { int numChannels = m_ScanMode.reconstructionLines; // transmitEventsCount defines only the number of acoustic measurements (angles); there will be one event added to the start for OA measurement m_ScanMode.TransmitEvents = new TransmitEventNative[m_ScanMode.transmitEventsCount]; for (int ev = 0; ev < m_ScanMode.transmitEventsCount; ++ev) { m_ScanMode.TransmitEvents[ev].transmitEventDelays = new float[numChannels]; m_ScanMode.TransmitEvents[ev].BurstHalfwaveClockCountPerChannel = new int[numChannels]; m_ScanMode.TransmitEvents[ev].BurstCountPerChannel = new int[numChannels]; m_ScanMode.TransmitEvents[ev].BurstUseNegativePolarityPerChannel = new bool[numChannels]; m_ScanMode.TransmitEvents[ev].ChannelMultiplexerSetups = nullptr; for (int i = 0; i < numChannels; ++i) { m_ScanMode.TransmitEvents[ev].BurstHalfwaveClockCountPerChannel[i] = m_BurstHalfwaveClockCount; // 120 MHz / (2 * (predefinedBurstHalfwaveClockCount + 1)) --> 7.5 MHz m_ScanMode.TransmitEvents[ev].BurstCountPerChannel[i] = 1; // Burst with 1 cycle m_ScanMode.TransmitEvents[ev].BurstUseNegativePolarityPerChannel[i] = true; m_ScanMode.TransmitEvents[ev].transmitEventDelays[i] = 0; } } m_ScanMode.transmitSequenceCount = 1; m_ScanMode.transmitSequences = new SequenceNative[m_ScanMode.transmitSequenceCount]; m_ScanMode.transmitSequences[0].startEvent = 0; m_ScanMode.transmitSequences[0].endEvent = m_ScanMode.transmitEventsCount; } void mitk::USDiPhASDevice::InitializeScanMode() { // create a scanmode to be used for measurements: m_ScanMode.scanModeName = "InterleavedMode"; // configure a linear transducer m_ScanMode.transducerName = "L5-10"; m_ScanMode.transducerCurvedRadiusMeter = 0; m_ScanMode.transducerElementCount = 128; m_ScanMode.transducerFrequencyHz = 7500000; m_ScanMode.transducerPitchMeter = 0.0003f; m_ScanMode.transducerType = 1; // configure the receive paramters: m_ScanMode.receivePhaseLengthSeconds = 65e-6f; // 5 cm imaging depth m_ScanMode.tgcdB = new unsigned char[8]; for (int tgc = 0; tgc < 8; ++tgc) m_ScanMode.tgcdB[tgc] = tgc * 2 + 10; m_ScanMode.accumulation = 1; m_ScanMode.bandpassApply = false; m_ScanMode.averagingCount = 1; // configure general processing: m_ScanMode.transferChannelData = false; // configure reconstruction processing: m_ScanMode.averageSpeedOfSound = 1540; m_ScanMode.computeBeamforming = true; // setup beamforming parameters: SetInterleaved(true); m_ScanMode.reconstructedLinePitchMmOrAngleDegree = 0.15f; m_ScanMode.reconstructionLines = 256; m_ScanMode.reconstructionSamplesPerLine = 2048; m_ScanMode.transferBeamformedData = true; // configure the transmit sequence(s): m_ScanMode.transmitEventsCount = 1; m_ScanMode.transmitPhaseLengthSeconds = 1e-6f; m_ScanMode.voltageV = 70; UpdateTransmitEvents(); // configure bandpass: m_ScanMode.bandpassApply = false; m_ScanMode.bandpassFrequencyLowHz = 1e6f; m_ScanMode.bandpassFrequencyHighHz = 20e6f; // configure image generation: m_ScanMode.imageWidth = 512; m_ScanMode.imageHeight = 512; m_ScanMode.imageMultiplier = 1; m_ScanMode.imageLeveling = 0; m_ScanMode.transferImageData = true; // Trigger setup: m_ScanMode.triggerSetup.enabled = true; m_ScanMode.triggerSetup.constantPulseRepetitionRateHz = 20; m_ScanMode.triggerSetup.triggerWidthMicroseconds = 15; m_ScanMode.triggerSetup.delayTrigger2Microseconds = 300; } // callback for the DiPhAS API void mitk::USDiPhASDevice::MessageCallback(const char* message) { MITK_INFO << "DiPhAS API: " << message << '\n'; } void mitk::USDiPhASDevice::SetBursts(int bursts) { m_BurstHalfwaveClockCount = bursts; } bool mitk::USDiPhASDevice::IsInterleaved() { return m_Interleaved; } void mitk::USDiPhASDevice::SetInterleaved(bool interleaved) { m_Interleaved = interleaved; if (interleaved) { m_ScanMode.scanModeName = "Interleaved Beamforming Mode"; m_CurrentBeamformingAlgorithm = Beamforming::Interleaved_OA_US; paramsInterleaved.SpeedOfSoundMeterPerSecond = m_ScanMode.averageSpeedOfSound; paramsInterleaved.angleSkipFactor = 1; paramsInterleaved.OptoacousticDelay = 0.0000003; // 300ns paramsInterleaved.filter = Filter::None; m_ScanMode.beamformingAlgorithmParameters = ¶msInterleaved; } else { m_ScanMode.scanModeName = "Plane Wave Beamforming Mode"; m_CurrentBeamformingAlgorithm = Beamforming::PlaneWaveCompound; paramsPlaneWave.SpeedOfSoundMeterPerSecond = m_ScanMode.averageSpeedOfSound; paramsPlaneWave.angleSkipFactor = 0; paramsPlaneWave.usePhaseCoherence = 0; m_ScanMode.beamformingAlgorithmParameters = ¶msPlaneWave; } m_ScanMode.beamformingAlgorithm = m_CurrentBeamformingAlgorithm; } \ No newline at end of file