RNifti
Fast R and C++ Access to NIfTI Images
NiftiImage.h
1 #ifndef _NIFTI_IMAGE_H_
2 #define _NIFTI_IMAGE_H_
3 
4 #include <Rcpp.h>
5 
6 #include "niftilib/nifti1_io.h"
7 
21 {
22 public:
27  struct Block
28  {
29  const NiftiImage &image;
30  const int dimension;
31  const int index;
40  Block (const NiftiImage &image, const int dimension, const int index)
41  : image(image), dimension(dimension), index(index)
42  {
43  if (dimension != image->ndim)
44  throw std::runtime_error("Blocks must be along the last dimension in the image");
45  }
46 
55  Block & operator= (const NiftiImage &source)
56  {
57  if (source->datatype != image->datatype)
58  throw std::runtime_error("New data does not have the same datatype as the target block");
59 
60  size_t blockSize = 1;
61  for (int i=1; i<dimension; i++)
62  blockSize *= image->dim[i];
63 
64  if (blockSize != source->nvox)
65  throw std::runtime_error("New data does not have the same size as the target block");
66 
67  blockSize *= image->nbyper;
68  memcpy(static_cast<char*>(image->data) + blockSize*index, source->data, blockSize);
69  return *this;
70  }
71  };
72 
79  static short sexpTypeToNiftiType (const int sexpType)
80  {
81  if (sexpType == INTSXP || sexpType == LGLSXP)
82  return DT_INT32;
83  else if (sexpType == REALSXP)
84  return DT_FLOAT64;
85  else
86  throw std::runtime_error("Array elements must be numeric");
87  }
88 
89 protected:
90  nifti_image *image;
91  bool persistent;
97  void copy (nifti_image * const source);
98 
103  void copy (const NiftiImage &source);
104 
109  void copy (const Block &source);
110 
116  void initFromNiftiS4 (const Rcpp::RObject &object, const bool copyData = true);
117 
123  void initFromMriImage (const Rcpp::RObject &object, const bool copyData = true);
124 
129  void initFromList (const Rcpp::RObject &object);
130 
136  void initFromArray (const Rcpp::RObject &object, const bool copyData = true);
137 
142  void updatePixdim (const std::vector<float> &pixdim);
143 
148  void setPixunits (const std::vector<std::string> &pixunits);
149 
150 public:
155  : image(NULL), persistent(false) {}
156 
161  NiftiImage (const NiftiImage &source)
162  : image(NULL), persistent(false)
163  {
164  this->copy(source);
165 #ifndef NDEBUG
166  Rprintf("Creating NiftiImage with pointer %p (from NiftiImage)\n", this->image);
167 #endif
168  }
169 
176  NiftiImage (nifti_image * const image, const bool copy = false)
177  : image(NULL), persistent(false)
178  {
179  if (copy)
180  this->copy(image);
181  else
182  this->image = image;
183 #ifndef NDEBUG
184  Rprintf("Creating NiftiImage with pointer %p (from pointer)\n", this->image);
185 #endif
186  }
187 
194  NiftiImage (const std::string &path, const bool readData = true)
195  : persistent(false)
196  {
197  this->image = nifti_image_read(path.c_str(), readData);
198  if (this->image == NULL)
199  throw std::runtime_error("Failed to read image from path " + path);
200 #ifndef NDEBUG
201  Rprintf("Creating NiftiImage with pointer %p (from string)\n", this->image);
202 #endif
203  }
204 
210  NiftiImage (const SEXP object, const bool readData = true);
211 
216  {
217  if (!persistent)
218  {
219 #ifndef NDEBUG
220  Rprintf("Freeing NiftiImage with pointer %p\n", this->image);
221 #endif
222  nifti_image_free(image);
223  }
224  }
225 
229  operator nifti_image* () const { return image; }
230 
234  nifti_image * operator-> () const { return image; }
235 
241  {
242  copy(source);
243 #ifndef NDEBUG
244  Rprintf("Creating NiftiImage with pointer %p (from NiftiImage)\n", this->image);
245 #endif
246  return *this;
247  }
248 
254  NiftiImage & operator= (const Block &source)
255  {
256  copy(source);
257 #ifndef NDEBUG
258  Rprintf("Creating NiftiImage with pointer %p (from Block)\n", this->image);
259 #endif
260  return *this;
261  }
262 
267  void setPersistence (const bool persistent)
268  {
269  this->persistent = persistent;
270 #ifndef NDEBUG
271  if (persistent)
272  Rprintf("Setting NiftiImage with pointer %p to be persistent\n", this->image);
273 #endif
274  }
275 
279  bool isNull () const { return (image == NULL); }
280 
284  bool isPersistent () const { return persistent; }
285 
289  int nDims () const
290  {
291  if (image == NULL)
292  return 0;
293  else
294  return image->ndim;
295  }
296 
304  {
305  int ndim = image->ndim;
306  while (image->dim[ndim] < 2)
307  ndim--;
308  image->dim[0] = image->ndim = ndim;
309 
310  return *this;
311  }
312 
318  void rescale (const std::vector<float> &scales);
319 
324  void update (const SEXP array);
325 
331  mat44 xform (const bool preferQuaternion = true) const;
332 
338  const Block slice (const int i) const { return Block(*this, 3, i); }
339 
345  Block slice (const int i) { return Block(*this, 3, i); }
346 
352  const Block volume (const int i) const { return Block(*this, 4, i); }
353 
359  Block volume (const int i) { return Block(*this, 4, i); }
360 
366  void toFile (const std::string fileName, const short datatype) const;
367 
373  void toFile (const std::string fileName, const std::string &datatype) const;
374 
379  Rcpp::RObject toArray () const;
380 
386  Rcpp::RObject toPointer (const std::string label) const;
387 
394  Rcpp::RObject toArrayOrPointer (const bool internal, const std::string label) const;
395 
400  Rcpp::RObject headerToList () const;
401 };
402 
403 inline void NiftiImage::copy (nifti_image * const source)
404 {
405  if (image != NULL)
406  nifti_image_free(image);
407 
408  if (source == NULL)
409  image = NULL;
410  else
411  {
412  image = nifti_copy_nim_info(source);
413  if (source->data != NULL)
414  {
415  size_t dataSize = nifti_get_volsize(source);
416  image->data = calloc(1, dataSize);
417  memcpy(image->data, source->data, dataSize);
418  }
419  }
420 }
421 
422 inline void NiftiImage::copy (const NiftiImage &source)
423 {
424  nifti_image *sourceStruct = source;
425  copy(sourceStruct);
426 }
427 
428 inline void NiftiImage::copy (const Block &source)
429 {
430  if (image != NULL)
431  nifti_image_free(image);
432 
433  nifti_image *sourceStruct = source.image;
434  if (sourceStruct == NULL)
435  image = NULL;
436  else
437  {
438  image = nifti_copy_nim_info(sourceStruct);
439  image->dim[0] = source.image->dim[0] - 1;
440  image->dim[source.dimension] = 1;
441  image->pixdim[source.dimension] = 1.0;
442  nifti_update_dims_from_array(image);
443 
444  if (sourceStruct->data != NULL)
445  {
446  size_t blockSize = nifti_get_volsize(image);
447  image->data = calloc(1, blockSize);
448  memcpy(image->data, static_cast<char*>(source.image->data) + blockSize*source.index, blockSize);
449  }
450  }
451 }
452 
453 // Convert an S4 "nifti" object, as defined in the oro.nifti package, to a "nifti_image" struct
454 inline void NiftiImage::initFromNiftiS4 (const Rcpp::RObject &object, const bool copyData)
455 {
456  nifti_1_header header;
457  header.sizeof_hdr = 348;
458 
459  const std::vector<short> dims = object.slot("dim_");
460  for (int i=0; i<8; i++)
461  header.dim[i] = dims[i];
462 
463  header.intent_p1 = object.slot("intent_p1");
464  header.intent_p2 = object.slot("intent_p2");
465  header.intent_p3 = object.slot("intent_p3");
466  header.intent_code = object.slot("intent_code");
467 
468  header.datatype = object.slot("datatype");
469  header.bitpix = object.slot("bitpix");
470 
471  header.slice_start = object.slot("slice_start");
472  header.slice_end = object.slot("slice_end");
473  header.slice_code = Rcpp::as<int>(object.slot("slice_code"));
474  header.slice_duration = object.slot("slice_duration");
475 
476  const std::vector<float> pixdims = object.slot("pixdim");
477  for (int i=0; i<8; i++)
478  header.pixdim[i] = pixdims[i];
479  header.xyzt_units = Rcpp::as<int>(object.slot("xyzt_units"));
480 
481  header.vox_offset = object.slot("vox_offset");
482 
483  header.scl_slope = object.slot("scl_slope");
484  header.scl_inter = object.slot("scl_inter");
485  header.toffset = object.slot("toffset");
486 
487  header.cal_max = object.slot("cal_max");
488  header.cal_min = object.slot("cal_min");
489  header.glmax = header.glmin = 0;
490 
491  strncpy(header.descrip, Rcpp::as<std::string>(object.slot("descrip")).c_str(), 79);
492  header.descrip[79] = '\0';
493  strncpy(header.aux_file, Rcpp::as<std::string>(object.slot("aux_file")).c_str(), 23);
494  header.aux_file[23] = '\0';
495  strncpy(header.intent_name, Rcpp::as<std::string>(object.slot("intent_name")).c_str(), 15);
496  header.intent_name[15] = '\0';
497  strncpy(header.magic, Rcpp::as<std::string>(object.slot("magic")).c_str(), 3);
498  header.magic[3] = '\0';
499 
500  header.qform_code = object.slot("qform_code");
501  header.sform_code = object.slot("sform_code");
502 
503  header.quatern_b = object.slot("quatern_b");
504  header.quatern_c = object.slot("quatern_c");
505  header.quatern_d = object.slot("quatern_d");
506  header.qoffset_x = object.slot("qoffset_x");
507  header.qoffset_y = object.slot("qoffset_y");
508  header.qoffset_z = object.slot("qoffset_z");
509 
510  const std::vector<float> srow_x = object.slot("srow_x");
511  const std::vector<float> srow_y = object.slot("srow_y");
512  const std::vector<float> srow_z = object.slot("srow_z");
513  for (int i=0; i<4; i++)
514  {
515  header.srow_x[i] = srow_x[i];
516  header.srow_y[i] = srow_y[i];
517  header.srow_z[i] = srow_z[i];
518  }
519 
520  if (header.datatype == DT_UINT8 || header.datatype == DT_INT16 || header.datatype == DT_INT32 || header.datatype == DT_INT8 || header.datatype == DT_UINT16 || header.datatype == DT_UINT32)
521  header.datatype = DT_INT32;
522  else if (header.datatype == DT_FLOAT32 || header.datatype == DT_FLOAT64)
523  header.datatype = DT_FLOAT64; // This assumes that sizeof(double) == 8
524  else
525  throw std::runtime_error("Data type is not supported");
526 
527  this->image = nifti_convert_nhdr2nim(header, NULL);
528 
529  const SEXP data = PROTECT(object.slot(".Data"));
530  if (!copyData || Rf_length(data) <= 1)
531  this->image->data = NULL;
532  else
533  {
534  const size_t dataSize = nifti_get_volsize(this->image);
535  this->image->data = calloc(1, dataSize);
536  if (header.datatype == DT_INT32)
537  {
538  Rcpp::IntegerVector intData(data);
539  std::copy(intData.begin(), intData.end(), static_cast<int32_t*>(this->image->data));
540  }
541  else
542  {
543  Rcpp::DoubleVector doubleData(data);
544  std::copy(doubleData.begin(), doubleData.end(), static_cast<double*>(this->image->data));
545  }
546  }
547  UNPROTECT(1);
548 }
549 
550 inline void NiftiImage::initFromMriImage (const Rcpp::RObject &object, const bool copyData)
551 {
552  Rcpp::Reference mriImage(object);
553  Rcpp::Function getXform = mriImage.field("getXform");
554  Rcpp::NumericMatrix xform = getXform();
555 
556  this->image = NULL;
557 
558  if (Rf_length(mriImage.field("tags")) > 0)
559  initFromList(mriImage.field("tags"));
560 
561  Rcpp::RObject data = mriImage.field("data");
562  if (data.inherits("SparseArray"))
563  {
564  Rcpp::Language call("as.array", data);
565  data = call.eval();
566  }
567 
568  const short datatype = (Rf_isNull(data) ? DT_INT32 : sexpTypeToNiftiType(data.sexp_type()));
569 
570  int dims[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
571  const std::vector<int> dimVector = mriImage.field("imageDims");
572  const int nDims = std::min(7, int(dimVector.size()));
573  dims[0] = nDims;
574  size_t nVoxels = 1;
575  for (int i=0; i<nDims; i++)
576  {
577  dims[i+1] = dimVector[i];
578  nVoxels *= dimVector[i];
579  }
580 
581  if (this->image == NULL)
582  this->image = nifti_make_new_nim(dims, datatype, FALSE);
583  else
584  {
585  std::copy(dims, dims+8, this->image->dim);
586  this->image->datatype = datatype;
587  nifti_datatype_sizes(image->datatype, &image->nbyper, NULL);
588  }
589 
590  if (copyData && !Rf_isNull(data))
591  {
592  // NB: nifti_get_volsize() will not be right here if there were tags
593  const size_t dataSize = nVoxels * image->nbyper;
594  this->image->data = calloc(1, dataSize);
595  if (datatype == DT_INT32)
596  memcpy(this->image->data, INTEGER(data), dataSize);
597  else
598  memcpy(this->image->data, REAL(data), dataSize);
599  }
600  else
601  this->image->data = NULL;
602 
603  const std::vector<float> pixdimVector = mriImage.field("voxelDims");
604  const int pixdimLength = pixdimVector.size();
605  for (int i=0; i<std::min(pixdimLength,nDims); i++)
606  this->image->pixdim[i+1] = std::abs(pixdimVector[i]);
607 
608  const std::vector<std::string> pixunitsVector = mriImage.field("voxelDimUnits");
609  setPixunits(pixunitsVector);
610 
611  if (xform.rows() != 4 || xform.cols() != 4)
612  this->image->qform_code = this->image->sform_code = 0;
613  else
614  {
615  mat44 matrix;
616  for (int i=0; i<4; i++)
617  {
618  for (int j=0; j<4; j++)
619  matrix.m[i][j] = static_cast<float>(xform(i,j));
620  }
621 
622  this->image->qto_xyz = matrix;
623  this->image->qto_ijk = nifti_mat44_inverse(image->qto_xyz);
624  nifti_mat44_to_quatern(image->qto_xyz, &image->quatern_b, &image->quatern_c, &image->quatern_d, &image->qoffset_x, &image->qoffset_y, &image->qoffset_z, NULL, NULL, NULL, &image->qfac);
625 
626  this->image->sto_xyz = matrix;
627  this->image->sto_ijk = nifti_mat44_inverse(image->sto_xyz);
628 
629  this->image->qform_code = this->image->sform_code = 2;
630  }
631 }
632 
633 template <typename TargetType>
634 inline void copyIfPresent (const Rcpp::List &list, const std::set<std::string> names, const std::string &name, TargetType &target)
635 {
636  if (names.count(name) == 1)
637  target = Rcpp::as<TargetType>(list[name]);
638 }
639 
640 // Special case for char, because Rcpp tries to be too clever and convert it to a string
641 template <>
642 inline void copyIfPresent (const Rcpp::List &list, const std::set<std::string> names, const std::string &name, char &target)
643 {
644  if (names.count(name) == 1)
645  target = static_cast<char>(Rcpp::as<int>(list[name]));
646 }
647 
648 inline void NiftiImage::initFromList (const Rcpp::RObject &object)
649 {
650  Rcpp::List list(object);
651  nifti_1_header *header = nifti_make_new_header(NULL, DT_FLOAT64);
652 
653  const Rcpp::CharacterVector _names = list.names();
654  std::set<std::string> names;
655  for (Rcpp::CharacterVector::const_iterator it=_names.begin(); it!=_names.end(); it++)
656  names.insert(Rcpp::as<std::string>(*it));
657 
658  copyIfPresent(list, names, "sizeof_hdr", header->sizeof_hdr);
659 
660  copyIfPresent(list, names, "dim_info", header->dim_info);
661  if (names.count("dim") == 1)
662  {
663  std::vector<short> dim = list["dim"];
664  for (int i=0; i<std::min(dim.size(),size_t(8)); i++)
665  header->dim[i] = dim[i];
666  }
667 
668  copyIfPresent(list, names, "intent_p1", header->intent_p1);
669  copyIfPresent(list, names, "intent_p2", header->intent_p2);
670  copyIfPresent(list, names, "intent_p3", header->intent_p3);
671  copyIfPresent(list, names, "intent_code", header->intent_code);
672 
673  copyIfPresent(list, names, "datatype", header->datatype);
674  copyIfPresent(list, names, "bitpix", header->bitpix);
675 
676  copyIfPresent(list, names, "slice_start", header->slice_start);
677  if (names.count("pixdim") == 1)
678  {
679  std::vector<float> pixdim = list["pixdim"];
680  for (int i=0; i<std::min(pixdim.size(),size_t(8)); i++)
681  header->pixdim[i] = pixdim[i];
682  }
683  copyIfPresent(list, names, "vox_offset", header->vox_offset);
684  copyIfPresent(list, names, "scl_slope", header->scl_slope);
685  copyIfPresent(list, names, "scl_inter", header->scl_inter);
686  copyIfPresent(list, names, "slice_end", header->slice_end);
687  copyIfPresent(list, names, "slice_code", header->slice_code);
688  copyIfPresent(list, names, "xyzt_units", header->xyzt_units);
689  copyIfPresent(list, names, "cal_max", header->cal_max);
690  copyIfPresent(list, names, "cal_min", header->cal_min);
691  copyIfPresent(list, names, "slice_duration", header->slice_duration);
692  copyIfPresent(list, names, "toffset", header->toffset);
693 
694  if (names.count("descrip") == 1)
695  strcpy(header->descrip, Rcpp::as<std::string>(list["descrip"]).substr(0,79).c_str());
696  if (names.count("aux_file") == 1)
697  strcpy(header->aux_file, Rcpp::as<std::string>(list["aux_file"]).substr(0,23).c_str());
698 
699  copyIfPresent(list, names, "qform_code", header->qform_code);
700  copyIfPresent(list, names, "sform_code", header->sform_code);
701  copyIfPresent(list, names, "quatern_b", header->quatern_b);
702  copyIfPresent(list, names, "quatern_c", header->quatern_c);
703  copyIfPresent(list, names, "quatern_d", header->quatern_d);
704  copyIfPresent(list, names, "qoffset_x", header->qoffset_x);
705  copyIfPresent(list, names, "qoffset_y", header->qoffset_y);
706  copyIfPresent(list, names, "qoffset_z", header->qoffset_z);
707 
708  if (names.count("srow_x") == 1)
709  {
710  std::vector<float> srow_x = list["srow_x"];
711  for (int i=0; i<std::min(srow_x.size(),size_t(4)); i++)
712  header->srow_x[i] = srow_x[i];
713  }
714  if (names.count("srow_y") == 1)
715  {
716  std::vector<float> srow_y = list["srow_y"];
717  for (int i=0; i<std::min(srow_y.size(),size_t(4)); i++)
718  header->srow_y[i] = srow_y[i];
719  }
720  if (names.count("srow_z") == 1)
721  {
722  std::vector<float> srow_z = list["srow_z"];
723  for (int i=0; i<std::min(srow_z.size(),size_t(4)); i++)
724  header->srow_z[i] = srow_z[i];
725  }
726 
727  if (names.count("intent_name") == 1)
728  strcpy(header->intent_name, Rcpp::as<std::string>(list["intent_name"]).substr(0,15).c_str());
729  if (names.count("magic") == 1)
730  strcpy(header->magic, Rcpp::as<std::string>(list["magic"]).substr(0,3).c_str());
731 
732  this->image = nifti_convert_nhdr2nim(*header, NULL);
733  this->image->data = NULL;
734  free(header);
735 }
736 
737 inline void NiftiImage::initFromArray (const Rcpp::RObject &object, const bool copyData)
738 {
739  int dims[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
740  const std::vector<int> dimVector = object.attr("dim");
741 
742  const int nDims = std::min(7, int(dimVector.size()));
743  dims[0] = nDims;
744  for (int i=0; i<nDims; i++)
745  dims[i+1] = dimVector[i];
746 
747  const short datatype = sexpTypeToNiftiType(object.sexp_type());
748  this->image = nifti_make_new_nim(dims, datatype, int(copyData));
749 
750  if (copyData)
751  {
752  const size_t dataSize = nifti_get_volsize(image);
753  if (datatype == DT_INT32)
754  memcpy(this->image->data, INTEGER(object), dataSize);
755  else
756  memcpy(this->image->data, REAL(object), dataSize);
757  }
758  else
759  this->image->data = NULL;
760 
761  if (object.hasAttribute("pixdim"))
762  {
763  const std::vector<float> pixdimVector = object.attr("pixdim");
764  const int pixdimLength = pixdimVector.size();
765  for (int i=0; i<std::min(pixdimLength,nDims); i++)
766  this->image->pixdim[i+1] = pixdimVector[i];
767  }
768 
769  if (object.hasAttribute("pixunits"))
770  {
771  const std::vector<std::string> pixunitsVector = object.attr("pixunits");
772  setPixunits(pixunitsVector);
773  }
774 }
775 
776 inline NiftiImage::NiftiImage (const SEXP object, const bool readData)
777  : persistent(false)
778 {
779  Rcpp::RObject imageObject(object);
780  bool resolved = false;
781 
782  if (imageObject.hasAttribute(".nifti_image_ptr"))
783  {
784  Rcpp::XPtr<NiftiImage> imagePtr(SEXP(imageObject.attr(".nifti_image_ptr")));
785  if (imagePtr.get() != NULL)
786  {
787  this->image = *imagePtr;
788  this->persistent = true;
789  resolved = true;
790 
791  if (imageObject.hasAttribute("dim"))
792  update(object);
793  }
794  else if (Rf_isString(object))
795  throw std::runtime_error("Internal image is not valid");
796  else
797  Rf_warning("Ignoring invalid internal pointer");
798  }
799 
800  if (!resolved)
801  {
802  if (Rf_isNull(object))
803  this->image = NULL;
804  else if (Rf_isString(object))
805  {
806  const std::string path = Rcpp::as<std::string>(object);
807  this->image = nifti_image_read(path.c_str(), readData);
808  if (this->image == NULL)
809  throw std::runtime_error("Failed to read image from path " + path);
810  }
811  else if (imageObject.inherits("nifti"))
812  initFromNiftiS4(imageObject, readData);
813  else if (imageObject.inherits("MriImage"))
814  initFromMriImage(imageObject, readData);
815  else if (Rf_isVectorList(object))
816  initFromList(imageObject);
817  else if (imageObject.hasAttribute("dim"))
818  initFromArray(imageObject, readData);
819  else
820  throw std::runtime_error("Cannot convert object of class \"" + Rcpp::as<std::string>(imageObject.attr("class")) + "\" to a nifti_image");
821  }
822 
823  if (this->image != NULL)
824  nifti_update_dims_from_array(this->image);
825 
826 #ifndef NDEBUG
827  Rprintf("Creating NiftiImage with pointer %p (from SEXP)\n", this->image);
828 #endif
829 }
830 
831 inline mat33 topLeftCorner (const mat44 &matrix)
832 {
833  mat33 newMatrix;
834  for (int i=0; i<3; i++)
835  {
836  for (int j=0; j<3; j++)
837  newMatrix.m[i][j] = matrix.m[i][j];
838  }
839 
840  return newMatrix;
841 }
842 
843 inline void NiftiImage::updatePixdim (const std::vector<float> &pixdim)
844 {
845  const int nDims = image->dim[0];
846  const std::vector<float> origPixdim(image->pixdim+1, image->pixdim+4);
847 
848  for (int i=1; i<8; i++)
849  image->pixdim[i] = 0.0;
850 
851  const int pixdimLength = pixdim.size();
852  for (int i=0; i<std::min(pixdimLength,nDims); i++)
853  image->pixdim[i+1] = pixdim[i];
854 
855  if (!std::equal(origPixdim.begin(), origPixdim.begin() + std::min(3,nDims), pixdim.begin()))
856  {
857  mat33 scaleMatrix;
858  for (int i=0; i<3; i++)
859  {
860  for (int j=0; j<3; j++)
861  {
862  if (i != j)
863  scaleMatrix.m[i][j] = 0.0;
864  else if (i >= nDims)
865  scaleMatrix.m[i][j] = 1.0;
866  else
867  scaleMatrix.m[i][j] = pixdim[i] / origPixdim[i];
868  }
869  }
870 
871  if (image->qform_code > 0)
872  {
873  mat33 prod = nifti_mat33_mul(scaleMatrix, topLeftCorner(image->qto_xyz));
874  for (int i=0; i<3; i++)
875  {
876  for (int j=0; j<3; j++)
877  image->qto_xyz.m[i][j] = prod.m[i][j];
878  }
879  image->qto_ijk = nifti_mat44_inverse(image->qto_xyz);
880  nifti_mat44_to_quatern(image->qto_xyz, &image->quatern_b, &image->quatern_c, &image->quatern_d, &image->qoffset_x, &image->qoffset_y, &image->qoffset_z, NULL, NULL, NULL, &image->qfac);
881  }
882 
883  if (image->sform_code > 0)
884  {
885  mat33 prod = nifti_mat33_mul(scaleMatrix, topLeftCorner(image->sto_xyz));
886  for (int i=0; i<3; i++)
887  {
888  for (int j=0; j<3; j++)
889  image->sto_xyz.m[i][j] = prod.m[i][j];
890  }
891  image->sto_ijk = nifti_mat44_inverse(image->sto_xyz);
892  }
893  }
894 }
895 
896 inline void NiftiImage::setPixunits (const std::vector<std::string> &pixunits)
897 {
898  for (int i=0; i<pixunits.size(); i++)
899  {
900  if (pixunits[i] == "m")
901  image->xyz_units = NIFTI_UNITS_METER;
902  else if (pixunits[i] == "mm")
903  image->xyz_units = NIFTI_UNITS_MM;
904  else if (pixunits[i] == "um")
905  image->xyz_units = NIFTI_UNITS_MICRON;
906  else if (pixunits[i] == "s")
907  image->time_units = NIFTI_UNITS_SEC;
908  else if (pixunits[i] == "ms")
909  image->time_units = NIFTI_UNITS_MSEC;
910  else if (pixunits[i] == "us")
911  image->time_units = NIFTI_UNITS_USEC;
912  else if (pixunits[i] == "Hz")
913  image->time_units = NIFTI_UNITS_HZ;
914  else if (pixunits[i] == "ppm")
915  image->time_units = NIFTI_UNITS_PPM;
916  else if (pixunits[i] == "rad/s")
917  image->time_units = NIFTI_UNITS_RADS;
918  }
919 }
920 
921 inline void NiftiImage::rescale (const std::vector<float> &scales)
922 {
923  std::vector<float> pixdim(image->pixdim+1, image->pixdim+4);
924 
925  for (int i=0; i<std::min(3, int(scales.size())); i++)
926  {
927  if (scales[i] != 1.0)
928  {
929  pixdim[i] /= scales[i];
930  image->dim[i+1] = static_cast<int>(std::floor(image->dim[i+1] * scales[i]));
931  }
932  }
933 
934  updatePixdim(pixdim);
935  nifti_update_dims_from_array(image);
936 
937  // Data vector is now the wrong size, so drop it
938  free(image->data);
939 }
940 
941 inline void NiftiImage::update (const SEXP array)
942 {
943  Rcpp::RObject object(array);
944  if (!object.hasAttribute("dim"))
945  return;
946 
947  for (int i=0; i<8; i++)
948  image->dim[i] = 0;
949  const std::vector<int> dimVector = object.attr("dim");
950 
951  const int nDims = std::min(7, int(dimVector.size()));
952  image->dim[0] = nDims;
953  for (int i=0; i<nDims; i++)
954  image->dim[i+1] = dimVector[i];
955 
956  if (object.hasAttribute("pixdim"))
957  {
958  const std::vector<float> pixdimVector = object.attr("pixdim");
959  updatePixdim(pixdimVector);
960  }
961 
962  if (object.hasAttribute("pixunits"))
963  {
964  const std::vector<std::string> pixunitsVector = object.attr("pixunits");
965  setPixunits(pixunitsVector);
966  }
967 
968  // This NIfTI-1 library function clobbers dim[0] if the last dimension is unitary; we undo that here
969  nifti_update_dims_from_array(image);
970  image->dim[0] = image->ndim = nDims;
971 
972  image->datatype = NiftiImage::sexpTypeToNiftiType(object.sexp_type());
973  nifti_datatype_sizes(image->datatype, &image->nbyper, NULL);
974 
975  free(image->data);
976 
977  const size_t dataSize = nifti_get_volsize(image);
978  image->data = calloc(1, dataSize);
979  if (image->datatype == DT_INT32)
980  memcpy(image->data, INTEGER(object), dataSize);
981  else
982  memcpy(image->data, REAL(object), dataSize);
983 }
984 
985 inline mat44 NiftiImage::xform (const bool preferQuaternion) const
986 {
987  if (image == NULL)
988  {
989  mat44 matrix;
990  for (int i=0; i<4; i++)
991  {
992  for (int j=0; j<4; j++)
993  matrix.m[i][j] = 0.0;
994  }
995  return matrix;
996  }
997  else if (image->qform_code <= 0 && image->sform_code <= 0)
998  {
999  // No qform or sform so return RAS matrix (NB: other software may assume differently)
1000  mat44 matrix;
1001  for (int i=0; i<4; i++)
1002  {
1003  for (int j=0; j<4; j++)
1004  matrix.m[i][j] = (i==j ? 1.0 : 0.0);
1005  }
1006  return matrix;
1007  }
1008  else if ((preferQuaternion && image->qform_code > 0) || image->sform_code <= 0)
1009  return image->qto_xyz;
1010  else
1011  return image->sto_xyz;
1012 }
1013 
1014 template <typename SourceType, typename TargetType>
1015 inline TargetType convertValue (SourceType value)
1016 {
1017  return static_cast<TargetType>(value);
1018 }
1019 
1020 template <typename SourceType, typename TargetType>
1021 inline void convertArray (const SourceType *source, const size_t length, TargetType *target)
1022 {
1023  std::transform(source, source + length, target, convertValue<SourceType,TargetType>);
1024 }
1025 
1026 template <typename TargetType>
1027 inline void changeDatatype (nifti_image *image, const short datatype)
1028 {
1029  TargetType *data;
1030  const size_t dataSize = image->nvox * sizeof(TargetType);
1031  data = static_cast<TargetType *>(calloc(1, dataSize));
1032 
1033  switch (image->datatype)
1034  {
1035  case DT_UINT8:
1036  convertArray(static_cast<uint8_t *>(image->data), image->nvox, data);
1037  break;
1038 
1039  case DT_INT16:
1040  convertArray(static_cast<int16_t *>(image->data), image->nvox, data);
1041  break;
1042 
1043  case DT_INT32:
1044  convertArray(static_cast<int32_t *>(image->data), image->nvox, data);
1045  break;
1046 
1047  case DT_FLOAT32:
1048  convertArray(static_cast<float *>(image->data), image->nvox, data);
1049  break;
1050 
1051  case DT_FLOAT64:
1052  convertArray(static_cast<double *>(image->data), image->nvox, data);
1053  break;
1054 
1055  case DT_INT8:
1056  convertArray(static_cast<int8_t *>(image->data), image->nvox, data);
1057  break;
1058 
1059  case DT_UINT16:
1060  convertArray(static_cast<uint16_t *>(image->data), image->nvox, data);
1061  break;
1062 
1063  case DT_UINT32:
1064  convertArray(static_cast<uint32_t *>(image->data), image->nvox, data);
1065  break;
1066 
1067  case DT_INT64:
1068  convertArray(static_cast<int64_t *>(image->data), image->nvox, data);
1069  break;
1070 
1071  case DT_UINT64:
1072  convertArray(static_cast<uint64_t *>(image->data), image->nvox, data);
1073  break;
1074 
1075  default:
1076  throw std::runtime_error("Unsupported data type (" + std::string(nifti_datatype_string(image->datatype)) + ")");
1077  }
1078 
1079  free(image->data);
1080  image->data = data;
1081  image->datatype = datatype;
1082  nifti_datatype_sizes(datatype, &image->nbyper, &image->swapsize);
1083 }
1084 
1085 inline void NiftiImage::toFile (const std::string fileName, const short datatype) const
1086 {
1087  // Copy the source image only if the datatype will be changed
1088  NiftiImage imageToWrite(image, datatype != DT_NONE);
1089 
1090  switch (datatype)
1091  {
1092  case DT_NONE:
1093  imageToWrite.setPersistence(true);
1094  break;
1095 
1096  case DT_UINT8:
1097  changeDatatype<uint8_t>(imageToWrite, datatype);
1098  break;
1099 
1100  case DT_INT16:
1101  changeDatatype<int16_t>(imageToWrite, datatype);
1102  break;
1103 
1104  case DT_INT32:
1105  changeDatatype<int32_t>(imageToWrite, datatype);
1106  break;
1107 
1108  case DT_FLOAT32:
1109  changeDatatype<float>(imageToWrite, datatype);
1110  break;
1111 
1112  case DT_FLOAT64:
1113  changeDatatype<double>(imageToWrite, datatype);
1114  break;
1115 
1116  case DT_INT8:
1117  changeDatatype<int8_t>(imageToWrite, datatype);
1118  break;
1119 
1120  case DT_UINT16:
1121  changeDatatype<uint16_t>(imageToWrite, datatype);
1122  break;
1123 
1124  case DT_UINT32:
1125  changeDatatype<uint32_t>(imageToWrite, datatype);
1126  break;
1127 
1128  case DT_INT64:
1129  changeDatatype<int64_t>(imageToWrite, datatype);
1130  break;
1131 
1132  case DT_UINT64:
1133  changeDatatype<uint64_t>(imageToWrite, datatype);
1134  break;
1135 
1136  default:
1137  throw std::runtime_error("Unsupported data type (" + std::string(nifti_datatype_string(datatype)) + ")");
1138  }
1139 
1140  const int status = nifti_set_filenames(imageToWrite, fileName.c_str(), false, true);
1141  if (status != 0)
1142  throw std::runtime_error("Failed to set filenames for NIfTI object");
1143  nifti_image_write(imageToWrite);
1144 }
1145 
1146 inline void NiftiImage::toFile (const std::string fileName, const std::string &datatype) const
1147 {
1148  static std::map<std::string,short> datatypeCodes;
1149  if (datatypeCodes.empty())
1150  {
1151  datatypeCodes["auto"] = DT_NONE;
1152  datatypeCodes["none"] = DT_NONE;
1153  datatypeCodes["unknown"] = DT_NONE;
1154  datatypeCodes["uint8"] = DT_UINT8;
1155  datatypeCodes["char"] = DT_UINT8;
1156  datatypeCodes["int16"] = DT_INT16;
1157  datatypeCodes["short"] = DT_INT16;
1158  datatypeCodes["int32"] = DT_INT32;
1159  datatypeCodes["int"] = DT_INT32;
1160  datatypeCodes["float32"] = DT_FLOAT32;
1161  datatypeCodes["float"] = DT_FLOAT32;
1162  datatypeCodes["float64"] = DT_FLOAT64;
1163  datatypeCodes["double"] = DT_FLOAT64;
1164  datatypeCodes["int8"] = DT_INT8;
1165  datatypeCodes["uint16"] = DT_UINT16;
1166  datatypeCodes["uint32"] = DT_UINT32;
1167  datatypeCodes["int64"] = DT_INT64;
1168  datatypeCodes["uint64"] = DT_UINT64;
1169  }
1170 
1171  if (datatypeCodes.count(datatype) == 0)
1172  {
1173  std::ostringstream message;
1174  message << "Datatype \"" << datatype << "\" is not valid";
1175  Rf_warning(message.str().c_str());
1176  toFile(fileName, DT_NONE);
1177  }
1178  else
1179  toFile(fileName, datatypeCodes[datatype]);
1180 }
1181 
1182 template <typename SourceType, int SexpType>
1183 inline Rcpp::RObject imageDataToArray (const nifti_image *source)
1184 {
1185  if (source == NULL)
1186  return Rcpp::RObject();
1187  else if (source->data == NULL)
1188  {
1189  Rf_warning("Internal image contains no data - filling array with NAs");
1190  Rcpp::Vector<SexpType> array(static_cast<int>(source->nvox));
1191  // Rcpp's proxy infrastructure should handle converting NA_REAL to the appropriate NA
1192  std::fill(array.begin(), array.end(), NA_REAL);
1193  return array;
1194  }
1195  else
1196  {
1197  SourceType *original = static_cast<SourceType *>(source->data);
1198  Rcpp::Vector<SexpType> array(static_cast<int>(source->nvox));
1199 
1200  if (SexpType == INTSXP || SexpType == LGLSXP)
1201  std::transform(original, original + source->nvox, array.begin(), convertValue<SourceType,int>);
1202  else if (SexpType == REALSXP)
1203  std::transform(original, original + source->nvox, array.begin(), convertValue<SourceType,double>);
1204  else
1205  throw std::runtime_error("Only numeric arrays can be created");
1206 
1207  return array;
1208  }
1209 }
1210 
1211 inline void finaliseNiftiImage (SEXP xptr)
1212 {
1213  NiftiImage *object = (NiftiImage *) R_ExternalPtrAddr(xptr);
1214  object->setPersistence(false);
1215  delete object;
1216  R_ClearExternalPtr(xptr);
1217 }
1218 
1219 inline void addAttributes (Rcpp::RObject &object, nifti_image *source, const bool realDim = true)
1220 {
1221  const int nDims = source->dim[0];
1222  Rcpp::IntegerVector dim(source->dim+1, source->dim+1+nDims);
1223 
1224  if (realDim)
1225  object.attr("dim") = dim;
1226  else
1227  object.attr("imagedim") = dim;
1228 
1229  Rcpp::DoubleVector pixdim(source->pixdim+1, source->pixdim+1+nDims);
1230  object.attr("pixdim") = pixdim;
1231 
1232  if (source->xyz_units == NIFTI_UNITS_UNKNOWN && source->time_units == NIFTI_UNITS_UNKNOWN)
1233  object.attr("pixunits") = "Unknown";
1234  else
1235  {
1236  Rcpp::CharacterVector pixunits(2);
1237  pixunits[0] = nifti_units_string(source->xyz_units);
1238  pixunits[1] = nifti_units_string(source->time_units);
1239  object.attr("pixunits") = pixunits;
1240  }
1241 
1242  NiftiImage *wrappedSource = new NiftiImage(source, true);
1243  wrappedSource->setPersistence(true);
1244  Rcpp::XPtr<NiftiImage> xptr(wrappedSource);
1245  R_RegisterCFinalizerEx(SEXP(xptr), &finaliseNiftiImage, FALSE);
1246  object.attr(".nifti_image_ptr") = xptr;
1247 }
1248 
1249 inline Rcpp::RObject NiftiImage::toArray () const
1250 {
1251  Rcpp::RObject array;
1252 
1253  if (this->isNull())
1254  return array;
1255 
1256  switch (image->datatype)
1257  {
1258  case DT_UINT8:
1259  array = imageDataToArray<uint8_t,INTSXP>(image);
1260  break;
1261 
1262  case DT_INT16:
1263  array = imageDataToArray<int16_t,INTSXP>(image);
1264  break;
1265 
1266  case DT_INT32:
1267  array = imageDataToArray<int32_t,INTSXP>(image);
1268  break;
1269 
1270  case DT_FLOAT32:
1271  array = imageDataToArray<float,REALSXP>(image);
1272  break;
1273 
1274  case DT_FLOAT64:
1275  array = imageDataToArray<double,REALSXP>(image);
1276  break;
1277 
1278  case DT_INT8:
1279  array = imageDataToArray<int8_t,INTSXP>(image);
1280  break;
1281 
1282  case DT_UINT16:
1283  array = imageDataToArray<uint16_t,INTSXP>(image);
1284  break;
1285 
1286  case DT_UINT32:
1287  array = imageDataToArray<uint32_t,INTSXP>(image);
1288  break;
1289 
1290  case DT_INT64:
1291  array = imageDataToArray<int64_t,INTSXP>(image);
1292  break;
1293 
1294  case DT_UINT64:
1295  array = imageDataToArray<uint64_t,INTSXP>(image);
1296  break;
1297 
1298  default:
1299  throw std::runtime_error("Unsupported data type (" + std::string(nifti_datatype_string(image->datatype)) + ")");
1300  }
1301 
1302  addAttributes(array, image);
1303  const Rcpp::IntegerVector dim = array.attr("dim");
1304  array.attr("class") = Rcpp::CharacterVector::create("niftiImage");
1305  return array;
1306 }
1307 
1308 inline Rcpp::RObject NiftiImage::toPointer (const std::string label) const
1309 {
1310  if (this->isNull())
1311  return Rcpp::RObject();
1312  else
1313  {
1314  Rcpp::RObject string = Rcpp::wrap(label);
1315  addAttributes(string, image, false);
1316  string.attr("class") = Rcpp::CharacterVector::create("internalImage", "niftiImage");
1317  return string;
1318  }
1319 }
1320 
1321 inline Rcpp::RObject NiftiImage::toArrayOrPointer (const bool internal, const std::string label) const
1322 {
1323  return (internal ? toPointer(label) : toArray());
1324 }
1325 
1326 inline Rcpp::RObject NiftiImage::headerToList () const
1327 {
1328  if (this->image == NULL)
1329  return Rcpp::RObject();
1330 
1331  nifti_1_header header = nifti_convert_nim2nhdr(this->image);
1332  Rcpp::List result;
1333 
1334  result["sizeof_hdr"] = header.sizeof_hdr;
1335 
1336  result["dim_info"] = int(header.dim_info);
1337  result["dim"] = std::vector<short>(header.dim, header.dim+8);
1338 
1339  result["intent_p1"] = header.intent_p1;
1340  result["intent_p2"] = header.intent_p2;
1341  result["intent_p3"] = header.intent_p3;
1342  result["intent_code"] = header.intent_code;
1343 
1344  result["datatype"] = header.datatype;
1345  result["bitpix"] = header.bitpix;
1346 
1347  result["slice_start"] = header.slice_start;
1348  result["pixdim"] = std::vector<float>(header.pixdim, header.pixdim+8);
1349  result["vox_offset"] = header.vox_offset;
1350  result["scl_slope"] = header.scl_slope;
1351  result["scl_inter"] = header.scl_inter;
1352  result["slice_end"] = header.slice_end;
1353  result["slice_code"] = int(header.slice_code);
1354  result["xyzt_units"] = int(header.xyzt_units);
1355  result["cal_max"] = header.cal_max;
1356  result["cal_min"] = header.cal_min;
1357  result["slice_duration"] = header.slice_duration;
1358  result["toffset"] = header.toffset;
1359  result["descrip"] = std::string(header.descrip, 80);
1360  result["aux_file"] = std::string(header.aux_file, 24);
1361 
1362  result["qform_code"] = header.qform_code;
1363  result["sform_code"] = header.sform_code;
1364  result["quatern_b"] = header.quatern_b;
1365  result["quatern_c"] = header.quatern_c;
1366  result["quatern_d"] = header.quatern_d;
1367  result["qoffset_x"] = header.qoffset_x;
1368  result["qoffset_y"] = header.qoffset_y;
1369  result["qoffset_z"] = header.qoffset_z;
1370  result["srow_x"] = std::vector<float>(header.srow_x, header.srow_x+4);
1371  result["srow_y"] = std::vector<float>(header.srow_y, header.srow_y+4);
1372  result["srow_z"] = std::vector<float>(header.srow_z, header.srow_z+4);
1373 
1374  result["intent_name"] = std::string(header.intent_name, 16);
1375  result["magic"] = std::string(header.magic, 4);
1376 
1377  result.attr("class") = Rcpp::CharacterVector::create("niftiHeader");
1378 
1379  return result;
1380 }
1381 
1382 #endif
Block(const NiftiImage &image, const int dimension, const int index)
Standard constructor for this class.
Definition: NiftiImage.h:40
NiftiImage(const std::string &path, const bool readData=true)
Initialise using a path string.
Definition: NiftiImage.h:194
Block & operator=(const NiftiImage &source)
Copy assignment operator, which allows a block in one image to be replaced with the contents of anoth...
Definition: NiftiImage.h:55
NiftiImage(const NiftiImage &source)
Copy constructor.
Definition: NiftiImage.h:161
Block slice(const int i)
Extract a slice block from a 3D image.
Definition: NiftiImage.h:345
Thin wrapper around a C-style nifti_image struct that allows C++-style destruction.
Definition: NiftiImage.h:20
const int index
The location along dimension.
Definition: NiftiImage.h:31
void setPersistence(const bool persistent)
Allows the image to be marked as persistent, so that it can be passed back to R.
Definition: NiftiImage.h:267
bool isPersistent() const
Determines whether or not the image is marked as persistent.
Definition: NiftiImage.h:284
nifti_image * image
The wrapped nifti_image pointer.
Definition: NiftiImage.h:90
void initFromNiftiS4(const Rcpp::RObject &object, const bool copyData=true)
Initialise the object from an S4 object of class "nifti".
Definition: NiftiImage.h:454
Inner class referring to a subset of an image.
Definition: NiftiImage.h:27
void copy(nifti_image *const source)
Copy the contents of a nifti_image to create a new image.
Definition: NiftiImage.h:403
NiftiImage()
Default constructor.
Definition: NiftiImage.h:154
Block volume(const int i)
Extract a volume block from a 4D image.
Definition: NiftiImage.h:359
bool persistent
Marker of persistence, which determines whether the nifti_image should be freed on destruction...
Definition: NiftiImage.h:91
const int dimension
The dimension along which the block applies (which should be the last)
Definition: NiftiImage.h:30
const NiftiImage & image
The parent image.
Definition: NiftiImage.h:29
void toFile(const std::string fileName, const short datatype) const
Write the image to a NIfTI-1 file.
Definition: NiftiImage.h:1085
void update(const SEXP array)
Update the image from an R array.
Definition: NiftiImage.h:941
NiftiImage(nifti_image *const image, const bool copy=false)
Initialise using an existing nifti_image pointer.
Definition: NiftiImage.h:176
static short sexpTypeToNiftiType(const int sexpType)
Convert between R SEXP object type and nifti_image datatype codes.
Definition: NiftiImage.h:79
void initFromArray(const Rcpp::RObject &object, const bool copyData=true)
Initialise the object from an R array.
Definition: NiftiImage.h:737
Rcpp::RObject headerToList() const
Create an R list containing raw image metadata.
Definition: NiftiImage.h:1326
~NiftiImage()
Destructor which frees the wrapped pointer, unless the object is marked as persistent.
Definition: NiftiImage.h:215
mat44 xform(const bool preferQuaternion=true) const
Obtain an xform matrix, indicating the orientation of the image.
Definition: NiftiImage.h:985
void rescale(const std::vector< float > &scales)
Rescales the image, changing its image dimensions and pixel dimensions.
Definition: NiftiImage.h:921
nifti_image * operator->() const
Allows a NiftiImage object to be treated as a pointer to a nifti_image.
Definition: NiftiImage.h:234
bool isNull() const
Determines whether or not the internal pointer is NULL.
Definition: NiftiImage.h:279
void updatePixdim(const std::vector< float > &pixdim)
Modify the pixel dimensions, and potentially the xform matrices to match.
Definition: NiftiImage.h:843
const Block slice(const int i) const
Extract a slice block from a 3D image.
Definition: NiftiImage.h:338
int nDims() const
Returns the number of dimensions in the image.
Definition: NiftiImage.h:289
Rcpp::RObject toArray() const
Create an R array from the image.
Definition: NiftiImage.h:1249
Rcpp::RObject toArrayOrPointer(const bool internal, const std::string label) const
A conditional method that calls either toArray or toPointer.
Definition: NiftiImage.h:1321
void initFromList(const Rcpp::RObject &object)
Initialise the object from an R list with named elements, which can only contain metadata.
Definition: NiftiImage.h:648
void initFromMriImage(const Rcpp::RObject &object, const bool copyData=true)
Initialise the object from a reference object of class "MriImage".
Definition: NiftiImage.h:550
NiftiImage & drop()
Drop unitary dimensions.
Definition: NiftiImage.h:303
const Block volume(const int i) const
Extract a volume block from a 4D image.
Definition: NiftiImage.h:352
Rcpp::RObject toPointer(const std::string label) const
Create an internal image to pass back to R.
Definition: NiftiImage.h:1308
void setPixunits(const std::vector< std::string > &pixunits)
Modify the pixel dimension units.
Definition: NiftiImage.h:896