diff --git a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp index c17b5b2437..c06b431da0 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.cpp @@ -1,860 +1,859 @@ /*=================================================================== 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_ImageTimestampBuffer(), 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, 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::GetNextRawImage( mitk::Image::Pointer& image) { // 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; } // make sure image is nullptr image = nullptr; float ImageEnergyValue = 0; for (int i = 100; i > 90 && ImageEnergyValue <= 0; --i) { if (m_ImageTimestampBuffer[(m_LastWrittenImage + i) % 100] != 0) { ImageEnergyValue = m_Pyro->GetClosestEnergyInmJ(m_ImageTimestampBuffer[(m_LastWrittenImage + i) % 100]); if (ImageEnergyValue > 0) { image = &(*m_ImageBuffer[(m_LastWrittenImage + i) % 100]); } } } // if we did not get any usable Energy value, compensate using this default value if (image == nullptr) { image = &(*m_ImageBuffer[m_LastWrittenImage]); ImageEnergyValue = 40; if (image == nullptr) return; } // do image processing before displaying it if (image.IsNotNull()) { // now apply filters to the image, if the options have been selected. if ((m_CompensateForScattering || m_UseBModeFilter) && m_DataType == DataType::Beamformed_Short) { if (m_Device->GetScanMode().beamformingAlgorithm == Beamforming::PlaneWaveCompound) { if(m_UseBModeFilter) image = ApplyBmodeFilter(image, true, m_VerticalSpacing); } // this is for ultrasound only mode else { Image::Pointer imagePA = Image::New(); unsigned int dim[] = { image->GetDimension(0),image->GetDimension(1),1}; imagePA->Initialize(image->GetPixelType(), 3, dim); imagePA->SetGeometry(image->GetGeometry()); mitk::ImageReadAccessor inputReadAccessorCopy(image, image->GetSliceData(0)); imagePA->SetSlice(inputReadAccessorCopy.GetData(), 0); // first, seperate the PA image from the USImages // then, we compensate the PAImage using our ImageEnergyValue if(m_CompensateEnergy) imagePA = MultiplyImage(imagePA, 1/ImageEnergyValue); // TODO: add the correct prefactor here!!!! // now we apply the BModeFilter if (m_UseBModeFilter) { image = ApplyBmodeFilter(image, true, m_VerticalSpacing); // the US Images get a logarithmic filter imagePA = ApplyBmodeFilter(imagePA, false, m_VerticalSpacing); } // and finally the scattering corrections if (m_CompensateForScattering) { auto curResizeImage = m_FluenceCompResized.at(m_ScatteringCoefficient); // just for convenience // update the fluence reference images! bool doResampling = image->GetDimension(0) != curResizeImage->GetDimension(0) || image->GetDimension(1) != curResizeImage->GetDimension(1) || image->GetGeometry()->GetSpacing()[0] != curResizeImage->GetGeometry()->GetSpacing()[0] || image->GetGeometry()->GetSpacing()[1] != curResizeImage->GetGeometry()->GetSpacing()[1]; if (doResampling) { curResizeImage = ApplyResampling(m_FluenceCompOriginal.at(m_ScatteringCoefficient), image->GetGeometry()->GetSpacing(), image->GetDimensions()); double* rawOutputData = new double[image->GetDimension(0)*image->GetDimension(1)]; double* rawScatteringData = (double*)curResizeImage->GetData(); int sizeRawScatteringData = curResizeImage->GetDimension(0) * curResizeImage->GetDimension(1); int imageSize = image->GetDimension(0)*image->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; int lowerBound = std::round(upperCutoffmm / image->GetGeometry()->GetSpacing()[1])*image->GetDimension(0); int upperBound = lowerBound + sizeRawScatteringData; for (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 (int i = lowerBound; i < upperBound && i < imageSize; ++i) { rawOutputData[i] = 1 / rawScatteringData[i-lowerBound]; } for (int i = upperBound; i < imageSize; ++i) { rawOutputData[i] = 0; // everything than cannot be compensated shall be treated as garbage } unsigned int dim[] = { image->GetDimension(0), image->GetDimension(1), 1 }; curResizeImage->Initialize(mitk::MakeScalarPixelType(), 3, dim); curResizeImage->SetGeometry(image->GetGeometry()); curResizeImage->SetSlice(rawOutputData,0); delete[] 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."; } // actually apply the scattering compensation imagePA = ApplyScatteringCompensation(imagePA, m_ScatteringCoefficient); } mitk::ImageReadAccessor inputReadAccessor(imagePA, imagePA->GetSliceData(0)); image->SetSlice(inputReadAccessor.GetData(), 0); } } } } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyBmodeFilter(mitk::Image::Pointer inputImage, bool useLogFilter, float resampleSpacing) { // 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::Image< float, 3 > itkFloatImageType; 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 typedef itk::ResampleImageFilter < itkFloatImageType, itkFloatImageType > ResampleImageFilter; ResampleImageFilter::Pointer resampleImageFilter = ResampleImageFilter::New(); itkFloatImageType::Pointer itkImage; mitk::CastToItkImage(inputImage, itkImage); itkFloatImageType::Pointer bmode; if (useLogFilter) { bModeFilter->SetInput(itkImage); bModeFilter->SetDirection(1); bmode = bModeFilter->GetOutput(); } else { photoacousticBModeFilter->SetInput(itkImage); photoacousticBModeFilter->SetDirection(1); bmode = photoacousticBModeFilter->GetOutput(); } // resampleSpacing == 0 means: do no resampling if (resampleSpacing == 0) { return mitk::GrabItkImageMemory(bmode); } itkFloatImageType::SpacingType outputSpacing; itkFloatImageType::SizeType inputSize = itkImage->GetLargestPossibleRegion().GetSize(); itkFloatImageType::SizeType outputSize = inputSize; outputSize[0] = inputSize[0]; // don't do any resampling in x-direction! outputSpacing[0] = itkImage->GetSpacing()[0] * (static_cast(inputSize[0]) / static_cast(outputSize[0])); outputSpacing[1] = resampleSpacing; 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()); } mitk::Image::Pointer mitk::USDiPhASImageSource::ApplyScatteringCompensation(mitk::Image::Pointer inputImage, int scattering) { typedef itk::Image< float, 3 > itkFloatImageType; 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::Image< double, 3 > itkFloatImageType; 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::Image< float, 3 > itkFloatImageType; 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) { //get the timestamp we might save later on m_CurrentImageTimestamp = std::chrono::high_resolution_clock::now().time_since_epoch().count(); // 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 (unsigned char 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 (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 } for (unsigned char i = 0; i < beamformedTotalDatasets; i++) { image->SetSlice(&flipme[i*beamformedLines*beamformedSamples], i); // set every image to a different slice } delete[] flipme; 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... } } } // save the raw images when recording for (unsigned char i = 0; i < channelDataTotalDatasets; ++i) { mitk::Image::Pointer rawImage = mitk::Image::New(); rawImage->Initialize(mitk::MakeScalarPixelType(), 3, dim); rawImage->SetSlice(&noOffset[i*channelDataChannelsPerDataset*channelDataSamplesPerChannel]); float& recordTime = m_Device->GetScanMode().receivePhaseLengthSeconds; int& speedOfSound = m_Device->GetScanMode().averageSpeedOfSound; mitk::Vector3D rawSpacing; rawSpacing[0] = m_Device->GetScanMode().reconstructedLinePitchMmOrAngleDegree; rawSpacing[1] = recordTime / channelDataSamplesPerChannel / 2 / 1000000; // save in us - rawSpacing[2] = 0.6; + rawSpacing[2] = 1; rawImage->GetGeometry()->SetSpacing(rawSpacing); rawImage->GetGeometry()->Modified(); m_RawRecordedImages.push_back(rawImage); } delete[] noOffset; } itk::Index<3> pixel = { { (image->GetDimension(0) / 2), 22.0/532.0*m_Device->GetScanMode().reconstructionSamplesPerLine, 0 } }; //22/532*2048 = 84 if (!m_Pyro->IsSyncDelaySet() &&(image->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] = image; m_LastWrittenImage = (m_LastWrittenImage + 1) % m_BufferSize; // if the user decides to start recording, we feed the vector the generated images if (m_CurrentlyRecording) { for (unsigned char index = 0; index < image->GetDimension(2); ++index) { if (image->IsSliceSet(index)) { m_RecordedImages.push_back(Image::New()); unsigned int dim[] = { image ->GetDimension(0), image->GetDimension(1), 1}; m_RecordedImages.back()->Initialize(image->GetPixelType(), 3, dim); m_RecordedImages.back()->SetGeometry(image->GetGeometry()); mitk::ImageReadAccessor inputReadAccessor(image, image->GetSliceData(index)); m_RecordedImages.back()->SetSlice(inputReadAccessor.GetData(),0); } } m_ImageTimestampRecord.push_back(m_CurrentImageTimestamp); // save timestamps for each laser 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] = 0.6; + 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 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"; + 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 itk::Index<3> pixel = { { m_RecordedImages.at(1)->GetDimension(0) / 2, 22.0/532.0*m_Device->GetScanMode().reconstructionSamplesPerLine, 0 } }; //22/532*2048 = 84 - GetPixelValues(pixel); + 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[6] << "\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) +void mitk::USDiPhASImageSource::GetPixelValues(itk::Index<3> pixel, std::vector& values) { 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 { Image::Pointer image = ApplyBmodeFilter(m_RecordedImages.at(index)); - m_PixelValues.push_back(image.GetPointer()->GetPixelValueByIndex(pixel)); + 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 + 1; // the PA event is not included in the transmitEvents, so we add 1 here if (raw) { width = recordedList.at(0)->GetDimension(0); height = recordedList.at(0)->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, recordedList.size() / events}; unsigned int dimSound[] = { width, height, recordedList.size() / events * (events-1)}; PAImage->Initialize(recordedList.back()->GetPixelType(), 3, dimLaser); PAImage->SetGeometry(recordedList.back()->GetGeometry()); USImage->Initialize(recordedList.back()->GetPixelType(), 3, dimSound); USImage->SetGeometry(recordedList.back()->GetGeometry()); for (int index = 0; index < recordedList.size(); ++index) { mitk::ImageReadAccessor inputReadAccessor(recordedList.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 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 = 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, recordedList.size()}; USImage->Initialize(recordedList.back()->GetPixelType(), 3, dimSound); USImage->SetGeometry(recordedList.back()->GetGeometry()); for (int index = 0; index < recordedList.size(); ++index) { mitk::ImageReadAccessor inputReadAccessor(recordedList.at(index)); USImage->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 5e0aba171b..e174250b49 100644 --- a/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h +++ b/Modules/US/USHardwareDiPhAS/mitkUSDiPhASImageSource.h @@ -1,189 +1,189 @@ /*=================================================================== 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; typedef mitk::USDiPhASDeviceCustomControls::SavingSettings SavingSettings; /** * 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 SetSavingSettings(SavingSettings settings); void SetVerticalSpacing(float mm); void ModifyDataType(DataType dataT); void ModifyUseBModeFilter(bool isSet); void ModifyScatteringCoefficient(int coeff); void ModifyCompensateForScattering(bool useIt); void ModifyEnergyCompensation(bool compensate); /** * 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_RawRecordedImages; std::vector m_ImageTimestampRecord; std::vector m_ImageTimestampBuffer; long long m_CurrentImageTimestamp; bool m_CurrentlyRecording; mitk::OphirPyro::Pointer m_Pyro; bool m_PyroConnected; std::vector m_FluenceCompOriginal; std::vector m_FluenceCompResized; std::vector::Pointer> m_FluenceCompResizedItk; 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, bool useLogFilter = false, float resampleSpacing = 0.15); mitk::Image::Pointer ApplyScatteringCompensation(mitk::Image::Pointer inputImage, int scatteringCoefficient); mitk::Image::Pointer ApplyResampling(mitk::Image::Pointer inputImage, mitk::Vector3D outputSpacing, unsigned int outputSize[3]); mitk::Image::Pointer MultiplyImage(mitk::Image::Pointer inputImage, double value); void OrderImagesInterleaved(Image::Pointer PAImage, Image::Pointer USImage, std::vector recordedList, bool raw); void OrderImagesUltrasound(Image::Pointer USImage, std::vector recordedList); - void GetPixelValues(itk::Index<3> pixel); + void GetPixelValues(itk::Index<3> pixel, std::vector& values); float GetPixelValue(itk::Index<3> pixel); std::vector m_PixelValues; 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. */ SavingSettings m_SavingSettings; float m_StartTime; bool m_UseGUIOutPut; BeamformerStateInfoNative m_BeamformerInfos; bool m_UseBModeFilter; bool m_DataTypeModified; DataType m_DataTypeNext; bool m_UseBModeFilterModified; bool m_UseBModeFilterNext; float m_VerticalSpacing; float m_VerticalSpacingNext; bool m_VerticalSpacingModified; int m_ScatteringCoefficient; int m_ScatteringCoefficientNext; bool m_ScatteringCoefficientModified; bool m_CompensateForScattering; bool m_CompensateForScatteringNext; bool m_CompensateForScatteringModified; bool m_CompensateEnergy; bool m_CompensateEnergyNext; bool m_CompensateEnergyModified; DataType m_DataType; }; } // namespace mitk #endif // MITKUSDiPhASImageSource_H diff --git a/Modules/USUI/Qmitk/QmitkUSControlsCustomDiPhASDeviceWidget.ui b/Modules/USUI/Qmitk/QmitkUSControlsCustomDiPhASDeviceWidget.ui index 580f079648..3e803e283d 100644 --- a/Modules/USUI/Qmitk/QmitkUSControlsCustomDiPhASDeviceWidget.ui +++ b/Modules/USUI/Qmitk/QmitkUSControlsCustomDiPhASDeviceWidget.ui @@ -1,648 +1,648 @@ QmitkUSControlsCustomDiPhASDeviceWidget 0 0 351 903 Form 0 25 16777215 25 QFrame::WinPanel QFrame::Raised Started Qt::AlignCenter <html><head/><body><p><span style=" font-weight:600;">Record Images</span></p></body></html> Save Beamformed Save Raw 100 25 Start Recording Qt::Horizontal 40 20 <html><head/><body><p><span style=" font-weight:600;">Receive Parameters</span></p></body></html> 0 0 0 0 16777215 16777215 42 Qt::Horizontal 42 Qt::Horizontal 0 Scan Depth [mm] 1 100 1 Averaging Count false Beamformed Data Image Data - 2.000000000000000 + 3.000000000000000 - 1000.000000000000000 + 200.000000000000000 45.000000000000000 DataType TGC Min TGC Max Compensate Fluence For Scattering false 5 25 15 false Avg. μs' [1/cm] <html><head/><body><p><span style=" font-weight:600;">Beamforming Parameters</span></p></body></html> 256 4096 256 2048 0.010000000000000 0.050000000000000 0.300000000000000 Qt::PreventContextMenu 128 1024 128 128 Samples per Line Reconstructed Lines 1000 1000000 1540 Speed of Sound [m/s] Pitch of Transducer [mm] <html><head/><body><p><span style=" font-weight:600;">Display Parameters</span></p></body></html> Envelope Filter true Compensate Energy Values 0.010000000000000 1.200000000000000 0.050000000000000 0.300000000000000 Vertical Spacing <html><head/><body><p><span style=" font-weight:600;">Transmit Parameters</span></p></body></html> 1.000000000000000 15.000000000000000 0.100000000000000 7.500000000000000 1 1 Transmit Events true false 1 1.000000000000000 10000.000000000000000 1.000000000000000 Transmit Phase Length [us] Excitation Frequency [MHz] false Interleaved Ultrasound only false 5 75 75 Voltage [V] Mode false <html><head/><body><p><span style=" font-weight:600;">Bandpass Parameters</span></p></body></html> false High Cut [MHz] false Low Cut [MHz] false Bandpass Enabled false false 5.000000000000000 false Off On Qt::Vertical 20 40 diff --git a/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.cpp b/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.cpp index 0100d6b356..3f7a15781c 100644 --- a/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.cpp +++ b/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.cpp @@ -1,368 +1,379 @@ /*=================================================================== 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. ===================================================================*/ // Blueberry #include #include // Qmitk #include "QmitkLaserControl.h" // Qt #include #include const std::string OPOLaserControl::VIEW_ID = "org.mitk.views.lasercontrol"; void OPOLaserControl::SetFocus() { } void OPOLaserControl::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); - connect(m_Controls.buttonInitLaser, SIGNAL(clicked()), this, SLOT(InitLaser())); + connect(m_Controls.buttonInitLaser, SIGNAL(clicked()), this, SLOT(InitResetLaser())); connect(m_Controls.buttonTune, SIGNAL(clicked()), this, SLOT(TuneWavelength())); connect(m_Controls.buttonFastTuning, SIGNAL(clicked()), this, SLOT(StartFastTuning())); connect(m_Controls.buttonFlashlamp, SIGNAL(clicked()), this, SLOT(ToggleFlashlamp())); connect(m_Controls.buttonQSwitch, SIGNAL(clicked()), this, SLOT(ToggleQSwitch())); connect(m_Controls.sliderWavelength, SIGNAL(valueChanged(int)), this, SLOT(SyncWavelengthSetBySlider())); connect(m_Controls.spinBoxWavelength, SIGNAL(valueChanged(double)), this, SLOT(SyncWavelengthSetBySpinBox())); connect(&m_ShutterWatcher, SIGNAL(finished()), this, SLOT(EnableLaser())); m_SyncFromSpinBox = true; m_SyncFromSlider = true; m_PumpLaserConnected = false; m_OPOConnected = false; m_PyroConnected = false; } void OPOLaserControl::EnableLaser() { m_Controls.buttonQSwitch->setEnabled(true); m_Controls.buttonQSwitch->setText("Start Laser"); this->GetState(); } void OPOLaserControl::SyncWavelengthSetBySlider() { if (m_SyncFromSlider) { m_SyncFromSpinBox = false; m_Controls.spinBoxWavelength->setValue(m_Controls.sliderWavelength->value() / 10); } else m_SyncFromSlider = true; } void OPOLaserControl::SyncWavelengthSetBySpinBox() { if (m_SyncFromSpinBox) { m_SyncFromSlider = false; m_Controls.sliderWavelength->setValue(m_Controls.spinBoxWavelength->value() * 10); } else m_SyncFromSpinBox = true; } -void OPOLaserControl::InitLaser() +void OPOLaserControl::InitResetLaser() { m_Controls.buttonInitLaser->setEnabled(false); m_Controls.buttonInitLaser->setText("working ..."); - if (!m_PumpLaserConnected) + if (!m_PumpLaserConnected && !m_OPOConnected) { m_PumpLaserController = mitk::QuantelLaser::New(); - - if (m_PumpLaserController->OpenConnection("OpotekPhocusMobile")) - { - m_Controls.buttonFlashlamp->setEnabled(true); - m_Controls.buttonQSwitch->setEnabled(false); - m_Controls.buttonInitLaser->setText("Reset and Release Laser"); - - std::string answer(""); - std::string triggerCommand("TRIG "); - if (m_Controls.checkBoxTriggerExternally->isChecked()) - { - triggerCommand.append("EE"); // set both Triggers external - m_PumpLaserController->SendAndReceiveLine(&triggerCommand, &answer); - MITK_INFO << answer; - } - else - { - triggerCommand.append("II"); // set both Triggers internal - m_PumpLaserController->SendAndReceiveLine(&triggerCommand, &answer); - MITK_INFO << answer; - std::string energyCommand("QDLY 30"); - m_PumpLaserController->SendAndReceiveLine(&energyCommand, &answer); - MITK_INFO << answer; - } - - m_PumpLaserConnected = true; - this->GetState(); - } - else - { - QMessageBox::warning(NULL, "Laser Control", "Opotek Pump Laser Initialization Failed."); - m_Controls.buttonInitLaser->setText("Init Laser"); - m_Controls.buttonInitLaser->setEnabled(true); - return; - } - } - else - { - // destroy and free - if (m_PumpLaserController->CloseConnection()) - { - m_Controls.buttonFlashlamp->setEnabled(false); - m_Controls.buttonQSwitch->setEnabled(false); - m_Controls.buttonInitLaser->setText("Init Laser"); - m_PumpLaserConnected = false; - } - else - { - QMessageBox::warning(NULL, "Laser Control", "Opotek Pump Laser Release Failed."); - m_Controls.buttonInitLaser->setText("Reset and Release Laser"); - } - } - - if (!m_OPOConnected) - { m_OPOMotor = mitk::GalilMotor::New(); - if (m_OPOMotor->OpenConnection("OpotekPhocusMobile")) - { - m_Controls.buttonTune->setEnabled(true); - m_Controls.buttonFastTuning->setEnabled(true); - m_Controls.sliderWavelength->setMinimum(m_OPOMotor->GetMinWavelength() * 10); - m_Controls.sliderWavelength->setMaximum(m_OPOMotor->GetMaxWavelength() * 10); - m_Controls.spinBoxWavelength->setMinimum(m_OPOMotor->GetMinWavelength()); - m_Controls.spinBoxWavelength->setMaximum(m_OPOMotor->GetMaxWavelength()); - m_Controls.sliderWavelength->setValue(m_OPOMotor->GetCurrentWavelength() * 10); - m_Controls.spinBoxWavelength->setValue(m_OPOMotor->GetCurrentWavelength()); - m_OPOConnected = true; // not always right FIXME - } - else - { - QMessageBox::warning(NULL, "Laser Control", "OPO Initialization Failed."); - m_Controls.buttonInitLaser->setText("Init Laser"); - } + InitThread *initThread = new InitThread(m_PumpLaserController, m_OPOMotor); + connect(initThread, SIGNAL(result(bool, bool)), this, SLOT(InitLaser(bool, bool))); + connect(initThread, SIGNAL(finished()), initThread, SLOT(deleteLater())); + initThread->start(); } else { // destroy and free - if (m_OPOMotor->CloseConnection()) - { - m_Controls.buttonTune->setEnabled(false); - m_Controls.buttonFastTuning->setEnabled(false); - m_Controls.buttonInitLaser->setText("Init Laser"); - m_OPOConnected = false; - } - else - { - QMessageBox::warning(NULL, "Laser Control", "OPO Release Failed."); - m_Controls.buttonInitLaser->setText("Reset and Release Laser"); - } + m_Controls.buttonFlashlamp->setEnabled(false); + m_Controls.buttonQSwitch->setEnabled(false); + m_Controls.buttonTune->setEnabled(false); + m_Controls.buttonFastTuning->setEnabled(false); + + ResetThread *resetThread = new ResetThread(m_PumpLaserController, m_OPOMotor); + connect(resetThread, SIGNAL(result(bool, bool)), this, SLOT(ResetLaser(bool, bool))); + connect(resetThread, SIGNAL(finished()), resetThread, SLOT(deleteLater())); + resetThread->start(); } - m_Controls.buttonInitLaser->setEnabled(true); - this->GetState(); /* 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_CurrentPulseEnergy = 0; m_PyroConnected = true; //QFuture future = QtConcurrent::run(this, &OPOLaserControl::ShowEnergy); //m_EnergyWatcher.setFuture(future); } 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; }*/ } +void OPOLaserControl::InitLaser(bool successLaser, bool successMotor) +{ + if (successLaser && successMotor) + { + m_Controls.buttonFlashlamp->setEnabled(true); + m_Controls.buttonQSwitch->setEnabled(false); + m_Controls.buttonInitLaser->setText("Reset and Release Laser"); + + std::string answer(""); + std::string triggerCommand("TRIG "); + if (m_Controls.checkBoxTriggerExternally->isChecked()) + { + triggerCommand.append("EE"); // set both Triggers external + m_PumpLaserController->SendAndReceiveLine(&triggerCommand, &answer); + MITK_INFO << answer; + } + else + { + triggerCommand.append("II"); // set both Triggers internal + m_PumpLaserController->SendAndReceiveLine(&triggerCommand, &answer); + MITK_INFO << answer; + std::string energyCommand("QDLY 30"); + m_PumpLaserController->SendAndReceiveLine(&energyCommand, &answer); + MITK_INFO << answer; + } + + m_PumpLaserConnected = true; + + m_Controls.buttonTune->setEnabled(true); + m_Controls.buttonFastTuning->setEnabled(true); + m_Controls.sliderWavelength->setMinimum(m_OPOMotor->GetMinWavelength() * 10); + m_Controls.sliderWavelength->setMaximum(m_OPOMotor->GetMaxWavelength() * 10); + m_Controls.spinBoxWavelength->setMinimum(m_OPOMotor->GetMinWavelength()); + m_Controls.spinBoxWavelength->setMaximum(m_OPOMotor->GetMaxWavelength()); + m_Controls.sliderWavelength->setValue(m_OPOMotor->GetCurrentWavelength() * 10); + m_Controls.spinBoxWavelength->setValue(m_OPOMotor->GetCurrentWavelength()); + + m_OPOConnected = true; // not always right FIXME + + this->GetState(); + } + else + { + if(!successLaser) + QMessageBox::warning(NULL, "Laser Control", "Opotek Pump Laser Initialization Failed."); + if(!successMotor) + QMessageBox::warning(NULL, "Laser Control", "OPO Initialization Failed."); + + m_Controls.buttonInitLaser->setText("Init Laser"); + return; + } + + m_Controls.buttonInitLaser->setEnabled(true); + this->GetState(); +} + +void OPOLaserControl::ResetLaser(bool successLaser, bool successMotor) +{ + if (successLaser && successMotor) + { + m_Controls.buttonFlashlamp->setEnabled(false); + m_Controls.buttonQSwitch->setEnabled(false); + m_Controls.buttonInitLaser->setText("Init Laser"); + m_PumpLaserConnected = false; + + m_Controls.buttonTune->setEnabled(false); + m_Controls.buttonFastTuning->setEnabled(false); + m_Controls.buttonInitLaser->setText("Init Laser"); + m_OPOConnected = false; + } + else + { + if(!successMotor) + QMessageBox::warning(NULL, "Laser Control", "OPO Release Failed."); + if(!successLaser) + QMessageBox::warning(NULL, "Laser Control", "Opotek Pump Laser Release Failed."); + + m_Controls.buttonInitLaser->setText("Reset and Release Laser"); + } + + m_Controls.buttonInitLaser->setEnabled(true); + this->GetState(); +} + void OPOLaserControl::TuneWavelength() { if (m_Controls.checkBoxCalibration->isChecked()) { m_OPOMotor->TuneToWavelength(m_Controls.spinBoxPosition->value(), true); } else { m_OPOMotor->TuneToWavelength(m_Controls.spinBoxWavelength->value(), false); } QString wavelengthText = QString::number(m_OPOMotor->GetCurrentWavelength()); wavelengthText.append("nm"); m_Controls.labelWavelength->setText(wavelengthText); } void OPOLaserControl::StartFastTuning() { std::vector listOfWavelengths; double tmpWavelength = 0; int currentRow = 0; bool success = false; do { if (currentRow != 0) listOfWavelengths.push_back(tmpWavelength); if (m_Controls.tableFastTuningWavelengths->item(0, currentRow)) { QString test = m_Controls.tableFastTuningWavelengths->item(0, currentRow)->text(); tmpWavelength = test.toDouble(&success); currentRow++; } else tmpWavelength = 0; if (success == 0) tmpWavelength = 0; } while (tmpWavelength<950.1 && tmpWavelength>689.9); m_OPOMotor->FastTuneWavelengths(listOfWavelengths); } void OPOLaserControl::ShutterCountDown() { m_Controls.buttonFlashlamp->setText("Stop Lamp"); m_Controls.buttonQSwitch->setEnabled(false); m_Controls.buttonQSwitch->setText("10s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("9s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("8s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("7s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("6s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("5s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("4s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("3s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("2s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); m_Controls.buttonQSwitch->setText("1s ..."); std::this_thread::sleep_for(std::chrono::seconds(1)); return; } void OPOLaserControl::ShowEnergy() { /*forever { std::this_thread::sleep_for(std::chrono::milliseconds(500)); MITK_INFO << "[Pyro Debug] ShowEnergy()"; if (!m_PyroConnected) { MITK_INFO << "[Pyro Debug] StopDataAcquisition: " << m_Pyro->StopDataAcquisition(); MITK_INFO << "[Pyro Debug] CloseConnection: " << m_Pyro->CloseConnection(); m_CurrentPulseEnergy = 0; return; } // m_Pyro->GetDataFromSensor(); m_CurrentPulseEnergy = 60000 * m_Pyro->LookupCurrentPulseEnergy(); m_Controls.labelEnergy->setText(std::to_string(m_CurrentPulseEnergy).append(" mJ").c_str()); }*/ } void OPOLaserControl::ToggleFlashlamp() { m_Controls.buttonFlashlamp->setText("..."); if (!m_PumpLaserController->IsFlashing()) { if (m_PumpLaserController->StartFlashing()) { QFuture future = QtConcurrent::run(this, &OPOLaserControl::ShutterCountDown); m_ShutterWatcher.setFuture(future); } else m_Controls.buttonFlashlamp->setText("Start Lamp"); } else { if (m_PumpLaserController->StopFlashing()) { m_Controls.buttonFlashlamp->setText("Start Lamp"); m_Controls.buttonQSwitch->setText("Start Laser"); m_Controls.buttonQSwitch->setEnabled(false); } else m_Controls.buttonFlashlamp->setText("Stop Lamp"); } this->GetState(); } void OPOLaserControl::ToggleQSwitch() { m_Controls.buttonQSwitch->setText("..."); if (!m_PumpLaserController->IsEmitting()) { if (m_PumpLaserController->StartQswitching()) m_Controls.buttonQSwitch->setText("Stop Laser"); else m_Controls.buttonQSwitch->setText("Start Laser"); } else { if (m_PumpLaserController->StopQswitching()) m_Controls.buttonQSwitch->setText("Start Laser"); else m_Controls.buttonQSwitch->setText("Stop Laser"); } this->GetState(); } void OPOLaserControl::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes) { } void OPOLaserControl::GetState() { mitk::QuantelLaser::LaserState pumpLaserState = m_PumpLaserController->GetState(); if (pumpLaserState == mitk::QuantelLaser::STATE0) m_Controls.labelStatus->setText("PL0: Boot Fault."); else if (pumpLaserState == mitk::QuantelLaser::STATE1) m_Controls.labelStatus->setText("PL1: Warm Up."); else if (pumpLaserState == mitk::QuantelLaser::STATE2) m_Controls.labelStatus->setText("PL2: Laser Ready."); else if (pumpLaserState == mitk::QuantelLaser::STATE3) m_Controls.labelStatus->setText("PL3: Flashing. Pulse Disabled."); else if (pumpLaserState == mitk::QuantelLaser::STATE4) m_Controls.labelStatus->setText("PL4: Flashing. Shutter Closed."); else if (pumpLaserState == mitk::QuantelLaser::STATE5) m_Controls.labelStatus->setText("PL5: Flashing. Shutter Open."); else if (pumpLaserState == mitk::QuantelLaser::STATE6) m_Controls.labelStatus->setText("PL6: Flashing. Pulse Enabled."); else if (pumpLaserState == mitk::QuantelLaser::STATE7) m_Controls.labelStatus->setText("PL7: Undefined State."); else if (pumpLaserState == mitk::QuantelLaser::UNCONNECTED) m_Controls.labelStatus->setText("PL Not Connected."); } \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.h b/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.h index abf6c1f227..19147f9cb2 100644 --- a/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.h +++ b/Plugins/org.mitk.gui.qt.lasercontrol/src/internal/QmitkLaserControl.h @@ -1,100 +1,142 @@ /*=================================================================== 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 OPOLaserControl_h #define OPOLaserControl_h #include #include #include "ui_QmitkLaserControlControls.h" #include #include +#include // Photoacoustics Hardware #include #include //#include #include /** \brief OPOLaserControl \warning This class is not yet documented. Use "git blame" and ask the author why he is a lazy fuck. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ class OPOLaserControl : public QmitkAbstractView { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; protected slots: /// \brief Called when the user clicks the GUI button void GetState(); - void InitLaser(); + void InitLaser(bool successLaser, bool successMotor); + void ResetLaser(bool successLaser, bool successMotor); + void InitResetLaser(); void TuneWavelength(); void StartFastTuning(); void ShutterCountDown(); void EnableLaser(); void ShowEnergy(); void ToggleFlashlamp(); void ToggleQSwitch(); void SyncWavelengthSetBySlider(); void SyncWavelengthSetBySpinBox(); protected: virtual void CreateQtPartControl(QWidget *parent) override; virtual void SetFocus() override; /// \brief called by QmitkFunctionality when DataManager's selection has changed virtual void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes ) override; Ui::OPOLaserControlControls m_Controls; bool m_PumpLaserConnected; bool m_OPOConnected; bool m_PyroConnected; bool m_SyncFromSpinBox; bool m_SyncFromSlider; double m_CurrentPulseEnergy; QFutureWatcher m_ShutterWatcher; QFutureWatcher m_EnergyWatcher; mitk::QuantelLaser::Pointer m_PumpLaserController; mitk::GalilMotor::Pointer m_OPOMotor; //mitk::OphirPyro::Pointer m_Pyro; }; +class InitThread : public QThread +{ + Q_OBJECT + void run() Q_DECL_OVERRIDE + { + emit result(m_PumpLaserController->OpenConnection("OpotekPhocusMobile"), m_OPOMotor->OpenConnection("OpotekPhocusMobile")); + } + signals: + void result(bool, bool); + public: + InitThread(mitk::QuantelLaser::Pointer ql, mitk::GalilMotor::Pointer gm) + { + m_PumpLaserController = ql; + m_OPOMotor = gm; + } + mitk::QuantelLaser::Pointer m_PumpLaserController; + mitk::GalilMotor::Pointer m_OPOMotor; +}; + +class ResetThread : public QThread +{ + Q_OBJECT + void run() Q_DECL_OVERRIDE + { + emit result(m_PumpLaserController->CloseConnection(), m_OPOMotor->CloseConnection()); + } +signals: + void result(bool, bool); +public: + ResetThread(mitk::QuantelLaser::Pointer ql, mitk::GalilMotor::Pointer gm) + { + m_PumpLaserController = ql; + m_OPOMotor = gm; + } + mitk::QuantelLaser::Pointer m_PumpLaserController; + mitk::GalilMotor::Pointer m_OPOMotor; +}; + #endif // OPOLaserControl_h +