/* Helper function to write a DICOM file using C++ */ static int write_dcm_cpp(const char *path, const Image *const im, const Dcm_meta *const meta, const float max_val) { #define BUF_LEN 1024 char buf[BUF_LEN]; // Ensure the image is monochromatic if (im->nc != 1) { SIFT3D_ERR("write_dcm_cpp: image %s has %d channels. " "Currently only signle-channel images are supported.\n", path, im->nc); return SIFT3D_FAILURE; } // If no metadata was provided, initialize default metadata Dcm_meta meta_new; set_meta_defaults(meta, &meta_new); // Create a new fileformat object DcmFileFormat fileFormat; // Set the file type to derived DcmDataset *const dataset = fileFormat.getDataset(); OFCondition status = dataset->putAndInsertString(DCM_ImageType, "DERIVED"); if (status.bad()) { std::cerr << "write_dcm_cpp: Failed to set the image type" << std::endl; return SIFT3D_FAILURE; } // Set the class UID dataset->putAndInsertString(DCM_SOPClassUID, UID_CTImageStorage); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the SOPClassUID\n"); return SIFT3D_FAILURE; } // Set the photometric interpretation const char *photoInterp; if (im->nc == 1) { photoInterp = "MONOCHROME2"; } else if (im->nc == 3) { photoInterp = "RGB"; } else { SIFT3D_ERR("write_dcm_cpp: failed to determine the " "photometric representation for %d channels \n", im->nc); return SIFT3D_FAILURE; } dataset->putAndInsertString(DCM_PhotometricInterpretation, photoInterp); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the photometric " "interpretation \n"); return SIFT3D_FAILURE; } // Set the pixel representation to unsigned dataset->putAndInsertUint16(DCM_PixelRepresentation, 0); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel " "representation \n"); return SIFT3D_FAILURE; } // Set the number of channels (Samples Per Pixel) and set the planar // configuration to interlaced pixels assert(SIFT3D_IM_GET_IDX(im, 0, 0, 0, 1) == SIFT3D_IM_GET_IDX(im, 0, 0, 0, 0) + 1); snprintf(buf, BUF_LEN, "%d", im->nc); dataset->putAndInsertString(DCM_SamplesPerPixel, buf); dataset->putAndInsertString(DCM_PlanarConfiguration, "0"); // Set the bits allocated and stored, in big endian format const unsigned int dcm_high_bit = dcm_bit_width - 1; dataset->putAndInsertUint16(DCM_BitsAllocated, dcm_bit_width); dataset->putAndInsertUint16(DCM_BitsStored, dcm_bit_width); dataset->putAndInsertUint16(DCM_HighBit, dcm_high_bit); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the bit widths \n"); return SIFT3D_FAILURE; } // Set the patient name status = dataset->putAndInsertString(DCM_PatientName, meta_new.patient_name); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the patient name\n"); return SIFT3D_FAILURE; } // Set the patient ID status = dataset->putAndInsertString(DCM_PatientID, meta_new.patient_id); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the patient ID \n"); return SIFT3D_FAILURE; } // Set the study UID status = dataset->putAndInsertString(DCM_StudyInstanceUID, meta_new.study_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "StudyInstanceUID \n"); return SIFT3D_FAILURE; } // Set the series UID status = dataset->putAndInsertString(DCM_SeriesInstanceUID, meta_new.series_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "SeriesInstanceUID \n"); return SIFT3D_FAILURE; } // Set the series description status = dataset->putAndInsertString(DCM_SeriesDescription, meta_new.series_descrip); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the series " "description \n"); return SIFT3D_FAILURE; } // Set the instance UID status = dataset->putAndInsertString(DCM_SOPInstanceUID, meta_new.instance_uid); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to set the " "SOPInstanceUID \n"); return SIFT3D_FAILURE; } // Set the dimensions OFCondition xstatus = dataset->putAndInsertUint16(DCM_Rows, im->ny); OFCondition ystatus = dataset->putAndInsertUint16(DCM_Columns, im->nx); snprintf(buf, BUF_LEN, "%d", im->nz); OFCondition zstatus = dataset->putAndInsertString(DCM_NumberOfFrames, buf); if (xstatus.bad() || ystatus.bad() || zstatus.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the dimensions \n"); return SIFT3D_FAILURE; } // Set the instance number snprintf(buf, BUF_LEN, "%u", meta_new.instance_num); status = dataset->putAndInsertString(DCM_InstanceNumber, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the instance " "number \n"); return SIFT3D_FAILURE; } // Set the ImagePositionPatient vector const double imPosX = static_cast<double>(im->nx - 1) * im->ux; const double imPosY = static_cast<double>(im->ny - 1) * im->uy; const double imPosZ = static_cast<double>(meta_new.instance_num) * im->uz; snprintf(buf, BUF_LEN, "%f\\%f\\%f", imPosX, imPosY, imPosZ); status = dataset->putAndInsertString(DCM_ImagePositionPatient, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the " "ImagePositionPatient vector \n"); return SIFT3D_FAILURE; } // Set the slice location snprintf(buf, BUF_LEN, "%f", imPosZ); status = dataset->putAndInsertString(DCM_SliceLocation, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the slice " "location \n"); return SIFT3D_FAILURE; } // Set the pixel spacing snprintf(buf, BUF_LEN, "%f\\%f", im->ux, im->uy); status = dataset->putAndInsertString(DCM_PixelSpacing, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel " "spacing \n"); return SIFT3D_FAILURE; } // Set the aspect ratio snprintf(buf, BUF_LEN, "%f\\%f", im->ux, im->uy); status = dataset->putAndInsertString(DCM_PixelAspectRatio, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the pixel aspect " "aspect ratio \n"); return SIFT3D_FAILURE; } // Set the slice thickness snprintf(buf, BUF_LEN, "%f", im->uz); status = dataset->putAndInsertString(DCM_SliceThickness, buf); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: Failed to set the slice " "thickness \n"); return SIFT3D_FAILURE; } // Count the number of pixels in the image unsigned long numPixels = im->dims[0]; for (int i = 1; i < IM_NDIMS; i++) { numPixels *= im->dims[i]; } // Get the image scaling factor const float dcm_max_val = static_cast<float>(1 << dcm_bit_width) - 1.0f; const float im_max = max_val < 0.0f ? im_max_abs(im) : max_val; const float scale = im_max == 0.0f ? 1.0f : dcm_max_val / im_max; // Render the data to an 8-bit unsigned integer array assert(dcm_bit_width == 8); assert(fabsf(dcm_max_val - 255.0f) < FLT_EPSILON); uint8_t *pixelData = new uint8_t[numPixels]; int x, y, z, c; SIFT3D_IM_LOOP_START_C(im, x, y, z, c) const float vox = SIFT3D_IM_GET_VOX(im, x, y, z, c); if (vox < 0.0f) { SIFT3D_ERR("write_dcm_cpp: Image cannot be " "negative \n"); return SIFT3D_FAILURE; } pixelData[c + x + y * im->nx + z * im->nx * im->ny] = static_cast<uint8_t>(vox * scale); SIFT3D_IM_LOOP_END_C // Write the data status = dataset->putAndInsertUint8Array(DCM_PixelData, pixelData, numPixels); delete[] pixelData; if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to set the pixel data \n"); return SIFT3D_FAILURE; } // Choose the encoding format #if 0 DJEncoderRegistration::registerCodecs(); const E_TransferSyntax xfer = EXS_JPEGProcess14SV1TransferSyntax; DJ_RPLossless rp_lossless; status = dataset->chooseRepresentation(xfer, &rp_lossless); #else const E_TransferSyntax xfer = EXS_LittleEndianExplicit; dataset->chooseRepresentation(xfer, NULL); #endif if (!dataset->canWriteXfer(xfer)) { SIFT3D_ERR("write_dcm_cpp: Failed to choose the encoding " "format \n"); return SIFT3D_FAILURE; } // Force the media storage UIDs to be re-generated by removing them dataset->remove(DCM_MediaStorageSOPClassUID); dataset->remove(DCM_MediaStorageSOPInstanceUID); // Save the file status = fileFormat.saveFile(path, xfer); if (status.bad()) { SIFT3D_ERR("write_dcm_cpp: failed to write file %s (%s) \n", path, status.text()); return SIFT3D_FAILURE; } return SIFT3D_SUCCESS; #undef BUF_LEN }
/* Helper funciton to read a directory of DICOM files using C++ */ static int read_dcm_dir_cpp(const char *path, Image *const im) { struct stat st; DIR *dir; struct dirent *ent; int i, nx, ny, nz, nc, num_files, off_z; // Verify that the directory exists if (stat(path, &st)) { SIFT3D_ERR("read_dcm_dir_cpp: cannot find file %s \n", path); return SIFT3D_FAILURE; } else if (!S_ISDIR(st.st_mode)) { SIFT3D_ERR("read_dcm_dir_cpp: file %s is not a directory \n", path); return SIFT3D_FAILURE; } // Open the directory if ((dir = opendir(path)) == NULL) { SIFT3D_ERR("read_dcm_dir_cpp: unexpected error opening " "directory %s \n", path); return SIFT3D_FAILURE; } // Get all of the .dcm files in the directory std::vector<Dicom> dicoms; while ((ent = readdir(dir)) != NULL) { // Form the full file path std::string fullfile(std::string(path) + sepStr + ent->d_name); // Check if it is a DICOM file if (im_get_format(fullfile.c_str()) != DICOM) continue; // Read the file Dicom dicom(fullfile); if (!dicom.isValid()) { closedir(dir); return SIFT3D_FAILURE; } // Add the file to the list dicoms.push_back(dicom); } // Release the directory closedir(dir); // Get the number of files num_files = dicoms.size(); // Verify that dicom files were found if (num_files == 0) { SIFT3D_ERR("read_dcm_dir_cpp: no DICOM files found in %s \n", path); return SIFT3D_FAILURE; } // Check that the files are from the same series const Dicom &first = dicoms[0]; for (int i = 1; i < num_files; i++) { const Dicom &dicom = dicoms[i]; if (!first.eqSeries(dicom)) { SIFT3D_ERR("read_dcm_dir_cpp: file %s is from a " "different series than file %s \n", dicom.name().c_str(), first.name().c_str()); return SIFT3D_FAILURE; } } // Initialize the output dimensions nx = first.getNx(); ny = first.getNy(); nc = first.getNc(); // Verify the dimensions of the other files, counting the total // series z-dimension nz = 0; for (i = 0; i < num_files; i++) { // Get a slice const Dicom &dicom = dicoms[i]; // Verify the dimensions if (dicom.getNx() != nx || dicom.getNy() != ny || dicom.getNc() != nc) { SIFT3D_ERR("read_dcm_dir_cpp: slice %s " "(%d, %d, %d) does not match the " "dimensions of slice %s (%d, %d, %d) \n", dicom.name().c_str(), dicom.getNx(), dicom.getNy(), dicom.getNc(), first.name().c_str(), nx, ny, nc); return SIFT3D_FAILURE; } // Count the z-dimension nz += dicom.getNz(); } // Resize the output im->nx = nx; im->ny = ny; im->nz = nz; im->nc = nc; im->ux = first.getUx(); im->uy = first.getUy(); im->uz = first.getUz(); im_default_stride(im); if (im_resize(im)) return SIFT3D_FAILURE; // Sort the slices by z position std::sort(dicoms.begin(), dicoms.end()); // Allocate a temporary image for the slices Image slice; init_im(&slice); // Read the image data off_z = 0; for (i = 0; i < num_files; i++) { int x, y, z, c; const char *slicename = dicoms[i].name().c_str(); // Read the slice if (read_dcm(slicename, &slice)) { im_free(&slice); return SIFT3D_FAILURE; } // Copy the data to the volume SIFT3D_IM_LOOP_START_C(&slice, x, y, z, c) SIFT3D_IM_GET_VOX(im, x, y, z + off_z, c) = SIFT3D_IM_GET_VOX(&slice, x, y, z, c); SIFT3D_IM_LOOP_END_C off_z += slice.nz; } assert(off_z == nz); im_free(&slice); return SIFT3D_SUCCESS; }
/* Load the data from a DICOM file */ Dicom::Dicom(std::string path) : filename(path), valid(false) { // Load the image as a DcmFileFormat DcmFileFormat fileFormat; OFCondition status = fileFormat.loadFile(path.c_str()); if (status.bad()) { SIFT3D_ERR("Dicom.Dicom: failed to read DICOM file %s (%s)\n", path.c_str(), status.text()); return; } // Get the dataset DcmDataset *const data = fileFormat.getDataset(); // Get the series UID const char *seriesUIDStr; status = data->findAndGetString(DCM_SeriesInstanceUID, seriesUIDStr); if (status.bad() || seriesUIDStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get SeriesInstanceUID " "from file %s (%s)\n", path.c_str(), status.text()); return; } seriesUID = std::string(seriesUIDStr); #if 0 // Read the patient position const char *patientPosStr; status = data->findAndGetString(DCM_PatientPosition, patientPosStr); if (status.bad() || patientPosStr == NULL) { std::cerr << "Dicom.Dicom: failed to get PatientPosition " << "from file " << path << " (" << status.text() << ")" << std::endl; return; } // Interpret the patient position to give the sign of the z axis double zSign; switch (patientPosStr[0]) { case 'H': zSign = -1.0; break; case 'F': zSign = 1.0; break; default: std::cerr << "Dicom.Dicom: unrecognized patient position: " << patientPosStr << std::endl; return; } #else //TODO: Is this needed? const double zSign = 1.0; #endif // Read the image position patient vector const char *imPosPatientStr; status = data->findAndGetString(DCM_ImagePositionPatient, imPosPatientStr); if (status.bad() || imPosPatientStr == NULL) { SIFT3D_ERR("Dicom.Dicom: failed to get ImagePositionPatient " "from file %s (%s)\n", path.c_str(), status.text()); return; } // Parse the image position patient vector to get the z coordinate double imPosZ; if (sscanf(imPosPatientStr, "%*f\\%*f\\%lf", &imPosZ) != 1) { SIFT3D_ERR("Dicom.Dicom: failed to parse " "ImagePositionPatient tag %s from file %s\n", imPosPatientStr, path.c_str()); return; } // Compute the z-location of the upper-left corner, in feet-first // coordinates z = zSign * imPosZ; // Load the DicomImage object DicomImage dicomImage(path.c_str()); if (dicomImage.getStatus() != EIS_Normal) { SIFT3D_ERR("Dicom.Dicom: failed to open image %s (%s)\n", path.c_str(), DicomImage::getString(dicomImage.getStatus())); return; } // Check for color images if (!dicomImage.isMonochrome()) { SIFT3D_ERR("Dicom.Dicom: reading of color DICOM images is " "not supported at this time \n"); return; } nc = 1; // Read the dimensions nx = dicomImage.getWidth(); ny = dicomImage.getHeight(); nz = dicomImage.getFrameCount(); if (nx < 1 || ny < 1 || nz < 1) { SIFT3D_ERR("Dicom.Dicom: invalid dimensions for file %s " "(%d, %d, %d)\n", path.c_str(), nx, ny, nz); return; } // Read the pixel spacing const char *pixelSpacingStr; status = data->findAndGetString(DCM_PixelSpacing, pixelSpacingStr); if (status.bad()) { SIFT3D_ERR("Dicom.Dicom: failed to get pixel spacing from " "file %s (%s)\n", path.c_str(), status.text()); return; } if (sscanf(pixelSpacingStr, "%lf\\%lf", &ux, &uy) != 2) { SIFT3D_ERR("Dicom.Dicom: unable to parse pixel spacing from " "file %s \n", path.c_str()); return; } if (ux <= 0.0 || uy <= 0.0) { SIFT3D_ERR("Dicom.Dicom: file %s has invalid pixel spacing " "[%f, %f]\n", path.c_str(), ux, uy); return; } // Read the slice thickness Float64 sliceThickness; status = data->findAndGetFloat64(DCM_SliceThickness, sliceThickness); if (!status.good()) { SIFT3D_ERR("Dicom.Dicom: failed to get slice thickness from " "file %s (%s)\n", path.c_str(), status.text()); return; } // Convert to double uz = sliceThickness; if (uz <= 0.0) { SIFT3D_ERR("Dicom.Dicom: file %s has invalid slice " "thickness: %f \n", path.c_str(), uz); return; } // Set the window dicomImage.setMinMaxWindow(); valid = true; }
/* Helper function to read a DICOM file using C++ */ static int read_dcm_cpp(const char *path, Image *const im) { // Read the image metadata Dicom dicom(path); if (!dicom.isValid()) return SIFT3D_FAILURE; // Load the DicomImage object DicomImage dicomImage(path); if (dicomImage.getStatus() != EIS_Normal) { SIFT3D_ERR("read_dcm_cpp: failed to open image %s (%s)\n", path, DicomImage::getString(dicomImage.getStatus())); return SIFT3D_FAILURE; } // Initialize the image fields im->nx = dicom.getNx(); im->ny = dicom.getNy(); im->nz = dicom.getNz(); im->nc = dicom.getNc(); im->ux = dicom.getUx(); im->uy = dicom.getUy(); im->uz = dicom.getUz(); // Resize the output im_default_stride(im); if (im_resize(im)) return SIFT3D_FAILURE; // Get the bit depth of the image const int bufNBits = 32; const int depth = dicomImage.getDepth(); if (depth > bufNBits) { SIFT3D_ERR("read_dcm_cpp: buffer is insufficiently wide " "for %d-bit data of image %s \n", depth, path); return SIFT3D_FAILURE; } // Get the number of bits by which we need to shift the 32-bit data, to // recover the original resolution (DICOM uses Big-endian encoding) const uint32_t shift = isLittleEndian() ? static_cast<uint32_t>(bufNBits - depth) : 0; // Read each frame for (int i = 0; i < im->nz; i++) { // Get a pointer to the data, rendered as a 32-bit int const uint32_t *const frameData = static_cast<const uint32_t *const>( dicomImage.getOutputData( static_cast<int>(bufNBits), i)); if (frameData == NULL) { SIFT3D_ERR("read_dcm_cpp: could not get data from " "image %s frame %d (%s)\n", path, i, DicomImage::getString(dicomImage.getStatus())); return SIFT3D_FAILURE; } // Copy the frame const int x_start = 0; const int y_start = 0; const int z_start = i; const int x_end = im->nx - 1; const int y_end = im->ny - 1; const int z_end = z_start; int x, y, z; SIFT3D_IM_LOOP_LIMITED_START(im, x, y, z, x_start, x_end, y_start, y_end, z_start, z_end) // Get the voxel and shift it to match the original // magnitude const uint32_t vox = frameData[x + y * im->nx] >> shift; // Convert to float and write to the output image SIFT3D_IM_GET_VOX(im, x, y, z, 0) = static_cast<float>(vox); SIFT3D_IM_LOOP_END } return SIFT3D_SUCCESS; }
/* Print an error message */ static void err_msg(const char *msg) { SIFT3D_ERR("kpSift3D: %s \n" "Use \"kpSift3D --help\" for more information. \n", msg); }