/**************************************************************

phtfile.c (C-Munipack project)
Photometry file read/write routines
Copyright (C) 2003 David Motl, dmotl@volny.cz

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <expat.h>
#include <math.h>
#include <float.h>
#include <limits.h>

#include "hash.h"
#include "console.h"
#include "comfun.h"
#include "cmpack_common.h"
#include "phtfile.h"
#include "wcsobj.h"
#include "xmldom.h"

/*
This is the old C-Munipack 1.1 format:

 NL   NX   NY  LOWBAD HIGHBAD  THRESH     AP1  PH/ADU  RNOISE              JD           FILTER    EXPTIME    FWHM  OFFSETX  OFFSETY
  2  382  255     0.0 65535.0  105.92    2.00    2.30   15.00  2452909.317326            Clear      20.00    2.10    -0.00    -0.00

     0  122.034   22.033  11.1179  10.9562  10.8833  10.8484  10.8278  10.8127  10.8021  10.7941  10.7882  10.7814  99.9990  99.9990
      449.033 15.08  0.00  0.0012   0.0011   0.0011   0.0011   0.0011   0.0012   0.0013   0.0015   0.0018   0.0021   9.9990   9.9990
*/

/******************* Constants and definitions **********************/

/* List allocation increment */
#define ALLOC_BY		64

/* XML parser buffer size in bytes */
#define BUFFSIZE 		8192

/* Initial string in a binary file */
#define MAGIC_STRING	"C-Munipack photometry file\r\n"

/* Current file format revision number */
#define CURRENT_REVISION 4

/* Minimum supported revision number */
#define MINIMUM_REVISION 1

/* Header length */
#define MAX_HEADER_SIZE	540

/* Aperture record length */
#define APERTURE_LENGTH	12

/** \brief Photometry file parameters */
typedef struct _PhtFileHeader
{
	int width;							/**< Width of frame in pixels */
	int height;							/**< Height of frame in pixels */
	double jd;							/**< Julian date of observation */
	char filter[71];					/**< Color filter name */
	double exptime;						/**< Exposure duration in seconds */
	double ccdtemp;						/**< CCD temperature in deg. C */
	char origin[71];					/**< Origin of the file (photometry software) */
	struct tm crtime;					/**< Creation time */
	double range[2];					/**< Low and high cutoff of pixel values */
	double gain;						/**< ADC gain */
	double rnoise;						/**< Readout noise */
	double fwhm_exp;					/**< Expected FWHM of objects */
	double fwhm_mean;					/**< Measured mean FWHM of objects */
	double fwhm_err;					/**< Standard deviation of FHWM of objects */
	double threshold;					/**< Detection threshold */
	double sharpness[2];				/**< Allowed range of roundness of objects */
	double roundness[2];				/**< Allowed range of sharpness of objects */
	int	matched;						/**< Nonzero if the frame has been matched */
	int match_rstars;					/**< Maximum number of stars used in Solve procedure */
	int match_istars;					/**< Number of vertices of polygons */
	int match_mstars;					/**< Number of stars that were successfully matched */
	double match_clip;					/**< Clipping threshold for matching */
	double offset[2];					/**< Frame offset in pixels */
	char object[71];					/**< Object designation */
	double ra;							/**< Object's right ascension */
	double dec;							/**< Object's declination */
	char location[71];					/**< Observing location name */
	double lon;							/**< Observer's longitude */
	double lat;							/**< Observer's latitude */
} PhtFileHeader;

/********************** Local functions ****************************/

static int file_close(CmpackPhtFile *file);
static void file_clear(CmpackPhtFile *f);
static void info_clear(CmpackPhtInfo *info);
static void info_copy(CmpackPhtInfo *dst, const CmpackPhtInfo *src);
static void apertures_init(CmpackApertures *tab, int napertures);
static int aperture_add(CmpackApertures *tab, unsigned mask, const CmpackPhtAperture *data);
static int aperture_find(CmpackApertures *tab, int aper_id);
static void objects_init(CmpackObjects *tab, int nobjects);
static int object_add(CmpackObjects *tab, unsigned mask, const CmpackPhtObject *obj, int ndata);
static void object_copy(CmpackObject *dst, const CmpackObject *src);
static int object_find(CmpackObjects *tab, int obj_id);
static void data_init(CmpackMagList *tab, int napertures);

/* Create new photometry file context */
static CmpackPhtFile *file_create(void)
{
	CmpackPhtFile *f = (CmpackPhtFile*)cmpack_calloc(1, sizeof(CmpackPhtFile));
	f->refcnt = 1;
	info_clear(&f->hd);
	return f;
}

/* Frees all allocated memory in internal structures */
static void file_clear(CmpackPhtFile *f)
{
	int i;

	/* Clear file header */
	info_clear(&f->hd);

	/* Clear table of apertures */
	cmpack_free(f->ap.list);
	f->ap.list = NULL;
	f->ap.count = f->ap.capacity = 0;

	/* Clear objects and measurements */
	for (i=0; i<f->st.count; i++)
		cmpack_free(f->st.list[i].data.items);
	cmpack_free(f->st.list);
	f->st.list = NULL;
	f->st.count = f->st.capacity = 0;

	/* Clear WCS data */
	if (f->wcs) {
		cmpack_wcs_destroy(f->wcs);
		f->wcs = NULL;
	}
}

static int check_document(CmpackElement *node)
{
	const char *verstr;

	if (strcmp(node->node.nodeName, "phot")==0) {
		verstr = cmpack_xml_attr_s(node, "version", NULL);
		if (verstr) 
			return (atoi(verstr)==1);
	}
	return 0;
}

static int file_loadxml(CmpackPhtFile *pht, FILE *from)
{
	const char *aux;
	CmpackDocument *doc;
	CmpackElement *root, *head, *apertures, *objects;
	CmpackPhtObject obj;
	CmpackPhtAperture aper;

	memset(&obj, 0, sizeof(CmpackPhtObject));

	doc = cmpack_xml_doc_from_file(from);
	if (!doc)
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Check document format and version */
	root = cmpack_xml_doc_get_root(doc);
	if (!root || !check_document(root))
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Read file header */
	head = cmpack_xml_element_first_element(root, "head");
	if (head) {
		pht->hd.width = cmpack_xml_child_value_i(head, "width", 0);
		pht->hd.height = cmpack_xml_child_value_i(head, "height", 0);
		pht->hd.jd = cmpack_xml_child_value_d(head, "jd", 0);
		pht->hd.filter = cmpack_strdup(cmpack_xml_child_value(head, "filter", NULL));
		pht->hd.exptime = cmpack_xml_child_value_d(head, "exptime", 0);
		pht->hd.ccdtemp = cmpack_xml_child_value_d(head, "temp", DBL_MAX);
		pht->hd.origin = cmpack_strdup(cmpack_xml_child_value(head, "origin", NULL));
		cmpack_xml_child_value_tm(head, "crdate", &pht->hd.crtime);
		pht->hd.range[0] = cmpack_xml_child_value_d(head, "phot_datalo", 0);
		pht->hd.range[1] = cmpack_xml_child_value_d(head, "phot_datahi", 0);
		pht->hd.gain = cmpack_xml_child_value_d(head, "phot_gain", 0);
		pht->hd.rnoise = cmpack_xml_child_value_d(head, "phot_rnoise", 0);
		pht->hd.fwhm_exp = cmpack_xml_child_value_d(head, "phot_fwhm_exp", 0);
		pht->hd.fwhm_mean = cmpack_xml_child_value_d(head, "phot_fwhm_mean", 0);
		pht->hd.fwhm_err = cmpack_xml_child_value_d(head, "phot_fwhm_err", 0);
		pht->hd.threshold = cmpack_xml_child_value_d(head, "phot_thresh", 0);
		pht->hd.sharpness[0] = cmpack_xml_child_value_d(head, "phot_losharp", 0);
		pht->hd.sharpness[1] = cmpack_xml_child_value_d(head, "phot_hisharp", 0);
		pht->hd.roundness[0] = cmpack_xml_child_value_d(head, "phot_loround", 0);
		pht->hd.roundness[1] = cmpack_xml_child_value_d(head, "phot_hiround", 0);
		pht->hd.matched = cmpack_xml_element_first_element(head, "offsetx")!=NULL;
		pht->hd.match_rstars = cmpack_xml_child_value_i(head, "match_rstars", 0);
		pht->hd.match_istars = cmpack_xml_child_value_i(head, "match_istars", 0);
		pht->hd.match_clip = cmpack_xml_child_value_d(head, "match_clip", 0);
		pht->hd.match_mstars = cmpack_xml_child_value_i(head, "match_stars", 0);
		pht->hd.offset[0] = cmpack_xml_child_value_d(head, "offsetx", 0);
		pht->hd.offset[1] = cmpack_xml_child_value_d(head, "offsety", 0);
		pht->hd.object.designation = cmpack_strdup(cmpack_xml_child_value(head, "object", NULL));
		aux = cmpack_xml_child_value(head, "ra2000", NULL);
		pht->hd.object.ra_valid = (aux!=NULL);
		pht->hd.object.ra = (aux!=NULL ? atof(aux) : 0);
		aux = cmpack_xml_child_value(head, "dec2000", NULL);
		pht->hd.object.dec_valid = (aux!=NULL);
		pht->hd.object.dec = (aux!=NULL ? atof(aux) : 0);
		pht->hd.location.designation = cmpack_strdup(cmpack_xml_child_value(head, "location", NULL));
		aux = cmpack_xml_child_value(head, "longitude", NULL);
		pht->hd.location.lon_valid = (aux!=NULL); 
		pht->hd.location.lon = (aux!=NULL ? atof(aux) : 0);
		aux = cmpack_xml_child_value(head, "latitude", NULL);
		pht->hd.location.lat_valid = (aux!=NULL);
		pht->hd.location.lat = (aux!=NULL ? atof(aux) : 0);
		pht->hd.trafo.xx = cmpack_xml_child_value_d(head, "m11", 0);
		pht->hd.trafo.xy = cmpack_xml_child_value_d(head, "m21", 0);
		pht->hd.trafo.x0 = cmpack_xml_child_value_d(head, "m31", 0);
		pht->hd.trafo.yx = cmpack_xml_child_value_d(head, "m12", 0);
		pht->hd.trafo.yy = cmpack_xml_child_value_d(head, "m22", 0);
		pht->hd.trafo.y0 = cmpack_xml_child_value_d(head, "m32", 0);
	}

	/* Read table of apertures */
	apertures = cmpack_xml_element_first_element(root, "apertures");
	if (apertures) {
		CmpackElement *aperture = cmpack_xml_element_first_element(apertures, "aper");
		while (aperture) {
			aper.id = cmpack_xml_attr_i(aperture, "id", -1);
			if (aper.id>=0) {
				aper.radius = cmpack_xml_attr_d(aperture, "radius", 0);
				aperture_add(&pht->ap, CMPACK_PA_ID | CMPACK_PA_RADIUS, &aper);
			}
			aperture = cmpack_xml_element_next_element(aperture);
		}
	}

	/* Read table of objects */
	objects = cmpack_xml_element_first_element(root, "body");
	if (objects) {
		CmpackElement *object = cmpack_xml_element_first_element(objects, "object");
		while (object) {
			obj.id = cmpack_xml_attr_i(object, "id", -1);
			if (obj.id>=0) {
				obj.x = cmpack_xml_attr_d(object, "x", 0);
				obj.y = cmpack_xml_attr_d(object, "y", 0);
				obj.skymed = cmpack_xml_attr_d(object, "skymed", 0);
				obj.skysig = cmpack_xml_attr_d(object, "skysig", 0);
				obj.ref_id = cmpack_xml_attr_i(object, "x-ref", -1);
				obj.fwhm   = cmpack_xml_attr_d(object, "fwhm", 0);
				object_add(&pht->st, CMPACK_PO_ID | CMPACK_PO_REF_ID |
					CMPACK_PO_CENTER | CMPACK_PO_SKY | CMPACK_PO_FWHM, &obj, 0);
			}
			object = cmpack_xml_element_next_element(object);
		}
	}

	/* Read table measurements */
	objects = cmpack_xml_element_first_element(root, "body");
	if (objects) {
		CmpackElement *object = cmpack_xml_element_first_element(objects, "object");
		while (object) {
			int st_index = object_find(&pht->st, cmpack_xml_attr_i(object, "id", -1));
			if (st_index>=0) {
				CmpackElement *data = cmpack_xml_element_first_element(object, "p");
				data_init(&pht->st.list[st_index].data, pht->ap.count);
				while (data) {
					int ap_index = aperture_find(&pht->ap, cmpack_xml_attr_i(data, "a", -1));
					if (ap_index>=0 && cmpack_xml_element_has_attribute(data, "m")) {
						CmpackMagData *mag = pht->st.list[st_index].data.items + ap_index;
						mag->valid = cmpack_xml_element_has_attribute(data, "m");
						mag->mag   = cmpack_xml_attr_d(data, "m", DBL_MAX);
						mag->err   = cmpack_xml_attr_d(data, "e", -1.0);
						mag->code  = cmpack_xml_attr_i(data, "c", 0);
					}
					data = cmpack_xml_element_next_element(data);
				}
			}
			object = cmpack_xml_element_next_element(object);
		}
	}

	cmpack_xml_doc_free(doc);
	return CMPACK_ERR_OK;
}

