Skip to content

Commit c682464

Browse files
committed
Added the DicomMultiFrameStreamer
1 parent 90afb06 commit c682464

File tree

9 files changed

+279
-15
lines changed

9 files changed

+279
-15
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ endif()
66
project(FAST)
77

88
set(VERSION_MAJOR 4)
9-
set(VERSION_MINOR 10)
9+
set(VERSION_MINOR 11)
1010
set(VERSION_PATCH 0)
1111
set(VERSION_SO 4) # SO version, should be incremented by 1 every time the existing API changes
1212
set(FAST_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")

cmake/ModuleDicom.cmake

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ if(FAST_MODULE_Dicom)
2424
endif()
2525
else()
2626
fast_download_dependency(dcmtk
27-
3.6.3
28-
f40ba5df0307c0ac20a200c2384835c404adb3341c44cd67201946bd4c9006d6
29-
libdcmdata.so libdcmimgle.so libofstd.so liboflog.so
27+
3.6.7
28+
e681ed35c487f24de9fe2845b261bda8d0024a95a714f311f128c78ee164dd4b
29+
libdcmimage.so libdcmjpeg.so libdcmdata.so libdcmimgle.so libofstd.so liboflog.so
3030
)
3131
endif()
3232
endif()

source/FAST/DataHub.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ static QJsonDocument getJSONFromURL(const std::string& url) {
5656

5757
reply->deleteLater();
5858
QJsonDocument result;
59-
//std::cout << url << std::endl;
6059
if (reply->error() == QNetworkReply::NoError) {
6160
auto status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
6261
auto res = reply->readAll();
@@ -98,7 +97,7 @@ void DataHub::downloadTextFile(const std::string& url, const std::string& destin
9897
throw Exception("FAST DataHub item " + name + " not found. Please check spelling.");
9998
} else {
10099
//failure
101-
throw Exception("Failed to retrieve data from FAST DataHub.\nServer could be down, or you may have no internet access.\nPlease try again later.");
100+
throw Exception("Failed to retrieve pipeline data from FAST DataHub.\nServer could be down, or you may have no internet access.\nPlease try again later.");
102101
}
103102
}
104103

@@ -476,19 +475,12 @@ QPixmap DataHubBrowser::downloadThumbnail(const std::string& URL) {
476475
}
477476

478477
void DataHubBrowser::download(std::string itemID) {
479-
std::cout << "asd" << std::endl;
480-
std::cout << m_hub.getStorageDirectory() << std::endl;
481-
std::cout << "asd11" << std::endl;
482478
auto item = m_hub.getItem(itemID);
483-
std::cout << "asd1" << std::endl;
484479
auto progressWidget = new DownloadProgressWidget(item);
485-
std::cout << "asd2" << std::endl;
486480
connect(&m_hub, &DataHub::progress, progressWidget, &DownloadProgressWidget::updateProgress);
487481
progressWidget->show();
488-
std::cout << "asd3" << std::endl;
489482
connect(&m_hub, &DataHub::finished, [=]() {
490483
for(int i = 0; i < m_listWidget->count(); ++i) {
491-
std::cout << "asd5" << std::endl;
492484
DataHubItemWidget* widget = (DataHubItemWidget*)m_listWidget->itemWidget(m_listWidget->item(i));
493485
if(widget->id == itemID) {
494486
widget->setDownloaded(true);
@@ -497,7 +489,6 @@ void DataHubBrowser::download(std::string itemID) {
497489
progressWidget->close();
498490
});
499491
m_hub.download(itemID);
500-
std::cout << "asd6" << std::endl;
501492
}
502493

503494
}

source/FAST/Python/setup.py.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ elif is_apple:
146146
'libopenvino_tensorflow_frontend.2230.dylib',
147147
'libopenvino_paddle_frontend.2230.dylib',
148148
'libopenvino_ir_frontend.2230.dylib',
149+
# DCMTK jpeg
150+
'libijg8.dylib',
151+
'libijg12.dylib',
152+
'libijg16.dylib',
149153
])
150154
}
151155
# Override platform tag, or it gets tag 10_15 for some reason
@@ -187,6 +191,10 @@ else:
187191
'libInferenceEngineTensorRT.so',
188192
'libInferenceEngineTensorFlow.so',
189193
'libInferenceEngineONNXRuntime.so',
194+
# DCMTK jpeg
195+
'libijg8.so',
196+
'libijg12.so',
197+
'libijg16.so',
190198
])
191199
}
192200
# Override platform tag

