File: DicomComponent.cpp

package info (click to toggle)
camitk 6.0.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 389,508 kB
  • sloc: cpp: 103,476; sh: 2,448; python: 1,618; xml: 984; makefile: 128; perl: 84; sed: 20
file content (336 lines) | stat: -rw-r--r-- 12,132 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/
// CamiTK includes
#include "DicomComponent.h"

#include <ImageOrientationHelper.h>
#include <FrameOfReference.h>
#include <Transformation.h>
#include <TransformationManager.h>
#include <Log.h>
#include "DicomParser.h"
#include "DicomSeries.h"

// Qt includes
#include <QDir>

// VTK includes
#include <vtkStringArray.h>
#include <vtkPointData.h>
#include <vtkImageChangeInformation.h>

// GDCM includes
#include <vtkGDCMImageReader.h>
#include <gdcmReader.h>
#include <gdcmScanner.h>
#include <gdcmIPPSorter.h>

using namespace camitk;

// --------------- Constructor -------------------
DicomComponent::DicomComponent(DicomSeries* dicomSeries) : ImageComponent("", "Dicom Image") {

    // associated the component's dicom series
    series = dicomSeries;

    std::vector<std::string> stdFileNames = dicomSeries->getStdFileNames();
    // scan files for series description
    gdcm::Scanner scanner;
    gdcm::Tag seriesDescriptionTag = gdcm::Tag(0x0008, 0x103e);
    scanner.AddTag(seriesDescriptionTag);
    scanner.Scan(stdFileNames);
    QString imageName = QString(scanner.GetValue(stdFileNames.at(0).c_str(), seriesDescriptionTag));
    setName(imageName);
    frameOfReference->setName(imageName + " (main)");
    TransformationManager::getFrameOfReferenceOwnership(getDataFrame())->setName(imageName + " (data)");

    // Use Image Position Patient filter (IPPSorter) to correctly sort slices according to their Z spacing
    // Also deduce the actual Z spacing
    std::vector< std::string > files;
    double zSpacing = 0.0;
    if (stdFileNames.size() > 1) {
        gdcm::IPPSorter ippSorter;
        ippSorter.SetComputeZSpacing(true);
        ippSorter.SetZSpacingTolerance(0.001);
        if (!ippSorter.Sort(stdFileNames)) {
            CAMITK_ERROR(tr("IPPSorter sorting failed. Try to adjust Z spacing tolerance."))
            files = stdFileNames;
            zSpacing = DicomParser::getZSpacing(files);
        }
        else {
            files = ippSorter.GetFilenames();
            zSpacing = ippSorter.GetZSpacing();
        }
    }
    else {
        files = stdFileNames;
        zSpacing = DicomParser::getZSpacing(files);
    }


    // convert this list as a vtkStringArray
    vtkSmartPointer<vtkStringArray> fileNamesSorted = vtkSmartPointer<vtkStringArray>::New();
    for (std::string file : files) {
        fileNamesSorted->InsertNextValue(file.c_str());
    }

    // get image orientation information
    // we need to get the rotation matrix from tag "Direct cos angle"
    // ImageOrientationHelper::PossibleImageOrientations orientationFromCos = readDirectCosinesAngle(files);


    // get the image position orientation
    // TODO read the tag (0018, 5100) Patient position
    // https://dicom.nema.org/medical/Dicom/2016e/output/chtml/part03/sect_C.7.3.html#sect_C.7.3.1.1.2

    // create image data corresponding to the component
    imageReader = vtkSmartPointer<vtkGDCMImageReader>::New();
    if (fileNamesSorted->GetSize() == 1) {
        imageReader->SetFileName(fileNamesSorted->GetValue(0).c_str());
    }
    else {
        imageReader->SetFileNames(fileNamesSorted);
    }
    imageReader->Update();
    vtkSmartPointer<vtkImageData> rawData = imageReader->GetOutput();

    /// TODO Store Image position patient as a CamiTK property?

    // Update Z-spacing
    // vtkGDCMImageReader misses this information, see: http://gdcm.sourceforge.net/2.4/html/classvtkGDCMImageReader.html#details
    // Use the value found from IPPSorter or DicomParser::GetZSpacing()
    double* spacing = imageReader->GetDataSpacing();

    // Update Z Spacing using a VTK pipeline
    vtkSmartPointer<vtkImageChangeInformation> imageInfoChanger = vtkSmartPointer<vtkImageChangeInformation>::New();
    imageInfoChanger->SetInputData(rawData); //translatedData);
    double origin[3] = {0.0, 0.0, 0.0}; //We set the image origin at (0,0,0)
    imageInfoChanger->SetOutputOrigin(origin);
    imageInfoChanger->SetOutputSpacing(spacing[0], spacing[1], zSpacing);
    imageInfoChanger->Update();

    // Flip all actors in the Y axis
    // Explanation:
    // DICOM stores the upper left pixel as the first pixel in an image.
    // However, VTK stores the lower left pixel as the first pixel in an image
    // As we based our image frame on RAI coordinate (DICOM LPS located at the bottom left hand corner)
    // with a Radiologist point of view (Axial, Coronal, Sagittal)
    // To ensure that VTK frame is in RAI coordinates, we flip the image in the Y axis
    vtkSmartPointer<vtkImageFlip> flipYFilter = vtkSmartPointer<vtkImageFlip>::New();
    flipYFilter->SetFilteredAxis(1); // flip y axis
    flipYFilter->SetInputConnection(imageInfoChanger->GetOutputPort());
    flipYFilter->Update();

    // Get DICOM patient position and orientation and set it as this component's frame transform
    double* position = imageReader->GetImagePositionPatient();
    double* orientation = imageReader->GetImageOrientationPatient();

    double* xdir = &orientation[0];
    double* ydir = &orientation[3];
    double  zdir[3];
    vtkMath::Cross(xdir, ydir, zdir);
    vtkSmartPointer<vtkMatrix4x4> affineMatrix = vtkSmartPointer<vtkMatrix4x4>::New();

    for (int i = 0; i < 3; i++) {
        affineMatrix->Element[i][0] = xdir[i];
        affineMatrix->Element[i][1] = ydir[i];
        affineMatrix->Element[i][2] = zdir[i];
        affineMatrix->Element[i][3] = position[i];
    }

    // get the processed image (flipped and with correct z-spacing)
    vtkSmartPointer<vtkImageData> image = flipYFilter->GetOutput();
    setImageData(image, false);

    //apply the affine matrix to the image
    updateMainTransformation(affineMatrix);

    // Wait for the LUT update in CamiTK and / or support for color image
    if (getLut()) { // sometimes ImageComponent happens not to have a lut, strange behaviour
        updateLUT();
    }
    else {
        CAMITK_WARNING(tr("Image LUT is null."))
    }

}

