From 3c7f26be3d1db98d09c482ac7021bca0d1e2181f Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sun, 26 Nov 2023 15:35:06 -0500 Subject: [PATCH] Edge cases (https://github.com/rordenlab/dcm2niix/issues/763, https://github.com/rordenlab/dcm2niix/issues/749) --- FILENAMING.md | 5 +- console/main_console.cpp | 6 ++ console/nii_dicom.cpp | 8 ++- console/nii_dicom.h | 3 +- console/nii_dicom_batch.cpp | 127 ++++++++++++++++++++++++++++-------- console/nii_dicom_batch.h | 2 +- 6 files changed, 119 insertions(+), 32 deletions(-) diff --git a/FILENAMING.md b/FILENAMING.md index c637a6e0..436114a1 100644 --- a/FILENAMING.md +++ b/FILENAMING.md @@ -70,7 +70,7 @@ dcm2niix will attempt to write your image using the naming scheme you specify wi ## Special Characters -[Some characters are not permitted](https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names) in file names. The following characters will be replaced with underscorces (`_`). Note that the forbidden characters vary between operating systems (Linux only forbids the forward slash, MacOS forbids forward slash and colon, while Windows forbids any of the characters listed below). To ensure that files can be easily copied between file systems, [dcm2niix restricts file names to characters allowed by Windows](https://github.com/rordenlab/dcm2niix/issues/237). While technically legal in all filesystems, the semicolon can wreak havoc in [Windows](https://stackoverflow.com/questions/3869594/semi-colons-in-windows-filenames) and [Linux](https://forums.plex.tv/t/linux-hates-semicolons-in-file-names/49098/2). +[Some characters are not permitted](https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names) in file names. The following characters will be replaced with underscorces (`_`). Note that the forbidden characters vary between operating systems (Linux only forbids the forward slash, MacOS forbids forward slash and colon, while Windows forbids any of the characters listed below). To ensure that files can be easily copied between file systems, [dcm2niix restricts file names to characters allowed by Windows](https://github.com/rordenlab/dcm2niix/issues/237). While technically legal in all filesystems, the semicolon can wreak havoc in [Windows](https://stackoverflow.com/questions/3869594/semi-colons-in-windows-filenames) and [Linux](https://forums.plex.tv/t/linux-hates-semicolons-in-file-names/49098/2). Likewise, the [dollar sign can cause issues](https://github.com/rordenlab/dcm2niix/issues/749). ### List of Forbidden Characters ``` @@ -83,7 +83,8 @@ dcm2niix will attempt to write your image using the naming scheme you specify wi | (vertical bar or pipe) ? (question mark) * (asterisk) -; (semicolon) +; (semicolon) +$ (dollar sign) ``` [Control characters](https://en.wikipedia.org/wiki/ASCII#Control_characters) like backspace and tab are also forbidden. diff --git a/console/main_console.cpp b/console/main_console.cpp index 4e54a939..8865955b 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -347,6 +347,12 @@ int main(int argc, const char *argv[]) { opts.isAnonymizeBIDS = false; else opts.isAnonymizeBIDS = true; + } else if (argv[i][2] == 'i') { //"-bi M2022" provide BIDS subject ID + i++; + snprintf(opts.bidsSubject, kOptsStr-1, "%s", argv[i]); + } else if (argv[i][2] == 'v') { //"-bv 1222" provide BIDS subject visit + i++; + snprintf(opts.bidsSession, kOptsStr-1, "%s", argv[i]); } else printf("Error: Unknown command line argument: '%s'\n", argv[i]); } else if ((argv[i][1] == 'c') && ((i + 1) < argc)) { diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d5efb0f6..f6a1387d 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -956,6 +956,7 @@ struct TDICOMdata clear_dicom_data() { d.CSA.coilNumber = -1; strcpy(d.CSA.bidsDataType, ""); strcpy(d.CSA.bidsEntitySuffix, ""); + strcpy(d.CSA.bidsTask, ""); return d; } //clear_dicom_data() @@ -4327,6 +4328,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kSeriesTime 0x0008 + (0x0031 << 16) #define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM //#define kContentTime 0x0008+(0x0033 << 16 ) //TM +#define kAccessionNumber 0x0008 + (0x0050 << 16) #define kModality 0x0008 + (0x0060 << 16) //CS #define kManufacturer 0x0008 + (0x0070 << 16) #define kInstitutionName 0x0008 + (0x0080 << 16) @@ -4343,7 +4345,6 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kIconSQ 0x0009 + (0x1110 << 16) #define kPatientName 0x0010 + (0x0010 << 16) #define kPatientID 0x0010 + (0x0020 << 16) -#define kAccessionNumber 0x0008 + (0x0050 << 16) #define kPatientBirthDate 0x0010 + (0x0030 << 16) #define kPatientSex 0x0010 + (0x0040 << 16) #define kPatientAge 0x0010 + (0x1010 << 16) @@ -4480,6 +4481,7 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD #define kScanOptionsSiemens 0x0021 + (0x105C << 16) //CS Siemens ONLY #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText #define kCSASeriesHeaderInfoXA 0x0021 + (0x1019 << 16) +#define kCSASeriesHeaderInfoXA2 0x0021 + (0x11FE << 16) #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS #define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS @@ -4871,7 +4873,7 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD } if ((volumeNumber == 1) && (acquisitionTimePhilips >= 0.0) && (inStackPositionNumber > 0)) { d.CSA.sliceTiming[inStackPositionNumber - 1] = acquisitionTimePhilips; - printf("%d\t%f\n", inStackPositionNumber, acquisitionTimePhilips); + //printf("%d\t%f\n", inStackPositionNumber, acquisitionTimePhilips); acquisitionTimePhilips = - 1.0; } int ndim = nDimIndxVal; @@ -6145,6 +6147,8 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); break; } +// case kCSASeriesHeaderInfoXA2: +// printf("do something profound\n"); case kCSASeriesHeaderInfoXA: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 9bcf3a65..5c11f8bf 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20231005" +#define kDCMdate "v1.0.20231111" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -232,6 +232,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; bool isPhaseMap; char bidsDataType[kDICOMStr]; //anat, func, dwi char bidsEntitySuffix[kDICOMStrLarge]; //anat, func, dwi + char bidsTask[kDICOMStr]; //rest, naming40 }; struct TDICOMdata { long seriesNum; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0670318b..063432b1 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1333,6 +1333,14 @@ tse3d: T2*/ json_Float(fp, "\t\"PatientWeight\": %g,\n", d.patientWeight); //d.patientBirthDate //convert from DICOM YYYYMMDD to JSON //d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY; + //issue 763 following BIDS standard, unit for Age is YEARS + int ageLen = strlen(d.patientAge); + if ((ageLen > 1) && (d.patientAge[ageLen-1] == 'Y')) { + char ageStr[kDICOMStr]; + strcpy(ageStr, d.patientAge); + ageStr[ageLen -1] = '\0'; + fprintf(fp, "\t\"PatientAge\": %d,\n", atoi(ageStr)); + } } if (d.isQuadruped) json_Bool(fp, "\t\"Quadruped\": %s,\n", true); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP] @@ -2330,8 +2338,13 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st allB0 = false; if (numDti > 1) allB0 = false; - if (nConvert > 1) - allB0 = false; + if (nConvert > 1) { + for (int i = 0; i < nConvert; i++) { //for each image + float b0 = dcmList[dcmSort[i].indx].CSA.dtiV[0]; + if (!isSameFloat(b0, 0.0)) + allB0 = false; + } + } if ((numDti == 1) && (dti4D->S[0].V[0] > 50.0)) allB0 = false; if (allB0) { @@ -3364,6 +3377,16 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts strcat(outname, dcm.accessionNumber); if (f == 'H') { printWarning("hazardous (%%h) bids naming experimental\n"); + char bidsSubject[kOptsStr] = "sub-"; + if (strlen(opts.bidsSubject) <= 0) + strcat(bidsSubject, "1"); + else + strcat(bidsSubject, opts.bidsSubject); + char bidsSession[kOptsStr] = "ses-"; + if (strlen(opts.bidsSession) <= 0) + strcat(bidsSession, "1"); + else + strcat(bidsSession, opts.bidsSession); createDummyBidsBoilerplate(pth, (strstr(dcm.CSA.bidsDataType, "func") != NULL)); if (strlen(dcm.CSA.bidsDataType) < 1) { strcat(outname, "Unknown"); @@ -3376,13 +3399,22 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts } else { isAddNamePostFixes = false; - strcat(outname, "sub-1"); + strcat(outname, bidsSubject); + strcat(outname, pathSep); + strcat(outname, bidsSession); strcat(outname, pathSep); strcat(outname, dcm.CSA.bidsDataType); strcat(outname, pathSep); - strcat(outname, "sub-1"); - if (strstr(dcm.CSA.bidsDataType, "func") != NULL) - strcat(outname, "_task-rest"); + strcat(outname, bidsSubject); + strcat(outname, "_"); + strcat(outname, bidsSession); + if (strstr(dcm.CSA.bidsDataType, "func") != NULL) { + strcat(outname, "_task-"); + if (strlen(dcm.CSA.bidsTask) > 0) + strcat(outname, dcm.CSA.bidsTask); + else + strcat(outname, "rest"); + } strcat(outname, dcm.CSA.bidsEntitySuffix); } } @@ -3485,6 +3517,16 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts else strcat(outname, "NA"); } + if (f == 'W') {//Weird includes personal data in filename patientWeight + snprintf(newstr, PATH_MAX, "dob%sg%cwt%d", dcm.patientBirthDate, dcm.patientSex, (int)round(dcm.patientWeight)); + if (strstr(dcm.institutionName, "Richland")) + strcat(newstr, "R"); + strcat(outname, newstr); + } + if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) { + snprintf(newstr, PATH_MAX, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) + strcat(outname, newstr); + } if (f == 'X') strcat(outname, dcm.studyID); if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) { @@ -3629,6 +3671,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts for (size_t pos = 0; pos < strlen(outname); pos++) if ((outname[pos] == '\\') || (outname[pos] == '/') || (outname[pos] == ' ') || (outname[pos] == '<') || (outname[pos] == '>') || (outname[pos] == ':') || (outname[pos] == ';') || (outname[pos] == '"') // || (outname[pos] == '/') || (outname[pos] == '\\') //|| (outname[pos] == '^') issue398 + || (outname[pos] == '$') //issue749 || (outname[pos] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) outname[pos] = '_'; #else @@ -4063,19 +4106,19 @@ int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) { strcat(command, opts.pigzname); if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { char newstr[256]; - //snprintf(newstr, 256, "\"%s -n -f -%d \"", blockSize, opts.gzLevel); - snprintf(newstr, 256, "\"%s -n -f -%d '", blockSize, opts.gzLevel); + snprintf(newstr, 256, "\"%s -n -f -%d \"", blockSize, opts.gzLevel); + //749 snprintf(newstr, 256, "\"%s -n -f -%d '", blockSize, opts.gzLevel); strcat(command, newstr); } else { char newstr[256]; - //snprintf(newstr, 256, "\"%s -n \"", blockSize); - snprintf(newstr, 256, "\"%s -n '", blockSize); + snprintf(newstr, 256, "\"%s -n \"", blockSize); + //749 snprintf(newstr, 256, "\"%s -n '", blockSize); strcat(command, newstr); } strcat(command, fname); //issue749 use single quote to prevent expansion of $ - strcat(command, "'"); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' - //strcat(command, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + //749 strcat(command, "'"); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + strcat(command, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' #if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) DWORD exitCode; PROCESS_INFORMATION ProcessInfo = {0}; @@ -5262,18 +5305,18 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, strcat(command, opts.pigzname); if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { char newstr[256]; - snprintf(newstr, 256, "\" -n -f -%d > '", opts.gzLevel); + snprintf(newstr, 256, "\" -n -f -%d > \"", opts.gzLevel); + //749 snprintf(newstr, 256, "\" -n -f -%d > '", opts.gzLevel); strcat(command, newstr); } else - strcat(command, "\" -n -f > '"); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' + strcat(command, "\" -n -f > \""); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' + //749 strcat(command, "\" -n -f > '"); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' strcat(command, fname); //issue749 single not double quotes so $ character does not cause issues - strcat(command, ".gz'"); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' - //strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' - //strcat(command, "x.gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + //749 strcat(command, ".gz'"); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' if (opts.isVerbose) printMessage("Compress: %s\n", command); - printMessage("Compress: %s\n", command); FILE *pigzPipe; if ((pigzPipe = popen(command, "w")) == NULL) { printError("Unable to open pigz pipe\n"); @@ -6546,7 +6589,7 @@ void setBidsSiemens(struct TDICOMdata *d, int nConvert, int isVerbose, const cha strcpy(modalityBIDS, "sbref"); //if seriesDesc trace", "fa", "adc" isDerived = true; isDirLabel = true; - } else if ((strstr(seqDetails, "_asl") != NULL) || (strstr(seqDetails, "_pasl") != NULL) || (strstr(seqDetails, "pcasl") != NULL) || (strstr(seqDetails, "PCASL") != NULL)) { //prog_asl + } else if ((strstr(seqDetails, "fairest")) || (strstr(seqDetails, "_asl") != NULL) || (strstr(seqDetails, "_pasl") != NULL) || (strstr(seqDetails, "pcasl") != NULL) || (strstr(seqDetails, "PCASL") != NULL)) { //prog_asl strcpy(dataTypeBIDS, "perf"); strcpy(modalityBIDS, "asl"); if (strstr(d->seriesDescription, "_m0") != NULL) @@ -6713,6 +6756,28 @@ void setBidsSiemens(struct TDICOMdata *d, int nConvert, int isVerbose, const cha seqDetails, d->pulseSequenceName, d->sequenceName); if (isDerived) strcpy(dataTypeBIDS, "derived"); + //bork - ARC data follows + /* + if (strstr(dataTypeBIDS, "dwi")) { + if (strstr(d->protocolName, "12 dirs")) + strcpy(dataTypeBIDS, "discard"); + } + if (strstr(dataTypeBIDS, "fmap")) + strcpy(dataTypeBIDS, "discard"); + if (strstr(dataTypeBIDS, "perf")) + strcpy(dataTypeBIDS, "discard"); + if (strstr(dataTypeBIDS, "func")) { + if (d->TR > 9999) { + if (nConvert < 60) + strcpy(dataTypeBIDS, "discard"); + strcpy(d->CSA.bidsTask, "naming40"); + } else { + if (!strcasestr(d->protocolName, "REST")) + strcpy(dataTypeBIDS, "discard"); + } + if (nConvert < 10) + strcpy(dataTypeBIDS, "discard"); + }*/ } // setBidsSiemens() void setBidsPhilips(struct TDICOMdata *d, int nConvert, int isVerbose) { @@ -7050,16 +7115,16 @@ void setBidsGE(struct TDICOMdata *d, int nConvert, int isVerbose, const char *fi strcpy(dataTypeBIDS, "derived"); } // setBidsGE() -void setBids(struct TDICOMdata *d, const char *filename, int nConvert, int isVerbose) { +bool setBids(struct TDICOMdata *d, const char *filename, int nConvert, int isVerbose) { if (d->modality == kMODALITY_PT) { strcpy(d->CSA.bidsDataType, "PET"); strcpy(d->CSA.bidsEntitySuffix, "PET"); - return; + return true; } if (d->modality == kMODALITY_CT) { strcpy(d->CSA.bidsDataType, "CT"); strcpy(d->CSA.bidsEntitySuffix, "CT"); - return; + return true; } if (d->manufacturer == kMANUFACTURER_SIEMENS) setBidsSiemens(d, nConvert, isVerbose, filename); @@ -7067,6 +7132,7 @@ void setBids(struct TDICOMdata *d, const char *filename, int nConvert, int isVer setBidsPhilips(d, nConvert, isVerbose); if (d->manufacturer == kMANUFACTURER_GE) setBidsGE(d, nConvert, isVerbose, filename); + return ((!strstr(d->CSA.bidsDataType, "discard")) && (!strstr(d->CSA.bidsDataType, "derived"))); //printf("%s\\%s\n", d->CSA.bidsDataType, d->CSA.bidsEntitySuffix); } @@ -7974,7 +8040,12 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first checkDateTimeOrder(&dcmList[dcmSort[0].indx], &dcmList[dcmSort[nConvert - 1].indx]); } - setBids(&dcmList[indx0], nameList->str[dcmSort[0].indx], nConvert, opts.isVerbose); + bool ok = setBids(&dcmList[indx0], nameList->str[dcmSort[0].indx], nConvert, opts.isVerbose); + + if (opts.isIgnoreDerivedAnd2D && !ok) { + printMessage("Ignoring derived image(s) of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } int sliceDir = sliceTimingCore(dcmSort, dcmList, &hdr0, opts.isVerbose, nameList->str[dcmSort[0].indx], nConvert, opts); #ifdef myReportSliceFilenames if (sliceDir < 0) { @@ -9850,9 +9921,14 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe #endif #endif //printMessage("%d %s\n",opts->compressFlag, opts->compressname); - strcpy(opts->indir, ""); strcpy(opts->outdir, ""); + strcpy(opts->indir, ""); + strcpy(opts->pigzname, ""); + strcpy(opts->optsname, ""); + strcpy(opts->indirParent, ""); strcpy(opts->imageComments, ""); + strcpy(opts->bidsSubject, ""); + strcpy(opts->bidsSession, ""); opts->isOnlySingleFile = false; //convert all files in a directory, not just a single file opts->isOneDirAtATime = false; opts->isRenameNotConvert = false; @@ -9905,8 +9981,7 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe opts->numSeries = 0; memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber)); strcpy(opts->filename, "%f_%p_%t_%s"); - - opts->isDumpNotConvert = false; + opts->isDumpNotConvert = false; } // setDefaultOpts() #if defined(_WIN64) || defined(_WIN32) diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index c4757bfc..ed09c775 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -76,7 +76,7 @@ extern "C" { bool isDumpNotConvert; bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop, isGuessBidsFilename; int saveFormat, isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, onlySearchDirForDICOM, gzLevel, diffCyclingModeGE; //support for compressed data 0=none, - char filename[kOptsStr], outdir[kOptsStr], indir[kOptsStr], pigzname[kOptsStr], optsname[kOptsStr], indirParent[kOptsStr], imageComments[24]; + char filename[kOptsStr], outdir[kOptsStr], indir[kOptsStr], pigzname[kOptsStr], optsname[kOptsStr], indirParent[kOptsStr], imageComments[24], bidsSubject[kOptsStr], bidsSession[kOptsStr]; 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