static int file_loadsrt(CmpackPhtFile *pht, FILE *f)
{
	int		i, j, index, start, end, length, mag_valid, err_valid;
	char	line1[MAXLINE], line2[MAXLINE], key[128], value[128];
	char	*endptr, *start1, *start2;
	double	mag, err, ap0;
	CmpackPhtObject obj;
	CmpackPhtAperture aper;

	memset(&obj, 0, sizeof(CmpackPhtObject));
	ap0 = 0.0;
	
	/* Check file format */
	fgets(line1, MAXLINE, f);
	if (!strstr(line1, " NL") || !strstr(line1, "JD"))
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Read header information */
	fgets(line2, MAXLINE, f);
	start = 0;
	length = (int)strlen(line1);
	while (start<length) {
		end = start + (int)strspn(line1+start, " \t\r\n");
		end += (int)strcspn(line1+end, " \t\r\n");
		line1[end] = line2[end] = '\0';
		key[0] = value[0] = '\0';
		sscanf(line1+start, " %80[^ \t\r\n]", key);
		sscanf(line2+start, " %80[^ \t\r\n]", value);
		if (strcmp(key, "NX")==0)
			pht->hd.width = atoi(value);
		else if (strcmp(key, "NY")==0)
			pht->hd.height = atoi(value);
		else if (strcmp(key, "LOWBAD")==0)
			pht->hd.range[0] = atof(value);
		else if (strcmp(key, "HIGHBAD")==0)
			pht->hd.range[1] = atof(value);
		else if (strcmp(key, "THRESH")==0)
			pht->hd.threshold = atof(value);
		else if (strcmp(key, "PH/ADU")==0)
			pht->hd.gain = atof(value);
		else if (strcmp(key, "RNOISE")==0)
			pht->hd.rnoise = atof(value);
		else if (strcmp(key, "JD")==0)
			pht->hd.jd = atof(value);
		else if (strcmp(key, "FILTER")==0)
			pht->hd.filter = cmpack_strdup(value);
		else if (strcmp(key, "EXPTIME")==0)
			pht->hd.exptime = atof(value);
		else if (strcmp(key, "FWHM")==0)
			pht->hd.fwhm_exp = atof(value);
		else if (strcmp(key, "AP1")==0)
			ap0 = atof(value);
		else if (strcmp(key, "OFFSETX")==0) {
			pht->hd.offset[0] = atof(value);
			pht->hd.matched = 1;
		} else if (strcmp(key, "OFFSETY")==0) {
			pht->hd.offset[1] = atof(value);
			pht->hd.matched = 1;
		}
		start = end+1;
	}
	memset(&pht->hd.trafo, 0, 6*sizeof(double));

	/* Single blank line */
	fgets(line1, 200, f);
	j = 3;

	/* Make table of apertures */
	apertures_init(&pht->ap, 12);
	for (i=0; i<12; i++) {
		aper.id = i+1;
		aper.radius = (i==0 ? ap0 : 0);
		aperture_add(&pht->ap, CMPACK_PA_ID | CMPACK_PA_RADIUS, &aper);
	}

	/* Read magnitudes and errors of stars */
	while (fgets(line1, 200, f) && fgets(line2, 200, f)) {
		j+=2;
		obj.id = strtol(line1, &endptr, 10);
		if (obj.id>=0 && endptr!=line1) {
			/* Star */
			obj.ref_id = (pht->hd.matched ? (j-5)/3 : -1);
			index = object_add(&pht->st, CMPACK_PO_ID | CMPACK_PO_REF_ID, &obj, 12);
			if (index>=0) {
				CmpackObject *star = &pht->st.list[index];
				start1 = endptr;
				star->info.x = strtod(start1, &endptr);
				if (start1!=endptr) {
					start1 = endptr;
					star->info.y = strtod(start1, &endptr);
					if (start1!=endptr) 
						start1 = endptr;
				}
				start2 = line2;
				for (i=0; i<3; i++) {
					start2 += strspn(start2, " \t\r\n");
					start2 += strcspn(start2, " \t\r\n");
				}
				/* Read magnitudes */
				for (i=0; i<12; i++) {
					CmpackMagData *dt = pht->st.list[index].data.items + i;
					start1 += strspn(start1, " \t\r\n");
					mag = strtod(start1, &endptr);
					mag_valid = (endptr!=start1 && mag > -99 && mag < 99);
					dt->valid = mag_valid;
					dt->mag = (mag_valid ? mag : DBL_MAX);
					if (endptr==start1)
						start1 += strcspn(start1, " \t\r\n");
					else 
						start1 = endptr;
					start2 += strspn(start2, " \t\r\n");
					err = strtod(start2, &endptr);
					err_valid = (endptr!=start2 && err >= 0 && err <= 9);
					dt->err = (err_valid ? err : -1.0);
					if (endptr==start2)
						start2 += strcspn(start2, " \t\r\n");
					else
						start2 = endptr;
					dt->code = 0;
				}
			}
		}
		/* Skip empty line */
		fgets(line1, 200, f);
		j++;
	}
	return CMPACK_ERR_OK;
}

/* Header string is padded with spaces */
static char *info_gets(const char *src, int srclen)
{
	char *dst;

	if (srclen>0) {
		const char *ptr = src + (srclen-1);
		while (ptr>=src && (*src>='\0' && *src<=' '))
			src++;
		while (ptr>=src && (*ptr>='\0' && *ptr<=' '))
			ptr--;
		if (ptr>=src) {
			int length = (int)(ptr-src+1);
			dst = (char*)cmpack_malloc(length+1);
			memcpy(dst, src, length);
			dst[length] = '\0';
			return dst;
		}
	}
	return NULL;
}

static int header_length(int revision)
{
	switch (revision)
	{
	case 1:		return 492;
	default:	return 540;
	}
}

static int object_length(int revision)
{
	switch (revision)
	{
	case 1: case 2: return 40;
	default:		return 48;
	}
}

static int data_length(int revision)
{
	switch (revision)
	{
	case 1: case 2: return 8;
	default:		return 12;
	}
}