source/FAST/Streamers/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ fast_add_python_interfaces(
2222
)
2323
fast_add_python_shared_pointers(Streamer RandomAccessStreamer FileStreamer MeshFileStreamer)
2424
fast_add_process_object(ImageFileStreamer ImageFileStreamer.hpp)
25+
if(FAST_MODULE_Dicom)
26+
fast_add_sources(DicomMultiFrameStreamer.cpp DicomMultiFrameStreamer.hpp)
27+
fast_add_process_object(DicomMultiFrameStreamer DicomMultiFrameStreamer.hpp)
28+
fast_add_test_sources(Tests/DicomMultiFrameStreamerTests.cpp)
29+
endif()
2530
if(FAST_MODULE_OpenIGTLink)
2631
fast_add_sources(
2732
OpenIGTLinkStreamer.hpp
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#include "DicomMultiFrameStreamer.hpp"
2+
#include "dcmtk/dcmimage/diregist.h"
3+
#include <dcmtk/dcmdata/dcfilefo.h>
4+
#include <dcmtk/dcmdata/dcdeftag.h>
5+
#include <dcmtk/dcmimgle/dcmimage.h>
6+
#include <dcmtk/dcmdata/dcpixel.h>
7+
#include <dcmtk/dcmjpeg/djdecode.h>
8+
#include <FAST/Data/Image.hpp>
9+
#include <memory>
10+
#include <FAST/Algorithms/Color/ColorToGrayscale.hpp>
11+
12+
namespace fast {
13+
14+
/*
15+
template <>
16+
std::string DicomMultiFrameStreamer::getDicomTag<std::string>(ushort group, ushort element) {
17+
if(!m_dataset)
18+
load();
19+
OFString patientID;
20+
m_dataset->findAndGetOFString(DcmTagKey(group, element), patientID);
21+
return patientID.c_str();
22+
}
23+
*/
24+
25+
DicomMultiFrameStreamer::DicomMultiFrameStreamer(std::string filename, bool loop, bool useFramerate, int framerate, bool convertToGrayscale, bool cropToROI) {
26+
createOutputPort(0);
27+
m_filename = filename;
28+
m_convertToGrayscale = convertToGrayscale;
29+
m_cropToROI = cropToROI;
30+
m_useFramerate = useFramerate;
31+
setLooping(loop);
32+
setFramerate(framerate);
33+
DJDecoderRegistration::registerCodecs();// register JPEG codecs
34+
}
35+
36+
void DicomMultiFrameStreamer::execute() {
37+
startStream();
38+
39+
waitForFirstFrame();
40+
}
41+
42+
void DicomMultiFrameStreamer::load() {
43+
if(!fileExists(m_filename))
44+
throw FileNotFoundException(m_filename);
45+
46+
m_image = std::make_shared<DicomImage>(m_filename.c_str());
47+
if(m_image->getStatus() != EIS_Normal) {
48+
throw Exception("Error creating dicom image object");
49+
}
50+
m_fileFormat = std::make_shared<DcmFileFormat>();
51+
OFCondition status = m_fileFormat->loadFile(m_filename.c_str());
52+
if(!status.good()) {
53+
m_dataset = m_fileFormat->getDataset();
54+
} else {
55+
throw Exception("Unable to read dicom dataset");
56+
}
57+
if(m_image->getNumberOfFrames() == 1)
58+
throw Exception("Dicom file given to DicomMultiFrameStreamer is not a multi frame file.");
59+
}
60+
61+
void DicomMultiFrameStreamer::generateStream() {
62+
if(!m_image)
63+
load();
64+
const int width = m_image->getWidth();
65+
const int height = m_image->getHeight();
66+
const int frames = m_image->getNumberOfFrames();
67+
68+
Vector3f spacing = Vector3f::Ones();
69+
if(m_useFramerate && getFramerate() == -1) {
70+
// Use stored framerate
71+
OFString cineRate;
72+
OFCondition status = m_dataset->findAndGetOFString(DCM_CineRate, cineRate, 0);
73+
if(status.good())
74+
setFramerate(std::stoi(cineRate.c_str()));
75+
}
76+
77+
// Get pixel spacing and ROI information from ultrasound roi sequence
78+
DcmElement *roiSequenceElement;
79+
m_dataset->findAndGetElement(DCM_SequenceOfUltrasoundRegions, roiSequenceElement);
80+
81+
Vector2i ROIOffset;
82+
Vector2i ROISize;
83+
bool ROIfound = false;
84+
if (roiSequenceElement && roiSequenceElement->ident() == EVR_SQ) {
85+
DcmSequenceOfItems *roiSequence = OFstatic_cast(DcmSequenceOfItems *, roiSequenceElement);
86+
87+
int i = 0;
88+
DcmItem *roiItem = roiSequence->getItem(i);
89+
90+
long int x, y, x1, y1;
91+
roiItem->findAndGetLongInt(DCM_RegionLocationMinX0, x);
92+
roiItem->findAndGetLongInt(DCM_RegionLocationMinY0, y);
93+
roiItem->findAndGetLongInt(DCM_RegionLocationMaxX1, x1);
94+
roiItem->findAndGetLongInt(DCM_RegionLocationMaxY1, y1);
95+
Float64 spacingX, spacingY;
96+
roiItem->findAndGetFloat64(DCM_PhysicalDeltaX, spacingX, 0);
97+
roiItem->findAndGetFloat64(DCM_PhysicalDeltaY, spacingY, 0);
98+
spacing.x() = spacingX;
99+
spacing.y() = spacingY;
100+
ROIOffset = Vector2i(x, y);
101+
ROISize = Vector2i(x1 - x, y1 - y);
102+
ROIfound = true;
103+
}
104+
105+
std::cout << "size: " << width << " " << height << " " << frames << std::endl;
106+
107+
auto previousTime = std::chrono::high_resolution_clock::now();
108+
while(true) {
109+
bool pause = getPause();
110+
if(pause)
111+
waitForUnpause();
112+
pause = getPause();
113+
114+
{
115+
std::unique_lock<std::mutex> lock(m_stopMutex);
116+
if(m_stop) {
117+
m_streamIsStarted = false;
118+
m_firstFrameIsInserted = false;
119+
break;
120+
}
121+
}
122+
int frameNr = getCurrentFrameIndex();
123+
auto frameData = make_uninitialized_unique<uchar>(3*width*height);
124+
m_image->getOutputData(frameData.get(), 3*width*height, 0, frameNr);
125+
auto imageFrame = Image::create(width, height, TYPE_UINT8, 3, std::move(frameData));
126+
imageFrame->setSpacing(spacing);
127+
if(m_convertToGrayscale) {
128+
imageFrame = ColorToGrayscale::create()->connect(imageFrame)->runAndGetOutputData<Image>();
129+
}
130+
if(m_cropToROI && ROIfound) {
131+
imageFrame = imageFrame->crop(ROIOffset, ROISize);
132+
}
133+
if(!pause) {
134+
if(m_framerate > 0) {
135+
std::chrono::duration<float, std::milli> passedTime = std::chrono::high_resolution_clock::now() - previousTime;
136+
std::chrono::duration<int, std::milli> sleepFor(1000 / m_framerate - (int)passedTime.count());
137+
if(sleepFor.count() > 0)
138+
std::this_thread::sleep_for(sleepFor);
139+
previousTime = std::chrono::high_resolution_clock::now();
140+
}
141+
getCurrentFrameIndexAndUpdate(); // Update index
142+
}
143+
144+
try {
145+
addOutputData(0, imageFrame);
146+
frameAdded();
147+
if(frameNr == getNrOfFrames()) {
148+
throw FileNotFoundException();
149+
}
150+
} catch(FileNotFoundException &e) {
151+
break;
152+
} catch(ThreadStopped &e) {
153+
break;
154+
}
155+
}
156+
}
157+
158+
int DicomMultiFrameStreamer::getNrOfFrames() {
159+
if(!m_image)
160+
load();
161+
return m_image->getNumberOfFrames();
162+
}
163+
164+
DicomMultiFrameStreamer::DicomMultiFrameStreamer() {
165+
166+
}
167+
168+
DicomMultiFrameStreamer::~DicomMultiFrameStreamer() {
169+
DJDecoderRegistration::cleanup();
170+
}
171+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
3+
#include <FAST/Streamers/RandomAccessStreamer.hpp>
4+
5+
class DicomImage;
6+
class DcmDataset;
7+
class DcmFileFormat;
8+
9+
namespace fast {
10+
11+
/**
12+
* @brief Stream images from a dicom multi-frame image
13+
*
14+
* <h3>Output ports</h3>
15+
* - 0: Image
16+
*
17+
* @ingroup streamers ultrasound
18+
*/
19+
class FAST_EXPORT DicomMultiFrameStreamer : public RandomAccessStreamer {
20+
FAST_PROCESS_OBJECT(DicomMultiFrameStreamer)
21+
public:
22+
/**
23+
* @brief Create instance
24+
* @param filename Dicom file to open
25+
* @param loop Whether to loop or not
26+
* @param useFramerate Whether to use framerate from dicom file or not. If this is set to false, images will be streamed as fast as possible
27+
* @param framerate If framerate is > 0, this framerate will be used for streaming the images
28+
* @param grayscale Convert images to grayscale
29+
* @param cropToROI Try to extract ROI from dicom and crop the images to this ROI
30+
* @return instance
31+
*/
32+
FAST_CONSTRUCTOR(DicomMultiFrameStreamer,
33+
std::string, filename,,
34+
bool, loop, = false,
35+
bool, useFramerate, = true,
36+
int, framerate, = -1,
37+
bool, grayscale, = false,
38+
bool, cropToROI, = false
39+
);
40+
int getNrOfFrames() override;
41+
//template <class T>
42+
//T getDicomTag(ushort group, ushort element);
43+
~DicomMultiFrameStreamer();
44+
private:
45+
DicomMultiFrameStreamer();
46+
void execute();
47+
void generateStream() override;
48+
void load();
49+
50+
bool m_useFramerate = true;
51+
bool m_convertToGrayscale = false;
52+
bool m_cropToROI = false;
53+
std::string m_filename;
54+
std::shared_ptr<DicomImage> m_image;
55+
std::shared_ptr<DcmFileFormat> m_fileFormat;
56+
DcmDataset* m_dataset; // owned by DcmFileFormat
57+
};
58+
59+
//template<>
60+
//std::string DicomMultiFrameStreamer::getDicomTag<std::string>(ushort group, ushort element);
61+
62+
}

source/FAST/Streamers/OpenIGTLinkStreamer.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ FAST_SIMPLE_DATA_OBJECT(String, std::string);
3131
* <h3>Output ports</h3>
3232
* Multiple ports possible dependeing on number of streams from OpenIGTLink server
3333
*
34-
* @ingroup streamers
34+
* @ingroup streamers ultrasound
3535
*/
3636
class FAST_EXPORT OpenIGTLinkStreamer : public Streamer {
3737
FAST_PROCESS_OBJECT(OpenIGTLinkStreamer)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <FAST/Testing.hpp>
2+
#include <FAST/Streamers/DicomMultiFrameStreamer.hpp>
3+
#include <FAST/Visualization/ImageRenderer/ImageRenderer.hpp>
4+
#include <FAST/Visualization/SimpleWindow.hpp>
5+
#include <FAST/Visualization/Widgets/PlaybackWidget/PlaybackWidget.hpp>
6+
7+
using namespace fast;
8+
9+
TEST_CASE("DicomMultiFrameStreamer", "[fast][DicomMultiFrameStreamer][visual]") {
10+
auto streamer = DicomMultiFrameStreamer::create(
11+
"file",
12+
false,
13+
true,
14+
-1,
15+
true,
16+
true
17+
);
18+
19+
CHECK_THROWS(
20+
auto playbackWidget = new PlaybackWidget(streamer);
21+
auto renderer = ImageRenderer::create()->connect(streamer);
22+
23+
auto window = SimpleWindow2D::create()->connect(renderer)->connect(playbackWidget);
24+
window->setTimeout(2000);
25+
window->run();
26+
);
27+
}

0 commit comments

Comments
 (0)