File: flat.f08

package info (click to toggle)
munipack 0.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 33,104 kB
  • sloc: cpp: 29,677; sh: 4,909; f90: 2,872; makefile: 278; python: 140; xml: 72; awk: 12
file content (877 lines) | stat: -rw-r--r-- 27,193 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
!
!  Flat     Average of a set of flat-fields.
!  Copyright (C) 1997 - 2024  Filip Hroch, Masaryk University, Brno, CZ
!
!  This file is part of Munipack.
!
!  Munipack 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.
!
!  Munipack 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 Munipack.  If not, see <http://www.gnu.org/licenses/>.
!

program flatmaker

  use fitscorr
  use oakleaf
  use titsio
  use iso_fortran_env

  implicit none

  ! debuging
  logical, parameter :: debug = .false.

  ! verbosity
  logical :: verbose = .false.

  ! Default Output image name:
  character(len=FLEN_FILENAME) :: flatname = 'flat.fits'
  character(len=FLEN_FILENAME) :: biasname = ''
  character(len=FLEN_FILENAME) :: darkname = ''
  character(len=FLEN_FILENAME) :: maskname = ''

  ! lower and upper limits
  real :: saturate =  -1
  real :: threshold = epsilon(threshold)
  logical :: saturate_set = .false.
  logical :: threshold_set = .false.

  integer :: eq, stat, naxis
  integer, dimension(2) :: naxes
  integer :: nflat = 0     ! counter of flats
  integer :: bitpix = -32
  integer :: maxiter = 7
  integer :: status = 0
  character(len=8) :: approximation = 'STANDARD'

  real :: flat_mean = 1, flat_err = 0
  real :: gain_mean = 1, gain_err = 0

  real :: time = 1
  real :: xdark = -1.0
  real :: gain = 1
  logical :: gain_set = .false.

  ! mean flat with statistical error
  real, dimension(:,:), allocatable :: flat, flaterr
  logical, dimension(:,:), allocatable :: bitmask

  character(len=80) :: msg
  character(len=4*FLEN_FILENAME) :: record, key, val
  character(len=FLEN_VALUE) :: dateobs, filter, imagetyp

  character(len=FLEN_FILENAME), dimension(:), allocatable :: flatnames
  character(len=FLEN_KEYWORD), dimension(6) :: keys = [ &
       FITS_KEY_DATEOBS, &
       FITS_KEY_EXPTIME, &
       FITS_KEY_FILTER, &
       FITS_KEY_SATURATE, &
       FITS_KEY_TEMPERATURE, &
       FITS_KEY_GAIN ]

  ! FITS wrappers
  type(CorrFits), allocatable :: darkfits, biasfits, maskfits
  type(CorrFits), dimension(:), allocatable :: flats

  ! Section: Input  ---------------------------------------
  do
     read(*,'(a)',iostat=stat,iomsg=msg) record
     if( stat == IOSTAT_END ) exit
     if( stat > 0 ) then
        write(error_unit,*) trim(msg)
        error stop 'Input error.'
     end if

     eq = index(record,'=')
     if( eq == 0 ) error stop 'Malformed input record.'
     key = record(:eq-1)
     val = record(eq+1:)

     if( key == 'VERBOSE' ) then
        read(val,*) verbose
     endif

     if( key == 'OUTPUT' ) then
        read(val,*) flatname
     endif

     if( key == 'BITPIX' ) then
        read(val,*) bitpix
     endif

     if( key == 'SATURATE' ) then
        read(val,*) saturate
        if( .not. (saturate > 0) ) stop 'Saturation > 0 is required.'
        saturate_set = .true.
     endif

     if( key == 'THRESHOLD' ) then
        read(val,*) threshold
        if( .not. (threshold > 0) ) stop 'Threshold > 0 is required.'
        threshold_set = .true.
     endif

     if( key == 'GAIN' ) then
        read(val,*) gain
        gain_set = .true.
     endif

     if( key == 'APPROXIMATION' ) then
        read(val,*) approximation
        if( approximation == 'BASIC' ) then
           maxiter = 0
        else !if( approximation == 'STANDARD' ) then
           maxiter = 3
        end if
     endif

     if( key == 'FITS_KEY_DATEOBS' ) then
        read(val,*) keys(1)
     endif

     if( key == 'FITS_KEY_EXPTIME' ) then
        read(val,*) keys(2)
     endif

     if( key == 'FITS_KEY_FILTER' ) then
        read(val,*) keys(3)
     endif

     if( key == 'FITS_KEY_SATURATE' ) then
        read(val,*) keys(4)
     endif

     if( key == 'FITS_KEY_TEMPERATURE' ) then
        read(val,*) keys(5)
     endif

     if( key == 'FITS_KEY_GAIN' ) then
        read(val,*) keys(6)
     endif

     if( key == 'BIAS' ) then
        read(val,*) biasname
     end if

     if( key == 'XDARK' ) then
        read(val,*) xdark
     end if

     if( key == 'DARK' ) then
        read(val,*) darkname
     end if

     if( key == 'MASK' ) then
        read(val,*) maskname
     end if

     if( key == 'NFILES' ) then

        read(val,*) nflat
        allocate(flatnames(nflat),stat=stat,errmsg=msg)
        if( stat /= 0 ) then
           write(error_unit,*) trim(msg)
           error stop 'Insufficient memory.'
        end if
        nflat = 0

     end if

     if( key == 'FILE' ) then

        nflat = nflat + 1
        if( nflat > size(flatnames) ) stop 'NFILES unspecified?'
        read(val,*) flatnames(nflat)

     end if

  enddo

  if( nflat == 0 ) stop 'No input image(s).'
  if( nflat /= size(flatnames) ) error stop 'n /= size(flatnames)'

  ! Section: FITS files input ---------------------------------------
  block

    integer :: n
    character :: gflag
    logical :: filter_match = .true.
    logical :: filter_empty = .false.
    logical :: gain_default = .false.

    ! bias
    if( biasname /= '' ) then
       if( verbose ) write(error_unit,'(a)',advance="no") &
            "Bias frame: "//trim(biasname)//","

       allocate(biasfits)
       call biasfits%Load(biasname,keys)
       if( .not. biasfits%status ) stop 'Failed to load bias frame.'

       if( verbose ) write(error_unit,'(a,1pg0.3,a,0pf0.1)') &
            ' exp.time[s] = ',biasfits%exptime, &
            ', T[degC] = ',biasfits%temper
    end if

    ! dark
    if( darkname /= '' ) then
       if( verbose ) write(error_unit,'(a)',advance="no") &
            "Dark frame: "//trim(darkname)//", "

       allocate(darkfits)
       call darkfits%Load(darkname,keys)
       if( .not. darkfits%status ) stop 'Failed to load dark frame.'

       if( verbose ) write(error_unit,'(a,1pg0.3,a,0pf0.1)') &
            ' exp.time[s] = ',darkfits%exptime, &
            ', T[degC] = ',darkfits%temper
    end if

    ! mask
    if( maskname /= '' ) then
       if( verbose ) write(error_unit,*) "Mask frame: ",trim(maskname)

       allocate(maskfits)
       call maskfits%Load(maskname)
       if( .not. maskfits%status ) stop 'Failed to load the mask frame.'
    end if

    if( verbose ) write(error_unit,*) &
         "Filename, filter, exptime[s], gain[ct/adu], saturation[ct], T[degC]:"

    ! flats
    allocate(flats(nflat),stat=stat,errmsg=msg)
    if( stat /= 0 ) then
       write(error_unit,*) trim(msg)
       error stop 'Insufficient memory.'
    end if

    do n = 1, size(flatnames)

       if( verbose ) write(error_unit,'(a)',advance="no") &
            trim(flatnames(n))//":"

       call flats(n)%Load(flatnames(n),keys)
       if( .not. flats(n)%status ) stop 'Flat-field frame read failed.'

       if( n == 1 .and. allocated(biasfits) ) then
          if( .not. all(biasfits%naxes == flats(n)%naxes) ) then
             stop "Dimensions of bias and the frame does not corresponds."
          end if
       end if

       if( n == 1 .and. allocated(darkfits) ) then
          if( .not. all(darkfits%naxes == flats(n)%naxes) ) then
             stop "Dimensions of dark and the frame does not corresponds."
          end if
       end if

       if( n == 1 .and. allocated(maskfits) ) then
          if( .not. all(maskfits%naxes == flats(n)%naxes) ) then
             stop "Dimensions of bitmask and the frame does not corresponds."
          end if
       end if

       if( n > 1 ) then
          if( .not. all(flats(n-1)%naxes == flats(n)%naxes) ) then
             stop "Dimensions of images mutually does not corresponds."
          end if
       endif

       ! setup saturation
       if( saturate_set ) flats(n)%saturate = saturate

       ! set gain by the provided value
       if( gain_set ) then
          flats(n)%gain = gain
          flats(n)%gain_set = .true.
       end if

       if( verbose ) then
          if( flats(n)%gain_set .or. gain_set ) then
             gflag = ' '
          else
             gflag = '!'
             gain_default = .true.
          end if

          write(error_unit,'(2x,a,2x,1pg0.3,2x,0pf0.3,a,2x,1p,g0.2,2x,0pf0.1)')&
               trim(flats(n)%filter),flats(n)%exptime,flats(n)%gain,gflag, &
               flats(n)%saturate,flats(n)%temper

          ! detect filter mismatch
          if( flats(n)%filter /= flats(1)%filter ) filter_match = .false.
          if( flats(n)%filter == '' ) filter_empty = .true.

       end if
    end do

    if( .not. filter_match ) write(error_unit,*) &
         "Warning: incompatible filters detected (try --verbose)."

    if( filter_empty ) write(error_unit,*) &
         "Warning: empty filter value is encountered (try --verbose). "

    if( verbose .and. gain_default ) &
       write(error_unit,*) "Gain default setup is indicated by '!' flag."

  end block

  ! setup common parameters
  naxis = flats(1)%naxis
  naxes = flats(1)%naxes
  dateobs = flats(1)%dateobs
  imagetyp = flats(1)%imagetyp
  filter = flats(1)%filter

  allocate(flat(naxes(1),naxes(2)),flaterr(naxes(1),naxes(2)), &
       bitmask(naxes(1),naxes(2)),stat=stat,errmsg=msg)
  if( stat /= 0 ) then
     write(error_unit,*) trim(msg)
     error stop 'Insufficient memory.'
  end if

  if( allocated(maskfits) ) then
     bitmask = maskfits%image > 0.5
     deallocate(maskfits)
  else
     bitmask = .true.
  end if

  if( verbose ) then
     write(error_unit,*)
     write(error_unit,*) 'Flat-field frames:',nflat
     write(error_unit,*) 'Flat frame dimensions:',naxes(1),'x',naxes(2)
     write(error_unit,*) 'Filter: ',trim(filter)
     write(error_unit,*) 'Accuracy of approximation: ',trim(approximation)
  end if

  ! Section: flat-field preparation --------------------------------
  ! All flats are corrected for gain, bias, darks.
  block

    real, dimension(:,:), allocatable :: bias, ebias, dark, edark
    logical, dimension(:,:), allocatable :: mask
    real :: exptime
    logical :: exptime_set
    integer :: n,m

    n = naxes(1)
    m = naxes(2)
    allocate(bias(n,m),ebias(n,m),dark(n,m),edark(n,m),mask(n,m), &
         stat=stat,errmsg=msg)
    if( stat /= 0 ) then
       write(error_unit,*) trim(msg)
       error stop 'Insufficient memory.'
    end if

    if( allocated(biasfits) ) then
       bias = biasfits%image
       ebias = biasfits%imgerr
       deallocate(biasfits)
    else
       bias = 0
       ebias = 0
    end if
    if( allocated(darkfits) ) then
       dark = darkfits%image
       edark = darkfits%imgerr
       exptime = darkfits%exptime
       exptime_set = darkfits%exptime_set .and. darkfits%exptime > 0
       deallocate(darkfits)
    else
       dark = 0
       edark = 0
       exptime_set = .false.
    end if

    if( verbose ) write(error_unit,*) &
         'Pre-corrections by gain, bias, or dark of raw flats ...'

    do n = 1, size(flats)

       ! dark frame multiplicator
       if( xdark > 0 )then
          time = xdark
       else if( flats(n)%exptime_set .and. exptime_set ) then
          time = flats(n)%exptime / exptime
       else
          time = 1
       end if

       ! Preparatory correct input flats (updates images in FitsCorr).
       ! Standard deviation is set with assumption of Poisson distribution
       ! of data. It requires large light fluxes, around half of a full range.
       associate (image => flats(n)%image, imgerr => flats(n)%imgerr, &
            saturate => flats(n)%saturate, gain => flats(n)%gain )

         mask = threshold < image .and. image < saturate .and. bitmask
         where( mask )
            image = gain*(image - (bias + time*dark))
            mask = image > epsilon(image)
         elsewhere
            ! Important. Pixels out of the mask has set negative values
            ! during all computations below, use of individual masks
            ! for every frame takes a lot of memory.
            image = -1
         end where
         where( mask )
            imgerr = sqrt(image + gain**2*(ebias**2 + time**2*edark**2))
         elsewhere
            imgerr = -1
         end where
         ! IMPORTANT
         ! imgerr means standard deviation: stdsig
         ! similarity stderr and imgerr, or meaning in dark.f08,
         ! is pure coincidental (and memory saving)

         ! determine of mean level of our current frame
         call rmean(pack(image,mask),flats(n)%mean,flats(n)%stderr,flats(n)%sig)

       end associate
    end do

    deallocate(mask,bias,ebias,dark,edark)
  end block


  ! Section: Initial flat-field estimate as mean of scaled flats  ------
  !          by averadge levels.
  block

    real, dimension(:), allocatable :: x
    integer :: i,j,n,m
    real :: thresh

    n = naxes(1)
    m = naxes(2)
    allocate(x(nflat))

    ! Initial mean flat
    if( verbose ) write(error_unit,'(a)') &
         'Calculating the initial flat-field frame (iter. #0) ...'
    do j = 1,naxes(2)
       do i = 1,naxes(1)

          ! this is initial estimate only
          ! lighter frames has higher influence due Poisson statistics
          n = 0
          do m = 1, nflat
             associate( image => flats(m)%image, imgerr => flats(m)%imgerr, &
                  mean => flats(m)%mean, &
                  saturate => flats(m)%saturate, gain => flats(m)%gain )

               thresh = gain*threshold
               if( thresh < image(i,j) .and. image(i,j) < saturate .and. &
                    bitmask(i,j) .and. mean > 0 ) then
                  n = n + 1
                  x(n) = image(i,j) / mean
               end if
             end associate
          end do

          if( n > 1 ) then
             call rmean(x(1:n),flat(i,j),flaterr(i,j))
          else
             flat(i,j) = 1
             flaterr(i,j) = 0
          end if

       enddo
    enddo

    deallocate(x)
  end block

  ! Section: debug prints, write out the first estimate -----------
  if( debug ) then

     block
       real, allocatable, dimension(:,:) :: res
       integer :: i,j

       allocate(res(naxes(1),naxes(2)),stat=stat,errmsg=msg)
       if( stat /= 0 ) then
          write(error_unit,*) trim(msg)
          error stop 'Insufficient memory.'
       end if

       where( bitmask .and. flaterr > 0 )
          res = (flat - 1) / (flaterr * sqrt(real(nflat)))
       end where

       open(1,file='/tmp/flatdebug_zero.dat')
       do i = 1,size(flat,1),2
          do j = 1,size(flat,2),2
             if( bitmask(i,j) .and. flaterr(i,j) > 0 .and. &
                  abs(res(i,j)) < 25 ) then
                write(1,*) flat(i,j)-1,res(i,j), flat(i,j)
             end if
          end do
       end do
       close(1)
       deallocate(res)
     end block
  end if

  ! Section: estimate the flat-field with scaling by individual -----
  !          ratios against previously estimated flat-field.
  block

    logical, dimension(:,:), allocatable :: mask
    real, dimension(:,:), allocatable  :: res
    real, dimension(:), allocatable :: u,ue,v,ve,gains
    integer :: i,j,m,n,iter
    logical :: terminate, reliable
    real :: t, dt, d, sig, avg, thresh

    allocate(res(naxes(1),naxes(2)),u(nflat),ue(nflat),v(nflat),ve(nflat), &
         gains(nflat),mask(naxes(1),naxes(2)),stat=stat,errmsg=msg)
    if( stat /= 0 ) then
       write(error_unit,*) trim(msg)
       error stop 'Insufficient memory.'
    end if

    terminate = .false.
    do iter = 1, maxiter

       ! Now, we're improving accuracy of approximation. The number of
       ! iterations is controled by `terminate' variable which tests
       ! convergence of subsequent estimates of the created flat.

       if( terminate ) exit

       if( verbose ) then
          write(error_unit,'(a)') &
               'Scaling individual frames by the new flat ...'
          write(error_unit,'(a)') &
               "Filename, mean level[ct], std.err., std.dev., gain, reliable:"
       end if

       ! Update mean levels for individual frames
       do n = 1, nflat

          associate( image => flats(n)%image, imgerr => flats(n)%imgerr, &
               mean => flats(n)%mean, stderr => flats(n)%stderr, &
               stdsig => flats(n)%sig, &
               saturate => flats(n)%saturate, gain => flats(n)%gain )

            thresh = flats(n)%gain * threshold
            mask = thresh < image .and. image < saturate .and. imgerr > 0 &
                 .and. flat > 0 .and. flaterr > 0 .and. bitmask
            call fmean(pack(image,mask),pack(imgerr,mask),pack(flat,mask), &
                 pack(flaterr,mask),t,dt,sig,reliable=reliable)
!            call fmean(pack(flat,mask),pack(flaterr,mask), &
!                 pack(image,mask),pack(imgerr,mask),t,dt,sig,reliable=reliable)
            ! We assumes Poisson distribution of flat pixels, their dispersion
            ! is bound to the mean value. Data with strong fluence of
            ! non-Poisson noise component should be avoided already.

            ! update only when our estimate is realiable
            if( reliable ) then
               mean = t
               stderr = dt
               stdsig = sig
            end if

            if( verbose ) then

               gains(n) = t / sig**2
               write(error_unit, &
                    '(2a,2x,1pg0.5,2x,1pg0.3,1x,1pg0.5,2x,0pf0.3,2x,l1)') &
                    trim(flatnames(n)),": ",t,dt,sig,gains(n),reliable

            end if

          end associate

       enddo ! over all frames

       ! update flat
       if( verbose ) write(error_unit,'(a,i0,a)') &
            'Calculating accurate flat-field frame (iter. #',iter,') ...'
       res = -1
       do j = 1,naxes(2)
          do i = 1,naxes(1)

             n = 0
             do m = 1, nflat
                associate( image => flats(m)%image, imgerr => flats(m)%imgerr, &
                     mean => flats(m)%mean, sig => flats(m)%sig, &
                     saturate => flats(m)%saturate, gain => flats(m)%gain )

                  thresh = gain*threshold
                  if( thresh < image(i,j) .and. image(i,j) < saturate .and. &
                       imgerr(i,j) > 0 .and. bitmask(i,j) .and. &
                       mean > 0 .and. sig > 0 ) then
                     n = n + 1
                     u(n) = image(i,j)
                     ue(n) = imgerr(i,j)
                     v(n) = mean
                     ve(n) = sig
                  end if
                end associate
             enddo

             if( n > 0 ) then

                avg = flat(i,j)

                if( debug .and. i == naxes(1)/2 .and. j == naxes(2)/2 ) &
!                     call fmean(v(1:n),ve(1:n),u(1:n),ue(1:n), &
!                     flat(i,j),flaterr(i,j),reliable=reliable,verbose=.true.)
                     call fmean(u(1:n),ue(1:n),v(1:n),ve(1:n), &
                     flat(i,j),flaterr(i,j),reliable=reliable,verbose=.true.)

                call fmean(u(1:n),ue(1:n),v(1:n),ve(1:n), &
                     flat(i,j),flaterr(i,j))
!                call fmean(v(1:n),ve(1:n),u(1:n),ue(1:n), &
!                     flat(i,j),flaterr(i,j))

                ! Non reliable pixels are silently ignored. Some data
                ! has strongly non-gaussian distribution: the case of
                ! bad columns and pixels, an overscan data and etc.
                ! if( .not. reliable .and. verbose ) write(*,*) i,j

                ! the absolute difference between the result of previous
                ! computation and the current one controls termination
                if( reliable ) res(i,j) = abs(avg - flat(i,j))

             else
                flat(i,j) = 1
                flaterr(i,j) = 0
             end if

          enddo
       enddo

       ! condition for terminate: the mean difference of running flats
       ! between two latest subsequent iterations is smaller
       ! the mean std.err. limit
       d = qmedian(pack(res,res>0))
       flat_err = qmedian(pack(flaterr,flaterr>0))
       terminate = d < flat_err
       if( verbose ) &
            write(error_unit,'(a,3x,1p,2(g0.1,2x),l1)') &
            'Mean residual and std.dev., terminate:',d,flat_err,terminate

    end do ! iter

    ! final mean over the whole area
    call rmean(pack(flat,bitmask),flat_mean)

    if( verbose ) &
       call rmean(gains,gain_mean,gain_err)

    if( maxiter == 0 ) flat_err = qmedian(pack(flaterr,flaterr>0))

    deallocate(mask,res,u,ue,v,ve,gains)
  end block

  ! Section: diagnostics  ----------------------------------------
  block

    character(len=FLEN_FILENAME) :: buf
    logical, dimension(:,:), allocatable :: mask
    real, dimension(:,:), allocatable  :: res,des
    real :: thresh
    integer :: i,j,n

    allocate(mask(naxes(1),naxes(2)),res(naxes(1),naxes(2)), &
         des(naxes(1),naxes(2)),stat=stat,errmsg=msg)
    if( stat /= 0 ) then
       write(error_unit,*) trim(msg)
       error stop 'Insufficient memory.'
    end if

    if( debug ) then
       ! (**)
       ! Diagnostics. The second column of the files
       ! are residuals intended for Normality testing.
       !
       ! https://stackoverflow.com/questions/2471884/
       ! or:
       ! gnuplot> binwidth=0.05
       ! gnuplot> bin(x,width)=width*floor(x/width)
       ! gnuplot> plot '/tmp/flatdebug_666.fits.dat' \
       !          using (bin($2,binwidth)):(1.0) smooth freq with boxes

       do n = 1, nflat

          associate( image => flats(n)%image, imgerr => flats(n)%imgerr, &
               mean => flats(n)%mean, saturate => flats(n)%saturate, &
               gain => flats(n)%gain  )

            thresh = gain*threshold
            mask = thresh < image .and. image < saturate .and. &
                 flat > 0 .and. flaterr > 0 .and. bitmask
            where( mask )
               des = sqrt(imgerr**2 + mean**2*flaterr**2)
               res = (image - mean*flat) / des
            end where

            write(buf,'(a,i0,a)') '/tmp/flatdebug_',n,'.dat'
            open(1,file=buf)
            write(1,'(2a)') '# ',trim(flatnames(n))
            do i = 1,size(image,1),2
               do j = 1,size(image,2),2
                  if( mask(i,j) .and. abs(res(i,j)) < 5 ) then
                     write(1,*) image(i,j)-mean, res(i,j), flat(i,j)
                  end if
               end do
            end do
            close(1)
          end associate

        end do
     end if ! debug
     deallocate(mask,res,des)
   end block

   ! Section: save in integers implies to scale the flat ------------------
   block

     integer :: waterline
     real :: maxflat

     ! Representation of flat by integers can't be recommended in any case,
     ! but it can be useful for a compatibility
     if( bitpix > 0 ) then

        ! scaled to waterline
        waterline = nint(10.0**(int(log10(2.0**(bitpix-1)))))
        ! waterline updates mean levels on 1e2,1e4 and 1e9
        flat_mean = waterline * flat_mean
        flat = waterline * flat
        flaterr = waterline * flaterr
        maxflat = 2.0**bitpix - 1

        ! range cut-off
        flat = max(0.0,min(flat,maxflat))
        flaterr = max(0.0,min(flaterr,maxflat))
        if( verbose ) write(error_unit,*) &
             'Warning: Numerical accurate degraded by conversion to integers.'
     end if
   end block

  if( verbose ) then
     write(error_unit,'(2a)') ' Output image: ',trim(flatname)
     write(error_unit,'(a,3x,1pg0.7)') ' Final mean:',flat_mean
     write(error_unit,'(a,3x,1pg0.1)') &
          ' Expected photometry standard error per pixel:',flat_err
     write(error_unit,'(a,2(2x,f0.3),a,f0.3,a)') &
          ' Estimated relative gain, std.err:',gain_mean,gain_err, &
          ' (original gain was ',gain,').'
  end if

  ! Section: FITS save ----------------------------------------------
  block

    integer, parameter :: group = 1
    character(len=*), parameter :: afid = 'FLAT'
    character(len=FLEN_CARD) :: buf
    integer :: n
    type(fitsfiles) :: fits

    call fits_create_scratch(fits,status)
    call fits_insert_img(fits,bitpix,naxis,naxes,status)
    if( dateobs /= '' ) &
         call fits_write_key(fits,keys(1),dateobs, &
         'UTC of the first on input',status)
    if( filter /= '' ) &
         call fits_write_key(fits,keys(3),filter,'filter',status)
    if( imagetyp /= '' ) &
         call fits_write_key(fits,FITS_KEY_IMAGETYP,imagetyp, &
         'Image type',status)
    if( verbose ) then
       call fits_write_key(fits,'GAIN_AVG',gain_mean,6, &
            '[ct/ADU] estimated gain',status)
       call fits_write_key(fits,'GAIN_STD',gain_err,2, &
            '[ct/ADU] std.dev of estimated gain',status)
    end if

    if( nflat > 0 ) then
       write(buf,'(a,i0,a)') 'Result of flat-fielding of ',nflat,' frames(s):'
       call fits_write_comment(fits,buf,status)
       do n = 1, nflat
          call fits_write_comment(fits,"'"//trim(flatnames(n))//"'",status)
       enddo
    endif

    if( gain_set ) then
       write(buf,'(f0.3)') gain
       call fits_write_history(fits,afid//" gain: "//trim(buf),status)
    end if

    if( darkname /= '' ) then
       write(buf,'(a,g0.5)') afid//" dark: '"//trim(darkname)//"' *",time
       call fits_write_history(fits,buf,status)
    end if

    if( biasname /= '' ) then
       buf = afid//" bias: '"//trim(biasname)//"'"
       call fits_write_history(fits,buf,status)
    end if

    if( maskname /= '' ) then
       buf = afid//" bitmask: '"//trim(maskname)//"'"
       call fits_write_history(fits,buf,status)
    end if

    if( threshold_set ) then
       write(buf,*) afid//" threshold: ", threshold, " (no gain applied)"
       call fits_write_history(fits,buf(2:),status)
    end if

    if( saturate_set ) then
       write(buf,*) afid//" saturation: ",saturate," (no gain applied)"
       call fits_write_history(fits,buf(2:),status)
    end if

    write(buf,*) afid//" mean level: ",flat_mean
    call fits_write_history(fits,buf(2:),status)

    call fits_update_key(fits,FITS_KEY_CREATOR,FITS_VALUE_CREATOR, &
         FITS_COM_CREATOR,status)
    call fits_write_comment(fits,MUNIPACK_VERSION,status)

    ! flat-field data
    call fits_write_image(fits,group,flat,status)

    ! standard error of mean
    call fits_insert_img(fits,bitpix,naxis,naxes,status)
    call fits_update_key(fits,'EXTNAME',EXT_STDERR,'',status)
    call fits_write_comment(fits,&
         'The estimation of standard error of mean of pixels of flat-field.',&
         status)

    call fits_write_image(fits,group,flaterr,status)

    if( status == 0 ) then
       if( fits_file_exist(flatname) ) call fits_file_delete(flatname)
       call fits_file_duplicate(fits,flatname,status)
    end if
    call fits_delete_file(fits,status)

    call fits_report_error(error_unit,status)

  end block

  deallocate(flats,flatnames,flat,flaterr,bitmask)

  if( status == 0 ) then
     stop 0
  else
     stop 'An error occurred during flat-field determination.'
  end if

end program flatmaker