/* Read photometry file from binary file */
static int file_loadbin(CmpackPhtFile *f, FILE *from)
{
	size_t len, bytes;
	int hdrlen, napertures, nstars;
	char hdr[MAX_HEADER_SIZE], line[1024];
	double aux;
	CmpackPhtObject obj;

	memset(&obj, 0, sizeof(CmpackPhtObject));

	/* Magic string */
	len = strlen(MAGIC_STRING);
	bytes = fread(line, 1, len, from);
	if (bytes!=len)
		return CMPACK_ERR_READ_ERROR;
	line[bytes] = '\0';
	if (strcmp(line, MAGIC_STRING)!=0)
		return CMPACK_ERR_UNKNOWN_FORMAT;
	
	/* Version identifier */
	if (fread(&f->revision, sizeof(int32_t), 1, from)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (f->revision<MINIMUM_REVISION || f->revision>CURRENT_REVISION)
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Header length */
	if (fread(&hdrlen, sizeof(int32_t), 1, from)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (hdrlen!=header_length(f->revision))
		return CMPACK_ERR_UNKNOWN_FORMAT;

	/* Read and parse the header */
	if (fread(hdr, hdrlen, 1, from)!=1)
		return CMPACK_ERR_READ_ERROR;
	f->hd.width = *(int32_t*)(hdr+4);
	f->hd.height = *(int32_t*)(hdr+8);
	memcpy(&f->hd.jd, hdr+12, sizeof(double));
	f->hd.filter = info_gets(hdr+20, 70);
	memcpy(&f->hd.exptime, hdr+90, sizeof(double));
	memcpy(&f->hd.ccdtemp, hdr+98, sizeof(double));
	f->hd.origin = info_gets(hdr+106, 70);
	f->hd.crtime.tm_year = *(int16_t*)(hdr+176);
	f->hd.crtime.tm_mon = *(int8_t*)(hdr+178);
	f->hd.crtime.tm_mday = *(int8_t*)(hdr+179);
	f->hd.crtime.tm_hour = *(int8_t*)(hdr+180);
	f->hd.crtime.tm_min = *(int8_t*)(hdr+181);
	f->hd.crtime.tm_sec = *(int8_t*)(hdr+182);
	memcpy(&f->hd.range[0], hdr+184, sizeof(double));
	memcpy(&f->hd.range[1], hdr+192, sizeof(double));
	memcpy(&f->hd.gain, hdr+200, sizeof(double));
	memcpy(&f->hd.rnoise, hdr+208, sizeof(double));
	memcpy(&f->hd.fwhm_exp, hdr+216, sizeof(double));
	memcpy(&f->hd.fwhm_mean, hdr+224, sizeof(double));
	memcpy(&f->hd.fwhm_err, hdr+232, sizeof(double));
	memcpy(&f->hd.threshold, hdr+240, sizeof(double));
	memcpy(&f->hd.sharpness[0], hdr+248, sizeof(double));
	memcpy(&f->hd.sharpness[1], hdr+256, sizeof(double));
	memcpy(&f->hd.roundness[0], hdr+264, sizeof(double));
	memcpy(&f->hd.roundness[1], hdr+272, sizeof(double));
	f->hd.matched = *(int32_t*)(hdr+280);
	f->hd.match_rstars = *(int32_t*)(hdr+284);
	f->hd.match_istars = *(int32_t*)(hdr+288);
	f->hd.match_mstars = *(int32_t*)(hdr+292);
	memcpy(&f->hd.match_clip, hdr+296, sizeof(double));
	memcpy(&f->hd.offset[0], hdr+304, sizeof(double));
	memcpy(&f->hd.offset[1], hdr+312, sizeof(double));
	f->hd.object.designation = info_gets(hdr+320, 70);
	memcpy(&aux, hdr+390, sizeof(double));
	f->hd.object.ra_valid = (aux>=0 && aux<=24);
	f->hd.object.ra = (f->hd.object.ra_valid ? aux : 0);
	memcpy(&aux, hdr+398, sizeof(double));
	f->hd.object.dec_valid = (aux>=-90 && aux<=90);
	f->hd.object.dec = (f->hd.object.dec_valid ? aux : 0);
	f->hd.location.designation = info_gets(hdr+406, 70);
	memcpy(&aux, hdr+476, sizeof(double));
	f->hd.location.lon_valid = (aux>=-360 && aux<=360);
	f->hd.location.lon = (f->hd.location.lon_valid ? aux : 0);
	memcpy(&aux, hdr+484, sizeof(double));
	f->hd.location.lat_valid = (aux>=-90 && aux<=90);
	f->hd.location.lat = (f->hd.location.lat_valid ? aux : 0);
	if (f->revision>=2 && hdrlen>=header_length(2)) {
		memcpy(&f->hd.trafo.xx, hdr+492, sizeof(double));
		memcpy(&f->hd.trafo.xy, hdr+500, sizeof(double));
		memcpy(&f->hd.trafo.x0, hdr+508, sizeof(double));
		memcpy(&f->hd.trafo.yx, hdr+516, sizeof(double));
		memcpy(&f->hd.trafo.yy, hdr+524, sizeof(double));
		memcpy(&f->hd.trafo.y0, hdr+532, sizeof(double));
	}

	/* Read length of WCS data */
	if (f->revision>=4) {
		int32_t len;
		if (fread(&len, sizeof(int32_t), 1, from)!=1)
			return CMPACK_ERR_READ_ERROR;
		if (len<0)
			return CMPACK_ERR_READ_ERROR;
		f->wcs_offset = ftell(from);
		f->wcs_length = len;
		if (fseek(from, len, SEEK_CUR)!=0)
			return CMPACK_ERR_READ_ERROR;
	}
		
	/* Read number of apertures */
	if (fread(&napertures, sizeof(int32_t), 1, from)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (napertures<0 || napertures>=256)
		return CMPACK_ERR_READ_ERROR;
	f->ap_loaded = 0;
	f->ap_offset = ftell(from);
	apertures_init(&f->ap, napertures);
	f->ap.count = napertures;
	if (fseek(from, APERTURE_LENGTH*napertures, SEEK_CUR)!=0)
		return CMPACK_ERR_READ_ERROR;

	/* Read number of stars */
	if (fread(&nstars, sizeof(int32_t), 1, from)!=1)
		return CMPACK_ERR_READ_ERROR;
	if (nstars<0) 
		return CMPACK_ERR_READ_ERROR;
	f->st_loaded = 0;
	f->st_offset = ftell(from);
	objects_init(&f->st, nstars);
	f->st.count = nstars;
	f->dt_loaded = 0;
	f->dt_offset = f->st_offset + object_length(f->revision)*nstars;
	f->delayload = 1;
	return CMPACK_ERR_OK;
}

static int load_apertures(CmpackPhtFile *f, int index)
{
	int i, k, napertures;
	long offset;
	char *buf;

	if (f->delayload) {
		napertures = index - f->ap_loaded + 1;
		if (napertures < 64)
			napertures = 64;
		if (napertures > f->ap.count - f->ap_loaded)
			napertures = f->ap.count - f->ap_loaded;
		if (napertures>0) {
			/* Set file position */
			offset = f->ap_loaded*APERTURE_LENGTH;
			if (fseek(f->f, f->ap_offset + offset, SEEK_SET)!=0) 
				return CMPACK_ERR_READ_ERROR;
			/* Allocate buffer are read data */
			buf = (char*)cmpack_malloc(napertures*APERTURE_LENGTH);
			if (fread(buf, APERTURE_LENGTH, napertures, f->f)!=napertures) {
				cmpack_free(buf);
				return CMPACK_ERR_READ_ERROR;
			}
			/* Decode data in buffer */
			for (k=0, i=f->ap_loaded; k<napertures; k++, i++) {
				const char *sptr = buf + (k*APERTURE_LENGTH);
				CmpackPhtAperture *aper = f->ap.list+i;
				aper->id = *(int32_t*)(sptr);
				memcpy(&aper->radius, sptr+4, sizeof(double));
			}
			cmpack_free(buf);
			/* Update number of loaded records */
			f->ap_loaded = i;
		}
		return (f->ap_loaded>index ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
	}
	return CMPACK_ERR_OK;
}

static int load_stars(CmpackPhtFile *f, int index)
{
	int i, k, nstars, recsize = object_length(f->revision);
	long offset;
	char *buf;

	if (f->delayload) {
		nstars = index - f->st_loaded + 1;
		if (nstars < 64)
			nstars = 64;
		if (nstars > f->st.count - f->st_loaded)
			nstars = f->st.count - f->st_loaded;
		if (nstars>0) {
			/* Set file position */
			offset = f->st_loaded*recsize;
			if (fseek(f->f, f->st_offset + offset, SEEK_SET)!=0) 
				return CMPACK_ERR_READ_ERROR;
			/* Allocate buffer are read data */
			buf = (char*)cmpack_malloc(nstars*recsize);
			if (fread(buf, recsize, nstars, f->f)!=nstars) {
				cmpack_free(buf);
				return CMPACK_ERR_READ_ERROR;
			}
			for (k=0, i=f->st_loaded; k<nstars; k++, i++) {
				const char *sptr = buf + (k*recsize);
				CmpackPhtObject *obj = &f->st.list[i].info;
				obj->id = *(int32_t*)(sptr);
				obj->ref_id = *(int32_t*)(sptr+4);
				memcpy(&obj->x, sptr+8, sizeof(double));
				memcpy(&obj->y, sptr+16, sizeof(double));
				memcpy(&obj->skymed, sptr+24, sizeof(double));
				memcpy(&obj->skysig, sptr+32, sizeof(double));
				if (f->revision>=3)
					memcpy(&obj->fwhm, sptr+40, sizeof(double));
				else
					obj->fwhm = 0;
			}
			cmpack_free(buf);
			/* Update number of loaded records */
			f->st_loaded = i;
		}
		return (f->st_loaded>index ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
	}
	return CMPACK_ERR_OK;
}

static int load_data(CmpackPhtFile *f, int index)
{
	int i, j, k, nstars, napertures, recsize = data_length(f->revision);
	char *buf;
	long offset;
	
	if (f->delayload) {
		nstars = index - f->dt_loaded + 1;
		if (nstars < 32)
			nstars = 32;
		if (nstars > f->st.count - f->dt_loaded)
			nstars = f->st.count - f->dt_loaded;
		napertures = f->ap.count;
		if (nstars>0 && napertures>0) {
			/* Set file position */
			offset = f->dt_loaded*napertures*recsize;
			if (fseek(f->f, f->dt_offset + offset, SEEK_SET)!=0) 
				return CMPACK_ERR_READ_ERROR;
			/* Allocate buffer are read data */
			buf = (char*)cmpack_malloc(nstars*napertures*recsize);
			if (fread(buf, recsize*napertures, nstars, f->f)!=nstars) {
				cmpack_free(buf);
				return CMPACK_ERR_READ_ERROR;
			}
			/* Decode data in buffer */
			for (k=0, i=f->dt_loaded; k<nstars; k++, i++) {
				data_init(&f->st.list[i].data, napertures);
				for (j=0; j<napertures; j++) {
					const int *sptr = (int32_t*)(buf + (k*napertures + j)*recsize);
					CmpackMagData *data = f->st.list[i].data.items + j;
					data->valid = (sptr[0]!=INT_MAX);
					if (data->valid)
						data->mag = (double)sptr[0] / 0x1000000;
					else
						data->mag = DBL_MAX;
					if (sptr[1]!=INT_MAX)
						data->err = (double)sptr[1] / 0x1000000;
					else
						data->err = -1.0;
					data->code = (f->revision>=3 ? sptr[2] : 0);
				}
			}
			cmpack_free(buf);
			/* Update number of loaded records */
			f->dt_loaded = i;
		}
		return (f->dt_loaded>index ? CMPACK_ERR_OK : CMPACK_ERR_OUT_OF_RANGE);
	}
	return CMPACK_ERR_OK;
}

static int load_wcs(CmpackPhtFile *f)
{
	char *buf;

	if (f->delayload && !f->wcs && f->wcs_offset>0 && f->wcs_length>0) {
		/* Set file position */
		if (fseek(f->f, f->wcs_offset, SEEK_SET)!=0) 
			return CMPACK_ERR_READ_ERROR;
		/* Allocate buffer are read data */
		buf = (char*)cmpack_malloc(f->wcs_length);
		if (fread(buf, f->wcs_length, 1, f->f)!=1) {
			cmpack_free(buf);
			return CMPACK_ERR_READ_ERROR;
		}
		f->wcs = cmpack_wcs_new_from_FITS_header(buf, f->wcs_length/80);
		cmpack_free(buf);
	}
	return (f->wcs!=NULL ? CMPACK_ERR_OK : CMPACK_ERR_UNDEF_VALUE);
}

/* Writes content of the file to the stream in XML format */
static int file_writexml(CmpackPhtFile *pht, FILE *to)
{
	int i, j, mag_valid;
	char buf[64];
	CmpackDateTime dt;

	/* Prolog */
	fprintf(to, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
	fprintf(to, "<phot version=\"1\">\n");
	/* Write headers */
	fprintf(to, "<head>\n");
	if (pht->hd.width>0)
		fprintf(to, "\t<width>%d</width>\n", pht->hd.width);
	if (pht->hd.height>0)
		fprintf(to, "\t<height>%d</height>\n", pht->hd.height);
	if (pht->hd.jd>0) {
		cmpack_decodejd(pht->hd.jd, &dt);
		fprintf(to, "\t<jd>%.*f</jd>\n", JD_PRECISION, pht->hd.jd);
		cmpack_datetostr(&dt.date, buf, 64);
		fprintf(to, "\t<date>%s</date>\n", buf);
		cmpack_timetostr(&dt.time, buf, 64);
		fprintf(to, "\t<time>%s</date>\n", buf);
	}
	if (pht->hd.filter && pht->hd.filter[0]!='\0') {
		char *aux = xml_encode_string(pht->hd.filter);
		fprintf(to, "\t<filter>%s</filter>\n", aux);
		cmpack_free(aux);
	}
	if (pht->hd.exptime>0)
		fprintf(to, "\t<exptime>%.*f</exptime>\n", EXP_PRECISION, pht->hd.exptime);
	if (pht->hd.ccdtemp>0)
		fprintf(to, "\t<temp>%.*f</ccdtemp>\n", TEMP_PRECISION, pht->hd.ccdtemp);
	if (pht->hd.origin && pht->hd.origin[0]!='\0') {
		char *aux = xml_encode_string(pht->hd.origin);
		fprintf(to, "\t<origin>%s</origin>", aux);
		cmpack_free(aux);
	}
	if (pht->hd.crtime.tm_year>0) {
		struct tm *t = &pht->hd.crtime;
		fprintf(to, "\t<crdate>%04d-%02d-%02d %02d:%02d:%02d</crdate>\n", 
			t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
	}
	if (pht->st.count>0)
		fprintf(to, "\t<phot_stars>%d</phot_stars>\n", pht->st.count);
	if (pht->hd.range[0]!=0)
		fprintf(to, "\t<phot_datalo>%.8g</phot_datalo>\n", pht->hd.range[0]);
	if (pht->hd.range[1]!=0)
		fprintf(to, "\t<phot_datahi>%.8g</phot_datahi>\n", pht->hd.range[1]);
	if (pht->hd.gain!=0)
		fprintf(to, "\t<phot_gain>%.6g</phot_gain>\n", pht->hd.gain);
	if (pht->hd.rnoise!=0)
		fprintf(to, "\t<phot_rnoise>%.6g</phot_rnoise>\n", pht->hd.rnoise);
	if (pht->hd.fwhm_exp>0)
		fprintf(to, "\t<phot_fwhm_exp>%.6g</phot_fwhm_exp>\n", pht->hd.fwhm_exp);
	if (pht->hd.fwhm_mean>0)
		fprintf(to, "\t<phot_fwhm_mean>%.6g</phot_fwhm_mean>\n", pht->hd.fwhm_mean);
	if (pht->hd.fwhm_err>0)
		fprintf(to, "\t<phot_fwhm_err>%.6g</phot_fwhm_err>\n", pht->hd.fwhm_err);
	if (pht->hd.threshold!=0)
		fprintf(to, "\t<phot_thresh>%.6g</phot_thresh>\n", pht->hd.threshold);
	if (pht->hd.sharpness[0]!=0)
		fprintf(to, "\t<phot_losharp>%.6g</phot_losharp>\n", pht->hd.sharpness[0]);
	if (pht->hd.sharpness[1]!=0)
		fprintf(to, "\t<phot_hisharp>%.6g</phot_hisharp>\n", pht->hd.sharpness[1]);
	if (pht->hd.roundness[0]!=0)
		fprintf(to, "\t<phot_loround>%.6g</phot_loround>\n", pht->hd.roundness[0]);
	if (pht->hd.roundness[1]!=0)
		fprintf(to, "\t<phot_hiround>%.6g</phot_hiround>\n", pht->hd.roundness[1]);
	if (pht->hd.matched) {
		fprintf(to, "\t<match_rstars>%d</match_rstars>\n", pht->hd.match_rstars);
		fprintf(to, "\t<match_istars>%d</match_istars>\n", pht->hd.match_istars);
		fprintf(to, "\t<match_clip>%.6g</match_clip>\n", pht->hd.match_clip);
		fprintf(to, "\t<match_stars>%d</match_stars>\n", pht->hd.match_mstars);
		fprintf(to, "\t<offsetx>%.*f</offsetx>\n", POS_PRECISION, pht->hd.offset[0]);
		fprintf(to, "\t<offsety>%.*f</offsety>\n", POS_PRECISION, pht->hd.offset[1]);
	}
	if (pht->hd.object.designation && pht->hd.object.designation[0]!='\0') {
		char *aux = xml_encode_string(pht->hd.object.designation);
		fprintf(to, "\t<object>%s</object>", aux);
		cmpack_free(aux);
	}
	if (pht->hd.object.ra_valid) {
		cmpack_ratostr(pht->hd.object.ra, buf, 64);
		fprintf(to, "\t<ra2000>%s</ra2000>", buf);
	}
	if (pht->hd.object.dec_valid) {
		cmpack_dectostr(pht->hd.object.dec, buf, 64);
		fprintf(to, "\t<dec2000>%s</dec2000>", buf);
	}
	if (pht->hd.location.designation && pht->hd.location.designation[0]!='\0') {
		char *aux = xml_encode_string(pht->hd.location.designation);
		fprintf(to, "\t<location>%s</location>", aux);
		cmpack_free(aux);
	}
	if (pht->hd.location.lon_valid) {
		cmpack_lontostr(pht->hd.location.lon, buf, 64);
		fprintf(to, "\t<longitude>%s</longitude>", buf);
	}
	if (pht->hd.location.lat_valid) {
		cmpack_lattostr(pht->hd.location.lat, buf, 64);
		fprintf(to, "\t<latitude>%s</latitude>", buf);
	}
	if (pht->hd.trafo.xx!=0 || pht->hd.trafo.xy!=0 || pht->hd.trafo.yx!=0 || pht->hd.trafo.yy!=0) {
		fprintf(to, "\t<m11>%.8g</m11>", pht->hd.trafo.xx);
		fprintf(to, "\t<m21>%.8g</m21>", pht->hd.trafo.xy);
		fprintf(to, "\t<m31>%.8g</m31>", pht->hd.trafo.x0);
		fprintf(to, "\t<m12>%.8g</m12>", pht->hd.trafo.yx);
		fprintf(to, "\t<m22>%.8g</m22>", pht->hd.trafo.yy);
		fprintf(to, "\t<m32>%.8g</m32>", pht->hd.trafo.y0);
	}
	
	fprintf(to, "</head>\n");
	/* Write apertures */
	if (pht->ap.count>0) {
		fprintf(to,  "<apertures>\n");
		for (i=0; i<pht->ap.count; i++) {
			CmpackPhtAperture *aper = &pht->ap.list[i];
			fprintf(to, "\t<aper id=\"%d\"", aper->id);
			if (aper->radius>0) 
				fprintf(to, " radius=\"%.3f\"", aper->radius);
			fprintf(to, "/>\n");
		}
  		fprintf(to, "</apertures>\n");
	}
	/* Write stars */
	if (pht->st.count>0) {
		fprintf(to,  "<body>\n");
		for (i=0; i<pht->st.count; i++) {
			CmpackObject *st = &pht->st.list[i];
			fprintf(to, "<object id=\"%d\"", st->info.id); 
			/* Write attributes */
			fprintf(to, " x=\"%.3f\" y=\"%.3f\"", st->info.x, st->info.y);
			if (st->info.ref_id>=0)
				fprintf(to, " x-ref=\"%d\"", st->info.ref_id);
			if (st->info.skymed!=0)
				fprintf(to, " skymed=\"%.6f\"", st->info.skymed);
			if (st->info.skysig!=0)
				fprintf(to, " skysig=\"%.6f\"", st->info.skysig);
			if (st->info.fwhm!=0)
				fprintf(to, " fwhm=\"%.6f\"", st->info.fwhm);
			/* Number of valid measurements */
			mag_valid = 0;
			for (j=0; j<pht->ap.count; j++) {
				if (st->data.items[j].valid) {
					mag_valid = 1;
					break;
				}
			}
			if (mag_valid) {
				fprintf(to, ">\n");
				/* Write measurements */
				for (j=0; j<pht->ap.count; j++) {
					CmpackMagData *mag = st->data.items + j;
					fprintf(to, "\t<p a=\"%d\"", pht->ap.list[j].id);
					if (mag->valid && mag->mag > -99 && mag->mag < 99)
						fprintf(to, " m=\"%.*f\"", MAG_PRECISION, mag->mag);
					if (mag->valid && mag->err >= 0 && mag->err <= 9)
						fprintf(to, " e=\"%.*f\"", MAG_PRECISION, mag->err);
					if (mag->code!=0)
						fprintf(to, " c=\"%d\"", mag->code);
					fprintf(to, "/>\n");
				}
				fprintf(to, "</object>\n");
			} else {
				/* Table of magnitudes is empty */
				fprintf(to, "/>\n");
			}
		}
		fprintf(to,  "</body>\n");
	}
	/* Epilog */
	fprintf(to,  "</phot>\n");
	return CMPACK_ERR_OK;
}

/* Writes content of the file to the stream in XML format */
static int file_writesrt(CmpackPhtFile *pht, FILE *to)
{
	int i, j, refid, maxid;
	double ap0, mag, err;

	ap0 = (pht->ap.count>0 ? pht->ap.list[0].radius : 0.0);
	if (!pht->hd.matched) {
		fprintf(to, " NL   NX   NY  LOWBAD HIGHBAD  THRESH     AP1  PH/ADU  RNOISE              JD           FILTER    EXPTIME    FWHM\n");
		fprintf(to, " %2d %4d %4d %7.0f %7.0f %7.2f %7.2f %7.2f %7.2f %15.6f %16s %10.2f %7.2f\n",
			2, pht->hd.width, pht->hd.height, pht->hd.range[0], pht->hd.range[1], 
			pht->hd.threshold, ap0, pht->hd.gain, pht->hd.rnoise, pht->hd.jd, 
			pht->hd.filter, pht->hd.exptime, pht->hd.fwhm_exp);
		fprintf(to, "\n");
		for (i=0; i<pht->st.count; i++) {
			CmpackObject *st = &pht->st.list[i];
			fprintf(to, " %5d %8.3f %8.3f", st->info.id, st->info.x, st->info.y);
			for (j=0; j<pht->ap.count; j++) {
				if (j<st->data.size && st->data.items[j].valid && st->data.items[j].mag > -99 && st->data.items[j].mag < 99)
					mag = st->data.items[j].mag;
				else
					mag = 99.9999;
				fprintf(to," %8.4f", mag);
			}
			fprintf(to, "\n");
			fprintf(to, "     %8.3f %5.2f %5.2f", st->info.skymed, st->info.skysig, 0.0);
			for (j=0; j<pht->ap.count; j++) {
				if (j<st->data.size && st->data.items[j].valid && st->data.items[j].err >= 0 && st->data.items[j].err <= 9)
					err = st->data.items[j].err;
				else
					err = 9.9999;
				fprintf(to," %*.4f", (j==0?7:8), err);
			}
			fprintf(to,"\n\n");
		}
	} else {
		fprintf(to, " NL   NX   NY  LOWBAD HIGHBAD  THRESH     AP1  PH/ADU  RNOISE              JD           FILTER    EXPTIME    FWHM  OFFSETX  OFFSETY\n");
		fprintf(to, " %2d %4d %4d %7.0f %7.0f %7.2f %7.2f %7.2f %7.2f %15.6f %16s %10.2f %7.2f %8.2f %8.2f\n",
			2, pht->hd.width, pht->hd.height, pht->hd.range[0], pht->hd.range[1], 
			pht->hd.threshold, ap0, pht->hd.gain, pht->hd.rnoise, pht->hd.jd, 
			pht->hd.filter, pht->hd.exptime, pht->hd.fwhm_mean, pht->hd.offset[0], pht->hd.offset[1]);
		fprintf(to, "\n");
		maxid = 0;
		for (i=0; i<pht->st.count; i++) {
			if (pht->st.list[i].info.ref_id > maxid)
				maxid = pht->st.list[i].info.ref_id;
		}
		for (refid=1; refid<=maxid; refid++) {
			i = cmpack_pht_find_object(pht, refid, 1);
			if (i>=0) {
				CmpackObject *st = &pht->st.list[i];
				fprintf(to, " %5d %8.3f %8.3f", st->info.id, st->info.x, st->info.y);
				for (j=0; j<pht->ap.count; j++) {
					if (j<st->data.size && st->data.items[j].valid && st->data.items[j].mag > -99 && st->data.items[j].mag < 99)
						mag = st->data.items[j].mag;
					else
						mag = 99.9999;
					fprintf(to," %8.4f", mag);
				}
				fprintf(to, "\n");
				fprintf(to, "     %8.3f %5.2f %5.2f", st->info.skymed, st->info.skysig, 0.0);
				for (j=0; j<pht->ap.count; j++) {
					if (j<st->data.size && st->data.items[j].valid && st->data.items[j].err >= 0 && st->data.items[j].err <= 9)
						err = st->data.items[j].err;
					else
						err = 9.9999;
					fprintf(to," %*.4f", (j==0?7:8), err);
				}
				fprintf(to,"\n\n");
			} else {
				fprintf(to,"\n\n\n");
			}
		}
	}
	return CMPACK_ERR_OK;
}
/* Save string padded with spaces */
static void info_puts(char *dst, int dstlen, const char *src)
{
	if (src && src[0]!='\0') {
		int len = (int)strlen(src);
		if (len>dstlen)
			len = dstlen;
		memcpy(dst, src, len);
		if (len<dstlen)
			memset(dst+len, ' ', dstlen-len);
	} else {
		memset(dst, ' ', dstlen);
	}
}

/* Read photometry file from binary file */
static int file_writebin(CmpackPhtFile *f, FILE *to)
{
	int i, j, count, revision, hdrlen, napertures, nstars, nkeyrec = 0;
	char hdr[MAX_HEADER_SIZE], *buf = NULL;
	double aux;

	/* Magic string */
	size_t len = strlen(MAGIC_STRING);
	if (fwrite(MAGIC_STRING, 1, len, to)!=len)
		return CMPACK_ERR_WRITE_ERROR;
	
	/* Version identifier */
	revision = CURRENT_REVISION;
	if (fwrite(&revision, sizeof(int32_t), 1, to)!=1)
		return CMPACK_ERR_WRITE_ERROR;

	/* Header length */
	hdrlen = header_length(revision);
	if (fwrite(&hdrlen, sizeof(int32_t), 1, to)!=1)
		return CMPACK_ERR_WRITE_ERROR;

	/* Read and parse the header */
	*(int32_t*)(hdr+4) = f->hd.width;
	*(int32_t*)(hdr+8) = f->hd.height;
	memcpy(hdr+12, &f->hd.jd, sizeof(double));
	info_puts(hdr+20, 70, f->hd.filter);
	memcpy(hdr+90, &f->hd.exptime, sizeof(double));
	memcpy(hdr+98, &f->hd.ccdtemp, sizeof(double));
	info_puts(hdr+106, 70, f->hd.origin);
	*(int16_t*)(hdr+176) = f->hd.crtime.tm_year;
	*(int8_t*)(hdr+178) = f->hd.crtime.tm_mon;
	*(int8_t*)(hdr+179) = f->hd.crtime.tm_mday;
	*(int8_t*)(hdr+180) = f->hd.crtime.tm_hour;
	*(int8_t*)(hdr+181) = f->hd.crtime.tm_min;
	*(int8_t*)(hdr+182) = f->hd.crtime.tm_sec;
	memcpy(hdr+184, &f->hd.range[0], sizeof(double));
	memcpy(hdr+192, &f->hd.range[1], sizeof(double));
	memcpy(hdr+200, &f->hd.gain, sizeof(double));
	memcpy(hdr+208, &f->hd.rnoise, sizeof(double));
	memcpy(hdr+216, &f->hd.fwhm_exp, sizeof(double));
	memcpy(hdr+224, &f->hd.fwhm_mean, sizeof(double));
	memcpy(hdr+232, &f->hd.fwhm_err, sizeof(double));
	memcpy(hdr+240, &f->hd.threshold, sizeof(double));
	memcpy(hdr+248, &f->hd.sharpness[0], sizeof(double));
	memcpy(hdr+256, &f->hd.sharpness[1], sizeof(double));
	memcpy(hdr+264, &f->hd.roundness[0], sizeof(double));
	memcpy(hdr+272, &f->hd.roundness[1], sizeof(double));
	*(int32_t*)(hdr+280) = f->hd.matched;
	*(int32_t*)(hdr+284) = f->hd.match_rstars;
	*(int32_t*)(hdr+288) = f->hd.match_istars;
	*(int32_t*)(hdr+292) = f->hd.match_mstars;
	memcpy(hdr+296, &f->hd.match_clip, sizeof(double));
	memcpy(hdr+304, &f->hd.offset[0], sizeof(double));
	memcpy(hdr+312, &f->hd.offset[1], sizeof(double));
	info_puts(hdr+320, 70, f->hd.object.designation);
	aux = (f->hd.object.ra_valid ? f->hd.object.ra : DBL_MAX);
	memcpy(hdr+390, &aux, sizeof(double));
	aux = (f->hd.object.dec_valid ? f->hd.object.dec : DBL_MAX);
	memcpy(hdr+398, &aux, sizeof(double));
	info_puts(hdr+406, 70, f->hd.location.designation);
	aux = (f->hd.location.lon_valid ? f->hd.location.lon : DBL_MAX);
	memcpy(hdr+476, &aux, sizeof(double));
	aux = (f->hd.location.lat_valid ? f->hd.location.lat : DBL_MAX);
	memcpy(hdr+484, &aux, sizeof(double));
	memcpy(hdr+492, &f->hd.trafo.xx, sizeof(double));
	memcpy(hdr+500, &f->hd.trafo.xy, sizeof(double));
	memcpy(hdr+508, &f->hd.trafo.x0, sizeof(double));
	memcpy(hdr+516, &f->hd.trafo.yx, sizeof(double));
	memcpy(hdr+524, &f->hd.trafo.yy, sizeof(double));
	memcpy(hdr+532, &f->hd.trafo.y0, sizeof(double));
	if (fwrite(hdr, hdrlen, 1, to)!=1)
		return CMPACK_ERR_WRITE_ERROR;

	/* Write WCS data */
	if (cmpack_wcs_to_FITS_header(f->wcs, &buf, &nkeyrec)==0 && buf && nkeyrec>0) {
		int32_t len = nkeyrec*80;
		if (fwrite(&len, sizeof(int32_t), 1, to)!=1 || fwrite(buf, 80, nkeyrec, to)!=nkeyrec) {
			cmpack_free(buf);
			return CMPACK_ERR_WRITE_ERROR;
		}
		cmpack_free(buf);
	} else {
		int32_t len = 0;
		if (fwrite(&len, sizeof(int32_t), 1, to)!=1) 
			return CMPACK_ERR_WRITE_ERROR;
	}

	/* Read table of apertures */
	napertures = f->ap.count;
	if (fwrite(&napertures, sizeof(int32_t), 1, to)!=1)
		return CMPACK_ERR_WRITE_ERROR;
	if (napertures>0) {
		buf = (char*)cmpack_malloc(napertures*APERTURE_LENGTH);
		for (i=0; i<napertures; i++) {
			char *sptr = buf + (i*APERTURE_LENGTH);
			*(int32_t*)(sptr) = f->ap.list[i].id;
			memcpy(sptr+4, &f->ap.list[i].radius, sizeof(double));
		}
		if (fwrite(buf, APERTURE_LENGTH, napertures, to)!=napertures) {
			cmpack_free(buf);
			return CMPACK_ERR_WRITE_ERROR;
		}
		cmpack_free(buf);
	}

	/* Read table of stars */
	nstars = f->st.count;
	if (fwrite(&nstars, sizeof(int32_t), 1, to)!=1)
		return CMPACK_ERR_WRITE_ERROR;
	if (nstars>0) {
		int recsize = object_length(revision);
		buf = (char*)cmpack_malloc(nstars*recsize);
		for (i=0; i<nstars; i++) {
			char *sptr = buf + (i*recsize);
			*(int32_t*)(sptr) = f->st.list[i].info.id;
			*(int32_t*)(sptr+4) = f->st.list[i].info.ref_id;
			memcpy(sptr+8, &f->st.list[i].info.x, sizeof(double));
			memcpy(sptr+16, &f->st.list[i].info.y, sizeof(double));
			memcpy(sptr+24, &f->st.list[i].info.skymed, sizeof(double));
			memcpy(sptr+32, &f->st.list[i].info.skysig, sizeof(double));
			memcpy(sptr+40, &f->st.list[i].info.fwhm, sizeof(double));
		}
		if (fwrite(buf, recsize, nstars, to)!=nstars) {
			cmpack_free(buf);
			return CMPACK_ERR_WRITE_ERROR;
		}
		cmpack_free(buf);
	}

	/* Read table of measurements */
	count = nstars * napertures;
	if (count>0) {
		int recsize = data_length(revision);
		buf = (char*)cmpack_malloc(count*recsize);
		for (i=0; i<nstars; i++) {
			CmpackMagList *tab = &f->st.list[i].data;
			for (j=0; j<napertures; j++) {
				int *sptr = (int32_t*)(buf + (i*napertures + j)*recsize);
				if (j<tab->size) {
					CmpackMagData *data = tab->items + j;
					if (data->valid && data->mag > -99 && data->mag < 99) {
						if (data->mag>=0)
							sptr[0] = (int32_t)(data->mag * 0x1000000 + 0.5);
						else
							sptr[0] = (int32_t)(data->mag * 0x1000000 - 0.5);
					} else
						sptr[0] = INT_MAX;
					if (data->valid && data->err >= 0 && data->err <= 9) 
						sptr[1] = (int32_t)(data->err * 0x1000000 + 0.5);
					else
						sptr[1] = INT_MAX;
					sptr[2] = data->code;
				} else {
					sptr[0] = sptr[2] = INT_MAX;
					sptr[2] = CMPACK_ERR_UNDEF_VALUE;
				}
			}
		}
		if (fwrite(buf, recsize, count, to)!=count) {
			cmpack_free(buf);
			return CMPACK_ERR_WRITE_ERROR;
		}
		cmpack_free(buf);
	}
		
	return CMPACK_ERR_OK;
}

static int file_close(CmpackPhtFile *file)
{
	int res = CMPACK_ERR_OK;

	if (file->f && !file->readonly && file->changed) {
		rewind(file->f);
		switch (file->format) 
		{
		case CMPACK_PHT_XML:
			res = file_writexml(file, file->f);
			break;
		case CMPACK_PHT_DAOPHOT:
			res = file_writesrt(file, file->f);
			break;
		default:
			res = file_writebin(file, file->f);
			break;
		}
	}
	if (res==0) {
		if (file->f) {
			fclose(file->f);
			file->f = NULL;
		}
		file->readonly = 1;
	}
	return res;
}

/*****************   Public functions    *******************/

/* Creates a empty temporary file. This file is maintained only 
 * in the memory. Returns pointer to internal file structure or
 * NULL if failed. 
 */
CmpackPhtFile *cmpack_pht_init(void)
{
	return file_create();
}

/* Frees all allocated memory in internal structures */
CmpackPhtFile *cmpack_pht_reference(CmpackPhtFile* file)
{
	file->refcnt++;
	return file;
}

/* Frees all allocated memory in internal structures */
void cmpack_pht_destroy(CmpackPhtFile *file)
{
	if (file) {
		file->refcnt--;
		if (file->refcnt==0) {
			if (file->f) 
				file_close(file);
			file_clear(file);
			cmpack_free(file);
		}
	}
}
		
/* Opens file. You can open the file for reading only or in 
 * read-write mode. Returns pointer to internal file structure 
 * handle or NULL if failed.
 */
int cmpack_pht_open(CmpackPhtFile **pfile, const char *filename, CmpackOpenMode mode, unsigned flags)
{
	int res = 0;
	FILE *f = NULL;
	char line[MAXLINE];
	CmpackPhtFile *file;
	CmpackPhtFormat fmt = CMPACK_PHT_DEFAULT;

	*pfile = NULL;
	if (mode == CMPACK_OPEN_READWRITE) 
		f = fopen(filename, "rb+");
	else if (mode == CMPACK_OPEN_CREATE) 
		f = fopen(filename, "wb+");
	else
		f = fopen(filename, "rb");
	if (!f) 
		return CMPACK_ERR_OPEN_ERROR;

	file = file_create();

	if (mode != CMPACK_OPEN_CREATE) {
		/* Autodetect format */
		size_t bytes = fread(line, 1, MAXLINE, f); 
		fseek(f, 0, SEEK_SET);
		if (memstr(line, "<?xml", bytes) && memstr(line, "<phot", bytes)) {
			/* XML photometry file */
			res = file_loadxml(file, f);
			fmt = CMPACK_PHT_XML;
		} else
		if (strstr(line, " NL   NX   NY  LOWBAD HIGHBAD  THRESH")==line) {
			/* Text photometry file */
			res = file_loadsrt(file, f);
			fmt = CMPACK_PHT_DAOPHOT;
		} else 
		if (strstr(line, MAGIC_STRING)==line) {
			/* Binary photometry file */
			res = file_loadbin(file, f);
			fmt = CMPACK_PHT_DEFAULT;
		} else {
			/* Unknown format */
			res = CMPACK_ERR_UNKNOWN_FORMAT;
		}
		if (res!=0) {
			/* Failed to open the file */
			fclose(f);
			cmpack_pht_destroy(file);
			return res;
		}
	} else {
		/* New format according to given flags */
		if (flags & CMPACK_SAVE_XML_FORMAT)
			fmt = CMPACK_PHT_XML;
		else if (flags & CMPACK_SAVE_DAO_FORMAT)
			fmt = CMPACK_PHT_DAOPHOT;
		else
			fmt = CMPACK_PHT_DEFAULT;
	}
		
	/* Normal return */
	file->f = f;
	file->changed = (mode == CMPACK_OPEN_CREATE);
	file->readonly = (mode == CMPACK_OPEN_READONLY);
	file->format = fmt;
	file->flags = flags;
	if (file->delayload && !file->readonly) {
		load_stars(file, file->st.count-1);
		load_apertures(file, file->ap.count-1);
		load_data(file, file->st.count-1);
		load_wcs(file);
		file->delayload = 0;
	}
	*pfile = file;
	return CMPACK_ERR_OK;
}

/* Save data to a file */
int cmpack_pht_close(CmpackPhtFile *file)
{
	int res = file_close(file);
	if (res==0)
		cmpack_pht_destroy(file);
	return res;
}

/* Makes copy of the file */
void cmpack_pht_clear(CmpackPhtFile *file)
{
	if (!file->readonly) {
		file_clear(file);
		file->changed = 1;
	}
}

/* Makes copy of the file */
int cmpack_pht_copy(CmpackPhtFile *fd, CmpackPhtFile *fs)
{
	int i;
	
	if (fd->readonly)
		return CMPACK_ERR_READ_ONLY;

	file_clear(fd);
	info_copy(&fd->hd, &fs->hd);
	if (fs->ap.count>0) {
		if (fs->delayload && fs->ap_loaded!=fs->ap.count)
			load_apertures(fs, fs->ap.count-1);
		fd->ap.capacity = fd->ap.count = fs->ap.count;
		fd->ap.list = (CmpackPhtAperture*)cmpack_malloc(fd->ap.capacity*sizeof(CmpackPhtAperture));
		memcpy(fd->ap.list, fs->ap.list, fd->ap.count*sizeof(CmpackPhtAperture));
	}
	if (fs->st.count>0) {
		if (fs->delayload && fs->st_loaded!=fs->st.count)
			load_stars(fs, fs->st.count-1);
		if (fs->delayload && fs->dt_loaded!=fs->st.count)
			load_data(fs, fs->st.count-1);
		fd->st.capacity = fd->st.count = fs->st.count;
		fd->st.list = (CmpackObject*)cmpack_calloc(fd->st.capacity, sizeof(CmpackObject));
		for (i=0; i<fs->st.count; i++) 
			object_copy(&fd->st.list[i], &fs->st.list[i]);
	}
	if (fs->delayload) 
		load_wcs(fs);
	if (fs->wcs)
		fd->wcs = cmpack_wcs_copy(fs->wcs);
	fd->changed = 1;
	return CMPACK_ERR_OK;
}

/* Test if the file is a photometry file */
int cmpack_pht_test(const char *filename)
{
	int bytes, filesize;
	char buffer[2048];

	FILE *f = fopen(filename, "rb");
	if (f) {
		fseek(f, 0, SEEK_END);
		filesize = ftell(f);
		fseek(f, 0, SEEK_SET);
		bytes = (int)fread(buffer, 1, 2048, f);
		fclose(f);
		return cmpack_pht_test_buffer(buffer, bytes, filesize);
	}
	return 0;
}

/* Test if the file is a photometry file */
int cmpack_pht_test_buffer(const char *buffer, int buflen, int filesize)
{
	if (memstr(buffer, "<?xml", buflen) && memstr(buffer, "<phot", buflen)) {
		/* XML photometry file */
		return 1;
	}
	if (memstr(buffer, " NL   NX   NY  LOWBAD HIGHBAD  THRESH", buflen)==buffer) {
		/* Text photometry file */
		return 1;
	}
	if (memstr(buffer, MAGIC_STRING, buflen)==buffer) {
		/* Binary photometry file */
		return 1;
	}
	return 0;
}

/********************   Header manipulation    ***********************/

static void info_clear(CmpackPhtInfo *info)
{
	cmpack_free(info->filter);
	cmpack_free(info->origin);
	cmpack_free(info->object.designation);
	cmpack_free(info->location.designation);
	memset(info, 0, sizeof(CmpackPhtInfo));
	info->ccdtemp = INVALID_TEMP;
}

static void info_copy(CmpackPhtInfo *dst, const CmpackPhtInfo *src)
{
	*dst = *src;
	if (src->filter)
		dst->filter = cmpack_strdup(src->filter);
	if (src->origin)
		dst->origin = cmpack_strdup(src->origin);
	if (src->object.designation)
		dst->object.designation = cmpack_strdup(src->object.designation);
	if (src->location.designation)
		dst->location.designation = cmpack_strdup(src->location.designation);
}

int cmpack_pht_set_info(CmpackPhtFile *file, unsigned mask, const CmpackPhtInfo *info)
{
	if (file->readonly)
		return CMPACK_ERR_READ_ONLY;

	if (mask & CMPACK_PI_JD) {
		file->hd.jd = info->jd;
	}
	if (mask & CMPACK_PI_FRAME_PARAMS) {
		file->hd.width = info->width;
		file->hd.height = info->height;
		file->hd.jd = info->jd;
		cmpack_free(file->hd.filter);
		file->hd.filter = cmpack_strdup(info->filter);
		file->hd.exptime = info->exptime;
		file->hd.ccdtemp = info->ccdtemp;
	}
	if (mask & CMPACK_PI_ORIGIN_CRDATE) {
		cmpack_free(file->hd.origin);
		file->hd.origin = cmpack_strdup(info->origin);
		file->hd.crtime = info->crtime;
	}
	if (mask & CMPACK_PI_PHOT_PARAMS) {
		memcpy(file->hd.range, info->range, 2*sizeof(double));
		file->hd.gain = info->gain;
		file->hd.rnoise = info->rnoise;
		file->hd.fwhm_exp = info->fwhm_exp;
		file->hd.fwhm_mean = info->fwhm_mean;
		file->hd.fwhm_err = info->fwhm_err;
		file->hd.threshold = info->threshold;
		memcpy(file->hd.sharpness, info->sharpness, 2*sizeof(double));
		memcpy(file->hd.roundness, info->roundness, 2*sizeof(double));
	}
	if (mask & CMPACK_PI_MATCH_PARAMS) {
		file->hd.matched = info->matched;
		file->hd.match_rstars = info->match_rstars;
		file->hd.match_istars = info->match_istars;
		file->hd.match_clip = info->match_clip;
		file->hd.match_mstars = info->match_mstars;
		memcpy(file->hd.offset, info->offset, 2*sizeof(double));
	}
	if (mask & CMPACK_PI_TRAFO) {
		file->hd.trafo = info->trafo;
	}
	if (mask & CMPACK_PI_OBJECT) {
		cmpack_free(file->hd.object.designation);
		file->hd.object.designation = cmpack_strdup(info->object.designation);
		file->hd.object.ra_valid = info->object.ra_valid;
		file->hd.object.ra = info->object.ra;
		file->hd.object.dec_valid = info->object.dec_valid;
		file->hd.object.dec = info->object.dec;
	}
	if (mask & CMPACK_PI_LOCATION) {
		cmpack_free(file->hd.location.designation);
		file->hd.location.designation = cmpack_strdup(info->location.designation);
		file->hd.location.lon_valid = info->location.lon_valid;
		file->hd.location.lon = info->location.lon;
		file->hd.location.lat_valid = info->location.lat_valid;
		file->hd.location.lat = info->location.lat;
	}
	file->changed = 1;
	return CMPACK_ERR_OK;
}

int cmpack_pht_get_info(CmpackPhtFile *file, unsigned mask, CmpackPhtInfo *info)
{
	if (mask & CMPACK_PI_JD) {
		info->jd = file->hd.jd;
	}
	if (mask & CMPACK_PI_FRAME_PARAMS) {
		info->width = file->hd.width;
		info->height = file->hd.height;
		info->jd = file->hd.jd;
		info->filter = file->hd.filter;
		info->exptime = file->hd.exptime;
		info->ccdtemp = file->hd.ccdtemp;
	}
	if (mask & CMPACK_PI_ORIGIN_CRDATE) {
		info->origin = file->hd.origin;
		info->crtime = file->hd.crtime;
	}
	if (mask & CMPACK_PI_PHOT_PARAMS) {
		memcpy(info->range, file->hd.range, 2*sizeof(double));
		info->gain = file->hd.gain;
		info->rnoise = file->hd.rnoise;
		info->fwhm_exp = file->hd.fwhm_exp;
		info->fwhm_mean = file->hd.fwhm_mean;
		info->fwhm_err = file->hd.fwhm_err;
		info->threshold = file->hd.threshold;
		memcpy(info->sharpness, file->hd.sharpness, 2*sizeof(double));
		memcpy(info->roundness, file->hd.roundness, 2*sizeof(double));
	}
	if (mask & CMPACK_PI_MATCH_PARAMS) {
		info->matched = file->hd.matched;
		info->match_rstars = file->hd.match_rstars;
		info->match_istars = file->hd.match_istars;
		info->match_clip = file->hd.match_clip;
		info->match_mstars = file->hd.match_mstars;
		memcpy(info->offset, file->hd.offset, 2*sizeof(double));
	}
	if (mask & CMPACK_PI_TRAFO) 
		info->trafo = file->hd.trafo;
	if (mask & CMPACK_PI_OBJECT) 
		info->object = file->hd.object;
	if (mask & CMPACK_PI_LOCATION)
		info->location = file->hd.location;
	return CMPACK_ERR_OK;
}

/********************   Table of apertures   ***********************/

static void apertures_init(CmpackApertures *tab, int size)
{
	if (tab->capacity < size) {
		tab->list = (CmpackPhtAperture*)cmpack_calloc(size, sizeof(CmpackPhtAperture));
		tab->capacity = size;
	}
	tab->count = 0;
}

static int aperture_add(CmpackApertures *tab, unsigned mask, const CmpackPhtAperture *data)
{
	int i = tab->count;
	if (tab->count>=tab->capacity) {
		tab->capacity += ALLOC_BY;
		tab->list = (CmpackPhtAperture*)cmpack_realloc(tab->list, tab->capacity*sizeof(CmpackPhtAperture));
	}
	memset(&tab->list[i], 0, sizeof(CmpackPhtAperture));
	tab->list[i].id = data->id;
	if (mask & CMPACK_PA_RADIUS)
		tab->list[i].radius = data->radius;
	tab->count++;
	return i;
}

static int aperture_find(CmpackApertures *tab, int aper_id)
{
	int i;

	for (i=0; i<tab->count; i++) {
		if (tab->list[i].id == aper_id)
			return i;
	}
	return -1;
}

/* Returns number of apertures */
int cmpack_pht_aperture_count(CmpackPhtFile *f)
{
	return f->ap.count;
}

/* Find aperture by identifier */
int cmpack_pht_find_aperture(CmpackPhtFile *f, int apid)
{
	if (apid>=0) {
		if (f->delayload && f->ap_loaded!=f->ap.count) {
			int res = load_apertures(f, f->ap.count-1);
			if (res!=0)
				return -1;
		}
		return aperture_find(&f->ap, apid);
	}
	return -1;
}

/* Add aperture to a file */
int cmpack_pht_add_aperture(CmpackPhtFile *f, unsigned mask, const CmpackPhtAperture *data)
{
	if (!f->readonly && data->id>=0 && aperture_find(&f->ap, data->id)<0) {
		int index = aperture_add(&f->ap, mask, data);
		if (index>=0)
			f->changed = 1;
		return index;
	}
	return -1;
}

/* Get aperture parameters */
int cmpack_pht_get_aperture(CmpackPhtFile *f, int index, unsigned mask, CmpackPhtAperture *info)
{
	CmpackPhtAperture *data;

	if (index<0 && index>=f->ap.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	if (f->delayload && f->ap_loaded<=index) {
		int res = load_apertures(f, index);
		if (res!=0)
			return res;
	}

	data = &f->ap.list[index];
	if (mask & CMPACK_PA_ID)
		info->id = data->id;
	if (mask & CMPACK_PA_RADIUS)
		info->radius = data->radius;
	return 0;
}

/* Set aperture radius */
int cmpack_pht_set_aperture(CmpackPhtFile *f, int index, unsigned mask, const CmpackPhtAperture *info)
{
	CmpackPhtAperture *data;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (index<0 && index>=f->ap.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	data = &f->ap.list[index];
	if (mask & CMPACK_PA_RADIUS)
		data->radius = info->radius;
	f->changed = 1;
	return 0;
}

/********************   Table of stars   ***********************/

static void objects_init(CmpackObjects *tab, int size)
{
	if (tab->capacity < size) {
		tab->list = (CmpackObject*)cmpack_calloc(size, sizeof(CmpackObject));
		tab->capacity = size;
	}
	tab->count = 0;
}

static int object_add(CmpackObjects *tab, unsigned mask, const CmpackPhtObject *obj, int ndata)
{
	int i = tab->count;
	if (tab->count>=tab->capacity) {
		tab->capacity += ALLOC_BY;
		tab->list = (CmpackObject*)cmpack_realloc(tab->list, tab->capacity*sizeof(CmpackObject));
	}
	memset(&tab->list[i], 0, sizeof(CmpackObject));
	tab->list[i].info.id = obj->id;
	tab->list[i].info.ref_id = -1;
	if (mask & CMPACK_PO_REF_ID)
		tab->list[i].info.ref_id = obj->ref_id;
	if (mask & CMPACK_PO_CENTER) {
		tab->list[i].info.x = obj->x;
		tab->list[i].info.y = obj->y;
	}
	if (mask & CMPACK_PO_SKY) {
		tab->list[i].info.skymed = obj->skymed;
		tab->list[i].info.skysig = obj->skysig;
	}
	if (mask & CMPACK_PO_FWHM) 
		tab->list[i].info.fwhm = obj->fwhm;
	if (ndata>0)
		data_init(&tab->list[i].data, ndata);
	tab->count++;
	return i;	
}

static void object_copy(CmpackObject *dst, const CmpackObject *src)
{
	dst->info = src->info;
	data_init(&dst->data, src->data.size);
	if (dst->data.size>0)
		memcpy(dst->data.items, src->data.items, dst->data.size*sizeof(CmpackMagData));
}

static int object_find(CmpackObjects *tab, int star_id)
{
	int i;

	for (i=0; i<tab->count; i++) {
		if (tab->list[i].info.id == star_id)
			return i;
	}
	return -1;
}

/* Returns number of stars */
int cmpack_pht_object_count(CmpackPhtFile *f)
{
	return f->st.count;
}

/* Find star by identifier */
int cmpack_pht_find_object(CmpackPhtFile *f, int id, int ref_id)
{
	int i;

	if (id>=0) {
		if (f->delayload && f->st_loaded!=f->st.count) {
			int res = load_stars(f, f->st.count-1);
			if (res!=0)
				return -1;
		}
		if (!ref_id) 
			return object_find(&f->st, id);
		else {
			for (i=0; i<f->st.count; i++) {
				if (f->st.list[i].info.ref_id == id)
					return i;
			}
		}
	}
	return -1;
}

/* Find aperture / create new aperture */
int cmpack_pht_add_object(CmpackPhtFile *f, unsigned mask, const CmpackPhtObject *info)
{
	if (!f->readonly && info->id>=0 && object_find(&f->st, info->id)<0) {
		int index = object_add(&f->st, mask, info, 0);
		if (index>=0)
			f->changed = 1;
		return index;
	}
	return -1;
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_set_object(CmpackPhtFile *f, int index, unsigned mask, const CmpackPhtObject *obj)
{
	CmpackPhtObject *data;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (index<0 && index>=f->st.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	data = &f->st.list[index].info;
	if (mask & CMPACK_PO_REF_ID) 
		data->ref_id = obj->ref_id;
	if (mask & CMPACK_PO_CENTER) {
		data->x = obj->x;
		data->y = obj->y;
	}
	if (mask & CMPACK_PO_SKY) {
		data->skymed = obj->skymed;
		data->skysig = obj->skysig;
	}
	if (mask & CMPACK_PO_FWHM) 
		data->fwhm = obj->fwhm;
	f->changed = 1;
	return 0;
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_get_object(CmpackPhtFile *f, int index, unsigned mask, CmpackPhtObject *obj)
{
	CmpackPhtObject *data;

	if (index<0 || index>=f->st.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	if (f->delayload && f->st_loaded<=index) {
		int res = load_stars(f, index);
		if (res!=0)
			return res;
	}

	data = &f->st.list[index].info;
	if (mask & CMPACK_PO_ID)
		obj->id = data->id;
	if (mask & CMPACK_PO_REF_ID) 
		obj->ref_id = data->ref_id;
	if (mask & CMPACK_PO_CENTER) {
		obj->x = data->x;
		obj->y = data->y;
	}
	if (mask & CMPACK_PO_SKY) {
		obj->skymed = data->skymed;
		obj->skysig = data->skysig;
	}
	if (mask & CMPACK_PO_FWHM) 
		obj->fwhm = data->fwhm;
	return 0;
}

/********************   Table of measurements   ***********************/

/* Search magnitude */
static void data_init(CmpackMagList *tab, int napertures)
{
	cmpack_free(tab->items);
	if (napertures>0) {
		tab->items = cmpack_calloc(napertures, sizeof(CmpackMagData));
		tab->size = napertures;
	} else {
		tab->items = NULL;
		tab->size = 0;
	}
}

/* Change size of table of measurement */
static void data_setsize(CmpackMagList *tab, int napertures)
{
	if (tab->size!=napertures) {
		if (napertures>0) {
			tab->items = cmpack_realloc(tab->items, napertures*sizeof(CmpackMagData));
			if (tab->size < napertures)
				memset(tab->items + tab->size, 0, (napertures-tab->size)*sizeof(CmpackPhtData));
			tab->size = napertures;
		} else {
			cmpack_free(tab->items);
			tab->items = NULL;
			tab->size = 0;
		}
	}
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_set_data(CmpackPhtFile *f, int st_index, int ap_index, const CmpackPhtData *info)
{
	return cmpack_pht_set_data_with_code(f, st_index, ap_index, info, 0);
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_set_data_with_code(CmpackPhtFile *f, int st_index, int ap_index, const CmpackPhtData *info, 
	CmpackError code)
{
	CmpackMagList *tab;
	CmpackMagData *data;

	if (f->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (st_index<0 || st_index>=f->st.count || ap_index<0 || ap_index>=f->ap.count)
		return CMPACK_ERR_OUT_OF_RANGE;

	tab = &f->st.list[st_index].data;
	if (ap_index>=tab->size)
		data_setsize(tab, f->ap.count);

	data = tab->items + ap_index;
	data->valid = info->mag_valid;
	data->mag = info->magnitude;
	data->err = info->mag_error;
	data->code = code;
	f->changed = 1;
	return 0;
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_get_data(CmpackPhtFile *f, int st_index, int ap_index, CmpackPhtData *out)
{
	CmpackMagList *tab;

	if (st_index<0 || st_index>=f->st.count || ap_index<0 || ap_index>=f->ap.count) 
		return CMPACK_ERR_OUT_OF_RANGE;

	if (f->delayload && f->dt_loaded<=st_index) {
		int res = load_data(f, st_index);
		if (res!=0)
			return res;
	}

	tab = &f->st.list[st_index].data;
	if (ap_index>=tab->size || !tab->items[ap_index].valid) {
		if (out) {
			out->mag_valid = 0;
			out->magnitude = INVALID_MAG;
			out->mag_error = INVALID_MAGERR;
		}
	} else {
		CmpackMagData *data = tab->items + ap_index;
		if (out) {
			out->mag_valid = data->valid;
			out->magnitude = data->mag;
			out->mag_error = data->err;
		}
	}
	return CMPACK_ERR_OK;
}

/* Set magnitude for active star and active aperture */
int cmpack_pht_get_data_with_code(CmpackPhtFile *f, int st_index, int ap_index, CmpackPhtData *out, CmpackError *code)
{
	CmpackMagList *tab;

	if (st_index<0 || st_index>=f->st.count || ap_index<0 || ap_index>=f->ap.count) 
		return CMPACK_ERR_OUT_OF_RANGE;

	if (f->delayload && f->dt_loaded<=st_index) {
		int res = load_data(f, st_index);
		if (res!=0)
			return res;
	}

	tab = &f->st.list[st_index].data;
	if (ap_index>=tab->size) {
		if (out) {
			out->mag_valid = 0;
			out->magnitude = INVALID_MAG;
			out->mag_error = INVALID_MAGERR;
		}
		if (code) 
			*code = CMPACK_ERR_OK;
	} else {
		CmpackMagData *data = tab->items + ap_index;
		if (out) {
			out->mag_valid = data->valid;
			out->magnitude = data->mag;
			out->mag_error = data->err;
		}
		if (code) 
			*code = data->code;
	}
	return CMPACK_ERR_OK;
}

int cmpack_pht_get_code(CmpackPhtFile *f, int st_index, int ap_index, CmpackError *code)
{
	CmpackMagList *tab;

	if (st_index<0 || st_index>=f->st.count || ap_index<0 || ap_index>=f->ap.count) 
		return CMPACK_ERR_OUT_OF_RANGE;

	if (f->delayload && f->dt_loaded<=st_index) {
		int res = load_data(f, st_index);
		if (res!=0)
			return res;
	}

	tab = &f->st.list[st_index].data;
	if (ap_index>=tab->size) {
		if (code)
			*code = CMPACK_ERR_OK;
	} else {
		CmpackMagData *data = tab->items + ap_index;
		if (code) 
			*code = data->code;
	}
	return CMPACK_ERR_OK;
}

/* Update World Coordinate System (WCS) data in the file */
int cmpack_pht_set_wcs(CmpackPhtFile *fs, const CmpackWcs *wcs)
{
	if (fs->readonly)
		return CMPACK_ERR_READ_ONLY;
	if (fs->wcs) {
		cmpack_wcs_destroy(fs->wcs);
		fs->wcs = NULL;
	}
	if (wcs)
		fs->wcs = cmpack_wcs_copy(wcs);
	return CMPACK_ERR_OK;
}

/* Read World Coordinate System (WCS) data from the file */
int cmpack_pht_get_wcs(CmpackPhtFile *fs, CmpackWcs **wcs)
{
	if (fs->delayload) {
		int res = load_wcs(fs);
		if (res!=0)
			return res;
	}
	if (fs->wcs) {
		*wcs = fs->wcs;
		return CMPACK_ERR_OK;
	} else {
		*wcs = NULL;
		return CMPACK_ERR_UNDEF_VALUE;
	}
}

/* Dump table of objects to a file */
int cmpack_pht_dump(CmpackPhtFile *fs, CmpackTable **ptable, int aperture, CmpackConsole *con, unsigned flags)
{
	int		i, pos_column, aper_valid, id_column, sky_column, fwhm_column, mag_column;
	char	msg[MAXLINE];
	CmpackTable *table;

	*ptable = NULL;
	
	pos_column = id_column = sky_column = fwhm_column = mag_column = -1;

	load_stars(fs, fs->st.count-1);

	aper_valid = aperture>=0 && aperture<fs->ap.count;
	if (!aper_valid && fs->ap.count>0) {
		aperture = 0;
		aper_valid = 1;
	}
	if (aper_valid) {
		load_apertures(fs, aperture);
		load_data(fs, fs->st.count-1);
	}

	/* Create table */
	table = cmpack_tab_init(CMPACK_TABLE_UNSPECIFIED);

	/* Columns */
	id_column = cmpack_tab_add_column_int(table, "OBJ_ID", 0, INT_MAX, -1);
	pos_column = cmpack_tab_add_column_dbl(table, "CENTER_X", POS_PRECISION, 0, INT_MAX, INVALID_XY);
	cmpack_tab_add_column_dbl(table, "CENTER_Y", POS_PRECISION, 0, INT_MAX, INVALID_XY);
	sky_column = cmpack_tab_add_column_dbl(table, "SKY", SKY_PRECISION, -999.0, 1e99, INVALID_SKY);
	fwhm_column = cmpack_tab_add_column_dbl(table, "FWHM", 2, 0.0, 1e99, INVALID_FWHM);
	if (aper_valid) {
		mag_column = cmpack_tab_add_column_dbl(table, "MAG", MAG_PRECISION, -99.0, 99.0, INVALID_MAG);
		cmpack_tab_add_column_dbl(table, "s1", MAG_PRECISION, 0.0, 9.0, INVALID_MAGERR);
	}

	/* Table header */
	if (fs->hd.width>0 && fs->hd.height>0) {
		cmpack_tab_pkyi(table, "Width", fs->hd.width);
		cmpack_tab_pkyi(table, "Height", fs->hd.height>0);
	}
	if (fs->hd.filter && *fs->hd.filter!='\0')
		cmpack_tab_pkys(table, "Filter", fs->hd.filter);
	if (aper_valid)
		cmpack_tab_pkyi(table, "Aperture", fs->ap.list[aperture].id);
	
	/* Process frames */
	for (i=0; i<fs->st.count; i++) {
		CmpackObject *obj = fs->st.list+i;
		CmpackPhtData data;

		cmpack_tab_append(table);
		if (id_column>=0) 
			cmpack_tab_ptdi(table, id_column, obj->info.id);
		if (pos_column>=0) {
			cmpack_tab_ptdd(table, pos_column, obj->info.x);
			cmpack_tab_ptdd(table, pos_column+1, obj->info.y);
		}
		if (sky_column>=0 && obj->info.skymed>0) 
			cmpack_tab_ptdd(table, sky_column, obj->info.skymed);
		if (fwhm_column>=0 && obj->info.fwhm>0) 
			cmpack_tab_ptdd(table, fwhm_column, obj->info.fwhm);

		if (aper_valid && mag_column>=0 && cmpack_pht_get_data(fs, i, aperture, &data)==0 && data.mag_valid) {
			cmpack_tab_ptdd(table, mag_column, data.magnitude);
			if (data.mag_error>0)
				cmpack_tab_ptdd(table, mag_column+1, data.mag_error);
		}

		/* Debug printout */
		if (is_debug(con)) {

			/* Object properties */
			sprintf(msg, "OBJECT-ID: %d", obj->info.id);
			printout(con, 1, msg);
			if (pos_column>=0) {
				sprintf(msg, "CENTER: %.*f, %.*f pxl", POS_PRECISION, obj->info.x, POS_PRECISION, obj->info.y); 
				printout(con, 1, msg);
			}
			if (sky_column>=0) {
				sprintf(msg, "SKY: %.*f +- %.*f ADU", SKY_PRECISION, obj->info.skymed, SKY_PRECISION, obj->info.skysig); 
				printout(con, 1, msg);
			}
			if (fwhm_column>=0) {
				sprintf(msg, "FWHM: %.*f pxl", FWHM_PRECISION, obj->info.fwhm); 
				printout(con, 1, msg);
			}

			/* Aperture data */
			if (aper_valid && cmpack_pht_get_data(fs, i, aperture, &data)==0 && data.mag_valid) {
				sprintf(msg, "APERTURE: #%d (%.2f pxl)", fs->ap.list[aperture].id, fs->ap.list[aperture].radius);
				printout(con, 1, msg);
				if (mag_column>=0) {
					sprintf(msg, "MAG: %.*f +- %.*f mag", MAG_PRECISION, data.magnitude, MAG_PRECISION, data.mag_error); 
					printout(con, 1, msg);
				}
			}
		}
	}

	*ptable = table;
	return CMPACK_ERR_OK;
}
