Skip to content

Commit

Permalink
BUG: Fix several problems when reading overlays.
Browse files Browse the repository at this point in the history
Problem 1 (#2154): very long time to read overlays in multiframes.
Problem 2 (#2155): multiframe overlays ignore Image Frame Origin.
Problem 3 (#2986): overlay origin read as if it was x\y instead of
row\column.

Overlays are now read with DCMTK, allowing to read only one frame of the
file each time. This greatly reduces the reading time, although there's
still room for improvement. The other problems have also been fixed when
doing the new implementation.

However a new limitation has been introduced (#2987) due to a bug in the
currently used version of DCMTK (3.6.5). The limitation is that all
overlays are cropped to fit in the image confines. This will be fixed
when we upgrade DCMTK to a newer version (DCMTK 3.6.6 has already fixed
its bug).
  • Loading branch information
Woundorf committed Apr 21, 2022
1 parent b75a31b commit 298ee66
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 300 deletions.
19 changes: 19 additions & 0 deletions starviewer/src/core/dicomvaluerepresentationconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ QVector2D DICOMValueRepresentationConverter::decimalStringTo2DDoubleVector(const
return vector2DValue;
}

QVector<int64_t> DICOMValueRepresentationConverter::signed64BitVeryLongToInt64Vector(const QString &signed64BitVeryLongString)
{
// This is necessary because splitRef on an empty string returns a vector with an empty string
if (signed64BitVeryLongString.isEmpty())
{
return {};
}

QVector<QStringRef> strings = signed64BitVeryLongString.splitRef(ValuesSeparator);
QVector<int64_t> values(strings.size());

for (int i = 0; i < strings.size(); i++)
{
values[i] = strings[i].toLongLong();
}

return values;
}

QVector<uint> DICOMValueRepresentationConverter::unsignedLongStringToUintVector(const QString &unsignedLongString)
{
// This is necessary because splitRef on an empty string returns a vector with an empty string
Expand Down
4 changes: 4 additions & 0 deletions starviewer/src/core/dicomvaluerepresentationconverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class DICOMValueRepresentationConverter {
/// ok will be false and a 0 initialized vector will be returned. It's an specific merthod for decimal strings with two values
static QVector2D decimalStringTo2DDoubleVector(const QString &decimalString, bool *ok = 0);

/// Given a string containing multiple DICOM 64-bit signed integers (DICOM VR SV), converts it to a vector of C++ 64-bit signed integers (int64_t).
/// Works also for smaller types.
static QVector<int64_t> signed64BitVeryLongToInt64Vector(const QString &signed64BitVeryLongString);

/// Given a string containing multiple unsigned longs (DICOM VR UL), converts it to a vector of unsigned ints.
static QVector<uint> unsignedLongStringToUintVector(const QString &unsignedLongString);

Expand Down
5 changes: 3 additions & 2 deletions starviewer/src/core/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,9 @@ bool Image::readOverlays(bool splitOverlays)
{
ImageOverlayReader reader;
reader.setFilename(this->getPath());
reader.setNumberOfOverlays(m_numberOfOverlays);
reader.setFrameNumber(m_frameNumber);

if (reader.read())
{
if (splitOverlays)
Expand All @@ -741,7 +744,6 @@ bool Image::readOverlays(bool splitOverlays)
if (!mergeOk)
{
ERROR_LOG("Ha fallat el merge d'overlays! Possible causa: falta de memòria");
DEBUG_LOG("Ha fallat el merge d'overlays! Possible causa: falta de memòria");
return false;
}

Expand All @@ -757,7 +759,6 @@ bool Image::readOverlays(bool splitOverlays)
else
{
ERROR_LOG("Ha fallat la lectura de l'overlay de la imatge amb path: " + this->getPath());
DEBUG_LOG("Ha fallat la lectura de l'overlay de la imatge amb path: " + this->getPath());
return false;
}
}
Expand Down
42 changes: 0 additions & 42 deletions starviewer/src/core/imageoverlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,8 @@
#include "logging.h"
#include "drawerbitmap.h"
#include "imageoverlayregionfinder.h"
#include "mathtools.h"

#include <QRect>
#include <QRegExp>
#include <QStringList>

#include <gdcmOverlay.h>

namespace udg {

Expand Down Expand Up @@ -139,41 +134,6 @@ bool ImageOverlay::operator ==(const udg::ImageOverlay &overlay) const
return equal;
}

ImageOverlay ImageOverlay::fromGDCMOverlay(const gdcm::Overlay &gdcmOverlay)
{
ImageOverlay imageOverlay;
imageOverlay.setRows(gdcmOverlay.GetRows());
imageOverlay.setColumns(gdcmOverlay.GetColumns());
const signed short *origin = gdcmOverlay.GetOrigin();
imageOverlay.setOrigin(static_cast<int>(origin[0]), static_cast<int>(origin[1]));

if (imageOverlay.getColumns() == 0 || imageOverlay.getRows() == 0)
{
imageOverlay.setData(0);
}
else
{
try
{
size_t bufferSize = gdcmOverlay.GetUnpackBufferLength();
unsigned char *buffer = new unsigned char[bufferSize];
gdcmOverlay.GetUnpackBuffer(reinterpret_cast<char*>(buffer), bufferSize);
imageOverlay.setData(buffer);
}
catch (const std::bad_alloc&)
{
imageOverlay.setData(0);

ERROR_LOG(QString("No hi ha memòria suficient per carregar l'overlay [%1*%2] = %3 bytes")
.arg(imageOverlay.getRows()).arg(imageOverlay.getColumns()).arg((unsigned long)imageOverlay.getRows() * imageOverlay.getColumns()));
DEBUG_LOG(QString("No hi ha memòria suficient per carregar l'overlay [%1*%2] = %3 bytes")
.arg(imageOverlay.getRows()).arg(imageOverlay.getColumns()).arg((unsigned long)imageOverlay.getRows() * imageOverlay.getColumns()));
}
}

return imageOverlay;
}

ImageOverlay ImageOverlay::mergeOverlays(const QList<ImageOverlay> &overlaysList, bool &ok)
{
// Fem tria dels overlays que es puguin considerar vàlids
Expand Down Expand Up @@ -244,8 +204,6 @@ ImageOverlay ImageOverlay::mergeOverlays(const QList<ImageOverlay> &overlaysList
{
ERROR_LOG(QString("No hi ha memòria suficient per crear el buffer per l'overlay fusionat [%1*%2] = %3 bytes")
.arg(outRows).arg(outColumns).arg((unsigned long)outRows * outColumns));
DEBUG_LOG(QString("No hi ha memòria suficient per crear el buffer per l'overlay fusionat [%1*%2] = %3 bytes")
.arg(outRows).arg(outColumns).arg((unsigned long)outRows * outColumns));

ok = false;
return ImageOverlay();
Expand Down
8 changes: 0 additions & 8 deletions starviewer/src/core/imageoverlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@
#ifndef UDGIMAGEOVERLAY_H
#define UDGIMAGEOVERLAY_H

#include <QString>
#include <QSharedPointer>

class QRect;

namespace gdcm {
class Overlay;
}

namespace udg {

class DrawerBitmap;
Expand Down Expand Up @@ -66,9 +61,6 @@ class ImageOverlay {
/// Compara aquest overlay amb un altre i diu si són iguals.
bool operator ==(const ImageOverlay &overlay) const;

/// Construeix un ImageOverlay a partir d'un gdcm::Overlay
static ImageOverlay fromGDCMOverlay(const gdcm::Overlay &gdcmOverlay);

/// Fusiona una llista d'overlays en un únic overlay
/// Només fusionarà aquells overlays que reuneixin les condicions necessàries per considerar-se vàlids, és a dir,
/// que tingui un nombre de files i columnes > 0 i que tingui dades. El paràmetre ok, servirà per indicar els casos
Expand Down
104 changes: 79 additions & 25 deletions starviewer/src/core/imageoverlayreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,111 @@

#include "imageoverlayreader.h"

#include "dicomtagreader.h"
//#include "dicomvaluerepresentationconverter.h"
#include "imageoverlay.h"
#include "logging.h"

#include <gdcmImageReader.h>
#include <gdcmOverlay.h>
#include <dcdatset.h> // DcmDataset
#include <dcmimage.h> // DicomImage

namespace udg {

ImageOverlayReader::ImageOverlayReader()
: m_numberOfOverlays(0), m_frameNumber(0)
{
}

ImageOverlayReader::~ImageOverlayReader()
void ImageOverlayReader::setFilename(const QString &filename)
{
m_filename = filename;
}

void ImageOverlayReader::setFilename(const QString &filename)
void ImageOverlayReader::setNumberOfOverlays(int numberOfOverlays)
{
m_filename = filename;
m_numberOfOverlays = numberOfOverlays;
}

void ImageOverlayReader::setFrameNumber(int frameNumber)
{
m_frameNumber = frameNumber;
}

bool ImageOverlayReader::read()
{
m_overlaysList.clear();

gdcm::Image image = getGDCMImageFromFile(m_filename);

for (size_t overlayIndex = 0; overlayIndex < image.GetNumberOfOverlays(); ++overlayIndex)
DICOMTagReader dicomReader(m_filename);
DicomImage dicomImage(dicomReader.getDcmDataset(), dicomReader.getDcmDataset()->getOriginalXfer(), CIF_UsePartialAccessToPixelData, 0, 1);

for (int plane = 0; plane < m_numberOfOverlays; plane++)
{
m_overlaysList << ImageOverlay::fromGDCMOverlay(image.GetOverlay(overlayIndex));
int frameToRead = 0;

DICOMTag numberOfFramesInOverlayTag(DICOMNumberOfFramesInOverlay);
numberOfFramesInOverlayTag.setGroup(dicomImage.getOverlayGroupNumber(plane));

if (dicomReader.hasAttribute(numberOfFramesInOverlayTag))
{
// Multi-frame Overlay Module (PS3.3§C.9.3)
int numberOfFramesInOverlay = dicomReader.getValueAttributeAsQString(numberOfFramesInOverlayTag).toInt();

DICOMTag imageFrameOriginTag(DICOMImageFrameOrigin);
imageFrameOriginTag.setGroup(dicomImage.getOverlayGroupNumber(plane));
int imageFrameOrigin = 0;

if (dicomReader.hasAttribute(imageFrameOriginTag))
{
imageFrameOrigin = dicomReader.getValueAttributeAsQString(imageFrameOriginTag).toInt() - 1; // in DICOM first frame is 1; for us is 0
}

frameToRead = m_frameNumber - imageFrameOrigin;

if (frameToRead < 0 || frameToRead >= numberOfFramesInOverlay)
{
continue; // no overlay for this frame and plane
}
}

// TODO Use the below code after upgrading to DCMTK 3.6.6 or later
// The below code gets the full uncropped overlay but crashes causes a segmentation fault in DCMTK 3.6.5 if overlay origin is not (0,0).
// DICOMTag overlayOriginTag(DICOMOverlayOrigin);
// overlayOriginTag.setGroup(dicomImage.getOverlayGroupNumber(plane));
// QString overlayOrigin = dicomReader.getValueAttributeAsQString(overlayOriginTag);
// const QVector<int64_t> &origin = DICOMValueRepresentationConverter::signed64BitVeryLongToInt64Vector(overlayOrigin);
// int x = origin[1]; // column
// int y = origin[0]; // row

// uint width, height;
// const void *data = dicomImage.getFullOverlayData(plane, width, height, frameToRead);

uint x, y, width, height;
EM_Overlay mode;
const void *data = dicomImage.getOverlayData(plane, x, y, width, height, mode, frameToRead);

if (!data)
{
WARN_LOG(QString("Could not fetch overlay data for plane %1 and frame %2.").arg(plane).arg(m_frameNumber));
continue;
}

ImageOverlay overlay;
overlay.setRows(height);
overlay.setColumns(width);
overlay.setOrigin(x, y);
unsigned char *dataCopy = new unsigned char[width * height];
std::memcpy(dataCopy, data, width * height);
overlay.setData(dataCopy);

m_overlaysList.append(overlay);
}

return true;
return !m_overlaysList.isEmpty();
}

QList<ImageOverlay> ImageOverlayReader::getOverlays() const
{
return m_overlaysList;
}

gdcm::Image ImageOverlayReader::getGDCMImageFromFile(const QString &filename)
{
gdcm::ImageReader imageReader;
imageReader.SetFileName(qPrintable(filename));
if (!imageReader.Read())
{
ERROR_LOG("Ha fallat la lectura del fitxer: " + filename + " [ImageOverlayReader]");
DEBUG_LOG("Ha fallat la lectura del fitxer: " + filename);
return gdcm::Image();
}

return imageReader.GetImage();
}

}
29 changes: 8 additions & 21 deletions starviewer/src/core/imageoverlayreader.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,38 @@

#include <QList>

#include "imageoverlay.h"

namespace gdcm {
class Image;
}

namespace udg {

class ImageOverlay;

/**
Classe per llegir overlays a través d'un arxiu. Per llegir els overlays caldrà assignar primer el nom de l'arxiu
del que volem llegir els overlays i després fer-ne la lectura. Un cop feta la lectura podrem obtenir els overlays amb getOverlays()
Exemple:
\code
ImageOverlayReader reader;
reader.setFilename("C:\samples\image.dcm");
if (read())
{
return reader.getOverlays();
}
\endcode
*/
class ImageOverlayReader {
public:
ImageOverlayReader();
~ImageOverlayReader();

/// Nom del fitxer a partir del que hem de llegir l'overlay
void setFilename(const QString &filename);
/// Sets the number of overlays in the image.
void setNumberOfOverlays(int numberOfOverlays);
/// Sets the frame number of the image. Needed to get the proper overlay in multiframe overlays.
void setFrameNumber(int frameNumber);

/// Un cop donat un nom d'arxiu, en llegeix els overlays. Retorna fals en cas d'error, cert altrament
bool read();

/// Retorna la llista d'overlays llegits
QList<ImageOverlay> getOverlays() const;

private:
/// Ens retorna una gdcm::Image a partir del nom de fitxer especificat. Retornarà nul en cas d'error
virtual gdcm::Image getGDCMImageFromFile(const QString &filename);

private:
/// Nom de l'arxiu del que hem de llegir els overlays
QString m_filename;
/// Number of overlays in the image.
int m_numberOfOverlays;
/// Frame number of the image.
int m_frameNumber;

/// Llista d'overlays llegits
QList<ImageOverlay> m_overlaysList;
Expand Down
4 changes: 0 additions & 4 deletions starviewer/tests/auto/unittests/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ set(SOURCES
test_imageorientation.cpp
test_imageorientationoperationsmapper.cpp
test_imageoverlay.cpp
test_imageoverlayreader.cpp
test_imageoverlayregionfinder.cpp
test_imageplane.cpp
test_leanbodymassformula.cpp
Expand Down Expand Up @@ -133,6 +132,3 @@ target_include_directories(test_core SYSTEM PRIVATE
${DCMTK_INCLUDE_DIRS}/dcmtk/dcmdata
)
target_link_libraries(test_core ${DCMTK_LIBRARIES})

find_package(GDCM REQUIRED)
target_link_libraries(test_core gdcmMSFF)
Loading

0 comments on commit 298ee66

Please sign in to comment.