// --------------- destructor -------------------
DicomComponent::~DicomComponent() {
    if (series != nullptr) {
        delete series;
    }
}

// --------------- updateLUT -------------------
void DicomComponent::updateLUT() {
    // Update LUT
    // Initialize our lut with vtkGDCMImageReader information found, as our LUT needs repair ...
    double range[2] = {0.0, 0.0};
    imageReader->GetOutput()->GetScalarRange(range);
    getLut()->SetRange(range);  // we need to set up range and table values
    getLut()->SetLevel((range[1] + range[0]) / 2);
    getLut()->SetNumberOfTableValues(abs(range[0]) + abs(range[1]));
}

// --------------- readDirectCosinesAngle -------------------
camitk::ImageOrientationHelper::PossibleImageOrientations DicomComponent::readDirectCosinesAngle(const std::vector< std::string >& fileNames) const {

    // scan files Image Orientation Patient
    gdcm::Scanner scanner;
    gdcm::Tag iopTag = gdcm::Tag(0x0020, 0x0037);
    scanner.AddTag(iopTag);
    // Scan should never failed, since DicomParser::parseDirectory() has already filter files.
    CAMITK_ERROR_IF((!scanner.Scan(fileNames)), tr("Scan failed looking for tag (0x0020, 0x0037) Image Orientation Patient."))

    // Check value tag exists
    gdcm::Scanner::TagToValue const& ttv = scanner.GetMapping(fileNames[0].c_str());
    gdcm::Scanner::TagToValue::const_iterator it = ttv.find(iopTag);
    if (!(it != ttv.end())) {
        CAMITK_WARNING(tr("No tag (0x0020, 0x0037) Image Orientation Patient found on image \"%1\"").arg(QString::fromStdString(fileNames[0])))
        return ImageOrientationHelper::UNKNOWN;
    }

    // Then we know it exists
    std::string value = scanner.GetValue(fileNames[0].c_str(), iopTag);

    // convert the string into the appropriate couple of cosine vectors
    double x[3] = {0.0, 0.0, 0.0};
    double y[3] = {0.0, 0.0, 0.0};
    std::sscanf(value.c_str(), R"(%lf\%lf\%lf\%lf\%lf\%lf)", &x[0], &x[1], &x[2], &y[0], &y[1], &y[2]);

    // get the 90 degrees closest cosines for each vector
    x[0] = roundCosine(x[0]);
    x[1] = roundCosine(x[1]);
    x[2] = roundCosine(x[2]);
    y[0] = roundCosine(y[0]);
    y[1] = roundCosine(y[1]);
    y[2] = roundCosine(y[2]);

    // return the appropriate ImageOrientation corresponding to this cosines
    // we only need to check one component of vector X and Y as they are colinear (other components are 0) to one of the frame vectors

    if ((x[0] == 1.0) && (y[1] == 1.0)) { // identity matrix => Image frame is the scanner frame
        return ImageOrientationHelper::RAI;
    }

    // other cases Image frame is different
    if (x[0] == 1.0) {
        if (y[1] == -1.0) {
            return ImageOrientationHelper::RPS;
        }
        if (y[2] == 1.0) {
            return ImageOrientationHelper::RIP;
        }
        if (y[2] == -1.0) {
            return ImageOrientationHelper::RSA;
        }
    }
    if (x[0] == -1.0) {
        if (y[1] == 1.0) {
            return ImageOrientationHelper::LAS;
        }
        if (y[2] == -1.0) {
            return ImageOrientationHelper::LPI;
        }
        if (y[2] == 1.0) {
            return ImageOrientationHelper::LIA;
        }
    }
    if (x[1] == 1.0) {
        if (y[0] == 1.0) {
            return ImageOrientationHelper::ARS;
        }
        if (y[0] == -1.0) {
            return ImageOrientationHelper::ALI;
        }
        if (y[2] == 1.0) {
            return ImageOrientationHelper::AIR;
        }
        if (y[2] == -1.0) {
            return ImageOrientationHelper::ASL;
        }
    }
    if (x[1] == -1.0) {
        if (y[0] == 1.0) {
            return ImageOrientationHelper::PRI;
        }
        if (y[0] == -1.0) {
            return ImageOrientationHelper::PLS;
        }
        if (y[2] == 1.0) {
            return ImageOrientationHelper::PIL;
        }
        if (y[2] == -1.0) {
            return ImageOrientationHelper::PSR;
        }
    }
    if (x[2] == 1.0) {
        if (y[0] == 1.0) {
            return ImageOrientationHelper::IRA;
        }
        if (y[0] == -1.0) {
            return ImageOrientationHelper::ILP;
        }
        if (y[1] == 1.0) {
            return ImageOrientationHelper::IAL;
        }
        if (y[1] == -1.0) {
            return ImageOrientationHelper::IPR;
        }
    }
    if (x[2] == -1.0) {
        if (y[0] == 1.0) {
            return ImageOrientationHelper::SRP;
        }
        if (y[0] == -1.0) {
            return ImageOrientationHelper::SLA;
        }
        if (y[1] == 1.0) {
            return ImageOrientationHelper::SAR;
        }
        if (y[1] == -1.0) {
            return ImageOrientationHelper::SPL;
        }
    }

    // should never return UNKNOW
    CAMITK_WARNING(tr("No orientation found for this image (direct cosines)."))
    return ImageOrientationHelper::UNKNOWN;

}

// --------------- roundCosine -------------------
double DicomComponent::roundCosine(const double& value) const {
    if (value < -0.5) {
        return -1.0;
    }
    if ((value >= -0.5) && (value <= 0.5)) {
        return 0.0;
    }
    else {
        return 1.0;
    }
}