#define NFKEYS 11
#define NSTDKEYS 10
#define NAXES 3
#define SKIP 10
/* macros significantly speed-ups processing */
#define MAX(x,y) (x) > (y) ? (x) : (y)
#define MIN(x,y) (x) < (y) ? (x) : (y)
#define PRESCALE(flux,thresh,sense) ((flux - thresh) / sense)
#define CLIP(flux,high) MIN(MAX(((int)(flux+0.5)),0),high)
float sRGBGamma(float r)
{
const float q = 1.0 / 2.4;
return r < 0.0031308f ? 12.92f * r : 1.055f*powf(r,q) - 0.055f;
}
void XYZ_sRGB(float X, float Y, float Z, float *R, float *G, float *B)
{
float x = X / 100;
float y = Y / 100;
float z = Z / 100;
/* transform to RGB and apply gamma function */
*R = sRGBGamma( 3.2406f*x - 1.5372f*y - 0.4986f*z);
*G = sRGBGamma(-0.9689f*x + 1.8758f*y + 0.0415f*z);
*B = sRGBGamma( 0.0557f*x - 0.2040f*y + 1.0570f*z);
}
float AdobeGamma(float r)
{
const float q = 1.0 / 2.19921875;
return powf(r,q);
}
void XYZ_AdobeRGB(float X, float Y, float Z, float *R, float *G,float *B)
{
float x = X / 100;
float y = Y / 100;
float z = Z / 100;
/* transform to RGB and apply gamma function */
*R = AdobeGamma( 2.04159f*x - 0.56501f*y - 0.34473f*z);
*G = AdobeGamma(-0.96924f*x + 1.87597f*y + 0.04156f*z);
*B = AdobeGamma( 0.01344f*x - 0.11836f*y + 1.01517f*z);
}
float fscale(float r, int ctype, float f0, float zero)
{
switch (ctype) {
case 0:
return r;
case 5:
return (r > 0.0f ? f0*sqrtf(r) : 0.0f) + zero;
case 6:
return f0*r*r + zero;
case 7:
return f0*(2.0f/(1.0f + expf(-2.5f*r)) - 1.0f) + zero;
default:
return r - trunc(r); /* iso-contours */
}
}
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 qblack,float rsense, int scale,
float f0, float thresh, float sense, float sthresh, float swidth,
int ctype, float satur, int dcspace, float zero, int verb,
int est, int scotop, int saturate)
{
char *fkeys[NFKEYS] = {"OBJECT", "OBSERVER", "FILTER", "DATE-OBS",
"CAMTYPE", "EXPTIME", "SITE", "TEMPERAT",
"XFACTOR", "YFACTOR", "TELESCOP" };
char *stdkeys[NSTDKEYS] = {"Title","Author","Description","Copyright",
"Creation Time","Software","Disclaimer",
"Warning","Source","Comment"};
char *stdvalues[NSTDKEYS];
char *fval[NFKEYS];
long naxes[NAXES];
/* 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;
/* 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, a, b;
int iflux;
float *pic;
int imgpix[3] = {0,0,0};
/* display colour-space */
void (*XYZ_RGB)(float,float,float,float *,float *,float *);
float (*Gamma)(float);
/* --------------------------------------------------------------------*/
/* Part: Initilization */
status = 0;
if( dcspace == 1 ) {
XYZ_RGB = &XYZ_AdobeRGB;
Gamma = &AdobeGamma;
}
else { /* if( dcspace == 0 ) */
XYZ_RGB = &XYZ_sRGB;
Gamma = &sRGBGamma;
}
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);
if( verb )
fprintf(stderr,"Finishing FITS load..\n");
/* --------------------------------------------------------------------*/
/* Part: Estimation of intensity parameters */
if( abs(bitpix) > 8 && bit_depth < 16 && est ) {
int res;
int npix = naxes[0]*naxes[1];
float *ypic = naxis == 2 ? pic : pic + npix;
if( naxis == 2 ) /* gray images, single plane */
ypic = pic;
else /* Y component for colour images */
ypic = pic + npix;
res = tone(npix,ypic,qblack,rsense,&thresh,&sense,verb);
if( ! res ) {
fprintf(stderr,"A wicked star constellation for estimation of scale.\n");
goto finish;
}
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;
if( verb )
fprintf(stderr,"thresh=%f sense=%f lim=%d\n", thresh, sense, lim);
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;
int npix = naxes[0] * naxes[1];
for( j = height-1; j >= 0; j-- ) {
jj = j*scale;
for( i = 0; i < width; i++ ) {
ii = i*scale;
/* greyscale */
if( naxis == 2 ) {
flux = farray2d(pic,naxes[0],naxes[1],ii,jj,scale);
float f = MAX(PRESCALE(flux,thresh,sense),0);
if( ctype > 0 )
f = fscale(f,ctype,f0,zero);
float g = Gamma(f);
imgpix[0] = CLIP(255.0*g,lim);
}
else if( naxis == 3 ) {
/* colour */
Z = farray2d(pic, naxes[0],naxes[1],ii,jj,scale);
Y = farray2d(pic + npix,naxes[0],naxes[1],ii,jj,scale);
X = farray2d(pic+2*npix,naxes[0],naxes[1],ii,jj,scale);
X = MAX(100*PRESCALE(X,thresh,sense),0.0);
Y = MAX(100*PRESCALE(Y,thresh,sense),0.0);
Z = MAX(100*PRESCALE(Z,thresh,sense),0.0);
/* intensity scaling */
if( ctype > 0 || saturate ) {
/* convert to Lab */
XYZ_Lab(X,Y,Z,&L,&a,&b);
/* ITT */
if( ctype > 0 )
L = 100 * fscale(L / 100,ctype,f0,zero);
/* colour saturation */
if( saturate ) {
float hue = atan2f(b,a);
float r2 = a*a + b*b;
float chroma = r2 > 0 ? satur*sqrtf(r2) : 0;
a = chroma*cosf(hue);
b = chroma*sinf(hue);
}
/* convert back to XYZ */
Lab_XYZ(L,a,b,&X,&Y,&Z);
}
/* apply night vision simulation */
if( scotop ) {
float r = (Y - sthresh) / swidth;
float w = (1 + erf(r)) / 2;
float s = Scotopic(X,Y,Z);
float ws = s*(1.0f - w);
X = w*X + 0.95047f * ws;
Y = w*Y + ws;
Z = w*Z + 1.08883f * ws;
}
/* convert to output colour-space */
(*XYZ_RGB)(X,Y,Z,&R,&G,&B);
imgpix[0] = CLIP(255.0f*R,lim);
imgpix[1] = CLIP(255.0f*G,lim);
imgpix[2] = CLIP(255.0f*B,lim);
}
/* save to the image stream */
for( k = 0; k < color_bytes; k++) {
if( bit_depth == 16 ) {
/* confirmed by Imagemagick */
int n = imgpix[k];
image[m+1]= (png_byte) (n / 256);
image[m] = (png_byte) (n % 256);
}
else { /* if ( bit_depth == 8 ) { */
image[m] = (png_byte) imgpix[k];
}
m = m + bytes_per_pixel;
}
}
}
/* intensity table */
if( verb ) {
FILE *itt;
if( (itt = fopen("itt.dat","w")) ) {
for(i = 0; i <= lim; i++) {
flux = fscale((float)i/(float)lim,ctype,f0,zero);
iflux = CLIP(255.0*flux,lim);
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.4/fitspng.h 0000644 0040317 0001750 00000002246 13421434603 013651 0 ustar hroch hroch /*
FITSPNG
Copyright (C) 2006-2019 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 .
*/
/* fitspng.c */
int fitspng(char *, char *,int,float,float,int,
float,float,float,float,float,int,float,int,float,
int,int,int,int);
/* cielab.c */
void XYZ_Lab(float, float, float,float *, float *, float *);
void Lab_XYZ(float, float, float,float *, float *, float *);
/* ecdf.c */
int ecdf(long, const float *, float *, float *);
float quantile(long, const float *, const float *, float);
/* tone.c */
int tone(long, const float *, float, float,float *, float *,int);
fitspng-1.4/fitspng.html 0000644 0040317 0001750 00000051465 13421434603 014375 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 image format. FITS images stores measurements of a
physical quantity — an array of the angular distribution of
the photon events or the optical intensity.
Fitspng does visualisation of such array by converting them
to the images in PNG format.
Tone mapping
Fitspng applies a
global tone mapping
technique to transform of a wide dynamical range of FITS files
(naturally including
HDR)
to the limited range of, so called, modern displaying devices and
image formats.
The tone mapping includes these steps:
a linear pre-scaling and optional application of an intensity
transfer function.
Pre-scaling
maps linearly the captured counts, related to optical
intensities
I (I ∋ â„, I ≥ 0 ), to the range
0 ≤ t ≤ 1 by the two parameter transformation:
t = (I - B) / s.
As the parameters, B – black level, and
s – sensitivity, has been selected.
B sets a background level in original data. It corresponds to zero
in transformed values, and finally to the black colour.
The sensitivity s > 0 adjusts
a range of FITS values visible on PNG pictures.
It simulates the artificial sensitivity of a detector:
adjusting of sensitivity s leads to a dim, or highlight, images
— it operates like a gain of an electronic amplifier of a detector.
This parameter is a reciprocal quantity to ISO value used by photographers.
The linear transformation
should be followed by a cut-off, which assigns
all values below the level B to be black,
and over the interval to be white:
t = { (I - B) / s ∈ (0, 1) }
Itt scaling applies a
intensity transfer function f(t)
(the intensity transfer table ITT in the past)
on the pre-scaled data t:
τ = f0 · f(t) + z0,
f0, z0 are
Itt function scaling parameters (f0=1,
z0=0 by default).
If f(t) is the linear function, the function becomes identity
Ï„ = t, and f0, z0 remains no applied.
Various Itt functions (linear, logistics .. ) are
described in paragraph Itt functions.
Gamma correction
Finally, the light-sensitivity model, specific to a displaying device,
sRGB or
AdobeRGB (pdf)
is applied onto τ values by Gamma function Γ(τ):
p = { 255 Γ(τ) ∈ (0, 255) },
where p is the final value stored in PNG.
The described pre-scaling is applied on all pixels in the frame;
one is applied on values for grey-scale,
and on every channel of colour images.
Both B, s parameters can be leaved unspecified:
they are estimated
by the included machine algorithm described in
Tone parameters estimation.
A manual setup of the pre-scale parameters is available in two modes:
- Absolute:
the linear pre-scaling is given by the provided parameters
B, s or,
- Relative:
if internally estimated parameters are scaled.
Tone parameters estimation
A reliable setup of the scaling parameters B, s is crucial
for proper function of Fitspng; a novel
approach on base of quantiles of an empirical distribution function
has been developed.
Background level is estimated as
25% quantile
QB of
empirical CDF of observed pixels.
It is close to CDF of
Normal
distribution for the sky, a star free background.
The black level is adjusted by the quantile:
B = QB(¼)
The pixels included in determination of the CDF
are pre-selected from a grid over the full frame
(over 30 thousands of data points). The one-quarter value
is a result of empirical experiments.
Light upper level is estimated
as 95% quantile of the empirical CDF constructed on base of
pixels with values above ≥ 3 D
of the background CDF. The pixels contains only a star light,
or another star-gazing objects,
and provides the range for observed intensity values.
s0 = QL(95%)
Parameter D is a quantile estimation of dispersion
on base of ¼ and ¾ of QB
quantiles (QN(½) ≠0.6745 is 50% quantile of
Normal distribution):
D = [QB(¾) - QB(¼)] / [2 · QN(½)]
Relative parameters q, v
are defined by the way:
The relative parameter q ∈ (0,1) is a quantile of
QB(q).
Common properties of QB(q) are:
q = ½ is arithmetic mean,
q = ¼ (standard background level) is the mean of absolute deviations
under the mean, q=0 or q=1 are minimal and maximal values.
The parameter 0.001 < v < 1000 (mostly)
adjusts relative slope against to the pre-defined value
s0.
The empirical CDF for an ordinary picture of sky is
displayed on the graph below.
The vertical axis shows quantiles, the horizontal axis of picture values.
The background noise follows Normal distribution up to Q = 0.8;
the star light is getting importance over the background since the level.
The empirical CDF of an ordinary picture of sky, see text for description.
The histogram of the same frame.
Itt functions
Fitspng implements the functions:
- linear: f(t) = t,
- square root (sqrt): f(t) = √t, t > 0,
- square (sqr): f(t) = t2,
- logistics: f(t) = 1 / [1 + exp(-2.5 t)] - 1.
The choice of analytical form of the functions
asymptotically reproduces the linear function close to zero.
The square root function reveals the relative error of observed values:
the error is proportional to √t / t (reciprocity to the Itt above) by
Poisson
distribution. The bright parts of life has smaller relative errors
over the dark parts.
The logistics
function emulates the gradation curve of
the classical photographic emulsion.
Colour tuning
Colour FITS frames, defined by
Munipack,
are recognised. The colours can be tuned.
Saturation
The colour saturation of images should be reduced when
the relative saturation parameter has value c < 1,
or enhanced when c > 1. The saturation of the final frame
is computed by formula
c √C,
with the chroma C = a2 + b2 specifying
of the saturation in
L*a*b
colour-space.
Nite vision
The human eye sensitivity in high and low light conditions
can be artificially simulated by greying of dim parts of images
by application of
PurkynÄ› effect.
The photopic (colour) and scotopic vision is modelled by the
mix weighting function
w = ½ {1 + erf [(t - tn) / sn]},
with the night vision threshold tn,
and a half-with of sn of the mesotopic vision region.
Be warned, the function has been selected by heuristic: there is no
a proof of validity.
The values are mixed with X, Y, Z channels giving ranges
0 ≤ tn ≤ 100 and
sn > 0. Pictures becomes fully coloured
for tn → 0, or complete in night vision for
tn → 100. The mesotopic regime has tn
around 50.
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
FITS header meta-information is stored as an EXIF information
of PNG files: the EXIF text strings has free format and no formalised
custom.
Fitspng invocation
Synopsis
fitspng [options] file(s)
Command line options
- -fr q, v
-
Relative pre-scaling mode: q, v represents quantiles
of a background (sky) and v relative sensitivity.
- -fl B, s
-
Absolute pre-scaling mode: B, s are directly used
for scaling of the output frame.
This setup completely disables the internal parameters estimation.
- -f [linear|sqrt|sqr|logistic]
-
Itt function: linear, square root, square, logistic.
- -f0 f0
-
The scale factor of Itt function.
- -fz z0
-
The zero point of Itt function.
- -fs c
-
The relative colour saturation (Colour FITS only).
- -fn tn, sn
-
When used, switch-on, and setup,
mode which emulates the humans night vision (Colour FITS only).
- -cs [sRGB|AdobeRGB]
-
The colour space of the output image.
- -s s
-
Shrink image: scale-down the size of the image by the specified factor
s > 1: every output pixel is the arithmetic mean of
s2 input pixels.
- -o filename
-
Specify an output file name, single file only.
If this switch is omitted, the output filename is determined by
modification of the input filename(s): suffixes, like *.fits,
are replaced by *.png, and the directory
path is removed: /space/image.fits is converted
to image.png. The approach leaves original
data untouched, results are stored in the current working
directory (see also Examples).
- -B [8|16]
-
The bit depth of the output PNG frame: 8 (default) or 16 bites per pixel.
A post-processing can be affected by the choice; most utilities
doesn't work with 16-bit colour depth correctly.
- -v, --verbose
-
Be verbose.
- -h, --help, --version
-
Shows summary of options, or the current version.
Examples of usage
Convert a FITS image to PNG:
$ fitspng -o grey.png grey.fits
Emulate the human night vision:
$ fitspng -o scotopic.png colour.fits
Emulate the classical photography sensitivity function (gradation curve):
$ fitspng -f logistic -o photo.png colour.fits
Create a semi-grey image:
$ fitspng -fs 0.2 -o reduced.png colour.fits
Generate thumbnails of FITS files in /space;
files are stored under the current directory
fitspng -s 10 /space/*.fits
The same result should be emulated with help of shell scripting
(providing powerful file name manipulations):
$ for FITS in /space/*.fits; do
NAME=$(basename $FITS);
PNG=${NAME%fits}png;
fitspng -s 10 -o $PNG $FITS;
done
Fitspng has no parallel run support build-in due portability issues;
however, the execution time on multiprocessor systems should be significantly
reduced with help of an external utility:
$ ls /space/*.fits | xargs -n 1 -P 2 fitspng
Gallery
#1, Colour image in sRGB
#2, Colour image in AdobeRGB
#3, Highlighted
#4, Shadowed
#5, Reduced colours
#6, Enhanced colours
#7, Photography tone
#8, Night vision
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 -s 10 -o IMG_5952.png IMG_5952.fits #1
$ fitspng -cs AdobeRGB -s 10 -o IMG_5952_a.png IMG_5952.fits #2
$ fitspng -fr 3,10 -s 10 -o IMG_5952_fr10.png IMG_5952.fits #3
$ fitspng -fr 3,50 -s 10 -o IMG_5952_fr50.png IMG_5952.fits #4
$ fitspng -fs 0.5 -s 10 -o IMG_5952_s05.png IMG_5952.fits #5
$ fitspng -fs 1.5 -s 10 -o IMG_5952_s15.png IMG_5952.fits #6
$ fitspng -f logistic -s 10 -o IMG_5952_ph.png IMG_5952.fits #7
$ fitspng -fn 3000,100 -s 10 -o IMG_5952_fn.png IMG_5952.fits #8
Download and installation
The tar-ball,
or the development
repository, is freely available under GPL-3 licence.
Both
cfitsio and libpng
libraries, including files required for development (headers, static libraries),
are 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 should 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.