diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c36d12b9..23aefd8c 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -3152,12 +3152,25 @@ unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) { #ifdef myEnableJasper unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { +#if defined(JAS_VERSION_MAJOR) && JAS_VERSION_MAJOR >= 3 + jas_conf_clear(); + jas_conf_set_debug_level(0); + jas_conf_set_max_mem_usage(1 << 30); // 1 GiB + if (jas_init_library()) { + return NULL; + } + if (jas_init_thread()) { + jas_cleanup_library(); + return NULL; + } +#else if (jas_init()) { return NULL; } + jas_setdbglevel(0); +#endif jas_stream_t *in; jas_image_t *image; - jas_setdbglevel(0); if (!(in = jas_stream_fopen(imgname, "rb"))) { printError("Cannot open input image file %s\n", imgname); return NULL; @@ -3284,6 +3297,10 @@ unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, s jas_matrix_destroy(data); jas_image_destroy(image); jas_image_clearfmts(); +#if defined(JAS_VERSION_MAJOR) && JAS_VERSION_MAJOR >= 3 + jas_cleanup_thread(); + jas_cleanup_library(); +#endif return img; } //nii_loadImgCoreJasper() #endif diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 9980a6ce..9e1138cf 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1221,7 +1221,18 @@ void nii_SaveBIDSX(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".json"); +#ifdef USING_R + FILE *fp = NULL; + if (opts.isImageInMemory) + { + ImageList *images = (ImageList *)opts.imageList; + fp = images->jsonHandle(); + } + if (fp == NULL) + fp = fopen(txtname, "w"); +#else FILE *fp = fopen(txtname, "w"); +#endif fprintf(fp, "{\n"); switch (d.modality) { case kMODALITY_CR: @@ -2246,14 +2257,12 @@ following logic now occurs earlier to aid bids guess fclose(fp); } // nii_SaveBIDSX() -#ifndef USING_R - void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { //swap endian from big->little or little->big // must be told which is native to detect datatype and number of voxels // one could also auto-detect: hdr->sizeof_hdr==348 if (!isNative) -#ifdef USING_MGH_NIFTI_IO +#if defined(USING_MGH_NIFTI_IO) || defined(USING_R) swap_nifti_header(hdr, 1); #else swap_nifti_header(hdr); @@ -2265,7 +2274,7 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { int bitpix = hdr->bitpix; int datatype = hdr->datatype; if (isNative) -#ifdef USING_MGH_NIFTI_IO +#if defined(USING_MGH_NIFTI_IO) || defined(USING_R) swap_nifti_header(hdr, 1); #else swap_nifti_header(hdr); @@ -2281,6 +2290,8 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { nifti_swap_8bytes(nVox, im); } +#ifndef USING_R + void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename) { struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); dti4D->sliceOrder[0] = -1; @@ -3983,175 +3994,7 @@ void writeNiiGz(char *baseName, struct nifti_1_header hdr, unsigned char *src_bu } //writeNiiGz() #endif -#ifdef USING_R - -// Version of nii_saveNII() for R/divest: create nifti_image pointer and push onto stack -int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { - hdr.vox_offset = 352; - // Extract the basename from the full file path - char *start = niiFilename + strlen(niiFilename); - while (start >= niiFilename && *start != '/' && *start != kPathSeparator) - start--; - std::string name(++start); - nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); - if (image == NULL) - return EXIT_FAILURE; - image->data = (void *)im; - ImageList *images = (ImageList *)opts.imageList; - images->append(image, name); - free(image); - return EXIT_SUCCESS; -} - -void nii_saveAttributes(struct TDICOMdata &data, struct nifti_1_header &header, struct TDCMopts &opts, const char *filename) { - ImageList *images = (ImageList *)opts.imageList; - switch (data.modality) { - case kMODALITY_CR: - images->addAttribute("modality", "CR"); - break; - case kMODALITY_CT: - images->addAttribute("modality", "CT"); - break; - case kMODALITY_MR: - images->addAttribute("modality", "MR"); - break; - case kMODALITY_PT: - images->addAttribute("modality", "PT"); - break; - case kMODALITY_US: - images->addAttribute("modality", "US"); - break; - } - switch (data.manufacturer) { - case kMANUFACTURER_SIEMENS: - images->addAttribute("manufacturer", "Siemens"); - break; - case kMANUFACTURER_GE: - images->addAttribute("manufacturer", "GE"); - break; - case kMANUFACTURER_MEDISO: - images->addAttribute("manufacturer", "Mediso"); - break; - case kMANUFACTURER_PHILIPS: - images->addAttribute("manufacturer", "Philips"); - break; - case kMANUFACTURER_TOSHIBA: - images->addAttribute("manufacturer", "Toshiba"); - break; - case kMANUFACTURER_UIH: - images->addAttribute("manufacturer", "UIH"); - break; - case kMANUFACTURER_BRUKER: - images->addAttribute("manufacturer", "Bruker"); - break; - case kMANUFACTURER_HITACHI: - images->addAttribute("manufacturer", "Hitachi"); - break; - case kMANUFACTURER_CANON: - images->addAttribute("manufacturer", "Canon"); - break; - case kMANUFACTURER_MRSOLUTIONS: - images->addAttribute("manufacturer", "MRSolutions"); - break; - case kMANUFACTURER_HYPERFINE: - images->addAttribute("manufacturer", "Hyperfine"); - break; - } - images->addAttribute("scannerModelName", data.manufacturersModelName); - images->addAttribute("imageType", data.imageType); - if (data.seriesNum > 0) - images->addAttribute("seriesNumber", int(data.seriesNum)); - images->addAttribute("seriesDescription", data.seriesDescription); - images->addAttribute("sequenceName", data.sequenceName); - images->addAttribute("protocolName", data.protocolName); - images->addDateAttribute("studyDate", data.studyDate); - images->addTimeAttribute("studyTime", data.studyTime); - images->addAttribute("fieldStrength", data.fieldStrength); - images->addAttribute("flipAngle", data.flipAngle); - images->addAttribute("echoTime", data.TE); - images->addAttribute("repetitionTime", data.TR); - images->addAttribute("inversionTime", data.TI); - if (!data.isXRay) { - images->addAttribute("sliceThickness", data.zThick); - images->addAttribute("sliceSpacing", data.zSpacing); - } - if (data.CSA.multiBandFactor > 1) - images->addAttribute("multibandFactor", data.CSA.multiBandFactor); - if (data.phaseEncodingSteps > 0) - images->addAttribute("phaseEncodingSteps", data.phaseEncodingSteps); - if (data.phaseEncodingLines > 0) - images->addAttribute("phaseEncodingLines", data.phaseEncodingLines); - // Calculations relating to the reconstruction in the phase encode direction, - // which are needed to derive effective echo spacing and readout time below. - // See the nii_SaveBIDS() function for details - int reconMatrixPE = data.phaseEncodingLines; - if ((header.dim[2] > 0) && (header.dim[1] > 0)) { - if (header.dim[1] == header.dim[2]) //phase encoding does not matter - reconMatrixPE = header.dim[2]; - else if (data.phaseEncodingRC == 'C') - reconMatrixPE = header.dim[2]; - else if (data.phaseEncodingRC == 'R') - reconMatrixPE = header.dim[1]; - } - double bandwidthPerPixelPhaseEncode = data.bandwidthPerPixelPhaseEncode; - if (bandwidthPerPixelPhaseEncode == 0.0) - bandwidthPerPixelPhaseEncode = data.CSA.bandwidthPerPixelPhaseEncode; - double effectiveEchoSpacing = 0.0; - if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) - effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); - if (data.effectiveEchoSpacingGE > 0.0) { - double roundFactor = data.isPartialFourier ? 4.0 : 2.0; - double totalReadoutTime = ((ceil(1.0 / roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; - effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); - } - images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); - if (data.manufacturer == kMANUFACTURER_UIH) - images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); - else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) - images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); - images->addAttribute("pixelBandwidth", data.pixelBandwidth); - if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) - images->addAttribute("dwellTime", data.dwellTime * 1e-9); - // Phase encoding polarity - // We only save these attributes if both direction and polarity are known - bool isSkipPhaseEncodingAxis = data.is3DAcq; - if (data.echoTrainLength > 1) - isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI - if (((data.phaseEncodingRC == 'R') || (data.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && ((data.CSA.phaseEncodingDirectionPositive == 1) || (data.CSA.phaseEncodingDirectionPositive == 0))) { - if (data.phaseEncodingRC == 'C') { - images->addAttribute("phaseEncodingDirection", "j"); - // Notice the XOR (^): the sense of phaseEncodingDirectionPositive - // is reversed if we are flipping the y-axis - images->addAttribute("phaseEncodingSign", ((data.CSA.phaseEncodingDirectionPositive == 0) ^ opts.isFlipY) ? -1 : 1); - } else if (data.phaseEncodingRC == 'R') { - images->addAttribute("phaseEncodingDirection", "i"); - images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); - } - } - // Slice timing (stored in seconds) - if (data.CSA.sliceTiming[0] >= 0.0 && (data.manufacturer == kMANUFACTURER_UIH || data.manufacturer == kMANUFACTURER_GE || (data.manufacturer == kMANUFACTURER_SIEMENS && !data.isXA10A))) { - std::vector sliceTimes; - for (int i = 0; i < header.dim[3]; i++) { - if (data.CSA.sliceTiming[i] < 0.0) - break; - sliceTimes.push_back(data.CSA.sliceTiming[i] / 1000.0); - } - images->addAttribute("sliceTiming", sliceTimes); - } - images->addAttribute("patientIdentifier", data.patientID); - images->addAttribute("patientName", data.patientName); - images->addDateAttribute("patientBirthDate", data.patientBirthDate); - if (strlen(data.patientAge) > 0 && strcmp(data.patientAge, "000Y") != 0) - images->addAttribute("patientAge", data.patientAge); - if (data.patientSex == 'F') - images->addAttribute("patientSex", "F"); - else if (data.patientSex == 'M') - images->addAttribute("patientSex", "M"); - images->addAttribute("patientWeight", data.patientWeight); - images->addAttribute("comments", data.imageComments); -} - -#else +#ifndef USING_R int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) { //given "/dir/file.nii" creates "/dir/file.nii.gz" @@ -5303,9 +5146,25 @@ void removeSclSlopeInter(struct nifti_1_header *hdr, unsigned char *img) { //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); } -#ifndef USING_R - int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { +#ifdef USING_R + ImageList *images = (ImageList *)opts.imageList; + if (opts.isImageInMemory) { + hdr.vox_offset = 352; + // Extract the basename from the full file path + char *start = niiFilename + strlen(niiFilename); + while (start >= niiFilename && *start != '/' && *start != kPathSeparator) + start--; + std::string name(++start); + nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); + if (image == NULL) + return EXIT_FAILURE; + image->data = (void *)im; + images->append(image, name); + free(image); + return EXIT_SUCCESS; + } +#else if (opts.isOnlyBIDS) return EXIT_SUCCESS; if (opts.saveFormat != kSaveFormatNIfTI) { @@ -5314,6 +5173,7 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, free(dti4D); return ret; } +#endif hdr.vox_offset = 352; size_t imgsz = nii_ImgBytes(hdr); if (imgsz < 1) { @@ -5342,6 +5202,9 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) writeNiiGz(niiFilename, hdr, im, imgsz, opts.gzLevel, false); +#ifdef USING_R + images->appendPath(std::string(niiFilename) + ".nii.gz"); +#endif if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) return EXIT_SUCCESS; @@ -5410,6 +5273,9 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) #endif +#ifdef USING_R + images->appendPath(fname); +#else if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { #ifndef myDisableGzSizeLimits if ((imgsz + hdr.vox_offset) > kMaxPigz) { @@ -5419,11 +5285,10 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, #endif return pigz_File(fname, opts, imgsz); } +#endif return EXIT_SUCCESS; } // nii_saveNII() -#endif - int nii_saveNIIx(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts) { struct TDICOMdata dcm = clear_dicom_data(); return nii_saveNII(niiFilename, hdr, im, opts, dcm); @@ -6749,7 +6614,7 @@ void setBidsSiemens(struct TDICOMdata *d, int nConvert, int isVerbose, const cha for (int i = 0; i < len; i++) { char ch = seqName[i]; if (ch == '*') continue; - if ((ch >= '0' & ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) strncat(acqStr, &ch, 1); if (isdigit(ch)) break; } @@ -6940,7 +6805,7 @@ void setBidsPhilips(struct TDICOMdata *d, int nConvert, int isVerbose) { if (len > 0) { for (int i = 0; i < len; i++) { char ch = seqName[i]; - if ((ch >= '0' & ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) strncat(acqStr, &ch, 1); } } //len > 0 @@ -6948,7 +6813,7 @@ void setBidsPhilips(struct TDICOMdata *d, int nConvert, int isVerbose) { if (len > 0) { for (int i = 0; i < len; i++) { char ch = d->scanningSequence[i]; - if ((ch >= '0' & ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) strncat(acqStr, &ch, 1); } } //len > 0 @@ -6956,7 +6821,7 @@ void setBidsPhilips(struct TDICOMdata *d, int nConvert, int isVerbose) { if (len > 0) { for (int i = 0; i < len; i++) { char ch = d->pulseSequenceName[i]; - if ((ch >= '0' & ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) strncat(acqStr, &ch, 1); } } //len > 0 @@ -7116,7 +6981,7 @@ void setBidsGE(struct TDICOMdata *d, int nConvert, int isVerbose, const char *fi if (len > 0) { for (int i = 0; i < len; i++) { char ch = seqName[i]; - if ((ch >= '0' & ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) strncat(acqStr, &ch, 1); //if (isdigit(ch)) break; // do not use, unlike Siemens digit can come at start 3DFSE or end EFGRE3D } @@ -8334,7 +8199,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d else { if (volOrderIndex) //reorder volumes imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); -#ifndef USING_R if ((opts.isIgnoreDerivedAnd2D) && (numADC > 0)) printMessage("Ignoring derived diffusion image(s). Better isotropic and ADC maps can be generated later processing.\n"); if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) { //ADC maps can disrupt analysis: save a copy with the ADC map, and another without @@ -8397,11 +8261,12 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_saveNII(pathoutnameROI, hdrr, imgR, opts, dcmList[dcmSort[0].indx]); } } -#endif imgM = removeADC(&hdr0, imgM, numADC); -#ifndef USING_R if (iVaries) printMessage("Saving as 32-bit float (slope, intercept or bits allocated varies).\n"); +#ifndef USING_R + // divest does not support non-NIfTI formats, and requires only one + // image per series, so skip this to avoid double-saving if (opts.saveFormat != kSaveFormatNIfTI) returnCode = nii_saveForeign(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], dti4D, dcmList[indx0].CSA.numDti); else if (opts.isSave3D) @@ -8437,8 +8302,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d // Hence this extra test if (returnCode != EXIT_SUCCESS) returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); - if (returnCode == EXIT_SUCCESS) - nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif #ifdef USING_DCM2NIIXFSWRAPPER @@ -9464,7 +9327,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { for (size_t i = 1; i < nDcm; i++) { bool matched = false; // If the file matches an existing series, add it to the corresponding file list - for (int j = 0; j < opts->series.size(); j++) { + for (size_t j = 0; j < opts->series.size(); j++) { bool isMultiEcho = false, isNonParallelSlices = false, isCoilVaries = false; if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { opts->series[j].files.push_back(nameList.str[i]); diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index ed09c775..f568fe79 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -80,7 +80,7 @@ extern "C" { double seriesNumber[MAX_NUM_SERIES]; //requires double must store -1 (report but do not convert) as well as seriesUidCrc (uint32) long numSeries; #ifdef USING_R - bool isScanOnly; + bool isScanOnly, isImageInMemory; void *imageList; std::vector series;