Example #1
0
bool Camera_INDIClass::ReadFITS(usImage& img, bool takeSubframe, const wxRect& subframe)
{
    int xsize, ysize;
    fitsfile *fptr;  // FITS file pointer
    int status = 0;  // CFITSIO status value MUST be initialized to zero!
    int hdutype, naxis;
    int nhdus=0;
    long fits_size[2];
    long fpixel[3] = {1,1,1};
    size_t bsize = static_cast<size_t>(cam_bp->bloblen);

    // load blob to CFITSIO
    if (fits_open_memfile(&fptr,
                          "",
                          READONLY,
                          &(cam_bp->blob),
                          &bsize,
                          0,
                          NULL,
                          &status) )
    {
        pFrame->Alert(_("Unsupported type or read error loading FITS file"));
        return true;
    }
    if (fits_get_hdu_type(fptr, &hdutype, &status) || hdutype != IMAGE_HDU) {
        pFrame->Alert(_("FITS file is not of an image"));
        PHD_fits_close_file(fptr);
        return true;
    }

    // Get HDUs and size
    fits_get_img_dim(fptr, &naxis, &status);
    fits_get_img_size(fptr, 2, fits_size, &status);
    xsize = (int) fits_size[0];
    ysize = (int) fits_size[1];
    fits_get_num_hdus(fptr,&nhdus,&status);
    if ((nhdus != 1) || (naxis != 2)) {
        pFrame->Alert(_("Unsupported type or read error loading FITS file"));
        PHD_fits_close_file(fptr);
        return true;
    }
    if (takeSubframe) {
        if (img.Init(FullSize)) {
            pFrame->Alert(_("Memory allocation error"));
            PHD_fits_close_file(fptr);
            return true;
        }
        img.Clear();
        img.Subframe = subframe;
        unsigned short *rawdata = new unsigned short[xsize*ysize];
        if (fits_read_pix(fptr, TUSHORT, fpixel, xsize*ysize, NULL, rawdata, NULL, &status) ) {
            pFrame->Alert(_("Error reading data"));
            PHD_fits_close_file(fptr);
            return true;
        }
        int i = 0;
        for (int y = 0; y < subframe.height; y++)
        {
            unsigned short *dataptr = img.ImageData + (y + subframe.y) * img.Size.GetWidth() + subframe.x;
            memcpy(dataptr, &rawdata[i], subframe.width * sizeof(unsigned short));
            i += subframe.width;
        }
        delete[] rawdata;
    }
    else {
        if (img.Init(xsize,ysize)) {
            pFrame->Alert(_("Memory allocation error"));
            PHD_fits_close_file(fptr);
            return true;
        }
        // Read image
        if (fits_read_pix(fptr, TUSHORT, fpixel, xsize*ysize, NULL, img.ImageData, NULL, &status) ) {
            pFrame->Alert(_("Error reading data"));
            PHD_fits_close_file(fptr);
            return true;
        }
    }

    PHD_fits_close_file(fptr);
    return false;
}
oskar_Sky* oskar_sky_from_fits_file(int precision, const char* filename,
        double min_peak_fraction, double min_abs_val,
        const char* default_map_units, int override_units, double frequency_hz,
        double spectral_index, int* status)
{
    double image_crval_deg[2], image_crpix[2];
    double image_cellsize_deg = 0.0, image_freq_hz = 0.0;
    double beam_area_pixels = 0.0, pixel_area_sr = 0.0;
    char *reported_map_units = 0, ordering = 0, coordsys = 0;
    int naxis = 0, nside = 0;
    int image_size[2];
    oskar_Sky* t = 0;
    oskar_Mem* data = 0;
    fitsfile* fptr;

    /* Determine whether this is a regular FITS image or HEALPix data. */
    fits_open_file(&fptr, filename, READONLY, status);
    if (*status || !fptr)
    {
        *status = OSKAR_ERR_FILE_IO;
        return 0;
    }
    fits_get_img_dim(fptr, &naxis, status);
    fits_close_file(fptr, status);
    if (naxis == 0)
    {
        /* Try to load HEALPix data. */
        data = oskar_mem_read_healpix_fits(filename, 0,
                &nside, &ordering, &coordsys, &reported_map_units, status);
        pixel_area_sr = (4.0 * M_PI) / oskar_mem_length(data);

        /* Check HEALPix ordering scheme. */
        if (!*status && ordering != 'R')
        {
            *status = OSKAR_ERR_FILE_IO;
            fprintf(stderr, "HEALPix data is not in RING format.\n");
        }
    }
    else
    {
        /* Try to load image pixels. */
        data = oskar_mem_read_fits_image_plane(filename, 0, 0, 0,
                image_size, image_crval_deg, image_crpix,
                &image_cellsize_deg, 0, &image_freq_hz, &beam_area_pixels,
                &reported_map_units, status);
        pixel_area_sr = pow(image_cellsize_deg * M_PI / 180.0, 2.0);
    }

    /* Make sure pixels are in Jy. */
    if (image_freq_hz == 0.0)
        image_freq_hz = frequency_hz;
    oskar_convert_brightness_to_jy(data, beam_area_pixels, pixel_area_sr,
            image_freq_hz, min_peak_fraction, min_abs_val, reported_map_units,
            default_map_units, override_units, status);
    free(reported_map_units);

    /* Convert the image into a sky model. */
    if (naxis == 0)
        t = oskar_sky_from_healpix_ring(precision, data, image_freq_hz,
                spectral_index, nside, (coordsys == 'G'), status);
    else
        t = oskar_sky_from_image(precision, data,
                image_size, image_crval_deg, image_crpix,
                image_cellsize_deg, image_freq_hz, spectral_index, status);

    /* Free pixel data and return sky model. */
    oskar_mem_free(data, status);
    return t;
}
Example #3
0
std::string read_image_3D(std::string pathname_3D, float *&array_2D_real, float *&array_2D_imag, int &naxis_2D, long *&naxes_2D) {
  TRACE_ENTER();
  // open FITS image file in READONLY mode
  fitsfile *fptr;
  int status = 0;
  fits_open_image(&fptr, pathname_3D.c_str(), READONLY, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // read number of HDUs in the file
  int nhdus = 0;
  fits_get_num_hdus(fptr, &nhdus, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect only one HDU in the file
  if (nhdus != 1) {
    std::ostringstream exit_oss;
    exit_oss << "nhdus is " << nhdus << ", not 1";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // read the type of the HDU
  int hdutype = 0;
  fits_movabs_hdu(fptr, nhdus, &hdutype, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect the HDU to be an image
  if (hdutype != IMAGE_HDU) {
    std::ostringstream exit_oss;
    exit_oss << "hdutype is " << hdutype << ", not IMAGE_HDU";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // read the type of data in the HDU
  int bitpix = 0;
  fits_get_img_type (fptr, &bitpix, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect the data to be floats
  if (bitpix != FLOAT_IMG) {
    std::ostringstream exit_oss;
    exit_oss << "bitpix is " << bitpix << ", not FLOAT_IMG";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // get the number of dimensions in the image
  int naxis_3D = 0;
  fits_get_img_dim (fptr, &naxis_3D, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  naxis_2D = naxis_3D-1;
  // we expect 3 dimensions in the image
  if (naxis_3D != 3) {
    std::ostringstream exit_oss;
    exit_oss << "naxis_3D is " << naxis_3D << ", not 3";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // get the size of each dimension in the image
  long *naxes_3D = new long[naxis_3D];
  int maxdim = naxis_3D;
  fits_get_img_size(fptr, maxdim, naxes_3D, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  naxes_2D = new long[naxis_2D];
  for (int i = 0; i < naxis_2D; i++) {
    naxes_2D[i] = naxes_3D[i];
  }
  // we expect two sub-images in the third dimension
  if (naxes_3D[2] != 2) {
    std::ostringstream exit_oss;
    exit_oss << "naxes_2D[2] is " << naxes_2D[2] << ", not 2";
    TRACE_ERROR (exit_oss.str());
    return exit_oss.str();
  }
  // fits_read_subset
  long *fpixel = new long[naxis_3D];
  for (int i = 0; i < naxis_3D; i++) {
    fpixel[i] = 1;
  }
  long *lpixel = new long[naxis_3D];
  for (int i = 0; i < naxis_3D; i++) {
    lpixel[i] = naxes_3D[i];
  }
  long *inc = new long[naxis_3D];
  for (int i = 0; i < naxis_3D; i++) {
    inc[i] = 1;
  }
  float nulval = 0;
  long nelements_2D = 1;
  for (int i = 0; i < naxis_2D; i++) {
    nelements_2D = nelements_2D*naxes_2D[i];
  }
  int anynul = 0;
  // read array_2D_real
  array_2D_real = new float[nelements_2D];
  fpixel[2] = 1;
  lpixel[2] = 1;
  fits_read_subset(fptr, TFLOAT, fpixel, lpixel, inc, &nulval, array_2D_real, &anynul, &status);
  // read array_2D_imag
  array_2D_imag = new float[nelements_2D];
  fpixel[2] = 2;
  lpixel[2] = 2;
  fits_read_subset(fptr, TFLOAT, fpixel, lpixel, inc, &nulval, array_2D_imag, &anynul, &status);
  // free arrays
  delete [] inc;
  delete [] lpixel;
  delete [] fpixel;
  // test read result
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // close the FITS image file
  fits_close_file(fptr, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  return "READ_OK";
}
Example #4
0
std::string read_image(std::string pathname, float *&array, int &naxis, long *&naxes) {
  TRACE_ENTER();
  // open FITS image file in READONLY mode
  fitsfile *fptr;
  int status = 0;
  fits_open_image(&fptr, pathname.c_str(), READONLY, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // read number of HDUs in the file
  int nhdus = 0;
  fits_get_num_hdus(fptr, &nhdus, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect only one HDU in the file
  if (nhdus != 1) {
    std::ostringstream exit_oss;
    exit_oss << "nhdus is " << nhdus << ", not 1";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // read the type of the HDU
  int hdutype = 0;
  fits_movabs_hdu(fptr, nhdus, &hdutype, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect the HDU to be an image
  if (hdutype != IMAGE_HDU) {
    std::ostringstream exit_oss;
    exit_oss << "hdutype is " << hdutype << ", not IMAGE_HDU";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // read the type of data in the HDU
  int bitpix = 0;
  fits_get_img_type (fptr, &bitpix, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect the data to be floats
  if (bitpix != FLOAT_IMG) {
    std::ostringstream exit_oss;
    exit_oss << "bitpix is " << bitpix << ", not FLOAT_IMG";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // get the number of dimensions in the image
  naxis = 0;
  fits_get_img_dim (fptr, &naxis, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // we expect 2 or 3 dimensions in the image
  if ((naxis < 2) || (naxis > 3)) {
    std::ostringstream exit_oss;
    exit_oss << "naxis is " << naxis << ", neither 2 nor 3";
    TRACE_ERROR(exit_oss.str());
    return exit_oss.str();
  }
  // get the size of each dimension in the image
  naxes = new long[naxis];
  int maxdim = naxis;
  fits_get_img_size(fptr, maxdim, naxes, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // fits_read_subset
  long *fpixel = new long[naxis];
  for (int i = 0; i < naxis; i++) {
    fpixel[i] = 1;
  }
  long *lpixel = new long[naxis];
  for (int i = 0; i < naxis; i++) {
    lpixel[i] = naxes[i];
  }
  long *inc = new long[naxis];
  for (int i = 0; i < naxis; i++) {
    inc[i] = 1;
  }
  float nulval = 0;
  long nelements = 1;
  for (int i = 0; i < naxis; i++) {
    nelements = nelements * naxes[i];
  }
  array = new float[nelements];
  int anynul = 0;
  fits_read_subset(fptr, TFLOAT, fpixel, lpixel, inc, &nulval, array, &anynul, &status);
  delete [] inc;
  delete [] lpixel;
  delete [] fpixel;
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  // close the FITS image file
  fits_close_file(fptr, &status);
  if (status != 0) {
    return FORMAT_STATUS(status);
  }
  return "READ_OK";
}
Example #5
0
static GwyContainer*
fits_load(const gchar *filename,
          G_GNUC_UNUSED GwyRunType mode,
          GError **error)
{
    GwyContainer *container = NULL;
    fitsfile *fptr = NULL;
    GwyDataField *field = NULL, *mask;
    gint status = 0;   /* Must be initialised to zero! */
    gint hdutype, naxis, anynull, nkeys, k;
    glong res[3];    /* First index is the fast looping one. */
    char strvalue[FLEN_VALUE];
    gchar *invalid = NULL;
    gdouble real, off;

    if (fits_open_image(&fptr, filename, READONLY, &status)) {
        err_FITS(error, status);
        return NULL;
    }

    if (fits_get_hdu_type(fptr, &hdutype, &status)) {
        err_FITS(error, status);
        goto fail;
    }

    gwy_debug("hdutype %d", hdutype);
    if (hdutype != IMAGE_HDU) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Only two-dimensional images are supported."));
        goto fail;
    }

    if (fits_get_img_dim(fptr, &naxis, &status)) {
        err_FITS(error, status);
        goto fail;
    }

    gwy_debug("naxis %d", naxis);
    if (naxis != 2 && naxis != 3) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Only two-dimensional images are supported."));
        goto fail;
    }

    if (fits_get_img_size(fptr, naxis, res, &status)) {
        err_FITS(error, status);
        goto fail;
    }

    if (naxis == 3 && res[2] != 1) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Only two-dimensional images are supported."));
        goto fail;
    }

    gwy_debug("xres %ld, yres %ld", res[0], res[1]);
    if (err_DIMENSION(error, res[0]) || err_DIMENSION(error, res[1]))
        goto fail;

    field = gwy_data_field_new(res[0], res[1], res[0], res[1], FALSE);
    invalid = g_new(gchar, res[0]*res[1]);
    if (fits_read_imgnull(fptr, TDOUBLE, 1, res[0]*res[1],
                          field->data, invalid, &anynull, &status)) {
        err_FITS(error, status);
        goto fail;
    }

    container = gwy_container_new();
    gwy_container_set_object_by_name(container, "/0/data", field);

    /* Failures here are non-fatal.  We already have an image. */
    if (fits_get_hdrspace(fptr, &nkeys, NULL, &status)) {
        g_warning("Cannot get the first hdrspace.");
        goto fail;
    }

    if (!fits_read_key(fptr, TSTRING, "BUINT   ", strvalue, NULL, &status)) {
        gint power10;

        gwy_debug("BUINT = <%s>", strvalue);
        gwy_si_unit_set_from_string_parse(gwy_data_field_get_si_unit_z(field),
                                          strvalue, &power10);
        if (power10)
            gwy_data_field_multiply(field, pow10(power10));
    }
    status = 0;

    if (get_real_and_offset(fptr, 1, res[0], &real, &off)) {
        if (real < 0.0) {
            off += real;
            real = -real;
            gwy_data_field_invert(field, FALSE, TRUE, FALSE);
        }
        gwy_data_field_set_xreal(field, real);
        gwy_data_field_set_xoffset(field, off);
    }

    if (get_real_and_offset(fptr, 2, res[1], &real, &off)) {
        if (real < 0.0) {
            off += real;
            real = -real;
            gwy_data_field_invert(field, TRUE, FALSE, FALSE);
        }
        gwy_data_field_set_yreal(field, real);
        gwy_data_field_set_yoffset(field, off);
    }

    /* Create a mask of invalid data. */
    for (k = 0; k < field->xres*field->yres; k++) {
        if (invalid[k])
            field->data[k] = NAN;
    }
    if ((mask = gwy_app_channel_mask_of_nans(field, TRUE))) {
        gwy_container_set_object_by_name(container, "/0/mask", mask);
        g_object_unref(mask);
    }

fail:
    fits_close_file(fptr, &status);
    gwy_object_unref(field);
    g_free(invalid);

    return container;
}
Example #6
0
// getImageToArray: extract a sub-section from an image HDU, return array
void *getImageToArray(fitsfile *fptr, int *dims, double *cens, char *slice,
		      int *odim1, int *odim2, int *bitpix, int *status){
  int i, naxis;
  int xcen, ycen, dim1, dim2, type;
  int tstatus = 0;
  int doscale = 0;
  void *obuf;
  long totpix, totbytes;
  long naxes[IDIM], fpixel[IDIM], lpixel[IDIM], inc[IDIM];
  double bscale = 1.0;
  double bzero = 0.0;
  char comment[81];
  char *s, *tslice;
  int nslice, idx, iaxis0, iaxis1;
  int iaxes[2] = {0, 1};
  int saxes[IDIM] = {0, 0, 0, 0};
  // seed buffers
  for(i=0; i<IDIM; i++){
    naxes[i] = 0;
    fpixel[i] = 1;
    lpixel[i] = 1;
    inc[i] = 1;
  }
  // get image dimensions and type
  fits_get_img_dim(fptr, &naxis, status);
  fits_get_img_size(fptr, min(IDIM,naxis), naxes, status);
  fits_get_img_type(fptr, bitpix, status);
  if( naxis < 2 ){
    *status = BAD_DIMEN;
    return NULL;
  }
  // parse slice string into primary axes and slice axes
  if( slice && *slice ){
    tslice = (char *)strdup(slice);
    for(s=(char *)strtok(tslice, " :,"), nslice=0, idx=0;
	(s != NULL) && (nslice < IDIM); 
	s=(char *)strtok(NULL," :,"), nslice++){
      if( !strcmp(s, "*") ){
	if( idx < 2 ){
	  iaxes[idx++] = nslice;
	}
      } else {
	saxes[nslice] = atoi(s);
	if( (saxes[nslice] < 1) || (saxes[nslice] > naxes[nslice]) ){
	  *status = SEEK_ERROR;
	  return NULL;
	}
      }
    }
    free(tslice);      
  }
  // convenience variables for the primary axis indexes
  iaxis0 = iaxes[0];
  iaxis1 = iaxes[1];
  // get limits of extracted section
  if( dims && dims[0] && dims[1] ){
    dim1 = min(dims[0], naxes[iaxis0]);
    dim2 = min(dims[1], naxes[iaxis1]);
    // read image section
    if( cens ){
      xcen = cens[0];
      ycen = cens[1];
    } else {
      xcen = dim1/2;
      ycen = dim2/2;
    }
    fpixel[iaxis0] = (int)(xcen - (dim1+1)/2);
    fpixel[iaxis1] = (int)(ycen - (dim2+1)/2);
    lpixel[iaxis0] = (int)(xcen + (dim1/2));
    lpixel[iaxis1] = (int)(ycen + (dim2/2));
  } else {
    // read entire image
    fpixel[iaxis0] = 1;
    fpixel[iaxis1] = 1;
    lpixel[iaxis0] = naxes[iaxis0];
    lpixel[iaxis1] = naxes[iaxis1];
  }
  // stay within image limits
  fpixel[iaxis0] = max(fpixel[iaxis0], 1);
  fpixel[iaxis0] = min(fpixel[iaxis0], naxes[iaxis0]);
  lpixel[iaxis0] = max(lpixel[iaxis0], 1);
  lpixel[iaxis0] = min(lpixel[iaxis0], naxes[iaxis0]);
  fpixel[iaxis1] = max(fpixel[iaxis1], 1);
  fpixel[iaxis1] = min(fpixel[iaxis1], naxes[iaxis0]);
  lpixel[iaxis1] = max(lpixel[iaxis1], 1);
  lpixel[iaxis1] = min(lpixel[iaxis1], naxes[iaxis0]);
  // for sliced dimensions, set first and last pixel to the specified slice
  for(i=0; i<min(IDIM,naxis); i++){
    if( saxes[i] ){
      // 1 pixel slice in this dimension
      fpixel[i] = saxes[i];
      lpixel[i] = saxes[i];
      // stay within image limits
      fpixel[i] = max(fpixel[i], 1);
      fpixel[i] = min(fpixel[i], naxes[i]);
      lpixel[i] = max(lpixel[i], 1);
      lpixel[i] = min(lpixel[i], naxes[i]);
    }
  }
  // section dimensions
  *odim1 = lpixel[iaxis0] - fpixel[iaxis0] + 1;
  *odim2 = lpixel[iaxis1] - fpixel[iaxis1] + 1;
  totpix = *odim1 * *odim2;
  // make sure we have an image with valid dimensions size
  if( totpix <= 1 ){
    *status = NEG_AXIS;
    return NULL;
  }
  // are we scaling?
  fits_read_key(fptr, TDOUBLE, "BSCALE", &bscale, comment, &tstatus);
  if( tstatus != VALUE_UNDEFINED ){
    fits_read_key(fptr, TDOUBLE, "BZERO", &bzero, comment, &tstatus);
  }
  if( (bscale != 1.0) || (bzero != 0.0) ){
    doscale = 1;
  }
  // allocate space for the pixel array
  switch(*bitpix){
    case 8:
      if( doscale ){
	// scaled data has to be float
	*bitpix = -32;
	type = TFLOAT;
	totbytes = totpix * sizeof(float);
      } else {
	type = TBYTE;
	totbytes = totpix * sizeof(char);
      }
      break;
    case 16:
      if( doscale ){
	// scaled data has to be float
	*bitpix = -32;
	type = TFLOAT;
	totbytes = totpix * sizeof(float);
      } else {
	type = TSHORT;
	totbytes = totpix * sizeof(short);
      }
      break;
    case -16:
      if( doscale ){
	// scaled data has to be float
	*bitpix = -32;
	type = TFLOAT;
	totbytes = totpix * sizeof(float);
      } else {
	type = TUSHORT;
	totbytes = totpix * sizeof(unsigned short);
      }
      break;
    case 32:
      if( doscale ){
	// scaled data has to be float
	*bitpix = -32;
	type = TFLOAT;
	totbytes = totpix * sizeof(float);
      } else {
	type = TINT;
	totbytes = totpix * sizeof(int);
      }
      break;
    case 64:
      if( doscale ){
	// scaled data has to be float
	*bitpix = -32;
	type = TFLOAT;
	totbytes = totpix * sizeof(float);
      } else {
	type = TLONGLONG;
	totbytes = totpix * sizeof(long long);
      }
      break;
    case -32:
      type = TFLOAT;
      totbytes = totpix * sizeof(float);
      break;
    case -64:
      type = TDOUBLE;
      totbytes = totpix * sizeof(double);
      break;
  default:
    return NULL;
  }
#if EM
  // sanity check on memory limits
  if( totbytes > max_memory ){
    *status = MEMORY_ALLOCATION;
    return NULL;
  }
#endif
  // try to allocate that much memory
  if(!(obuf = (void *)malloc(totbytes))){
    *status = MEMORY_ALLOCATION;
    return NULL;
  }
  /* read the image section */
  fits_read_subset(fptr, type, fpixel, lpixel, inc, 0, obuf, 0, status);
  // return pixel buffer (and section dimensions)
  return obuf;
}