#define NFKEYS 12
#define NSTDKEYS 10
#define NAXES 3
#define SKIP 10
int icut(float flux, int low, int high)
{
int iflux;
if( flux < low )
iflux = low;
else if( flux > high )
iflux = high;
else
iflux = rintf(flux);
return(iflux);
}
float Gamma(float r)
{
if( r < 0.00304f )
return 12.92f*r;
else
return 1.055f*powf(r,0.4166667f) - 0.055f;
}
// http://en.wikipedia.org/wiki/CIELUV_color_space
void XYZ_Luv(float X, float Y, float Z, float Yn, float xw, float yw,
float *L, float *u, float *v)
{
float s,u1,v1,yy,t;
s = X + 15.0f*Y + 3.0f*Z;
if( s > 0.0f ) {
u1 = 4.0f*X/s;
v1 = 9.0f*Y/s;
}
else {
u1 = 0.0f;
v1 = 0.0f;
}
yy = Y/Yn;
if( yy > 0.0088565f /* = powf(6.0/29.0,3)*/ )
*L = 116.0f*cbrtf(yy) - 16.0f;
else
*L = 903.30f*yy; /* = pow(26.0/3.0,3) */
t = 13.0f*(*L);
*u = t*(u1 - xw);
*v = t*(v1 - yw);
}
void Luv_XYZ(float L, float u, float v, float Yn, float xw, float yw,
float *X, float *Y, float *Z)
{
float u1,v1,s,t,w;
s = 13.0f*L;
if( s > 0.0f ) {
u1 = u/s + xw;
v1 = v/s + yw;
}
else {
u1 = xw;
v1 = yw;
}
if( L <= 8.0f )
*Y = L*0.0011071f*Yn; /* = powf(3.0/29.0,3) */
else {
w = (L + 16.0f)/116.0f;
*Y = w*w*w*Yn;
}
t = *Y/(4.0f*v1);
*X = t*(9.0f*u1);
*Z = t*(12.0f - 3.0f*u1 - 20.0f*v1);
}
void XYZ_sRGB(float X, float Y, float Z, float *R, float *G, float *B)
{
/* correction to D65 colour temperature */
X = X*0.950456f;
Z = Z*1.088754f;
/* transform to RGB and apply gamma function */
*R = Gamma( 3.2410f*X - 1.5374f*Y - 0.4986f*Z);
*G = Gamma(-0.9692f*X + 1.8760f*Y + 0.0416f*Z);
*B = Gamma( 0.0556f*X - 0.2040f*Y + 1.0570f*Z);
}
void XYZ_AdobeRGB(float X, float Y, float Z, float *R, float *G,float *B)
{
const float q = 1.0f/2.19921875f;
/* correction to D65 colour temperature */
X = X*0.950456f;
Z = Z*1.088754f;
/* transform to RGB and apply gamma function */
*R = powf( 2.04159f*X - 0.56501f*Y - 0.34473f*Z,q);
*G = powf(-0.96924f*X + 1.87597f*Y + 0.04156f*Z,q);
*B = powf( 0.01344f*X - 0.11836f*Y + 1.01517f*Z,q);
}
float fscale(float flux, int ctype, float thresh, float slope,
float f0, float zero)
{
float f;
float r;
r = (flux - thresh)*slope;
switch ( ctype ) {
case 1:
f = f0*asinhf(r) + zero;
break;
case 2:
f = (r > -0.5 ? f0*logf(2.0f*r + 1.0f) : 0.0f) + zero;
break;
case 3:
f = f0*Gamma(r) + zero;
break;
case 4:
r = 2.0f*(r - 0.5f);
f = f0*0.5f*(erff(r) + 1.0f) + zero;
break;
case 5:
f = (r > 0.0f ? f0*sqrtf(r) : 0.0f) + zero ;
break;
case 6:
f = f0*r*r + zero;
break;
case 7:
// f = f0*(2.0f/(1.0f + expf(-r)) - 1.0f) + zero;
f = f0/(1.0f + expf(-6.0f*(r-0.5f))) + zero;
break;
case 8:
f = f0*atanf(r) + zero;
break;
case 9:
f = f0*r/(1.0f + r) + zero;
break;
default:
f = r;
}
return(f);
}
float Scotopic(float X, float Y, float Z)
{
return 0.36169f*Z + 1.18214f*Y - 0.80498f*X;
}
float farray2d(float *pic, int w, int h, int x, int y, int d)
{
int xdim,ydim,ndim;
int x1 = x, y1 = y, x2 = x + d, y2 = y + d;
int i, j, l, n;
double s, d2;
assert(pic != NULL);
assert(d >= 1);
xdim = w;
ydim = h;
if( d == 1 ) {
assert((0 <= x && x < xdim) && (0 <= y && y < ydim));
return(pic[y*xdim + x]);
}
else if( d > 1 ) {
d2 = d*d;
if( (0 <= x1 && x1 < xdim) && (0 <= y1 && y1 < ydim) &&
(0 <= x2 && x2 < xdim) && (0 <= y2 && y2 < ydim)) {
s = 0.0;
for(j = y1; j < y2; j++) {
float *nrow = pic + j*xdim;
for(i = x1; i < x2; i++)
s = s + *(nrow + i);
}
return(s/d2);
}
else {
s = 0.0;
ndim = w*h;
n = 0;
for(j = y1; j < y2; j++) {
for(i = x1; i < x2; i++) {
l = i+j*xdim;
if( 0 <= l && l < ndim ) {
s = s + pic[l];
n++;
}
}
}
return( n > 0 ? s/n : 0);
}
}
return(0.0);
}
int fitspng(char *fitsname, char *png, int bit_depth,
float udev,float vdev, int scale,
float f0, float med, float mad, float sthresh, float swidth,
int ctype, float satur, float xw, float yw, int dcspace,
float zero, int verb, int est, int scotop)
{
char *fkeys[NFKEYS] = {"OBJECT", "OBSERVER", "FILTER", "DATE-OBS",
"CAMTYPE", "EXPTIME", "SITE", "TEMPERAT",
"XFACTOR", "YFACTOR", "TELESCOP", "CBALANCE"};
char *stdkeys[NSTDKEYS] = {"Title","Author","Description","Copyright",
"Creation Time","Software","Disclaimer",
"Warning","Source","Comment"};
char *stdvalues[NSTDKEYS];
char *fval[NFKEYS];
long naxes[NAXES];
float *d;
/* FITS input */
fitsfile *f;
int naxis,bitpix,status,nullval=0,fpixel=1;
int i,j,k,m,n,lim;
char line[FLEN_CARD];
/* data scaling */
float r,thresh, slope;
/* png */
png_uint_32 height, width, bytes_per_pixel, color_bytes;
png_byte *image;
FILE *fp;
png_structp png_ptr;
png_infop info_ptr;
png_text text_ptr[NSTDKEYS];
char buf[NFKEYS*FLEN_CARD + 100];
png_bytep *row_pointers;
char *tm[6], *c, *c0;
/* intensity scaling */
int ii,jj;
float flux, X, Y, Z, R, G, B, L, u, v;
int iflux;
float *pic;
/* display colour-space */
void (*XYZ_RGB)(float,float,float,float *,float *,float *);
/* --------------------------------------------------------------------*/
/* Part: Initilization */
status = 0;
if( dcspace == 0 )
XYZ_RGB = &XYZ_sRGB;
else if( dcspace == 1 )
XYZ_RGB = &XYZ_AdobeRGB;
else
XYZ_RGB = &XYZ_sRGB;
image = NULL;
pic = NULL;
for( i = 0; i < NFKEYS; i++)
fval[i] = NULL;
for( i = 0; i < NSTDKEYS; i++)
stdvalues[i] = NULL;
for( i = 0; i < 6; i++ )
tm[i] = NULL;
if( verb )
fprintf(stderr,"Finishing initialisation..\n");
/* --------------------------------------------------------------------*/
/* Part: Load input FITS */
/* check whatever filename has extension - usefull only for selecting
of single bands in colour images
*/
fits_open_image(&f,fitsname, READONLY, &status);
if( status )
goto finish;
fits_get_img_type(f,&bitpix,&status);
fits_get_img_dim(f,&naxis,&status);
if( status )
goto finish;
if( !(naxis == 2 || naxis == 3)) {
fprintf(stderr,"Crash: Only grey or colour FITS files are supported.\n");
goto finish;
}
if( naxis == 3 ) {
fits_read_key(f,TSTRING,"CSPACE",line,NULL,&status);
if( status || strstr(line,"XYZ") == NULL ) {
fprintf(stderr,"Crash: Only CIE 1931 XYZ colour-space is supported yet.\n");
goto finish;
}
}
/* keywords */
for(i = 0; i < NFKEYS; i++) {
fits_read_key(f,TSTRING,fkeys[i],line,NULL,&status);
if( status == 0 ) {
if( fval[i] == NULL )
fval[i] = strdup(line);
else {
if( verb )
fprintf(stderr,"Ignoring keywords: %s=%s\n",fkeys[i],fval[i]);
}
}
else
status = 0;
}
fits_get_img_size(f,NAXES,naxes,&status);
if( status )
goto finish;
n = 1;
for( i = 0; i < naxis; i++)
n = n*naxes[i];
pic = malloc(n*sizeof(float));
if( pic == NULL ) {
fprintf(stderr,"Crash: There is no room for an input image.\n");
goto finish;
}
fits_read_img(f,TFLOAT,fpixel,n,&nullval,pic,&i,&status);
if( status )
goto finish;
fits_close_file(f, &status);
thresh = 0.0;
slope = 1.0;
if( verb )
fprintf(stderr,"Finishing FITS load..\n");
/* --------------------------------------------------------------------*/
/* Part: Estimation of intensity parameters */
if( abs(bitpix) > 8 && bit_depth < 16 && est ) {
n = (naxes[0]*naxes[1])/(SKIP*SKIP);
if( (d = malloc((n+1)*sizeof(float))) ) {
/* scale by Y component for colour images */
k = naxis == 2 ? 0 : n;
for( i = 0, j = 0; j < n; i = i + SKIP, j++)
d[j] = pic[k+i];
med = qmed(n,d,n/2+1);
/* only median+ (right tail) part of histogram is examined */
int imax = naxes[0]*naxes[1] - SKIP - 1;
for( i = 0, j = 0; j < n && i < imax; i = i + SKIP) {
r = pic[k+i] - med;
if( r > 0.0 )
d[j++] = r;
}
mad = qmed(j,d,j/2+1);
if( verb )
fprintf(stderr,"med=%f mad=%f n=%d\n", med, mad, n);
if( fabs(mad) < 1.0/pow((double) 2.0,(double) bit_depth) )
mad = 1.0;
free(d);
}
else
fprintf(stderr,"There is no room for a scaling buffer.\n");
if( verb )
fprintf(stderr,"Finishing of estimate of scale parameters..\n");
}
/* --------------------------------------------------------------------*/
/* Part: Intensity conversion */
/* fill an output array */
height = naxes[1]/scale;
width = naxes[0]/scale;
bytes_per_pixel = bit_depth/8;
color_bytes = naxis == 2 ? 1 : 3;
n = width*height;
if( height < 1 || width < 1 ) {
fprintf(stderr,"Size of scaled image is zero.\n");
goto finish;
}
/* setup bit limit */
lim = 1;
for( i = 0; i < bit_depth; i++)
lim = 2*lim;
lim = lim - 1;
/* prepare flux scaling */
if( ! est ) {
thresh = med;
slope = 1.0/mad;
}
else {
if( naxis == 2 ) {
thresh = med - udev*mad;
slope = 1.0/(mad * vdev);
}
else if( naxis == 3 ) {
thresh = 0.0;
slope = 1.0/(mad * vdev);
}
}
if( verb )
fprintf(stderr,"thresh=%f slope=%f 1/slope=%f\n", thresh, slope, 1.0/slope);
if( (image = malloc(height*width*bytes_per_pixel*color_bytes)) == NULL ) {
fprintf(stderr,"Crash: There is no room for an output image.\n");
goto finish;
}
m = 0;
for( j = height-1; j >= 0; j-- ) {
jj = j*scale;
for( i = 0; i < width; i++ ) {
ii = i*scale;
/* 16-bit */
if( bit_depth == 16 ) {
for( k = 0; k < color_bytes; k++) {
flux = farray2d(pic+k*naxes[0]*naxes[1],naxes[0],naxes[1],ii,jj,scale);
n = icut(flux,0,lim);
// printf("%f %d %d %d\n",flux,n,n/256,n%256);
image[m] = n / 256;
image[m+1] = /*n / 256*/ 0;
m = m + bytes_per_pixel;
}
}
/* 8-bit */
else if( bit_depth == 8 ) {
/* greyscale */
if( naxis == 2 ) {
flux = farray2d(pic,naxes[0],naxes[1],ii,jj,scale);
flux = fscale(flux,ctype,thresh,slope,f0,zero);
image[m] = icut(255.0*Gamma(flux),0,lim);
m = m + bytes_per_pixel;
}
else if( naxis == 3 ) {
/* colour */
Z = farray2d(pic,naxes[0],naxes[1],ii,jj,scale);
Y = farray2d(pic+naxes[0]*naxes[1],naxes[0],naxes[1],ii,jj,scale);
X = farray2d(pic+2*naxes[0]*naxes[1],naxes[0],naxes[1],ii,jj,scale);
X = X > thresh ? X - thresh : 0.0;
Y = Y > thresh ? Y - thresh : 0.0;
Z = Z > thresh ? Z - thresh : 0.0;
XYZ_Luv(X,Y,Z,1.0f/slope,xw,yw,&L,&u,&v);
/* apply tone curve */
L = 100.0f*fscale(L/100.0f,ctype,0.0,1.0,f0,zero);
/* colour saturation */
float hue = atan2f(v,u);
float chroma = satur*sqrtf(v*v + u*u);
u = chroma*cosf(hue);
v = chroma*sinf(hue);
/* convert back to XYZ */
Luv_XYZ(L,u,v,1.0,xw,yw,&X,&Y,&Z);
/* apply night vision simulation */
if( scotop ) {
float w = 1.0f/(1.0f + expf(-2.5f*(L - sthresh)/swidth));
float s = Scotopic(X,Y,Z);
float w1 = 1.0f - w;
X = w*X + w1*s;
Y = w*Y + w1*s;
Z = w*Z + w1*s;
}
/* convert to output colour-space */
(*XYZ_RGB)(X,Y,Z,&R,&G,&B);
image[m] = icut(255.0f*R,0,lim);
m = m + bytes_per_pixel;
image[m] = icut(255.0f*G,0,lim);;
m = m + bytes_per_pixel;
image[m] = icut(255.0f*B,0,lim);;
m = m + bytes_per_pixel;
}
}
}
}
/* intensity table */
if( verb ) {
FILE *itt;
if( (itt = fopen("itt.dat","w")) ) {
for(i = 1; i < lim; i++) {
r = i;
if( r < 0.0 ) r = 1e-5;
switch ( ctype ) {
case 1:
flux = f0*asinhf(r/2.0) + zero;
break;
case 2:
flux = f0*logf(r) + zero;
break;
case 3:
flux = f0*erff(r) + zero;
break;
case 4:
flux = f0*sqrtf(r) + zero;
break;
default:
flux = r + zero;
}
if( flux < 0.0 )
iflux = 0;
else if( flux > lim )
iflux = lim;
else
iflux = rintf(flux);
fprintf(itt,"%d %d\n",i,iflux);
}
fclose(itt);
}
}
if( verb )
fprintf(stderr,"Finishing intensity transformation..\n");
/* --------------------------------------------------------------------*/
/* Part: Save to PNG */
if( png )
fp = fopen(png, "wb");
else
fp = stdout;
if (!fp) {
fprintf(stderr,"Crash: Initialising of an output file failed.\n");
goto finish;
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
if (!png_ptr) {
fclose(fp);
goto finish;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
fclose(fp);
png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
goto finish;
}
png_init_io(png_ptr, fp);
png_set_write_status_fn(png_ptr, NULL);
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
color_bytes == 1 ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
/*
png_set_gAMA(png_ptr, info_ptr, gamma);
*/
/*
png_color_16 back;
back.gray = 0;
png_set_bKGD(png_ptr, info_ptr, &back);
png_set_pHYs(png_ptr, info_ptr, res_x, res_y,unit_type);
png_set_sCAL(png_ptr, info_ptr, unit, width, height)
*/
if( fval[0] )
stdvalues[0] = strdup(fval[0]);
else
stdvalues[0] = strdup("");
if( fval[1] )
stdvalues[1] = strdup(fval[1]);
else
stdvalues[1] = strdup("");
/* numerical constant in declaraton of buf must be greater than max. length
of sum of following string(s) */
strcpy(buf,"An image");
if( fval[0] )
sprintf(buf+strlen(buf)," of the %s",fval[0]);
if( fval[6] )
sprintf(buf+strlen(buf)," taken at %s observatory",fval[6]);
if( fval[1] )
sprintf(buf+strlen(buf)," by %s",fval[1]);
if( fval[4] )
sprintf(buf+strlen(buf)," by the %s instrument",fval[4]);
if( fval[10] )
sprintf(buf+strlen(buf)," of the %s telescope",fval[10]);
if( fval[3] )
sprintf(buf+strlen(buf)," at %s UT (start time)",fval[3]);
if( fval[5] )
sprintf(buf+strlen(buf)," of exposure %s sec",fval[5]);
if( fval[2] && color_bytes == 1 )
sprintf(buf+strlen(buf)," with the %s filter",fval[2]);
strcat(buf,".");
if( fval[7] )
sprintf(buf+strlen(buf)," The instrument temperature: %s.",fval[7]);
if( fval[8] )
sprintf(buf+strlen(buf)," XBinnig: %s.",fval[8]);
if( fval[9] )
sprintf(buf+strlen(buf)," YBinnig: %s.",fval[9]);
stdvalues[2] = strdup(buf);
stdvalues[3] = strdup("");
/* decode time (round fractional seconds and adds timezone),
Standard FITS headers must contains DATE-OBS as
YYYY-MM-DDTHH:MM:SSS.SSS, however the rule is violated
by many software writing obsolete YYYY-MM-DD keyword.
*/
i = 0;
if( fval[3] ) {
for( i = 0, c = fval[3], c0 = fval[3]; *c != '\0'; c++) {
if( *c == '-' || *c == 'T' || *c == ':' || *c == ' ' || *(c+1) == '\0') {
n = c - c0;
if( *(c+1) == '\0' )
n++;
tm[i++] = strndup(c0,n);
c0 = c + 1;
}
}
/* This loop is my own bugy implementation of bugy strtok
(https://sourceware.org/bugzilla/show_bug.cgi?id=16640) */
}
if( i == 6 ) {
if( sscanf(tm[5],"%f",&r) == 1 )
i = rint(r);
else
i = 0;
sprintf(buf,"%s-%s-%s %s:%s:%02d GMT",tm[0],tm[1],tm[2],tm[3],tm[4],i);
stdvalues[4] = strdup(buf);
} else if( i == 3 ) {
sprintf(buf,"%s-%s-%s",tm[0],tm[1],tm[2]);
stdvalues[4] = strdup(buf);
}
else
stdvalues[4] = strdup("");
stdvalues[5] = strdup("Created by FITSPNG.");
stdvalues[6] = strdup("");
stdvalues[7] = strdup("");
strcpy(buf,"");
if( fval[4] )
strcat(buf,fval[4]);
if( fval[10] ) {
strcat(buf,", ");
strcat(buf,fval[10]);
}
stdvalues[8] = strdup(buf);
strcpy(buf,"Converted from the original FITS image:");
sprintf(buf+strlen(buf)," %s",fitsname);
stdvalues[9] = strdup(buf);
for(i = 0; i < NSTDKEYS; i++ ) {
text_ptr[i].key = stdkeys[i];
text_ptr[i].text = stdvalues[i];
text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
}
png_set_text(png_ptr, info_ptr, text_ptr, NSTDKEYS);
png_write_info(png_ptr, info_ptr);
#ifdef WORDS_BIGENDIAN
;
#else
if( bit_depth == 16 )
png_set_swap(png_ptr);
#endif
if( (row_pointers = malloc(height*sizeof(row_pointers))) == NULL ) {
fprintf(stderr,"There is no room for all rows of image.\n");
png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
goto finish;
}
for (i = 0; i < height; i++)
row_pointers[i] = image + i*width*bytes_per_pixel*color_bytes;
png_write_image(png_ptr,row_pointers);
png_write_end(png_ptr, NULL);
png_free(png_ptr,row_pointers);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
if( verb )
fprintf(stderr,"Finishing save of PNG.\n");
finish:
fits_report_error(stderr, status);
for( i = 0; i < NFKEYS; i++)
free(fval[i]);
for( i = 0; i < NSTDKEYS; i++)
free(stdvalues[i]);
for( i = 0; i < 6; i++ )
free(tm[i]);
free(image);
free(pic);
return(status);
}
fitspng-1.3/fitspng.h 0000644 0040317 0001750 00000001366 13162245162 013654 0 ustar hroch hroch /*
FITSPNG
Copyright (C) 2006-2017 Filip Hroch, Masaryk University, Brno, CZ
Fitspng 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 3 of the License, or
(at your option) any later version.
Fitspng 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 Fitspng. If not, see .
*/
/* qmed.c */
extern float qmed(int, float *, int);
fitspng-1.3/fitspng.html 0000644 0040317 0001750 00000033155 13162245162 014372 0 ustar hroch hroch
Fitspng
Fitspng
Fitspng is an utility intended to convert of images in astronomical FITS
to PNG format.
Introduction
FITS format is a general purpose
astronomical format storing images in high dynamical range
(naturally including
HDR).
The primary goal of fitspng is to transform of the high dynamic range images
to a limited numerical range of PNG
format. The transformation uses global tone mapping technique by a set of tone functions.
Their scaling parameters can by estimated by a machine or provided by user.
Tone mapping
Fitspng uses global tone mapping technique to convert of a wide dynamical
range (which can be practically infinite) to limited range of modern
displaying devices.
The usage of the tone mapping is crucial to get a visually satisfactory
picture results.
The tone mapping is generally described by following transformation:
PNG value = f0 * Func[ (FITS value - black) * sensitivity ] + zero
where Func is a selected function (linear, asinh, ..) and f0, zero are
profile scaling parameters (f0 is defaulted to 1 and zero to 0).
FITS values are pre-scaled by parameters black and sensitivity
to an appropriate domain of Func. The black sets a level of
FITS values corresponding to the black on output. The sensitivity parameter
sets an artificial (fake) sensitivity of detector. The sensitivity
dims or highlights of the image like a gain tuning of an electric amplifier in detector.
The sensitivity is a reciprocal to ISO value used by photographers.
Both parameters black and sensitivity can be leave unspecified.
In this case, ones are estimated by the
included machine algorithm with help of statistical parameters: median and mean of absolute
deviations (mad). Both parameters are describing of a histogram of data:
median marks centre
of histogram and mad scales width of histogram.
There are available two modes:
- The linear scaling is directly given by the parameters.
- If relative scaling mode is enabled, the parameters are estimated as:
black = median - u*mad
sensitivity = 1/(v*mad)
Both median and mad parameters are determined using of a ten node
pixel grid over the image. Mad is mean of positive deviations from median.
The default is u = 0.5, v = 15.
If any pixel is out of range 0 to 255 as result of mapping, the value is replaced
by 0 or 255 respectively.
Colour processing
If colour FITS (as defined by Munipack) is converted, one can manipulate
with colours by specifying of saturation in *L*u*v colour-space.
Moreover, the human eye has different colour sensitivity in high and low light
conditions. The effect can be simulated by greying of dim parts of images
application of PurkynÄ› effect.
Output colour spaces
Fitspng transforms images to sRGB or AdobeRGB colour-space. The transformation
is started from FITS provided CIE 1931 XYZ colour-space
to CIE *L*u*v.
The luminosity
channel is scaled according of parameters and tone functions and
finally transformed to sRGB
or AdobeRGB
(pdf). Note that both RGB
conversions uses gamma function to scale output intensities of RGBs.
In the case of grey scale image, values are equivalent to *L component whilst
*u,*v are identically zero. Therefore just a tone mapping
and the gamma correction is applied to output images.
Image resize
An output image can be scaled-down by an integer factor. Every pixel
of the scaled image is computed as the arithmetical mean of a square
with side given by this factor.
The photometric information is preserved while a noise is suppressed
during the re-scaling. This sufficient fast method provides high quality images.
Exif information
Any exposure parameters of images are stored in FITS header which is practically
analogy of an EXIF information stored as a text strings in PNG so
basic header information from FITS files is extracted during
the conversion.
Command line options
- -f [linear|asinh|log|gamma|normal|sqrt|sqr|logistic|atan]
-
Global tone mapping function profile: linear, asinh, log,
gamma (as in sRGB), normal (Gauss cumulative distribution function),
square root, square, logistic (emulating classical photography
sensitivity) and atan.
- -f0 f0
-
Scale of output profile: f0*Func(...) + z.
- -fz z
-
Zero of output profile: f0*Func(...) + z.
- -fr u,v
-
Relative scaling mode. u,v multipliers for median and mad.
- -fl t,s
-
Absolute scaling mode. t,s are directly used for determining of mapping parameters.
The setup completely disables internal parameter estimation.
- -fs x
-
The colour saturation is multiplied by the given factor (for colour FITS only).
- -fw x,y
-
Coordinates of the
white point.
- -fn st,ss
-
When used, switch-on mode which emulates humans night vision. It is useful
only for colour FITS.
- -cs [sRGB|AdobeRGB]
-
Select the colour-space of output image.
- -s s
-
Scale the size of image down by the specified factor s as a (non-zero) positive
integer number. If the s factor is greater of one, any output pixel
is constructed as the arithmetical mean of s*s input pixels.
- -o name
-
Output image name. Default value is
fitspng.png
.
- -B [8|16]
-
8 bites per pixel of colour (grey) depth of output. This is default.
16 bites per pixel of colour (grey) depth of output. There is frequently
problem with additional rendering. Most of utilities doesn't work with this
colour depth correctly. On the other side, 16 bit per pixel images
has saved photometric content more precisely.
- -v
-
Print additional information during processing.
- -h, --help, --version
-
Show summary of options or the current version.
Examples of usage
Convert a FITS image to PNG:
$ fitspng grey.fits -o grey.png
Emulate humans night vision:
$ fitspng colour.fits -o colour.png -fn 100,10
Emulate the classical photography sensitivity function (density curve):
$ fitspng colour.fits -o colour.png -f logistic
Create a semi-grey image:
$ fitspng colour.fits -o colour.png -fs 0.2
Select just only Y band of a colour FITS (using
FITS
file name extension):
$ fitspng "colour.fits[*,*,2]" -o green.png
Generate thumbnails (with help of shell scripting):
$ for A in *.fits; do fitspng -s 10 $A -o ${A%fits}png; done
Gallery
All this gallery examples has been generated
by processing of the reference raw photo
IMG_5952.CR2
:
$ rawtran -o IMG_5952.fits IMG_5952.CR2
$ fitspng IMG_5952.fits -o IMG_5952.png -s 10
$ fitspng IMG_5952.fits -o IMG_5952_a.png -s 10 -cs AdobeRGB
$ fitspng IMG_5952.fits -o IMG_5952_fn.png -s 10 -fn 3000,100
$ fitspng IMG_5952.fits -o IMG_5952_ph.png -s 10 -f logistic
$ fitspng IMG_5952.fits -o IMG_5952_s05.png -s 10 -fs 0.5
$ fitspng IMG_5952.fits -o IMG_5952_s15.png -s 10 -fs 1.5
$ fitspng IMG_5952.fits -o IMG_5952_fr10.png -s 10 -fr 3,10
$ fitspng IMG_5952.fits -o IMG_5952_fr50.png -s 10 -fr 3,50
Colour image in sRGB
Colour image in AdobeRGB
Highlighted
Shadowed
Reduced colours
Enhanced colours
Photography tone
Night vision
Download and installation
The tar-ball
or the development
repository with a source code is freely available under GPL-3 licence.
Both
cfitsio and libpng
libraries with files required for development (headers, static libraries) is
necessary for building.
-
Fitspng runs under any Unix-like operating system (all flavours
of GNU/Linux and BSD, Solaris, Mac OS X, etc).
Fitspng packages can be found in
Debian and
Ubuntu.
-
Jean-Paul Godard
reported successful compilation under W7 (64b) environment
and mingw.
-
Fitspng can be compiled under Windows 8 (64-bit) with mingw
compiler (libpng will be required).
A recommended way of installation under Unix-like system is:
$ tar zxf fitspng-X.Y.Z.tar.gz
$ cd fitspng-X.Y.Z/
$ autoreconf -i # for Mercurial
$ ./configure CFLAGS="-O4 -DNEBUG"
$ make
# make install
The last step must be executed under root account.
Both binary and
man page are installed under /usr/local
tree. It would be
nice to keep the directory for case of later uninstalling.
See also
Munipack is a general utility
to work with FITS images.
Development notes can be found in
Hroch's diary.
License
Fitspng is free software
licensed under the GNU General Public License.
This gives you the freedom to use and modify Fitspng to suit your needs.