aboutsummaryrefslogtreecommitdiff
path: root/noao/onedspec
diff options
context:
space:
mode:
Diffstat (limited to 'noao/onedspec')
-rw-r--r--noao/onedspec/Revisions5321
-rw-r--r--noao/onedspec/aidpars.par25
-rw-r--r--noao/onedspec/autoidentify.par38
-rw-r--r--noao/onedspec/bplot.cl54
-rw-r--r--noao/onedspec/calibrate.par13
-rw-r--r--noao/onedspec/continuum.par25
-rw-r--r--noao/onedspec/deredden.par10
-rw-r--r--noao/onedspec/dispcor.par19
-rw-r--r--noao/onedspec/dispcor/dcio.x1155
-rw-r--r--noao/onedspec/dispcor/dctable.h11
-rw-r--r--noao/onedspec/dispcor/dctable.x145
-rw-r--r--noao/onedspec/dispcor/dispcor.h16
-rw-r--r--noao/onedspec/dispcor/dispcor.x233
-rw-r--r--noao/onedspec/dispcor/mkpkg28
-rw-r--r--noao/onedspec/dispcor/ranges.x239
-rw-r--r--noao/onedspec/dispcor/refaverage.x84
-rw-r--r--noao/onedspec/dispcor/reffollow.x114
-rw-r--r--noao/onedspec/dispcor/refgspec.x268
-rw-r--r--noao/onedspec/dispcor/refinterp.x127
-rw-r--r--noao/onedspec/dispcor/refmatch.x43
-rw-r--r--noao/onedspec/dispcor/refmsgs.x108
-rw-r--r--noao/onedspec/dispcor/refnearest.x104
-rw-r--r--noao/onedspec/dispcor/refnoextn.x29
-rw-r--r--noao/onedspec/dispcor/refprecede.x114
-rw-r--r--noao/onedspec/dispcor/refspectra.com15
-rw-r--r--noao/onedspec/dispcor/refspectra.h30
-rw-r--r--noao/onedspec/dispcor/refspectra.x186
-rw-r--r--noao/onedspec/dispcor/reftable.x109
-rw-r--r--noao/onedspec/dispcor/t_dispcor.x1336
-rw-r--r--noao/onedspec/dispcor/t_disptrans.x413
-rw-r--r--noao/onedspec/dispcor1.par5
-rw-r--r--noao/onedspec/disptrans.par12
-rw-r--r--noao/onedspec/doc/aidpars.hlp563
-rw-r--r--noao/onedspec/doc/autoidentify.hlp370
-rw-r--r--noao/onedspec/doc/bplot.hlp201
-rw-r--r--noao/onedspec/doc/calibrate.hlp195
-rw-r--r--noao/onedspec/doc/continuum.hlp263
-rw-r--r--noao/onedspec/doc/deredden.hlp201
-rw-r--r--noao/onedspec/doc/dispcor.hlp497
-rw-r--r--noao/onedspec/doc/disptrans.hlp193
-rw-r--r--noao/onedspec/doc/dopcor.hlp184
-rw-r--r--noao/onedspec/doc/fitprofs.hlp403
-rw-r--r--noao/onedspec/doc/identify.hlp810
-rw-r--r--noao/onedspec/doc/lcalib.hlp125
-rw-r--r--noao/onedspec/doc/mkspec.hlp86
-rw-r--r--noao/onedspec/doc/names.hlp67
-rw-r--r--noao/onedspec/doc/ndprep.hlp115
-rw-r--r--noao/onedspec/doc/odcombine.hlp480
-rw-r--r--noao/onedspec/doc/onedspec.hlp293
-rw-r--r--noao/onedspec/doc/refspectra.hlp413
-rw-r--r--noao/onedspec/doc/reidentify.hlp516
-rw-r--r--noao/onedspec/doc/rspectext.hlp138
-rw-r--r--noao/onedspec/doc/sapertures.hlp217
-rw-r--r--noao/onedspec/doc/sarith.hlp571
-rw-r--r--noao/onedspec/doc/sbands.hlp209
-rw-r--r--noao/onedspec/doc/scombine.hlp765
-rw-r--r--noao/onedspec/doc/scoords.hlp83
-rw-r--r--noao/onedspec/doc/scopy.hlp541
-rw-r--r--noao/onedspec/doc/sensfunc.hlp447
-rw-r--r--noao/onedspec/doc/sfit.hlp262
-rw-r--r--noao/onedspec/doc/sflip.hlp114
-rw-r--r--noao/onedspec/doc/sinterp.hlp146
-rw-r--r--noao/onedspec/doc/skytweak.hlp311
-rw-r--r--noao/onedspec/doc/skytweak.key35
-rw-r--r--noao/onedspec/doc/slist.hlp142
-rw-r--r--noao/onedspec/doc/specplot.hlp387
-rw-r--r--noao/onedspec/doc/specshift.hlp67
-rw-r--r--noao/onedspec/doc/specwcs.hlp586
-rw-r--r--noao/onedspec/doc/splot.hlp1118
-rw-r--r--noao/onedspec/doc/standard.hlp551
-rw-r--r--noao/onedspec/doc/sys/1and2dspec.hlp66
-rw-r--r--noao/onedspec/doc/sys/Headers.hlp189
-rw-r--r--noao/onedspec/doc/sys/Onedspec.hlp2219
-rw-r--r--noao/onedspec/doc/sys/Review.hlp512
-rw-r--r--noao/onedspec/doc/sys/TODO28
-rw-r--r--noao/onedspec/doc/sys/coincor.ms46
-rw-r--r--noao/onedspec/doc/sys/identify.ms347
-rw-r--r--noao/onedspec/doc/sys/onedproto.ms1673
-rw-r--r--noao/onedspec/doc/sys/onedv210.ms680
-rw-r--r--noao/onedspec/doc/sys/revisions.v3.ms382
-rw-r--r--noao/onedspec/doc/sys/revisions.v31.ms329
-rw-r--r--noao/onedspec/doc/sys/revisions.v31.ms.bak307
-rw-r--r--noao/onedspec/doc/sys/rvidentify.ms304
-rw-r--r--noao/onedspec/doc/sys/sensfunc.ms83
-rw-r--r--noao/onedspec/doc/sys/specwcs.ms612
-rw-r--r--noao/onedspec/doc/telluric.hlp350
-rw-r--r--noao/onedspec/doc/telluric.key35
-rw-r--r--noao/onedspec/doc/wspectext.hlp96
-rw-r--r--noao/onedspec/dopcor.par10
-rw-r--r--noao/onedspec/ecidentify.par26
-rw-r--r--noao/onedspec/ecidentify/eccenter.x34
-rw-r--r--noao/onedspec/ecidentify/eccolon.x243
-rw-r--r--noao/onedspec/ecidentify/ecdb.x268
-rw-r--r--noao/onedspec/ecidentify/ecdelete.x28
-rw-r--r--noao/onedspec/ecidentify/ecdofit.x128
-rw-r--r--noao/onedspec/ecidentify/ecdoshift.x44
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfcolon.x102
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfeval.x68
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecffit.com23
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecffit.h20
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecffit.key53
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecffit.x193
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfgdata.x37
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfget.x84
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfgraph.x50
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfnearest.x85
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfreject.x53
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfrms.x26
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfset.x92
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfshift.x55
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecfsolve.x196
-rw-r--r--noao/onedspec/ecidentify/ecffit/ecftitle.x48
-rw-r--r--noao/onedspec/ecidentify/ecffit/mkpkg21
-rw-r--r--noao/onedspec/ecidentify/ecfitdata.x146
-rw-r--r--noao/onedspec/ecidentify/ecgdata.x74
-rw-r--r--noao/onedspec/ecidentify/ecgetim.x17
-rw-r--r--noao/onedspec/ecidentify/ecgline.x20
-rw-r--r--noao/onedspec/ecidentify/ecgraph.x155
-rw-r--r--noao/onedspec/ecidentify/ecidentify.h94
-rw-r--r--noao/onedspec/ecidentify/ecidentify.key76
-rw-r--r--noao/onedspec/ecidentify/ecidentify.x535
-rw-r--r--noao/onedspec/ecidentify/ecinit.x64
-rw-r--r--noao/onedspec/ecidentify/ecline.x22
-rw-r--r--noao/onedspec/ecidentify/eclinelist.x281
-rw-r--r--noao/onedspec/ecidentify/eclog.x77
-rw-r--r--noao/onedspec/ecidentify/ecmark.x71
-rw-r--r--noao/onedspec/ecidentify/ecnearest.x26
-rw-r--r--noao/onedspec/ecidentify/ecnewfeature.x91
-rw-r--r--noao/onedspec/ecidentify/ecnext.x23
-rw-r--r--noao/onedspec/ecidentify/ecpeak.x24
-rw-r--r--noao/onedspec/ecidentify/ecprevious.x23
-rw-r--r--noao/onedspec/ecidentify/ecrms.x28
-rw-r--r--noao/onedspec/ecidentify/ecshift.x77
-rw-r--r--noao/onedspec/ecidentify/ecshow.x78
-rw-r--r--noao/onedspec/ecidentify/mkpkg39
-rw-r--r--noao/onedspec/ecidentify/t_eciden.x68
-rw-r--r--noao/onedspec/ecidentify/t_ecreid.x181
-rw-r--r--noao/onedspec/ecreidentify.par11
-rw-r--r--noao/onedspec/fitprofs.par29
-rw-r--r--noao/onedspec/fortran/mkpkg10
-rw-r--r--noao/onedspec/fortran/nlcfit.f400
-rw-r--r--noao/onedspec/fortran/polft1.f205
-rw-r--r--noao/onedspec/fortran/trans.f21
-rw-r--r--noao/onedspec/gcurval.dat1
-rw-r--r--noao/onedspec/getairm.x54
-rw-r--r--noao/onedspec/getcalib.x415
-rw-r--r--noao/onedspec/getextn.x209
-rw-r--r--noao/onedspec/hireswcal.cl68
-rw-r--r--noao/onedspec/identify.par33
-rw-r--r--noao/onedspec/identify/autoid/aidautoid.x314
-rw-r--r--noao/onedspec/identify/autoid/aidget.x21
-rw-r--r--noao/onedspec/identify/autoid/aidgraph.x240
-rw-r--r--noao/onedspec/identify/autoid/aidinit.x93
-rw-r--r--noao/onedspec/identify/autoid/aidlog.x57
-rw-r--r--noao/onedspec/identify/autoid/aidset.x162
-rw-r--r--noao/onedspec/identify/autoid/aidshift.x67
-rw-r--r--noao/onedspec/identify/autoid/autoid.h90
-rw-r--r--noao/onedspec/identify/autoid/autoid.x1600
-rw-r--r--noao/onedspec/identify/autoid/mkpkg17
-rw-r--r--noao/onedspec/identify/idcenter.x37
-rw-r--r--noao/onedspec/identify/idcolon.x284
-rw-r--r--noao/onedspec/identify/iddb.x515
-rw-r--r--noao/onedspec/identify/iddelete.x26
-rw-r--r--noao/onedspec/identify/iddofit.x108
-rw-r--r--noao/onedspec/identify/iddoshift.x41
-rw-r--r--noao/onedspec/identify/identify.h90
-rw-r--r--noao/onedspec/identify/identify.key90
-rw-r--r--noao/onedspec/identify/idfitdata.x177
-rw-r--r--noao/onedspec/identify/idgdata.x67
-rw-r--r--noao/onedspec/identify/idgraph.x111
-rw-r--r--noao/onedspec/identify/ididentify.x631
-rw-r--r--noao/onedspec/identify/idinit.x368
-rw-r--r--noao/onedspec/identify/idlabel.x30
-rw-r--r--noao/onedspec/identify/idlinelist.x385
-rw-r--r--noao/onedspec/identify/idlog.x72
-rw-r--r--noao/onedspec/identify/idmap.x375
-rw-r--r--noao/onedspec/identify/idmark.x98
-rw-r--r--noao/onedspec/identify/idnearest.x29
-rw-r--r--noao/onedspec/identify/idnewfeature.x87
-rw-r--r--noao/onedspec/identify/idnoextn.x11
-rw-r--r--noao/onedspec/identify/idpeak.x95
-rw-r--r--noao/onedspec/identify/idrms.x28
-rw-r--r--noao/onedspec/identify/idshift.x106
-rw-r--r--noao/onedspec/identify/idshow.x79
-rw-r--r--noao/onedspec/identify/mkpkg48
-rw-r--r--noao/onedspec/identify/peaks.gx578
-rw-r--r--noao/onedspec/identify/peaks.x578
-rw-r--r--noao/onedspec/identify/reidentify.x482
-rw-r--r--noao/onedspec/identify/t_autoid.x252
-rw-r--r--noao/onedspec/identify/t_identify.x89
-rw-r--r--noao/onedspec/identify/t_reidentify.x1083
-rw-r--r--noao/onedspec/irsiids/addsets.par8
-rw-r--r--noao/onedspec/irsiids/batchred.cl168
-rw-r--r--noao/onedspec/irsiids/batchred.par38
-rw-r--r--noao/onedspec/irsiids/bplot.cl35
-rw-r--r--noao/onedspec/irsiids/bswitch.par15
-rw-r--r--noao/onedspec/irsiids/coefs.par3
-rw-r--r--noao/onedspec/irsiids/coincor.par9
-rw-r--r--noao/onedspec/irsiids/coincor.x123
-rw-r--r--noao/onedspec/irsiids/conversion.x213
-rw-r--r--noao/onedspec/irsiids/doc/addsets.hlp66
-rw-r--r--noao/onedspec/irsiids/doc/batchred.hlp145
-rw-r--r--noao/onedspec/irsiids/doc/bswitch.hlp228
-rw-r--r--noao/onedspec/irsiids/doc/coefs.hlp57
-rw-r--r--noao/onedspec/irsiids/doc/coincor.hlp101
-rw-r--r--noao/onedspec/irsiids/doc/extinct.hlp49
-rw-r--r--noao/onedspec/irsiids/doc/flatdiv.hlp94
-rw-r--r--noao/onedspec/irsiids/doc/flatfit.hlp188
-rw-r--r--noao/onedspec/irsiids/doc/powercor.hlp62
-rw-r--r--noao/onedspec/irsiids/doc/process.hlp20
-rw-r--r--noao/onedspec/irsiids/doc/slist1d.hlp59
-rw-r--r--noao/onedspec/irsiids/doc/subsets.hlp49
-rw-r--r--noao/onedspec/irsiids/doc/sums.hlp44
-rw-r--r--noao/onedspec/irsiids/doc/widstape.hlp90
-rw-r--r--noao/onedspec/irsiids/extinct.cl22
-rw-r--r--noao/onedspec/irsiids/extinct.par11
-rw-r--r--noao/onedspec/irsiids/flatdiv.par12
-rw-r--r--noao/onedspec/irsiids/flatfit.par24
-rw-r--r--noao/onedspec/irsiids/getnimage.x133
-rw-r--r--noao/onedspec/irsiids/idsmtn.h101
-rw-r--r--noao/onedspec/irsiids/irsiids.hd18
-rw-r--r--noao/onedspec/irsiids/mkpkg22
-rw-r--r--noao/onedspec/irsiids/powercor.cl4
-rw-r--r--noao/onedspec/irsiids/powercor.par7
-rw-r--r--noao/onedspec/irsiids/slist1d.par3
-rw-r--r--noao/onedspec/irsiids/subsets.par6
-rw-r--r--noao/onedspec/irsiids/sums.par8
-rw-r--r--noao/onedspec/irsiids/t_addsets.x195
-rw-r--r--noao/onedspec/irsiids/t_bswitch.x924
-rw-r--r--noao/onedspec/irsiids/t_coefs.x88
-rw-r--r--noao/onedspec/irsiids/t_coincor.x102
-rw-r--r--noao/onedspec/irsiids/t_flatdiv.x276
-rw-r--r--noao/onedspec/irsiids/t_flatfit.x740
-rw-r--r--noao/onedspec/irsiids/t_slist1d.x163
-rw-r--r--noao/onedspec/irsiids/t_subsets.x121
-rw-r--r--noao/onedspec/irsiids/t_sums.x239
-rw-r--r--noao/onedspec/irsiids/t_widstape.x343
-rw-r--r--noao/onedspec/irsiids/widstape.par8
-rw-r--r--noao/onedspec/lcalib.par10
-rw-r--r--noao/onedspec/mkpkg72
-rw-r--r--noao/onedspec/mkspec.par11
-rw-r--r--noao/onedspec/names.par7
-rw-r--r--noao/onedspec/ndprep.cl65
-rw-r--r--noao/onedspec/odcombine.par54
-rw-r--r--noao/onedspec/odcombine/mkpkg18
-rw-r--r--noao/onedspec/odcombine/odcombine.par54
-rw-r--r--noao/onedspec/odcombine/src/generic/icaclip.x2206
-rw-r--r--noao/onedspec/odcombine/src/generic/icaverage.x406
-rw-r--r--noao/onedspec/odcombine/src/generic/iccclip.x1790
-rw-r--r--noao/onedspec/odcombine/src/generic/icgdata.x1207
-rw-r--r--noao/onedspec/odcombine/src/generic/icgrow.x263
-rw-r--r--noao/onedspec/odcombine/src/generic/icmedian.x692
-rw-r--r--noao/onedspec/odcombine/src/generic/icmm.x644
-rw-r--r--noao/onedspec/odcombine/src/generic/icomb.x1917
-rw-r--r--noao/onedspec/odcombine/src/generic/icpclip.x878
-rw-r--r--noao/onedspec/odcombine/src/generic/icsclip.x1922
-rw-r--r--noao/onedspec/odcombine/src/generic/icsigma.x434
-rw-r--r--noao/onedspec/odcombine/src/generic/icsort.x1096
-rw-r--r--noao/onedspec/odcombine/src/generic/icstat.x892
-rw-r--r--noao/onedspec/odcombine/src/generic/mkpkg25
-rw-r--r--noao/onedspec/odcombine/src/generic/xtimmap.x1080
-rw-r--r--noao/onedspec/odcombine/src/icaclip.gx575
-rw-r--r--noao/onedspec/odcombine/src/icaverage.gx114
-rw-r--r--noao/onedspec/odcombine/src/iccclip.gx471
-rw-r--r--noao/onedspec/odcombine/src/icemask.x114
-rw-r--r--noao/onedspec/odcombine/src/icgdata.gx307
-rw-r--r--noao/onedspec/odcombine/src/icgrow.gx135
-rw-r--r--noao/onedspec/odcombine/src/icgscale.x88
-rw-r--r--noao/onedspec/odcombine/src/ichdr.x55
-rw-r--r--noao/onedspec/odcombine/src/icimstack.x186
-rw-r--r--noao/onedspec/odcombine/src/iclog.x422
-rw-r--r--noao/onedspec/odcombine/src/icmask.com8
-rw-r--r--noao/onedspec/odcombine/src/icmask.h9
-rw-r--r--noao/onedspec/odcombine/src/icmask.x499
-rw-r--r--noao/onedspec/odcombine/src/icmedian.gx231
-rw-r--r--noao/onedspec/odcombine/src/icmm.gx189
-rw-r--r--noao/onedspec/odcombine/src/icomb.gx674
-rw-r--r--noao/onedspec/odcombine/src/icombine.com45
-rw-r--r--noao/onedspec/odcombine/src/icombine.h53
-rw-r--r--noao/onedspec/odcombine/src/icombine.x476
-rw-r--r--noao/onedspec/odcombine/src/icpclip.gx233
-rw-r--r--noao/onedspec/odcombine/src/icpmmap.x34
-rw-r--r--noao/onedspec/odcombine/src/icrmasks.x41
-rw-r--r--noao/onedspec/odcombine/src/icscale.x351
-rw-r--r--noao/onedspec/odcombine/src/icsclip.gx504
-rw-r--r--noao/onedspec/odcombine/src/icsection.x94
-rw-r--r--noao/onedspec/odcombine/src/icsetout.x322
-rw-r--r--noao/onedspec/odcombine/src/icsigma.gx122
-rw-r--r--noao/onedspec/odcombine/src/icsort.gx386
-rw-r--r--noao/onedspec/odcombine/src/icstat.gx238
-rw-r--r--noao/onedspec/odcombine/src/mkpkg62
-rw-r--r--noao/onedspec/odcombine/src/tymax.x27
-rw-r--r--noao/onedspec/odcombine/src/xtimmap.com8
-rw-r--r--noao/onedspec/odcombine/src/xtimmap.gx552
-rw-r--r--noao/onedspec/odcombine/src/xtprocid.x38
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icaclip.x2206
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icaverage.x522
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/iccclip.x1790
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icgdata.x1558
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icgrow.x263
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icmedian.x692
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icmm.x644
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icomb.x2054
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icpclip.x878
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icsclip.x1922
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icsigma.x562
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icsort.x1096
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/icstat.x892
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/mkpkg25
-rw-r--r--noao/onedspec/odcombine/srcwt/generic/xtimmap.x1079
-rw-r--r--noao/onedspec/odcombine/srcwt/icaclip.gx575
-rw-r--r--noao/onedspec/odcombine/srcwt/icaverage.gx143
-rw-r--r--noao/onedspec/odcombine/srcwt/iccclip.gx471
-rw-r--r--noao/onedspec/odcombine/srcwt/icemask.x128
-rw-r--r--noao/onedspec/odcombine/srcwt/icgdata.gx397
-rw-r--r--noao/onedspec/odcombine/srcwt/icgdata.gxBAK307
-rw-r--r--noao/onedspec/odcombine/srcwt/icgrow.gx135
-rw-r--r--noao/onedspec/odcombine/srcwt/icgscale.x92
-rw-r--r--noao/onedspec/odcombine/srcwt/ichdr.x55
-rw-r--r--noao/onedspec/odcombine/srcwt/icimstack.x186
-rw-r--r--noao/onedspec/odcombine/srcwt/iclog.x422
-rw-r--r--noao/onedspec/odcombine/srcwt/icmask.com8
-rw-r--r--noao/onedspec/odcombine/srcwt/icmask.h9
-rw-r--r--noao/onedspec/odcombine/srcwt/icmask.x499
-rw-r--r--noao/onedspec/odcombine/srcwt/icmedian.gx231
-rw-r--r--noao/onedspec/odcombine/srcwt/icmm.gx189
-rw-r--r--noao/onedspec/odcombine/srcwt/icomb.gx711
-rw-r--r--noao/onedspec/odcombine/srcwt/icombine.com46
-rw-r--r--noao/onedspec/odcombine/srcwt/icombine.h56
-rw-r--r--noao/onedspec/odcombine/srcwt/icombine.x488
-rw-r--r--noao/onedspec/odcombine/srcwt/icpclip.gx233
-rw-r--r--noao/onedspec/odcombine/srcwt/icpmmap.x34
-rw-r--r--noao/onedspec/odcombine/srcwt/icrmasks.x41
-rw-r--r--noao/onedspec/odcombine/srcwt/icscale.x391
-rw-r--r--noao/onedspec/odcombine/srcwt/icsclip.gx504
-rw-r--r--noao/onedspec/odcombine/srcwt/icsection.x94
-rw-r--r--noao/onedspec/odcombine/srcwt/icsetout.x322
-rw-r--r--noao/onedspec/odcombine/srcwt/icsigma.gx154
-rw-r--r--noao/onedspec/odcombine/srcwt/icsort.gx386
-rw-r--r--noao/onedspec/odcombine/srcwt/icstat.gx238
-rw-r--r--noao/onedspec/odcombine/srcwt/mkpkg62
-rw-r--r--noao/onedspec/odcombine/srcwt/tymax.x27
-rw-r--r--noao/onedspec/odcombine/srcwt/xtimmap.com8
-rw-r--r--noao/onedspec/odcombine/srcwt/xtimmap.gx552
-rw-r--r--noao/onedspec/odcombine/srcwt/xtprocid.x38
-rw-r--r--noao/onedspec/odcombine/t_odcombine.x1071
-rw-r--r--noao/onedspec/odcombine/x_odcombine.x1
-rw-r--r--noao/onedspec/odropenp.x92
-rw-r--r--noao/onedspec/onedspec.cl57
-rw-r--r--noao/onedspec/onedspec.hd58
-rw-r--r--noao/onedspec/onedspec.men51
-rw-r--r--noao/onedspec/onedspec.par10
-rw-r--r--noao/onedspec/refspectra.par16
-rw-r--r--noao/onedspec/reidentify.par36
-rw-r--r--noao/onedspec/rspectext.cl115
-rw-r--r--noao/onedspec/rstext.par4
-rw-r--r--noao/onedspec/sapertures.par16
-rw-r--r--noao/onedspec/sarith.par22
-rw-r--r--noao/onedspec/sbands.par8
-rw-r--r--noao/onedspec/scombine/README17
-rw-r--r--noao/onedspec/scombine/generic/icaclip.x555
-rw-r--r--noao/onedspec/scombine/generic/icaverage.x84
-rw-r--r--noao/onedspec/scombine/generic/iccclip.x453
-rw-r--r--noao/onedspec/scombine/generic/icgrow.x76
-rw-r--r--noao/onedspec/scombine/generic/icmedian.x139
-rw-r--r--noao/onedspec/scombine/generic/icmm.x152
-rw-r--r--noao/onedspec/scombine/generic/icpclip.x224
-rw-r--r--noao/onedspec/scombine/generic/icsclip.x486
-rw-r--r--noao/onedspec/scombine/generic/icsort.x275
-rw-r--r--noao/onedspec/scombine/generic/mkpkg16
-rw-r--r--noao/onedspec/scombine/icgdata.x199
-rw-r--r--noao/onedspec/scombine/iclog.x301
-rw-r--r--noao/onedspec/scombine/icombine.com36
-rw-r--r--noao/onedspec/scombine/icombine.h74
-rw-r--r--noao/onedspec/scombine/icombine.x174
-rw-r--r--noao/onedspec/scombine/icscale.x463
-rw-r--r--noao/onedspec/scombine/icstat.x160
-rw-r--r--noao/onedspec/scombine/icsum.x48
-rw-r--r--noao/onedspec/scombine/iscombine.key23
-rw-r--r--noao/onedspec/scombine/iscombine.par18
-rw-r--r--noao/onedspec/scombine/mkpkg35
-rw-r--r--noao/onedspec/scombine/scombine.par37
-rw-r--r--noao/onedspec/scombine/t_scombine.x630
-rw-r--r--noao/onedspec/scombine/x_scombine.x1
-rw-r--r--noao/onedspec/scoords.par5
-rw-r--r--noao/onedspec/scopy.cl30
-rw-r--r--noao/onedspec/scopy.par17
-rw-r--r--noao/onedspec/sensfunc.par17
-rw-r--r--noao/onedspec/sensfunc/mkpkg38
-rw-r--r--noao/onedspec/sensfunc/sensfunc.h64
-rw-r--r--noao/onedspec/sensfunc/sensfunc.key81
-rw-r--r--noao/onedspec/sensfunc/sfadd.x105
-rw-r--r--noao/onedspec/sensfunc/sfapertures.x27
-rw-r--r--noao/onedspec/sensfunc/sfcgraph.x104
-rw-r--r--noao/onedspec/sensfunc/sfcolon.x193
-rw-r--r--noao/onedspec/sensfunc/sfcolors.x28
-rw-r--r--noao/onedspec/sensfunc/sfcomposite.x147
-rw-r--r--noao/onedspec/sensfunc/sfdata.x59
-rw-r--r--noao/onedspec/sensfunc/sfdelete.x127
-rw-r--r--noao/onedspec/sensfunc/sfeout.x114
-rw-r--r--noao/onedspec/sensfunc/sfextinct.x226
-rw-r--r--noao/onedspec/sensfunc/sffit.x78
-rw-r--r--noao/onedspec/sensfunc/sfginit.x89
-rw-r--r--noao/onedspec/sensfunc/sfgraph.x289
-rw-r--r--noao/onedspec/sensfunc/sfimage.x234
-rw-r--r--noao/onedspec/sensfunc/sfmarks.x46
-rw-r--r--noao/onedspec/sensfunc/sfmove.x166
-rw-r--r--noao/onedspec/sensfunc/sfnearest.x69
-rw-r--r--noao/onedspec/sensfunc/sfoutput.x114
-rw-r--r--noao/onedspec/sensfunc/sfreset.x62
-rw-r--r--noao/onedspec/sensfunc/sfrms.x43
-rw-r--r--noao/onedspec/sensfunc/sfsensfunc.x255
-rw-r--r--noao/onedspec/sensfunc/sfshift.x81
-rw-r--r--noao/onedspec/sensfunc/sfstats.x152
-rw-r--r--noao/onedspec/sensfunc/sfstds.x266
-rw-r--r--noao/onedspec/sensfunc/sftitle.x23
-rw-r--r--noao/onedspec/sensfunc/sfundelete.x141
-rw-r--r--noao/onedspec/sensfunc/sfvstats.x104
-rw-r--r--noao/onedspec/sensfunc/sfweights.x51
-rw-r--r--noao/onedspec/sensfunc/t_sensfunc.x99
-rw-r--r--noao/onedspec/setdisp.par6
-rw-r--r--noao/onedspec/sfit.par25
-rw-r--r--noao/onedspec/sflip.par6
-rw-r--r--noao/onedspec/sinterp.par14
-rw-r--r--noao/onedspec/skytweak.par19
-rw-r--r--noao/onedspec/slist.par3
-rw-r--r--noao/onedspec/smw/README6
-rw-r--r--noao/onedspec/smw/funits.x445
-rw-r--r--noao/onedspec/smw/mkpkg48
-rw-r--r--noao/onedspec/smw/shdr.x1269
-rw-r--r--noao/onedspec/smw/smwclose.x46
-rw-r--r--noao/onedspec/smw/smwct.x19
-rw-r--r--noao/onedspec/smw/smwctfree.x19
-rw-r--r--noao/onedspec/smw/smwctran.gx166
-rw-r--r--noao/onedspec/smw/smwctran.x312
-rw-r--r--noao/onedspec/smw/smwdaxis.x109
-rw-r--r--noao/onedspec/smw/smwequispec.x86
-rw-r--r--noao/onedspec/smw/smwesms.x96
-rw-r--r--noao/onedspec/smw/smwgapid.x30
-rw-r--r--noao/onedspec/smw/smwgwattrs.x134
-rw-r--r--noao/onedspec/smw/smwmerge.x102
-rw-r--r--noao/onedspec/smw/smwmultispec.x30
-rw-r--r--noao/onedspec/smw/smwmw.x38
-rw-r--r--noao/onedspec/smw/smwnd.x19
-rw-r--r--noao/onedspec/smw/smwndes.x82
-rw-r--r--noao/onedspec/smw/smwnewcopy.x58
-rw-r--r--noao/onedspec/smw/smwoldms.x101
-rw-r--r--noao/onedspec/smw/smwonedspec.x109
-rw-r--r--noao/onedspec/smw/smwopen.x70
-rw-r--r--noao/onedspec/smw/smwopenim.x69
-rw-r--r--noao/onedspec/smw/smwsapid.x40
-rw-r--r--noao/onedspec/smw/smwsaveim.x251
-rw-r--r--noao/onedspec/smw/smwsaxes.x247
-rw-r--r--noao/onedspec/smw/smwsctran.x96
-rw-r--r--noao/onedspec/smw/smwsmw.x21
-rw-r--r--noao/onedspec/smw/smwswattrs.x162
-rw-r--r--noao/onedspec/smw/units.x529
-rw-r--r--noao/onedspec/specplot.h49
-rw-r--r--noao/onedspec/specplot.key134
-rw-r--r--noao/onedspec/specplot.par28
-rw-r--r--noao/onedspec/specshift.par4
-rw-r--r--noao/onedspec/splot.par52
-rw-r--r--noao/onedspec/splot/anshdr.x84
-rw-r--r--noao/onedspec/splot/autoexp.x79
-rw-r--r--noao/onedspec/splot/avgsnr.x72
-rw-r--r--noao/onedspec/splot/conflam.x28
-rw-r--r--noao/onedspec/splot/confnu.x28
-rw-r--r--noao/onedspec/splot/deblend.x627
-rw-r--r--noao/onedspec/splot/eqwidth.x109
-rw-r--r--noao/onedspec/splot/eqwidthcp.x240
-rw-r--r--noao/onedspec/splot/fixx.x27
-rw-r--r--noao/onedspec/splot/flatten.x110
-rw-r--r--noao/onedspec/splot/fudgept.x38
-rw-r--r--noao/onedspec/splot/fudgex.x46
-rw-r--r--noao/onedspec/splot/getimage.x159
-rw-r--r--noao/onedspec/splot/gfit.x391
-rw-r--r--noao/onedspec/splot/mkpkg38
-rw-r--r--noao/onedspec/splot/mktitle.x41
-rw-r--r--noao/onedspec/splot/plotstd.x70
-rw-r--r--noao/onedspec/splot/replot.x27
-rw-r--r--noao/onedspec/splot/smooth.x54
-rw-r--r--noao/onedspec/splot/spdeblend.x819
-rw-r--r--noao/onedspec/splot/splabel.x112
-rw-r--r--noao/onedspec/splot/splot.key116
-rw-r--r--noao/onedspec/splot/splot.log8
-rw-r--r--noao/onedspec/splot/splot.x605
-rw-r--r--noao/onedspec/splot/splotcolon.x263
-rw-r--r--noao/onedspec/splot/splotfun.x127
-rw-r--r--noao/onedspec/splot/stshelp.key7
-rw-r--r--noao/onedspec/splot/stshelp.x34
-rw-r--r--noao/onedspec/splot/sumflux.x165
-rw-r--r--noao/onedspec/splot/usercoord.x94
-rw-r--r--noao/onedspec/splot/voigt.x71
-rw-r--r--noao/onedspec/splot/wrspect.x397
-rw-r--r--noao/onedspec/standard.key11
-rw-r--r--noao/onedspec/standard.par21
-rw-r--r--noao/onedspec/t_calibrate.x437
-rw-r--r--noao/onedspec/t_deredden.x361
-rw-r--r--noao/onedspec/t_dopcor.x293
-rw-r--r--noao/onedspec/t_fitprofs.x1151
-rw-r--r--noao/onedspec/t_lcalib.x98
-rw-r--r--noao/onedspec/t_mkspec.x120
-rw-r--r--noao/onedspec/t_names.x45
-rw-r--r--noao/onedspec/t_rstext.x91
-rw-r--r--noao/onedspec/t_sapertures.x428
-rw-r--r--noao/onedspec/t_sarith.x1423
-rw-r--r--noao/onedspec/t_sbands.x585
-rw-r--r--noao/onedspec/t_scoords.x179
-rw-r--r--noao/onedspec/t_sfit.x986
-rw-r--r--noao/onedspec/t_sflip.x145
-rw-r--r--noao/onedspec/t_sinterp.x232
-rw-r--r--noao/onedspec/t_slist.x105
-rw-r--r--noao/onedspec/t_specplot.x2030
-rw-r--r--noao/onedspec/t_specshift.x222
-rw-r--r--noao/onedspec/t_standard.x835
-rw-r--r--noao/onedspec/t_tweak.x1352
-rw-r--r--noao/onedspec/telluric.par21
-rw-r--r--noao/onedspec/wspectext.cl47
-rw-r--r--noao/onedspec/x_onedspec.x43
519 files changed, 131524 insertions, 0 deletions
diff --git a/noao/onedspec/Revisions b/noao/onedspec/Revisions
new file mode 100644
index 00000000..6da71399
--- /dev/null
+++ b/noao/onedspec/Revisions
@@ -0,0 +1,5321 @@
+.help revisions Jun88 noao.onedspec
+.nf
+
+splot/eqwidthcp.x
+ The 'sg' and 'lg' pointers were allocated as TY_REAL and re-allocated
+ as TY_INT (5/4/13)
+
+splot/anshdr.x
+ Added a F_FLUSHNL flag to the logfile descriptors to flush the
+ data as it is written (5/12/12, MJF)
+
+splot/spdeblend.x
+ Added overplotting of individual components. (12/5/11, Valdes)
+
+====
+2.16
+====
+
+hireswcal.cl
+ A script I wrote for Simon Schuler to apply the wavelength calibrations
+ that he got for hires spectra from Geoff Marcy. This is put here for
+ safe keeping. (11/16/11, Valdes)
+
+t_deredden.x
+ There was a small error in coding the formulae of Cardelli, et al.
+ (6/28/11, Valdes)
+
+splot/spdeblend.x
+ An integer allocated array was being freed as a real array causing
+ an error with deblending on 64-bit systems. (6/6/11, Valdes)
+
+=======
+2.15.1a
+=======
+
+======
+2.15.1
+======
+
+scombine/icgdata.x
+ This code uses the SX array to pass in mask values. The code, taken from
+ an old version of imcombine, treats the mask array as integers. All the
+ dereferencing of this mask array were changed to reals.
+
+scombine/t_scombine.x
+ The SX pointer (allocated as real) was being used in a Memi, causing
+ a segfault on some 64-bit systems (4/3/11/, MJF)
+
+specplot.h
+ Fixed improper use of P2R in macro. (3/31/11, MJF)
+
+continuum.par
+odcombine.par
+sfit.par
+splot.par
+ The prompt strings which said the "grow" parameter is in pixels were
+ changed to remove this. The grow parameter is in user coordinate units.
+ (6/28/10, Valdes)
+
+=======
+V2.14.1
+=======
+
+t_specplot.x
+specplot.par
+doc/specplot.hlp
+ Added a new "transform" parameter to allow scaling the spectrum pixel
+ values. Currently on "log" is implemented. (1/5/09, Valdes)
+
+doc/splot.hlp
+ The description of the 'e' key incorrectly said a core flux is
+ output. (8/22/08, Valdes)
+
+dispcor.par
+ Changed "Conserve flux" to "Conserve total flux" per user request.
+ (6/13/08)
+
+rspectext.cl
+ Added "addonly" values to all hedit commands. (4/1/08, Valdes)
+
+odcombine/t_odcombine.x
+ Fixed some procedure calls being closed with a ']' instead of a ')'
+ (2/17/08, MJF)
+
+ecidentify/eclinelist.x
+ A check is made that the second closest match has a match distance
+ more that 25% greater than the nearest match. (12/10/07, Valdes)
+
+t_dopcor.x
+ For clarity when the velocity applied is in km/s this is used in the
+ log and in DOPCORn keywords. (12/10/07, Valdes)
+
+=====
+V2.14
+=====
+
+doc/standard.hlp
+ Clarified the equations and formating. There was an inconsistency
+ between the Vega flux and magnitude given in the text. Either value
+ could be changed but the old version of this task was based on the
+ specified Vega flux so the magnitude was changes (0.048-0.0336)
+ to make the description consistent. (4/3/07, Valdes)
+
+splot/wrspect.x
+ For log sampled data (dc-flag=1) that also has a ltv offset the
+ new WCS for the output was wrong. (12/4/06, Valdes)
+
+smw/shdr.x
+ The shdr_rebin procedure was rebinning the target spectrum to its natural
+ units while the reference spectrum might be in different units. The
+ correct thing to do is rebin in the reference units. (10/27/06, Valdes)
+
+
+=======
+V2.12.3
+=======
+
+splot/splot.x
+ Needed to initialize the aperture so that the aperture selection
+ behavior is consistent when calling splot more than once. (5/16/06, Valdes)
+
+doc/specplot.hlp
+ Added a quick example illustrating using batch mode plotting.
+ (5/2/05, Valdes)
+
+t_specplot.x
+ To support cursor file input without including the x, y, wcs fields
+ the call to sp_nearest was modified to avoid floating exceptions.
+ (5/2/05, Valdes)
+
+t_sarith.x
+ Error handling was improved for onedspec output. When no pixels
+ were selected by using w1/w2 the warning was reported but then
+ a segmentation error would occur when trying to close the output
+ image. (3/8/05, Valdes)
+
+t_tweak.x
+doc/telluric.hlp
+ The normalization used is now printed. (1/12/05, Valdes)
+
+t_dopcor.x
+doc/dopcor.hlp
+ A keyword is added to log the operation. (10/29/04, Valdes)
+
+t_deredden.x
+ Adjusted the error reporting to print the warning before closing and
+ deleting the output image. (9/27/04, Valdes)
+
+ecidentify/ecfitdata.x
+ The EC_FITPT routine used the wrong pointer for the physical to logical
+ coordinate conversion. (9/10/04, Valdes)
+
+odcombine/ +
+doc/odcombine.hlp +
+x_onedspec.x
+onedspec.cl
+onedspec.men
+onedspec.hd
+mkpkg
+ Added a new task ODCOMBINE which is layered more directly on the
+ source for IMCOMBINE. This version supports bad pixel masks as well
+ as most of the new features of IMCOMBINE. (6/21/04, Valdes)
+
+scombine/x_scombine.x +
+scombine/mkpkg
+onedspec.cl
+mkpkg
+ Packaged SCOMBINE as its own executable noaobin$x_scombine.e.
+ (6/21/04, Valdes)
+
+t_standard.x
+ In case someone puts the query parameter "star_name" on the command
+ line and the calibration cannot be found the task will not only try
+ twice before aborting rather than go into an infinite loop.
+ (5/21/04, Valdes)
+
+dispcor/t_dispcor.x
+dispcor/dispcor.x
+dispcor.par
+doc/dispcor.hlp
+ Added the new parameter "blank" to control the output values when there
+ are no input values; i.e. the out of bounds values. (5/18/04, Valdes)
+
+dispcor/t_discpor.x
+ For 2D spectra the "global" option was not working. (5/14/04, Valdes)
+
+scombine/t_scombine.x
+ A check for the input format was added. If the input is 2D spectra
+ then a format error is printed. (5/14/04, Valdes)
+
+identify/autoid/autoid.x
+ Changed "AID_NT(aid) = min (2 * AID_NR(aid), AID_NTF(aid))" to
+ "AID_NT(aid) = AID_NTF(aid)". While this may have speed consequences
+ it avoids preselecting target lines. (4/23/04, Cooke, Valdes)
+
+doc/scombine.hlp
+ Made corrections suggested by Francois Schweizer on 2/25/04.
+ (3/10/04, valdes with input from schweizer)
+
+t_sarith.x
+ When the task was modified 8/3/02 to add something to the WCS the
+ wrong pointer was used resulting in a segmentation violation when
+ using the "merge" option. (3/9/04, Valdes)
+
+=======
+V2.12.2
+=======
+
+identify/autoid/aidlog.x
+ Added a test to avoid an arithmetic error if the dispersion
+ turns out to be zero. (1/29/04, Valdes)
+
+identify/autoid/aidautoid.x
+identify/autoid/autoid.x
+ Two new debugging characters, "nm", were added.
+ (1/29/04, Valdes)
+
+aidpars.par
+ Default values were changed:
+ cddir: "unknown" -> "sign"
+ ntarget: 30 -> 100
+ ndmax: 20 -> 500
+ fmatch: 0.3 -> 0.2
+ (1/29/04, Valdes)
+
+identify/autoid/autoid.x
+ The parameters that can be specified by header keywords can now be
+ either the keyword or the keyword prefixed by '!'. This was done
+ because there are a number of other IRAF tasks that use the '!' prefix
+ and users may be confused and use this syntax. (1/29/04, Valdes)
+
+identify/autoid/autoid.x
+aidpars.par
+ The number of highest vote potential dispersions checked was
+ previously limited to a maximum of three times the number of target lines.
+ Now the number may be as large as specified by the "ndmax" parameter.
+ The default parameter value was greatly increased to 500.
+ (1/29/04, Valdes)
+
+identify/autoid/aidautoid.x
+doc/aidpars.hlp
+ The algorithm was modified to iterate on the pattern parameter
+ "npattern". After exhausting the search with the initial
+ number of lines per pattern the value is reduced successively by
+ one down to the minimum of 3. This makes the algorithm take longer
+ but the search is more exhaustive. Use of larger patterns initially
+ allows finding fewer and more likely candidates first to speed
+ a solution. (1/29/04, Valdes)
+
+aidpars.par
+identify/autoid/autoid.x
+doc/aidpars.hlp
+ The "rms" parameter is now specified in units of the "fwidth"
+ parameter rather than in pixels. This is because if fwidth is
+ made larger to deal with broad lines (i.e. a wide slit) then the
+ expected uncertainties in pixel centroids will be larger. The
+ default value was changed from 0.3 pixels to 0.1 of fwidth.
+ (1/29/04, Valdes)
+
+identify/autoid/autoid.x
+doc/aidpars.hlp
+identify/id_peaks.x
+ The selection of target lines was changed from using id_peaks to a
+ new routine id_upeaks. In the former routine the ntarget strongest
+ peaks are selected regardless of position in the spectrum. But this
+ can result in no lines being used in some parts of the spectrum if
+ the spectrum is dominated by strong lines in just one part of the
+ spectrum. The id_upeaks routine finds lines over the whole spectrum
+ by dividing the spectrum into regions and then alternatively selecting
+ the brightest line in each region until the desired number of lines
+ is obtained. In this case the number of regions if hardwired at 5.
+ (1/29/04, Valdes)
+
+aidpars.par
+identify/autoid/autoid.h
+doc/aidpars.hlp
+identify/autoid/aidinit.x
+identify/autoid/aidset.x
+identify/autoid/autoid.x
+ Added two new parameters to aidpars. The first is "maxnl" which
+ defines the maximum non-linearity to accept after a dispersion function
+ fit. Previously the maximum was hardwired in the code to be 0.5%
+ which was too small for many applications. The default is set at
+ 2%. The second new parameter is "crquad" which defines a quadratic
+ correction to the pixel positions of detected lines in order to
+ "linearize" the pattern of line spacings which are matched against
+ the coordinate list. This was found to not be as important as the
+ "maxnl" limitation in handling non-linear dispersion and has a default
+ value of zero. (1/29/04)
+
+sensfunc/sfstds.x
+ Added a check for names with a kernel section. Specifically, names
+ that end in ']'. (12/18/03, Valdes)
+
+smw/shdr.x
+ Now if CUNITn is specified in velocity (m/s or km/s) and if
+ CTYPEn is VELO (or VELOCITY) then internally the velocity zero point
+ reference of 21 centimenters will be automatically added.
+ (8/19/03, Valdes)
+
+smw/shdr.x
+ Experience has shown that data with no units that users want to
+ import is mostly in Angstroms. So rather than use the old FITS
+ standard that units are meters it will now assume Angstroms.
+ (8/15/03, Valdes)
+
+smw/smwaxes.x
+ A check is made if the physical axis is ra or dec in which case the
+ image is considered not to be dispersion corrected. (8/5/03, Valdes)
+
+smw/smwsctran.x
+ If there is an error the physical coordinate system is used instead of
+ the world coordinate system. This is meant to allow coupled WCS
+ (particularly celestial WCS) to be used without an error.
+ (8/5/03, Valdes)
+
+t_dopcor.x
+ Moved the erract before the imunmap/imdelete to produce the correct
+ error message. (7/8/03, Valdes)
+
+dispcor/t_disptrans.x
+ When the subroutine dispcor was modified with an extra arguement this
+ task was not modified. The extra argument was added. (6/4/03, Valdes)
+
+identify/idinit.x
+ The restore function was not resetting the shift value at the right
+ time. This had the effect of causing the user shift to be wrong
+ in the REIDENTIFY output when refit=no. (5/27/03, Valdes)
+
+t_tweak.x
+ The statistics computation is now relative to neighboring points.
+ This change was developed working with the Coude-Feed spectral atlas
+ pipeline. (4/3/03, Valdes)
+
+t_tweak.x
+ WHen an error, such as calibration values too low, occurs in twk_fit it
+ would exit without closing the graphics. Now the graphics is opened with
+ AW_DEFER and the error action is to first close the graphics before
+ returning to the calling routine with the error. (2/28/03, Valdes)
+
+t_tweak.x (Bug 520)
+ The erract was after error cleanup which could cause the incorrect
+ error to be reported. Since the error action is to WARN it makes sense
+ to immediate report the error and then do the clean up. (2/24/03, Valdes)
+
+dispcor/refspectra.x
+dispcor/refgspec.x
+dispcor/refspectra.com
+ The "select" parameter is now included in the common so that
+ refgspec does not try to look for the sort and group keyword if
+ it is not needed. (9/5/02, Valdes)
+
+t_sarith.x
+ When a new MWCS pointer is created the attribute "sformat" has to
+ be added. (8/3/02, Valdes)
+
+t_fitprofs.x
+ Incorrectly used pargi instead of pargb. (8/2/02, Valdes)
+
+splot/getimage.x
+ Gt_setr was being called with an integer argument. (8/2/02, Valdes)
+
+
+splot/eqwidthcp.x
+ 1. A pargd was used with the real variable cont.
+ 2. Variable pi never used.
+ (8/2/02, Valdes)
+
+scombine/icscale.x
+ Needed to dereference the error string in icgscale.
+ (8/2/02, Valdes)
+
+t_dispcor.x
+ String argument was incorrectly given as NULL.
+ (8/2/02, Valdes)
+
+t_dispcor.x
+dispcor/dcio.x
+sensfunc/sfoutput.x
+ The axis variable is not used and was deleted. (8/2/02, Valdes)
+
+=======
+V2.12.1
+=======
+
+scombine/t_scombine.x
+doc/scombine.hlp
+ The rejection option is ignored when "combine=sum". The documentation
+ did not make this clear and the output log would show whatever was
+ set for the rejection parameter. The help was clarified and the
+ code changed to show a rejection of none. (7/5/02, Valdes)
+
+irsiids/libpkg.a
+ Removed a stray libpkg.a link (6/5/02, MJF)
+
+identify/idcenter.x
+ecidentify/eccenter.x
+ The handling of INDEF values between reals and doubles was not done
+ correctly. (5/28/02, Valdes)
+
+=====
+V2.12
+=====
+
+t_slist.x
+irsiids/t_slist1d.x
+ The SMW pointer was being closed without resetting the pointer in
+ the SHDR structure. (5/30/02, Valdes)
+
+identify/iddofit.x
+identify/idinit.x
+ When deleting features during the fitting the memory allocated for
+ the labels was not being updated correctly. Also in freeing the
+ feature memory there was no need to free labels beyond the number
+ of features. (4/30/02, Valdes)
+
+splot/wrspect.x
+ Removed possibility of an infinite loop and make error checking
+ a little more obvious. (2/28/02, Valdes, 3/21/02, Valdes))
+
+dispcor/dcio.x
+ Removed the special test for dispersion corrected data with no DCLOG
+ keyword which prevents re-calibration. (2/4/02, Valdes)
+
+doc/bplot.hlp
+ Fixed a typo in the help page. (1/5/02, MJF)
+
+mkpkg
+ Added missing <mach.h> dependency to getcalib.x (12/13/01, MJF)
+
+sensfunc/mkpkg
+ Removed unneeded dependencies for sfimage.x (12/13/01, MJF)
+
+irsiids/t_flatfit.x
+irsiids/t_subsets.x
+irsiids/t_sums.x
+t_mkspec.x
+t_sinterp.x
+irsiids/t_bswitch.x
+ imgl1r() called with extra arg. (9/20/01, Valdes)
+
+dispcor/t_dispcor.x
+ dc_gec() missing arg. (9/20/01, Valdes)
+
+identify/autoid/aidautoid.x
+ aid_eval() called with extra arg. (9/20/01, Valdes)
+
+identify/idinit.x
+ id_gid() define as a function but should be a subroutine.
+ (9/20/01, Valdes)
+
+identify/idshift.x
+ id_getid() called as subroutine. (9/20/01, Valdes)
+
+ecidentify/ecffit/ecfshift.x
+ ecf_pshift() was incorrect. (9/20/01, Valdes)
+
+identify/iddofit.x
+ When removing lines deleted during fitting the labels were not being
+ correctly maintained. (8/2/01, Valdes)
+
+splot/eqwidth.x
+ Instead of refusing to compute errors when there is a negative value
+ (anywhere in the spectrum and not just in the region) the routine
+ simply sets the pixel value in evaluating the sigma.
+ (5/16/01, Valdes)
+
+doc/deredden.hlp
+ Added information about the range of validity of the extinction
+ function. (4/9/01, Valdes)
+
+t_fitprof.x
+ The input image was being unmmaped before the output image which
+ can cause problems. (3/9/01, Valdes)
+
+splot/getimage.x
+ The nline parameter was not being set to the current line which could
+ cause the ')' and '(' to misbehave. (2/15/01, Valdes)
+
+doc/sarith.hlp
+ The help said incorrectly that flux conservation was used.
+ (1/17/01, Valdes)
+
+smw/smwsaveim.x
+smw/smwequispec.x
+ The APNUM keywords change to AP when the aperture number is greater
+ than 999. (11/8/00, Valdes)
+
+identify/iddb.x
+ The user units string is now recorded. This is to allow velocity
+ units to include the reference point. (5/4/00, Valdes)
+
+splot/eqwidthcp.x
+ When x becomes large enough the parabola fitting routine has a
+ divide by zero. The parabola fitting routine was converted to work in
+ double, to check the x values for degeneracy, and to avoid the squares
+ of large numbers. The calling routine was also modified to work in
+ double. (2/15/00, Valdes)
+
+smw/smwonedspec.x
+smw/smwoldms.x
+ Put an error check on imdelf. The list expansion should have expanded
+ to only the header keywords present but for some undiagnosed reason
+ sometimes the list expansion returned a non-existant keyword which
+ wouod cause an error. (2/2/00)
+
+dispcor/dispcor.x
+ The input linei argument should really be the aperture number needed
+ to go back to pixel coordinates. The change was to use this value only
+ to flag if the WCS is 1D (for long slit data) and otherwise to do
+ what the procedure did before. (2/1/00, Valdes)
+
+dispcor/t_dispcor.x
+ During changes for 2D dispcor the arguments for a routine changed and
+ this was not reflected in the dc_global routines resulting in a
+ segmentation violation when the global option is selected.
+ (1/27/00, Valdes)
+
+dispcor/dcio.o
+ Touched but not changes. (1/27/00, Valdes)
+
+getcalib.x
+t_standard.x
+standard.par
+lcalib.par
+splot.par
+doc/standard.hlp
+doc/lcalib.hlp
+doc/splot.hlp
+ 1. The calibration files may now be blackbody curves scaled to a
+ specified magnitude.
+ 2. STANDARD displays the data and bandpasses in the units of the
+ data rather than Angstroms. The changes were made to allow
+ output in other untis but for now this has been disabled.
+ (1/24/00, Valdes)
+
+t_sarith.x
+ The WCS was set wrong when copying/extracting a region of a
+ log-linear spectrum. (12/14/99, Valdes)
+
+=======
+V2.11.3
+=======
+
+t_fitprof.x
+ Fixed double/int mismatch in a min call. (11/22/99, Valdes)
+
+smw/mkpkg
+ecidentify/ecffit/mkpkg
+sensfunc/mkpkg
+splot/mkpkg
+mkpkg
+ Added missing dependencies. (10/11/99, Valdes)
+
+t_fitprofs.x
+doc/fitprofs.hlp
+ The background region specification was extended to allow a third
+ argument as a scaling factor. (9/22/99, Valdes)
+
+doc/specwcs.hlp
+ Fixed typo defining the variable n. (9/13/99, Valdes)
+
+t_tweak.x
+ 1. Changed wavelength evaluations to double precision.
+ 2. The normalization step for the rms calculation was removed for
+ the case of sky subtraction.
+ (9/8/99, Valdes)
+
+dispcor/t_dispcor.x
+dispcor/dcio.x
+dispcor/dispcor.x
+doc/dispcor.hlp
+ Now allows NDSPEC format spectra. (9/7/99, Valdes)
+
+t_fitprofs.x
+doc/fitprofs.hlp
+ 1. The background region specification was extended to allow taking
+ the average or median or a region.
+ 2. The image identification label was incorrect.
+ 3. If verbose=no there was an attempt to close a non-existent structure.
+ (6/26/99, Valdes)
+
+t_fitprofs.x
+ Fixed bug which only allowed the last component to be saved to an
+ image. (8/25/99, Valdes)
+
+=======
+V2.11.2
+=======
+
+smw/units.x
+smw/funits.x
+ Stripped trailing whitespace from units label. (8/5/99, Valdes)
+
+identify/t_reidentify.x
+ File date changed but no changes made to the file. (7/22/99, Valdes)
+
+rspectext.cl
+ Added explicit add and del parameters to all the HEDIT calls.
+ (7/15/99, Valdes)
+
+t_sarith.x
+ The option to have a single second operand to work on a set of
+ first operands was not working. (5/28/99, Valdes)
+
+splot/splot.key
+doc/splot.hlp
+ Added a reference to :.help and :/help. (5/12/99, Valdes)
+
+doc/sys/1and2dspec.hlp
+doc/sys/Onedspec.hlp
+doc/sys/Review.hlp
+doc/fitprofs.hlp
+doc/reidentify.hlp
+doc/sensfunc.hlp
+doc/aidpars.hlp
+doc/autoidentify.hlp
+doc/skytweak.hlp
+doc/telluric.hlp
+irsiids/doc/powercor.hlp
+irsiids/doc/widstape.hlp
+ Fixed minor formating problems. (4/22/99, Valdes)
+
+identify/idlinelist.x
+ The call to id_peak was using physical pixels while the subroutine
+ expects logical pixels. A conversion from physical to logical was
+ added before calling id_peak. (3/8/99, Valdes)
+
+scombine/t_scombine.x
+ Changed UT(shin) = imgetr (im, Memc[gain -> snoise]).
+ (1/29/99, Valdes)
+
+identify/idgraph.x
+ Removed violation of GTOOLS data structure. (12/18/98, Valdes)
+
+identify/t_reidentify.x
+ 1. When interactive=yes and ans is not NO when starting on a new
+ image in ri_image, the curfit descriptor was initialized to the
+ defaults rather than to reference solution. This was because
+ of a missing ic_copy.
+ 2. When the reference and image names are the same the task will now
+ skip the reidentify,
+ (12/3/98, Valdes)
+
+shdr.x
+ Needed to check if data is defined for associated types before trying
+ to set flux units. (11/27/98, Valdes)
+
+shdr.x
+ Improved the recognition of CTYPE values. Most notably WAVELENGTH
+ is converted to "waveleng" by MWCS in making the label attribute.
+ (11/25/98, Valdes)
+
+doc/splot.hlp
+ Fixed help that said the output of a long slit or ND image would be
+ a 1D image. (11/18/98, Valdes)
+
+dispcor/dispcor.x
+dispcor/refspectra.x
+ The weights when weighting multiple dispersion solutions were only
+ being recorded in the WCS attributes to 3 significant digits. This
+ could cause the weights to become unnormalized and cause small
+ shifts. Now whenever the weights are converted to strings the
+ format is %.8g. (11/17/98, Valdes)
+
+t_fitprofs.x
+ When the input peak value was INDEF the task would fail with a
+ floating overflow value if scale < 1. This was caused by not checking
+ for INDEF before dividing by the scale. (11/5/98, Valdes)
+
+smw/smwnewcopy.x
+ The structure copy was wrong. (10/28/98, Valdes)
+
+sensfunc/sfimage.x
+t_calibraate.x
+ The flux calibration gets the wrong sign if dw<0. (9/25/98, Valdes)
+
+smw/shdr.x
+ Default units of Angstroms was added if DC-FLAG is dispersion corrected.
+ (9/24/98, Valdes)
+
+dispcor/dcio.x
+ The weights are now adjusted to produce weighted average rather than
+ weighted sum. (8/25/98, Valdes)
+
+splot/wrspect.x
+ The filling in of data outside of NP1/NP2 was done incorrectly.
+ Normally NP1/NP2 cover the entire image line but in echelle data
+ it is common for NP2 to be less than the full line. In this case
+ the result of saving an image was loss of the last valid point.
+ (7/14/98, Valdes)
+
+identify/idinit.x
+ When restoring a solution without a dispersion function the shift
+ failed to be restored. This causes a problem with REIDENTIFY when
+ working on long slit data with a significant systematic tilt and
+ measuring the spatial distortion. (6/1/98, Valdes)
+
+t_tweak.x
+telluric.par
+doc/telluric.hlp
+ If the calibration is < 0 it is detected but there was an error in
+ the error clean up giving a "memory corruption" error. This error
+ was fixed and a new threshold parameter was added to allow the task
+ to continue if the calibration data has low values.
+ (4/21/98, Valdes)
+
+t_sarith.x
+ When rebinning non-linear spectra the dispersion type was not be reset
+ to linear resulting in an incorrect spectral WCS. See buglog 400.
+ (4/17/98, Valdes)
+
+doc/scopy.hlp
+ Added a note about using epar to set nsum to examples in section III
+ as suggested by Ivan King on 4/3/98. (4/8/98, Valdes)
+
+scombine/t_scombine.x
+ If the input spectra are not dispersion corrected and first=no the
+ task was incorrectly setting the dispersion correction flag.
+ (3/3/98, Valdes)
+
+splot/deblend.x
+ The dorefit code was not handling the case of a mixture of profile types.
+ (2/12/98, Valdes)
+
+t_tweak.x
+ The extra argument in a twk_colon call was removed.
+ (2/5/98, Valdes)
+
+dispcor/t_dispcor.x
+ Added some errchk declarations.
+ (1/26/98, Valdes)
+
+identify/idlinelist.x
+doc/autoidentify.hlp
+doc/identify.hlp
+doc/reidentify.hlp
+ When a coordinate list is read it will be sorted and identical
+ entries will be eliminated. Thus, line lists no longer need to
+ be sorted. (1/12/98, Valdes)
+
+=======
+V2.11.1
+=======
+
+doc/splot.hlp
+ Added another paragraph and a correction to the flux calculation
+ done by 'e'. (12/22/97, Valdes)
+
+splot/gfit.x
+ The test for computing errors when negative data is detected was
+ incorrect and would given an error message even when errors were
+ not desired. (10/23/97, Valdes)
+
+dispcor/dispcor.x
+smw/shdr.x
+ Changed the maximum distance that the endpoints can be from pixel
+ edges before using the pixel values directly instead of integrating
+ the interpolator from 0.001 to 0.00001. (10/7/97, Valdes)
+
+doc/calibrate.hlp
+ Added brief discussion about pixels falling outside the wavelength
+ range of the sensitivity function. (9/23/97, Valdes)
+
+===========
+V2.11export
+===========
+
+identify/iddb.x
+ecidentify/ecdb.x
+ Increased the number of digits recorded in the database for the fit
+ and user values to 9. (8/22/97, Valdes)
+
+swm/shdr.x
+ Added an arbitrary reference for the velocity CTYPE value.
+ (8/20/97, Valdes)
+
+smw/smwsaxes.x
+ The earlier fix for transposed data was incorrect. The origin terms
+ do not need to be changed but the order of the CD matrix terms
+ was incorrect. (8/15/97, Valdes)
+
+dispcor/refnoextn.x
+ Added fit and fits to the possible extensions. (8/14/97, Valdes)
+
+smw/smwsaxes.x
+ When the LTERM is adjusted to correct for a transpose only the matrix
+ terms were being corrected. The origin terms also needed to be
+ corrected. (8/6/97, Valdes)
+
+scombine/t_scombine.x
+ Previously an end input pixel had to completely overlap an output pixel
+ otherwise it was flagged as missing data. This was changed to use
+ the end pixels if they overlapped at all. This change was done to
+ allow small dispersion shifts to not affect the end point combining.
+ (8/6/97, Valdes)
+
+smw/shdr.x
+ No change. (8/6/97, Valdes)
+
+identify/t_reidentify.x
+doc/reidentify.hlp
+reidentify.par
+imred/*/reidentify.par
+twodspec/longslit/reidentify.par
+ The shift parameter was restored to it's previous usage. The automatic
+ pattern matching algorithm is not selected by setting the shift to INDEF
+ and using the new parameter crsearch. (7/21/97, Valdes)
+
+identify/idshift.x
+identify/t_reidentify.x
+ The symbol table of reference solutions was being modified by the shift
+ calculation causing the loop over solutions to be wrong. Now
+ ri_image marks and frees the symbol table between calls and loops
+ through the symbol table solutions in a way that is not affected by
+ new entries in the symbol table. Also idshift marks and frees the
+ symbol table. Note that marking and freeing is not enough because
+ the loop using sthead/stnext will not work. (7/19/97, Valdes)
+
+identify/autoid/aidshift.x
+identify/ididentify.x
+identify/idreidentify.x
+identify/doc/reidentify.x
+identify/reidentify.par
+ 1. aid_shift was not using crsearch/cdsearch as expected.
+ 2. The call to id_shift in the interactive routines had an incorrect
+ argument value.
+ 3. The help page for REIDENTIFY was clarified about what the shift
+ parameter means.
+ 4. The parameter prompt for shift in REIDENTIFY was corrected.
+ parameter means.
+ (7/17/97, Valdes)
+
+identify/t_reidentify.x
+ The nlost parameter now applies when not tracing. (7/17/97, Valdes)
+
+t_sarith.x
+ The power option did not work because the apow routine takes only
+ integer powers. Replace the apow routine with an explicit calculation.
+ (7/15/97, Valdes)
+
+=========
+V2.11Beta
+=========
+
+identify/t_reidentify.x
+ The number of features was being used to calculate how many features
+ might be lost before it was set. (6/3/97, Valdes)
+
+doc/disptrans.hlp
+doc/onedspec.hlp
+doc/splot.hlp
+ Added new unit abbreviations. (5/27/97, Valdes)
+
+splot/splot.x
+splot/splotcolon.x
+splot/splabel.x +
+splot/splot.key
+doc/splot.hlp
+ Added colon commands for labeling. (5/16/97, Valdes)
+
+t_tweak.x
+ 1. The ? help file was specified as .hlp instead of .key.
+ 2 Add a divide by zero check.
+ (5/14/97, Valdes)
+
+t_scoords.x +
+scoords.par +
+doc/scoords.hlp +
+x_onedspec.x
+mkpkg
+onedspec.cl
+onedspec.hd
+onedspec.men
+ Added a new task that sets a pixel array spectral coordinate system
+ in 1D spectra. (5/9/97, Valdes)
+
+doc/sapertures.hlp
+doc/sinterp.hlp
+doc/sflip.hlp
+doc/disptrans.hlp
+doc/skytweak.hlp
+doc/telluric.hlp
+doc/sfit.hlp
+doc/continuum.hlp
+doc/fitprofs.hlp
+irsiids/doc/slist1d.hlp
+ Changed revision versions. (4/22/97, Valdes)
+
+t_tweak.x +
+skytweak.par +
+telluric.par +
+doc/skytweak.hlp +
+doc/telluric.hlp +
+x_onedspec.x
+onedspec.cl
+onedspec.men
+onedspec.hd
+ Added tasks for tweaking calibration spectra and applying a sky
+ subtraction or telluric correction. (3/28/97, Valdes)
+
+t_sarith.x
+ The wrong INDEF type was used. (3/17/97, Valdes)
+
+smw/shdr.x
+ The shdr_linear needed to transform the requested input range to
+ the image WCS units. (3/13/97, Valdes)
+
+t_calibrate.x
+t_standard.x
+ Changed to allow input spectra in various units. (3/12/97, Valdes)
+
+autoidentify.par
+identify.par
+ecidentify.par
+identify/t_autoid.x
+identify/t_identify.x
+identify/identify.h
+identify/idinit.x
+identify/idgdata.x
+identify/iddofit.x
+identify/idmap.x
+identify/iddb.x
+identify/idlinelist.x
+identify/idfitdata.x
+ecidentify/ecinit.x
+ecidentify/ecgdata.x
+ecidentify/t_eciden.x
+ecidentify/eclinelist.x
+ecidentify/ecidentify.h
+ecidentify/ecdb.x
+ecidentify/ecfitdata.x
+dispcor/t_dispcor.x
+dispcor/dcio.x
+dispcor/dispcor.h
+doc/autoidentify.hlp
+doc/identify.hlp
+imred/echelle/doc/ecidentify.hlp
+imred/irs/identify.par
+imred/iids/identify.par
+imred/kpnocoude/identify.par
+noao/lib/linelists/*
+ Changes to allow IDENTIFY/ECIDENTIFY to work in user or line list
+ specified units and to have DISPCOR pass on the units. (3/11/97, Valdes)
+
+splot/usercoords.x
+ This routine had a couple of places where it calls smw_c?tran? and
+ then takes log sampling in EQUISPEC/NDSPEC explicitly into account.
+ Since this is now done by the lower level routines the log conversions
+ were removed. (3/3/97, Valdes)
+
+dispcor/dcio.x
+doc/dispcor.hlp
+ The coordinate transformation between logical and world was changed
+ to always produce linear wavelength. It use to be that for
+ equispec and ndspec formats with dc-flag=1 this would produce
+ log wavelengths. This prevented resampling from log back to
+ linear. [The date on dcio.x was touched but no actual change was
+ made.] (3/3/97, Valdes)
+
+smw/shdr.x
+ The SHDR routines that convert between world and logical were updated
+ for the changes in smw_c?tran?. Previously, these routines explicitly
+ applied the log transformation for log sample spectra. For this
+ reason most ONEDSPEC tasks operated correctly. However, DISPCOR
+ does not use SHDR_LW/SHDR_WL so it failed. Now the log conversions
+ are done in smw_c?tran? and not in the SHDR routines.
+ (3/3/97, Valdes)
+
+smw/smwsctran.x
+smw/smwctran.gx +
+noao/lib/smw.h
+ The coordinate transformations in ONEDSPEC tasks assume that dispersion
+ coordinates are always in linear dispersion whether or not the
+ spectra are stored in log sampling (DC-FLAG=1). However, this
+ was not true for EQUISPEC/NDSPEC format. Now calls to the smw_c?tran?
+ routines will return linear dispersion for all supported ONEDSPEC
+ WCS types. This was needed to fix the problem with DISPCOR and
+ log sampled input spectra.
+
+smw/smwsaveim.x
+ Deleted unused procedure name in errchk. No functional change.
+ (3/3/97, Valdes)
+
+scombine/t_scombine.x
+ Moved the call to smw_openim to "clean up" the WCS from after the
+ image size is changed to before because otherwise an error would
+ occur trying to access aperture information for any new lines added.
+ I no longer recall the purpose of this "clean up" step.
+ (2/19/97, Valdes)
+
+splot/sumflux.x
+ The conversion to "angstrom" units used the wpc instead of abs(wpc).
+ (2/6/97, Valdes)
+
+identify/idpeak.x
+identify/ididentify.x
+identify/idshift.x
+identify/reidentify.x
+identify/autoid/autoid.x
+ Added a new procedure, id_peaks, that replaces calls to find_peaks.
+ The new procedure calls find_peaks and then converts the pixels to
+ physical coordinates. (1/30/97, Valdes)
+
+t_calibrate.x
+ The calculation of dw for the flux correction used the index i
+ instead of k. This means that dw was constant which is incorrect
+ for spectra with non-linear dispersion. (1/22/97, Valdes)
+
+doc/splot.hlp
+splot/autoexp.x
+ The intensity range produced by the 'a', ',', '.', and 'z' keys
+ could be wrong if the dispersion function was sufficiently non-linear.
+ This now fixed and if the positions for the 'a' key are the same
+ it autoscales; i.e. 'a' 'a' is a short cut to autoscale.
+ (1/10/97, Valdes)
+
+splot/splot.x
+ The whitespace was being removed from the units parameter so that
+ any units string that requires whitespace (such as "km/s 4000 ang")
+ would fail to be recognized. (12/3/96, Valdes)
+
+scombine/t_scombine.x
+scombine/icscale.x
+ The feature of getting scaling, zero, and weight values from the image
+ headers did not work because the header values were not cached.
+ (11/11/96, Valdes)
+
+t_sarith.x
+doc/sarith.hlp
+ The noise spectrum type is now only copied unmodified. This is
+ a quick kludge until the noise is properly handled.
+ (9/11/96, Valdes)
+
+smw/shdr.x
+ Added a spectrum type field to the spectrum structure and a specific
+ procedure to decode the spectrum type.
+ (9/11/96, Valdes)
+
+splot/gfit.x
+splot/spdeblend.x
+ Allow error estimates with negative pixels if invgain=0 otherwise
+ print a warning. (7/23/96, Valdes)
+
+doc/standard.hlp
+ Added comments about proper use of extinction files.
+ (6/26/96, Valdes)
+
+doc/dispcor.hlp
+ Update the help file to indicate that the input limits are in
+ non-log units even with logarithmic sampling is selected.
+ (6/14/96, Valdes)
+
+t_standard.x
+ Make minor change to beginning of std_flux to avoid optimizer error
+ on Solaris with V4.0 compiler. (6/10/96, Valdes)
+
+t_sarith.x
+ Altered order of opening the output so that any error in reading
+ the input data is caught first. (5/14/96, Valdes)
+
+smw/shdr.x
+ Separated call for imgs3r in order to error check for failure to
+ get the pixel data (such as occurs with a long pathname to the pixel
+ file). (5/14/96, Valdes)
+
+rspectext.cl
+ Removed use of CL variable "list". (5/6/96, Valdes)
+
+noao$lib/smw.h
+ Changed SMW_NSPLIT from 200 to 500. (4/18/96, Valdes)
+
+smw/smwmerge.x
+ 1. When the output format is multispec the code did not open a
+ single MWCS but simply opened another split MWCS.
+ 2. A pointer rather than the string was incorrectly passed to
+ smw_swattrs.
+ (4/18/96, Valdes)
+
+smw/shdr.x
+ If the units are not defined by an attribute a check is made for
+ a CUNITn keyword. (4/17/96, Valdes)
+
+identify/autoid/autoid.x
+ 1. Removed useless call to id_log.
+ 2. Fixed realloc bug.
+ 3. Fixed bug allowing lines to be found multiple times.
+ (4/12/96, Valdes)
+
+identify/autoid/aidshift.x
+ Added missing argument in call to aid_init. (4/11/96, Valdes)
+
+identify/idlinelist.x
+ Minor efficiency change that avoids extract calls to id_fitpt.
+ (4/11/96, Valdes)
+
+identify/t_autoid.x
+ Minor bug fix so that log header is printed. (4/5/96, Valdes)
+
+t_rstext.x
+rspectext.cl
+doc/rspectext.hlp
+ The task now automatically senses the presence of a header.
+ (3/7/96, Valdes)
+
+identify/identify.h
+identify/peaks.gx
+identify/autoid/autoid.x
+ 1. The ID_FTYPE entry in the structure was being clobbered by a typo
+ in the include file which also mapped ID_LABEL to the same location.
+ 2. The peak finding routines were modified so that values of INDEF
+ for the threshold and contrast would disable these tests. This
+ is needed when absorption peak data is negated to find the
+ absorption peaks which are all negative.
+ 3. The autoid.x uses of find_peaks were modified to set the contrast
+ and threshold to INDEF instead of zero.
+ (2/24/96, Valdes)
+
+splot/getimage.x
+ There was a bug in initializing the image section limits such that
+ when an image section of the form [n,*] is used and there is not
+ display limits (xmin and xmax are INDEF) then the plotted spectrum
+ will cover the range of the first axis rather than the second.
+ (2/22/96, Valdes)
+
+identify/autoid/* +
+identify/t_autoid.x +
+identify/t_identify.x
+identify/t_reidentify.x
+identify/idcolon.x
+identify/iddb.x
+identify/iddoshift.x
+identify/idfitdata.x
+identify/idgdata.x
+identify/ididentify.x
+identify/idinit.x
+identify/idlinelist.x
+identify/idlog.x
+identify/idmap.x
+identify/idshift.x
+identify/idshow.x
+identify/peaks.gx
+identify/peaks.x
+identify/reidentify.x
+identify/identify.h
+identify/identify.key
+identify/mkpkg
+doc/aidpars.hlp +
+doc/autoidentify.hlp +
+doc/identify.hlp
+doc/reidentify.hlp
+x_onedspec.x
+aidpars.par +
+autoidentify.par
+identify.par
+reidentify.par
+onedspec.cl
+onedspec.par
+onedspec.men
+onedspec.hd
+ Added an automatic line identification algorithm. This algorithm
+ is part of the new task AUTOIDENTIFY and modified versions of
+ IDENITFY and REIDENTIFY. A new pset task AIDPARS contains the
+ algorithm parameters. (2/1/96, Valdes)
+
+onedspec.hd
+onedspec.men
+ Added linelists$README and onedstds$README as the help topics
+ "linelists" and "onedstds". (1/26/96, Valdes)
+
+smw/shdr.x
+ When extracting a wavelength range (without rebinning) and with the
+ range flipped there was an error in not checking for the existence
+ of the associated spectra causing a segmentation violation.
+ (1/22/96, Valdes)
+
+specplot.x
+ The scale and offset parameters may now be a constant value, an
+ @file containing the values, or a keyword name. (1/13/96, Valdes)
+
+smw/smwopen.x
+ The arrays for the aperture, beam, and limits in equispec format were
+ not being initialized to reasonable values which could cause an
+ error when doing an ES to MS conversion. Replaced
+ mallocs with callocs. (1/9/96, Valdes)
+
+smw/smwesms.x
+ Fixed a typo: smwopn -> smw_open. (1/9/96, Valdes)
+
+smw/smwsaxes.x
+ Uncalibrated long slit (2D) spectra which have been rotated are now
+ allowed. The rotated WCS is reset to pixels. If the dispersion
+ calibration flag is set and the spectra have been rotated then
+ an error is reported. (1/4/96, Valdes)
+
+t_sarith.x
+doc/sarith.hlp
+doc/scopy.hlp
+ Preiously both w1 and w2 had to be specified to select a wavelength
+ region to be copied or operated upon. Now if only one is specified
+ the second will default to the appropriate starting or ending
+ pixel. (12/20/95, Valdes)
+
+t_sbands.x
+ 1. Converted to work in double precision except the spectrum data
+ obtained by shdr_open is only in real.
+ 2. Increased the index and eqwidth precision printed from
+ %7.4g to %9.6g.
+ (12/5/95, Valdes)
+
+identify/idgraph.x
+ If the graph x window is outside of the data the x window is now
+ autoscaled. This occurs when a user sets window limits in pixel space
+ and then does a fit to wavelength. The new graph was then plotted in
+ the windowed pixel space and no data would be seen. (12/5/95, Valdes)
+
+t_calibrate.x
+ The airmass value computed by get_airm was being ignored causing
+ a floating exception (bug log 321). This was fixed. (12/4/95, Valdes).
+
+scombine/generic/icpclip.x
+ Fixed a bug where a variable was improperly used for two different
+ purposes causing the algorithm to fail (bug 316). (10/19/95, Valdes)
+
+identify/peaks.x
+ There was an index bug in is_local_max. (9/26/95, Valdes)
+
+t_slist.x
+ Fixed another case of closing the mwcs pointer without invalidating
+ it in the shdr pointer. (9/26/95, Valdes)
+
+t_fitprofs.x
+ Added a check and appropriate error message for a missing positions
+ file. (9/22/95, Valdes)
+
+doc/splot.hlp
+ Added explicit equations for the quantities measured by the 'e'
+ key in SPLOT. (9/22/95, Valdes)
+
+identify/ididentify.x
+idenitfy/identify.key
+doc/identify.hlp
+ A new key, 'e', has been added to add features from a line list without
+ doing any fits. This is like the 'l' but without the automatic
+ fitting before and after adding new features. (9/5/95, Valdes)
+
+identify/t_reidentify.x
+doc/reidentify.hlp
+ If there are no reference features the "addfeatures" option will add
+ new features before doing a fit or shift. (9/5/95, Valdes)
+
+splot/getimage.x
+ The change of 5/1/95 allows parsing an image section to determine
+ the dispersion line. However this ignored any range along the
+ dispersion. This change completely parses any image section and
+ sets the display range in pixels or wavelength to that of the
+ image section along the dispersion. (8/28/95, Valdes)
+
+identify/t_reidentify.x
+identify/iddb.x
+ 1. If the reference image does not exist REIDENTIFY would catch the
+ error but then attempt to close an unopened database leading to
+ a seg vio error rather than the warning. A check was added for
+ the database being open.
+ 2. Added a new database procedure that scans a database and saves
+ the records. This allows REIDENTIFY to use a reference database
+ even when the reference image doesn't exist.
+ (8/23/95, Valdes)
+
+smw/smwdaxis.x
+smw/shdr.x
+ In the absence of DISPAXIS the software will recognize the FITS
+ CTYPE keyword with values of LAMBDA, FREQ, VELO*, WAVELENGTH
+ in the units defined in the original FITS paper. (8/20/95, Valdes)
+
+t_sfit.x
+ The logic for checking whether all lines and all bands has been done
+ is not as straightforward as indicated in the entry of 4/29/94.
+ The checking on bands has been eliminated though a record of
+ the bands dones is written to the header. (8/15/95, Valdes)
+
+smw/smwsaveim.x
+scombine/t_scombine.x
+ When a new image is opened NEW_COPY it inherits IM_NPHYSDIM and IM_NDIM.
+ A routine can change IM_NDIM but not IM_NPHYSDIM. The routine to
+ save an equispec WCS needs to preserve the IM_NPHYSDIM when updating
+ an exisiting 2D image which may have been specified as a 1D section.
+ In order to tell the routine that a new lower dimensional image
+ is desired with a NEW_COPY header the higher level routine can set
+ the temporary keyword SMW_NDIM and the routine setting up the WCS
+ will use this in prference to the IM_NPHYSDIM. (8/14/95, Valdes)
+
+identify/idlinelist.x
+ The way memory was being allocated for labels was such that not
+ all memory would be deallocated at the end. (8/3/95, Valdes)
+
+identify/iddb.x
+ When "adding" features the NALLOC value was not properly updated
+ resulting in free uninitialized pointers leading to a segvio.
+ (8/3/95, Valdes)
+
+smw/smwdaxis.x
+ If the image header dispersion axis is unreasonable a warning is
+ printed and the "dispaxis" parameter is used instead. (8/2/95, Valdes)
+
+sbands.x
+ Changed the index and eq width format from 7.2f to 7.4g.
+ (7/28/95, Valdes)
+
+splot/voigt.x +
+t_fitprofs.x
+splot/splot.x
+splot/anshdr.x
+splot/eqwidthcp.x
+splot/gfit.x
+splot/deblend.x
+splot/spdeblend.x
+splot/splot.key
+splot/mkpkg
+doc/fitprofs.hlp
+doc/splot.hlp
+fitprofs.par
+splot.par
+ Added lorentzian and voigt profile fitting and deblending. This changed
+ the FITPROFS parameters and the input line lists for FITPROFS and
+ SPLOT though the old line lists will still work. A new parameter was
+ also added to SPLOT and FITPROFS to set the number of Monte-Carlo
+ samples used in the error estimates.
+ (7/28/95, Valdes)
+
+splot/splot.x
+ Changed when the shdr structure is closed to avoid an error.
+ (8/24/95, Valdes)
+
+t_sapertures.x
+doc/sapertures.hlp
+ Modified to allow aperture ID table to be from an image header
+ in the same way as done in the APEXTRACT package.
+ (7/24/95, Valdes)
+
+t_specplot.x
+specplot.key
+doc/specplot.hlp
+ Added a new key 'f' to toggle between logical pixels and world
+ coordinates. (7/21/95, Valdes)
+
+dispcor/dcio.x
+dispcor/dispcor.h
+ The application of a shift now also works with non-linear dispersions
+ in the input image. This is a feature used in the DOFIBERS script
+ to align sky lines. (7/19/95, Valdes)
+
+splot/wrspect.x
+ The BANDID keyword was being written with garbage characters
+ because a pargstr was used instead of pargi. (7/14/95, Valdes)
+
+dispcor/dcio.x
+ When there is only a shift in the database (a feature added 4/21/94)
+ and the image has more than one aperture the weight parameter was being
+ clobbered causing incorrect results. (7/13/95, Valdes)
+
+t_sapertures.x
+ Fixed the "dtype" parameter behavior which was not correct.
+ (6/30/95, Valdes)
+
+smw/smwonedspec.x
+smw/smwsaxes.x
+ 1. For the simplest spectra a heuristic to determine DC-FLAG was
+ added such that if the wavelength of the first pixel and the
+ increment per pixel are both unequal to 1 then the spectrum is
+ assumed to be dispersion calibrated.
+ 2. The label and units are not overridden if either is present.
+ If neither is present but the spectrum is considered to be
+ dispersion corrected then it defaults to Wavlength(angstroms).
+ (6/30/95, Valdes)
+
+ididentify.x
+reidentify.x
+ When a line center fails to be found with the 'm' key a message is
+ printed pointing to the threshold parameter. (6/30/95, Valdes)
+
+t_sbands.x
+ The allocation scheme was incorrect causing a segmentation violation
+ after the first 10 bands. (6/30/95, Valdes)
+
+=======
+V2.10.4
+=======
+
+t_sarith.x
+ The "units_display" WCS attribute is copied if set. (5/13/95, Valdes)
+
+splot/splot.x
+splot/getimage.x
+t_specplot.x
+ 1. The task "units" parameter value is mapped to "display" if null.
+ 2. The units are set with shdr_units.
+ (5/13/95, Valdes)
+
+smw/shdr.x
+ 1. The spectrum structure is loaded in the image MWCS units ("units").
+ 2. The special unit string "display" changes units to the "units_display"
+ attribute in shdr_units.
+ 3. The special unit string "default" changes units to the image MWCS
+ units in shdr_units.
+ (5/13/95, Valdes)
+
+doc/sfit.hlp
+ Added a description of the "sample" range syntax. (5/12/95, Valdes)
+
+splot/splot.x
+splot/getimage.x
+doc/splot.hlp
+ Because it can be desirable to use image sections on the input but
+ this will cause problems if the user attempts to update the image
+ SPLOT was modified to parse the image section for the specified image
+ line, column, or band and then map the full image. (5/1/95, Valdes)
+
+t_sbands.x
+doc/t_sbands.x
+ Increase the length and changed to g format for the flux so that
+ flux calibrated data will print. (4/12/95, Valdes)
+
+doc/wspectext.hlp
+ Fixed typo in example. (4/12/95, Valdes)
+
+t_sarith.x
+ Image extensions are no only stripped for onedspec format output
+ images rather than in all image names. This is necessary to allow
+ STF images with explicit extensions not matching the imtype value
+ to be specified. (3/31/95, Valdes)
+
+scombine/icscale.x
+doc/scombine.hlp
+ The behavior of the weights when using both multiplicative and zero
+ point scaling was incorrect; the zero levels have to account for
+ the scaling. (3/27/95, Valdes)
+
+splot/flatten.x
+ Removed use of faulty fp_equal test for equality with zero. This would
+ cause continuum normalization to fail for fluxed data. (2/23/95, Valdes)
+
+sensfunc/sfshift.x
+ Deleted points and stars are now ignored in the grey shift calculation.
+ (2/22/95, Valdes)
+
+t_sinterp.x
+ Updated the image header keywords to give a complete and standard
+ linear WCS. (2/21/95, Valdes)
+
+splot/gfit.x
+ If the marked region does not span the profile peak then an pointer
+ indexing error occurs when estimating the initial sigma. Modified
+ to estimate the sigma differently in this case. (2/17/95, Valdes)
+
+t_fitprofs.x
+splot/spdeblend.x
+splot/gfit.x
+ 1. The indexing was incorrect in the Monte-Carlo error estimation.
+ 2. Change the number of Monte-Carlo samples from 100 to 50.
+ (2/16/95, Valdes)
+
+smw/shdr.x
+ If an associated spectrum doesn't exist free any previous spectrum.
+ (2/13/95, Valdes)
+
+getcalib.x
+ Added missing length argument to strcpy which caused an unaligned
+ access error on the Alpha. (1/27/95, Valdes)
+
+t_dopcor.x
+ Fixed typo bug which prevents more than 8 spectra in multispec format
+ to work. This affects primarily echelle data. (1/18/95, Valdes)
+
+smw/swmctran.x
+ The equispec coordinate transformations now include mapping apertures
+ and lines. (1/16/95, Valdes)
+
+smw/smwopenim.x
+ Changed unknown coordinate system from a fatal error to a warning.
+ (1/14/95, Valdes)
+
+t_standard.x
+ Fixed bug in closing sh structure. (1/3/95, Valdes)
+
+t_standard.x
+t_calibrate.x
+standard.par
+calibrate.par
+doc/standard.hlp
+doc/calibrate.hlp
+ If the exposure time and airmass cannot be determined from the header
+ they are queried and updated in the images. New query parameters
+ were added. (1/2/95, Valdes)
+
+dispcor/refmsgs.x
+dispcor/refgspec.x
+dispcor/reftable.x
+dispcor/refspectra.h
+dispcor/refinterp.x
+dispcor/reffollow.x
+dispcor/refnearest.x
+dispcor/refprecede.x
+ Added error information if no reference spectrum is found to aid in
+ diagnosing the problem. (12/30/94, Valdes)
+
+dispcor/t_dispcor.x
+dispcor/dcio.x
+ 1. Improved the error messages again to more clearly pinpoint problems
+ with the dispersion database.
+ 2. The image extensions are now stripped in REFSPEC keywords.
+ (12/30/94, Valdes)
+
+identify/identify.x
+identify/reidentify.x
+identify/iddofit.x
+identify/identify.key
+ 1. Added 'v' to change fitting weights. (12/29/94, Valdes)
+
+identify/t_reidentify.x
+doc/reidentify.hlp
+ The step parameter for multispec/equispec data is now ignored and
+ all apertures are reidentified expect for a value of zero indicates
+ don't reidentify anything but the reference aperture. (11/15/94, Valdes)
+
+onedspec.men
+doc/mkspec.hlp
+ Highlighted the fact that the MKSPEC task is obsolete. (11/12/94, Valdes)
+
+doc/identify.hlp
+identify/identify.key
+identify/idcolon.x
+ The help described one of the options for :label to be "coords" when
+ it is actually "coord". Rather than modify the code I modified the
+ help. The colon procedure was modified only in that when it
+ reports the current value of the label parameter it shows coord
+ and not coords. (11/8/94, Valdes)
+
+doc/onedspec.hlp
+doc/specwcs.hlp
+ Added description of dispaxis and nsum package parameters to the package
+ description. (11/1/94, Valdes)
+
+scombine/t_scombine.x
+ There was a problem with using SCOMBINE with 2D/3D spectra in that
+ it assumed the number of spectra is the second image dimension.
+ Changed this to the approriate number of spectra for all spectral
+ formats. (10/27/94, Valdes)
+
+dispcor/dctable.x
+ If ignoreaps=yes and there are apertures defined with an aperture table
+ or reference image then the defaults for the wavelength scale if
+ an undefined aperture is encountered will be that of the first defined
+ aperture unless an explicit value has been given with the task parameters.
+ This is needed to make the IMRED reductions scripts run as desired.
+ (10/12/94, Valdes)
+
+smw/smwonedspec.x
+smw/smwoldms.x
+ Added a missing call to close the image header keyword template list
+ which caused memory to not be freed. (10/4/94, Valdes)
+
+identify/t_reidentify.x
+ Now checks for a zero step and only operates on the specified reference
+ line. (9/15/94, Valdes)
+
+t_sfit.x
+doc/sfit.hlp
+doc/continuum.hlp
+ Extended SFIT and CONTINUUM to work on NDSPEC spectra. (9/13/94, Valdes)
+
+splot/splot.x
+ 1. The 'p' and 'u' now restore the "world" system before setting the
+ dispersion. Previously if the user switched to "pixel" (with '$')
+ then a units conversion error would occur if the user tried to
+ set the dispersion.
+ 2. The 'v' key now toggles even if no input units are specified.
+ (8/17/94, Valdes)
+
+splot/wrspect.x
+ Fixed a bug in which the output units when saving a spectrum were
+ incorrectly set to be the current display units rather than the MWCS
+ units.
+ (8/17/94, Valdes)
+
+splot/wrspect.x
+ Fixed a typo in a pointer assignment in the case of overwriting
+ an existing 2D image which caused a segmentation violation.
+ (8/17/94, Valdes)
+
+doc/splot.hlp
+doc/fitprofs.hlp
+ Fixed various typos and added suggestions as pointed out by Dave Bell.
+ (8/17/94, Valdes)
+
+splot/gfit.x
+splot/spdeblend.x
+t_fitprofs.x
+ Added a check for both sigma0 and invgain being zero.
+ (8/17/94, Valdes)
+
+t_fitprofs.x
+ Failed to treat the scaling of the sigmas properly to avoid overflow
+ problems.
+ (8/17/94, Valdes)
+
+onedspec.cl
+onedspec.hd
+onedspec.men
+x_onedspec.x
+dispcor/mkpkg
+dispcor/t_disptrans.x +
+disptrans.par +
+doc/disptrans.hlp +
+ Added a new task to convert the WCS dispersion relation between units
+ and to apply a vacuum/air conversion. (8/8/94, Valdes)
+
+t_slist.x
+ Removed the restriction against N-dim spectra so that this could
+ be used with BPLOT to expand a list of apertures. (7/29/94, Valdes)
+
+splot/spdeblend.x
+splot/gfit.x
+ 1. The sigmas needed to be scaled to unit mean to avoid possible
+ overflow problems during the fitting.
+ 2. There was an incorrect calling sequence in gfit for the new
+ model parameters.
+ (7/26/94, Valdes)
+
+noao/lib/units.h
+smw/units.x
+splot/splot.key
+specplot.key
+doc/onedspec.hlp
+doc/splot.hlp
+ Added nanometers as a unit. (7/21/94, Valdes)
+
+noao/lib/smw.h
+smw/shdr.x
+splot/wrspect.x
+splot/splot.x
+ 1. Added a reddening correction flag to the basic spectrum data structure.
+ 2. When writing out a spectrum with WRSPECT also update the calibration
+ parameters.
+ 3. Restructured WRSPECT to be more general for use with SPECTOOL and
+ put an SPLOT specific routine to handle the parameter queries.
+ (7/20/94, Valdes)
+
+t_sflip.x +
+sflip.par +
+doc/sflip.hlp +
+x_onedspec.x
+mkpkg
+onedspec.cl
+onedspec.men
+onedspec.hd
+ Added a new task for flipping spectra. (7/18/94, Valdes)
+
+splot/wrspect.x
+splot/splot.x
+smw/smwswattrs.x
+ Fixed a rather tricky bug with replacing a spectrum in the current
+ image with SPLOT. (7/13/94, Valdes)
+
+splot/spdeblend.x +
+splot/deblend.x
+splot/gfit.x
+splot/sumflux.x
+splot/eqwidth.x
+splot/splot.x
+splot.par
+t_fitprofs.x
+fitprofs.par
+doc/splot.hlp
+doc/fitprofs.hlp
+ 1. Separated the SPLOT specific delending routine from the mathematical
+ deblending routines called by the various gaussian fitting routines.
+ 2. Replaced deblending code with a version that uses a sigma array
+ and subsampling of the pixels. This version also allows contraining
+ the relative line strengths but this feature is not used by
+ SPLOT of FITPROFS.
+ 3. Added constant noise and inverse gain parameters to SPLOT and FITPROFS.
+ 4. If a sigma0 and inverse gain are specified the deblending estimates
+ errors in the fit parameters using Monte-Carlo simulation. The
+ errors are recorded in the log and :show output. This was
+ added to both SPLOT and FITPROFS.
+ 5. If a sigma0 and inverse gain are specified the centroid, flux, and
+ equivalent width estimates (from 'e' key) include error estimates.
+ The errors are recorded in the log and :show output.
+ (7/12/94, Valdes)
+
+dispcor/t_dispcor.x
+dispcor/dcio.x
+ 1. Added a check for the existence of both IDENTIFY and ECIDENTIFY
+ database files for the same image.
+ 2. The recent errcode check addition (5/20) was incorrect in that
+ it would not proceed to look for an ECIDENTIFY file if no
+ IDENTIFY file was found; i.e. echelle data would fail. The
+ appropriate checking of errors is now done.
+ (7/11/94, Valdes)
+
+t_dopcor.x
+ The verbose output was enhanced to show the old redshift in the case
+ of adding to warn a user. This only applies to multispec images
+ which store the redshift separately. (7/7/94, Valdes)
+
+t_sbands.x
+ Instead of passing a file name to the routine which reads the bandpass
+ descriptions a file descriptor is not passed. This allows the
+ calling procedure to use either a file or a string file.
+ (6/30/94, Valdes)
+
+doc/sbands.hlp
+doc/splot.hlp
+ Typo fixes. (6/30/94, Valdes)
+
+doc/dopcor.hlp
+ Made a slight change to description of isvelocity to make as clear as
+ possible that velocities are relativistic and not c*z velocities.
+ (6/30/94, Valdes)
+
+t_rstext.x +
+rstext.par +
+rspectext.cl
+x_onedspec.e
+onedspec.cl
+mkpkg
+ Added a compiled task to reformat the input RSPECTEXT file into the
+ formats needed by RTEXTIMAGE and DISPCOR and modified RSPECTEXT
+ to use it. This improves the speed of this script task enormously for
+ large input text files since the CL facilities can be slow.
+ (6/20/94, Valdes)
+
+splot/wrspect.x
+ Failed to initialize a pointer to NULL. This became a seg vio after the
+ changes for the BANDID info. (6/15/94, Valdes)
+
+scombine/generic/iccclip.x
+scombine/generic/icsclip.x
+ Found and fixed another typo bug. (6/7/94, Valdes/Zhang)
+
+scombine/generic/icaclip.x
+scombine/generic/iccclip.x
+scombine/generic/icpclip.x
+scombine/generic/icsclip.x
+scombine/generic/icgrow.x
+scombine/generic/icmedian.x
+ The restoration of deleted pixels to satisfy the nkeep parameter
+ was being done inside the iteration loop causing the possiblity
+ of a non-terminating loop; i.e. pixels are rejected, they are
+ restored, and the number left then does not statisfy the termination
+ condition. The restoration step was moved following the iterative
+ rejection.
+
+ There was a bug in how the restored points were added back when
+ mclip=no and there are multiple residuals with the same value.
+
+ Also updated icgrow and icmedian. All these files are the same
+ as the generic files from IMCOMBINE reduced to only the real datatype.
+ (6/13/94, Valdes)
+
+t_sbands.x
+ When scanning the bandpass file, if there was an filter file then
+ the scanning of the filter file caused the remaining scan of the
+ bandpass line to be terminated. This was fixed by using getline
+ instead of fscan in the scanning the bandpass file. (6/3/94, Valdes)
+
+doc/sbands.hlp
+ Fixed a discrepancy in the bandpass file description between the
+ description section and the examples. (6/2/94, Valdes)
+
+splot/splot.x
+splot/splotcolon.x
+splot/splot.key
+splot.par
+doc/splot.hlp
+ Added an overplot options to permanently toggle overplotting.
+ (5/31/94, Valdes)
+
+scombine/icscale.x
+ The sigma scaling flag, doscale1, would not be set in the case of
+ a mean offset of zero though the scale factors could be different.
+ (5/25/94, Valdes/Zhang)
+
+scombine/generic/icsclip.gx
+ There was a missing line: l = Memi[mp1]. (5/25/94, Valdes/Zhang)
+
+scombine/generic/icaclip.x
+scombine/generic/iccclip.x
+scombine/generic/icpclip.x
+scombine/generic/icsclip.x
+ The reordering step when a central median is used during rejection
+ but the final combining is average was incorrect if the number
+ of rejected low pixels was greater than the number of pixel
+ number of pixels not rejected. (5/25/94, Valdes)
+
+dispcor/dcio.x
+dispcor/t_dispcor.x
+ All warning messages were being converted to a single warning which
+ was not appropriate in all cases. Added an errcode check.
+ (5/20/94, Valdes)
+
+============================
+V2.10.3beta internal release
+============================
+
+noao/lib/smw.h
+smw/shdr.x
+t_fitprofs.x
+t_sarith.x
+splot/wrspect.x
+ The spectrum data structure was modified so that it can contain
+ all the associated spectra such as the spectrum, raw spectrum,
+ sky, continuum, and sigma. Also the STYPE field was changed
+ to an array of string pointers SID to contain the specturm
+ type strings for all the associated spectra. Except for the
+ SID changes (in FITPROFS, SARITH, and SPLOT) the structure
+ changes are invisible to any spectral task. (5/4/94, Valdes)
+
+scombine/icscale.x
+scombine/t_scombine.x
+ There is now a warning error if the scale, zero, or weight type
+ is unknown. (5/2/94, Valdes)
+
+t_sfit.x
+sfit.par
+continuum.par
+doc/sfit.hlp
+doc/continuum.hlp
+ 1. The sample regions are now set to the task parameter after each
+ fit. Previously this was only done for the first spectrum and
+ after that it was set to "*".
+ 2. A straightforward replication of the line selection mechanism
+ to allow band selection was added.
+ (4/29/94, Valdes)
+
+identify/t_reidentify.x
+ The refit=no options would not work if there was not dispersion
+ function even though it makes sense to do so. It was case of
+ the if clauses not being defined correctly. (4/28/94, Valdes)
+
+dispcor/dcio.x
+ A possibly very useful and common case is when IDENITFY/REIDENTIFY
+ are used on previously dispersion corrected data to get only a
+ shift with no dispersion function. DISPCOR was modified to
+ allow this case. (4/21/94, Valdes)
+
+scombine/iclog.x
+ Changed the mean, median, mode, and zero formats from 6g to 7.5g to
+ insure 5 significant digits regardless of signs and decimal points.
+ (4/13/94, Valdes)
+
+noao/lib/smw.h
+smw/shdr.x
+ The standard spectrum data structure now includes a pointer for a
+ continuum spectrum. Currently it is unused. (4/12/94, Valdes)
+
+scombine/icscale.x
+ When the combine object is "sum" the task attempts to compute the
+ total exposure time. Since a missing exposure time is represented
+ as INDEF this caused an arithmetic error. The task was modified to
+ not compute or output a total exposure time if any of the spectra
+ have an undefined exposure time. (4/11/94, Valdes)
+
+identify/idmark.x
+ Changed the mark and mark label color to be the tick label color
+ currently in effect. Eventually the user should have more control
+ over the color but this cannot be done without changing GTOOLS or
+ IDENTIFY more than is appropriate at the moment. (4/11/94, Valdes)
+
+doc/identify.hlp
+ Fixed a typo in the description of the Legendre polynomial formula.
+ (4/11/94, Valdes)
+
+smw/shdr.x
+ The case of DC-FLAG=-1 was not being handled by shdr_lw and shdr_wl.
+ (4/9/93, Valdes)
+
+smw/shdr.x
+ The flux units were not being copied when the spectrum header is
+ copied. (3/31/94, Valdes)
+
+t_sarith.x
+ The string used to read in the aperture, band, and beam lists was
+ SZ_FNAME which is too short for possible input lines. Changed
+ the lengths to SZ_LINE. (3/31/94, Valdes)
+
+splot.par
+ Changed the mode of line and band to be query so that if SPLOT is run
+ from epar line and band queries will still be made. (3/21/94, Valdes)
+
+scombine/generic/icaclip.x
+scombine/generic/iccclip.x
+scombine/generic/icsclip.x
+ The image sigma was incorrectly computed when an offset scaling is used.
+ (3/8/94, Valdes)
+
+smw/shdr.x
+ The call to shdr_units can specify "default" to restore the original
+ units. (3/7/94, Valdes)
+
+smw/shdr.x
+splot/wrspect.x
+t_sarith.x
+t_fitprofs.
+ Fixed problems when NP1 > 1 due to a IMSHIFT operation that moves
+ the first physical pixel higher logical coordinates (or the
+ first logical pixel in the image corresponds to a negative
+ physical pixel coordinate). (3/5/94, Valdes)
+
+t_deredden.x
+ Fixed bug causing memory corruption. (3/2/94, Valdes)
+
+scombine/icscale.x
+scombine/iclog.x
+ 1. The exposure time was not being summed when summing spectra.
+ 2. The exposure time is now printed whenever the exposure time is used
+ even if the times are all equal.
+ (2/24/94, Valdes)
+
+t_deredden.x
+doc/deredden.hlp
+ Overriding a previous correction will apply to the original data
+ rather than being incremental. (2/23/94, Valdes)
+
+smw/shdr.x
+noao$lib/smw.h
+ Added structure fields for the flux units and shdr_open sets the
+ field if possible. The flux units are determined first by any
+ BUNIT keyword, then if the flux calibration flag is set by
+ the magnitude of the data. (2/22/94, Valdes)
+
+smw/funits.x +
+noao$lib/funits.h +
+ Added a flux units package. (2/21/94, Valdes)
+
+smw/shdr.x
+ Added a routine to change the units. (2/19/94, Valdes)
+
+splot/usercoord.x
+ The routine was not correct for input log-linear spectra (dc-flag=1).
+ (2/19/94, Valdes)
+
+dispcor/dispcor.x
+ Fixed typo (out[1] -> out[i]) which was causing the non-flux conserving
+ mode to fail. (2/18/94, Valdes)
+
+splot.par
+specplot.par
+doc/splot.hlp
+doc/specplot.hlp
+ 1. SPLOT will write out the current display units to the WCS attribute
+ "units_display".
+ 2. The default "units" task parameter now has the null string value
+ to allow selecting the units given by "units_display" or the WCS
+ units in that order. (2/18/94, Valdes)
+
+smw/smwsaveim.x
+smw/smwesms.x
+smw/smwmerge.x
+smw/smwndes.x
+smw/shdr.x
+ 1. A new WCS attribute "units_display" has been defined. It is now
+ stored in the image and transfered when copying WCS if it is defined.
+ 2. When a spectrum is opened with shdr_open the user units are set
+ to that specified by "units_display" if present. Otherwise
+ the units of the WCS are used.
+ (2/18/94, Valdes)
+
+noao$lib/smw.h
+shdr.x
+ Added a field to the standard spectrum data structure to contain an
+ error array. This array is filled in by shdr_open if a new flag
+ value is used. Since there are no current tasks which use the
+ new value this feature is unused in current tasks. (2/7/94, Valdes)
+
+noao$lib/smw.h
+t_fitprofs.x
+t_sarith.x
+splot/wrspect.x
+smw/smwsaveim.x
+ Added a field to the standard spectrum data structure to contain the
+ type of spectrum; i.e. spectrum, background, sigma. This type is
+ stored in the BANDIDn keywords for multispec format data extracted by
+ APEXTRACT. This information, if present, is now updated on outputing a
+ new spectrum. This is particularly important for SCOPY when the bands
+ are adjusted. (2/4/94, Valdes)
+
+dispcor/t_dispcor.x
+ Deleted unused variable, junk, which somehow snuck in. (2/7/94, Valdes)
+
+t_specplot.x
+specplot.key
+doc/specplot.hlp
+ Extended the :units command to allow specifying individual spectra.
+ This is intended to allow multiple spectra to be plotted on a velocity
+ scale with different zero points. (2/4/94, Valdes)
+
+smw/shdr.x
+smw/smwmw.x
+ Added checks for the aperture number to be outside of the range of
+ spectra in N-dimensional spectra. (1/8/94, Valdes)
+
+splot/splot.x
+splot/splotcolon.x
+splot/splot.key
+doc/splot.hlp
+splot.par
+ A new options, "flip", has been added to select plotting the spectra
+ in decreasing wavelength. (12/8/93, Valdes)
+
+dispcor/dispcor.x
+doc/dispcor.hlp
+ When flux=no DISPCOR now computes an average across the output pixel
+ rather than interpolating to the pixel center. This allows
+ flux density conservation. (12/6/93, Valdes)
+
+identify/idinit.x
+ Changed aclrr to aclri. (12/1/93, Valdes)
+
+doc/identify.hlp
+ Added a description of the function coefficients. (12/1/93, Valdes)
+
+t_calibrate.x
+ Added a warning if the exposure time is not found. (11/19/93, Valdes)
+
+sensfunc/sfoutput.x
+ Instead of using the dispersion range from a single standard star
+ the code now uses the maximum range and minimum dispersion.
+ (11/15/93, Valdes)
+
+t_sbands.x +
+sbands.par +
+doc/sbands.hlp +
+x_onedspec.x
+onedspec.cl
+onedspec.men
+onedspec.hd
+mkpkg
+ Added a new task to do bandpass spectrophotometry. (11/1/93, Valdes)
+
+rspectext.cl
+wspectext.cl
+doc/rspectext.hlp
+doc/wspectext.hlp
+onedspec.cl
+onedspec.men
+onedspec.hd
+ Added two script tasks to convert between 1D image spectra and
+ ascii text spectra. (10/22/93, Valdes)
+
+splot/splot.x
+splot/getimage.x
+splot/splotfun.x
+doc/splot.hlp
+ If a wavelength scale is set with 'p' or 'u' then all subsequent
+ spectra which are not dispersion calibrated will use that wavelength
+ scale. (9/2/93, Valdes)
+
+t_sapertures.x
+ The negative beam number warning is only issued if verbose = yes.
+ (9/1/93, Valdes)
+
+dispcor/t_dispcor.x
+smw/smwesms.x
+ The aperture IDs were not being properly propagated. (9/1/93, Valdes)
+
+t_fitprofs.x
+fitprofs.par
+doc/fitprofs.hlp
+ 1. Fixed bug with close MWCS
+ 2. Add a bands parameter for 3D images.
+ (8/31/93, Valdes)
+
+t_deredden.x
+ There was an error in freeing the sh pointer causing a segmentation
+ violation after the spectra are successfully dereddened. (8/13/93, Valdes)
+
+splot/splot.x
+doc/splot.hlp
+ The '(' and ')' keys will now cycle in bands if there is only one line.
+ (8/10/93, Valdes)
+
+t_sapertures.x
+ Modified to ignore attempts to set a negative beam number.
+ (8/9/93, Valdes)
+
+splot/wrspect.x
+ Added check against an error opening an output image in shdr_open.
+ (8/4/93, Valdes)
+
+splot/fudgex.x
+ Added check against a divide by zero if the cursor is not moved.
+ (8/4/93, Valdes)
+
+splot/splotcolon.x
+ The call to ans_hdr in the COMMENT case was missing the key argument.
+ (8/3/93, Valdes)
+
+smw/smwonedspec.x
+ For spectra which are dispersion corrected (DC-FLAG set) but have no
+ units the code was setting the "label" rather than "units" to
+ "angstroms". (8/3/93, Valdes)
+
+============
+V2.10.3 beta
+============
+
+splot.par
+splot/smooth.x
+doc/splot.hlp
+ 1. The parameter file parameter prompt for the smoothing box size was
+ modified to request an odd number.
+ 2. If an even number is given, a warning is printed.
+ 3. The help for the parameter boxsize indicates the the value must
+ be odd.
+ (6/28/93, Valdes)
+
+scombine/icscale.x
+ The result of reading an @file for the zero or weight parameter was
+ being placed in the scales array. This has been fixed. This
+ affected only one IRAFX users. (6/28/93, Valdes)
+
+specplot.key
+ Added missing :redshift and :velocity commands in the summary. Also
+ sorted and cleaned up the multicolumn lists. (6/15/93, Valdes)
+
+t_dopcor.x
+dopcor.par
+doc/dopcor.hlp
+ An new parameter has been added to allow combining sequential
+ corrections in "multispec" format spectra. (6/15/93, Valdes)
+
+usercoord.x
+wrspect.x
+t_dopcor.x
+ When smw_swattrs is called it is possible that the smw pointer will be
+ changes (promoting an equispec format to multispec). If this happens
+ and the pointer is part of an open shdr structure then the routine
+ must invalidate the mwcs stuff and possibly open or update the shdr
+ structure. (6/14/93, Valdes)
+
+bplot.cl
+doc/bplot.hlp
+ The query parameters from SPLOT were added as hidden parameters in
+ BPLOT to allow such things as writing output spectra without generating
+ queries. (6/8/93, Valdes)
+
+identify/ididentify.x
+ Added newlines when printing to the status line. This is needed when
+ redirecting the output to a file in the IMRED scripts. (6/4/93, Valdes)
+
+identify/iddelete.x
+ The label pointers needed to be updated when deleting a feature.
+ (6/4/93, Valdes)
+
+t_specplot.x
+ Modified the log output format to include the aperture number.
+ (5/25/93, Valdes)
+
+t_sarith.x
+t_fitprofs.x
+wrspect.x
+ The conversion from logical to physical coordinates was incorrect in
+ that it truncated the physical coordinates. This could cause a subtle
+ error in the coordinate system. (5/20/93, Valdes)
+
+identify/idmap.x
+ The user specified vector axis is interpreted as a logical axis rather
+ than a physical axis. This is only significant for transposed images.
+ (5/14/93, Valdes)
+
+smw/smwsaxes.x
+smw/smwsaveim.x
+ Transposed NDSPEC images are now allowed. (5/11/93, Valdes)
+
+getcalib.x
+ Added a search for alternate standard names in a file <caldir>names.men
+ if that file is present. (5/4/93, Valdes)
+
+splot/splot.x
+splot/anshdr.x
+splot/avgsnr.x
+ Added logging of the 'm' key output. (5/4/93, Valdes)
+
+splot/splot.x
+splot/splotfun.x
+ 1. fun_do was not initializing the pointers passed to getimage.
+ This proves to be a problem if an error occurs in getting the
+ second image data, such as due to a mistype, so that the
+ next time the routine is called an invalid pointer is found
+ and a segmentation error occurs.
+ 2. Added a time delay on an error message in fun_do followed by the
+ function mode prompt.
+ (3/2/93, Valdes)
+
+sensfunc/sfstds.x
+ 1. Eliminated input stars/apertures that have no data.
+ 2. Eliminated input flux points outside the range of the
+ star/aperture wavelength range.
+ 3. Improved the iterative fitting to drop back to a polynomial
+ function if the lowest order spline does not fit.
+ (2/12/93, Valdes)
+
+identify/idgraph.x
+ Because these procedures used the SX array as temporary storage it
+ caused the initialize option to fail. (2/3/92, Valdes)
+
+onedspec.men
+ Removed reference to dispaxis. (1/21/93, Valdes)
+
+scombine/generic/icaclip.x
+scombine/generic/iccclip.x
+scombine/generic/icpclip.x
+scombine/generic/icsclip.x
+ When using mclip=yes and when more pixels are rejected than allowed by
+ the nkeep parameter there was a subtle bug in how the pixels are added
+ back which can result in a segmentation violation.
+ if (nh == n2) ==> if (nh == n[i])
+ (1/20/93, Valdes)
+
+sensfunc/sensfunc.h
+sensfunc/sfgraph.x
+sensfunc/sfginit.x
+sensfunc/sfimage.x
+sensfunc/sfcgraph.x
+sensfunc/sfextinct.x
+sensfunc/sfcolors.x
+sensfunc/sfcolon.x
+sensfunc/sfmove.x
+sensfunc/sfundelete.x
+sensfunc/sfdelete.x
+sensfunc/sfadd.x
+sensfunc/mkpkg
+sensfunc/sensfunc.key
+sensfunc.par
+doc/sensfunc.hlp
+ Added color support. (12/17/92, Valdes)
+
+splot/gfit.x
+splot/eqwidthcp.x
+splot/deblend.x
+splot/splot.x
+identify/idmark.x
+ Added color support. (12/8/92, Valdes)
+
+splot/sumflux.x
+ 1. There was no check of whether esum was INDEF (a possible value) before
+ multiplying by wpc. A check was added.
+ 2. Because of a change to fp_equalr which occured on (10/18) the
+ equivalent widths of flux calibrated data would be INDEF. To
+ compensate the test is made on scaled data.
+ (12/7/92, Valdes)
+
+units.h
+ The conversion factors for millimeter and centimeter were off by a
+ factor of 10. (12/4/92, Valdes)
+
+dispcor/dcio.x
+ The wrong axis was selected in computing the logical NW. (11/24/92, Valdes)
+
+splot/splot.x
+splot/usercoord.x
+splot/splot.key
+splot/mkpkg
+doc/splot.hlp
+splot.par
+ Changed the 'u' and 'p' keys to include additional ways to adjust the
+ dispersion scale. In particular a doppler and zeropoint adjustment can
+ be made using the cursor and entering a coordinate. Note that these
+ two adjustments apply to all coordinate systems and units and do not
+ require assuming a linear dispersion. In effect these are interactive,
+ cursor marking versions of DOPCOR (without the flux correction) and
+ SPECSHIFT. The coordinates are specified in the current displayed
+ units. The code that does the adjustment is now well integrated with
+ the MWCS rather than fudging the W0 and WP entries. The output of a
+ new spectrum with 'i' will properly handle the adjusted coordinate
+ system. (11/20/92, Valdes)
+
+bplot.cl
+irsiids/bplot.cl
+doc/bplot.hlp
+gcurval -> gcurval.dat
+ Changed the name of the default cursor file to avoid stripping.
+ (11/20/92, Valdes)
+
+splot/wrspect.x
+ Fixed typo affecting 3D images: PNDIM(out) --> PNDIM(sh2).
+ (11/19/92, Valdes)
+
+splot/wrspect.x
+ A spectrum was being written using the W0, WPC of the current units
+ rather than Angstroms as it should be. A call to un_ctran to convert
+ to the MWCS units was added. (11/17/92, Valdes)
+
+t_specplot.x
+specplot.h
+doc/specplot.hlp
+specplot.key
+ Added a color parameter for specifying the color of each spectrum
+ on color graphics terminals. (10/30/92, Valdes)
+
+t_sarith.x
+t_fitprofs.x
+splot/wrspect.x
+ 1. The doppler correction was still not properly handled. Instead of
+ dividing by (1 - z) it should multiple by (1 + z) in order to
+ be symmetric with the WCS driver.
+ 2. To avoid roundoff with multispec format W0 and W1 (which are real)
+ are not used when recalculating the w1, dw attribute values. Instead
+ shdr_lw is called to get the double precision values.
+ (10/16/92, Valdes)
+
+dispcor/t_dispcor.x
+dispcor/dcio.x
+doc/dispcor.hlp
+ DISPCOR will now allow multiple uses of IDENTIFY dispersion solutions
+ in a simple way with but with continuing protection against accidental
+ multiple uses of the same dispersion solutions. When a spectrum is
+ first dispersion corrected using one or more reference spectra keywords
+ the dispersion flag is set and the reference spectra keywords are moved to
+ DCLOGn keywords. If DISPCOR is called again without setting new
+ reference spectra keywords then the spectra are resampled (rebinned)
+ using the current coordinate system. If new reference spectra are set
+ then DISPCOR will apply these new dispersion functions. Thus the user
+ now explicitly enables multiple dispersion functions by adding
+ reference spectra keywords and DISPCOR eliminates accidental multiple
+ uses of the same dispersion function by renaming the reference
+ spectra. The renamed keywords also provide a history.
+
+ Some additional log and verbose output was added to better inform the
+ user about what is done.
+ (10/15/92, Valdes)
+
+t_specshift.x +
+specshift.par +
+doc/specshift.hlp +
+x_onedspec.x
+mkpkg
+onedspec.cl
+onedspec.men
+onedspec.hd
+imred$argus/argus.cl
+imred$ctioslit/ctioslit.cl
+imred$echelle/echelle.cl
+imred$hydra/hydra.cl
+imred$iids/iids.cl
+imred$irs/irs.cl
+imred$kpnocoude/kpnocoude.cl
+imred$kpnoslit/kpnoslit.cl
+imred$specred/specred.cl
+imred$argus/argus.men
+imred$ctioslit/ctioslit.men
+imred$echelle/echelle.men
+imred$hydra/hydra.men
+imred$iids/iids.men
+imred$irs/irs.men
+imred$kpnocoude/kpnocoude.men
+imred$kpnoslit/kpnoslit.men
+imred$specred/specred.men
+ The new task SPECSHIFT applies a coordinate system shift to selected
+ spectra. For linear coordinate systems this is done by changing
+ the wavelength of the first physical pixel. For nonlinear systems
+ the existing shift coefficient is adjusted.
+ (10/14/92, Valdes)
+
+dispcor/dcio.x
+ Added step to update the linear part of the nonlinear WCS.
+ This is mostly cosmetic.
+ (10/14/92, Valdes)
+
+dispcor/idmap.x
+ Changed the way the image is opened to avoid updating the WCS.
+ (10/14/92, Valdes)
+
+*doc/onedspec.hlp
+smw.x
+ 1. Spectra in a single image which all have the same linear dispersion
+ are now stored with linear axis types. This gives a simpler header
+ structure than the multispec axis type for this common case. This
+ modification applies to 1, 2, and 3 dimensional images.
+ 2. Extensions were added to allow importing spectra which use
+ a different WCS driver than multispec or linear.
+ (10/13/92, Valdes)
+
+doc/onedspec.hlp
+ First an error in a font switch causing part of the text to all be in
+ standout. (10/9/92, Valdes)
+
+scombine/t_scombine.x
+scombine/icombine.h
+scombine/icombine.com
+scombine/icombine.x
+scombine/icscale.x
+scombine/iclog.x
+scombine/generic/iccclip.x
+scombine/generic/icsclip.x
+scombine/generic/icpclip.x
+scombine/generic/icaclip.x
+scombine/generic/icgrow.x
+scombine.par
+doc/scombine.hlp
+ The weighting was changed from using the square root of the exposure time
+ or spectrum statistics to using the values directly. This corresponds
+ to variance weighting. Other options for specifying the scaling and
+ weighting factors were added; namely from a file or from a different
+ image header keyword. The \fInkeep\fR parameter was added to allow
+ controling the maximum number of pixels to be rejected by the clipping
+ algorithms. The \fIsnoise\fR parameter was added to include a sensitivity
+ or scale noise component to the noise model.
+ (10/2/92, Valdes)
+
+splot/usercoords.x
+ This routine no longer puts a default value in the wavelength parameters.
+ This will allow using SPLOT to noninteractively set wavelengths.
+ (9/17/92, Valdes)
+
+identify/idfitdata.x
+identify/idmark.x
+identify/idgdata.x
+identify/idcenter.x
+ IDENITFY/REIDENTIFY use the standard SHDR interface which eliminates
+ data with negative physical coordinates. This occurs because NP1 is
+ then computed to be positive. The case where this can occur is using
+ IMSHIFT with a positive shift though explicit use of NP1 could also do
+ it. However, the above routines use the MWCS logical-physical and
+ physical-logical conversions without accounting for NP1. This results
+ in incorrect results. The routines were fixed to apply NP1. (9/16/92,
+ Valdes)
+
+splot/splot.x
+splot/getimage.x
+ Modified getimage to also allow specification of the aperture. This
+ is needed in order for the scrolling through lines, the '(' and ')'
+ keys, to work correctly by indicating that the aperture number is
+ to be ignored. (9/8/92, Valdes)
+
+dispcor/dcio.x
+ The computation of the aperture center was not prepared to deal with
+ INDEF aperture limits. (9/3/92, Valdes)
+
+smw.x
+ There was a type mismatch when setting aplow and aphigh to INDEF.
+ Changed to set them to INDEFD. This bug caused the APLOW and APHIGH
+ keywords to appear in the image header unexpectedly with IDENTIFY
+ on the VaxStation port. (8/31/92, Valdes)
+
+ecidentify/ecgetim.x
+identify/idnoextn.x
+ The algorithm for stripping the image extension could get confused
+ with the name such as ec025.john.ec --> ec025n.ec. The routines
+ were modified to use xt_imroot which does a better job. (8/31/92, Valdes)
+
+t_sarith.x
+smw.x
+ Added provision to save multispec title in MSTITLE keyword when
+ separating out multispec spectra or converting to simple 1D format and
+ to restore the title when combining 1D spectra into a multispec
+ spectrum. (8/24/92, Valdes)
+
+sensfunc/sfsvstats.x
+ A real variable was used where a double should have been giving round
+ off errors in the computation of the standard deviation. (8/13/92, Valdes)
+
+t_sfit.x
+ Output images are of type real regardless of the input type.
+ (8/11/92, Valdes)
+
+scombine/icscale.x
+ The zero level offsets were being incorrectly scaled twice.
+ (8/10/92, Valdes)
+
+dispcor/refgspec.x
+ Arguments incompatible with intrinsic function:
+ sortval = mod (sortval + 24. - timewrap, 24.)
+ Changed second 24. to 24.0D0. (8/10/92, Valdes)
+
+splot/fixx.x
+ Arguments incompatible with intrinsic function:
+ z1 = max (0.5, min (double (SN(sh)+.499), shdr_wl(sh, z1)))
+ z2 = max (0.5, min (double (SN(sh)+.499), shdr_wl(sh, z2)))
+ The 0.5 should be double. (8/10/92, Valdes)
+
+shdr.x
+ Arguments incompatible with intrinsic function: on lines 268-269,
+ 319-320, need to real the image limits. (8/10/92, Valdes)
+
+units.x
+onedspec.hlp
+ The velocity label was changed to "cz velocity" to show that it
+ is c*z and not a true velocity. (7/30/92, Valdes)
+
+dispcor/t_dispcor.x
+ Changed WCSDIM to be 3 in the case of a 3D image. (7/27/92, Valdes)
+
+splot/splot.x
+ Getttng a new image always forces the data to be read even if the
+ same image is given. (7/20/92, Valdes)
+
+smw.x
+ Altered the way in which old APNUM keywords are deleted to avoid
+ a problem with the limit on the number of keywords that can be
+ mapped with imofnl in the imio$db package. (7/17/92, Valdes)
+
+splot/replot.x
+ Replaced gascale with gt_ascale to do the autoscaling only within
+ the GTOOLS window. (7/16/92, Valdes)
+
+t_sapertures.x
+sapertures.par
+doc/sapertures.hlp
+ Modified this task to allow resetting the WCS to pixels and changing
+ any of the WCS fields. (7/2/92, Valdes)
+
+splot/wrspect.x
+ Harmless typo fix mwopen -> mw_open. (7/1/92, Valdes)
+
+t_sarith.x
+ Modified to properly handle 3D images. (7/1/92, Valdes)
+
+t_sarith.x
+ Onedspec output format now splits out the bands as well.
+ (7/1/92, Valdes)
+
+=======
+V2.10.2
+=======
+
+t_dopcor.x
+doc/dopcor.hlp
+ 1. The conversion from velocity to z was incorrect.
+ 2. Checks were added for reasonable velocities and redshifts.
+ 3. A negative sign for a header parameter changes the sense of
+ a redshift if the parameter is a redshift.
+
+=======
+V2.10.1
+=======
+
+t_deredden.x
+ The declaration for decode_ranges was incorrect. Changed from bool to int.
+ (7/21/92, Valdes)
+
+shdr.x
+ 1. An earlier fix left the aaxis parameter undefined for longslit images.
+ This meant that references to IM_LEN(im,aaxis) yield the dimension
+ of the image rather than the axis length.
+ 2. Discovered that image sections don't automatically reset the lengths
+ of the higher dimensions to 1 as assumed in several tasks. SHDR now
+ resets these. (7/20/92, Valdes)
+
+=======
+V2.10.0
+=======
+
+irsiids/batchred.cl
+ The parameter "recformat" in STANDARD and CALIBRATE and "apertures" in
+ CALIBRATE are no longer present. The BATCHRED task was modified to not
+ add these parameters to the PROCESS script. (7/6/92, Valdes)
+
+shdr.x
+ The resampling in shdr_linear and shdr_rebin is now an average rather
+ than a sum. (6/23/92, Valdes)
+
+splot/wrspect.x
+ New output spectra are created type real. (6/22/92, Valdes)
+
+scombine/icscale.x
+scombine/t_scombine.x
+ The exposure time is only required now if scaling or weighting by
+ the exposure time. (6/22/92, Valdes)
+
+mwcs$wfmspec.x
+ The inverse coordinate transform could fail in some cases. An extra
+ check was added to avoid this. (6/17/92, Valdes)
+
+smw.x
+ Added special case to convert a 2D image which has a second dimension
+ length of 1 to a 1D image. Note this is different than a 1D section
+ of a 2D image. (6/17/92, Valdes)
+
+shdr.x
+ Added additional check for a 2D image with the dispersion axis
+ along a dimension of length 1; for example [800,1] with dispaxis=2.
+ This will also give an warning and then choose the appropriate
+ axis. (6/17/92, Valdes)
+
+t_sarith.x
+t_fitprofs.x
+splot/wrspect.x
+ The doppler correction was not properly handled when creating a new
+ output spectrum. (6/17/92, Valdes)
+
+shdr.x
+ The change to catch an inappropriate dispersion axis for TWODSPEC
+ images was not complete. I'm not fully sure anymore what should be
+ done but I made the checking better. (6/3/92, Valdes)
+
+t_sinterp.x
+ Change the roundoff when computing the number of pixels to nearest
+ integer. (6/3/92, Valdes)
+
+scombine/t_scombine:
+ There was a bug in which the j loop index was redefined in the loop
+ when checkin the MINMAX rejection limits. (6/1/92, Valdes)
+
+t_sarith.hlp
+ Needed to allocate the coeff pointer in sa_1d. Attempting to copy
+ a long slit spectrum to onedspec format caused a segmentation violation.
+ (5/27/92, Valdes)
+
+doc/scopy.hlp
+doc/sarith.hlp
+ The examples incorrectly showed nsum to be a task parameter.
+ (5/21/92, Valdes)
+
+bplot.cl
+ The error when a nonexistent image was specified was not properly
+ handled. (5/18/92, Valdes)
+
+splot/splot.key
+ Clarified 'o' key description. (5/14/92, Valdes)
+
+smw.x
+scombine/t_scombine.x
+ 1. Added additional commands to delete keywords which should not be
+ present.
+ 2. When mapping the output image a copy of the input image header is
+ made. This header may contain WCS keywords which are invalid.
+ A call is now made to smw_openim() which has the effect of cleaning
+ up the header.
+ (5/14/92, Valdes)
+
+=====
+V2.10
+=====
+
+doc/*.hlp
+doc/sys/onedv210.ms +
+ Make documentation changes to allow all revisions to be obtained with
+ "help onedspec.* sec=rev". The package revisions summary was prepared
+ and installed. (5/6/92, Valdes)
+
+splot/splot.x
+splot/splotcolon.x
+splot/splot.key
+splot.par
+doc/splot.hlp
+ 1. Added the option "wreset" to have the graph limits automatically
+ restored to the initial values for each new spectrum.
+ 2. Added colon commands to change the options interactively.
+ (5/6/92, Valdes)
+
+shdr.x
+ A 1D image section of a 2D (not multispec) image which is not along
+ the specified dispersion axis will now print a warning and use the
+ specified axis rather than aborting. (5/6/92, Valdes)
+
+smw.x
+shdr.x
+ Added checks in the case of log-linear dispersion (DC-FLAG=1) that
+ the coordinates make sense. Otherwise a linear dispersion is used.
+ This comes up when DC-FLAG is set to 1 but the other coordinate
+ information is incorrect or missing resulting in pixel coordinates.
+ Without this check there would be an attempt to take the dex of
+ a pixel coordinate causing a floating overflow error.
+ (5/5/92, Valdes)
+
+identify/t_reidentify.x
+ Added call to strip whitespace from the reference image name
+ accidentally entered by the user. Extra whitespace caused a
+ mysterious behavior in finding a database entry which was hard
+ to track down.
+ (5/1/92, Valdes)
+
+identify/idinit.x
+ Added check to not unmap the database if it was never openned.
+ This would cause a segmentation error if a database was never
+ accessed.
+ (5/1/92, Valdes)
+
+identify/iddb.x
+identify/t_reidentify.x
+identify/identify.h
+ The database interaction was poorly done resulting in repeatedly
+ opening and reading the database file. If there are many entries this
+ becomes very slow. The DTTEXT routines were modified to add a remap
+ routine allowing a database file to remain open but automatically
+ closing and opening a new database if the database name changes. It
+ also allows changing access modes by closing and opening the file but
+ leaving the rest of the data structure alone. This avoids the need to
+ rescan the file each time the access mode changes and allows existence
+ checks for entries (from the original scan) while still in APPEND mode
+ without having to switch file access modes. The identify structure was
+ extended to include the database pointer so that id_dbread and
+ id_dbwrite could use the remap routine without closing the database
+ between calls. Thus, repeated calls to id_dbread and id_dbwrite for
+ the same image are much more efficient and the database is only scanned
+ once in the first read. There is still a slight inefficiency in that
+ switching between reading and writing requires reopening the file. For
+ the purposes of simple checking for existing entries without needing to
+ read the entry and change modes a new routine id_dbcheck was added.
+ Finally, the logic in REIDENTIFY was modified so that repeated mode
+ switches between reading and writing are avoided. The id_dbcheck
+ routine is used when override checking is enabled. REIDENTIFY
+ is now much faster when dealing with large numbers of spectra in
+ images (long slit with a fine step size or multifiber spectra with
+ many fibers). (4/30/92, Valdes)
+
+smw.x
+ An axis map is set for 1D multispec images. (4/27/92, Valdes)
+
+shdr.x
+ Shdr_system was changing the wrong pointers causing later calls to
+ shdr_open to produce an invalid coordinate system. (2/18/92, Valdes)
+
+scombine/t_scombine.x
+scombine/iclog.x
+scombine/icscale.x
+scombine/icombine.x
+scombine.par
+doc/scombine.hlp
+ 1. The gain and read noise must be read when the image is open and
+ are stored in the RA and DEC spectrum structure parameters.
+ 2. NCOMBINE is not used on input.
+ 3. The exposure time is taken from the spectrum structure and the
+ keyword name is no longer a parameter.
+ (2/12/92, Valdes)
+
+scombine/icscale.x
+ Changed action for negative scaling, etc. to a warning.
+ (2/10/92, Valdes)
+
+calibrate.par
+sensfunc.par
+standard.par
+onedspec.par
+doc/calibrate.hlp
+doc/sensfunc.hlp
+doc/standard.hlp
+doc/package.hlp
+ 1. Redirected observatory parameter to package parameter
+ 2. Added observatory package parameter
+ (2/6/92, Valdes)
+
+ecidentify/ecdofit.x
+ecidentify/ecffit/ecfcolon.x
+ 1. The rejected points were not being reset between fits resulting in
+ misleading RMS values.
+ 2. Expanded the :show in fit mode.
+ (2/6/92, Valdes)
+
+t_standard.x
+standard.key
+ 1. The abbreviation of N or Y for NO or YES is now allowed.
+ 2. The key file was moved from noaolib$scr to onedspec$
+ (2/6/92, Valdes)
+
+t_calibrate.x
+t_standard.x
+irsiids/t_bswitch.x
+ Converted from obsimcheck to obsimopen. (2/4/92, Valdes)
+
+identify/*
+doc/identify.hlp
+ Added feature labels. (1/30/92, Valdes)
+
+refspectra.par
+doc/refspectra.hlp
+dispcor/ref*
+ 1. Added group parameter
+ 2. Sort parameter is now used as a double
+ 3. If group or sort keywords are specified but not found it is a fatal
+ error.
+ (1/29/92, Valdes)
+
+t_sfit.x
+sfit.par
+continuum.par
+eccontinuum.par
+doc/sfit.hlp
+doc/continuum.hlp
+ Added the new "markrej" parameter used in ICFIT to control whether
+ rejected points are marked. (1/21/92, Valdes)
+
+getcalib.x
+ The standard star parameter query will now print the file "standards.men"
+ in the calibration directory if the user supplied name does not match an
+ available file. (1/20/92, Valdes)
+
+irsiids/t_widstape.x
+ Modified the widstape task to support the new mag tape name syntax.
+ (1/7/92, Davis)
+
+identify/t_reidentify.x
+ If there is no dispersion function then no shift will now be computed.
+ (11/18/91, Valdes)
+
+ecidentify/ecffit/ecffit.x
+ Removed the progress print statements because they mess up the screen
+ clear on XTERM. Someday it might be desirable to put them back again.
+ (11/11/91, Valdes)
+
+doc/bswitch.hlp
+ Fixed minor typo where the keyword BEAM-NUM was refered to as BEAM.
+ (6/19/91, Valdes)
+
+t_combine.x
+ 1. The final coord scale must have WPC > 0. Needed to add an abs(WPC)
+ in case an input spectrum had negative WPC. (5/3/91, Valdes)
+
+getnimage.x
+t_bswitch.x
+ Moved procedure add_spec from getnimage.x to t_bswitch.x (4/25/91, Valdes)
+
+t_calibrate.x
+ MWCS modifications. Aperture selection option removed. (4/24/91, Valdes)
+
+splot/getimage.x
+splot/splotfun.x
+splot/splot.x
+splot/replot.x
+splot/autoexp.x
+ Modified to use separate coordinate array. (3/29/91, Valdes)
+
+iwewcs.x
+gmwcs.x
+wfinit.x
+wfmspec.x
+mwopenim1.x
+mkpkg
+idsm_keywrds.x
+load_hdr.x
+ Initial WCS modifications (3/28/91, Valdes)
+
+====
+V3.1
+====
+
+t_calibrate.x
+ Moved calibration messages outside of loop over bands.
+ (3/26/91, Valdes)
+
+ecidentify/ecidentify.x
+ecidentify.par
+ Added autowrite parameter which is similar to that of IDENTIFY.
+ (3/21/91, Valdes)
+
+ecidentify/ecffit/ecfsolve.x
+ The residual vector was not correctly set by ecf_solve. (3/18/91, Valdes)
+
+t_scopy.x
+ 1. If no beam number is found for ONEDSPEC images it defaults to 1.
+ 2. The image titles are converted to APID for ONEDSPEC images
+ going to MULTISPEC if the title differs from the main MULTISPEC
+ title.
+ 3. Added checking for repeated aperture numbers in ONEDSPEC to
+ MULTISPEC.
+ (3/13/91, Valdes)
+
+identify/t_reidentify.x
+reidentify.par
+ Interactive parameter is now four valued to allow better control
+ of reidentification queries such as in the IMRED scripts.
+
+dispcor/ecio.x
+dispcor/ecdispcor.x
+ 1. Fixed datatype error when reading the low and high values from the
+ APNUM keywords.
+ 2. Added REFSHFT capability for use with the FOE package.
+ 3. Added support for third dimension produced by APEXTRACT.
+ (1/31/91, Valdes)
+
+t_scopy.x
+ Fixed bugs in renumber option. It was renumbering before checking the
+ aperture list rather than after.
+ (1/31/91, Valdes)
+
+ecdispcor.par
+ The parameter override needed to be changed to the parameter rebin.
+ (1/16/91, Valdes)
+
+identify/iddb.x
+ REIDENTIFY checked if an entry in the database was absent by checking for
+ an error return from id_dbread. The error return was made without
+ first closing the database file. When reidentifying a large number
+ of images/apertures the task would run out of file descriptors.
+ The fix was to put a database close statement before the error
+ call. (1/7/91, Valdes)
+
+splot/getimage.x
+load_hdr.x
+ 1. Added error checking for aperture out of bounds in multispec format.
+ 2. Added automatic limit on band specification in multispec format.
+ 3. Added missing nband=0 for case of 1D image section.
+ (1/7/91, Valdes)
+
+identify/idlinelist.x
+ The 'l' did not find lines because the first pass to finding MAXFEATURES
+ did not discriminate against finding the same line with different
+ user coordinates. This locked out weaker features during the finding.
+ Then when the features were added to the feature least the MINSEP
+ parameter eliminated the duplicates resulting in fewer than MAXFEATURES
+ features. (12/19/90, Valdes)
+
+splot/stshelp.key
+splot/getimage.x
+splot/anshdr.x
+splot/mktitle.x
+splot/mkpkg
+splot/splotfun.x
+splot/splot.x
+splot/splot.key
+splot.par
+ 1. Added support for bands in 3D images. This involved adding a
+ band task parameter and a '%' key.
+ 2. The 'o' overplot key is now a toggle for the next graph. It
+ does not query for the image. The user follows 'o' with 'g',
+ '#', or '%'.
+ (12/19/90, Valdes)
+
+splot/deblend.x
+splot/gfit.x +
+splot/stsfit.x
+splot/stsfit.key +
+splot/splot.x
+splot/mkpkg
+noao$lib/scr/splot.key -
+splot/splot.key +
+ 1. The background was not subtracted in the initial amplitude estimate.
+ 2. The tau parameter in the call the hfti was too large. Changed
+ from .001 to 1E-10.
+ 3. Added new gaussian fitting function, key 'G'.
+ 4. Changed line help to use a file rather than coding the print
+ statements.
+ 5. Moved key file to source directory.
+ (12/19/90, Valdes)
+
+t_scopy.x
+ 1. Added a renumber option.
+ 2. For an input list of 1D images without onedspec extensions one can
+ uses a null aperture list to pack them into a single multispec
+ image.
+ (12/13/90, Valdes)
+
+splot/deblend.x
+ 1. Fixed bug that was scaling twice in computing the initial peak values.
+ This was also fixed in NEWIMRED.
+ 2. Last deblending prompt was not erased. Replaced with exiting
+ deblending message.
+ (12/4/90, Valdes)
+
+t_sfit.x
+ Fixed logfile prefix string from STFONTINUUM to SFIT.
+ (11/20/90, Valdes and Seaman)
+
+t_bswitch.x
+t_calibrate.x
+t_standard.x
+sensfunc/sfimage.x
+bswitch.par
+calibrate.par
+standard.par
+sensfunc.par
+doc/bswitch.hlp
+doc/calibrate.hlp
+doc/standard.hlp
+doc/sensfunc.hlp
+ Converted to using observatory database. (11/19/90, Valdes)
+
+t_fitprofs.x
+onedspec.hd
+doc/fitprofs.hlp +
+ 1. Modified to write output model even if there is a fitting error to
+ avoid output images with not pixel file.
+ 2. The image title was not dereferenced when generating the log title
+ string with onedspec format.
+ 3. Added help page.
+ (11/2/90, Valdes)
+
+identify/iddoshift.x
+ Added image label shift info.
+ (10/29/90, Valdes)
+
+indentify/t_reidentify.x
+ 1. The entrance into the interactive mode was not initializing such things
+ as the feature type and width. It now initializes using parameters from
+ IDENTIFY if needed.
+ 2. When not in verbose mode but when entering the interactive IDENTIFY
+ it did not print the revised statistics line. This has been fixed.
+ (10/22/90, Valdes)
+
+ecidentify.par
+noao$imred/echelle/doc/ecidentify.hlp
+ecidentify/ ecidentify/ecffit
+noao$lib/scr/ecidentify.key --> ecidentify/ecidentify.key
+noao$lib/scr/ecffit.key --> ecidentify/ecffit/ecffit.key
+ 1. Moved key files to source directory.
+ 2. Made changes allowing iterative rejection in the echelle dispersion
+ tasks. This adds three parameters to the ECIDENTIFY parameter
+ file, the database files (backwards compatible), and colon
+ commands in fitting mode. The feature lists printed and in the
+ database now include an additional column to indicated rejected
+ lines. (10/15/90, Valdes)
+
+splot/splot.x
+ Changed the temporary spool file to be in tmp$. (10/3/90, Valdes)
+
+doc/dispcor.hlp
+ Added notes warning that flux conservation will change the units of the
+ flux. (10/3/90, Valdes)
+
+splot/splot.x
+doc/splot.hlp
+noao$lib/scr/splot.key
+ Added :log and :nolog commands to toggle logging of measurements.
+ (10/3/90, Valdes)
+
+load_hdr.x
+ Header keyword datatype conversion errors are now a warning.
+ (10/3/90, Valdes)
+
+identify/idcolon.x
+ Unrecognized or ambiguous colon commands are now noted. (10/2/90, Valdes)
+
+dispcor.par (also in imred.iids and imred.irs)
+dispcor/dispcor.x
+dispcor/dcio.x
+dispcor/ranges.x
+doc/dispcor.hlp
+ 1. is_in_range not considers INDEF to be equivalent to MAX_INT. This
+ has the effect that if no range is specified, "", then INDEF is in
+ the range while is some specific range which is not open ended will
+ not include INDEF in the list.
+ 2. Added new verbose parameter and modified program to print messages
+ when spectra are skipped.
+ 3. Ignoreaps now only applies to the global wavelength determination.
+ (10/2/90, Valdes)
+
+ecidentify/ecffit/ecfgraph.x
+ Put a check to avoid trying to plot points outside the defined window.
+ Plotting very deviant points outside the rescaled window causes a
+ gio floating overflow error. This fix is a workaround before the
+ real bug gets fixed. (9/20/90, Valdes)
+
+identify/idgdata.x
+identify/idmap.x
+ Make changes to allow working with 3D multispec images.
+ (9/14/90, Valdes)
+
+calibrate.x
+sensfunc/sfgimage.x
+ Make simple changes to allow working with 3D multispec images.
+ (9/12/90, Valdes)
+
+splot/splot.x
+splot/fudgex.x
+doc/splot.hlp
+ 1. Changed the 'x' key to use only the x cursor values and connect the
+ nearest pixels. (8/31/90, Valdes)
+ 2. Added a new option, xydraw, to select drawing between x-y points
+ instead of using nearest pixel values. (9/5/90, Valdes)
+
+bplot.cl
+doc/bplot.hlp
+ BPLOT revised to use new SLIST. This is a much simpler and better
+ script. It selects on aperture numbers.
+ (8/24/90, Valdes)
+
+t_slist.x
+doc/slist.hlp
+ SLIST now has a format parameter. In multispec mode more approriate
+ output is obtained. The multispec mode allows selection by aperture.
+ The short header listing is good for making lists for scripts to scan.
+ (8/24/90, Valdes)
+
+================================
+V3 of ONEDSPEC installed 8/23/90
+================================
+
+fortran/polft1.f
+ Fixed bug in which reference was made to a part of some work arrays
+ not used by the program. This caused an arithmetic error on the MIPS.
+ (7/20/90, Valdes)
+
+onedspec.cl
+onedspec.men
+onedspec.cl
+bplot.cl
+doc/msdispcor.hlp +
+doc/bplot.hlp
+ 1. Added MSDISPCOR to the package.
+ 2. Replaced the old BPLOT with the code from MSBPLOT. This program
+ also uses change to SPLOT which selects by aperture number.
+
+load_hdr.x
+splot/splot.x
+splot/mktitle.x
+splot/deblend.x
+splot/eqwidth.x
+splot/eqwidthcp.x
+splot/anshdr.x +
+splot/anssave.x -
+splot/mkpkg
+doc/splot.hlp
+noao$lib/scr/splot.key
+t_standard.x
+ 1. Added mapping of APID keyword, if present, to the iids structure
+ LABEL field.
+ 2. SPLOT, STANDARD modified to use LABEL field instead of IM_TITLE.
+ 3. SPLOT modified to use different line type during overplotting.
+ 4. Removed maximum number limit for deblending.
+ 5. SPLOT now uses aperture number if the image is multispec/echelle.
+ 6. Added a new key, "#", to get new aperture without query about
+ image.
+
+t_specplot.x
+load_hdr.x
+idsm_keywrds.x
+dispcor/dcio.x
+dispcor/dispcor.x
+dispcor/msdispcor.x
+dispcor/ecdispcor.x
+sensfunc/sfoutput.x
+ Added CD1_1 as allowed substitute for WPC and CDELT1
+
+onedspec.hd
+ The revisions help is now a sys option.
+
+t_scopy.x +
+t_sapertures.x +
+scopy.par +
+sapertures.par +
+doc/scopy.hlp +
+doc/sapertures.hlp +
+mkpkg
+x_onedspec.x
+t_msselect.x -
+ 1. New task SCOPY added to handle copying and extraction apertures
+ between different formats
+ 2. New task SAPERTURES added to modify APNUM and APID info using
+ a text file.
+ 3. Removed MSSELECT/ECSELECT as they are replaced by SCOPY.
+
+onedspec.cl
+onedspec.hd
+onedspec.men
+x_onedspec.x
+t_sfit.x +
+sfit.par +
+continuum.par
+t_ecctm.x -
+continuum.cl -
+mkpkg
+doc/sfit.hlp
+doc/continuum.hlp
+ 1. New task SFIT added. This is a modification of Rob Seamans ECCONTINUUM
+ task.
+ 2. A new output option was added to output the data with any rejected
+ points replaced by fitting values. This replacement also may be used
+ with the difference and ratio output types.
+ 3. ECCONTINUUM is just a different name for SFIT.
+ 4. CONTINUUM is just a different name for SFIT. The script version
+ based on FIT1D has been removed.
+
+onedspec.par
+ Incremented version number to V3.
+
+t_specplot.x
+specplot.par
+specplot.key +
+doc/specplot.hlp
+noao$lib/scr/specplot.key -
+ 1. Added apertures and logfile parameters.
+ 2. Moved key file to source directory
+ 3. Added to save sp_vshow parameters in logfile.
+ 4. Added option to undelete last deleted spectrum.
+ 5. Extended to also plot anything in third dimension.
+ 6. Added sysid parameter.
+ 7. Added ability to set line type to histogram
+
+dispcor/dispcor.x
+ 1. Added aperture position information to APNUM keyword.
+
+msdispcor.par +
+dispcor/msdispcor.x
+dispcor/msio.x
+dispcor/msdispcor.com
+dispcor/mkpkg
+ 1. Added logfile. This is particularly for logging reference
+ shift interpolation information.
+ 2. Added support for 3D format
+ 3. Added aperture position info for spatial interpolation. The positions
+ are read for the object from the APNUM keyword, propagated as
+ needed, and read from the database for the dispersion functions.
+ 3a. A reference shift spectrum may be specified.
+ 4. Communicate aperture number through ms_seteval call and then do a
+ lookup for all other parameters.
+ 5. Propagate independent beam number.
+ 6. The number of apertures in the reference spectrum need not be
+ the same as the object spectrum though all object spectra must
+ have a reference dispersion function.
+ 7. Everything is now done by aperture number. This allows line
+ numbers to change, particularly between the dispersion reference
+ image and the data image.
+ 8. Dependence of msdispcor.x on msdispcor.com removed.
+ 9. Fixed rounding problem in wavelengths.
+
+dispcor/ecdispcor.x
+dispcor/ecio.x
+dispcor/ecdispcor.com
+dispcor/mkpkg
+ 1. Everything is done by aperture number using a call to ec_seteval.
+ This removes dependence on ecdispcor.com
+ 2. Aperture limit info is propagated
+
+identify/identify.x --> identify/t_identify.x
+identify/identify.h
+identify/linelist.x + *.x
+identify/iddoshift.x
+identify/iddb.x
+identify.par
+mkpkg
+ 1. Added an autowrite parameter to IDENTIFY.
+ 2. Simplified linelist package by passing id pointer. This affects
+ calling sequence of a number of procedures.
+ 3. Zero weight points are ignored and the number of valid features
+ used in the shift is printed.
+ 4. New id structure made some minor changes in main task.
+ 5. Dependence on center1d.h removed by including emmission/absorption
+ definitions in identify.h and new field in id structure.
+
+identify/iddb.x
+identify/identify.h
+identify/idgdata.x
+identify/ididentify.x
+identify/idinit.x
+identify/identify.key +
+identify/idmap.x +
+identify/idnoextn.x +
+identify/idgetim.x -
+idreplot.x -
+mkpkg
+ Added support for multispec format.
+ 1. The database name string includes aperture number.
+ 2. Image remains open for efficient movement through 2D image.
+ 3. A number of new fields are part of the id structure including
+ the image pointer, spectrum format, image axis, line number,
+ aperture info, and structure for saving copies of id structure.
+ 4. Added j, k, o keys to scroll through apertures.
+ 5. Changes are saved internally for multiple apertures until done
+ with the image.
+
+identify/idreidentify.x --> identify/t_reidentify.x
+reidentify.par
+reidentify.x -
+ 1. REIDENTIFY completely rewritten for efficiency, support for
+ multiaperture data, and for additional features and algorithms.
+ 2. The same number or order of apertures is not required.
+ 3. REIDENTIFY parameters changed to include interactive, track,
+ override, addfeatures, coordlist, match, maxfeatures, minsep,
+ graphics, cursor, and answer.
+
+------------------------------------------------------------------------------
+
+load_hdr.x
+splot/getimage.x
+ 1. Fixed bug that was setting NP1 to 1 instead of zero.
+ 2. Now load_hdr adjusts W0 to first good pixel.
+ 3. SPLOT no longer adjust W0 to first good pixel since it is done
+ by load_hdr.
+ (7/11/90, Valdes)
+
+onedspec$ecidentify/ecidentify.x
+onedspec$ecidentify/t_ecreid.x
+onedspec$ecidentify/ecdofit.x
+onedspec$ecidentify/ecffit/ecffit.x
+onedspec$ecidentify/ecffit/ecfsolve.x
+ 1. Added a fixed order fitting option so that ECREIDENTIFY will refit
+ with order fixed. This is mostly just passing a parameter down
+ to ecf_solve. (6/12/90, Valdes)
+
+onedspec$dispcor/msio.x
+ If an aperture identify entry was missing from the database the task
+ would quit with not error message. This is fixed now though the
+ new version to be installed soon will not have this approach to
+ mapping the dispersion solutions anyway. (6/4/90, Valdes)
+
+onedspec$doc/splot.hlp
+ Included query parameters since a user was asking about them.
+ (6/1/90, Valdes)
+
+====
+V2.9
+====
+
+onedspec$t_sums.x
+onedspec$sums.par
+ If an image already exists a new query parameter will be used to get
+ a new image name. (3/29/90, Valdes)
+
+onedspec$batchred.cl
+ Turned on extinction correction in calibrate for the case the spectra
+ are not already extinction calibrated. (3/29/90, Valdes)
+
+onedspec$ecidentify/ecdofit.x
+ When INDEF valued lines were used and features were deleted during
+ fitting the resorting of the feature list would get messed up.
+ This is a very rare condition which has now been fixed.
+ (3/16/90, Valdes)
+
+onedspec$identify/idgdata.x
+onedspec$dispcor/dcio.x
+onedspec$dispcor/ecdispcor.x
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/msdispcor.x
+onedspec$dispcor/disptable.r
+onedspec$sensfunc/sfoutput.x
+onedspec$load_hdr.x
+onedspec$idsm_keywrds.x
+onedspec$t_specplot.x
+ Added CDn_n to the set of keywords which may be used for the dispersion.
+ (2/8/90, Valdes)
+
+onedspec$splot/eqwidths.x
+onedspec$splot/sumflux.x
+ The equivalent width is now computed using the ratio of the spectrum
+ to the continuum. The previous approximation is printed in the log
+ file for comparison.
+ (3/5/90, Valdes)
+
+onedspec$splot/splot.x
+onedspec$splot/mktitle.x
+ 1. For :show added test for existence of spool file and an appropriate
+ message if it does not exist.
+ 2. Increase length of plotted title to SZ_LINE from 32.
+ (3/2/90, Valdes)
+
+onedspec$identify/iddofit.x
+ When INDEF valued lines were used and features were deleted during
+ fitting the resorting of the feature list would get messed up.
+ This is a very rare condition which has now been fixed.
+ (1/17/90, Valdes)
+
+onedspec$dispcor/ecdispcor.x
+ The sum option was actually the same as the average option!
+ (1/15/90, Valdes)
+
+199c199
+< call calloc (spec, nw, TY_REAL)
+---
+> call malloc (spec, nw, TY_REAL)
+208a209
+> call aclrr (Memr[spec], nw)
+212c213,218
+< case SUM, AVERAGE:
+---
+> case SUM:
+> do j = 1, nw
+> if (Memr[spec+j-1] != 0.)
+> Memr[outdata+j-1] = Memr[outdata+j-1] +
+> Memr[spec+j-1]
+> case AVERAGE:
+
+onedspec$load_hdr.x
+ Add limit checks for NP1 and NP2. (11/8/89, Valdes)
+
+onedspec$sensfunc/sfstds.x
+ The data for apertures which are in the aperture list when the ignoreaps
+ flag is set was not being read unless the aperture list included
+ aperture 1. This has been fixed. (11/8/89, Valdes)
+
+onedspec$load_hdr.x
+onedspec$t_specplot.x
+onedspec$splot/splot.x
+onedspec$splot/mktitle.x
+ 1. The new APID titles for multispec format spectra is now mapped into
+ the unused LABEL element of the IDS structure. For other formats
+ or if the keyword is missing then the image title is substituted.
+ 2. SPLOT now labels with the LABEL string rather the the image
+ title to allow individual titles for multispec spectra.
+ 3. SPECPLOT uses the APID titles if present.
+ (10/27/89, Valdes)
+
+onedspec$identify/iddofit.x
+ The order of evaluation in complex if statements is not necessarily
+ left to right as I'd thought. This caused a bus error on the
+ Convex. The particular change is as follows:
+
+ old:
+ if (rejpts != NULL && Memi[rejpts+k-1] == YES)
+ WTS(id,j) = 0.
+ else
+ WTS(id,j) = Memd[wts+k-1]
+ new:
+ WTS(id,j) = Memd[wts+k-1]
+ if (rejpts != NULL)
+ if (Memi[rejpts+k-1] == YES)
+ WTS(id,j) = 0.
+
+onedspec$load_hdr.x
+ Modified header access to use imaccf to check if header parameter exists
+ rather than rely on an error return. On a Sun3x the error checking
+ results in an exception. (9/28/89, Valdes)
+
+onedspec$t_calibrate.x
+ The data outside of calibration range message was changed to print how many
+ pixels are outside of the calibration range is printed once.
+ (8/8/89, Valdes)
+
+====
+V2.8
+====
+
+onedspec$idsmtn.h
+onedspec$t_subsets.x
+onedspec$t_standard.x
+onedspec$t_slist.x
+onedspec$t_shedit.x
+onedspec$t_flatdiv.x
+onedspec$t_calibrate.x
+onedspec$t_bswitch.x
+onedspec$t_addsets.x
+onedspec$load_hdr.x
+onedspec$idsm_keywrds.x
+onedspec$sensfunc/sfimage.x
+onedspec$splot/mktitle.x
+onedspec$shparams.par
+ The exposure time is used as a real rather than an integer (7/11/89, Valdes)
+
+onedspec$t_specplot.x
+ The wavelengths were off by one pixel because CRPIX was uninitialized
+ and so defaulting to zero instead of 1. (6/6/89, Valdes)
+
+onedspec$sensfunc/sfstds.x
+ Previously added check for INDEF exposure time extended to also check
+ for zero exposure time. (6/1/89, Valdes)
+
+onedspec$dispcor/msio.x
+ Because of a recent change in IDENTIFY in which 2D images with a
+ second dimension of 1 are treated as 1D images a related change
+ was required to allow multispec format spectra to be dispersion
+ corrected if there is only one spectrum. (5/15/89, Valdes)
+
+onedspec$load_hdr.x
+ Airmass values less than 1 are mapped in INDEF to force an airmass
+ computation. (5/8/89, Valdes)
+
+onedspec$splot/getimage.x
+ If the spectrum has only 1 line (even if it is two dimensional) there
+ is no query for the line number. Also the line number given by the
+ user for 2D images is limited to the range of image lines to avoid
+ an out of bounds error. (5/6/89, Valdes)
+
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/dcio.x
+onedspec$doc/dispcor.hlp
+ 1. The output spectrum will be of real datatype if the input spectrum
+ is short datatype.
+ 2. The last dispersion function defined for a 2D image is used for
+ all lines of a 2D image.
+ (5/6/89, Valdes)
+
+onedspec$doc/dispcor.hlp
+ Fixed mistake in description of the ignoreaps parameter. (5/6/89, Valdes)
+
+onedspec$identify/identify.h
+onedspec$identify/*.x
+ 1. Added weights to the IDENTIFY data structure.
+ 2. Modified files to use the weights parameter.
+ 3. The weights are currently used to flag iteratively rejected points
+ during fitting of the dispersion function.
+ 4. Reidentify now prints the RMS of only those lines used in the fit
+ and shows the number of points fit.
+ 5. The database files now include a column for the weights.
+ (5/5/89, Valdes)
+
+onedspec$t_standard.x
+onedspec$standard.par
+ 1. A warning message is printed if the exposure time is not found.
+ 2. Removed ennumerated value in parameter file.
+ (4/10/89, Valdes)
+
+onedspec$sensfunc/sfstds.x
+ 1. Standard values with negative counts are ignored thus avoiding
+ arithmetic problems.
+ 2. Warning message is printed if the exposure time in not defined and
+ a value of 1 is used. (4/10/89, Valdes)
+
+onedspec$dispcor/msio.x +
+onedspec$dispcor/msdispcor.com +
+onedspec$dispcor/msdispcor.x +
+onedspec$t_msselect.x +
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/mkpkg
+onedspec$mkpkg
+onedspec$x_onedspec.x
+ 1. New task MSDISPCOR to make dispersion correction in related
+ spectra in "multispec" format. This is a cross between
+ ECDISPCOR and DISPCOR.
+ 2. New tasks MSSELECT and ECSELECT to extract subsets of spectra
+ from echelle and multispec format. ECSELECT is simply an
+ alternate task name for MSSELECT.
+ 3. These new tasks use the procedures in the ONEDSPEC object
+ library but appear as logical tasks in the new MSRED package
+ and in the ECHELLE package.
+ (3/29/89, Valdes)
+
+onedspec$dispcor/dispcor.x
+ When not flux conserving the procedure asieval was being called
+ with a double value instead of a real giving completely incorrect
+ results. (3/22/89, Valdes)
+
+onedspec$dispcor/refmatch.x
+ There was a bug in the matching option in which the object image was
+ begin substituted for the reference image. (3/14/89, Valdes)
+
+onedspec$t_specplot.x
+onedspec$splot.par
+onedspec$splot/wrspect.x
+onedspec$load_hdr.x
+onedspec$identify/iddb.x
+ 1. Modified SPECPLOT to accept "multispec" and "echelle" formats.
+ 2. Modified SPLOT to accept "multispec" format for output. This is
+ only cosmetic since it is the same as "echelle" format.
+ 3. Modified ONEDSPEC header reader to accept "multispec" format.
+ This is only cosmetic since it is the same as "echelle" format.
+ 4. Modified IDENTIFY to not include the image section in the REFSPEC
+ parameter for use with "multispec" format.
+ (3/8/89, Valdes)
+
+onedspec$dispcor/dispcor.x
+onedspec$doc/dispcor.hlp
+ Simple modification to allow task to operate on all lines in a 2D
+ image. This is how the old program also worked. (3/8/89, Valdes)
+
+onedspec$t_calibrate.x
+ 1. CALIBRATE did not take the differing lengths of the echelle orders
+ into account and so gave many warnings about spectrum extends outside
+ of flux calibration limits.
+ 2. The warning is now only printed once per spectrum/order rather than
+ for each pixel.
+ (2/27/89, Valdes)
+
+onedspec$t_specplot.x
+ Made CRPIX1 a real valued parameter. (2/27/89, Valdes)
+
+onedspec$t_widstape.x
+ The function mtfile is now used to determine if the input file is
+ a mag tape. Previously, the code was checking that the first two
+ letters of the input file were 'mt', which fails for remote tape
+ drives. (2/22/89 ShJ)
+
+onedspec$doc/refspectra.hlp
+ A new help page for the refspectra task has been installed.
+ (2/27/88, Davis)
+
+onedspec$doc/continuum.hlp
+ Added a warning about near zero divisions. (2/14/89, Valdes)
+
+onedspec$identify/idlinelist.x
+onedspec$ecidentify/eclinelist.x
+ Setting the coordinate line list to null no longer issues a warning.
+ (2/13/89, Valdes)
+
+onedspec$specplot.x
+onedspec$doc/specplot.hlp
+noao$lib/scr/specplot.key
+ 1. Added vertical shifts in scale.
+ 2. Added horizontal shifts in velocity.
+ 3. Added velocity and redshift colon commands.
+ (2/8/89, Valdes)
+
+onedspec$splot/splot.x
+ The default key now prints the spectrum value at the x coordinate in
+ addition to the cursor x, y coordinates. (2/7/89, Valdes)
+
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/ecdispcor.x
+onedspec$dispcor.par
+onedspec$ecdispcor.par
+imred$coude/dispcor.par
+imred$echelle/ecdispcor.par
+imred$iids/dispcor.par
+imred$irs/dispcor.par
+imred$specphot/dispcor.par
+onedspec$doc/dispcor.hlp
+imred$echelle/doc/ecdispcor.hlp
+ Changed "override" parameter to "rebin". Also rebin=no acts only
+ on nondispersion corrected spectra while rebin=yes acts only on
+ dispersion corrected spectra. (2/2/89, Valdes)
+
+onedspec$dispcor/refaverage.x
+onedspec$dispcor/reffollow.x
+onedspec$dispcor/refgspec.x
+onedspec$dispcor/refinterp.x
+onedspec$dispcor/refmatch.x
+onedspec$dispcor/refnearest.x
+onedspec$dispcor/refprecede.x
+onedspec$refspectra.par
+onedspec$doc/refspectra.hlp
+imred$coude/refspectra.par
+imred$echelle/refspectra.par
+imred$iids/refspectra.par
+imred$irs/refspectra.par
+imred$specphot/refspectra.par
+ Added timewrap parameter and reorganized calling sequences so the
+ sortval is set only in refgspec. (2/2/89, Valdes)
+
+onedspec$reidentify.x
+ Stripped the image extension from the reference spectrum.
+ (1/31/89, Valdes)
+
+noao$lib/scr/ecidentify.key
+ Fixed minor typo "j Go to next order" --> "k Go to next order".
+ (1/26/89, Valdes)
+
+onedspec$dcio.x
+ An erroneous sfree in dc_gspec was removed. (1/26/89, Valdes)
+
+onedspec$idsm_keywrds.x
+onedspec$load_hdr.x
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/ecdispcor.x
+ Changed CRPIX usage to real. (1/26/89, Valdes)
+
+onedspec$names.par
+imred$coude/names.par
+imred$iids/names.par
+imred$irs/names.par
+ Made the "input" parameter prompt indicate it is a list rather than
+ a single file. (1/24/89, Valdes)
+
+onedspec$splot.par
+imred$coude/splot.par
+imred$echelle/splot.par
+imred$iids/splot.par
+imred$irs/splot.par
+imred$specphot/splot.par
+ Made the minimum line number be 1 instead of 0. (1/24/89, Valdes)
+
+onedspec$splot/splot.x
+ The 'w' window option in SPLOT now only redraws automatically in
+ "auto" mode. (1/24/89, Valdes)
+
+onedspec$ecidentify/ecffit/ecffit.x
+ The 'o' key now accepts the default order for fitting; i.e. a
+ carriage return for the prompt. Also the message about fitting
+ now also includes the order offset being used. (1/24/89, Valdes)
+
+onedspec$idgdata.x
+ Now allow 2D images with a second dimension of 1. (1/24/89, Valdes)
+
+onedspec$dispcor/refinterp.x
+ When interpolating on a parameter that is the same for a set of arcs
+ and an object one wants two arcs to be identified; i.e. the one before
+ and after. This did not happen until this bug fix. (1/20/89 Valdes)
+
+onedspec$sensfunc.par
+imred$echelle/sensfunc.par
+imred$iids/sensfunc.par
+imred$irs/sensfunc.par
+imred$specphot/sensfunc.par
+onedspec$standard.par
+imred$echelle/standard.par
+imred$iids/standard.par
+imred$irs/standard.par
+imred$specphot/standard.par
+ Fixed missing default value for answer parameter. (1/20/89, Valdes)
+
+onedspec$splot/pixind.x
+ Removed use of AINT function which was misbehaving on Sun386i.
+ (12/16/88 Valdes)
+
+onedspec$identify/reidentfy.x
+onedspec$identify/idreidentfy.x
+onedspec$identify/idreplot.x +
+onedspec$doc/reidentfy.hlp
+onedspec$reidentfy.par
+twodspec$longslit/reidentfy.par
+imred$coude/reidentfy.par
+imred$iids/reidentfy.par
+imred$irs/reidentfy.par
+imred$specplot/reidentfy.par
+ Added plotfile for residuals. (12/16/88 Valdes)
+
+onedspec$dispcor/dcio.x
+ If a reference spectrum is an image section its database entry will
+ be the file with the section stripped. Since the database entry
+ is written by IDENTIFY I copied the database access code that
+ strips the image section. (12/8/88 Valdes)
+
+onedspec$dispcor/dispcor.x
+ The use of some real variables in the flux conservation calculation
+ resulted in incorrect results when the resolution was very high.
+ The code was carefully rewritten to do all possible calculations in
+ double precision. (12/8/88 Valdes)
+
+onedspec$t_specplot.x +
+onedspec$specplot.par +
+onedspec$specplot.h +
+onedspec$doc/specplot.hlp +
+noao$lib/scr/specplot.key +
+onedspec$x_onedspec.x
+onedspec$onedspec.cl
+onedspec$onedspec.men
+onedspec$onedspec.hd
+onedspec$mkpkg
+ New task added (12/7/88 Valdes)
+
+onedspec$t_standard.x
+ Fixed minor bug: missing parg in eprintf when dispersion solution
+ missing. (11/4/88 & 11/17/88)
+
+onedspec$identify/ididentify.x
+onedspec$identify/idfitdata.x
+ The nonmonotonic error message was being lost because it is flushed
+ immediately to the screen and then the screen is cleared to redraw
+ the graph. This has now been fixed by checking for an error just
+ before the cursor read. (11/2/88)
+
+onedspec$identify/identify.x
+onedspec$identify/iddb.x
+onedspec$identify.par
+onedspec$doc/identify.hlp
+ 1. Added the additional icfit parameters (except naverage) to IDENTIFY
+ so the user can set the default fitting parameters more fully.
+ 2. All the ICFIT fitting parameters are now written to the database and
+ read back. This allows IDENTIFY and REIDENTIFY to start with exactly
+ the same fitting parameters as previously used. (11/2/88)
+
+onedspec$t_bswitch.x
+ Added a test for the extinction correction request before trying to compute
+ the airmass. (11/1/88)
+
+onedspec$ecidentify/eccolon.x
+ 1. When the label parameter was initially set to user all the labels
+ were being printed not just those for the current aperture. The bug has
+ been fixed. (9/9/88)
+
+onedspec$dispcor/dispcor.x
+ 1. A bug was fixed in the log+ option of dispcor and ecdispcor. The
+ problem was that the end points of the wavelength region were in
+ linear wavelength units but the w1 and dw parameters were in log units,
+ causing an erroneous computation of the index for the first pixel.
+ This bug has been fixed. (9/9/88)
+
+onedspec$dispcor/refspectra.x
+onedspec$onedspec.cl
+onedspec$onedspec.men
+onedspec$batchred.cl +
+onedspec$batchred.par +
+onedspec$bswitch.par +
+onedspec$coefs.par -
+onedspec$standard.par
+onedspec$sensfunc.par
+ 1. BATCHRED and BSWITCH were put back into this package.
+ 2. COEFS was removed from this package.
+ 3. Enumerated strings were added to SENSFUNC and STANDARD parameter
+ files to prevent the tasks from dying on a bad value (i.e. clgwrd
+ was causing an error). By putting the allowed values in the parameter
+ file the CL will wait for an allowed value.
+ 4. REFSPECTRA does not change the value of the confirm parameter now.
+ (7/29/88 Valdes)
+
+
+onedspec$splot/deblend.x
+onedspec$doc/splot.hlp
+ 1. The fitting parameter initialization was being done even before the
+ 'q'. Thus, the '-' subtraction did not use the fit but the initial
+ parameters.
+ 2. Modified the initial sigma to be 1/4 of the range divided by the number
+ of lines. The 1/2 was too large. (7/26/88 Valdes)
+
+onedspec$splot.par
+onedspec$splot/deblend.x
+onedspec$splot/scr_help.x -
+onedspec$doc/splot.hlp
+ 1. Removed unused parameters inblend, fixsep, difference, subtract from
+ parameter file.
+ 2. Fixed bug with '-' in deblending (continuum was not being subtracted).
+ 3. Removed unused source file.
+ 4. Update the help page. (7/19/88 Valdes)
+
+onedspec$splot/deblend.x
+ Fixed bug introduced below. (7/12/88 Valdes)
+
+onedspec$splot/deblend.x
+onedspec$onedspec.hd
+noao$lib/scr/deblend.key
+onedspec$doc/splot.hlp
+ 1. After moving the parameter initialization to within the options loop the
+ initializations were being done wrong.
+ 2. The 'd' option was not doing what it was supposed to.
+ 3. Added a print newline to clear the status line if four lines were
+ entered since this does not go through the 'q' case which was
+ doing the clear.
+ 4. The n sigma cases had the wrong mneumonics in the help.
+ 5. The src definitions in the help table were pointing to wrong files
+ since the names and directories for the files have been changed
+ (7/1/88 Valdes)
+
+
+onedspec$t_names.x
+onedspec$mkpkg
+ Modified this task to use the ODR package. This also strips the image
+ extension allowing the append option to work. (6/28/88 Valdes)
+
+onedspec$coincor.x
+ When doing both coincidence and power law corrections failed to put the
+ output of the coicidence correction as the input to the power law
+ correction. (6/23/88 Valdes)
+
+onedspec$identify/idgdata.x
+ Added an error check to IMMAP. Failure to do this gave a segmentation
+ violation on the SUNS. (6/23/88 Valdes)
+
+onedspec$continuum.cl
+ 1. Added a parameter to allow a cursor list text file to be passed to the
+ normcontinuum task.
+
+onedspec$ecidentify/ecgdata.x
+onedspec$ecidentify/ecffit/ecfcolon.x
+onedspec$ecidentify/ecffit/ecfset.x
+onedspec$ecidentify/ecffit/ecfsolve.x
+onedspec$ecidentify/ecffit/ecfrms.x +
+onedspec$ecidentify/ecffit/mkpkg
+noao$lib/scr/ecidentify.key
+noao$lib/scr/ecffit.key +
+ The following was fixed. (5/20/88 Valdes)
+ 1. Error in graph title string.
+ 2. Missing cursor key help.
+ 3. Error in ":function" command in fitting mode.
+ 4. Rms calculated with deleted points.
+
+onedspec$dispcor/dispcor.x
+onedspec$dispcor/ecdispcor.x
+ 1. Failed to initialize the output spectrum to zero so that points
+ outsided the input data range are zero. (5/17/88 Valdes)
+
+onedspec$dispcor/refaverage.x
+ 1. Instead of checking the reference spectra for aperture and reference
+ flag it was test the input image. This was changed. (5/17/88 Valdes)
+
+onedspec$load_hdr.x
+onedspec$splot/deblend.x
+ 1. The deblending was fitting a function without the factor of 2 in
+ the Gaussian sigma definition. This caused the printed Gaussian
+ parameters to be off by a factor of sqrt(2).
+ 2. Slight change to not have the header loading change the specified
+ input line. It is up to the calling code to determine if this is
+ a valid line. (5/17/88 Valdes)
+
+onedspec$identify/idreidentify.x
+ 1. Added check for nonmonotonic dispersion solution. (4/30/88)
+
+onedspec$onedspec.cl
+onedspec$onedspec.men
+onedspec$onedspec.hd
+ 1. Task EXTINCT was removed. The script and help page remain in case
+ they are desired. Later they will also disappear. The function of
+ this script is replaced by CALIBRATE. (4/26/88 Valdes)
+ 2. Task BATCHRED was removed to the IMRED packages. (4/27/88 Valdes)
+
+onedspec$splot/splot.x
+onedspec$splot/deblend.x
+onedspec$t_flatfit.x
+onedspec$identify/ididentify.x
+onedspec$ecidentify/ecidentify.x
+onedspec$ecidentify/ecffit/ecffit.x
+noao$lib/scr/splot.key
+noao$lib/scr/identify.key
+noao$lib/scr/ecidentify.key
+noao$lib/scr/deblend.key
+noao$lib/scr/ecffit.key
+noao$lib/scr/flatfit.key
+ Added 'I' interrupt key. (4/20/88 Valdes)
+
+onedspec$identify/identify.h
+ Variables defined as integers instead of real (ID_MATCH, ID_MINSEP)
+ (4/18/88 Valdes)
+
+onedspec$sensfunc/t_sensfunc.x
+onedspec$sensfunc/sfsensfunc.x
+onedspec$sensfunc/sfstds.x
+onedspec$sensfunc/sfginit.x
+onedspec$sensfunc/sfoutput.x
+onedspec$sensfunc.par
+onedspec$doc/sensfunc.hlp
+noao$lib/scr/sensfunc.key
+ 1. Added beam number to output sensitivity image header.
+ 2. Added 'I' interrupt key.
+ 3. Added aperture number selection.
+ 4. Added interactive query. (4/15/88 Valdes)
+
+onedspec$splot/getimage.x
+ Modified to recognize echelle format spectra on input. (4/8/88 Valdes)
+
+onedspec$load_hdr.x
+ Modified to recognize echelle format spectra on input. (4/8/88 Valdes)
+
+onedspec$mkpkg
+onedspec$splot/mkpkg
+onedspec$identify/mkpkg
+onedspec$fortran/mkpkg
+onedspec$onedutil.cl
+onedspec$onedspec.cl
+onedspec$onedspec.par
+onedspec$onedspec.men
+onedspec$onedspec.hd
+onedspec$identify/*
+onedspec$t_flatdiv.x
+onedspec$t_coefs.x
+onedspec$t_combine.x
+onedspec$dispcor.par
+onedspec$identify/identify.par --> onedspec$identify.par
+onedspec$identify/reidentify.par --> onedspec$reidentify.par
+onedspec$doc/dispcor.hlp
+
+onedspec$dispcor/* +
+onedspec$ecidentify/* +
+onedspec$x_onedspec.x +
+onedspec$refspectra.par +
+onedspec$dispcor1.par +
+onedspec$ecidentify.par +
+onedspec$ecreidentify.par +
+onedspec$doc/refspectra.hlp +
+
+onedspec$x_wavecal.x -
+onedspec$x_fluxcal.x -
+onedspec$x_onedutil.x -
+onedspec$identify/x_identify.x -
+onedspec$identify/libpkg.a -
+onedspec$dbx/ -
+onedspec$dbxio.h -
+onedspec$userstd/ -
+onedspec$t_dispcor.x -
+onedspec$fudge.x -
+onedspec$rlsq.x -
+onedspec$userstdc.x -
+onedspec$readstd.x -
+onedspec$qsortra.x -
+onedspec$statfile.x -
+onedspec$ascrcomp.x -
+onedspec$identify/icghelp.x -
+onedspec$splot/spflip.x -
+ The ONEDSPEC package has been completely reorganized by combining
+ executables, eliminating obsolete procedures, and adding new
+ versions of IDENTIFY and DISPCOR as well and new tasks for echelle
+ format data. (4/7/88 Valdes)
+
+-------------------------------------------------------------------------------
+
+onedspec$mkpkg
+onedspec$splot/deblend.x
+ Fixed bugs related to initial guesses for width and peak and scaling.
+ Replaced Gauss-Jordan routine by Householder transformation routine
+ for stability. (4/6/88 Valdes)
+
+onedspec$load_hdr.x
+ The test for wavelengths in meters per second was W0 < 0.001.
+ Now the test is abs(W0) < 0.001. (3/10/88 Valdes)
+
+onedspec$identify/ididentify.x
+ The 't' was calling fit_to_pix with the real valued cursor position
+ while the procedure expects a double. Added a double coercion to fix
+ the bug. (2/18/88 Valdes)
+
+onedspec$splot/anssave.x
+onedspec$splot/mktitle.x
+onedspec$splot/getimage.x
+onedspec$splot/splot.x
+onedspec$splot/splotfun.x
+ 1. Titles (on the graph and in the log file) for two dimensional images
+ now contain the line number given as an image section.
+ 2. The log file title now includes a time stamp. (1/29/88 Valdes)
+
+onedspec$identify/ididentify.x
+ When recentering all the features the fitted coordinates are now
+ updated and the tick marks moved to the new center position.
+ (1/4/87 Valdes)
+
+onedspec$identify/iddb.x
+ DBGETR was declared as real for the new shift parameter causing a wrong
+ wavelength scale to appear. (12/22/87 Valdes)
+
+onedspec$doc/identify.hlp
+ Fixed minor typo. (12/7/87 Valdes)
+
+onedspec$sextract.cl +
+onedspec$doc/sextract.cl +
+onedspec$onedutil.cl
+onedspec$onedutil.par
+onedspec$onedutil.men
+onedspec$onedutil.hd
+ Added a new task, SEXTRACT, to extract subspectra. (11/19/87)
+
+onedspec$t_dispcor.x
+ The default starting wavelength and wavelength interval are now printed
+ in g format so that the user sees the full value. (11/9/87)
+
+onedspec$identify/identify.x
+onedspec$identify/reidentify.x
+onedspec$identify/idgraph.x
+ 1. The XTOOLS change to XT_MK1D now permits the sections "column 51"
+ and "column 051" to be recognized identically.
+ 2. REIDENTIFY now aborts with a useful error message if their is not
+ database record for the reference image instead of later causing
+ a segmentation error.
+ 3. IDENTIFY can now plot in point mode using the GTOOLS commands if
+ desired.
+ (11/9/97 Valdes)
+
+noao$onedspec$sensfunc/sfextinct.x
+noao$onedspec$sensfunc/sfsensfunc.x
+noao$onedspec$sensfunc/sfreset.x
+noao$onedspec$sensfunc/sfmarks.x
+noao$onedspec$sensfunc/sfadd.x
+noao$onedspec$sensfunc/sfdelete.x
+noao$onedspec$sensfunc/sfundelete.x
+noao$onedspec$sensfunc/sfmove.x
+noao$onedspec$sensfunc/sfgraph.x
+noao$onedspec$sensfunc/sfginit.x
+noao$onedspec$sensfunc/sfcomposite.x
+noao$onedspec$sensfunc/sfcolon.x
+noao$onedspec$sensfunc/sfshift.x
+noao$onedspec$sensfunc/sensfunc.h
+noao$onedspec$doc/sensfunc.hlp
+noao$lib/scr/sensfunc.key
+ A number of changes were made based on user comments.
+ 1. A bug was fixed which caused the ":order" command to crash the task.
+ The integer valued order was being passed as a char in the colon
+ decoding task.
+ 2. The shift key 's' now toggles allowing a shift to be undone
+ without initializing all the data. Also a message is printed
+ to indicate what has been done.
+ 3. The composite key 'c' now toggles allowing a composite to be undone
+ without initializing all the data. Also a message is printed
+ to indicate what has been done. A deleted composite point deletes
+ the original data at that wavelength when toggling back.
+ 4. The extinction key 'e' now toggles allowing an extinction
+ correction to be undone without initializing all the data.
+ Also a message is printed to indicate what has been done.
+ 5. A different symbol may be used to indicated added points.
+ 6. Changing the function or order does not automatically do a
+ new fit.
+ 7. A new key 'g' was added to do a fit and redraw the graph(s).
+ The existing 'f' key does a fit and overplots as before.
+ (11/6/87 Valdes)
+
+onedspec$splot/replot.x
+onedspec$splot/splot.x
+onedspec$splot/autoexp.x
+ Modified REPLOT to use GTOOLS task GTVPLOT. This allows the user to
+ select point mode. The calling sequence for REPLOT has a new argument
+ to allow calling this procedure for overplotting. (11/5/87 Valdes)
+
+onedspec$identify/*
+onedspec$identify/iddoshift.x +
+ Added shift options to IDENTIFY and a refit option to REIDENTIFY.
+ This allows maintaining the same coordinate function with an additive
+ shift. (11/3/87 Valdes)
+
+onedspec$sensfunc/sfgraphs.x -
+onedspec$sensfunc/mkpkg.x
+ A zero length file, possibly confused with sfgraph.x was deleted and
+ deleted from the mkpkg. (10/26/87 Valdes)
+
+onedspec$splot/deblend.x
+onedspec$splot/sumflux.x
+ 1. The input data to the deblending routine are now scaled to values
+ near unity. Also the fitting is iterated three times to make the
+ results more consistent.
+ 2. When computing the line center with 'e' the data is scaled to
+ avoid underflows in summing residuals to the 1.5 power.
+ (See bug report 16) (10/22/87 Valdes)
+
+onedspec$sensfunc/sfsensfunc.x
+onedspec$sensfunc/sfextinct.x
+ 1. Aperture number for new aperture in title was undefined in the first
+ graph. Set title after determining aperture number.
+ 2. In a rare case it was possible for a square root of zero to occur
+ in the extinction significance calculation which is fatal on VMS.
+ Added check of argument before square root call. (Valdes)
+
+onedspec$splot.par
+ Changed all interactive query parameters from auto mode to query
+ mode to force a query even when run in menu mode and with :go.
+ (9/15/87 Valdes)
+
+onedspec$t_standard.x
+onedspec$t_lcalib.x
+onedspec$splot/plotstd.x
+onedspec$standard.par
+onedspec$lcalib.par
+onedspec$splot.par
+onedspec$doc/standard.hlp
+onedspec$doc/lcalib.hlp
+onedspec$doc/splot.hlp
+ The magnitude to absolute flux conversion constant has been made a
+ user changable parameter in the three tasks dealing with the flux
+ calibration tables. (9/3/87 Valdes)
+
+onedspec$t_sensfunc.x -
+onedspec$sensfunc/* +
+noao$lib/scr/sensfunc.key +
+onedspec$sensfunc.par
+onedspec$doc/sensfunc.hlp
+onedspec$t_standard.x
+onedspec$doc/standard.hlp
+onedspec$bswitch.par
+onedspec$getextn.x
+ SENSFUNC has been completely rewritten. It now allows determination
+ of extinction, display of flux calibrated spectra, and many nice
+ features for displaying and manipulating the data. For full details
+ read the new help page.
+
+ The new sensfunc required some modifications to STANDARD in the
+ format of the output file produced by standard. The parameters for
+ BSWITCH no longer have the grey scale parameter add_const or the
+ (never implemented) revised extinction file rev_ext_file which are
+ not produced by SENSFUNC any more.
+
+ The extinction loading procedure was modified to allow a null
+ extinction file to correspond to no extinction and to eliminate the
+ procedure get_new_ext and fix_ext which were used for the old grey
+ constant and never implemented revised extinction file. (9/3/87
+ Valdes)
+
+onedspec$splot/mkpkg
+onedspec$splot/splot.x
+onedspec$splot/splotfun.x
+ Errors getting a spectrum in function mode were ignored and the spectrum
+ was replotted. Changed to return the error as a warning and not redraw
+ the plot.
+
+onedspec$t_dispcor.x (routine reinterp)
+ The reinterpolation now has additional tests:
+ 1) When the interpolation point is within a minimum distance of
+ an input pixel (0.001) it does not interpolate. This was
+ done because the interpolation grid is sometimes meant to be
+ identical with the input but the computation of the output grid
+ is very slightly off (this was observed in COMBINE).
+ 2) If one of the points to be interpolated between has a value of
+ 0.0 (used to mark missing data in ONEDSPEC) then the rebinned
+ point is set to 0.0 in order to propagate the missing point.
+ This is important for combining spectra with COMBINE. (8/5/87 Valdes)
+
+====
+V2.5
+====
+
+onedspec$t_sinterp.x
+ Valdes, June 22, 1987
+ 1. Removed a warning message to allow comments in the input table.
+
+onedspec$splot/avgsnr.x
+ Valdes, June 19, 1987
+ 1. A possible type of data is Fnu calibrated data with values in the
+ range 1e-25. Attempting to determine an average, rms, and
+ signal-to-noise ratio with SPLOT caused a divide by zero error
+ due to underflowing the sum of squares. This has been modified
+ to shift and scale the data before computing the sum of squares.
+
+onedspec$t_standard.x
+ Valdes, June 12, 1987
+ 1. There was an uninitialized memory problem with the space allocated
+ for adding points. This bug was introduced with the May 15th
+ modifications to the structure of the calibration files.
+
+onedspec$load_hdr.x
+onedspec$idsm_keywrds.x
+onedspec$t_calibrate.x
+ Valdes, June 9, 1987
+ 1. Added EXPTIME as a recognized exposure time keyword.
+ 2. Added check against INDEF or 0 exposure time in CALIBRATE.
+
+onedspec$bplot.cl
+ Valdes, June 4, 1987
+ 1. The BPLOT script is now back the way it was earlier because the
+ earlier bug with the CL and list files seems to have gone away
+ while the new script relies on writing to parameter files which
+ doesn't work in the background.
+
+onedspec$onedspec.cl
+onedspec$onedspec.hd
+onedspec$onedspec.men
+onedspec$powercor.cl +
+onedspec$powercor.par +
+onedspec$getcalib.x
+onedspec$doc/powercor.hlp +
+ Valdes, June 1, 1987
+ 1. Added task POWERCOR from IIDS.
+ 2. Added an error check for a bad extinction file.
+
+onedspec$splot/deblend.x
+ Valdes, May 19, 1987
+ 1. A bug that was introduced into deblending during the last set of
+ changes was fixed.
+
+onedspec$onedutil.par
+onedspec$lcalib.par
+onedspec$t_lcalib.x
+ Valdes, May 19, 1987
+ 1. Make the default for the calibration parameters in LCALIB to
+ be package parameters of the same name in keeping with the way these
+ parameters are used in the other ONEDSPEC tasks.
+ 2. Added the calibration parameters to the ONEDUTIL package and
+ the default is to refer to the parameters of the package that loaded
+ it. This will be either ONEDSPEC or one of the IMRED packages.
+ 3. Modified LCALIB to not require the extinction file when reading
+ star calibration info.
+
+onedspec$mkpkg
+onedspec$bswitch.par
+onedspec$lcalib.par
+onedspec$onedspec.par
+onedspec$splot.par
+onedspec$standard.par
+onedspec$t_lcalib.x
+onedspec$t_standard.x
+onedspec$x_fluxcal.x
+onedspec$x_onedutil.x
+onedspec$getcalib.x
+onedspec$getextn.x
+onedspec$plotstd.x -
+onedspec$splot/mkpkg
+onedspec$splot/plotstd.x +
+onedspec$doc/standard.hlp
+onedspec$doc/lcalib.hlp
+onedspec$doc/onedspec.hlp
+onedspec$doc/splot.hlp
+onedspec$doc/bswitch.hlp
+noao$imred/echelle (par files)
+noao$imred/iids (par files)
+noao$imred/irs (par files)
+noao$imred/specphot (par files)
+noao$lib/onedstds (data files)
+ Valdes, May 15, 1987
+ 1. The major change was to change the format of the calibration data
+ from the very constrained old format to a more flexible format.
+ This also involved adding a new parameter "extinction" and changing
+ "calib_file" to "caldir".
+ 2. The calibration data files were converted to the new format in a
+ number of subdirectories.
+ 3. The parameter files in the IMRED directories were also updated.
+ 4. Moved plotstd.x to splot directory. It is an splot routine and should
+ be with the other splot source.
+ 5. Moved LCALIB from the FLUXCAL executable to the ONEDUTIL executable.
+
+onedspec$splot/usercoord.x
+ Valdes, May 8, 1987
+ 1. When setting a wavelength scale using the 'u' key on data lacking
+ any wavelength information (W0 and WPC == INDEF) there was a bug
+ causing a message of the form "cursor not moved".
+
+onedspec$splot/deblend.x
+onedspec$splot/splot.x
+onedspec$splot/eqwidthcp.x
+ Valdes, April 30, 1987
+ 1. I missed a couple of places where READ_WRITE access was used
+ in SPLOT (see March 13, 1987). These have been removed.
+ 2. There was a bug in the 'k' and 'v' type equivalent width
+ procedures which produced wrong results unless the cursor was
+ very near the center.
+ 3. When applying deblending to a single line the starting position
+ is now the minimum or maximum point of the continuum subtracted
+ profile rather than the center of the continuum limits.
+
+onedspec$splot/deblend.x
+onedspec$splot/splot.x
+onedspec$splot/anssave.x
+onedspec$splot/eqwidthcp.x
+onedspec$splot/eqwidth.x
+onedspec$doc/splot.hlp
+noao$lib/scr/splot.key
+ Valdes, April 28, 1987
+ 1. SPLOT now prints only one line of output on the graphics status line
+ when doing deblending or equivalent width measurments. The full
+ output is saved in the log file and also internally. These changes
+ were made to allow reasonable behavior in terminals which cannot
+ display text and graphics simultaneously (PC emulators, VT240's).
+ 2. To get the full output of previous measurements during the course of
+ the task execution a new command ":show" has been added.
+ 3. It was possible for deblending to yeild negative sigmas. This has been
+ fixed as well.
+
+onedspec$doc/names.hlp
+ Valdes, April 27, 1987
+ 1. A bug note was added to the task help stating that the append option
+ is intended only for image sections. Appending any other string
+ produces names not acceptable to ONEDSPEC.
+
+onedspec$identify/identify.x
+onedspec$identify/ididentify.x
+onedspec$identify/idlinelist.x
+onedspec$identify/idnewfeature.x
+ Valdes, April 15, 1987
+ 1. Added bell if feature not found with 'm'.
+ 2. When automatically identifying lines, 'l' it now requires a new line
+ to be within the matching distance relative to the current fit and
+ if two centers are withing "minsep" then the closest match to the
+ user coordinate is selected.
+ 3. Default initial graph for fitting is residuals vs. wavelength.
+
+onedspec$t_standard.x
+ Davis, April 13, 1987
+ 1. At Frank's suggestion I added a test in STANDARD to make sure that
+ the exposure time is never less than 1 second.
+
+onedspec$t_standard.x
+ Davis, April 10, 1987
+ 1. In order to check for an INDEF valued exposure time STANDARD on VMS/IRAF
+ was testing a boolean compared to a fp 0.0. The test was always coming up
+ true if the exposure keyword was defined; and exposure time was being set
+ to 1. If no exposure keyword was present INDEFI was being used for the
+ exposure time. I changed the test to test for an integer INDEF and
+ every thing seemed ok. Lyra, IRAF and IRAFX were updated.
+
+onedspec$t_standard.x
+ Valdes, April 3, 1987
+ 1. STANDARD was using INDEF if there was no exposure time in the
+ header rather than the intended 1.0 as described in the
+ documentation. It now uses 1 for the exposure time if there
+ is no exposure time in the header.
+
+onedspec$coincor.x
+ Valdes, March 23, 1987
+ 1. In the power correction the value of the output when the input
+ was negative was undefined. Now it is the input value.
+
+onedspec$splot/getimage.x
+onedspec$splot/wrspect.x
+onedspec$splot/deblend.x
+onedspec$splot/eqwidth.x
+onedspec$splot/eqwidthcp.x
+ Valdes, March 13, 1987
+ 1. SPLOT no longer opens the image READ_WRITE. This was unnecessary
+ and would prevent someone from examining data for which they don't
+ have write permission.
+ 2. Modified the deblend and eqivalent width options to deactivate the
+ workstation since they produce multiline output.
+
+onedspec$t_dispcor.x
+onedspec$dispcor.par
+onedspec$doc/dispcor.par
+ Valdes, March 5, 1987
+ 1. It is now a fatal error if the dispersion solution (from IDENTIFY)
+ is nonmonotonic.
+ 2. The starting wavelength and wavelength intervals are now list
+ structured parameters to allow files containing the values to
+ be used. With no file the user is queried and a carriage
+ return or nonnumeric value will use the default value.
+ 3. The way wavelength information is printed out has been improved.
+ 4. A missing carriage return was added to the error message when
+ an image is not found.
+ 5. The order of the parameters, some default values, some of the
+ prompts, and their modes have been changed to be more consistent
+ with other tasks and more easily useable with command line arguments.
+ 6. The help page was modified to reflect these changes.
+
+onedspec$identify/ididentify.x
+onedspec$identify/idreidentify.x
+onedspec$identify/idfitdata.x
+onedspec$identify/idcolon.x
+ Valdes, March 5, 1987
+ 1. IDENTIFY now prints a warning about a nonmonotonic coordinate
+ solution.
+ 2. Changes were made to not print the current feature when error
+ messages are printed thus giving the user a change to read them.
+ 3. When attempting to change images to a nonexistant image
+ the immap was improperly error checked. This could result in
+ fatal errors (particularly on VMS).
+
+onedspec$dispcor.par
+ Valdes, February 27, 1987
+ 1. Prompt was changed from
+ "File containing ..." to "Database containing ..."
+
+onedspec$userstd/nearpt.x
+onedspec$oned.h
+ Valdes, February 25, 1987
+ 1. Changed nearest point algorithm to use NDC coordinates. This required
+ adding the GIO pointer to the arguments.
+ 2. Change all procedures calling near_pt to include GIO pointer
+ argument.
+ 3. Changed maximum distance to 0.05 (NDC)
+
+onedspec$splot/splot.x
+ Valdes, February 25, 1987
+ 1. When exiting from the 'f' function mode in SPLOT the function
+ status line is now erased.
+
+noao$onedspec
+ Valdes, February 19, 1987
+ 1. Made required GIO modifications. The tasks affected are SPLOT,
+ STANDARD, FLATFIT, SENSFUNC, and IDENTIFY. Please report any
+ bugs.
+
+onedspec$coincor.x
+onedspec$t_coincor.x
+onedspec$t_flatdiv.x
+onedspec$t_flatfit.x
+onedspec$doc/coincor.hlp
+ Valdes, February 9, 1987
+ 1. A number of interface errors were fixed.
+ 2. The coincidence correction procedure now takes an input and output
+ array. Previously it modified the given array.
+ 3. The basic IIDS correction is now checked for values which would
+ cause the log function to give an exception or instruction error.
+ 4. The major change in COINCOR is that if the output root image name
+ is null then the operation is done in place. When dealing with
+ ~1000 images this saves on disk space and directory manipulations.
+ 5. The help page for COINCOR was appropriately updated.
+
+onedspec$fortran/polft1.f
+onedspec$getextn.x
+onedspec$t_calibrate.x
+onedspec$t_sensfunc.x
+ Valdes, February 5, 1987
+ 1. The following errors reported by Skip Schaller (Steward Obs, AOS port)
+ were fixed.
+ polft1.f: Minus sign out of place in expression
+ getextn.x: Remove declaration for max(), min(), log10()
+ t_calibrate.x: Remove declaration for min()
+ t_sensfunc.x: Remove declaration for log10()
+
+onedspec$oned.h
+ Valdes, January 30, 1987
+ 1. The maximum number of beams the package can handle has been
+ increased from 50 to 100.
+
+onedspec$t_combine.x
+onedspec$combine.par
+onedspec$doc/combine.hlp
+ Valdes, January 30, 1987
+ 1. An new parameter called "combine" was added which specifies the type
+ of combining (either average or sum). The help documentation was
+ updated.
+
+onedspec$identify/idcolon.x
+onedspec$identify/ididentify.x
+ Valdes, January 16, 1987
+ 1. Colon command dictionary and switch rewritten to use macro definitions.
+ 2. ? help facility rewritten to use system paging facility instead of ad
+ hoc menu facility.
+
+onedspec$gcurval
+ Valdes, January 12, 1987
+ 1. Changed "0 0 0 q" to "0 0 1 q" since this was detected as an error
+ in V2.5. This file is used by BPLOT.
+
+onedspec$batchred.cl
+noao$imred/iids/batchred.cl
+noao$imred/irs/batchred.cl
+ Valdes, December 29, 1986
+ 1. This script creates the user script "process.cl". It was creating
+ it with an out-of-date syntax which no longer worked. Modified
+ BATCHRED to create a valid script.
+
+onedspec$lcalib.par
+ Valdes, December 18, 1986
+ 1. The default for the calibration file in task LCALIB is now that
+ for the task STANDARD.
+
+onedspec$identify/idreidentify.x
+ Valdes, December 3, 1986
+ 1. REIDENTIFY was not correctly tracking when there was no fit.
+ 75: FIT(j) = FIT(i) ==> FIT(j) = fit
+
+onedspec$t_flatfit.x
+onedspec$t_flatdiv.x
+onedspec$flatfit.par
+onedspec$flatdiv.par
+onedspec$doc/flatfit.hlp
+onedspec$doc/flatdiv.hlp
+ Valdes, December 2, 1986
+ 1. The tasks FLATFIT and FLATDIV may optionally apply coincidence
+ corrections. They were not updated to include the IIDS nonlinear
+ correction made earlier. They have now been updated.
+
+onedspec$t_bswitch.x
+onedspec$t_flatfit.x
+onedspec$t_sums.x
+ Valdes, December 1, 1986
+ 1. The tasks BSWITCH, FLATFIT, and SUMS created new images with only
+ the standard ONEDSPEC header information and without any other
+ user parameters. These tasks worked this way because they may
+ sum many spectra for each beam and the connection between the
+ input image header and output image header was not obvious. They
+ have been modified to use the last input image for each beam as
+ the image header template for the output image of that beam.
+ When there is no summing then the output image header will be
+ a copy of the input image header with updated ONEDSPEC parameters.
+
+onedspec$identify/idlinelist.x
+ Valdes, November 25, 1986
+ 1. It used to be that if there were no coordinate list then the
+ default user coordinate was the pixel coordinate. This changed
+ at some point. This has been fixed.
+
+onedspec$identify/identify.x
+ Valdes, November 21, 1986
+ 1. The common variable labels is now initialized every time the
+ task runs.
+
+onedspec$load_hdr.x
+onedspec$splot/splot.x
+onedspec$splot/usercoord.x
+ Valdes, November 17, 1986
+ 1. Since people insist on using W0 and WPC to define the wavelength
+ coordinates and are then confused because CRVAL1 and CDELT1 are
+ used I changed the default precedence. The ONEDSPEC package now
+ looks for W0 and WPC first and then resorts to the FITS coordinate
+ keywords. Also if the coordinate values are less the 0.001
+ it assumes that the units are meters and converts to Angstroms.
+ This arises when a strict interpretation of the FITS coordinates
+ (units of meters) is used for optical spectral data.
+ 2. The key 'p' in SPLOT has been modified to query for the starting
+ and ending wavelength. The default values are those last defined.
+ Thus, this key may be used at any time to set the wavelength scale.
+ To return to wavelength scale after '$' the user simply types
+ carriage return to accept the defaults.
+ 3. The key 'u' in SPLOT has been modified to work in all cases.
+ Previously it only worked if the plot was in pixel coordinates.
+ If run in wavelength coordinates funny results would be obtained.
+ Now the user may mark two points even in wavelength coordinates.
+
+onedspec$coincor.x
+ Valdes, November 13, 1986
+ 1. The power law correction is applied only to positive data.
+ Negative data is not changed.
+
+onedspec$splot/eqwidth.x
+onedspec$splot/deblend.x
+onedspec$splot/eqwidthcp.x
+ Valdes, November 3, 1986
+ 1. Changed print format statements to keep columns from running together
+ for flux calibrated data.
+
+onedspec$splot/*.x
+onedspec$splot/mkpkg
+onedspec$splot/idsmtn.h -
+onedspec$splot/oned.h -
+ Valdes, October 28, 1986
+ 1. Changed include references to point to include files in the main
+ package directory ("idsmtn.h" -> "../idsmtn.h" and
+ "oned.h" -> "../oned.h").
+ 2. Deleted the copies of the include file in this directory.
+
+onedspec$t_coincor.x
+onedspec$coincor.x
+onedspec$coincor.par
+onedspec$doc/coincor.hlp
+onedspec$oned.h
+onedspec$onedspec.par
+onedspec$onedspec.men
+ Valdes, October 21, 1986
+ 1. Modified COINCOR to include a power law correction as part of the
+ IIDS correction.
+ 2. A new paramter was added to COINCOR and ONEDSPEC, called "power",
+ for the IIDS power law correction.
+ 3. The help page for COINCOR was revised.
+
+onedspec$splot/splot.x
+onedspec$splot/getimage.x
+onedspec$splot/wrspect.x
+ Valdes, October 20, 1986
+ 1. Added ability to write modified spectrum to the current image in
+ SPLOT.
+ 2. There were several errors in the code which were fixed. These
+ included modifying an IMIO buffer and extra arguments.
+
+onedspec$splot/splot.x
+onedspec$splot/eqwidth.x
+onedspec$splot/eqwidthcp.x
+onedspec$splot/deblend.x
+onedspec$splot/saveans.x
+onedspec$doc/splot.hlp
+ Valdes, October 15, 1986
+ 1. The routines for the keys 'd', 'e', 'h', 'k', and 'v' now print
+ information in a same format. They all have a header line and
+ a line containing the values. There reason for this is that,
+ with the additional information now included, it requires two
+ lines for "quantity: value" format anyway. They also print the
+ information which is common to all methods in the same order.
+ 2. The deblending routine 'd' now includes the continuum, equivalent
+ width, and sigma of the Gaussian fits. It also plots the continuum
+ slope as is done with the 'e' key.
+ 3. The equivalent width routine 'e' now includes the continuum.
+ 4. The 'h', 'k', and 'v' routines now include flux and FWHM.
+ 5. The 'h', 'k', and 'v' routines now work on emission lines as well
+ as absorption lines.
+ 6. The 'h', 'k', and 'v' routines define the gaussian profile in the
+ same way as the deblend routine; i.e. exp (-0.5 * (dw/sigma)**2)
+ 7. Help revised.
+
+onedspec$splot/splot.x
+onedspec$splot/autoexp.x
+ Valdes, October 14, 1986
+ 1. The SPLOT windowing keys 'a', 'z', ',', and '.' were not compatible
+ with the GTOOLS windowing. AUTOEXP.X was rewritten to use the
+ GTOOLS structure while operating as before.
+
+onedspec$splot/splot.x
+onedspec$splot/eqwidthcp.x
+onedspec$splot/scrhelp.x
+onedspec$splot/stshelp.x
+onedspec$doc/splot.hlp
+ Valdes, October 8, 1986
+ 1. There are two methods of measuring equivalent widths using a simple
+ Gaussian line model. The original method which requires a unit
+ continuum has been restored as the 'k' key. (See the revision
+ of September 18, 1986).
+ 2. The second method recently added which uses the y cursor to mark
+ the continuum and uses the half flux level for determining the
+ line width is available with the last available key; the 'v' key.
+ 3. The 'h' key for one sided measurements still requires a second key
+ but now in addition to defining which side of the line to use
+ it also defines which method to used.
+ 4. The help page has been updated to reflect the changes.
+
+onedspec$doc/rebin.hlp
+ Valdes, October 7, 1986
+ 1. Typo in V2.3 documentation fixed: "set to know" -> "set to no".
+
+onedspec$t_shedit.x +
+onedspec$shedit.par +
+onedspec$shparams.par +
+onedspec$doc/shedit.hlp +
+onedspec$onedspec.cl
+onedspec$onedspec.men
+onedspec$onedspec.hd
+ Valdes, September 29, 1986
+ 1. A onedspec header editor called SHEDIT has been added. It uses
+ EPARAM as the editor.
+ 2. A help page is available.
+
+onedspec$identify/reidentify.x
+ Valdes, September 25, 1986
+ 1. REIDENTIFY was passing a constant 0. to ID_REIDENTIFY which expects
+ a double. Replaced 0. with "double (0.)" as the argument.
+ This caused a failure in the AOS IRAF.
+
+onedspec$splot/eqwidthcp.x
+onedspec$splot/doc/splot.hlp
+ Valdes, September 18, 1986
+ 1. The 'k' key used to determine equivalent widths by fitting a Gaussian
+ profile based only on the depth of the core, the line width at some
+ point, and the continuum had several problems. First, people failed
+ to realize that the continuum had to be 1. Second, the y cursor
+ position was used for measuring the width of the line. Third, if
+ the y cursor position was not within the line then square root and
+ logarithm exceptions occured. These problems have been fixed as
+ follows:
+ 1. The y cursor is now used to mark the continuum. This
+ has been made very clear in the documentation.
+ 2. This allows equivalent widths to be measured for any
+ absorption line even when the continuum is not 1!
+ 3. The level at which the width of the line is measured is
+ now the point half way between the continuum and the minimum
+ point in the line. Previously this point was set by the
+ y cursor position.
+ 4. If the y cursor position is below the line minimum or
+ the left and right edges of the line are not found at the half
+ flux point an informative error is printed and the equivalent
+ width is not evaluated.
+ 5. The search for the left and right edges was previously
+ limited to +- 9 pixels. This limit has been removed. The
+ search now extends to the limits of the spectrum if necessary.
+ 6. The information printed includes the gaussian parameters
+ as well as the equivalent width.
+ 7. The gaussian model is plotted over the spectrum in order
+ to judge the reasonableness of the equivalent width measurement.
+
+onedspec$splot.par
+onedspec$doc/splot.hlp
+ Valdes, September 11, 1986
+ 1. Added ? to boolean prompts. The prompt
+ Fix separation of lines:
+ was confusing a user who tried to give the value of the separation.
+ The new prompt is
+ Fix separation of lines?:
+ 2. This parameter was also not in the documentation!
+
+onedspec$t_dispcor.x
+ Valdes, September 11, 1986
+ 1. DISPCOR requires reference spectra to exist as well as the identify
+ database entry. The error message was misleading. The error message
+ is now more specific.
+
+onedspec$splot/splot.x
+onedspec$splot/anssave.x
+ Valdes, September 8, 1986
+ 1. Modified SPLOT to append to the answer file each time an aswer is
+ written rather than opening the answer file at the beginning and
+ closing it at the end. This eliminates the annoying creation of
+ a file everytime SPLOT is used.
+
+onedspec$t_dispcor.x
+ Valdes, September 8, 1986
+ 1. Procedure dcorrect was defined as a function but used as a subroutine.
+ This was found and corrected during the Alliant port.
+
+onedspec$identify/xtpage.x +
+onedspec$identify/xtmenu.x +
+onedspec$identify/ididentify.x
+ Valdes, September 5, 1986
+ 1. Added paging and menu features to '?' help.
+
+onedspec$bplot.cl
+ Valdes, August 26, 1986
+ 1. The BPLOT script has been rewritten. Rather than calling SPLOT
+ in a loop, once for each image, a cursor command file is created
+ containing cursor commands for all the images and then SPLOT is
+ called with a list of images. This fixes an undiagnosed bug and
+ is more efficient.
+
+onedspec$identify/ididentify.x
+onedspec$identify/iddofit.x
+onedspec$identify/idgdata.x
+onedspec$identify/idfitdata.x
+ Valdes, August 22, 1986
+ 1. ICFIT no longer inherits the window from IDENTIFY. Entering ICFIT
+ will do autoscaling.
+ 2. IDENTIFY now uses the image header coordinate information if there
+ is no database dispersion solution. The parameters used are
+ CRPIX, CRVAL, and CDELT. This allows IDENTIFY to be used with
+ linearized spectra in the ONEDSPEC related packages.
+
+onedspec$identify/identify.com
+onedspec$identify/identify.x
+onedspec$identify/idcenter.x
+onedspec$identify/idcolon.x
+onedspec$identify/idshow.x
+onedspec$identify/reidentify.x
+onedspec$identify/identify.par
+onedspec$identify/reidentify.par
+ Valdes, August 18, 1986
+ 1. IDENTIFY and REIDENTIFY modified to include a detection threshold
+ parameter for feature centering.
+ 2. The help pages were updated.
+
+====================================
+Version 2.3 Release, August 18, 1986
+====================================
+
+onedspec$splot/wrspect.hlp: Valdes, August 14, 1986
+ 1. The test for whether a new image will overwrite an existing image
+ used ACCESS which is for nonimage files only. This caused a problem
+ with recognizing the automatic image extensions. The modification
+ uses IMMAP and IFERR to check if the new image would overwrite an
+ existing image.
+
+onedspec$doc/setdisp.hlp: Valdes, August 8, 1986
+ 1. The wording defining the meaning of "dispaxis" was changed because
+ of user confusion.
+
+onedspec$identify/idmark.x: Valdes, August 8, 1986
+ 1. The optional labels have been adjusted to be half size and
+ to have a path of up. Note that on a vt640 the default text
+ quality uses hardware generation so this change will not be
+ visible unless you reset the text quality to high.
+ 2. The size of the ticks and the gaps have changed slightly.
+
+onedspec$t_dispcor.x: Davis, July 28, 1986
+ 1. DISPCOR was failing with a bus error on class2. It turned out that
+ the get_feature1 routine was trying to get the flex_par parameter out
+ of the image header after the image had been closed. I moved the
+ imunmap call to the end of the routine.
+
+onedspec$t_dispcor.x: Valdes, July 7, 1986
+ 1. DISPCOR was opening comparison images when collecting dispersion
+ solutions from the database and failing to close them. In one
+ particular large usage 509 images were opened before
+ an out of memory failure!
+
+onedspec$splot: Valdes, July 7, 1986
+ 1. In SPLOT the 'w' key has been redefined to 'i' (create a new image).
+ Key 'w' now windows the graph.
+ 2. The help page and menus updated.
+
+onedspec$identify/: Valdes, July 7, 1986
+ 1. Redefined the 'r' key to be 't' so that 'r' can be the standard
+ redraw key.
+ 2. Help page and '?' menu updated.
+
+onedspec$doc/standard.hlp, lcalib.hlp, sinterp.hlp: Valdes, July 7, 1986
+ 1. Help pages updated to reflect name changes in the standard
+ calibration files.
+
+onedspec$identify/: Valdes, July 3, 1986
+ 1. Modified package to use new ICFIT package.
+ 2. Changed coordinate list parameter to onedstds$henear.dat.
+ 3. Updated help page for IDENTIFY to refect new default coordlist.
+
+onedspec$identify/identify.x,reidentify.x,idgetim.x: Valdes, July 1, 1986
+ 1. Replaced calls to imtgetim with idgetim. Idgetim calls
+ imtgetim to get next image name but it then removes any
+ image extension. This is necessary to prevent having two
+ different names by which an image may be identified in the
+ database.
+
+=====================================
+STScI Pre-release and SUN 2.3 Release
+=====================================
+
+ondespec$getnimage.x: Valdes, June 19, 1986
+ 1. Changed BOOLS in common to INTS for safety's sake.
+
+onedspec$(t_sensfunc.x,fudge.x,userstd.x): Valdes, June 19, 1986
+ 1. SENSFUNC was not correctly accumulating grey constant corrections
+ between different apertures. This was fixed by rewriting the
+ RLSQ procedures (moved into a file of their own, rlsq.x) and
+ making appropriate changes in the rest of the code.
+ 2. The grey constant was being computed incorrectly.
+
+onedspec$t_flatfit.x: Valdes, June 18, 1986
+ 1. FLATFIT aborted when an error is made specifying a nonexistant
+ image. It now prints an error message and goes on to the
+ next spectrum.
+
+onedspec$t_coefs.x: Valdes, June 16, 1986
+ 1. Task was calling the wrong database package. This produced
+ totally wrong code since one package returns a structure
+ pointer and the other returns FIO channel number.
+ This error was probably introduced in May.
+
+onedspec$t_standard.x: Valdes, June 12, 1986
+ 1. Minor bug in STANDARD introduced when fixing problem with
+ wavelengths (May 19). Title was no longer being written to
+ the STD file.
+
+onedspec$t_dispcor.x: Valdes, June 12, 1986
+ 1. DISPCOR had a fixed limit of 100 comparison spectra for all
+ apertures in the database. If this limit was exceeded memory
+ would be corrupted (i.e. no check for exceeding the end of the
+ array). This has been changed to use dynamic memory allocation
+ so that there is no limit on the number of comparison spectra.
+
+onedspec$identify/ididentify.x: Valdes, June 11, 1986
+ 1. Windowing key 'w' added.
+ 2. Help page updated to reflect the 'w' and 'y' keys.
+
+onedspec$splot.x: Valdes, June 10, 1986
+ 1. Now sets dispersion correction flag when the user defines
+ a wavelength scale and writes a new image.
+
+onedspec$identify/splot.x: Valdes, June 9, 1986
+ 1. Added check on the validity of the imio pointer when attempting
+ to unmap the image. This occured with a next image failed to
+ access the specified image.
+
+onedspec$identify/ididentify.x: Valdes, June 9, 1986
+ 1. Changed Memr to Memd in 'y' option.
+
+onedspec$identify/reidentify.x: Valdes, June 2, 1986
+ 1. Changed from file template to image template.
+
+onedspec$t_sensfunc.x: Valdes, June 2, 1986
+ 1. Added check for square root of zero which is a fatal error on VMS.
+
+onedspec$t_standard.x,t_sensfunc.x: Valdes, May 19, 1986
+ 1. The output of STANDARD gave the wavelengths of the left edge of the
+ first pixel and the right edge of the last pixel instead of the
+ centers. This causes slight errors downstream in SENSFUNC.
+ This has been changed to give the actual W0 and WEND.
+ I tried to check that all wavelengths were being calculated and
+ used correctly.
+ 2. SENSFUNC was not correctly using the output of STANDARD. In some
+ cases it assumed the starting and ending wavelengths were at
+ the edges of the pixel and in other cases it assumed they were
+ at the centers of the pixels. The errors largely canceled out
+ except that the W0 in the header for the SENSITIVITY image was
+ wrong but WPC and the number of points was correct. Again, I tried
+ to check that everything is now consistent.
+ 3. SENSFUNC was extrapolating observations when forming the composite
+ sensitivity curve. This leads to significant errors when some
+ observations do not extend as far as others in wavelength. This
+ was noticed as a large increase in the RMS relative to the original
+ RMS based only on the observations. Now extrapolations are not
+ allowed and only observations covering a given range of wavelengths
+ are used in forming the composite curve. Note that interpolations
+ are still used if an observation does not contain a point at a
+ particular wavelength.
+ 4. The help page for SENSFUNC was modified to explain the difference
+ between the RMS of the input points and the RMS of the composite
+ points.
+ 5. INTRP.F had to be modified because it considered a wavelength
+ equal to the first wavelength in the table as an extrapolation.
+
+onedspec$bswitch.par: Valdes, May 19, 1986
+ 1. The BSWITCH parameter "add_const" has been changed to use the value
+ from SENSFUNC of the same name. The help page was also modified
+
+onedspec$t_sensfunc.x: Valdes, May 16, 1986
+ 1. SENSFUNC was not writing a complete header needed by LONGSLIT.
+ Now it goes through the standard ONEDSPEC header package to create
+ the senstivity images.
+
+onedspec$t_bswitch.x: Valdes, May 14, 1986
+ 1. BSWITCH was not reinitializing properly when not using IDSMODE.
+ The effect was to give extraneous output.
+ 2. All occurances of "== INDEFI" where changed to use the "IS_INDEF"
+ macro.
+
+onedspec$t_rebin.x: Valdes, May 14, 1986
+ 1. If the image has not been dispersion corrected then an error is
+ printed and the next image is processed.
+
+onedspec$bplot.cl: Valdes, May 13, 1986
+ 1. BPLOT has been modified to call SPLOT separately for each input
+ image. This has the effect of repeating the cursor file for each
+ image.
+
+onedspec$t_coefs.x: Valdes, May 12, 1986
+ 1. COEFS was not writing a correct IDENTIFY database entry.
+
+onedspec$t_rebin.x: Valdes, May 10, 1986
+ 1. Rebinning into logarithmic intervals was not working. This has
+ been fixed. A number of logical changes were required.
+ 2. Rather than use an interative method for determining the coordinate
+ transformation the transformation can be determined explicitly since
+ both the input and output coordinates are linear.
+ 3. The logarithm flag was previously ignored if a primary spectrum was
+ used. This prevented making the input and primary spectrum
+ the same and then specifying either log or linear output. This
+ is a common way to use this task for converting to log intervals.
+ 4. The primary spectrum was not being unmapped.
+
+onedutil$bplot.cl: Valdes, May 9, 1986
+ 1. BPLOT has been modified to use the new SPLOT. The script is now
+ a simple one line call to splot.
+ 2. The input is now a image list instead of a file containing
+ image names. Note that to use a file containing image names
+ the syntax is now "@file".
+ 3. The cursor input file is now a parameter of the task allowing
+ users to define their own set of commands.
+ 4. The graphics device parameter is now standardized with other
+ graphics tasks.
+ 5. A modified help page is available.
+
+onedspec$splot.x: Valdes, May 9, 1986
+ 1. SPLOT now accepts a list of input spectra and processes them
+ sequentially. The parameter name has been changed from "image"
+ to "images".
+ 2. New SPLOT parameters XMIN, XMAX, YMIN, YMAX allow the user to
+ set the limits of the initial plot. These values may be modified
+ interactively with :/xwindow and :/ywindow.
+ 3. A modified help page is available.
+
+onedspec$identify/reidentify.x: Valdes, May 8, 1986
+ 1. Set log output to be flushed with every line written instead of
+ being buffered.
+
+onedspec$sflip.x: Valdes, May 8, 1986
+ 1. A new task has been added to the ONEDUTIL package call SFLIP.
+ It flips the dispersion direction of spectra while maintaining
+ the proper dispersion image header parameters.
+ 2. A help page has been added for the task SFLIP.
+
+onedspec$splot: Valdes, May 7, 1986
+ 1. Changed interpretation of W0 in logarithmic binning to be the
+ logarithm of the wavelength of the first pixel.
+
+onedspec$t_dispcor.x, t_rebin.x, t_combine.x: Valdes, May 7, 1986
+ 1. Changed meaning of w0 in logarithmic coordinates to be consistent
+ with usual linear formula. That is with a logarithmic wavelength
+ interval the zero point is the logarithm of the starting wavelength.
+ 2. Assumed increasing wavelengths in both the output spectra
+ and the input spectra. This restriction has been lifted.
+ 3. Default output bins are in increasing wavelength with increasing pixel
+ coordinate even when the input dispersion relation has the opposite
+ sense.
+ 4. The logic in REBIN for col_out = 0 was modified appropriately.
+ 5. The help page for DISPCOR has been modified to indicate the new
+ ability to have arbitrary input and output dispersion directions.
+
+onedspec$userstd: Valdes, May 6, 1986
+ 1. Previously no graph of the errors would be made if the residuals
+ were all the same.
+ 2. Warning message was removed.
+ 3. Boxes now drawn in NDC with standard size and do not depend on the
+ range of the data or the size of the graph.
+
+onedspec$userstdc.x: Valdes, May 6, 1986
+ 1. Code incorrectly limited highest order for fit to one less than the
+ number of points. The order is now limited to the number of points.
+ 2. Previously no graph of the errors would be made if the residuals
+ were all the same.
+ 3. Warning messages were removed.
+ 4. Boxes now drawn in NDC with standard size and do not depend on the
+ range of the data or the size of the graph.
+
+onedspec$identify/idlog.x: Valdes, May 1, 1986
+ 1. Column headings were adjusted.
+
+onedspec$onedspec.cl: Valdes, May 1, 1986
+ 1. Removed loading of list and plot packages in ONEDSPEC package script.
+ These packages are loaded with the NOAO package.
+
+onedspec: Valdes, April 27, 1986
+ 1. Package pathname "noao.onedspec.onedutil" added to help pages for
+ ONEDUTIL package tasks.
+
+onedspec: Valdes, April 7, 1986
+ 1. OBSERVATORY task from IMRED package loaded with ONEDSPEC.
+ 2. Latitude parameter removed from the ONEDSPEC package parameters.
+ 3. DISPCOR, STANDARD, and BSWITCH latitude parameters changed to
+ reference OBSERVATORY parameters.
+ 4. The help pages for these tasks were revised.
+
+onedspec$t_flatfit.x: Valdes, April 7, 1986
+ 1. Fixed minor bug.
+
+onedspec$t_sinterp.x: Valdes, April 6, 1986
+ 1. Fixed bug in SINTERP. It was using CURFIT with a pointer argument
+ for the weights instead of a real array. CURFIT is used only if
+ the interpolation mode is one of the CURFIT types. Obviously
+ this option was never tested.
+ 2. Entry points removed for portability.
+ 3. The interpolation wavelengths when generating a curve were strongly
+ subject to accumulated roundoff error; x = x + dx. This was modified
+ to use the construct, x = x1 + (i - 1) * dx, which may still have
+ a precision limitation but not an accumulated roundoff error.
+
+onedspec: Valdes, April 5, 1986
+ 1. Found very bad error in numerous places. The arguments to CLGCUR
+ were too few and of those that were there one was of the wrong
+ datatype!!! This was not a problem on the VAXes but very
+ bad and hard to find on the SUN.
+ 2. Fixed SUN bugs in SENSFUNC due to the statement:
+ call amovks (1, Mems[flags], npts)
+ Apparently numeric constants are integer sized which causes problems
+ on the SUN which has high order bytes first. Watch out for this
+ construct!
+
+onedspec$getnimage: Valdes, April 4, 1986
+ 1. The entry points in this procedure caused tasks to fail on the
+ SUN. USE OF ENTRY POINTS IS HAZARDOUS TO THE HEALTH OF PORTABLE
+ PROGRAMS. I shall have to see if there are any more entry points
+ in ONEDSPEC.
+
+onedspec$getairm: Valdes, April 4, 1986
+ 1. Fixed minor bug in determining HA from ST and RA. ST was still
+ assumed to be in seconds which is not the case any more.
+
+onedspec: Valdes, March 28, 1986
+ 1. ADDSETS would fail if an image was missing. I modified it
+ to detect missing files and continue on.
+
+onedspec: Valdes, March 27, 1986
+ 1. The header parameters CRPIXn, CRVALn, CDELTn have been added to
+ the image headers. They replace W0 and WPC though W0 and WPC
+ are still recorded in the header (for now).
+ 2. A new task, SETDISP, has been added to set the dispersion axis
+ (must be 1 for ONEDSPEC), the dispersion type, and the dispersion
+ unit. These are currently only used for labeling in IDENTIFY
+ and thus the task is optional for the moment.
+ 3. SPLOT modified to label the wavelength axis using CTYPE1 and CUNIT1.
+
+onedspec$splot/deblend.x: Valdes, March 27, 1986
+ 1. Moved deblend.x and eqwidthcp.x to splot directory.
+ 2. There was a typo(?) in deblend.x of SPLOT which converted
+ sigma to FWHM as FWHM = 2.345 * sigma. This has been corrected
+ to FWHM = 2.355 * sigma.
+ 3. The help page for SPLOT was updated.
+
+onedspec$identify: Valdes, March 26, 1986
+ 1. Fixed bug in IDENTIFY which failed to add new lines with the 'l'
+ command when the initial wavelength axis was pixels.
+
+onedspec$identify: Valdes, March 24, 1986
+ 1. Fixed minor bug in REIDENTIFY. It was calling IC_FREE instead
+ of IC_FREED (the new double precision version) while the rest
+ of the package was in double precision.
+
+onedspec: Valdes, March 21-22, 1986
+ 1. Continued changes in the ONEDSPEC header parameters. All the
+ internal ONEDSPEC header parameters are initialized. Those not
+ in the image header are initialized to INDEF if no other default
+ makes sense. Then when a new image is created only the parameters
+ which are not INDEF are written to the new image header. Hopefully
+ there isn't a obscure use in the package that assumes the default
+ value of a parameter is zero (this was the previous default default).
+ A bug of this sort occurred in SPLOT which assumed that W0 and WPC
+ are zero if the image has not been dispersion corrected. This was
+ changed.
+ 2. SLIST now prints INDEF for the parameters which are indefinite.
+ 3. UT and ST are now stored internally as real values like all the other
+ time and angle parameters. Previously the were stored as integer
+ seconds.
+ 4. UT, ST, RA, DEC, and HA are written to new images as sexigesimal
+ strings instead of real values. This is contrary to the FITS standard
+ but this is the way its been done previously.
+ 5. Comments for parameters which are updated by ONEDSPEC are deleted
+ when a new image is created. This is because the database interface
+ does not allow comments and when entering a new value the comment
+ could be partially overwritten resulting in a nonsensical FITS cards.
+ Parameters which ONEDSPEC does not use are not touched.
+
+onedspec$fortran/intrp.f: Valdes, March 20, 1986
+ 1. Converted entry points into separate procedures. Entry points,
+ while legal FORTRAN, tend to cause problems except in the very
+ best compilers. The change was sparked by the failure of the
+ SUN optimizer. It is not 100% certain that this caused the
+ failure but it works now.
+
+onedspec: Valdes, March 19, 1986
+ 1. All double precision variables have been change to single
+ precision. The double precision is an anachronism. There were
+ numerous type mismatches with calling procedures using double
+ precision and the called procedure expecting single precision.
+ These problems were only found recently on the SUN workstation
+ which has a reversed order to the bytes. On the VAX this error
+ is not caught.
+ 2. The header parameters are accessed through the image database
+ interface rather than directly. This cleans things up alot and
+ will make the transition to a real database easier.
+ It does, however, mean that comments and sexigesimal notation are
+ no longer used.
+ 3. Most tasks creating an output image now make a copy of the relevant
+ input image header. This allows header parameters which are not
+ recognized by ONEDSPEC to be propagated to the new images.
+
+onedspec$t_combine.x: Valdes, March 19, 1986
+ 1. Rebinning did not work after fix to DISPCOR (Feb 14) because macro codes
+ were wrong.
+
+onedspec$t_rebin.x: Valdes, March 19, 1986
+ 1. Did not work after fix to DISPCOR (Feb 14) because macro codes
+ were wrong.
+
+onedspec$identify: Valdes, March 14, 1986
+ 1. Modified IDENTIFY to store the line list internally instead of
+ scanning the line list file every time.
+
+onedspec: Valdes, March 14, 1986
+ 1. Fixed a bug in LOAD_HDR.X which caused a roundoff error in the UT
+ and ST values. This was a problem when creating a new image since
+ it inherited slightly different values than the original image.
+ 2. A double precision airmass variable was being passed to GET_AIRM which
+ expected a single precisions variable. This bug became apparent
+ on the SUN workstation. Modified GET_AIRM to expect a double
+ precision airmass variable.
+
+onedspec: Valdes, March 13, 1986
+ 1. Modified IDENTIFY and REIDENTIFY to be double precision. It uses
+ the double precision ICFIT and CURFIT procedures.
+ 2. The help pages for IDENTIFY and REIDENTIFY were updated for the
+ changes since Release 2.2
+ 3. Fixed bug in SLIST which printed W0, WPC, and AIRMASS incorrectly
+ on the SUN workstation. Pargr was used instead of pargd. Also fixed
+ possible problem with assigning INDEFR to a double variable.
+
+onedspec: Valdes, March 11, 1986
+ 1. SENSFUNC was not putting the dispersion correction flag, DC-FLAG,
+ in the header for the sensitivity image. This causes LONGSLIT.FLUXCAL
+ to fail. This has been fixed.
+
+onedspec: Valdes, March 6, 1986
+ 1. Added parameter to SPLOT to allow selection of the graphics output
+ device.
+ 2. Help page for SPLOT modified.
+ 3. New parameter file for SPLOT. Also installed in IMRED packages.
+
+onedspec: Valdes, Feb 27, 1986
+ 1. IDENTIFY and REIDENTIFY have been modified to do shifts in user
+ coordinates instead of pixel coordinates. This applies to the 's'
+ and 'x' keys in IDENTIFY and to REIDENTIFY. The shift specified in
+ REIDENTIFY is now in user coordinates. Unless otherwise specified
+ the shifts printed by these tasks are in user coordinates instead
+ of pixels.
+ 2. A new key has been added to IDENTIFY. The key 'r' resets the
+ current feature to the position of the cursor. This replaces the
+ need to mark the new position and then delete the old position.
+ 3. The output of 's' and 'x' in IDENTIFY is slightly different.
+===========
+Release 2.2
+===========
+From Valdes Feb 28, 1986:
+
+1. Fixed bug in FLATDIV which printed the image title as garbage. Also
+the output record number is increment for each input spectrum regardless
+of whether the input spectrum is found, has already been flatted, or
+is flattened.
+------
+From Valdes Feb 24, 1986:
+
+1. Removed junk file identify/isdir.x.
+------
+From Valdes Feb 14, 1986:
+
+1. t_sensfunc.x, userstd.x, and fudge.x have been modified to allow
+the grey scale correction to be determined interactively even when
+points are deleted.
+
+2. Fixed bug in DISPCOR to allow interpolation between solutions. This
+did not work before.
+------
+From Valdes Feb 10, 1986:
+
+1. FLATDIV has been modified to do in-place flattening when the input
+and output spectra are the same.
+------
+From Valdes Jan 24, 1986:
+
+1. In IDENTIFY the 'l' always does a fit first before identifying
+additional lines.
+------
+From Valdes Jan 21, 1986:
+
+1. HELP pages updated.
+
+2. The log information written by REIDENTIFY has been made more compact
+and a option to futher reduce this log information "verbose" has been added.
+------
+From Valdes Jan 17, 1986:
+
+1. Bugs fixed affecting SPLOT and DISPCOR.
+------
+From Valdes Jan 6, 1986:
+
+1. Problem with cursor key 'o' in SENSFUNC fixed.
+
+2. The 's' shift option in IDENTIFY has been modified. It now prints
+the initial shift, the mean pixel shift, and the mean fractional shift
+in user units. This can be conveniently used for determining velocity
+shifts from a standard.
+------
+From Valdes Jan 2, 1986:
+
+1. If the HA field was missing from a field it was being initialized to
+0. which is a valid HA value. This has been changed to initialize to -100.
+This value will force recomputation of the HA when determining the air mass.
+
+2. A bug in computing the air mass when the HA is not defined was found
+and fixed.
+------
+From Valdes Dec 30, 1985:
+
+1. A bug in DISPCOR when using a reference image and the directory
+structured database has been fixed.
+------
+From Valdes Dec 9, 1985:
+
+1. NORMCONTINUUM has been renamed to CONTINUUM and modified to have the
+output type as a hidden parameter.
+
+2. The standard line lists have been put in the directory stdlines$.
+------
+From Valdes Nov 26, 1985:
+
+1. SPLOT modified to use gtools graphics options. These options are
+accessed with :/ commands; i.e. ":/xwindow x1 x2" sets the x display
+window.
+
+2. SPLOT parameter "auto" replaced by parameter "options" which allows
+several plotting options to be given. The options are given as a list of
+possibly abbreviated strings. The two options currently defined are
+"auto" and "zero". Auto is the same as before; it replots the graph
+after any command that changes the graph. Zero makes the initial
+default for the graph have zero as the minimum Y.
+------
+From Valdes Nov 15, 1985:
+
+1. Modified IDENTIFY, REIDENTIFY, and DISPCOR to use directory type database
+structure. Instead of a single massive database textfile separate
+database text files are created for each image in the database directory.
+------
+From Valdes Oct 28, 1985:
+
+1. Increased the efficiency of widstape from 7 seconds per spectrum to
+about 2 seconds per spectrum by using low level formating.
+------
+From Valdes Oct 23, 1985:
+
+1. Bug fix to allow zero entries in the calibration files.
+------
+From Valdes Oct 9, 1985:
+
+1. Cursor parameter added to the tasks flatfit, splot, and standard.
+
+2. Defined widstape from ONEDSPEC package in the DATAIO package. The
+source and executable, however, still reside in ONEDSPEC (x_onedutil.e).
+Widstape and widsout should be combined and the source put in DATAIO
+at some point.
+------
+From Valdes Oct 7, 1985:
+
+1. Parameter indirections removed.
+
+2. Tasks IRS and IIDS moved to the IMRED package. ONEDSPEC need not
+be loaded directly. The usually method should be to load IMRED and then
+the appropriate instrument package.
+------
+From Valdes Oct 4, 1985:
+
+1. Add script task normcontinuum to fit the continuum of spectra and
+output a continuum normalized spectrum. This script is based on
+images.fit1d.
+------
+From Valdes October 1, 1985:
+
+1. The source code for identify and reidentify has been moved from the
+longslit package to the onedspec package since these tasks are essentially
+one dimensional.
+
+------
+From Valdes August 19, 1985:
+
+1. Makelib file created to maintain archive for the onedspec package.
+The archive is libods.a. Makefile modified to use the library.
+This removes all the .o files making directory easier to list.
+
+2. An attempt to write to an existing image in splot requires the
+user to confirm. Overwriting an existing image now maintains the pixel
+files correctly.
+
+3. New script task revisions pages the package revision file.
+.endhelp
diff --git a/noao/onedspec/aidpars.par b/noao/onedspec/aidpars.par
new file mode 100644
index 00000000..005414c0
--- /dev/null
+++ b/noao/onedspec/aidpars.par
@@ -0,0 +1,25 @@
+# Parameters for autoidentify task.
+
+reflist,s,h,"",,,Reference coordinate list
+refspec,s,h,"",,,Reference spectrum
+#crval,s,h,"INDEF",,,Coordinate reference value
+#cdelt,s,h,"INDEF",,,Coordinate interval per pixel
+crpix,s,h,"INDEF",,,Coordinate reference pixel
+crquad,s,h,"INDEF",,,Quadratic pixel distortion at reference pixel
+cddir,s,h,"sign","unknown|sign|increasing|decreasing",,Dispersion direction
+crsearch,s,h,"INDEF",,,Coordinate value search radius
+cdsearch,s,h,"INDEF",,,Coordinate interval search radius
+ntarget,i,h,100,,,Number of target features
+#nreference,i,h,40,,,Number of reference features
+npattern,i,h,5,3,10,Number of lines in patterns
+nneighbors,i,h,10,2,,Number of nearest neighbors in patterns
+nbins,i,h,6,1,,Maximum number of search bins
+ndmax,i,h,500,1,,Maximum number of dispersions to evaluate
+aidord,i,h,3,2,,Dispersion fitting order
+maxnl,r,h,0.02,0.,,Maximum non-linearity
+nfound,i,h,6,3,,Minimum number of lines in final solution
+sigma,r,h,.05,0.,,Sigma of line centering (pixels)
+minratio,r,h,0.1,0., 1.,Minimum spacing ratio to use
+rms,r,h,0.1,0.,,RMS goal (fwidths)
+fmatch,r,h,0.2,0.,1.,Matching goal (fraction unmatched)
+debug,s,h,"",,,Print debugging information
diff --git a/noao/onedspec/autoidentify.par b/noao/onedspec/autoidentify.par
new file mode 100644
index 00000000..4d6c4c27
--- /dev/null
+++ b/noao/onedspec/autoidentify.par
@@ -0,0 +1,38 @@
+# Parameters for AUTOIDENTIFY.
+
+images,s,a,,,,"Images containing features to be identified"
+crval,s,a,,,,"Approximate coordinate (at reference pixel)"
+cdelt,s,a,,,,"Approximate dispersion"
+coordlist,f,h,,,,"Coordinate list"
+units,s,h,"",,,Coordinate units
+interactive,s,h,"yes","no|yes|NO|YES",,"Examine identifications interactively?"
+aidpars,pset,h,,,,"Automatic identification algorithm parameters
+"
+section,s,h,"middle line",,,"Section to apply to two dimensional images"
+nsum,s,h,"1",,,"Number of lines/columns/bands to sum in 2D/3D images
+"
+ftype,s,h,"emission","emission|absorption",,Feature type
+fwidth,r,h,4.,,,Feature width in pixels
+cradius,r,h,5.,,,Centering radius in pixels
+threshold,r,h,0.,0.,,Feature threshold for centering
+minsep,r,h,2.,0.,,"Minimum pixel separation"
+match,r,h,-3.,,,"Coordinate list matching limit
+"
+function,s,h,"spline3","legendre|chebyshev|spline1|spline3",,"Coordinate function"
+order,i,h,1,1,,"Order of coordinate function"
+sample,s,h,"*",,,"Coordinate sample regions"
+niterate,i,h,10,0,,"Rejection iterations"
+low_reject,r,h,2.,0.,,"Lower rejection sigma"
+high_reject,r,h,2.,0.,,"Upper rejection sigma"
+grow,r,h,0.,0.,,"Rejection growing radius
+"
+dbwrite,s,h,"yes","no|yes|NO|YES",,"Write results to database?"
+overwrite,b,h,"yes",,,"Overwrite existing database entries?"
+database,f,h,database,,,"Database in which to record feature data"
+verbose,b,h,yes,,,"Verbose output?"
+logfile,s,h,"logfile",,,"List of log files"
+plotfile,s,h,"",,,"Plot file for residuals"
+graphics,s,h,"stdgraph",,,"Graphics output device"
+cursor,*gcur,h,"",,,"Graphics cursor input
+"
+query,s,q,,,," "
diff --git a/noao/onedspec/bplot.cl b/noao/onedspec/bplot.cl
new file mode 100644
index 00000000..146fa2f5
--- /dev/null
+++ b/noao/onedspec/bplot.cl
@@ -0,0 +1,54 @@
+# BPLOT -- Batch plotting of spectra with SPLOT
+
+procedure bplot (images)
+
+string images {prompt="List of images to plot"}
+string apertures = "" {prompt="List of apertures to plot"}
+int band = 1 {prompt="Band to plot"}
+string graphics = "stdgraph" {prompt="Graphics output device"}
+string cursor = "onedspec$gcurval.dat" {prompt="Cursor file(s)\n\nSPLOT query parameters to fix"}
+
+string next_image = "" {prompt="Next image to plot"}
+string new_image = "" {prompt="Image to create"}
+bool overwrite = yes {prompt="Overwrite image?"}
+string spec2 = "" {prompt="Spectrum"}
+real constant = 0. {prompt="Constant to be applied"}
+real wavelength = 0. {prompt="Dispersion coordinate"}
+file linelist = "" {prompt="File"}
+real wstart = 0. {prompt="Starting wavelength"}
+real wend = 0. {prompt="Ending wavelength"}
+real dw = 0. {prompt="Wavelength per pixel"}
+int boxsize = 2 {prompt="Smoothing box size\n"}
+
+struct *ilist, *clist
+
+begin
+ int line, ap
+ file ifile, cfile, cur, image
+
+ ifile = mktemp ("bplot")
+ cfile = mktemp ("bplot")
+
+ slist (images, apertures=apertures, long_header=no, > ifile)
+ files (cursor, > cfile)
+ cur = ""
+
+ ilist = ifile; clist = cfile
+ while (fscan (ilist, image, line, ap) != EOF) {
+ if (nscan() < 3)
+ next
+ if ((cursor != "") && (fscan (clist, cur) == EOF)) {
+ clist = cfile
+ line = fscan (clist, cur)
+ }
+ splot (image, line=ap, band=band, graphics=graphics, cursor=cur,
+ next_image=next_image, new_image=new_image,
+ overwrite=overwrite, spec2=spec2, constant=constant,
+ wavelength=wavelength, linelist=linelist, wstart=wstart,
+ wend=wend, dw=dw, boxsize=boxsize)
+ }
+ clist = ""; ilist = ""
+
+ delete (ifile, verify=no)
+ delete (cfile, verify=no)
+end
diff --git a/noao/onedspec/calibrate.par b/noao/onedspec/calibrate.par
new file mode 100644
index 00000000..5f805c46
--- /dev/null
+++ b/noao/onedspec/calibrate.par
@@ -0,0 +1,13 @@
+# CALIBRATE parameter file
+
+input,s,a,,,,Input spectra to calibrate
+output,s,a,,,,Output calibrated spectra
+extinct,b,h,yes,,,Apply extinction correction?
+flux,b,h,yes,,,Apply flux calibration?
+extinction,s,h,,,,Extinction file
+observatory,s,h,)_.observatory,,,Observatory of observation
+ignoreaps,b,h,no,,,Ignore aperture numbers in flux calibration?
+sensitivity,s,h,"sens",,,Image root name for sensitivity spectra
+fnu,b,h,no,,,Create spectra having units of FNU?
+airmass,r,q,,1.,,Airmass
+exptime,r,q,,,,Exposure time (seconds)
diff --git a/noao/onedspec/continuum.par b/noao/onedspec/continuum.par
new file mode 100644
index 00000000..6a43d804
--- /dev/null
+++ b/noao/onedspec/continuum.par
@@ -0,0 +1,25 @@
+input,s,a,,,,Input images
+output,s,a,,,,Output images
+lines,s,h,"*",,,Image lines to be fit
+bands,s,h,"1",,,Image bands to be fit
+type,s,h,"ratio","data|fit|difference|ratio",,Type of output
+replace,b,h,no,,,Replace rejected points by fit?
+wavescale,b,h,yes,,,Scale the X axis with wavelength?
+logscale,b,h,no,,,Take the log (base 10) of both axes?
+override,b,h,no,,,Override previously fit lines?
+listonly,b,h,no,,,List fit but don't modify any images?
+logfiles,s,h,"logfile",,,List of log files
+interactive,b,h,yes,,,Set fitting parameters interactively?
+sample,s,h,"*",,,Sample points to use in fit
+naverage,i,h,1,,,Number of points in sample averaging
+function,s,h,"spline3","spline3|legendre|chebyshev|spline1",,Fitting function
+order,i,h,1,1,,Order of fitting function
+low_reject,r,h,2.,0.,,Low rejection in sigma of fit
+high_reject,r,h,0.,0.,,High rejection in sigma of fit
+niterate,i,h,10,0,,Number of rejection iterations
+grow,r,h,1.,0.,,Rejection growing radius
+markrej,b,h,yes,,,Mark rejected points?
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Graphics cursor input
+ask,s,q,"yes","yes|no|skip|YES|NO|SKIP",," "
+mode,s,h,"ql"
diff --git a/noao/onedspec/deredden.par b/noao/onedspec/deredden.par
new file mode 100644
index 00000000..f787033a
--- /dev/null
+++ b/noao/onedspec/deredden.par
@@ -0,0 +1,10 @@
+# DEREDDEN parameter file
+
+input,s,a,,,,Input spectra to correct
+output,s,a,,,,Output corrected spectra
+value,r,a,,,,Extinction parameter value
+R,r,h,3.1,,,A(V)/E(B-V)
+type,s,h,"E(B-V)","A(V)|E(B-V)|c",,Type of extinction parameter
+apertures,s,h,"",,,Apertures to correct
+override,b,h,no,,,Override previous correction?
+uncorrect,b,h,yes,,,Uncorrect previous correction?
diff --git a/noao/onedspec/dispcor.par b/noao/onedspec/dispcor.par
new file mode 100644
index 00000000..0fd17027
--- /dev/null
+++ b/noao/onedspec/dispcor.par
@@ -0,0 +1,19 @@
+input,s,a,,,,List of input spectra
+output,s,a,,,,List of output spectra
+linearize,b,h,yes,,,Linearize (interpolate) spectra?
+database,s,h,"database",,,Dispersion solution database
+table,s,h,"",,,Wavelength table for apertures
+w1,r,h,INDEF,,,Starting wavelength
+w2,r,h,INDEF,,,Ending wavelength
+dw,r,h,INDEF,,,Wavelength interval per pixel
+nw,i,h,INDEF,,,Number of output pixels
+log,b,h,no,,,Logarithmic wavelength scale?
+flux,b,h,yes,,,Conserve total flux?
+blank,r,h,0.,,,Output value of points not in input
+samedisp,b,h,no,,,Same dispersion in all apertures?
+global,b,h,no,,,Apply global defaults?
+ignoreaps,b,h,no,,,Ignore apertures?
+confirm,b,h,no,,,Confirm dispersion coordinates?
+listonly,b,h,no,,,List the dispersion coordinates only?
+verbose,b,h,yes,,,Print linear dispersion assignments?
+logfile,s,h,"",,,Log file
diff --git a/noao/onedspec/dispcor/dcio.x b/noao/onedspec/dispcor/dcio.x
new file mode 100644
index 00000000..b700da6a
--- /dev/null
+++ b/noao/onedspec/dispcor/dcio.x
@@ -0,0 +1,1155 @@
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <pkg/dttext.h>
+include <smw.h>
+include <units.h>
+include "dispcor.h"
+
+# Symbol table structure for the dispersion solutions.
+define LEN_DC 11 # Length of dispersion solution struct.
+define DC_FORMAT Memi[$1] # Type of dispersion
+define DC_PAPS Memi[$1+1] # Pointer to aperture numbers
+define DC_PAPCEN Memi[$1+2] # Pointer to aperture centers
+define DC_PUN Memi[$1+3] # Pointer to units
+define DC_PSHIFT Memi[$1+4] # Pointer to shifts
+define DC_PCOEFF Memi[$1+5] # Pointer to coefficients
+define DC_NAPS Memi[$1+6] # Number of apertures
+define DC_OFFSET Memi[$1+7] # Aperture to order offset
+define DC_SLOPE Memi[$1+8] # Aperture to order slope
+define DC_COEFFS Memi[$1+9] # Dispersion coefficients
+define DC_SHIFT Memr[P2R($1+10)]# Dispersion function shift
+
+
+# DC_OPEN -- Initialize the dispersion data structures
+# DC_CLOSE -- Close the dispersion data structures
+# DC_GMS -- Get a multispec spectrum
+# DC_GMSDB -- Get a multispec dispersion database entry
+# DC_REFSHFT -- Get a reference shift
+# DC_GEC -- Get an echelle spectrum
+# DC_GECDB -- Get an echelle dispersion database entry
+# DC_ECMS -- Convert echelle dispersion coeffs to multispec coeffs
+
+
+# DC_OPEN -- Initialize the dispersion routines. This consists
+# of opening a symbol table for the dispersion solution functions. A
+# symbol table is used since the same dispersion reference (arc image)
+# may be be used multiple times and the database access is slow.
+
+procedure dc_open (stp, db)
+
+pointer stp # Symbol table pointer
+char db[SZ_FNAME] # Database name
+
+pointer sym, stopen(), stenter(), stpstr()
+
+begin
+ stp = stopen ("disp", 10, 10, 10*SZ_FNAME)
+ sym = stenter (stp, "database", 1)
+ Memi[sym] = stpstr (stp, db, 0)
+end
+
+
+# DC_CLOSE -- Close the dispersion data structures.
+
+procedure dc_close (stp)
+
+int i
+pointer stp, sym, sthead, stnext
+
+begin
+ # Close each dispersion function and then the symbol table.
+ for (sym = sthead (stp); sym != NULL; sym = stnext (stp, sym)) {
+ if (DC_FORMAT(sym) == 1) {
+ do i = 1, DC_NAPS(sym) {
+ call un_close (Memi[DC_PUN(sym)+i-1])
+ call mfree (Memi[DC_PCOEFF(sym)+i-1], TY_DOUBLE)
+ }
+ call mfree (DC_PAPS(sym), TY_INT)
+ call mfree (DC_PAPCEN(sym), TY_REAL)
+ call mfree (DC_PUN(sym), TY_POINTER)
+ call mfree (DC_PSHIFT(sym), TY_DOUBLE)
+ call mfree (DC_PCOEFF(sym), TY_POINTER)
+ } else if (DC_FORMAT(sym) == 2) {
+ call un_close (DC_PUN(sym))
+ call mfree (DC_COEFFS(sym), TY_DOUBLE)
+ }
+ }
+ call stclose (stp)
+end
+
+
+# DC_GMS -- Get a multispec spectrum. This consists of mapping the image
+# and setting a MWCS coordinate transformation. If not dispersion corrected
+# the dispersion function is found in the database for the reference
+# spectra and set in the SMW.
+
+procedure dc_gms (spec, im, smw, stp, ignoreaps, ap, fd1, fd2)
+
+char spec[ARB] #I Spectrum name
+pointer im #I IMIO pointer
+pointer smw #I SMW pointer
+pointer stp #I Dispersion symbol table
+int ignoreaps #I Ignore aperture numbers?
+pointer ap #O Aperture data structure
+int fd1 #I Logfile descriptor
+int fd2 #I Logfile descriptor
+
+double wt1, wt2, dval
+int i, j, k, k1, k2, l, dc, sfd, naps, naps1, naps2, ncoeffs
+pointer sp, str1, str2, papcen, pshift, coeffs, ct1, ct2, un, un1, un2
+pointer paps1, paps2, punits1, punits2, pshift1, pshift2, pcoeff1, pcoeff2
+
+bool un_compare()
+double smw_c1trand()
+int imaccf(), nscan(), stropen()
+pointer smw_sctran(), un_open()
+errchk dc_gmsdb, dc_refshft, imgstr, smw_sctran, un_open
+
+define done_ 90
+
+begin
+ call smark (sp)
+ call salloc (str1, SZ_LINE, TY_CHAR)
+ call salloc (str2, SZ_LINE, TY_CHAR)
+
+ # Set WCS attributes
+ naps = IM_LEN(im,2)
+ call calloc (ap, LEN_AP(naps), TY_STRUCT)
+ do i = 1, naps {
+ DC_PL(ap,i) = i
+ DC_CF(ap,i) = NULL
+ call smw_gwattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), DC_CF(ap,i))
+ if (i == 1) {
+ iferr (call mw_gwattrs (SMW_MW(smw,0), 1, "units", Memc[str1],
+ SZ_LINE))
+ Memc[str1] = EOS
+ DC_UN(ap,i) = un_open (Memc[str1])
+ }
+ dc = DC_DT(ap,i)
+ }
+
+ # Check if the spectra have been dispersion corrected
+ # by an earlier version of DISPCOR. If so then don't allow
+ # another database dispersion correction. This assumes all
+ # spectra have the same dispersion type. Check for a
+ # reference spectrum.
+
+ #if ((imaccf (im, "REFSPEC1") == NO) ||
+ # (dc > -1 && imaccf (im, "DCLOG1") == NO)) {
+ if (imaccf (im, "REFSPEC1") == NO) {
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ "%s: Resampling using current coordinate system\n")
+ call pargstr (spec)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ "%s: Resampling using current coordinate system\n")
+ call pargstr (spec)
+ }
+ goto done_
+ }
+
+ # Get the reference spectra dispersion function from the database
+ # and determine a reference shift.
+
+ iferr {
+ call imgstr (im, "REFSPEC1", Memc[str1], SZ_LINE)
+ call sscan (Memc[str1])
+ call gargwrd (Memc[str1], SZ_LINE)
+ call gargd (wt1)
+ if (nscan() == 1)
+ wt1 = 1.
+ } then {
+ call strcpy (spec, Memc[str1], SZ_FNAME)
+ wt1 = 1.
+ }
+ iferr (call dc_gmsdb (Memc[str1], stp, paps1, papcen, punits1, pshift,
+ pcoeff1, naps1)) {
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+ call salloc (pshift1, naps1, TY_DOUBLE)
+ call amovd (Memd[pshift], Memd[pshift1], naps1)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSPEC1 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt1)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSPEC1 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt1)
+ }
+
+ iferr (call dc_refshft (spec, stp, Memc[str1], "REFSHFT1", im,
+ Memi[paps1], Memr[papcen], Memd[pshift1], naps1, fd1, fd2))
+ ;
+
+ iferr {
+ call imgstr (im, "REFSPEC2", Memc[str1], SZ_LINE)
+ call sscan (Memc[str1])
+ call gargwrd (Memc[str1], SZ_LINE)
+ call gargd (wt2)
+ if (nscan() == 1)
+ wt2 = 1.
+ call dc_gmsdb (Memc[str1], stp, paps2, papcen, punits2, pshift,
+ pcoeff2, naps2)
+ call salloc (pshift2, naps2, TY_DOUBLE)
+ call amovd (Memd[pshift], Memd[pshift2], naps2)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSPEC2 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt2)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSPEC2 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt2)
+ }
+ iferr (call dc_refshft (spec, stp, Memc[str1],
+ "REFSHFT2", im, Memi[paps2], Memr[papcen], Memd[pshift2],
+ naps2, fd1, fd2))
+ ;
+ } then
+ wt2 = 0.
+
+ # Adjust weights to unit sum.
+ dval = wt1 + wt2
+ wt1 = wt1 / dval
+ wt2 = wt2 / dval
+
+ # Enter dispersion function in the MWCS.
+ do i = 1, naps {
+ j = DC_AP(ap,i)
+ for (k1=0; k1<naps1 && Memi[paps1+k1]!=j; k1=k1+1)
+ ;
+ if (k1 == naps1)
+ for (k1=0; k1<naps1 && !IS_INDEFI(Memi[paps1+k1]); k1=k1+1)
+ ;
+ if (k1 == naps1) {
+ if (ignoreaps == YES)
+ k1 = 0
+ else {
+ call sprintf (Memc[str1], SZ_LINE,
+ "%s - Missing reference for aperture %d")
+ call pargstr (spec)
+ call pargi (j)
+ call fatal (1, Memc[str1])
+ }
+ }
+ un1 = Memi[punits1+k1]
+
+ # The following assumes some knowledge of the data structure in
+ # order to shortten the the attribute string.
+ coeffs = Memi[pcoeff1+k1]
+ if (coeffs == NULL) {
+ if (DC_DT(ap,i) == 2) {
+ sfd = NULL
+ if (wt2 <= 0.)
+ call sshift1 (Memd[pshift1+k1], DC_CF(ap,i))
+ } else {
+ ncoeffs = 6
+ l = 20 * (ncoeffs + 2)
+ if (wt2 > 0.)
+ l = 2 * l
+ call realloc (DC_CF(ap,i), l, TY_CHAR)
+ call aclrc (Memc[DC_CF(ap,i)], l)
+ sfd = stropen (Memc[DC_CF(ap,i)], l, NEW_FILE)
+ call fprintf (sfd, "%.8g %g")
+ call pargd (wt1)
+ call pargd (Memd[pshift1+k1])
+ dval = DC_DW(ap,i) * (DC_NW(ap,i) - 1) / 2.
+ call fprintf (sfd, " 1 2 1 %d %g %g")
+ call pargi (DC_NW(ap,i))
+ call pargd (DC_W1(ap,i) + dval)
+ call pargd (dval)
+ }
+ } else {
+ ncoeffs = nint (Memd[coeffs])
+ l = 20 * (ncoeffs + 2)
+ if (wt2 > 0.)
+ l = 2 * l
+ call realloc (DC_CF(ap,i), l, TY_CHAR)
+ call aclrc (Memc[DC_CF(ap,i)], l)
+ sfd = stropen (Memc[DC_CF(ap,i)], l, NEW_FILE)
+ call fprintf (sfd, "%.8g %g %d %d")
+ call pargd (wt1)
+ call pargd (Memd[pshift1+k1])
+ call pargi (nint (Memd[coeffs+1]))
+ call pargi (nint (Memd[coeffs+2]))
+ do k = 3, ncoeffs {
+ call fprintf (sfd, " %.15g")
+ call pargd (Memd[coeffs+k])
+ }
+ }
+
+ if (wt2 > 0.) {
+ for (k2=0; k2<naps2 && Memi[paps2+k2]!=j; k2=k2+1)
+ ;
+ if (k2 == naps2)
+ for (k2=0; k2<naps2 && !IS_INDEFI(Memi[paps2+k2]); k2=k2+1)
+ ;
+ if (k2 == naps2) {
+ if (ignoreaps == YES)
+ k2 = 0
+ else {
+ call sprintf (Memc[str1], SZ_LINE,
+ "%s - Missing reference for aperture %d")
+ call pargstr (spec)
+ call pargi (j)
+ if (sfd != NULL)
+ call strclose (sfd)
+ call sfree (sp)
+ call fatal (1, Memc[str1])
+ }
+ }
+ un2 = Memi[punits2+k2]
+ if (!un_compare (un1, un2)) {
+ call sfree (sp)
+ call error (2,
+ "Can't combine references with different units")
+ }
+ if (DC_DT(ap,i)==2 && !(coeffs==NULL&&Memi[pcoeff2+k2]==NULL)) {
+ call sfree (sp)
+ call error (2,
+ "Can't combine references with non-linear dispersions")
+ }
+ coeffs = Memi[pcoeff2+k2]
+ if (coeffs == NULL) {
+ if (DC_DT(ap,i) == 2) {
+ dval = (wt1*Memd[pshift1+k1] + wt2*Memd[pshift2+k2]) /
+ (wt1 + wt2)
+ call sshift1 (dval, DC_CF(ap,i))
+ } else {
+ call fprintf (sfd, " %.8g %g")
+ call pargd (wt2)
+ call pargd (Memd[pshift2+k2])
+ dval = DC_DW(ap,i) * (DC_NW(ap,i) - 1) / 2.
+ call fprintf (sfd, " 1 2 1 %d %g %g")
+ call pargi (DC_NW(ap,i))
+ call pargd (DC_W1(ap,i) + dval)
+ call pargd (dval)
+ }
+ } else {
+ call fprintf (sfd, " %.8g %g %d %d")
+ call pargd (wt2)
+ call pargd (Memd[pshift2+k2])
+ call pargi (nint (Memd[coeffs+1]))
+ call pargi (nint (Memd[coeffs+2]))
+ ncoeffs = nint (Memd[coeffs])
+ do k = 3, ncoeffs {
+ call fprintf (sfd, " %.15g")
+ call pargd (Memd[coeffs+k])
+ }
+ }
+ }
+
+ if (i == 1) {
+ un = un1
+ if (UN_LABEL(un) != EOS)
+ call mw_swattrs (SMW_MW(smw,0), 1, "label", UN_LABEL(un))
+ if (UN_UNITS(un) != EOS)
+ call mw_swattrs (SMW_MW(smw,0), 1, "units", UN_UNITS(un))
+ call un_close (DC_UN(ap,i))
+ DC_UN(ap,i) = un
+ } else if (!un_compare (un, un1)) {
+ call sfree (sp)
+ call error (3, "Units must be the same for all apertures")
+ }
+ DC_DT(ap,i) = 2
+ call smw_swattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), Memc[DC_CF(ap,i)])
+ if (sfd != NULL)
+ call strclose (sfd)
+ }
+
+ # Update the linear part of WCS.
+ ct1 = smw_sctran (smw, "logical", "physical", 2)
+ ct2 = smw_sctran (smw, "physical", "world", 3)
+ do i = 1, naps {
+ call smw_gwattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), DC_CF(ap,i))
+ wt1 = nint (smw_c1trand (ct1, double(i)))
+ call smw_c2trand (ct2, double(DC_NW(ap,i)), wt1, DC_W2(ap,i), wt2)
+ DC_DW(ap,i) = (DC_W2(ap,i) - DC_W1(ap,i)) / (DC_NW(ap,i) - 1)
+ call smw_swattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), Memc[DC_CF(ap,i)])
+ }
+ call smw_ctfree (ct1)
+ call smw_ctfree (ct2)
+
+done_ # Set aperture parameters in terms of logical image.
+ ct1 = smw_sctran (smw, "physical", "logical", 1)
+ j = nint (smw_c1trand (ct1, 1D0))
+ do i = 1, naps {
+ k = nint (smw_c1trand (ct1, double(DC_NW(ap,i))))
+ DC_NW(ap,i) = min (IM_LEN(im,1), max (j, k))
+ }
+ call smw_ctfree (ct1)
+
+ ct1 = smw_sctran (smw, "logical", "world", 3)
+ do i = 1, naps {
+ wt1 = i
+ call smw_c2trand (ct1, 1D0, wt1, DC_W1(ap,i), wt2)
+ call smw_c2trand (ct1, double(DC_NW(ap,i)), wt1, DC_W2(ap,i), wt2)
+ DC_DW(ap,i) = (DC_W2(ap,i) - DC_W1(ap,i)) / (DC_NW(ap,i) - 1)
+ }
+ call smw_ctfree (ct1)
+
+ do i = 1, naps
+ call mfree (DC_CF(ap,i), TY_CHAR)
+ call sfree (sp)
+end
+
+
+# DC_GMSDB -- Get a dispersion database entry.
+# The database entry is read only once from the database and stored in a
+# symbol table keyed by the spectrum name. Subsequent requests for the
+# reference spectrum returns the data from the symbol table.
+
+procedure dc_gmsdb (spec, stp, paps, papcen, punits, pshift, pcoeff, naps)
+
+char spec[ARB] # Spectrum image name
+pointer stp # Symbol table pointer
+pointer paps # Pointer to aperture numbers
+pointer papcen # Pointer to aperture centers
+pointer punits # Pointer to units
+pointer pshift # Pointer to shifts
+pointer pcoeff # Pointer to coefficients
+int naps # Number of apertures
+
+double dval
+int i, n, dtgeti(), getline(), ctod()
+real low, high, dtgetr()
+pointer sp, str, coeffs, sym, db, dt, dt1
+pointer stfind(), stenter(), strefsbuf(), dtmap1(), un_open()
+errchk dtmap1, dtgeti, dtgad, un_open
+
+begin
+ # Check if dispersion solution is in the symbol table from a previous
+ # call. If not in the symbol table get it from the database and
+ # store it in the symbol table.
+
+ sym = stfind (stp, spec)
+ if (sym == NULL) {
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call strcpy ("id", Memc[str], SZ_LINE)
+ call imgcluster (spec, Memc[str+2], SZ_LINE-2)
+ call xt_imroot (Memc[str+2], Memc[str+2], SZ_LINE-2)
+ db = strefsbuf (stp, Memi[stfind (stp, "database")])
+ dt = dtmap1 (Memc[db], Memc[str], READ_ONLY)
+ call strcpy ("ec", Memc[str], SZ_LINE)
+ call imgcluster (spec, Memc[str+2], SZ_LINE-2)
+ call xt_imroot (Memc[str+2], Memc[str+2], SZ_LINE-2)
+ ifnoerr (dt1 = dtmap1 (Memc[db], Memc[str], READ_ONLY)) {
+ call sprintf (Memc[str], SZ_LINE,
+ "Ambiguous database files: %s/%s and %s/%s")
+ call pargstr (DT_DNAME(dt))
+ call pargstr (DT_FNAME(dt))
+ call pargstr (DT_DNAME(dt1))
+ call pargstr (DT_FNAME(dt1))
+ call dtunmap (dt)
+ call dtunmap (dt1)
+ call fatal (3, Memc[str])
+ }
+
+ naps = max (1, DT_NRECS(dt))
+ call calloc (paps, naps, TY_INT)
+ call calloc (papcen, naps, TY_REAL)
+ call calloc (punits, naps, TY_POINTER)
+ call calloc (pshift, naps, TY_DOUBLE)
+ call calloc (pcoeff, naps, TY_POINTER)
+ if (DT_NRECS(dt) > 0) {
+ for (i = 1; i <= naps; i = i + 1) {
+ iferr (Memi[paps+i-1] = dtgeti (dt, i, "aperture"))
+ Memi[paps+i-1] = INDEFI
+ iferr (low = dtgetr (dt, i, "aplow"))
+ low = INDEF
+ iferr (high = dtgetr (dt, i, "aphigh"))
+ high = INDEF
+ if (IS_INDEF(low) || IS_INDEF(high))
+ Memr[papcen+i-1] = 0.
+ else
+ Memr[papcen+i-1] = (low + high) / 2.
+ iferr (call dtgstr (dt, i, "units", Memc[str], SZ_LINE))
+ call strcpy ("Angstroms", Memc[str], SZ_LINE)
+ Memi[punits+i-1] = un_open (Memc[str])
+ iferr (Memd[pshift+i-1] = dtgetr (dt, i, "shift"))
+ Memd[pshift+i-1] = 0.
+ iferr {
+ n = dtgeti (dt, i, "coefficients")
+ call malloc (coeffs, 1+n, TY_DOUBLE)
+ Memd[coeffs] = n
+ call dtgad (dt, i, "coefficients", Memd[coeffs+1], n, n)
+ Memi[pcoeff+i-1] = coeffs
+ } then
+ Memi[pcoeff+i-1] = NULL
+ }
+ } else {
+ Memi[paps] = INDEFI
+ Memr[papcen] = INDEFR
+ Memi[punits] = un_open ("")
+ Memd[pshift] = 0.
+ call malloc (coeffs, 100, TY_DOUBLE)
+ n = 3
+ call seek (Memi[dt], BOF)
+ while (getline (Memi[dt], Memc[str]) != EOF) {
+ i = 1
+ if (ctod (Memc[str], i, dval) == 0)
+ next
+ if (mod (n, 100) == 0)
+ call realloc (coeffs, n+100, TY_DOUBLE)
+ Memd[coeffs+n] = dval
+ n = n + 1
+ }
+ Memd[coeffs] = n - 1
+ Memd[coeffs+1] = 5
+ Memd[coeffs+2] = n - 3
+ Memi[pcoeff] = coeffs
+ }
+
+ call dtunmap (dt)
+ call sfree (sp)
+
+ sym = stenter (stp, spec, LEN_DC)
+ DC_FORMAT(sym) = 1
+ DC_PAPS(sym) = paps
+ DC_PAPCEN(sym) = papcen
+ DC_PUN(sym) = punits
+ DC_PSHIFT(sym) = pshift
+ DC_PCOEFF(sym) = pcoeff
+ DC_NAPS(sym) = naps
+ } else {
+ if (DC_FORMAT(sym) != 1)
+ call error (1, "Not a multispec dispersion function")
+ paps = DC_PAPS(sym)
+ papcen = DC_PAPCEN(sym)
+ punits = DC_PUN(sym)
+ pshift = DC_PSHIFT(sym)
+ pcoeff = DC_PCOEFF(sym)
+ naps = DC_NAPS(sym)
+ }
+end
+
+
+# DC_REFSHFT -- Compute dispersion shift.
+
+procedure dc_refshft (spec, stp, refspec, keywrd, im, aps, apcens, shifts,
+ naps, fd1, fd2)
+
+char spec[ARB] # Spectrum to be corrected
+pointer stp # Symbol table pointer
+char refspec[ARB] # Reference spectrum
+char keywrd[ARB] # Header keyword (for log only)
+pointer im # IMIO pointer to spectrum to be corrected
+int aps[naps] # Reference apertures
+real apcens[naps] # Reference aperture centers
+double shifts[naps] # Reference aperture shifts (to be modified)
+int naps # Number of refernce apertures
+int fd1 # Logfile descriptor
+int fd2 # Logfile descriptor
+
+int i, j, k, pnaps
+double apcen, shift, sumx, sumy, sumxx, sumyy, sumxy, a, b
+pointer sp, refshft, option, paps, papcen, punits, pshift, pcoeff
+bool streq()
+errchk imgstr, dc_gmsdb
+
+begin
+ call smark (sp)
+ call salloc (refshft, SZ_FNAME, TY_CHAR)
+ call salloc (option, SZ_FNAME, TY_CHAR)
+
+ # Parse header parameter.
+ call imgstr (im, keywrd, Memc[refshft], SZ_FNAME)
+ call sscan (Memc[refshft])
+ call gargwrd (Memc[refshft], SZ_FNAME)
+ if (streq (Memc[refshft], refspec)) {
+ call sfree (sp)
+ return
+ }
+ call gargwrd (Memc[option], SZ_FNAME)
+
+ # Get reference shift apertures.
+ call dc_gmsdb (Memc[refshft], stp, paps, papcen, punits, pshift,
+ pcoeff, pnaps)
+ if (pnaps == 0) {
+ call sfree (sp)
+ return
+ }
+
+ # Compute mean shift and RMS.
+ sumy = 0.
+ sumyy = 0.
+ do i = 1, pnaps {
+ sumy = sumy + Memd[pshift+i-1]
+ sumyy = sumyy + Memd[pshift+i-1] ** 2
+ }
+ sumy = sumy / pnaps
+ sumyy = sqrt (max (0.D0, sumyy / pnaps - sumy ** 2))
+
+ # Print.
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: %s = '%s %s', shift = %.6g, rms = %.6g\n")
+ call pargstr (spec)
+ call pargstr (keywrd)
+ call pargstr (Memc[refshft])
+ call pargstr (Memc[option])
+ call pargd (sumy)
+ call pargd (sumyy)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: %s = '%s %s', shift = %.6g, rms = %.6g\n")
+ call pargstr (spec)
+ call pargstr (keywrd)
+ call pargstr (Memc[refshft])
+ call pargstr (Memc[option])
+ call pargd (sumy)
+ call pargd (sumyy)
+ }
+
+ if (streq (Memc[option], "interp")) {
+ if (pnaps > 1) {
+ sumx = 0.
+ sumy = 0.
+ sumxx = 0.
+ sumyy = 0.
+ sumxy = 0.
+ do i = 0, pnaps-1 {
+ apcen = Memr[papcen+i]
+ shift = Memd[pshift+i]
+ sumx = sumx + apcen
+ sumy = sumy + shift
+ sumxx = sumxx + apcen * apcen
+ sumyy = sumyy + shift * shift
+ sumxy = sumxy + apcen * shift
+ }
+ b = pnaps * sumxx - sumx * sumx
+ a = (sumy * sumxx - sumx * sumxy) / b
+ b = (pnaps * sumxy - sumx * sumy) / b
+ } else {
+ a = sumy
+ b = 0.
+ }
+ do i = 1, naps
+ shifts[i] = shifts[i] + a + b * apcens[i]
+ if (fd1 != NULL) {
+ call fprintf (fd1, "\tintercept = %.6g, slope = %.6g\n")
+ call pargd (a)
+ call pargd (b)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "\tintercept = %.6g, slope = %.6g\n")
+ call pargd (a)
+ call pargd (b)
+ }
+ } else if (streq (Memc[option], "nearest")) {
+ do i = 1, naps {
+ k = 0
+ sumy = abs (apcens[i] - Memr[papcen])
+ for (j = 1; j < pnaps; j = j + 1)
+ if (abs (apcens[i] - Memr[papcen+j]) < sumy) {
+ k = j
+ sumy = abs (apcens[i] - Memr[papcen+k])
+ }
+ shifts[i] = shifts[i] + Memd[pshift+k]
+ if (fd1 != NULL) {
+ call fprintf (fd1, "\t%4d %7.2f %4d %7.2f %.6g\n")
+ call pargi (aps[i])
+ call pargr (apcens[i])
+ call pargi (Memi[paps+k])
+ call pargr (Memr[papcen+k])
+ call pargd (Memd[pshift+k])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "\t%4d %7.2f %4d %7.2f %.6g\n")
+ call pargi (aps[i])
+ call pargr (apcens[i])
+ call pargi (Memi[paps+k])
+ call pargr (Memr[papcen+k])
+ call pargd (Memd[pshift+k])
+ }
+ }
+ } else
+ call aaddkd (shifts, sumy, shifts, naps)
+
+ call sfree (sp)
+end
+
+
+# DC_GEC -- Get an echelle spectrum. This consists of mapping the image
+# and setting a MWCS coordinate transformation. If not dispersion corrected
+# the dispersion function is found in the database for the reference
+# spectra and set in the SMW.
+
+procedure dc_gec (spec, im, smw, stp, ap, fd1, fd2)
+
+char spec[ARB] #I Spectrum name
+pointer im #I IMIO pointer
+pointer smw #I SMW pointers
+pointer stp #I Symbol table
+pointer ap #O Aperture data structure
+int fd1 #I Logfile descriptor
+int fd2 #I Logfile descriptor
+
+double wt1, wt2, dval
+int i, j, k, l, dc, sfd, naps, ncoeffs, offset, slope
+pointer sp, str1, str2, coeff, coeffs, ct1, ct2, un1, un2, un3
+pointer pshift1, pshift2, pshift3, pcoeff1, pcoeff2, pcoeff3
+
+bool un_compare()
+double smw_c1trand()
+int imaccf(), nscan(), stropen()
+pointer smw_sctran(), un_open()
+errchk dc_gecdb, imgstr, smw_sctran, un_open
+
+define done_ 90
+
+begin
+ call smark (sp)
+ call salloc (str1, SZ_LINE, TY_CHAR)
+ call salloc (str2, SZ_LINE, TY_CHAR)
+ coeff = NULL
+
+ # Set WCS attributes
+ naps = IM_LEN(im,2)
+ call calloc (ap, LEN_AP(naps), TY_STRUCT)
+ do i = 1, naps {
+ DC_PL(ap,i) = i
+ call smw_gwattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), coeff)
+ if (i == 1) {
+ iferr (call mw_gwattrs (SMW_MW(smw,0), 1, "units", Memc[str1],
+ SZ_LINE))
+ Memc[str1] = EOS
+ DC_UN(ap,i) = un_open (Memc[str1])
+ }
+ dc = DC_DT(ap,i)
+ }
+
+ # Check if the spectra have been dispersion corrected
+ # by an earlier version of DISPCOR. If so then don't allow
+ # another database dispersion correction. This assumes all
+ # spectra have the same dispersion type. Check for a
+ # reference spectrum.
+
+ #if ((imaccf (im, "REFSPEC1") == NO) ||
+ # (dc > -1 && imaccf (im, "DCLOG1") == NO)) {
+ if (imaccf (im, "REFSPEC1") == NO) {
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ "%s: Resampling using current coordinate system\n")
+ call pargstr (spec)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ "%s: Resampling using current coordinate system\n")
+ call pargstr (spec)
+ }
+ goto done_
+ }
+
+ # Get the reference spectra dispersion function from the database
+ # and determine a reference shift.
+
+ iferr {
+ call imgstr (im, "REFSPEC1", Memc[str1], SZ_LINE)
+ call sscan (Memc[str1])
+ call gargwrd (Memc[str1], SZ_LINE)
+ call gargd (wt1)
+ if (nscan() == 1)
+ wt1 = 1.
+ } then {
+ call strcpy (spec, Memc[str1], SZ_LINE)
+ wt1 = 1.
+ }
+ call salloc (pshift1, naps, TY_DOUBLE)
+ call salloc (pcoeff1, naps, TY_POINTER)
+ slope = 0
+ iferr (call dc_gecdb (Memc[str1], stp, ap, un1, Memd[pshift1],
+ Memi[pcoeff1], naps, offset, slope)) {
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSPEC1 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt1)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSPEC1 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt1)
+ }
+
+ iferr {
+ call imgstr (im, "refshft1", Memc[str1], SZ_LINE)
+ call salloc (pshift3, naps, TY_DOUBLE)
+ call salloc (pcoeff3, naps, TY_POINTER)
+ call dc_gecdb (Memc[str1], stp, ap, un3, Memd[pshift3],
+ Memi[pcoeff3], naps, offset, slope)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSHFT1 = '%s', shift = %.6g\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (Memd[pshift3])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSHFT1 = '%s', shift = %.6g\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (Memd[pshift3])
+ }
+ call aaddd (Memd[pshift1], Memd[pshift3], Memd[pshift1], naps)
+ } then
+ ;
+
+ iferr {
+ call imgstr (im, "REFSPEC2", Memc[str1], SZ_LINE)
+ call sscan (Memc[str1])
+ call gargwrd (Memc[str1], SZ_LINE)
+ call gargd (wt2)
+ if (nscan() == 1)
+ wt2 = 1.
+ call salloc (pshift2, naps, TY_DOUBLE)
+ call salloc (pcoeff2, naps, TY_POINTER)
+ call dc_gecdb (Memc[str1], stp, ap, un2, Memd[pshift2],
+ Memi[pcoeff2], naps, offset, slope)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSPEC2 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt2)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSPEC2 = '%s %.8g'\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (wt2)
+ }
+
+ iferr {
+ call imgstr (im, "refshft2", Memc[str1], SZ_LINE)
+ call salloc (pshift3, naps, TY_DOUBLE)
+ call salloc (pcoeff3, naps, TY_POINTER)
+ call dc_gecdb (Memc[str1], stp, ap, un3, Memd[pshift3],
+ Memi[pcoeff3], naps, offset, slope)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s: REFSHFT2 = '%s', shift = %.6g\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (Memd[pshift3])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s: REFSHFT2 = '%s', shift = %.6g\n")
+ call pargstr (spec)
+ call pargstr (Memc[str1])
+ call pargd (Memd[pshift3])
+ }
+ call aaddd (Memd[pshift1], Memd[pshift3], Memd[pshift1], naps)
+ } then
+ ;
+ } then
+ wt2 = 0.
+
+ # Adjust weights to unit sum.
+ dval = wt1 + wt2
+ wt1 = wt1 / dval
+ wt2 = wt2 / dval
+
+ # Enter dispersion function in the MWCS.
+ do i = 1, naps {
+ coeffs = Memi[pcoeff1+i-1]
+ ncoeffs = nint (Memd[coeffs])
+ l = 20 * (ncoeffs + 2)
+ if (wt2 > 0.)
+ l = 2 * l
+ call realloc (coeff, l, TY_CHAR)
+ call aclrc (Memc[coeff], l)
+ sfd = stropen (Memc[coeff], l, NEW_FILE)
+ call fprintf (sfd, "%.8g %g")
+ call pargd (wt1)
+ call pargd (Memd[pshift1+i-1])
+
+ # The following assumes some knowledge of the data structure in
+ # order to shortten the the attribute string.
+
+ call fprintf (sfd, " %d %d %.8g %.8g")
+ call pargi (nint (Memd[coeffs+1]))
+ call pargi (nint (Memd[coeffs+2]))
+ call pargd (Memd[coeffs+3])
+ call pargd (Memd[coeffs+4])
+ do j = 5, ncoeffs {
+ call fprintf (sfd, " %.15g")
+ call pargd (Memd[coeffs+j])
+ }
+
+ if (wt2 > 0.) {
+ coeffs = Memi[pcoeff2+i-1]
+ ncoeffs = nint (Memd[coeffs])
+ call fprintf (sfd, "%.8g %g")
+ call pargd (wt2)
+ call pargd (Memd[pshift2+i-1])
+ call fprintf (sfd, " %d %d %.8g %.8g")
+ call pargi (nint (Memd[coeffs+1]))
+ call pargi (nint (Memd[coeffs+2]))
+ call pargd (Memd[coeffs+3])
+ call pargd (Memd[coeffs+4])
+ do j = 5, ncoeffs {
+ call fprintf (sfd, " %.15g")
+ call pargd (Memd[coeffs+j])
+ }
+ if (!un_compare (un1, un2)) {
+ call sfree (sp)
+ call error (2,
+ "Can't combine references with different units")
+ }
+ }
+
+ if (i == 1) {
+ if (UN_LABEL(un1) != EOS)
+ call mw_swattrs (SMW_MW(smw,0), 1, "label", UN_LABEL(un1))
+ if (UN_UNITS(un1) != EOS)
+ call mw_swattrs (SMW_MW(smw,0), 1, "units", UN_UNITS(un1))
+ call un_close (DC_UN(ap,i))
+ DC_UN(ap,i) = un1
+ }
+ DC_DT(ap,i) = 2
+ call smw_swattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), Memc[coeff])
+ call strclose (sfd)
+ }
+
+ # Update the linear part of WCS.
+ ct1 = smw_sctran (smw, "logical", "physical", 2)
+ ct2 = smw_sctran (smw, "physical", "world", 3)
+ do i = 1, naps {
+ call smw_gwattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), coeff)
+ wt1 = nint (smw_c1trand (ct1, double(i)))
+ call smw_c2trand (ct2, 1D0, wt1, DC_W1(ap,i), wt2)
+ call smw_c2trand (ct2, double(DC_NW(ap,i)), wt1, DC_W2(ap,i), wt2)
+ DC_DW(ap,i) = (DC_W2(ap,i) - DC_W1(ap,i)) / (DC_NW(ap,i) - 1)
+ call smw_swattrs (smw, DC_PL(ap,i), 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i), DC_Z(ap,i),
+ DC_LW(ap,i), DC_UP(ap,i), Memc[coeff])
+ }
+ call smw_ctfree (ct1)
+ call smw_ctfree (ct2)
+
+done_ # Set aperture parameters in terms of logical image.
+ ct1 = smw_sctran (smw, "physical", "logical", 1)
+ j = nint (smw_c1trand (ct1, 1D0))
+ do i = 1, naps {
+ k = nint (smw_c1trand (ct1, double(DC_NW(ap,i))))
+ DC_NW(ap,i) = min (IM_LEN(im,1), max (j, k))
+ }
+ call smw_ctfree (ct1)
+
+ ct1 = smw_sctran (smw, "logical", "world", 3)
+ do i = 1, naps {
+ wt1 = i
+ call smw_c2trand (ct1, 1D0, wt1, DC_W1(ap,i), wt2)
+ call smw_c2trand (ct1, double(DC_NW(ap,i)), wt1, DC_W2(ap,i), wt2)
+ DC_DW(ap,i) = (DC_W2(ap,i) - DC_W1(ap,i)) / (DC_NW(ap,i) - 1)
+ }
+ call smw_ctfree (ct1)
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# DC_GECDB -- Get a dispersion database entry.
+# The database entry is read only once from the database and stored in a
+# symbol table keyed by the spectrum name. Subsequent requests for the
+# reference spectrum returns the data from the symbol table.
+
+procedure dc_gecdb (spec, stp, ap, un, shifts, pcoeff, naps, offset, slope)
+
+char spec[ARB] # Spectrum image name
+pointer stp # Symbol table pointer
+pointer ap # Aperture data structure
+pointer un # Units
+double shifts[naps] # Shifts
+pointer pcoeff[naps] # Pointer to coefficients
+int naps # Number of apertures
+int offset # Aperture to order offset
+int slope # Aperture to order slope
+
+double shift
+real dtgetr()
+int i, rec, offst, slpe, n, dtlocate(), dtgeti()
+pointer sp, str, coeffs, sym, db, dt
+pointer stfind(), stenter(), strefsbuf(), dtmap1(), un_open()
+errchk dtmap1, dtlocate, dtgeti, dtgad, un_open
+
+begin
+ # Check if dispersion solution is in the symbol table from a previous
+ # call. If not in the symbol table get it from the database and
+ # store it in the symbol table.
+
+ sym = stfind (stp, spec)
+ if (sym == NULL) {
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call strcpy ("ec", Memc[str], SZ_LINE)
+ call imgcluster (spec, Memc[str+2], SZ_LINE-2)
+ call xt_imroot (Memc[str+2], Memc[str+2], SZ_LINE-2)
+ db = strefsbuf (stp, Memi[stfind (stp, "database")])
+ dt = dtmap1 (Memc[db], Memc[str], READ_ONLY)
+
+ call sprintf (Memc[str], SZ_LINE, "ecidentify %s")
+ call pargstr (spec)
+ iferr (rec = dtlocate (dt, Memc[str])) {
+ call sprintf (Memc[str], SZ_LINE,
+ "DISPCOR: Echelle dispersion function not found (%s/%s)")
+ call pargstr (DT_DNAME(dt))
+ call pargstr (DT_FNAME(dt))
+ call fatal (0, Memc[str])
+ }
+
+ iferr (call dtgstr (dt, rec, "units", Memc[str], SZ_LINE))
+ call strcpy ("Angstroms", Memc[str], SZ_LINE)
+ un = un_open (Memc[str])
+ iferr (offst = dtgeti (dt, rec, "offset"))
+ offst = 0
+ iferr (slpe = dtgeti (dt, rec, "slope"))
+ slpe = 1
+ iferr (shift = dtgetr (dt, rec, "shift"))
+ shift = 0.
+ n = dtgeti (dt, rec, "coefficients")
+ call malloc (coeffs, n, TY_DOUBLE)
+ call dtgad (dt, rec, "coefficients", Memd[coeffs], n, n)
+
+ sym = stenter (stp, spec, LEN_DC)
+ DC_FORMAT(sym) = 2
+ DC_PUN(sym) = un
+ DC_OFFSET(sym) = offst
+ DC_SLOPE(sym) = slpe
+ DC_SHIFT(sym) = shift
+ DC_COEFFS(sym) = coeffs
+
+ call dtunmap (dt)
+ call sfree (sp)
+ } else {
+ if (DC_FORMAT(sym) != 2)
+ call error (1, "Not an echelle dispersion function")
+ un = DC_PUN(sym)
+ offst = DC_OFFSET(sym)
+ slpe = DC_SLOPE(sym)
+ coeffs = DC_COEFFS(sym)
+ shift = DC_SHIFT(sym)
+ }
+
+ # Check aperture to order parameters.
+ if (slope == 0) {
+ offset = offst
+ slope = slpe
+ } else if (offset != offst || slope != slpe) {
+ call eprintf (
+ "WARNING: Echelle order offsets/slopes are not the same.\n")
+ }
+
+ # Convert to multispec coefficients
+ do i = 1, naps {
+ DC_BM(ap,i) = offset + slope * DC_AP(ap,i)
+ call dc_ecms (DC_BM(ap,i), Memd[coeffs], pcoeff[i])
+ shifts[i] = shift / DC_BM(ap,i)
+ }
+end
+
+
+# DC_ECMS -- Convert echelle dispersion coefficients to multispec coefficients
+
+procedure dc_ecms (order, eccoeff, mscoeff)
+
+int order # Echelle order
+double eccoeff[ARB] # Echelle dispersion coefficients
+pointer mscoeff # Pointer to multispec coefficients
+
+int i, j, k, type, xorder, yorder
+double xmin, xmax, ymin, ymax, ymaxmin, yrange, y, coeff, a, b, c
+
+begin
+ type = nint (eccoeff[1])
+ xorder = nint (eccoeff[2])
+ yorder = nint (eccoeff[3])
+ xmin = eccoeff[5]
+ xmax = eccoeff[6]
+ ymin = eccoeff[7]
+ ymax = eccoeff[8]
+
+ yrange = 2. / (ymax - ymin)
+ ymaxmin = (ymax + ymin) / 2
+ y = (order - ymaxmin) * yrange
+
+ call malloc (mscoeff, 5+xorder, TY_DOUBLE)
+ Memd[mscoeff] = 4+xorder
+ Memd[mscoeff+1] = type
+ Memd[mscoeff+2] = xorder
+ Memd[mscoeff+3] = xmin
+ Memd[mscoeff+4] = xmax
+
+ switch (type) {
+ case 1:
+ do k = 1, xorder {
+ j = 9 + k - 1
+ coeff = eccoeff[j]
+ if (yorder > 1) {
+ j = j + xorder
+ coeff = coeff + eccoeff[j] * y
+ }
+ if (yorder > 2) {
+ a = 1
+ b = y
+ do i = 3, yorder {
+ c = 2 * y * b - a
+ j = j + xorder
+ coeff = coeff + eccoeff[j] * c
+ a = b
+ b = c
+ }
+ }
+ Memd[mscoeff+4+k] = coeff / order
+ }
+ case 2:
+ do k = 1, xorder {
+ j = 9 + k - 1
+ coeff = eccoeff[j]
+ if (yorder > 1) {
+ j = j + xorder
+ coeff = coeff + eccoeff[j] * y
+ }
+ if (yorder > 2) {
+ a = 1
+ b = y
+ do i = 3, yorder {
+ c = ((2 * i - 3) * y * b - (i - 2) * a) / (i - 1)
+ j = j + xorder
+ coeff = coeff + eccoeff[j] * c
+ a = b
+ b = c
+ }
+ }
+ Memd[mscoeff+4+k] = coeff / order
+ }
+ }
+end
diff --git a/noao/onedspec/dispcor/dctable.h b/noao/onedspec/dispcor/dctable.h
new file mode 100644
index 00000000..4cf3657a
--- /dev/null
+++ b/noao/onedspec/dispcor/dctable.h
@@ -0,0 +1,11 @@
+# Wavelength table structure
+define TBL_LEN 14
+define TBL_W1 Memd[P2D($1)] # Starting wavelength
+define TBL_W2 Memd[P2D($1+2)] # Ending wavelength
+define TBL_DW Memd[P2D($1+4)] # Wavelength interval
+define TBL_WMIN Memd[P2D($1+6)] # Minimum wavelength for global
+define TBL_WMAX Memd[P2D($1+8)] # Maximum wavelength for global
+define TBL_AP Memi[$1+10] # Aperture
+define TBL_NW Memi[$1+11] # Number of points
+define TBL_NWMAX Memi[$1+12] # Maximum number of points for global
+define TBL_CONFIRM Memi[$1+13] # Confirm?
diff --git a/noao/onedspec/dispcor/dctable.x b/noao/onedspec/dispcor/dctable.x
new file mode 100644
index 00000000..93f27531
--- /dev/null
+++ b/noao/onedspec/dispcor/dctable.x
@@ -0,0 +1,145 @@
+include <imhdr.h>
+include <mach.h>
+include "dctable.h"
+include <smw.h>
+
+
+# DC_TABLE -- Set default wavelengths.
+# This may be specified by the task parameters alone, from a reference image,
+# or from a text table. A reference image or table allows separate
+# wavelength parameters for each aperture. The text table columns are the
+# aperture number, starting wavelength, ending wavelength, wavelength
+# interval per pixel, and number of pixels. Any of these values may be
+# INDEF.
+
+procedure dc_table (table, naps)
+
+pointer table # Table pointer (returned)
+int naps # Number of apertures (returned)
+
+int i, j, ap, nw, fd, clgeti(), open(), fscan(), nscan(), btoi(), nowhite()
+double ws, we, dw, clgetd()
+pointer sp, fname, tbl, mw, sh, immap(), smw_openim()
+bool clgetb()
+errchk smw_openim(), shdr_open()
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call clgstr ("table", Memc[fname], SZ_FNAME)
+
+ # Set defaults.
+ naps = 0
+ call malloc (table, 10, TY_INT)
+ call malloc (Memi[table], TBL_LEN, TY_STRUCT)
+ tbl= Memi[table]
+ TBL_W1(tbl) = clgetd ("w1")
+ TBL_W2(tbl) = clgetd ("w2")
+ TBL_DW(tbl) = clgetd ("dw")
+ TBL_NW(tbl) = clgeti ("nw")
+ TBL_WMIN(tbl) = MAX_REAL
+ TBL_WMAX(tbl) = -MAX_REAL
+ TBL_NWMAX(tbl) = 0
+ TBL_CONFIRM(tbl) = btoi (clgetb ("confirm"))
+
+ # Read a reference image or table if specified and add entries to
+ # the table array.
+
+ if (nowhite (Memc[fname], Memc[fname], SZ_FNAME) > 0) {
+ ifnoerr (fd = immap (Memc[fname], READ_ONLY, 0)) {
+ mw = smw_openim (fd)
+ call shdr_open (fd, mw, 1, 1, INDEFI, SHHDR, sh)
+ if (DC(sh) == DCLINEAR || DC(sh) == DCLOG) {
+ do j = 1, IM_LEN(fd,2) {
+ call shdr_open (fd, mw, j, 1, INDEFI, SHHDR, sh)
+ call dc_getentry (false, AP(sh), table, naps, i)
+ tbl = Memi[table+i]
+ TBL_AP(tbl) = AP(sh)
+ TBL_NW(tbl) = SN(sh)
+ TBL_W1(tbl) = W0(sh)
+ TBL_W2(tbl) = W1(sh)
+ TBL_DW(tbl) = WP(sh)
+ }
+ }
+ call shdr_close (sh)
+ call smw_close (mw)
+ call imunmap (fd)
+ } else {
+ ifnoerr (fd = open (Memc[fname], READ_ONLY, TEXT_FILE)) {
+ while (fscan (fd) != EOF) {
+ call gargi (ap)
+ call gargd (ws)
+ call gargd (we)
+ call gargd (dw)
+ call gargi (nw)
+ if (nscan() < 5)
+ next
+
+ call dc_getentry (false, ap, table, naps, i)
+ tbl = Memi[table+i]
+ TBL_AP(tbl) = ap
+ TBL_W1(tbl) = ws
+ TBL_W2(tbl) = we
+ TBL_DW(tbl) = dw
+ TBL_NW(tbl) = nw
+ }
+ call close (fd)
+ } else
+ call error (1, "Can't access wavelength table")
+ }
+ }
+
+ # If ignoreaps=yes then replace INDEFs in the default entry with
+ # the first non-INDEF entry.
+
+ if (clgetb ("ignoreaps") && naps > 0) {
+ tbl= Memi[table]
+ if (IS_INDEFD(TBL_W1(tbl)))
+ TBL_W1(tbl) = TBL_W1(Memi[table+1])
+ if (IS_INDEFD(TBL_W2(tbl)))
+ TBL_W2(tbl) = TBL_W2(Memi[table+1])
+ if (IS_INDEFD(TBL_DW(tbl)))
+ TBL_DW(tbl) = TBL_DW(Memi[table+1])
+ if (IS_INDEFI(TBL_NW(tbl)))
+ TBL_NW(tbl) = TBL_NW(Memi[table+1])
+ }
+
+ call sfree (sp)
+end
+
+
+# DC_GETENTRY -- Get entry from wavelength table. Return the index. Allocate
+# a new entry if needed.
+
+procedure dc_getentry (apflag, ap, table, naps, index)
+
+bool apflag # Ignore aperture numbers?
+int ap # Aperture
+pointer table # Wavelength table
+int naps # Number of apertures
+int index # Table index of entry
+
+pointer tbl
+
+begin
+ for (index=1; index<=naps; index=index+1)
+ if (apflag || TBL_AP(Memi[table+index]) == ap)
+ return
+
+ naps = naps + 1
+ if (mod (naps, 10) == 0)
+ call realloc (table, naps+10, TY_INT)
+ call malloc (Memi[table+naps], TBL_LEN, TY_STRUCT)
+
+ index = naps
+ tbl = Memi[table+index]
+ TBL_AP(tbl) = ap
+ TBL_W1(tbl) = TBL_W1(Memi[table])
+ TBL_W2(tbl) = TBL_W2(Memi[table])
+ TBL_DW(tbl) = TBL_DW(Memi[table])
+ TBL_NW(tbl) = TBL_NW(Memi[table])
+ TBL_WMIN(tbl) = TBL_WMIN(Memi[table])
+ TBL_WMAX(tbl) = TBL_WMAX(Memi[table])
+ TBL_NWMAX(tbl) = TBL_NWMAX(Memi[table])
+ TBL_CONFIRM(tbl) = TBL_CONFIRM(Memi[table])
+end
diff --git a/noao/onedspec/dispcor/dispcor.h b/noao/onedspec/dispcor/dispcor.h
new file mode 100644
index 00000000..51167973
--- /dev/null
+++ b/noao/onedspec/dispcor/dispcor.h
@@ -0,0 +1,16 @@
+# Aperture data structure
+
+define LEN_AP ($1*20) # Length of DC data structure
+define DC_PL Memi[$1+($2-1)*20+1] # Physical line number
+define DC_AP Memi[$1+($2-1)*20+2] # Aperture number
+define DC_BM Memi[$1+($2-1)*20+3] # Beam number
+define DC_DT Memi[$1+($2-1)*20+4] # Dispersion type
+define DC_NW Memi[$1+($2-1)*20+5] # Number of pixels in spectrum
+define DC_W1 Memd[P2D($1+($2-1)*20+6)] # Wavelength of first pixel
+define DC_W2 Memd[P2D($1+($2-1)*20+8)] # Wavelength of last pixel
+define DC_DW Memd[P2D($1+($2-1)*20+10)] # Wavelength interval per pixel
+define DC_Z Memd[P2D($1+($2-1)*20+12)] # Redshift
+define DC_LW Memr[P2R($1+($2-1)*20+14)] # Aperture lower limit (2)
+define DC_UP Memr[P2R($1+($2-1)*20+16)] # Aperture upper limit (2)
+define DC_CF Memi[$1+($2-1)*20+18] # Pointer to coefficients
+define DC_UN Memi[$1+($2-1)*20+19] # Units
diff --git a/noao/onedspec/dispcor/dispcor.x b/noao/onedspec/dispcor/dispcor.x
new file mode 100644
index 00000000..7f2c32a8
--- /dev/null
+++ b/noao/onedspec/dispcor/dispcor.x
@@ -0,0 +1,233 @@
+include <math/iminterp.h>
+
+# DISPCOR -- Dispersion correct input spectrum to output spectrum.
+# This procedure uses the MWCS forward and inverse transformations
+# and interpolate the input data, conserving flux if desired. Image
+# interpolation uses the image interpolation package and flux conservation
+# integrates the interpolation function across the output pixel. This
+# procedure does some CLIO to get the interpolation function and to
+# query whether to conserve flux.
+
+procedure dispcor (cti, linei, cto, lineo, in, npts, out, nw, flux)
+
+pointer cti #I MWCS input inverse transformation
+int linei #I Spectrum line
+pointer cto #I MWCS output forward transformation
+int lineo #I Spectrum line
+real in[npts] #I Input spectrum
+int npts #I Number of input pixels
+real out[nw] #O Output spectrum
+int nw #I Number of output pixels
+bool flux #I Conserve flux
+
+char interp[10]
+bool ofb_a, ofb_b
+int i, j, ia, ib, clgwrd()
+real a, b, sum, asieval(), asigrl()
+double x, xmin, xmax, w, y1, y2, smw_c1trand()
+pointer asi, temp
+
+begin
+ # Get the image buffers fit the interpolation function to the
+ # input spectrum. Extend the interpolation by one pixel at each end.
+
+ call malloc (temp, npts+2, TY_REAL)
+ call amovr (in, Memr[temp+1], npts)
+ Memr[temp] = in[1]
+ Memr[temp+npts+1] = in[npts]
+
+ call asiinit (asi, clgwrd ("interp", interp, 10, II_FUNCTIONS))
+ call asifit (asi, Memr[temp], npts+2)
+
+ call mfree (temp, TY_REAL)
+
+ # Determine edges of output pixels in input spectrum and integrate
+ # using ASIGRL. If not flux conserving take the average.
+
+ xmin = 0.5
+ xmax = npts + 0.5
+
+ x = 0.5
+ if (IS_INDEFI(lineo))
+ w = smw_c1trand (cto, x)
+ else {
+ y1 = lineo
+ call smw_c2trand (cto, x, y1, w, y2)
+ }
+ if (IS_INDEFI(linei))
+ x = smw_c1trand (cti, w)
+ else {
+ #y2 = linei
+ call smw_c2trand (cti, w, y2, x, y1)
+ }
+ ofb_b = (x < xmin || x > xmax)
+ b = max (xmin, min (xmax, x)) + 1
+ do i = 1, nw {
+ ofb_a = ofb_b
+ a = b
+ x = i + 0.5
+ if (IS_INDEFI(lineo))
+ w = smw_c1trand (cto, x)
+ else {
+ y1 = lineo
+ call smw_c2trand (cto, x, y1, w, y2)
+ }
+ if (IS_INDEFI(linei))
+ x = smw_c1trand (cti, w)
+ else {
+ #y2 = linei
+ call smw_c2trand (cti, w, y2, x, y1)
+ }
+ ofb_b = (x < xmin || x > xmax)
+ b = max (xmin, min (xmax, x)) + 1
+ if (ofb_a && ofb_b)
+ out[i] = 0.
+ else if (a <= b) {
+ ia = nint (a + 0.5)
+ ib = nint (b - 0.5)
+ if (abs (a+0.5-ia) < 0.00001 && abs (b-0.5-ib) < 0.00001) {
+ sum = 0.
+ do j = ia, ib
+ sum = sum + asieval (asi, real(j))
+ out[i] = sum
+ } else
+ out[i] = asigrl (asi, a, b)
+ if (!flux)
+ out[i] = out[i] / max (b - a, 1e-4)
+ } else {
+ ib = nint (b + 0.5)
+ ia = nint (a - 0.5)
+ if (abs (a-0.5-ia) < 0.00001 && abs (b+0.5-ib) < 0.00001) {
+ sum = 0.
+ do j = ib, ia
+ sum = sum + asieval (asi, real(j))
+ out[i] = sum
+ } else
+ out[i] = asigrl (asi, b, a)
+ if (!flux)
+ out[i] = out[i] / max (a - b, 1e-4)
+ }
+ }
+
+ call asifree (asi)
+end
+
+
+# DISPCORA -- Dispersion correct input spectrum to output spectrum.
+# This procedure uses the MWCS forward and inverse transformations
+# and interpolate the input data, conserving flux if desired. Image
+# interpolation uses the image interpolation package and flux conservation
+# integrates the interpolation function across the output pixel. This
+# procedure does some CLIO to get the interpolation function and to
+# query whether to conserve flux.
+#
+# This differs from DISPCOR by the "blank" argument.
+
+procedure dispcora (cti, linei, cto, lineo, in, npts, out, nw, flux, blank)
+
+pointer cti #I MWCS input inverse transformation
+int linei #I Spectrum line
+pointer cto #I MWCS output forward transformation
+int lineo #I Spectrum line
+real in[npts] #I Input spectrum
+int npts #I Number of input pixels
+real out[nw] #O Output spectrum
+int nw #I Number of output pixels
+bool flux #I Conserve flux
+real blank #I Out of bounds value (INDEF to leave unchanged
+
+char interp[10]
+bool ofb_a, ofb_b
+int i, j, ia, ib, clgwrd()
+real a, b, sum, asieval(), asigrl()
+double x, xmin, xmax, w, y1, y2, smw_c1trand()
+pointer asi, temp
+
+begin
+ # Get the image buffers fit the interpolation function to the
+ # input spectrum. Extend the interpolation by one pixel at each end.
+
+ call malloc (temp, npts+2, TY_REAL)
+ call amovr (in, Memr[temp+1], npts)
+ Memr[temp] = in[1]
+ Memr[temp+npts+1] = in[npts]
+
+ call asiinit (asi, clgwrd ("interp", interp, 10, II_FUNCTIONS))
+ call asifit (asi, Memr[temp], npts+2)
+
+ call mfree (temp, TY_REAL)
+
+ # Determine edges of output pixels in input spectrum and integrate
+ # using ASIGRL. If not flux conserving take the average.
+
+ xmin = 0.5
+ xmax = npts + 0.5
+
+ x = 0.5
+ if (IS_INDEFI(lineo))
+ w = smw_c1trand (cto, x)
+ else {
+ y1 = lineo
+ call smw_c2trand (cto, x, y1, w, y2)
+ }
+ if (IS_INDEFI(linei))
+ x = smw_c1trand (cti, w)
+ else {
+ #y2 = linei
+ call smw_c2trand (cti, w, y2, x, y1)
+ }
+ ofb_b = (x < xmin || x > xmax)
+ b = max (xmin, min (xmax, x)) + 1
+ do i = 1, nw {
+ ofb_a = ofb_b
+ a = b
+ x = i + 0.5
+ if (IS_INDEFI(lineo))
+ w = smw_c1trand (cto, x)
+ else {
+ y1 = lineo
+ call smw_c2trand (cto, x, y1, w, y2)
+ }
+ if (IS_INDEFI(linei))
+ x = smw_c1trand (cti, w)
+ else {
+ #y2 = linei
+ call smw_c2trand (cti, w, y2, x, y1)
+ }
+ ofb_b = (x < xmin || x > xmax)
+ b = max (xmin, min (xmax, x)) + 1
+ if (ofb_a && ofb_b) {
+ if (!IS_INDEFR(blank))
+ out[i] = blank
+ } else if (a == b) {
+ if (!IS_INDEFR(blank))
+ out[i] = blank
+ } else if (a < b) {
+ ia = nint (a + 0.5)
+ ib = nint (b - 0.5)
+ if (abs (a+0.5-ia) < 0.00001 && abs (b-0.5-ib) < 0.00001) {
+ sum = 0.
+ do j = ia, ib
+ sum = sum + asieval (asi, real(j))
+ out[i] = sum
+ } else
+ out[i] = asigrl (asi, a, b)
+ if (!flux)
+ out[i] = out[i] / max (b - a, 1e-4)
+ } else {
+ ib = nint (b + 0.5)
+ ia = nint (a - 0.5)
+ if (abs (a-0.5-ia) < 0.00001 && abs (b+0.5-ib) < 0.00001) {
+ sum = 0.
+ do j = ib, ia
+ sum = sum + asieval (asi, real(j))
+ out[i] = sum
+ } else
+ out[i] = asigrl (asi, b, a)
+ if (!flux)
+ out[i] = out[i] / max (a - b, 1e-4)
+ }
+ }
+
+ call asifree (asi)
+end
diff --git a/noao/onedspec/dispcor/mkpkg b/noao/onedspec/dispcor/mkpkg
new file mode 100644
index 00000000..e609106e
--- /dev/null
+++ b/noao/onedspec/dispcor/mkpkg
@@ -0,0 +1,28 @@
+# DISPCOR Task
+
+$checkout libpkg.a ..
+$update libpkg.a
+$checkin libpkg.a ..
+$exit
+
+libpkg.a:
+ dcio.x dispcor.h <error.h> <imhdr.h> <imset.h> <pkg/dttext.h>\
+ <smw.h> <units.h>
+ dctable.x dctable.h <imhdr.h> <mach.h> <smw.h>
+ dispcor.x <math/iminterp.h>
+ ranges.x <ctype.h> <mach.h>
+ refaverage.x refspectra.h
+ reffollow.x refspectra.h <mach.h>
+ refgspec.x refspectra.com refspectra.h <error.h>
+ refinterp.x refspectra.h <mach.h>
+ refmatch.x refspectra.h
+ refmsgs.x refspectra.com refspectra.h
+ refnearest.x refspectra.h <mach.h>
+ refnoextn.x
+ refprecede.x refspectra.h <mach.h>
+ refspectra.x refspectra.com refspectra.h
+ reftable.x refspectra.h <error.h>
+ t_dispcor.x dctable.h dispcor.h <error.h> <imhdr.h> <imio.h>\
+ <mach.h> <mwset.h> <smw.h> <units.h>
+ t_disptrans.x <error.h> <imhdr.h> <math/curfit.h> <smw.h> <units.h>
+ ;
diff --git a/noao/onedspec/dispcor/ranges.x b/noao/onedspec/dispcor/ranges.x
new file mode 100644
index 00000000..403b81f7
--- /dev/null
+++ b/noao/onedspec/dispcor/ranges.x
@@ -0,0 +1,239 @@
+include <mach.h>
+include <ctype.h>
+
+define FIRST 0 # Default starting range
+define LAST MAX_INT # Default ending range
+define STEP 1 # Default step
+define EOLIST -1 # End of list
+
+# DECODE_RANGES -- Parse a string containing a list of integer numbers or
+# ranges, delimited by either spaces or commas. Return as output a list
+# of ranges defining a list of numbers, and the count of list numbers.
+# Range limits must be positive nonnegative integers. ERR is returned as
+# the function value if a conversion error occurs. The list of ranges is
+# delimited by EOLIST.
+
+int procedure decode_ranges (range_string, ranges, max_ranges, nvalues)
+
+char range_string[ARB] # Range string to be decoded
+int ranges[3, max_ranges] # Range array
+int max_ranges # Maximum number of ranges
+int nvalues # The number of values in the ranges
+
+int ip, nrange, first, last, step, ctoi()
+
+begin
+ ip = 1
+ nvalues = 0
+
+ do nrange = 1, max_ranges - 1 {
+ # Defaults to all nonnegative integers
+ first = FIRST
+ last = LAST
+ step = STEP
+
+ # Skip delimiters
+ while (IS_WHITE(range_string[ip]) || range_string[ip] == ',')
+ ip = ip + 1
+
+ # Get first limit.
+ # Must be a number, '-', 'x', or EOS. If not return ERR.
+ if (range_string[ip] == EOS) { # end of list
+ if (nrange == 1) {
+ # Null string defaults
+ ranges[1, 1] = first
+ ranges[2, 1] = last
+ ranges[3, 1] = step
+ ranges[1, 2] = EOLIST
+ nvalues = MAX_INT
+ return (OK)
+ } else {
+ ranges[1, nrange] = EOLIST
+ return (OK)
+ }
+ } else if (range_string[ip] == '-')
+ ;
+ else if (range_string[ip] == 'x')
+ ;
+ else if (IS_DIGIT(range_string[ip])) { # ,n..
+ if (ctoi (range_string, ip, first) == 0)
+ return (ERR)
+ } else
+ return (ERR)
+
+ # Skip delimiters
+ while (IS_WHITE(range_string[ip]) || range_string[ip] == ',')
+ ip = ip + 1
+
+ # Get last limit
+ # Must be '-', or 'x' otherwise last = first.
+ if (range_string[ip] == 'x')
+ ;
+ else if (range_string[ip] == '-') {
+ ip = ip + 1
+ while (IS_WHITE(range_string[ip]) || range_string[ip] == ',')
+ ip = ip + 1
+ if (range_string[ip] == EOS)
+ ;
+ else if (IS_DIGIT(range_string[ip])) {
+ if (ctoi (range_string, ip, last) == 0)
+ return (ERR)
+ } else if (range_string[ip] == 'x')
+ ;
+ else
+ return (ERR)
+ } else
+ last = first
+
+ # Skip delimiters
+ while (IS_WHITE(range_string[ip]) || range_string[ip] == ',')
+ ip = ip + 1
+
+ # Get step.
+ # Must be 'x' or assume default step.
+ if (range_string[ip] == 'x') {
+ ip = ip + 1
+ while (IS_WHITE(range_string[ip]) || range_string[ip] == ',')
+ ip = ip + 1
+ if (range_string[ip] == EOS)
+ ;
+ else if (IS_DIGIT(range_string[ip])) {
+ if (ctoi (range_string, ip, step) == 0)
+ ;
+ } else if (range_string[ip] == '-')
+ ;
+ else
+ return (ERR)
+ }
+
+ # Output the range triple.
+ ranges[1, nrange] = first
+ ranges[2, nrange] = last
+ ranges[3, nrange] = step
+ nvalues = nvalues + abs (last-first) / step + 1
+ }
+
+ return (ERR) # ran out of space
+end
+
+
+# GET_NEXT_NUMBER -- Given a list of ranges and the current file number,
+# find and return the next file number. Selection is done in such a way
+# that list numbers are always returned in monotonically increasing order,
+# regardless of the order in which the ranges are given. Duplicate entries
+# are ignored. EOF is returned at the end of the list.
+
+int procedure get_next_number (ranges, number)
+
+int ranges[ARB] # Range array
+int number # Both input and output parameter
+
+int ip, first, last, step, next_number, remainder
+
+begin
+ # If number+1 is anywhere in the list, that is the next number,
+ # otherwise the next number is the smallest number in the list which
+ # is greater than number+1.
+
+ number = number + 1
+ next_number = MAX_INT
+
+ for (ip=1; ranges[ip] != EOLIST; ip=ip+3) {
+ first = min (ranges[ip], ranges[ip+1])
+ last = max (ranges[ip], ranges[ip+1])
+ step = ranges[ip+2]
+ if (number >= first && number <= last) {
+ remainder = mod (number - first, step)
+ if (remainder == 0)
+ return (number)
+ if (number - remainder + step <= last)
+ next_number = number - remainder + step
+ } else if (first > number)
+ next_number = min (next_number, first)
+ }
+
+ if (next_number == MAX_INT)
+ return (EOF)
+ else {
+ number = next_number
+ return (number)
+ }
+end
+
+
+# GET_PREVIOUS_NUMBER -- Given a list of ranges and the current file number,
+# find and return the previous file number. Selection is done in such a way
+# that list numbers are always returned in monotonically decreasing order,
+# regardless of the order in which the ranges are given. Duplicate entries
+# are ignored. EOF is returned at the end of the list.
+
+int procedure get_previous_number (ranges, number)
+
+int ranges[ARB] # Range array
+int number # Both input and output parameter
+
+int ip, first, last, step, next_number, remainder
+
+begin
+ # If number-1 is anywhere in the list, that is the previous number,
+ # otherwise the previous number is the largest number in the list which
+ # is less than number-1.
+
+ number = number - 1
+ next_number = 0
+
+ for (ip=1; ranges[ip] != EOLIST; ip=ip+3) {
+ first = min (ranges[ip], ranges[ip+1])
+ last = max (ranges[ip], ranges[ip+1])
+ step = ranges[ip+2]
+ if (number >= first && number <= last) {
+ remainder = mod (number - first, step)
+ if (remainder == 0)
+ return (number)
+ if (number - remainder >= first)
+ next_number = number - remainder
+ } else if (last < number) {
+ remainder = mod (last - first, step)
+ if (remainder == 0)
+ next_number = max (next_number, last)
+ else if (last - remainder >= first)
+ next_number = max (next_number, last - remainder)
+ }
+ }
+
+ if (next_number == 0)
+ return (EOF)
+ else {
+ number = next_number
+ return (number)
+ }
+end
+
+
+# IS_IN_RANGE -- Test number to see if it is in range.
+# If the number is INDEFI then it is mapped to the maximum integer.
+
+bool procedure is_in_range (ranges, number)
+
+int ranges[ARB] # Range array
+int number # Number to be tested against ranges
+
+int ip, first, last, step, num
+
+begin
+ if (IS_INDEFI (number))
+ num = MAX_INT
+ else
+ num = number
+
+ for (ip=1; ranges[ip] != EOLIST; ip=ip+3) {
+ first = min (ranges[ip], ranges[ip+1])
+ last = max (ranges[ip], ranges[ip+1])
+ step = ranges[ip+2]
+ if (num >= first && num <= last)
+ if (mod (num - first, step) == 0)
+ return (true)
+ }
+
+ return (false)
+end
diff --git a/noao/onedspec/dispcor/refaverage.x b/noao/onedspec/dispcor/refaverage.x
new file mode 100644
index 00000000..e25866c4
--- /dev/null
+++ b/noao/onedspec/dispcor/refaverage.x
@@ -0,0 +1,84 @@
+include "refspectra.h"
+
+# REFAVERAGE -- Assign reference spectrum by averageing reference list.
+# In earlier version the reference apertures were always set to all
+
+procedure refaverage (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+int ap
+double sortval
+real wt1, wt2
+pointer sp, image, ref1, ref2, gval
+
+bool refgref(), refginput()
+int imtgetim(), imtlen()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (ref1, SZ_FNAME, TY_CHAR)
+ call salloc (ref2, SZ_FNAME, TY_CHAR)
+
+ # Get reference spectra to average.
+ switch (imtlen (refs)) {
+ case 0:
+ call error (0, "No reference spectra specified")
+ case 1:
+ ap = imtgetim (refs, Memc[ref1], SZ_FNAME)
+ call refnoextn (Memc[ref1])
+ if (!refgref (Memc[ref1], ap, sortval, gval)) {
+ call sfree (sp)
+ return
+ }
+ wt1 = 1.
+ wt2 = 0.
+ case 2:
+ ap = imtgetim (refs, Memc[ref1], SZ_FNAME)
+ ap = imtgetim (refs, Memc[ref2], SZ_FNAME)
+ call refnoextn (Memc[ref1])
+ call refnoextn (Memc[ref2])
+ if (!refgref (Memc[ref1], ap, sortval, gval)) {
+ call sfree (sp)
+ return
+ }
+ if (!refgref (Memc[ref2], ap, sortval, gval)) {
+ call sfree (sp)
+ return
+ }
+ wt1 = 0.5
+ wt2 = 0.5
+ default:
+ ap = imtgetim (refs, Memc[ref1], SZ_FNAME)
+ ap = imtgetim (refs, Memc[ref2], SZ_FNAME)
+ call refnoextn (Memc[ref1])
+ call refnoextn (Memc[ref2])
+ if (!refgref (Memc[ref1], ap, sortval, gval)) {
+ call sfree (sp)
+ return
+ }
+ if (!refgref (Memc[ref2], ap, sortval, gval)) {
+ call sfree (sp)
+ return
+ }
+ wt1 = 0.5
+ wt2 = 0.5
+ call eprintf ("WARNING: Averaging only first two reference spectra")
+ }
+
+ # Assign reference spectra to each input spectrum.
+ # Skip spectra which are not of the appropriate aperture
+ # or have been assigned previously (unless overriding).
+
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+
+ call refspectra (Memc[image], Memc[ref1], wt1, Memc[ref2], wt2)
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/reffollow.x b/noao/onedspec/dispcor/reffollow.x
new file mode 100644
index 00000000..a320c504
--- /dev/null
+++ b/noao/onedspec/dispcor/reffollow.x
@@ -0,0 +1,114 @@
+include <mach.h>
+include "refspectra.h"
+
+
+# REFFOLLOW -- Assign following reference spectrum based on sort key.
+# If there is no following spectrum assign the nearest preceding spectrum.
+
+procedure reffollow (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+bool ignoreaps # Ignore apertures?
+
+int i, i1, i2, nrefs, ap
+double sortval, d, d1, d2
+pointer sp, image, gval, refimages, refaps, refvals, refgvals
+
+bool clgetb(), streq(), refginput(), refgref()
+int imtgetim(), imtlen()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+
+ # Task parameters
+ ignoreaps = clgetb ("ignoreaps")
+
+ # Tabulate reference spectra. This expands the reference list,
+ # checks the spectrum is a reference spectrum of the appropriate
+ # aperture.
+
+ call salloc (refimages, imtlen (refs), TY_INT)
+ call salloc (refaps, imtlen (refs), TY_INT)
+ call salloc (refvals, imtlen (refs), TY_DOUBLE)
+ call salloc (refgvals, imtlen (refs), TY_INT)
+ nrefs = 0
+ while (imtgetim (refs, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refgref (Memc[image], ap, sortval, gval))
+ next
+
+ for (i=0; i<nrefs; i=i+1)
+ if (streq (Memc[image], Memc[Memi[refimages+i]]))
+ break
+ if (i == nrefs) {
+ call salloc (Memi[refimages+nrefs], SZ_FNAME, TY_CHAR)
+ call salloc (Memi[refgvals+nrefs], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[refimages+i]], SZ_FNAME)
+ Memi[refaps+i] = ap
+ Memd[refvals+i] = sortval
+ call strcpy (Memc[gval], Memc[Memi[refgvals+i]], SZ_FNAME)
+ nrefs = i + 1
+ }
+ }
+ if (nrefs < 1)
+ call error (0, "No reference images specified")
+
+ # Assign following reference spectra to each input spectrum.
+ # Skip input spectra which are not of the appropriate aperture
+ # or have been assigned previously (unless overriding).
+
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+
+ i1 = 0
+ i2 = 0
+ d1 = MAX_REAL
+ d2 = -MAX_REAL
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]]))
+ next
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ next
+ d = sortval - Memd[refvals+i-1]
+ if ((d > 0.) && (d < d1)) {
+ i1 = i
+ d1 = d
+ }
+ if ((d <= 0.) && (d > d2)) {
+ i2 = i
+ d2 = d
+ }
+ }
+
+ if (i2 > 0) # Nearest following spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i2-1]], 1.,
+ Memc[Memi[refimages+i2-1]], 0.)
+ else if (i1 > 0) # Nearest preceding spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i1-1]], 1.,
+ Memc[Memi[refimages+i1-1]], 0.)
+ else { # No reference spectrum found
+ call refprint (STDERR, NO_REFSPEC, Memc[image], "", "", "",
+ ap, 0, "")
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]])) {
+ call refprint (STDERR, REF_GROUP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ call refprint (STDERR, REF_AP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/refgspec.x b/noao/onedspec/dispcor/refgspec.x
new file mode 100644
index 00000000..bb851307
--- /dev/null
+++ b/noao/onedspec/dispcor/refgspec.x
@@ -0,0 +1,268 @@
+include <error.h>
+include "refspectra.h"
+
+# REFOPEN -- Set verbose and log file descriptors and open symbol table.
+# REFCLOSE -- Close file descriptors and symbol table
+# REFGSPEC -- Get a spectrum from the symbol table. Map it only once.
+# REFGINPUT -- Get input spectrum. Apply various checks.
+# REFGREF -- Get reference spectrum. Apply various checks.
+
+define REF_LEN 6 # Length of reference structure
+define REF_SORTVAL Memd[P2D($1)] # Sort value
+define REF_AP Memi[$1+2] # Aperture number
+define REF_GVAL Memi[$1+3] # Sort value
+define REF_SPEC1 Memi[$1+4] # Offset for reference spectrum 1
+define REF_SPEC2 Memi[$1+5] # Offset for reference spectrum 2
+
+
+# REFOPEN -- Set verbose and log file descriptors and open symbol table.
+# The file descriptors and symbol table pointer are in common. A null
+# file descriptor indicates no output.
+
+procedure refopen ()
+
+bool clgetb()
+real clgetr()
+pointer rng_open(), stopen()
+int fd, btoi(), clpopnu(), clgfil(), open(), nowhite()
+errchk open()
+
+include "refspectra.com"
+
+begin
+ call malloc (sort, SZ_FNAME, TY_CHAR)
+ call malloc (group, SZ_FNAME, TY_CHAR)
+
+ # Check log files
+ logfiles = clpopnu ("logfiles")
+ while (clgfil (logfiles, Memc[sort], SZ_FNAME) != EOF) {
+ fd = open (Memc[sort], APPEND, TEXT_FILE)
+ call close (fd)
+ }
+ call clprew (logfiles)
+
+ # Get other parameters
+ call clgstr ("apertures", Memc[sort], SZ_FNAME)
+ iferr (aps = rng_open (Memc[sort], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+ call clgstr ("refaps", Memc[sort], SZ_FNAME)
+ iferr (raps = rng_open (Memc[sort], INDEF, INDEF, INDEF))
+ call error (0, "Bad reference aperture list")
+ call clgstr ("sort", Memc[sort], SZ_FNAME)
+ call clgstr ("group", Memc[group], SZ_FNAME)
+ time = btoi (clgetb ("time"))
+ timewrap = clgetr ("timewrap")
+ verbose = btoi (clgetb ("verbose"))
+
+ fd = nowhite (Memc[sort], Memc[sort], SZ_FNAME)
+ fd = nowhite (Memc[group], Memc[group], SZ_FNAME)
+
+ # Open symbol table.
+ stp = stopen ("refspectra", 10, 20, 10*SZ_FNAME)
+end
+
+
+# REFCLOSE -- Finish up
+
+procedure refclose ()
+
+include "refspectra.com"
+
+begin
+ call mfree (sort, TY_CHAR)
+ call mfree (group, TY_CHAR)
+ call clpcls (logfiles)
+ call stclose (stp)
+ call rng_close (raps)
+ call rng_close (aps)
+end
+
+
+# REFGSPEC -- Get a spectrum from the symbol table. Map it only once.
+# All access to spectra is through this routine. It returns header parameters.
+# Because the spectra may be accessed in very random order and many times
+# the information is stored in a symbol table keyed on the spectrum name.
+# The spectrum need be mapped only once! Any error from IMMAP is returned.
+
+procedure refgspec (spec, ap, sortval, gval, ref1, ref2)
+
+char spec[ARB] # Spectrum image name
+int ap # Spectrum aperture number
+double sortval # Spectrum sort value
+pointer gval # Group string
+pointer ref1 # Reference spectrum 1
+pointer ref2 # Reference spectrum 2
+
+pointer sym, stfind(), stenter(), stpstr(), strefsbuf()
+pointer im, str, immap()
+bool streq()
+int imgeti(), strlen()
+double imgetd()
+errchk immap, imgetd, imgstr
+
+include "refspectra.com"
+
+begin
+ # Check if spectrum is in the symbol table from a previous call.
+ # If not in the symbol table map the image, get the header parameters,
+ # and store them in the symbol table.
+
+ sym = stfind (stp, spec)
+ if (sym == NULL) {
+ im = immap (spec, READ_ONLY, 0)
+ iferr (ap = imgeti (im, "BEAM-NUM"))
+ ap = 1
+
+ # Failure to find a specified keyword is a fatal error.
+ iferr {
+ if (Memc[sort] == EOS || streq (Memc[sort], "none") ||
+ select == MATCH || select == AVERAGE)
+ sortval = INDEFD
+ else {
+ sortval = imgetd (im, Memc[sort])
+ if (time == YES)
+ sortval = mod (sortval + 24. - timewrap, 24.0D0)
+ }
+
+ call malloc (str, SZ_FNAME, TY_CHAR)
+ if (Memc[group] == EOS || streq (Memc[group], "none") ||
+ select == MATCH || select == AVERAGE)
+ Memc[str] = EOS
+ else
+ call imgstr (im, Memc[group], Memc[str], SZ_FNAME)
+ gval = stpstr (stp, Memc[str], strlen (Memc[str])+1)
+ } then
+ call erract (EA_FATAL)
+
+ iferr (call imgstr (im, "refspec1", Memc[str], SZ_FNAME))
+ Memc[str] = EOS
+ ref1 = stpstr (stp, Memc[str], strlen (Memc[str])+1)
+ iferr (call imgstr (im, "refspec2", Memc[str], SZ_FNAME))
+ Memc[str] = EOS
+ ref2 = stpstr (stp, Memc[str], strlen (Memc[str])+1)
+ call mfree (str, TY_CHAR)
+
+ call imunmap (im)
+
+ sym = stenter (stp, spec, REF_LEN)
+ REF_AP(sym) = ap
+ REF_SORTVAL(sym) = sortval
+ REF_GVAL(sym) = gval
+ REF_SPEC1(sym) = ref1
+ REF_SPEC2(sym) = ref2
+ }
+ ap = REF_AP(sym)
+ sortval = REF_SORTVAL(sym)
+ gval = strefsbuf (stp, REF_GVAL(sym))
+ ref1 = strefsbuf (stp, REF_SPEC1(sym))
+ ref2 = strefsbuf (stp, REF_SPEC2(sym))
+end
+
+
+# REFGINPUT -- Get input spectrum. Apply various checks.
+# This calls REFGSPEC and then checks:
+# 1. The spectrum is found.
+# 2. The spectrum has not been assigned reference spectra previously.
+# If it has then determine whether to override the assignment.
+# 3. Check if the aperture is correct.
+# Return true if the spectrum is acceptable and false if not.
+
+bool procedure refginput (spec, ap, val, gval)
+
+char spec[ARB] # Spectrum image name
+int ap # Spectrum aperture number (returned)
+double val # Spectrum sort value (returned)
+pointer gval # Spectrum group value (returned)
+
+bool clgetb(), rng_elementi()
+pointer ref1, ref2
+errchk refgspec
+
+include "refspectra.com"
+
+define err_ 99
+
+begin
+ # Get the spectrum from the symbol table.
+ iferr (call refgspec (spec, ap, val, gval, ref1, ref2)) {
+ call refmsgs (NO_SPEC, spec, "", "", "", ap, 0, "")
+ goto err_
+ }
+
+ # Check if it has a previous reference spectrum. Override if desired.
+ if (Memc[ref1] != EOS) {
+ if (!clgetb ("override")) {
+ call refmsgs (DEF_REFSPEC, spec, Memc[ref1], "", "", ap, 0,
+ Memc[ref2])
+ goto err_
+ } else {
+ call refmsgs (OVR_REFSPEC, spec, Memc[ref1], "", "", ap, 0,
+ Memc[ref2])
+ }
+ }
+
+ # Check aperture numbers.
+ if (aps != NULL) {
+ if (!rng_elementi (aps, ap)) {
+ call refmsgs (BAD_AP, spec, "", "", "", ap, 0, "")
+ goto err_
+ }
+ }
+
+ return (true)
+
+err_
+ return (false)
+end
+
+
+# REFGREF -- Get reference spectrum. Apply various checks.
+# This calls REFGSPEC and then checks:
+# 1. The spectrum is found.
+# 2. The spectrum is a reference spectrum, i.e. has an IDENTIFY
+# record. This is signaled by having a reference equivalent to
+# itself.
+# 3. Check if the aperture is correct.
+# Return true if the spectrum is acceptable and false if not.
+
+bool procedure refgref (spec, ap, val, gval)
+
+char spec[ARB] # Spectrum image name
+int ap # Spectrum aperture number (returned)
+double val # Spectrum sort value (returned)
+pointer gval # Spectrum group value (returned)
+
+bool strne(), rng_elementi()
+pointer ref1, ref2
+errchk refgspec
+
+include "refspectra.com"
+
+define err_ 99
+
+begin
+ # Get spectrum from symbol table.
+ iferr (call refgspec (spec, ap, val, gval, ref1, ref2)) {
+ call refmsgs (NO_REF, spec, "", "", "", ap, 0, "")
+ goto err_
+ }
+
+ # Check if spectrum is a reference spectrum.
+ if (strne (spec, Memc[ref1])) {
+ call refmsgs (NOT_REFSPEC, spec, "", "", "", ap, 0, "")
+ goto err_
+ }
+
+ # Check aperture numbers.
+ if (raps != NULL) {
+ if (!rng_elementi (raps, ap)) {
+ call refmsgs (BAD_REFAP, spec, "", "", "", ap, 0, "")
+ goto err_
+ }
+ }
+
+ return (true)
+
+err_
+ return (false)
+end
diff --git a/noao/onedspec/dispcor/refinterp.x b/noao/onedspec/dispcor/refinterp.x
new file mode 100644
index 00000000..b074c053
--- /dev/null
+++ b/noao/onedspec/dispcor/refinterp.x
@@ -0,0 +1,127 @@
+include <mach.h>
+include "refspectra.h"
+
+
+# REFINTERP -- Assign reference spectra to interpolate between based on sort
+# key. The nearest preceding and following spectra are assigned weights based
+# on their distance. If there is no preceding and following spectrum then
+# the nearest spectrum is assigned.
+
+procedure refinterp (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+bool ignoreaps # Ignore apertures?
+
+int i, i1, i2, nrefs, ap
+double sortval, d, d1, d2
+real wt1, wt2
+pointer sp, image, gval, refimages, refaps, refvals, refgvals
+
+bool clgetb(), streq(), refginput(), refgref()
+int imtgetim(), imtlen()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+
+ # Task parameters
+ ignoreaps = clgetb ("ignoreaps")
+
+ # Tabulate reference spectra. This expands the reference list,
+ # checks the spectrum is a reference spectrum of the appropriate
+ # aperture.
+
+ call salloc (refimages, imtlen (refs), TY_INT)
+ call salloc (refaps, imtlen (refs), TY_INT)
+ call salloc (refvals, imtlen (refs), TY_DOUBLE)
+ call salloc (refgvals, imtlen (refs), TY_INT)
+ nrefs = 0
+ while (imtgetim (refs, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refgref (Memc[image], ap, sortval, gval))
+ next
+
+ for (i=0; i<nrefs; i=i+1)
+ if (streq (Memc[Memi[refimages+i]], Memc[image]))
+ break
+ if (i == nrefs) {
+ call salloc (Memi[refimages+nrefs], SZ_FNAME, TY_CHAR)
+ call salloc (Memi[refgvals+nrefs], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[refimages+i]], SZ_FNAME)
+ Memi[refaps+i] = ap
+ Memd[refvals+i] = sortval
+ call strcpy (Memc[gval], Memc[Memi[refgvals+i]], SZ_FNAME)
+ nrefs = i + 1
+ }
+ }
+ if (nrefs < 1)
+ call error (0, "No reference images specified")
+
+
+ # Assign following reference spectra to each input spectrum.
+ # Skip input spectra which are not of the appropriate aperture
+ # or have been assigned previously (unless overriding).
+
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+
+ i1 = 0
+ i2 = 0
+ d1 = MAX_REAL
+ d2 = -MAX_REAL
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]]))
+ next
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ next
+ d = sortval - Memd[refvals+i-1]
+ if ((d >= 0.) && (d < d1)) {
+ i1 = i
+ d1 = d
+ } else if ((d <= 0.) && (d > d2)) {
+ i2 = i
+ d2 = d
+ }
+ }
+
+ if (i1 > 0 && i2 > 0) { # Weight spectra
+ if (d1 - d2 == 0.) {
+ wt1 = 0.5
+ wt2 = 0.5
+ } else {
+ wt1 = -d2 / (d1 - d2)
+ wt2 = d1 / (d1 - d2)
+ }
+ call refspectra (Memc[image], Memc[Memi[refimages+i1-1]], wt1,
+ Memc[Memi[refimages+i2-1]], wt2)
+ } else if (i1 > 0) # Nearest preceding spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i1-1]], 1.,
+ Memc[Memi[refimages+i1-1]], 0.)
+ else if (i2 > 0) # Nearest following spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i2-1]], 1.,
+ Memc[Memi[refimages+i2-1]], 0.)
+ else { # No reference spectrum found
+ call refprint (STDERR, NO_REFSPEC, Memc[image], "", "", "",
+ ap, 0, "")
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]])) {
+ call refprint (STDERR, REF_GROUP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ call refprint (STDERR, REF_AP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/refmatch.x b/noao/onedspec/dispcor/refmatch.x
new file mode 100644
index 00000000..c94a7113
--- /dev/null
+++ b/noao/onedspec/dispcor/refmatch.x
@@ -0,0 +1,43 @@
+include "refspectra.h"
+
+# REFMATCH -- Assign reference spectrum by match against reference list.
+
+procedure refmatch (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+int ap
+double sortval
+pointer sp, image, refimage, gval
+
+bool refgref(), refginput()
+int imtgetim(), imtlen()
+
+begin
+ if (imtlen (input) != imtlen (refs))
+ call error (0, "Input and reference list have different lengths")
+
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (refimage, SZ_FNAME, TY_CHAR)
+
+ # Assign reference spectra to each input spectrum.
+ # Skip spectra which are not of the appropriate aperture
+ # or have been assigned previously (unless overriding).
+
+ while ((imtgetim (input, Memc[image], SZ_FNAME) != EOF) &&
+ (imtgetim (refs, Memc[refimage], SZ_FNAME) != EOF)) {
+ call refnoextn (Memc[image])
+ call refnoextn (Memc[refimage])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+ if (!refgref (Memc[refimage], ap, sortval, gval))
+ next
+
+ call refspectra (Memc[image], Memc[refimage], 1.,
+ Memc[refimage], 0.)
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/refmsgs.x b/noao/onedspec/dispcor/refmsgs.x
new file mode 100644
index 00000000..a374088e
--- /dev/null
+++ b/noao/onedspec/dispcor/refmsgs.x
@@ -0,0 +1,108 @@
+include "refspectra.h"
+
+
+# REFMSGS -- Print any verbose messages to log files. All messages
+# except the assignments go through this procedure. It calls REFPRINT with
+# each output stream.
+
+procedure refmsgs (msg, spec, ref, gval, gvalref, ap, apref, ref2)
+
+int msg # Message code
+char spec[ARB] # Spectrum
+char ref[ARB] # Reference spectrum
+char gval[ARB] # Group value
+char gvalref[ARB] # Group value in reference
+int ap # Aperture
+int apref # Aperture in reference
+char ref2[ARB] # Reference spectrum 2
+
+int fd, clgfil(), open()
+pointer sp, logfile
+include "refspectra.com"
+
+begin
+ if (verbose == NO)
+ return
+
+ call smark (sp)
+ call salloc (logfile, SZ_FNAME, TY_CHAR)
+ while (clgfil (logfiles, Memc[logfile], SZ_FNAME) != EOF) {
+ fd = open (Memc[logfile], APPEND, TEXT_FILE)
+ call refprint (fd, msg, spec, ref, gval, gvalref, ap, apref, ref2)
+ call close (fd)
+ }
+ call clprew (logfiles)
+
+ call sfree (sp)
+end
+
+
+# REFPRINT -- Print requested message with appropriate parameters if non-null
+# stream is specified.
+
+procedure refprint (fd, msg, spec, ref, gval, gvalref, ap, apref, ref2)
+
+int fd # File descriptor
+int msg # Message code
+char spec[ARB] # Spectrum
+char ref[ARB] # Reference spectrum
+char gval[ARB] # Group value
+char gvalref[ARB] # Group value in reference
+int ap # Aperture
+int apref # Aperture in reference
+char ref2[ARB] # Reference spectrum 2
+
+include "refspectra.com"
+
+begin
+ if (fd == NULL)
+ return
+
+ switch (msg) {
+ case NO_SPEC:
+ call fprintf (fd, "[%s] Spectrum not found\n")
+ call pargstr (spec)
+ case NO_REF:
+ call fprintf (fd, "[%s] Reference spectrum not found\n")
+ call pargstr (spec)
+ case NOT_REFSPEC:
+ call fprintf (fd, "[%s] Not a reference spectrum\n")
+ call pargstr (spec)
+ case NO_REFSPEC:
+ call fprintf (fd, "[%s] No reference spectrum found\n")
+ call pargstr (spec)
+ case DEF_REFSPEC:
+ call fprintf (fd, "[%s] Reference spectra already defined: %s %s\n")
+ call pargstr (spec)
+ call pargstr (ref)
+ call pargstr (ref2)
+ case OVR_REFSPEC:
+ call fprintf (fd,
+ "[%s] Overriding previous reference spectra: %s %s\n")
+ call pargstr (spec)
+ call pargstr (ref)
+ call pargstr (ref2)
+ case BAD_AP:
+ call fprintf (fd, "[%s] Wrong aperture: %d\n")
+ call pargstr (spec)
+ call pargi (ap)
+ case BAD_REFAP:
+ call fprintf (fd, "[%s] Wrong reference aperture: %d\n")
+ call pargstr (spec)
+ call pargi (ap)
+ case REF_GROUP:
+ call fprintf (fd, "Input [%s] %s = %s : Ref [%s] %s = %s\n")
+ call pargstr (spec)
+ call pargstr (Memc[group])
+ call pargstr (gval)
+ call pargstr (ref)
+ call pargstr (Memc[group])
+ call pargstr (gvalref)
+ case REF_AP:
+ call fprintf (fd, "Input [%s] ap = %d : Ref [%s] ap = %d\n")
+ call pargstr (spec)
+ call pargi (ap)
+ call pargstr (ref)
+ call pargi (apref)
+ }
+end
diff --git a/noao/onedspec/dispcor/refnearest.x b/noao/onedspec/dispcor/refnearest.x
new file mode 100644
index 00000000..78ebb62b
--- /dev/null
+++ b/noao/onedspec/dispcor/refnearest.x
@@ -0,0 +1,104 @@
+include <mach.h>
+include "refspectra.h"
+
+
+# REFNEAREST -- Assign nearest reference spectrum based on sort key.
+
+procedure refnearest (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+bool ignoreaps # Ignore apertures?
+
+int i, i1, nrefs, ap
+double sortval, d, d1
+pointer sp, image, gval, refimages, refaps, refvals, refgvals
+
+bool clgetb(), streq(), refginput(), refgref()
+int imtgetim(), imtlen()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+
+ # Task parameters
+ ignoreaps = clgetb ("ignoreaps")
+
+ # Tabulate reference spectra. This expands the reference list,
+ # checks the spectrum is a reference spectrum of the appropriate
+ # aperture.
+
+ call salloc (refimages, imtlen (refs), TY_POINTER)
+ call salloc (refaps, imtlen (refs), TY_INT)
+ call salloc (refvals, imtlen (refs), TY_DOUBLE)
+ call salloc (refgvals, imtlen (refs), TY_POINTER)
+ nrefs = 0
+ while (imtgetim (refs, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refgref (Memc[image], ap, sortval, gval))
+ next
+
+ for (i=0; i<nrefs; i=i+1)
+ if (streq (Memc[image], Memc[Memi[refimages+i]]))
+ break
+ if (i == nrefs) {
+ call salloc (Memi[refimages+nrefs], SZ_FNAME, TY_CHAR)
+ call salloc (Memi[refgvals+nrefs], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[refimages+i]], SZ_FNAME)
+ Memi[refaps+i] = ap
+ Memd[refvals+i] = sortval
+ call strcpy (Memc[gval], Memc[Memi[refgvals+i]], SZ_FNAME)
+ nrefs = i + 1
+ }
+ }
+ if (nrefs < 1)
+ call error (0, "No reference images specified")
+
+
+ # Assign nearest reference spectra to each input spectrum.
+ # Skip input spectra which are not of the appropriate aperture
+
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+
+ i1 = 0
+ d1 = MAX_REAL
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]]))
+ next
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ next
+ d = abs (sortval - Memd[refvals+i-1])
+ if (d < d1) {
+ i1 = i
+ d1 = d
+ }
+ }
+
+ if (i1 > 0) # Assign nearest reference spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i1-1]], 1.,
+ Memc[Memi[refimages+i1-1]], 0.)
+ else { # No reference spectrum found
+ call refprint (STDERR, NO_REFSPEC, Memc[image], "", "", "",
+ ap, 0, "")
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]])) {
+ call refprint (STDERR, REF_GROUP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ call refprint (STDERR, REF_AP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/refnoextn.x b/noao/onedspec/dispcor/refnoextn.x
new file mode 100644
index 00000000..4c48b194
--- /dev/null
+++ b/noao/onedspec/dispcor/refnoextn.x
@@ -0,0 +1,29 @@
+# REFNOEXTN -- Strip any image extensions
+
+procedure refnoextn (spec)
+
+char spec[ARB] # Image name
+
+int i, strlen()
+bool streq()
+
+begin
+ i = strlen (spec)
+ call imgimage (spec, spec, i)
+
+ i = strlen (spec)
+ switch (spec[i]) {
+ case 'h':
+ if (i > 3 && spec[i-3] == '.')
+ spec[i-3] = EOS
+ case 'l':
+ if (i > 2 && streq (spec[i-2], ".pl"))
+ spec[i-2] = EOS
+ case 's':
+ if (i > 4 && streq (spec[i-4], ".fits"))
+ spec[i-4] = EOS
+ case 't':
+ if (i > 3 && streq (spec[i-3], ".fit"))
+ spec[i-3] = EOS
+ }
+end
diff --git a/noao/onedspec/dispcor/refprecede.x b/noao/onedspec/dispcor/refprecede.x
new file mode 100644
index 00000000..c2ba0467
--- /dev/null
+++ b/noao/onedspec/dispcor/refprecede.x
@@ -0,0 +1,114 @@
+include <mach.h>
+include "refspectra.h"
+
+
+# REFPRECEDE -- Assign preceding reference spectrum based on sort key.
+# If there is no preceding spectrum assign the nearest following spectrum.
+
+procedure refprecede (input, refs)
+
+pointer input # List of input spectra
+pointer refs # List of reference spectra
+
+bool ignoreaps # Ignore aperture numbers?
+
+int i, i1, i2, nrefs, ap
+double sortval, d, d1, d2
+pointer sp, image, gval, refimages, refaps, refvals, refgvals
+
+bool clgetb(), streq(), refginput(), refgref()
+int imtgetim(), imtlen()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+
+ # Task parameters
+ ignoreaps = clgetb ("ignoreaps")
+
+ # Tabulate reference spectra. This expands the reference list,
+ # checks the spectrum is a reference spectrum of the appropriate
+ # aperture.
+
+ call salloc (refimages, imtlen (refs), TY_INT)
+ call salloc (refaps, imtlen (refs), TY_INT)
+ call salloc (refvals, imtlen (refs), TY_DOUBLE)
+ call salloc (refgvals, imtlen (refs), TY_INT)
+ nrefs = 0
+ while (imtgetim (refs, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refgref (Memc[image], ap, sortval, gval))
+ next
+
+ for (i=0; i<nrefs; i=i+1)
+ if (streq (Memc[image], Memc[Memi[refimages+i]]))
+ break
+ if (i == nrefs) {
+ call salloc (Memi[refimages+nrefs], SZ_FNAME, TY_CHAR)
+ call salloc (Memi[refgvals+nrefs], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[refimages+i]], SZ_FNAME)
+ Memi[refaps+i] = ap
+ Memd[refvals+i] = sortval
+ call strcpy (Memc[gval], Memc[Memi[refgvals+i]], SZ_FNAME)
+ nrefs = i + 1
+ }
+ }
+ if (nrefs < 1)
+ call error (0, "No reference images specified")
+
+ # Assign preceding reference spectra to each input spectrum.
+ # Skip input spectra which are not of the appropriate aperture
+ # or have been assigned previously (unless overriding).
+
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ if (!refginput (Memc[image], ap, sortval, gval))
+ next
+
+ i1 = 0
+ i2 = 0
+ d1 = MAX_REAL
+ d2 = -MAX_REAL
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]]))
+ next
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ next
+ d = sortval - Memd[refvals+i-1]
+ if ((d >= 0.) && (d < d1)) {
+ i1 = i
+ d1 = d
+ }
+ if ((d < 0.) && (d < d2)) {
+ i2 = i
+ d2 = d
+ }
+ }
+
+ if (i1 > 0) # Nearest preceding spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i1-1]], 1.,
+ Memc[Memi[refimages+i1-1]], 0.)
+ else if (i2 > 0) # Nearest following spectrum
+ call refspectra (Memc[image], Memc[Memi[refimages+i2-1]], 1.,
+ Memc[Memi[refimages+i2-1]], 0.)
+ else { # No reference spectrum found
+ call refprint (STDERR, NO_REFSPEC, Memc[image], "", "", "",
+ ap, 0, "")
+ do i = 1, nrefs {
+ if (!streq (Memc[gval], Memc[Memi[refgvals+i-1]])) {
+ call refprint (STDERR, REF_GROUP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ if (!ignoreaps && ap != Memi[refaps+i-1])
+ call refprint (STDERR, REF_AP, Memc[image],
+ Memc[Memi[refimages+i-1]], Memc[gval],
+ Memc[Memi[refgvals+i-1]], ap, Memi[refaps+i-1], "")
+ next
+ }
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/refspectra.com b/noao/onedspec/dispcor/refspectra.com
new file mode 100644
index 00000000..7c5dc622
--- /dev/null
+++ b/noao/onedspec/dispcor/refspectra.com
@@ -0,0 +1,15 @@
+# Common parameters for logging and the spectrum symbol table.
+
+pointer aps # Pointer to aperture list
+pointer raps # Pointer to reference aperture list
+pointer sort # Pointer to sort keyword
+pointer group # Pointer to group keyword
+int select # Selection type
+int time # Is sort keyword a time?
+real timewrap # Timewrap parameter
+int verbose # Verbose output?
+int logfiles # List of log files
+pointer stp # Symbol table for previously mapped spectra
+
+common /refcom/ aps, raps, sort, group, select, time, timewrap, verbose,
+ logfiles, stp
diff --git a/noao/onedspec/dispcor/refspectra.h b/noao/onedspec/dispcor/refspectra.h
new file mode 100644
index 00000000..c7ba397a
--- /dev/null
+++ b/noao/onedspec/dispcor/refspectra.h
@@ -0,0 +1,30 @@
+# Selection method keywords and codes.
+
+define SELECT "|match|nearest|preceding|following|interp|average|"
+define MATCH 1 # Match input and reference lists
+define NEAREST 2 # Nearest reference
+define PRECEDING 3 # Preceding reference
+define FOLLOWING 4 # Following reference
+define INTERP 5 # Interpolate between nearest references
+define AVERAGE 6 # Average first two reference spectra
+
+# Reference list types.
+
+define LIST 1 # References are an image list
+define TABLE 2 # Referenece are a table
+
+# Maximum number of aperture ranges.
+define NRANGES 100
+
+# Message codes (see procedure refprint)
+
+define NO_SPEC 1 # Spectrum not found (immap failed)
+define NO_REF 2 # Reference spectrum not found (immap failed)
+define NOT_REFSPEC 3 # Not a reference spectrum
+define NO_REFSPEC 4 # No reference spectrum found
+define DEF_REFSPEC 5 # Reference spectra already defined
+define OVR_REFSPEC 6 # Override reference spectra
+define BAD_AP 7 # Bad aperture
+define BAD_REFAP 8 # Bad reference aperture
+define REF_GROUP 9 # Group
+define REF_AP 10 # Aperture
diff --git a/noao/onedspec/dispcor/refspectra.x b/noao/onedspec/dispcor/refspectra.x
new file mode 100644
index 00000000..57a18e43
--- /dev/null
+++ b/noao/onedspec/dispcor/refspectra.x
@@ -0,0 +1,186 @@
+include "refspectra.h"
+
+
+# T_REFSPECTRA -- Assign reference spectra.
+# Reference spectra are assigned to input spectra from a specified list of
+# reference spectra with various criteria. This procedure only gets some
+# of the task parameters and switches to separate procedures for each
+# implemented assignment method. The reference spectra may be specified by
+# and image list or a lookup table. The difference is determined by attempting
+# to map the first reference element in the list as an image.
+
+procedure t_refspectra ()
+
+pointer input # List of input images
+pointer refs # List of reference images
+#int select # Selection method for reference spectra
+int type # Type of reference specification
+
+int clgwrd(), imtgetim()
+pointer sp, ref, im, imtopenp(), immap()
+errchk immap
+
+include "refspectra.com"
+
+begin
+ call smark (sp)
+ call salloc (ref, SZ_LINE, TY_CHAR)
+
+ # Get input and reference spectra lists. Determine selection method.
+ input = imtopenp ("input")
+ call clgstr ("records", Memc[ref], SZ_LINE)
+ call odr_openp (input, Memc[ref])
+ refs = imtopenp ("references")
+ select = clgwrd ("select", Memc[ref], SZ_FNAME, SELECT)
+
+ # Determine if reference list is a table.
+ if (imtgetim (refs, Memc[ref], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[ref])
+ iferr {
+ im = immap (Memc[ref], READ_ONLY, 0)
+ call imunmap (im)
+ type = LIST
+ } then
+ type = TABLE
+ } else
+ call error (0, "No reference spectra specified")
+ call imtrew (refs)
+
+ # Initialize confirm flag, symbol table and logging streams.
+ call refconfirm1 ()
+ call refopen ()
+
+ # Switch of reference list type and selection method.
+ if (type == LIST) {
+ switch (select) {
+ case MATCH:
+ call refmatch(input, refs)
+ case NEAREST:
+ call refnearest (input, refs)
+ case PRECEDING:
+ call refprecede (input, refs)
+ case FOLLOWING:
+ call reffollow (input, refs)
+ case INTERP:
+ call refinterp (input, refs)
+ case AVERAGE:
+ call refaverage (input, refs)
+ }
+ } else
+ call reftable (input, Memc[ref], select)
+
+ call refclose ()
+ call imtclose (input)
+ call imtclose (refs)
+ call sfree (sp)
+end
+
+
+# REFSPECTRA -- Confirm and set reference spectra in header.
+# 1. Confirm assignments if desired.
+# 2. Log output to logfiles if desired.
+# 3. Update assignment if desired.
+# Note that if wt1 > 0.995 then only the first reference spectrum is
+# set with no weight specified. No weight implies no interpolation.
+
+procedure refspectra (image, ref1, wt1, ref2, wt2)
+
+char image[ARB] # Spectrum image name
+char ref1[ARB] # Reference spectrum image name
+real wt1 # Weight
+char ref2[ARB] # Reference spectrum image name
+real wt2 # Weight
+bool confirm # Confirm assignments?
+
+int fd, clgfil(), open(), clgwrd()
+bool clgetb(), streq()
+pointer im, sp, str, immap()
+errchk immap
+
+include "refspectra.com"
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Confirm assignments.
+ if (confirm) {
+ if (wt1 < 0.995) {
+ call printf ("[%s] refspec1='%s %.8g'\n")
+ call pargstr (image)
+ call pargstr (ref1)
+ call pargr (wt1)
+ call printf ("[%s] refspec2='%s %.8g' ")
+ call pargstr (image)
+ call pargstr (ref2)
+ call pargr (wt2)
+ } else {
+ call printf ("[%s] refspec1='%s' ")
+ call pargstr (image)
+ call pargstr (ref1)
+ }
+ call flush (STDOUT)
+ fd = clgwrd ("answer", Memc[str], SZ_LINE, "|no|yes|YES|")
+ switch (fd) {
+ case 1:
+ call sfree (sp)
+ return
+ case 3:
+ confirm = false
+ }
+ }
+
+ # Log output.
+ while (clgfil (logfiles, Memc[str], SZ_LINE) != EOF) {
+ if (streq (Memc[str], "STDOUT") && confirm)
+ next
+ fd = open (Memc[str], APPEND, TEXT_FILE)
+ if (wt1 < 0.995) {
+ call fprintf (fd, "[%s] refspec1='%s %.8g'\n")
+ call pargstr (image)
+ call pargstr (ref1)
+ call pargr (wt1)
+ call fprintf (fd, "[%s] refspec2='%s %.8g'\n")
+ call pargstr (image)
+ call pargstr (ref2)
+ call pargr (wt2)
+ } else {
+ call fprintf (fd, "[%s] refspec1='%s'\n")
+ call pargstr (image)
+ call pargstr (ref1)
+ }
+ call close (fd)
+ }
+ call clprew (logfiles)
+
+ # If updating the assigments map the spectrum READ_WRITE and set
+ # the keywords REFSPEC1 and REFSPEC2. REFSPEC2 is not set if not
+ # interpolating.
+
+ if (clgetb ("assign")) {
+ im = immap (image, READ_WRITE, 0)
+ if (wt1 < 0.9999995D0) {
+ call sprintf (Memc[str], SZ_LINE, "%s %.8g")
+ call pargstr (ref1)
+ call pargr (wt1)
+ call imastr (im, "refspec1", Memc[str])
+ call sprintf (Memc[str], SZ_LINE, "%s %.8g")
+ call pargstr (ref2)
+ call pargr (wt2)
+ call imastr (im, "refspec2", Memc[str])
+ } else {
+ call imastr (im, "refspec1", ref1)
+ iferr (call imdelf (im, "refspec2"))
+ ;
+ }
+ call imunmap (im)
+ }
+
+ call sfree (sp)
+ return
+
+entry refconfirm1 ()
+
+ confirm = clgetb ("confirm")
+
+end
diff --git a/noao/onedspec/dispcor/reftable.x b/noao/onedspec/dispcor/reftable.x
new file mode 100644
index 00000000..abdf1f4a
--- /dev/null
+++ b/noao/onedspec/dispcor/reftable.x
@@ -0,0 +1,109 @@
+include <error.h>
+include "refspectra.h"
+
+
+# REFTABLE -- For each input image select reference spectrum list from a table.
+# The table is read from the file and stored in a simple symbol table.
+#
+# The table consists of pairs of words. The first word is a list of spectra
+# and the second word is the reference spectrum list to be used for each
+# spectrum in the first list. Note that the first list is not an input
+# list. As a convenience if a reference list is missing the preceding list
+# is implied. Some examples follow.
+#
+# spec1 spec2,spec3,spec4
+# spec5
+# spec6,spec7 spect8,spec9
+# spec10 spec11
+# spec12 spec13
+# spec14 spec15
+
+procedure reftable (list, table, select)
+
+pointer list # List of input spectra
+char table[ARB] # Reference table
+int select # Selection method
+
+int i, fd, input, refs
+pointer stp, sym
+pointer sp, image, ref1, ref2
+
+pointer stopen(), strefsbuf(), stenter(), stpstr(), stfind(), imtopen()
+int imtgetim(), open(), fscan(), nscan()
+errchk open
+
+begin
+ # Read the table. Return an error if the file can't be opened.
+ # Read each table entry of spectrum list and reference list.
+ # Expand the input list to make a symbol table keyed on the
+ # spectrum with the reference list string as it's value.
+ # As a convenience if a reference list is missing the preceding
+ # list is implied.
+
+ fd = open (table, READ_ONLY, TEXT_FILE)
+
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (ref1, SZ_FNAME, TY_CHAR)
+ call salloc (ref2, SZ_FNAME, TY_CHAR)
+
+ stp = stopen ("table", 10, 10, 20*SZ_FNAME)
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[image], SZ_FNAME)
+ call gargwrd (Memc[ref1], SZ_FNAME)
+ if (nscan() < 1)
+ next
+ if (nscan() < 2)
+ call strcpy (Memc[ref2], Memc[ref1], SZ_FNAME)
+ else
+ call strcpy (Memc[ref1], Memc[ref2], SZ_FNAME)
+
+ i = stpstr (stp, Memc[ref1], SZ_FNAME)
+
+ input = imtopen (Memc[image])
+ while (imtgetim (input, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ sym = stenter (stp, Memc[image], 1)
+ Memi[sym] = i
+ }
+ call imtclose (input)
+ }
+ call close (fd)
+
+ # For each input spectrum find the appropriate reference spectrum list.
+ # If no list is found print a message and continue. Switch on the
+ # selection method.
+
+ while (imtgetim (list, Memc[image], SZ_FNAME) != EOF) {
+ call refnoextn (Memc[image])
+ sym = stfind (stp, Memc[image])
+ if (sym == NULL) {
+ call refmsgs (NO_REFSPEC, Memc[image], "", "", "", 0, 0, "")
+ next
+ }
+
+ input = imtopen (Memc[image])
+ refs = imtopen (Memc[strefsbuf (stp, Memi[sym])])
+
+ switch (select) {
+ case MATCH:
+ call refmatch(input, refs)
+ case NEAREST:
+ call refnearest (input, refs)
+ case PRECEDING:
+ call refprecede (input, refs)
+ case FOLLOWING:
+ call reffollow (input, refs)
+ case INTERP:
+ call refinterp (input, refs)
+ case AVERAGE:
+ call refaverage (input, refs)
+ }
+
+ call imtclose (input)
+ call imtclose (refs)
+ }
+
+ call stclose (stp)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/t_dispcor.x b/noao/onedspec/dispcor/t_dispcor.x
new file mode 100644
index 00000000..94aeba44
--- /dev/null
+++ b/noao/onedspec/dispcor/t_dispcor.x
@@ -0,0 +1,1336 @@
+include <error.h>
+include <imhdr.h>
+include <imio.h>
+include <mach.h>
+include <mwset.h>
+include "dispcor.h"
+include "dctable.h"
+include <smw.h>
+include <units.h>
+
+# Dispersion types.
+define MULTISPEC 1
+define ECHELLE 2
+
+
+# T_DISPCOR -- Dispersion correct spectra.
+
+procedure t_dispcor ()
+
+int in # List of input spectra
+int out # List of output spectra
+bool linearize # Linearize spectra?
+bool log # Log scale?
+bool flux # Conserve flux?
+real blank # Blank value
+int ignoreaps # Ignore aperture numbers?
+int fd1 # Log file descriptor
+int fd2 # Log file descriptor
+
+int i, format, naps
+int open(), nowhite(), imtopenp(), imtgetim(), errcode(), btoi()
+pointer sp, input, output, str, err, stp, table
+pointer im, im1, smw, smw1, ap, immap(), smw_openim()
+bool clgetb()
+real clgetr()
+errchk open, immap, smw_openim, dc_gms, dc_gec, dc_multispec, dc_echelle
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (err, SZ_LINE, TY_CHAR)
+
+ # Task parameters
+ in = imtopenp ("input")
+ out = imtopenp ("output")
+ call clgstr ("records", Memc[str], SZ_LINE)
+ call odr_openp (in, Memc[str])
+ call odr_openp (out, Memc[str])
+ call clgstr ("database", Memc[str], SZ_FNAME)
+ call clgstr ("logfile", Memc[err], SZ_LINE)
+ linearize = clgetb ("linearize")
+ ignoreaps = btoi (clgetb ("ignoreaps"))
+
+ # Initialize the database cacheing and wavelength table.
+ call dc_open (stp, Memc[str])
+ if (linearize) {
+ log = clgetb ("log")
+ flux = clgetb ("flux")
+ blank = clgetr ("blank")
+
+ call dc_table (table, naps)
+ if (clgetb ("global")) {
+ if (clgetb ("samedisp"))
+ call dc_global1 (in, stp, log, table, naps)
+ else
+ call dc_global (in, stp, log, table, naps)
+ }
+ }
+
+ # Open logfile if specified.
+ if (clgetb ("verbose"))
+ fd1 = STDOUT
+ if (nowhite (Memc[err], Memc[err], SZ_LINE) != 0)
+ fd2 = open (Memc[err], APPEND, TEXT_FILE)
+ else
+ fd2 = NULL
+
+ # Loop through each input image. Do the dispersion correction
+ # in place if no output spectrum list is given or if the input
+ # and output spectra names are the same.
+
+ while (imtgetim (in, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (out, Memc[output], SZ_FNAME) == EOF)
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ iferr {
+ im = NULL; im1 = NULL
+ smw = NULL; smw1 = NULL
+ ap = NULL
+
+ i = immap (Memc[input], READ_ONLY, 0); im = i
+ i = smw_openim (im); smw = i
+
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ # Use first line for reference.
+ switch (SMW_LDIM(smw)) {
+ case 1:
+ call strcpy (Memc[input], Memc[str], SZ_LINE)
+ case 2:
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (Memc[str], SZ_LINE, "%s[*,1]")
+ call pargstr (Memc[input])
+ case 2:
+ call sprintf (Memc[str], SZ_LINE, "%s[1,*]")
+ call pargstr (Memc[input])
+ }
+ case 3:
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (Memc[str], SZ_LINE, "%s[*,1,1]")
+ call pargstr (Memc[input])
+ case 2:
+ call sprintf (Memc[str], SZ_LINE, "%s[1,*,1]")
+ call pargstr (Memc[input])
+ case 3:
+ call sprintf (Memc[str], SZ_LINE, "%s[*,1,1]")
+ call pargstr (Memc[input])
+ }
+ }
+ im1 = immap (Memc[str], READ_ONLY, 0)
+ smw1 = smw_openim (im1)
+ call smw_ndes (im1, smw1)
+ if (SMW_PDIM(smw1) == 1)
+ call smw_esms (smw1)
+
+ call dc_gms (Memc[input], im1, smw1, stp, YES, ap, fd1, fd2)
+ call dc_ndspec (im, smw, smw1, ap, Memc[input],
+ Memc[output], linearize, log, flux, blank, table, naps,
+ fd1, fd2)
+ default:
+ # Get dispersion functions. Determine type of dispersion
+ # by the error return.
+
+ format = MULTISPEC
+ iferr (call dc_gms (Memc[input], im, smw, stp, ignoreaps,
+ ap, fd1, fd2)) {
+ if (errcode() > 1 && errcode() < 100)
+ call erract (EA_ERROR)
+ format = ECHELLE
+ iferr (call dc_gec (Memc[input], im, smw, stp, ap,
+ fd1, fd2)) {
+ if (errcode() > 1 && errcode() < 100)
+ call erract (EA_ERROR)
+ call erract (EA_WARN)
+ iferr (call dc_gms (Memc[input], im, smw, stp,
+ ignoreaps, ap, fd1, fd2))
+ call erract (EA_WARN)
+ call sprintf (Memc[err], SZ_LINE,
+ "%s: Dispersion data not found")
+ call pargstr (Memc[input])
+ call error (1, Memc[err])
+ }
+ }
+
+ switch (format) {
+ case MULTISPEC:
+ call dc_multispec (im, smw, ap, Memc[input],
+ Memc[output], linearize, log, flux, blank, table,
+ naps, fd1, fd2)
+ case ECHELLE:
+ call dc_echelle (im, smw, ap, Memc[input],
+ Memc[output], linearize, log, flux, blank, table,
+ naps, fd1, fd2)
+ }
+ }
+ } then
+ call erract (EA_WARN)
+
+ if (ap != NULL)
+ call mfree (ap, TY_STRUCT)
+ if (smw1 != NULL)
+ call smw_close (smw1)
+ if (im1 != NULL)
+ call imunmap (im1)
+ if (smw != NULL)
+ call smw_close (smw)
+ if (im != NULL)
+ call imunmap (im)
+ }
+
+ # Finish up.
+ if (linearize)
+ do i = 0, naps
+ call mfree (Memi[table+i], TY_STRUCT)
+ call mfree (table, TY_INT)
+ call dc_close (stp)
+ call imtclose (in)
+ call imtclose (out)
+ if (fd1 != NULL)
+ call close (fd1)
+ if (fd2 != NULL)
+ call close (fd2)
+ call sfree (sp)
+end
+
+
+# DC_NDSPEC -- Dispersion correct N-dimensional spectrum.
+
+procedure dc_ndspec (in, smw, smw1, ap, input, output, linearize, log, flux,
+ blank, table, naps, fd1, fd2)
+
+pointer in # Input IMIO pointer
+pointer smw # SMW pointer
+pointer smw1 # SMW pointer
+pointer ap # Aperture pointer
+char input[ARB] # Input multispec spectrum
+char output[ARB] # Output root name
+bool linearize # Linearize?
+bool log # Log wavelength parameters?
+bool flux # Conserve flux?
+real blank # Blank value
+pointer table # Wavelength table
+int naps # Number of apertures
+int fd1 # Log file descriptor
+int fd2 # Log file descriptor
+
+int i, j, nin, ndim, dispaxis, n1, n2, n3
+pointer sp, temp, str, out, mwout, cti, cto, indata, outdata
+pointer immap(), imgs3r(), imps3r(), mw_open(), smw_sctran()
+bool clgetb(), streq()
+errchk immap, mw_open, smw_open, dispcor, imgs3r, imps3r
+
+begin
+ # Determine the wavelength parameters.
+ call dc_wavelengths (in, ap, output, log, table, naps, 1,
+ DC_AP(ap,1), DC_W1(ap,1), DC_W2(ap,1), DC_DW(ap,1), DC_NW(ap,1))
+ DC_Z(ap,1) = 0.
+ if (log)
+ DC_DT(ap,1) = 1
+ else
+ DC_DT(ap,1) = 0
+
+ call dc_log (fd1, output, ap, 1, log)
+ call dc_log (fd2, output, ap, 1, log)
+
+ if (clgetb ("listonly"))
+ return
+
+ call smark (sp)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Open output image. Use temp. image if output is the same as input.
+ if (streq (input, output)) {
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ out = immap (Memc[temp], NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ } else {
+ out = immap (output, NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ }
+
+ # Set dimensions.
+ ndim = SMW_LDIM(smw)
+ dispaxis = SMW_LAXIS(smw,1)
+ n1 = DC_NW(ap,1)
+ n2 = SMW_LLEN(smw,2)
+ n3 = SMW_LLEN(smw,3)
+ nin = IM_LEN(in,dispaxis)
+ IM_LEN(out,dispaxis) = n1
+
+ # Set WCS header.
+ mwout = mw_open (NULL, ndim)
+ call mw_newsystem (mwout, "world", ndim)
+ do i = 1, ndim
+ call mw_swtype (mwout, i, 1, "linear", "")
+ if (UN_LABEL(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, dispaxis, "label", UN_LABEL(DC_UN(ap,1)))
+ if (UN_UNITS(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, dispaxis, "units", UN_UNITS(DC_UN(ap,1)))
+ call smw_open (mwout, NULL, out)
+ call smw_swattrs (mwout, INDEFI, INDEFI, INDEFI, INDEFI, DC_DT(ap,1),
+ DC_W1(ap,1), DC_DW(ap,1), DC_NW(ap,1), DC_Z(ap,1), INDEFR, INDEFR,
+ "")
+
+ # Set WCS transformations.
+ cti = smw_sctran (smw1, "world", "logical", 3)
+ switch (dispaxis) {
+ case 1:
+ cto = smw_sctran (mwout, "logical", "world", 1)
+ case 2:
+ cto = smw_sctran (mwout, "logical", "world", 2)
+ case 3:
+ cto = smw_sctran (mwout, "logical", "world", 4)
+ }
+
+ # Dispersion correct.
+ do j = 1, n3 {
+ do i = 1, n2 {
+ switch (dispaxis) {
+ case 1:
+ indata = imgs3r (in, 1, nin, i, i, j, j)
+ outdata = imps3r (out, 1, n1, i, i, j, j)
+ case 2:
+ indata = imgs3r (in, i, i, 1, nin, j, j)
+ outdata = imps3r (out, i, i, 1, n1, j, j)
+ case 3:
+ indata = imgs3r (in, i, i, j, j, 1, nin)
+ outdata = imps3r (out, i, i, j, j, 1, n1)
+ }
+
+ call aclrr (Memr[outdata], n1)
+ call dispcora (cti, 1, cto, INDEFI, Memr[indata], nin,
+ Memr[outdata], n1, flux, blank)
+ }
+ }
+
+ # Save REFSPEC keywords if present.
+ call dc_refspec (out)
+
+ # Finish up. Replace input by output if needed.
+ call smw_ctfree (cti)
+ call smw_ctfree (cto)
+ call smw_saveim (mwout, out)
+ call smw_close (mwout)
+ call imunmap (out)
+ call imunmap (in)
+ if (streq (input, output)) {
+ call imdelete (input)
+ call imrename (Memc[temp], output)
+ }
+
+ call sfree (sp)
+end
+
+
+# DC_MULTISPEC -- Linearize multispec apertures into an MULTISPEC format
+# spectrum. The number of pixels in each image line is the maximum
+# required to contain the longest spectrum.
+
+procedure dc_multispec (in, smw, ap, input, output, linearize, log, flux,
+ blank, table, naps, fd1, fd2)
+
+pointer in # Input IMIO pointer
+pointer smw # SMW pointer
+pointer ap # Aperture pointer
+char input[ARB] # Input multispec spectrum
+char output[ARB] # Output root name
+bool linearize # Linearize?
+bool log # Log wavelength parameters?
+bool flux # Conserve flux?
+real blank # Blank value
+pointer table # Wavelength table
+int naps # Number of apertures
+int fd1 # Log file descriptor
+int fd2 # Log file descriptor
+
+int i, j, nc, nl, nb, axis[2]
+pointer sp, temp, str, out, mwout, cti, cto, indata, outdata
+pointer immap(), imgl3r(), impl3r()
+pointer mw_open(), smw_sctran()
+bool clgetb(), streq()
+errchk immap, mw_open, smw_open, dispcor, imgl3r, impl3r
+
+data axis/1,2/
+
+begin
+ # Determine the wavelength parameters for each aperture.
+ # The options are to have all apertures have the same dispersion
+ # or have each aperture have independent dispersion. The global
+ # parameters have already been calculated if needed.
+
+ nc = IM_LEN(in,1)
+ nl = IM_LEN(in,2)
+ nb = IM_LEN(in,3)
+
+ if (linearize) {
+ if (log)
+ DC_DT(ap,1) = 1
+ else
+ DC_DT(ap,1) = 0
+ if (clgetb ("samedisp")) {
+ call dc_wavelengths1 (in, smw, ap, output, log, table, naps,
+ DC_W1(ap,1), DC_W2(ap,1), DC_DW(ap,1), DC_NW(ap,1))
+ if ((DC_DW(ap,1)*(DC_W2(ap,1)-DC_W1(ap,1)) <= 0.) ||
+ (DC_NW(ap,1) < 1))
+ call error (1, "Error in wavelength scale")
+ do i = 2, nl {
+ DC_W1(ap,i) = DC_W1(ap,1)
+ DC_W2(ap,i) = DC_W2(ap,1)
+ DC_DW(ap,i) = DC_DW(ap,1)
+ DC_NW(ap,i) = DC_NW(ap,1)
+ DC_Z(ap,i) = 0.
+ DC_DT(ap,i) = DC_DT(ap,1)
+ }
+ } else {
+ do i = 1, nl {
+ call dc_wavelengths (in, ap, output, log, table, naps, i,
+ DC_AP(ap,i), DC_W1(ap,i), DC_W2(ap,i), DC_DW(ap,i),
+ DC_NW(ap,i))
+ DC_Z(ap,i) = 0.
+ DC_DT(ap,i) = DC_DT(ap,1)
+ }
+ }
+ }
+ call dc_log (fd1, output, ap, nl, log)
+ call dc_log (fd2, output, ap, nl, log)
+
+ if (clgetb ("listonly"))
+ return
+
+ call smark (sp)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Use a temporary image if the output has the same name as the input.
+ if (streq (input, output)) {
+ if (linearize) {
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ out = immap (Memc[temp], NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ } else {
+ call imunmap (in)
+ i = immap (input, READ_WRITE, 0)
+ in = i
+ out = i
+ }
+ } else {
+ out = immap (output, NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ }
+
+ # Set MWCS or linearize
+ if (!linearize) {
+ if (out != in)
+ do j = 1, nb
+ do i = 1, nl
+ call amovr (Memr[imgl3r(in,i,j)], Memr[impl3r(out,i,j)],
+ IM_LEN(in,1))
+ call smw_saveim (smw, out)
+ } else {
+ if (nb > 1)
+ i = 3
+ else
+ i = 2
+ mwout = mw_open (NULL, i)
+ call mw_newsystem (mwout, "multispec", i)
+ call mw_swtype (mwout, axis, 2, "multispec", "")
+ if (UN_LABEL(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, 1, "label", UN_LABEL(DC_UN(ap,1)))
+ if (UN_UNITS(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, 1, "units", UN_UNITS(DC_UN(ap,1)))
+ if (i == 3)
+ call mw_swtype (mwout, 3, 1, "linear", "")
+ call smw_open (mwout, NULL, out)
+ do i = 1, nl {
+ call smw_swattrs (mwout, i, 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i),
+ DC_Z(ap,i), DC_LW(ap,i), DC_UP(ap,i), "")
+ call smw_gapid (smw, i, 1, Memc[str], SZ_LINE)
+ call smw_sapid (mwout, i, 1, Memc[str])
+ }
+
+ IM_LEN(out,1) = DC_NW(ap,1)
+ do i = 2, nl
+ IM_LEN(out,1) = max (DC_NW(ap,i), IM_LEN(out,1))
+ cti = smw_sctran (smw, "world", "logical", 3)
+ cto = smw_sctran (mwout, "logical", "world", 3)
+ do j = 1, nb {
+ do i = 1, nl {
+ indata = imgl3r (in, i, j)
+ outdata = impl3r (out, i, j)
+ call aclrr (Memr[outdata], IM_LEN(out,1))
+ call dispcora (cti, i, cto, i, Memr[indata], nc,
+ Memr[outdata], DC_NW(ap,i), flux, blank)
+ if (DC_NW(ap,i) < IM_LEN(out,1))
+ call amovkr (Memr[outdata+DC_NW(ap,i)-1],
+ Memr[outdata+DC_NW(ap,i)],IM_LEN(out,1)-DC_NW(ap,i))
+ }
+ }
+ call smw_ctfree (cti)
+ call smw_ctfree (cto)
+ call smw_saveim (mwout, out)
+ call smw_close (mwout)
+ }
+
+ # Save REFSPEC keywords if present.
+ call dc_refspec (out)
+
+ # Finish up. Replace input by output if needed.
+ if (out == in) {
+ call imunmap (in)
+ } else {
+ call imunmap (in)
+ call imunmap (out)
+ if (streq (input, output)) {
+ call imdelete (input)
+ call imrename (Memc[temp], output)
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# DC_ECHELLE -- Linearize echelle orders into an ECHELLE format
+# spectrum. The number of pixels in each image line is the maximum
+# required to contain the longest spectrum.
+
+procedure dc_echelle (in, smw, ap, input, output, linearize, log, flux,
+ blank, table, naps, fd1, fd2)
+
+pointer in # IMIO pointer
+pointer smw # SMW pointers
+pointer ap # Aperture pointer
+char input[ARB] # Input multispec spectrum
+char output[ARB] # Output root name
+bool linearize # Linearize?
+bool log # Log wavelength parameters?
+bool flux # Conserve flux?
+real blank # Blank value
+pointer table # Wavelength table
+int naps # Number of apertures
+int fd1 # Log file descriptor
+int fd2 # Log file descriptor
+
+int i, j, nc, nl, nb, axis[2]
+pointer sp, temp, str, out, mwout, cti, cto, indata, outdata
+pointer immap(), imgl3r(), impl3r()
+pointer mw_open(), smw_sctran()
+bool clgetb(), streq()
+errchk immap, mw_open, smw_open, dispcor, imgl3r, impl3r
+
+data axis/1,2/
+
+begin
+ # Determine the wavelength parameters for each aperture.
+
+ nc = IM_LEN(in,1)
+ nl = IM_LEN(in,2)
+ nb = IM_LEN(in,3)
+
+ if (linearize) {
+ if (log)
+ DC_DT(ap,1) = 1
+ else
+ DC_DT(ap,1) = 0
+ do i = 1, nl {
+ call dc_wavelengths (in, ap, output, log, table, naps,
+ i, DC_AP(ap,i), DC_W1(ap,i), DC_W2(ap,i), DC_DW(ap,i),
+ DC_NW(ap,i))
+ DC_Z(ap,i) = 0.
+ DC_DT(ap,i) = DC_DT(ap,1)
+ }
+ }
+ call dc_log (fd1, output, ap, nl, log)
+ call dc_log (fd2, output, ap, nl, log)
+
+ if (clgetb ("listonly"))
+ return
+
+ call smark (sp)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Use a temporary image if the output has the same name as the input.
+ if (streq (input, output)) {
+ if (linearize) {
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ out = immap (Memc[temp], NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ } else {
+ call imunmap (in)
+ i = immap (input, READ_WRITE, 0)
+ in = i
+ out = i
+ }
+ } else {
+ out = immap (output, NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ }
+
+ # Set MWCS or linearize
+ if (!linearize) {
+ if (out != in)
+ do j = 1, nb
+ do i = 1, nl
+ call amovr (Memr[imgl3r(in,i,j)], Memr[impl3r(out,i,j)],
+ IM_LEN(in,1))
+ call smw_saveim (smw, out)
+ } else {
+ if (nb > 1)
+ i = 3
+ else
+ i = 2
+ mwout = mw_open (NULL, i)
+ call mw_newsystem (mwout, "multispec", i)
+ call mw_swtype (mwout, axis, 2, "multispec", "")
+ if (UN_LABEL(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, 1, "label", UN_LABEL(DC_UN(ap,1)))
+ if (UN_UNITS(DC_UN(ap,1)) != EOS)
+ call mw_swattrs (mwout, 1, "units", UN_UNITS(DC_UN(ap,1)))
+ if (i == 3)
+ call mw_swtype (mwout, 3, 1, "linear", "")
+ call smw_open (mwout, NULL, out)
+ do i = 1, nl {
+ call smw_swattrs (mwout, i, 1, DC_AP(ap,i), DC_BM(ap,i),
+ DC_DT(ap,i), DC_W1(ap,i), DC_DW(ap,i), DC_NW(ap,i),
+ DC_Z(ap,i), DC_LW(ap,i), DC_UP(ap,i), "")
+ call smw_gapid (smw, i, 1, Memc[str], SZ_LINE)
+ call smw_sapid (mwout, i, 1, Memc[str])
+ }
+
+ IM_LEN(out,1) = DC_NW(ap,1)
+ do i = 2, nl
+ IM_LEN(out,1) = max (DC_NW(ap,i), IM_LEN(out,1))
+ cti = smw_sctran (smw, "world", "logical", 3)
+ cto = smw_sctran (mwout, "logical", "world", 3)
+ do j = 1, nb {
+ do i = 1, nl {
+ indata = imgl3r (in, i, j)
+ outdata = impl3r (out, i, j)
+ call aclrr (Memr[outdata], IM_LEN(out,1))
+ call dispcora (cti, i, cto, i, Memr[indata], nc,
+ Memr[outdata], DC_NW(ap,i), flux, blank)
+ if (DC_NW(ap,i) < IM_LEN(out,1))
+ call amovkr (Memr[outdata+DC_NW(ap,i)-1],
+ Memr[outdata+DC_NW(ap,i)],IM_LEN(out,1)-DC_NW(ap,i))
+ }
+ }
+ call smw_ctfree (cti)
+ call smw_ctfree (cto)
+ call smw_saveim (mwout, out)
+ call smw_close (mwout)
+ }
+
+ # Save REFSPEC keywords if present.
+ call dc_refspec (out)
+
+ # Finish up. Replace input by output if needed.
+ if (out == in) {
+ call imunmap (in)
+ } else {
+ call imunmap (in)
+ call imunmap (out)
+ if (streq (input, output)) {
+ call imdelete (input)
+ call imrename (Memc[temp], output)
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# DC_GLOBAL1 -- Set global wavelength parameters using the minimum and
+# maximum wavelengths and and the minimum dispersion over all apertures.
+
+procedure dc_global1 (in, stp, log, table, naps)
+
+pointer in # Input list
+pointer stp # Symbol table
+bool log # Logarithmic scale?
+pointer table # Wavelength table
+int naps # Number of apertures
+
+int i, nwmax, imtgetim()
+double w1, w2, dw, wmin, wmax, dwmin
+pointer sp, input, str, im, mw, ap, tbl, immap(), smw_openim()
+errchk dc_gms, dc_gec, smw_openim
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Go through all the reference spectra and determine the
+ # minimum and maximum wavelengths and maximum number of pixels.
+ # If there is no entry in the wavelength table add it.
+
+ wmin = MAX_REAL
+ wmax = -MAX_REAL
+ dwmin = MAX_REAL
+
+ while (imtgetim (in, Memc[input], SZ_FNAME) != EOF) {
+ iferr (im = immap (Memc[input], READ_ONLY, 0))
+ next
+ mw = smw_openim (im)
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ nwmax = SMW_NW(mw)
+ dw = SMW_DW(mw)
+ w1 = SMW_W1(mw)
+ w2 = w1 + dw * (nwmax - 1)
+ wmin = min (wmin, w1, w2)
+ wmax = max (wmax, w1, w2)
+ dwmin = min (dwmin, abs (dw))
+ default:
+ iferr {
+ iferr (call dc_gms (Memc[input], im, mw, stp, NO, ap,
+ NULL, NULL)) {
+ iferr (call dc_gec (Memc[input], im, mw, stp, ap,
+ NULL, NULL)) {
+ call sprintf (Memc[str], SZ_LINE,
+ "%s: Dispersion data not found")
+ call pargstr (Memc[input])
+ call error (1, Memc[str])
+ }
+ }
+
+ do i = 1, IM_LEN(im,2) {
+ w1 = DC_W1(ap,i)
+ w2 = DC_W2(ap,i)
+ dw = DC_DW(ap,i)
+ wmin = min (wmin, w1, w2)
+ wmax = max (wmax, w1, w2)
+ dwmin = min (dwmin, abs (dw))
+ }
+ } then
+ ;
+ }
+
+ call mfree (ap, TY_STRUCT)
+ call smw_close (mw)
+ call imunmap (im)
+ }
+ call imtrew (in)
+
+ nwmax = (wmax - wmin) / dwmin + 1.5
+
+ # Enter the global entry in the first table entry.
+ tbl = Memi[table]
+ call dc_defaults (wmin, wmax, nwmax,
+ TBL_W1(tbl), TBL_W2(tbl), TBL_DW(tbl), TBL_NW(tbl))
+
+ call sfree (sp)
+end
+
+
+# DC_GLOBAL -- Set global wavelength parameters. This is done for each
+# aperture separately. The wavelength table may be used to specify separate
+# fixed parameters for each aperture.
+
+procedure dc_global (in, stp, log, table, naps)
+
+pointer in # Input list
+pointer stp # Symbol table
+bool log # Logarithmic scale?
+pointer table # Wavelength table
+int naps # Number of apertures
+
+int i, j, nw, imtgetim()
+double w1, w2, dw
+pointer sp, input, str, im, mw, ap, tbl, immap(), smw_openim()
+errchk dc_gms, dc_gec, smw_openim
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Go through all the reference spectra and determine the
+ # minimum and maximum wavelengths and maximum number of pixels.
+ # Do this by aperture. If there is no entry in the wavelength
+ # table add it.
+
+ while (imtgetim (in, Memc[input], SZ_FNAME) != EOF) {
+ iferr (im = immap (Memc[input], READ_ONLY, 0))
+ next
+ mw = smw_openim (im)
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ tbl = Memi[table]
+ nw = SMW_NW(mw)
+ dw = SMW_DW(mw)
+ w1 = SMW_W1(mw)
+ w2 = w1 + dw * (nw - 1)
+ TBL_WMIN(tbl) = min (TBL_WMIN(tbl), w1, w2)
+ TBL_WMAX(tbl) = max (TBL_WMAX(tbl), w1, w2)
+ TBL_NWMAX(tbl) = max (TBL_NWMAX(tbl), nw)
+ default:
+ iferr {
+ iferr (call dc_gms (Memc[input], im, mw, stp, NO, ap,
+ NULL, NULL)) {
+ iferr (call dc_gec (Memc[input], im, mw, stp, ap,
+ NULL, NULL)) {
+ call sprintf (Memc[str], SZ_LINE,
+ "%s: Dispersion data not found")
+ call pargstr (Memc[input])
+ call error (1, Memc[str])
+ }
+ }
+
+ do i = 1, IM_LEN(im,2) {
+ call dc_getentry (false, DC_AP(ap,i), table, naps, j)
+ tbl = Memi[table+j]
+
+ nw = DC_NW(ap,i)
+ w1 = DC_W1(ap,i)
+ w2 = DC_W2(ap,i)
+ TBL_WMIN(tbl) = min (TBL_WMIN(tbl), w1, w2)
+ TBL_WMAX(tbl) = max (TBL_WMAX(tbl), w1, w2)
+ TBL_NWMAX(tbl) = max (TBL_NWMAX(tbl), nw)
+ }
+ } then
+ ;
+ }
+
+ call mfree (ap, TY_STRUCT)
+ call smw_close (mw)
+ call imunmap (im)
+ }
+ call imtrew (in)
+
+ do i = 0, naps {
+ tbl = Memi[table+i]
+ call dc_defaults (TBL_WMIN(tbl), TBL_WMAX(tbl), TBL_NWMAX(tbl),
+ TBL_W1(tbl), TBL_W2(tbl), TBL_DW(tbl), TBL_NW(tbl))
+ }
+
+ call sfree (sp)
+end
+
+
+# DC_WAVELENGTHS1 -- Set output wavelength parameters for a spectrum.
+# Fill in any INDEF values using the limits of the dispersion function
+# over all apertures and the minimum dispersion over all apertures. The
+# user may then confirm and change the wavelength parameters if desired.
+
+procedure dc_wavelengths1 (im, smw, ap, output, log, table, naps, w1, w2, dw,nw)
+
+pointer im # IMIO pointer
+pointer smw # SMW pointer
+pointer ap # Aperture structure
+char output[ARB] # Output image name
+bool log # Logarithm wavelength parameters?
+pointer table # Wavelength table
+int naps # Number of apertures
+double w1, w2, dw # Image wavelength parameters
+int nw # Image wavelength parameter
+
+int i, n, nwt, clgeti(), clgwrd()
+double a, b, c, w1t, w2t, dwt, y1, y2, dy, clgetd()
+pointer sp, key, str, tbl
+bool clgetb()
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Get aperture parameters.
+ tbl = Memi[table]
+ w1t = TBL_W1(tbl)
+ w2t = TBL_W2(tbl)
+ dwt = TBL_DW(tbl)
+ nwt = TBL_NW(tbl)
+
+ # If there are undefined wavelength scale parameters get
+ # defaults based on the reference spectrum.
+
+ if (IS_INDEFD(w1t)||IS_INDEFD(w2t)||IS_INDEFD(dwt)||IS_INDEFD(nwt)) {
+ a = MAX_REAL
+ b = -MAX_REAL
+ c = MAX_REAL
+
+ do i = 1, IM_LEN(im,2) {
+ n = DC_NW(ap,i)
+ y1 = DC_W1(ap,i)
+ y2 = DC_W2(ap,i)
+ dy = DC_DW(ap,i)
+ a = min (a, y1, y2)
+ b = max (b, y1, y2)
+ c = min (c, dy)
+ }
+ n = (b - a) / c + 1.5
+ }
+
+ call dc_defaults (a, b, n, w1t, w2t, dwt, nwt)
+ w1 = w1t
+ w2 = w2t
+ dw = dwt
+ nw = nwt
+
+ # Print the wavelength scale and allow the user to confirm and
+ # change the wavelength scale. A test is done to check which
+ # parameters the user changes and give them priority in filling
+ # in the remaining parameters.
+
+ if (TBL_CONFIRM(tbl) == YES) {
+ repeat {
+ call printf ("%s: w1 = %g, w2 = %g, dw = %g, nw = %d\n")
+ call pargstr (output)
+ call pargd (w1)
+ call pargd (w2)
+ call pargd (dw)
+ call pargi (nw)
+
+ i = clgwrd ("dispcor1.change", Memc[str],SZ_LINE, "|yes|no|NO|")
+ switch (i) {
+ case 2:
+ break
+ case 3:
+ TBL_CONFIRM(tbl) = NO
+ break
+ }
+ call clputd ("dispcor1.w1", w1)
+ call clputd ("dispcor1.w2", w2)
+ call clputd ("dispcor1.dw", dw)
+ call clputi ("dispcor1.nw", nw)
+ a = w1
+ b = w2
+ c = dw
+ n = nw
+ w1 = clgetd ("dispcor1.w1")
+ w2 = clgetd ("dispcor1.w2")
+ dw = clgetd ("dispcor1.dw")
+ nw = clgeti ("dispcor1.nw")
+
+ # If no INDEF's set unchanged parameters to INDEF.
+ i = 0
+ if (IS_INDEFD(w1))
+ i = i + 1
+ if (IS_INDEFD(w2))
+ i = i + 1
+ if (IS_INDEFD(dw))
+ i = i + 1
+ if (IS_INDEFI(nw))
+ i = i + 1
+ if (i == 0) {
+ if (w1 == a)
+ w1 = INDEFD
+ if (w2 == b)
+ w2 = INDEFD
+ if (dw == c)
+ dw = INDEFD
+ if (nw == n)
+ nw = INDEFI
+ }
+
+ call dc_defaults (a, b, n, w1, w2, dw, nw)
+
+ if (clgetb ("global")) {
+ TBL_W1(tbl) = w1
+ TBL_W2(tbl) = w2
+ TBL_DW(tbl) = dw
+ TBL_NW(tbl) = nw
+ }
+ }
+ }
+ call sfree (sp)
+end
+
+
+# DC_WAVELENGTHS -- Set output wavelength parameters for a spectrum for
+# each aperture. The fixed parameters are given in the wavelength table.
+# If there is no entry in the table for an aperture use the global
+# default (entry 0). Fill in INDEF values using the limits and number
+# of pixels for the aperture. The user may then confirm and change the
+# wavelength parameters if desired.
+
+procedure dc_wavelengths (im, ap, output, log, table, naps, line, apnum,
+ w1, w2, dw, nw)
+
+pointer im # IMIO pointer
+pointer ap # Aperture structure
+char output[ARB] # Output image name
+bool log # Logarithm wavelength parameters?
+pointer table # Wavelength table
+int naps # Number of apertures
+int line # Line
+int apnum # Aperture number
+double w1, w2, dw # Image wavelength parameters
+int nw # Image wavelength parameter
+
+int i, n, nwt, clgeti(), clgwrd()
+double a, b, c, w1t, w2t, dwt, clgetd()
+pointer sp, str, tbl
+bool clgetb()
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Get aperture parameters.
+ call dc_getentry (false, apnum, table, naps, i)
+ tbl = Memi[table+i]
+
+ w1t = TBL_W1(tbl)
+ w2t = TBL_W2(tbl)
+ dwt = TBL_DW(tbl)
+ nwt = TBL_NW(tbl)
+
+ # If there are undefined wavelength scale parameters get
+ # defaults based on the reference spectrum.
+
+ if (IS_INDEFD(w1t)||IS_INDEFD(w2t)||IS_INDEFD(dwt)||IS_INDEFI(nwt)) {
+ a = DC_W1(ap,line)
+ b = DC_W2(ap,line)
+ n = DC_NW(ap,line)
+ }
+
+ call dc_defaults (a, b, n, w1t, w2t, dwt, nwt)
+ w1 = w1t
+ w2 = w2t
+ dw = dwt
+ nw = nwt
+
+ # Print the wavelength scale and allow the user to confirm and
+ # change the wavelength scale. A test is done to check which
+ # parameters the user changes and give them priority in filling
+ # in the remaining parameters.
+
+ if (TBL_CONFIRM(tbl) == YES) {
+ repeat {
+ call printf (
+ "%s: ap = %d, w1 = %g, w2 = %g, dw = %g, nw = %d\n")
+ call pargstr (output)
+ call pargi (apnum)
+ call pargd (w1)
+ call pargd (w2)
+ call pargd (dw)
+ call pargi (nw)
+ i = clgwrd ("dispcor1.change", Memc[str],SZ_LINE, "|yes|no|NO|")
+ switch (i) {
+ case 2:
+ break
+ case 3:
+ TBL_CONFIRM(tbl) = NO
+ break
+ }
+ call clputd ("dispcor1.w1", w1)
+ call clputd ("dispcor1.w2", w2)
+ call clputd ("dispcor1.dw", dw)
+ call clputi ("dispcor1.nw", nw)
+ a = w1
+ b = w2
+ c = dw
+ n = nw
+ w1 = clgetd ("dispcor1.w1")
+ w2 = clgetd ("dispcor1.w2")
+ dw = clgetd ("dispcor1.dw")
+ nw = clgeti ("dispcor1.nw")
+
+ # If no INDEF's set unchanged parameters to INDEF.
+ i = 0
+ if (IS_INDEFD(w1))
+ i = i + 1
+ if (IS_INDEFD(w2))
+ i = i + 1
+ if (IS_INDEFD(dw))
+ i = i + 1
+ if (IS_INDEFI(nw))
+ i = i + 1
+ if (i == 0) {
+ if (w1 == a)
+ w1 = INDEFD
+ if (w2 == b)
+ w2 = INDEFD
+ if (dw == c)
+ dw = INDEFD
+ if (nw == n)
+ nw = INDEFI
+ }
+
+ call dc_defaults (a, b, n, w1, w2, dw, nw)
+
+ if (clgetb ("global")) {
+ TBL_W1(tbl) = w1
+ TBL_W2(tbl) = w2
+ TBL_DW(tbl) = dw
+ TBL_NW(tbl) = nw
+ }
+ }
+ }
+ call sfree (sp)
+end
+
+
+# DC_DEFAULTS -- Given some set of wavelength scale with others undefined
+# (INDEF) plus some defaults fill in the undefined parameters and make
+# the wavelength scale consistent. The logic of this task is complex
+# and is meant to provide an "intelligent" result based on what users
+# want.
+
+procedure dc_defaults (a, b, n, w1, w2, dw, nw)
+
+double a # Default wavelength endpoint
+double b # Default wavelength endpoint
+int n # Default number of pixels
+double w1 # Starting wavelength
+double w2 # Ending wavelength
+double dw # Wavelength interval
+int nw # Number of pixels
+
+int nindef
+
+begin
+ # Determine how many input parameters are specfied.
+ nindef = 0
+ if (IS_INDEFD(w1))
+ nindef = nindef + 1
+ if (IS_INDEFD(w2))
+ nindef = nindef + 1
+ if (IS_INDEFD(dw))
+ nindef = nindef + 1
+ if (IS_INDEFI(nw))
+ nindef = nindef + 1
+
+ # Depending on how many parameters are specified fill in the
+ # INDEF parameters.
+
+ switch (nindef) {
+ case 0:
+ # All parameters specified. First round NW to be consistent with
+ # w1, w2, and dw. Then adjust w2 to nearest pixel. It is possible
+ # that nw will be negative. Checks for this should be made by the
+ # call in program.
+
+ nw = (w2 - w1) / dw + 1.5
+ w2 = w1 + dw * (nw - 1)
+ case 1:
+ # Find the unspecified parameter and compute it from the other
+ # three specified parameters. For nw need to adjust w2 to
+ # agree with a pixel.
+
+ if (IS_INDEFD(w1))
+ w1 = w2 - dw * (nw - 1)
+ if (IS_INDEFD(w2))
+ w2 = w1 + dw * (nw - 1)
+ if (IS_INDEFD(dw))
+ dw = (w2 - w1) / (nw - 1)
+ if (IS_INDEFI(nw)) {
+ nw = (w2 - w1) / dw + 1.5
+ w2 = w1 + dw * (nw - 1)
+ }
+ case 2:
+ # Fill in two unspecified parameters using the defaults.
+ # This is tricky.
+
+ if (IS_INDEFD(dw)) {
+ if (IS_INDEFD(w1)) {
+ if (abs (w2 - a) > abs (w2 - b))
+ w1 = a
+ else
+ w1 = b
+ dw = (w2 - w1) / (nw - 1)
+ } else if (IS_INDEFD(w2)) {
+ if (abs (w1 - a) > abs (w1 - b))
+ w2 = a
+ else
+ w2 = b
+ dw = (w2 - w1) / (nw - 1)
+ } else {
+ dw = (b - a) / n
+ nw = abs ((w2 - w1) / dw) + 1.5
+ dw = (w2 - w1) / (nw - 1)
+ }
+ } else if (IS_INDEFI(nw)) {
+ if (IS_INDEFD(w1)) {
+ if (dw > 0.)
+ w1 = min (a, b)
+ else
+ w1 = max (a, b)
+ nw = (w2 - w1) / dw + 1.5
+ w1 = w2 - dw * (nw - 1)
+ } else {
+ if (dw > 0.)
+ w2 = max (a, b)
+ else
+ w2 = min (a, b)
+ nw = (w2 - w1) / dw + 1.5
+ w2 = w1 + dw * (nw - 1)
+ }
+ } else {
+ if (dw > 0.)
+ w1 = min (a, b)
+ else
+ w1 = max (a, b)
+ w2 = w1 + dw * (nw - 1)
+ }
+ case 3:
+ # Find the one specfied parameter and compute the others using
+ # the supplied defaults.
+
+ if (!IS_INDEFD(w1)) {
+ if (abs (w1 - a) > abs (w1 - b))
+ w2 = a
+ else
+ w2 = b
+ dw = (b - a) / n
+ nw = abs ((w2 - w1) / dw) + 1.5
+ dw = (w2 - w1) / (nw - 1)
+ } else if (!IS_INDEFD(w2)) {
+ if (abs (w2 - a) > abs (w2 - b))
+ w1 = a
+ else
+ w1 = b
+ dw = (b - a) / n
+ nw = abs ((w2 - w1) / dw) + 1.5
+ dw = (w2 - w1) / (nw - 1)
+ } else if (!IS_INDEFI(nw)) {
+ w1 = min (a, b)
+ w2 = max (a, b)
+ dw = (w2 - w1) / (nw - 1)
+ } else if (dw < 0.) {
+ w1 = max (a, b)
+ w2 = min (a, b)
+ nw = (w2 - w1) / dw + 1.5
+ w2 = w1 + dw * (nw - 1)
+ } else {
+ w1 = min (a, b)
+ w2 = max (a, b)
+ nw = (w2 - w1) / dw + 1.5
+ w2 = w1 + dw * (nw - 1)
+ }
+ case 4:
+ # Given only defaults compute a wavelength scale. The dispersion
+ # is kept close to the default.
+ w1 = min (a, b)
+ w2 = max (a, b)
+ dw = (b - a) / (n - 1)
+ nw = abs ((w2 - w1) / dw) + 1.5
+ dw = (w2 - w1) / (nw - 1)
+ }
+end
+
+
+# DC_LOG -- Print log of wavlength paramters
+
+procedure dc_log (fd, output, ap, naps, log)
+
+int fd # Output file descriptor
+char output[ARB] # Output image name
+pointer ap # Aperture structure
+int naps # Number of apertures
+bool log # Log dispersion?
+
+int i
+
+begin
+ if (fd == NULL)
+ return
+
+ for (i=2; i<=naps; i=i+1) {
+ if (DC_W1(ap,i) != DC_W1(ap,1))
+ break
+ if (DC_W2(ap,i) != DC_W2(ap,1))
+ break
+ if (DC_DW(ap,i) != DC_DW(ap,1))
+ break
+ if (DC_NW(ap,i) != DC_NW(ap,1))
+ break
+ }
+
+ if (naps == 1 || i <= naps) {
+ do i = 1, naps {
+ call fprintf (fd,
+ "%s: ap = %d, w1 = %8g, w2 = %8g, dw = %8g, nw = %d")
+ call pargstr (output)
+ call pargi (DC_AP(ap,i))
+ call pargd (DC_W1(ap,i))
+ call pargd (DC_W2(ap,i))
+ call pargd (DC_DW(ap,i))
+ call pargi (DC_NW(ap,i))
+ if (log) {
+ call fprintf (fd, ", log = %b")
+ call pargb (log)
+ }
+ call fprintf (fd, "\n")
+ }
+ } else {
+ call fprintf (fd,
+ "%s: w1 = %8g, w2 = %8g, dw = %8g, nw = %d")
+ call pargstr (output)
+ call pargd (DC_W1(ap,1))
+ call pargd (DC_W2(ap,1))
+ call pargd (DC_DW(ap,1))
+ call pargi (DC_NW(ap,1))
+ if (log) {
+ call fprintf (fd, ", log = %b")
+ call pargb (log)
+ }
+ call fprintf (fd, "\n")
+ }
+ call flush (fd)
+end
+
+
+# DC_REFSPEC -- Save REFSPEC keywords in DCLOG keywords.
+
+procedure dc_refspec (im)
+
+pointer im #U IMIO pointer
+
+int i, j, imaccf()
+pointer sp, dckey, dcstr, refkey, refstr
+
+begin
+ call smark (sp)
+ call salloc (dckey, SZ_FNAME, TY_CHAR)
+ call salloc (dcstr, SZ_LINE, TY_CHAR)
+ call salloc (refkey, SZ_FNAME, TY_CHAR)
+ call salloc (refstr, SZ_LINE, TY_CHAR)
+
+ for (i=1;; i=i+1) {
+ call sprintf (Memc[dckey], SZ_FNAME, "DCLOG%d")
+ call pargi (i)
+ if (imaccf (im, Memc[dckey]) == NO)
+ break
+ }
+
+ do j = 1, 4 {
+ if (j == 1)
+ call strcpy ("REFSPEC1", Memc[refkey], SZ_FNAME)
+ else if (j == 2)
+ call strcpy ("REFSPEC2", Memc[refkey], SZ_FNAME)
+ else if (j == 3)
+ call strcpy ("REFSHFT1", Memc[refkey], SZ_FNAME)
+ else if (j == 4)
+ call strcpy ("REFSHFT2", Memc[refkey], SZ_FNAME)
+
+ ifnoerr (call imgstr (im, Memc[refkey], Memc[refstr], SZ_LINE)) {
+ call sprintf (Memc[dckey], SZ_FNAME, "DCLOG%d")
+ call pargi (i)
+ call sprintf (Memc[dcstr], SZ_LINE, "%s = %s")
+ call pargstr (Memc[refkey])
+ call pargstr (Memc[refstr])
+ call imastr (im, Memc[dckey], Memc[dcstr])
+ call imdelf (im, Memc[refkey])
+ i = i + 1
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor/t_disptrans.x b/noao/onedspec/dispcor/t_disptrans.x
new file mode 100644
index 00000000..ee108472
--- /dev/null
+++ b/noao/onedspec/dispcor/t_disptrans.x
@@ -0,0 +1,413 @@
+include <error.h>
+include <imhdr.h>
+include <math/curfit.h>
+include <smw.h>
+include <units.h>
+
+define AIRVAC "|none|air2vac|vac2air|"
+define NONE 1 # No correction
+define AIR2VAC 2 # Correct air to vacuum
+define VAC2AIR 3 # Correct vacuum to air
+
+
+# T_DISPTRANS -- Tranform dispersion systems and apply air-vac conversion.
+# This task uses the UNITS package to convert the input dispersion
+# coordinates to the desired output coordinates. An air to vacuum or
+# vacuum to air correction is made. Since the input and output units
+# may not be linearly related and the MWCS supports only polynomial
+# representations a cubic splines are fit to the desired output coordinates
+# until an error tolerance is reached. The user may then select to
+# store the new WCS as either the spline approximation or to linearize
+# the coordinates by resampling the data. Note that if the input and
+# output units ARE linearly related and there is no air/vacuum conversion
+# then linearization or storing of a nonlinear dispersion function is
+# skipped. The operations are done in double precision.
+
+procedure t_disptrans ()
+
+int inlist # List of input spectra
+int outlist # List of output spectra
+pointer units # Output dispersion units
+double maxerr # Maximum error (in pixels)
+bool linearize # Linearize ouptut dispersion?
+bool verbose # Verbose?
+
+int air # Air-vacuum conversion?
+double t # Temperture in degrees C
+double p # Pressure in mmHg
+double f # Water vapour pressure in mmHg
+
+int i, j, n, nw, format, dtype, dtype1, axis[2]
+double err, w1, dw
+pointer ptr, in, out, mwin, mwout, ctin, ctout, sh, cv, inbuf, outbuf
+pointer sp, input, output, title, coeff, x, y, w, nx
+
+bool clgetb(), streq()
+int clgwrd(), imtopenp(), imtgetim()
+double clgetd(), shdr_lw(), dcveval()
+pointer immap(), smw_openim(), smw_sctran(), mw_open(), imgl3r(), impl3r()
+errchk immap, impl3r
+errchk smw_openim, smw_gwattrs, shdr_open, mw_open
+errchk dt_airvac, dt_cvfit, dt_setwcs, dispcor
+
+data axis/1,2/
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (units, SZ_FNAME, TY_CHAR)
+ call salloc (title, SZ_LINE, TY_CHAR)
+ coeff = NULL
+
+ # Parameters
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ call clgstr ("units", Memc[units], SZ_FNAME)
+ maxerr = clgetd ("error")
+ linearize = clgetb ("linearize")
+ verbose = clgetb ("verbose")
+ air = clgwrd ("air", Memc[input], SZ_FNAME, AIRVAC)
+ t = clgetd ("t")
+ p = clgetd ("p")
+ f = clgetd ("f")
+
+ # Loop over input images.
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ iferr {
+ in = NULL
+ out = NULL
+ mwin = NULL
+ mwout = NULL
+ ctin = NULL
+ ctout = NULL
+ sh = NULL
+ cv = NULL
+
+ # Open input and output images and wcs.
+ if (streq (Memc[input], Memc[output]))
+ ptr = immap (Memc[input], READ_WRITE, 0)
+ else
+ ptr = immap (Memc[input], READ_ONLY, 0)
+ in = ptr
+
+ if (streq (Memc[input], Memc[output]))
+ ptr = in
+ else
+ ptr = immap (Memc[output], NEW_COPY, in)
+ out = ptr
+
+ ptr = smw_openim (in); mwin = ptr
+ format = SMW_FORMAT(mwin)
+ switch (format) {
+ case SMW_ND:
+ call error (1,
+ "DISPTRANS does not apply to 2D and 3D images")
+ case SMW_ES:
+ call smw_esms (mwin)
+ }
+
+ if (IM_NDIM(out) == 3 && IM_LEN(out,3) == 1)
+ IM_NDIM(out) = 2
+ i = max (2, IM_NDIM(out))
+ ptr = mw_open (NULL, i); mwout = ptr
+ call mw_newsystem (mwout, "multispec", i)
+ call mw_swtype (mwout, axis, 2, "multispec", "")
+ if (i == 3)
+ call mw_swtype (mwout, 3, 1, "linear", "")
+ call smw_open (mwout, NULL, out)
+
+ # Allocate and set arrays.
+ call malloc (x, SMW_LLEN(mwin,1), TY_DOUBLE)
+ call malloc (y, SMW_LLEN(mwin,1), TY_DOUBLE)
+ call malloc (w, SMW_LLEN(mwin,1), TY_DOUBLE)
+ call malloc (nx, SMW_NSPEC(mwin), TY_INT)
+ do i = 1, SMW_LLEN(mwin,1)
+ Memd[x+i-1] = i
+
+ # Set the output MWCS dispersion function.
+ # Only compute new coordinates once if possible.
+
+ dtype = DCLINEAR
+ do i = 1, SMW_NSPEC(mwin) {
+ if (format == SMW_MS || i == 1) {
+ call shdr_open (in, mwin, i, 1, INDEFI, SHDATA, sh)
+ call shdr_units (sh, Memc[units])
+ n = SN(sh)
+ do j = 1, n
+ Memd[y+j-1] = shdr_lw (sh, Memd[x+j-1])
+ call dt_airvac (sh, Memd[y], n, air, t, p, f)
+
+ # Fit dispersion function.
+ dtype1 = DCLINEAR
+ call dt_cvfit (cv, CHEBYSHEV, 2, Memd[x], Memd[y],
+ Memd[w], n, err)
+ if (err > maxerr) {
+ dtype1 = DCFUNC
+ do j = 1, n-4 {
+ call dt_cvfit (cv, SPLINE3, j, Memd[x],
+ Memd[y], Memd[w], n, err)
+ if (err <= maxerr)
+ break
+ }
+ }
+
+ w1 = dcveval (cv, 1D0)
+ dw = (dcveval (cv, double(n)) - w1) / (n - 1)
+ }
+ if (linearize) {
+ call dt_setwcs (cv, mwin, mwin, i, dtype1, w1, dw)
+ call dt_setwcs (cv, mwin, mwout, i, DCLINEAR, w1, dw)
+ if (dtype1 != DCLINEAR)
+ dtype = dtype1
+ } else
+ call dt_setwcs (cv, mwin, mwout, i, dtype1, w1, dw)
+ Memi[nx+i-1] = n
+ }
+ call dcvfree (cv)
+
+ # Set label and units. The check on unit class is done
+ # so that if not a velocity the dictionary expansion
+ # unit is used. However for velocity the units do not
+ # include the reference coordinate so the user string
+ # is used.
+
+ call mw_swattrs (SMW_MW(mwout,0), 1, "label", LABEL(sh))
+ if (UN_CLASS(UN(sh)) != UN_VEL) {
+ call mw_swattrs (SMW_MW(mwout,0), 1, "units", UNITS(sh))
+ call mw_swattrs (SMW_MW(mwout,0), 1, "units_display",
+ UNITS(sh))
+ } else {
+ call mw_swattrs (SMW_MW(mwout,0), 1, "units", Memc[units])
+ call mw_swattrs (SMW_MW(mwout,0), 1, "units_display",
+ Memc[units])
+ }
+
+ # Linearize or copy the pixels as requested.
+ if (linearize && dtype != DCLINEAR) {
+ ptr = smw_sctran (mwin, "world", "logical", 3); ctin = ptr
+ ptr = smw_sctran (mwout, "logical", "world", 3); ctout = ptr
+ n = IM_LEN(in,1)
+ do j = 1, IM_LEN(out,3) {
+ do i = 1, IM_LEN(out,2) {
+ nw = Memi[nx+i-1]
+ inbuf = imgl3r (in, i, j)
+ outbuf = impl3r (out, i, j)
+ call dispcor (ctin, i, ctout, i, Memr[inbuf], n,
+ Memr[outbuf], nw, NO)
+ if (nw < n)
+ call amovkr (Memr[outbuf+nw-1], Memr[outbuf+nw],
+ n-nw)
+ }
+ }
+ call smw_ctfree (ctin)
+ call smw_ctfree (ctout)
+ } else if (in != out) {
+ n = IM_LEN(in,1)
+ do j = 1, IM_LEN(out,3) {
+ do i = 1, IM_LEN(out,2) {
+ inbuf = imgl3r (in, i, j)
+ outbuf = impl3r (out, i, j)
+ call amovr (Memr[inbuf], Memr[outbuf], n)
+ }
+ }
+ }
+
+ # Verbose output
+ if (verbose) {
+ call printf ("%s: Dispersion transformed to %s")
+ call pargstr (Memc[output])
+ call pargstr (UNITS(sh))
+ switch (air) {
+ case 1:
+ call printf (".\n")
+ case 2:
+ call printf (" in vacuum with\n")
+ call printf (
+ " t = %.4g C, p = %.6g mmHg, f = %.4g mmHg.\n")
+ call pargd (t)
+ call pargd (p)
+ call pargd (f)
+ case 3:
+ call printf (" in air with\n")
+ call printf (
+ " t = %.4g C, p = %.6g mmHg, f = %.4g mmHg.\n")
+ call pargd (t)
+ call pargd (p)
+ call pargd (f)
+ }
+ call flush (STDOUT)
+ }
+
+ } then {
+ if (out != NULL && out != in) {
+ call imunmap (out)
+ call imdelete (Memc[output])
+ }
+ call erract (EA_WARN)
+ }
+
+ # Finish up.
+ call mfree (x, TY_DOUBLE)
+ call mfree (y, TY_DOUBLE)
+ call mfree (w, TY_DOUBLE)
+ call mfree (nx, TY_INT)
+ if (mwout != NULL && out != NULL)
+ call smw_saveim (mwout, out)
+ if (sh != NULL)
+ call shdr_close (sh)
+ if (ctin != NULL)
+ call smw_ctfree (ctin)
+ if (ctout != NULL)
+ call smw_ctfree (ctout)
+ if (mwin != NULL)
+ call smw_close (mwin)
+ if (mwout != NULL)
+ call smw_close (mwout)
+ if (out != NULL && out != in)
+ call imunmap (out)
+ if (in != NULL)
+ call imunmap (in)
+ }
+
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# DT_AIRVAC -- Convert dispersion coordinates to air or vacuum values.
+# The coordinates are first transformed to microns since that is what
+# the formulas expect. After correction they are transformed back to the
+# original units. The index of refraction formulas used are from
+# Allen's Astrophysical Quantities (1973).
+
+procedure dt_airvac (sh, x, n, air, t, p, f)
+
+pointer sh #I Spectrum pointer
+double x[n] #U Dispersion vector
+int n #I Number of pixels
+int air #I Correction type
+double t #I Temperture in deg C
+double p #I Total pressure in mmHg
+double f #I Water vapour pressure in mmHg
+
+int i
+double x2, a
+pointer un, un_open()
+errchk un_open, un_ctrand
+
+begin
+ if (air == NONE)
+ return
+
+ un = un_open ("microns")
+ call un_ctrand (UN(sh), un, x, x, n)
+ do i = 1, n {
+ x2 = 1 / x[i] **2
+ a = 64.328 + 29498.1 / (146 - x2) + 255.4 / (41 - x2)
+ a = a * p * (1 + (1.049 - 0.0157 * t) * 1e-6 * p) /
+ (720.883 * (1 + 0.003661 * t))
+ a = a - (0.0624 - 0.000680 * x2) / (1 + 0.003661 * t) * f
+ a = 1 + a / 1e6
+ switch (air) {
+ case AIR2VAC:
+ x[i] = a * x[i]
+ case VAC2AIR:
+ x[i] = x[i] / a
+ }
+ }
+ call un_ctrand (un, UN(sh), x, x, n)
+ call un_close (un)
+end
+
+
+# DT_CVFIT -- Fit a dispersion function and return the curfit pointer and
+# maximum error in pixels.
+
+procedure dt_cvfit (cv, func, order, x, y, w, n, maxerr)
+
+pointer cv #O Fitted dispersion function
+int func #I Dispersion function type
+int order #I Dispersion function order
+double x[n] #I Pixel coordinates
+double y[n] #I Desired world coordinates
+double w[n] #O Errors in pixels
+int n #I Number of pixels
+double maxerr #O Maximum error
+
+int i
+double minerr, dcveval()
+
+begin
+ if (cv != NULL)
+ call dcvfree (cv)
+ call dcvinit (cv, func, order, x[1], x[n])
+ call dcvfit (cv, x, y, w, n, WTS_UNIFORM, i)
+ do i = 2, n-1
+ w[i] = abs ((y[i] - dcveval (cv, x[i])) / ((y[i+1] - y[i-1]) / 2))
+ w[1] = abs ((y[1] - dcveval (cv, x[1])) / (y[2] - y[1]))
+ w[n] = abs ((y[n] - dcveval (cv, x[n])) / (y[n] - y[n-1]))
+ call alimd (w, n, minerr, maxerr)
+end
+
+
+# DT_SETWCS -- Set the multispec WCS. If the type is nonlinear then
+# the fitted function is stored.
+
+procedure dt_setwcs (cv, mwin, mwout, l, dtype, w1, dw)
+
+pointer cv #I Dispersion function
+pointer mwin #I Input SMW pointer
+pointer mwout #I Output, SMW pointer
+int l #I Image line
+int dtype #I Dispersion function type
+double w1 #I Coordinate of first pixel
+double dw #I Coordinate interval at first physical pixel
+
+int i, ap, bm, dt, nw, n, fd, dcvstati(), stropen()
+double a, b, z, lw, up
+pointer sp, title, coeff, coeffs
+
+begin
+ call smark (sp)
+ call salloc (title, SZ_LINE, TY_CHAR)
+
+ coeff = NULL
+ call smw_gwattrs (mwin, l, 1, ap, bm, dt, a, b, nw, z, lw, up, coeff)
+ call smw_gapid (mwin, l, 1, Memc[title], SZ_LINE)
+
+ switch (dtype) {
+ case DCFUNC:
+ n = dcvstati (cv, CVNSAVE)
+ call malloc (coeffs, n, TY_DOUBLE)
+ call dcvsave (cv, Memd[coeffs])
+ call realloc (coeff, 20*(n+2), TY_CHAR)
+ fd = stropen (Memc[coeff], 20*(n+2), NEW_FILE)
+ call fprintf (fd, "1 0 %d %d")
+ call pargi (nint (Memd[coeffs]))
+ call pargi (nint (Memd[coeffs+1]))
+ do i = 2, n-1 {
+ call fprintf (fd, " %g")
+ call pargd (Memd[coeffs+i])
+ }
+ call close (fd)
+ call mfree (coeffs, TY_DOUBLE)
+ default:
+ Memc[coeff] = EOS
+ }
+ dt = dtype
+ a = w1
+ b = dw
+ z = 0.
+ call smw_swattrs (mwout, l, 1, ap, bm, dt, a, b, nw, z, lw, up,
+ Memc[coeff])
+ call smw_sapid (mwout, l, 1, Memc[title])
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/dispcor1.par b/noao/onedspec/dispcor1.par
new file mode 100644
index 00000000..633ae4ca
--- /dev/null
+++ b/noao/onedspec/dispcor1.par
@@ -0,0 +1,5 @@
+change,s,q,,"yes|no|NO",," Change wavelength coordinate assignments?"
+w1,r,q,,,," Starting wavelength"
+w2,r,q,,,," Ending wavelength"
+dw,r,q,,,," Wavelength interval per pixel"
+nw,i,q,,,," Number of output pixels"
diff --git a/noao/onedspec/disptrans.par b/noao/onedspec/disptrans.par
new file mode 100644
index 00000000..1c40c895
--- /dev/null
+++ b/noao/onedspec/disptrans.par
@@ -0,0 +1,12 @@
+input,s,a,,,,Input spectra
+output,s,a,,,,Output spectra
+units,s,a,,,,Output dispersion units
+error,r,h,0.01,1E-6,,Maximum output coordinate error in pixels
+linearize,b,h,no,,,Resample the output to linear dispersion intervals?
+verbose,b,h,yes,,,"Print log of transformations?
+
+# AIR/VACUUM CONVERSION"
+air,s,h,"none","none|air2vac|vac2air",,Air-vacuum conversion?
+t,r,h,15.,,,Temperture in degrees C
+p,r,h,760,,,Pressure in mmHg
+f,r,h,4,,,Water vapour pressure in mmHg
diff --git a/noao/onedspec/doc/aidpars.hlp b/noao/onedspec/doc/aidpars.hlp
new file mode 100644
index 00000000..be846306
--- /dev/null
+++ b/noao/onedspec/doc/aidpars.hlp
@@ -0,0 +1,563 @@
+.help aidpars Jan04 noao.onedspec
+.ih
+NAME
+aidpars -- Automatic line identification parameters and algorithm
+.ih
+SUMMARY
+The automatic line identification parameters and algorithm used in
+\fBautoidentify\fR, \fBidentify\fR, and \fBreidentify\fR are described.
+.ih
+USAGE
+aidpars
+.ih
+PARAMETERS
+.ls reflist = ""
+Optional reference coordinate list to use in the pattern matching algorithm
+in place of the task coordinate list. This file is a simple text list of
+dispersion coordinates. It would normally be a culled and limited list of
+lines for the specific data being identified.
+.le
+.ls refspec = ""
+Optional reference dispersion calibrated spectrum. This template spectrum
+is used to select the prominent lines for the pattern matching algorithm.
+It need not have the same dispersion increment or dispersion coverage as
+the target spectrum.
+.le
+.ls crpix = "INDEF"
+Coordinate reference pixel for the coordinate reference value specified by
+the \fIcrval\fR parameter. This may be specified as a pixel coordinate
+or an image header keyword name (with or without a '!' prefix). In the
+latter case the value of the keyword in the image header of the spectrum
+being identified is used. A value of INDEF translates to the middle of
+the target spectrum.
+.le
+.ls crquad = INDEF
+Quadratic correction to the detected pixel positions to "linearize" the
+pattern of line spacings. The corrected positions x' are derived from
+the measured positions x by
+
+.nf
+ x' = x + crquad * (x - crpix)**2
+.fi
+
+where crpix is the pixel reference point as defined by the \fIcrpix\fR
+parameter. The measured and corrected positions may be examined by
+using the 't' debug flag. The value may be a number or a header
+keyword (with or without a '!' prefix). The default of INDEF translates
+to zero; i.e. no quadratic correction.
+.le
+.ls cddir = "sign" (unknown|sign|increasing|decreasing)
+The sense of the dispersion increment with respect to the pixel coordinates
+in the input spectrum. The possible values are "increasing" or
+"decreasing" if the dispersion coordinates increase or decrease with
+increasing pixel coordinates, "sign" to use the sign of the dispersion
+increment (positive is increasing and negative is decreasing), and
+"unknown" if the sense is unknown and to be determined by the algorithm.
+.le
+.ls crsearch = "INDEF"
+Coordinate reference value search radius. The value may be specified
+as a numerical value or as an image header keyword (with or without
+a '!' prefix) whose value is to be used. The algorithm will search
+for a final coordinate reference value within this amount of the value
+specified by \fIcrval\fR. If the value is positive the search radius is
+the specified value. If the value is negative it is the absolute value
+of this parameter times \fIcdelt\fR times the number of pixels in the
+input spectrum; i.e. it is the fraction of dispersion range covered by the
+target spectrum assuming a dispersion increment per pixel of \fIcdelt\fR.
+A value of INDEF translates to -0.1 which corresponds to a search radius
+of 10% of the estimated dispersion range.
+.le
+.ls cdsearch = "INDEF"
+Dispersion coordinate increment search radius. The value may be specified
+as a numerical value or as an image header keyword (with or without
+a '!' prefix) whose value is to be used. The algorithm will search
+for a dispersion coordinate increment within this amount of the value
+specified by \fIcdelt\fR. If the value is positive the search radius is
+the specified value. If the value is negative it is the absolute value of
+this parameter times \fIcdelt\fR; i.e. it is a fraction of \fIcdelt\fR.
+A value of INDEF translates to -0.1 which corresponds to a search radius
+of 10% of \fIcdelt\fR.
+.le
+.ls ntarget = 100
+Number of spectral lines from the target spectrum to use in the pattern
+matching.
+.le
+.ls npattern = 5
+Initial number of spectral lines in patterns to be matched. There is a
+minimum of 3 and a maximum of 10. The algorithm starts with the specified
+number and if no solution is found with that number it is iteratively
+decreased by one to the minimum of 3. A larger number yields fewer
+and more likely candidate matches and so will produce a result sooner.
+But in order to be thorough the algorithm will try smaller patterns to
+search more possiblities.
+.le
+.ls nneighbors = 10
+Number of neighbors to use in making patterns of lines. This parameter
+restricts patterns to include lines which are near each other.
+.le
+.ls nbins = 6
+Maximum number of bins to divide the reference coordinate list or spectrum
+in searching for a solution. When there are no weak dispersion constraints
+the algorithm subdivides the full range of the coordinate list or reference
+spectrum into one bin, two bins, etc. up to this maximum. Each bin is
+searched for a solution.
+.le
+.ls ndmax = 1000
+Maximum number of candidate dispersions to examine. The algorithm ranks
+candidate dispersions by how many candidate spectral lines are fit and the
+the weights assigned by the pattern matching algorithm. Starting from
+the highest rank it tests each candidate dispersion to see if it is
+a satisfactory solution. This parameter determines how many candidate
+dispersion in the ranked list are examined.
+.le
+.ls aidord = 3 (minimum of 2)
+The order of the dispersion function fit by the automatic identification
+algorithm. This is the number of polynomial coefficients so
+a value of two is a linear function and a value of three is a quadratic
+function. The order should be restricted to values of two or three.
+Higher orders can lead to incorrect solutions because of the increased
+degrees of freedom if finding incorrect line identifications.
+.le
+.ls maxnl = 0.02
+Maximum non-linearity allowed in any trial dispersion function.
+The definition of the non-linearity test is
+
+.nf
+ maxnl > (w(0.5) - w(0)) / (w(1) - w(0)) - 0.5
+.fi
+
+where w(x) is the dispersion function value (e.g. wavelength) of the fit
+and x is a normalized pixel positions where the endpoints of the spectrum
+are [0,1]. If the test fails on a trial dispersion fit then a linear
+function is determined.
+.le
+.ls nfound = 6
+Minimum number of identified spectral lines required in the final solution.
+If a candidate solution has fewer identified lines it is rejected.
+.le
+.ls sigma = 0.05
+Sigma (uncertainty) in the line center estimates specified in pixels.
+This is used to propagate uncertainties in the line spacings in
+the observed patterns of lines.
+.le
+.ls minratio = 0.1
+Minimum spacing ratio used. Patterns of lines in which the ratio of
+spacings between consecutive lines is less than this amount are excluded.
+.le
+.ls rms = 0.1
+RMS goal for a correct dispersion solution. This is the RMS in the
+measured spectral lines relative to the expected positions from the
+coordinate line list based on the coordinate dispersion solution.
+The parameter is specified in terms of the line centering parameter
+\fIfwidth\fR since for broader lines the pixel RMS would be expected
+to be larger. A pixel-based RMS criterion is used to be independent of
+the dispersion. The RMS will be small for a valid solution.
+.le
+.ls fmatch = 0.2
+Goal for the fraction of unidentified lines in a correct dispersion
+solution. This is the fraction of the strong lines seen in the spectrum
+which are not identified and also the fraction of all lines in the
+coordinate line list, within the range of the dispersion solution, not
+identified. Both fractions will be small for a valid solution.
+.le
+.ls debug = ""
+Print or display debugging information. This is intended for the developer
+and not the user. The parameter is specified as a string of characters
+where each character displays some information. The characters are:
+
+.nf
+ a: Print candidate line assignments.
+ b: Print search limits.
+ c: Print list of line ratios.
+* d: Graph dispersions.
+* f: Print final result.
+* l: Graph lines and spectra.
+ r: Print list of reference lines.
+* s: Print search iterations.
+ t: Print list of target lines.
+ v: Print vote array.
+ w: Print wavelength bin limits.
+.fi
+
+The items with an asterisk are the most useful. The graphs are exited
+with 'q' or 'Q'.
+.le
+.ih
+DESCRIPTION
+The \fBaidpars\fR parameter set contains the parameters for the automatic
+spectral line identification algorithm used in the task \fBautoidentify\fR,
+\fBidentify\fR, and \fBreidentify\fR. These tasks include the parameter
+\fIaidpars\fR which links to this parameters set. Typing \fBaidpars\fR
+allows these parameters to be edited. When editing the parameters of the
+other tasks with \fBeparam\fR one can edit the \fBaidpars\fR parameters by
+type ":e" when pointing to the \fIaidpars\fR task parameter. The values of
+the \fBaidpars\fR parameters may also be set on the command line for the
+task. The discussion which follows describes the parameters and the
+algorithm.
+
+The goal of the automatic spectral line identification algorithm is to
+automate the identification of spectral lines so that given an observed
+spectrum of a spectral line source (called the target spectrum) and a file
+of known dispersion coordinates for the lines, the software will identify
+the spectral lines and use these identifications to determine a
+dispersion function. This algorithm is quite general so that the correct
+identifications and dispersion function may be found even when there is
+limited or no knowledge of the dispersion coverage and resolution of the
+observation.
+
+However, when a general line list, including a large dispersion range and
+many weak lines, is used and the observation covers a much smaller portion
+of the coordinate list the algorithm may take a long to time or even fail
+to find a solution. Thus, it is highly desirable to provide additional
+input giving approximate dispersion parameters and their uncertainties.
+When available, a dispersion calibrated reference spectrum (not necessarily
+of the same resolution or wavelength coverage) also aids the algorithm by
+indicating the relative strengths of the lines in the coordinate file. The
+line strengths need not be very similar (due to different lamps or
+detectors) but will still help separate the inherently weak and strong
+lines.
+
+
+The Input
+
+The primary inputs to the algorithm are the observed one dimensional target
+spectrum in which the spectral lines are to be identified and a dispersion
+function determined and a file of reference dispersion coordinates. These
+inputs are provided in the tasks using the automatic line identification
+algorithm.
+
+One way to limit the algorithm to a specific dispersion region and to the
+important spectral lines is to use a limited coordinate list. One may do
+this with the task coordinate list parameter (\fIcoordlist\fR). However,
+it is desirable to use a standard master line list that includes all the
+lines, both strong and weak. Therefore, one may specify a limited line
+list with the parameter \fIreflist\fR. The coordinates in this list will
+be used by the automatic identification algorithm to search for patterns
+while using the primary coordinate list for adding weaker lines during the
+dispersion function fitting.
+
+The tasks \fBautoidentify\fR and \fBidentify\fR also provide parameters to
+limit the search range. These parameters specify a reference dispersion
+coordinate (\fIcrval\fR) and a dispersion increment per pixel (\fIcdelt\fR).
+When these parameters are INDEF this tells the algorithm to search for a
+solution over the entire range of possibilities covering the coordinate
+line list or reference spectrum.
+
+The reference dispersion coordinate refers to an approximate coordinate at
+the reference pixel coordinate specified by the parameter \fIcrpix\fR.
+The default value for the reference pixel coordinate is INDEF which
+translates to the central pixel of the target spectrum.
+
+The parameters \fIcrsearch\fR and \fIcdsearch\fR specify the expected range
+or uncertainty of the reference dispersion coordinate and dispersion
+increment per pixel respectively. They may be specified as an absolute
+value or as a fraction. When the values are positive they are used
+as an absolute value;
+
+.nf
+ crval(final) = \fIcrval\fR +/- \fIcrsearch\fR
+ cdelt(final) = \fIcdelt\fR +/- \fIcdsearch\fR.
+.fi
+
+When the values are negative they are used as a fraction of the dispersion
+range or fraction of the dispersion increment;
+
+.nf
+ crval(final) = \fIcrval\fR +/- abs (\fIcrsearch\fR * \fIcdelt\fR) * N_pix
+ cdelt(final) = \fIcdelt\fR +/- abs (\fIcdsearch\fR * \fIcdelt\fR)
+.fi
+
+where abs is the absolute value function and N_pix is the number of pixels
+in the target spectrum. When the ranges are not given explicitly, that is
+they are specified as INDEF, default values of -0.1 are used.
+
+The parameters \fIcrval\fR, \fIcdelt\fR, \fIcrpix\fR, \fIcrsearch\fR,
+and \fIcdsearch\fR may be given explicit numerical values or may
+be image header keyword names. In the latter case the values of the
+indicated keywords are used. This feature allows the approximate
+dispersion range information to be provided by the data acquisition
+system; either by the instrumentation or by user input.
+
+Because sometimes only the approximate magnitude of the dispersion
+increment is known and not the sign (i.e. whether the dispersion
+coordinates increase or decrease with increasing pixel coordinates)
+the parameter \fIcdsign\fR specifies if the dispersion direction is
+"increasing", "decreasing", "unknown", or defined by the "sign" of the
+approximate dispersion increment parameter (sign of \fIcdelt\fR).
+
+The above parameters defining the approximate dispersion of the target
+spectrum apply to \fIautoidentify\fR and \fIidentify\fR. The task
+\fBreidentify\fR does not use these parameters except that the \fIshift\fR
+parameter corresponds to \fIcrsearch\fR if it is non-zero. This task
+assumes that spectra to be reidentified are the same as a reference
+spectrum except for a zero point dispersion offset; i.e. the approximate
+dispersion parameters are the same as the reference spectrum. The
+dispersion increment search range is set to be 5% and the sign of the
+dispersion increment is the same as the reference spectrum.
+
+An optional input is a dispersion calibrated reference spectrum (referred to
+as the reference spectrum in the discussion). This is specified either in
+the coordinate line list file or by the parameter \fIrefspec\fR. To
+specify a spectrum in the line list file the comment "# Spectrum <image>"
+is included where <image> is the image filename of the reference spectrum.
+Some of the standard line lists in linelists$ may include a reference
+spectrum. The reference spectrum is used to select the strongest lines for
+the pattern matching algorithm.
+
+
+The Algorithm
+
+First a list of the pixel positions for the strong spectral lines in the
+target spectrum is created. This is accomplished by finding the local
+maxima, sorting them by pixel value, and then using a centering algorithm
+(\fIcenter1d\fR) to accurately find the centers of the line profiles. Note
+that task parameters \fIftype\fR, \fIfwidth\fR, \fIcradius\fR,
+\fIthreshold\fR, and \fIminsep\fR are used for the centering. The number
+of spectral lines selected is set by the parameter \fIntarget\fR.
+
+In order to insure that lines are selected across the entire spectrum
+when all the strong lines are concentrated in only a part of the
+spectrum, the spectrum is divided into five regions and approximately
+a fifth of the requested number of lines is found in each region.
+
+A list of reference dispersion coordinates is selected from the coordinate
+file (\fIcoordlist\fR or \fIreflist\fR). The number of reference
+dispersion coordinates is set at twice the number of target lines found.
+The reference coordinates are either selected uniformly from the coordinate
+file or by locating the strong spectral lines (in the same way as for the
+target spectrum) in a reference spectrum if one is provided. The selection
+is limited to the expected range of the dispersion as specified by the
+user. If no approximate dispersion information is provided the range of
+the coordinate file or reference spectrum is used.
+
+The ratios of consecutive spacings (the lists are sorted in increasing
+order) for N-tuples of coordinates are computed from both lists. The size
+of the N-tuple pattern is set by the \fInpattern\fR parameter. Rather than
+considering all possible combinations of lines only patterns of lines with
+all members within \fInneighbors\fR in the lists are used; i.e. the first
+and last members of a pattern must be within \fInneighbors\fR of each other
+in the lists. The default case is to find all sets of five lines which are
+within ten lines of each other and compute the three spacing ratios.
+Because very small spacing ratios become uncertain, the line patterns are
+limited to those with ratios greater than the minimum specified by the
+\fIminratio\fR parameter. Note that if the direction of the dispersion is
+unknown then one computes the ratios in the reference coordinates in both
+directions.
+
+The basic idea is that similar patterns in the pixel list and the
+dispersion list will have matching spacing ratios to within a tolerance
+derived by the uncertainties in the line positions (\fIsigma\fR) from the
+target spectrum. The reference dispersion coordinates are assumed to have
+no uncertainty. All matches in the ratio space are found between patterns
+in the two lists. When matches are made then the candidate identifications
+(pixel, reference dispersion coordinate) between the elements of the
+patterns are recorded. After finding all the matches in ratio space a
+count is made of how often each possible candidate identification is
+found. When there are a sufficient number of true pairs between the lists
+(of order 25% of the shorter list) then true identifications will appear in
+common in many different patterns. Thus the highest counts of candidate
+identifications are the most likely to be true identifications.
+
+Because the relationship between the pixel positions of the lines in the
+target spectrum and the line positions in the reference coordinate space
+is generally non-linear the line spacing ratios are distorted and may
+reduce the pattern matching. The line patterns are normally restricted
+to be somewhat near each other by the \fInneighbors\fR so some degree of
+distortion can be tolerated. But in order to provide the ability to remove
+some of this distortion when it is known the parameter \fIcrquad\fR is
+provided. This parameter applies a quadratic transformation to the measured
+pixel positions to another set of "linearized" positions which are used
+in the line ratio pattern matching. The form of the transformation is
+
+.nf
+ x' = x + crquad * (x - crpix)**2
+.fi
+
+where x is the measured position, x' is the transformed position,
+crquad is the value of the distortion parameter, and crpix is the value
+of the coordinate reference position.
+
+If approximate dispersion parameters and search ranges are defined then
+candidate identifications which fall outside the range of dispersion
+function possibilities are rejected. From the remaining candidate
+identifications the highest vote getters are selected. The number selected
+is three times the number of target lines.
+
+All linear dispersions functions, where dispersion and pixel coordinates
+are related by a zero point and slope, are found that pass within two
+pixels of two or more of the candidate identifications. The dispersion
+functions are ranked primarily by the number of candidate identifications
+fitting the dispersion and secondarily by the total votes in the
+identifications. Only the highest ranking candidate linear dispersion
+are kept. The number of candidate dispersions kept is set by the
+parameter \fIndmax\fR.
+
+The candidate dispersions are evaluated in order of their ranking. Each
+line in the coordinate file (\fIcoordlist\fR) is converted to a pixel
+coordinate based on the dispersion function. The centering algorithm
+attempts to find a line profile near that position as defined by the
+\fImatch\fR parameter. This may be specified in pixel or dispersion
+coordinates. All the lines found are used to fit a polynomial dispersion
+function with \fIaidord\fR coefficients. The order should be linear or
+quadratic because otherwise the increased degrees of freedom allow
+unrealistic dispersion functions to appear to give a good result. A
+quadratic function (\fIaidord\fR = 3) is allowed since this is the
+approximate form of many dispersion functions.
+
+However, to avoid unrealistic dispersion functions a test is made that
+the maximum amplitude deviation from a linear function is less than
+an amount specified by the \fImaxnl\fR parameter. The definition of
+the test is
+
+.nf
+ maxnl > (w(0.5) - w(0)) / (w(1) - w(0)) - 0.5
+.fi
+
+where w(x) is the dispersion function value (e.g. wavelength) of the fit
+and x is a normalized pixel positions where the endpoints of the spectrum
+are [0,1]. What this relation means is that the wavelength interval
+between one end and the center relative to the entire wavelength interval
+is within maxnl of one-half. If the test fails then a linear function
+is fit. The process of adding lines based on the last dispersion function
+and then refitting the dispersion function is iterated twice. At the end
+of this step if fewer than the number of lines specified by the parameter
+\fInfound\fR have been identified the candidate dispersion is eliminated.
+
+The quality of the line identifications and dispersion solution is
+evaluated based on three criteria. The first one is the root-mean-square
+of the residuals between the pixel coordinates derived from lines found
+from the dispersion coordinate file based on the dispersion function and
+the observed pixel coordinates. This pixel RMS is normalized by the target
+RMS set with the \fIrms\fR parameter. Note that the \fIrms\fR parameter
+is specified in units of the \fIfwidth\fR parameter. This is because if
+the lines are broader, requiring a larger fwidth to obtain a centroid,
+then the expected uncertainty would be larger. A good solution will have
+a normalized rms value less than one. A pixel RMS criterion, as opposed
+to a dispersion coordinate RMS, is used since this is independent of the
+actual dispersion of the spectrum.
+
+The other two criteria are the fraction of strong lines from the target
+spectrum list which were not identified with lines in the coordinate file
+and the fraction of all the lines in the coordinate file (within the
+dispersion range covered by the candidate dispersion) which were not
+identified. These are normalized to a target value given by \fIfmatch\fR.
+The default matching goal is 0.3 which means that less than 30% of
+the lines should be unidentified or greater than 70% should be identified.
+As with the RMS, a value of one or less corresponds to a good solution.
+
+The reason the fraction identified criteria are used is that the pixel RMS
+can be minimized by finding solutions with large dispersion increment per
+pixel. This puts all the lines in the coordinate file into a small range
+of pixels and so (incorrect) lines with very small residuals can be found.
+The strong line identification criterion is clearly a requirement that
+humans use in evaluating a solution. The fraction of all lines identified,
+as opposed to the number of lines identified, in the coordinate file is
+included to reduce the case of a large dispersion increment per pixel
+mapping a large number of lines (such as the entire list) into the range of
+pixels in the target spectrum. This can give the appearance of finding a
+large number of lines from the coordinate file. However, an incorrect
+dispersion will also find a large number which are not matched. Hence the
+fraction not matched will be high.
+
+The three criteria, all of which are normalized so that values less
+than one are good, are combined to a single figure of merit by a weighted
+average. Equal weights have been found to work well; i.e. each criterion
+is one-third of the figure of merit. In testing it has been found that all
+correct solutions over a wide range of resolutions and dispersion coverage
+have figures of merit less than one and typically of order 0.2. All
+incorrect candidate dispersion have values of order two to three.
+
+The search for the correct dispersion function terminates immediately,
+but after checking the first five most likely candidates, when
+a figure of merit less than one is found. The order in which the candidate
+dispersions are tested, that is by rank, was chosen to try the most promising
+first so that often the correct solution is found on the first attempt.
+
+When the approximate dispersion is not known or is imprecise it is
+often the case that the pixel and coordinate lists will not overlap
+enough to have a sufficient number true coordinate pairs. Thus, at a
+higher level the above steps are iterated by partitioning the dispersion
+space searched into bins of various sizes. The largest size is the
+maximum dispersion range including allowance for the search radii.
+The smallest size bin is obtained by dividing the dispersion range by
+the number specified by the \fInbins\fR parameter. The actual number
+of bins searched at each bin size is actually twice the number of
+bins minus one because the bins are overlapped by 50%.
+
+The search is done starting with bins in the middle of the size range and
+in the middle of the dispersion range and working outward towards larger
+and smaller bins and larger and smaller dispersion ranges. This is done to
+improved the chances of finding the correction dispersion function in the
+smallest number of steps.
+
+Another iteration performed if no solution is found after trying all the
+candidate dispersion and bins is to reduce the number of lines in the
+pattern. So the parameter \fInpattern\fR is an initial maximum pattern.
+A larger pattern gives fewer and higher quality candidate identifications
+and so converges faster. However, if no solution is found the algorithm
+tries more possible matches produced by a lower number of lines in
+the pattern. The pattern groups are reduced to a minimum of three lines.
+
+When a set of line identifications and dispersion solution satisfying the
+figure of merit criterion is found a final step is performed.
+Up to this point only linear dispersion functions are used since higher order
+function can be stretch in unrealistic ways to give good RMS values
+and fit all the lines. The final step is to use the line identifications
+to fit a dispersion function using all the parameters specified by the
+user (such as function type, order, and rejection parameters). This
+is iterated to add new lines from the coordinate list based on the
+more general dispersion function and then obtain a final dispersion
+function. The line identifications and dispersion function are then
+returned to the task using this automatic line identification algorithm.
+
+If a satisfactory solution is not found after searching all the
+possibilities the algorithm will inform the task using it and the task will
+report this appropriately.
+.ih
+EXAMPLES
+1. List the parameters.
+
+.nf
+ cl> lpar aidpars
+.fi
+
+2. Edit the parameters with \fBeparam\fR.
+
+.nf
+ cl> aidpars
+.fi
+
+3. Edit the \fBaidpars\fR parameters from within \fBautoidentify\fR.
+
+.nf
+ cl> epar autoid
+ [edit the parameters]
+ [move to the "aidpars" parameter and type :e]
+ [edit the aidpars parameters and type :q or EOF character]
+ [finish editing the autoidentify parameters]
+ [type :wq or the EOF character]
+.fi
+
+4. Set one of the parameters on the command line.
+
+.nf
+ cl> autoidentify spec002 5400 2.5 crpix=1
+.fi
+.ih
+REVISIONS
+.ls AIDPARS V2.12.2
+There were many changes made in the paramters and algorithm. New parameters
+are "crquad" and "maxnl". Changed definitions are for "rms". Default
+value changes are for "cddir", "ntarget", "ndmax", and "fmatch". The most
+significant changes in the algorithm are to allow for more non-linear
+dispersion with the "maxnl" parameter, to decrease the "npattern" value
+if no solution is found with the specified value, and to search a larger
+number of candidate dispersions.
+.le
+.ls AIDPARS V2.11
+This parameter set is new in this version.
+.le
+.ih
+SEE ALSO
+autoidentify, identify, reidentify, center1d
+.endhelp
diff --git a/noao/onedspec/doc/autoidentify.hlp b/noao/onedspec/doc/autoidentify.hlp
new file mode 100644
index 00000000..a344031a
--- /dev/null
+++ b/noao/onedspec/doc/autoidentify.hlp
@@ -0,0 +1,370 @@
+.help autoidentify Jan96 noao.onedspec
+.ih
+NAME
+autoidentify -- Automatically identify lines and fit dispersion
+.ih
+SUMMARY
+Spectral lines are automatically identified from a list of coordinates
+by pattern matching. The identified lines are then used to fit a
+dispersion function which is written to a database for later use
+in dispersion calibration. After a solution is found the identified
+lines and dispersion function may be examined interactively.
+.ih
+USAGE
+autoidentify images crval cdelt
+.ih
+PARAMETERS
+.ls images
+List of images containing one dimensional spectra in which to identify
+spectral lines and fit dispersion functions. For two and three dimensional
+spectral and spatial data one may use an image section to select a one
+dimensional spectral vector or use the \fIsection\fR parameter.
+.le
+.ls crval, cdelt
+These parameters specify an approximate coordinate value and coordinate
+interval per pixel. They may be specified as numerical values, INDEF, or
+image header keyword names whose values are to be used. The coordinate
+reference value is for the pixel specified by the parameter
+\fIaidpars.crpix\fR. The default reference pixel is INDEF which means the
+middle of the spectrum. By default only the magnitude of the coordinate
+interval is used and the search will include both increasing and decreasing
+coordinate values with increasing pixel values. If one or both of these
+parameters are specified as INDEF the search for a solution will be slower
+and more likely to fail.
+.le
+.ls coordlist = ""
+Coordinate list consisting of an list of spectral line coordinates.
+A comment line of the form "# units <units>", where <units> is one of the
+understood units names, defines the units of the coordinate list. If no units
+are specified then Angstroms are assumed.
+The line list is used for both the final identifications and for the set of
+lines to use in the automatic search. A restricted search list may be
+specified with the parameter \fIaidpars.reflist\fR. The line list may
+contain a comment line of the form "# Spectrum <name>", where <name> is a
+filename containing a reference spectrum. The reference spectrum will be
+used in selecting the strong lines for the automatic search. A reference
+spectrum may also be specified with the parameter \fIaidpars.refspec\fR.
+
+Some standard line lists are available in the directory "linelists$".
+See the help topic \fIlinelists\fR for the available line lists.
+.le
+.ls units = ""
+The units to use if no database entry exists. The units are specified as
+described in
+
+.nf
+ cl> help onedspec.package section=units
+.fi
+
+If no units are specified and a coordinate list is used then the units of
+the coordinate list are selected. If a database entry exists then the
+units defined there override both this parameter and the coordinate list.
+.le
+.ls interactive = yes (no|yes|NO|YES)
+After automatically identifying the spectral lines and dispersion function
+review and modify the solution interactively? If "yes" a query is given
+for each spectrum providing the choice of interactive review. The
+query may be turned off during execution. If "YES" the interactive review
+is entered automatically without a query. The interactive, graphical
+review is the same as the task \fBidentify\fR with a few restriction.
+.le
+.ls aidpars = "" (parameter set)
+Parameter set for the automatic line identification algorithm. The
+parameters are described in the help topic \fBaidpars\fR.
+.le
+
+For two and three dimensional spectral images the following parameters are
+used to select a one dimensional spectrum.
+.ls section = "middle line"
+If an image is not one dimensional or specified as a one dimensional image
+section then the image section given by this parameter is used. The
+section defines a one dimensional spectrum. The dispersion direction is
+derived from the vector direction.
+
+The section parameter may be specified directly as an image section or
+in one of the following forms
+
+.nf
+line|column|x|y|z first|middle|last|# [first|middle|last|#]]
+first|middle|last|# [first|middle|last|#] line|column|x|y|z
+.fi
+
+where each field can be one of the strings separated by | except for #
+which is an integer number. The field in [] is a second designator which
+is used with three dimensional data. Abbreviations are allowed though
+beware that 'l' is not a sufficient abbreviation.
+.le
+.ls nsum = "1"
+Number of lines, columns, or bands across the designated dispersion axis to
+be summed when the image is a two or three dimensional image.
+It does not apply to multispec format spectra. If the image is three
+dimensional an optional second number can be specified for the higher
+dimensional axis (the first number applies to the lower axis number and
+the second to the higher axis number). If a second number is not specified
+the first number is used for both axes.
+.le
+
+The following parameters are used in finding spectral lines.
+.ls ftype = "emission"
+Type of spectral lines to be identified. The possibly abbreviated choices are
+"emission" and "absorption".
+.le
+.ls fwidth = 4.
+Full-width at the base (in pixels) of the spectral lines to be identified.
+.le
+.ls cradius = 5.
+The maximum distance, in pixels, allowed between a line position
+and the initial estimate when defining a new line.
+.le
+.ls threshold = 0.
+In order for a line center to be determined the range of pixel intensities
+around the line must exceed this threshold.
+.le
+.ls minsep = 2.
+The minimum separation, in pixels, allowed between line positions
+when defining a new line.
+.le
+.ls match = -3.
+The maximum difference for a match between the line coordinate derived from
+the dispersion function and a coordinate in the coordinate list. Positive
+values are in user coordinate units and negative values are in units of
+pixels.
+.le
+
+The following parameters are used to fit a dispersion function to the user
+coordinates. The \fBicfit\fR routines are used and further descriptions
+about these parameters may be found under that topic.
+.ls function = "spline3"
+The function to be fit to user coordinates as a function of the pixel
+coordinates. The choices are "chebyshev", "legendre", "spline1", or "spline3".
+.le
+.ls order = 1
+Order of the fitting function. The order is the number of polynomial
+terms (coefficients) or the number of spline pieces.
+.le
+.ls sample = "*"
+Sample regions for fitting specified in pixel coordinates.
+.le
+.ls niterate = 10
+Number of rejection iterations.
+.le
+.ls low_reject = 3.0, high_reject = 3.0
+Lower and upper residual rejection in terms of the RMS of the fit.
+.le
+.ls grow = 0
+Distance from a rejected point in which additional points are automatically
+rejected regardless of their residuals.
+.le
+
+The following parameters control the input and output.
+.ls dbwrite = "yes" (no|yes|NO|YES)
+Automatically write or update the database with the line identifications
+and dispersion function? If "no" or "NO" then there is no database
+output. If "YES" the results are automatically written to the database.
+If "yes" a query is made allowing the user to reply with "no", "yes", "NO"
+or "YES". The negative responses do not write to the database and the
+affirmative ones do write to the database. The upper-case responses
+suppress any further queries for any remaining spectra.
+.le
+.ls overwrite = yes
+Overwrite previous solutions in the database? If there is a previous
+solution for the spectrum being identified this parameter selects whether
+to skip the spectrum ("no") or find a new solution ("yes"). In the later
+case saving the solution to the database will overwrite the previous
+solution.
+.le
+.ls database = "database"
+Database for reading and writing the line identifications and
+dispersion functions.
+.le
+.ls verbose = yes
+Print results of the identification on the standard output?
+.le
+.ls logfile = "logfile"
+Filename for recording log information about the identifications.
+The null string, "", may be specified to skip recording the log information.
+.le
+.ls plotfile = ""
+Filename for recording log plot information as IRAF metacode. A
+null string, "", may be specified to skip recording the plot information.
+(Plot output is currently not implemented.)
+.le
+.ls graphics = "stdgraph"
+Graphics device for the interactive review. The default is the standard
+graphics device which is generally a graphics terminal.
+.le
+.ls cursor = ""
+Cursor input file for the interactive review. If a cursor file is not
+given then the standard graphics cursor is read.
+.le
+
+.ls query
+Parameter used by the program to query the user.
+.le
+.ih
+DESCRIPTION
+\fBAutoidentify\fR automatically identifies spectral lines from a list of
+spectral line coordinates (\fIcoordlist\fR) and determines a dispersion
+function. The identified lines and the dispersion function may be reviewed
+interactively (\fIinteractive\fR) and the final results are recorded in a
+\fIdatabase\fR.
+
+Each image in the input list (\fIimages\fR) is considered in turn. If the
+image is not one dimensional or a one dimensional section of an image then
+the parameter \fIsection\fR is used to select a one dimensional
+spectrum. It defines the dispersion direction and central spatial
+coordinate(s). If the image is not one dimensional or a set of one
+dimensional spectra n multispec format then the \fInsum\fR parameter
+selects the number of neighboring lines, columns, and bands to sum.
+
+This task is not intended to be used on all spectra in an image since in
+most cases the dispersion functions will be similar though possibly with a
+zero point shift. Once one spectrum is identified the others may be
+reidentified with \fBreidentify\fR.
+
+The coordinate list of spectral lines often covers a much larger dispersion
+range than the spectra being identified. This is true of the standard line
+lists available in the "linelists$" directory. While the algorithm for
+identifying the lines will often succeed with a large line list it is not
+guaranteed nor will it find the solution quickly without additional
+information. Thus it is highly desirable to provide the algorithm with
+approximate information about the spectra. Generally this information is
+known by the observer or recorded in the image header.
+
+As implied in the previous paragraph, one may use a
+limited coordinate line list that matches the dispersion coverage of the
+spectra reasonably well (say within 100% of the dispersion range).
+This may be done with the \fIcoordlist\fR parameter or a second
+coordinate list used only for the automatic search may be specified
+with the parameter \fIaidpars.reflist\fR. This allows using a smaller
+culled list of lines for finding the matching patterns and a large list
+with weaker lines for the final dispersion function fit.
+
+The alternative to a limited list is to use the parameters \fIcrval\fR and
+\fIcdelt\fR to specify the approximate coordinate range and dispersion
+interval per pixel. These parameters may be given explicitly or by
+specifying image header keywords. The pixel to which \fIcrval\fR refers is
+specified by the parameter \fIaidpars.crpix\fR. By default this is INDEF
+which means use the center of the spectrum. The direction in which the
+dispersion coordinates increase relative to the pixel coordinates may be
+specified by the \fIaidpars.cddir\fR parameter. The default is "unknown"
+to search in either direction.
+
+The algorithm used to automatically identify the spectral lines and
+find a dispersion function is described under the help topic
+\fBaidpars\fR. This topic also describes the various algorithm
+parameters. The default parameters are adequate for most data.
+
+The characteristics of the spectral lines to be found and identified are
+set by several parameters. The type of spectral lines, whether "emission"
+or "absorption", is set by the parameter \fIftype\fR. For arc-line
+calibration spectra this parameter is set to "emission". The full-width
+(in pixels) at the base of the spectral lines is set by the parameter
+\fIfwidth\fR. This is used by the centering algorithm to define the extent
+of the line profile to be centered. The \fIthreshold\fR parameter defines
+a minimum contrast (difference) between a line peak and the neighboring
+continuum. This allows noise peaks to be ignored. Finding the center of a
+possible line begins with an initial position estimate. This may be an
+interactive cursor position or the expected position from the coordinate
+line list. The centering algorithm then searches for a line of the
+specified type, width, and threshold within a given distance, specified by
+the \fIcradius\fR parameter. These parameters and the centering algorithm
+are described by the help topic \fBcenter1d\fR.
+
+To avoid finding the same line multiple times, say when there are two lines
+in the line list which are blended into a single in the observation, the
+\fIminsep\fR parameter rejects any new line position found within that
+distance of a previously defined line.
+
+The automatic identification of lines includes matching a line position in
+the spectrum against the list of coordinates in the coordinate line list.
+The \fImatch\fR parameter defines how close the measured line position must
+be to a coordinate in the line list to be considered a possible
+identification. This parameter may be specified either in user coordinate
+units (those used in the line list) by using a positive value or in pixels
+by using a negative value. In the former case the line position is
+converted to user coordinates based on a dispersion function and in the
+latter the line list coordinate is converted to pixels using the inverse of
+the dispersion function.
+
+The dispersion function is determined by fitting a set of pixel positions
+and user coordinate identifications by least squares to a specified
+function type. The fitting requires a function type, \fIfunction\fR, and
+the order (number of coefficients or spline pieces), \fIorder\fR.
+In addition the fitting can be limited to specified regions, \fIsample\fR,
+and provide for the rejection of points with large residuals. These
+parameters are set in advance and used during the automatic dispersion
+function determination. Later the fitting may be modified interactively.
+For additional discussion of these parameters see \fBicfit\fR.
+
+The output of this program consists of log information, plot information,
+and the line identifications and dispersion function. The log information
+may be appended to the file specified by the \fIlogfile\fR parameter
+and printed to the standard output (normally the terminal) by
+setting the \fIverbose\fR parameter to yes. This information consists
+of a banner line, a line of column labels, and results for each spectrum.
+For each spectrum the spectrum name, the number of spectral lines found,
+the dispersion coordinate at the middle of the spectrum, the dispersion
+increment per pixel, and the root-mean-square (RMS) of the residuals for
+the lines used in the dispersion function fit is recorded. The units of
+the RMS are those of the user (line list) coordinates. If a solution is
+not found the spectrum name and a message is printed.
+
+The line identifications and dispersion function are written to the
+specified \fIdatabase\fR. The current format of the database is described
+in the help for \fIidentify\fR. If a database entry is already present for
+a spectrum and the parameter \fIoverwrite\fR is "no" then the spectrum is
+skipped and a message is printed to the standard output. After a solution
+is found and after any interactive review (see below) the results may be
+written to the database. The \fIdbwrite\fR parameter may be specified as
+"no" or "NO" to disable writing to the database (and no queries will be
+made), as "yes" to query whether to or not to write to the database, or as
+"YES" to automatically write the results to the database with no queries.
+When a query is given the responses may be "no" or "yes" for an individual
+spectrum or "NO" or "YES" for all remaining spectra without further
+queries.
+
+After a solution is found one may review and modify the line
+identifications and dispersion function using the graphical functions of
+the \fBidentify\fR task (with the exception that a new spectrum may not be
+selected). The review mode is selected with the \fIinteractive\fR
+parameter. If the parameter is "no" or "NO" then no interactive review
+will be provided and there will be no queries either. If the parameter is
+"YES" then the graphical review mode will be entered after each solution is
+found without any query. If the parameter is "yes" then a query will be
+made after a solution is found and after any log information is written to
+the terminal. One may respond to the query with "no" or "yes" for an
+individual spectrum or "NO" or "YES" for all remaining spectra without
+further queries. For "yes" or "YES" the \fIidentify\fR review mode is
+entered. To exit type 'q'.
+.ih
+EXAMPLES
+1. The following example finds a dispersion solution for the middle column
+of a long slit spectrum of a He-Ne-Ar arc spectrum using all the
+interactive options.
+
+.nf
+ cl> autoid arc0022 6000 6 coord=linelists$henear.dat sec="mid col"
+ AUTOIDENITFY: NOAO/IRAF IRAFX valdes@puppis Thu 15:50:31 25-Jan-96
+ Spectrum # Found Midpoint Dispersion RMS
+ arc0022[50,*] 50 5790. 6.17 0.322
+ arc0022[50,*]: Examine identifications interactively? (yes):
+ arc0022[50,*]: Write results to database? (yes): yes
+.fi
+
+2. The next example shows a non-interactive mode with no queries for
+the middle fiber of an extracted multispec image.
+
+.nf
+ cl> autoid.coordlist="linelists$henear.dat"
+ cl> autoid a0003 5300 3.2 interactive- verbose- dbwrite=YES
+.fi
+.ih
+REVISIONS
+.ls AUTOIDENTIFY V2.11
+This task is new in this version.
+.le
+.ih
+SEE ALSO
+identify, reidentify, aidpars, linelists, center1d, icfit, gtools
+.endhelp
diff --git a/noao/onedspec/doc/bplot.hlp b/noao/onedspec/doc/bplot.hlp
new file mode 100644
index 00000000..f2214b94
--- /dev/null
+++ b/noao/onedspec/doc/bplot.hlp
@@ -0,0 +1,201 @@
+.help bplot Mar92 noao.onedspec
+.ih
+NAME
+bplot -- Plot spectra noninteractively using SPLOT
+.ih
+USAGE
+bplot images [records]
+.ih
+PARAMETERS
+.ls images
+List of images to be plotted. These may be one dimensional, multiaperture,
+long slit, or nonspectral images.
+.le
+.ls records (imred.irs and imred.iids only)
+List of records to be appended to the input image root names when
+using record number extension format. The syntax of this list is comma
+separated record numbers or ranges of record numbers. A range consists of
+two numbers separated by a hyphen. A null list may be used if no record
+number extensions are desired.
+.le
+.ls apertures = ""
+List of apertures/lines/columns to be plotted in each image. If
+\fIapertures\fR is null all of the apertures/lines/columns will be plotted.
+.le
+.ls band = 1
+The band or plane of a three dimensional image to be plotted in each image.
+.le
+.ls graphics = "stdgraph"
+Output graphics device. This may be one of "stdgraph", "stdplot",
+"stdvdm", or the actual device name.
+.le
+.ls cursor = "onedspec$gcurval.dat"
+File(s) containing cursor commands for the SPLOT task.
+The files will be cycled sequentially. If there is more than one file
+usually the number of files will agree with the number of apertures
+for each image since otherwise different cursor/aperture pairings will
+occur. The default is a file containing only the (q)uit command.
+.le
+
+The following parameters are used in response to particular keystrokes.
+In \fBsplot\fR they are query parameters but in \fBbplot\fR they are hidden
+parameters.
+.ls next_image = ""
+In response to 'g' (get next image) this parameter specifies the image.
+.le
+.ls new_image = ""
+In response to 'i' (write current spectrum) this parameter specifies the
+name of a new image to create or existing image to overwrite.
+.le
+.ls overwrite = yes
+Overwrite an existing output image? If set to yes it is possible to write
+back into the input spectrum or to some other existing image. Otherwise
+the user is queried again for a new image name.
+.le
+.ls spec2 = ""
+When adding, subtracting, multiplying, or dividing by a second spectrum
+('+', '-', '*', '/' keys in the 'f' mode) this parameter is used to get
+the name of the second spectrum.
+.le
+.ls constant = 0.
+When adding or multiplying by a constant ('p' or 'm' keys in the 'f' mode)
+the parameter is used to get the constant.
+.le
+.ls wavelength = 0.
+This parameter is used to get a dispersion coordinate value during deblending or
+when changing the dispersion coordinates with 'u'.
+.le
+.ls linelist = ""
+During deblending this parameter is used to get a list of line positions
+and widths.
+.le
+.ls wstart = 0., wend = 0., dw = 0.
+In response to 'p' (convert to a linear wavelength scale) these parameter
+specify the starting wavelength, ending wavelength, and wavelength per pixel.
+.le
+.ls boxsize = 2
+In response to 's' (smooth) this parameter specifies the box size in pixels
+to be used for the boxcar smooth
+.le
+.ih
+DESCRIPTION
+The spectra in the input image list are successively processed by the task
+\fBsplot\fR with input supplied by the cursor parameter and the output sent
+to the specified graphics device. The range of apertures and bands
+specified by \fIapertures\fR and \fIbands\fR will be processed for each
+image. In the \fBiids/irs\fR packages the record extension syntax is used
+with input root names and a record number list. The hidden parameters from
+\fBsplot\fR apply to this task.
+
+The cursor file(s) consists of line(s) of the form:
+
+ [x y 1] key [command]
+
+where x and y are the position of the cursor (may be zero or absent if the
+cursor position is irrelevant) and key is one of the keystrokes understood
+by \fBsplot\fR. If the key is ":" then the \fIcolon\fR command string follows.
+The default cursor file consists of the single line:
+
+ 0 0 1 q
+
+If more than one cursor file is specified they are sequentially assigned to
+each aperture and the list is repeated as needed. This allows the aperture
+to be manipulated in differing ways.
+.ih
+EXAMPLES
+1. To plot all of apertures of the multiaperture spectra indicated by the file
+"nite1.lst" on the default plotter and run in the background:
+
+.nf
+ cl> bplot @nite1.lst graphics=stdplot &
+.fi
+
+2. To preview the plots:
+
+.nf
+ cl> bplot @nite1.lst graphics=stdgraph
+.fi
+
+3. To produce a histogram type plot about Balmer alpha for aperture 5 of
+each spectrum with the IRAF banner suppressed:
+
+.nf
+ cl> type curfile
+ 6555 0 1 a
+ 6570 0 1 a
+ q
+ cl> splot.options="auto hist nosysid"
+ cl> splot.xmin=6555
+ cl> splot.xmax=6570
+ cl> bplot @nite1.lst apertures=5 cursor=curfile
+.fi
+
+4. To produce plots with four spectra per page:
+
+.nf
+ cl> bplot @nite1.lst ... >G nite1.mc
+ cl> gkimosaic nite1.mc dev=stdplot
+.fi
+
+The first command redirects the output of the graphics to the metacode
+file nite1.mc. The task \fBgkimosaic\fR is used to make multiple plots
+per page. Other tasks in the \fBplot\fR package may be used to
+manipulate and redisplay the contents of the metacode file.
+
+5. To plot a list of apertures with a different cursor file for each aperture:
+
+.nf
+ cl> bplot @nite1.lst apertures=3,9,14 cursor=@nite1.cur
+.fi
+
+In this case the file "nite1.cur" is assumed to be a list of
+individual cursor file names, for instance:
+
+.nf
+ cur.03
+ cur.09
+ cur.14
+.fi
+
+that are in one to one correspondence with the range of apertures.
+.ih
+REVISIONS
+.ls BPLOT V2.10.3
+The query parameters from SPLOT were added as hidden parameters in BPLOT
+to allow use of those keys in a batch way.
+.le
+.ls BPLOT V2.10
+The \fIapertures\fR and \fIband\fR parameters been added to select
+apertures from multiple spectra and long slit images, and bands from 3D
+images. Since the task is a script calling \fBsplot\fR, the many revisions
+to that task also apply. The version in the \fBirs/iids\fR packages
+selects spectra using the record number extension syntax.
+.le
+.ih
+BUGS
+The cursor file command keystrokes cannot include any of the cursor
+mode (CAPITALIZED) keys. This results from the implementation of
+the cursor mode commands as external to both BPLOT and SPLOT.
+
+When first entered, SPLOT will always display an initial plot. BPLOT
+calls SPLOT once for each aperture in each image and thus produces
+N(apertures)*N(images) initial plots. The plots are not optional because
+of the possible confusion a blank screen might cause an inexperienced
+user. If the initial plots are unwanted they must be edited out of the
+graphics stream. This can be done as follows, by directing the
+graphics output of BPLOT to a metacode file and then using GKIEXTRACT
+to remove only the desired plots from the metacode file:
+
+.nf
+ cl> bplot @nite1.lst cursor=curfile >G nite1.mc
+ cl> gkiextract nite1.mc 2x2 | gkimosaic dev=stdplot
+.fi
+
+This assumes that curfile is designed to produce only one plot in
+addition to the non-optional initial plot. In this case there will be
+two plots per aperture per image and we extract every other plot starting
+with the second (as encoded in the range string: "2x2").
+.ih
+SEE ALSO
+splot, specplot, slist, gkiextract, gkimosaic, implot, graph, ranges
+.endhelp
diff --git a/noao/onedspec/doc/calibrate.hlp b/noao/onedspec/doc/calibrate.hlp
new file mode 100644
index 00000000..cf68ac29
--- /dev/null
+++ b/noao/onedspec/doc/calibrate.hlp
@@ -0,0 +1,195 @@
+.help calibrate Mar93 noao.onedspec
+.ih
+NAME
+calibrate -- Apply extinction corrections and flux calibrations
+.ih
+USAGE
+calibrate input output [records]
+.ih
+PARAMETERS
+.ls input
+List of input spectra to be calibrated. When using record format
+extensions the root names are specified, otherwise full image names
+are used.
+.le
+.ls output
+List of calibrated spectra. If no output list is specified or if the
+output name is the same as the input name then the calibrated spectra
+replace the input spectra. When using record format extensions the output
+names consist of root names to which the appropriate record number
+extension is added. The record number extension will be the same as the
+input record number extension. The output spectra are coerced to have
+real datatype pixels regardless of the pixel type.
+.le
+.ls records (imred.irs and imred.iids only)
+The set of record number extensions to be applied to each input and output
+root name when using record number extension format. The syntax consists
+of comma separated numbers or ranges of numbers. A range consists of
+two numbers separated by a hyphen. This parameter is not queried
+when record number formats are not used.
+.le
+.ls extinct = yes
+Apply extinction correction if a spectrum has not been previously
+corrected? When applying an extinction correction, an extinction file
+is required.
+.le
+.ls flux = yes
+Apply a flux calibration if a spectrum has not been previously calibrated?
+When applying a flux calibration, sensitivity spectra are required.
+.le
+.ls extinction = <no default>
+Extinction file for the observation. Standard extinction files
+are available in the "onedstds$" directory.
+.le
+.ls observatory = ")_.observatory"
+Observatory at which the spectra were obtained if not specified in the
+image header by the keyword OBSERVAT. The default is a redirection to the
+package parameter of the same name. The observatory may be one of the
+observatories in the observatory database, "observatory" to select the
+observatory defined by the environment variable "observatory" or the
+parameter \fBobservatory.observatory\fR, or "obspars" to select the current
+parameters in the \fBobservatory\fR task. See \fBobservatory\fR for
+additional information.
+.le
+.ls ignoreaps = no
+Ignore aperture numbers and apply a single flux calibration to all
+apertures? Normally multiaperture instruments have separate sensitivity
+functions for each aperture while long slit or Fabry-Perot data use a
+single sensitivity function where the apertures are to be ignored. The
+sensitivity spectra are obtained by adding the aperture number as an
+extension to the sensitivity spectrum root name. When apertures are
+ignored the specified sensitivity spectrum name is used without adding an
+extension and applied to all input apertures.
+.le
+.ls sensitivity = "sens"
+The root name for the sensitivity spectra produced by \fBsensfunc\fR.
+Normally with multiaperture instruments, \fBsensfunc\fR will produce a
+spectrum appropriate to each aperture with an aperture number extension.
+If the apertures are ignored (\fIignoreaps\fR = yes) then the sensitivity
+spectrum specified is used for all apertures and no aperture number is
+appended automatically.
+.le
+.ls fnu = no
+The default calibration is into units of flux per unit wavelength (F-lambda).
+If \fIfnu\fR = yes then the calibrated spectrum will be in units of
+flux per unit frequency (F-nu).
+.le
+.ls airmass, exptime
+If the airmass and exposure time are not in the header nor can they be
+determined from other keywords in the header then these query parameters
+are used to request the airmass and exposure time. The values are updated
+in the input and output images.
+.le
+.ih
+DESCRIPTION
+The input spectra are corrected for extinction and calibrated to a flux
+scale using sensitivity spectra produced by the task \fBsensfunc\fR.
+One or both calibrations may be performed by selecting the appropriate
+parameter flags. It is an error if no calibration is specified. Normally
+the spectra should be extinction corrected if also flux calibrating.
+The image header keywords DC-FLAG (or the dispersion type field in the
+"multispec" world coordinate system), EX-FLAG, and CA-FLAG are checked for
+dispersion solution (required), previous extinction correction, and
+previous flux calibration. If previously calibrated the spectrum is
+skipped and a new output image is not created.
+
+The input spectra are specified by a list of root names (when using record
+extension format) or full image names. The output calibrated spectra may
+replace the input spectra if no output spectra list is specified or if the
+output name is the same as the input name. When using record number
+extensions the output spectra will have the same extensions applied to the
+root names as those used for the input spectra.
+
+When applying an extinction correction the AIRMASS keyword is sought.
+If the keyword is not present then the airmass at the time defined
+by the other header keywords is computed using the
+latitude of the observatory and observation parameters in the image
+header. The observatory is first determined from the image under the
+keyword OBSERVAT. If absent the observatory specified by the task
+parameter "observatory" is used. See \fBobservatory\fR for further
+details of the observatory database. If the air mass cannot be
+determined an error results. Currently a single airmass is used
+and no correction for changing extinction during the observation is
+made and adjustment to the middle of the exposure. The task
+\fBsetairmass\fR provides a correction for the exposure time to compute
+an effective air mass. Running this task before calibration is
+recommended.
+
+If the airmass is not in the header and cannot be computed then
+the user is queried for a value. The value entered is then
+recorded in both the input and output image headers. Also if
+the exposure time is not found then it is also queried and
+recorded in the image headers.
+
+The extinction correction is given by the factor
+
+ 10. ** (0.4 * airmass * extinction)
+
+where the extinction is the value interpolated from the specified
+extinction file for the wavelength of each pixel. After extinction
+correction the EX-FLAG is set to 0.
+
+When applying a flux calibration the spectra are divided by the
+aperture sensitivity which is represented by a spectrum produced by
+the task \fBsensfunc\fR. The sensitivity spectrum is in units of:
+
+ 2.5 * Log10 [counts/sec/Ang / ergs/cm2/sec/Ang].
+
+A new spectrum is created in "F-lambda" units - ergs/cm2/sec/Angstrom
+or "F-nu" units - ergs/cm2/sec/Hz. The sensitivity must span the range of
+wavelengths in the spectrum and interpolation is used if the wavelength
+coordinates are not identical. If some pixels in the spectrum being
+calibrated fall outside the wavelength range of the sensitivity function
+spectrum a warning message giving the number of pixels outside the
+range. In this case the sensitivity value for the nearest wavelength
+in the sensitivity function is used.
+
+Multiaperture instruments typically have
+a separate aperture sensitivity function for each aperture. The appropriate
+sensitivity function for each input spectrum is selected based on the
+spectrum's aperture by appending this number to the root sensitivity function
+spectrum name. If the \fIignoreaps\fR flag is set, however, the aperture
+number relation is ignored and the single sensitivity spectrum (without
+extension) is applied.
+.ih
+EXAMPLES
+1. To flux calibrates a series of spectra replacing the input spectra by
+the calibrated spectra:
+
+ cl> calibrate nite1 ""
+
+2. To only extinction correct echelle spectra:
+
+ cl> calibrate ccd*.ec.imh new//ccd*.ec.imh flux-
+
+3. To flux calibrate a long slit spectrum:
+
+.nf
+ cl> dispaxis = 2
+ cl> calibrate obj.imh fcobj.imh
+.fi
+.ih
+REVISIONS
+.ls CALIBRATE V2.10.3
+This task was revised to operate on 2D and 3D spatial spectra; i.e. long
+slit and Fabry-Perot data cubes. This task now includes the functionality
+previously found in \fBlongslit.extinction\fR and \fBlongslit.fluxcalib\fR.
+
+A query for the airmass and exposure time is now made if the information
+is not in the header and cannot be computed from other header keywords.
+.le
+.ls CALIBRATE V2.10
+This task was revised to operate on nonlinear dispersion corrected spectra
+and 3D images (the \fBapextract\fR "extras"). The aperture selection
+parameter was eliminated (since the header structure does not allow mixing
+calibrated and uncalibrated spectra) and the latitude parameter was
+replaced by the observatory parameter. The observatory mechanism insures
+that if the observatory latitude is needed for computing an airmass and the
+observatory is specified in the image header the correct calibration will
+be applied. The record format syntax is available in the \fBirs/iids\fR
+packages. The output spectra are coerced to have real pixel datatype.
+.le
+.ih
+SEE ALSO
+setairmass, standard, sensfunc, observatory, continuum
+.endhelp
diff --git a/noao/onedspec/doc/continuum.hlp b/noao/onedspec/doc/continuum.hlp
new file mode 100644
index 00000000..6bb4e05e
--- /dev/null
+++ b/noao/onedspec/doc/continuum.hlp
@@ -0,0 +1,263 @@
+.help continuum Mar92 noao.onedspec
+.ih
+NAME
+continuum -- Continuum normalize spectra
+.ih
+USAGE
+continuum input output
+.ih
+PARAMETERS
+.ls input
+Input spectra to be continuum normalized. These may be any combination
+of echelle, multiaperture, one dimensional, long slit, and spectral
+cube images.
+.le
+.ls output
+Output continuum normalized spectra. The number of output spectra must
+match the number of input spectra. \fBOutput\fR may be omitted if
+\fBlistonly\fR is yes.
+.le
+.ls lines = "*", bands = "1"
+A range specifications for the image lines and bands to be fit. Unspecified
+lines and bands will be copied from the original. If the value is "*", all of
+the currently unprocessed lines or bands will be fit. A range consists of
+a first line number and a last line number separated by a hyphen. A
+single line number may also be a range and multiple ranges may be
+separated by commas.
+.le
+.ls type = "ratio"
+Type of output spectra. The choices are "fit" for the fitted function,
+"ratio" for the ratio of the input spectra to the fit, "difference" for
+the difference between the input spectra and the fit, and "data" for
+the data minus any rejected points replaced by the fit.
+.le
+.ls replace = no
+Replace rejected points by the fit in the difference, ratio, and
+data output types?
+.le
+.ls wavescale = yes
+Wavelength scale the X axis of the plot? This option requires that the
+spectra be wavelength calibrated. If \fBwavescale\fR is no, the plots
+will be in "channel" (pixel) space.
+.le
+.ls logscale = no
+Take the log (base 10) of both axes? This can be used when \fBlistonly\fR
+is yes to measure the exponent of the slope of the continuum.
+.le
+.ls override = no
+Override previously normalized spectra? If \fBoverride\fR is yes and
+\fBinteractive\fR is yes, the user will be prompted before each order is
+refit. If \fBoverride\fR is no, previously fit spectra are silently
+skipped.
+.le
+.ls listonly = no
+Don't modify any images? If \fBlistonly\fR is yes, the \fBoutput\fR
+image list may be skipped.
+.le
+.ls logfiles = "logfile"
+List of log files to which to write the power series coefficients. If
+\fBlogfiles\fR = NULL (""), the coefficients will not be calculated.
+.le
+.ls interactive = yes
+Perform the fit interactively using the icfit commands? This will allow
+the parameters for each spectrum to be adjusted independently. A separate
+set of the fit parameters (below) will be used for each spectrum and any
+interactive changes to the parameters for a specific spectrum will be
+remembered when that spectrum is fit in the next image.
+.le
+.ls sample = "*"
+The ranges of X values to be used in the continuum fits. The units will vary
+depending on the setting of the \fBwavescale\fR and \fBlogscale\fR
+parameters. The default units are in wavelength if the spectra have
+been dispersion corrected.
+.le
+.ls naverage = 1
+Number of sample points to combined to create a fitting point.
+A positive value specifies an average and a negative value specifies
+a median.
+.le
+.ls function = spline3
+Function to be fit to the spectra. The functions are
+"legendre" (legendre polynomial), "chebyshev" (chebyshev polynomial),
+"spline1" (linear spline), and "spline3" (cubic spline). The functions
+may be abbreviated. The power series coefficients can only be
+calculated if \fBfunction\fR is "legendre" or "chebyshev".
+.le
+.ls order = 1
+The order of the polynomials or the number of spline pieces.
+.le
+.ls low_reject = 2., high_reject = 0.
+Rejection limits below and above the fit in units of the residual sigma.
+.le
+.ls niterate = 10
+Number of rejection iterations.
+.le
+.ls grow = 1.
+When a pixel is rejected, pixels within this distance of the rejected pixel
+are also rejected.
+.le
+.ls markrej = yes
+Mark rejected points? If there are many rejected points it might be
+desired to not mark rejected points.
+.le
+.ls graphics = "stdgraph"
+Graphics output device for interactive graphics.
+.le
+.ls cursor = ""
+Graphics cursor input.
+.le
+.ih
+DESCRIPTION
+A one dimensional function is fit to the continuum of spectra in a list of
+echelle, multispec, or onedspec format images and then divided into the
+spectrum to produce continuum normalized spectra. The first two formats
+will normalize the spectra or orders (i.e. the lines) in each image. In
+this description the term "spectrum" will refer to a line (in whatever
+band) of an image while "image" will refer to all spectra in an image. The
+parameters of the fit may vary from spectrum to spectrum within images and
+between images. The fitted function may be a legendre polynomial,
+chebyshev polynomial, linear spline, or cubic spline of a given order or
+number of spline pieces. The output image is of pixel type real.
+
+The line/band numbers (for two/three dimensional images) are written to a
+list of previously processed lines in the header keywords \fISFIT\fR and
+\fISFITB\fR of the output image. A subsequent invocation of SFIT will only
+process those requested spectra that are not in this list. This ensures
+that even if the output image is the same as the input image that no
+spectra will be processed twice and permits an easy exit from the task in
+the midst of processing many spectra without losing any work or requiring
+detailed notes.
+
+The points to be fit in each spectrum are determined by
+selecting a sample of X values specified by the parameter \fIsample\fR
+and taking either the average or median of the number of points
+specified by the parameter \fInaverage\fR. The type of averaging is
+selected by the sign of the parameter with positive values indicating
+averaging, and the number of points is selected by the absolute value
+of the parameter. The sample units will vary depending on the settings
+of the \fBwavescale\fR and the \fBlogscale\fR parameters. Note that a
+sample that is specified in wavelength units may be entirely outside
+the domain of the data (in pixels) if some of the spectra are not
+dispersion corrected. The syntax of the sample specification is a comma
+separated, colon delimited list similar to the image section notation.
+For example, the \fBsample\fR, "6550:6555,6570:6575" might be used to
+fit the continuum near H-alpha.
+
+If \fIlow_reject\fR and/or \fIhigh_reject\fR are greater than zero the
+sigma of the residuals between the fitted points and the fitted
+function is computed and those points whose residuals are less than
+\fI-low_reject\fR * sigma and greater than \fIhigh_reject\fR * sigma
+are excluded from the fit. Points within a distance of \fIgrow\fR
+pixels of a rejected pixel are also excluded from the fit. The
+function is then refit without the rejected points. This rejection
+procedure may be iterated a number of times given by the parameter
+\fIniterate\fR. This is how the continuum is determined.
+
+If \fIreplace\fR is set then any rejected points from the fitting
+are replaced by the fit in the data before outputing the difference,
+ratio, or data. For example with replacing the difference will
+be zero at the rejected points and the data output will be cleaned
+of deviant points.
+
+A range specification is used to select the \fIlines\fR and \fIbands\fR to be
+fit. These parameters may either be specified with the same syntax as the
+\fBsample\fR parameter, or with the "hyphen" syntax used elsewhere in
+IRAF. Note that a NULL range for \fBlines/bands\fR expands to \fBno\fR
+lines, not to all lines. An asterisk (*) should be used to represent a
+range of all of the image lines/bands. The fitting parameters (\fIsample,
+naverage, function, order, low_reject, high_reject, niterate, grow\fR)
+may be adjusted interactively if the parameter \fIinteractive\fR is
+yes. The fitting is performed with the \fBicfit\fR package. The
+cursor mode commands for this package are described in a separate help
+entry under "icfit". Separate copies of the fitting parameters are
+maintained for each line so that interactive changes to the parameter
+defaults will be remembered from image to image.
+.ih
+PROMPTS
+If several images or lines/bands are specified, the user is asked whether
+to perform an interactive fit for each spectrum. The response
+may be \fByes, no, skip, YES, NO\fR or \fBSKIP\fR. The meaning of each
+response is:
+
+.nf
+ yes - Fit the next spectrum interactively.
+ no - Fit the next spectrum non-interactively.
+ skip - Skip the next spectrum in this image.
+
+ YES - Interactively fit all of the spectra of
+ all of the images with no further prompts.
+ NO Non-interactively fit all chosen spectra of all images.
+ SKIP - This will produce a second prompt, "Skip what?",
+ with the choices:
+
+ spectrum - skip this spectrum in all images
+ image - skip the rest of the current image
+ all - \fBexit\fR the program
+ This will \fBunlearn\fR the fit parameters
+ for all spectra!
+ cancel - return to the main prompt
+.fi
+.ih
+EXAMPLES
+1. To normalize all orders of the echelle spectrum for hd221170
+
+ cl> continuum hd221170.ec nhd221170.ec type=ratio
+
+Each order of the spectrum is graphed and the interactive options for
+setting and fitting the continuum are available. The important
+parameters are low_rejection (for an absorption spectrum), the function
+type, and the order of the function; these fit parameters are
+originally set to the defaults in the \fBcontinuum\fR parameter file. A
+'?' will display a menu of cursor key options. Exiting with 'q' will
+update the output normalized order for the current image and proceed to
+the next order or image.
+
+The parameters of the fit for each order are initialized to the current
+values the first time that the order is fit. In subsequent images, the
+parameters for a order are set to the values from the previous image.
+The first time an order is fit, the sample region is reset to the
+entire order. Deleted points are ALWAYS forgotten from order to order
+and image to image.
+
+2. To do several images at the same time
+
+ cl> continuum spec*.imh c//spec*.imh
+
+Note how the image template concatenation operator is used to construct
+the output list of spectra. Alternatively:
+
+ cl> continuum @inlist @outlist
+
+where the two list files could have been created with the sections
+command or by editing.
+
+3. To measure the power law slope of the continuum (fluxed data)
+
+ cl> continuum uv.* type=ratio logscale+ listonly+ fun=leg order=2
+.ih
+REVISIONS
+.ls CONTINUUM V2.10.4
+The task was expanded to include fitting specified bands in 3D multispec
+spectra.
+
+The task was expanded to include long slit and spectral cube data.
+.le
+.ls CONTINUUM V2.10
+This task was changed from a script based on \fBimages.fit1d\fR to a
+task based on \fBsfit\fR. This provides for individual independent
+continuum fitting in multiple spectra images and for additional
+flexibility and record keeping. The parameters have been largely
+changed.
+.le
+.ih
+BUGS
+The errors are not listed for the power series coefficients.
+
+Spectra that are updated when \fBlogscale\fR is yes are written with a
+linear wavelength scale, but with a log normalized data value.
+
+Selection by aperture number is not supported.
+.ih
+SEE ALSO
+sfit, fit1d, icfit, ranges
+.endhelp
diff --git a/noao/onedspec/doc/deredden.hlp b/noao/onedspec/doc/deredden.hlp
new file mode 100644
index 00000000..862c441c
--- /dev/null
+++ b/noao/onedspec/doc/deredden.hlp
@@ -0,0 +1,201 @@
+.help deredden Feb94 noao.onedspec
+.ih
+NAME
+deredden -- Apply interstellar reddening correction
+.ih
+USAGE
+deredden input output [records] value
+.ih
+PARAMETERS
+.ls input
+List of input spectra to be dereddened. When using record
+format extensions the root names are specified, otherwise full
+image names are used.
+.le
+.ls output
+List of derreddened spectra. If no output list is specified then
+the input spectra are modified. Also the output name may be
+the same as the input name to replace the input spectra by the
+calibrated spectra. When using record format extensions the
+output names consist of root names to which the appropriate
+record number extension is added. The record number extension
+will be the same as the input record number extension.
+.le
+.ls records (imred.irs and imred.iids only)
+The set of record number extensions to be applied to each input
+and output root name when using record number extension
+format. The syntax consists of comma separated numbers or
+ranges of numbers. A range consists of two numbers separated
+by a hyphen. This parameter is not queried when record number
+formats are not used.
+.le
+.ls value
+Extinction parameter value as selected by the type parameter.
+This value may be a visual extinction, A(V), the color excess between
+B and V, E(B-V), or the logarithmic H beta extinction.
+These quantities are discussed further below.
+.le
+.ls R = 3.1
+The ratio of extinction at V, A(V), to color excess between B and V, E(B-V).
+.le
+.ls type = "E(B-V)"
+The type of extinction parameter used. The values may be:
+.ls A(V)
+The absolute extinction at the V band at 5550 Angstroms.
+.le
+.ls E(B-V)
+The color excess between the B and V bands.
+.le
+.ls c
+The logarithmic H beta extinction.
+.le
+.le
+.ls apertures = ""
+List of apertures to be selected from input one dimensional spectra
+to be calibrated. If no list is specified then all apertures are
+corrected. The syntax is the same as the record number
+extensions. This parameter is ignored for N-dimensional spatial
+spectra such as calibrated long slit and Fabry-Perot data.
+.le
+.ls override = no, uncorrect = yes
+If a spectrum has been previously corrected it will contain the header
+parameter DEREDDEN. If this parameter is present and the override
+parameter is no then a warning will be issued and no further correction
+will be applied. The override parameter permits overriding this check. If
+overriding a previous correction the \fIuncorrect\fR parameter determines
+whether the spectra are first uncorrected to the original values before
+applying the new correction. If \fIuncorrect\fR is yes then the image
+header DEREDDEN parameter will refer to a correction from the original data
+while if it is no then the new correction is differential and the keyword
+will only reflect the last correction. When correcting individual spectra
+separately in a multispectra image with different extinction parameters the
+uncorrect parameter should be no.
+.le
+.ih
+DESCRIPTION
+The input spectra are corrected for interstellar extinction, or
+reddening, using the empirical selective extinction function of
+Cardelli, Clayton, and Mathis, \fBApJ 345:245\fR, 1989, (CCM).
+The function is defined over the range 0.3-10 inverse microns
+or 100-3333 nanometers. If the input data extend outside this
+range an error message will be produced.
+
+The extinction function requires two parameters, the absolute extinction at
+5550A, A(V), and the ratio, R(V), of this extinction to the color excess
+between 4350A and 5550A, E(B-V).
+
+One of the input task parameters is R(V). If it is not known one
+may use the default value of 3.1 typical of the average
+interstellar extinction. The second input parameter is chosen by
+the parameter \fItype\fR which may take the values "A(V)", "E(B-V)", or
+"c". The value of the parameter is specified by the parameter
+\fIvalue\fR.
+
+If A(V) is used then the CCM function can be directly evaluated. If
+E(B-V) is used then A(V) is derived by:
+
+.nf
+(1) A(V) = R(V) * E(B-V)
+.fi
+
+For planetary nebula studies the logarithmic extinction at H beta,
+denoted as c, is often determined instead of E(B-V). If this type
+of input is chosen then A(V) is derived by:
+
+.nf
+(2) A(V) = R(V) * c * (0.61 + 0.024 * c).
+.fi
+
+This relation is based on the relation betwen E(B-V) and c computed
+by Kaler and Lutz, \fBPASP 97:700\fR, 1985 to include corrections between
+the monochromatic parameter c and the broadband parameter E(B-V).
+In particular the function is a least squares fit to the values of
+c and E(B-V) in Table III of the form:
+
+.nf
+(3) E(B-V) = c * (A + B * c)
+.fi
+
+The input spectra are specified by a list of root names (when using record
+extension format) or full image names. They are required to be dispersion
+corrected (DC-FLAG >= 0) and not previously corrected (DEREDDEN absent).
+Spectra not satisfying these requirements are skipped with a warning. The
+DEREDDEN flag may be overridden with the \fIoverride\fR parameter. This
+may be done if different extinction parameters are required for different
+spectra in the same multiple spectrum image or if a new correction is
+to be applied. The \fIuncorrect\fR parameter determines whether the
+previous correction is removed so that the final correction is relative
+to the original data or if the new correction is differential on the
+previous correction. Note that if applying separate corrections to
+different spectra in a single multispectral image then override should
+be yes and uncorrect should be no.
+
+A subset of apertures to be corrected may be selected from one dimensional
+spectra with the \fIapertures\fR parameter. Long slit or other higher
+dimensional spatially sampled spectra are treated as a unit. The output
+calibrated spectra may replace the input spectra if no output spectra list
+is specified or if the output name is the same as the input name. When
+using record number extensions the output spectra will have the same
+extensions applied to the root names as those used for the input spectra.
+
+Note that by specifying a negative extinction parameter this task may
+be used to add interstellar extinction.
+.ih
+EXAMPLES
+1. To deredden a spectrum with an extinction of 1.2 magnitudes at V:
+
+.nf
+ cl> deredden obj1.ms drobj1.ms 1.2 type=A
+.fi
+
+2. To deredden a spectrum in place with a color excess of 0.65 and
+and R(V) value of 4.5:
+
+.nf
+ cl> deredden obj2.ms obj2.ms R=4.5
+ E(B-V): .65
+.fi
+
+3. To deredden a series of IRS planetary nebula spectra using the
+H beta extinction in the irs package:
+
+.nf
+ cl> deredden pn12 drpn12 1-5,12-14 type=c
+ c: 1.05
+.fi
+
+4. To redden a spectrum:
+
+.nf
+ cl> deredden artspec artspec -1.2 type=A
+.fi
+
+5. To deredden a long slit or Fabry-Perot spectrum either DISPAXIS
+must be in the image header or be specified in the package parameters.
+The summing parameters are ignored.
+
+.nf
+ cl> deredden obj1 drobj1 1.2 type=A
+.fi
+.ih
+REVISIONS
+.ls DEREDDEN V2.10.3
+Extended to operate on two and three dimensional spatial spectra such as
+calibrated long slit and Fabry-Perot data.
+
+An option was added to allow a previous correction to be undone in order
+to keep the DEREDDEN information accurate relative to the original
+data.
+.le
+.ls DEREDDEN V2.10
+This task is new.
+.le
+.ih
+NOTES
+Since there can be only one deredding flag in multispectral images
+one needs to override the flag if different spectra require different
+corrections and then only the last correction will be recorded.
+.ih
+SEE ALSO
+calibrate
+.endhelp
diff --git a/noao/onedspec/doc/dispcor.hlp b/noao/onedspec/doc/dispcor.hlp
new file mode 100644
index 00000000..9e916e70
--- /dev/null
+++ b/noao/onedspec/doc/dispcor.hlp
@@ -0,0 +1,497 @@
+.help dispcor Oct92 noao.onedspec
+.ih
+NAME
+dispcor -- Dispersion correct and resample spectra
+.ih
+USAGE
+dispcor input output [records]
+.ih
+PARAMETERS
+.ls input
+List of input spectra or root names to be dispersion corrected. These may
+be echelle or non-echelle spectra, the task will determine which from the
+database dispersion functions. When using the record number extension
+format, record number extensions will be appended to each root name in the
+list.
+.le
+.ls output
+List of dispersion corrected output spectra or root names. When using the
+record number extension format, record number extensions will be appended
+to each root name in the list. The output extension will be the same as
+the input extension. If "no" output list is specified then the output
+spectrum will replace the input spectrum after dispersion correction.
+.le
+.ls records (imred.irs and imred.iids only)
+List of records or ranges of records to be appended to the input and output
+root names when using record number extension format. The syntax of this
+list is comma separated record numbers or ranges of record numbers. A
+range consists of two numbers separated by a hyphen. A null list may be
+used if no record number extensions are desired. This is a positional
+query parameter only if the record format is specified.
+.le
+.ls linearize = yes
+Interpolate the spectra to a linear dispersion sampling? If yes, the
+spectra will be interpolated to a linear or log linear sampling using
+the linear dispersion parameters specified by other parameters. If
+no, the nonlinear dispersion function(s) from the dispersion function
+database are assigned to the input image world coordinate system
+and the spectral data are not interpolated.
+.le
+.ls database = "database"
+Database containing dispersion solutions created by \fBidentify\fR or
+\fBecidentify\fR. If the spectra have been previous dispersion corrected
+this parameter is ignored unless a new reference spectra are defined.
+.le
+.ls table = ""
+Wavelength coordinate table or reference image. Elements in this optional
+table or reference image override the wavelength coordinates given below
+for specified apertures. See the DISCUSSION for additional information.
+.le
+.ls w1 = INDEF, w2 = INDEF, dw = INDEF, nw = INDEF
+The starting wavelength, ending wavelength, wavelength interval per pixel,
+and the number of pixels in the output spectra. Any combination of these
+parameters may be used to restrict the wavelength coordinates of the output
+spectra. If two or more have the value INDEF then suitable defaults based
+on the number of input pixels and the wavelength range of the reference
+dispersion solutions are used. These defaults may either come from all
+spectra, all spectra of the same aperture, or individually for each
+spectrum depending on the values of the \fIglobal\fR and \fIsamedisp\fR
+parameters. Note that these parameters are specified in linear units even
+if a logarithmic wavelength scale is selected. The conversion between
+linear and logarithmic intervals between pixels is given below. These
+values may be overridden for specified apertures by a wavelength table or
+reference image. Otherwise these values apply to all apertures.
+.le
+.ls log = no
+Transform to linear logarithmic wavelength coordinates? Linear logarithmic
+wavelength coordinates have wavelength intervals which are constant
+in the logarithm (base 10) of the wavelength. Note that if conserving flux
+this will change the flux units to flux per log lambda interval.
+Note that if the input spectra are in log sampling then \fIlog\fR=no will
+resample back to linear sampling and \fIlog\fR=yes will resample keeping
+the output spectra in log sampling.
+.le
+.ls flux = yes
+Conserve the total flux during interpolation rather than the flux density?
+If "no", the output spectrum is average of the input spectrum across each
+output wavelength coordinate. This conserves flux density. If "yes" the
+input spectrum is integrated over the extent of each output pixel. This
+conserves the total flux. Note that in this case units of the flux will
+change; for example rebinning to logarithmic wavelengths will produce flux
+per log lambda. For flux calibrated data you most likely would not want to
+conserve flux.
+.le
+.ls blank = 0.
+Output value corresponding to points outside the range of the input
+data. In other words, the out of bounds value. This only has an
+effect when linearizing and the output spectral coordinates extend
+beyond the input spectral range.
+.le
+.ls samedisp = no
+Use the same dispersion parameters for all apertures? If yes then all
+apertures in a single image will have the same dispersion parameters.
+If the \fIglobal\fR parameter is all selected then all spectra in all
+images will have the same dispersion paramters. This parameter
+would not normally be used with echelle spectra where each order
+has a different wavelength coverage.
+.le
+.ls global = no
+Apply global wavelength defaults? Defaults for the INDEF wavelength
+coordinate parameters are determined if two or less of the wavelength
+parameters are specified. The defaults are based on the number of
+pixels and the wavelengths of the first and last pixel as given by the
+dispersion solution. If this parameter is "no" this is done
+independently for each input spectrum. If this parameter is "yes"
+then the maximum number of pixels and the minimum and maximum
+wavelengths of all the input spectra or those of the same aperture are
+used to provide defaults for the spectra. The parameter
+\fIsamedisp\fR determines whether the global coordinates are over all
+spectra or only those with the same aperture number. The global option
+is used to have all the dispersion corrected spectra have the same
+wavelength coordinates without actually specifying the wavelength
+parameters.
+.le
+.ls ignoreaps = no
+If a reference dispersion solution is not found for an aperture
+use the first reference dispersion solution and ignore the aperture
+number? If not ignoring the apertures all spectra must have a matching
+aperture for the dispersion solution and the task aborts if this is
+not the case. Ignoring the apertures avoids this abort and instead
+the first dispersion solution is used. Note this parameter does not
+mean ignore matches between reference and spectrum aperture numbers
+but only ignore the aperture number if no matching reference is
+found.
+
+Also if a reference table or image is given and \fIignoreaps\fR=yes
+then the default dispersion parameters for any aperture not defined
+by the table or image will be that of the first defined aperture.
+This can still be overridden by giving explicit values for
+\fIw1, w2, dw\fR and \fInw\fR.
+.le
+.ls confirm = no
+Confirm the wavelength parameters for each spectrum? If \fIyes\fR
+the wavelength parameters will be printed and the user will be asked
+whether to accept them. If the parameters are not acceptable the
+user will be queried for new values. The confirmation and parameter
+changes are repeated until an acceptable set of parameters is obtained.
+When the \fIglobal\fR parameter is \fIyes\fR changes to the wavelength
+parameters will remain in effect until changed again.
+.le
+.ls listonly = no
+List the dispersion coordinates only? If set then the dispersion coordinates
+are listed but the spectra are not dispersion corrected. This may be used
+to determine what the default wavelengths would be based on the dispersion
+solutions.
+.le
+.ls verbose = yes
+Print the dispersion function and coordinate assignments?
+.le
+.ls logfile = ""
+Log file for recording the dispersion correction operations. If no file
+name is given then no log information is recorded.
+.le
+.ih
+DESCRIPTION
+The dispersion coordinate systems of the input spectra are set or changed
+in the output spectra. The output spectra may be the same as the input
+spectra if no output spectra are specified or the output name is the
+same as the input name. The input and output spectra are specified
+by image templates or lists. In the \fBirs/iids\fR packages the
+input and output spectra are specified as root names and the record
+numbers are specified by the \fIrecord\fR parameter. The records are
+given as a set of comma separate single numbers or ranges of hyphen
+separated numbers. If no records are specified then the input and output
+images are assumed to be full names.
+
+The dispersion coordinate system is defined either in the image header or
+by dispersion functions in the specified database. To use reference
+spectra dispersion functions they must first be assigned to the image with
+\fBidentify (reidentify)\fR, \fBecidentify (ecreidentify)\fR,
+\fBrefspectra\fR, or \fBhedit\fR. These tasks define the image header
+keywords REFSPEC1, REFSPEC2, REFSHFT1, and REFSHFT2. The test which
+determines whether to use the current dispersion coordinate system or
+reference spectra dispersion solutions is the presence of the REFSPEC1
+keyword. Since it is an error to apply a dispersion function to data which
+have already been dispersion corrected the any dispersion function keywords
+are deleted after use and a record of them entered in sequential image
+header keywords beginning with DCLOG.
+
+Dispersion functions are specified by one or both of the reference spectrum
+image header keywords REFSPEC1 and REFSPEC2 containing the name of
+calibration spectra with dispersion function solutions (either echelle
+dispersion functions from \fBecidentify\fR or non-echelle dispersion
+functions from \fBidentify\fR) in the database. There must be a dispersion
+function for each aperture in the input spectrum unless the \fIignoreaps\fR
+flag is set. If the flag is not set the task will abort if a matching
+aperture is not found while if it is set spectra without a matching
+aperture in the reference dispersion solutions will use the first
+dispersion solution. Note that aperture number matching is done in both
+cases and the \fIignoreaps\fR parameter only applies to non-matching
+spectra. The common situation for using the \fIignoreaps\fR option is when
+there is a single reference dispersion solution which is to be applied to a
+number of spectra with different aperture numbers; hence effectively
+ignoring the reference spectrum aperture number.
+
+If two reference spectra are specified the names may be followed by a
+weighting factor (assumed to be 1 if missing). The wavelength of a pixel
+is then the weighted averge of the wavelengths of the two dispersion
+functions. The task \fBrefspectra\fR provides a number of ways to assign
+reference spectra. Note, however, that these assignments may be made
+directly using the task \fBhedit\fR or with some other task or script if
+none of the methods are suitable. Also note that \fBidentify\fR and
+\fBreidentify\fR add the REFSPEC1 keyword refering to the image itself
+when a database entry is written.
+
+In addition to the one or two reference dispersion functions for each input
+aperture there may also be image header keywords REFSHFT1 and REFSHFT2
+specifying reference spectra whose dispersion function zero point shifts
+(the "shift" parameter in the database files) are to be applied to the
+reference dispersion functions. The shifts from REFSHFT1 will be applied
+to the dispersion functions from REFSPEC1 and similarly for the second
+dispersion functions. The reference shifts need not be present for every
+aperture in a multispectrum image. By default the mean shift from all the
+reference apertures having a zero point shift is applied to all the
+reference dispersion functions. If the REFSHFT keyword has the modifier
+word "nearest" following the spectrum name then the shift from the nearest
+aperture in spatial position (from the aperture extraction limits in the
+original 2D spectrum as recorded in the 6th and 7th fields of the APNUM
+keywords) is used for a particular input aperture. If the modifier word is
+"interp" then the nearest two apertures are used to interpolate a zero
+point shift spatially.
+
+The purpose of the reference shift keywords is to apply a wavelength zero
+point correction to the reference dispersion functions determined from
+separate arc calibration observations using a few apertures taken at the
+same time as object observations. For example, consider multifiber
+observations in which one or more fibers are assigned to arc lamps at the
+same time the other fibers are used to observe various objects. The basic
+dispersion reference, the REFSPEC keywords, will come from arc observations
+taken through all the fibers. The arc fibers used during an object
+observation are then calibrated against their corresponding fibers in the
+arc calibration observations to determine a zero point shift. The REFSHFT
+keywords will contain the name of the object spectrum itself and the shifts
+from the simultaneous arc fibers will be interpolated spatially to the
+nonarc object fibers and applied to the dispersion functions from the arc
+calibrations for those fibers.
+
+The reference shift keywords are currently added with \fBhedit\fR and zero
+point shifts computed with \fBidentify/reidentify\fR. The complexities of
+this have been hidden in the multifiber \fBimred\fR instrument reduction
+packages. The reference shift correction feature was added primarily for
+use in those reduction packages.
+
+If the \fIlinearize\fR parameter is no the dispersion functions, weights,
+and shifts are transferred from the database to the world coordinate system
+keywords in the image header. Except for printing processing information
+that is all that is done to the spectra.
+
+If the \fIlinearize\fR parameter is yes the spectra are interpolated to a
+linear wavelength scale and the dispersion coordinate system in the header
+is set apprpriately. A linear wavelength coordinate system is defined by a
+starting wavelength, an ending wavelength, a wavelength interval per pixel,
+and the number of pixels. These four parameters actually overspecify the
+coordinate system and only three of these values are needed to define it.
+The output coordinate system is specified by giving a set or subset of
+these parameters using the parameters \fIw1\fR, \fIw2\fR, \fIdw\fR, and
+\fInw\fR.
+
+When the \fIlog\fR option is used these parameters are still specified and
+computed in non-log units but the effective interval per pixel is
+
+.nf
+ dw_log = (log10(w2) - log10(w1)) / (nw - 1)
+ dw_log = (log10(w1+dw*(nw-1)) - log10(w1)) / (nw - 1)
+.fi
+
+In other words, the logarithmic interval divides the starting and ending
+wavelength into the required number of pixels in log step. To avoid
+confusion in this case it is best to specify the starting and ending
+wavelengths (in non-log units) and the number of pixels.
+
+Note that if \fIlog\fR=yes the input spectra in either linear
+or log sampling will be resampled to produces an output spectrum in
+log sampling. Similarly, if \fIlog\fR=no the input spectra will
+be resampled to linear sampling. This means that log sampled input
+spectra will be resampled to linear sampling.
+
+Default values for any parameters which are not specified, by using the
+value INDEF, are supplied based on the wavelengths of the first and last
+pixel as given by the dispersion function and the number of pixels in the
+input image. The defaults may either be determined separately for each
+spectrum (\fIglobal\fR = \fIno\fR), from all spectra with the same aperture
+(\fIglobal\fR = \fIyes\fR and \fIsamedisp\fR = \fIno\fR), or from all the
+spectra (\fIglobal\fR = \fIyes\fR and \fIsamedisp\fR = \fIyes\fR). As
+indicated, the parameter \fIsamedisp\fR determines whether defaults are
+determined independently for each aperture or set the same for all
+apertures.
+
+Another way to specify the wavelengths when there are many apertures is to
+use a wavelength table or reference image. If an spectrum image name is
+specified with the \fItable\fR parameter then the dispersion parameters for
+each apertures are set to be the same as the reference spectrum.
+Alternatively, a text file table consisting of lines containing an aperture
+number, the starting wavelength, the ending wavelength, the wavelength
+interval per pixel, and the number of output pixels may be specified. Any
+of these values may be specified as INDEF (though usually the aperture
+number is not). One way to view the wavelength table/reference spectrum is
+that an entry in the wavelength table/reference spectrum overrides the
+values of the parameters \fIw1\fR, \fIw2\fR, \fIdw\fR, and \fInw\fR, which
+normally apply to all apertures, for the specified aperture. The
+wavelength table is used to specify explicit independent values for
+apertures. The global mechanism can supply independent values for the
+INDEF parameters when the \fIsamedisp\fR parameter is no.
+
+If one wishes to verify and possibly change the defaults assigned,
+either globally or individually, the \fIconfirm\fR flag may be set. The
+user is asked whether to accept these values. By responding with no the
+user is given the chance to change each parameter value. Then the new
+parameters are printed and the user is again asked to confirm the
+parameters. This is repeated until the desired parameters are set. When
+the defaults are not global the changed parameters will not be used for the
+next spectrum. When the global option is used any changes made are
+retained (either for all apertures or independently for each aperture)
+until changed again.
+
+When adjusting the wavelengths the user should specify which parameter is
+free to change by entering INDEF. If none of the parameters are specified
+as INDEF then those values which were not changed, i.e. by accepting the
+current value, are the first to be changed.
+
+Once the wavelength scale has been defined the input spectrum is
+interpolated for each output pixel. Output wavelengths outside the range
+of the input spectrum are set to the value given by the \fIblank\fR parameter
+value. The default interpolation function
+is a 5th order polynomial. The choice of interpolation type is made
+with the package parameter "interp". It may be set to "nearest",
+"linear", "spline3", "poly5", or "sinc". Remember that this
+applies to all tasks which might need to interpolate spectra in the
+\fBonedspec\fR and associated packages. For a discussion of interpolation
+types see \fBonedspec\fR.
+
+When it is desired to conserve total flux, particularly when the dispersion is
+significantly reduced, the parameter \fIflux\fR is set to yes and the
+output pixel value is obtained by integrating the interpolation function
+across the wavelength limits of the output pixel. If it is set to no
+then the flux density is conserved by averaging across the output pixel
+limits.
+
+The input spectrum name, reference spectra, and the wavelength parameters
+will be printed on the standard output if the \fIverbose\fR parameter is
+set and printed to a log file if one is specified with the \fIlogfile\fR
+parameter. If one wishes to only check what wavelengths will be determined
+for the defaults without actually dispersion correcting the spectra the
+\fIlistonly\fR flag may be set.
+
+Other tasks which may be used to change the dispersion coordinate system
+are \fBscopy\fR, \fBspecshift\fR, and \fBsapertures\fR.
+.ih
+EXAMPLES
+In the examples when the task is used in the IRS and IIDS packages,
+shown with the "ir>" prompt the spectra have a record number extension
+image name format and the records parameter must be specified. In
+the other case shown with the "on>" prompt the records parameter is
+not used.
+
+1. Dispersion correct spectra so that they have the same number of pixels
+and the wavelengths limits are set by the reference spectra.
+
+.nf
+ir> dispcor spec dcspec 9,10,447-448
+dcspec.0009: ap = 0, w1 = 5078.84, w2 = 6550.54, dw = 1.797, nw = 820
+dcspec.0010: ap = 1, w1 = 5078.71, w2 = 6552.81, dw = 1.800, nw = 820
+dcspec.0447: ap = 0, w1 = 5082.57, w2 = 6551.45, dw = 1.794, nw = 820
+dcspec.0448: ap = 1, w1 = 5082.03, w2 = 6553.66, dw = 1.797, nw = 820
+
+on> dispcor allspec.ms dcallspec.ms
+dcallspec.ms: ap = 1, w1 = 5078.84, w2 = 6550.54, dw = 1.797, nw = 820
+dcallspec.ms: ap = 2, w1 = 5078.71, w2 = 6552.81, dw = 1.800, nw = 820
+dcallspec.ms: ap = 3, w1 = 5082.57, w2 = 6551.45, dw = 1.794, nw = 820
+dcallspec.ms: ap = 4, w1 = 5082.03, w2 = 6553.66, dw = 1.797, nw = 820
+.fi
+
+2. Confirm and change assignments.
+
+.nf
+on> dispcor spec* %spec%new%* confirm+
+new009: ap = 0, w1 = 5078.84, w2 = 6550.54, dw = 1.797, nw = 820
+ Change wavelength coordinate assignments? (yes):
+ Starting wavelength (5078.8421234): 5070
+ Ending wavelength (6550.535123):
+ Wavelength interval per pixel (1.79693812):
+ Number of output pixels (820): INDEF
+new009: ap = 0, w1 = 5070., w2 = 6550.53, dw = 1.795, nw = 826
+ Change wavelength coordinate assignments? (yes): no
+new010: ap = 1, w1 = 5078.71, w2 = 6552.81, dw = 1.800, nw = 820
+ Change wavelength coordinate assignments? (no): yes
+ Starting wavelength (5078.7071234): 5100
+ Ending wavelength (6550.805123): 6500
+ Wavelength interval per pixel (1.79987512): INDEF
+ Number of output pixels (820): INDEF
+new010: ap = 1, w1 = 5100., w2 = 6500., dw = 1.797, nw = 780
+ Change wavelength coordinate assignments? (yes): no
+new447: ap = 0, w1 = 5082.57, w2 = 6551.45, dw = 1.793, nw = 820
+ Change wavelength coordinate assignments? (yes): no
+new448: ap = 1, w1 = 5082.03, w2 = 6553.66, dw = 1.797, nw = 820
+ Change wavelength coordinate assignments? (no):
+.fi
+
+3. Confirm global assignments and do dispersion correction in place.
+record format.
+
+.nf
+ir> dispcor irs "" 9,10,447,448 confirm+ global+ samedisp+
+irs.0009: ap = 0, w1 = 5078.71, w2 = 6553.66, dw = 1.801, nw = 820
+ Change wavelength coordinate assignments? (yes):
+ Starting wavelength (5078.7071234): 5100
+ Ending wavelength (6553.664123): 6500
+ Wavelength interval per pixel (1.80092412):
+ Number of output pixels (820):
+irs.0009: ap = 0, w1 = 5100., w2 = 6500., dw = 1.799, nw = 779
+ Change wavelength coordinate assignments? (yes): no
+irs.0010: ap = 1, w1 = 5100., w2 = 6500., dw = 1.799, nw = 779
+ Change wavelength coordinate assignments? (no):
+irs.0447: ap = 0, w1 = 5100., w2 = 6500., dw = 1.799, nw = 779
+ Change wavelength coordinate assignments? (no):
+irs.0448: ap = 1, w1 = 5100., w2 = 6500., dw = 1.799, nw = 779
+ Change wavelength coordinate assignments? (no):
+.fi
+
+4. Make a nonlinear dispersion correction in place.
+
+.nf
+on> dispcor spec* "" linearize=no verbose- logfile=logfile
+.fi
+
+5. Apply a single dispersion solution to a set of record number format
+images.
+
+ir> dispcor nite101 dcnite101 "1-10" ignore+ confirm-
+
+.ih
+REVISIONS
+.ls DISPCOR V2.12.3
+Added the blank parameter value.
+.le
+.ls DISPCOR V2.11.3
+Long slit and data cubes can be used with this task to either resample
+using the existing WCS or to use a single dispersion function from
+IDENTIFY. It uses the first one found.
+.le
+.ls DISPCOR V2.10.3
+Provision was added for IDENTIFY dispersion solutions consisting of
+only a shift (as produced by the 'g' key in IDENTIFY or the refit=no
+flag in REIDENTIFY) to be applied to previously LINEARIZED spectra.
+Thus it is possible to use IDENIFY/REIDENTIFY to automatically
+compute a zero point shift based on 1 or more lines and then shift
+all the spectra to that zero point.
+
+DISPCOR will now allow multiple uses of IDENTIFY dispersion solutions
+in a simple way with but with continuing protection against accidental
+multiple uses of the same dispersion solutions. When a spectrum is
+first dispersion corrected using one or more reference spectra keywords
+the dispersion flag is set and the reference spectra keywords are moved to
+DCLOGn keywords. If DISPCOR is called again without setting new
+reference spectra keywords then the spectra are resampled (rebinned)
+using the current coordinate system. If new reference spectra are set
+then DISPCOR will apply these new dispersion functions. Thus the user
+now explicitly enables multiple dispersion functions by adding
+reference spectra keywords and DISPCOR eliminates accidental multiple
+uses of the same dispersion function by renaming the reference
+spectra. The renamed keywords also provide a history.
+
+The flux conservation option now computes an average across the
+output pixel rather than interpolating to the middle of the output
+pixel when \fIflux\fR is no. This preserves the flux density and
+includes all the data; i.e. a coarse resampling will not eliminate
+features which don't fall at the output pixel coordinates.
+
+Some additional log and verbose output was added to better inform the
+user about what is done.
+
+Better error information is now printed if a database dispersion function
+is not found.
+.le
+.ls DISPCOR V2.10
+This is a new version with many differences. It replaces the previous
+three tasks \fBdispcor, ecdispcor\fR and \fBmsdispcor\fR. It applies both
+one dimensional and echelle dispersion functions. The new parameter
+\fIlinearize\fR selects whether to interpolate the spectra to a uniform
+linear dispersion (the only option available previously) or to assign a
+nonlinear dispersion function to the image without any interpolation. The
+interpolation function parameter has been eliminated and the package
+parameter \fIinterp\fR is used to select the interpolation function. The
+new interpolation type "sinc" may be used but care should be exercised.
+The new task supports applying a secondary zero point shift spectrum to a
+master dispersion function and a spatial interpolation of the shifts when
+calibration spectra are taken at the same time on a different region of the
+same 2D image. The optional wavelength table may now also be an image to
+match dispersion parameters. The \fIapertures\fR and \fIrebin\fR
+parameters have been eliminated. If an input spectrum has been previously
+dispersion corrected it will be resampled as desired. Verbose and log file
+parameters have been added to log the dispersion operations as desired.
+The record format syntax is available in the \fBirs/iids\fR packages.
+.le
+.ih
+SEE ALSO
+package, refspectra, scopy, specshift, sapertures
+.endhelp
diff --git a/noao/onedspec/doc/disptrans.hlp b/noao/onedspec/doc/disptrans.hlp
new file mode 100644
index 00000000..d73a4cb4
--- /dev/null
+++ b/noao/onedspec/doc/disptrans.hlp
@@ -0,0 +1,193 @@
+.help disptrans Aug94 noao.onedspec
+.ih
+NAME
+disptrans -- Transform dispersion units and apply air correction
+.ih
+USAGE
+disptrans input output units
+.ih
+PARAMETERS
+.ls input
+List of dispersion calibrated input spectra to be dispersion transformed.
+.le
+.ls output
+List of output dispersion transformed spectra. If given the input names
+(or a null list), each input spectrum will be replaced by the transformed
+output spectrum.
+.le
+.ls units
+Output dispersion units. A wide range of dispersion units may be
+specified and they are described in the UNITS section.
+.le
+.ls error = 0.01
+Maximum error allowed in the output dispersion transformation expressed
+as a pixel error; that is, the equivalent pixel shift in the output
+dispersion function corresponding to the maximum difference between
+the exact transformation and the dispersion function approximation.
+The smaller the allowed error the higher the order of dispersion
+function used.
+.le
+.ls linearize = no
+Resample the spectrum data to linear increments in the output dispersion
+system? If no then the output dispersion function is stored in the
+spectrum header and if yes the spectrum is resampled into the same
+number of pixels over the same dispersion range but in even steps
+of the output dispersion units.
+.le
+.ls verbose = yes
+Print a log of each spectrum transformed to the standard output?
+.le
+
+.ls air = "none" (none|air2vac|vac2air)
+Apply an air to vacuum or vacuum to air conversion? It is the
+responsibility of the user to know whether the input dispersion
+is in air or vacuum units and to select the appropriate conversion.
+The conversion types are "none" for no conversion, "air2vac" to
+convert from air to vacuum, and "vac2air" to convert from vacuum
+to air.
+.le
+.ls t = 15, p = 760, f = 4
+Temperature t in degrees C, pressure p in mmHg, and water vapour pressure f
+in mmHg for the air index of refraction.
+.le
+
+OTHER PARAMETERS
+
+.ls interp = "poly5" (nearest|linear|poly3|poly5|spline3|sinc)
+Spectrum interpolation type used when spectra are resampled. The choices are:
+
+.nf
+ nearest - nearest neighbor
+ linear - linear
+ poly3 - 3rd order polynomial
+ poly5 - 5th order polynomial
+ spline3 - cubic spline
+ sinc - sinc function
+.fi
+.le
+.ih
+DESCRIPTION
+The dispersion function in the input spectra, y = f(x) where x is the
+pixel coordinate and y is the input dispersion coordinate, is
+transformed to y' = g(x) where y' is in the new dispersion units. This is done
+by evaluating the input dispersion coordinate y at each pixel, applying an
+air to vacuum or vacuum to air conversion if desired, and applying the
+specified unit transformation y' = h(y). Since the transformations are
+nonlinear functions and the output dispersion function must be expressed in
+polynomial form, the function g(x) is determined by fitting a cubic spline
+to the set of x and y' values. The lowest number of spline pieces is used
+which satisfies the specified error. Note that this error is not a random
+error but difference between the smooth fitted function and the smooth
+dispersion function in the header. As a special case, the first
+fit tried is a linear function. If this satisfies the error condition
+then a simpler dispersion description is possible. Also this is
+appropriate for dispersion units which are simply related by a
+scale change such as Angstroms to nanometers or Hertz to Mev.
+
+The error condition is that the maximum difference between the exact or
+analytic (the air/vacuum conversion is never exact) transformation and the
+fitted function value at any pixel be less than the equivalent shift in
+pixel coordinate evaluated at that point. The reason for using an error
+condition in terms of pixels is that it is independent of the dispersion of
+the spectra and the resolution of spectra is ultimately limited by the
+pixel sampling.
+
+After the new dispersion function is determined the function is either
+stored in the coordinate system description for the spectrum or used to
+resample the pixels to linear increments in the output dispersion units.
+The resampling is not done if the new dispersion function is already linear
+as noted above. The sampling uses the mean value over the input spectrum
+covered by an output spectrum pixel (it is flux per unit dispersion element
+preserving as opposed to flux/counts preserving). The linear sampling
+parameters are limited to producing the same number of output pixels as
+input pixels over the same range of dispersion. If one wants to have more
+control over the resampling then the \fIlinearize\fR parameter should be
+set to no and the task \fBdispcor\fR used on the output spectrum.
+
+Note that an alternative to using this task is to do the original
+dispersion calibration (based on calibration spectra) with IDENTIFY
+and DISPCOR in the desired units. However, currently the standard
+lines lists are in Angstroms. There are, however, linelists for
+He-Ne-Ar, Th-Ar, and Th in vacuum wavelengths.
+.ih
+UNITS
+The dispersion units are specified by strings having a unit type from the
+list below along with the possible preceding modifiers, "inverse", to
+select the inverse of the unit and "log" to select logarithmic units. For
+example "log angstroms" to select the logarithm of wavelength in Angstroms
+and "inv microns" to select inverse microns. The various identifiers may
+be abbreviated as words but the syntax is not sophisticated enough to
+recognized standard scientific abbreviations except for those given
+explicitly below.
+
+.nf
+ angstroms - Wavelength in Angstroms
+ nanometers - Wavelength in nanometers
+ millimicrons - Wavelength in millimicrons
+ microns - Wavelength in microns
+ millimeters - Wavelength in millimeters
+ centimeter - Wavelength in centimeters
+ meters - Wavelength in meters
+ hertz - Frequency in hertz (cycles per second)
+ kilohertz - Frequency in kilohertz
+ megahertz - Frequency in megahertz
+ gigahertz - Frequency in gigahertz
+ m/s - Velocity in meters per second
+ km/s - Velocity in kilometers per second
+ ev - Energy in electron volts
+ kev - Energy in kilo electron volts
+ mev - Energy in mega electron volts
+
+ nm - Wavelength in nanometers
+ mm - Wavelength in millimeters
+ cm - Wavelength in centimeters
+ m - Wavelength in meters
+ Hz - Frequency in hertz (cycles per second)
+ KHz - Frequency in kilohertz
+ MHz - Frequency in megahertz
+ GHz - Frequency in gigahertz
+ wn - Wave number (inverse centimeters)
+.fi
+
+The velocity units require a trailing value and unit defining the
+velocity zero point. For example to transform to velocity relative to
+a wavelength of 1 micron the unit string would be:
+
+.nf
+ km/s 1 micron
+.fi
+.ih
+AIR/VACUUM CONVERSION
+The air to vacuum and vacuum to air conversions are obtained by multiplying
+or dividing by the air index of refraction as computed from the
+formulas in Allen's Astrophysical Quantities (p. 124 in 1973 edition).
+These formulas include temperature, pressure, and water vapour terms
+with the default values being the standard ones.
+.ih
+EXAMPLES
+1. Convert a spectrum dispersion calibrated in Angstroms to electron
+volts and resample to a linear sampling.
+
+.nf
+ cl> disptrans spec1 evspec1 ev linear+
+ evspec1: Dispersion transformed to ev.
+.fi
+
+2. Apply an air to vacuum correction to an echelle spectrum using the
+default standard temperature and pressure. Don't resample but rather use
+a nonlinear dispersion function.
+
+.nf
+ cl> disptrans highres.ec vac.ec angs air=air2vac
+ vac.ec: Dispersion transformed to angstroms in vacuum with
+ t = 15. C, p = 760. mmHg, f = 4. mmHg.
+.fi
+.ih
+REVISIONS
+.ls DISPTRANS V2.10.4
+New task with this release.
+.le
+.ih
+SEE ALSO
+dispcor, identify, scopy, dopcor
+.endhelp
diff --git a/noao/onedspec/doc/dopcor.hlp b/noao/onedspec/doc/dopcor.hlp
new file mode 100644
index 00000000..6bcd0992
--- /dev/null
+++ b/noao/onedspec/doc/dopcor.hlp
@@ -0,0 +1,184 @@
+.help dopcor Jun94 noao.onedspec
+.ih
+NAME
+dopcor -- Apply doppler correction
+.ih
+USAGE
+dopcor input output redshift
+.ih
+PARAMETERS
+.ls input
+List of input spectra to be doppler corrected.
+.le
+.ls output
+List of doppler corrected spectra. If no output list is specified then
+the input spectra are modified. Also the output name may be
+the same as the input name to replace the input spectra by the
+calibrated spectra.
+.le
+.ls redshift
+Redshift or radial velocity (km/s) to be removed? The spectra are corrected so
+that the specified redshift is removed; i.e. spectra with a positive
+velocity are shifted to shorter wavelengths and vice-versa. This parameter
+may be either a number or an image header keyword with the desired redshift
+or velocity value. An image header keyword may also have an initial minus
+sign, '-', to specify the negative of a velocity or the redshift complement
+(1/(1+z)-1) of a redshift. The choice between a redshift and a velocity is
+made with the \fIisvelocity\fR parameter.
+.le
+.ls isvelocity = no
+Is the value specified by the \fIredshift\fR parameter a velocity? If
+no then the value is interpreted as a redshift and if it is yes then
+it is interpreted as a physical velocity in kilometers per second. Note that
+this is a relativistic velocity and not c*z! For nearby cosmological
+velocities users should specify a redshift (z = v_cosmological / c).
+.le
+.ls add = no
+Add doppler correction to existing correction in "multispec" spectra?
+.le
+.ls dispersion = yes
+Apply a correction to the dispersion function?
+.le
+.ls flux = no
+Apply a flux correction?
+.le
+.ls factor = 3
+Flux correction factor as a power of 1+z when applying a flux correction.
+.le
+.ls apertures = ""
+List of apertures to be corrected. If none are specified then all apertures
+are corrected. An aperture list consists of comma separated aperture
+number or aperture number ranges. A range is hypen separated and may
+include an interval step following the character 'x'. See \fBranges\fR
+for further information. For N-dimensional spatial spectra such as
+long slit and Fabry-Perot spectra this parameter is ignored.
+.le
+.ls verbose = no
+Print corrections performed? The information includes the output image
+name, the apertures, the redshift, and the flux correction factor.
+.le
+.ih
+DESCRIPTION
+The input spectra (as specified by the input image list and apertures) are
+corrected by removing a specified doppler shift and written to the
+specified output images. The correction is such that if the actual
+shift of the observed object is specified then the corrected spectra
+will be the rest spectra. The opposite sign for a velocity or the
+redshift complement (1/(1+z)-1) may be used to add a doppler shift
+to a spectrum.
+
+There are two common usages. One is to take spectra with high doppler
+velocities, such as cosmological sources, and correct them to rest with
+respect to the earth. In this case the measured redshift or velocity is
+specified to "remove" this component. The other usage is to correct
+spectra to heliocentric or local standard of rest. The heliocentric or LSR
+velocities can be computed and entered in the image header with the task
+\fBrvcorrect\fR. In this case it is tempting to again think you are
+"removing" the velocity so that you specify the velocity as given in the
+header. But actually what is needed is to "add" the computed standard of
+rest velocity to the observed spectrum taken with respect to the telescope
+to place the dispersion in the desired center of rest. Thus, in this case
+you specify the opposite of the computed heliocentric or LSR velocity; i.e.
+use a negative.
+
+The redshift or space velocity in km/s is specified either as a number or
+as an image header keyword containing the velocity or redshift. If a
+number is given it applies to all the input spectra while an image header
+keyword may differ for each image. The latter method of specifying a
+velocity is useful if velocity corrections are recorded in the image
+header. See \fBrvcorrect\fR for example.
+
+The choice between a redshift and a space velocity for the \fIredshift\fR
+parameter is made using the \fIisvelocity\fR parameter. If isvelocity=yes
+then the header dispersion solution is modified according to the
+relativistic Doppler correction:
+
+ lambda_new = lamda_old * sqrt((1 + v/c)/(1 - v/c))
+
+where v is the value of "redshift". If isvelocity=no, \fIredshift\fR is
+interpreted as a cosmological redshift and the header dispersion solution
+is modified to give:
+
+ lambda_new = lamda_old * z
+
+where z is the value of "redshift"
+
+If the \fIadd\fR parameter is used and the image uses a "multispec"
+format where the previous doppler factor is stored separately
+then the new doppler factor is:
+
+ znew = (1 + z) * (1 + zold) - 1 = z + zold + z * zold
+
+where z is the specified doppler factor, zold is the previous one,
+and znew is the final doppler factor. If the \fIadd\fR parameter
+is no then the previous correction is replaced by the new correction.
+Note that for images using a linear or equispec coordinate system
+the corrections are always additive since a record is not kept of
+the previous correction. Also any flux correction is made based
+on the specified doppler correction rather than znew.
+
+There are two corrections which may be made and the user selects one
+or both of these. A correction to the dispersion function is selected
+with the \fIdispersion\fR parameter. This correction is a term to be
+applied to the dispersion coordinates defined for the image. \fIThe spectrum
+is not resampled, only the dispersion coordinate function is affected\fR.
+A correction to the flux, pixel values, is selected with the \fIflux\fR
+parameter. This correction is only significant for cosmological redshifts.
+As such the correction is dependent on a cosmological model as well as
+whether a total flux or surface brightness is measured. To provide the
+range of possible corrections the flux correction factor is defined by
+the \fIfactor\fR parameter as the power of 1+z (where z is the
+redshift) to be multiplied into the observed pixel values.
+
+A keyword DOPCORnn is added to the image header. The index starts from
+01 and increments if multiple corrections are applied. The value of
+the keywords gives the redshift applied, the flux factor if used, and
+the apertures which were corrected.
+.ih
+EXAMPLES
+1. To dispersion and flux correct a quasar spectrum with redshift of
+3.2 to a rest frame:
+
+.nf
+ cl> dopcor qso001.ms qso001rest.ms 3.2 flux+
+.fi
+
+2. To correct a set of spectra (in place) to heliocentric rest the task
+\fBrvcorrect\fR is used to set the VHELIO keyword using an observed
+velocity of 0. Then:
+
+.nf
+ cl> dopcor *.imh "" -vhelio isvel+
+.fi
+
+3. To artificially add a redshift of 3.2 to a spectrum the complementary
+redshift is computed:
+
+.nf
+ cl> = 1/(1+3.2)-1
+ -0.76190476190476
+ cl> dopcor artspec "" -0.762 flux+
+.fi
+.ih
+REVISIONS
+.ls DOPCOR V2.10.3
+This task was extended to work on two and three dimensional spatial spectra
+such as long slit and Fabry-Perot spectra.
+
+The \fIadd\fR parameter was added.
+.le
+.ls DOPCOR V2.10.3
+A keyword is added to log the correction applied.
+.le
+.ls DOPCOR V2.10.2
+A sign error in converting velocity to redshift was fixed. A validity
+check on the velocities and redshifts was added. The documentation
+was corrected and improved.
+.le
+.ls DOPCOR V2.10
+This task is new.
+.le
+.ih
+SEE ALSO
+ranges, rvcorrect
+.endhelp
diff --git a/noao/onedspec/doc/fitprofs.hlp b/noao/onedspec/doc/fitprofs.hlp
new file mode 100644
index 00000000..ed21e7b1
--- /dev/null
+++ b/noao/onedspec/doc/fitprofs.hlp
@@ -0,0 +1,403 @@
+.help fitprofs Mar92 noao.onedspec
+.ih
+NAME
+fitprofs -- Fit 1D profiles to features in image vectors
+.ih
+USAGE
+fitprofs input
+.ih
+PARAMETERS
+.ls input
+List of input images to be fit. The images may be one dimensional
+spectra (one or more spectra per image) or long slit spectra. Other
+types of nonspectral images may also be used and for two dimensional
+images the fitting direction will be determined from either the keyword
+DISPAXIS in the image header or the \fIdispaxis\fR parameter.
+.le
+.ls lines = ""
+List of lines, columns, or apertures to be selected from the input image
+format. The default empty list, "", selects all vectors in the images.
+The syntax is a list of comma separated numbers or ranges, where a range
+is a pair of hyphen separated numbers.
+.le
+.ls bands = ""
+List of bands for 3D images. The empty list, "", selects all bands.
+.le
+.ls dispaxis = ")_.dispaxis", nsum = ")_.nsum"
+Parameters for defining vectors in 2D and 3D images. The
+dispersion axis is 1 for line vectors, 2 for column vectors, and 3 for band
+vectors. A DISPAXIS parameter in the image header has precedence over the
+\fIdispaxis\fR parameter. The default values defer to the package
+parameters of the same name.
+.le
+
+The following are the fitting parameters.
+.ls region = ""
+Region of the input vectors to be fit specified as a pair of space
+separated numbers. The coordinates are defined in terms of the linear
+image header coordinate parameters. For dispersion corrected spectra this
+is usually wavelength in Angstroms and for other data it is usually pixels.
+A fitting region must be specified.
+.le
+.ls positions = ""
+File of initial or fixed profile positions and (optional) peaks, profile
+types, and widths. The
+format consists of lines with one or more whitespace separated fields.
+The fields are the position, peak relative to the continuum with
+negative values being absorption, profile type of gaussian, lorentzian,
+or voigt, and the gaussian and/or lorentzian full width at half maximum.
+Trailing fields may be missing and fields to be set from default parameters
+or the image data (the peak value) may be given as INDEF.
+Comments and any additional columns are ignored. The positions and
+widths are specified in the coordinate units of the image, usually
+wavelength for dispersion corrected spectra and pixels otherwise.
+.le
+.ls background = ""
+Background values defining the linear background. If not specified the
+single pixel values nearest the fitting region endpoints are used.
+Otherwise two whitespace separated values are expected. If a value is
+a number then that is the background at the lower or upper end of the
+fitting region (ordered in pixel space not wavelength). The special
+values "avg(w1,w2,z)" or "med(w1,w2,z)" (note that there can be no
+whitespace) may be specified, where w1 and w2 are dispersion values, and z
+is a multiplier. This will take the average or median of pixels within the
+specified range and multiply the result by the third argument. The
+dispersion point used for that value in computing the linear background is
+the average of the dispersion coordinates of the pixels used.
+.le
+.ls profile = "gaussian" (gaussian|lorentzian|voigt)
+Default profile type to be fit when a profile type is not specified in
+the positions file. The type are "gaussian", "lorentzian", or "voigt".
+.le
+.ls gfwhm = 20., lfwhm = 20.
+Default gaussian and lorentzian full width at half maximum (FWHM).
+These values are used for the initial and/or fixed width when they are
+not specified in the position file.
+.le
+.ls fitbackground = yes
+Fit the background? If "yes" a linear background across the fitting region
+will be fit simultaneously with the profiles. If "no" the background will
+be fixed.
+.le
+.ls fitpositions = "all"
+Position fitting option. This may be "fixed" to fix all positions at their
+initial values, "single" to fit a single shift to the positions while
+keeping their separations fixed, or "all" to independently fit all the
+positions.
+.le
+.ls fitgfwhm = "all", fitlfwhm = "all"
+Profile width fitting options. These may be "fixed" to fix all widths
+at their initial values, "single" to fit a single scale factor to the initial
+widths, or "all" to independently fit all the widths.
+.le
+
+The following parameters are used for error estimates as described
+below in the ERROR ESTIMATES section.
+.ls nerrsample = 0
+Number of samples for the error computation. A value less than 10 turns
+off the error computation. A value of ~10 does a rough error analysis, a
+value of ~50 does a reasonable error analysis, and a value >100 does a
+detailed error analysis. The larger this value the longer the analysis
+takes.
+.le
+.ls sigma0 = INDEF, invgain = INDEF
+The pixel sigmas are modeled by the formula:
+
+.nf
+ sigma**2 = sigma0**2 + invgain * I
+.fi
+
+where I is the pixel value and "**2" means the square of the quantity. If
+either parameter is specified as INDEF or with a value less than zero then
+no sigma estimates are made and so no error estimates for the measured
+parameters is made.
+.le
+
+The following parameters determine the output of the task.
+.ls components = ""
+All profiles defined by the position file are simultaneously fit but only
+a subset of the fitted profiles may be selected for output. A profile
+or component is identified by the order number in the position file;
+i.e. the first entry in the position file is 1, the second is 2, etc.
+The components to be output are specified by a range list. The empty
+list, "", selects all profiles.
+.le
+.ls verbose = yes
+Print fitting results and record of output images created on the
+standard output (normally the terminal).
+The fitting information is printed to the logfile so there is normally
+no need to redirect this output. The output may be turned off when
+the task is run as a background task.
+.le
+.ls logfile = "logfile"
+Logfile for fitting results. If not specified the results will not be
+logged.
+.le
+.ls plotfile = "plotfile"
+File to contain plot output. The plots show the image vector with
+overplots of the total fit, the individual components, and the residuals.
+The plotfile may be examined and manipulated later with tools such as
+\fBgkimosaic\fR.
+.le
+.ls output = ""
+List of output images. If not specified then no output images are created.
+If images are specified the list is matched with the input list.
+.le
+.ls option = "fit" (fit|difference)
+Image output option. The choices are "fit" to output the fitted image
+vector which is the sum of the fitted profiles (without a background),
+or "difference" to output the data with the profiles subtracted.
+.le
+.ls clobber = no, merge = no
+Clobber or modify any existing output images? If clobbering is not
+enabled a warning is printed and any existing output images are not
+modified. If clobbering is enabled then either new images are created
+if merge is "no" or the new fits are merged with the existing images.
+Merging is meaningful when only a subset of the input is fit such
+as selected lines or apertures.
+.le
+.ih
+DESCRIPTION
+\fBFitprofs\fR fits one dimensional profile functions to image vectors
+and outputs the fitting parameters, plots, and model or residual
+image vectors. This is done noninteractively using a file of initial
+profile positions and widths. Interactive profile fitting may be
+done with the deblending option of \fBsplot\fR or
+\fBstsdas.fitting.ngaussfit\fR.
+
+The input consists of images in a variety of formats. These include
+all the spectral formats as well as standard images. For two dimensional
+images (or the first 2D plane of higher dimensional images) either the
+lines or columns may be fit with possible summing of adjacent lines or
+columns to increase the signal-to-noise. A subset of the image apertures,
+lines, or columns may be specified or all image vectors may be fit.
+
+The fitting parameters consist of a fitting region, a list of initial
+positions, peaks, and widths, initial background endpoints, the fitting
+function, and the parameters to be fit or constrained. The coordinates and
+units used for the positions and widths are those defined by the standard
+linear coordinate header parameters. For dispersion corrected spectra
+these are generally wavelengths in Angstroms and otherwise they are
+generally pixels. A fitting region must be specified by a pair of
+numbers.
+
+The background parameter may be left empty to select the pixel values at
+the endpoints of the fitting region for defining the initial linear
+background. Or values at the endpoints of the fitting region may be given
+explicitly in pixel space order (i.e. the first value is for the edge of
+the fitting region which has smaller pixel coordinate0 Values can also be
+computed from the data using the functions "avg(w1,w2)" or "med(w1,w2)"
+where w1 and w2 are dispersion coordinates. The pixels in the specified
+range are average or medianed and the dispersion point for the linear
+background is the average of the dispersion coordinates of the pixels.
+
+The position list file consists of one or more columns.
+The format of this file has
+one or more columns. The columns are the wavelength, the peak value
+(relative to the continuum with negative values being absorption),
+the profile type (gaussian, lorentzian, or voigt), and the
+gaussian and/or lorentzian FWHM. End columns may be missing
+or INDEF values may be specified to use the default parameter
+values (the profile and widths) or determine the peak from the data.
+Below are examples of the file line formats
+
+.nf
+ wavelength
+ wavelength peak
+ wavelength peak (gaussian|lorenzian|voigt)
+ wavelength peak gaussian gfwhm
+ wavelength peak lorentzian lfwhm
+ wavelength peak voigt gfwhm
+ wavelength peak voigt gfwhm lfwhm
+
+ 1234.5 <- Wavelength only
+ 1234.5 -100 <- Wavelength and peak
+ 1234.5 INDEF v <- Wavelength and profile type
+ 1234.5 INDEF g 12 <- Wavelength and gaussian FWHM
+.fi
+
+where peak is the peak value, gfwhm is the gaussian FWHM, and lfwhm is
+the lorentzian FWHM. This format is the same as used by \fBsplot\fR
+and also by \fBartdata.mk1dspec\fR (except in the latter case the
+peak is normalized to a continuum of 1).
+
+The profile parameters fit are the central position, the peak amplitude,
+and the profile widths. The fitting may be constrained in number of ways.
+The linear background may be fixed or simultaneously fit with the
+profiles. The profile positions may be fixed, the relative separations
+fixed but a single zero point shift fit, or all positions may be fit
+simultaneously. The profile widths may also be fixed, the relative ratios
+of the widths fixed while fitting a single scale factor, or all widths fit
+simultaneously. The profile amplitudes are always fit.
+
+The fitting technique uses a nonlinear iterative Levenberg-Marquardt
+algorithm to reduce the Chi-square of the fit. The execution time
+increases rapidly with the number of profiles fit so there is an
+effective limit to the number of profiles that can be fit at once.
+
+The output includes a number of formats. The fitted parameters are
+recorded in a logfile (if specified) and printed on the standard
+output (if the verbose flag is set). This output includes the date,
+image vector, fitting parameters used, and a table of fitted or
+derived quantities. The parameters included some quantities relevant to
+spectral lines but others apply to any image data. The quantities are
+the profile center, the background or continuum at the center of the
+profile, the integral or flux of the profile (which is negative for
+profiles below the background), the equivalent width, the profile peak
+amplitude or core value, and the profile full width at half
+maximum. Pure gaussian and lorentzian profiles will have one of
+the widths set to zero while voigt profiles will have both values.
+
+Summary plots are recored in a plotfile (if specified). The plots
+show the data with the total fit, individual profiles, and residuals
+overplotted. The plotfile may be examined and printed using the
+task \fBgkimosaic\fR as well as other tasks which interpret GKI metacode.
+
+The final output consists of images in the same format as the input.
+The images may be of the total fit (sum of profiles without background)
+or of the difference (residuals) of the data minus the model.
+.ih
+ERROR ESTIMATES
+Error estimates may be computed for the fitted parameters.
+This requires a model for the pixel sigmas. Currently this
+model is based on a Poisson statistics model of the data. The model
+parameters are a constant Gaussian sigma and an "inverse gain" as specified
+by the parameters \fIsigma0\fR and \fIinvgain\fR. These parameters are
+used to compute the pixel value sigma from the following formula:
+
+.nf
+ sigma**2 = sigma0**2 + invgain * I
+.fi
+
+where I is the pixel value and "**2" means the square of the quantity.
+
+If either the constant sigma or the inverse gain are specified as INDEF or
+with values less than zero then no noise model is applied and no error
+estimates are computed. Also if the number of error samples is less than
+10 then no error estimates are computed. Note that for processed spectra
+this noise model will not generally be the same as the detector readout
+noise and gain. These parameters would need to be estimated in some way
+using the statistics of the spectrum. The use of an inverse gain rather
+than a direct gain was choosed to allow a value of zero for this
+parameters. This provides a model with constant uncertainties.
+
+The error estimates are computed by Monte-Carlo simulation. The model is
+fit to the data (using the noise sigmas) and this model is used to describe
+the noise-free spectrum. A number of simulations, given by the
+\fInerrsample\fR, are created in which random Gaussian noise is added to
+the noise-free spectrum based on the pixel sigmas from the noise model.
+The model fitting is done for each simulation and the absolute deviation of
+each fitted parameter to model parameter is recorded. The error estimate
+for the each parameter is then the absolute deviation containing 68.3% of
+the parameter estimates. This corresponds to one sigma if the distribution
+of parameter estimates is Gaussian though this method does not assume
+this.
+
+The Monte-Carlo technique automatically includes all effects of
+parameter correlations and does not depend on any approximations.
+However the computation of the errors does take a significant
+amount of time. The amount of time and the accuracy of the
+error estimates depend on how many simulations are done. A
+small number of samples (of order 10) is fast but gives crude
+estimates. A large number (greater than 100) is slow but gives
+very good estimates. A compromise value of 50 is recommended
+for many applications.
+
+.ih
+EXAMPLES
+1. The following example creates an artificial spectrum and fits it.
+It requires the \fBartdata\fR and \fBproto\fR packages be loaded.
+
+.nf
+ cl> mk1dspec test slope=1 temp=0 lines=testlines nl=20
+ cl> mknoise test rdnoise=10 poisson=yes
+ cl> fields testlines fields=1,3 > fitlines
+ cl> fitprofs test reg="4000 8000" pos=fitlines
+ # Jul 27 17:49 test - Ap 1:
+ # Nfit=20, background=YES, positions=all, gfwhm=all, lfwhm=all
+ # center cont flux eqw core gfwhm lfwhm
+ 6832.611 1363.188 -13461.8 9.875 -408.339 30.97 0.
+ 7963.674 1507.641 -8193.58 5.435 -395.207 19.48 0.
+ 5688.055 1217.01 -7075.11 5.814 -392.006 16.96 0.
+ 6831.3 1363.02 -7102.01 5.21 -456.463 14.62 0.
+ 7217.335 1412.323 -10110. 7.158 -427.797 22.2 0.
+ 6709.286 1347.437 -4985.06 3.7 -225.346 20.78 0.
+ 6434.317 1312.319 -7121.03 5.426 -342.849 19.51 0.
+ 6130.415 1273.506 -6164. 4.84 -224.146 25.83 0.
+ 4569.375 1074.138 -3904.6 3.635 -183.963 19.94 0.
+ 5656.645 1212.999 -8202.81 6.762 -303.617 25.38 0.
+ 4219.53 1029.458 -5161.64 5.014 -241.135 20.11 0.
+ 4551.424 1071.845 -3802.61 3.548 -139.39 25.63 0.
+ 4604.649 1078.643 -5539.15 5.135 -264.654 19.66 0.
+ 6966.557 1380.294 -11717.5 8.489 -600.581 18.33 0.
+ 4259.019 1034.501 -4280.38 4.138 -213.446 18.84 0.
+ 5952.958 1250.843 -8006.98 6.401 -318.313 23.63 0.
+ 4531.89 1069.351 -712.598 0.6664 -155.197 4.313 0.
+ 7814.418 1488.579 -2926.49 1.966 -164.891 16.67 0.
+ 5310.929 1168.846 -10132.2 8.669 -487.502 19.53 0.
+ 5022.948 1132.066 -7532.8 6.654 -325.594 21.73 0.
+
+.fi
+
+2. Suppose there is no obvious continuum level near the fitting
+region but you want to specify a flat continuum level as the average
+of pixels in a specified wavelength region. The background region
+would be specified as
+
+.nf
+ background = "avg(4250,4425.3) avg(4250,4425.3)"
+.fi
+
+Note that the value must be given twice to get a flat continuum.
+.ih
+REVISIONS
+.ls FITPROFS V2.11.3
+Modified to allow a more general specification of the background.
+.le
+.ls FITPROFS V2.11
+Modified to include lorentzian and voigt profiles. The parameters and
+positions file format have changed in this version. A new parameter
+controls the number of Monte-Carlo samples used in the error estimates.
+.le
+.ls FITPROFS V2.10.3
+Error estimates based on a simple noise model are now computed.
+.le
+.ls FITPROFS V2.10
+This task is new.
+.le
+.ih
+TIME REQUIREMENTS
+The following CPU times were obtained with a Sun Sparcstation I. The
+number of pixels in the fitting region and the number of lines fit
+were varied. The worst case of fitting all parameters and a background
+was considered as well as the constrained case of fitting line positions
+and a single width with fixed background.
+
+.nf
+ Npixels Nprofs Fitbkg Fitpos Fitsig CPU(sec)
+ 100 5 yes all all 1.9
+ 100 10 yes all all 3.3
+ 100 15 yes all all 5.6
+ 100 20 yes all all 9.0
+ 512 5 yes all all 4.7
+ 512 10 yes all all 10.0
+ 512 15 yes all all 17.6
+ 512 20 yes all all 27.8
+ 1000 5 yes all all 8.0
+ 1000 10 yes all all 18.0
+ 1000 15 yes all all 31.8
+ 1000 20 yes all all 50.2
+ 1000 25 yes all all 72.8
+ 1000 30 yes all all 100.2
+ 512 5 no all single 2.8
+ 512 10 no all single 5.3
+ 512 15 no all single 8.6
+ 512 20 no all single 12.8
+.fi
+
+Crudely this implies CPU time goes as the 1.4 power of the number of profiles
+and the 0.75 power of the number of pixels.
+.ih
+SEE ALSO
+splot, stsdas.fitting.ngaussfit
+.endhelp
diff --git a/noao/onedspec/doc/identify.hlp b/noao/onedspec/doc/identify.hlp
new file mode 100644
index 00000000..fea7086c
--- /dev/null
+++ b/noao/onedspec/doc/identify.hlp
@@ -0,0 +1,810 @@
+.help identify Jan96 noao.onedspec
+.ih
+NAME
+identify -- Identify features in one dimensional image vectors
+.ih
+SUMMARY
+Features are interactively marked in one dimensional image vectors.
+The features may be spectral lines when the vector is a spectrum
+or profile positions when the vector is a spatial cut. A function
+may be fit to the user coordinates as a function of pixel coordinates.
+This is primarily used to find dispersion functions for spectra
+such as arc-line calibration spectra. The profile position measurements
+are generally used for geometric calibrations.
+.ih
+USAGE
+identify images
+.ih
+PARAMETERS
+.ls images
+List of images in which to identify features and fit coordinate functions.
+.le
+.ls section = "middle line"
+If an image is not one dimensional or specified as a one dimensional image
+section then the image section given by this parameter is used. The
+section defines a one dimensional vector. The image is still considered to
+be two or three dimensional. It is possible to change the data vector
+within the program.
+
+The section parameter may be specified directly as an image section or
+in one of the following forms
+
+.nf
+line|column|x|y|z first|middle|last|# [first|middle|last|#]]
+first|middle|last|# [first|middle|last|#] line|column|x|y|z
+.fi
+
+where each field can be one of the strings separated by | except for #
+which is an integer number. The field in [] is a second designator
+which is used with three dimensional data. See the example section for
+examples of this syntax. Abbreviations are allowed though beware that 'l'
+is not a sufficient abbreviation.
+.le
+.ls database = "database"
+Database in which the feature data and coordinate functions are recorded.
+.le
+.ls coordlist = "linelists$idhenear.dat"
+User coordinate list consisting of an list of line coordinates. A
+comment line of the form "# units <units>", where <units> is one of the
+understood units names, defines the units of the line list. If no units
+are specified then Angstroms are assumed. Some standard line lists are
+available in the directory "linelists$". The standard line lists are
+described under the topic \fIlinelists\fR.
+.le
+.ls units = ""
+The units to use if no database entry exists. The units are specified as
+described in
+
+.nf
+ cl> help onedspec.package section=units
+.fi
+
+If no units are specified and a coordinate list is used then the units of
+the coordinate list are selected. If a database entry exists then the
+units defined there override both this parameter and the coordinate list.
+.le
+.ls nsum = "10"
+Number of lines, columns, or bands across the designated vector axis to be
+summed when the image is a two or three dimensional spatial spectrum.
+It does not apply to multispec format spectra. If the image is three
+dimensional an optional second number can be specified for the higher
+dimensional axis (the first number applies to the lower axis number and
+the second to the higher axis number). If a second number is not specified
+the first number is used for both axes.
+.le
+.ls match = -3.
+The maximum difference for a match between the feature coordinate function
+value and a coordinate in the coordinate list. Positive values
+are in user coordinate units and negative values are in units of pixels.
+.le
+.ls maxfeatures = 50
+Maximum number of the strongest features to be selected automatically from
+the coordinate list (function 'l') or from the image data (function 'y').
+.le
+.ls zwidth = 100.
+Width of graphs, in user coordinates, when in zoom mode (function 'z').
+.le
+
+The following parameters are used in determining feature positions.
+.ls ftype = "emission"
+Type of features to be identified. The possibly abbreviated choices are
+"emission" and "absorption".
+.le
+.ls fwidth = 4.
+Full-width at the base (in pixels) of features to be identified.
+.le
+.ls cradius = 5.
+The maximum distance, in pixels, allowed between a feature position
+and the initial estimate when defining a new feature.
+.le
+.ls threshold = 0.
+In order for a feature center to be determined the range of pixel intensities
+around the feature must exceed this threshold.
+.le
+.ls minsep = 2.
+The minimum separation, in pixels, allowed between feature positions
+when defining a new feature.
+.le
+
+The following parameters are used to fit a function to the user coordinates.
+The \fBicfit\fR package is used and further descriptions about these parameters
+may be found under that package.
+.ls function = "spline3"
+The function to be fit to the user coordinates as a function of the pixel
+coordinate. The choices are "chebyshev", "legendre", "spline1", or "spline3".
+.le
+.ls order = 1
+Order of the fitting function. The order is the number of polynomial terms
+or number of spline pieces.
+.le
+.ls sample = "*"
+Sample regions for fitting. This is in pixel coordinates and not the user
+coordinates.
+.le
+.ls niterate = 0
+Number of rejection iterations.
+.le
+.ls low_reject = 3.0, high_reject = 3.0
+Lower and upper residual rejection in terms of the RMS of the fit.
+.le
+.ls grow = 0
+Distance from a rejected point in which additional points are automatically
+rejected regardless of their residuals.
+.le
+
+The following parameters control the input and output.
+.ls autowrite = no
+Automatically write or update the database? If "no" then when exiting the
+program a query is given if the feature data and fit have been modified.
+The query is answered with "yes" or "no" to save or not save the results.
+If \fIautowrite\fR is "yes" exiting the program automatically updates the
+database.
+.le
+.ls graphics = "stdgraph"
+Graphics device. The default is the standard graphics device which is
+generally a graphics terminal.
+.le
+.ls cursor = ""
+Cursor input file. If a cursor file is not given then the standard graphics
+cursor is read.
+.le
+
+The following parameters are queried when the 'b' key is used.
+.ls crval, cdelt
+These parameters specify an approximate coordinate value and coordinate
+interval per pixel when the automatic line identification
+algorithm ('b' key) is used. The coordinate value is for the
+pixel specified by the \fIcrpix\fR parameter in the \fBaidpars\fR
+parameter set. The default value of \fIcrpix\fR is INDEF which then
+refers the coordinate value to the middle of the spectrum. By default
+only the magnitude of the coordinate interval is used. Either value
+may be given as INDEF. In this case the search for a solution will
+be slower and more likely to fail. The values may also be given as
+keywords in the image header whose values are to be used.
+.le
+.ls aidpars = "" (parameter set)
+This parameter points to a parameter set for the automatic line
+identification algorithm. See \fIaidpars\fR for further information.
+.le
+.ih
+CURSOR KEYS
+.ls ?
+Clear the screen and print a menu of options.
+.le
+.ls a
+Apply next (c)enter or (d)elete operation to (a)ll features
+.le
+.ls b
+Identify features and find a dispersion function automatically using
+the coordinate line list and approximate values for the dispersion.
+.le
+.ls c
+(C)enter the feature nearest the cursor. Used when changing the position
+finding parameters or when features are defined from a previous feature list.
+.le
+.ls d
+(D)elete the feature nearest the cursor. (D)elete all features when preceded
+by the (a)ll key. This does not affect the dispersion function.
+.le
+.ls e
+Find features from a coordinate list without doing any fitting. This is
+like the 'l' key without any fitting.
+.le
+.ls f
+(F)it a function of the pixel coordinates to the user coordinates. This enters
+the interactive function fitting package.
+.le
+.ls g
+Fit a zero point shift to the user coordinates by minimizing the difference
+between the user and fitted coordinates. The coordinate function is
+not changed.
+.le
+.ls i
+(I)nitialize (delete features and coordinate fit).
+.le
+.ls j
+Go to the preceding line, column, or band in a 2D/3D or multispec image.
+.le
+.ls k
+Go to the next line, column, or band in a 2D/3D or multispec image.
+.le
+.ls l
+(L)ocate features in the coordinate list. A coordinate function must be
+defined or at least two features must have user coordinates from which a
+coordinate function can be determined. If there are features an
+initial fit is done, then features are added from the coordinate list,
+and then a final fit is done.
+.le
+.ls m
+(M)ark a new feature using the cursor position as the initial position
+estimate.
+.le
+.ls n
+Move the cursor or zoom window to the (n)ext feature (same as +).
+.le
+.ls o
+Go to the specified line, column, or band in a 2D/3D or multispec image.
+For 3D images two numbers are specified.
+.le
+.ls p
+(P)an to the original window after (z)ooming on a feature.
+.le
+.ls q
+(Q)uit and continue with next image.
+.le
+.ls r
+(R)edraw the graph.
+.le
+.ls s
+(S)hift the fit coordinates relative to the pixel coordinates. The
+user specifies the desired fit coordinate at the position of the cursor
+and a zero point shift to the fit coordinates is applied. If features
+are defined then they are recentered and the shift is the average shift.
+The shift in pixels, user coordinates, and z (fractional shift) is printed.
+.le
+.ls t
+Reset the current feature to the position of the cursor. The feature
+is \fInot\fR recentered. This is used to mark an arbitrary position.
+.le
+.ls u
+Enter a new (u)ser coordinate for the current feature.
+When (m)arking a new feature the user coordinate is also requested.
+.le
+.ls v
+Modify the fitting weight of the current feature. The weights are
+integers with the lowest weight being the default of 1.
+.le
+.ls w
+(W)indow the graph. A window prompt is given and a number of windowing
+options may be given. For more help type '?' to the window prompt or
+see help under \fIgtools\fR.
+.le
+.ls x
+Find a zero point shift for the current dispersion function. This is used
+by starting with the dispersion solution and features from a different
+spectrum. The mean shift in user coordinates, mean shift in pixels, and
+the fractional shift in user coordinates is printed.
+.le
+.ls y
+Up to \fImaxfeatures\fR emission peaks are found automatically (in order of
+peak intensity) and, if a dispersion solution is defined, the peaks are
+identified from the coordinate list.
+.le
+.ls z
+(Z)oom on the feature nearest the cursor. The width of the zoom window
+is determined by the parameter \fIzwidth\fR.
+.le
+.ls .
+Move the cursor or zoom window to the feature nearest the cursor.
+.le
+.ls 4 +
+Move the cursor or zoom window to the (n)ext feature.
+.le
+.ls 4 -
+Move the cursor or zoom window to the previous feature.
+.le
+
+Parameters are shown or set with the following "colon commands", which may be
+abbreviated. To show the value of a parameter type the parameter name alone
+and to set a new value follow the parameter name by the value.
+.ls :show file
+Show the values of all the parameters. If a file name is given then the
+output is appended to that file. If no file is given then the terminal
+is cleared and the output is sent to the terminal.
+.le
+.ls :features file
+Print the feature list and the fit rms. If a file name is given then the
+output is appended to that file. If no file is given then the terminal
+is cleared and the output is sent to the terminal.
+.le
+.ls :coordlist file
+Set or show the coordinate list file.
+.le
+.ls :cradius value
+Set or show the centering radius in pixels.
+.le
+.ls :threshold value
+Set or show the detection threshold for centering.
+.le
+.ls :database name
+Set or show the database for recording feature records.
+.le
+.ls :ftype value
+Set or show the feature type (emission or absorption).
+.le
+.ls :fwidth value
+Set or show the feature width in pixels.
+.le
+.ls :image imagename
+Set a new image or show the current image.
+.le
+.ls :labels value
+Set or show the feature label type (none, index, pixel, coord, user, or both).
+None produces no labeling, index labels the features sequentially in order
+of pixel position, pixel labels the features by their pixel coordinates,
+coord labels the features by their user coordinates (such as wavelength),
+user labels the features by the user or line list supplied string, and
+both labels the features by both the user coordinates and user strings.
+.le
+.ls :match value
+Set or show the coordinate list matching distance.
+.le
+.ls :maxfeatures value
+Set or show the maximum number of features automatically found.
+.le
+.ls :minsep value
+Set or show the minimum separation allowed between features.
+.le
+.ls :read name ap
+Read a record from the database. The record name defaults to the image name
+and, for 1D spectra, the aperture number defaults to aperture of
+the current image.
+.le
+.ls :write name ap
+Write a record to the database. The record name defaults to the image name
+and, for 1D spectra, the aperture number defaults to aperture of
+the current image.
+.le
+.ls :add name ap
+Add features from a database record. The record name defaults to the image name
+and, for 1D spectra, the aperture number defaults to aperture of
+the current image. Only the features are added to any existing list
+of features. The dispersion function is not read.
+.le
+.ls :zwidth value
+Set or show the zoom width in user units.
+.le
+.ls :/help
+Print additional help for formatting graphs. See help under "gtools".
+.le
+.ih
+DESCRIPTION
+Features in the input images are identified interactively and assigned
+user coordinates. A "coordinate function" mapping pixel coordinates to
+user coordinates may be determined from the identified features. A
+user coordinate list may be defined to automatically identify additional
+features. This task is used to measure positions of features,
+determine dispersion solutions for spectra, and to identify features in
+two and three dimensional images for mapping a two or three dimensional
+coordinate transformation. Because of this dual use the terms vector
+and feature are used rather than spectrum and spectral line.
+
+Each image in the input list is considered in turn. If the image is
+not one dimensional or a one dimensional section of an image
+then the image section given by the parameter
+\fIsection\fR is used. This parameter may be specified in several ways as
+described in the PARAMETERS and EXAMPLES sections. The image section is used
+to select a starting vector and image axis.
+
+If the image is not one dimensional or in multispec format then the number
+of lines, columns, or bands given by the parameter \fInsum\fR are summed.
+The one dimensional image vector is graphed. The initial feature list and
+coordinate function are read from the database if an entry exists. The
+features are marked on the graph. The image coordinates are in pixels
+unless a coordinate function is defined, in which case they are in user
+coordinate units. The pixel coordinate, coordinate function value, and
+user coordinate for the current feature are printed.
+
+The graphics cursor is used to select features and perform various
+functions. A menu of the keystroke options and functions is printed
+with the key '?'. The cursor keys and their functions are defined in
+the CURSOR KEYS section and described further below. The standard
+cursor mode keys are also available to window and redraw the graph and
+to produce hardcopy "snaps".
+
+There are a number of ways of defining features. They fall into
+two categories; interactively defining features with the cursor
+and using automatic algorithms.
+
+The 'm' key is the principle interactive feature marking method. Typing
+'m' near the position of a feature applies a feature centering algorithm
+(see \fBcenter1d\fR) and, if a center is found, the feature is entered in
+the feature list and marked on the spectrum. If the new position is within
+a distance given by the parameter \fIminsep\fR of a previous feature it is
+considered to be the same feature and replaces the old feature. Normally
+the position of a new feature will be exactly the same as the original
+feature. The coordinate list is searched for a match between the
+coordinate function value (when defined) and a user coordinate in the
+list. If a match is found it becomes the default user coordinate which the
+user may override. The new feature is marked on the graph and it becomes
+the current feature. The redefinition of a feature which is within the
+minimum separation may be used to set the user coordinate from the
+coordinate list. The 't' key allows setting the position of a feature to
+other than that found by the centering algorithm.
+
+The principle automatic feature identification algorithm is executed
+with the 'b' key. The user is queried for an approximate coordinate
+value and coordinate interval per pixel. The coordinate value
+is for the center of the spectrum by default though this may be changed
+with the \fBaidpars\fR parameters. Only the magnitude of the
+coordinate interval per pixel is used by default though this also
+may be changed. Either value may be given as INDEF to do an unconstrained
+search, however, this will be much slower and more likely to fail.
+The algorithm searches for matches between the strong lines in the
+spectrum and lines in the coordinate list. The algorithm is described
+in the documentation for \fBaidpars\fR.
+
+The 'b' key works with no predefined dispersion solution or features. If
+two or more features are identified, with 'm', spanning the range of the
+data or if a coordinate function is defined, from a previous solution, then
+the 'e', 'l', and 'y' keys may be used to identify additional features from
+a coordinate list. The 'e' key only adds features at the coordinates of
+the line lists if the centering algorithm finds a feature at that
+wavelength (as described below). The 'y' key works in reverse by finding
+the prominent features using a peak finding algorithm and then looking in
+the coordinate list for entries near the estimated position. Up to a
+maximum number of features (\fImaxfeatures\fR) will be selected. If there
+are more peaks only the strongest are kept. In either of these cases there
+is no automatic fitting and refitting of the dispersion function.
+
+The 'l' key combines automatic fits with locating lines from the coordinate
+list. If two or more features are defined an initial fit is made. Then
+for each coordinate value in the coordinate list the pixel coordinate is
+determined and a search for a feature at that point is made. If a feature
+is found (based on the parameters \fIftype, fwidth\fR, \fIcradius\fR, and
+\fBthreshold\fR) its user coordinate value based on the coordinate function
+is determined. If the coordinate function value matches the user
+coordinate from the coordinate list within the error limit set by the
+parameter \fImatch\fR then the new feature is entered in the feature list.
+Up to a maximum number of features, set by the parameter \fImaxfeatures\fR,
+may be defined in this way. A new user coordinate function is fit to all
+the located features. Finally, the graph is redrawn in user coordinates
+with the additional features found from the coordinate list marked.
+
+A minimum of two features must be defined for the 'l' key algorithm to
+work. However, three or more features are preferable to determine changes
+in the dispersion as a function of position.
+
+The 'f' key fits a function of the pixel coordinates to the user
+coordinates. The type of function, order and other fitting parameters
+are initially set with the parameters \fIfunction, order, sample,
+niterate, low_reject, high_reject\fR and \fIgrow\fR.. The value of the
+function for a particular pixel coordinate is called the function
+coordinate and each feature in the feature list has a function
+coordinate value. The fitted function also is used to convert pixel
+coordinates to user coordinates in the graph. The fitting is done
+within the interactive curve fitting package which has its own set of
+interactive commands. For further information on this package see the
+help material under \fBicfit\fR.
+
+If a zero point shift is desired without changing the coordinate function
+the user may specify the coordinate of a point in the spectrum with
+the 's' key from which a shift is determined. The 'g' key also
+determines a shift by minimizing the difference between the user
+coordinates and the fitted coordinates. This is used when a previously
+determined coordinate function is applied to a new spectrum having
+fewer or poorer lines and only a zero point shift can reasonably be
+determined. Note that the zero point shift is in user coordinates.
+This is only an approximate correction for shifts in the raw spectra
+since these shifts are in pixels and the coordinate function should
+also be appropriately shifted.
+
+One a set of features is defined one may select features for various
+operations. To select feature as the current feature the keys '.', 'n',
+'+', and '-' are used. The '.' selects the feature nearest the cursor, the
+'n' and '+' select the next feature, and the '-' selects the previous
+feature relative to the current feature in the feature list as ordered by
+pixel coordinate. These keys are useful when redefining the user
+coordinate with the 'u' key, changing the fitting weight of a feature with
+'v', and when examining features in zoom mode.
+
+Features may be deleted with the key 'd'. All features are deleted
+when the 'a' key immediately precedes the delete key. Deleting the
+features does not delete the coordinate function. Features deleted in the
+curve fitting package also are removed from the feature list upon
+exiting the curve fitting package.
+
+It is common to transfer the feature identifications and coordinate function
+from one image to another. When a new image without a database entry
+is examined, such as when going to the next image in the input list,
+changing image lines or columns with 'j', 'k' and 'o', or selecting
+a new image with the ":image" command, the current feature list and coordinate
+function are kept. Alternatively, a database record from a different
+image may be read with the ":read" command. When transferring feature
+identifications between images the feature coordinates will not agree exactly
+with the new image feature positions and several options are available to
+reregister the feature positions. The key 'c' centers the feature nearest
+the cursor using the current position as the starting point. When preceded
+with the 'a' key all the features are recentered (the user must refit
+the coordinate function if desired). As an aside, the recentering
+function is also useful when the parameters governing the feature
+centering algorithm are changed. An additional options is the ":add"
+command to add features from a database record. This does not overwrite
+previous features (or the fitting functions) as does ":read".
+
+The (c)entering function is applicable when the shift between the current
+and true feature positions is small. Larger shifts may be determined
+automatically with the 's' or 'x' keys.
+
+A zero point shift is specified interactively with the 's' key by using the
+cursor to indicate the coordinate of a point in the spectrum. If there are
+no features then the shift is exactly as marked by the cursor. If there
+are features the specified shift is applied, the features are recentered,
+and the mean shift for all the features is determined.
+
+The 'x' key uses the automatic line identification algorithm (see
+\fBaidpars\fR) with the constraint that the dispersion is nearly the
+same and the is primarily a shift in the coordinate zero point. If
+features are defined, normally by inheritance from another spectrum, then a
+first pass is done to identify those features in the spectrum. Since this
+only works when the shifts are significantly less than the dispersion range
+of the spectrum (i.e. a significant number of features are in common) a
+second pass using the full coordinate line list is performed if a shift
+based on the features is not found. After a shift is found any features
+remaining from the original list are recentered and a mean shift is
+computed.
+
+In addition to the single keystroke commands there are commands initiated
+by the key ':' (colon commands). As with the keystroke commands there are
+a number of standard graphics features available beginning with ":."
+(type ":.help" for these commands). The identify colon commands
+allow the task parameter values to be listed and to be reset
+within the task. A parameter is listed by typing its name. The colon command
+":show" lists all the parameters. A parameter value is reset by
+typing the parameter name followed by the new value; for example
+":match 10". Other colon commands display the feature list (:features),
+control reading and writing records to the database (:read and :write),
+and set the graph display format.
+
+The feature identification process for an image is completed by typing
+'q' to quit. Attempting to quit an image without explicitly
+recording changes in the feature database produces a warning message
+unless the \fIautowrite\fR parameter is set. If this parameter is
+not set a prompt is given asking whether to save the results otherwise
+the results are automatically saved. Also
+the reference spectrum keyword REFSPEC is added to the image header at
+this time. This is used by \fBrefspectra\fR and \fBdispcor\fR.
+As an immediate exit the 'I' interrupt key may be used. This does not save
+the feature information and may leave the graphics in a confused state.
+.ih
+DATABASE RECORDS
+The database specified by the parameter \fIdatabase\fR is a directory of
+simple text files. The text files have names beginning with 'id' followed
+by the entry name, usually the name of the image. The database text files
+consist of a number of records. A record begins with a line starting with the
+keyword "begin". The rest of the line is the record identifier. Records
+read and written by \fBidentify\fR have "identify" as the first word of the
+identifier. Following this is a name which may be specified following the
+":read" or ":write" commands. If no name is specified then the image name
+is used. For 1D spectra the database entry includes the aperture number
+and so to read a solution from a aperture different than the current image
+and aperture number must be specified. For 2D/3D images the entry name
+has the 1D image section which is what is specified to read the entry.
+The lines following the record identifier contain
+the feature information and dispersion function coefficients.
+
+The dispersion function is saved in the database as a series of
+coefficients. The section containing the coefficients starts with the
+keyword "coefficients" and the number of coefficients.
+
+The first four coefficients define the type of function, the order
+or number of spline pieces, and the range of the independent variable
+(the line or column coordinate along the dispersion). The first
+coefficient is the function type code with values:
+
+.nf
+ Code Type
+ 1 Chebyshev polynomial
+ 2 Legendre polynomial
+ 3 Cubic spline
+ 4 Linear spline
+.fi
+
+The second coefficient is the order (actually the number of terms) of
+the polynomial or the number of pieces in the spline.
+
+The next two coefficients are the range of the independent variable over
+which the function is defined. These values are used to normalize the
+input variable to the range -1 to 1 in the polynomial functions. If the
+independent variable is x and the normalized variable is n, then
+
+.nf
+ n = (2 * x - (xmax + xmin)) / (xmax - xmin)
+.fi
+
+where xmin and xmax are the two coefficients.
+
+The spline functions divide the range into the specified number of
+pieces. A spline coordinate s and the nearest integer below s,
+denoted as j, are defined by
+
+.nf
+ s = (x - xmin) / (xmax - xmin) * npieces
+ j = integer part of s
+.fi
+
+where npieces are the number of pieces.
+
+The remaining coefficients are those for the appropriate function.
+The number of coefficients is either the same as the function order
+for the polynomials, npieces+1 for the linear spline, or npieces + 3
+for the cubic spline.
+
+1. Chebyshev Polynomial
+
+The polynomial can be expressed as the sum
+
+.nf
+ y = sum from i=1 to order {c_i * z_i}
+.fi
+
+where the c_i are the coefficients and the z_i are defined
+interactively as:
+
+.nf
+ z_1 = 1
+ z_2 = n
+ z_i = 2 * n * z_{i-1} - z_{i-2}
+.fi
+
+2. Legendre Polynomial
+
+The polynomial can be expressed as the sum
+
+.nf
+ y = sum from i=1 to order {c_i * z_i}
+.fi
+
+where the c_i are the coefficients and the z_i are defined
+interactively as:
+
+.nf
+ z_1 = 1
+ z_2 = n
+ z_i = ((2*i-3) * n * z_{i-1} - (i-2) * z_{i-2}) / (i-1)
+.fi
+
+3. Linear Spline
+
+The linear spline is evaluated as
+
+.nf
+ y = c_j * a + c_{j+1} * b
+.fi
+
+where j is as defined earlier and a and b are fractional difference
+between s and the nearest integers above and below
+
+.nf
+ a = (j + 1) - s
+ b = s - j
+.fi
+
+4. Cubic Spline
+
+The cubic spline is evaluated as
+
+.nf
+ y = sum from i=0 to 3 {c_{i+j} * z_i}
+.fi
+
+where j is as defined earlier. The term z_i are computed from
+a and b, as defined earlier, as follows
+
+.nf
+ z_0 = a**3
+ z_1 = 1 + 3 * a * (1 + a * b)
+ z_2 = 1 + 3 * b * (1 + a * b)
+ z_3 = b**3
+.fi
+.ih
+EXAMPLES
+1. Because this task is interactive and has many possible applications
+it is difficult to provide actual examples. Instead some uses of the task
+are described.
+
+.ls o
+For defining distortions in the slit dimension as a function of
+wavelength the positions of objects are marked at some wavelength.
+The task \fBreidentify\fR is then used to trace the features to other
+wavelengths.
+.le
+.ls o
+For determining dispersion solutions in a one dimensional
+spectrum an arc calibration is used. Three emission features are marked
+and the (l)ocate key is used to find additional features from a
+coordinate list of arc lines. The dispersion solution is fit interactively
+and badly determined or misidentified lines are deleted. The
+solution may be written to the database or transferred to the object
+spectrum by reading the object image and deleting all the features.
+Deleting the features does not delete the coordinate function.
+.le
+.ls o
+For determining a two or three dimensional coordinate transformation a
+dispersion solution is determined at one slit position in a long slit arc
+spectrum or one spatial position in a Fabry-Perot spectrum as in the
+previous example. The features are then traced to other positions with the
+task \fBreidentify\fR.
+.le
+
+2. For images which are two or three dimensional it is necessary to
+specify the image axis for the data vector and the number of pixels at each
+point across the vector direction to sum. One way specify a vector is to
+use an image section to define a vector. For example, to select column
+20:
+
+.nf
+ cl> identify obj[20,*]
+.fi
+
+The alternative is to use the section parameter. Below are some examples
+of the section parameter syntax for an image "im2d" which is 100x200
+and "im3d" which is 100x200x50. On the left is the section string syntax
+and on the right is the image section
+
+.nf
+ Section parameter | Image section | Description
+ ------------------|---------------------|---------------------
+ first line | im2d[*,1] | First image line
+ middle column | im2d[50,*] | Middle image column
+ last z | im3d[100,200,*] | Last image z vector
+ middle last y | im3d[50,*,50] | Image y vector
+ line 20 | im2d[*,20] | Line 20
+ column 20 | im2d[20,*] | Column 20
+ x 20 | im2d[*,20] | Line 20
+ y 20 | im2d[20,*] | Column 20
+ y 20 30 | im2d[20,*,30] | Column 20
+ z 20 30 | im3d[20,30,*] | Image z vector
+ x middle | im3d[*,100,25] | Middle of image
+ y middle | im3d[50,*,25] | Middle of image
+ z middle | im3d[50,100,*] | Middle of image
+.fi
+
+The most common usage should be "middle line", "middle column" or "middle z".
+
+The summing factors apply to the axes across the specified vector. For
+3D images there may be one or two values. The following shows which axes
+are summed, the second and third columns, when the vector axis is that shown
+in the first column.
+
+.nf
+ Vector axis | Sum axis in 2D | Sum axes in 3D
+ ------------------|---------------------|--------------------
+ 1 | 2 | 2 3
+ 2 | 1 | 1 3
+ 3 | - | 1 2
+.fi
+
+.ih
+REVISIONS
+.ls IDENTIFY V2.11
+The dispersion units are now determined from a user parameter,
+the coordinate list, or the database entry.
+
+A new key, 'e', has been added to add features from a line list without
+doing any fits. This is like the 'l' but without the automatic
+fitting before and after adding new features.
+
+A new key, 'b', has been added to apply an automatic line identification
+algorithm.
+
+The 'x' key has been changed to use the automatic line identification
+algorithm. The allows finding much larger shifts.
+
+The match parameter may now be specified either in user coordinates or
+in pixels. The default is now 3 pixels.
+
+The default threshold value has been changed to 0.
+.le
+.ls IDENTIFY V2.10.3
+The section and nsum parameter syntax was extended to apply to 3D
+images. The previous values and defaults may still be used.
+
+The 'v' key was added to allow assigning weights to features.
+.le
+.ls IDENTIFY V2.10
+The principle revision is to allow multiple aperture images and long slit
+spectra to be treated as a unit. New keystrokes allow jumping or scrolling
+within multiple spectra in a single image. For aperture spectra the
+database entries are referenced by image name and aperture number and not
+with image sections. Thus, IDENTIFY solutions are not tied to specific
+image lines in this case. There is a new autowrite parameter which may
+be set to eliminate the save to database query upon exiting. The new
+colon command "add" may be used to add features based on some other
+spectrum or arc type and then apply the fit to the combined set of features.
+.le
+.ih
+SEE ALSO
+autoidentify, reidentify, aidpars, center1d, linelists, fitcoords, icfit,
+gtools
+.endhelp
diff --git a/noao/onedspec/doc/lcalib.hlp b/noao/onedspec/doc/lcalib.hlp
new file mode 100644
index 00000000..cc327217
--- /dev/null
+++ b/noao/onedspec/doc/lcalib.hlp
@@ -0,0 +1,125 @@
+.help lcalib Mar92 noao.onedspec
+.ih
+NAME
+lcalib -- List information about the spectral calibration data
+.ih
+USAGE
+lcalib option star_name
+.ih
+PARAMETERS
+.ls option
+Chooses calibration data to be listed. Option
+may be: "bands" to list the bandpasses at each wavelength, "ext" to
+list the extinction at each wavelength, "mags", "fnu", or "flam"
+to list the magnitude, or flux of
+the star (selected by the star_name parameter) at each wavelength, or
+"stars" to list the star names available in the calibration directory.
+.le
+.ls star_name
+Selects which star's magnitude list is chosen if the option parameter
+is "mags", "fnu", "flam", or "bands". Also if '?' a list of available
+stars in the specified calibration directory is given.
+.le
+
+The following three queried parameters apply if the selected calibration
+file is for a blackbody. See \fBstandard\fR for further details.
+.ls mag
+The magnitude of the observed star in the band given by the
+\fImagband\fR parameter. If the magnitude is not in the same band as
+the blackbody calibration file then the magnitude may be converted to
+the calibration band provided the "params.dat" file containing relative
+magnitudes between the two bands is in the calibration directory
+.le
+.ls magband
+The standard band name for the input magnitude. This should generally
+be the same band as the blackbody calibration file. If it is
+not the magnitude will be converted to the calibration band.
+.le
+.ls teff
+The effective temperature (deg K) or the spectral type of the star being
+calibrated. If a spectral type is specified a "params.dat" file must exist
+in the calibration directory. The spectral types are specified in the same
+form as in the "params.dat" file. For the standard blackbody calibration
+directory the spectral types are specified as A0I, A0III, or A0V, where A
+can be any letter OBAFGKM, the single digit subclass is between 0 and 9,
+and the luminousity class is one of I, III, or V. If no luminousity class
+is given it defaults to dwarf.
+.le
+
+.ls extinction
+Extinction file. The current standard extinction files:
+.nf
+ onedstds$kpnoextinct.dat - KPNO standard extinction
+ onedstds$ctioextinct.dat - CTIO standard extinction
+.fi
+.le
+.ls caldir
+Calibration directory containing standard star data. The directory name
+must end with /. The current calibration directories available in the
+onedstds$ may be listed with the command:
+
+.nf
+ cl> page onedstds$README
+.fi
+.le
+.ls fnuzero = 3.68e-20
+The absolute flux per unit frequency at a magnitude of zero. This is used
+to convert the calibration magnitudes to absolute flux by the formula
+
+ Flux = fnuzero * 10. ** (-0.4 * magnitude)
+
+The flux units are also determined by this parameter. However, the
+frequency to wavelength interval conversion assumes frequency in hertz.
+The default value is based on a calibration of Vega at 5556 Angstroms of
+3.52e-20 ergs/cm2/s/hz for a magnitude of 0.048. This default value
+is that used in earlier versions of this task which did not allow the
+user to change this calibration.
+.le
+.ih
+DESCRIPTION
+LCALIB provides a means of checking the flux calibration data. The calibration
+data consists of extinction, bandpasses, and stellar magnitudes.
+
+The extinction is given in an extinction file consisting of lines with
+wavelength and extinction. The wavelengths must be order in increasing
+wavelength and the wavelengths must be in Angstroms. There are two
+standard extinction files currently available, "onedstds$kpnoextinct.dat",
+and "onedstds$ctioextinct.dat".
+
+The standard star data are in files in a calibration
+directory specified with the parameter \fIcaldir\fR. A standard star
+file is selected by taking the star name given, by the parameter
+\fIstar_name\fR, removing blanks, +'s and -'s, appending ".dat", and converting
+to lower case. This file name is appended to the specified calibration
+directory. A calibration file consists of lines containing a wavelength,
+a stellar magnitude, and a bandpass full width. The wavelengths are in
+Angstroms. Comment lines beginning with # may be included in the file.
+The star names printed by this task are just the first line of each file
+in the calibration directory with the first character (#) removed.
+The calibration files may be typed, copied, and printed. \fBLcalib\fR
+may also be used to list data from the calibration files.
+.ih
+EXAMPLES
+
+.nf
+ # List the extinction table
+ cl> lcalib ext
+ # Plot the extinction table
+ cl> lcalib ext | graph
+ # Plot the energy distribution
+ cl> lcalib mags "bd+28 4211" | graph
+ # List the names of all the stars
+ cl> lcalib stars caldir=onedstds$irscal/
+ # As above but for IIDS file
+ cl> lcalib stars calib_file=onedstds$iidscal/
+.fi
+.ih
+REVISIONS
+.ls LCALIB V2.10
+This task has a more compact listing for the "stars" option and allows
+paging a list of stars when the star name query is not recognized.
+.le
+.ih
+SEE ALSO
+standard, sensfunc, onedstds$README
+.endhelp
diff --git a/noao/onedspec/doc/mkspec.hlp b/noao/onedspec/doc/mkspec.hlp
new file mode 100644
index 00000000..96efd726
--- /dev/null
+++ b/noao/onedspec/doc/mkspec.hlp
@@ -0,0 +1,86 @@
+.help mkspec Mar92 noao.onedspec
+.ih
+NAME
+mkspec -- generate an artificial spectrum or image (obsolete)
+.ih
+USAGE
+mkspec image_name image_title ncols nlines function
+.ih
+PARAMETERS
+.ls image_name
+The name to be given to the image file
+.le
+.ls image_title
+A character string to be used to describe the image
+.le
+.ls ncols
+The number of pixels in the spectrum (the length of the image).
+.le
+.ls nlines
+The number or lines (rows) in the image.
+.le
+.ls function
+An indicator specifying the form of the spectrum: 1 - a constant,
+2 - a ramp running from start_level to end_level, 3 - a black body
+extending in wavelength (Angstroms) from start_wave to end_wave
+at a given temperature (in degrees K).
+.le
+.ls constant
+The value to be assigned to the spectrum if function=1 (constant).
+.le
+.ls start_level
+The starting value to be assigned to the spectrum at pixel 1 if
+function=2 (ramp).
+.le
+.ls end_level
+The ending value of the spectrum assigned at pixel=ncols if function=2.
+.le
+.ls start_wave
+The wavelength (Angstroms) assigned to pixel 1 if function=3 (Black Body).
+.le
+.ls end_wave
+The wavelength (Angstroms) assigned to the last pixel if function=3.
+.le
+.ls temperature
+The black body temperature (degrees K) for which the spectrum
+is to be created if function=3.
+.le
+.ih
+DESCRIPTION
+An artificial image is created with the specified name and length.
+The image may have a constant value (function=1), or may be a ramp
+with either positive or negative slope (function=2), or may be
+a black body curve (function=3).
+
+Only those parameters specific to the functional form of the image
+need be specified. In all cases the parameters image_name, image_title,
+ncols, nlines, and function are required. If function=1, parameter constant
+is required; if function=2, start_level and end_level are required;
+if function=3, start_wave, end_wave, and temperature are required.
+
+All black body functions are normalized to 1.0 at their peak
+intensity which may occur at a wavelength beyond the extent of
+the generated spectrum.
+
+NOTE THAT THIS TASK IS OBSOLETE AND ARTDATA.MK1DSPEC SHOULD BE USED.
+In particular this task does not set the header dispersion coordinate
+system.
+.ih
+EXAMPLES
+
+.nf
+ cl> mkspec allones "Spectrum of 1.0" 1024 1 1 constant=1.0
+ cl> mkspec ramp "From 100.0 to 0.0" 1024 64 2 start=100 \
+ >>> end=0.0
+ cl> mkspec bb5000 "5000 deg black body" 512 1 3 start=3000 \
+ >>> end=8000 temp=5000
+.fi
+.ih
+REVISIONS
+.ls MKSPEC V2.10
+This task is unchanged.
+.le
+.ih
+SEE ALSO
+artdata.mk1dspec, artdata.mk2dspec, artdata.mkechelle
+.endhelp
diff --git a/noao/onedspec/doc/names.hlp b/noao/onedspec/doc/names.hlp
new file mode 100644
index 00000000..9004b20e
--- /dev/null
+++ b/noao/onedspec/doc/names.hlp
@@ -0,0 +1,67 @@
+.help names Mar92 noao.onedspec
+.ih
+NAME
+names -- Generate image names from a root and a range descriptor
+.ih
+USAGE
+names input records
+.ih
+PARAMETERS
+.ls input
+The root file name for the input records to be calibrated.
+.le
+.ls records
+The range of spectra to be included in the calibration operation.
+Each range item will be appended to the root name to form an
+image file name.
+.le
+.ls append = ""
+If not a null string, this character string will be appended to
+all the generated image names. This allows for a specification of
+image sections.
+.le
+.ls check = no
+If set to yes, a check is made that each name implied by the range
+specification has at least an image header. The pixel file is not
+checked. If set to no, then all possible image names are generated
+even if no image exists.
+.le
+.ih
+DESCRIPTION
+A sequence of image names is generated from the input root file name
+and the range description by appending the possible range values to
+the root in the form "root.nnnn". At least four digits will follow the
+root.
+
+If an append string is specified, this is added to the image name as well.
+
+The generated image names are written to STDOUT, but may be redirected
+to a file for further use.
+.ih
+EXAMPLES
+The following will generate names of the form nite1.0001, nite1.0002 ...
+nite1.0010 and place the list in the file nite1.lst.
+
+.nf
+ cl> names nite1 1-10 >nite1.lst
+.fi
+
+The next example uses the append option to specify that only the
+first 512 pixels of each image (spectrum) are to used in the image name.
+
+.nf
+ cl> names nite1 1-10 append="[1:512]" >nite1.lst
+.fi
+.ih
+REVISIONS
+.ls NAMES V2.10
+This task is unchanged.
+.le
+.ih
+.ih
+BUGS
+The append option is only useful for adding image sections since it is
+added after the ONEDSPEC name is generated. Appending other strings
+produces names such as root.0012str which are not recognized by
+the package.
+.endhelp
diff --git a/noao/onedspec/doc/ndprep.hlp b/noao/onedspec/doc/ndprep.hlp
new file mode 100644
index 00000000..6f59ba4b
--- /dev/null
+++ b/noao/onedspec/doc/ndprep.hlp
@@ -0,0 +1,115 @@
+.help ndprep Mar92 noao.onedspec
+.ih
+NAME
+ndprep -- Make a neutral density filter calibration image
+.ih
+USAGE
+ndprep filter_curve output
+.ih
+PARAMETERS
+.ls filter_curve
+Neutral density filter curve. The directory specified by the parameter
+\fIdirectory\fR is prepended to this name so if a directory is specified
+then it should not be given here. If '?' a list of filter curves
+in the specified directory is typed.
+.le
+.ls output
+Output neutral density filter image.
+.le
+.ls w0
+Starting wavelength for the output image in Angstroms.
+.le
+.ls dw
+Wavelength increment for the output image in Angstroms.
+.le
+.ls nw
+Number of wavelength points for the output image (i.e. the size of the
+output image).
+.le
+.ls nspace = 0
+Number of spatial points for a two dimensional image. If the value is
+zero then a one dimensional image is created.
+.le
+.ls logarithm = no
+Use logarithmic wavelengths and intervals? If yes then the wavelengths
+will have the same starting and ending points and number of pixels but
+the wavelength intervals will be logarithmic.
+.le
+.ls flux = yes
+Conserve flux when rebinning to logarithmic wavelength intervals?
+.le
+.ls dispaxis = 1
+Dispersion axis for two dimensional images. Dispersion along the lines
+is 1 and dispersion along the columns is 2.
+.le
+.ls directory = "onedstds$ctio/"
+Directory containing neutral density filter curves. This directory is
+prepended to the specified fiter curve file (and so must end with '/'
+or '$').
+.le
+.ih
+DESCRIPTION
+A neutral density (ND) filter curve is converted to a calibration image
+with the same size and wavelength range as the images to be calibrated.
+A list of standard neutral density curves is typed if the filter
+curve name is given as '?'. The ND curves are text files containing
+wavelength and filter transmission pairs. Comments begin with '#'.
+A plot of the ND curve can be obtained using \fBgraph\fR.
+
+The ND curve is first interpolated to a one dimensional image of
+\fInw\fR wavelength points with starting wavelength \fIwO\fR and
+wavelength increment \fIdw\fR using the task \fBsinterp\fR. The
+wavelength parameters must be in the same units as the filter curves
+(currently Angstroms) even if the final calibration image is to be in
+logarithmic wavelength intervals. If logarithmic wavelength format
+is specified the image is rebinned over the same wavelength range with
+the same number of points using the task \fBdispcor\fR. The rebinning
+may include flux conservation to account for the changing size of
+pixels or simply interpolate. Note that flux conservation will
+change the apparent shape of the ND curve.
+
+If the number of points across the dispersion, \fInspace\fR is zero then
+the final calibration image is one dimensional. If it is greater than
+zero the one dimensional ND image is expanded to the specified number
+of spatial points with the dispersion axis specified by the parameter
+\fIdispaxis\fR (1 = dispersion along the lines, 2 = dispersion along
+the columns).
+.ih
+EXAMPLES
+To get a list of standard ND filter curves:
+
+ cl> ndprep ?
+
+To graph the ND filter curve:
+
+ cl> graph onedstds$ctio/nd1m.100mag.dat
+
+Naturally, if a calibration image is made then the image plotting tasks
+such as \fBgraph\fR, \fBimplot\fR, and \fBsplot\fR may also be used.
+
+To make a one dimensional ND calibration spectrum:
+
+.nf
+ cl> ndprep w0=4000 dw=1.2 nw=512
+ Input ND filter curve: onedstds$ctio/nd1m.100mag.dat
+ Output calibration image: NDimage
+.fi
+
+To make a two dimensional ND calibration spectrum in logarithmic wavelength:
+
+.nf
+ cl> ndprep w0=4000 dw=1.2 nw=512 nspace=200 log+
+ Input ND filter curve: onedstds$ctio/nd4m.u000mag.dat
+ Output calibration image: NDimage
+.fi
+.ih
+REVISIONS
+.ls NDPREP V2.10
+This task was moved from the \fBproto\fR package. It was originally
+written at CTIO for CTIO data. It's functionality is largely unchanged
+though it has been updated for changes in the \fBonedspec\fR package.
+.le
+.ih
+SEE ALSO
+sinterp, dispcor
+.endhelp
diff --git a/noao/onedspec/doc/odcombine.hlp b/noao/onedspec/doc/odcombine.hlp
new file mode 100644
index 00000000..11ddffe5
--- /dev/null
+++ b/noao/onedspec/doc/odcombine.hlp
@@ -0,0 +1,480 @@
+.help odcombine Apr04 onedspec
+.ih
+NAME
+odcombine -- Combine spectra using various algorithms
+.ih
+USAGE
+odcombine input output
+.ih
+PARAMETERS
+.ls input
+List of input images containing spectra to be combined. The spectra
+in the images to be combined are selected with the \fIapertures\fR and
+\fIgroup\fR parameters. Only the primary spectrum is combined and
+the associated band spectra are ignored. This task does not work on
+higher dimensional spectra data. To apply it first use a task to
+extract it to 1D spectra. The simplest method is \fBscopy\fR.
+.le
+.ls output
+List of output images to be created containing the combined spectra. If
+the grouping option is "all" then only one output image is created with the
+specified name. If the grouping option is "images" then there will be one
+output image for each input image and the output list must match the input
+list in number. If the grouping option is "apertures" then only one output
+root name is specified and there will be one output image for each selected
+aperture. In this case the output images will have a name formed from the
+root name and a four digit aperture number extension. In all cases the
+output images contain a single 1D spectrum. Other tasks, such as
+\fBscopy\fR, may be used to pack the spectra into a single file.
+.le
+
+
+There are a number of additional optional output files that may be produced.
+The lists are handled in the same was as for the primary output; i.e.
+depending on the grouping a single name, root name, or a matching list
+is specified.
+.ls headers = "" (optional)
+Optional output multiextension FITS file(s). The extensions are dataless
+headers from each input image.
+.le
+.ls bpmasks = "" (optional)
+Optional output bad pixel mask(s) with good values of 0 and bad values of
+1. Output pixels are marked as bad when no input pixels contributed to the
+output pixel. The file name is also added to the output image header under
+the keyword BPM.
+.le
+.ls rejmask = "" (optional)
+Optional output mask file(s) identifying rejected or excluded pixels. The
+pixel mask is the size of the output image but there is one extra dimension
+with length equal to the number of input images. Each element of the
+highest dimension is a mask corresponding to an input image with values of
+1 for rejected or excluded pixels and values of 0 for pixels which were
+used. The order of the masks is the order of the input images and image
+header keywords, indexed by the pixel coordinate of the highest dimension
+identify the input images. Note that the pixel positions are in the output
+pixel coordinate system.
+.le
+.ls nrejmasks = "" (optional)
+Optional output pixel mask(s) giving the number of input pixels rejected or
+excluded from the input images.
+.le
+.ls expmasks = "" (optional)
+Optional output exposure mask(s) giving the sum of the exposure values of
+the input images with non-zero weights that contributed to that pixel.
+Since masks are integer, the exposure values may be scaled to preserve
+dynamic range and fractional significance. The scaling values are given in
+the header under the keywords MASKSCAL and MASKZERO. Exposure values are
+computed from the mask values by scale * value + zero where scale is the
+value of the MASKSCAL keyword and zero is the value of the MASKZERO
+keyword.
+.le
+.ls sigma = "" (optional)
+Optional output sigma image(s). The sigma is the standard deviation,
+corrected for a finite population, of the input pixel values (excluding
+rejected pixels) about the output combined pixel values.
+.le
+.ls logfile = "STDOUT" (optional)
+Optional output log file. If no file is specified then no log information is
+produced. The special filename "STDOUT" prints log information to the
+terminal.
+.le
+
+
+.ce
+Grouping Parameters
+.ls apertures = ""
+List of apertures to be selected for combining. If none is specified
+then all apertures are selected. The syntax is a blank or comma separated
+list of aperture numbers or hypen separated aperture ranges.
+.le
+.ls group = "apertures" (all|images|apertures)
+Option for grouping input spectra for combining (after selection by aperture)
+from one or more input images. The options are:
+.ls "all"
+Combine all spectra from all images in the input list into a single output
+spectrum.
+.le
+.ls "images"
+Combine all spectra in each input image into a single spectrum in
+separate output images.
+.le
+.ls "apertures"
+Combine all spectra of the same aperture from all input images and put it
+into an output image with specified root name and a four digit aperture
+number extension.
+.le
+.le
+
+
+.ce
+Dispersion Matching Parameters
+.ls first = no
+Use the first input spectrum of each set to be combined to define the
+dispersion coordinates for combining and output? If yes then all other
+spectra to be combined will be interpolated to the dispersion of this
+spectrum and that dispersion defines the dispersion of the
+output spectrum. If no, then all the spectra are interpolated to a linear
+dispersion as determined by the following parameters. The interpolation
+type is set by the package parameter \fIinterp\fR.
+.le
+.ls w1 = INDEF, w2=INDEF, dw = INDEF, nw = INDEF, log = no
+The output linear or log linear wavelength scale if the dispersion of the
+first spectrum is not used. INDEF values are filled in from the maximum
+wavelength range and minimum dispersion of the spectra to be combined. The
+parameters are aways specified in linear wavelength even when the log
+parameter is set to produce constant pixel increments in the log of the
+wavelength. The dispersion is interpreted in that case as the difference
+in the log of the endpoints divided by the number of pixel.
+.le
+
+
+.ce
+Combining Parameters
+.ls combine = "average" (average|median|sum)
+Type of combining operation performed on the final set of pixels (after
+offsetting, masking, thresholding, and rejection). The choices are
+"average", "median", or "sum". The median uses the average of the two central
+values when the number of pixels is even. For the average and sum, the
+pixel values are multiplied by the weights (1 if no weighting is used)
+and summed. The average is computed by dividing by the sum of the weights.
+If the sum of the weights is zero then the unweighted average is used.
+.le
+.ls reject = "none" (none|minmax|ccdclip|crreject|sigclip|avsigclip|pclip)
+Type of rejection operation performed on the pixels remaining after offsetting,
+masking and thresholding. The algorithms are described in the
+help page for \fBimcombine\fR. The rejection choices are:
+
+.nf
+ none - No rejection
+ minmax - Reject the nlow and nhigh pixels
+ ccdclip - Reject pixels using CCD noise parameters
+ crreject - Reject only positive pixels using CCD noise parameters
+ sigclip - Reject pixels using a sigma clipping algorithm
+ avsigclip - Reject pixels using an averaged sigma clipping algorithm
+ pclip - Reject pixels using sigma based on percentiles
+.fi
+
+.le
+.ls outtype = "real" (none|short|ushort|integer|long|real|double)
+Output image pixel datatype. The pixel datatypes are "double", "real",
+"long", "integer", unsigned short "ushort", and "short" with highest
+precedence first. If "none" is specified then the highest precedence
+datatype of the input images is used. When there is a mixture of
+short and unsigned short images the highest precedence become integer.
+The datatypes may be abbreviated to a single character.
+.le
+.ls outlimits = ""
+Output region limits specified as a pair of whitespace separated pixel
+values.
+.le
+
+
+.ce
+Masking Parameters
+.ls smaskformat = "bpmspectrum" (bpmspectrum|bpmpixel)
+When a mask is applied it must be matched to the input spectrum. If the
+value of this parameter is "bpmspectrum" the mask file is assumed to have a
+spectral file structure with aperture and dispersion information. The mask
+spectrum is matched to the input spectrum by aperture number and is
+rebinned from its dispersion to match the rebinned dispersion of the input
+spectrum. If the value is "bpmpixel" the mask file is assumed to have
+minimal header information and the pixel information is matched to the
+input image pixels. This means the mask pixels are extracted from the same
+line as the input spectrum and the mask pixels are resampled in the same
+way as the input spectrum pixels.
+.le
+.ls smasktype = "none" (none|goodvalue|badvalue|goodbits|badbit)
+Type of pixel masking to use. If "none" or "" then no pixel masking is
+done even if an image has an associated pixel mask. The other choices are
+to select the value in the pixel mask to be treated as good (goodvalue) or
+bad (badvalue) or the bits (specified as a value) to be treated as good
+(goodbits) or bad (badbits). The pixel mask filename is specified by the
+image header keyword "BPM". Note that if the input image contains
+multiple spectra then the mask file must also contain at least the
+selected apertures if the mask format is "bpmspectrum" or matching
+image dimensions if the mask format is "bpmpixel".
+.le
+.ls maskvalue = 0
+Mask value used with the \fImasktype\fR parameter. If the mask type
+selects good or bad bits the value may be specified using IRAF notation
+for decimal, octal, or hexadecimal; i.e 12, 14b, 0cx to select bits 3
+and 4.
+.le
+.ls blank = 0.
+Output value to be used when there are no pixels.
+.le
+
+
+.ce
+Scaling/Weighting Parameters
+
+The following scaling and weighting parameters have the following behavior
+and constraints, which are particularly relevant to multispec formats where
+multiple spectra are contained in an image with a single image header.
+When using image statistics these are calculated from the rebinned spectra
+being combined as expected. When using header keywords the values will be
+the same for all spectra from the same input file.
+
+When using a file then the list will be applied repeatedly to each
+group being combined. If the grouping is by aperture then the values will
+be matched in the order of the input images. Note that if an image does
+not contain a specified aperture the ordering will be wrong. If the
+grouping is by image then the file will be matched to the spectra in the
+order of the apertures in the image. And if the grouping is "all" then the
+list is matched in the order of the images and apertures within the
+images with the apertures in an image varying first.
+
+.ls scale = "none" (none|mode|median|mean|exposure|@<file>|!<keyword>)
+Multiplicative image scaling to be applied. The choices are none, multiply
+by the reciprocal of the mode, median, or mean of the specified statistics
+section, multiply by the reciprocal of the exposure time in the image header,
+multiply by the values in a specified file, or multiply by a specified
+image header keyword. When specified in a file the scales must be one per
+line in the order of the input images.
+.le
+.ls zero = "none" (none|mode|median|mean|@<file>|!<keyword>)
+Additive zero level image shifts to be applied. The choices are none, add
+the negative of the mode, median, or mean of the specified statistics
+section, add the values given in a file, or add the values given by an
+image header keyword. When specified in a file the zero values must be one
+per line in the order of the input images. File or keyword zero offset
+values do not allow a correction to the weights.
+.le
+.ls weight = "none" (none|mode|median|mean|exposure|@<file>|!<keyword>)
+Weights to be applied during the final averaging. The choices are none,
+the mode, median, or mean of the specified statistics section, the exposure
+time, values given in a file, or values given by an image header keyword.
+When specified in a file the weights must be one per line in the order of
+the input images and the only adjustment made by the task is for the number of
+images previously combined. In this case the weights should be those
+appropriate for the scaled images which would normally be the inverse
+of the variance in the scaled image.
+.le
+.ls statsec = ""
+Section of images to use in computing image statistics for scaling and
+weighting. If no section is given then the entire region of the input is
+sampled (for efficiency the images are sampled if they are big enough).
+When the images are offset relative to each other one can precede the image
+section with one of the modifiers "input", "output", "overlap". The first
+interprets the section relative to the input image (which is equivalent to
+not specifying a modifier), the second interprets the section relative to
+the output image, and the last selects the common overlap and any following
+section is ignored.
+.le
+.ls expname = ""
+Image header keyword to be used with the exposure scaling and weighting
+options. Also if an exposure keyword is specified that keyword will be
+added to the output image using a weighted average of the input exposure
+values.
+.le
+
+
+.ce
+Algorithm Parameters
+.ls lthreshold = INDEF, hthreshold = INDEF
+Low and high thresholds to be applied to the input pixels. This is done
+before any scaling, rejection, and combining. If INDEF the thresholds
+are not used.
+.le
+.ls nlow = 1, nhigh = 1 (minmax)
+The number of low and high pixels to be rejected by the "minmax" algorithm.
+These numbers are converted to fractions of the total number of input images
+so that if no rejections have taken place the specified number of pixels
+are rejected while if pixels have been rejected by masking, thresholding,
+or nonoverlap, then the fraction of the remaining pixels, truncated
+to an integer, is used.
+.le
+.ls nkeep = 1
+The minimum number of pixels to retain or the maximum number to reject
+when using the clipping algorithms (ccdclip, crreject, sigclip,
+avsigclip, or pclip). When given as a positive value this is the minimum
+number to keep. When given as a negative value the absolute value is
+the maximum number to reject. The latter is in addition to pixels
+missing due to non-overlapping offsets, bad pixel masks, or thresholds.
+.le
+.ls mclip = yes (ccdclip, crreject, sigclip, avsigcliip)
+Use the median as the estimate for the true intensity rather than the
+average with high and low values excluded in the "ccdclip", "crreject",
+"sigclip", and "avsigclip" algorithms? The median is a better estimator
+in the presence of data which one wants to reject than the average.
+However, computing the median is slower than the average.
+.le
+.ls lsigma = 3., hsigma = 3. (ccdclip, crreject, sigclip, avsigclip, pclip)
+Low and high sigma clipping factors for the "ccdclip", "crreject", "sigclip",
+"avsigclip", and "pclip" algorithms. They multiply a "sigma" factor
+produced by the algorithm to select a point below and above the average or
+median value for rejecting pixels. The lower sigma is ignored for the
+"crreject" algorithm.
+.le
+.ls rdnoise = "0.", gain = "1.", snoise = "0." (ccdclip, crreject)
+CCD readout noise in electrons, gain in electrons/DN, and sensitivity noise
+as a fraction. These parameters are used with the "ccdclip" and "crreject"
+algorithms. The values may be either numeric or an image header keyword
+which contains the value. The noise model for a pixel is:
+
+.nf
+ variance in DN = (rdnoise/gain)^2 + DN/gain + (snoise*DN)^2
+ variance in e- = (rdnoise)^2 + (gain*DN) + (snoise*(gain*DN))^2
+ = rdnoise^2 + Ne + (snoise * Ne)^2
+.fi
+
+where DN is the data number and Ne is the number of electrons. Sensitivity
+noise typically comes from noise introduced during flat fielding.
+.le
+.ls sigscale = 0.1 (ccdclip, crreject, sigclip, avsigclip)
+This parameter determines when poisson corrections are made to the
+computation of a sigma for images with different scale factors. If all
+relative scales are within this value of unity and all relative zero level
+offsets are within this fraction of the mean then no correction is made.
+The idea is that if the images are all similarly though not identically
+scaled, the extra computations involved in making poisson corrections for
+variations in the sigmas can be skipped. A value of zero will apply the
+corrections except in the case of equal images and a large value can be
+used if the sigmas of pixels in the images are independent of scale and
+zero level.
+.le
+.ls pclip = -0.5 (pclip)
+Percentile clipping algorithm parameter. If greater than
+one in absolute value then it specifies a number of pixels above or
+below the median to use for computing the clipping sigma. If less
+than one in absolute value then it specifies the fraction of the pixels
+above or below the median to use. A positive value selects a point
+above the median and a negative value selects a point below the median.
+The default of -0.5 selects approximately the quartile point.
+See the DESCRIPTION section for further details.
+.le
+.ls grow = 0.
+Radius in pixels for additional pixel to be rejected in an image with a
+rejected pixel from one of the rejection algorithms. This applies only to
+pixels rejected by one of the rejection algorithms and not the masked or
+threshold rejected pixels.
+.le
+
+The following parameters are internal to the task and not user parameters:
+
+.nf
+ offsets, masktype, maskvalue
+.fi
+
+.ce
+Environment Variables
+
+.ls <package>.interp
+When the spectra have to be interpolated to a common pixel sampling
+the "interp" parameter from the package from which ODCOMBINE is used
+will be used.
+.le
+.ih
+DESCRIPTION
+\fBOdcombine\fR combines input spectra by interpolating them (if necessary)
+to a common dispersion sampling, rejecting pixels exceeding specified low
+and high thresholds or identified as bad in a bad pixel mask, scaling them
+in various ways, applying a rejection algorithm based on known or empirical
+noise statistics, and computing the sum, weighted average, or median of the
+remaining pixels. Note that the "sum" option is the direct summation of
+the pixels and does not perform any rejection or scaling of the data
+regardless of the parameter settings.
+
+The input spectra are specified using an image list in which each image
+may contain multiple spectra. The set of spectra may be restricted
+by the \fIaperture\fR parameter to specific apertures. The set of input
+spectra may then be grouped using the \fIgroup\fR parameter and each
+group combined separately into final output spectra. The grouping
+options are to select all the input spectra regardless of the input
+image or aperture number, select all spectra of the same aperture,
+or select all the spectra from the same input image.
+
+The output consists of one image for each combined group. The output
+images and combined spectra inherit the header parameters from the first
+spectrum in the combined group. There are a number of additional optional
+outputs provided. The optional logfile lists parameters, the spectra
+combined for each group, scaling, weights, etc., and the output names.
+
+The spectral combining is done using pixels at common dispersion
+coordinates rather than physical or logical pixel coordinates. If the
+spectra to be combined do not have identical dispersion coordinates then
+the spectra are interpolated to a common dispersion sampling before
+combining. The interpolation conserves pixel values rather pixel fluxes.
+This means that flux calibrated data is treated correctly and that
+spectra in counts are not corrected in the interpolation for changes in
+pixel widths. The default interpolation function is a 5th order
+polynomial. The choice of interpolation type is made with the package
+parameter "interp". It may be set to "nearest", "linear", "spline3",
+"poly5", or "sinc". Remember that this applies to all tasks which might
+need to interpolate spectra in the \fBonedspec\fR and associated packages.
+For a discussion of interpolation types see \fBonedspec\fR.
+
+There are two choices for the common dispersion coordinate sampling. If the
+\fIfirst\fR parameter is set then the dispersion sampling of the first
+spectrum is used. If this dispersion is nonlinear then the end points and
+number of pixels are preserved and a linear dispersion is applied between
+the endpoints. If the parameter is not set then the user specified linear
+or log linear dispersion system is used. Any combination of starting
+wavelength, ending wavelength, wavelength per pixel, and number of output
+pixels may be specified. Unspecified values will default to reasonable
+values based on the minimum or maximum wavelengths of all spectra, the
+minimum dispersion, and the number of pixels needed to satisfy the other
+parameters. If the parameters overspecify the linear system then the
+ending wavelength is adjusted based on the other parameters. Note that for
+a log linear system the wavelengths are still specified in nonlog units and
+the dispersion is finally recalculated using the difference of the log
+wavelength endpoints divided by the number pixel intervals (the number of
+pixels minus one).
+
+This task is layered on top of the \fBimcombine\fR task. What happens
+is that the spectra for each group to be combined is extracted from
+the input, resampled to a common dispersion, and the resulting spectra
+written to temporary images, one per spectrum. The temporary images
+are written to the current working directory with names begining with
+"tmp". The same is done with any bad pixel masks. Then the list of
+images are combined using the IMCOMBINE algorithms. When the combining
+is completed the temporary images are removed. If ODCOMBINE aborts
+for some reason these file may be left behind and the user may delete
+them. Details of what IMCOMBINE does are presented separate under the
+help topic for the IMCOMBINE task.
+
+.ih
+EXAMPLES
+1. Combine orders of echelle images.
+
+.nf
+ cl> odcombine *.ec *%.ec%% group=images combine=sum
+.fi
+
+2. Combine all spectra using range syntax and scale by the exposure times.
+
+.nf
+ cl> names irs 10-42 > irs.dat
+ cl> odcombine @irs.dat irscombine group=all scale=exptime
+.fi
+
+3. Combine spectra by apertures using exposure time scaling and weighting.
+
+.nf
+ cl> odcombine *.ms comb1d \\
+ >>> group=apertures scale=exptime weights=exptime
+ cl> scopy comb1d.* comb.ms format="multispec"
+ cl> imdel comb1d.*
+.fi
+.ih
+REVISIONS
+.ls ODCOMBINE V2.12.3
+This is a new version that incorporates most of the features of
+IMCOMBINE.
+
+In addition to the many new features, including application of pixel
+masks, the following functional differences from the old SCOMBINE
+are noted.
+
+.ls 1
+The output is always a single spectrum per image.
+.le
+.ls 2
+The "first" option does not allow rebinning to a non-linear dispersion.
+Instead, it rebins to the nearest linear dispersion matching the first
+spectrum.
+.le
+.ih
+SEE ALSO
+imcombine, scombine, scopy, sarith, lscombine
+.endhelp
diff --git a/noao/onedspec/doc/onedspec.hlp b/noao/onedspec/doc/onedspec.hlp
new file mode 100644
index 00000000..a1c06ab9
--- /dev/null
+++ b/noao/onedspec/doc/onedspec.hlp
@@ -0,0 +1,293 @@
+.help package Nov94 noao.onedspec
+.ih
+NAME
+onedspec -- generic 1D spectral reduction and analysis package
+.ih
+USAGE
+onedspec
+.ih
+PARAMETERS
+.ls observatory = "observatory"
+Observatory at which the spectra were obtained if not specified in the
+image header by the keyword OBSERVAT. This parameter is used by several
+tasks in the package through parameter redirection so this parameter may be
+used to affect all these tasks at the same time. The observatory may be
+one of the observatories in the observatory database, "observatory" to
+select the observatory defined by the environment variable "observatory" or
+the parameter \fBobservatory.observatory\fR, or "obspars" to select the
+current parameters set in the \fBobservatory\fR task. See help for
+\fBobservatory\fR for additional information.
+.le
+.ls caldir = ""
+Calibration directory containing standard star data. This parameter
+is used by several tasks in the package through redirection. A list of
+standard calibration directories may be obtained by listing the file
+"onedstds$README"; for example:
+
+ cl> page onedstds$README
+
+The user may copy or create their own calibration files and specify
+the directory. The directory "" refers to the current working directory.
+.le
+.ls interp = "poly5" (nearest|linear|poly3|poly5|spline3|sinc)
+Spectrum interpolation type used when spectra are resampled. The choices are:
+
+.nf
+ nearest - nearest neighbor
+ linear - linear
+ poly3 - 3rd order polynomial
+ poly5 - 5th order polynomial
+ spline3 - cubic spline
+ sinc - sinc function
+.fi
+.le
+
+The following parameters apply to two and three dimensional images
+such as long slit or Fabry-Perot spectra. They allow selection of
+a line or column as the spectrum "aperture" and summing of neighboring
+elements to form a one dimensional spectrum as the tasks in the
+ONEDSPEC package expect.
+
+.ls dispaxis = 1
+The image axis corresponding to the dispersion. If there is an image
+header keyword DISPAXIS then the value of the keyword will be used
+otherwise this package parameter is used. The dispersion coordinates
+are a function of column, line, or band when this parameter is 1, 2
+or 3.
+.le
+.ls nsum = "1"
+The number of neighboring elements to sum. This is a string parameter
+that can have one or two numbers. For two dimensional images only
+one number is needed and specifies the number of lines or columns
+to sum depending on the dispersion axis. For three dimensional
+images two numbers may be given (if only one is given it defaults
+to the same value for both spatial axes) to specify the summing of
+the two spatial axes. The order is the lower dimensional spatial
+axis first.
+
+For an even value the elements summed are the central specified
+"aperture", nsum / 2 - 1 below, and nsum /2 above; i.e the
+central value is closer to the lower element than the upper.
+For example, for nsum=4 and an aperture of 10 for a dispersion
+axis of 1 in a two dimensional image the spectrum used will be
+the sum of lines 9 to 12.
+.le
+
+.ls records = ""
+This is a dummy parameter. It is applicable only in the \fBimred.irs\fR
+and \fBimred.iids\fR packages.
+.le
+.ls version = "ONEDSPEC V3: November 1991"
+Package version identification.
+.le
+.ih
+DESCRIPTION
+The \fBonedspec\fR package contains generic tasks for the reduction,
+analysis, and display of one dimensional spectra. The specifics of
+individual tasks may be found in their IRAF "help" pages. This document
+describes the general and common features of the tasks.
+
+The functions provided in the \fBonedspec\fR package with applicable tasks
+are summarized in Table 1.
+
+.ce
+Table 1: Functions provided in the \fBonedspec\fR package
+
+.nf
+1. Graphical display of spectra
+ bplot - Batch plots of spectra
+ identify - Identify features and fit dispersion functions
+ specplot - Stack and plot multiple spectra
+ splot - Interactive spectral plot/analysis
+
+2. Determining and applying dispersion calibrations
+ dispcor - Dispersion correct spectra
+ dopcor - Apply doppler corrections
+ identify - Identify features and fit dispersion functions
+ refspectra - Assign reference spectra to other spectra
+ reidentify - Automatically identify features in spectra
+ specshift - Shift spectral dispersion coordinate system
+
+3. Determining and applying flux calibrations
+ calibrate - Apply extinction and flux calibrations to spectra
+ deredden - Apply interstellar extinction correction
+ dopcor - Apply doppler corrections
+ lcalib - List calibration file data
+ sensfunc - Create sensitivity function
+ standard - Tabulate standard star data
+
+4. Fitting spectral features and continua
+ continuum - Fit the continuum in spectra
+ fitprofs - Fit gaussian profiles
+ sfit - Fit spectra and output fit, ratio, or difference
+ splot - Interactive spectral plot/analysis
+
+5. Arithmetic and combining of spectra
+ sarith - Spectrum arithmetic
+ scombine - Combine spectra
+ splot - Interactive spectral plot/analysis
+
+6. Miscellaneous functions
+ mkspec - Generate an artificial spectrum
+ names - Generate a list of image names from a string
+ sapertures - Set or change aperture header information
+ scopy - Select and copy spectra
+ sinterp - Interpolate a table of x,y to create a spectrum
+ slist - List spectrum header parameters
+ splot - Interactive spectral plot/analysis
+.fi
+
+There are other packages which provide additional functions or specialized
+tasks for spectra. Radial velocity measurements are available in the
+\fBnoao.rv\fR package. The \fBnoao.imred\fR package contains a number
+of packages for specific types of data or instruments. These packages
+are listed in Table 2.
+
+.ce
+Table 2: \fBImred\fR spectroscopy packages
+
+.nf
+ argus - CTIO ARGUS reduction package
+ ctioslit - CTIO spectrophotometric reduction package
+ echelle - Echelle spectral reductions (slit and FOE)
+ hydra - KPNO HYDRA (and NESSIE) reduction package
+ iids - KPNO IIDS spectral reductions
+ irs - KPNO IRS spectral reductions
+ kpnocoude - KPNO coude reduction package (slit and 3 fiber)
+ kpnoslit - KPNO low/moderate dispersion slits (Goldcam, RCspec, Whitecam)
+ specred - Generic slit and fiber spectral reduction package
+.fi
+
+Finally, there are non-NOAO packages which may contain generally useful
+software for spectra. Currently available packages are \fBstsdas\fR
+and \fBxray\fR.
+.ih
+SPECTRUM IMAGE FORMATS AND COORDINATE SYSTEMS
+See the separate help topic \fIspecwcs\fR.
+.ih
+INTERPOLATION
+Changing the dispersion sampling of spectra, such as when converting to a
+constant sampling interval per pixel or a common sampling for combining or
+doing arithmetic on spectra, requires interpolation. The tasks which
+reinterpolate spectra, if needed, are \fBdispcor, sarith, scombine,\fR and
+\fBsplot\fR.
+
+The interpolation type is set by the package parameter \fIinterp\fR.
+The available interpolation types are:
+
+.nf
+ nearest - nearest neighbor
+ linear - linear
+ poly3 - 3rd order polynomial
+ poly5 - 5th order polynomial
+ spline3 - cubic spline
+ sinc - sinc function
+.fi
+
+The default interpolation type is a 5th order polynomial.
+
+The choice of interpolation type depends on the type of data, smooth
+verses strong, sharp, undersampled features, and the requirements of
+the user. The "nearest" and "linear" interpolation are somewhat
+crude and simple but they avoid "ringing" near sharp features. The
+polynomial interpolations are smoother but have noticible ringing
+near sharp features. They are, unlike the sinc function described
+below, localized.
+
+In V2.10 a "sinc" interpolation option is available. This function
+has advantages and disadvantages. It is important to realize that
+there are disadvantages! Sinc interpolation approximates applying a phase
+shift to the fourier transform of the spectrum. Thus, repeated
+interpolations do not accumulate errors (or nearly so) and, in particular,
+a forward and reverse interpolation will recover the original spectrum
+much more closely than other interpolation types. However, for
+undersampled, strong features, such as cosmic rays or narrow emission or
+absorption lines, the ringing can be more severe than the polynomial
+interpolations. The ringing is especially a concern because it extends
+a long way from the feature causing the ringing; 30 pixels with the
+truncated algorithm used. Note that it is not the truncation of the
+interpolation function which is at fault!
+
+Because of the problems seen with sinc interpolation it should be used with
+care. Specifically, if there are no undersampled, narrow features it is a
+good choice but when there are such features the contamination of the
+spectrum by ringing is much more severe than with other interpolation
+types.
+.ih
+UNITS
+In versions of the NOAO spectroscopy packages prior to V2.10 the dispersion
+units used were restricted to Angstroms. In V2.10 the first,
+experimental, step of generalizing to other units was taken by
+allowing the two principle spectral plotting tasks, \fBsplot\fR and
+\fBspecplot\fR, to plot in various units. Dispersion functions are still
+assumed to be in Angstroms but in the future the generalization will be
+completed to all the NOAO spectroscopy tasks.
+
+The dispersion units capability of the plotting tasks allows specifying
+the units with the "units" task parameter and interactively changing the
+units with the ":units" command. In addition the 'v' key allows plotting
+in velocity units with the zero point velocity defined by the cursor
+position.
+
+The units are specified by strings having a unit type from the list below
+along with the possible preceding modifiers, "inverse", to select the
+inverse of the unit and "log" to select logarithmic units. For example "log
+angstroms" to plot the logarithm of wavelength in Angstroms and "inv
+microns" to plot inverse microns. The various identifiers may be
+abbreviated as words but the syntax is not sophisticated enough to
+recognized standard scientific abbreviations except as noted below.
+
+.nf
+ Table 1: Unit Types
+
+ angstroms - Wavelength in Angstroms
+ nanometers - Wavelength in nanometers
+ millimicrons - Wavelength in millimicrons
+ microns - Wavelength in microns
+ millimeters - Wavelength in millimeters
+ centimeter - Wavelength in centimeters
+ meters - Wavelength in meters
+ hertz - Frequency in hertz (cycles per second)
+ kilohertz - Frequency in kilohertz
+ megahertz - Frequency in megahertz
+ gigahertz - Frequency in gigahertz
+ m/s - Velocity in meters per second
+ km/s - Velocity in kilometers per second
+ ev - Energy in electron volts
+ kev - Energy in kilo electron volts
+ mev - Energy in mega electron volts
+ z - Redshift
+
+ nm - Wavelength in nanometers
+ mm - Wavelength in millimeters
+ cm - Wavelength in centimeters
+ m - Wavelength in meters
+ Hz - Frequency in hertz (cycles per second)
+ KHz - Frequency in kilohertz
+ MHz - Frequency in megahertz
+ GHz - Frequency in gigahertz
+ wn - Wave number (inverse centimeters)
+.fi
+
+The velocity and redshift units require a trailing value and unit defining the
+velocity zero point. For example to plot velocity relative to
+a wavelength of 1 micron the unit string would be:
+
+.nf
+ km/s 1 micron
+.fi
+
+Some additional examples of units strings are:
+
+.nf
+ milliang
+ megahertz
+ inv mic
+ log hertz
+ m/s 3 inv mic
+ z 5015 ang
+.fi
+.ih
+SEE ALSO
+apextract, longslit, rv, imred, specwcs
+.endhelp
diff --git a/noao/onedspec/doc/refspectra.hlp b/noao/onedspec/doc/refspectra.hlp
new file mode 100644
index 00000000..01cfab30
--- /dev/null
+++ b/noao/onedspec/doc/refspectra.hlp
@@ -0,0 +1,413 @@
+.help refspectra Mar92 noao.onedspec
+.ih
+NAME
+refspectra -- Assign reference spectra
+.ih
+USAGE
+refspectra input [records]
+.ih
+PARAMETERS
+.ls input
+List of input spectra or root names to be assigned reference spectra.
+When using the record number extension format, record number extensions
+will be appended to each root name in the list.
+.le
+.ls records (imred.irs and imred.iids packages only)
+List of records or ranges of records to be appended to the input root
+names when using record number extension format. The syntax of this
+list is comma separated record numbers or ranges of record numbers. A
+range consists of two numbers separated by a hyphen. An example of this
+syntax is "1-5,13,17-19". A null list ("") may
+be used if no record number extensions are desired. This is a
+positional query parameter only if the record format is specified with
+the \fIrecformat\fR parameter.
+.le
+.ls references = "*.imh"
+List of reference spectra to be assigned or a "reference spectra assignment
+table" (see DESCRIPTION section).
+.le
+.ls apertures = ""
+List of apertures to be SELECTED from the input list of spectra. If no list
+is specified then all apertures are selected. The syntax is the same as the
+record number extensions.
+.le
+.ls refaps = ""
+List of reference spectra apertures to be SELECTED. If no list is specified
+then all apertures are selected. The syntax is the same as the record number
+extensions.
+.le
+.ls ignoreaps = yes
+Ignore the input and reference apertures when ASSIGNING reference spectra.
+If the aperture numbers are not ignored then only the reference spectra with
+the same aperture number as a particular input spectra are used when assigning
+reference spectra. Otherwise all the reference spectra are used. This does
+not apply to the "match" and "average" options which always ignore the aperture
+numbers. Note that this parameter applies to relating reference spectra to
+input spectra and does not override the aperture selections on the input
+spectra and reference spectra.
+.le
+.ls select = "interp"
+Selection method for assigning reference spectra. The methods are:
+.ls average
+Average two reference spectra without regard to any aperture,
+sort, or group parameters.
+If only one reference spectrum is specified then it is assigned with a
+warning. If more than two reference spectra are specified then only the
+first two are used and a warning is given. There is no checking of the
+aperture numbers or group values.
+.le
+.ls following
+Select the nearest following spectrum in the reference list based on the
+sort and group parameters. If there is no following spectrum use the
+nearest preceding spectrum.
+.le
+.ls interp
+Interpolate between the preceding and following spectra in the reference
+list based on the sort and group parameters. If there is no preceding and
+following spectrum use the nearest spectrum. The interpolation is weighted
+by the relative distances of the sorting parameter (see cautions in
+DESCRIPTION section).
+.le
+.ls match
+Match each input spectrum with the reference spectrum list in order.
+This overrides any aperture or group values.
+.le
+.ls nearest
+Select the nearest spectrum in the reference list based on the sort and
+group parameters.
+.le
+.ls preceding
+Select the nearest preceding spectrum in the reference list based on the
+sort and group parameters. If there is no preceding spectrum use the
+nearest following spectrum.
+.le
+.le
+.ls sort = "jd"
+Image header keyword to be used as the sorting parameter for selection
+based on order. The header parameter must be numeric but otherwise may
+be anything. Common sorting parameters are times or positions.
+A null string, "", or the word "none" may be use to disable the sorting
+parameter.
+.le
+.ls group = "ljd"
+Image header keyword to be used to group spectra. For those selection
+methods which use the group parameter the reference and object spectra must
+have identical values for this keyword. This can be anything but it must
+be constant within a group. Common grouping parameters are the date of
+observation "date-obs" (provided it does not change over a night) or the
+local Julian day number. A null string, "", or the word "none" may be use
+to disable the grouping parameter.
+.le
+.ls time = no, timewrap = 17.
+Is the sorting parameter a 24 hour time? If so then the time orgin
+for the sorting is specified by the timewrap parameter. This time
+should precede the first observation and follow the last observation
+in a 24 hour cycle.
+.le
+.ls override = no
+Override previous assignments? If an input spectrum has reference
+spectra assigned previously the assignment will not be changed unless
+this flag is set.
+.le
+.ls confirm = yes
+Confirm reference spectrum assignments? If \fIyes\fR then the reference
+spectra assignments for each input spectrum are printed and the user may
+either accept the assignment or not. Rejected assignments leave the
+input spectrum unchanged.
+.le
+.ls assign = yes
+Assign the reference spectrum by entering it in the image header?
+The input spectra are only modified if this parameter is \fIyes\fR.
+This parameter may be set to \fIno\fR to get a list of assignments
+without actually entering the assignments in the image headers.
+.le
+.ls logfiles = "STDOUT,logfile"
+List of log files for recording reference spectra assignments.
+The file STDOUT prints to the standard output. If not specified ("")
+then no logs will be recorded.
+.le
+.ls verbose = yes
+Verbose log output? This prints additional information about the input
+and reference spectra. This is useful for diagnosing why certain spectra
+are ignored or not assigned as intended.
+.le
+.ih
+DESCRIPTION
+This task allows the user to define which reference spectra are to be
+used in the calculation of the dispersion solution of object spectra.
+The assignment of reference spectra to object spectra is often
+a complex task because of the number of spectra, the use of many distinct
+apertures, and different modes of observing such as interspersed arc
+calibration spectra or just one calibration for a night. This task
+provides a number of methods to cover many of the common cases.
+
+A reference spectrum is defined to be a spectrum that has been used to
+calculate a wavelength solution with the tasks IDENTIFY or REIDENTIFY.
+These tasks have set the keyword REFSPEC1 in the image header
+equal to the spectrum's own name.
+
+Wavelength reference spectra are assigned to input spectra by entering
+the reference spectrum name or pair of names in the image
+header under the keywords REFSPEC1 and REFSPEC2. When two reference
+spectra are assigned, the spectrum names may be followed by a weighting
+factor (assumed to be 1 if missing). The wavelength of a pixel is
+then the weighted average of the wavelengths from the reference
+spectra dispersion solutions. The weighting factors are calculated
+by choosing an appropriate selection method, ie average, interpolation,
+etc. Note, however, that these assignments may be made directly using
+the task \fBhedit\fR or with some other task or script if none of the
+methods are suitable.
+
+The spectra to be assigned references are specified by an input list.
+Optional numeric record format extensions may be appended to each name
+(used as a root name) in the input list in the \fBiids/irs\fR packages.
+The input spectra may be restricted to a particular set of aperture numbers
+by the parameter \fIapertures\fR; the spectra not in the list of apertures
+are skipped. If the aperture list is null (i.e. specified as "") then all
+apertures are selected. One further selection may be made on the input
+spectra. If the parameter \fIoverride\fR is no then input spectra which
+have existing reference spectra assignments (which includes the reference
+spectra) are skipped.
+
+The reference spectra parameter \fIreferences\fR may take two forms.
+It may be an image list of spectra or a text file containing
+a "reference spectrum assignment table". The table consists of pairs
+of strings/lists with the first string being a list of object spectra
+and the second string being a list of reference spectra. If this
+table is used, then only those object spectra in the table that are also
+listed in the input parameter list are processed. The example below
+illustrates the reference spectrum assignment table:
+
+.nf
+ spec1 spec2,spec3,spec4
+ spec5
+ spec6,spec7 spect8,spec9
+ spec10 spec11
+ spec12 spec13
+ spec14 spec15
+.fi
+
+As a convenience, if a reference list in the table is missing, the preceding
+reference list is implied. This table may be used to make arbitrary assignments.
+
+The reference spectra in the specified list may also be restricted to a
+subset of aperture numbers. However, in the case of averaging, the
+reference aperture selection is ignored. In the case of matching, if
+a reference spectrum is not selected then the matching input spectrum
+is also skipped (in order to maintain a one-to-one correspondence).
+Spectra in the reference list which are not reference spectra (as
+defined earlier) are also ignored and a warning is printed. Note that
+no check is made that a dispersion solution actually exists in the
+dispersion solution database.
+
+There may be cases where there are only reference spectra for some
+apertures and it is desired to apply these reference spectra to the
+other apertures. The \fIignoreaps\fR flag may be used to force an
+assignment between reference and object spectra with different
+aperture numbers. Note that this flag is applied after the input and
+reference list aperture number selections are made; in other words this
+applies only to the assignments and not the input selection process.
+
+Once the appropriate reference spectra from the reference list have been
+determined for an input spectrum they are assigned using one of the
+methods selected by the parameter \fIselect\fR. The "match" method
+simply pairs each element of the input spectrum list with each element
+in the reference spectrum list. If a reference assignment table
+is used with "match", then only the first spectrum in the reference
+list for each input spectrum is assigned.
+
+The "average" method assigns the first two spectra in the reference list
+ignoring aperture numbers or groups. The spectra are averaged by assigning
+equal weights. There is no weighting based on any sort parameter. If
+there are more than two spectra in the reference list then only the first
+two spectra are used and the remainder are ignored. If a reference
+assignment table is used only the first two reference spectra listed for
+each object in the table are averaged.
+
+The remaining selection methods group the spectra using a header keyword
+which must be constant within a group. If no group parameter is specfied
+(the null string "" or the word "none")
+then grouping does not occur. Only reference spectra with the same
+group header value as the object are assigned to an object spectrum.
+One likely group parameter is the "date-obs" keyword. This is usually
+constant over a night at CTIO and KPNO. At other sites this may not
+be the case. Therefore, the task \fBsetjd\fR may be used to set a
+local Julian day number which is constant over a night at any
+observatory.
+
+Within a group the spectra are ordered based on a numeric image header
+parameter specified by the \fIsort\fR parameter. A null string "" or the
+word "null" may be used to select no sort parameter. Parameters which are
+times, as indicated by the \fItime\fR parameter, are assumed to be cyclic
+with a period of 24 hours. The time wrap parameter defines the origin of a
+cycle and should precede the first observation and follow the last
+observation in a 24 hour period; i.e. for nighttime observations this
+parameter value should bee sometime during the day. Particularly with
+interpolating or choosing the nearest reference spectrum it is important
+that the sorting parameter refer to the middle of the exposure. A Julian
+date at the middle of an exposure may be calculated with the task
+\fBsetjd\fR or a middle UT time may be computed with the task
+\fBsetairmass\fR.
+
+The selection methods may choose the "nearest", "preceding", or "following"
+reference spectrum. Alternatively, the reference wavelengths may be
+interpolated between the preceding and following reference spectra with
+weights given by the relative distances measured by the sorting parameter.
+In the cases where a preceding or following spectrum is required and one is
+not found then the nearest reference spectrum is used. These methods are
+used for observing sequences where the reference spectra are taken either
+nearby in time or space.
+
+The option "interp" should not be used without some thought as to the
+nature of the interpolation. If the sorting parameter is a time (a 24 hour
+cyclic parameter as opposed to a continuous parameter such as a Julian
+date) then the user must be aware of when these times were recorded in the
+header. For example, let us assume that the sort parameter is "ut" and
+that this time was recorded in the header at the beginning of the
+exposure. If the object spectrum exposure time is longer than the
+reference spectra exposure times, then interpolation will weight the
+preceding reference spectrum too heavily. This problem can be circumvented
+by using the "average" selection method along with the reference assignment
+table. Or the sort time parameter in the headers of the spectra can be
+changes with \fIsetjd\fR or \fIsetairmass\fR or edited to reflect the
+values at mid-exposure (see EXAMPLES).
+
+Once the reference spectrum or spectra for a input spectrum have been
+identified the user may also chose to override any previous reference
+assignments, to accept or not accept the current reference assignments
+(in the case of not accepting the reference assignment the image header
+is not updated), to only list the current reference assignments and not
+update any image headers, as well as to record the reference assignments
+to log files. These options are separately controlled by the remaining
+task parameters.
+.ih
+KEYWORDS
+This task uses the header keyword BEAM-NUM to sort the apertures. It
+has an integer value. If the keyword does not exist then all apertures
+are assumed to be 1.
+
+The keyword REFSPEC1 is used to search for reference spectra. This
+keyword can be previously created by the tasks IDENTIFY and REIDENTIFY.
+
+The two keywords REFSPEC1 and optionally REFSPEC2 are created by the
+task when the assign parameter is set to yes. They take the form:
+
+.nf
+ REFSPEC1='d1.0001' or
+
+ REFSPEC1='d5.0001 0.756'
+ REFSPEC2='d5.0002 0.244'
+.fi
+
+.ih
+EXAMPLES
+1. Compute a Julian date at the midpoint of the exposure for sorting
+and a local Julian day number for grouping and then assign spectra
+using interpolation.
+
+.nf
+ cl> setjd *.imh jd=jd ljd=ljd
+ cl> refspec *.imh sort=jd group=ljd select=interp
+.fi
+
+2. Specifically assign reference spectra to input spectra.
+
+.nf
+ cl> refspectra spec1,spec3 refe=spec2,spec4 select=match
+.fi
+
+3. Use a reference assignment table to assign reference spectra to input
+spectra using the "average" option. First a table is created using an
+editor.
+
+.nf
+ cl> type reftable
+ spec1 spec2,spec3,spec4
+ spec5
+ spec6,spec7 spect8,spec9
+ spec10 spec11
+ spec12 spec13
+ spec14 spec15
+ cl> refspec spec*.imh recfor- select=average refe=reftable
+.fi
+
+4. Assign the nearest reference spectrum in zenith distance using
+wildcard lists. By default the aperture numbers must match.
+
+ cl> refspec *.imh "" sort=zd select=nearest time-
+
+5. Assign a specific reference spectrum to all apertures.
+
+ cl> refspec *.imh "" refer=refnite1 ignoreaps+
+
+6. Confirm assignments.
+
+.nf
+ cl> hselect irs.*.imh "$I,beam-num,ut,refspec1" yes
+ irs.0009.imh 0 0:22:55 irs.0009
+ irs.0010.imh 1 0:22:53 irs.0010
+ irs.0100.imh 0 8:22:55
+ irs.0101.imh 1 8:22:53
+ irs.0447.imh 0 13:00:07 irs.0447
+ irs.0448.imh 1 13:00:05 irs.0448
+ cl> refspec irs 100-101 refer=irs.*.imh conf+ ver+ select=nearest\
+ >>> ignoreaps-
+ [irs.0100] Not a reference spectrum
+ [irs.0101] Not a reference spectrum
+ [irs.0100] refspec1='irs.0447' Accept assignment (yes)?
+ [irs.0101] refspec1='irs.0448' Accept assignment (yes)?
+.fi
+
+Because the reference spectrum list includes all spectra the
+warning messages "Not a reference spectrum" are printed with verbose
+output. Remember a reference spectrum is any spectrum which has a
+reference spectrum assigned which refers to itself.
+
+7. Assign reference spectra with weights using interpolation. In this
+example we want to sort by "ut" but this keyword value was
+recorded at the beginning of the integration. So we first create an
+new keyword and then compute its value to be that of mid-exposure. The
+new keyword is then used as the sorting parameter.
+
+.nf
+ cl> hedit *.imh utmid 0. add+ ver- show-
+ cl> hedit *.imh utmid "(ut)" ver- show-
+ cl> hedit *.imh utmid "(mod(utmid+exptime/7200.,24.))" ver- show-
+ cl> refspec *.imh refer=*.imh recfor- select=interp sort=utmid
+.fi
+
+8. Assign reference spectra using the "average" option and the reference
+assignment table with data with record number extensions. First edit
+the file reftable:
+
+.nf
+ cl> type reftable
+ spec.0001 arc1.0001,arc2.0001
+ spec.0002 arc1.0002,arc2.0002
+ spec.0003 arc1.0003,arc2.0003
+ spec.0004 arc1.0004,arc2.0004
+ cl> refspec spec.*.imh recfor- refer=reftable select=average
+.fi
+
+9. Assign a reference spectrum for aperture 1 to the object spectra
+for apertures 2 thru 5.
+
+.nf
+ cl> refspec spec 2-5 recfor+ refer=arc.*.imh refaps=1 ignoreaps+
+.fi
+.ih
+REVISIONS
+.ls REFSPECTRA V2.10.3
+If no reference spectrum is found in the interp, nearest, following,
+preceding methods then a list of the reference spectra is given
+showing why each was not acceptable.
+.le
+.ls REFSPECTRA V2.10
+A group parameter was added to allow restricting assignments by observing
+period; for example by night. The record format option was removed and
+the record format syntax is available in the \fBirs/iids\fR packages.
+.le
+.ih
+SEE ALSO
+identify, reidentify, dispcor, setjd, setairmass
+.endhelp
diff --git a/noao/onedspec/doc/reidentify.hlp b/noao/onedspec/doc/reidentify.hlp
new file mode 100644
index 00000000..07eb2238
--- /dev/null
+++ b/noao/onedspec/doc/reidentify.hlp
@@ -0,0 +1,516 @@
+.help reidentify Jan96 noao.onedspec
+.ih
+NAME
+reidentify -- Reidentify features
+.ih
+SUMMARY
+Given a reference vector with identified features and (optionally) a
+coordinate function find the same features in other elements of the
+reference image and fit a new dispersion function or determine a
+zero point shift. After all vectors of the reference image are
+reidentified use the reference vectors to reidentify corresponding
+vectors in other images. This task is used for transferring dispersion
+solutions in arc calibration spectra and for mapping geometric and
+dispersion distortion in two and three dimensional images.
+.ih
+USAGE
+reidentify reference images
+.ih
+PARAMETERS
+.ls reference
+Image with previously identified features to be used as features reference for
+other images. If there are multiple apertures, lines, or columns in the
+image a master reference is defined by the \fIsection\fR parameter.
+The other apertures in multispec images or other lines, or columns
+(selected by \fIstep\fR) are reidentified as needed.
+.le
+.ls images
+List of images in which the features in the reference image are to be
+reidentified. In two and three dimensional images the reidentifications are
+done by matching apertures, lines, columns, or bands with those in the reference
+image.
+.le
+.ls interactive = no
+Examine and fit features interactively? If the task is run interactively a
+query (which may be turned off during execution) will be given for each
+vector reidentified after printing the results of the automatic fit and the
+user may chose to enter the interactive \fBidentify\fR task.
+.le
+.ls section = "middle line"
+If the reference image is not one dimensional or specified as a one dimensional
+image section then this parameter selects the master reference image
+vector. The master reference is used when reidentifying other vectors in
+the reference image or when other images contain apertures not present in
+the reference image. This parameter also defines the direction
+(columns, lines, or z) of the image vectors to be reidentified.
+
+The section parameter may be specified directly as an image section or
+in one of the following forms
+
+.nf
+line|column|x|y|z first|middle|last|# [first|middle|last|#]]
+first|middle|last|# [first|middle|last|#] line|column|x|y|z
+.fi
+
+where each field can be one of the strings separated by | except for #
+which is an integer number. The field in [] is a second designator which
+is used with three dimensional data. See the example section for
+\fBidentify\fR for examples of this syntax. Abbreviations are allowed
+though beware that 'l' is not a sufficient abbreviation.
+.le
+.ls newaps = yes
+Reidentify new apertures in the images which are not in the reference
+image? If no, only apertures found in the reference image will be
+reidentified in the other images. If yes, the master reference spectrum
+is used to reidentify features in the new aperture and then the
+new aperture solution will be added to the reference apertures. All
+further identifications of the new aperture will then use this solution.
+.le
+.ls override = no
+Override previous solutions? If there are previous solutions for a
+particular image vector being identified, because of a previous
+\fBidentify\fR or \fBreidentify\fR, this parameter selects whether
+to simply skip the reidentification or do a reidentification and
+overwrite the solution in the database.
+.le
+.ls refit = yes
+Refit the coordinate function? If yes and there is more than one feature
+and a coordinate function was defined in the reference image database then a new
+coordinate function of the same type as in the reference is fit
+using the new pixel positions. Otherwise only a zero point shift is
+determined for the revised coordinates without changing the
+form of the coordinate function.
+.le
+
+The following parameters are used for selecting and reidentifying additional
+lines, columns, or apertures in two dimensional formats.
+.ls trace = no
+There are two methods for defining additional reference lines, columns, or
+bands in two and three dimensional format images as selected by the
+\fIstep\fR parameter. When \fItrace\fR is no the master reference line or
+column is used for each new reference vector. When this parameter is yes
+then as the reidentifications step across the image the last reidentified
+features are used as the reference. This "tracing" is useful if there is a
+coherent shift in the features such as with long slit spectra. However,
+any features lost during the tracing will be lost for all subsequent lines
+or columns while not using tracing always starts with the initial set of
+reference features.
+.le
+.ls step = "10"
+The step from the reference line, column, or band used for selecting and/or
+reidentifying additional lines, columns, or bands in a two or three
+dimensional reference image. For three dimensional images there may be two
+numbers to allow independent steps along different axes. If the step is
+zero then only the reference aperture, line, column, or band is used. For
+multiaperture images if the step is zero then only the requested aperture
+is reidentified and if it is non-zero (the value does not matter) then all
+spectra are reidentified. For long slit or Fabry-Perot images the step is
+used to sample the image and the step should be large enough to map any
+significant changes in the feature positions.
+.le
+.ls nsum = "10"
+Number of lines, columns, or bands across the designated vector axis to be
+summed when the image is a two or three dimensional spatial spectrum.
+It does not apply to multispec format spectra. If the image is three
+dimensional an optional second number can be specified for the higher
+dimensional axis (the first number applies to the lower axis number and
+the second to the higher axis number). If a second number is not specified
+the first number is used for both axes. This parameter is not used for
+multispec type images.
+.le
+.ls shift = "0"
+Shift in user coordinates to be added to the reference features before
+centering. If the image is three dimensional then two numbers may be
+specified for the two axes. Generally no shift is used by setting the
+value to zero. When stepping to other lines, columns, or bands in the
+reference image the shift is added to the primary reference spectrum if not
+tracing. When tracing the shift is added to last spectrum when stepping to
+higher lines and subtracted when stepping to lower lines. If a value
+if INDEF is specified then an automatic algorithm is applied to find
+a shift.
+.le
+.ls search = 0.
+If the \fIshift\fR parameter is specified as INDEF then an automatic
+search for a shift is made. There are two algorithms. If the search
+value is INDEF then a cross-correlation of line peaks is done. Otherwise
+if a non-zero value is given then a pattern matching algorithm (see
+\fIautoidentify\fR) is used. A positive value specifies the search radius in
+dispersion units and a negative value specifies a search radius as a
+fraction of the reference dispersion range.
+.le
+.ls nlost = 0
+When reidentifying features by tracing, if the number of features not found
+in the new image vector exceeds this number then the reidentification
+record is not written to the database and the trace is terminated. A
+warning is printed in the log and in the verbose output.
+.le
+
+The following parameters define the finding and recentering of features.
+See also \fBcenter1d\fR.
+.ls cradius = 5.
+Centering radius in pixels. If a reidentified feature falls further
+than this distance from the previous line or column when tracing or
+from the reference feature position when reidentifying a new image
+then the feature is not reidentified.
+.le
+.ls threshold = 0.
+In order for a feature center to be determined, the range of pixel
+intensities around the feature must exceed this threshold. This parameter
+is used to exclude noise peaks and terminate tracing when the signal
+disappears. However, failure to properly set this parameter, particularly
+when the data values are very small due to normalization or flux
+calibration, is a common error leading to failure of the task.
+.le
+
+The following parameters select and control the automatic addition of
+new features during reidentification.
+.ls addfeatures = no
+Add new features from a line list during each reidentification? If
+yes then the following parameters are used. This function can be used
+to compensate for lost features from the reference solution, particularly
+when tracing. Care should be exercised that misidentified features
+are not introduced.
+.le
+.ls coordlist = "linelists$idhenear.dat"
+User coordinate list consisting of a list of line coordinates.
+Some standard line lists are available in the directory "linelists$".
+The standard line lists are described under the topic \fIlinelists\fR.
+.le
+.ls match = -3.
+The maximum difference for a match between the feature coordinate function
+value and a coordinate in the coordinate list. Positive values
+are in user coordinate units and negative values are in units of pixels.
+.le
+.ls maxfeatures = 50
+Maximum number of the strongest features to be selected automatically from
+the coordinate list.
+.le
+.ls minsep = 2.
+The minimum separation, in pixels, allowed between feature positions
+when defining a new feature.
+.le
+
+The following parameters determine the input and output of the task.
+.ls database = "database"
+Database containing the feature data for the reference image and in which
+the features for the reidentified images are recorded.
+.le
+.ls logfiles = "logfile"
+List of files in which to keep a processing log. If a null file, "",
+is given then no log is kept.
+.le
+.ls plotfile = ""
+Optional file to contain metacode plots of the residuals.
+.le
+.ls verbose = no
+Print reidentification information on the standard output?
+.le
+.ls graphics = "stdgraph"
+Graphics device. The default is the standard graphics device which is
+generally a graphics terminal.
+.le
+.ls cursor = ""
+Cursor input file. If a cursor file is not given then the standard graphics
+cursor is read.
+.le
+
+The following parameters are queried when the 'b' key is used in the
+interactive review.
+.ls crval, cdelt
+These parameters specify an approximate coordinate value and coordinate
+interval per pixel when the automatic line identification
+algorithm ('b' key) is used. The coordinate value is for the
+pixel specified by the \fIcrpix\fR parameter in the \fBaidpars\fR
+parameter set. The default value of \fIcrpix\fR is INDEF which then
+refers the coordinate value to the middle of the spectrum. By default
+only the magnitude of the coordinate interval is used. Either value
+may be given as INDEF. In this case the search for a solution will
+be slower and more likely to fail. The values may also be given as
+keywords in the image header whose values are to be used.
+.le
+.ls aidpars = "" (parameter set)
+This parameter points to a parameter set for the automatic line
+identification algorithm. See \fIaidpars\fR for further information.
+.le
+.ih
+DESCRIPTION
+Features (spectral lines, cross-dispersion profiles, etc.) identified in a
+single reference vector (using the tasks \fBidentify\fR or
+\fBautoidentify\fR) are reidentified in other reference vectors and the set
+of reference vectors are reidentified in other images with the same type of
+vectors. A vector may be a single one dimensional (1D) vector in a two or
+three dimensional (2D or 3D) image, the sum of neighboring vectors to form
+a 1D vector of higher signal, or 1D spectra in multiaperture images. The
+number of vectors summed in 2D and 3D images is specified by the parameter
+\fInsum\fR. This parameter does not apply to multiaperture images.
+
+As the previous paragraph indicates, there are two stages in this task.
+The first stage is to identify the same features from a single reference
+vector to a set of related reference vectors. This generally consists
+of other vectors in the same reference image such as other lines or
+columns in a long slit spectrum or the set of 1D aperture spectra in
+a multiaperture image. In these cases the vectors are identified by
+a line, column, band, or aperture number. The second stage is to
+reidentify the features from the reference vectors in the matching
+vectors of other images. For example the same lines in the reference
+image and another image or the same apertures in several multiaperture
+images. For multiaperture images the reference vector and target vector
+will have the same aperture number but may be found in different image
+lines. The first stage may be skipped if all the reference vectors
+have been identified.
+
+If the images are 2D or 3D or multiaperture format and a \fIstep\fR greater
+than zero is specified then additional vectors (lines/columns/bands) in the
+reference image will be reidentified from the initial master reference
+vector (as defined by an image section or \fIsection\fR parameter) provided
+they have not been reidentified previously or the \fIoverride\fR flag is
+set. For multiple aperture spectral images, called multiaperture, a step
+size of zero means don't reidentify any other aperture and any other step
+size reidentifies all apertures. For two and three dimensional images,
+such as long slit and Fabry-Perot spectra, the step(s) should be large
+enough to minimize execution time and storage requirements but small enough
+to follow shifts in the features (see the discussion below on tracing).
+
+The reidentification of features in other reference image vectors
+may be done in two ways selected by the parameter \fItrace\fR. If not
+tracing, the initial reference vector is applied to the other selected
+vectors. If tracing, the reidentifications are made with respect to the
+last set of identifications as successive steps away from the reference
+vector are made. The tracing method is appropriate for two and three
+dimensional spatial images, such as long slit and Fabry-Perot spectra, in
+which the positions of features traced vary smoothly. This allows
+following large displacements from the initial reference by using suitably
+small steps. It has the disadvantage that features lost during the
+reidentifications will not propagate (unless the \fIaddfeatures\fR option
+is used). By not tracing, the original set of features is used for every
+other vector in the reference image.
+
+When tracing, the parameter \fInlost\fR is used to terminate the
+tracing whenever this number of features has been lost. This parameter,
+in conjunction with the other centering parameters which define
+when a feature is not found, may be useful for tracing features
+which disappear before reaching the limits of the image.
+
+When reidentifying features in other images, the reference
+features are those from the same aperture, line, column, or band of the
+reference image. However, if the \fInewaps\fR parameter is set
+apertures in multiaperture spectra which are not in the reference
+image may be reidentified against the master reference aperture and
+added to the list of apertures to be reidentified in other images.
+This is useful when spectra with different aperture numbers are
+stored as one dimensional images.
+
+The reidentification of features between a reference vector and
+a target vector is done as follows. First a mean shift between
+the two vectors is determined. After correcting for the shift
+the estimated pixel position of each reference feature in the
+target vector is used as the starting point for determining
+a feature center near this position. The centering fails the
+feature is dropped and a check against the \fInlost\fR is made.
+If it succeeds it is added to the list of features found in the
+target spectrum. A zero point shift or new dispersion
+function may be determined. New features may then be added from
+a coordinate list. The details are given below.
+
+There may be a large shift between the two vectors such that the same
+feature in the target vector is many pixels away from the pixel position in
+the reference spectrum. A shift must then be determined. The \fIshift\fR
+parameter may be used to specify a shift. The shift is in user coordinates
+and is added to the reference user coordinates before trying to center
+on a feature. For example if the reference spectrum has a feature at
+5015A but in the new spectrum the feature is at 5025A when the reference
+dispersion function is applied then the shift would be +10. Thus
+a reference feature at 5015A would have the shift added to get 5025A,
+then the centering would find the feature some pixel value and that
+pixel value would be used with the true user coordinate of 5015A in the
+new dispersion solution.
+
+When tracing a 2D/3D reference spectrum the shift is applied to the
+previous reidentified spectrum rather than the initial reference spectrum.
+The shift is added for increasing line or column values and subtracted for
+decreasing line or column values. This allows "tracing" when there is a
+rotation or tilt of the 2D or 3D spectrum. When not tracing the shift is
+always added to the reference spectrum features as described previously.
+
+When reidentify other images with the reference spectrum the shift
+parameter is always just added to the reference dispersion solution
+matching the aperture, line, or column being reidentified.
+
+If the \fIshift\fR parameter is given as INDEF then an automatic
+search algorithm is applied. There are two algorithms that may be
+used. If the \fIsearch\fR parameter is INDEF then a cross-correlation
+of the features list with the peaks found in the target spectrum is
+performed. This algorithm can only find small shifts since otherwise
+many lines may be missing off either end of the spectrum relative to
+the reference spectrum.
+
+If the search parameter is non-zero then the pattern matching algorithm
+described in \fIaidpars\fR is used. The search parameter specified a
+search radius from the reference solution. If the value is positive the
+search radius is a distance in dispersion units. If the value is negative
+then the absolute value is used as a fraction of the dispersion range in
+the reference solution. For example, a value of -0.1 applied to reference
+dispersion solution with a range of 1000A would search for a new solution
+within 100A of the reference dispersion solution.
+
+The pattern matching algorithm has to stages. First if there are
+more than 10 features in the reference the pattern matching tries
+to match the lines in the target spectrum to those features with
+a dispersion per pixel having the same sign and a value within 2%.
+If no solution is found then the \fIlinelist\fR is used to match
+against the lines in the target spectrum, again with the dispersion
+per pixel having the same sign and a value within 5%. The first
+stage works when the set of features is nearly the same while the
+second stage works when the shifts are large enough that many features
+in the reference and target spectra are different.
+
+The centering algorithm is described under the topic \fIcenter1d\fR and
+also in \fBidentify\fR. If a feature positions shifts by more than the
+amount set by the parameter \fIcradius\fR from the starting position
+(possibly after adding a shift) or the feature strength (peak to valley) is
+less than the detection \fIthreshold\fR then the new feature is discarded.
+The \fIcradius\fR parameter should be set large enough to find the correct
+peak in the presence of any shifts but small enough to minimize incorrect
+identifications. The \fIthreshold\fR parameter is used to eliminate
+identifications with noise. Failure to set this parameter properly for the
+data (say if data values are very small due to a calibration or
+normalization operation) is the most common source of problems in using
+this task.
+
+If a fitting function is defined for the features in the reference image,
+say a dispersion function in arc lamp spectra, then the function is refit
+at each reidentified line or column if the parameter \fIrefit\fR is yes.
+If refitting is not selected then a zero point shift in the user
+coordinates is determined without changing the form of the fitting
+function. The latter may be desirable for tracking detector shifts through
+a sequence of observation using low quality calibration spectra. When
+refitting, the fitting parameters from the reference are used including
+iterative rejection parameters to eliminate misidentifications.
+
+If the parameter \fIaddfeatures\fR is set additional features may be added
+from a line list. If there are reference features then the new features
+are added AFTER the initial reidentification and function fit. If the
+reference consists only of a dispersion function, that is it has no
+features, then new features will be added followed by a function fit and
+then another pass of adding new features. A maximum number of added
+features, a matching distance in user coordinates, and a minimum separation
+from other features are additional parameters. This option is similar to
+that available in \fBidentify\fR and is described more fully in the help
+for that task.
+
+A statistics line is generated for each reidentified vector. The line
+contains the name of the image being reidentified (which for two
+dimensional images includes the image section and for multiaperture
+spectra includes the aperture number), the number of features found
+relative to the number of features in the reference, the number of
+features used in the function fit relative to the number found, the
+mean pixel, user coordinate, and fractional user coordinate shifts
+relative to the reference coordinates, and the RMS relative to the
+final coordinate system (whether refit or simply shifted) excluding any
+iteratively rejected features from the calculation.
+
+If the task is run with the \fIinteractive\fR flag the statistics line
+is printed to the standard output (the terminal) and a query is
+made whether to examine and/or refit the features. A response
+of yes or YES will put the user in the interactive graphical mode
+of \fBidentify\fR. See the description of this task for more
+information. The idea is that one can monitor the statistics information,
+particularly the RMS if refitting, and select only those which may be
+questionable to examine interactively. A response of no or NO will
+continue on to the next reidentification. The capitalized responses
+turn off the query and act as permanent response for all other
+reidentifications.
+
+This statistics line, including headers, is written to any specified
+log files. The log information includes the image being
+reidentified and the reference image, and the initial shift.
+
+If an accessible file name is given for the plot file then a residual plot
+of the reidentified lines is recorded in this file. The plot file can
+be viewed with \fBgkimosaic, stdgraph\fR or reading the file
+with ".read" when in cursor mode (for example with "=gcur").
+
+The reidentification results for this task are recorded in a
+\fIdatabase\fR. Currently the database is a directory and entries
+in the database are text files with filenames formed by adding
+the prefix "id" to the image name without an image extension.
+.ih
+EXAMPLES
+1. Arc lines and a dispersion solution were defined for the middle
+aperture in the multispec for arc spectrum a042.ms. To reidentify the
+other apertures in the reference image and then another arc image:
+
+.nf
+ cl> reiden a042.ms a045.ms inter+ step=1 ver+
+ REIDENTIFY: NOAO/IRAF V2.9 valdes@puppis Fri 29-Jun-90
+ Reference image = a042.ms.imh, New image = a042.ms, Refit = yes
+ Image Data Found Fit Pix Shift User Shift RMS
+ a042.ms - Ap 24 48/48 47/48 -2.38E-4 -3.75E-6 0.699
+ Fit dispersion function interactively? (no|yes|NO|YES) (yes): y
+ a042.ms - Ap 24 48/48 47/48 -2.38E-4 -3.75E-6 0.699
+ a042.ms - Ap 23 48/48 47/48 0.216 1.32 0.754
+ Fit dispersion function interactively? (no|yes|NO|YES) (yes): n
+ a042.ms - Ap 22 48/48 47/48 0.0627 0.383 0.749
+ Fit dispersion function interactively? (no|yes|NO|YES) (yes): n
+ a042.ms - Ap 21 48/48 47/48 0.337 2.06 0.815
+ <etc>
+ Reference image = a042.ms.imh, New image = a045.ms, Refit = yes
+ Image Data Found Fit Pix Shift User Shift RMS
+ a045.ms - Ap 24 48/48 47/48 -2.38E-4 -3.75E-6 0.699
+ Fit dispersion function interactively? (no|yes|NO|YES) (yes): y
+ a045.ms - Ap 24 48/48 47/48 -2.38E-4 -3.75E-6 0.699
+ a045.ms - Ap 23 48/48 47/48 0.216 1.32 0.754
+ Fit dispersion function interactively? (no|yes|NO|YES) (yes): N
+ a045.ms - Ap 22 48/48 47/48 0.0627 0.383 0.749
+ a042.ms - Ap 21 48/48 47/48 0.337 2.06 0.815
+ a042.ms - Ap 20 48/48 47/48 -0.293 -1.79 0.726
+ a042.ms - Ap 19 48/48 48/48 0.472 2.88 0.912
+.fi
+
+This example is verbose and includes interactive review of reidentifications.
+The statistics lines have been shortened.
+
+2. To trace a stellar profile and arc lines in long slit images for the
+purpose of making a distortion correction:
+
+.nf
+ cl> reiden rog022[135,*] "" trace+
+ cl> reiden rog023 "" sec="mid line" trace+
+.fi
+.ih
+REVISIONS
+.ls REIDENTIFY V2.11
+The \fIsearch\fR parameter and new searching algorithm has been added.
+
+The task will now work with only a warning if the reference image is absent;
+i.e. it is possible to reidentify given only the database.
+
+The \fIaddfeatures\fR function will now add features before a fit if there
+are no reference database features. Previously features could only be
+added after an initial fit using the reference features and, so, required
+the reference database to contain features for reidentification. This
+new feature is useful if one wants to uses a dispersion function from one
+type of calibration but wants to add features for a different kind of
+calibration.
+.le
+.ls REIDENTIFY V2.10.3
+The section, nsum, step, and shift parameter syntax was extended to apply to 3D
+images. The previous values and defaults may still be used.
+
+For multiaperture data a step of zero selects only the reference aperture
+to be reidentified and any other step selects reidentifying all apertures.
+.le
+.ls REIDENTIFY V2.10
+This task is a new version with many new features. The new features
+include an interactive options for reviewing identifications, iterative
+rejection of features during fitting, automatic addition of new features
+from a line list, and the choice of tracing or using a single master
+reference when reidentifying features in other vectors of a reference
+spectrum. Reidentifications from a reference image to another image is
+done by matching apertures rather than tracing. New apertures not present
+in the reference image may be added.
+.le
+.ih
+SEE ALSO
+autoidentify, identify, aidpars, center1d, linelists, fitcoords
+.endhelp
diff --git a/noao/onedspec/doc/rspectext.hlp b/noao/onedspec/doc/rspectext.hlp
new file mode 100644
index 00000000..2973f552
--- /dev/null
+++ b/noao/onedspec/doc/rspectext.hlp
@@ -0,0 +1,138 @@
+.help rspectext Oct93 onedspec
+.ih
+NAME
+rspectext -- convert 1D ascii text spectra to IRAF image spectra
+.ih
+USAGE
+rspectext input output
+.ih
+PARAMETERS
+.ls input
+Input list of ascii text spectra. These may have a optional FITS header
+at the beginning and then two columns of wavelength and flux.
+.le
+.ls output
+Output list of IRAF spectra image names. The list must match the
+input list.
+.le
+
+
+The following parameters are only used if there is no FITS header
+with the data.
+.ls title = ""
+Title to be assigned to the spectra.
+.le
+.ls flux = no
+Are the flux values flux calibrated? If so then header keywords are
+inserted to identify this for the IRAF spectral software.
+.le
+.ls dtype = "linear" (none|linear|log|nonlinear|interp)
+Type of dispersion to assign to the spectra. The options are:
+.ls none
+No dispersion function and nothing is added to the image header.
+.le
+.ls linear
+Store the linear dispersion parameters \fBcrval1\fR and \fBcdelt1\fR
+in the image header. The wavelength values are ignored. This may
+be used if the wavelength values are known to be linear but one wants
+to avoid possible roundoff and resampling errors introduced by the
+"interp" option.
+.le
+.ls log
+Store the log-linear dispersion parameters \fBcrval1\fR and \fBcdelt1\fR in
+the image header. The wavelength values are ignored. This may be used if
+the wavelength values are known to be linear in the log of the wavelength
+but one wants to avoid possible roundoff and resampling errors introduced
+by the "interp" option.
+.le
+.ls nonlinear
+Store the wavelength values in the image header as a lookup table.
+The flux values are not resampled. The wavelength values need not
+be evenly sampled.
+.le
+.ls interp
+Use the wavelength values to resample to a linear dispersion between
+the first and last wavelength values. The dispersion per pixel is
+determined by the number of pixels and the endpoint wavelengths.
+.le
+.le
+.ls crval1 = 1., cdelt1 = 1.
+The wavelength coordinate of the first pixel and the wavelength interval
+per pixel to be used with the linear and log dispersion types.
+.le
+.ih
+DESCRIPTION
+Ascii text files consisting of an optional FITS header (usually produced
+by \fBwspectext\fR) and a two column list of wavelengths and fluxes
+are converted to IRAF image spectra. If a header is included then
+the header information is assumed to describe the spectra including
+any dispersion function. If no header is given then the minimal
+information for describing spectra in IRAF is added. The dispersion
+function can be set either a linear or log-linear based on two
+keywords (ignoring the wavelength values) or from the wavelength
+values. The latter may be stored in the header as a lookup table
+allowing for nonlinear dispersions or resample to a linear dispersion.
+This task is a script based on \fBrtextimage\fR for the creating
+the image and entering the flux values, \fBhedit\fR to set some
+of the header keywords, and \fBdispcor\fR to handle the nonlinear
+or resampled dispersion functions.
+.ih
+EXAMPLES
+1. Create spectrum from a text file originally produced by \fBwspectext\fR.
+
+.nf
+ cl> type text001
+ BITPIX = 8 / 8-bit ASCII characters
+ NAXIS = 1 / Number of Image Dimensions
+ NAXIS1 = 100 / Length of axis
+ ORIGIN = 'NOAO-IRAF: WTEXTIMAGE' /
+ IRAF-MAX= 0. / Max image pixel (out of date)
+ IRAF-MIN= 0. / Min image pixel (out of date)
+ IRAF-B/P= 32 / Image bits per pixel
+ IRAFTYPE= 'REAL FLOATING ' / Image datatype
+ OBJECT = 'TITLE ' /
+ FILENAME= 'TEST ' / IRAF filename
+ FORMAT = '5G14.7 ' / Text line format
+ APNUM1 = '1 1 '
+ DC-FLAG = 0
+ WCSDIM = 1
+ CTYPE1 = 'LINEAR '
+ CRVAL1 = 4000.
+ CRPIX1 = 1.
+ CDELT1 = 10.1010101010101
+ CD1_1 = 10.1010101010101
+ LTM1_1 = 1.
+ WAT0_001= 'system=equispec '
+ WAT1_001= 'wtype=linear label=Wavelength units=Angstroms '
+ END
+
+ 4000.00 1000.
+ 4010.10 1005.54
+ 4020.20 1011.05
+ ...
+ cl> rspectext text001 spec001
+.fi
+
+2. Create a spectrum with a nonlinear dispersion using the wavelength
+values as a lookup table.
+
+.nf
+ cl> type text002
+ 4000.00 1000.
+ 4010.10 1005.54
+ 4020.20 1011.05
+ ...
+ cl> rspectext text002 spec002 title="HH12" dtype=nonlinear
+.fi
+.ih
+REVISIONS
+.ls RSPECTEXT V2.11
+The task now automatically senses the presence of a header.
+.le
+.ls RSPECTEXT V2.10.3
+This is a new task with this version.
+.le
+.ih
+SEE ALSO
+wspectext, rtextimage, dispcor, mkms, imspec, sinterp
+.endhelp
diff --git a/noao/onedspec/doc/sapertures.hlp b/noao/onedspec/doc/sapertures.hlp
new file mode 100644
index 00000000..37398d6a
--- /dev/null
+++ b/noao/onedspec/doc/sapertures.hlp
@@ -0,0 +1,217 @@
+.help sapertures Jul95 noao.onedspec
+.ih
+NAME
+sapertures -- Set or change aperture header information
+.ih
+USAGE
+sapertures input
+.ih
+PARAMETERS
+.ls input
+List of spectral images to be modified.
+.le
+.ls apertures = ""
+List of apertures to be modified. The null list
+selects all apertures. A list consists of comma separated
+numbers and ranges of numbers. A range is specified by a hyphen. An
+optional step size may be given by using the 'x' followed by a number.
+See \fBxtools.ranges\fR for more information.
+.le
+.ls apidtable = ""
+Aperture table. This may be either a text file or an image.
+A text file consisting of lines with an aperture number,
+beam number, dispersion type code, coordinate of the first physical
+pixel, coordinate interval per physical pixel, redshift factor,
+lower extraction aperture position, upper extraction aperture position,
+and aperture title or identification. An image will contain the
+keywords SLFIBnnn with string value consisting of aperture number,
+beam number, optional right ascension and declination, and aperture title.
+Any field except the aperture number may be given the value INDEF to
+indicate that the value is not to be changed from the current value. Any
+apertures not in this table are assigned the values given by the task
+parameters described below.
+
+As a special case a file having just the aperture number, beam number, and
+spectrum aperture identification may be used. This file format as well as
+use of an image header is the same as that in the \fBapextract\fR package.
+.le
+.ls wcsreset = no
+Reset the world coordinate system (WCS) of the selected apertures to
+uncorrected pixels. If this parameter is set the \fIapidtable\fR and task
+aperture parameters are ignored. This option sets the dispersion type flag
+to -1, the starting coordinate value to 1, the interval per pixel to 1, and
+no redshift factor and leaves the other parameters unchanged. The option
+is useful when it is desired to apply a second dispersion correction using
+\fBidentify\fR and \fBdispcor\fR.
+.le
+.ls verbose = no
+Print a record of each aperture modified? Only those apertures
+in which the beam number or label are changed are printed.
+.le
+
+If no aperture table is specified or if there is not an aperture
+entry in the table for a selected aperture the following parameter
+values are used. A value of INDEF will leave the corresponding
+parameter unchanged.
+.ls beam = INDEF
+Beam number.
+.le
+.ls dtype = INDEF
+Dispersion type. The dispersion types are:
+
+.nf
+ -1 Linear with dispersion correction flag off
+ 0 Linear with dispersion correction flag on
+ 1 Log-linear with dispersion correction flag on
+.fi
+
+.le
+.ls w1 = INDEF
+Coordinate of the first physical pixel. Note that it is possible
+that the physical pixels are not the same as the logical pixels if
+an image section has been extracted.
+.le
+.ls dw = INDEF
+Coordinate interval per physical pixel. Note that it is possible
+that the physical pixels intervals are not the same as the logical pixels
+intervals if an image section has been extracted.
+.le
+.ls z = INDEF
+Redshift factor. This is usually set with the task \fBdopcor\fR.
+Coordinates are divided by one plus the redshift factor (1+z).
+.le
+.ls aplow = INDEF, aphigh = INDEF
+The aperture extraction limits. These are set when the \fBapextract\fR
+package is used and it is unlikely that one would use this task to
+change them.
+.le
+.ls title = INDEF
+Aperture title or identification string.
+.le
+.ih
+DESCRIPTION
+This task sets or changes any of the aperture specific parameters except
+the aperture number and the number of valid pixels. It is particularly
+useful for images which use the "multispec" world coordinate system
+attribute strings which are not readily accessible with other header
+editors. A list of images and a list of apertures is used to select which
+spectra are to be modified. The default empty string for the apertures
+selects all apertures. The new values are specified either in an aperture
+table file or with task parameters. The aperture table is used to give
+different values to specific apertures. If all apertures are to have the
+same values this file need not be used.
+
+The aperture parameters which may be modified are the beam number, the
+dispersion type, the coordinate of the first physical pixel, the coordinate
+interval per physical pixel, the redshift factor, the aperture extraction
+limits, and the title. The task has parameters for each of these and the
+aperture table consists of lines starting with an aperture number followed
+by the above parameters in the list order and separated by whitespace. As
+a special case the aperture table may be a file abbreviated to aperture
+number, beam number, and title or an image with keywords SLFIBnnn
+containing the aperture number, beam number, optional right ascension and
+declination, and title. These special cases allow use of the same file
+orimage used in the \fBapextract\fR package. If any of the parameters are
+specified as INDEF then the value will be unchanged.
+
+If the \fIwcsreset\fR parameter is set then the aperture table and
+task aperture parameters are ignored and the selected apertures are
+reset to have a dispersion type of -1, a starting coordinate of 1,
+a coordinate interval of 1, and a redshift factor of 0. This other
+parameters are not changed. These choice of parameters has the effect
+of resetting the spectrum to physical pixel coordinates and flagging
+the spectra as not being dispersion calibrated. One use of this option
+is to allow the \fBdispcor\fR task to be reapplied to previously
+dispersion calibrated spectra.
+
+The \fIverbose\fR parameter lists the old and new values when there is
+a change. If there are no changes there will be no output.
+.ih
+EXAMPLES
+1. To add titles to a multifiber extraction and change one of the
+beam numbers:
+
+.nf
+ cl> type m33aps
+ 36 2 Henear
+ 37 0 Sky
+ 38 1 New title
+ 39 1 Another title
+ 41 0 Sky
+ 42 1 Yet another title
+ 43 1 YAT
+ 44 1 Was a sky but actually has an object
+ 45 1 Wow
+ 46 1 Important new discovery
+ 47 0 Sky
+ 48 2 Henear
+ cl> saper m33.ms apid=m33aps v+
+ demoobj1.ms:
+ Aperture 37: --> Sky
+ Aperture 38: --> New title
+ Aperture 39: --> Another title
+ Aperture 41: --> Sky
+ Aperture 42: --> Yet another title
+ Aperture 43: --> YAT
+ Aperture 44: beam 0 --> beam 1
+ Aperture 44: --> Was a sky but actually has an object
+ Aperture 45: --> Wow
+ Aperture 46: --> Important new discovery
+ Aperture 47: --> Sky
+.fi
+
+2. To reset a dispersion calibrated multifiber spectrum:
+
+.nf
+ cl> saper test.ms wcsreset+ verbose+
+ test.ms:
+ Aperture 1:
+ w1 4321. --> 1.
+ dw 1.23 --> 1.
+ Aperture 2:
+ w1 4321. --> 1.
+ dw 1.23 --> 1.
+ <etc.>
+.fi
+
+3. To set a constant wavelength length scale (with the default parameters):
+
+.nf
+ cl> saper test.ms dtype=0 w1=4321 dw=1.23 v+
+ test.ms:
+ Aperture 1:
+ w1 1. --> 4321.
+ dw 1. --> 1.23
+ Aperture 2:
+ w1 1. --> 4321.
+ dw 1. --> 1.23
+ <etc.>
+.fi
+
+4. To reset the wavelengths and title of only aperture 3:
+
+.nf
+ cl> saper test.ms aper=3 w1=4325 dw=1.22 title=HD12345 v+
+ test.ms:
+ Aperture 3:
+ w1 4321. --> 4325.
+ dw 1.23 --> 1.22
+ apid --> HD12345
+.fi
+.ih
+REVISIONS
+.ls SAPERTURES V2.11
+This task has been modified to allow use of image header keywords
+as done in the APEXTRACT package.
+.le
+.ls SAPERTURES V2.10.3
+This task has been greatly expanded to allow changing any of the WCS
+parameters as well as the beam number and aperture title.
+.le
+.ls SAPERTURES V2.10
+This task is new.
+.le
+.ih
+SEE ALSO
+specshift, imcoords.wcsreset, hedit, ranges, onedspec.package
+.endhelp
diff --git a/noao/onedspec/doc/sarith.hlp b/noao/onedspec/doc/sarith.hlp
new file mode 100644
index 00000000..a7e7cf87
--- /dev/null
+++ b/noao/onedspec/doc/sarith.hlp
@@ -0,0 +1,571 @@
+.help sarith Mar93 noao.onedspec
+.ih
+NAME
+sarith -- Spectrum arithmetic
+.ih
+USAGE
+sarith input1 op input2 output
+.ih
+PARAMETERS
+.ls input1
+List of input images to be used as operands.
+.le
+.ls op
+Operator to be applied to the first operand or to both operands. The
+unary or single operand operators are:
+
+.nf
+ abs - absolute value
+ copy - copy (see also \fBscopy\fR)
+ dex - decimal exponentiation (antilog of base 10 logarithm)
+ exp - base e exponentiation (antilog of natural logarithm)
+ flam - convert F-nu to F-lambda
+ fnu - convert F-lambda to F-nu
+ inv - inverse
+ ln - natural logarithm
+ log - base 10 logarithm
+ lum - convert magnitude to luminosity
+ mag - convert luminosity to magnitude
+ sqrt - square root
+.fi
+
+The binary or two operand operators are:
+
+.nf
+ replace - replace first operand values by second operand values
+ + - addition
+ - - subtraction
+ * - multiplication
+ / - division
+ ^ - exponentiation
+.fi
+.le
+.ls input2
+Lists of input spectra or constants to be used as second operands for
+binary operations. If a single value is specified it applies
+to all the first operand input images otherwise the list must match
+the first operand list in number.
+.le
+.ls output
+List of resultant output images or root names. Image
+sections are ignored and if the output format is "onedspec" then any record
+extensions are stripped to form the root name. If no output list is
+specified then the input list is used and the input images are replaced by
+the resultant spectra. If a single output name is specified then all
+resultant spectra are written to the same output image or image root
+name. This allows packing or merging multiple spectra and requires
+properly setting the \fIclobber\fR, \fImerge\fR, \fIrenumber\fR and
+\fIoffset\fR parameters to achieve the desired output. If more than one
+output image is specified then it must match the input image list in
+number.
+.le
+.ls w1 = INDEF, w2 = INDEF
+Starting and ending wavelengths to be copied. If \fIw1\fR is not specified
+then the wavelength of the starting edge of the first pixel is used
+(wavelength at pixel coordinate 0.5) and if \fIw2\fR is not specified then
+the wavelength of the ending edge of the last pixel is used (wavelength of
+the last pixel plus 0.5). If both are not specified, that is set to INDEF,
+then the whole spectrum is copied and the \fIrebin\fR parameter is
+ignored. Note that by specifying both endpoints the copied region can be
+set to have increasing or decreasing wavelength per pixel. If the spectrum
+only partially covers the specified range only that portion of the spectrum
+within the range is copied. It is an error if the range is entirely
+outside that of a spectrum.
+.le
+.ls apertures = "", beams = ""
+List of apertures and beams to be selected from the input spectra. The
+logical intersection of the two lists is selected. The null list
+selects all apertures or beams. A list consists of comma separated
+numbers and ranges of numbers. A range is specified by a hyphen. An
+optional step size may be given by 'x' followed by a number.
+See \fBxtools.ranges\fR for more information. If the first character
+is "!" then the apertures/beams not in the list are selected. Note
+that a "!" in either of the lists complements the intersection of the
+two lists.
+For longslit input spectra the aperture numbers
+selects the lines or columns to be extracted. For 3D Fabry-Perot
+spectra the aperture numbers select the first spatial axis.
+.le
+.ls bands = ""
+List of bands in 3D multispec.
+For 3D spatial spectra the band parameter applies to the second
+spatial axis.
+The null list selects all bands. The syntax is as described above.
+.le
+.ls apmodulus = 0
+Modulus to be applied to the input aperture numbers before matching against
+the aperture list. If zero then no modulus is used. This is used to
+select apertures which are related by the same modulus, typically a
+factor of 10; for example, 10, 1010, and 2010 with a modulus of 1000 are
+related.
+.le
+.ls reverse = no
+Reverse the order of the operands in a binary operation? Because the first
+operand is used as the image header template, dispersion coordinate
+template, and output image in the case of a null output list it must be an
+image and not a constant. To allow certain operations, for
+example subtracting a spectra from a constant or using the subtractand as
+the dispersion coordinate template, the reverse option is used to reverse
+the order of the operands in a binary operation.
+.le
+.ls ignoreaps = no
+Ignore aperture numbers in the second operand? Normally, spectra in
+binary operations must have matching aperture numbers, otherwise an
+error is printed. If this parameter is yes then the spectra are matched
+by line number with the last line being used if the second operand spectrum
+has fewer lines than the first operand spectrum. This is generally
+used to allow using a single spectrum with multiple aperture spectra.
+.le
+.ls format = "multispec" (multispec|onedspec)
+Output image format and name syntax. The "multispec" format consists of
+one or more spectra in the same image file. The "onedspec" format consists
+of a single spectrum per image with names having a root name and a four
+digit aperture number extension. Note that converting to "onedspec" format
+from three dimensional images where the third dimension contains associated
+spectra will not include data from the extra dimension. Image sections may
+be used in this case.
+.le
+.ls renumber = no
+Renumber the output aperture numbers? If set the output aperture
+numbers, including any preexisting spectra when merging, are renumbered
+beginning with 1. The \fIoffset\fR parameter may be used to
+change the starting number.
+.le
+.ls offset = 0
+Offset to be added to the input or renumbered aperture number to form
+the final output aperture number.
+.le
+.ls clobber = no
+Modify an existing output image either by overwriting or merging?
+.le
+.ls merge = no
+Merge apertures into existing spectra? This
+requires that the \fIclobber\fR parameter be set. If not merging
+then the selected spectra entirely replace those in existing output images.
+If merging then the input spectra replace those in the output image
+with the same aperture number and new apertures are added if not present.
+.le
+.ls rebin = yes
+Rebin the spectrum to the exact wavelength range specified by the \fIw1\fR
+and \fIw2\fR parameters? If the range is given as INDEF for both endpoints
+this parameter does not apply. If a range is given and this parameter is
+not set then the pixels in the specified range (using the nearest pixels to
+the endpoint wavelengths) are copied without rebinning. In this case the
+wavelength of the first pixel may not be exactly that specified by \fIw1\fR
+and the dispersion, including non-linear dispersions, is unchanged. If
+this parameter is set the spectra are interpolated to have the first and
+last pixels at exactly the specified endpoint wavelengths while preserving
+the same number of pixels in the interval. Linear and log-linear
+dispersion types are maintained while non-linear dispersions are
+linearized.
+.le
+.ls errval = 0.
+Value for resultant pixel if an arithmetic error occurs such as dividing
+by zero or the square root of a negative number.
+.le
+.ls verbose = no
+Print a record of each operation?
+.le
+.ih
+DESCRIPTION
+\fBSarith\fR performs arithmetic operations on spectra. It is
+distinguished from \fBimarith\fR in that it includes unary operators, like
+\fBimfunction\fR but with some specific to astronomical spectra, and binary
+operations between two spectra are performed in dispersion coordinate space
+(typically wavelength) rather than logical pixel space. In the latter case
+the spectra are checked for matching dispersion functions (which are not
+necessarily linear) and, if they don't match, the second operand is
+interpolated without flux conservation. (If flux conservation is desired
+then the task \fBdispcor\fR should be used first.) Thus, the spectra may
+have different dispersion functions but the arithmetic is done at matching
+wavelengths. The default interpolation function is a 5th order
+polynomial. The choice of interpolation type is made with the package
+parameter "interp". It may be set to "nearest", "linear", "spline3",
+"poly5", or "sinc". Remember that this applies to all tasks which might
+need to interpolate spectra in the \fBonedspec\fR and associated packages.
+For a discussion of interpolation types see \fBonedspec\fR.
+
+The unary operators operate on the spectra in the first operand list to
+produce the specified output spectra, which may be the same as the
+input spectra. The operators include:
+
+.nf
+ abs - absolute value
+ copy - copy (see also \fBscopy\fR)
+ dex - decimal exponentiation (antilog of base 10 logarithm)
+ exp - base e exponentiation (antilog of natural logarithm)
+ flam - convert F-nu to F-lambda
+ fnu - convert F-lambda to F-nu
+ inv - inverse
+ ln - natural logarithm
+ log - base 10 logarithm
+ lum - convert magnitude to luminosity
+ mag - convert luminosity to magnitude
+ sqrt - square root
+.fi
+
+The luminosity to magnitude and magnitude to luminosity operators are
+based on the standard relation:
+
+.nf
+ mag = -2.5 * log (lum)
+.fi
+
+where the log is base 10. The F-nu to F-lambda and F-lambda to F-nu
+operators are based on the relation:
+
+.nf
+ F-nu = F-lambda * lambda / nu
+.fi
+
+where lambda is wavelength and nu is frequency (currently the wavelength
+is assumed to be Angstroms and so F-lambda is in units of per Angstrom
+and F-nu is in units of per Hertz). In all the operators it is the
+responsibility of user as to the appropriateness of the operator to
+the input.
+
+The binary operators operate on the spectra in the first operand list
+and the spectra or numerical constants in the second operand. Numeric
+constants are equivalent to spectra having the specified value at all
+pixels. The binary operators are the standard arithmetic ones plus
+exponentiation and replacement:
+
+.nf
+ replace - replace first operand values by second operand values
+ + - addition
+ - - subtraction
+ * - multiplication
+ / - division
+ ^ - exponentiation
+.fi
+
+If the second operand is a spectrum, as mentioned previously, it is
+interpolated, without flux conservation, to the dispersion
+function of the first operand spectrum if necessary.
+
+There is a distinctions between the first operand and the second operand.
+The first operand must always be a spectrum. It supplies the dispersion
+function to be matched by the second operand spectrum. It also supplies
+a copy of it's image header when a new output spectrum is created.
+In cases where it is desired to have the second operand be the
+dispersion/header reference and/or the first operand be a constant
+the \fIreverse\fR parameter is used. For example to subtract a
+spectrum from the constant 1:
+
+.nf
+ cl> sarith 1 - spec invspec reverse+
+.fi
+
+or to subtract two spectra using the subtractand as the dispersion
+reference:
+
+.nf
+ cl> sarith spec1 - spec2 diff reverse+
+.fi
+
+When a binary operation on a pair of spectra is performed the aperture
+numbers may be required to be the same if \fIignoreaps\fR is no. For
+images containing multiple spectra the apertures need not be in the
+same order but only that matching apertures exist. If this parameter
+is set to yes then aperture numbers are ignored when the operation is
+performed. For multiple spectra images the second operand spectra
+are matched by image line number rather than by aperture. If the
+second operand image has fewer lines, often just one line, then the
+last line is used repeatedly. This feature allows multiple spectra
+in the primary operand list to be operated upon by a single spectrum;
+for example to subtract one spectrum from all spectra in the
+in a multiple spectrum image.
+
+If it is an error to perform an operation on certain data values, for
+example division by zero or the square root of a negative number,
+then the output value is given the value specified by the parameter
+\fIerrval\fR.
+
+A log of the operations performed may be printed to the standard
+output, which may then be redirected if desired, if the \fIverbose\fR
+parameter is set. In the output the last bracketed number is the
+aperture number of the spectrum.
+
+INPUT/OUTPUT
+
+The arithmetic part of \fBsarith\fR is fairly straightforward and
+intuitive. The selection of input spectra from input images and
+the placing of output spectra in output images can be more confusing
+because there are many possibilities. This section concentrates
+on the topics of the input and output. Since the concepts apply to all
+of the operators it simplifies things to think in terms of copying
+input spectra to output spectra; the "copy" operator. Note that the
+task \fBscopy\fR is actually just this case of \fBsarith\fR with
+parameters set for copying. While the discussion here is similar
+to that in the help for \fBscopy\fR, the examples for that task
+are more focused for illustrating this topic than the \fBsarith\fR
+examples which concentrate more on the arithmetic aspects of
+the task.
+
+Input spectra are specified by an image list which may include explicit
+image names, wildcard templates and @files containing image names.
+The image names may also include image sections such as to select portions of
+the wavelength coverage. The input images may be either one or two
+dimensional spectra. One dimensional spectra may be stored in
+individual one dimensional images or as lines in two (or three)
+dimensional images. The one dimensional spectra are identified by
+an aperture number, which must be unique within an image, and a beam number.
+Two dimensional long slit and three dimensional Fabry-Perot spectra are
+treated, for the purpose of this
+task, as a collection of spectra with dispersion either along any axis
+specified by the DISPAXIS image header parameter
+or the \fIdispaxis\fR package parameter. The aperture and band
+parameters specify a spatial position. A number of adjacent
+lines, columns, and bands, specified by the \fInsum\fR package parameter,
+will be summed to form an aperture spectrum. If number is odd then the
+aperture/band number refers to the middle and if it is even it refers to the
+lower of the two middle lines or columns.
+
+In the case of many spectra each stored in separate one dimensional
+images, the image names may be such that they have a common root name
+and a four digit aperture number extension. This name syntax is
+called "onedspec" format. Including such spectra in an
+input list may be accomplished either with wildcard templates such as
+
+.nf
+ name*
+ name.????.imh
+.fi
+
+where the image type extension ".imh" must be given to complete the
+template but the actual extension could also be that for an STF type
+image, or using an @file prepared with the task \fBnames\fR.
+To generate this syntax for output images the \fIformat\fR parameter
+is set to "onedspec" (this will be discussed further later).
+
+From the input images one may select a range of wavelengths with the
+\fIw1\fR and \fIw2\fR parameters and a subset of spectra based on aperture and
+beam numbers using the \fIaperture\fR and \fIbeam\fR parameters.
+If the wavelength range is specified as INDEF the full spectra are
+used without any resampling. If the aperture and beam lists are not
+specified, an empty list, then all apertures and beams are selected. The
+lists may be those spectra desired or the complement obtained by prefixing
+the list with '!'. Only the selected wavelength range and spectra will
+be operated upon and passed on to the output images.
+
+Specifying a wavelength range is fairly obvious except for the question
+of pixel sampling. Either the pixels in the specified range are used
+without resampling or the pixels are resampled to correspond eactly
+to the requested range. The choice is made with the \fIrebin\fR parameter.
+In the first case the nearest pixels to the specified wavelength
+endpoints are determined and those pixels and all those in between
+are used. The dispersion relation is unchanged. In the second case
+the spectra are reinterpolated to have the specified starting and
+ending wavelengths with the same number of pixels between those points
+as in the original spectrum. The reinterpolation is done in either
+linear or log-linear dispersion. The non-linear dispersion functions
+are interpolated to a linear dispersion.
+
+Using \fBsarith\fR with long slit and Fabry-Perot images provides a quick
+and simple type of extraction as opposed to using the \fBapextract\fR
+package. When summing it is often desired to start each aperture after the
+number of lines summed. To do this specify a step size in the aperture/band
+list. For example to extract columns 3 to 23 summing every 5 columns you
+would use an aperture list of "3-23x5" and an \fInsum\fR of 5. If you do
+not use the step in the aperture list you would extract the sum of columns
+1 to 5, then columns 2 to 6, and so on.
+
+In the special case of subapertures extracted by \fBapextract\fR, related
+apertures are numbered using a modulus; for example apertures
+5, 1005, 2005. To allow selecting all related apertures using a single
+aperture number the \fIapmodulus\fR parameter is used to specify the
+modulus factor; 1000 in the above example. This is a very specialized
+feature which should be ignored by most users.
+
+The output list of images may consist of an empty list, a single image,
+or a list of images matching the input list in number. Note that it
+is the number of image names that matters and not the number of spectra
+since there may be any number of spectra in an image. The empty list
+converts to the same list as the input and is shorthand for replacing
+the input image with the output image upon completion; therefore it
+is equivalent to the case of a matching list. If the input
+consists of just one image then the distinction between a single
+output and a matching list is moot. The interesting distinction is
+when there is an input list of two or more images. The two cases
+are then a mapping of many-to-many or many-to-one. Note that it is
+possible to have more complex mappings by repeating the same output
+name in a matching list provided clobbering, merging, and possibly
+renumbering is enabled.
+
+In the case of a matching list, spectra from different input images
+will go to different output images. In the case of a single output
+image all spectra will go to the same output image. Note that in
+this discussion an output image when "onedspec" format is specified
+is actually a root name for possibly many images. However,
+it should be thought of as a single image from the point of view
+of image lists.
+
+When mapping many spectra to a single output image, which may have existing
+spectra if merging, there may be a conflict with repeated aperture
+numbers. One option is to consecutively renumber the aperture numbers,
+including any previous spectra in the output image when merging and then
+continuing with the input spectra in the order in which they are selected.
+This is specified with the \fIrenumber\fR parameter which renumbers
+beginning with 1.
+
+Another options which may be used independently of renumbering or in
+conjunction with it is to add an offset as specified by the \fIoffset\fR
+parameter. This is last step in determining the output aperture
+numbers so that if used with the renumber option the final aperture
+numbers begin with one plus the offset.
+
+It has been mentioned that it is possible to write and add to
+existing images. If an output image exists an error will be
+printed unless the \fIclobber\fR parameter is set. If clobbering
+is allowed then the existing output image will be replaced by the
+new output. Rather than replacing an output image sometimes one
+wants to replace certain spectra or add new spectra. This is
+done by selecting the \fImerge\fR option. In this case if the output
+has a spectrum with the same aperture number as the input spectrum
+it is replaced by the input spectrum. If the input spectrum aperture
+number is not in the output then the spectrum is added to the output
+image. To add spectra with the same aperture number and not
+replace the one in the output use the \fIrenumber\fR or
+\fIoffset\fR options.
+.ih
+EXAMPLES
+In addition to the examples in this section there are many examples
+in the help for \fBscopy\fR which illustrate aspects of selecting
+input spectra and producing various types of output. Those examples
+are equivalent to using the "copy" operator. The same examples will
+also apply with other operators where the input spectra are modified
+arithmetically before being copied to the output images.
+
+I. SIMPLE EXAMPLES
+
+The simple examples use only a single input image and create a new
+output image.
+
+1. Examples of unary operations:
+
+.nf
+ cl> sarith example1 mag "" magexample
+ cl> sarith magexample lum "" example2
+ cl> sarith example1 log "" logexample
+.fi
+
+Note that a place holder for the second operand is required on the command
+line which will be ignored.
+
+2. Examples of binary operations using constants:
+
+.nf
+ cl> sarith example1 + 1000 example2
+ cl> sarith example1 - 1000 example2 reverse+
+ cl> sarith example1 / 1000 example2
+ cl> sarith example1 ** 2 example2
+.fi
+
+3. Examples of binary operations between spectra with matching apertures:
+
+.nf
+ cl> sarith example1 + example2 example3
+ cl> sarith example1 - example2 example3
+.fi
+
+4. Example of binary operations between spectra with the second image
+consisting of a single spectrum:
+
+.nf
+ cl> sarith example1 / flatspec flatexample1 ignore+ errval=1
+.fi
+
+II. MORE COMPLEX EXAMPLES
+
+5. Unary and constant operations on a list of images:
+
+.nf
+ cl> sarith example* fnu "" %example%fnu%
+ cl> sarith example* + 1000 %example%fnu%
+.fi
+
+6. Binary operations on a list of images using a single second operand
+with matching apertures:
+
+.nf
+ cl> sarith example* - skyspec %example%skysub%*
+.fi
+
+7. Selecting apertures to operate upon:
+
+.nf
+ cl> sarith example* - skyspec %example%skysub%* aper=1,5,9
+.fi
+
+8. Extract the sum of each 10 columns in a long slit spectrum and normalize
+by the central spectrum:
+
+.nf
+ cl> nsum = "10"
+ cl> sarith longslit copy "" longslit.ms aper=5-500x10
+ longslit[5] --> longslit.ms[5]
+ longslit[15] --> longslit.ms[15]
+ longslit[25] --> longslit.ms[25]
+ ...
+ cl> sarith longslit.ms / longslit.ms[*,25] norm ignore+
+ longslit.ms[5] / longslit.ms[*,25][245] --> norm[5]
+ longslit.ms[15] / longslit.ms[*,25][245] --> norm[15]
+ longslit.ms[25] / longslit.ms[*,25][245] --> norm[25]
+ ...
+.fi
+
+9. In place operations:
+
+.nf
+ cl> sarith example* + 1000 example* clobber+
+ example1[1] + 1000. --> example1[1]
+ example1[2] + 1000. --> example1[2]
+ ...
+ example2[1] + 1000. --> example2[1]
+ example2[2] + 1000. --> example2[2]
+ ...
+ cl> sarith example* flam "" example* clobber+
+ example1[1] -- flam --> example1[1]
+ example1[2] -- flam --> example1[2]
+ ...
+ example2[1] -- flam --> example2[1]
+ example2[2] -- flam --> example2[2]
+ ...
+ cl> sarith example* - skyspec "" clobber+ ignore+
+ example1[1] + skyspec[1] --> example1[1]
+ example1[2] + skyspec[1] --> example1[2]
+ ...
+ example2[1] + skyspec[1] --> example2[1]
+ example2[2] + skyspec[1] --> example2[2]
+ ...
+.fi
+
+10. Merging existing spectra with the results of operations:
+
+.nf
+ cl> sarith example* / flat "" clobber+ merge+ renum+ ignor+
+.fi
+.ih
+REVISIONS
+.ls SARITH V2.11
+Previously both w1 and w2 had to be specified to select a range to
+be used. Now if only one is specified the second endpoint defaults
+to the first or last pixel.
+
+The noise band in multispec data is only copied from the primary
+spectrum and not modified. This is a kludge until the noise is
+handled properly.
+.le
+.ls SARITH V2.10.3
+Additional support for 3D multispec/equispec or spatial spectra has been
+added. The "bands" parameter allows selecting specific bands and
+the onedspec output format creates separate images for each selected
+aperture and band.
+.le
+.ls SARITH V2.10
+This task is new.
+.le
+.ih
+SEE ALSO
+scopy, splot, imarith, imfunction
+.endhelp
diff --git a/noao/onedspec/doc/sbands.hlp b/noao/onedspec/doc/sbands.hlp
new file mode 100644
index 00000000..0bde52ac
--- /dev/null
+++ b/noao/onedspec/doc/sbands.hlp
@@ -0,0 +1,209 @@
+.help sbands Nov93 onedspec
+.ih
+NAME
+sbands -- bandpass spectrophotometry of spectra
+.ih
+USAGE
+sbands input output bands
+.ih
+PARAMETERS
+.ls input
+Input list of spectra to be measured. These may be one dimensional
+spectra in individual or "multispec" format or calibrated spatial spectra such
+as long slit or Fabry-Perot images. The dispersion axis and summing
+parameters are specified by package parameters for the spatial spectra.
+.le
+.ls output
+Output file for the results. This may be a filename or "STDOUT" to
+write to the terminal.
+.le
+.ls bands
+Bandpass file consisting of lines with one, two, or three bandpasses per
+line. A bandpass is specified by an identification string (quoted if it is
+null or contains whitespace), the central wavelength, the width of the
+bandpass in wavelength, and a filter filename with the special value "none"
+if there is no filter (a flat unit response). This format is described
+further in the description section.
+.le
+.ls apertures = ""
+List of apertures to select from the input spectra. For one dimensional
+spectra this is the aperture number and for spatial spectra it is
+the column or line. If the null string is specified all apertures are
+selected. The aperture list syntax is a range list which includes
+intervals and steps (see \fBranges\fR).
+.le
+.ls normalize = yes
+Normalize the bandpass fluxes by the bandpass response? If no then
+the results will depend on the bandpass widths and filter function
+values. If yes then fluxes will be comparable to an average pixel
+value. When computing indices and equivalent widths the flux must
+either be normalized or the bandpasses and filter response functions
+must be the same.
+.le
+.ls mag = no, magzero = 0.
+Output the bandpass fluxes as magnitudes with specified magnitude
+zero point?
+.le
+.ls verbose = yes
+Include a verbose header giving a banner, the parameters used,
+the bandpasses, and column headings?
+.le
+.ih
+DESCRIPTION
+\fBSbands\fR performs bandpass spectrophotometry with one or more bandpasses
+on one or more spectra. A list of input spectra is specified. The spectra
+may be of any type acceptable in the \fBnoao.onedspec\fR package including
+multispec format with nonlinear dispersion, long slit spectra, and even
+3D cubes with one dispersion axis. The \fIapertures\fR parameter allows
+selecting a subset of the spectra by aperture number.
+
+The bandpasses are specified in a text file. A bandpass consists of four
+fields; an identification name, the wavelength of the bandpass center, a
+bandpass width, and a filename for a filter. The identification is a
+string which must be quoted if a null name or a name with whitespace is
+desired. The identification could be given as the central wavelength if
+nothing else is appropriate. The filter field is a filename for a text
+file containing the filter values. A filter file consists of a wavelength
+ordered list of wavelength and relative response. Extrapolation uses the
+end point values and interpolation is linear. The special name "none" is
+used if there is no filter. This is equivalent to unit response at all
+wavelengths.
+
+In the bandpass file there may be one, two, or three bandpasses on
+a line. Below are some examples of the three cases:
+
+.nf
+ alpha 5000 10 myalpha.dat
+ beta1 4000 100 none beta2 4100 100 none
+ line 4500 100 none red 4000 200 none blue 5000 200 none
+.fi
+
+The flux in each bandpass is measured by summing each pixel in the interval
+multiplied by the interpolated filter response at that pixel. At the edges
+of the bandpass the fraction of the pixel in the bandpass is used. If the
+bandpass goes outside the range of the data an INDEF value will be reported.
+If the \fInormalize\fR option is yes then the total flux is divided by
+the sum of the filter response values. If the \fImag\fR option is
+yes the flux will be converted to a magnitude (provided it is positive)
+using the formula
+
+.nf
+ magnitude = magzero - 2.5 * log10 (flux)
+.fi
+
+where \fImagzero\fR is a parameter for the zero point magnitude and log10
+is the base 10 logarithm. Note that there is no attempt to deal with the
+pixel flux units. This is the responsibility of the user.
+
+If there is only one bandpass (on one line of the band file) then only
+the band flux or magnitude is reported. If there are two bandpasses
+the fluxes or magnitudes for the two bands are reported as well as a
+band index, the flux ratio or magnitude difference (depending on the \fImag\fR)
+flag, and an equivalent width using the second band as the continuum.
+If there are three bandpasses then a continuum bandpass flux is computed
+as the interpolation between the bandpass centers to the center of the
+first bandpass. The special bandpass identification "cont" will
+be reported.
+
+The equivalent width is obtained from the two bandpasses by the
+formula
+
+.nf
+ eq. width = (1 - flux1 / flux2) * width1
+.fi
+
+where flux1 and flux2 are the two bandpass fluxes and width1 is the
+width of the first bandpass. Note that for this to be meaningful
+the bandpasses should be normalized or have the same width/response.
+
+The results of measuring each bandpass in each spectrum are written
+to the specified output file. This file may be given as "STDOUT" to
+write the results to the terminal. The output file contains lines
+with the spectrum name and aperture, the band identifications and
+fluxes or magnitudes, and the band index and equivalent width (if
+appropriate). The \fIverbose\fR option allows creating a more
+documented output by including a commented header with the task
+name and parameters, the bandpass definitions, and column labels.
+The examples below show the form of the output.
+.ih
+EXAMPLES
+The following examples use artificial data and arbitrary bands.
+
+1. Show example results with one, two, and three bandpass entries in
+the bandpass file.
+
+.nf
+ cl> type bands
+ test 6125 50 none red 6025 100 none blue 6225 100 none
+ test 6125 50 none red 6025 100 none
+ test 6125 50 none blue 6225 100 none
+ test 6125 50 none
+ cl> sbands oned STDOUT bands
+
+ # SBANDS: NOAO/IRAF IRAFX valdes@puppis Mon 15:31:45 01-Nov-93
+ # bands = bands, norm = yes, mag = no
+ # band filter wavelength width
+ # test none 6125. 50.
+ # red none 6025. 100.
+ # blue none 6225. 100.
+ # test none 6125. 50.
+ # red none 6025. 100.
+ # test none 6125. 50.
+ # blue none 6225. 100.
+ # test none 6125. 50.
+ #
+ # spectrum band flux band flux index eqwidth
+ oned(1) test 44.33 cont 97.97 0.45 27.37
+ oned(1) test 44.33 red 95.89 0.46 26.89
+ oned(1) test 44.33 blue 100.04 0.44 27.84
+ oned(1) test 44.33
+.fi
+
+2. This example shows measurements on a long slit spectrum with an
+aperture selection and magnitude output.
+
+.nf
+ cl> type lsbands.dat
+ band1 4500 40 none
+ band2 4600 40 none
+ band3 4700 40 none
+ cl> nsum=5
+ cl> sbands ls STDOUT lsbands.dat apertures=40-60x5 mag+ magzero=10.1
+
+ # SBANDS: NOAO/IRAF IRAFX valdes@puppis Mon 15:37:18 01-Nov-93
+ # bands = lsbands.dat, norm = yes, mag = yes, magzero = 10.10
+ # band filter wavelength width
+ # band1 none 4500. 40.
+ # band2 none 4600. 40.
+ # band3 none 4700. 40.
+ #
+ # spectrum band mag
+ ls[38:42,*](40) band1 3.14
+ ls[38:42,*](40) band2 3.19
+ ls[38:42,*](40) band3 3.15
+ ls[43:47,*](45) band1 3.13
+ ls[43:47,*](45) band2 3.15
+ ls[43:47,*](45) band3 3.14
+ ls[48:52,*](50) band1 2.34
+ ls[48:52,*](50) band2 2.43
+ ls[48:52,*](50) band3 2.43
+ ls[53:57,*](55) band1 3.10
+ ls[53:57,*](55) band2 3.15
+ ls[53:57,*](55) band3 3.12
+ ls[58:62,*](60) band1 3.14
+ ls[58:62,*](60) band2 3.19
+ ls[58:62,*](60) band3 3.15
+.fi
+.ih
+REVISIONS
+.ls SBANDS V2.10.4
+The flux column is now printed to 6 digits of precision with possible
+exponential format to permit flux calibrated spectra to print properly.
+.le
+.ls SBANDS V2.10.3
+The task is new in this release
+.le
+.ih
+SEE ALSO
+splot
+.endhelp
diff --git a/noao/onedspec/doc/scombine.hlp b/noao/onedspec/doc/scombine.hlp
new file mode 100644
index 00000000..06e63003
--- /dev/null
+++ b/noao/onedspec/doc/scombine.hlp
@@ -0,0 +1,765 @@
+.help scombine Sep97 noao.onedspec
+.ih
+NAME
+scombine -- Combine spectra
+.ih
+USAGE
+scombine input output
+.ih
+PARAMETERS
+.ls input
+List of input images containing spectra to be combined. The spectra
+in the images to be combined are selected with the \fIapertures\fR and
+\fIgroup\fR parameters. Only the primary spectrum is combined and
+the associated band spectra are ignored.
+.le
+.ls output
+List of output images to be created containing the combined spectra.
+If the grouping option is "all"
+or "apertures" then only one output image will be created. In the
+first case the image will contain only one spectrum and in the latter case
+there will be a spectrum for each selected aperture.
+If the grouping option is "images" then there will be one
+output spectrum per input spectrum.
+.le
+.ls noutput = ""
+List of output images to be created containing the number of spectra combined.
+The number of images required is the same as the \fIoutput\fR list.
+Any or all image names may be given as a null string, i.e. "", in which
+case no output image is created.
+.le
+.ls logfile = "STDOUT"
+File name for recording log information about the combining operation.
+The file name "STDOUT" is used to write the information to the terminal.
+If the null string is specified then no log information is printed or
+recorded.
+.le
+
+.ls apertures = ""
+List of apertures to be selected for combining. If none is specified
+then all apertures are selected. The syntax is a blank or comma separated
+list of aperture numbers or aperture ranges separated by a hyphen.
+.le
+.ls group = "apertures" (all|images|apertures)
+Option for grouping input spectra for combining (after selection by aperture)
+from one or more input images. The options are:
+.ls "all"
+Combine all spectra from all images in the input list into a single output
+spectrum.
+.le
+.ls "images"
+Combine all spectra in each input image into a single spectrum in
+separate output images.
+.le
+.ls "apertures"
+Combine all spectra of the same aperture from all input images and put it
+into a single output image with the other selected apertures.
+.le
+.le
+.ls combine = "average" (average|median|sum)
+Option for combining pixels at the same dispersion coordinate. after any
+rejection operation. The options are to compute the "average", "median",
+or "sum" of the pixels. The first two are applied after any pixel
+rejection. The sum option ignores the rejection and scaling parameters and
+no rejection is performed. In other words, the "sum" option is simply the
+direct summation of the pixels. The median uses the average of the two
+central values when the number of pixels is even.
+.le
+.ls reject = "none" (none|minmax|ccdclip|crreject|sigclip|avsigclip|pclip)
+Type of rejection operation performed on the pixels which overlap at each
+dispersion coordinate. The algorithms are discussed in the
+DESCRIPTION section. The rejection choices are:
+
+.nf
+ none - No rejection
+ minmax - Reject the nlow and nhigh pixels
+ sigclip - Reject pixels using a sigma clipping algorithm
+ avsigclip - Reject pixels using an averaged sigma clipping algorithm
+ ccdclip - Reject pixels using CCD noise parameters
+ crreject - Reject only positive pixels using CCD noise parameters
+ pclip - Reject pixels using sigma based on percentiles
+.fi
+
+.le
+
+.ls first = no
+Use the first input spectrum of each set to be combined to define the
+dispersion coordinates for combining and output? If yes then all other
+spectra to be combined will be interpolated to the dispersion of this
+reference spectrum and that dispersion defines the dispersion of the
+output spectrum. If no, then all the spectra are interpolated to a linear
+dispersion as determined by the following parameters. The interpolation
+type is set by the package parameter \fIinterp\fR.
+.le
+.ls w1 = INDEF, w2=INDEF, dw = INDEF, nw = INDEF, log = no
+The output linear or log linear wavelength scale if the dispersion of the
+first spectrum is not used. INDEF values are filled in from the maximum
+wavelength range and minimum dispersion of the spectra to be combined. The
+parameters are aways specified in linear wavelength even when the log
+parameter is set to produce constant pixel increments in the log of the
+wavelength. The dispersion is interpreted in that case as the difference
+in the log of the endpoints divided by the number of pixel increments.
+.le
+
+.ls scale = "none" (none|mode|median|mean|exposure|@<file>|!<keyword>)
+Multiplicative image scaling to be applied. The choices are none,
+multiply by the reciprocal of the mode , median, or mean of the specified
+statistics section, scale by the exposure time in the image header, multiply
+by the values in a specified file, or multiply by a specified image header
+keyword. When specified in a file the scales must be one per line in the
+order of the input spectra.
+.le
+.ls zero = "none" (none|mode|median|mean|@<file>|!<keyword>)
+Additive zero level image shifts to be applied. The choices are none,
+add the negative of the mode, median, or mean of the specified statistics
+section, add the values given in a file, or add values given by an
+image header keyword. When specified in a file the zero values must be one
+per line in the order of the input spectra. File or keyword zero offset
+values do not allow a correction to the weights.
+.le
+.ls weight = "none" (none|mode|median|mean|exposure|@<file>|!<keyword>)
+Weights to be applied during the final averaging. The choices are none,
+the mode, median, or mean of the specified statistics section, the exposure
+time, values given in a file, or values given by an image header keyword.
+When specified in a file the weights must be one per line in the order of
+the input spectra.
+.le
+.ls sample = ""
+Wavelength sample regions to use in computing spectrum statistics for
+scaling and weighting. If no sample regions are given then the entire
+input spectrum is used. The syntax is colon separated wavelengths
+or a file containing colon separated wavelengths preceded by the
+@ character; i.e. @<file>.
+.le
+
+.ce
+Algorithm Parameters
+.ls lthreshold = INDEF, hthreshold = INDEF
+Low and high thresholds to be applied to the input pixels. This is done
+before any scaling, rejection, and combining. If INDEF the thresholds
+are not used.
+.le
+.ls nlow = 1, nhigh = 1 (minmax)
+The number of low and high pixels to be rejected by the "minmax" algorithm.
+These numbers are converted to fractions of the total number of input spectra
+so that if no rejections have taken place the specified number of pixels
+are rejected while if pixels have been rejected by thresholding
+or nonoverlap, then the fraction of the remaining pixels, truncated
+to an integer, is used.
+.le
+.ls nkeep = 1
+The minimum number of pixels to retain or the maximum number to reject
+when using the clipping algorithms (ccdclip, crreject, sigclip,
+avsigclip, or pclip). When given as a positive value this is the minimum
+number to keep. When given as a negative value the absolute value is
+the maximum number to reject. This is actually converted to a number
+to keep by adding it to the number of images.
+.le
+.ls mclip = yes (ccdclip, crreject, sigclip, avsigcliip)
+Use the median as the estimate for the true intensity rather than the
+average with high and low values excluded in the "ccdclip", "crreject",
+"sigclip", and "avsigclip" algorithms? The median is a better estimator
+in the presence of data which one wants to reject than the average.
+However, computing the median is slower than the average.
+.le
+.ls lsigma = 3., hsigma = 3. (ccdclip, crreject, sigclip, avsigclip, pclip)
+Low and high sigma clipping factors for the "ccdclip", "crreject", "sigclip",
+"avsigclip", and "pclip" algorithms. They multiply a "sigma" factor
+produced by the algorithm to select a point below and above the average or
+median value for rejecting pixels. The lower sigma is ignored for the
+"crreject" algorithm.
+.le
+.ls rdnoise = "0.", gain = "1.", snoise = "0." (ccdclip, crreject)
+Effective CCD readout noise in electrons, gain in electrons/DN, and
+sensitivity noise as a fraction. These parameters are used with the
+"ccdclip" and "crreject" algorithms. The values may be either numeric or
+an image header keyword which contains the value. Note that if the spectra
+have been extracted from a 2D CCD image then the noise parameters must be
+adjusted for background and the aperture summing.
+.le
+.ls sigscale = 0.1 (ccdclip, crreject, sigclip, avsigclip)
+This parameter determines when poisson corrections are made to the
+computation of a sigma for images with different scale factors. If all
+relative scales are within this value of unity and all relative zero level
+offsets are within this fraction of the mean then no correction is made.
+The idea is that if the images are all similarly though not identically
+scaled, the extra computations involved in making poisson corrections for
+variations in the sigmas can be skipped. A value of zero will apply the
+corrections except in the case of equal images and a large value can be
+used if the sigmas of pixels in the images are independent of scale and
+zero level.
+.le
+.ls pclip = -0.5 (pclip)
+Percentile clipping algorithm parameter. If greater than
+one in absolute value then it specifies a number of pixels above or
+below the median to use for computing the clipping sigma. If less
+than one in absolute value then it specifies the fraction of the pixels
+above or below the median to use. A positive value selects a point
+above the median and a negative value selects a point below the median.
+The default of -0.5 selects approximately the quartile point.
+See the DESCRIPTION section for further details.
+.le
+.ls grow = 0
+Number of pixels to either side of a rejected pixel
+to also be rejected. This applies only to pixels rejected by one of
+the rejection algorithms and not the threshold rejected pixels.
+.le
+.ls blank = 0.
+Value to use when there are no input pixels to combine for an output pixel.
+.le
+.ih
+DESCRIPTION
+\fBScombine\fR combines input spectra by interpolating them (if necessary)
+to a common dispersion sampling, rejecting pixels exceeding specified low
+and high thresholds, scaling them in various ways, applying a rejection
+algorithm based on known or empirical noise statistics, and computing the
+sum, weighted average, or median of the remaining pixels. Note that
+the "sum" option is the direct summation of the pixels and does not
+perform any rejection or scaling of the data regardless of the parameter
+settings.
+
+The input spectra are specified using an image list in which each image
+may contain multiple spectra. The set of spectra may be restricted
+by the \fIaperture\fR parameter to specific apertures. The set of input
+spectra may then be grouped using the \fIgroup\fR parameter and each
+group combined separately into a final output spectrum. The grouping
+options are to select all the input spectra regardless of the input
+image or aperture number, select all spectra of the same aperture,
+or select all the spectra from the same input image.
+
+The output consists of either a single image with one spectrum for each
+combined group or, when grouping by image, an image with the single
+combined spectra from each input image. The output images and
+combined spectra inherit the header parameters from the first spectrum
+of the combined group. In addition to the combined spectrum an associated
+integer spectrum containing the number of pixels combined
+and logfile listing the combined spectra, scaling, weights, etc, may
+be produced.
+
+The spectral combining is done using pixels at common dispersion
+coordinates rather than physical or logical pixel coordinates. If the
+spectra to be combined do not have identical dispersion coordinates then
+the spectra are interpolated to a common dispersion sampling before
+combining. The interpolation conserves pixel values rather pixel fluxes.
+This means that flux calibrated data is treated correctly and that
+spectra in counts are not corrected in the interpolation for changes
+in pixel widths.
+The default interpolation function is a 5th order polynomial. The
+choice of interpolation type is made with the package parameter "interp".
+It may be set to "nearest", "linear", "spline3", "poly5", or "sinc".
+Remember that this applies to all tasks which might need to interpolate
+spectra in the \fBonedspec\fR and associated packages. For a discussion of
+interpolation types see \fBonedspec\fR.
+
+There are two choices for the common dispersion coordinate sampling. If the
+\fIfirst\fR parameter is set then the dispersion sampling of the first
+spectrum is used. This dispersion system may be nonlinear. If the
+parameter is not set then the user specified linear or log linear
+dispersion system is used. Any combination of starting wavelength, ending
+wavelength, wavelength per pixel, and number of output pixels may be
+specified. Unspecified values will default to reasonable values based on
+the minimum or maximum wavelengths of all spectra, the minimum dispersion,
+and the number of pixels needed to satisfy the other parameters. If the
+parameters overspecify the linear system then the ending wavelength is
+adjusted based on the other parameters. Note that for a log linear system
+the wavelengths are still specified in nonlog units and the dispersion is
+finally recalculated using the difference of the log wavelength endpoints
+divided by the number pixel intervals (the number of pixels minus one).
+
+There are several stages to combining a selected group of spectra. The
+first is interpolation to a common dispersion sampling as discussed
+above. The second stage is to eliminate any pixels outside the specified
+thresholds. Note that the thresholds apply to the interpolated
+spectra. Scaling and zero offset factors are computed and applied to the
+spectra if desire. The computation of these factors as well as weights is
+discussed in the following section. Next there is a choice of rejection
+algorithms to identify and eliminate deviant pixels. Some of these are
+based on order statistics and some relative to the distance from an initial
+median or average using a noise model cutoff. A growing factor may be
+applied to neighbors of rejected pixels to reject additional pixels. The
+various algorithms are described in detail in a following section.
+Finally, the remaining pixels are combined by summing (which may not be
+appropriate when pixels are rejected), computing a median, or computing a
+weighted or unweighted average. The combined spectrum is written to an
+output image as well the number of pixels used in the final combining.
+
+SCALES AND WEIGHTS
+
+In order to combine spectra with rejection of pixels based on deviations
+from some average or median they must be scaled to a common level. There
+are two types of scaling available, a multiplicative intensity scale and an
+additive zero point shift. The intensity scaling is defined by the
+\fIscale\fR parameter and the zero point shift by the \fIzero\fR
+parameter. These parameters may take the values "none" for no scaling,
+"mode", "median", or "mean" to scale by statistics of the spectrum pixels,
+"exposure" (for intensity scaling only) to scale by the exposure time
+keyword in the image header, any other image header keyword specified by
+the keyword name prefixed by the character '!', and the name of a file
+containing the scale factors for the input image prefixed by the
+character '@'.
+
+Examples of the possible parameter values are shown below where
+"myval" is the name of an image header keyword and "scales.dat" is
+a text file containing a list of scale factors.
+
+.nf
+ scale = none No scaling
+ zero = mean Intensity offset by the mean
+ scale = exposure Scale by the exposure time
+ zero = !myval Intensity offset by an image keyword
+ scale = @scales.dat Scales specified in a file
+.fi
+
+The spectrum statistics factors are computed within specified sample
+regions given as a series of colon separated wavelengths. If no
+regions are specified then all pixels are used. If the
+wavelength sample list is too long the regions can be defined in a file and
+specified in the \fIsample\fR parameter using the syntax @<file> where file
+is the filename.
+
+The statistics are as indicated by their names. In particular, the
+mode is a true mode using a bin size which is a fraction of the
+range of the pixels and is not based on a relationship between the
+mode, median, and mean. Also thresholded pixels are excluded from the
+computations as well as during the rejection and combining operations.
+
+The "exposure" option in the intensity scaling uses the value of the image
+header keyword (EXPTIME, EXPOSURE, or ITIME). Note that the exposure
+keyword is also updated in the final image as the weighted average of the
+input values. If one wants to use a nonexposure time keyword and keep the
+exposure time updating feature the image header keyword syntax is
+available; i.e. !<keyword>.
+
+Scaling values may be defined as a list of values in a text file. The file
+name is specified by the standard @file syntax. The list consists of one
+value per line. The order of the list is assumed to be the same as the
+order of the input spectra. It is a fatal error if the list is incomplete
+and a warning if the list appears longer than the number of input spectra.
+Consideration of the grouping parameter must be included in
+generating this list since spectra may come from different images,
+some apertures may be missing, and, when there are multiple output spectra
+or images, the same list will be repeatedly used.
+
+If both an intensity scaling and zero point shift are selected the
+multiplicative scaling is done first. Use of both makes sense for images
+if the intensity scaling is the exposure time to correct for
+different exposure times and with the zero point shift allowing for
+sky brightness changes. This is less relevant for spectra but the option
+is available.
+
+The spectrum statistics and scale factors are recorded in the log file
+unless they are all equal, which is equivalent to no scaling. The
+intensity scale factors are normalized to a unit mean and the zero
+point shifts are adjusted to a zero mean. When scal factors
+or zero point shifts are specified by the user in an @file or by an
+image header keyword, no normalization is done.
+
+Scaling affects not only the mean values between spectra but also the
+relative pixel uncertainties. For example scaling an spectrum by a
+factor of 0.5 will reduce the effective noise sigma of the spectrum
+at each pixel by the square root of 0.5. Changes in the zero
+point also changes the noise sigma if the spectrum noise characteristics
+are Poissonian. In the various rejection algorithms based on
+identifying a noise sigma and clipping large deviations relative to
+the scaled median or mean, one may need to account for the scaling induced
+changes in the spectrum noise characteristics.
+
+In those algorithms it is possible to eliminate the "sigma correction"
+while still using scaling. The reasons this might be desirable are 1) if
+the scalings are similar the corrections in computing the mean or median
+are important but the sigma corrections may not be important and 2) the
+spectrum statistics may not be Poissonian, either inherently or because the
+spectra have been processed in some way that changes the statistics. In the
+first case because computing square roots and making corrections to every
+pixel during the iterative rejection operation may be a significant
+computational speed limit the parameter \fIsigscale\fR selects how
+dissimilar the scalings must be to require the sigma corrections. This
+parameter is a fractional deviation which, since the scale factors are
+normalized to unity, is the actual minimum deviation in the scale factors.
+For the zero point shifts the shifts are normalized by the mean shift
+before adjusting the shifts to a zero mean. To always use sigma scaling
+corrections the parameter is set to zero and to eliminate the correction in
+all cases it is set to a very large number.
+
+If the final combining operation is "average" then the spectra may be
+weighted during the averaging. The weights are specified in the same way
+as the scale factors. The weights, scaled to a unit sum, are printed in
+the log output.
+
+The weights are only used for the final weighted average and sigma image
+output. They are not used to form averages in the various rejection
+algorithms. For weights in the case of no scaling or only multiplicative
+scaling the weights are used as given or determined so that images
+with lower signal levels will have lower weights. However, for
+cases in which zero level scaling is used the weights are computed
+from the initial weights (the exposure time, image statistics, or
+input values) using the formula:
+
+.nf
+ weight_final = weight_initial / (scale * zero)
+.fi
+
+where the zero values are those before adjustment to zero mean over
+all images. The reasoning is that if the zero level is high the sky
+brightness is high and so the S/N is lower and the weight should be lower.
+
+
+THRESHOLD REJECTION
+
+There is an initial threshold rejection step which may be applied. The
+thresholds are given by the parameters \fIlthreshold\fR and
+\fIhthreshold\fR. Values of INDEF mean that no threshold value is
+applied. Threshold rejection may be used to exclude very bad pixel values
+or as a way of masking images. The former case is useful to exclude very
+bright cosmic rays. Some of the rejection algorithms, such as "avsigclip",
+can perform poorly if very strong cosmic rays are present. For masking one
+can use a task like \fBimedit\fR or \fBimreplace\fR to set parts of the
+spectra to be excluded to some very low or high magic value.
+
+
+REJECTION ALGORITHMS
+
+The \fIreject\fR parameter selects a type of rejection operation to
+be applied to pixels not thresholded. If no rejection
+operation is desired the value "none" is specified. This task is
+closely related to the image combining task \fBimcombine\fR and, in
+particular, has the same rejection algorithms.
+Some the algorithms are more appropriate to images but are available
+in this task also for completeness.
+
+MINMAX
+.in 4
+A specified fraction of the highest and lowest pixels are rejected.
+The fraction is specified as the number of high and low pixels, the
+\fInhigh\fR and \fInlow\fR parameters, when data from all the input spectra
+are used. If pixels are missing where there is no overlap or have been
+rejected by thresholding then a matching fraction of the remaining pixels,
+truncated to an integer, are used. Thus,
+
+.nf
+ nl = n * nlow/nspectra + 0.001
+ nh = n * nhigh/nspectra + 0.001
+.fi
+
+where n is the number of pixels to be combined, nspectra is the number
+of input spectra, nlow and nhigh
+are task parameters and nl and nh are the final number of low and
+high pixels rejected by the algorithm. The factor of 0.001 is to
+adjust for rounding of the ratio.
+
+As an example with 10 input spectra and specifying one low and two high
+pixels to be rejected the fractions to be rejected are 0.1 and 0.2
+and the number rejected as a function of n is:
+
+.nf
+ n 0 1 2 3 4 5 6 7 8 9 10
+ nl 0 0 0 0 0 1 1 1 1 1 2
+ nh 0 0 0 0 0 0 0 0 0 0 1
+.fi
+.in -4
+CCDCLIP
+.in 4
+If the noise characteristics of the spectra can be described by fixed
+gaussian noise, a poissonian noise which scales with the square root of
+the intensity, and a sensitivity noise which scales with the intensity,
+the sigma in data values at a pixel with true value <I>,
+as approximated by the median or average with the lowest and highest value
+excluded, is given as:
+
+.nf
+ sigma = ((rn / g) ** 2 + <I> / g + (s * <I>) ** 2) ** 1/2
+.fi
+
+where rn is the read out noise in electrons, g is the gain in
+electrons per data value, s is a sensitivity noise given as a fraction,
+and ** is the exponentiation operator. Often the sensitivity noise,
+due to uncertainties in the pixel sensitivities (for example from the
+flat field), is not known in which case a value of zero can be used.
+
+This model is typically valid for CCD images. During extraction of
+spectra from CCD images the noise parameters of the spectrum pixels
+will be changed from those of the CCD pixels. Currently it is up to
+the user to determine the proper modifications of the CCD read noise
+gain, and sensitivity noise.
+
+The read out noise is specified by the \fIrdnoise\fR parameter. The value
+may be a numeric value to be applied to all the input spectra or an image
+header keyword containing the value for spectra from each image.
+Similarly, the parameter \fIgain\fR specifies the gain as either a value or
+image header keyword and the parameter \fIsnoise\fR specifies the
+sensitivity noise parameter as either a value or image header keyword.
+
+The algorithm operates on each output pixel independently. It starts by
+taking the median or unweighted average (excluding the minimum and maximum)
+of the unrejected pixels provided there are at least two input pixels. The
+expected sigma is computed from the CCD noise parameters and pixels more
+that \fIlsigma\fR times this sigma below or \fIhsigma\fR times this sigma
+above the median or average are rejected. The process is then iterated
+until no further pixels are rejected. If the average is used as the
+estimator of the true value then after the first round of rejections the
+highest and lowest values are no longer excluded. Note that it is possible
+to reject all pixels if the average is used and is sufficiently skewed by
+bad pixels such as cosmic rays.
+
+If there are different CCD noise parameters for the input images
+(as might occur using the image header keyword specification) then
+the sigmas are computed for each pixel from each image using the
+same estimated true value.
+
+If the images are scaled and shifted and the \fIsigscale\fR threshold
+is exceedd then a sigma is computed for each pixel based on the
+spectrum scale parameters; i.e. the median or average is scaled to that of the
+original image before computing the sigma and residuals.
+
+After rejection the number of retained pixels is checked against the
+\fInkeep\fR parameter. If there are fewer pixels retained than specified
+by this parameter the pixels with the smallest residuals in absolute
+value are added back. If there is more than one pixel with the same
+absolute residual (for example the two pixels about an average
+or median of two will have the same residuals) they are all added
+back even if this means more than \fInkeep\fR pixels are retained.
+Note that the \fInkeep\fR parameter only applies to the pixels used
+by the clipping rejection algorithm and does not apply to threshold
+or bad pixel mask rejection.
+
+This is the best clipping algorithm to use if the CCD noise parameters are
+adequately known. The parameters affecting this algorithm are \fIreject\fR
+to select this algorithm, \fImclip\fR to select the median or average for
+the center of the clipping, \fInkeep\fR to limit the number of pixels
+rejected, the CCD noise parameters \fIrdnoise, gain\fR and \fIsnoise\fR,
+\fIlsigma\fR and \fIhsigma\fR to select the clipping thresholds,
+and \fIsigscale\fR to set the threshold for making corrections to the sigma
+calculation for different image scale factors.
+
+.in -4
+CRREJECT
+.in 4
+This algorithm is identical to "ccdclip" except that only pixels above
+the average are rejected based on the \fIhsigma\fR parameter. This
+is appropriate for rejecting cosmic ray events and works even with
+two spectra.
+
+.in -4
+SIGCLIP
+.in 4
+The sigma clipping algorithm computes at each output pixel the median or
+average excluding the high and low values and the sigma about this
+estimate. There must be at least three input pixels, though for this method
+to work well there should be at least 10 pixels. Values deviating by more
+than the specified sigma threshold factors are rejected. These steps are
+repeated, except that after the first time the average includes all values,
+until no further pixels are rejected or there are fewer than three pixels.
+
+After rejection the number of retained pixels is checked against the
+\fInkeep\fR parameter. If there are fewer pixels retained than specified
+by this parameter the pixels with the smallest residuals in absolute
+value are added back. If there is more than one pixel with the same
+absolute residual (for example the two pixels about an average
+or median of two will have the same residuals) they are all added
+back even if this means more than \fInkeep\fR pixels are retained.
+Note that the \fInkeep\fR parameter only applies to the pixels used
+by the clipping rejection algorithm and does not apply to threshold
+rejection.
+
+The parameters affecting this algorithm are \fIreject\fR to select
+this algorithm, \fImclip\fR to select the median or average for the
+center of the clipping, \fInkeep\fR to limit the number of pixels
+rejected, \fIlsigma\fR and \fIhsigma\fR to select the
+clipping thresholds, and \fIsigscale\fR to set the threshold for
+making corrections to the sigma calculation for different spectrum scale
+factors.
+
+.in -4
+AVSIGCLIP
+.in 4
+The averaged sigma clipping algorithm assumes that the sigma about the
+median or mean (average excluding the low and high values) is proportional
+to the square root of the median or mean at each point. This is
+described by the equation:
+
+.nf
+ sigma(column,line) = sqrt (gain(line) * signal(column,line))
+.fi
+
+where the \fIestimated\fR signal is the mean or median (hopefully excluding
+any bad pixels) and the gain is the \fIestimated\fR proportionality
+constant having units of photons/data number.
+
+This noise model is valid for spectra whose values are proportional to the
+number of photons recorded. In effect this algorithm estimates a
+photon per data value gain for each spectrum.
+The gain proportionality factor is computed
+independently for each output spectrum by averaging the square of the residuals
+(at points having three or more input values) scaled by the median or
+mean.
+
+Once the proportionality factor is determined, deviant pixels exceeding the
+specified thresholds are rejected at each point by estimating the sigma
+from the median or mean. If any values are rejected the median or mean
+(this time not excluding the extreme values) is recomputed and further
+values rejected. This is repeated until there are no further pixels
+rejected or the number of remaining input values falls below three. Note
+that the proportionality factor is not recomputed after rejections.
+
+If the spectra are scaled differently and the sigma scaling correction
+threshold is exceedd then a correction is made in the sigma
+calculations for these differences, again under the assumption that
+the noise in an spectra scales as the square root of the mean intensity.
+
+After rejection the number of retained pixels is checked against the
+\fInkeep\fR parameter. If there are fewer pixels retained than specified
+by this parameter the pixels with the smallest residuals in absolute
+value are added back. If there is more than one pixel with the same
+absolute residual (for example the two pixels about an average
+or median of two will have the same residuals) they are all added
+back even if this means more than \fInkeep\fR pixels are retained.
+Note that the \fInkeep\fR parameter only applies to the pixels used
+by the clipping rejection algorithm and does not apply to threshold
+rejection.
+
+This algorithm works well for even a few input spectra. It works better if
+the median is used though this is slower than using the average. Note that
+if the spectra have a known read out noise and gain (the proportionality
+factor above) then the "ccdclip" algorithm is superior. However, currently
+the CCD noise characteristics are not well propagated during extraction so
+this empirical algorithm is the one most likely to be useful. The two
+algorithms are related in that the average sigma proportionality factor is
+an estimate of the gain.
+
+The parameters affecting this algorithm are \fIreject\fR to select
+this algorithm, \fImclip\fR to select the median or average for the
+center of the clipping, \fInkeep\fR to limit the number of pixels
+rejected, \fIlsigma\fR and \fIhsigma\fR to select the
+clipping thresholds, and \fIsigscale\fR to set the threshold for
+making corrections to the sigma calculation for different image scale
+factors.
+
+.in -4
+PCLIP
+.in 4
+The percentile clipping algorithm is similar to sigma clipping using the
+median as the center of the distribution except that, instead of computing
+the sigma of the pixels from the CCD noise parameters or from the data
+values, the width of the distribution is characterized by the difference
+between the median value and a specified "percentile" pixel value. This
+width is then multipled by the scale factors \fIlsigma\fR and \fIhsigma\fR
+to define the clipping thresholds above and below the median. The clipping
+is not iterated.
+
+The pixel values at each output point are ordered in magnitude and the
+median is determined. In the case of an even number of pixels the average
+of the two middle values is used as the median value and the lower or upper
+of the two is the median pixel when counting from the median pixel to
+selecting the percentile pixel. The parameter \fIpclip\fR selects the
+percentile pixel as the number (if the absolute value is greater
+than unity) or fraction of the pixels from the median in the ordered set.
+The direction of the percentile pixel from the median is set by the sign of
+the \fIpclip\fR parameter with a negative value signifying pixels with
+values less than the median. Fractional values are internally converted to
+the appropriate number of pixels for the number of input spectra. A minimum
+of one pixel and a maximum corresponding to the extreme pixels from the
+median are enforced. The value used is reported in the log output. Note
+that the same percentile pixel is used even if pixels have been rejected by
+nonoverlap or thresholding; for example, if the 3nd pixel below
+the median is specified then the 3rd pixel will be used whether there are
+10 pixels or 5 pixels remaining after the preliminary steps.
+
+After rejection the number of retained pixels is checked against the
+\fInkeep\fR parameter. If there are fewer pixels retained than specified
+by this parameter the pixels with the smallest residuals in absolute
+value are added back. If there is more than one pixel with the same
+absolute residual (for example the two pixels about an average
+or median of two will have the same residuals) they are all added
+back even if this means more than \fInkeep\fR pixels are retained.
+Note that the \fInkeep\fR parameter only applies to the pixels used
+by the clipping rejection algorithm and does not apply to threshold
+or bad pixel mask rejection.
+
+Some examples help clarify the definition of the percentile pixel. In the
+examples assume 10 pixels. The median is then the average of the
+5th and 6th pixels. A \fIpclip\fR value of 2 selects the 2nd pixel
+above the median (6th) pixel which is the 8th pixel. A \fIpclip\fR
+value of -0.5 selects the point halfway between the median and the
+lowest pixel. In this case there are 4 pixels below the median,
+half of that is 2 pixels which makes the percentile pixel the 3rd pixel.
+
+The percentile clipping algorithm is most useful for clipping small
+excursions, such as the wings of bright lines when combining
+disregistered observations, that are missed when using
+the pixel values to compute a sigma. It is not as powerful, however, as
+using the CCD noise parameters (provided they are accurately known) to clip
+about the median. This algorithm is primarily used with direct images
+but remains available for spectra.
+
+The parameters affecting this algorithm are \fIreject\fR to select this
+algorithm, \fIpclip\fR to select the percentile pixel, \fInkeep\fR to limit
+the number of pixels rejected, and \fIlsigma\fR and \fIhsigma\fR to select
+the clipping thresholds.
+
+
+.in -4
+GROW REJECTION
+
+Neighbors of pixels rejected by the rejection algorithms
+may also be rejected. The number of neighbors to be rejected on either
+side is specified by the \fIgrow\fR parameter.
+
+This rejection step is also checked against the \fInkeep\fR parameter
+and only as many pixels as would not violate this parameter are
+rejected. Unlike it's application in the rejection algorithms at
+this stage there is no checking on the magnitude of the residuals
+and the pixels retained which would otherwise be rejected are randomly
+selected.
+
+
+COMBINING
+
+After all the steps of offsetting the input images, masking pixels,
+threshold rejection, scaling, and applying a rejection algorithms the
+remaining pixels are combined and output. The pixels may be combined
+by computing the median or by computing a weighted average.
+.ih
+EXAMPLES
+1. Combine orders of echelle images.
+
+.nf
+ cl> scombine *.ec *%.ec%% group=images combine=sum
+.fi
+
+2. Combine all spectra using range syntax and scale by the exposure times.
+
+.nf
+ cl> names irs 10-42 > irs.dat
+ cl> scombine @irs.dat irscombine group=all scale=exptime
+.fi
+
+3. Combine spectra by apertures using exposure time scaling and weighting.
+
+.nf
+ cl> scombine *.ms combine.ms nout=ncombine.ms \\
+ >>> group=apertures scale=exptime weights=exptime
+.fi
+.ih
+REVISIONS
+.ls SCOMBINE V2.10.3
+The weighting was changed from using the square root of the exposure time
+or spectrum statistics to using the values directly. This corresponds
+to variance weighting. Other options for specifying the scaling and
+weighting factors were added; namely from a file or from a different
+image header keyword. The \fInkeep\fR parameter was added to allow
+controlling the maximum number of pixels to be rejected by the clipping
+algorithms. The \fIsnoise\fR parameter was added to include a sensitivity
+or scale noise component to the noise model.
+.le
+.ls SCOMBINE V2.10
+This task is new.
+.le
+.ih
+NOTES
+The pixel uncertainties and CCD noise model are not well propagated. In
+particular it would be desirable to propagate the pixel uncertainties
+and CCD noise parameters from the initial CCD images.
+.ih
+SEE ALSO
+imcombine, odcombine, lscombine
+.endhelp
diff --git a/noao/onedspec/doc/scoords.hlp b/noao/onedspec/doc/scoords.hlp
new file mode 100644
index 00000000..9a529ffa
--- /dev/null
+++ b/noao/onedspec/doc/scoords.hlp
@@ -0,0 +1,83 @@
+.help scoords May97 onedspec
+.ih
+NAME
+scoords -- set spectrum coordinates from a pixel array (1D only)
+.ih
+USAGE
+scoords images coords
+.ih
+PARAMETERS
+.ls images
+List of one dimensional spectrum image names.
+.le
+.ls coords
+List of file names containing the coordinate values. There may be
+one file which applies to all input images or a matching list
+of one coordinate file for each input image. The coordinate files
+are a list of coordinate values with one coordinate per line.
+The coordinates must be ordered in increasing or decreasing value.
+The number of coordinates must match the number of pixels in the image.
+.le
+.ls label = ""
+Optional coordinate axis label. A typical value is "Wavelength"
+for wavelength coordinates.
+.le
+.ls units = ""
+Optional coordinate axis units. A typical value is "Angstroms". In
+order to allow coordinate conversions by other IRAF spectra tasks
+the value should be specified as one of the known units
+(see units description in \fBonedspec.package\fR).
+.le
+.ls verbose = yes
+Print a line as each spectrum is processed?
+.le
+.ih
+DESCRIPTION
+\fBScoords\fR sets spectral coordinates in one dimensional spectral
+images as a list of coordinates in the image header. The
+coordinate file(s) consists of coordinate values given one per line.
+Each coordinate value is assigned to an image pixel in the order given
+and so the number of coordinate values must match the number of pixels
+in the spectrum. Also the coordinates must be monotonically increasing
+or decreasing.
+
+When multiple spectra are to be set a matching list of coordinates can
+be specified or a single coordinate file for all images may be used.
+
+The coordinate system set in the header is an example of the "multispec"
+world coordinate system. This is understood by all the standard
+IRAF tasks. It is described under the help topic "onedspec.specwcs".
+Once the coordinates are set one may resample the spectrum to a
+more compact linear description using the task \fBdispcor\fR.
+
+Since the coordinate values are stored in the header (double
+precision numbers) the header can become quite large if the spectrum
+is long. Be sure the environment variable "min_lenuserarea" which
+defines the maximum size of the image header in number of characters
+is large enough to hold all the coordinates.
+.ih
+EXAMPLES
+1. Set the coordinates for a spectrum.
+
+.nf
+ cl> type coords.dat
+ 4000.
+ 4010.123
+ 4020.246
+ 4031.7
+ <etc>
+ cl> scoords spec coords.dat label=Wavelength units=Angstroms
+ cl> listpix spec wcs=world
+ 4000. 124.
+ 4010.123 543
+ <etc>
+.fi
+.ih
+REVISIONS
+.ls SCOORDS V2.11
+This is a new task with this version.
+.le
+.ih
+SEE ALSO
+rtextimage, dispcor, specwcs, onedspec.package
+.endhelp
diff --git a/noao/onedspec/doc/scopy.hlp b/noao/onedspec/doc/scopy.hlp
new file mode 100644
index 00000000..d0863687
--- /dev/null
+++ b/noao/onedspec/doc/scopy.hlp
@@ -0,0 +1,541 @@
+.help scopy Mar93 noao.onedspec
+.ih
+NAME
+scopy -- Select and copy spectra
+.ih
+USAGE
+scopy input output
+.ih
+PARAMETERS
+.ls input
+List of input images containing spectra to be copied.
+.le
+.ls output
+List of output image names or root names. Image
+sections are ignored and if the output format is "onedspec" then any record
+extensions are stripped to form the root name. If no output list is
+specified then the input list is used and the input images are replaced by
+the copied output spectra. If a single output name is specified then all
+copied spectra are written to the same output image or image root
+name. This allows packing or merging multiple spectra and requires
+properly setting the \fIclobber\fR, \fImerge\fR, \fIrenumber\fR and
+\fIoffset\fR parameters to achieve the desired output. If more than one
+output image is specified then it must match the input image list in
+number.
+.le
+.ls w1 = INDEF, w2 = INDEF
+Starting and ending wavelengths to be copied. If \fIw1\fR is not specified
+then the wavelength of the starting edge of the first pixel is used
+(wavelength at pixel coordinate 0.5) and if \fIw2\fR is not specified then
+the wavelength of the ending edge of the last pixel is used (wavelength of
+the last pixel plus 0.5). If both are not specified, that is set to INDEF,
+then the whole spectrum is copied and the \fIrebin\fR parameter is
+ignored. Note that by specifying both endpoints the copied region can be
+set to have increasing or decreasing wavelength per pixel. If the spectrum
+only partially covers the specified range only that portion of the spectrum
+within the range is copied. It is an error if the range is entirely
+outside that of a spectrum.
+.le
+.ls apertures = "", beams = ""
+List of apertures and beams to be selected from the input spectra. The
+logical intersection of the two lists is selected. The null list
+selects all apertures or beams. A list consists of comma separated
+numbers and ranges of numbers. A range is specified by a hyphen. An
+optional step size may be given by 'x' followed by a number.
+See \fBxtools.ranges\fR for more information. If the first character
+is "!" then the apertures/beams not in the list are selected. Note
+that a "!" in either of the lists complements the intersection of the
+two lists. For longslit input spectra the aperture numbers
+selects the lines or columns to be extracted. For 3D Fabry-Perot
+spectra the aperture numbers select the first spatial axis.
+.le
+.ls bands = ""
+List of bands in 3D multispec.
+For 3D spatial spectra the band parameter applies to the second
+spatial axis.
+The null list selects all bands. The syntax is as described above.
+.le
+.ls apmodulus = 0
+Modulus to be applied to the input aperture numbers before matching against
+the aperture list. If zero then no modulus is used. This is allows
+selecting apertures which are related by the same modulus, typically a
+factor of 10; for example, 10, 1010 and 2010 with a modulus of 1000 are
+related.
+.le
+.ls format = "multispec" (multispec|onedspec)
+Output image format and name syntax. The "multispec" format consists of
+one or more spectra in the same image file. The "onedspec" format consists
+of a single spectrum per image with names having a root name and a four
+digit aperture number extension. Note that converting to "onedspec" format
+from three dimensional images where the third dimension contains associated
+spectra will not include data from the extra dimension. Image sections may
+be used in that case.
+.le
+.ls renumber = no
+Renumber the output aperture numbers? If set the output aperture
+numbers, including any preexisting spectra when merging, are renumbered
+beginning with 1. The \fIoffset\fR parameter may be used to
+change the starting number.
+.le
+.ls offset = 0
+Offset to be added to the input or renumbered aperture number to form
+the final output aperture number.
+.le
+.ls clobber = no
+Modify an existing output image either by overwriting or merging?
+.le
+.ls merge = no
+Merge apertures into existing spectra? This
+requires that the \fIclobber\fR parameter be set. If not merging
+then the selected spectra entirely replace those in existing output images.
+If merging then the input spectra replace those in the output image
+with the same aperture number and new apertures are added if not present.
+.le
+.ls rebin = yes
+Rebin the spectrum to the exact wavelength range specified by the \fIw1\fR
+and \fIw2\fR parameters? If the range is given as INDEF for both endpoints
+this parameter does not apply. If a range is given and this parameter is
+not set then the pixels in the specified range (using the nearest pixels to
+the endpoint wavelengths) are copied without rebinning. In this case the
+wavelength of the first pixel may not be exactly that specified by \fIw1\fR
+and the dispersion, including non-linear dispersions, is unchanged. If
+this parameter is set the spectra are interpolated to have the first and
+last pixels at exactly the specified endpoint wavelengths while preserving
+the same number of pixels in the interval. Linear and log-linear
+dispersion types are maintained while non-linear dispersions are
+linearized.
+.le
+.ls verbose = no
+Print a record of each aperture copied?
+.le
+.ih
+DESCRIPTION
+\fBScopy\fR selects regions of spectra from an input list of spectral
+images and copies them to output images. This task can be used to extract
+aperture spectra from long slit and Fabry-Perot images and to select,
+reorganize, merge, renumber, pack, and unpack spectra in many ways. Below
+is a list of some of the uses and many examples are given in the EXAMPLES
+section.
+
+.nf
+ o Pack many spectra into individual images into a single image
+ o Unpack images with multiple spectra into separate images
+ o Extract a set of lines or columns from long slit spectra
+ o Extract a set of spatial positions from Fabry-Perot spectra
+ o Extract specific wavelength regions
+ o Select a subset of spectra to create a new image
+ o Merge a subset of spectra into an existing image
+ o Combine spectra from different images into one image
+ o Renumber apertures
+.fi
+
+Input spectra are specified by an image list which may include explicit
+image names, wildcard templates and @files containing image names.
+The image names may also include image sections such as to select portions of
+the wavelength coverage. The input images may be either one or two
+dimensional spectra. One dimensional spectra may be stored in
+individual one dimensional images or as lines in two (or three)
+dimensional images. The one dimensional spectra are identified by
+an aperture number, which must be unique within an image, and a beam number.
+Two dimensional long slit and three dimensional Fabry-Perot spectra are
+treated, for the purpose of this
+task, as a collection of spectra with dispersion either along any axis
+specified by the DISPAXIS image header parameter
+or the \fIdispaxis\fR package parameter. The aperture and band
+parameters specify a spatial position. A number of adjacent
+lines, columns, and bands, specified by the \fInsum\fR package parameter,
+will be summed to form an aperture spectrum. If number is odd then the
+aperture/band number refers to the middle and if it is even it refers to the
+lower of the two middle lines or columns.
+
+In the case of many spectra each stored in separate one dimensional
+images, the image names may be such that they have a common root name
+and a four digit aperture number extension. This name syntax is
+called "onedspec" format. Including such spectra in an
+input list may be accomplished either with wildcard templates such as
+
+.nf
+ name*
+ name.????.imh
+.fi
+
+where the image type extension ".imh" must be given to complete the
+template but the actual extension could also be that for an STF type
+image, or using an @file prepared with the task \fBnames\fR.
+To generate this syntax for output images the \fIformat\fR parameter
+is set to "onedspec" (this will be discussed further later).
+
+From the input images one may select a range of wavelengths with the
+\fIw1\fR and \fIw2\fR parameters and a subset of spectra based on aperture and
+beam numbers using the \fIaperture\fR and \fIbeam\fR parameters.
+If the wavelength range is specified as INDEF the full spectra are
+copied without any resampling. If the aperture and beam lists are not
+specified, an empty list, then all apertures and beams are selected. The
+lists may be those spectra desired or the complement obtained by prefixing
+the list with '!'. Only the selected wavelength range and spectra will
+be operated upon and passed on to the output images.
+
+Specifying a wavelength range is fairly obvious except for the question
+of pixel sampling. Either the pixels in the specified range are copied
+without resampling or the pixels are resampled to correspond eactly
+to the requested range. The choice is made with the \fIrebin\fR parameter.
+In the first case the nearest pixels to the specified wavelength
+endpoints are determined and those pixels and all those in between
+are copied. The dispersion relation is unchanged. In the second case
+the spectra are reinterpolated to have the specified starting and
+ending wavelengths with the same number of pixels between those points
+as in the original spectrum. The reinterpolation is done in either
+linear or log-linear dispersion. The non-linear dispersion functions
+are interpolated to a linear dispersion.
+
+Using \fBscopy\fR with long slit or Fabry-Perot images provides a quick and
+simple type of extraction as opposed to using the \fBapextract\fR package.
+When summing it is often desired to start each aperture after the number of
+lines summed. To do this specify a step size in the aperture/band list. For
+example to extract columns 3 to 23 summing every 5 columns you would use an
+aperture list of "3-23x5" and an \fInsum\fR of 5. If you do not use the
+step in the aperture list you would extract the sum of columns 1 to 5, then
+columns 2 to 6, and so on.
+
+In the special case of subapertures extracted by \fBapextract\fR, related
+apertures are numbered using a modulus; for example apertures
+5, 1005, 2005. To allow selecting all related apertures using a single
+aperture number the \fIapmodulus\fR parameter is used to specify the
+modulus factor; 1000 in the above example. This is a very specialized
+feature which should be ignored by most users.
+
+The output list of images may consist of an empty list, a single image,
+or a list of images matching the input list in number. Note that it
+is the number of image names that matters and not the number of spectra
+since there may be any number of spectra in an image. The empty list
+converts to the same list as the input and is shorthand for replacing
+the input image with the output image upon completion; therefore it
+is equivalent to the case of a matching list. If the input
+consists of just one image then the distinction between a single
+output and a matching list is moot. The interesting distinction is
+when there is an input list of two or more images. The two cases
+are then a mapping of many-to-many or many-to-one. Note that it is
+possible to have more complex mappings by repeating the same output
+name in a matching list provided clobbering, merging, and possibly
+renumbering is enabled.
+
+In the case of a matching list, spectra from different input images
+will go to different output images. In the case of a single output
+image all spectra will go to the same output image. Note that in
+this discussion an output image when "onedspec" format is specified
+is actually a root name for possibly many images. However,
+it should be thought of as a single image from the point of view
+of image lists.
+
+When mapping many spectra to a single output image, which may have existing
+spectra if merging, there may be a conflict with repeated aperture
+numbers. One option is to consecutively renumber the aperture numbers,
+including any previous spectra in the output image when merging and then
+continuing with the input spectra in the order in which they are selected.
+This is specified with the \fIrenumber\fR parameter which renumbers
+beginning with 1.
+
+Another options which may be used independently of renumbering or in
+conjunction with it is to add an offset as specified by the \fIoffset\fR
+parameter. This is last step in determining the output aperture
+numbers so that if used with the renumber option the final aperture
+numbers begin with one plus the offset.
+
+It has been mentioned that it is possible to write and add to
+existing images. If an output image exists an error will be
+printed unless the \fIclobber\fR parameter is set. If clobbering
+is allowed then the existing output image will be replaced by the
+new output. Rather than replacing an output image sometimes one
+wants to replace certain spectra or add new spectra. This is
+done by selecting the \fImerge\fR option. In this case if the output
+has a spectrum with the same aperture number as the input spectrum
+it is replaced by the input spectrum. If the input spectrum aperture
+number is not in the output then the spectrum is added to the output
+image. To add spectra with the same aperture number and not
+replace the one in the output use the \fIrenumber\fR or
+\fIoffset\fR options.
+
+To print a record as each input spectrum is copied the \fIverbose\fR
+parameter may be set. The syntax is the input image name followed
+by the aperture number in []. An arrow then points to the output
+image name with the final aperture number also in [], except for
+"onedspec" format where the image name extension gives the aperture
+number. It is important to remember that it is the aperture numbers
+which are shown and not the image lines; there is not necessarily any
+relation between image lines and aperture numbers though often they
+are the same.
+.ih
+EXAMPLES
+Because there are so many possiblities there are many examples. To
+help find examples close to those of interest they are divided into
+three sections; examples involving standard multispec images only, examples
+with onedspec format images, and examples with long slit and Fabry-Perot
+images. In the examples the verbose flag is set to yes and the output is
+shown.
+
+I. MULTISPEC IMAGES
+
+The examples in this section deal with the default spectral format of
+one or more spectra in an image. Note that the difference between
+a "onedspec" image and a "multispec" image with one spectrum is purely
+the image naming syntax.
+
+1. Select a single spectrum (aperture 3):
+
+.nf
+ cl> scopy example1 ap3 aperture=3
+ example1[3] --> ap3[3]
+.fi
+
+2. Select a wavelength region from a single spectrum:
+
+.nf
+ cl> scopy example1 ap3 aperture=3 w1=5500 w2=6500
+ example1[3] --> ap3[3]
+.fi
+
+3. Select a subset of spectra (apertures 1, 2, 4, 6, and 9):
+
+.nf
+ cl> scopy example1 subset apertures="1-2,4,6-9x3"
+ example1[1] --> subset[1]
+ example1[2] --> subset[2]
+ example1[4] --> subset[4]
+ example1[6] --> subset[6]
+ example1[9] --> subset[9]
+.fi
+
+This example shows various features of the aperture list syntax.
+
+4. Select the same apertures (1 and 3) from multiple spectra and in the
+same wavelength region:
+
+.nf
+ cl> scopy example* %example%subset%* apertures=1,3 w1=5500 w2=6500
+ example1[1] --> subset1[1]
+ example1[3] --> subset1[3]
+ example2[1] --> subset2[1]
+ example2[3] --> subset2[3]
+ ...
+.fi
+
+The output list uses the pattern substitution feature of image templates.
+
+5. Select the same aperture from multiple spectra and pack them in a
+a single image:
+
+.nf
+ cl> scopy example* ap2 aperture=2 renumber+
+ example1[2] --> ap2[1]
+ example2[2] --> ap2[2]
+ example3[2] --> ap2[3]
+ ...
+.fi
+
+6. To renumber the apertures sequentially starting with 11:
+
+.nf
+ cl> scopy example1 renum renumber+
+ example1[1] --> renum[11]
+ example1[5] --> renum[12]
+ example1[9] --> renum[13]
+ ...
+.fi
+
+7. To replace apertures (2) in one image with that from another:
+
+.nf
+ cl> scopy example1 example2 aperture=2 clobber+ merge+
+ example1[2] --> example2[2]
+.fi
+
+8. To merge two sets of spectra with different aperture numbers into
+ one image:
+
+.nf
+ cl> scopy example![12]* merge
+ example1[1] -> merge[1]
+ example1[3] -> merge[3]
+ ...
+ example2[2] -> merge[2]
+ example2[4] -> merge[4]
+ ...
+.fi
+
+The input list uses the ![] character substitution syntax of image templates.
+
+9. To merge a set of spectra with the same aperture numbers into another
+existing image:
+
+.nf
+ cl> scopy example2 example1 clobber+ merge+ renumber+
+ example1[5] --> example1[2]
+ example1[9] --> example1[3]
+ example2[1] --> example1[4]
+ example2[5] --> example1[5]
+ example2[9] --> example1[6]
+.fi
+
+Both images contained apertures 1, 5, and 9. The listing does not show
+the renumbering of the aperture 1 from example1 since the aperture number
+was not changed.
+
+10. Select parts of a 3D image where the first band is the
+variance weighted extraction, band 2 is nonweighted extraction,
+band 3 is the sky, and band 4 is the sigma:
+
+.nf
+ cl> scopy example3d.ms[*,*,1] var1.ms
+ example3d.ms[*,*,1][1] --> var1.ms[1]
+ example3d.ms[*,*,1][2] --> var1.ms[2]
+ ...
+ cl> scopy example3d.ms[10:400,3,3] skyap3
+ example3d.ms[10:400,3,3][3] --> skyap3[3]
+ cl> scopy example3d.ms[*,*,1] "" clobber+
+ example3d.ms[*,*,1][1] --> example3d.ms[1]
+ example3d.ms[*,*,1][2] --> example3d.ms[2]
+ ...
+.fi
+
+Note that this could also be done with \fBimcopy\fR. The last example
+is done in place; i.e. replacing the input image by the output image
+with the other bands eliminatated; i.e. the output image is two dimensional.
+
+II. ONEDSPEC IMAGES
+
+1. Expand a multi-spectrum image to individual single spectrum images:
+
+.nf
+ cl> scopy example1 record format=onedspec
+ example1[1] --> record.0001
+ example1[5] --> record.0005
+ example1[9] --> record.0009
+ ...
+.fi
+
+2. Pack a set of individual 1D spectra into a single image:
+
+.nf
+ cl> scopy record.????.imh record.ms
+ record.0001[1] --> record.ms[1]
+ record.0005[5] --> record.ms[5]
+ record.0009[9] --> record.ms[9]
+ ...
+.fi
+
+3. Copy a set of record syntax spectra to a different rootname and renumber:
+
+.nf
+ cl> scopy record.????.imh newroot format=onedspec
+ record.0001[1] --> newroot.0001
+ record.0005[5] --> newroot.0002
+ record.0009[9] --> newroot.0003
+ ...
+.fi
+
+III. LONG SLIT IMAGES
+
+To define the dispersion axis either the image header parameter DISPAXIS
+must be set (using HEDIT for example) or a the package \fIdispaxis\fR
+parameter must be set. In these examples the output is the default
+multispec format.
+
+1. To extract column 250 into a spectrum:
+
+.nf
+ cl> scopy longslit1 c250 aperture=250
+ longslit1[250] --> c250[250]
+.fi
+
+2. To sum and extract every set of 10 columns:
+
+.nf
+ cl> nsum = 10 (or epar the package parameters)
+ cl> scopy longslit1 sum10 apertures=5-500x10
+ longslit1[5] --> sum10[5]
+ longslit1[15] --> sum10[15]
+ longslit1[25] --> sum10[25]
+ ...
+.fi
+
+3. To extract the sum of 10 columns centered on column 250 from a set
+of 2D images:
+
+.nf
+ cl> nsum = 10 (or epar the package parameters)
+ cl> scopy longslit* %longslit%c250.%* aperture=250
+ longslit1[250] --> c250.1[250]
+ longslit2[250] --> c250.2[250]
+ longslit3[250] --> c250.3[250]
+ ...
+.fi
+
+4. To extract the sum of 10 columns centered on column 250 from a set of
+2D images and merge them into a single, renumbered output image:
+
+.nf
+ cl> nsum = 10 (or epar the package parameters)
+ cl> scopy longslit* c250 aperture=250 renum+
+ longslit1[250] --> c250[1]
+ longslit2[250] --> c250[2]
+ longslit3[250] --> c250[3]
+ ...
+.fi
+
+IV. FABRY-PEROT IMAGES
+
+To define the dispersion axis either the image header parameter DISPAXIS
+must be set (using HEDIT for example) or a the package \fIdispaxis\fR
+parameter must be set. In these examples the output is the default
+multispec format.
+
+1. To extract a spectrum from the spatial position (250,250) where
+dispaxis=3:
+
+.nf
+ cl> scopy fp1 a250 aperture=250 band=250
+ longslit1[250] --> a250[250]
+.fi
+
+2. To sum and extract every set of 10 lines and bands (dispaxis=1):
+
+.nf
+ cl> nsum = "10"
+ cl> scopy fp1 sum10 apertures=5-500x10 bands=5-500x10
+ longslit1[5] --> sum10[5]
+ longslit1[15] --> sum10[15]
+ longslit1[25] --> sum10[25]
+ ...
+.fi
+
+3. To extract the sum of 10 columns and 20 lines centered on column 250 and
+line 100 from a set of 3D images with dispaxis=3:
+
+.nf
+ cl> nsum = "10 20"
+ cl> scopy longslit* %longslit%c250.%* aperture=250 band=100
+ longslit1[250] --> c250.1[250]
+ longslit2[250] --> c250.2[250]
+ longslit3[250] --> c250.3[250]
+ ...
+.fi
+.ih
+REVISIONS
+.ls SCOPY V2.11
+Previously both w1 and w2 had to be specified to select a range to
+copy. Now if only one is specified the second endpoint defaults
+to the first or last pixel.
+.le
+.ls SCOPY V2.10.3
+Additional support for 3D multispec/equispec or spatial spectra has been
+added. The "bands" parameter allows selecting specific bands and
+the onedspec output format creates separate images for each selected
+aperture and band.
+.le
+.ls SCOPY V2.10
+This task is new.
+.le
+.ih
+SEE ALSO
+ranges, sarith, imcopy, dispcor, specshift
+.endhelp
diff --git a/noao/onedspec/doc/sensfunc.hlp b/noao/onedspec/doc/sensfunc.hlp
new file mode 100644
index 00000000..1ebd7e24
--- /dev/null
+++ b/noao/onedspec/doc/sensfunc.hlp
@@ -0,0 +1,447 @@
+.help sensfunc Mar93 noao.onedspec
+.ih
+NAME
+sensfunc -- Determine sensitivity and extinction functions
+.ih
+USAGE
+sensfunc standards sensitivity
+.ih
+PARAMETERS
+.ls standards = "std"
+Input standard star data file created by the task \fBstandard\fR.
+.le
+.ls sensitivity = "sens"
+Output sensitivity function image name or rootname. Generally each
+aperture results in an independent sensitivity function with the
+aperture number appended to the rootname. If the parameter \fIignoreaps\fR
+is set, however, the aperture numbers are ignored and a single sensitivity
+function is determined with the output image having the specified name
+with no extension.
+.le
+.ls apertures = ""
+List of apertures to be selected from the input file. All other apertures
+are ignored. If no list is specified then all apertures are selected.
+See \fBranges\fR for the syntax.
+.le
+.ls ignoreaps = no
+Ignore aperture numbers and create a single sensitivity function? Normally
+each aperture produces an independent sensitivity function. If the
+apertures are ignored then all the observations are combined into
+a single sensitivity function.
+.le
+.ls logfile = "logfile"
+Output log filename for statistical information about the stars used
+and the sensitivity function and extinction function.
+If no filename is given then no file is written.
+.le
+.ls extinction = <no default>
+Input extinction file. Any extinction determination made will be
+relative to this extinction. If no file is given then no extinction
+correction is applied and any extinction determination from the
+standard star data will be an absolute determination of the
+extinction. The default value is redirected to the package parameter
+of the same name. The extinction file is generally one of the standard
+extinctions in the calibration directory "onedstds$".
+
+If extinction corrected spectra were used as input to \fBstandard\fR
+then it is important that the same extinction file be used here.
+This includes using no extinction file in both tasks.
+.le
+.ls newextinction = "extinct.dat"
+Output revised extinction file. If the extinction is revised and an
+output filename is given then a revised extinction file is written. It
+has the same format as the standard extinction files.
+.le
+.ls observatory = ")_.observatory"
+Observatory at which the spectra were obtained if not specified in the
+image header by the keyword OBSERVAT. The default is a redirection to look
+in the parameters for the parent package for a value. This is only used
+when graphing flux calibrated data of spectra which do not include the
+airmass in the image header. The observatory may be one of the
+observatories in the observatory database, "observatory" to select the
+observatory defined by the environment variable "observatory" or the
+parameter \fBobservatory.observatory\fR, or "obspars" to select the current
+parameters set in the \fBobservatory\fR task. See help for
+\fBobservatory\fR for additional information.
+.le
+.ls function = "spline3"
+Function used to fit the sensitivity data. The function types are
+"chebyshev" polynomial, "legendre" polynomial, "spline3" cubic spline,
+and "spline1" linear spline. The default value may be changed interactively.
+.le
+.ls order = 6
+Order of the sensitivity fitting function. The value corresponds to the
+number of polynomial terms or the number of spline pieces. The default
+value may be changed interactively.
+.le
+.ls interactive = yes
+Determine the sensitivity function interactively? If yes the user
+graphically interacts with the data, modifies data and parameters
+affecting the sensitivity function, and determines a residual extinction.
+.le
+.ls graphs = "sr"
+Graphs to be displayed per frame. From one to four graphs may be displayed
+per frame. The graph types are selected by single characters and are:
+
+.nf
+a - residual sensitivity vs airmass
+c - composite residual sensitivity and error bars vs wavelength
+e - input extinction and revised extinction vs wavelength
+i - Flux calibrated spectrum vs wavelength
+r - residual sensitivity vs wavelength
+s - sensitivity vs wavelength
+.fi
+
+All other characters including whitespace and commas are ignored. The order
+and number of graphs determines the positions of the graphs.
+.le
+.ls marks = "plus cross box"
+Symbols used to mark included, deleted, and added data respectively.
+The available mark types are point, box, plus, cross, diamond, hline
+(horizontal line), vline (vertical line), hebar (horizontal error bar),
+vebar (vertical error bar), and circle.
+.le
+.ls colors = "2 1 3 4"
+Colors to use for "lines", "marks", "deleted" data, and "added" data.
+The colors associated with the numbers is graphics device dependent.
+For example in XGTERM they are defined by resources while on other
+devices that don't support colors only one color will appear.
+.le
+.ls cursor = ""
+Graphics cursor input list. If not specified as a file then standard
+graphics cursor is read.
+.le
+.ls device = "stdgraph"
+Graphics output device.
+.le
+.ls answer
+Query parameter for selecting whether to fit apertures interactively.
+.le
+.ih
+CURSOR COMMANDS
+
+.nf
+? Print help
+a Add a point at the cursor position
+c Toggle use of composite points
+d Delete point, star, or wavelength nearest the cursor
+e Toggle residual extinction correction
+f Fit data with a sensitivity function and overplot
+g Fit data with a sensitivity function and redraw the graph(s)
+i Print information about point nearest the cursor
+m Move point, star, wavelength nearest the cursor to new sensitivity
+o Reset to original data
+q Quit and write sensitivity function for current aperture
+r Redraw graph(s)
+s Toggle shift of standard stars to eliminate mean deviations
+u Undelete point, star, or wavelength nearest the cursor
+w Change weights of point, star, or wavelength nearest the cursor
+
+:flux [min] [max] Limits for flux calibrated graphs (INDEF for autoscale)
+:function [type] Function to be fit to sensitivity data:
+ chebyshev - Chebyshev polynomial
+ legendre - Legendre polynomial
+ spline1 - Linear spline
+ spline3 - Cubic spline
+:graphs [types] Graphs to be displayed (up to four):
+ a - Residual sensitivity vs airmass
+ c - Composite residuals and error bars vs wavelength
+ e - Extinction (and revised extinction) vs wavelength
+ i - Flux calibrated image vs wavelength
+ l - Log of flux calibrated image vs wavelength
+ r - Residual sensitivity vs wavelength
+ s - Sensitivity vs wavelength
+:images [images] Images to flux calibrate and plot (up to four)
+:marks marks Mark types to use for included, delete, and added points:
+ point, box, plus, cross, diamond, hline,
+ vline, hebar, vebar, circle
+:order [order] Order of function
+:skys [images] Sky images for flux calibration (up to four)
+:stats [file] Statistics about stars and sensitivity fit
+:vstats [file] Verbose statistics about sensitivity fit
+.fi
+.ih
+DESCRIPTION
+Standard star calibration measurements are used to determine the system
+sensitivity as a function of wavelength for each independent aperture.
+If the parameter \fIignoreaps\fR is set then the aperture numbers are
+ignored and a single sensitivity function is determined from all the
+observations. Using measurements spanning a range of airmass it is
+also possible to derive an adjustment to the standard extinction curve
+or even an absolute determination. Extinction determination requires
+that the observations span a good range of airmass during photometric
+conditions. When conditions are poor and standard star observations
+are obtained during periods of variable transparency, the entire
+sensitivity curve may vary by a constant factor, assuming that the
+cause of the variations has no color effect. This is often the case
+during periods of thin clouds. In this case the mean sensitivity of
+each observation may be shifted to match the observation of greatest
+sensitivity. This allows for the possibility of deriving correct
+absolute fluxes if one observation of a standard was obtained during a
+clear period.
+
+The input data is a file of calibration information produced by the
+task \fBstandard\fR. The data consists of a spectrum identification
+line containing the spectrum image name, the sky image name if beam
+switching, the aperture number, the length of the spectrum, the
+exposure time, airmass, wavelength range, and title. Following the
+identification line are calibration lines consisting of the central
+bandpass wavelengths, the tabulated fluxes in the bandpasses, the
+bandpass widths, and the observed counts in the bandpasses. The
+spectrum identification and calibration lines repeat for each standard
+star observation. The parameter \fIapertures\fR may be used to select
+only specific apertures from the input data. This parameter is in the
+form of a range list (see help for \fBranges\fR) and if no list is
+given (specified by the null string "") then all apertures are selected.
+
+An input extinction file may also be specified. Any extinction
+determinations are then residuals to this input extinction table.
+The format of this table is described in \fBlcalib\fR.
+
+The calibration factor at each point is computed as
+
+ (1) C = 2.5 log (O / (T B F)) + A E
+
+where O is the observed counts in a bandpass of an observation,
+T is the exposure time of the observation, B is the bandpass width,
+F is the flux per Angstrom at the bandpass for the standard star,
+A is the airmass of the observation, and E is the extinction
+at the bandpass. Thus, C is the ratio of the observed count rate per
+Angstrom corrected to some extinction curve to the expected flux
+expressed in magnitudes. The goal of the task is to fit the observations
+to the relation
+
+ (2) C = S(W) + AE(W)
+
+where W is wavelength, S(W) is the sensitivity function, and E(W) is
+a residual extinction function relative to the extinction used in (1).
+In later discussion we will also refer to the residual sensitivity which
+is defined by
+
+ (3) R = C - S(W) - AE(W)
+
+The sensitivity function S(W) is output as an one dimensional image
+much like the spectra. The sensitivities are in magnitude units to
+better judge the variations and because the interpolation is smoother
+in the logarithmic space (mags = 2.5 log10[sensitivity]). There is one
+sensitivity function for each aperture unless the parameter
+\fIignoreaps\fR is set. In the first case the image names are formed
+from the specified rootname with the aperture number as a four digit
+numerical extension. In the latter case a single sensitivity function
+is determined from all data, ignoring the aperture numbers, and the
+specified output image is created without an extension. These images
+are used by \fBcalibrate\fR to correct observations to a relative of
+absolute flux scale. If no sensitivity function image rootname is
+specified then the sensitivity curves are not output.
+
+If a revised extinction function E(W) has been determined for one or
+more of the apertures then the functions are averaged over all
+apertures, added to the original extinction, and written to the
+specified extinction table. The format of this table is the same as
+the standard extinction tables and are, thus, interchangeable. If no
+new extinction filename is specified then no extinction table is
+recorded.
+
+If a log filename is given then statistical information about the
+sensitivity function determinations are recorded. This includes the
+names of the input standard star observations and the tabulated
+sensitivity, extinction, and error information.
+
+Some points to note are that if no input extinction is given then the
+E in (1) are zero and the E determined in (2) is the absolute extinction.
+If the data are not good enough to determine extinction then using one
+of the standard extinction curves the problem reduces to fitting
+
+ (4) C = S(W)
+
+The sensitivity and extinction functions are determined as fitted
+curves. The curves are defined by a function type and order. There
+are four function types and the order specifies either the number of
+terms in the polynomial or the number of pieces in the spline. The
+order is automatically reduced to the largest
+value which produces a nonsingular result. In this case the function
+will attempt to pass through every calibration point. Lower orders
+provide for a smoother representation of the function. The latter
+is generally more appropriate for a detector. The initial function
+type and order for the sensitivity function is specified by the
+parameters \fIfunction\fR and \fIorder\fR.
+
+If the \fIinteractive\fR flag is no then the default function and order
+is fit to equation (4) (i.e. there is no residual extinction determination
+or manipulation of the data). The sensitivity functions are output
+if an image rootname is given and the log information is output if a
+log filename is given.
+
+When the sensitivity is determined interactively a query is given for
+each aperture. The responses "no" and "yes" select fitting the sensitivity
+interactively or not for the specified aperture. The responses "NO" and
+"YES" apply to all apertures and no further queries will be given.
+When interactive fitting is selected the data are graphed
+on the specified graphics device and input is through the specified
+cursor list. The graphics output consists of from one to four graphs.
+The user selects how many and which types of graphs to display. The
+graph types and their single character code used to select them are:
+
+.nf
+ a - residual sensitivity vs airmass
+ c - composite residual sensitivity and error bars vs wavelength
+ e - input extinction and revised extinction vs wavelength
+ i - Flux calibrated spectrum vs wavelength
+ r - residual sensitivity vs wavelength
+ s - sensitivity vs wavelength
+.fi
+
+The initial graphs are selected with the parameter \fBgraphs\fR and changed
+interactively with the colon command ':graphs \fItypes\fR'. The ability
+to view a variety of graphs allows evaluating the effects of the
+sensitivity curve and extinction in various ways. The flux calibrated
+spectrum graph uses the current sensitivity function and checks for
+possible wiggles in the sensitivity curve which affect the shape of the
+continuum. The choice of graphs also allows the
+user to trade off plotting speed and resolution against the amount of
+information available simultaneously. Thus, with some graphics devices
+or over a slow line one can reduce the number of graphs for greater speed
+while on very fast devices with large screens one can look at more
+data. The parameter \fImarks\fR and the associated colon command
+':marks \fItypes\fR' also let the user define the symbols used to mark
+included, deleted, and added data points.
+
+The list of interactive commands in given in the section on CURSOR COMMANDS.
+The commands include deleting, undeleting, adding, moving, and identifying
+individual data points, whole stars, or all points at the same wavelength.
+Some other commands include 'c' to create composite points by averaging
+all points at the same wavelength (this requires exact overlap in the
+bandpasses) which then replace the individual data points in the fit.
+This is different than the composite point graph which displays the
+residual in the mean sensitivity
+and error \fIin the mean\fR but uses the input data in the fitting.
+The 's' command shifts the data so that the mean sensitivity of each
+star is the same as the star with the greatest mean sensitivity.
+This compensates for variable grey extinction due to clouds. Note
+that delete points are excluded from the shift calculation and a
+deleted star will not be used as the star of greatest sensitivity.
+Another useful command is 'o' to recover the original data. This cancels
+all changes made due to shifting, extinction corrections, deleting points,
+creating composite points, etc.
+
+The 'e' command attempts to compute a residual extinction by finding
+correlations between the sensitivity points at different airmass.
+Note that this is not iterative so that repeating this after having
+added an extinction correction simply redetermines the correction.
+At each wavelength or wavelength regions having multiple observations at
+different airmass a slope with airmass is determined. This slope is
+the residual extinction at that wavelength. A plot of the residual
+extinctions at each wavelength is made using the ICFIT procedure.
+The user may then examine and fit a curve through the residual extinction
+estimates as a function of wavelength (see \fBicfit\fR for a description
+of the commands). The user must decide how much wavelength dependence
+is derivable from the data. In many cases only a constant fit
+to a "gray extinction" or possibly a linear fit is realistic.
+The fitting is exited by the key 'q'.
+
+To help evaluate how important the residual extinction determination
+is a t-statistic significance is computed. This statistic is defined by
+
+ (5) t = sqrt (r**2 * (N - 2) / (1 - r**2))
+
+where the correlation coefficient
+
+ (6) r = RMS with correction / RMS without correction
+
+is the fractional improvement in the RMS due to the added extinction
+correction and N is the number of wavelength points. For large
+N this approaches a gaussian sigma but a more precise significance
+requires the t-distribution for N-2 degrees of freedom. Basically this
+asks, was the improvement in the RMS significantly more than would
+occur with random errors? A value greater than 3 is good while
+a value less than 1 is not significant. The user may then accept the
+revised extinction and apply it to the data.
+
+Note that when there are multiple apertures used each aperture has an
+independent system sensitivity but the residual extinction is the same.
+Therefore, the residual extinctions from each aperture are averaged at
+the end. If one determines a new extinction then one may replace the
+original input extinction by the new extinction and rederive the
+sensitivity.
+.ih
+EXAMPLES
+1. The following command generates sensitivity spectra
+
+ cl> sensfunc std sens
+
+This command uses the data from the \fBstandard\fR output
+file "std" to create sensitivity functions with rootname "sens".
+If not interactive the task will produce the output with some
+progress messages being printed. If it is interactive the graphics
+device will be used to display the data and the fit and user can
+change the function and order of the fit, delete bad points, shift
+data to correct for clouds or bandpass errors, and possibly determine
+a revised extinction function. The statistics of the
+sensitivity determination are written to the logfile ("logfile" by
+default).
+
+2. The following examples illustrate the colon command syntax. Generally
+if no argument is given the current value is displayed. For the statistics
+commands an optional output file may be given to record the information.
+
+.nf
+:flux 1e-12 INDEF Set lower limit for flux plots
+:flux INDEF INDEF Restore autoscaling in flux plots
+:func spline3 Select cubic spline function
+:g srae Graph sensitivity, residuals, airmass,
+ and extinction
+:g sii Graph sensitivity and two images
+:i n1.0004 n1.0008 Set first two images to graph (the defaults
+ are taken from the standard star list)
+:skys n1.0005 Subtract this sky image from first image
+ for flux calibrated spectrum
+:m plus Change the mark type for included points and
+ don't change the deleted or added point mark type
+:stats Print statistics to terminal
+:vstats stdstats Print verbose statistics to file
+.fi
+.ih
+REVISIONS
+.ls SENSFUNC V2.10.3+
+Deleted points and stars are now ignored from the grey shift calculation.
+.le
+.ls SENSFUNC V2.10.3
+A color parameter was added for graphics terminals supporting color.
+.le
+.ls SENSFUNC V2.10
+The latitude parameter has been replaced by the observatory parameter.
+The 'i' flux calibrated graph type now shows flux in linear scaling
+while the new graph type 'l' shows flux in log scaling. A new colon
+command allows fixing the flux limits for the flux calibrated graphs.
+.le
+.ls SENSFUNC V2.8
+This task has been completely rewritten from that of versions 2.5 and
+earlier.
+
+.nf
+1. The input standard data format is different.
+2. Extinction corrections beyond a grey term are now supported.
+3. Weighting by the counts is not supported.
+4. Tabular input is not supported.
+5. The data which can be displayed is greatly improved.
+6. The fitting options have been greatly enhanced.
+7. The fitting function types available have been extended.
+8. One or more flux calibrated images may be displayed using the
+ current sensitivity function.
+9. Additional flexibility is provided for treating apertures.
+.fi
+.le
+.ih
+BUGS
+If the flux points do not span the wavelength range, set by the
+standard star observations, then the fitting may fail at some maximum
+order. When it fails there is no message but the highest order which
+can be successfully fit is used. To work around this one can either
+add fake points, truncate the wavelength range in the first line of each
+tabulated object in the file produced by \fBstandard\fR, or exclude the
+part of the image data which cannot be uncalibrated (using
+\fBscopy\fR or \fBdispcor\fR).
+.ih
+SEE ALSO
+standard, lcalib, calibrate, observatory, icfit, ranges, scopy, dispcor
+.endhelp
diff --git a/noao/onedspec/doc/sfit.hlp b/noao/onedspec/doc/sfit.hlp
new file mode 100644
index 00000000..0416c622
--- /dev/null
+++ b/noao/onedspec/doc/sfit.hlp
@@ -0,0 +1,262 @@
+.help sfit Mar92 noao.onedspec
+.ih
+NAME
+sfit -- Fit spectra
+.ih
+USAGE
+sfit input output
+.ih
+PARAMETERS
+.ls input
+Input spectra to be fit. These may be any combination of echelle,
+multispec, onedspec, long slit, and spectral cube format images.
+.le
+.ls output
+Output fitted spectra. The number of output spectra must
+match the number of input spectra. \fBOutput\fR may be omitted if
+\fBlistonly\fR is yes.
+.le
+.ls lines = "*", bands = "1"
+A range specifications for the image lines and bands to be fit. Unspecified
+lines and bands will be copied from the original. If the value is "*", all of
+the currently unprocessed lines or bands will be fit. A range consists of
+a first line number and a last line number separated by a hyphen. A
+single line number may also be a range and multiple ranges may be
+separated by commas.
+.le
+.ls type = "fit"
+Type of output spectra. The choices are "fit" for the fitted function,
+"ratio" for the ratio of the input spectra to the fit, "difference" for
+the difference between the input spectra and the fit, and "data" for
+the data minus any rejected points replaced by the fit.
+.le
+.ls replace = no
+Replace rejected points by the fit in the difference, ratio, and
+data output types?
+.le
+.ls wavescale = yes
+Wavelength scale the X axis of the plot? This option requires that the
+spectra be wavelength calibrated. If \fBwavescale\fR is no, the plots
+will be in "channel" (pixel) space.
+.le
+.ls logscale = no
+Take the log (base 10) of both axes? This can be used when \fBlistonly\fR
+is yes to measure the exponent of the slope of the continuum.
+.le
+.ls override = no
+Override previously fit spectra? If \fBoverride\fR is yes and
+\fBinteractive\fR is yes, the user will be prompted before each order is
+refit. If \fBoverride\fR is no, previously fit spectra are silently
+skipped.
+.le
+.ls listonly = no
+Don't modify any images? If \fBlistonly\fR is yes, the \fBoutput\fR
+image list may be skipped.
+.le
+.ls logfiles = "logfile"
+List of log files to which to write the power series coefficients. If
+\fBlogfiles\fR = NULL (""), the coefficients will not be calculated.
+.le
+.ls interactive = yes
+Perform the fit interactively using the icfit commands? This will allow
+the parameters for each spectrum to be adjusted independently. A separate
+set of the fit parameters (below) will be used for each spectrum and any
+interactive changes to the parameters for a specific spectrum will be
+remembered when that spectrum is fit in the next image.
+.le
+.ls sample = "*"
+The ranges of X values to be used in the fits. The units will vary
+depending on the setting of the \fBwavescale\fR and \fBlogscale\fR
+parameters. The default units are in wavelength if the spectra have
+been dispersion corrected. The sample range syntax consists of
+pairs of values separated by colons and multiple ranges can be
+given separated by commas.
+.le
+.ls naverage = 1
+Number of sample points to combined to create a fitting point.
+A positive value specifies an average and a negative value specifies
+a median.
+.le
+.ls function = spline3
+Function to be fit to the spectra. The functions are
+"legendre" (legendre polynomial), "chebyshev" (chebyshev polynomial),
+"spline1" (linear spline), and "spline3" (cubic spline). The functions
+may be abbreviated. The power series coefficients can only be
+calculated if \fBfunction\fR is "legendre" or "chebyshev".
+.le
+.ls order = 1
+The order of the polynomials or the number of spline pieces.
+.le
+.ls low_reject = 3., high_reject = 3.
+Rejection limits below and above the fit in units of the residual sigma.
+.le
+.ls niterate = 0
+Number of rejection iterations.
+.le
+.ls grow = 1.
+When a pixel is rejected, pixels within this distance of the rejected pixel
+are also rejected.
+.le
+.ls markrej = yes
+Mark rejected points? If there are many rejected points it might be
+desired to not mark rejected points.
+.le
+.ls graphics = "stdgraph"
+Graphics output device for interactive graphics.
+.le
+.ls cursor = ""
+Graphics cursor input.
+.le
+.ih
+DESCRIPTION
+A one dimensional function is fit to spectra in a list of echelle,
+multispec, or onedspec format images. The first two formats will
+fit the spectra or orders (i.e. the lines) in each image.
+In this description the term "spectrum" will refer to a line of
+an image while "image" will refer to all spectra in an image.
+The parameters of the fit may vary from spectrum to spectrum within
+images and between images. The fitted function may
+be a legendre polynomial, chebyshev polynomial, linear spline, or cubic
+spline of a given order or number of spline pieces. The output spectra
+are formed from the fit, the ratio between the pixel values and the fit,
+the difference of the spectra to the fit, and the original data with
+rejected points possibly replaced. The output image is of pixel type real.
+
+The line/band numbers (for two/three dimensional images) are written to a
+list of previously processed lines in the header keywords \fISFIT\fR and
+\fISFITB\fR of the output image. A subsequent invocation of SFIT will only
+process those requested spectra that are not in this list. This ensures
+that even if the output image is the same as the input image that no
+spectra will be processed twice and permits an easy exit from the task in
+the midst of processing many spectra without losing any work or requiring
+detailed notes.
+
+The points to be fit in each spectrum are determined by
+selecting a sample of X values specified by the parameter \fIsample\fR
+and taking either the average or median of the number of points
+specified by the parameter \fInaverage\fR. The type of averaging is
+selected by the sign of the parameter with positive values indicating
+averaging, and the number of points is selected by the absolute value
+of the parameter. The sample units will vary depending on the settings
+of the \fBwavescale\fR and the \fBlogscale\fR parameters. Note that a
+sample that is specified in wavelength units may be entirely outside
+the domain of the data (in pixels) if some of the spectra are not
+dispersion corrected. The syntax of the sample specification is a comma
+separated, colon delimited list similar to the image section notation.
+For example, the \fBsample\fR, "6550:6555,6570:6575" might be used to
+fit the continuum near H-alpha.
+
+If \fIlow_reject\fR and/or \fIhigh_reject\fR are greater than zero the
+sigma of the residuals between the fitted points and the fitted
+function is computed and those points whose residuals are less than
+\fI-low_reject\fR * sigma and greater than \fIhigh_reject\fR * sigma
+are excluded from the fit. Points within a distance of \fIgrow\fR
+pixels of a rejected pixel are also excluded from the fit. The
+function is then refit without the rejected points. This rejection
+procedure may be iterated a number of times given by the parameter
+\fIniterate\fR.
+
+If \fIreplace\fR is set then any rejected points from the fitting
+are replaced by the fit in the data before outputing the difference,
+ratio, or data. For example with replacing the difference will
+be zero at the rejected points and the data output will be cleaned
+of deviant points.
+
+A range specification is used to select the \fIlines\fR and \fIbands\fR to be
+fit. These parameters may either be specified with the same syntax as the
+\fBsample\fR parameter, or with the "hyphen" syntax used elsewhere in
+IRAF. Note that a NULL range for \fBlines/bands\fR expands to \fBno\fR
+lines, not to all lines. An asterisk (*) should be used to represent a
+range of all of the image lines/bands. The fitting parameters (\fIsample,
+naverage, function, order, low_reject, high_reject, niterate, grow\fR)
+may be adjusted interactively if the parameter \fIinteractive\fR is
+yes. The fitting is performed with the \fBicfit\fR package. The
+cursor mode commands for this package are described in a separate help
+entry under "icfit". Separate copies of the fitting parameters are
+maintained for each line so that interactive changes to the parameter
+defaults will be remembered from image to image.
+.ih
+PROMPTS
+If several images or lines are specified, the user is asked whether
+to perform an interactive fit for each spectrum. The response
+may be \fByes, no, skip, YES, NO\fR or \fBSKIP\fR. The meaning of each
+response is:
+
+.nf
+ yes - Fit the next spectrum interactively.
+ no - Fit the next spectrum non-interactively.
+ skip - Skip the next spectrum in this image.
+
+ YES - Interactively fit all of the spectra of
+ all of the images with no further prompts.
+ NO Non-interactively fit all chosen spectra of all images.
+ SKIP - This will produce a second prompt, "Skip what?",
+ with the choices:
+
+ spectrum - skip this spectrum in all images
+ image - skip the rest of the current image
+ all - \fBexit\fR the program
+ This will \fBunlearn\fR the fit parameters
+ for all spectra!
+ cancel - return to the main prompt
+.fi
+.ih
+EXAMPLES
+1. To normalize all orders of the echelle spectrum for hd221170
+
+ cl> sfit hd221170.ec nhd221170.ec type=ratio
+
+Each order of the spectrum is graphed and the interactive options for
+setting and fitting the continuum are available. The important
+parameters are low_rejection (for an absorption spectrum), the function
+type, and the order of the function; these fit parameters are
+originally set to the defaults in the SFIT parameter file. A
+'?' will display a menu of cursor key options. Exiting with 'q' will
+update the output normalized order for the current image and proceed to
+the next order or image.
+
+The parameters of the fit for each order are initialized to the current
+values the first time that the order is fit. In subsequent images, the
+parameters for a order are set to the values from the previous image.
+The first time an order is fit, the sample region is reset to the
+entire order. Deleted points are ALWAYS forgotten from order to order
+and image to image.
+
+2. To do several images at the same time
+
+ cl> sfit spec*.imh c//spec*.imh
+
+Note how the image template concatenation operator is used to construct
+the output list of spectra. Alternatively:
+
+ cl> sfit @inlist @outlist
+
+where the two list files could have been created with the sections
+command or by editing.
+
+3. To measure the power law slope of the continuum (fluxed data)
+
+ cl> sfit uv.* type=ratio logscale+ listonly+ fun=leg order=2
+.ih
+REVISIONS
+.ls SFIT V2.10.4
+The task was expanded to include fitting specified bands in 3D multispec
+spectra.
+
+The task was expanded to include long slit and spectral cube data.
+.le
+.ls SFIT V2.10
+This task is new.
+.le
+.ih
+BUGS
+The errors are not listed for the power series coefficients.
+
+Spectra that are updated when \fBlogscale\fR is yes are written with a
+linear wavelength scale, but with a log normalized data value.
+
+Selection by aperture number is not supported.
+.ih
+SEE ALSO
+continuum, fit1d, icfit, ranges
+.endhelp
diff --git a/noao/onedspec/doc/sflip.hlp b/noao/onedspec/doc/sflip.hlp
new file mode 100644
index 00000000..66790e4e
--- /dev/null
+++ b/noao/onedspec/doc/sflip.hlp
@@ -0,0 +1,114 @@
+.help sflip Jul94 noao.onedspec
+.ih
+NAME
+sflip -- Flip data and/or dispersion coordinates in spectra
+.ih
+USAGE
+sflip input output
+.ih
+PARAMETERS
+.ls input
+List of input images containing spectra to be flipped.
+.le
+.ls output
+Matching list of output image names for flipped spectra.
+If no list is specified then the flipped spectra will replace the input
+spectra. If the output image name matching an input image name is the
+same then the flipped spectrum will replace the original spectrum.
+.le
+.ls coord_flip = no
+Flip the dispersion coordinates? If yes then the relationship between the
+logical pixel coordinates and the dispersion coordinates will be reversed so
+that the dispersion coordinate of the first pixel of the output image will
+correspond to the coordinate of the last pixel in the input image and
+vice-versa for the other endpoint pixel. The physical coordinates
+will also be flipped. Only the coordinate system along the dispersion
+axis is flipped.
+.le
+.ls data_flip = yes
+Flip the order of the data pixels as they are stored in the image along
+the dispersion axis? If yes then the first pixel in the input spectrum
+becomes the last pixel in the output spectrum along the dispersion
+axis of the image.
+.le
+.ih
+DESCRIPTION
+The dispersion coordinate system and/or the data in the spectra specified
+by the input list of images are flipped and stored in the matching output
+image given in the output list of images. If the output image list is left
+blank or an output image name is the same as an input image name then the
+operation is done so that the flipped spectra in the image replace the
+original spectra. All of the supported spectrum types are allowed; one
+dimensional images, collections of spectra in multispec format, and two and
+three dimensional spatial spectra in which one axis is dispersion. In all
+cases the flipping affects only the dispersion axis of the image as
+specified by the DISPAXIS header keyword or the "dispaxis" parameter. The
+parameters \fIcoord_flip\fR and \fIdata_flip\fR select whether the
+coordinate system and data are flipped. If neither operation is selected
+then the output spectra will simply be copies of the input spectra.
+
+Flipping of the coordinate system means that the relation between
+"logical" pixel coordinates (the index system of the image array)
+and the dispersion and physical coordinate systems is reversed.
+The dispersion coordinate of the first pixel in the flipped spectrum
+will be the same as the dispersion coordinate of the last pixel
+in the original spectrum and vice-versa for the other endpoint.
+
+Flipping of the data means that the order in which the pixels are stored
+in the image file is reversed along the image axis corresponding to
+the dispersion.
+
+While flipping spectra seems simple there are some subtleties. If
+both the coordinate system and the data are flipped then plots of
+the spectra in which the dispersion coordinates are shown will appear
+the same as in the original spectra. In particular the coordinate
+of a feature in the spectrum will remain unchanged. In contrast
+flipping either the coordinate system or the data will cause features
+in the spectrum to move to opposite ends of the spectrum relative
+to the dispersion coordinates.
+
+Since plotting programs often plot the dispersion axis in some standard way
+such as increasing from left to right, flipping both the dispersion
+coordinates and the data will produce plots that look identical even though
+the order of the points plotted will be reversed. Only if the spectra are
+plotted against logical pixel coordinates will a change be evident. Note
+also that the plotting programs themselves have options to reverse the
+displayed graph. So if all one wants is to reverse the direction of
+increasing dispersion in a plot then physically flipping of the spectra is
+not generally necessary.
+
+Flipping of both the coordinate system and the data is also equivalent
+to using an image section with a reversed axis. For example
+a one dimensional spectrum can be flipped in both dispersion coordinates
+and data pixel order by
+
+.nf
+ cl> imcopy spec1[-*] spec2
+.fi
+
+Higher dimensional spectra need appropriate dimensions in the image
+sections. One advantage of \fBsflip\fR is that it will determine the
+appropriate dispersion axis itself.
+.ih
+EXAMPLES
+In the following the spectra can be one dimensional, multispec,
+long slit, or spectral data cubes.
+
+.nf
+ cl> sflip spec1 spec1f # Flip data to new image
+ cl> sflip spec1 spec1 # Flip data to same image
+ cl> sflip spec1 spec1f coord+ data- # Flip coordinates and not data
+ cl> sflip spec1 spec1f coord+ # Flip both coordinates and data
+ cl> sflip spec* f//spec* # Flip a list of images
+.fi
+.ih
+REVISIONS
+.ls SFLIP V2.10.4
+New in this release. Note that the V2.9 SFLIP was different in that
+it was script which simply flipped the data. Coordinate systems were
+not handled in the same way.
+.le
+.ih
+SEE ALSO
+imcopy, scopy, dispcor, sapertures
+.endhelp
diff --git a/noao/onedspec/doc/sinterp.hlp b/noao/onedspec/doc/sinterp.hlp
new file mode 100644
index 00000000..b983beba
--- /dev/null
+++ b/noao/onedspec/doc/sinterp.hlp
@@ -0,0 +1,146 @@
+.help sinterp Mar92 noao.onedspec
+.ih
+NAME
+sinterp -- Interpolate a tables of x,y pairs to produce a spectrum
+.ih
+USAGE
+sinterp tbl_file
+.ih
+PARAMETERS
+.ls tbl_file
+The name of a file which contains the x,y pairs to be used as
+the basis for interpolation. The pairs must be in order of
+increasing x.
+.le
+
+The following parameters may or may not be necessary, depending
+on the options selected.
+
+.ls input
+If a few single elements are desired, rather than a full
+array of elements, the user may enter a sequence of x values
+from the terminal or a file to be used to interpolate into
+the x,y table (parameter curve_gen=no).
+.le
+.ls image
+If parameter make_image=yes, then an image file name is needed
+.le
+.ls order = 5
+If the interpolator is a polynomial fit or spline (interp_mode=
+chebyshev, legnedre, spline3, spline1), the order of the fit
+is required.
+.le
+.ls x1
+If parameter curve_gen=yes, this is the starting x value to
+begin the curve generation.
+.le
+
+Of the following three parameters, two must be specified, and the
+third will be derived.
+
+.ls x2 = 0.0
+As above, but x2 determines the endpoint of the curve.
+.le
+.ls dx = 0.0
+As above, but dx determines the pixel-to-pixel increment
+to be used during the curves generation.
+.le
+.ls npts = 0
+As above, but this determines the number of pixels to be generated.
+.le
+
+.ls curve_gen = no
+If this parameter is set to yes, then parameters x1, and two of
+the three x2, dx, npts are required. The output is in the form
+of new x,y pairs and may be redirected to a text file.
+But if parameter make_image is also yes, the output is
+in the form of an IRAF image file having the name given by
+the parameter image. If curve_gen=no, the user must supply
+a set of x values and interpolation is performed on those values.
+.le
+.ls make_image = no
+If set to yes, then curve_gen=yes is implied and an image file name
+is requied. A one dimensional IRAF image is created.
+.le
+.ls tbl_size = 1024
+This parameter defines the maximum size to be set aside for
+memory storage of the input x,y pairs.
+.le
+.ls interp_mode = "chebyshev"
+This parameter controls the method of interpolation. The linear
+and curve options are true interpolators, while chebyshev,
+legendre, spline3, and splin1 are fits to the data.
+.le
+.ih
+DESCRIPTION
+The specified file is read assuming it is a text file containing
+pairs of x,y values in the form: xxx yyy. The table is used
+to define the function y(x). The pairs must be entered in the file
+in increasing order of x.
+
+The user specifies either specific x values for which the function
+is to be evaluated, or specifies that a sequence of values beginning
+with x1 are to be generated. In the former case, the explicit x values
+may come either from the keyboard or from a file. In the latter case
+the user must also specify the sequence by defining the increment, dx,
+the endpoint, x2, and the number of points to generate in the sequence.
+Then y(x) is evaluated at x1, x1+dx, x1+2*dx, ... , x1+(n-2)*dx, x2.
+Only 2 of the 3 parameters (x2, dx, npts) are needed to fully
+specify the sequence.
+
+The output of the function evaluation is either new x,y pairs written
+to STDOUT, or an IRAF image.
+
+The function used to evaluated the tabular data may be any of the following
+forms:
+
+.ls (1)
+Linear interpolation between points.
+.le
+.ls (2)
+Smooth interpolation between points.
+.le
+.ls (3)
+A polynomial fit of either Legendre or Chebyshev types.
+.le
+.ls (4)
+A cubic or linear spline.
+.le
+
+If the table of x,y pairs is very large, the parameter tbl_size
+should be set to the number of pairs. For example, if a spectrum
+is available as a text file of x,y pairs (such as might be
+obtained from IUE), and the number of pairs is 4096, then tbl_size
+should be set to 4096. This provides for sufficient memory to
+contain the table.
+
+.ih
+EXAMPLES
+The following shows how a text file may be used to generate a spectrum:
+
+.nf
+ cl> sinterp textfile make+ x1=4000 x2=5000 npts=1024 \
+ >>> image=testimage interp_mode=curve
+.fi
+
+The following sequence shows how to generate a spectrum of an IRS
+standard star using the calibration file data as the source.
+
+.nf
+ cl> lcalib flam feige34 caldir=onedstds$irscal/ >textfile
+ cl> sinterp textfile make+ x1=3550 dx=1.242 npts=1024 \
+ >>> interp_mode=linear image=feige34
+.fi
+.ih
+REVISIONS
+.ls SINTERP V2.10.3+
+The image header dispersion coordinate system has been updated to the
+current system.
+.le
+.ls SINTERP V2.10
+This task is unchanged.
+.le
+.ih
+SEE ALSO
+lcalib
+.endhelp
diff --git a/noao/onedspec/doc/skytweak.hlp b/noao/onedspec/doc/skytweak.hlp
new file mode 100644
index 00000000..857e4380
--- /dev/null
+++ b/noao/onedspec/doc/skytweak.hlp
@@ -0,0 +1,311 @@
+.help skytweak Mar97 noao.onedspec
+.ih
+NAME
+skytweak -- sky subtract 1D spectra after tweaking sky spectra
+.ih
+SUMMARY
+Sky spectra are shifted and scaled to best subtract sky features from data
+spectra. This may be done non-interactively to minimize the RMS in some
+region or regions of the data spectra and interactively with a graphically
+search.
+.ih
+USAGE
+skytweak input output cal
+.ih
+PARAMETERS
+.ls input
+List of input data images containing one dimensional spectra to be
+corrected. All spectra in each image are corrected. The spectra need not
+be wavelength calibrated.
+.le
+.ls output
+List of output corrected images. The list must either match the input list
+or be an empty list. If an empty list is specified the input spectra will
+be replaced by the corrected spectra. The input spectra will also be
+replaced if the input and output image names are the same. Any other image
+name must be for a new image otherwise a warning message will be given and
+the task will proceed to the next input image.
+.le
+.ls cal
+List of sky calibration images. If a single image is specified it
+will apply to all the input images. Otherwise the list of calibration
+images must match the list of input images.
+.le
+.ls ignoreaps = no
+Ignore aperture numbers between the input spectra and the calibration
+spectra? If "no" then the calibration image must contain a spectrum
+with the same aperture number as each spectrum in the input image.
+Otherwise the first spectrum in the calibration image will be used
+for all spectra in the input image.
+.le
+.ls xcorr = yes
+Cross-correlate each input spectrum with the calibration spectrum to
+determine an shift for the calibration spectrum? Only regions specified by
+the sample regions parameter will be used in the cross-correlation.
+.le
+.ls tweakrms = yes
+Search for the minimum RMS in the corrected spectrum by adjusting the
+shifts and scales between the input spectrum and the calibration spectrum?
+The RMS is minimized in the specified sample regions.
+.le
+.ls interactive = yes
+Enter an interactive graphical mode to search for the best shift
+and scale between the input spectra and calibration spectra? This
+is done after the optional automatic cross-correlation and RMS minimization
+step. A query is made for each input spectrum so that the interactive
+step may be skipped during the execution of the task.
+.le
+.ls sample = "*"
+Sample regions to use for cross-correlation, automatic RMS minimization,
+and RMS values. The sample regions are specified by a list of comma
+separated ranges. The ranges are colon separate coordinate values.
+For dispersion calibrated spectra the coordinate values are in the
+dispersion units otherwise they are in pixel coordinates. The string "*"
+selects the entire spectrum. The sample regions may be changed
+interactively either with the cursor or with a colon command.
+.le
+.ls lag = 10
+The cross-correlation lag to use when \fIxcorr\fR = yes. The lag
+is given in pixels. This is the distance to either side of the
+initial shift over which the cross-correlation profile is computed.
+If a value of zero is given then the cross-correlation step is not done.
+.le
+.ls shift = 0., dshift = 1.
+The initial shift and shift step in pixels. This initializes the shift
+search parameters for the first spectrum. If \fIdshift\fR is zero then
+there will be no search for a new shift and the 'x' interactive function is
+disabled. These parameters may be changed interactively. After the
+first spectrum subsequent spectra begin with the values from the last
+spectrum.
+.le
+.ls scale = 1., dscale = 0.2
+The initial scale and scale step. This initializes the scale
+search parameters for the first spectrum. If \fIdscale\fR is zero then
+there will be no search for a new scale and the 'y' interactive function is
+disabled. These parameters may be changed interactively. After the
+first spectrum subsequent spectra begin with the values from the last
+spectrum.
+.le
+.ls offset = 1.
+The interactive search displays three candidate corrected spectra which
+have been normalized to a mean of one. The offset is added and subtracted
+to separate the three candidates. The value may be changed interactively.
+.le
+.ls smooth = 1
+The displayed candidate corrected spectra are smoothed by a moving
+boxcar average with a box size specified by this parameter. The smoothing
+only applies to the displayed spectra and does not affect the measured
+RMS or the output corrected spectra. The value may be changed interactively.
+.le
+.ls cursor = ""
+Input cursor for the interactive graphics. A null value selects the
+graphics cursor otherwise a file of cursor values may be specified.
+.le
+.ls answer
+Query parameter for responding to the interactive question. This parameter
+should not be specified on the command line.
+.le
+.ls interp = poly5
+The \fBpackage\fR parameter specifying the interpolation function for shifting
+the calibration spectra to match the input spectra.
+.le
+.ih
+DESCRIPTION
+Input one dimensional spectra are corrected to remove sky features by
+subtracting a shifted and scaled sky calibration spectra.
+The shifting
+allows for possible small shifts or errors in the dispersion zeropoints.
+
+The following describes the correction. Let J(x_i) be the calibration
+spectrum at a set of pixels x_i. An interpolation function is fit to this
+spectrum to give J(x). The shifted and scaled calibration function
+is then
+
+.nf
+ (1) J'(x) = J(x+dx) *scale
+.fi
+
+where dx is the pixel shift parameter and
+scale is the scale parameter.
+The output corrected spectrum is then computed as
+
+.nf
+ (2) I'(x_i) = I(x_i) - J'(x_i)
+.fi
+
+where I' is the corrected spectrum and I is the input spectrum. If the
+spectra are dispersion calibrated, possibly with different dispersion
+parameters, then the x values in (2) from the input spectrum are converted
+to matching pixels in the calibration spectrum using the dispersion
+functions of the two spectra.
+
+The purpose of this task is to determine the best values of the
+shift and scale parameters dx and scale. There
+are automatic and interactive methods provided. The automatic
+methods are cross-correlation of the calibration and input spectra
+to find a shift and an iterative search for the in both
+shift and scale that minimizes the RMS of I' in some region.
+The automatic methods are performed first, if selected, followed
+by the interactive, graphical step. The following describes
+the steps in the order in which they occur.
+
+The initial values of the shift and scale are set by the parameters
+\fIshift\fR and \fIscale\fR for the first spectrum. After that the values
+determined for the previous spectrum, those actually applied to correcting
+that spectrum, are used as the initial values for the next spectrum. The
+search steps and sample regions are also initialized by task parameters but
+may be modified during the interactive step and the modified values apply
+to subsequent spectra.
+
+If the \fIxcorr\fR parameter is yes and the \fIlag\fR parameter is
+not zero the calibration spectrum is cross-correlated against the input
+spectrum. Each spectrum is prepared as follows. A large scale continuum
+is fit by a quadratic chebyshev using 5 iterations of sigma clipping with a
+clipping factor of 3 sigma below the fit and 1 sigma above the fit and
+rejecting the deviant points along with one pixel on either side. This
+attempts to eliminate the effects of absorption lines. The continuum fit
+is subtracted from the spectrum and the spectrum is extended and tapered by
+a cosine function of length given by the \fIlag\fR parameter.
+
+The prepared spectra are then cross-correlated by shifting the calibration
+spectrum plus and minus the specified \fIlag\fR amount about the current
+shift value. Only the regions in the input spectrum specified by the
+sample regions parameter are used in the correlation. This produces a
+correlation profile whose peak defines the relative shift between the two
+spectra. The current shift value is updated. This method assumes the
+common telluric features dominate within the specified sample regions. The
+lag size should be roughly the profile widths of the telluric features.
+
+If the \fItweakrms\fR parameter is yes and \fIdshift\fR is greater than
+zero trial corrections at the current shift value and plus and minus one
+shift step with the scale value fixed at its current value are made and the
+RMS in the sample regions computed. If the RMS is smallest at the current
+shift value the shift step is divided in half otherwise the current shift
+value is set to the shift with the lowest RMS. The process is then
+repeated with the new shift and shift step values. This continues until
+either the shift step is less than 0.01 pixels or the shift is more than
+two pixels from the initial shift. In the latter case the final shift is
+reset to the original shift.
+
+The scale factor is then varied if \fIdscale\fR is greater than zero by the
+scale step at a fixed shift in the same way as above to search for a
+smaller RMS in the sample regions. This search terminates when the scale
+step is less than 0.01 or if the scale value has departed by 100% of the
+initial value. In the latter case the scale value is left unchanged.
+
+The search over the shifts and scales is repeated a second time after which
+the tweak algorithm terminates.
+
+After the optional cross-correlation and tweak steps the interactive search
+mode may be entered. This occurs if \fIinteractive\fR = yes. A query is
+asking whether to search interactively. The answers may be "no", "yes",
+"NO", or "YES". The lower case answers apply to the current spectrum and
+the upper case answers apply to all subsequent spectra. This means that if
+an answer of "NO" or "YES" is given then there will be no further queries
+for the remaining input spectra.
+
+If the interactive step is selected a graph of three candidate corrections
+for the input spectrum is displayed. There also may be a graph of the
+calibration or input spectrum shown for reference. Initially the
+calibration spectrum is displayed. The additional graph may be toggled off
+and on and between the input and calibration spectra with the 'c' and 'd'
+keys. The three candidate corrected spectra will be with the current shift
+and scale in the middle and plus or minus one step in either the shift or
+scale. Initially the spectra will be at different scale values.
+Information about the current shift and scale and the step used is given in
+the graph title.
+
+One may toggle between shift steps and scale steps with the 'x' (for shift)
+or 'y' (for scale) keys. The RMS in the title is the RMS within the
+currently defined sample regions. If one of the step values is zero then a
+display of different values of that parameter will not be selected. The
+step size will need to be set with a colon command to search in that
+parameter.
+
+If 'x' is typed when the three spectra are at different shifts then the
+nearest spectrum to the y cursor at the x cursor position will be
+selected. If the central spectrum is selected the step size is divided in
+half otherwise the current shift is changed and the selected spectrum
+becomes the middle spectrum. Three new spectra are then shown. The same
+applies if 'y' is typed when the three spectra are at different scales.
+This allows an interactive search similar to the iterative tweakrms method
+described previously except the user can use whatever criteria is desired
+to search for the best scale and shift.
+
+There are additional keystrokes and colon commands to set or change sample
+regions, reset the current shift, scale, and step sizes, expand the step
+size in the current mode, adjust the offsets between the spectra, and
+get help. The 'w' key and GTOOLS colon commands are available to window
+the graphs. Any changes in the x limits apply to both graphs while y limit
+adjustments apply to the graph pointed to by the cursor.
+
+Two other commands require a short explanation. The 'a' key may
+be used to run the tweakrms algorithm starting from the current
+shift, scale, and steps and the current sample regions. This allows
+one to graphically set or reset the sample regions before doing
+the RMS minimization. The ":smooth" command and associated
+\fIsmooth\fR task parameter allow the corrected spectra to be
+displayed with a boxcar smoothing to better see faint features in
+noise. It is important to realize that the smoothing is only
+done on the displayed spectra. The telluric correction and computed RMS
+are done in the unsmoothed data.
+
+After the interactive step is quit with 'q' or if the interactive
+step is not done then the final output spectrum is computed and
+written to the output image. A brief log output is printed for
+each spectrum.
+.ih
+CURSOR KEYS AND COLON COMMANDS
+.nf
+? - print help
+a - automatic RMS minimization within sample regions
+c - toggle calibration spectrum display
+d - toggle data spectrum display
+e - expand (double) the step for the current selection
+q - quit
+r - redraw the graphs
+s - add or reset sample regions
+w - window commands (see :/help for additional information)
+x - graph and select from corrected shifted candidates
+y - graph and select from corrected scaled candidates
+
+:help - print help
+:shift [value] - print or reset the current shift
+:scale [value] - print or reset the current scale
+:dshift [value] - print or reset the current shift step
+:dscale [value] - print or reset the current scale step
+:offset [value] - print or reset the current offset between spectra
+:sample [value] - print or reset the sample regions
+:smooth [value] - print or reset the smoothing box size
+.fi
+.ih
+EXAMPLES
+1. To interactively search for a best correction with the default
+cross-correlation and tweak steps:
+
+.nf
+ cl> skytweak spec001.ms skyspec001.ms spec005.ms
+.fi
+
+2. To search only for a scale factor:
+
+.nf
+ cl> skytweak spec001.ms skyspec001.ms spec005.ms xcorr- dshift=0.
+.fi
+
+3. To processes a set of spectra non-interactively with the same calibration
+spectrum and to replace the input spectra with the corrected spectra and
+log the processing:
+
+.nf
+ cl> skytweak spec* "" skyspec inter- > log
+.fi
+.ih
+REVISIONS
+.ls SKYTWEAK V2.11
+This task is new in this version.
+.le
+.ih
+SEE ALSO
+telluric
+.endhelp
diff --git a/noao/onedspec/doc/skytweak.key b/noao/onedspec/doc/skytweak.key
new file mode 100644
index 00000000..a694ba36
--- /dev/null
+++ b/noao/onedspec/doc/skytweak.key
@@ -0,0 +1,35 @@
+ SKYTWEAK COMMAND SUMMARY
+
+? - print help
+a - automatic RMS minimization within sample regions
+c - toggle calibration spectrum display
+d - toggle data spectrum display
+e - expand (double) the step for the current selection
+q - quit
+r - redraw the graphs
+s - add or reset sample regions
+w - window commands (see :/help for additional information)
+x - graph and select from corrected shifted candidates
+y - graph and select from corrected scaled candidates
+
+:help - print help
+:shift [value] - print or reset the current shift
+:scale [value] - print or reset the current scale
+:dshift [value] - print or reset the current shift step
+:dscale [value] - print or reset the current scale step
+:offset [value] - print or reset the current offset between spectra
+:sample [value] - print or reset the sample regions
+:smooth [value] - print or reset the smoothing box size
+
+
+The stacked display shows three corrected candidate spectra. The center
+one is for the current shift and scale and the other two are one step
+higher or lower in the shift or scale. The current values and the
+step is shown in the title. Toggle between the shift and scale candidates
+with 'x' or 'y'. Select the best spectrum with the cursor and typing
+'x' or 'y'. Selecting the middle spectrum with 'x' in the shift display
+divides the shift step in half. Selecting one of the other spectra
+changes the current shift. Selecting the middle spectrum with 'y'
+in the scale display divides the scale step in half. Selecting one of
+the other spectra changes the current scale. When 'q' is typed the
+final shift and scale will be that of the middle spectrum.
diff --git a/noao/onedspec/doc/slist.hlp b/noao/onedspec/doc/slist.hlp
new file mode 100644
index 00000000..322914b0
--- /dev/null
+++ b/noao/onedspec/doc/slist.hlp
@@ -0,0 +1,142 @@
+.help slist Mar92 noao.onedspec
+.ih
+NAME
+slist -- List spectral header information
+.ih
+USAGE
+slist images
+.ih
+PARAMETERS
+.ls images
+List of images to be listed.
+.le
+.ls apertures = ""
+List of apertures to be selected from the images for listing. A null
+list selects all apertures. See \fBranges\fR for the syntax of
+this list.
+.le
+.ls long_header = no
+If set to yes, then a multiline listing of the header elements is given.
+If set to no, then a single line per spectrum is given. The contents
+of the listing depend on the format.
+.le
+.ih
+DESCRIPTION
+This task lists header information from apertures in a list of input
+images. There is a short one line per spectrum listing and a more
+extended listing selected by the \fIlong_header\fR parameter.
+
+In both short and long outputs the aperture information consists of
+lines with the following whitespace separated fields: the image line,
+the aperture number, the beam number, the dispersion type, the
+wavelength of the first pixel, the wavelength interval per pixel,
+the number of valid pixels, and the aperture title. The dispersion
+type is an integer with a value of -1 if not dispersion corrected,
+0 if dispersion corrected to a linear wavelength sampling, 1 if
+dispersion corrected to a log wavelength sampling, and 2 if dispersion
+corrected to a nonlinear sampling. The wavelength per pixel is
+an approximation based on the wavelength endpoints divided by the
+number of pixels in the case of a nonlinear dispersion function.
+Also the wavelengths refer to the actual pixels taking any image sections
+into account and so may differ from the coordinate system information in
+the header which is defined for the original physical coordinates.
+The aperture titles may be identical with the image title if individual
+aperture titles are not defined.
+
+In the short output format the image title is given first followed
+by the above described information. This format is compact and
+suitable for easy use in other programs (see the example below).
+The long output format is blocked by image and gives the image name
+and title on the first line, the exposure time, universal time,
+and siderial time on the second line, the right ascention, declination,
+hour angle, and airmass on the third line, and then the individual
+aperture information on the remaining lines. If some of the header
+information is missing a value of INDEF is printed. The keywords used
+are EXPTIME/ITIME/EXPOSURE (in that order) for the exposure time,
+and UT, ST, RA, DEC, HA, and AIRMASS for the remaining values.
+
+ demoobj.ms: Hydra artificial image
+ EXPTIME = 2133.33 UT = 9:10:09.0 ST = 20:09:34.0
+ RA = 1:34:02.00 DEC = 30:37:03.0 HA = INDEF AIRMASS = 2.3
+.ih
+EXAMPLES
+1. List short header for an object and arc from a Hydra multifiber reduction
+for fibers 36 to 39.
+
+.nf
+ cl> slist demoobj.ms,demoarc1.ms ap=36-39
+ demoobj.ms 1 37 0 0 5785.85 6.140271 256 Sky fiber
+ demoobj.ms 2 38 1 0 5785.85 6.140271 256 SS313
+ demoobj.ms 3 39 1 0 5785.85 6.140271 256 SS444
+ demoarc1.ms 1 36 2 0 5785.85 6.140271 256 Arc fiber
+ demoarc1.ms 2 37 0 0 5785.85 6.140271 256 Sky fiber
+ demoarc1.ms 3 38 1 0 5785.85 6.140271 256 SS313
+ demoarc1.ms 4 39 1 0 5785.85 6.140271 256 SS444
+.fi
+
+Note that fiber 37 is the first image line in demoobj.ms and teh second image
+line in demoarc.ms. The dispersion is the same in all fibers by design.
+
+2. List long headers for the two images of example 1 but restricted to
+apertures 38 and 39.
+
+.nf
+ cl> slist demoobj.ms,demoarc1.ms ap=38,39 l+
+ demoobj.ms: Hydra artificial image
+ EXPTIME = 2133.33 UT = 9:10:09.0 ST = 20:09:34.0
+ RA = 1:34:02.00 DEC = 30:37:03.0 HA = INDEF AIRMASS = 2.3
+ 2 38 1 0 5785.85 6.140271 256 SS313
+ 3 39 1 0 5785.85 6.140271 256 SS444
+ demoarc1.ms: Hydra artificial image
+ EXPTIME = 2133.33 UT = 9:10:09.0 ST = 20:09:34.0
+ RA = 1:34:02.00 DEC = 30:37:03.0 HA = INDEF AIRMASS = 2.3
+ 3 38 1 0 5785.85 6.140271 256 SS313
+ 4 39 1 0 5785.85 6.140271 256 SS444
+.fi
+
+The other header parameters are the same because this is artificial
+data using the same template header.
+
+3. Dump the set of image headers on a printer in long format.
+
+.nf
+ cl> slist *.ms.imh l+ | lprint
+.fi
+
+4. The short form of SLIST may be used to get some of the aperture
+information for use in a script. The following simply prints the
+image line corresponding to a specified aperture. In a real application
+something more complex would be done.
+
+.nf
+ procedure example (images, aperture)
+
+ string images {prompt="List of images"}
+ int aperture {prompt="Aperture"}
+
+ begin
+ string temp, image
+ int line
+
+ # Use SLIST to print to a temporary file.
+ temp = mktemp ("example")
+ slist (images, aperture=aperture, long=no, > temp)
+
+ # Scan each line and print the line number.
+ list = temp
+ while (fscan (list, image, line) != EOF)
+ print (image, ": ", line)
+ list = ""
+ delete (temp, verify=no)
+ end
+.fi
+.ih
+REVISIONS
+.ls SLIST V2.10
+This task was revised to be relevant for the current spectral image
+formats. The old version is still available in the IRS/IIDS package.
+.le
+.ih
+SEE ALSO
+imheader, hselect
+.endhelp
diff --git a/noao/onedspec/doc/specplot.hlp b/noao/onedspec/doc/specplot.hlp
new file mode 100644
index 00000000..222d77ff
--- /dev/null
+++ b/noao/onedspec/doc/specplot.hlp
@@ -0,0 +1,387 @@
+.help specplot Jan96 noao.onedspec
+.ih
+NAME
+specplot -- stack and plot multiple spectra
+.ih
+USAGE
+specplot spectra
+.ih
+PARAMETERS
+.ls spectra
+List of spectra to plot. The spectra are assigned index numbers increasing
+from one in the order of the list.
+.le
+.ls apertures = ""
+List of apertures to plot. An empty list selects all apertures.
+An aperture list consists of a comma separated list of aperture numbers or
+hyphen separated range of numbers. A step size may also be specified preceded
+by 'x'. See \fBranges\fR for more.
+.le
+.ls bands = "1"
+List of bands to plot if the image is three dimensional. The list has
+the same syntax as for the apertures.
+.le
+.ls dispaxis = 1, nsum = 1
+Parameters for defining vectors in 2D images. The
+dispersion axis is 1 for line vectors and 2 for column vectors.
+A DISPAXIS parameter in the image header has precedence over the
+\fIdispaxis\fR parameter. These may be changed interactively.
+.le
+.ls autolayout = yes
+Automatically layout the spectra by shifting or scaling to a common mean
+and determining a separation step which does overlaps the spectra
+by the specified fraction? The algorithm uses the following parameters.
+.ls autoscale = yes
+Scale the spectra to a common mean? If no then the spectra are shifted
+to a common mean and if yes they are scaled to a common mean.
+.le
+.ls fraction = 1.
+The separation step which just avoids overlapping the spectra is multiplied
+by this number. Numbers greater than 1 increase the separation while numbers
+less than 1 decrease the separation and provide some amount of overlap.
+.le
+.le
+.ls units = ""
+Dispersion coordinate units. If the spectra have known units, currently
+this is generally Angstroms, the plotted units may be converted
+for plotting to other units as specified by this parameter.
+If this parameter is the null string then the units specified by the
+world coordinate system attribute "units_display" is used. If neither
+is specified than the units of the coordinate system are used.
+The units
+may also be changed interactively. See the units section of the
+\fBonedspec\fR help for a further description and available units.
+.le
+.ls transform = "none" (none|log)
+Transform for the input pixel values. Currently only "log" is implemented.
+If all pixels are negative the spectrum values will be unchanged and if
+some pixels are negative they are mapped to the lowest non-negative value in
+the spectrum. Note that this cannot be changed interactively or applied
+independently for each spectrum. To change the setting one must exit
+the task and execute it with the new value.
+.le
+.ls scale = 1., offset = 0. (value, @file, keyword)
+The scale and offset to apply to each spectrum. The value of the parameter
+may be a constant value applying to all spectra, a file containing the
+values specified as @<file> where <file> is the filename, or an image
+header keyword whose value is to be used.
+.le
+.ls step = 0
+The step separating spectra when not using the autolayout option.
+The value of this parameter depends on the range of the data.
+.le
+.ls ptype = "1"
+Default plotting type for the spectra. A numeric value selects line plots
+while marker type strings select marker plots. The sign of the line type
+number selects histogram style lines when negative or connected pixel
+values when positive. The absolute value selects the line type with 0
+being an invisible line, 1 being a solid line, and higher integers
+different types of lines depending on the capabilities of the graphics
+device. The marker type strings are "point", "box", "plus", "cross",
+"diamond", "hline", "vline", "hebar", "vebar", and "circle".
+The types for individual spectra may be changed interactively.
+.le
+.ls labels = "user"
+Spectrum labels to be used. If the null string or the word "none" is
+given then the spectra are not labeled. The word "imname" labels the
+spectra with the image name, the word "imtitle" labels them wih the
+image title, the word "index" labels them with the index number, and
+the word "user" labels them with user defined labels. The user labels
+may be given in the file specified by the parameter \fIulabels\fR, which
+are matched with the list of spectra, and also added interactively.
+.le
+.ls ulabels = ""
+File containing user labels.
+.le
+.ls xlpos = 1.02, ylpos = 0.0
+The starting position for the spectrum labels in fractions of the
+graph limits. The horizontal (x) position is measured from the left
+edge while the vertical position is measured from the mean value of the
+spectrum. For vertical positions a negative value may be used to label
+below the spectrum. The default is off the right edge of the graph at
+the mean level of the spectrum.
+.le
+.ls sysid = yes
+Include system banner and separation step label? This may be changed
+interactively using ":/sysid".
+.le
+.ls yscale = no
+Draw a Y axis scale? Since stacked plots are relative labeling the Y
+axes may not be useful. This parameter allows adding the Y axis scale
+if desired. The default is to not have a Y axis scale.
+.le
+.ls title = "", xlabel = "", ylabel = ""
+Title, x axis label, and y axis label for graphs. These may be changed
+interactively using ":/title", ":/xlabel", and ":/ylabel".
+.le
+.ls xmin = INDEF, xmax = INDEF, ymin = INDEF, ymax = INDEF
+The default limits for the initial graph. If INDEF then the limit is
+determined from the range of the data (autoscaling). These values can
+be changed with 'w' cursor key or the cursor commands ":/xwindow" and
+":/ywindow".
+.le
+.ls logfile = ""
+Logfile to record the final set of spectra and scale factors displayed.
+.le
+.ls graphics = "stdgraph"
+Output graphics device. One of "stdgraph", "stdplot", "stdvdm",
+@(enviroment variable), or actual device.
+.le
+.ls cursor = ""
+Graphics cursor input. When null the standard cursor is used otherwise
+the specified file is used.
+.le
+.ih
+DESCRIPTION
+\fBSpecplot\fR plots multiple spectra with provisions for scaling them,
+separating them vertically, shifting them horizontally, and labeling them.
+The layout can be defined by an automatic algorithm or explicitly and
+adjusted noninteractively (with some limitations) or interactively. The
+plotting units can be selected and the vertical axis scale can be shown or
+not as desired. This task is used for compressing many spectra to a page
+for review, intercomparison of spectra, classification against standards,
+and final display.
+
+The input list of spectra consists of one, two, or three dimensional images.
+The set of spectra may be restricted to specific apertures using the
+\fIapertures\fR parameter. Note that for true 2D images, such as long slit
+spectra, the aperture number corresponds to the line or column to be plotted
+and the dispersion axis and nsum parameter are determined either from the
+image header or the package parameters. Spectra extracted
+with the \fBapextract\fR package may be three dimensional where the 3rd
+dimension corresponds to related data. The higher dimensional data is
+also plotted though it may be restricted with the \fIbands\fR
+parameter.
+
+Each spectrum has a number of associated parameters which are initially
+assigned default values but which may be changed interactively. First each
+spectrum is assigned an index number. This is generally sequential
+starting from 1. Spectra added interactively are assigned the next higher
+or lower index relative to the spectrum being appended or inserted. The
+index is used for refering to parameters of a particular spectrum and for
+separating the spectra vertically. The spectra are scaled and shifted by
+the equation
+
+ I = value * scale + offset + (index - 1) * step
+
+where "I" is the final plotted value, "value" is the pixel value, "scale"
+is a multiplicative scaling, "offset" is a additive offset, and "step" is
+an additive separation step used to stack spectra vertically.
+
+The default values of the vertical scaling parameters may be set by an
+automatic layout algorithm or with explicit constants (the same for all
+spectra). The automatic mode is selected with the parameter
+\fIautolayout\fR and works as follows. All spectra are scaled or shifted
+to a common mean (depending on the parameter \fIautoscale\fR) relative to
+the lowest indexed spectrum. A step size is then computed to just avoid
+overlapping of the minimum of one spectrum with the maximum of another.
+Note that this may not yield a good layout if the spectra have large
+continuum slopes. Finally, to add some extra space between the spectra or
+to allow some overlap, the minimum step is multiplied by a specified
+overlap factor, \fIfraction\fR.
+
+In nonautomatic mode the user specifies the intensity scale, offset,
+and separation step explicitly with the parameters, \fIscale, offset\fR
+and \fIstep\fR. If the step is zero then spectra will be directly
+overplotted while a positive or negative value will separate the
+spectra either upward or downward with the index 1 spectrum having no
+offset. The scale and offset parameters may be specified as either
+constant values, the name of file containing the values (one per line)
+preceded by the '@' character, or the name of an image header keyword.
+This parameter as well as the scale and offset may be set or
+changed interactively via colon commands and the "offset" may also be
+set using the cursor to shift a spectrum vertically.
+
+In addition to shifting spectra vertically they may also be shifted
+horizontally as a velocity/redshift or a zero point change with either
+cursor or colon commands. The dispersion, inteval per pixel, may be
+modified, either with the 't' key or the "wpc" command, in which case if
+the dispersion is nonlinear the spectra will be linearized.
+
+Each spectrum may have a label associated with it. The label type may
+be the image name, the image title, the index number, or a user defined
+label. The default label type is specified by the parameter
+\fIlabels\fR. For user labels the initial labels may be specified in a
+file. Interactively the label type may be changed using the ":labels"
+command and the user assigned labels may be defined by a colon command
+or by using the cursor to mark the position for the label. The label
+position is given relative to the range of the graph and the mean
+intensity. The default values are set by the parameters \fIxlpos\fR
+and \fIylpos\fR. The positions may be changed interactively for all
+the spectra or individually. The latter may be done using the cursor
+to mark exactly where the label is to go.
+
+Each spectrum has an associated plotting type. The default type which
+applies to all spectra initially is specified by the parameter
+\fIptype\fR. This parameter specifies both whether line mode or
+marker mode is used and the line type, line style, or marker type to use.
+The line
+mode and types are given by a small integers with the style, connected
+pixel centers or histogram style, chosed by the sign of the integer.
+The type of lines produced depend on the capabilities of the terminal. In most
+cases a zero line type is invisible. (This may be used interactively
+to temporarily eliminate a spectrum from a plot instead of deleting the
+spectrum from the list of spectra). A line type of 1 is a solid line
+and additional line types are specified by higher numbers.
+The marker types are given by name as described in the parameter
+section. There is currently no combination of line and marker (such as
+connected points with vertical bars) or histogram type plotting. The
+plotting type may be changed interactively for individual spectra or
+for all spectra using colon commands.
+
+The cursor and colon commands generally apply to the spectrum nearest
+the cursor. This is determined by finding the nearest data point to
+the cursor. For the colon commands the spectrum may also be specified
+explicitly by the index number using an optional suffix "[index]", where
+index is the index number for the spectrum. Also the special index "*"
+may be specified to apply to all spectra.
+
+The operations of adding, deleting, moving, or shifting spectra affect
+the index numbers of the other spectra. When deleting a spectrum the
+index numbers of all spectra with greater index numbers are decreased
+by one resulting in the plotted spectra moving down (positive step).
+When adding a spectrum the index numbers above the inserted spectrum
+are increased by one resulting in the spectra moving up. Moving a
+spectrum to a new index number is equivalent to deleting the spectrum
+and then inserting it at the new index position. Spectra may be
+shifted to insert gaps in the plotted spectra. The specified value is
+added to all spectra above and including the one indicated if the value
+is positive to all spectra below and including the one indicated if the
+value is negative.
+.ih
+CURSOR COMMANDS
+
+The indicated spectrum is the one with a point closest to the cursor position.
+.nf
+
+? - Print help summary
+a - Append a new spectrum following the indicated spectrum
+i - Insert a new spectrum before the indicated spectrum
+d - Delete the indicated spectrum
+e - Insert last deleted spectrum before indicated spectrum
+f - Toggle between world coordinates and logical pixel coordinates
+l - Define the user label at the indicated position
+p - Define the label position at the indicated position
+o - Reorder the spectra to eliminate gaps
+q - Quit
+r - Redraw the plot
+s - Repeatedly shift the indicated spectrum position with the cursor
+ q - Quit shift x - Shift horizontally in velocity
+ s - Shift vertically in scale y - Shift vertically in offset
+ t - Shift horizontally in velocity z - Shift horizontally in velocity
+ and vertically in scale and vertically in offset
+t - Set a wavelength scale using the cursor
+u - Set a wavelength point using the cursor
+v - Set velocity plot with zero point at cursor
+w - Window the plot
+x - Cancel all scales and offsets
+y - Automatically layout the spectra with offsets to common mean
+z - Automatically layout the spectra scaled to common mean
+.fi
+.ih
+COLON COMMANDS
+
+A command without a value generally shows the current value of the
+parameter while with a value it sets the value of the parameter. The show
+commands print to the terminal unless a file is given. For the spectrum
+parameters the index specification, "[index]", is optional. If absent the
+nearest spectrum to the cursor when the command is given is selected except
+for the "units" command which selects all spectra. The index is either a
+number or the character *. The latter applies the command to all the
+spectra.
+
+.nf
+:show <file> Show spectrum parameters (file optional)
+:vshow <file> Show verbose parameters (file optional)
+:step <value> Set or show step
+:fraction <value> Set or show autolayout fraction
+:label <value> Set or show label type
+ (none|imtitle|imname|index|user)
+
+:move[index] <to_index> Move spectrum to new index position
+:shift[index|*] <value> Shift spectra by adding to index
+:w0[index|*] <value> Set or show zero point wavelength
+:wpc[index|*] <value> Set or show wavelength per channel
+:velocity[index|*] <value> Set or show radial velocity (km/s)
+:redshift[index|*] <value> Set or show redshift
+:offset[index|*] <value> Set or show intensity offset
+:scale[index|*] <value> Set or show intensity scale
+:xlpos[index|*] <value> Set or show X label position
+:ylpos[index|*] <value> Set or show Y label position
+:ptype[index|*] <value> Set or show plotting type
+:color[index|*] <value> Set or show color (1-9)
+:ulabel[index|*] <value> Set or show user labels
+:units[index|*] <value> Change coordinate units
+
+:/title <value> Set the title of the graph
+:/xlabel <value> Set the X label of the graph
+:/ylabel <value> Set the Y label of the graph
+:/xwindow <min max> Set the X graph range
+ (use INDEF for autoscaling)
+:/ywindow <min max> Set the X graph range
+ (use INDEF for autoscaling)
+
+
+Examples:
+ w0 Print value of wavelength zero point
+ w0 4010 Set wavelength zero point of spectrum nearest the cursor
+ w0[3] 4010 Set wavelength zero point of spectrum with index 3
+ w0[*] 4010 Set wavelength zero point of all spectra
+.fi
+.ih
+EXAMPLES
+1. To make a nice plot of a set of spectra with the default layout:
+
+ cl> specplot spec*
+
+2. To set the colors or line types for multiple spectra in a batch
+mode application create a cursor file like:
+
+ cl> type cursor.dat
+ :color[1] 2
+ :color[2] 3
+ :color[3] 4
+ r
+ cl> specplot im1,im2,im3 cursor=cursor.dat
+
+Note that the 'r' key is necessary redraw the graph with the changed
+attributes.
+.ih
+REVISIONS
+.ls SPECPLOT V2.11
+The scale and offset parameters may now be a value, a filename, or
+and image header keyword.
+
+The 'f' key was added to toggle between world and logical pixel coordinates.
+.le
+.ls SPECPLOT V2.10.3
+A color parameter was added for graphics terminals supporting color.
+
+The :units command was extended to have an optional spectrum specifier.
+This is primarily intended to plot different (or the same) spectra in
+velocity but with different velocity zeros.
+
+The default task units parameter has been changed to "" to allow picking
+up a "units_display" WCS attribute if defined.
+.le
+.ls SPECPLOT V2.10
+New parameters were added to select apertures and bands, plot
+additional dimensions (for example the additional output from the extras
+option in \fBapextract\fR), suppress the system ID banner, suppress the Y
+axis scale, output a logfile, and specify the plotting units. The \fIptype\fR
+parameter now allows negative numbers to select histogram style lines.
+Interactively, the plotting units may be changed and the 'v' key allows
+setting a velocity scale zero point with the cursor. The new version
+supports the new spectral WCS features including nonlinear dispersion
+functions.
+.le
+.ih
+NOTES
+The automatic layout algorithm is relatively simple and may not
+provide visually satisfactory results in all cases. The fonts and Y axis
+scale capabilities are not as good as might be desired for publication
+quality plots.
+.ih
+SEE ALSO
+bplot, splot, onedspec, gtools, ranges
+.endhelp
diff --git a/noao/onedspec/doc/specshift.hlp b/noao/onedspec/doc/specshift.hlp
new file mode 100644
index 00000000..c72ebd0a
--- /dev/null
+++ b/noao/onedspec/doc/specshift.hlp
@@ -0,0 +1,67 @@
+.help specshift Oct92 noao.onedspec
+.ih
+NAME
+specshift -- Shift dispersion coordinate systems
+.ih
+USAGE
+specshift spectra shift
+.ih
+PARAMETERS
+.ls spectra
+List of spectra to be modified.
+.le
+.ls shift
+Dispersion coordinate shift to be added to the current dispersion coordinate
+system.
+.le
+.ls apertures = ""
+List of apertures to be modified. The null list
+selects all apertures. A list consists of comma separated
+numbers and ranges of numbers. A range is specified by a hyphen. An
+optional step size may be given by using the 'x' followed by a number.
+See \fBxtools.ranges\fR for more information. This parameter is ignored
+for N-dimensional spatial spectra such as long slit and Fabry-Perot.
+.le
+.ls verbose = no
+Print a record of each aperture modified?
+.le
+.ih
+DESCRIPTION
+This task applies a shift to the dispersion coordinate system of selected
+spectra. The image data is not modified as with \fBimshift\fR but rather
+the coordinate system is shifted relative to the data. The spectra to be
+modified are selected by specifying a list of images and apertures. If no
+aperture list is specified then all apertures in the images are modified.
+For N-dimensional spatial spectra such as long slit and Fabry-Perot the
+aperture list is ignored.
+
+The specified shift is added to the existing world coordinates. For linear
+coordinate systems this has the effect of modifying CRVAL1, for linear
+"multispec" coordinate systems this modifies the dispersion coordinate of
+the first physical pixel, and for nonlinear "multispec" coordinate systems
+this modifies the shift coefficient(s).
+
+It is also possible to shift the linearized coordinate systems (but not the
+nonlinear coordinate systems) with \fBsapertures\fR or possibly with
+\fBwcsedit\fR or \fBhedit\fR if the coordinate system is stored with a
+global linear system.
+
+The \fIverbose\fR parameter lists the images, the apertures, the shift, and
+the old and new values for the first physical pixel are printed.
+.ih
+EXAMPLES
+1. To add 1.23 Angstroms to the coordinates of all apertures in the
+image "ngc456.ms":
+
+.nf
+ cl> specshift ngc456.ms 1.23
+.fi
+.ih
+REVISIONS
+.ls SPECSHIFT V2.10.3
+First version.
+.le
+.ih
+SEE ALSO
+sapertures, dopcor, imcoords.wcsreset, hedit, ranges, onedspec.package
+.endhelp
diff --git a/noao/onedspec/doc/specwcs.hlp b/noao/onedspec/doc/specwcs.hlp
new file mode 100644
index 00000000..ed8852e3
--- /dev/null
+++ b/noao/onedspec/doc/specwcs.hlp
@@ -0,0 +1,586 @@
+.help specwcs Mar93 noao.onedspec
+
+.ce
+\fBThe IRAF/NOAO Spectral World Coordinate Systems\fR
+
+
+.sh
+1. Types of Spectral Data
+
+Spectra are stored as one, two, or three dimensional images with one axis
+being the dispersion axis. A pixel value is the flux over
+some interval of wavelength and position. The simplest example of a
+spectrum is a one dimensional image which has pixel values as a
+function of wavelength.
+
+There are two types of higher dimensional spectral image formats. One type
+has spatial axes for the other dimensions and the dispersion axis may be
+along any of the image axes. Typically this type of format is used for
+long slit (two dimensional) and Fabry-Perot (three dimensional) spectra.
+This type of spectra is referred to as \fIspatial\fR spectra and the
+world coordinate system (WCS) format is called \fIndspec\fR.
+The details of the world coordinate systems are discussed later.
+
+The second type of higher dimensional spectral image consists of multiple,
+independent, one dimensional spectra stored in the higher dimensions with
+the first image axis being the dispersion axis; i.e. each line is a
+spectrum. This format allows associating many spectra and related
+parameters in a single data object. This type of spectra is referred to
+as \fImultispec\fR and the there are two coordinate system formats,
+\fIequispec\fR and \fImultispec\fR. The \fIequispec\fR format applies
+to the common case where all spectra have the same linear dispersion
+relation. The \fImultispec\fR format applies to the general case of spectra
+with differing dispersion relations or non-linear dispersion functions.
+These multi-spectrum formats are important since maintaining large numbers
+of spectra as individual one dimensional images is very unwieldy for the
+user and inefficient for the software.
+
+Examples of multispec spectral images are spectra extracted from a
+multi-fiber or multi-aperture spectrograph or orders from an echelle
+spectrum. The second axis is some arbitrary indexing of the spectra,
+called \fIapertures\fR, and the third dimension is used for
+associated quantities. The IRAF \fBapextract\fR package may produce
+multiple spectra from a CCD image in successive image lines with an
+optimally weighted spectrum, a simple aperture sum spectrum, a background
+spectrum, and sigma spectrum as the associated quantities along the third
+dimension of the image.
+
+Many \fBonedspec\fR package tasks which are designed to operate on
+individual one dimensional spectra may operate on spatial spectra by
+summing a number of neighboring spectra across the dispersion axis. This
+eliminates the need to "extract" one dimensional spectra from the natural
+format of this type of data in order to use tasks oriented towards the
+display and analysis of one dimensional spectra. The dispersion axis is
+either given in the image header by the keyword DISPAXIS or the package
+\fIdispaxis\fR parameter. The summing factors across the
+dispersion are specified by the \fInsum\fR package parameter.
+See "help onedspec.package" for information on these parmaeters.
+
+One dimensional spectra, whether from multispec spatial spectra, have
+several associated quantities which may appear in the image header as part
+of the coordinate system description. The primary identification of a
+spectrum is an integer aperture number. This number must be unique within
+a single image. There is also an integer beam number used for various
+purposes such as discriminating object, sky, and arc spectra in
+multi-fiber/multi-aperture data or to identifying the order number in
+echelle data. For spectra summed from spatial spectra the aperture number
+is the central line, column, or band. In 3D images the aperture index
+wraps around the lowest non-dispersion axis. Since most one dimensional
+spectra are derived from an integration over one or more spatial axes, two
+additional aperture parameters record the aperture limits. These limits
+refer to the original pixel limits along the spatial axis. This
+information is primarily for record keeping but in some cases it is used
+for spatial interpolation during dispersion calibration. These values are
+set either by the \fBapextract\fR tasks or when summing neighboring vectors
+in spatial spectra.
+
+An important task to be aware of for manipulating spectra between image
+formats is \fBscopy\fR. This task allows selecting spectra from multispec
+images and grouping them in various ways and also "extracts" apertures from
+long slit and 3D spectra simply and without resort to the more general
+\fBapextract\fR package.
+.sh
+2. World Coordinate Systems
+
+IRAF images have three types of coordinate systems. The pixel array
+coordinates of an image or image section, i.e. the lines and
+columns, are called the \fIlogical\fR coordinates. The logical coordinates of
+individual pixels change as sections of the image are used or extracted.
+Pixel coordinates are tied to the data, i.e. are fixed to features
+in the image, are called \fIphysical\fR coordinates. Initially the logical
+and physical coordinates are the equivalent but differ when image sections
+or other tasks which modify the sampling of the pixels are applied.
+
+The last type of coordinate system is called the \fIworld\fR coordinate
+system. Like the physical coordinates, the world coordinates are tied to
+the features in the image and remain unchanged when sections of the image
+are used or extracted. If a world coordinate system is not defined for an
+image, the physical coordinate system is considered to be the world
+coordinate system. In spectral images the world coordinate system includes
+dispersion coordinates such as wavelengths. In many tasks outside the
+spectroscopy packages, for example the \fBplot\fR, \fBtv\fR and
+\fBimages\fR packages, one may select the type of coordinate system to be
+used. To make plots and get coordinates in dispersion units for spectra
+with these tasks one selects the "world" system. The spectral tasks always
+use world coordinates.
+
+The coordinate systems are defined in the image headers using a set of
+reserved keywords which are set, changed, and updated by various tasks.
+Some of the keywords consist of simple single values following the FITS
+convention. Others, the WAT keywords, encode long strings of information,
+one for each coordinate axis and one applying to all axes, into a set of
+sequential keywords. The values of these keywords must then be pasted
+together to recover the string. The long strings contain multiple pieces
+called WCS \fIattributes\fR. In general the WCS keywords should be left to
+IRAF tasks to modify. However, if one wants modify them directly some
+tasks which may be used are \fBhedit\fR, \fBhfix\fR, \fBwcsedit\fR,
+\fBwcsreset\fR, \fBspecshift\fR, \fBdopcor\fR, and \fBsapertures\fR. The
+first two are useful for the simple keywords, the two "wcs" tasks are
+useful for the linear ndspec and equispec formats, the next two are for the
+common cases of shifting the coordinate zero point or applying a doppler
+correction, and the last one is the one to use for the more complex
+multispec format attributes.
+.sh
+3. Physical Coordinate System
+
+The physical coordinate system is used by the spectral tasks when there is
+no dispersion coordinate information (such as before dispersion
+calibration), to map the physical dispersion axis to the logical dispersion
+axis, and in the multispec world coordinate system dispersion functions
+which are defined in terms of physical coordinates.
+
+The transformation between logical and physical coordinates is defined by
+the header keywords LTVi, LTMi_j (where i and j are axis numbers) through
+the vector equation
+
+.nf
+ l = |m| * p + v
+.fi
+
+where l is a logical coordinate vector, p is a physical
+coordinate vector, v is the origin translation vector specified by
+the LTV keywords and |m| is the scale/rotation matrix
+specified by the LTM keywords. For spectra rotation terms (nondiagonal
+matrix elements) generally do not make sense (in fact many tasks will not
+work if there is a rotation) so the transformations along each axis are
+given by the linear equation
+
+where l is a logical coordinate vector, p is a physical coordinate vector,
+v is the origin translation vector specified by the LTV keywords and |m| is
+the scale/rotation matrix specified by the LTM keywords. For spectra a
+rotation term (nondiagonal matrix elements) generally does not make sense
+(in fact many tasks will not work if there is a rotation) so the
+transformations along each axis are given by the linear equation
+
+.nf
+ li = LTMi_i * pi + LTVi.
+.fi
+
+If all the LTM/LTV keywords are missing they are assumed to have zero
+values except that the diagonal matrix terms, LTMi_i, are assumed to be 1.
+Note that if some of the keywords are present then a missing LTMi_i will
+take the value zero which generally causes an arithmetic or matrix
+inversion error in the IRAF tasks.
+
+The dimensional mapping between logical and physical axes is given by the
+keywords WCSDIM and WAXMAP01. The WCSDIM keyword gives the dimensionality
+of the physical and world coordinate system. There must be coordinate
+information for that many axes in the header (though some may be missing
+and take their default values). If the WCSDIM keyword is missing it is
+assumed to be the same as the logical image dimensionality.
+
+The syntax of the WAXMAP keyword are pairs of integer values,
+one for each physical axis. The first number of each pair indicates which
+current \fIlogical\fR axis corresponds to the original \fIphysical\fR axis
+(in order) or zero if that axis is missing. When the first number is zero
+the second number gives the offset to the element of the original axis
+which is missing. As an example consider a three dimensional image in
+which the second plane is extracted (an IRAF image section of [*,2,*]).
+The keyword would then appear as WAXMAP01 = '1 0 0 1 2 0'. If this keyword
+is missing the mapping is 1:1; i.e. the dimensionality and order of the
+axes are the same.
+
+The dimensional mapping is important because the dispersion axis for
+the nspec spatial spectra as specified by the DISPAXIS keyword or task
+parameter, or the axis definitions for the equispec and or multispec
+formats are always in terms of the original physical axes.
+.sh
+4. Linear Spectral World Coordinate Systems
+
+When there is a linear or logarithmic relation between pixels and
+dispersion coordinates which is the same for all spectra the WCS header
+format is simple and uses the FITS convention (with the CD matrix keywords
+proposed by Hanisch and Wells 1992) for the logical pixel to world
+coordinate transformation. This format applies to one, two, and three
+dimensional data. The higher dimensional data may have either linear
+spatial axes or the equispec format where each one dimensional spectrum
+stored along the lines of the image has the same dispersion.
+
+The FITS image header keywords describing the spectral world coordinates
+are CTYPEi, CRPIXi, CRVALi, and CDi_j where i and j are axis numbers. As
+with the physical coordinate transformation the nondiagonal or rotation
+terms are not expected in the spectral WCS and may cause problems if they
+are not zero. The CTYPEi keywords will have the value LINEAR to identify
+the type of coordinate system. The transformation between dispersion
+coordinate, wi, and logical pixel coordinate, li, along axis i is given by
+
+.nf
+ wi = CRVALi + CDi_i * (li - CRPIXi)
+.fi
+
+If the keywords are missing then the values are assumed to be zero except
+for the diagonal elements of the scale/rotation matrix, the CDi_i, which
+are assumed to be 1. If only some of the keywords are present then any
+missing CDi_i keywords will take the value 0 which will cause IRAF tasks to
+fail with arithmetic or matrix inversion errors. If the CTYPEi keyword is
+missing it is assumed to be "LINEAR".
+
+If the pixel sampling is logarithmic in the dispersion coordinate, as
+required for radial velocity cross-correlations, the WCS coordinate values
+are logarithmic and wi (above) is the logarithm of the dispersion
+coordinate. The spectral tasks (though not other tasks) will recognize
+this case and automatically apply the anti-log. The two types of pixel
+sampling are identified by the value of the keyword DC-FLAG. A value of 0
+defines a linear sampling of the dispersion and a value of 1 defines a
+logarithmic sampling of the dispersion. Thus, in all cases the spectral
+tasks will display and analyze the spectra in the same dispersion units
+regardless of the pixel sampling.
+
+Other keywords which may be present are DISPAXIS for 2 and 3 dimensional
+spatial spectra, and the WCS attributes "system", "wtype", "label", and
+"units". The system attribute will usually have the value "world" for
+spatial spectra and "equispec" for equispec spectra. The wtype attribute
+will have the value "linear". Currently the label will be either "Pixel"
+or "Wavelength" and the units will be "Angstroms" for dispersion corrected
+spectra. In the future there will be more generality in the units
+for dispersion calibrated spectra.
+
+Figure 1 shows the WCS keywords for a two dimensional long slit spectrum.
+The coordinate system is defined to be a generic "world" system and the
+wtype attributes and CTYPE keywords define the axes to be linear. The
+other attributes define a label and unit for the second axis, which is the
+dispersion axis as indicated by the DISPAXIS keyword. The LTM/LTV keywords
+in this example show that a subsection of the original image has been
+extracted with a factor of 2 block averaging along the dispersion axis.
+The dispersion coordinates are given in terms of the \fIlogical\fR pixel
+coordinates by the FITS keywords as defined previously.
+
+.ce
+Figure 1: Long Slit Spectrum
+
+.nf
+ WAT0_001= 'system=world'
+ WAT1_001= 'wtype=linear'
+ WAT2_001= 'wtype=linear label=Wavelength units=Angstroms'
+ WCSDIM = 2
+ DISPAXIS= 2
+ DC-FLAG = 0
+
+ CTYPE1 = 'LINEAR '
+ LTV1 = -10.
+ LTM1_1 = 1.
+ CRPIX1 = -9.
+ CRVAL1 = 19.5743865966797
+ CD1_1 = 1.01503419876099
+
+ CTYPE2 = 'LINEAR '
+ LTV2 = -49.5
+ LTM2_2 = 0.5
+ CRPIX2 = -49.
+ CRVAL2 = 4204.462890625
+ CD2_2 = 12.3337936401367
+.fi
+
+Figure 2 shows the WCS keywords for a three dimensional image where each
+line is an independent spectrum or associated data but where all spectra
+have the same linear dispersion. This type of coordinate system has the
+system name "equispec". The ancillary information about each aperture is
+found in the APNUM keywords. These give the aperture number, beam number,
+and extraction limits. In this example the LTM/LTV keywords have their
+default values; i.e. the logical and physical coordinates are the same.
+
+.ce
+Figure 2: Equispec Spectrum
+
+.nf
+ WAT0_001= 'system=equispec'
+ WAT1_001= 'wtype=linear label=Wavelength units=Angstroms'
+ WAT2_001= 'wtype=linear'
+ WAT3_001= 'wtype=linear'
+ WCSDIM = 3
+ DC-FLAG = 0
+ APNUM1 = '41 3 7.37 13.48'
+ APNUM2 = '15 1 28.04 34.15'
+ APNUM3 = '33 2 43.20 49.32'
+
+ CTYPE1 = 'LINEAR '
+ LTM1_1 = 1.
+ CRPIX1 = 1.
+ CRVAL1 = 4204.463
+ CD1_1 = 6.16689700000001
+
+ CTYPE2 = 'LINEAR '
+ LTM2_2 = 1.
+ CD2_2 = 1.
+
+ CTYPE3 = 'LINEAR '
+ LTM3_3 = 1.
+ CD3_3 = 1.
+.fi
+.sh
+5. Multispec Spectral World Coordinate System
+
+The \fImultispec\fR spectral world coordinate system applies only to one
+dimensional spectra; i.e. there is no analog for the spatial type spectra.
+It is used either when there are multiple 1D spectra with differing
+dispersion functions in a single image or when the dispersion functions are
+nonlinear.
+
+The multispec coordinate system is always two dimensional though there may
+be an independent third axis. The two axes are coupled and they both have
+axis type "multispec". When the image is one dimensional the physical line
+is given by the dimensional reduction keyword WAXMAP. The second, line
+axis, has world coordinates of aperture number. The aperture numbers are
+integer values and need not be in any particular order but do need to be
+unique. This aspect of the WCS is not of particular user interest but
+applications use the inverse world to physical transformation to select a
+spectrum line given a specified aperture.
+
+The dispersion functions are specified by attribute strings with the
+identifier \fIspecN\fR where N is the \fIphysical\fR image line. The
+attribute strings contain a series of numeric fields. The fields are
+indicated symbolically as follows.
+
+.nf
+ specN = ap beam dtype w1 dw nw z aplow aphigh [functions_i]
+.fi
+
+where there are zero or more functions having the following fields,
+
+.nf
+ function_i = wt_i w0_i ftype_i [parameters] [coefficients]
+.fi
+
+The first nine fields in the attribute are common to all the dispersion
+functions. The first field of the WCS attribute is the aperture number,
+the second field is the beam number, and the third field is the dispersion
+type with the same function as DC-FLAG in the \fInspec\fR and
+\fIequispec\fR formats. A value of -1 indicates the coordinates are not
+dispersion coordinates (the spectrum is not dispersion calibrated), a value
+of 0 indicates linear dispersion sampling, a value of 1 indicates
+log-linear dispersion sampling, and a value of 2 indicates a nonlinear
+dispersion.
+
+The next two fields are the dispersion coordinate of the first
+\fIphysical\fR pixel and the average dispersion interval per \fIphysical\fR
+pixel. For linear and log-linear dispersion types the dispersion
+parameters are exact while for the nonlinear dispersion functions they are
+approximate. The next field is the number of valid pixels, hence it is
+possible to have spectra with varying lengths in the same image. In that
+case the image is as big as the biggest spectrum and the number of pixels
+selects the actual data in each image line. The next (seventh) field is a
+doppler factor. This doppler factor is applied to all dispersion
+coordinates by multiplying by 1/(1+z) (assuming wavelength dispersion
+units). Thus a value of 0 is no doppler correction. The last two fields
+are extraction aperture limits as discussed previously.
+
+Following these fields are zero or more function descriptions. For linear
+or log-linear dispersion coordinate systems there are no function fields.
+For the nonlinear dispersion systems the function fields specify a weight,
+a zero point offset, the type of dispersion function, and the parameters
+and coefficients describing it. The function type codes, ftype_i,
+are 1 for a chebyshev polynomial, 2 for a legendre polynomial, 3 for a
+cubic spline, 4 for a linear spline, 5 for a pixel coordinate array, and 6
+for a sampled coordinate array. The number of fields before the next
+function and the number of functions are determined from the parameters of
+the preceding function until the end of the attribute is reached.
+
+The equation below shows how the final wavelength is computed based on
+the nfunc individual dispersion functions W_i(p). Note that this
+is completely general in that different function types may be combined.
+However, in practice when multiple functions are used they are generally of
+the same type and represent a calibration before and after the actual
+object observation with the weights based on the relative time difference
+between the calibration dispersion functions and the object observation.
+
+.nf
+ w = sum from i=1 to nfunc {wt_i * (w0_i + W_i(p)) / (1 + z)}
+.fi
+
+The multispec coordinate systems define a transformation between physical
+pixel, p, and world coordinates, w. Generally there is an intermediate
+coordinate system used. The following equations define these coordinates.
+The first one shows the transformation between logical, l, and physical,
+p, coordinates based on the LTM/LTV keywords. The polynomial functions
+are defined in terms of a normalized coordinate, n, as shown in the
+second equation. The normalized coordinates run between -1 and 1 over the
+range of physical coordinates, pmin and pmax which are
+parameters of the function, upon which the coefficients were defined. The
+spline functions map the physical range into an index over the number of
+evenly divided spline pieces, npieces, which is a parameter of the
+function. This mapping is shown in the third and fourth equations where
+s is the continuous spline coordinate and j is the nearest integer less
+than or equal to s.
+
+.nf
+ p = (l - LTV1) / LTM1_1
+ n = (p - pmiddle) / (prange / 2)
+ = (p - (pmax+pmin)/2) / ((pmax-pmin) / 2)
+ s = (p - pmin) / (pmax - pmin) * npieces
+ j = int(s)
+.fi
+.sh
+5.1 Linear and Log Linear Dispersion Function
+
+The linear and log-linear dispersion functions are described by a
+wavelength at the first \fIphysical\fR pixel and a wavelength increment per
+\fIphysical\fR pixel. A doppler correction may also be applied. The
+equations below show the two forms. Note that the coordinates returned are
+always wavelength even though the pixel sampling and the dispersion
+parameters may be log-linear.
+
+.nf
+ w = (w1 + dw * (p - 1)) / (1 + z)
+ w = 10 ** {(w1 + dw * (p - 1)) / (1 + z)}
+.fi
+
+Figure 3 shows an example from a multispec image with
+independent linear dispersion coordinates. This is a linearized echelle
+spectrum where each order (identified by the beam number) is stored as a
+separate image line.
+
+.ce
+Figure 3: Echelle Spectrum with Linear Dispersion Function
+
+.nf
+ WAT0_001= 'system=multispec'
+ WAT1_001= 'wtype=multispec label=Wavelength units=Angstroms'
+ WAT2_001= 'wtype=multispec spec1 = "1 113 0 4955.44287109375 0.05...
+ WAT2_002= '5 256 0. 23.22 31.27" spec2 = "2 112 0 4999.0810546875...
+ WAT2_003= '58854293 256 0. 46.09 58.44" spec3 = "3 111 0 5043.505...
+ WAT2_004= '928358078002 256 0. 69.28 77.89"
+ WCSDIM = 2
+
+ CTYPE1 = 'MULTISPE'
+ LTM1_1 = 1.
+ CD1_1 = 1.
+
+ CTYPE2 = 'MULTISPE'
+ LTM2_2 = 1.
+ CD2_2 = 1.
+.fi
+.sh
+5.2 Chebyshev Polynomial Dispersion Function
+
+The parameters for the chebyshev polynomial dispersion function are the
+order (number of coefficients) and the normalizing range of physical
+coordinates, pmin and pmax, over which the function is
+defined and which are used to compute n. Following the parameters are
+the order coefficients, ci. The equation below shows how to
+evaluate the function using an iterative definition where x_1 = 1,
+x_2 = n, and x_i = 2 * n * x_{i-1} - x_{i-2}.
+
+The parameters for the chebyshev polynomial dispersion function are the
+order (number of coefficients) and the normalizing range of physical
+coordinates, pmin and pmax, over which the function is defined
+and which are used to compute n. Following the parameters are the
+order coefficients, c_i. The equation below shows how to evaluate the
+function using an iterative definition
+where x_1 = 1, x_2 = n, and x_i = 2 * n * x_{i-1} - x_{i-2}.
+
+.nf
+ W = sum from i=1 to order {c_i * x_i}
+.fi
+.sh
+5.3 Legendre Polynomial Dispersion Function
+
+The parameters for the legendre polynomial dispersion function are the
+order (number of coefficients) and the normalizing range of physical
+coordinates, pmin and pmax, over which the function is defined
+and which are used to compute n. Following the parameters are the
+order coefficients, c_i. The equation below shows how to evaluate the
+function using an iterative definition where x_1 = 1, x_2 = n, and
+x_i = ((2i-3)*n*x_{i-1}-(i-2)*x_{i-2})/(i-1).
+
+.nf
+ W = sum from i=1 to order {c_i * x_i}
+.fi
+
+Figure 4 shows an example from a multispec image with independent nonlinear
+dispersion coordinates. This is again from an echelle spectrum. Note that
+the IRAF \fBechelle\fR package determines a two dimensional dispersion
+function, in this case a bidimensional legendre polynomial, with the
+independent variables being the order number and the extracted pixel
+coordinate. To assign and store this function in the image is simply a
+matter of collapsing the two dimensional dispersion function by fixing the
+order number and combining all the terms with the same order.
+
+.ce
+Figure 4: Echelle Spectrum with Legendre Polynomial Function
+
+.nf
+ WAT0_001= 'system=multispec'
+ WAT1_001= 'wtype=multispec label=Wavelength units=Angstroms'
+ WAT2_001= 'wtype=multispec spec1 = "1 113 2 4955.442888635351 0.05...
+ WAT2_002= '83 256 0. 23.22 31.27 1. 0. 2 4 1. 256. 4963.0163112090...
+ WAT2_003= '976664 -0.3191636898579552 -0.8169352858733255" spec2 =...
+ WAT2_004= '9.081188912082 0.06387049476832223 256 0. 46.09 58.44 1...
+ WAT2_005= '56. 5007.401409453303 8.555959076467951 -0.176732458267...
+ WAT2_006= '09935064388" spec3 = "3 111 2 5043.505764869474 0.07097...
+ WAT2_007= '256 0. 69.28 77.89 1. 0. 2 4 1. 256. 5052.586239197408 ...
+ WAT2_008= '271 -0.03173489817897474 -7.190562320405975E-4"
+ WCSDIM = 2
+
+ CTYPE1 = 'MULTISPE'
+ LTM1_1 = 1.
+ CD1_1 = 1.
+
+ CTYPE2 = 'MULTISPE'
+ LTM2_2 = 1.
+ CD2_2 = 1.
+.fi
+.sh
+5.4 Linear Spline Dispersion Function
+
+The parameters for the linear spline dispersion function are the number of
+spline pieces, npieces, and the range of physical coordinates, pmin
+and pmax, over which the function is defined and which are used to
+compute the spline coordinate s. Following the parameters are the
+npieces+1 coefficients, c_i. The two coefficients used in a linear
+combination are selected based on the spline coordinate, where a and b
+are the fractions of the interval in the spline piece between the spline
+knots, a=(j+1)-s, b=s-j, and x_0=a, and x_1=b.
+
+.nf
+ W = sum from i=0 to 1 {c_(i+j) * x_i}
+.fi
+.sh
+5.5 Cubic Spline Dispersion Function
+
+The parameters for the cubic spline dispersion function are the number of
+spline pieces, npieces, and the range of physical coordinates, pmin
+and pmax, over which the function is defined and which are used
+to compute the spline coordinate s. Following the parameters are the
+npieces+3 coefficients, c_i. The four coefficients used are
+selected based on the spline coordinate. The fractions of the interval
+between the integer spline knots are given by a and b, a=(j+1)-s,
+b=s-j, and x_0 =a sup 3, x_1 =(1+3*a*(1+a*b)),
+x_2 =(1+3*b*(1+a*b)), and x_3 =b**3.
+
+The parameters for the cubic spline dispersion function are the number of
+spline pieces, npieces, and the range of physical coordinates, pmin
+and pmax, over which the function is defined and which are used to
+compute the spline coordinate s. Following the parameters are the
+npieces+3 coefficients, c_i. The four coefficients used are selected
+based on the spline coordinate. The fractions of the interval between the
+integer spline knots are given by a and b, a=(j+1)-s, b=s-j,
+and x_0=a**3, x_1=(1+3*a*(1+a*b)), x_2=(1+3*b*(1+a*b)), and x_3=b**3.
+
+.nf
+ W = sum from i=0 to 3 {c_(i+j) * x_i}
+.fi
+.sh
+5.6 Pixel Array Dispersion Function
+
+The parameters for the pixel array dispersion function consists of just the
+number of coordinates ncoords. Following this are the wavelengths at
+integer physical pixel coordinates starting with 1. To evaluate a
+wavelength at some physical coordinate, not necessarily an integer, a
+linear interpolation is used between the nearest integer physical coordinates
+and the desired physical coordinate where a and b are the usual
+fractional intervals k=int(p), a=(k+1)-p, b=p-k,
+and x_0=a, and x_1=b.
+
+.nf
+ W = sum from i=0 to 1 {c_(i+j) * x_i}
+.fi
+.sh
+5.7 Sampled Array Dispersion Function
+
+The parameters for the sampled array dispersion function consists of
+the number of coordinate pairs, ncoords, and a dummy field.
+Following these are the physical coordinate and wavelength pairs
+which are in increasing order. The nearest physical coordinates to the
+desired physical coordinate are located and a linear interpolation
+is computed between the two sample points.
+.endhelp
diff --git a/noao/onedspec/doc/splot.hlp b/noao/onedspec/doc/splot.hlp
new file mode 100644
index 00000000..a5bc3b96
--- /dev/null
+++ b/noao/onedspec/doc/splot.hlp
@@ -0,0 +1,1118 @@
+.help splot Jul95 noao.onedspec
+.ih
+NAME
+splot -- plot and analyze spectra
+.ih
+USAGE
+splot images [line [band]]
+.ih
+PARAMETERS
+.ls images
+List of images (spectra) to plot. If the image is 2D or 3D the line
+and band parameters are used. Successive images are plotted
+following each 'q' cursor command. One may use an image section
+to select a desired column, line, or band but the full image will
+be in memory and any updates to the spectrum will be part of the
+full image.
+.le
+.ls line, band
+The image line/aperture and band to plot in two or three dimensional
+images. For multiaperture spectra the aperture specified by the line
+parameter is first sought and if not found the specified image line is
+selected. For other two dimensional images, such as long slit spectra, the
+line parameter specifies a line or column. Note that if
+the line and band parameters are specified on the command line it will not
+be possible to change them interactively.
+.le
+.ls units = ""
+Dispersion coordinate units for the plot. If the spectra have known units,
+currently this is generally Angstroms, the units may be converted
+to other units for plotting as specified by this task parameter.
+If this parameter is the null string and the world coordinate system
+attribute "units_display" is defined then that will
+be used. If both this task parameters and "units_display" are not
+given then the spectrum dispersion units will be used.
+The units
+may also be changed interactively. See the units section of the
+\fBpackage\fR help for a further description and available units.
+.le
+.ls options = "auto" [auto,zero,xydraw,histogram,nosysid,wcreset,flip,overplot]
+A list of zero or more, possibly abbreviated, options. The options can
+also be toggled with colon commands. The currently defined options are
+"auto", "zero", "xydraw", "histogram", "nosysid", "wreset", "flip", and
+"overplot". Option "auto" automatically replots the graph whenever changes
+are made. Otherwise the graph is replotted with keystrokes 'c' or 'r'.
+Option "zero" makes the initial minimum y of the graphs occur at zero.
+Otherwise the limits are set automatically from the range of the data or
+the \fIymin\fR parameter. Option "xydraw" changes the 'x' draw key to use
+both x and y cursor values for drawing rather than the nearest pixel value
+for the y value. Option "histogram" plots the spectra in a histogram style
+rather than connecting the pixel centers. Option "nosysid" excludes the
+system banner from the graph title. Option "wreset" resets the graph
+limits to those specified by the \fIxmin, xmax, ymin, ymax\fR parameters
+whenever a new spectrum is plotted. The "flip" option selects that
+initially the spectra be plotted with decreasing wavelengths. The options
+may be queried and changed interactively. The "overplot" options overplots
+all graphs and a screen erase only occurs with the redraw key.
+.le
+.ls xmin = INDEF, xmax = INDEF, ymin = INDEF, ymax = INDEF
+The default limits for the initial graph. If INDEF then the limit is
+determined from the range of the data (autoscaling). These values can
+be changed interactively with 'w' window key options or the cursor commands
+":/xwindow" and ":/ywindow" (see \fBgtools\fR).
+.le
+.ls save_file = "splot.log"
+The file to contain any results generated by the equivalent width or
+deblending functions. Results are added to this file until the file is
+deleted. If the filename is null (""), then no results are saved.
+.le
+.ls graphics = "stdgraph"
+Output graphics device: one of "stdgraph", "stdplot", "stdvdm", or device
+name.
+.le
+.ls cursor = ""
+Graphics cursor input. When null the standard cursor is used otherwise
+the specified file is used.
+.le
+
+The following parameters are used for error estimates in the 'd',
+'k', and 'e' key measurements. See the ERROR ESTIMATES section for a
+discussion of the error estimates.
+.ls nerrsample = 0
+Number of samples for the error computation. A value less than 10 turns
+off the error computation. A value of ~10 does a rough error analysis, a
+value of ~50 does a reasonable error analysis, and a value >100 does a
+detailed error analysis. The larger this value the longer the analysis
+takes.
+.le
+.ls sigma0 = INDEF, invgain = INDEF
+The pixel sigmas are modeled by the formula:
+
+.nf
+ sigma**2 = sigma0**2 + invgain * I
+.fi
+
+where I is the pixel value and "**2" means the square of the quantity. If
+either parameter is specified as INDEF or with a value less than zero then
+no sigma estimates are made and so no error estimates for the measured
+parameters are made.
+.le
+
+The following parameters are for the interactive curve fitting function
+entered with the 't' key. This function is usually used for continuum
+fitting. The values of these parameters are updated during the fitting.
+See \fBicfit\fR for additional details on interactive curve fitting.
+.ls function = "spline3"
+Function to be fit to the spectra. The functions are
+"legendre" (legendre polynomial), "chebyshev" (chebyshev polynomial),
+"spline1" (linear spline), and "spline3" (cubic spline). The functions
+may be abbreviated.
+.le
+.ls order = 1
+The order of the polynomials or the number of spline pieces.
+.le
+.ls low_reject = 2., high_reject = 4.
+Rejection limits below and above the fit in units of the residual sigma.
+Unequal limits are used to reject spectral lines on one side of the continuum
+during continuum fitting.
+.le
+.ls niterate = 10
+Number of rejection iterations.
+.le
+.ls grow = 1.
+When a pixel is rejected, pixels within this distance of the rejected pixel
+are also rejected.
+.le
+.ls markrej = yes
+Mark rejected points? If there are many rejected points it might be
+desired to not mark rejected points.
+.le
+
+The following parameters are used to overplot standard star fluxes with
+the 'y' key. See \fBstandard\fR for more information about these parameters.
+.ls star_name
+Query parameter for the standard star fluxes to be overplotted.
+Unrecognized names or a "?" will print a list of the available stars
+in the specified calibration directory.
+.le
+.ls mag
+The magnitude of the observed star in the band given by the
+\fImagband\fR parameter. If the magnitude is not in the same band as
+the blackbody calibration file then the magnitude may be converted to
+the calibration band provided the "params.dat" file containing relative
+magnitudes between the two bands is in the calibration directory
+.le
+.ls magband
+The standard band name for the input magnitude. This should generally
+be the same band as the blackbody calibration file. If it is
+not the magnitude will be converted to the calibration band.
+.le
+.ls teff
+The effective temperature (deg K) or the spectral type of the star being
+calibrated. If a spectral type is specified a "params.dat" file must exist
+in the calibration directory. The spectral types are specified in the same
+form as in the "params.dat" file. For the standard blackbody calibration
+directory the spectral types are specified as A0I, A0III, or A0V, where A
+can be any letter OBAFGKM, the single digit subclass is between 0 and 9,
+and the luminousity class is one of I, III, or V. If no luminousity class
+is given it defaults to dwarf.
+.le
+.ls caldir = ")_.caldir"
+The standard star calibration directory. The default value redirects the
+value to the parameter of the same name in the package parameters.
+.le
+.ls fnuzero = 3.68e-20
+The absolute flux per unit frequency at a magnitude of zero used to
+to convert the calibration magnitudes to absolute flux.
+.le
+
+The following parameters are used for queries in response to particular
+keystrokes.
+.ls next_image
+In response to 'g' (get next image) this parameter specifies the image.
+.le
+.ls new_image
+In response to 'i' (write current spectrum) this parameter specifies the
+name of a new image to create or existing image to overwrite.
+.le
+.ls overwrite = no
+Overwrite an existing output image? If set to yes it is possible to write
+back into the input spectrum or to some other existing image. Otherwise
+the user is queried again for a new image name.
+.le
+.ls spec2
+When adding, subtracting, multiplying, or dividing by a second spectrum
+('+', '-', '*', '/' keys in the 'f' mode) this parameter is used to get
+the name of the second spectrum.
+.le
+.ls constant
+When adding or multiplying by a constant ('p' or 'm' keys in the 'f' mode)
+the parameter is used to get the constant.
+.le
+.ls wavelength
+This parameter is used to get a dispersion coordinate value during deblending or
+when changing the dispersion coordinates with 'u'.
+.le
+.ls linelist
+During deblending this parameter is used to get a list of line positions,
+peak values, profile types, and widths.
+.le
+.ls wstart, wend, dw
+In response to 'p' (convert to a linear wavelength scale) these parameters
+specify the starting wavelength, ending wavelength, and wavelength per pixel.
+.le
+.ls boxsize
+In response to 's' (smooth) this parameter specifies the box size in pixels
+to be used for the boxcar smooth. The value must be odd. If an even
+value is specified the next larger odd value is actually used.
+.le
+.ih
+DESCRIPTION
+\fBSplot\fR provides an interactive facility to display and analyze
+spectra. See also \fBbplot\fR for a version of this task useful for making
+many plots noninteractively. Each spectrum in the image list is displayed
+successively. To quit the current image and go on to the next the 'q'
+cursor command is used. If an image is two-dimensional, such as with
+multiple aperture or long slit spectra, the aperture or image column/line
+to be displayed is needed. If the image is three-dimensional, such as with
+the extra information produced by \fBapextract\fR, the band is needed.
+These parameters are queried unless specified on the command line. If
+given on the command line it will not be possible to change them
+interactively.
+
+The plots are made on the specfied graphics device which is usually to
+the graphics terminal. The initial plot limits are set with the parameters
+\fIxmin, xmax, ymin\fR, and \fIymax\fR. If a limit is INDEF then that limit
+is determined from the range of the data. The "zero" option may also
+be set in the \fIoptions\fR parameter to set the lower intensity limit
+to zero. Other options that may be set to control the initial plot
+are to exclude the system identification banner, and to select a
+histogram line type instead of connecting the pixel centers.
+The dispersion units used in the plot are set by the \fIunits\fR
+parameter. This allows converting to units other than those in which the
+dispersion coordinates are defined in the spectra.
+
+The \fIoption\fR parameter, mentioned in the previous paragraph, is a
+a list of zero or more options. As previously noted, some of the options
+control the initial appearance of the plots. The "auto" option determines
+how frequently plots are redrawn. For slow terminals or via modems one
+might wish to minimize the redrawing. The default, however, is to redraw
+when changes are made. The "xydraw" parameter is specific to the 'x'
+key.
+
+After the initial graph is made an interactive cursor loop is entered.
+The \fIcursor\fR parameter may be reset to read from a file but generally
+the graphics device cursor is read. The cursor loop takes single
+keystroke commands and typed in commands begun with a colon, called
+colon commands. These commands are described below and a summary of
+the commands may be produced interactively with the '?' key or
+a scrolling help on the status line with the '/' key.
+
+Modifications to the spectra being analyzed may be saved using the 'i' key
+in a new, the current, or other existing spectra. A new image is created
+as a new copy of the current spectrum and so if the current spectrum is
+part of a multiple spectrum image (including a long slit spectrum) the
+other spectra are copied. If other spectra in the same image are then
+modified and saved use the overwrite option to replace then in the new
+output image. If the output spectrum already exists then the
+\fIoverwrite\fR flag must be set to allow modifying the data. This
+includes the case when the output spectrum is the same as the input
+spectrum. The only odd case here is when the input spectrum is one
+dimensional and the output spectrum is two dimensional. In this case the
+user is queried for the line to be written.
+
+The other form of output, apart from that produced on the terminal, are
+measurements of equivalent widths, and other analysis functions. This
+information will be recorded in the \fIsave_file\fR if specified.
+
+The following keystrokes are active in addition to the normal IRAF
+cursor facilities (available with ":.help"):
+
+.ls ?
+Page help information.
+.le
+.ls /
+Cycle through short status line help.
+.le
+.ls <space>
+The space bar prints the cursor position and value of the nearest
+pixel.
+.le
+.ls a
+Expand and autoscale to the data range between two cursor positions.
+See also 'w', and 'z'. Selecting no range, that is the two
+cursor positions the same, produces an autoscale of the whole spectrum.
+.le
+.ls b
+Set the plot base level to zero rather than autoscaling.
+.le
+.ls c
+Clear all windowing and redraw the full current spectrum. This redraws the
+spectrum and cancels any effects of the 'a', 'z', and 'w' keys. The 'r'
+key is used to redraw the spectrum with the current windowing.
+.le
+.ls d
+Mark two continuum points and fit (deblend) multiple line profiles.
+The center, continuum at the center, core intensity, integrated flux,
+equivalent width, FWHMs for each profile are printed and saved
+in the log file. See 'k' for fitting a single profile and
+'-' to subtract the fitted profiles.
+.le
+.ls e
+Measure equivalent width by marking two continuum points around the line
+to be measured. The linear continuum is subtracted and the flux is
+determined by simply summing the pixels with partial pixels at the ends.
+Returned values are the line center, continuum at the region center,
+flux above or below the continuum, and the equivalent width.
+.le
+.ls f
+Enter arithmetic function mode. This mode allows arithmetic functions to be
+applied to the spectrum. The pixel values are modified according to the
+function request and may be saved as a new spectrum with the 'i'
+command. Operations with a second spectrum are done in wavelength
+space and the second spectrum is automatically resampled if necessary.
+If one spectrum is longer than the other, only the smaller number of
+pixels are affected. To exit this mode type 'q'.
+
+The following keystrokes are available in the function mode. Binary
+operations with a constant or a second spectrum produce a query for the
+constant value or spectrum name.
+.ls a
+Absolute value
+.le
+.ls d
+Power of base 10 (inverse log base 10)
+.le
+.ls e
+Power of base e (inverse log base e)
+.le
+.ls i
+Inverse/reciprocal (values equal to zero are set to 0.0 in the inverse)
+.le
+.ls l
+Log base 10 (values less than or equal to 0.0 are set to -0.5)
+.le
+.ls m
+Multiply by a constant (constant is queried)
+.le
+.ls n
+Log base e (values less than or equal to 0.0 are set to -0.5)
+.le
+.ls p
+Add by a constant (constant is queried)
+.le
+.ls q
+Quit Function mode
+.le
+.ls s
+Square root (values less than 0.0 are set to 0.0)
+.le
+.ls +
+Add another spectrum
+.le
+.ls -3 -
+Subtract another spectrum
+.le
+.ls *
+Multiply by another spectrum
+.le
+.ls /
+Divide by another spectrum
+.le
+.le
+.ls g
+Get another spectrum. The current spectrum is replaced by the new spectrum.
+The aperture/line and band are queried is necessary.
+.le
+.ls h
+Measure equivalent widths assuming a gaussian profile with the width
+measured at a specified point. Note that this is not a gaussian fit (see
+'k' to fit a gaussian)! The gaussian profile determined here may be
+subtracted with the '-' key. A second cursor key is requested with one of
+the following values:
+.ls a
+Mark the continuum level at the line center and use the LEFT half width
+at the half flux point.
+.le
+.ls b
+Mark the continuum level at the line center and use the RIGHT half width
+at the half flux point.
+.le
+.ls c
+Mark the continuum level at the line center and use the FULL width
+at the half flux point.
+.le
+.ls l
+Mark a flux level at the line center relative to a normalized continuum
+and use the LEFT width at that flux point.
+.le
+.ls r
+Mark a flux level at the line center relative to a normalized continuum
+and use the RIGHT width at that flux point.
+.le
+.ls k
+Mark a flux level at the line center relative to a normalized continuum
+and use the FULL width at that flux point.
+.le
+.le
+.ls i
+Write the current spectrum out to a new or existing image. The image
+name is queried and overwriting must be confirmed.
+.le
+.ls j
+Set the value of the nearest pixel to the x cursor to the y cursor position.
+.le
+.ls k + (g, l or v)
+Mark two continuum points and fit a single line profile. The second key
+selects the type of profile: g for gaussian, l for lorentzian, and v for
+voigt. Any other second key defaults to gaussian. The center, continuum
+at the center, core intensity, integrated flux, equivalent width, and FWHMs
+are printed and saved in the log file. See 'd' for fitting multiple
+profiles and '-' to subtract the fit.
+.le
+.ls l
+Convert to flux per unit wavelength (f-lambda). The spectrum is assumed
+to be flux calibrated in flux per unit frequency (f-nu). See also 'n'.
+.le
+.ls m
+Compute the mean, RMS, and signal-to-noise over a region marked with two
+x cursor positions.
+.le
+.ls n
+Convert to flux per unit frequency (f-nu). The spectrum is assumed
+to be flux calibrated in flux per unit wavelength (f-lambda). See also 'l'.
+.le
+.ls o
+Set overplot flag. The next plot will overplot the current plot.
+Normally this key is immediately followed by one of 'g', '#', '%', '(', or ')'.
+The ":overplot" colon command and overplot parameter option may be
+used to set overplotting to be permanently on.
+.le
+.ls p
+Define a linear wavelength scale. The user is queried for a starting
+wavelength and an ending wavelength. If either (though not both)
+are specified as INDEF a dispersion is queried for and used to compute
+an endpoint. A wavelength scale set this way will be used for
+other spectra which are not dispersion corrected.
+.le
+.ls q
+Quit and go on to next input spectrum. After the last spectrum exit.
+.le
+.ls r
+Redraw the spectrum with the current windowing. To redraw the full
+spectrum and cancel any windowing use the 'c' key.
+.le
+.ls s
+Smooth via a boxcar. The user is prompted for the box size.
+.le
+.ls t
+Fit a function to the spectrum using the ICFIT mode. Typically
+interactive rejection is used to exclude spectra lines from the fit
+in order to fit a smooth continuum. A second keystroke
+selects what to do with the fit.
+.ls /
+Normalize by the fit. When fitting the continuum this continuum
+normalizes the spectrum.
+.le
+.ls -3 -
+Subtract the fit. When fitting the continuum this continuum subtracts
+the spectrum.
+.le
+.ls f
+Replace the spectrum by the fit.
+.le
+.ls c
+Clean the spectrum by replacing any rejected points by the fit.
+.le
+.ls n
+Do the fitting but leave the spectrum unchanged (a NOP on the spectrum).
+This is useful to play with the spectrum using the capabilities of ICFIT.
+.le
+.ls q
+Quit and don't do any fitting. The spectrum is not modified.
+.le
+.le
+.ls u
+Adjust the user coordinate scale. There are three options, 'd' mark a
+position with the cursor and doppler shift it to a specified value,
+'z' mark a position with the cursor and zeropoint shift it to a specified
+value, or 'l' mark two postions and enter two values to define a linear
+(in wavelength) dispersion scale. The units used for input are those
+currently displayed. A wavelength scale set this way will be used for
+other spectra which are not dispersion corrected.
+.le
+.ls v
+Toggle to a velocity scale using the position of the cursor as the
+velocity origin and back.
+.le
+.ls w
+Window the graph. For further help type '?' to the "window:" prompt or
+see help under \fBgtools\fR. To cancel the windowing use 'a'.
+.le
+.ls x
+"Etch-a-sketch" mode. Straight lines are drawn between successive
+positions of the cursor. Requires 2 cursor settings in x. The nearest pixels
+are used as the endpoints. To draw a line between arbitrary y values first
+use 'j' to adjust the endpoints or set the "xydraw" option.
+.le
+.ls y
+Overplot standard star values from a calibration file.
+.le
+.ls z
+Zoom the graph by a factor of 2 in x.
+.le
+.ls (
+In multiaperture spectra go to the spectrum in the preceding image line.
+If there is only one line go to the spectrum in the preceding band.
+.le
+.ls )
+In multiaperture spectra go to the spectrum in the following image line.
+If there is only one line go to the spectrum in the following band.
+.le
+.ls #
+Get a different line in multiaperture spectra or two dimensional images.
+The aperture/line/column is queried.
+.le
+.ls %
+Get a different band in a three dimensional image.
+.le
+.ls $
+Switch between physical pixel coordinates and world (dispersion) coordinates.
+.le
+.ls -4 -
+Subtract the fits generated by the 'd' (deblend), 'k' (single profile fit),
+and 'h' (gaussian of specified width). The region to be subtracted is
+marked with two cursor positions.
+.le
+.ls -4 ','
+Shift the graph window to the left.
+.le
+.ls .
+Shift the graph window to the right.
+.le
+.ls I
+Force a fatal error interupt to leave the graph. This is used because
+the normal interupt character is ignored in graphics mode.
+.le
+
+.ls :show
+Page the full output of the previous deblend and equivalent width
+measurements.
+.le
+.ls :log
+Enable logging of measurements to the file specified by the parameter
+\fIsave_file\fR. When the program is first entered logging is enabled
+(provided a log file is specified). There is no way to change the file
+name from within the program.
+.le
+.ls :nolog
+Disable logging of measurements.
+.le
+.ls :dispaxis <val>
+Show or change dispersion axis for 2D images.
+.le
+.ls :nsum <val>
+Show or change summing for 2D images.
+.le
+.ls :units <value>
+Change the coordinate units in the plot. See below for more information.
+.le
+.ls :# <comment>
+Add comment to logfile.
+.le
+.ls Labels:
+.ls :label <label> <format>
+Add a label at the cursor position.
+.le
+.ls :mabove <label> <format>
+Add a tick mark and label above the spectrum at the cursor position.
+.le
+.ls :mbelow <label> <format>
+Add a tick mark and label below the spectrum at the cursor position.
+.le
+
+The label must be quoted if it contains blanks. A label beginning
+with % (i.e. %.2f) is treated as a format for the x cursor position.
+The optional format is a gtext string (see help on "cursors").
+The labels are not remembered between redraws.
+.le
+
+.ls :auto [yes|no]
+Enable/disable autodraw option
+.le
+.ls :zero [yes|no]
+Enable/disable zero baseline option
+.le
+.ls :xydraw [yes|no]
+Enable/disable xydraw option
+.le
+.ls :hist [yes|no]
+Enable/disable histogram line type option
+.le
+.ls :nosysid [yes|no]
+Enable/disable system ID option
+.le
+.ls :wreset [yes|no]
+Enable/disable window reset for new spectra option
+.le
+.ls :flip [yes|no]
+Enable/disable the flipped coordinates option
+.le
+.ls :overplot [yes|no]
+Enable/disable the permanent overplot option
+.le
+
+
+.ls :/help
+Get help on GTOOLS options.
+.le
+.ls :.help
+Get help on standard cursor mode options
+.le
+.ih
+PROFILE FITTING AND DEBLENDING
+The single profile ('k') and multiple profile deblending ('d') commands fit
+gaussian, lorentzian, and voigt line profiles with a linear background.
+The single profile fit, 'k' key, is a special case of the multiple profile
+fitting designed to be simple to use. Two cursor positions define the
+region to be fit and a fixed linear continuum. The second key is used to
+select the type of profile to fit with 'g' for gaussian, 'l' for
+lorentzian, and 'v' for voigt. Any other second key will default to a
+gaussian profile. The profile center, peak strength, and width(s) are then
+determined and the results are printed on the status line and in the log
+file. The meaning of these quantities is described later. The fit is also
+overplotted and may be subtracted from the spectrum subsequently with
+the '-' key.
+
+The more complex deblending function, 'd' key, defines the fitting region
+and initial linear continuum in the same way with two cursor positions.
+The continuum may be included in the fitting as an option. The lines to be
+fit are entered with the cursor near the line center ('g' for gaussian, 'l'
+for lorentzian, 'v' for voigt), by typing the wavelengths ('t'), or read
+from a file ('f'). The latter two methods are useful if the wavelengths of
+the lines are known accurately and if fits restricting the absolute or
+relative positions of the lines will be used. The 't' key is
+restricted to gaussian fits only.
+
+The 'f' key asks for a line list file. The format of this file has
+one or more columns. The columns are the wavelength, the peak value
+(relative to the continuum with negative values being absorption),
+the profile type (gaussian, lorentzian, or voigt), and the
+gaussian and/or lorentzian FWHM. End columns may be missing
+or INDEF values may be used to have values be approximated.
+Below are examples of the file line formats
+
+.nf
+ wavelength
+ wavelength peak
+ wavelength peak (gaussian|lorenzian|voigt)
+ wavelength peak gaussian gfwhm
+ wavelength peak lorentzian lfwhm
+ wavelength peak voigt gfwhm
+ wavelength peak voigt gfwhm lfwhm
+
+ 1234.5 <- Wavelength only
+ 1234.5 -100 <- Wavelength and peak
+ 1234.5 INDEF v <- Wavelength and profile type
+ 1234.5 INDEF g 12 <- Wavelength and gaussian FWHM
+.fi
+
+where peak is the peak value, gfwhm is the gaussian FWHM, and lfwhm is
+the lorentzian FWHM. This format is the same as used by \fBfitprofs\fR
+and also by \fBartdata.mk1dspec\fR (except in the latter case the
+peak is normalized to a continuum of 1).
+
+There are four queries made to define the set of parameters to be fit or
+constrained. The positions may be held "fixed" at their input values,
+allowed to shift by a "single" offset from the input values, or "all"
+positions may be fit independently. The widths may be
+constrained to a "single" value or "all" fit independently. The linear
+background may be included in the fit or kept fixed at that input using the
+cursor.
+
+As noted above, sometimes the absolute or relative wavelengths of the lines
+are known a priori and this information may be entered by typing the
+wavelengths explicitly using the 't' option or read from a file using the
+'f' option during marking. In this case one should fix or fit a single
+shift for the position. The latter may be useful if the lines are known
+but there is a measurable doppler shift.
+
+After the fit, the modeled lines are overplotted. The line center,
+flux, equivalent width, and full width half maxima are printed on the
+status line for the first line. The values for the other lines and
+the RMS of the fit may be examined by scrolling the status line
+using the '+', '-', and 'r' keys. To continue enter 'q'.
+
+The fitting may be repeated with different options until exited with 'q'.
+For each line in the blend the line center, continuum intensity at the
+line center, the core intensity above or below the continuum, the
+FWHM for the gaussian and lorentzian parts, the flux above or below the continuum, and the
+equivalent width are recorded in the log file. All these parameters
+except the continuum are based on the fitted analytic profiles.
+Thus, even though the fitted region may not extend into the wings of a line
+the equivalent width measurements include the wings in the fitted profile.
+For direct integration of the flux use the 'e' key.
+
+The fitted model may be subtracted from the data (after exiting the
+deblending function) using the '-' (minus) keystroke to delimit the region
+for which the subtraction is to be performed. This allows you to fit a
+portion of a line which may be contaminated by a blend and then subtract
+away the entire line to examine the remaining components.
+
+The fitting uses an interactive algorithm based on the Levenberg-Marquardt
+method. The iterations attempt to improve the fit by varying the parameters
+along the gradient of improvement in the chi square. This method requires
+that the initial values for the parameters be close enough that the
+gradient leads to the correct solution rather than an incorrect local
+minimum in the chi square. The initial values are determined as follows:
+
+.nf
+ 1. If the lines are input from a data file then those values
+ in the file are used. Missing information is determined
+ as below.
+ 2. The line centers are those specified by the user
+ either by marking with the cursor, entering the wavelenths,
+ for read from a file.
+ 3. The initial widths are obtained by dividing the width of
+ the marked fitting region by the number of lines and then
+ dividing this width by a factor depending on the profile
+ type.
+ 4. The initial peak intensities are the data values at the
+ given line centers with the marked continuum subtracted.
+.fi
+
+Note that each time a new fitting option is specified the initial parameters
+are those from the previous fits.
+Thus the results do depend on the history of previous fits until the
+fitting is exited.
+Within each fit an iteration of parameters is performed as
+described next.
+
+The iteration is more likely to fail if one initially attempts to fit too
+many parameters simultaneously. A constrained approach to the solution
+is obtained by iterating starting with a few parameters and then adding
+more parameters as the solution approaches the true chi square minimum.
+This is done by using the solutions from the more constrained options
+as the starting point for the less constrained options. In particular,
+the positions and a single width are fit first with fixed background.
+Then multiple widths and the background are added.
+
+To conclude, here are some general comments. The most restrictive
+(fixed positions and single width(s)) will give odd results if the initial
+positions are not close to the true centers. The most general
+(simultaneous positions, widths, and background) can also lead to
+incorrect results by using unphysically different widths to make one
+line very narrow and another very broad in an attempt to fit very
+blended lines. The algorithm works well when the lines are not
+severely blended and the shapes of the lines are close to the profile
+type.
+.ih
+CENTROID, FLUX, AND EQUIVALENT WIDTH DETERMINATIONS
+There are currently five techniques in SPLOT to measure equivalent widths
+and other line profile parameters. The simplest (conceptually) is by
+integration of the pixel values between two marked pixels. This is
+invoked with the 'e' keystroke. The user marks the two edges of the line
+at the continuum. The measured line center, contiuum value, line flux, and
+equivalent width are given by:
+
+.nf
+ center = sum (w(i) * (I(i)-C(i))**3/2) / sum ((I(i)-C(i))**3/2)
+ continuum = C(midpoint)
+ flux = sum ((I(i)-C(i)) * (w(i2) - w(i1)) / (i2 - i2)
+ eq. width = sum (1 - I(i)/C(i))
+.fi
+
+where w(i) is the wavelength of pixel i, i1 and i2 are the nearest integer
+pixel limits of the integrated wavelength range, I(i) is the data value of
+pixel i, C(i) is the continuum at pixel (i), and the sum is over the marked
+range of pixels. The continuum is a linear function between the two points
+marked. The factor mulitplying the continuum subtracted pixel values
+in the flux calculation is the wavelength interval per pixel so that
+the flux integration is done in wavelength units. (See the discussion
+at the end of this section concerning flux units).
+
+The most complex method for computing line profile parameters is performed
+by the profile fitting and deblending commands which compute a non-linear
+least-squares fit to the line(s). These are invoked with the 'd' or 'k'
+keystroke. These were described in detail previously.
+
+The fourth and fifth methods, selected with the 'h' key, determine the
+equivalent width from a gaussian profile defined by a constant continuum
+level "cont", a core depth "core", and the width of the line "dw" at some
+intermediate level "Iw".
+
+.nf
+ I(w) = cont + core * exp (-0.5*((w-center)/sigma)**2)
+ sigma = dw / 2 / sqrt (2 * ln (core/Iw))
+ fwhm = 2.355 * sigma
+ flux = core * sigma * sqrt (2*pi)
+ eq. width = abs (flux) / cont
+.fi
+
+where w is wavelength.
+
+For ease of use with a large number of lines only one cursor position is
+used to mark the center of the line and one flux level. Note that both
+the x any y cursor positions are read simultaneously. From the x cursor
+position the line center and core intensity are determined. The region around
+the specified line position is searched for a minimum or maximum and a
+parabola is fit to better define the extremum.
+
+The two methods based on the simple gaussian profile model differ in how
+they use the y cursor position and what part of the line is used. After
+typing 'h' one selects the method and whether to use the left, right, or
+both sides of the line by a second keystroke. The 'l', 'r', and 'k' keys
+require a continuum level of one. The y cursor position defines where the
+width of the line is determined. The 'a', 'b', and 'c' keys use the y
+cursor position to define the continuum and the line width is determined at
+the point half way between the line core and the continuum. In both cases
+the width at the appropriate level is determined by the interception of the
+y level with the data using linear interpolation between pixels. The
+one-sided measurements use the half-width on the appropriate side and
+the two-sided measurements use the full-width.
+
+The adopted gaussian line profile is drawn over the spectrum and the
+horizontal and vertical lines show the measured line width and the depth of
+the line center from the continuum. This model may also be subtracted
+from the spectrum using the '-' key.
+
+The major advantages of these methods are that only a single cursor setting
+(both the x and y positions are used) is required and they are fast. The
+'l', 'r', and 'k' keys give more flexibility in adjusting the width of the
+gaussian line at the expense or requiring that the spectrum be normalized
+to a unit continuum. The 'a', 'b', and 'c' keys allow measurements at any
+continuum level at the expense of only using the half flux level to
+determine the gaussian line width.
+
+All these methods print and record in the log file the line center,
+continuum intensity at the line center, the flux, and the equivalent
+width. For the 'e' key the flux is directly integrated while for the other
+methods the fitted gaussian is integrated. In addition, for the profile
+fitting methods the core intensity above or below the continuum, and the
+FWHMs are also printed. A zero value is record for the gaussian or
+lorentzian width if the value is not determined by profile fit. A brief
+line of data for each measurement is printed on the graphics status line.
+To get the full output and the output from previous measurements use the
+command ":show". This pages the output on the text output which may
+involve erasing the graphics.
+
+The integrated fluxes for all the methods are in the same units as the
+intensities and the integration is done in the same units as the
+plotted scale. It is the user's responsibility to keep track of the flux
+units. As a caution, if the data is in flux per unit frequency, say
+ergs/cm2/sec/hz, and the dispersion in Angstroms then the integrated
+flux will not be in the usual units but will be A-ergs/cm2/sec/hz.
+For flux in wavelength units, ergs/cm2/sec/A and the dispersion scale
+in Angstroms the integrated flux will be correct; i.e. ergs/cm2/sec.
+
+Note that one can compute integrated flux in pixel units by using the '$'
+to plot in pixels. This is appropriate if the pixel values are in
+data numbers or photon counts to get total data number or photons.
+.ih
+ERROR ESTIMATES
+The deblending ('d'), single profile fitting ('k'), and profile integration and
+equivalent width ('e') functions provide error estimates for the measured
+parameters. This requires a model for the pixel sigmas. Currently this
+model is based on a Poisson statistics model of the data. The model
+parameters are a constant gaussian sigma and an "inverse gain" as specified
+by the parameters \fIsigma0\fR and \fIinvgain\fR. These parameters are
+used to compute the pixel value sigma from the following formula:
+
+.nf
+ sigma**2 = sigma0**2 + invgain * I
+.fi
+
+where I is the pixel value and "**2" means the square of the quantity.
+
+If either the constant sigma or the inverse gain are specified as INDEF or
+with values less than zero then no noise model is applied and no error
+estimates are computed. Also if the number of error samples is less than
+10 then no error estimates are computed. Note that for processed spectra
+this noise model will not generally be the same as the detector readout
+noise and gain. These parameters would need to be estimated in some way
+using the statistics of the spectrum. The use of an inverse gain rather
+than a direct gain was choosed to allow a value of zero for this
+parameters. This provides a model with constant uncertainties.
+
+The direct profile integration error estimates are computed by error
+propagation assuming independent pixel sigmas. Also it is assumed that the
+marked linear background has no errors. The error estimates are one sigma
+estimates. They are given in the log output (which may also be view
+without exiting the program using the :show command) below the value to
+which they apply and in parenthesis.
+
+The deblending and profile fit error estimates are computed by Monte-Carlo
+simulation. The model is fit to the data (using the sigmas) and this model
+is used to describe the noise-free spectrum. A number of simulations,
+given by the \fInerrsample\fR parameter, are created in which random
+gaussian noise is added to the noise-free spectrum using the pixel
+sigmas from the noise model. The model fitting is done for each simulation
+and the absolute deviation of each fitted parameter to model parameter is
+recorded. The error estimate for the each parameter is then the absolute
+deviation containing 68.3% of the parameter estimates. This corresponds to
+one sigma if the distribution of parameter estimates is gaussian though
+this method does not assume this.
+
+The Monte-Carlo technique automatically includes all effects of
+parameter correlations and does not depend on any approximations.
+However the computation of the errors does take a significant
+amount of time. The amount of time and the accuracy of the
+error estimates depend on how many simulations are done. A
+small number of samples (of order 10) is fast but gives crude
+estimates. A large number (greater than 100) is slow but gives
+good estimates. A compromise value of 50 is recommended
+for many applications.
+.ih
+UNITS
+The dispersion units capability of \fBsplot\fR allows specifying the
+units with the \fIunits\fR parameter and interactively changing the units
+with the ":units" command. In addition the 'v' key allows plotting in
+velocity units with the zero point velocity defined by the cursor
+position.
+
+The units are specified by strings having a unit type from the list below
+along with the possible preceding modifiers, "inverse", to select the
+inverse of the unit and "log" to select logarithmic units. For example "log
+angstroms" to plot the logarithm of wavelength in Angstroms and "inv
+microns" to plot inverse microns. The various identifiers may be
+abbreviated as words but the syntax is not sophisticated enough to
+recognized standard scientific abbreviations except as noted below.
+
+.nf
+ angstroms - Wavelength in Angstroms
+ nanometers - Wavelength in nanometers
+ millimicrons - Wavelength in millimicrons
+ microns - Wavelength in microns
+ millimeters - Wavelength in millimeters
+ centimeter - Wavelength in centimeters
+ meters - Wavelength in meters
+ hertz - Frequency in hertz (cycles per second)
+ kilohertz - Frequency in kilohertz
+ megahertz - Frequency in megahertz
+ gigahertz - Frequency in gigahertz
+ m/s - Velocity in meters per second
+ km/s - Velocity in kilometers per second
+ ev - Energy in electron volts
+ kev - Energy in kilo electron volts
+ mev - Energy in mega electron volts
+
+ nm - Wavelength in nanometers
+ mm - Wavelength in millimeters
+ cm - Wavelength in centimeters
+ m - Wavelength in meters
+ Hz - Frequency in hertz (cycles per second)
+ KHz - Frequency in kilohertz
+ MHz - Frequency in megahertz
+ GHz - Frequency in gigahertz
+ wn - Wave number (inverse centimeters)
+.fi
+
+The velocity units require a trailing value and unit defining the
+velocity zero point. For example to plot velocity relative to
+a wavelength of 1 micron the unit string would be:
+
+.nf
+ km/s 1 micron
+.fi
+
+Some additional examples of units strings are:
+
+.nf
+ milliang
+ megahertz
+ inv mic
+ log hertz
+ m/s 3 inv mic
+.fi
+.ih
+EXAMPLES
+This task has a very large number of commands and capabilities which
+are interactive and graphical. Therefore it these examples are
+fairly superficial. The user is encouraged to simply experiment with
+the task. To get some help use the '?' or '/' keys.
+
+1. To plot a single spectrum and record any measurements in the file
+'ngc7662':
+
+ cl> splot spectrum save_file=ngc7662
+
+2. To force all plots to display zero as the minimum y value:
+
+ cl> splot spectrum options="auto, zero"
+
+Note that the options auto and zero can be abbreviated to one character.
+
+3. To successively display graphs for a set of spectra with the wavelength
+limits set to 3000 to 6000 angstroms:
+
+ cl> splot spec* xmin=3000 xmax=6000
+
+4. To make batch plots create a file containing the simple cursor command
+
+ 0 0 0 q
+
+or an empty file and then execute one of the following:
+
+.nf
+ cl> splot spec* graphics=stdplot cursor=curfile
+ cl> set stdvdm=splot.mc
+ cl> splot spec* graphics=stdvdm cursor=curfile
+ cl> splot spec* cursor=curfile >G splot.mc
+.fi
+
+The first example sends the plots to the standard plot device specified
+by the environment variable "stdplot". The next example sends the plots
+to the standard virtual display metacode file specified by the
+environment variable "stdvdm". The last example redirects the
+standard graphics to the metacode file splot.mc. To spool the metacode
+file the tasks \fBstdplot\fR and \fBgkimosaic\fR may be used.
+For a large number of plots \fBgkimosaic\fR is prefered since it places
+many plots on one page instead of one plot per page.
+The other GKI tasks in the \fBplot\fR package may be used to examine
+the contents of a metacode file. A simple script call \fBbplot\fR is provided
+which has the default cursor file given above and default device of "stdplot".
+
+5. More complex plots may be produced both interactively using the
+'=' key or the ":.snap" or ":.write" commands or by preparing a script
+of cursor commands.
+.ih
+REVISIONS
+.ls SPLOT V2.11
+The profile fitting and deblending was expanded to include lorentzian
+and voigt profiles. A new parameter controls the number of Monte-Carlo
+samples used in the error estimates.
+
+Added colon commands for labeling.
+.le
+.ls SPLOT V2.10.3
+The 'u' key now allows three ways to adjust the dispersion scale. The
+old method of setting a linear dispersion scale is retained as well
+as adding a doppler and zeropoint adjustment. The coordinates are
+input in the currently displayed units.
+
+If a wavelength scale is set with either 'p' or 'u' then any other
+spectra which are not dispersion corrected will adopt this wavelength
+scale.
+
+The '(' and ')' keys cycle through bands if there is only one spectrum.
+
+A new option, "flip", has been added to the options parameter to select
+that the spectra are plotted in decreasing wavelength.
+
+A new options "overplot" has been added to the options parameters and
+colon commands to permanently set overplotting. This allows quickly
+overplotting many spectra.
+
+This task will now write out the current display units in the "units_display"
+WCS attribute. The default task units have been changed to "" to allow
+picking up the "units_display" units if defined.
+
+The deblending and gaussian fitting code now subsamples the profile by
+a factor of 3 and fits the data pixels to the sum of the three
+subsamples. This accounts for finite sampling of the data.
+
+Error estimates are provided for the deblending ('d'), gaussian fitting
+('k'), and profile integration ('e') results.
+.le
+.ls SPLOT V2.10
+This is a new version with a significant number of changes. In addition to
+the task changes the other general changes to the spectroscopy packages
+also apply. In particular, long slit spectra and spectra with nonlinear
+dispersion functions may be used with this task. The image header or
+package dispaxis and nsum parameters allow automatically extracting spectra
+from 2D image. The task parameters have been modified primarily to obtain
+the desired initial graph without needing to do it interactively. In
+particular, the new band parameter selects the band in 3D images, the units
+parameter selects the dispersion units, and the new histogram, nosysid, and
+xydraw options select histogram line type, whether to include a system ID
+banner, and allow editing a spectrum using different endpoint criteria.
+
+Because nearly every key is used there has been some shuffling,
+consolidating, or elimination of keys. One needs to check the run time '?'
+help or the help to determine the key changes.
+
+Deblending may now use any number of components and simultaneous fitting of
+a linear background. A new simplified version of Gaussian fitting for a
+single line has been added in the 'k' key. The old 'k', 'h', and 'v'
+equivalent width commands are all part of the single 'h' command using a
+second key to select a specific option. The Gaussian line model from these
+modes may now be subtracted from the spectrum in the same way as the
+Gaussian fitting. The one-sided options, in particular, are interesting in
+this regard as a new capability.
+
+The arithmetic functions between two spectra are now done in wavelength
+with resampling to a common dispersion done automatically. The 't' key now
+provides for the full power of the ICFIT package to be used on a spectrum
+for continuum normalization, subtraction, or line and cosmic ray removal.
+The 'x' editing key may now use the nearest pixel values rather than only
+the y cursor position to replace regions by straight line segments. The
+mode is selected by the task option parameter "xydraw".
+
+Control over the graph window (plotting limits) is better integrated so
+that redrawing, zooming, shifting, and the GTOOLS window commands all work
+well together. The new 'c' key resets the window to the full spectrum
+allowing the 'r' redraw key to redraw the current window to clean up
+overplots from the Gaussian fits or spectrum editing.
+
+The dispersion units may now be selected and changed to be from hertz to
+Mev and the log or inverse (for wave numbers) of units taken. As part of
+the units package the 'v' key or colon commands may be used to plot in
+velocity relative to some origin. The $ key now easily toggles between the
+dispersion units (whatever they may be) and pixels coordinates.
+
+Selection of spectra has become more complex with multiaperture and long
+slit spectra. New keys allow selecting apertures, lines, columns, and
+bands as well as quickly scrolling through the lines in multiaperture
+spectra. Overplotting is also more general and consistent with other tasks
+by using the 'o' key to toggle the next plot to be overplotted. Overplots,
+including those of the Gaussian line models, are now done in a different
+line type.
+
+There are new colon commands to change the dispersion axis and summing
+parameters for 2D image, to toggle logging, and also to put comments
+into the log file. All the options may also be set with colon commands.
+.le
+.ih
+SEE ALSO
+bplot, gtools, icfit, standard, package, specplot, graph, implot, fitprofs
+.endhelp
diff --git a/noao/onedspec/doc/standard.hlp b/noao/onedspec/doc/standard.hlp
new file mode 100644
index 00000000..d0c84aef
--- /dev/null
+++ b/noao/onedspec/doc/standard.hlp
@@ -0,0 +1,551 @@
+.help standard Jan00 noao.onedspec
+.ih
+NAME
+standard -- Add standard stars to sensitivity file
+.ih
+USAGE
+standard input [records] output
+.ih
+PARAMETERS
+.ls input
+List of input standard star spectra or root names if using the record number
+extension format. All spectra of the same aperture must be of the same
+standard star. In beam switch mode or when the same star parameter is set
+all spectra must be of the same standard star regardless of aperture number.
+Normally the spectra will not be extinction corrected but if they are
+then the extinction file should also be given and the same extinction
+file should be used with \fBsensfunc\fR.
+.le
+.ls records (imred.irs and imred.iids only)
+List of records or ranges of records to be appended to the input spectra
+names when using record number extension format. The
+syntax of this list is comma separated record numbers or ranges of record
+numbers. A range consists of two numbers separated by a hyphen.
+A null list may be used if no record number extensions are
+desired. This is a positional query parameter only if the record
+format is specified.
+.le
+.ls output
+The name of a text file which will contain the output from \fBstandard\fR.
+Each execution of \fBstandard\fR appends to this file information about the
+standard stars, the calibration bandpasses, and observed counts (see the
+DESCRIPTION section for more details). The output must be explicitly
+deleted by the user if the filename is to be reused.
+.le
+.ls samestar = yes
+Is the same star in all apertures? If set to no then each aperture may
+contain a different standard star. The standard star name is queried
+each time a new aperture is encountered. Note that this occurs only
+once per aperture and multiple spectra with the same aperture number
+must be of the same star. If set to yes the standard star name is only
+queried once. When in beam switch mode this parameter is ignored since
+all apertures must contain the same star.
+.le
+.ls beam_switch = no
+Beam switch the spectra? If yes then a beam switch mode is used for the spectra
+in which successive pairs of object and sky observations from the same aperture
+are sky subtracted. This requires that the object type flag OFLAG be present
+and that the spectra are appropriately ordered. All object observations must be
+of the same standard star and the \fIsamestar\fR parameter is ignored.
+.le
+.ls apertures = ""
+List of apertures to be selected from the input list of spectra. If no list
+is specified then all apertures are selected. The syntax is the same as the
+record number extensions.
+.le
+.ls bandwidth = INDEF, bandsep = INDEF
+Bandpass widths and separations in wavelength units. If INDEF then the
+default bandpasses are those given in the standard star calibration
+file. If values for these parameters are specified then a default set
+of bandpasses of equal width and separation are defined over the range
+of the input spectrum. In both cases the default bandpasses can be
+changed interactively if desired.
+.le
+.ls fnuzero = 3.68e-20
+The absolute flux per unit frequency at an AB magnitude of zero. This is used
+to convert the calibration AB magnitudes to absolute flux by the formula
+
+.nf
+ f_nu = fnuzero * 10. ** (-0.4 * m_AB)
+.fi
+
+The flux units are also determined by this parameter. However, the
+frequency to wavelength interval conversion assumes frequency in hertz.
+The default value is based on a calibration of Vega at 5556 Angstroms of
+3.52e-20 ergs/cm2/s/Hz for an AB magnitude of 0.0336. This default value
+is that used in earlier versions of this task which did not allow the
+user to change this calibration.
+.le
+.ls extinction = <no default>
+Extinction file used to make second order extinction corrections across
+the bandpasses. The default value is redirected to the package
+parameter of the same name. See \fBlcalib\fR for a list of standard
+extinction files. Normally the input spectra will not be extinction
+corrected. But if they are this file will be used to remove the
+extinction and then the same file should be specified in \fBsensfunc\fR.
+Note that one can choose to use a null extinction file in both.
+.le
+.ls caldir = ")_.caldir"
+Calibration directory containing standard star data. The
+default value of ")_.caldir" means to use the package parameter "caldir".
+A list of standard calibration directories may be obtained by listing the
+file "onedstds$README"; for example:
+
+.nf
+ cl> page onedstds$README
+.fi
+
+The user may copy or create their own calibration files and specify the
+directory. The directory "" refers to the current working directory. The
+standard calibration directory for blackbody curves is
+"onedstds$blackbody/".
+.le
+.ls observatory = ")_.observatory"
+Observatory at which the spectra were obtained if not specified in the
+image header by the keyword OBSERVAT. The default is a redirection to look
+in the parameters for the parent package for a value. The observatory may
+be one of the observatories in the observatory database, "observatory" to
+select the observatory defined by the environment variable "observatory" or
+the parameter \fBobservatory.observatory\fR, or "obspars" to select the
+current parameters set in the \fBobservatory\fR task. See help for
+\fBobservatory\fR for additional information.
+.le
+.ls interact = no
+If set to no, then the default wavelength set (either that from the star
+calibration file or the set given by the \fIbandwidth\fR and \fIbandsep\fR
+parameters) is used to select wavelength points along the spectrum where the
+sensitivity is measured. If set to yes, the spectra may be plotted
+and the bandpasses adjusted.
+.le
+.ls graphics = "stdgraph"
+Graphics output device for use with the interactive mode. Normally this is
+the user's graphics terminal.
+.le
+.ls cursor = ""
+Graphics cursor input for use with the interactive mode. When null the
+standard graphics cursor is used otherwise the specified file is used.
+.le
+.ls star_name
+The name of the star observed in the current series of spectra. Calibration
+data for the star must be in the specified calibration directory "caldir".
+This is normally a interactive query parameter and should not be specified on
+the command line unless all spectra are of the same standard star.
+.le
+
+The following three queried parameters apply if the selected calibration
+file is for a blackbody.
+.ls mag
+The magnitude of the observed star in the band given by the
+\fImagband\fR parameter. If the magnitude is not in the same band as
+the blackbody calibration file then the magnitude may be converted to
+the calibration band provided the "params.dat" file containing relative
+magnitudes between the two bands is in the calibration directory
+.le
+.ls magband
+The standard band name for the input magnitude. This should generally
+be the same band as the blackbody calibration file. If it is
+not the magnitude will be converted to the calibration band.
+.le
+.ls teff
+The effective temperature (deg K) or the spectral type of the star being
+calibrated. If a spectral type is specified a "params.dat" file must exist
+in the calibration directory. The spectral types are specified in the same
+form as in the "params.dat" file. For the standard blackbody calibration
+directory the spectral types are specified as A0I, A0III, or A0V, where A
+can be any letter OBAFGKM, the single digit subclass is between 0 and 9,
+and the luminousity class is one of I, III, or V. If no luminousity class
+is given it defaults to dwarf.
+.le
+
+The following two parameters are queried if the image does not contain
+the information.
+.ls airmass, exptime
+If the airmass and exposure time are not in the header nor can they be
+determined from other keywords in the header then these query parameters
+are used to request the airmass and exposure time. The values are updated
+in the image.
+.le
+
+The following parameter is for the task to make queries.
+.ls answer
+Interactive query parameter.
+.le
+.ih
+CURSOR KEYS
+.nf
+? Display help page
+a Add a new band by marking the endpoints
+d Delete band nearest the cursor in wavelength
+r Redraw current plot
+q Quit with current bandpass definitions
+w Window plot (follow with '?' for help)
+I Interrupt task immediately
+
+:show Show current bandpass data
+.fi
+.ih
+DESCRIPTION
+Observations of standard stars are integrated over calibration bandpasses
+and written to an output file along with the associated calibration
+fluxes. The fluxes are obtained from tabulated standard star calibration
+files or a model flux distribution (currently just a blackbody) based on
+the magnitude and spectral type of the star. The output data is used by
+the task \fBsensfunc\fR to determine the detector sensitivity function and
+possibly the extinction. The spectra are required to be dispersion
+corrected. The input spectra may be in either "onedspec" or "echelle"
+format and may have many different observation apertures. The spectra may
+also be beam switched and use the a record number extension format.
+
+The input spectra are specified by a list of names or root names if using
+the record number extension format. In the latter case each name in the
+list has each of the specified record numbers appended. A subset of the
+input spectra may be selected by their aperture numbers using the parameter
+\fIapertures\fR. The spectrum name, aperture number, and title are printed
+to the standard output. The airmass is required but if absent from the image
+header it may be computed from the observation header parameters and the
+latitude task parameter (normally obtained from the \fBobservatory\fR task).
+If the airmass cannot be computed, due to missing keywords, then a
+query is made for the airmass. The airmass is then updated in the header.
+
+The name of the standard star or blackbody curve is obtained by querying
+the user. If the parameter \fIsamestar\fR is yes or beam switch mode is
+selected then all spectra are assumed to be of the same standard star and
+the query is made once. If the parameter is no then a query is made for
+each aperture. This allows each aperture to contain a different standard
+star. Note however that multiple observations with the same aperture
+number must be of the same standard star.
+
+The standard star name is either the name of an actual standard star or of
+a blackbody calibration. The latter generally have a star name consisting
+of just the standard bandpass identifier. If the standard star name is not
+recognized a menu of the available standard stars in the calibration
+directory, the file "standards.men", is printed and then the query is
+repeated. Thus, to get a list you can type ? or help.
+
+The standard star names must map to a file containing tabulated
+calibration data. The calibration filename is formed from the star
+name with blanks, "+", and "-" removed, converted to lower case, and
+the extension ".dat" added. This name is appended to a calibration
+directory, so the directory name must have an appropriate directory
+delimiter such as "$" or "/". Generally one of the system calibration
+directories is used but one may copy and modify or create new
+calibration files in a personal directory. For the current working
+directory the calibration directory is either null or "./".
+
+The calibration files may include comment parameter information consisting
+of the comment character '#', a parameter name, and the parameter value.
+These elements are separated by whitespace. Any other comment where the
+first word does not match one of the allowed parameter names is ignored by
+the program. The parameter names are "type" identifying the type of
+calibration file, "units" identifying wavelength units, "band" identifying
+the band for magnitudes, and "weff" identifying the effective wavelength of
+the band.
+
+There are two types of standard star calibration files as described
+below.
+
+.ls STANDARD STAR CALIBRATION FILES
+This type of file is any file that does not contain the parameter "type"
+with a value of "blackbody". The only other parameter used by this type of
+calibration file is the "units" parameter for the wavelength units. If the
+units are not specified then the wavelengths default to Angstroms. All
+older calibration files will have no parameter information so they are
+interpreted as standard star calibration files with wavelengths in
+Angstroms.
+
+The calibration files consist of lines with wavelengths, calibration
+magnitudes, and bandpass widths. The magnitudes are m_AB defined as
+
+.nf
+ m_AB(star) = -2.5 * log10 (f_nu) - 48.60
+.fi
+
+where f_nu is in erg/cm^2/s/Hz. The m_AB calibration magnitudes
+are converted to absolute flux per unit frequency using the
+parameter \fIfnuzero\fR defined by
+
+.nf
+ f_nu = fnuzero * 10. ** (-0.4 * m_AB)
+.fi
+
+Thus, \fIfnuzero\fR is the flux at m_AB of zero. The flux units are
+determined by this number. The default value was chosen such that Vega
+at 5556 Angstroms has an AB magnitude of 0.0336 and a flux of 3.52e-20
+ergs/cm2/s/Hz. This is the same value that was used by all previous
+versions of this task.
+.le
+
+.ls BLACKBODY CALIBRATION FILES
+This type of file has the comment parameter "type" with a value of
+"blackbody". It must also include the "band" and "weff"
+comment parameters. If no "units" comment parameter is given then
+the default units are Angstroms.
+
+The rest of the file consists of lines with wavelengths, m_AB of a zero
+magnitude star (in that band magnitude system), and the bandpass widths.
+The m_AB are defined as described previously. Normally all the m_AB values
+will be the same though it is possible to adjust them to produce a
+departure from a pure blackbody flux distribution.
+
+The actual m_AB calibration magnitudes for the star are obtained by
+the relation
+
+.nf
+ m_AB(star) = mag + m_AB(m=0) -
+ 2.5 * log10 (B(weff,teff)/B(w,teff))
+.fi
+
+where m is the magnitude of the star in the calibration band, m_AB(m=0) is
+the calibration value in the calibration file representing the magnitude of
+a m=0 star (basically the m_AB of Vega), weff is the effective wavelength
+for the calibration file, and teff is the effective temperature of the
+star. The function B(w,T) is the blackbody function in f_nu that provides
+the shape of the calibration. Note how the normalization is such that at
+weff the last term is zero and m_AB(star) = m + m_AB(m=0).
+
+The m_AB(star) computed using the calibration values and the blackbody
+function are then in the same units and form as for the standard
+star files. The conversion to f_nu and the remaining processing
+proceeds in the same way as for standard star calibration data.
+
+The parameters \Imag\fR and \fIteff\fR are specified by the user for each
+star as described in the section BLACKBODY PARAMETERS. These parameters
+are queried by the task for each star (unless forced to a value on the
+command line).
+.le
+
+The beam switch mode is selected with the \fIbeam_switch\fR parameter.
+This mode requires that all apertures are of the same star, the header
+keyword OFLAG be present to identify object and sky spectra, and that
+the sequence of spectra specified are paired such that if an object
+spectrum is encountered first the next spectrum for that aperture
+(spectra from other apertures may appear in between) is a sky spectrum
+or the reverse. These restrictions are not fundamental but are made so
+that this mode behaves the same as with the previous version of this
+task. The sky spectrum is subtracted from the object spectrum and the
+result is then used in generating the observed intensities in the calibration
+bandpasses.
+
+If the spectra have been extinction corrected (EX-FLAG = 0) the
+extinction correction is removed. The specified extinction file is
+used for this operation and so must be the same as that used when the
+extinction correction was made. The airmass is also required in this step
+and, if needed to compute the airmass, the observatory specified in the
+image or observatory parameter is used. The
+treatment of extinction in this task is subtle. The aim of this task
+is to produce observed integrated instrumental intensities without
+extinction correction. Thus, the extinction correction is removed from
+extinction corrected spectra. However, a correction is made for an
+extinction gradient across the bandpasses. This is done by applying an
+extinction correction, integrating across the bandpass, and then
+correcting the integrated intensity for the extinction at the center of
+the bandpass. An alternative way to look at this is that the integral
+is weighted by the ratio of the extinction correction at each pixel to
+the extinction correction at the center of the bandpass. This
+correction or weighting is why the extinction file and latitude are
+parameters in this task even though for nonextinction corrected spectra
+they appear not to be needed.
+
+The observed instrumental intensities are integrated within a set of
+bandpasses by summing the pixels using partial pixels at the bandpass
+edges. Initial bandpasses are defined in one of two ways. A set of
+evenly spaced bandpasses of constant width covering the range of the
+input spectrum may be specified using the parameters \fIbandwidth\fR
+and \fIbandsep\fR in the same units as the spectrum dispersion. If
+these parameters have the value INDEF then the bandpasses from the
+calibration file which are entirely within the spectrum are selected.
+Generally these bandpasses are the actual measured bandpasses though
+one is free to make calibration files using estimated points. The
+calibration bandpasses are preferable because they have been directly
+measured and they have been placed to avoid troubles with spectral
+lines. However, when the coverage or resolution is such that these
+bandpasses do not allow a good determination of the instrumental
+response the evenly spaced bandpasses may be needed. The calibration
+fluxes are linearly interpolated (or extrapolated) from the calibration
+data points to the defined bandpasses.
+
+Each spectrum adds a line to the output file containing the spectrum image
+name, the sky spectrum image name if beam switching, the aperture or beam
+number, the number of points in the spectrum, the exposure time, airmass,
+wavelength range, and title. If the airmass is not found in the image
+header it is computed using the latitude parameter and observation
+information from the header. If the airmass cannot be computed, due to
+missing keywords, then a query is made for the airmass.
+
+Following the spectrum information, calibration data is added for each
+bandpass. The bandpass wavelength, absolute flux (per Angstrom),
+bandpass width, and observed instrumental intensity in the bandpass are
+added to the output file. As discussed above, the observed intensity
+does not include an extinction term but does apply a small correction
+or weighting for the variation of the extinction across the bandpass.
+
+The setting and editing of the bandpasses may be performed
+interactively if the \fIinteract\fR flag is set. In this case the user
+is queried for each spectrum. The answers to this query may be "no" or
+"yes" to skip editing or edit the bandpasses for this spectrum, "NO" or
+"YES" to skip or not skip editing all spectra of the same aperture with
+no further queries for this aperture, and "NO!" or "YES!" to skip
+editing or edit all spectra with no further queries.
+
+When editing the bandpasses a graph of the spectrum is made with the
+bandpasses plotted at the computed intensity per pixel. The cursor and
+colon commands available are summarized in the section CURSOR KEYS.
+Basically bandpasses may be added or deleted and the current bandpass
+data may be examined. Additional keys allow the usual windowing and
+cursor mode operations. When satisfied with the bandpasses exit with
+'q'. The edited bandpasses for that aperture remain in effect until
+changed again by the user. Thus if there are many spectra from the
+same aperture one may reply with "NO" to queries for the next spectra
+to accept the current bandpasses for all other spectra of the same
+aperture.
+
+BLACKBODY PARAMETERS
+
+When a blackbody calibration is selected (the calibration file selected by
+the \fIstar_name\fR parameter has "# type blackbody") there are two
+quantities needed to scale the blackbody to the observation. These are the
+magnitude of the star in the same band as the observation and the effective
+temperature. The magnitude is used for the flux scaling and the effective
+temperature for the shape of the flux distribution. The values are
+obtained or derived from the user specified parameters \fImag\fR,
+\fImagband\fR, and \fIteff\fR. This section describes how the the
+values are derived from other parameters using the data file "params.dat"
+in the calibration directory.
+
+The effective temperature in degrees Kelvin may be specified directly or it
+may be derived from a spectral type for the star. In the latter case the
+file "params.dat" is searched for the effective temperature. The file
+consists of lines with the first value being the spectral type and the
+second the effective temperature. Other columns are described later. The
+spectral type can be any string without whitespace that matches what is in
+the file. However, the program finds the last spectral type that matches
+the first two characters when there is no complete match. This scheme is
+intended for the case where the spectral types are of the form A0I, A0III,
+or A0V, where A can be any spectral type letter OBAFGKM, the single digit
+subtype is between 0 and 9, and the luminousity class is one of I, III, or
+V. The two character match selects the last spectral type independent of
+the luminosity class. The standard file "onedstds$blackbody/params.dat"
+uses these spectral type identifiers with the dwarf class acting as the
+default.
+
+The magnitude band is specified along with the input magnitude. If the
+band is the same as the calibration band given in the calibration file then
+no further transformation is required. However if the magnitude is
+specified in a different band, a conversion is performed using information
+from the "params.dat" file based on the spectral type of the star.
+
+When an effective temperature is specified rather and a spectral type then
+the nearest tabulated temperature for the spectral types that have "V" as
+the third character is used. For the standard spectral type designations
+this means that when an effective temperature is specified the dwarf
+spectral type is used for the magnitude transformation.
+
+As mentioned previously, the "params.dat" data file has additional columns
+following the spectral type and effective temperature. These columns are
+relative magnitudes in various bands. The standard file has V magnitudes
+of zero so in this case the columns are also the X-V colors (where X is the
+appropriate magnitude). Given the spectral type the relative magnitudes
+for the calibration band, m_1, and the input magnitude band, m_2, are found
+and the calibration magnitude for the star is given by
+
+.nf
+ m_calibration = m_input + m_1 - m_2
+.fi
+
+If one of the magnitudes is missing, given as "INDEF" because the
+transformation is not available for the spectral type, the last spectral
+type matching the first two characters which does specify the two
+magnitudes will be used. For example if there is no information for a
+B3III star for a M-J color then the spectral type B3V might be used.
+
+In order for the program to determine the bands for each column in the data
+file there must be a comment before the data with the column names. It must
+begin with "# Type Teff" and then be followed by the same band identifiers
+used in the blackbody calibration files and as specified by the
+\fImagband\fR parameter. Any amount whitespace (space or tab) is used to
+separate the various fields in the comment and in the fields of the table.
+For example the file might have the comment
+
+.nf
+ # Type Teff V J H K L Lprime M
+.fi
+
+identifying the third column of the file as the V magnitude and the
+ninth file as the M magnitude.
+.ih
+EXAMPLES
+1. To compile observations of three standard stars using a beam
+switched instrument like the IIDS:
+
+.nf
+ cl> standard.recformat=yes
+ cl> standard nite1 1001-1008 std beam_switch+ interact-
+ [nite1.1001][0]: HZ 44 - Night 1
+ [nite1.1004][0]: HZ 44 - Night 1
+ [nite1.1005][0]: HZ 44 - Night 1
+ [nite1.1008][0]: HZ 44 - Night 1
+ Star name in calibration list: hz 44
+ cl> standard nite1 1009-1016 std beam_switch+ interact-
+ ...
+ cl> standard nite1 1017-1024 std beam_switch+ interact-
+ ...
+.fi
+
+This will create a file "std" which will contain sensitivity measurements
+from the beam-switched observations of the three standard stars given.
+Note that \fBstandard\fR is run separately for each standard star.
+
+The spectra will be from the images: nite1.1001, nite.1002 ... nite1.1024,
+and the default calibration file, "onedstds$irscal.dat" will be used.
+
+2. For echelle spectra all apertures, the orders, are of the same star and
+so the samestar parameter is set. Usually the resolution is much higher than
+the calibration data so in order to get sufficient coverage bandpasses must
+be interpolated from the calibration data. Therefore the evenly spaced
+bandpasses are used.
+
+.nf
+ cl> standard.recformat=no
+ cl> standard.samestar=yes
+ cl> standard ech001.ec std bandwidth=10 bandsep=15
+ [ech001.ec][0]: Feige 110
+ Star name in calibration list: feige 110
+ [ech001.ec][0]: Edit bandpasses? (no|yes|NO|YES|NO!|YES!): yes
+ [ech001.ec][1]: Edit bandpasses? (no|yes|NO|YES|NO!|YES!): yes
+ [ech001.ec][2]: Edit bandpasses? (no|yes|NO|YES|NO!|YES!): NO!
+.fi
+
+3. To use a blackbody infrared calibration where the V magnitude of
+the star is known.
+
+.nf
+ cl> standard std1.ms std caldir=onedstds$blackbody/
+ std1.ms(1): Standard Star
+ Star name in calibration list: J
+ Magnitude of star: 10.3
+ Magnitude type (|V|J|H|K|L|Lprime|M|): V
+ Effective temperature or spectral type: B3III
+ WARNING: Effective temperature for B3III not found - using B3V
+ Blackbody: V = 10.30, J = 10.32, Teff = 19000
+ std1[1]: Edit bandpasses? (no|yes|NO|YES|NO!|YES!) (yes):
+.fi
+
+Note the warning message and the confirmation information.
+.ih
+REVISIONS
+.ls STANDARD V2.10.4
+The calibration files can be defined to compute blackbody values.
+.le
+.ls STANDARD V2.10.3
+A query for the airmass and exposure time is now made if the information
+is not in the header and cannot be computed from other header keywords.
+.le
+.ls STANDARD V2.10
+Giving an unrecognized standard star name will page a list of standard
+stars available in the calibration directory and then repeat the
+query.
+.le
+.ih
+SEE ALSO
+observatory, lcalib, sensfunc
+.endhelp
diff --git a/noao/onedspec/doc/sys/1and2dspec.hlp b/noao/onedspec/doc/sys/1and2dspec.hlp
new file mode 100644
index 00000000..01f01763
--- /dev/null
+++ b/noao/onedspec/doc/sys/1and2dspec.hlp
@@ -0,0 +1,66 @@
+.help onedspec (Oct84) "Spectral Reductions"
+.ce
+Relationship Between Onedspec and Twodspec
+.ce
+Discussion
+.ce
+October 24, 1984
+.sp 3
+Two types of interactions between one dimensional and two dimensional
+spectra may be defined:
+
+.ls (1)
+Perform a one dimensional operation on the average or sum of a set
+of lines in a two dimensional image.
+.le
+.ls (2)
+Perform a one dimensional operation successively on a set of lines
+in a two dimensional image.
+.le
+
+The two functions might be combined as:
+
+.ls (3)
+Perform a one dimensional operation on the average or sum of a set
+of lines in a two dimensional image and apply the one dimensional
+result successively on a set of lines in a two dimensional image.
+.le
+
+Examples of this are dispersion solutions and flux calibrations for
+longslit spectra.
+
+ Some choices for implementation are:
+
+.ls (1)
+Use a 2-D to 1-D operator to create a 1-D spectrum by averaging or summing
+lines.
+.le
+.ls (2)
+Use an apply a 1-D arithmetic correction to a 2-D image operator.
+Alternatively, expand a 1-D correction to a 2-D correction.
+.le
+.ls (3)
+Convert the 2-D image to a group of 1-D images and provide the 1-D operators
+with the ability to perform averaging or summation.
+.le
+.ls (4)
+To perform a one dimensional operation successively on
+a set of lines first convert the two dimensional image into a group
+of one dimensional spectra. Perform the 1-D operation on the desired
+elements of the group and then reconstruct the 2-D image from the group
+of 1-D images.
+.le
+.ls (5)
+Built separate operators for 2-D images using the 1-D subroutines.
+.le
+.ls (6)
+Provide the ability in the 1-D operators to perform the desired 2-D
+operations directly.
+.le
+
+ Options (1) and (2) are essentially what is done on the IPPS. Option (5)
+would lessen the amount of development but increase the number of tasks
+to be written. I find option (6) desirable because of its
+increased generality but it would require a
+further definition of the data structures allowed and the syntax.
+.endhelp
diff --git a/noao/onedspec/doc/sys/Headers.hlp b/noao/onedspec/doc/sys/Headers.hlp
new file mode 100644
index 00000000..9bb394b7
--- /dev/null
+++ b/noao/onedspec/doc/sys/Headers.hlp
@@ -0,0 +1,189 @@
+.LP
+.SH
+Image Header Parameters
+.PP
+The ONEDSPEC package uses the extended image header to extract
+information required to direct processing of spectra. If the
+header information were to be ignored, the user would need to
+enter observing parameters to the program at the risk of
+typographical errors, and with the burden of supplying the
+data. For more than a few spectra this is a tedious job,
+and the image header information provides the means to eliminate
+almost all the effort and streamline the processing.
+.PP
+However, this requires that the header information be present,
+correct, and in a recognizable format. To meet the goal of
+providing a functional package in May 1985, the first iteration
+of the header format was to simply adopt the IIDS/IRS headers.
+This allowed for processing of the data which would be first
+used heavily on the system, but would need to be augmented at
+a later date. The header elements may be present in any order,
+but must be in a FITS-like format and have the following names
+and formats for the value fields:
+.sp 1
+.TS
+l c l
+l l l.
+Parameter Value Type Definition
+
+HA SX Hour angle (+ for west, - for east)
+RA SX Right Ascension
+DEC SX Declination
+UT SX Universal time
+ST SX Sidereal time
+AIRMASS R Observing airmass (effective)
+W0 R Wavelength at center of pixel 1
+WPC R Pixel-to-pixel wavelength difference
+NP1 I Index to first pixel containing good data (actually first-1)
+NP2 I Index to last pixel containing good data (last really)
+EXPOSURE I Exposure time in seconds (ITIME is an accepted alias)
+BEAM-NUM I Instrument aperture used for this data (0-49)
+SMODE I Number of apertures in instrument - 1 (IIDS only)
+OFLAG I Object or sky flag (0=sky, 1=object)
+DF-FLAG I Dispersion fit made on this spectrum (I=nr coefs in fit)
+SM-FLAG I Smoothing operation performed on this spectrum (I=box size)
+QF-FLAG I Flat field fit performed on this spectrum (0=yes)
+DC-FLAG I Spectrum has been dispersion corrected (0=linear, 1=logarithmic)
+QD-FLAG I Spectrum has been flat fielded (0=yes)
+EX-FLAG I Spectrum has been extinction corrected (0=yes)
+BS-FLAG I Spectrum is derived from a beam-switch operation (0=yes)
+CA-FLAG I Spectrum has been calibrated to a flux scale (0=yes)
+CO-FLAG I Spectrum has been coincidence corrected (0=yes)
+DF1 I If DF-FLAG is set, then coefficients DF1-DFn (n <= 25) exist
+.TE
+.PP
+The values for the parameters follow the guidelines adopted for
+FITS format tapes. All keywords occupy 8 columns and contain
+trailing blanks. Column 9 is an "=" followed by a space. The value field
+begins in column 11. Comments to the parameter may follow a "/" after
+the value field. The value type code is as follows:
+.RS
+.IP SX
+This is a sexagesimal string of the form '12:34:56 ' where the first
+quote appears in column 11 and the last in column 30.
+.IP R
+This is a floating point ("real") value beginning in column 11 and
+extending to column 30 with leading blanks.
+.IP I
+This is an integer value beginning in column 11 and extending to
+column 30 with leading blanks.
+.RE
+.sp 1
+.PP
+The parameters having FLAG designations all default to -1 to indicate
+that an operation has not been performed.
+The ONEDSPEC subroutines "load_ids_hdr" and "store_keywords" follow
+these rules when reading and writing spectral header fields.
+If not present in a header, load_ids_hdr will assume a value of zero
+except that all flags are set to -1, and the object flag parameter
+defaults to object.
+.PP
+When writing an image, only the above parameters are stored by store_keywords.
+Other header information is lost. This needs to be improved.
+.PP
+Not all programs need all the header elements. The following table
+indicates who needs what. Tasks not listed generally do not require
+any header information. Header elements not listed are not used.
+The task SLIST requires all the elements listed above.
+The task WIDTAPE requires almost all (except NP1 and NP2).
+The headings are abbreviated task names as follows:
+.sp 1
+.nr PS 8
+.ps 8
+.TS
+center;
+l l | l l | l l.
+ADD addsets COE coefs FIT flatfit
+BSW bswitch COM combine REB rebin
+CAL calibrate DIS dispcor SPL splot
+COI coincor FDV flatdiv STA standard
+.TE
+.sp 1
+.TS
+center, tab(/);
+l | l | l | l | l | l | l | l | l | l | l | l | l.
+Key/ADD/BSW/CAL/COI/COE/COM/DIS/FDV/FIT/REB/SPL/STA
+_
+HA// X////////// X/
+RA// X////////// X/
+DEC// X////////// X/
+ST// X////////// X/
+UT// X////////// X/
+AIRMASS// X////////// X/
+W0// X/ X/// X//// X/ X/ X/
+WPC// X/ X/// X//// X/ X/ X/
+NP1/////////// X///
+NP2/////////// X///
+EXPOSURE/ X/ X/// X/ X///// X///
+BEAM-NUM// X/ X//// X/ X/ X// X/ X//
+OFLAG// X////////// X/
+DF-FLAG//// X
+DC-FLAG// X//// X//// X/ X/ X/
+QD-FLAG//////// X/
+EX-FLAG// X/
+BS-FLAG// X/
+CA-FLAG/ X// X//////// X/
+CO-FLAG///// X//
+DFn//// X/
+.TE
+.nr PS 10
+.ps 10
+.bp
+.SH
+Headers From Other Instruments
+.PP
+The header elements listed above are currently created only when reading
+IIDS and IRS data from one of the specific readers: RIDSMTN and RIDSFILE.
+The time-like parameters, (RA, DEC, UT, ST, HA), are created in a
+compatible fashion by RCAMERA and RFITS (when the FITS tape is written
+by the KPNO CCD systems).
+.PP
+For any other header information, the ONEDSPEC package is at a loss
+unless the necessary information is edited into the headers with
+an editing task such as HEDIT. This is not an acceptable long term
+mode of operation, and the following suggestion is one approach to
+the header problem.
+.PP
+A translation table can be created as a text file which outlines
+the mapping of existing header elements to those required by the
+ONEDSPEC package. A mapping line is needed for each parameter
+and may take the form:
+.sp 1
+.RS
+.DC
+1D_param default hdr_param key_start value_start type conversion
+.DE
+.RE
+where the elements of an entry have the following definitions:
+.TS
+center;
+l l.
+1D_param T{The name of the parameter expected by the ONEDSPEC package,
+such as EXPOSURE, OFLAG, BEAM-NUM. T}
+
+default T{A value to be used if no entry is found for this parameter.T}
+
+hdr_param T{The string actually present in the existing image header to be
+associated with the ONEDSPEC parameter. T}
+
+key_start T{The starting column number at which the string starts
+in the header. T}
+
+value_start T{The starting column number at which the string describing the
+value of the parameter starts in the header. T}
+
+type T{The format type of the parameter: integer, real, string, boolean,
+sexagesimal. T}
+
+conversion T{If the format type is string, a further conversion may
+optionally be made to one of the formats listed under type. T}
+.TE
+.sp 1
+.PP
+A translation file can be built for each instrument and its
+peculiar header formats, and the file name associated with a
+package parameter. The two subroutines in ONEDSPEC dealing
+directly with the headers (load_ids_hdr and store_keywords)
+can be modified or replaced to access this file and
+translate the header elements.
+.endhelp
diff --git a/noao/onedspec/doc/sys/Onedspec.hlp b/noao/onedspec/doc/sys/Onedspec.hlp
new file mode 100644
index 00000000..85a3f20e
--- /dev/null
+++ b/noao/onedspec/doc/sys/Onedspec.hlp
@@ -0,0 +1,2219 @@
+.help spbasic
+.sh
+One Dimensional Package - Basic Operators
+
+.sh
+INTRODUCTION
+
+ The IRAF One Dimensional Package is intended to provide the basic
+tools required to reduce, analyze, and display data having a
+single dimension. This primarily refers to spectra, but may have
+applicability to time series photometry, or any other
+source of data which can be considered a simple vector.
+All such data will be referred to as spectra in the following discussion.
+Furthermore, the spectrum vector is assumed to be equally spaced
+along the independent variable (wavelength, channel, frequency,
+wavenumber,...). For the purposes of discussion, the independent
+variable will be referred to as wavelength but may be any of the
+possible physical transformations.
+
+ Spectra are to be stored as 2 dimensional IRAF floating point images
+having a single line
+and are therefore limited to lengths smaller than or equal to the
+largest representable positive integer. For 32 bit machines, this
+is about 2 billion points, so that disk space will likely be the
+operational limit. The precision and dynamic range for each pixel
+will be determined by the local machine.
+The second dimension of the spectrum is spatial, and therefore
+represents a special case of the long slit spectroscopic mode.
+
+ Each spectrum will, by default, be stored as a separate image
+file. Alternatively, an association
+can be declared for a related set of spectra
+through a "data group" mechanism. A data group can be defined to
+contain any number of related spectra so that an operation can
+be specified for the group. For example, one can group a single
+night of IIDS spectra into a group labeled JAN28, and then
+wavelength linearize JAN28. This helps minimize
+the user interaction which would otherwise be repetitive, and
+also reduces the user bookkeeping required.
+
+ Data input to the package is provided through the DATAIO
+package. Tape readers will be provided for FITS, IIDS and IRS mountain
+formats, Text ("card-image"), REDUCER and PDS. The descriptor fields
+included in these formats will be mapped into standard IRAF
+image header fields when possible. Special fields will be
+added to the image header to represent instrument
+related parameters.
+
+ Data output to tape (for visitor take home) will be
+either in FITS or text format.
+
+ A variety of graphics display options will be provided
+for both interactive use and for hardcopy generation.
+Scale expansion and contraction, labeling, multiple spectra
+plots, and axis limit specification are to be included in the
+options.
+
+ Specific reduction scripts will be provided to efficiently
+process raw data from the Kitt Peak instruments IIDS and IRS.
+
+
+.sh
+SCOPE OF SPECIFICATIONS
+
+This paper specifies the command format, parameters, and
+operations for the Basic contents of the One Dimensional
+Spectral Package. The Basic functions are those comprising the
+minimum set to reduce a large variety of spectra.
+More complicated operators and analysis functions
+are described in a companion paper on Intermediate Functions.
+Major projects in spectral analysis will be considered at
+a later date in the Advanced function set.
+
+The primary functions within the Basic operator set are:
+
+.ls 4 Transport
+Primarily magtape readers for the common tape formats. Included
+are FITS, IIDS/IRS, REDUCER, PDS, and Card-image formats.
+Tape writers will be initially limited to FITS and Card-image.
+.le
+.ls 4 Mathematical
+Add, subtract, multiply, divide spectra by spectra or constants.
+Apply functional operators such as log, exp, sqrt, sin, cos.
+Weighted sums and averages of spectra.
+.le
+.ls 4 Reduction operators
+Line identification, dispersion solution, flux calibration,
+coincidence correction, atmospheric extinction correction,
+flat fielding.
+.le
+.ls 4 Plotting
+Terminal package to expand, overplot, annotate plots. Hard
+copy package for printer/plotters.
+.le
+.ls 4 Utilities
+Header examination and modification. List, copy, delete spectra.
+Define, add, delete entries in a data group.
+.le
+.ls 4 Artificial spectra
+Generate ramps, Gaussian and Voigt lines, noise.
+.le
+
+These functions will be considered in detail in the following
+discussion.
+
+.ks
+A summary of the commands is given below:
+
+.nf
+rfits -- Convert FITS data files to IRAF data files
+riids -- Convert IIDS mountain tape format to IRAF data files
+rreducer -- Convert Reducer format tape to IRAF data files
+rpds -- Convert a PDS format tape to IRAF data files
+rtext -- Convert a card-image text file to an IRAF image file
+wfits -- Convert IRAF data files to FITS data format
+wtext -- Convert an IRAF image file to a card-image text file
+.sp 1
+coin_cor -- Correct specified spectra for photon coincidence
+line_list -- Create a new line list, or modify an existing one
+mlinid -- Manually identify line features in a spectrum
+alinid -- Automatically locate spectral features in a spectrum
+disp_sol -- Determine the dispersion relation for a set of spectra
+disp_cor -- Linearize spectra having dispersion relation coefficients
+cr_flat -- Create a flat field spectrum
+flt_field -- Correct spectra for pixel-to-pixel variations
+std_star -- Define the standard stars to be used for solving the
+ extinction and system sensitivity functions
+crext_func -- Create an extinction function from a set of observations
+crsens_func -- Create system sensitivity function
+ext_cor -- Extinction correct specified spectra
+sens_cor -- Correct the specified spectra for system sensitivity
+.fi
+.ju
+.ke
+
+.bp
+.sh
+TRANSPORT - INPUT
+
+Although the primary data input source for the near future
+will be magtape, direct links from other computers will
+be a likely source of input. The IRAF DATAIO package
+treats magtape as simple bit streams so that alternate
+input devices (e.g. disk, ethernet, phone lines) can also
+be accommodated with no programming modifications.
+
+This section describes the different formats to be made
+available in the initial release of the Spectroscopic
+package. Additional formats may be added if needed.
+
+In general, the following information will be copied to
+the standard image header: length of spectrum, title,
+abscissa units, brightness units, reference pixel
+abscissa value and increment, right ascension and declination
+of telescope.
+
+Non-standard header parameters include but are not limited to:
+integration time, UT and LST of the observation, airmass (or
+zenith distance), processing history, and comments.
+
+.sh
+FITS
+.ih
+NAME
+rfits -- Convert FITS data files to IRAF data files
+.ih
+USAGE
+rfits [source, filename, files]
+.ih
+DESCRIPTION
+FITS data is read from the specified source.
+The FITS header may optionally be printed on the standard
+output as either a full listing or a short description. Image data may
+optionally be converted to an IRAF image of specified data type.
+
+Eventually all data from the mountain will be in FITS format,
+with the exception of time-critical data transfer projects
+and special applications. The IRAF FITS reader will
+copy the data to disk for most applications.
+
+.ih
+PARAMETERS
+.ls 4 fits_source
+The FITS data source. If the data source is a disk file or an explicit tape file
+specification of the form mt*[n] where n is a file number then only that file
+is converted. If the general tape device name is given, i.e. mta, mtb800, etc,
+then the files specified by the files parameter will be read from the tape.
+.le
+.ls filename
+The IRAF file which will receive the FITS data if the make_image parameter
+switch set. For tape files specified by the files parameter the filename
+will be used as a prefix and the file number will be appended. Otherwise,
+the file will be named as specified. Thus,
+reading files 1 and 3 from a FITS tape with a filename of data will produce
+the files data1 and data3. It is legal to use a null filename. However,
+converting a source without a file number and with a null filename will cause
+a default file fits to be created.
+.le
+.ls files
+The files to be read from a tape are specified by the files string. The
+string can consist of any sequence of file numbers separated by
+at least one of whitespace, comma, or dash.
+A dash specifies a range of files. For example the string
+
+1 2, 3 - 5,8-6
+
+will convert the files 1 through 8.
+.le
+.ls print_header
+If this switch is set header information is printed on the standard output
+output. (default = yes)
+.le
+.ls short_header
+This switch controls the format of the header information printed when the
+print_header switch is set.
+When the short_header switch is set only the output filename,
+the FITS OBJECT string, and the image dimensions are printed.
+Otherwise, the output filename is followed by the full FITS header.
+(default = yes)
+.le
+.ls bytes_per_record
+The FITS standard record size is 2880 bytes which is the default for this
+parameter. However, non-standard FITS tapes with different record sizes can
+be read by setting the appropriate size.
+.le
+.ls make_image
+This switch determines whether FITS image data is converted to an IRAF image
+file. This switch is set to no to obtain just header information with the
+print_header switch. (default = yes)
+.le
+.ls data_type
+The IRAF image file may be of a different data type than the FITS image data.
+The data type may be specified as s for short, l for long, and r for real.
+The user must beware of truncation problems if an inappropriate data type is
+specified. If the FITS keywords BSCALE and BZERO are found then the image
+data is scaled appropriately. In this case the real data type may be most
+appropriate.
+.le
+.sh
+For spectroscopic applications, the parameter data_type would be
+specified as r for real, and the filename would probably be assigned
+as the "group" name as well. (see section on data groups.)
+
+
+.sh
+IIDS/IRS
+.ih
+NAME
+riids -- Convert IIDS mountain tape format to IRAF data files
+.ih
+USAGE
+riids [source, filename, form, records]
+.ih
+DESCRIPTION
+IIDS/IRS mountain format data is read from the specified source.
+The header may be printed
+on the standard output either in short form, label only, or a long
+form containing telescope and time information, processing flags,
+and wavelength solution values.
+
+Either raw or "mountain reduced" tapes can be specified with the
+parameter form.
+
+The IIDS format is destined for extinction. A FITS format will
+replace the current tape format, but an interim period will exist
+for which this tape reader must exist.
+.ih
+PARAMETERS
+.ls 4 iids_source
+The data source, either magtape or a data stream (e.g. disk file).
+The current IIDS tape format produces tapes having only a single
+file. If the source is a magtape, the general tape specification
+mt*[n], should either have n specified as 1, or [n] should not be present.
+.le
+.ls 4 filename
+The IRAF file which will contain the data if the make_image parameter
+is set. The filename will be used as a prefix and the record number
+will be used as the suffix. Thus reading records 1 through 100 from
+an IIDS tape with a file name of 'blue' will produce 100 files having
+names blue1, blue2, ..., blue100. A null filename will default to 'iids'.
+.le
+.ls 4 form
+This string parameter defines the tape to be either 'new' or 'red'.
+The 'new' designation refers to tapes made after January 1977, and
+'red' refers to mountain reduced tapes. (default = 'red')
+.le
+.ls 4 records
+The records specified by this string parameter will be copied to disk.
+The syntax is identical to that for the files parameter of the FITS reader.
+.le
+.ls 4 print_header
+If this switch is set, header information is printed on the standard
+output. (default = yes)
+.le
+.ls 4 short_header
+If this switch is set, only the filename and label information will be printed
+if the print_header switch is also set. If set to 'no', the long form
+will be printed. (default = yes)
+.le
+.ls 4 make_image
+See definition of this parameter under FITS.
+.le
+
+
+.sh
+REDUCER
+
+REDUCER tapes require several considerations beyond the
+previous simple formats. The spectra actually consist of
+many spectra having lengths of 4096 but slightly different
+spectral sampling. Thus, the reader can create many small
+independent spectra, or interpolate the data onto a common
+spectral scale to create a single large spectrum.
+The latter alternative seems to be more generally useful,
+unless the interpolation process introduces significant errors.
+Probably the initial reader will provide both options.
+
+A second consideration is the 60 bit word length conversion.
+The IRAF images are limited to 32 bit reals on most 32 bit machines.
+Some loss of precision and dynamic range will result while reading REDUCER
+format data.
+
+Also, there may be a considerable number (~100) of non-standard header
+elements. These can be handled in a normal fashion, and tools
+will be provided to extract or modify these elements as needed.
+New elements may be added as well.
+
+.ih
+NAME
+rreducer -- Convert Reducer format tape to IRAF data files
+.ih
+USAGE
+rreducer [source, filename, files]
+.ih
+DESCRIPTION
+REDUCER format data is read from the specified source.
+The header may be printed on the standard output either in short form
+consisting of the 80 character ID field, or a long form containing some
+selection (to be agreed upon) of the many header elements.
+
+Either a single long spectrum requiring interpolation
+to match the spectral characteristics of the first data block, or
+multiple short spectra having individual spectral parameters can
+be specified with the hidden parameter, interp.
+Interpolation is performed via a fifth order polynomial.
+
+Subsets of the spectrum can be selected with the blocks string
+parameter. This specifies which blocks in the file are to be extracted.
+
+.ih
+PARAMETERS
+.ls 4 reducer_source
+The data source, either magnetic tape or a data stream (e.g. disk
+file). See the definition of fits_source above for a description
+of how this parameter interacts with the files parameter.
+.le
+.ls 4 filename
+The filename which will contain the data.
+See the definition of this parameter under FITS.
+If no name is given, the default of 'reducer' will be used.
+.le
+.ls 4 files
+The files to be read from tape are given by the files string. See
+the description of this parameter under FITS.
+.le
+.ls 4 print_header
+If this switch is set header information will be printed on the
+standard output. (default = yes)
+.le
+.ls 4 short_header
+If this switch is set only the filename and the first 60 characters
+of the 80 character ID field will be printed if the print_header
+switch is also set. If set to no, the long form of the header
+will be printed, containing selected elements of the 100 word
+header record. (default = yes)
+.le
+.ls 4 make_image
+See the definition of this parameter under FITS.
+.le
+.ls 4 interp
+If this switch is set, a single long spectrum is produced. If
+set to no, multiple spectra will be generated, one for each
+header-data block. The resulting filenames will have suffixes
+of '.1' , '.2' ... '.n'. For example, if the given filename is
+fts and the tape file is 2, the resulting spectrum will be
+fts2 if interp is set to yes, but will be fts2.1, fts2.2, and
+fts2.3 if there are 3 header-data block sets and interp is set
+to no. (default = yes).
+.le
+.ls 4 blocks
+This string parameter allows selected extraction of the
+specified header-block sets, rather than the entire spectrum.
+Thus subsets of the spectrum may be extracted. The parameter
+specifies the starting block and ending block within a tape file.
+If an end-of-file is found prior to exhaustion of the
+specification, reading is terminated.
+For example, the string '12 19' specifies that the eight sets
+starting with the twelfth block are to be extracted to
+form the spectrum. (default = '1 32767', or all)
+.le
+
+
+.sh
+PDS
+
+Tapes from the new PDS 11/23 system will be either FITS or
+old format PDS 9 track tapes. This reader will accept the
+old format tapes which are based on the PDP 8 character set
+and either 10 or 12 bit format.
+
+.ih
+NAME
+rpds -- Convert a PDS format tape to IRAF data files
+.ih
+USAGE
+rpds [source, filename, files]
+.ih
+DESCRIPTION
+PDS format data is read from the specified source. The header
+may be printed on the standard output either in short form
+consisting of the 40 character ID field, filename, and size,
+or in long form including raster parameters and origin.
+
+Because PDS data is limited to no more than 12 bit data, the output image
+will be short integers if the number of lines ("scans") implies
+two dimensional data. If one dimensional data is implied, the
+output image will be converted to reals.
+.ih
+PARAMETERS
+.ls 4 pds_source
+The data source, either magtape or a data stream. See the definition
+of fits_source above for a description of how this parameter interacts
+with the files parameter.
+.le
+.ls 4 filename
+If no filename is given, the default of 'pds' will be used.
+.le
+.ls 4 files
+See the definition of this parameter under FITS.
+.le
+.ls 4 print_header
+If this switch is set, header information will be printed on the
+standard output. (default = yes).
+.le
+.ls 4 short_header
+If this switch is set, only the filename, size, and the 40 character ID
+field will be printed if the print_header switch is also set.
+If set to no, the long form of the header will be printed
+containing the full information block (delta X, delta Y, scan type,
+speed, origin, corner, travel). (default = yes)
+.le
+.ls 4 make_image
+See the definition of this parameter under FITS. (default = yes)
+.le
+.ls 4 data_type
+Specifies the IRAF image file output data type. Normally one
+dimensional PDS data (NSCANS=1) will be stored as real and
+two dimensional PDS data (NSCANS>1) will be stored as short.
+The data type may be specified as s (short), l (long), or r
+(real).
+.le
+
+
+.sh
+TEXT (Read Card-Image)
+
+Card-image tapes are probably the most portable form of data transport.
+Unlike FITS, there is no standard for internally documenting the
+contents of the text file. Header information is essentially
+lost. This makes card-image data transfer a relatively unattractive
+format.
+
+
+.ih
+NAME
+rtext -- Convert a card-image text file to an IRAF image file.
+.ih
+USAGE
+rtext [source, filename, files, ncols, nlines, label]
+.ih
+DESCRIPTION
+The card-image text file specified by the source parameter is
+converted to an IRAF image file. The file is read in a free form
+mode (values separated by spaces) converting data along lines (1-ncols) first.
+No header information is stored except for the image size and
+the label.
+
+If additional header information is to be stored, the standard
+image header utility must be used.
+
+Pixel values exactly equal to some constant will be assumed to be blanks
+if the blank switch is set to yes. The flag value for blanks can be
+set with the blank_value parameter.
+
+.ih
+PARAMETERS
+.ls 4 text_source
+The input data source. See the definition of this parameter under FITS.
+.le
+.ls 4 filename
+The IRAF file which will contain the image data if the make_image
+switch is set. If no filename is given, the default of 'text'
+will be used.
+.le
+.ls 4 files
+See the definition of this parameter under FITS.
+.le
+.ls 4 ncols
+The number of columns of data which describe the image extent.
+.le
+.ls 4 nlines
+The number of lines (or 'rows') of data which describe the image extent.
+For one dimensional spectra, this parameter will be 1.
+.le
+.ls 4 label
+This string parameter becomes the image identification label.
+Up to 80 characters may be stored.
+.le
+.ls 4 print_header
+If this switch is set, header information consisting of the filename,
+image label, and image size will be printed on the standard output.
+(default = yes)
+.le
+.ls 4 make_image
+If this switch is set, an IRAF image will be created. (default = yes)
+.le
+.ls 4 data_type
+The IRAF image may be either s (short), l (long), or r (real).
+(default = r)
+.le
+.ls 4 card_length
+The number of columns on the "card" in the card-image file.
+(default = 80)
+.le
+.ls 4 blank_value
+The value used to flag blank pixels if the blank switch is set to yes.
+(default = -32767)
+.le
+.ls 4 blank
+If this switch is set to yes, any pixel having exactly the value
+specified by the parameter blank_value will be flagged as a blank
+pixel. If set to no, all pixel values are assumed to be valid.
+.le
+
+
+.bp
+.sh
+TRANSPORT - OUTPUT
+
+The primary format for take away tapes will eventually be FITS.
+Because many facilities currently cannot read FITS format,
+the card-image format will also be provided.
+
+.sh
+FITS
+.ih
+NAME
+wfits -- Convert IRAF data files to FITS data format
+.ih
+USAGE
+wfits [destination, filename, files]
+.ih
+DESCRIPTION
+Data is read from the specified filename(s) and written to the
+destination, usually a magnetic tape specification.
+A short header consisting of the filename, size, and label
+may optionally be printed on the standard output.
+
+The data will be automatically scaled to either 16 or 32 bit integer format
+(BITPIX = 16 or 32) depending on the number of bits per pixel in the
+image data, unless the bitpix parameter is specified
+otherwise. The scaling parameters may be forced to
+exactly represent the original data (BSCALE = 1.0, BZERO = 0.0)
+by setting the scale switch to no.
+
+If only the header information is to be copied to the destination,
+the write_image parameter can be set to no. If this is the case,
+then the NAXIS FITS keyword will be assigned the value of 0;
+otherwise the value for
+NAXIS will be taken from the IRAF image header.
+
+Each non-standard header element will be written into the FITS file
+in a form to be determined. These elements may be entered as FITS
+COMMENT records, or perhaps added to the file as FITS "special
+records".
+
+Other keywords will be written following standard FITS specifications.
+A few special cases will be set as follows:
+
+.ls 4 NAXISn
+The NAXIS1, NAXIS2, ... NAXISn values will be taken from the
+image header
+.le
+.ls 4 OBJECT
+The first 60 characters of the image label will be used.
+.le
+.ls 4 BLANK
+Blank pixels will be written to tape having the IRAF value for
+indefinite appropriate to 8, 16, or 32 bit integers.
+.le
+.ls 4 ORIGIN = 'KPNO IRAF'
+.le
+
+.ih
+PARAMETERS
+.ls 4 fits_destination
+The data destination, usually a magnetic tape, but may be a disk
+file or STDOUT. If magtape,
+the tape should be specified with a file number of either 1
+or "eot". The file number refers to the file which will be written.
+Thus a file number of 2 would overwrite file 2. If the tape already
+has data written on it, the safest specification would be "eot".
+This forces the tape to be positioned between the double end-of-tape
+marks prior to writing.
+.le
+.ls 4 filename
+The IRAF filename providing the root for the source name. The files
+string, if given, will be used as the suffix for the file names
+to be written to tape. For example, if the filename is given as
+"image", and the files string is "1 -5", then files image1, image2,
+image3, image4, and image5 will be written to the destination
+in FITS format. If the files string is empty, only the specified
+filename will be converted.
+.le
+.ls 4 files
+See the definition of this parameter under the FITS reader.
+.le
+.ls 4 print_header
+If this switch is set, a short header will be printed on the
+standard output for each image converted. (default = yes)
+.le
+.ls 4 write_image
+If this switch is set to no, only header information will be
+written to the destination, but no image data.
+By using this parameter,
+one can generate a FITS tape containing header information only
+and may be used as a means for examining the IRAF image header
+or for generating a table of contents on a tape prior to writing
+the data. (default = yes)
+.le
+.ls 4 bitpix
+This parameter must be either 8, 16, or 32 to specify the
+allowable FITS pixel sizes.
+.le
+.ls 4 scale
+If this switch parameter is set to no, the FITS scaling
+parameters BSCALE and BZERO will be set to 1.0 and 0.0
+respectively. The data will be copied as it appears in the
+original data, with possible loss of dynamic range.
+Values exceeding the maximum value implied by the bitpix
+parameter will be set to the maximum representable value.
+(default = yes)
+.le
+
+
+.sh
+TEXT (Write Card-Image)
+
+Although this format is easily readable by the destination
+machine, there is no real standard for encoding information,
+neither the image data itself nor the descriptive parameters.
+
+.ih
+NAME
+wtext -- Convert an IRAF image file to a card-image text file
+.ih
+USAGE
+wtext [destination, filename, files]
+.ih
+DESCRIPTION
+Data is read from the specified filename(s) and written to
+the destination, usually a magnetic tape. The data will be
+blank padded, ASCII in a format consistent with the data type
+of the image pixels, (integer or floating point).
+A short header description, consisting of the filename
+being converted and the image label, may optionally be printed
+on the standard output.
+
+The column length of the "card" may be changed from the default
+of 80 using the card_length parameter, and the field width
+to be allocated for each data element may be changed from the
+default of 10 columns by setting the field_width parameter.
+
+If the data are integers, the equivalent of the FORTRAN format
+I<field_width> will be used;
+if the data are reals, the equivalent of the FORTRAN format
+1P<n>E<field_width>.3
+will be used, where n is the number of elements which can
+be output into one card length. For the default values of
+card_length = 80, and field_width = 10, n will be 8. (1P8E10.3).
+
+Several cards may be written as a single "block" for
+improving the efficiency on magtape. Reasonable efficiency (80 percent)
+is attained with a blocking factor of 50, but this value
+may be modified by changing the parameter blocking_factor.
+If the last block is unfilled, it will be truncated to the
+minimum number of card images required to flush the data.
+
+A legitimate value must be defined to represent blank pixels.
+The parameter blank_value is used to define this value and
+defaults to -32767.
+
+.ih
+PARAMETERS
+.ls 4 text_destination
+See the definition for fits_destination for a description of this
+parameter.
+.le
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 print_header
+If this switch is set, a short header is printed for each
+file converted. (default = yes)
+.le
+.ls 4 card_length
+The number of columns on the "card" to be generated. (default = 80)
+.le
+.ls 4 field_width
+The number of columns on the "card" to be allocated for each pixel value.
+(default = 10)
+.le
+.ls 4 blocking_factor
+The number of card images to be written as a single blocked record.
+(default = 50)
+.le
+.ls 4 blank_value
+The value to be assigned to blank pixels for the purpose of
+representing them on the card image. (default = -32767)
+.le
+.bp
+
+
+.sh
+MATHEMATICAL OPERATORS
+
+Because spectra are stored as IRAF images, the standard image
+calculator utility provides the basic arithmetic services.
+For example, to create a spectrum (called spavg) which is the average of two
+other spectra (sp1 and sp2), one can enter the command:
+.ls 8 cl>imcalc "spavg = (sp1 + sp2) / 2"
+.le
+
+Other arithmetic operations are performed in a similar fashion.
+The general form of the command string is
+output_image = expression where "expression" may consist of:
+.ls 8 1. Spectra or segments of spectra
+A segment of a spectrum is specified by the notation spectrum[x1:x2]
+where x1 and x2 are pixel indices along the spectrum. For example,
+to create a spectrum which is the difference of the first 100
+pixels of two other spectra, the following command would be used:
+.ls 16 cl> imcalc "spdiff = sp1[1:100] - sp2[1:100]"
+.le
+An option to specify wavelength delineated segments may be added
+if this appears generally feasible.
+.le
+.ls 8 2. Numeric constants
+.le
+.ls 8 3. Data group names
+If an operation is performed on a data group, the output
+will be a new data group containing spectra which have been
+individually treated by the specified calculation.
+For example, if JAN28 is a group containing 100 congruent spectra
+and response is the instrumental response as a function of
+wavelength as determined from a set of standards, then
+the after the following command is entered:
+.ls 16 cl> imcalc "JAN28X = JAN28 * response"
+.le
+
+a new data group will be generated containing 100 spectra which
+have been calibrated for the instrument response. The new spectra will
+be given names JAN28X1 through JAN28X100.
+.le
+.ls 8 4. Intrinsic functions
+.ks
+The following intrinsic functions are to be provided:
+
+.nf
+ abs atan2 cos int min sin
+ acos ceil cosh log mod sinh
+ aimag char double log10 nint sqrt
+ asin complex exp long real tan
+ atan conjug floor max short tanh
+.fi
+.ke
+.le
+
+Expression elements are to be
+separated by arithmetic and boolean operators (+,-,*,/,**,<,>,<=,=>,==,!,!=).
+The boolean operators provide a means to generate masks.
+
+Rules governing operations on non-congruent spectra are not yet fully defined.
+.bp
+
+.sh
+REDUCTION OPERATORS
+
+Most of the reduction operators discussed in this section are
+intended for spectra of the IIDS/IRS class, although they
+are sufficiently general to accommodate data obtained with
+the CryoCam (either multi-aperture or long-slit mode), Echelle,
+Coude Feed, and photographic (PDS) instruments. Some
+application to FTS data is also feasible.
+
+It is intended that many of these operators will never be
+directly executed by users, but that they will be driven by
+CL command scripts tuned for individual instruments.
+In some cases the scripts will be fairly elaborate and extensive
+to lead new users through the reduction phase along a reliable
+path.
+
+It will no doubt be necessary to either modify some
+of these operators, or create more specific operators for
+certain other instruments. These operators should be considered
+a sample of what will eventually be available in this package.
+
+The basic path which most spectroscopic data follows is:
+
+.ls 4 1.
+Coincidence Correction.
+.ls
+Many detectors can respond to incoming photevents at a limited
+rate. Once an event occurs, the detector cannot respond for some
+instrument dependent period, or dead-time. If events occur during
+this period, they will not be counted. If the event rate
+does not greatly exceed the detector limits, the uncounted events
+can be corrected for statistically.
+
+For many detectors, the coincidence correction is a well
+determined function and can be applied to the raw data
+to produce a reasonably corrected spectrum.
+.le
+.le
+.ls 4 2.
+Wavelength linearization.
+.ls
+Few instruments produce spectra having pixel to pixel wavelength
+differences which are constant across the entire spectrum.
+For subsequent reduction and analysis purposes, it is
+desirable to rectify the spectra. This is done by mapping the spectrum
+from the non-linear wavelength coordinate to a linear one.
+It is also desirable to provide a means of forcing the mapping
+to a grid which is common to many observations, and in some cases,
+to observations acquired with other instruments as well.
+
+The processes required for the mapping are outlined below.
+
+.le
+.ls 4 a.
+Manually identify a small number of spectral features having
+known wavelengths thereby creating a table of wavelength as
+a function of pixel number.
+.le
+.ls 4 b.
+Compute estimated relationship between wavelength and pixel number
+.le
+.ls 4 c.
+Automatically locate many features found in a user definable line list.
+Optionally locate additional features from other spectra using an alternate
+line list. (This allows spectra from several different sources to be used
+for the wavelength calibration, such as arc lamps, night/day sky.)
+.le
+.ls 4 d.
+Compute improved relationship between wavelength and pixel number.
+.le
+.ls 4 e.
+Perform 2.c. and 2.d. for all other spectral entries in the wavelength
+calibration data group.
+.le
+.ls 4 f.
+Compute relationship for wavelength as a function of pixel number and time (or
+zenith distance, or some other flexure parameter) as deduced from 2.e.
+.le
+.ls 4 g.
+Apply inverse of wavelength function to a data group. This requires
+interpolation of the data at pixels having fixed steps in wavelength.
+The start wavelength and the step size must be user definable.
+The interpolation may be via a polynomial of a user specified order (typically
+1 to 5), or a more sophisticated interpolator. The linearization
+in wavelength may also be a simple rebinning of the data to exactly preserve
+photon statistics.
+.le
+.le
+.ls 4 3.
+Field flattening.
+.ls
+Pixel to pixel sensitivity variations and other small scale
+fluctuations are removed by dividing the object spectra by the spectrum of
+a continuum source. The latter spectrum should have a very high
+signal-to-noise ratio so as not to introduce additional uncertainties
+into the data.
+
+If the spectrum of the continuum source has much low frequency
+modulation,
+it may be necessary to filter these variations before the division is performed.
+Otherwise fluctuations not characteristic
+of the instrument response may be introduced, and may be difficult to remove
+during the subsequent flux calibration process.
+.le
+.le
+.ls 4 4.
+Sky Subtraction
+.ls
+Except for extremely bright sources, all spectra require that the
+spectrum of the night sky be removed. In some cases, sky will
+be the dominant contributor to the raw spectrum.
+Sky subtraction is a simple subtraction operation and can be
+accomplished with the image calculator tools.
+.le
+.le
+.ls 4 5.
+Extinction Correction
+.ls
+The effects of the Earth's atmosphere produce a wavelength dependent
+reduction of flux across the spectrum. The extinction function
+is approximately known from extensive photometric measurements
+obtained at the observatory over a period of many years. But on
+any given night this function may deviate from the average, sometimes
+significantly. If the spectroscopic observer has acquired the necessary
+data, it is possible to solve for the extinction function directly.
+
+Therefore, it should be possible for the user to either derive the
+extinction function, input a user-defined function, or use the
+standard average function and subsequently correct spectra for the
+effects of the atmosphere as described by that function and the effective
+observing airmass. (Note that because exposures may be quite long, the
+effective airmass must be calculated as a function
+of position on the sky.)
+.le
+.le
+.ls 4 6.
+Flux Calibration (Correction for Instrument Response)
+.ls
+By observing objects having known wavelength dependent flux
+distributions, it is possible to determine the sensitivity
+variations of the instrument as a function of wavelength.
+Usually several standards are observed for each group of data
+and these must be averaged together after corrections for
+"grey shift" variations (wavelength independent flux reductions
+such as those introduced by thin clouds).
+
+Although the actual flux of the standards is generally known only
+for a limited selection of wavelengths, the instrument response
+usually varies smoothly between those wavelengths and a smooth
+interpolator generally provides satisfactory calibration values
+at intermediate wavelengths.
+
+In some cases, the system sensitivity response may be known
+from other observations, and the user will be allowed to directly
+enter the sensitivity function.
+.le
+.le
+
+The above reduction path is primarily tuned to IIDS/IRS style data.
+Other instruments may require additional or alternate steps.
+It may be necessary for multiaperture Cryocam spectra, for example,
+to undergo an additional hole to hole sensitivity correction
+based on the total sky flux through each hole.
+
+The tasks performing the procedures outlined above will be described
+in more detail in the following discussion.
+
+.sh
+COINCIDENCE CORRECTION
+.ih
+NAME
+coin_cor -- Correct specified spectra for photon coincidence
+.ih
+USAGE
+coin_cor [filename, files, destination, dead_time]
+.ih
+DESCRIPTION
+The spectra specified by the root filename and the files parameter
+are corrected for photon counting losses due to detector dead-time.
+The corrected spectra are written to filenames having the root
+specified by the destination.
+
+The correction, if typical of photomultiplier discriminators,
+is usually of the form:
+
+.br
+ Co(i) = C(i) exp[C(i) dt],
+.br
+ dt = t/T,
+.br
+
+where Co(i) is the corrected count at pixel i, C(i) is the raw count,
+t is the detector/discriminator dead-time, and T is the
+exposure time at pixel i.
+
+Clearly, the correction factor can become extremely large when the
+count rate, C(i)/T, is large compared with the dead-time, t.
+The above formula cannot be expected to
+exactly remove the effects of undetected photo-events when
+large corrections are required.
+
+The exposure time will be read from the image header.
+If no value exists, or if the value is less than or equal to
+zero, a request from standard input will be issued for this parameter.
+
+Because each detector may have unique coincidence properties,
+this routine may be package dependent.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 destination
+The IRAF filename providing the root for the name of the result
+spectra. The files parameter, if specified, will be used for the
+suffix. If the filename parameter is actually a data group name,
+the destination name will be used to create a new data group
+containing spectra having IRAF filenames with the destination
+group name as a root and a suffix starting with 1 and incremented for
+each converted spectrum.
+.le
+.ls 4 dead_time
+The value of this parameter, in seconds, represents the detector
+dead-time.
+.le
+.ls 4 print_header
+If this switch is set, a short header will be printed on the
+standard output for each spectrum corrected. (default = yes)
+.le
+.ls 4 exposure
+This parameter should be entered into the image header. If not
+present or not realistic, a request is made from standard input.
+.le
+
+.sh
+WAVELENGTH LINEARIZATION
+
+A package of routines is required to perform the operations
+leading to linearized data. These include:
+.ls 4 1. Spectral line list definition and editing facility
+.le
+.ls 4 2. Manual line identifier using graphics cursor.
+.le
+.ls 4 3. Automatic line identifier using preliminary identifications
+from manual identifier and locating lines from the predefined list.
+.le
+.ls 4 4. Computation of dispersion relationship as a function of
+pixel coordinate and a flexure parameter, probably zenith distance.
+.le
+.ls 4 5. Linearization of spectra according to dispersion relation.
+Correction can be to either a linear or logarithmic dispersion in
+the pixel coordinate.
+.le
+
+Perhaps the most critical aspect of determining the dispersion
+relation is the algorithm for locating spectral line centers.
+A variety of techniques are available, and some testing will
+be required before adopting a standard scheme. Probably several
+algorithms will be available and switch selectable at the command
+level.
+
+.sh
+LINE LIST PREPARATION
+.ih
+NAME
+line_list -- Create a new line list, or modify an existing one
+.ih
+USAGE
+line_list [filename, option]
+.ih
+DESCRIPTION
+The line list specified by the IRAF filename parameter will be
+either created, listed, or modified according to the option
+given. The IRAF database facility will be used to manage the
+line list file.
+
+Each entry within the list will contain an identification tag (e.g. HeII)
+a reference value (e.g. wavelength, frequency, wavenumber), and a weighting
+value such as 1.0 or 2.0 to be used later in the least-squares fitting.
+An optional descriptive header may be associated with the line list.
+(e.g. "HeII arc from 3500 to 11,000A")
+
+Either the header, entry identifier or value may be changed
+if the modify option is specified. Deletion or addition of
+entries is also possible with the appropriate option flags
+specifications.
+.ih
+PARAMETERS
+
+.ls 4 filename
+The IRAF filename to be assigned to the line list. The list will
+referenced by this name thereafter.
+.le
+.ls 4 option
+This string parameter determines the action of the line list task.
+If no option is specified, the default action is to list the
+specified line list on the standard output if the line list
+exists; if it does not exist, a new line list will be created
+with the given name.
+.ls 4 = create
+The identifications and values for the line list are read from
+the standard input on a record by record basis. Each input
+record contains data for one line according to the format:
+.br
+.ls 4 identification value
+.le
+.le
+.ls 4 = header
+A descriptive header is read from the standard input.
+.le
+.ls 4 = list (default)
+The line list is listed on the standard output.
+.le
+.ls 4 = add
+Additional entries to the list are read from the standard input.
+.le
+.ls 4 = delete
+The entries defined by the values read from the standard input
+are deleted from the line list. The entries deleted will be those
+having values nearest the entered value, unless the absolute
+difference from the listed value is too large. For example, one
+can enter 5015 to delete the helium line at 5015.675, but entering
+5014 would result in an error message that no match could be found.
+.le
+.ls 4 = id
+The entries defined by values entered as for delete will be modified.
+Input is expected in the format:
+.br
+approxvalue newidentifier
+.le
+.ls 4 = value
+As for option = id except that the input format contains
+the newvalue instead of the newidentifier.
+.le
+.ls 4 = weight
+As for option = id except that the nput format contains the newweight
+instead of the newidentifier.
+.le
+.le
+
+.sh
+MANUAL LINE IDENTIFICATION
+
+This routine provides the option of manually identifying the locations
+of spectral features by either setting a graphics cursor interactively,
+or by entering a list of feature positions.
+
+The primary uses for this routine are to identify features of known
+wavelength in preparation for a dispersion solution, and also to
+identify features in linearized spectra for velocity measurements.
+
+.ih
+NAME
+mlinid -- Manually identify line features in a spectrum
+.ih
+USAGE
+mlinid [filename, files]
+.ih
+DESCRIPTION
+A list file is created for each of
+the spectra specified by the IRAF filename parameter and files string
+containing the locations of spectral features and their associated
+reference value (e.g. wavelength, frequency, wavenumber).
+If invoked as an interactive task from a graphics terminal,
+the spectra will be displayed and cursor input requested to ascertain
+the approximate position of the feature. An improved position will
+be obtained via one of the line centering algorithms, and
+a request will be made for the reference value of the feature.
+The requests continue until EOF is detected.
+The name of the created list file is added to the spectral image
+header.
+
+Positions of features are given in the coordinate system defined
+by the standard image header entries CRPIX and CDELT
+defining the reference pixel and the
+pixel to pixel distance. For raw spectra these values simply define
+the pixel position of the feature. For dispersion corrected spectra
+these values define the position of the feature in wavelength units.
+
+If invoked as a background task, or from a non-graphics terminal,
+additional requests for the cursor x-coordinate and intensity
+will be made from the standard input.
+
+The procedure is repeated for all specified spectra.
+
+Because the dispersion solution may be a function of an additional
+instrument dependent parameter (e.g. zenith distance),
+the driving package script can indicate the header entry to be
+used as the second parameter. Values for this parameter, if present,
+will be written to the output list file.
+.ih
+PARAMETERS
+
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 cur (x,y)
+This is a list structured parameter of type "graphics cursor".
+The list contains the approximate values of the pixel
+coordinate for the spectral features to be identified
+and the intensity value of the continuum at the feature. If the
+task is invoked from a graphics terminal in an interactive mode,
+values for this parameter will be read from the terminal's
+graphics cursor.
+.le
+.ls 4 value
+This is a list structured parameter containing the reference values
+for the spectral features to be identified. If the task is invoked in
+an interactive mode, the user will be prompted for these values.
+.le
+.ls 4 center_option
+This string parameter controls which algorithm is to be used during
+the improved centering phase of the process. (default = cg)
+.ls 4 = cg
+This specifies a center of gravity algorithm defined as the
+first moment of the intensity above the continuum level
+across the spectral feature.
+The integrals are evaluated using the trapezoidal rule and
+the intensity will be weighted by the square root of the intensity
+if the switch parameter cgweight is set to yes. The integral
+is evaluated from the approximate position defined by x cursor position
+plus and minus the number of pixels specified by the parameter
+cgextent.
+.ls 4 cgweight
+This switch defines whether a weighted moment is used in the
+center of gravity centering algorithm. (default = yes)
+.le
+.ls 4 cgextent
+This integer parameter defines the limits of the integrals in the
+center of gravity centering algorithm. The integral extends from
+the approximate position minus the extent to the approximate position
+plus the extent in units of pixels. (default = 5).
+.le
+.le
+.ls 4 = parabola
+This specifies that the centering algorithm is to be a parabolic
+fit to the central 3 pixels. The improved center is taken as the
+center of the parabola. The central 3 pixels are defined as the
+most extreme local pixel plus and minus one pixel. The most extreme
+local pixel is that pixel nearest the approximate center having the
+greatest deviation from the local average value of the spectrum. The
+extent of "local" is taken as plus and minus the parameter parextent.
+.ls 4 parextent
+This integer parameter defines the extent in units of pixels
+of the search for a local extreme pixel. (default = 3)
+.le
+.le
+.ls 4 = gauss
+(This algorithm will not be implemented in the initial system release.)
+This specifies that the centering algorithm is to be a Gaussian
+fit to the region near the approximate center. The fit is
+made to a region specified by the parameter gextent. Because
+this is a three parameter non-linear least-squares fit
+(center, width, peak intensity), it is likely to
+be slow. It may also produce poor results with noisy data
+although centering on high signal to noise data should be
+excellent.
+.ls 4 gextent
+This integer parameter specifies the extent in pixels of the Gaussian fit.
+It may be necessary to include a significant region of continuum.
+(default = 9)
+.le
+.le
+.ls 4 = none
+If this option is chosen, no improvement to the approximate center
+will be made. This may be useful for asymmetric and weak features
+where the other techniques can be systematically incorrect.
+.le
+.ls 4 second_order
+This string parameter defines the name of the image header entry to be
+used as the second order correction parameter in the dispersion
+solution. Values for this parameter, if present, are read from the image header
+and written to the output list file. Examples of values are zenith_distance,
+sidereal_time, instr_temp. (default = none)
+.le
+
+.sh
+AUTOMATIC LINE IDENTIFICATION
+
+This task allows a user to locate a set of spectral features defined
+in a previously prepared list.
+
+.ih
+NAME
+alinid -- Automatically locate spectral features in a spectrum
+.ih
+USAGE
+alinid [filename, files, mfilename, mfiles, list]
+.ih
+DESCRIPTION
+A list file is created for each of the spectra specified by the
+IRAF filename and files parameters. The file will contain
+the positions of the features defined in the line list file
+specified by the list parameter. The name of the list file
+will be added to the spectral image header.
+
+A preliminary estimate of the
+relationship of feature position as a function of feature
+wavelength is obtained from the list file(s) created by the
+task MLINID and defined by the parameters mfilename and mfiles.
+A single preliminary estimate may be applied to a number of
+spectra by specifying a null mfiles string. Otherwise,
+a one-to-one correspondence is assumed between preliminary
+list files and spectra. If the entry for mfilename is also null,
+the linear dispersion relation for the pixel coordinate contained
+in the image header will be used. This provides the option
+of locating features in linearized spectra.
+
+The initial position estimate is improved using one of the centering
+algorithms defined by the center_option parameter and then
+written to a list file. Also written to the list file will be
+the feature's reference value (e.g. wavelength), weight,
+identification string, and the acceptability of the line.
+Acceptibility is noted as either accepted, set, deleted, or not
+found (see below).
+
+If the task is invoked from a graphics terminal as an interactive
+task, the interact switches may be set to yes.
+Then each spectrum will
+be displayed in segments expanded about each feature with the
+automatically defined center marked. The user can then accept
+the given position, mark a new center, or declare the line
+unacceptable.
+
+If the display switch is set, the spectrum is displayed
+and the features marked.
+
+If the task is invoked as a background task, or if the
+user terminal is non-graphics, then the display and interact
+switches cannot assume values of yes.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS
+.le
+.ls 4 files
+See the definition of this parameter under RFITS
+.le
+.ls 4 mfilename
+The root for the spectra names used to define the preliminary
+relationship between spectral feature coordinate and reference
+value. The mfiles string parameter is used to define the
+suffix of the spectral name. If this parameter is null, the
+preliminary relationship is assumed to be linear and defined
+by the standard image header entries CRPIX and CDELT.
+.le
+.ls 4 mfiles
+This string parameter serves the same purpose for mfilename
+as the files parameter serves for filename. Note that if this
+parameter is null, the single spectrum defined by mfilename
+is used to define the preliminary relationship for all
+spectra defined by filename and files.
+.le
+.ls 4 list
+This parameter specifies the IRAF file name containing the
+spectral line list to be scanned for features. (See the
+task LINE_LIST)
+.le
+.ls 4 interact
+If this switch is set to yes and the task is invoked interactively
+from a graphics terminal, the spectrum will be displayed on the
+terminal. Each feature will be marked with its computed center
+and the user can type one of the following single keystrokes:
+.ls 4 a
+to accept the displayed position
+.le
+.ls 4 s
+to set the cursor to the desired position
+.le
+.ls 4 d
+to delete the displayed feature from the line list during this
+invocation of the task
+.le
+.ls 4 b
+to reset the operational mode to a "batch" environment where
+no display or interaction is desired
+.le
+.ls 4 p
+to reset the operational mode to a "passive" environment where
+the spectra are displayed and marked, but no interaction is desired
+.le
+.le
+.ls 4 display
+If this switch is set to yes, and the task is invoked from
+a graphics terminal, the spectrum will be displayed and the
+identified lines marked for the user's inspection. No
+interaction is allowed unless the interact switch is also set to yes.
+(default = yes)
+.le
+.ls 4 center_option
+See the description of this parameter under MLINID.
+.le
+.ls 4 second_order
+See the description of this parameter under MLINID.
+.le
+
+.sh
+DISPERSION SOLUTION
+
+After several spectral features have been identified, either
+manually with MLINID or automatically with ALINID, the relationship
+between feature reference value and pixel coordinate can be calculated.
+The dispersion relation may require a second order correction
+to account for variations as a function of some additional
+parameter, such as zenith distance or time of day.
+
+.ih
+NAME
+disp_sol -- Determine the dispersion relation for a set of spectra.
+.ih
+USAGE
+disp_sol [filename, files, order, global]
+.ih
+DESCRIPTION
+The list files containing the postions and reference values for
+features in the specified spectra are combined to solve for the
+dispersion relation by a polynomial least-squares fit to the lists.
+The solution can include a second order
+correction parameter which is also contained in the list file.
+
+The solution takes the form of a polynomial in the pixel
+coordinate having the specified order. The second order
+is also fit by a polynomial. (The choice of a polynomial
+applies to the initial release. Additional forms, selectable by
+parameter, of the solution may be available later.)
+The polynomial coefficients are stored in the spectral image header
+if the store_coeffs switch is set to yes and the spectrum does not already
+contain a solution. If a solution already exists, the user is
+asked for confirmation to overwrite the solution, unless the overwrite
+switch is set to yes.
+
+If filename is the name of a data group, all line list files for
+spectra in that data group are combined into the solution.
+
+If invoked as an interactive task from a graphics terminal,
+a representation of the solution will be displayed and the user
+will be allowed to alter the weights of the line entries.
+If invoked from a non-graphics terminal, the representation
+will be in a tabular format (also available at a graphics terminal)
+for inspection and alteration. If invoked as a background task,
+an attempt will be made to reject discrepant points.
+
+The solution is made using all available line lists combined
+into a single data set if the global switch is set to yes.
+If global is set to no, each spectrum is treated as an
+independent data set.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 order
+The order of the polynomial for a least-squares fit to the
+dispersion solution. If the specified order exceeds the number
+of free parameters, the order will be reset to the maximum
+allowable. (default = 1 --> linear).
+.le
+.ls 4 global
+This switch determines if the data from all the specified spectra are
+to be treated as a single large data set. This is appropriate if the
+data represent a single congruent "setup". But if the data represent
+several different configurations, such as for multiaperture data,
+the global switch should be set to no. Note that if global is no, then
+no second order parameter solution is possible.
+.le
+.ls second_order
+This parameter specifies the order for the fit to the second
+order parameter. The limit described for the order parameter
+applies. (default = 0 --> no second parameter solution).
+.le
+.ls 4 interact
+If this switch is set to yes and the task is invoked interactively
+from a graphics terminal, the residuals of the solution will be displayed
+on the terminal. The user can type one of the following keystrokes:
+.ls 4 a
+to accept the current solution. The parameters of the fit
+are written into the headers of the spectra contributing to the fit.
+.le
+.ls 4 e
+to exit without saving the solution
+.le
+.ls 4 w
+to reset the weight of the point near the cursor positioned by the user.
+The user is then prompted for the new weight which may be set to zero
+to delete the point from the solution.
+.le
+.ls 4 t
+to display the solution parameters in tabular form
+.le
+.ls 4 o
+to specify a new order for the solution
+.le
+.ls 4 s
+to specify a new order for the second order parameter solution
+.le
+.ls 4 b
+to revert to batch mode to process the remainder of the solutions.
+This is only meaningful if the global switch is set to no.
+.le
+.ls 4 p
+to revert to passive mode as for ALINID. This is only meaningful
+if the global switch is set to no
+.le
+.le
+.ls 4 store_coeffs
+If this switch is set to yes, the dispersion solution polynomial
+coefficients will be written into the image header as special
+header elements. Otherwise, the solution is discarded. (default = yes)
+.le
+.ls 4 overwrite
+If this switch is set to yes, any existing dispersion solution contained
+in the image header will be overwritten without any request for confirmation
+from the user. If set to no, the user is asked if overwriting of the solution
+is acceptable. If no prior solution exists, this switch has no meaning.
+(default = no)
+.le
+
+.sh
+DISPERSION CORRECTION
+
+After the dispersion relation has been determined, the spectra
+are usually re-binned to create spectra having a linear
+relationship with wavelength. Although this is not always
+done, nor is it always desirable, subsequent processing
+is often simplified greatly by having to deal with only
+linearized data.
+
+.ih
+NAME
+disp_cor -- Linearize spectra having dispersion relation coefficients
+.ih
+USAGE
+disp_cor [filename, files, destination, option]
+.ih
+DESCRIPTION
+The spectra specified by the root filename and the files parameter
+are corrected for deviations from a linear wavelength relationship.
+The corrected spectra are written to filenames having the root
+specified by the destination parameter.
+
+The correction is performed by solving for the inverse relationship
+of pixel number as a function of equal increments in the wavelength.
+The new starting wavelength and increment are optionally specified
+by the parameters start and increment. If not specified, the current
+wavelength of the first pixel will be taken as the starting wavelength
+and the increment will be chosen to exactly fill the length of the
+current spectrum. The spectrum will be padded with INDEF on either
+end if the specified parameters request a larger spectral window than
+actually exists.
+
+The actual re-binning can be performed using one of several algorithms.
+The most efficient minimally smoothing algorithm to be available in the
+initial release is the fifth order polynomial interpolation.
+The most efficient count preserving algorithm is the simple partial-pixel
+summer.
+
+The interpolation can be either linear in wavelength or in the logarithm
+of wavelength. The latter is useful for subsequent radial velocity
+analyses. The choice is specified by the logarithm switch.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS
+.le
+.ls 4 destination
+See the definition of this parameter under COIN_COR.
+.le
+.ls 4 option
+This parameter specifies the algorithm to be used for the
+re-binning operation. The initial release will contain the
+following options:
+.ls 4 = linear
+to use a linear interpolation
+.le
+.ls 4 = poly
+to use a fifth order polynomial
+.le
+.ls 4 = sinc
+to use a sinc function interpolator
+.le
+.ls 4 = sum
+to use partial pixel summation
+.le
+.le
+.ls 4 start
+This parameter specifies the wavelength at which the corrected
+spectrum is to begin. The wavelength of the first pixel will
+be assigned this value. This parameter, combined with the increment
+parameter, allows data taken on different nights
+or with different instruments to be forced to be congruent.
+(default = value at first pixel)
+.le
+.ls 4 increment
+This parameter specifies the pixel to pixel wavelength (or logarithm of
+wavelength) increment
+that is to be used during the linearization process.
+(default = [wavelength at last pixel minus wavelength at first pixel]
+divided by [number of points in spectrum - 1])
+.le
+.ls 4 logarithm
+If this switch is set to yes, the linearization occurs with equal
+increments in the logarithm of wavelength. Otherwise, equal
+increments of wavelength are used. (default = no)
+.le
+.ls 4 print_header
+See the definition of this parameter for COIN_COR.
+.le
+
+.sh
+FIELD FLATTENING
+
+Most detectors exhibit variations in sensitivity across the field
+of interest. These are removed by dividing all observations by
+the spectrum of a smooth continuous source, such as an incandescant
+lamp. In order that these lamps, which usually have a low color
+temperature, produce sufficient energy in the blue and ultraviolet,
+they are often enclosed in a quartz rather than a glass bulb.
+Thus, the field flattening operation is often referred to as
+"quartz division".
+
+The operation is of marginal value unless the continuum source is
+observed properly. First, a very high signal-to-noise ratio per
+pixel is required. For certain detectors and applications this
+may not be possible in a reasonable amount of time. Second, the
+continuum source should not have any significant variations
+across small regions of the spectrum (high frequency "bumps").
+Otherwise the division will add these variations into the spectrum.
+
+There are basically two aspects to flat fielding spectra. The first
+is the removal of pixel-to-pixel sensitivity variations. The second
+is a more global pattern due to non-uniform iillumination and
+spatial and wavelength sensitivity variations across the detector.
+
+The very high frequency pixel-to-pixel variations are easily handled
+by a straightforward division of the observations by the continuum
+spectrum.
+
+The second problem is usually postponed in one-dimensional data
+reductions and included in the
+solution for the system sensitivity by observing standard stars.
+This aspect of the problem is adequately handled in this fashion
+and no special operators are provided in this package.
+
+If the continuum source exhibits large low frequency variations
+across the spectrum, it may be desirable to filter these.
+This is most easily done by fitting a moderately high order
+polynomial through the spectrum, and then dividing the polynomial
+representation into the original continuum spectrum. The result
+is a flat spectrum having an average value of unity and
+containing only the pixel-to-pixel sensitivity variations.
+
+Finally, it should be noted that the field flattening operation
+is most properly performed prior to the wavelength linearization
+of the spectra because the linearization process can smooth
+pixel-to-pixel variations.
+
+Flat fielding consists of two logical operations. The first
+is the solution for a continuum spectrum with the low frequency
+variations removed (CR_FLAT). It is assumed that multiple observations
+of the continuum source have been already averaged (using the
+image calculator program, for example).
+
+The second operation is the actual field flattening of the object
+spectra (FLT_FIELD).
+
+.ih
+NAME
+cr_flat -- Create a flat field spectrum
+.ih
+USAGE
+cr_flat [filename, destination]
+.ih
+DESCRIPTION
+The continuum spectrum specified by filename is corrected for
+low frequency spectral variations. Several algorithms may be
+available. The initial release will contain only a polynomial
+fitting technique. A fourier filtering algorithm may be added
+at a later date.
+
+The spectrum is fit by a polynomial in the pixel coordinate
+and the polynomial is divided into the original spectrum.
+Discrepant pixels may be rejected and the solution re-iterated.
+
+If invoked as an interactive task from a graphics terminal, the
+resultant spectrum is displayed and the user may alter the
+solution parameters if the interact switch is set to yes.
+If invoked from a non-graphics terminal, sufficient information
+concerning the fit is written to the terminal to allow
+the user to judge the quality of the fit and then alter the
+solution parameters.
+
+If invoked as a background task, or if the interact switch is set
+to no, default parameters will be assumed.
+
+The parameters of the fit are added to the image header for
+the corrected spectra.
+.ih
+PARAMETERS
+.ls 4 filename
+The IRAF filename containing the spectrum of the continuum
+source. If this is a data group name, then all spectra
+in the group will be corrected.
+.le
+.ls 4 destination
+The IRAF filename into which the resultant corrected
+spectrum is written. If the source filename is a data group,
+then the destination will be a new data group containing
+the names of the corrected spectra. The names will be
+assigned using the destination as a root name, and the
+ordinal of the spectrum in the list as a suffix.
+.le
+.ls 4 option
+This string parameter specifies the algorithm to be used
+in the correction process. Currently only option = poly
+is recognized.
+.le
+.ls 4 order
+This integer parameter specifies the initial order of the
+polynomial fit. (default = 8)
+.le
+.ls 4 reject
+This parameter specifies the number of standard deviations
+beyond which pixels are to be rejected. If the task
+is interactive, pixel rejection is performed only on command.
+If invoked as a background task, rejection is iterated
+until no further pixels are rejected, or until the iteration
+count has been attained (see parameter niter). (default = 2.2)
+.le
+.ls 4 niter
+This integer parameter specifies the number of iterations
+to be performed in background mode. It may be set to 0 to
+specify no pixel rejection. (default = 2).
+.le
+.ls 4 interact
+If this switch is set to yes and the task is invoked as
+an interactive task, the user can alter the fit parameters
+order, reject, and niter. If at a graphics terminal, the resultant
+spectrum is displayed and the user can command the operation
+with the following single keystrokes:
+.ls 4 a
+to accept the solution
+.le
+.ls 4 o
+to change the order of the fit
+.le
+.ls 4 r
+to reset the reject parameter
+.le
+.ls 4 n
+to reset the niter parameter
+.le
+.ls 4 b
+to reset the operational mode to a batch environment
+.le
+.ls 4 p
+to reset the operational mode to a passive environment
+.le
+.le
+
+If at a non-graphics terminal, the fit parameters are
+written to the terminal so that the user may assess the quality
+of the fit. A request for one of the interactive commands
+is then issued and the user may proceed as if on a graphics
+terminal.
+.le
+
+.ih
+NAME
+flt_field -- Correct spectra for pixel-to-pixel variations
+.ih
+USAGE
+flt_field [filename, files, flatname, destination]
+.ih
+DESCRIPTION
+The spectra specified by the IRAF filename parameter and the files
+string are divided by the flat field spectra specified by
+the parameter flatname. If filename and flatname are data group names,
+the division is performed on a one-for-one basis.
+
+This operation is little more than a simple division. An image
+header entry is added indicating that flattening by the
+appropriate spectrum has been performed.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 flatname
+This string parameter sepcifies the name of the flat field
+spectrum, or spectra if a data group name.
+It is not necessary that the flat field spectra be corrected
+for low frequency spectral variations.
+It is required that the spectra be congruent with the spectra
+to be flattened; that is, all spectra must have the same
+length, reference pixel, and pixel-to-pixel increment.
+.le
+.ls 4 destination
+See the definition of this parameter under COIN_COR.
+.le
+.ls 4 print_header
+See the definition of this parameter under COIN_COR.
+.le
+
+.sh
+EXTINCTION CORRECTION/FLUX CALIBRATION
+
+At each wavelength (lambda) along the spectrum, the observed
+flux (fobs) must be corrected for extinction (k) due to the
+Earth's atmosphere and the system sensitivity (S) to obtain
+a true flux (f) above the atmosphere.
+.sp 1
+fobs(lambda) = f(lambda) * exp{-z[k(lambda)+C]} * S(lambda)
+.sp 1
+where z is the path through the Earth's atmosphere during the
+observation and C is an optional "grey" opacity term.
+
+For most observations, the standard extinction function is adequate,
+but occasionally the additive term is beneficial. In rare cases,
+the observer has acquired sufficient high quality data to
+determine the extinction function across the spectral region
+of interest. And in other cases, the user may have a priori
+knowledge of the extinction function.
+
+Observations of standard stars are used to determine
+either the additive constant or a new extinction function,
+and the system sensitivity.
+The two operations, determining the extinction parameters
+and the system sensitivity curve, are therefore intimately
+related.
+
+The process breaks down into four basic operations:
+.ls 4 1.
+Define the standard stars and their observations. (STD_STAR)
+.le
+.ls 4 2.
+Define the extinction solution option and solve for the extinction
+additive term or complete function if necessary. (CREXT_FUNC)
+.le
+.ls 4 3.
+Determine the system sensitivity function. (CRSENS_FUNC)
+.le
+.ls 4 4.
+Remove the effects of the extinction and the system sensitivity
+from the observations. (EXT_COR, SENS_COR)
+.le
+
+These will be described below in more detail.
+
+.ih
+NAME
+std_star -- Define the standard stars to be used for solving the extinction and
+system sensitivity functions.
+.ih
+USAGE
+std_star [fnamelist, filelist, namelist, std_file]
+.ih
+DESCRIPTION
+The spectra defined by the list of filenames and associated files
+contained in the string list parameters fnamelist and filelist
+are compared with the standard flux measurements for the stars
+listed in the string list parameter namelist. The resultant
+table of ratios as a function of wavelength are saved in the
+IRAF file specified by the std_file parameter.
+
+All spectra must be wavelength linearized. The star names given
+in namelist must be in a form similar to that in the IIDS Reduction
+manual. If a star name cannot be matched to the standards contained
+in a calibration file, the user is prompted for additional
+information. The calibration file containing the list of reference
+flux values is specified by the calib_file parameter.
+.ih
+PARAMETERS
+.ls 4 fnamelist
+This is a list structured parameter containing the IRAF filenames
+associated with the spectra for each of the standard stars contained
+in the list of starnames defined by the list structured parameter
+namelist. Both these parameters must have the same number of elements.
+The filename specifications are defined as in RFITS.
+.le
+.ls 4 fileslist
+This is also a list structured parameter having the same number of
+elements as fnamelist although some may be null.
+The entries are defined as in RFITS.
+.le
+.ls 4 namelist
+This is also a list structured parameter having the same number
+of elements as fnamelist. All elements must exist and have a
+form to be decided on, but probably similar to that given in the IIDS
+Reduction manual, page 36. For example, a typical star name might
+be BD+8 2015, or HILTNER 102. Case will not be significant.
+.le
+.ls 4 std_file
+This string parameter defines the IRAF filename in which the
+results from the standard star observations are stored.
+This file will be used to contain further calibration information
+such as the extinction and sensitivity function for the
+current set of observations.
+.le
+.ls 4 calib_file
+This string parameter defines which of several calibration
+data files are to be accessed for the comparison of the
+observational data to the standard fluxes. Separate tools
+to examine, modify, and create these files are available
+in the utilities package. (default = onedspec$iids.cal)
+.le
+.ls 4 print_header
+If this parameter is set to yes, an informative header
+is listed on the standard output as the standard stars are processed
+(default = yes).
+.le
+
+.ih
+NAME
+crext_func -- Create an extinction function from a set of observations
+.ih
+USAGE
+crext_func [std_file, option]
+.ih
+DESCRIPTION
+The user may specify via the option parameter which of the four
+extinction solutions is to be used. These are:
+.sp 1
+.ls 4 1.
+Adopt standard extinction function (option = standard).
+.le
+.ls 4 2.
+Solve for an additive constant (option = additive).
+.le
+.ls 4 3.
+Solve for extinction function (option = new_function).
+.le
+.ls 4 4.
+Input a tabular extinction function consisting of extinction
+values at specified wavelengths (option = input).
+.le
+.sp 1
+If the first or last options are chosen, the std_file may be empty.
+If the second option is chosen, several observations at
+differing air masses must be included in the file specified by std_file.
+If the third option is chosen,
+at least two standard stars must be included in the list of observations.
+
+The derived extinction function is added to the IRAF file specified
+by the std_file parameter by creating a new spectrum containing the
+function and adding the spectrum name to the std_file.
+The new spectrum will adopt a name having a root from the
+name std_file and a suffix of ".ext". The spectrum is created by
+a spline interpolation through the extinction values.
+
+If invoked as an interactive task from a graphics terminal, the
+derived extinction function is displayed. The user may interactively
+alter the derived extinction values using the graphics cursor.
+If invoked from a non-graphics terminal, the user may alter the
+values by specifying the wavelength and new extinction value
+from the standard input. Interaction may be suppressed by setting the
+interact switch to no.
+
+.ih
+PARAMETERS
+.ls 4 std_file
+See the definition of this parameter under STD_STAR.
+.le
+.ls 4 option
+This parameter specifies which aspects of the extinction solution
+are to be computed. See description section for CREXT_FUNC.
+.le
+.ls 4 interact
+If this switch is set the user may alter the derived extinction values.
+If invoked from a graphics terminal and interact is set to yes, the
+following single keystroke commands may be typed:
+.ls 4 a
+to accept the current solution
+.le
+.ls 4 m
+to modify the extinction value at the cursor wavelength position (cursor-x)
+to the cursor extinction value position (cursor-y).
+.le
+.ls 4 i
+to insert a new wavelength-extinction value pair at the current
+crosshair position.
+.le
+.ls 4 d
+to delete the wavelength-extinction value pair at the current
+cursor wavelength position.
+.le
+.le
+
+.ih
+NAME
+crsens_func -- Create system sensitivity function.
+.ih
+USAGE
+crsens_func [std_file, option]
+.ih
+DESCRIPTION
+The standard star data and extinction function contained in the
+IRAF file specified by the std_file parameter are used to
+compute the system sensitivity as a function of wavelength.
+The derived function is written to the file specified by
+std_file.
+
+There must be at least one standard star observation contained
+in the std_file, unless the parameter option = input.
+This allows the user to enter any function in the
+form of wavelength-sensitivity pairs.
+
+If option = shift, a "grey" shift is applied to all observations
+necessary to bring relatively faint values up to the brightest
+to account for possible cloud variations.
+
+If invoked as an interactive task from a graphics terminal,
+and the interact switch is set to yes, the sensitivity values
+from each standard are plotted with any "grey" shift correction
+added. The user may delete or add new points as desired using
+the cursor. If invoked from a non-graphics terminal, a tabular
+list of the solution is presented and additions or deletions
+may be entered through the standard input.
+
+The final function written to the std_file is simply the name of a new
+spectrum derived from a spline fit to the sensitivity
+if the spline switch is set to yes. If spline = no, a linear
+interpolation between sensitivity points will be used.
+The sensitivity spectrum name will be taken from the file name
+given to std_file and with the suffix ".sen".
+.ih
+PARAMETERS
+.ls 4 std_file
+See the definition of this parameter under STD_STAR.
+.le
+.ls 4 option
+This parameter can assume the following string values:
+.ls 4 = input
+to indicate that the sensitivity function is to be entered as
+wavelength-sensitivity pairs.
+.le
+.ls 4 = shift
+to force a "grey" shift between all standard star spectra to
+account for clouds. This is actually a multiplicative factor
+across each of the affected spectra.
+.le
+.le
+.ls 4 spline
+This switch parameter determines if a spline fit is to be made
+between the sensitivity points (spline = yes), or a linear
+fit (spline = no). (default = yes).
+.le
+.ls 4 interact
+If invoked as an interactive task, the user may alter the sensitivity
+function values. If at a graphics terminal, the sensitivity curve
+is displayed first for each star in the solution. The user may
+add or delete values for any or all stars at a given wavelength.
+Subsequently, the derived average curve is displayed and the user
+may further modify the solution. The following keystrokes are
+available from the graphics terminal:
+.ls 4 a
+to accept the current displayed data (solution).
+.le
+.ls 4 d
+to delete the value at the cross-hairs. If several values
+are very close together, an expanded display is presented.
+.le
+.ls 4 i
+to insert the sensitivity value of the y-cursor at the wavelength position.
+.le
+.ls 4 c
+to "create" new sensitivity values at the wavelength position of the
+x-cursor. Normally sensitivity values are computed only at pre-defined
+wavelengths specified in the calib_file. Additional values
+may be computed by interpolation of the standard star fluxes
+from the calib_file. The name of the calib_file and the spectra
+in the current solution are taken from the std_file.
+.le
+.le
+
+.ih
+NAME
+ext_cor -- Extinction correct specified spectra
+.ih
+USAGE
+ext_cor [filename, files, std_file, destination]
+.ih
+DESCRIPTION
+The spectra specified by the filename and files parameters
+are corrected for atmospheric extinction according to the
+extinction correction function pointed to by the function
+name in std_file. The resulting new spectra are created with the
+root of the destination parameter and having suffixes of
+1 through n corresponding to the n spectra corrected.
+If filename is a data group name, a new data group will be created having
+the name given by the destination parameter.
+
+The correction has the form:
+.sp 1
+f(lambda) = fobs(lambda) / 10**{-z[a(lambda) + C]}
+.sp 1
+where:
+.ls 4 f(lambda) = the flux at wavelength lambda above the Earth's atmosphere.
+.le
+.ls 4 fobs(lambda) = the flux observed through the atmosphere
+.le
+.ls 4 z = the path length through the atmosphere is units of air masses
+(= 1 at the zenith)
+.le
+.ls 4 a(lambda) = the extinction function at wavelength lambda
+in magnitudes per airmass.
+.le
+.ls 4 C = the additive constant, if any, in magnitudes per airmass.
+.le
+.sp 1
+For each spectrum, the zenith distance must be present in the image header.
+This is assumed to be correct for the beginning of the observation.
+For short exposures, this is adequate for the correction, but for
+long exposures, an effective air mass must be calculated over the
+integration. To do so requires knowledge of the altitude and azimuth
+of the telescope (or equivalantly RA, Dec, and sidereal time).
+If these are not present, the approximate air mass calculation will be used
+based solely on the available zenith distance. If the zenith distance
+is not present, user input is requested.
+
+The air mass is calculated according to the following equation for a given
+telescope position (based on Allen p.125,133):
+.sp 1
+z = sqrt{[q sin (alt)]**2 + 2q + 1} - q sin(alt)
+.sp 1
+where:
+.ls 4 q
+= atmospheric scale height (approx = 750).
+.le
+.ls 4 alt
+= telescope altitude
+.le
+.sp 1
+If the telescope traverses a significant distance in elevation during
+the integration, an effective correction can be computed as:
+.sp 1
+f(lambda)a = f(lambda)obs*T / integral{10**[-z(t)(a(lambda) + c)]}dt
+.sp 1
+where the integral is over the integration time, T.
+
+This expression can then be evaluated numerically at each wavelength.
+Because this is a time-consuming operation, the switch effective_cor
+can be set to no and then a simplified correction scheme will be used.
+This will be to compute a midpoint airmass if sufficient information
+is available, or simply to use the header airmass otherwise.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 std_file
+See the definition of this parameter under STD_STAR.
+.le
+.ls 4 destination
+See the definition of this parameter under COIN_COR.
+.le
+.ls 4 effective_cor
+If this switch is set to yes, the procedure to compute an effective
+corrective term averaged over the integration time will be used.
+Although a slow process, this method is more accurate than
+simply using the correction at any given time of the integration
+such as the midpoint. If set to no, a midpoint zenith distance
+will be computed and used if sufficient header information
+exists. (default = no).
+.le
+.ls 4 print_header
+See the definition of this parameter for COIN_COR.
+.le
+
+.ih
+NAME
+sens_cor -- Correct the specified spectra for system sensitivity
+variations across the spectrum.
+.ih
+USAGE
+sens_cor [filename, files, std_file, destination]
+.ih
+DESCRIPTION
+The spectra specified by the filename and files parameters are
+corrected for instrumental sensitivity by the
+function pointed to by the spectrum name contained in std_file.
+The resulting spectra are stored according to the destination parameter.
+Filename may be a data group name. If so, then destination will be
+a new data group containing the names of the corrected spectra.
+
+This correction is a simple vector multiplcation.
+.ih
+PARAMETERS
+.ls 4 filename
+See the definition of this parameter under RFITS.
+.le
+.ls 4 files
+See the definition of this parameter under RFITS.
+.le
+.ls 4 std_file
+See the definition of this parameter under STD_STAR.
+.le
+.ls 4 destination
+See the definition of this parameter under COIN_COR.
+.le
+.ls 4 print_header
+See the definition of this parameter under COIN_COR.
+.le
+.endhelp
diff --git a/noao/onedspec/doc/sys/Review.hlp b/noao/onedspec/doc/sys/Review.hlp
new file mode 100644
index 00000000..5139f630
--- /dev/null
+++ b/noao/onedspec/doc/sys/Review.hlp
@@ -0,0 +1,512 @@
+.help onedspec Sep84 "Spectral Reductions"
+.ce
+\fBOne Dimensional Spectral Reductions\fR
+.ce
+Analysis and Discussion
+.ce
+September 4, 1984
+.sp 3
+.nh
+Introduction
+
+ The \fBonedspec\fR package is a collection of programs for the reduction
+and analysis of one dimensional spectral data. The more general problem of
+operations upon one dimensional images or vectors shall be dealt with elsewhere,
+primarily in the \fBimages\fR and \fBplot\fR packages. The problems of getting
+data in and out of the system are handled by the \fBdataio\fR package, at least
+for the standard data formats such as FITS.
+
+The operators provided in \fBonedspec\fR shall be general purpose and, as far
+as possible, independent of the instrument which produced the data. Instrument
+dependent reductions tailored for specific instruments will be implemented as
+subpackages of the \fBimred\fR (image reductions) package. For example,
+the subpackages \fBiids\fR and \fBirs\fR will be provided in \fBimred\fR for
+reducing data from the KPNO instruments of the same name. The \fBimred\fR
+packages shall call upon the basic operators in \fBonedspec\fR, \fBimages\fR,
+and other packages to reduce the data for a specific instrument.
+
+
+.ks
+.nf
+ iids(etc)
+ imred
+ imredtools
+ onedspec
+ plot
+ tv
+ dataio
+ images
+ dbms
+ lists
+ system
+ language
+
+.fi
+.ce
+Relationship of \fBOnedspec\fR to other IRAF Packages
+.ke
+
+
+The relationship of the \fBonedspec\fR packages to other related packages in
+the IRAF system is shown above. A program (CL script) in a package at one
+level in the hierarchy may only call programs in packages at lower levels.
+The system will load packages as necessary if not already loaded by the
+user. The user is expected to be familiar with the standard system packages.
+
+.nh
+Basic Functions Required for One-Dimensional Spectral Reductions
+
+ The following classes of functions have been identified (in the preliminary
+specifications document for \fBonedspec\fR) as necessary to perform basic one
+dimensional spectral reductions. Only a fraction of the functionality
+required is specific to the reduction of spectral data and is therefore
+provided by the \fBonedspec\fR package itself.
+
+.ls Transport
+Provided by the \fBdataio\fR package, although we do not currently have a
+reader for REDUCER format data tapes. Readers for all standard format
+tapes are either available or planned.
+.le
+.ls Mathematical
+Standard system functions provided by \fBimages\fR (arithmetic, forward and
+inverse FFT, filtering, etc.).
+.le
+.ls Reduction Operators
+The heart of \fBonedspec\fR. Operators are required (at a minimum) for
+coincidence correction, dispersion determination and correction, flat
+fielding, sky subtraction, extinction correction, and flux calibration.
+Operators for flat fielding and sky subtraction are already available elsewhere
+in IRAF. Basic continuum fitting and subtraction is possible with existing
+software but additional algorithms designed for spectral data are desirable.
+.le
+.ls Plotting
+Standard system functions provided by the \fBplot\fR package.
+.le
+.ls Utilities
+Standard system functions provided by the \fBdbms\fR package.
+.le
+.ls Artificial Spectra
+These functions belong in the \fBartdata\fR package, but it is expected that
+prototype operators will be built as part of the initial \fBonedspec\fR
+development.
+.le
+
+.nh
+Data Structures
+
+ Spectra will be stored as one or two dimensional IRAF images embedded in
+database format files. A free format header is associated with each image.
+Spectra may be grouped together as lines of a two dimensional image provided
+all can share the same header, but more commonly each image will contain a
+single spectrum. The second image dimension, if used, will contain vectors
+directly associated with the images, such as a signal to noise vector.
+If the image is two dimensional the spectrum must be the first image line.
+The database facilities will allow images to be grouped together in a single
+file if desired.
+
+While most or all \fBonedspec\fR operators will expect a one dimensional
+image as input, image sections may be used to operate on vector subsections
+of higher dimensioned images if desired. The datatype of an image is
+arbitrary, but all pixel data will be single precision real within
+\fBonedspec\fR. While the IRAF image format does not impose any restrictions on
+the size of an image or image line, not all spectral operators may be usable
+on very large images. In general, pointwise and local operations may easily
+be performed on images of any size with modest memory requirements, and
+most of the \fBonedspec\fR operations appear to fall into this class.
+
+.nh 2
+The IRAF Database Faciltities
+
+ An understanding of the IRAF database facilities is necessary to visualize
+how data will be treated by operators in \fBonedspec\fR and other packages.
+The database facilities will be used not just for image storage but also for
+program intercommunication, program output, and the storage of large
+astronomical catalogs (e.g. the SAO catalog). Access to both small and
+large databases will be quite efficient; achieving this requires little
+innovation since database technology is already highly developed. We begin by
+defining some important terms.
+
+.ls
+.ls DBIO
+The database i/o package, used by compiled programs to access a database.
+.le
+.ls DBMS
+The database management package, a CL level package used by the user to
+inspect, analyze, and manipulate the contents of a database.
+.le
+.ls database
+A set of one or more "relations" or tables (DBIO is a conventional relational
+database). A convenient way to think of an IRAF database is as a directory.
+The relations appear as distinct files in the directory.
+.le
+.ls relation
+A relation is a set of \fBrecords\fR. Each record consists of a set of
+\fBfields\fR, each characterized by a name and a datatype. All the records
+in a relation have the same set of fields. Perhaps the easiest way to
+visualize a relation is as a \fBtable\fR. The rows and columns of the table
+correspond to the records and fields of the relation.
+.le
+.ls field
+A field of a record is characterized by an alphanumeric name, datatype, and
+size. Fields may be one dimensional arrays of variable size. Fields may be
+added to a relation dynamically at run time. When a new field is added to
+a relation it is added to all records in the relation, but the value of the
+field in a particular record is undefined (and consumes no storage) until
+explicitly written into.
+.le
+.ls key
+.br
+A function of the values of one or more fields, used to select a subset of
+rows from a table. Technically, a valid key will permit selection of any
+single row from a table, but we often use the term is a less strict sense.
+.le
+.le
+
+
+An \fBimage\fR appears in the database as a record. The record is really
+just the image header; the pixels are stored external to the database in a
+separate file, storing only the name of the pixel storage file in the record
+itself (for very small images we are considering storing the pixels directly
+in the database file). Note that the record is a simple flat structure;
+this simple structure places restrictions on the complexity of objects which
+can be stored in the database.
+
+The records in a relation form a set, not an array. Records are referred to
+by a user-defined key. A simple key might be a single field containing a
+unique number (like an array index), or a unique name. More complex keys
+might involve pattern matching over one or more fields, selection of records
+with fields within a certain range of values, and so on.
+
+From the viewpoint of \fBonedspec\fR, a relation can be considered a
+\fBdata group\fR, consisting of a set of \fBspectra\fR.
+
+.nh 2
+Image Templates
+
+ The user specifies the set of spectra to be operated upon by means of an
+image template. Image templates are much like the filename templates commonly
+used in operating systems. The most simple template is the filename of
+a single data group; this template matches all spectra in the group. If there
+is only one spectrum in a file, then only one spectrum is operated upon.
+A slightly more complex template is a list of filenames of data groups.
+More complex templates will permit use of expressions referencing the values
+of specific fields to select a subset of the spectra in a group. The syntax
+of such expressions has not yet been defined (examples are given below
+nonetheless), but the function performed by an image template will be the same
+regardless of the syntax. In all cases the image template will be a single
+string valued parameter at the CL level.
+
+.nh 2
+Standard Calling Sequence
+
+ The standard calling sequence for a unary image operator is shown below
+The calling sequence for a binary operator would be the same with a second input
+parameter added as the second argument. In general, any data dependent
+control parameters should be implemented as positional arguments following
+the primary operands, and data independent or optional (rarely used) parameters
+should be implemented as hidden parameters.
+
+
+.ks
+.nf
+ imop (input, output, data_dependent_control_params)
+
+ imop image operator name
+ input image template specifying set of input images
+ output filename of output datagroup
+
+ data_dependent_control_parameters
+ (hidden parameters)
+
+for example,
+
+ coincor (spectra, newgroup, dead_time)
+.fi
+.ke
+
+
+If a series of spectra are to be processed it seems reasonable to add the
+processed spectra to a new or existing data group (possibly the same as an
+input datagroup). If the operation is to be performed in place a special
+notation (e.g. the null string) can be given as the output filename.
+At the \fBonedspec\fR level output filenames will not be defaulted.
+
+.nh 2
+Examples
+
+ Some examples of image templates might be useful to give a more concrete
+idea of the functionality which will be available. Bear in mind that what we
+are describing here is really the usage of one of the fundamental IRAF system
+interfaces, the DBMS database management subsystem, albeit from the point of
+view of \fBonedspec\fR. The same facilities will be available in any program
+which operates upon images, and in some non-image applications as well (e.g.
+the new \fBfinder\fR). Our philosopy, as always, is to make standard usage
+simple, with considerable sophistication available for those with time to
+learn more about the system.
+
+The simplest case occurs when there is one spectrum per data group (file).
+For example, assuming that the file "a" contains a single spectrum, the
+command
+
+ cl> coincor a, b, .2
+
+would perform coincidence correction for spectrum A, placing the result in
+B, using a dead time parameter of .2. For a more complex example, consider
+the following command:
+
+ cl> coincor "a.type=obj&coincor=no,b", a, .2
+
+This would perform coincidence correction for all spectra in group B plus all
+object spectra in group A which have not already been coincidence corrected,
+adding the corrected spectra to group A (notation approximate only). If the
+user does not trust the database explicit record numbers may be used and
+referenced via range list expressions, e.g.,
+
+ cl> coincor "a.recnum=(1,5,7:11),b", a, .2
+
+would select records 1, 5, and 7 through 11 from data group A. Alternatively
+the database utilities could be used to list the spectra matching the selection
+criteria prior to the operation if desired. For example,
+
+ cl> db.select "a.type=obj"
+
+would write a table on the standard output (the terminal) wherein each spectrum
+in data group A is shown as a row of field values. If one wanted to generate
+an explicit list of records to be processed with help from the database
+utilities, a set of records could be selected from a data group and selected
+fields from each record written into a text file:
+
+ cl> db.select "a.type=obj", "recnum, history" > reclistfile
+
+The output file "reclistfile" produced by this command would contain the
+fields "recnum" (record number) and "history" (description of processing
+performed to generate the record). The editor could be used to delete
+unwanted records, producing a list of record numbers suitable for use as
+an image template:
+
+ cl> coincor "a.recnum=@reclistfile", a, .2
+
+.nh
+Reduction Operators
+
+.nh 2
+Line List Preparation
+
+ I suggest maintaining the line lists as text files so that the user can
+edit them with the text editor, or process them with the \fBlists\fR operators.
+A master line list might be maintained in a database and the DBMS \fBselect\fR
+operator used to extract ASCII linelists in the wavelength region of interest,
+but this would only be necessary if the linelist is quite large or if a linelist
+record contains many fields. I don't think we need the \fBline_list\fR task.
+
+.nh 2
+Dispersion Solution
+
+ The problem with selecting a line list and doing the dispersion solution
+in separate operations is that the dispersion solution is invaluable as an aid
+for identifying lines and for rejecting lines. Having a routine which merely
+tweaks up the positions of lines in an existing lineset (e.g., \fBalinid\fR)
+is not all that useful. I would like to suggest the following alternate
+procedure for performing the dispersion solution for a set of calibration
+spectra which have roughly the same dispersion.
+
+.ls
+.ls [1] Generate Lineset [and fit dispersion]
+.sp
+Interactively determine the lineset to be used, i.e., wavelength (or whatever)
+and approximate line position in pixel units for N lines. Input is one or more
+comparison spectra and optionally a list of candidate lines in the region
+of interest. Output is the order for the dispersion curve and a linelist of
+the following (basic) form:
+
+ L# X Wavelength [Weight]
+
+It would be very useful if the program, given a rough guess at the dispersion,
+could match the standard linelist with the spectra and attempt to automatically
+identify the lines thus detected. The user would then interactively edit the
+resultant line set using plots of the fitted dispersion curve to reject
+misidentified or blended lines and to adjust weights until a final lineset
+is produced.
+.le
+
+.ls [2] Fit Dispersion
+.sp
+Given the order and functional type of the curve to be fitted and a lineset
+determined in step [1] (or a lineset produced some any other means, e.g. with
+the editor), for each spectrum in the input data group tweak the center of
+each line in the lineset via an automatic centering algorithm, fit the
+dispersion curve, and save the coefficients of the fitted curve in the
+image header. The approximate line positions would be used to find and measure
+the positions of the actual lines, and the dispersion curve would be fitted and
+saved in the image header of each calibration spectrum.
+
+While this operator would be intended to be used noninteractively, the default
+textual and graphics output devices could be the terminal. To use the program
+in batch mode the user would redirect both the standard output and the graphics
+output (if any), e.g.,
+
+.nf
+ cl> dispsol "night1.type=comp", linelistfile, order,
+ >>> device=stdplot, > dispsol.spool &
+.fi
+
+Line shifts, correlation functions, statistical errors, the computed residuals
+in the fitted dispersion curves, plots of various terms of the dispersion
+curves, etc. may be generated to provide a means for later checking for
+erroneous solutions to the individual spectra. There is considerable room for
+innovation in this area.
+.le
+
+.ls [3] Second Order Correction
+.sp
+If it is desired to interpolate the dispersion curve in some additional
+dimension such as time or hour angle, fit the individual dispersion solutions
+produced by [1] or [2] as a group to one or more additional dimensions,
+generating a dispersion solution of one, two or more dimensions as output.
+If the output is another one dimensional dispersion solution, the input
+solutions are simply averaged with optional weights. This "second order"
+correction to a group of dispersion solutions is probably best performed by
+a separate program, rather than building it into \fBalineid\fR, \fBdispsol\fR,
+etc. This makes the other programs simpler and makes it possible to exclude
+spectra from the higher dimensional fit without repeating the dispersion
+solutions.
+.le
+.le
+
+If the batch run [2] fails for selected spectra the dispersion solution for
+those spectra can be repeated interactively with operator [1].
+The curve fitting package should be used to fit the dispersion curve (we can
+extend the package to support \fBonedspec\fR if necessary).
+
+.nh 2
+Dispersion Correction
+
+ This function of this procedure is to change the dispersion of a
+spectrum or group of spectra from one functional form to another.
+At a mimimum it must be possible to produce spectra linear in wavelength or
+log wavelength (as specified), but it might also be useful to be able
+to match the dispersion of a spectrum to that of a second spectrum, e.g., to
+minimize the amount of interpolation required to register spectra, or
+to introduce a nonlinear dispersion for testing purposes. This might be
+implemented at the CL parameter level by having a string parameter which
+takes on the values "linear" (default), "log", or the name of a record
+defining the dispersion solution to be matched.
+
+It should be possible for the output spectrum to be a different size than
+the input spectrum, e.g., since we are already interpolating the data,
+it might be nice to produce an output spectrum of length 2**N if fourier
+analysis is to be performed subsequently. It should be possible to
+extract only a portion of a spectrum (perform subraster extraction) in the
+process of correcting the dispersion, producing an output spectrum of a
+user-definable size. It should be possible for an output pixel to lie at
+a point outside the bounds of the input spectrum, setting the value of the
+output pixel to INDEF or to an artificially generated value. Note that
+this kind of generality can be implemented at the \fBonedspec\fR level
+without compromising the simplicity of dispersion correction for a particular
+instrument at the \fBimred\fR level.
+
+.nh 3
+Line Centering Algorithms
+
+ For most data, the best algorithm in the set described is probably the
+parabola algorithm. To reject nearby lines and avoid degradation of the
+signal to noise the centering should be performed within a small aperture,
+but the aperture should be allowed to move several pixels in either direction
+to find the peak of the line.
+
+The parabola algorithm described has these features,
+but as described it finds the extrema within a window about the
+initial position. It might be preferable to simply walk up the peak nearest
+to the initial center. This has the advantage that it is possible to center
+on a line which has a nearby, stronger neighbor which cannot itself be used
+for some reason, but which might fall within \fBparextent\fR pixels of the
+starting center. The parabola algorithm as described also finds a local extrema
+rather than a local maximum; probably not what is desired for a dispersion
+solution. The restriction to 3 pixels in the final center determination is
+bad; the width of the centering function must be a variable to accommodate
+the wide range of samplings expected.
+
+The parabola algorithm described is basically a grid search over
+2*\fIparextent\fR pixels for the local extrema. What I am suggesting is
+an iterative gradient search for the local maximum. The properties of the
+two algorithms are probably sufficiently different to warrant implementation
+of both as an option (the running times are comparable). I suspect that
+everyone else who has done this will have their own favorite algorithm as
+well; probably we should study half a dozen but implement only one or two.
+
+.nh 2
+Field Flattening
+
+ It is not clear that we need special flat fielding operators for
+\fBonedspec\fR. We have a two-dimensional operator which fits image lines
+independently which might already do the job. Probably we should experiment
+with both the smoothing spline and possibly fourier filtering for removing
+the difficult medium frequency fluctuations. The current \fBimred\fR flat field
+operator implements the cubic smoothing spline (along with the Chebyshev and
+Legendre polynomials), and is available for experimentation.
+
+Building interactive graphics into the operator which fits a smooth curve to
+the continuum is probably not necessary. If a noninteractive \fBimred\fR or
+\fBimages\fR operator is used to fit the continuum the interactive graphics
+can still be available, but might better reside in a higher level CL script.
+The basic operator should behave like a subroutine and not write any output
+to the terminal unless enabled by a hidden parameter (we have been calling
+this parameter \fIverbose\fR in other programs).
+
+.nh 3
+Extinction Correction and Flux Calibration
+
+ I did not have time to review any of this.
+
+.nh
+Standard Library Packages
+
+ The following standard IRAF math library packages should be used in
+\fBonedspec\fR. The packages are very briefly described here but are
+fully documented under \fBhelp\fR on the online (kpnob:xcl) system.
+
+.nh 2
+Curve Fitting
+
+ The curve fitting package (\fBcurfit\fR) is currently capable of fitting
+the Chebyshev and Legendre polynomials and the cubic smoothing spline.
+Weighting is supported as an option.
+We need to add a piecewise linear function to support the
+dispersion curves for the high resolution FTS spectra. We may have to add a
+double precision version of the package to provide the 8-10 digits of
+precision needed for typical comparison line wavelength values, but
+normalization of the wavelength values may make this unnecessary for moderate
+resolution spectra.
+
+Ordinary polynomials are not supported because their numerical properties are
+very much inferior to those of orthogonal polynomials (the ls matrix can have
+a disastrously high condition number, and lacking normalization the function
+begin fitted is not invariant with respect to scale changes and translations
+in the input data). For low order fits the Chebyshev polynomials are
+considered to have the best properties from an approximation theoretic point
+of view, and for high order fits the smoothing spline is probably best because
+it can follow arbitrary trends in the data.
+
+.nh 2
+Interpolation
+
+ The image interpolation package (\fBiminterp\fR) currently supports the
+nearest neighbor, linear, third and fifth order divided differences,
+cubic interpolating spline, and sinc function interpolators.
+We should add the zeroth and first order partial pixel ("flux conserving")
+interpolants because they offer unique properties not provided by any
+of the other interpolants.
+
+.nh 2
+Interactive Graphics
+
+ We will define a standard interactive graphics utility package for
+interactive operations upon data vectors (to be available in a system library
+in object form). It should be possible to define a general package which
+can be used anywhere a data vector is to be plotted and
+examined interactively (not just in \fBonedspec\fR). Standard keystrokes
+should be defined for common operations such as expanding a region of
+the plot and restoring the original scale. This will not be attempted
+until an interactive version of the GIO interface is available later this
+fall.
+.endhelp
diff --git a/noao/onedspec/doc/sys/TODO b/noao/onedspec/doc/sys/TODO
new file mode 100644
index 00000000..0dfa136b
--- /dev/null
+++ b/noao/onedspec/doc/sys/TODO
@@ -0,0 +1,28 @@
+scombine:
+ 1. Combine with weights:
+ By signal level
+ By sigma spectrum
+
+doc:
+ Install SENSFUNC memo in the doc directory. (8/14)
+
+calibrate:
+ Have calibrate apply neutral density filter function. This may also
+ have to be included in STANDARD and SENSFUNC. (2/25/87)
+
+splot:
+ Add a deblend option for PCYGNI profiles. (Tyson, 3/19/87)
+
+Tim Heckman (U. Maryland) came by with questions and requests
+concerning deblending in SPLOT. Tim's comments are indicated in
+quotations.
+
+2. "The deblending should allow additional constraints if known.
+Specifically fixing the ratios of lines based on atomic physics."
+
+3. "The deblending should provide some uncertainty estimates." I added
+that there has also been a request to use known statistics in the
+pixel data themselves to generate uncertainty estimates.
+
+4. "It would be useful to provide other choices for the profile rather
+than just gaussians."
diff --git a/noao/onedspec/doc/sys/coincor.ms b/noao/onedspec/doc/sys/coincor.ms
new file mode 100644
index 00000000..1b4d29cc
--- /dev/null
+++ b/noao/onedspec/doc/sys/coincor.ms
@@ -0,0 +1,46 @@
+.EQ
+delim $$
+.EN
+.OM
+.TO
+IIDS Users
+.FR
+F. Valdes
+.SU
+IIDS count rate corrections
+.PP
+The IRAF task \fBcoincor\fR transforms the observed count rates to
+something proportional to the input count rate. The correction applied
+to the observed count rates depends upon the count rate and is instrument
+dependent. One correction common to photomultiplier detectors and the
+IIDS is for coincident events, which is the origin of the task name.
+The parameter \fIccmode\fR selects a particular type of correction.
+The value \fIccmode\fR = "iids" applies the following transformation to
+observed IIDS count rates.
+
+.EQ (1)
+ C sup ' ~=~(- ln (1- deadtime C)/ deadtime ) sup power
+.EN
+
+where $C$ is the orginal count rate, $C sup '$ is the corrected count
+rate, and $deadtime$ and $power$ are \fBcoincor\fR parameters. The term
+inside the parenthesis is the correction for dead-time in the counting
+of coincident events on the back phospher of the image tube. The power
+law correction is due to the non-linearity of the IIDS image tube chain.
+.PP
+The correction applied with the Mountain Reduction Code is only for
+coincidences, i.e. equation (1) with $power = 1$. To obtain just this
+correction with \fBcoincor\fR set $power = 1$. To take mountain reduced
+data and correct only for the non-linearity set \fIccmode\fR = "power".
+With raw IIDS data use \fBcoincor\fR with the default
+parameters.
+
+.LP
+References:
+.IP (1)
+L. Goad, \fBSPIE 172\fR, 1979, p. 86.
+.IP (2)
+G. Jacoby, Some Notes on the ONEDSPEC Package, \fBIRAF Handbook\fR
+.IP (3)
+P. Massey and J. De Veny, How Linear is the IIDS, \fBNOAO Newsletter\fR,
+#6, June 1986.
diff --git a/noao/onedspec/doc/sys/identify.ms b/noao/onedspec/doc/sys/identify.ms
new file mode 100644
index 00000000..6a69204b
--- /dev/null
+++ b/noao/onedspec/doc/sys/identify.ms
@@ -0,0 +1,347 @@
+.RP
+.TL
+Radial Velocity Measurements with IDENTIFY
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+August 1986
+Revised August 1990
+.AB
+The IRAF task \fBidentify\fP may be used to measure radial velocities.
+This is done using the classical method of determining
+the doppler shifted wavelengths of emission and absorption lines.
+This paper covers many of the features and techniques available
+through this powerful and versatile task which are not immediately
+evident to a new user.
+.AE
+.sp 3
+.NH
+\fBIntroduction\fP
+.PP
+The task \fBidentify\fP is very powerful and versatile. It can
+be used to measure wavelengths and wavelength shifts for
+doing radial velocity measurements from emission and
+absorption lines. When combined with the CL's ability
+to redirect input and output both from the standard text
+streams and the cursor and graphics streams virtually
+anything may be accomplished either interactively or
+automatically. This, of course, requires quite a bit of
+expertise and experience with \fBidentify\fP and with
+the CL which a new user is not expected to be aware of initially.
+This paper attempts to convey some of the possibilities.
+There are many variations on these methods which the user
+will learn through experience.
+.PP
+I want to make a caveat about the suggestions made in
+this paper. I wrote the \fBidentify\fP task and so I am
+an expert in its use. However, I am not a spectroscopist,
+I have not been directly involved in the science of
+measuring astronomical radial velocities, and I am not
+very familiar with the literature. Thus, the suggestions
+contained in this paper are based on my understanding of
+the basic principles and the abilities of the \fBidentify\fP
+task.
+.PP
+The task \fBidentify\fP is used to measure radial velocities
+by determining the wavelengths of individual emission
+and absorption lines. The user must compute the
+radial velocities separately by relating the observed
+wavelengths to the known rest wavelengths via the Doppler
+formula. This is a good method when the lines are
+strong, when there are only one or two features, and
+when there are many, possibly, weaker lines. The
+accuracy of this method is determined by the accuracy
+of the line centering algorithm.
+.PP
+The alternative method is to compare an observed spectrum
+to a template spectrum of known radial velocity. This
+is done by correlation or fourier ratio methods. These
+methods have the advantage of using all of the spectrum
+and are good when there are many very weak and possibly
+broad features. Their disadvantages are confusion
+with telluric lines, they don't work well with just a
+few real features, and they require a fair amount of
+preliminary manipulation of the spectrum to remove
+continuum and interpolate the spectrum in logarithmic
+wavelength intervals. IRAF tasks for correlation
+and fourier ratio methods are under development at
+this time. Many people assume that these more abstract
+methods are inherently better than the classical method.
+This is not true, it depends on the quality and type of
+data.
+.PP
+Wavelength measurements are best done on the original
+data rather than after linearizing the wavelength
+intervals. This is because 1) it is not necessary as
+will be shown below and 2) the interpolation used to
+linearize the wavelength scale can change the shape
+of the lines, particularly strong, narrow emission
+lines which are the best ones for determining radial
+velocities.
+.PP
+This paper is specifically about \fBidentify\fP but one
+should be aware of the task \fBsplot\fP which also may
+be used to measure radial velocities. It differs in
+several respects from \fBidentify\fP. \fBSplot\fP works
+only on linearized data; the wavelength and pixel
+coordinates are related by a zero point and wavelength
+interval. The line centering algorithms are different;
+the line centering is generally less robust (tolerant
+of error) and often less accurate. It has many nice
+features but is not designed for the specific purpose
+of measuring positions of lines and, thus, is not as
+easy to use for this purpose.
+.PP
+There are a number of sources of additional information
+relating to the use of the task \fBidentify\fP. The
+primary source is the manual pages for the task. As
+with all manual pages it is available online with the
+\fBhelp\fP command and in the \fIIRAF User Handbook\fP.
+The NOAO reduction guides or cookbooks for the echelle
+and IIDS/IRS include additional examples and discussion.
+The line centering algorithm is the most critical
+factor in determining dispersion solutions and radial
+velocities. It is described in more detail under the
+help topic \fBcenter1d\fP online or in the handbook.
+.NH
+Method 1
+.PP
+In this method, arc calibration images are used to determine
+a wavelength scale. The dispersion solution is then transferred
+to the object spectrum and the wavelengths of emission and
+absorption lines are measured and recorded. This is
+relatively straightforward but some tricks will make this easier
+and more accurate.
+.NH 2
+Transferring Dispersion Solutions
+.PP
+There are several ways to transfer the dispersion solution
+from an arc spectrum to an object spectrum differing in the
+order in which things are done.
+.IP (1)
+One way is to determine the dispersion solution for all the arc images
+first. To do this interactively specify all the arc images as the
+input to \fBidentify\fP. After determining the dispersion solution for
+the first arc and quitting (\fIq\fP key) the next arc will be displayed
+with the previous dispersion solution and lines retained. Then use the
+cursor commands \fIa\fP and \fIc\fP (all center) to recenter and
+\fIf\fP (fit) to recompute the dispersion solution. If large shifts
+are present use \fIs\fP (shift) or \fIx\fR (correlate peaks) to shift,
+recenter, and compute a wavelength zero point shift to the dispersion
+function. A new dispersion function should then be fit with \fIf\fP.
+These commands are relatively fast and simple.
+.IP
+An important reason for doing all the arc images first
+is that the same procedure can be done mostly noninteractively
+with the task \fBreidentify\fP. After determining a
+dispersion solution for one arc image \fBreidentify\fP
+does the recenter (\fIa\fP and \fIc\fP), shift and
+recenter (\fIs\fP), or correlation features, shift, and
+recenter (\fIx\fP) to transfer the dispersion solutions
+between arcs. This is usually done as a background task.
+.IP
+To transfer the solution to the object spectra specify
+the list of object spectra as input to \fBidentify\fP.
+For each image begin by entering the colon command
+\fI:read arc\fP where arc is the name of the arc image
+whose dispersion solution is to be applied; normally
+the one taken at the same time and telescope position as
+the object. This will read the dispersion solution and arc
+line positions. Delete the arc line positions with the
+\fIa\fP and \fId\fP (all delete) cursor keys. You
+can now measure the wavelengths of lines in the spectrum.
+.IP (2)
+An alternative method is to interactively alternate between
+arc and object spectra either in the input image list or
+with the \fI:image name\fP colon command.
+.NH 2
+Measuring Wavelengths
+.IP (1)
+To record the feature positions at any time use the \fI:features
+file\fP colon command where \fIfile\fP is where the feature
+information will be written. Repeating this with the same
+file appends to the file. Writing to the database with the
+\fI:write\fP colon command also records this information.
+Without an argument the results are put in a file with
+the same name as the image and a prefix of "id". You
+can use any name you like, however, with \fI:write
+name\fP. The \fI:features\fP command is probably preferable
+because it only records the line information while the
+database format includes the dispersion solution and
+other information not needed for computing radial
+velocities.
+.IP (2)
+Remember that when shifting between emission and absorption
+lines the parameter \fIftype\fP must be changed. This may be done
+interactively with the \fI:ftype emission\fP and \fI:ftype
+absorption\fP commands. This parameter does not need to be
+set except when changing between types of lines.
+.IP (3)
+Since the centering of the emission or absorption line is the
+most critical factor, one should experiment with the parameter
+\fIfwidth\fP. To change this parameter type \fI:fwidth value\fP.
+The positions of the marked features are not changed until a
+center command (\fIc\fP) command is given.
+.IP
+A narrow \fIfwidth\fP is less influenced by blends and wings but
+has a larger uncertainty. A broad \fIfwidth\fP uses all of the
+line profile and is thus stable but may be systematically influenced
+by blending and wings. One possible approach is to measure
+the positions at several values of \fIfwidth\fP and decide which
+value to use or use some weighting of the various measurements.
+You can record each set of measurements with the \fI:fe
+file\fP command.
+.IP (4)
+For calibration of systematic effects from the centering one should
+obtain the spectrum of a similar object with a known radial
+velocity. The systematic effect is due to the fact that the
+centering algorithm is measuring a weighted function of the
+line profile which may not be the true center of the line as
+tabulated in the laboratory or in a velocity standard. By
+using the same centering method on an object with the same line
+profiles and known velocity this effect can be eliminated.
+.IP (5)
+Since the arcs are not obtained at precisely the same time
+as the object exposures, there may be a wavelength shift relative
+to the arc dispersion solution. This may be calibrated from
+night sky lines in the object itself (the night sky lines are
+"good" in this case and should not be subtracted away). There are
+generally not enough night sky lines to act as the primary
+dispersion calibrator but just one can determine a possible
+wavelength zero point shift. Measure the night sky line
+positions at the same time the object lines are measured.
+Determine a zero point shift from the night sky to be
+taken out of the object lines.
+.NH
+Method 2
+.PP
+This method is similar to the correlation method in that a
+template spectrum is used and the average shift relative
+to the template measures the radial velocity. This has the
+advantage of not requiring the user to do a lot of calculations
+(the averaging of the line shifts is done by identify) but is
+otherwise no better than method 1. The template spectrum must
+have the same features as the object spectrum.
+.IP (1)
+Determine a dispersion solution for the template spectrum
+either from the lines in the spectrum or from an arc calibration.
+.IP (2)
+Mark the features to be correlated in the template spectrum.
+.IP (3)
+Transfer the template dispersion solution and line positions
+to an object spectrum using one of the methods described
+earlier. Then, for the current feature, point the cursor near
+the same feature in the object spectrum and type \fIs\fP. The
+mean shift in pixels, wavelength, and fractional wavelength (like
+a radial velocity without the factor of the speed of light)
+for the object is determined and printed. A new dispersion
+solution is determined but you may ignore this.
+.IP (4)
+When doing additional object spectra, remember to start over
+again with the template spectrum (using \fI:read template\fP)
+and not the solution from the last object spectrum.
+.IP (5)
+This procedure assumes that the dispersion solution between
+the template and object are the same. Checks for zero point
+shifts with night sky lines, as discussed earlier, should be
+made if possible. The systematic centering bias, however, is
+accounted for by using the same lines from the template radial
+velocity standard.
+.IP (6)
+One possible source of error is attempting to use very weak
+lines. The recentering may find the wrong lines and affect
+the results. The protections against this are the \fIthreshold\fP
+parameter and setting the centering error radius to be relatively small.
+.NH
+Method 3
+.PP
+This method uses only strong emission lines and works with
+linearized data without an \fBidentify\fP dispersion
+solution; though remember the caveats about rebinning the
+spectra. The recipe involves measuring
+the positions of emission lines. The
+strongest emission lines may be found automatically using
+the \fIy\fP cursor key. The number of emission lines to
+be identified is set by the \fImaxfeatures\fP parameter.
+The emission line positions are then written to a data file
+using the \fI:features file\fP colon command. This may
+be done interactively and takes only a few moments per
+spectrum. If done interactively, the images may be chained
+by specifying an image template. The only trick required
+is that when proceeding to the next spectrum the previous
+features are deleted using the cursor key combination \fIa\fP
+and \fId\fP (all delete).
+.PP
+For a large number of images, on the order of hundreds, this
+may be automated as follows. A file containing the cursor
+commands is prepared. The cursor command format consists
+of the x and y positions, the window (usually window 1), and
+the key stroke or colon command. Because each new image from
+an image template does not restart the cursor command file,
+the commands would have to be repeated for each image in
+the list. Thus, a CL loop calling the task each time with
+only one image is preferable. Besides redirecting the
+cursor input from a command file, we must also redirect the
+standard input for the response to the database save query, the
+standard output to discard the status line information, and ,
+possibly, the graphics to a metacode file which can then be
+reviewed later. The following steps indicate what is to be
+done.
+.IP (1)
+Prepare a file containing the images to be measured (one per line).
+This can usually be done using the sections command to expand
+a template and directing the output into a file.
+.IP (2)
+Prepare a cursor command file (let's call it cmdfile)
+containing the following two lines.
+.RS
+.IP
+.nf
+.ft CW
+1 1 1 y
+1 1 1 :fe positions.dat
+.ft P
+.fi
+.RE
+.IP (3)
+Enter the following commands.
+.RS
+.IP
+.nf
+.ft CW
+list="file"
+while (fscan (list,s1) !=EOF){
+print ("no") \(or identify (sl,maxfeatures=2, cursor="cmdfile",
+>"dev$null", >G "plotfile")
+}
+.ft P
+.fi
+.RE
+.LP
+Note that these commands could be put in a CL script and executed
+using the command
+.sp
+.IP
+.ft CW
+on> cl <script.cl
+.ft P
+.sp
+.PP
+The commands do the following. The first command initializes the
+image list for the loop. The second command is the loop to
+be run until the end of the image file is reached. The
+command in the loop directs the string "no" to the standard
+input of identify which will be the response to the database save
+query. The identify command uses the image name obtained from the list
+by the fscan procedure, sets the maximum number of features to be
+found to be 2 (this can be set using \fBeparam\fP instead), the
+cursor input is taken from the cursor command file, the standard
+output is discarded to the null device, and the STDGRAPH output
+is redirected to a plot file. If the plot file redirection is
+not used, the graphs will appear on the specified graphics
+device (usually the graphics terminal). The plot file can then
+be disposed of using the \fBgkimosaic\fP task to either the
+graphics terminal or a hardcopy device.
diff --git a/noao/onedspec/doc/sys/onedproto.ms b/noao/onedspec/doc/sys/onedproto.ms
new file mode 100644
index 00000000..b1b05201
--- /dev/null
+++ b/noao/onedspec/doc/sys/onedproto.ms
@@ -0,0 +1,1673 @@
+.RP
+.ND
+.TL
+Some Notes on the ONEDSPEC Package
+.AU
+G. Jacoby
+.AI
+.K2 "" "" "*"
+June 1985
+.AB
+The first phase of the ONEDSPEC prototype package is complete.
+Comments and some internal description is presented for each task
+in the package. Also presented are some more global descriptions
+of strategies used in the package and considerations for future
+improvements.
+.AE
+.SH
+1. Why is ONEDSPEC Different?
+.PP
+This section describes some of the ways in which the ONEDSPEC
+package diverges from other IRAF package strategies.
+A few of these should someday be modified to more closely
+adhere to IRAF conventions, but in other cases, restrictions
+or limitations in the IRAF system are revealed.
+.sp 1
+.SH
+Quantity
+.PP
+One of the major differences between a two dimensional image processing
+package and a one dimensional package is that spectra
+frequently congregate in groups of hundreds to thousands while two-dimensional
+images live in groups of tens to hundreds. What this means is that spectral
+processing must be somewhat more automated and streamlined - the software cannot
+rely on user input to provide assistance and it cannot afford
+excessive overhead; otherwise a large fraction of the processing time will be
+spent where it is least useful.
+.PP
+To process large volumes of spectra in a reasonably automated fashion,
+the software must be smart enough to know what to do with a variety
+of similar but different spectra. The way adopted here is to key
+off header parameters which define the type of spectrum and the
+processing required. In fact, most of the ONEDSPEC package will not
+work smoothly without some header parameter information.
+.PP
+It is also important that each task be self-reliant so that the
+overhead of task stop and restart is avoided. For many operations,
+the actual computation time is a fraction of a second, yet no
+operation in the ONEDSPEC package is faster than one second per spectrum
+due to task overhead. If task startup and stop were required for each
+spectrum, then the overhead would be much worse.
+.PP
+So the philosophy is one in which each task uses as much information
+as it can reasonably expect from the spectral image header.
+Usually this is not more than three or four elements.
+The strategy of using header information should not be limited to
+ONEDSPEC. Many image processing problems can be automated
+to a large degree if header information is used. The success of
+the KPNO CCD Mountain reduction system emphasizes this point.
+It would seem prudent that other IRAF applications make use of
+such information when possible.
+[See section 3 for a more detailed discussion of headers.]
+.sp 1
+.SH
+Spectral Image Names
+.PP
+One implication of the quantity problem is that it must be easy for the user to
+specify the names of large numbers of spectra. The approach taken for ONEDSPEC
+was to assign a root name to a group of spectra and then
+append an index number of 4 or more digits starting with 0000.
+So spectra, by default, have the form root.0000, root.0001, ...
+To specify the spectra, the user types only the root name and the range
+of indices such as "root" and "0-99,112-113,105-108".
+The range decoder accesses the spectral indices in the order given
+as opposed to access in ascending order, so that the spectrum root.0112
+will be processed before root.0105 in the example specification above.
+Spectra having more general names may be specified using the
+standard IRAF filename expansion methods if the
+the range specification is given as null.
+.PP
+The specification of large numbers of images is an area where
+most IRAF applications are weak. Resorting to odd combinations
+of bracket and backslash characters in filename specifications
+is obscure to new users and still fails to
+meet the general need. The range specification adopted for ONEDSPEC
+comes closer but introduces a fixed image name format.
+.sp 1
+.SH
+Apertures -- A way to group data
+.PP
+Many spectrographs generate multiple spectra simultaneously by
+placing more than one slit or aperture in the focal plane.
+Examples include the IIDS, IRS, and Cryogenic Camera in use
+at Kitt Peak. The Echelle may be considered a multi-aperture
+instrument for purposes of reductions by associating each order
+with an "aperture" number.
+.PP
+The concept of aperture can be generalized to indicate a set of
+spectral data having common group properties such as
+wavelength coverage. Most tasks in ONEDSPEC will key off
+an aperture number in the image header and treat those
+common aperture spectra uniformly.
+Defining data groups which are to be processed in this fashion
+is a technique not generally exploited by reduction programs.
+This is due in part to the problem of image header usage.
+.PP
+For programming convenience and to avoid an additional level
+of indirectness, in ONEDSPEC the aperture number is used directly as an
+index in many static arrays. The current implementation has
+a declaration for 50 apertures and due to the IIDS/IRS
+notation of apertures 0 and 1, the apertures are zero-indexed, contrary
+to standard IRAF nomenclature,
+from 0-49. It would certainly be better to map the aperture numbers
+to the allowable index range, but the added complexity of another
+level of indirectness seemed distracting. Actually the mapping
+can still be done by the header reader, "load_ids_hdr", and
+unmapped by the header writer, "store_keywords".
+.sp 1
+.SH
+Static versus dynamic arrays
+.PP
+Although dynamic storage would alleviate some of the memory
+requirements in the package, the use of static arrays aids
+readability and accounts for only about 10 percent of the total
+task memory space. Many of the arrays are arrays of pointers.
+For example, in the task BSWITCH, there is an array (called "imnames")
+of pointers for the names of spectral images, several for each aperture.
+The actual space for the names is dynamically allocated,
+so first we allocate an array of pointers for each
+aperture:
+.sp 1
+.DS
+ call salloc (imnames[aperture], nr_names, TY_POINT)
+.DE
+.sp 1
+Then, for each of these pointers, space must be allocated for the
+character arrays:
+.sp 1
+.DS
+ do i = 1, nr_names
+ call salloc (Memp[imnames[aperture]+i-1], SZ_LINE, TY_CHAR)
+.DE
+.sp 1
+Later to access the character strings, a name is specified as:
+.sp 1
+.DS
+ Memc[Memp[imnames[aperture]+nr_of_this_spectrum-1]]
+.DE
+.sp 1
+If the "imnames" array was also dynamically allocated, the
+above access would be even less readable.
+If memory requirements become a serious problem, then these ONEDSPEC
+tasks should be modified.
+.sp 1
+.SH
+Output image names
+.PP
+To retain the consistent usage of root names and ranges, output
+spectra also have the form root.nnnn. For user convenience,
+the current output root name and next suffix are maintained as
+package parameters onedspec.output and onedspec.next_rec.
+The latter parameter is automatically updated each time a
+new spectrum is written. This is done by the individual tasks
+which directly access this package parameter.
+.PP
+There is an interesting side effect when using indirect parameters
+(e.g. )onedspec.output) for input. In the local task parameter
+file, the mode of the parameter must be declared hidden. So when the user
+does an "lpar task", those parameters appear to be unnecessary
+(that is, they are enclosed in parenthesis). When run,
+prompts appear because the parameter is an automatic mode
+parameter in the package parameter file.
+If run as a background task, this is more annoying.
+Unfortunately, any other choice of parameter modes produces
+less desirable actions.
+.sp 1
+.SH
+ONEDUTIL
+.PP
+As the number of tasks in ONEDSPEC started growing, the
+need for a subdivision of the package became clear.
+The first cut was made at the utility level, and a number
+of task names (not necessarily physical tasks) were
+moved out into the ONEDUTIL submenu. In the future,
+additional tasks will eventually require another subpackage.
+.PP
+Actually, many of the tasks in ONEDUTIL may be more at home
+in some other package, but a conscious effort was made to
+avoid contaminating other IRAF packages with tasks written for
+the ONEDSPEC project. If all the following tasks are relocated,
+then the need for ONEDUTIL is reduced.
+.PP
+Two of the entries in ONEDUTIL may be considered as more appropriate
+to DATAIO - RIDSMTN and WIDSTAPE. In fact RIDSMTN can
+replace the version currently in DATAIO. WIDSTAPE may replace the
+DATAIO task WIDSOUT if the usage of header parameters does not
+present a problem.
+.PP
+The task MKSPEC may be a candidate for the ARTDATA package.
+It should be enhanced to include optional noise generation.
+Also, it may be appropriate for SINTERP to replace INTERP
+in the UTILITY package.
+.PP
+I suppose one could argue that SPLOT belongs in the PLOT package.
+Certainly, the kludge script BPLOT should be replaced by a more
+general batch plot utility in PLOT.
+Also, the two task names, IDENTIFY and REIDENTIFY are present
+in the ONEDSPEC menu for user convenience, but the task declarations
+in ONEDSPEC.CL refer to tasks in the LONGSLIT package.
+.PP
+Because ONEDUTIL is a logical separation of the tasks, not
+a complete physical task breakup, there is no subdirectory
+for ONEDUTIL as there is in other packages. This is a bit messy
+and it may be best to completely disentangle the tasks in the
+subpackage into a true package having all the implications.
+.LP
+.SH
+2. Task Information
+.PP
+There are currently about 30 tasks in the ONEDSPEC package.
+These are summarized in the menu listing below and
+a brief description of some less obvious aspects of each follows.
+.sp 1
+.DS L
+ ONEDSPEC
+
+ addsets - Add subsets of strings of spectra
+ batchred - Batch processing of IIDS/IRS spectra
+ bswitch - Beam-switch strings of spectra to make obj-sky pairs
+ calibrate - Apply sensitivity correction to spectra
+ coincor - Correct spectra for photon coincidence
+ dispcor - Dispersion correct spectra
+ extinct - Correct data for atmospheric extinction
+ flatfit - Sum and normalize flat field spectra
+ flatdiv - Divide spectra by flat field
+ identify - Identify features in spectrum for dispersion solution
+ iids - Set reduction parameters for IIDS
+ irs - Set reduction parameters for IRS
+ onedutil - Enter ONEDSPEC Utility package
+ process - A task generated by BATCHRED
+ reidentify- Automatically identify features in spectra
+ sensfunc - Create sensitivity function
+ slist - List spectral header elements
+ splot - Preliminary spectral plot/analysis
+ standard - Identify standard stars to be used in sensitivity calc
+ subsets - Substract pairs in strings of spectra
+
+ ONEDUTIL
+
+ bplot - Batch plots of spectra
+ coefs - Extract mtn reduced ceofficients from henear scans
+ combine - Combine spectra having different wavelength ranges
+ lcalib - List calibration file data
+ mkspec - Generate an artificial spectrum
+ names - Generate a list of image names from a string
+ rebin - Rebin spectra to new dispersion parameters
+ ridsmtn - Read IIDS/IRS mountain format tapes
+ sinterp - Interpolate a table of x,y pairs to create a spectrum
+ widstape - Write Cyber format IDSOUT tapes
+.DE
+.sp 1
+.SH
+ADDSETS
+.PP
+Spectra for a given object may have been observed through more than
+one instrument aperture. For the IIDS and IRS, this is the most common
+mode of operation. Both apertures are used to alternately observe
+the program objects.
+.PP
+Each instrument aperture may be considered an
+independent instrument having unique calibration properties, and
+the observations may then be processed completely independently
+until fully calibrated. At that point the data may be combined to
+improve signal-to-noise and reduce systematic errors associated
+with the alternating observing technique. Because the data are
+obtained in pairs for IIDS and IRS (but may be obtained in groups
+of larger sizes from other instruments), ADDSETS provides a way
+to combine the pairs of observations.
+.PP
+Each pair in the input string is added to produce a single output
+spectrum. Although the word "pair" is used here, the parameter
+"subset" defines the number of elements in a "pair" (default=2).
+The input string is broken down into groups where each group
+consists of the pair of spectra defined in order of the input
+list of image names.
+.PP
+"Add" in ADDSETS means:
+.RS
+.IP 1.
+Average the pairs if the data are calibrated to flux (CA_FLAG=0)
+optionally weighted by the integration time.
+.IP 2.
+Add the pairs if uncalibrated (CA_FLAG=-1).
+.RE
+.sp 1
+.SH
+BATCHRED
+.PP
+This is a script task which allows spectra from dual aperture instruments
+to be processed completely in a batch mode after the initial wavelength
+calibration and correction has been performed. The processes which
+may be applied and the tasks referenced are:
+.RS
+.IP 1.
+Declaring observations as standard stars for flux calibration (STANDARD).
+.IP 2.
+Solving for the sensitivity function based on the standard stars (SENSFUNC).
+.IP 3.
+Generating object minus sky differences and summing individual
+observations if several were made (BSWITCH).
+.IP 4.
+Correcting for atmospheric extinction (BSWITCH).
+.IP 5.
+Applying the system sensitivity function to generate flux calibrated
+data (CALIBRATE).
+.IP 6.
+Adding pairs of spectra obtained through the dual apertures (ADDSETS).
+.RE
+Any or all of these operations may be selected through the task
+parameters.
+.PP
+BATCHRED generates a secondary script task called PROCESS.CL
+which is a text file containing constructed commands to the
+ONEDSPEC package. This file may be edited by the user if an
+entry to BATCHRED is incorrect. It may also be saved, or appended
+by further executions of BATCHRED.
+.PP
+BATCHRED also generates a log file of the output generated by the
+ONEDSPEC tasks it calls.
+.sp 1
+.SH
+BSWITCH
+.PP
+This task combines multiple observations of a single object
+or multiple objects taken through a multiaperture instrument.
+Object minus sky differences are generated as pairs of
+spectra are accumulated, then optionally corrected for
+atmospheric extinction, and the differences added together
+with optional weighting using counting statistics.
+Each instrument aperture is considered an independent
+device.
+.PP
+Despite the apparently simple goal of this task, it is probably
+the most complicated in the ONEDSPEC package due to the
+bookkeeping load associated with automated handling of large data sets
+having a number of properties associated with each spectrum (e.g
+object or sky, aperture number, exposure times).
+.PP
+There are several modes in which BSWITCH can operate. The mode
+appropriate to the IIDS and IRS assumes that the spectra
+are input in an order such that after 2N (N=number of
+instrument apertures) spectra have been
+accumulated, an equal number of object and sky spectra have been
+encountered in each aperture.
+When in this mode, a check is made after 2N spectra
+have been processed, and the optional extinction correction is
+applied to the differences of the object minus sky, and then
+(optionally weighted and) added into an accumulator for the aperture.
+.PP
+If the IIDS mode is switched off, then no guarantee can be
+made that sky and object spectra pair off. If extinction
+correction is required, it is performed on each spectrum
+as it arrives, including sky spectra if any. The spectra are
+then added into separate accumulators for object and sky for
+each aperture after optional weighting is applied.
+.PP
+If after all spectra have been processed, there are no sky
+spectra, the object spectrum is written out. If there is no
+object spectrum, the sky spectrum is written out after
+multiplying by -1. (This allows adding an object later on with
+addsets, but the -1 multiply is probably a mistake.)
+If at least one of each, object and sky spectra were encountered,
+then the difference is computed and written out. Since
+all accumulations are performed in count rates and later converted
+back to counts, the object and sky spectra may have different
+exposure times (non IIDS mode only).
+.PP
+A statistics file is maintained to provide an indication of the
+quality of the individual spectra going into the sum. The
+statistics information is maintained internally and only
+written out after the sums have been generated.
+The basic data in the file is the count rate of the spectrum
+having the largest count rate, and the ratios of the count rates from
+all other spectra to that one.
+.PP
+If weighting is selected, the weights are taken as proportional to
+the count rate (prior to extinction correction) over a wavelength
+delimited region of the spectrum. (Perhaps the weight
+should be proportional to counts, not count rate.)
+The default wavelength region is the entire spectrum.
+If the total count rate is negative, the weight is assigned
+a value of 0.0 and will be disregarded in the sum. (The counts
+may be negative if the object minus sky difference approaches zero
+on a bright and cloudy night.)
+.PP
+If extinction is selected, an extinction table is read from the
+package calibration file. An optional additive term may be applied
+as computed by the system sensitivity task SENSFUNC which is placed
+in the parameter sensfunc.add_const. A revision to the standard
+extinction table (delta extinction as a function of wavelength)
+may be read from a text file whose name is specified by the parameter
+sensfunc.rev_ext_file. The file format is that of a text file
+having pairs of (wavelength, delta extinction) on each line.
+[The option to solve for this function in SENSFUNC has not yet been
+implemented, but BSWITCH can read the file that would be generated.
+Thus, one can experiment with revisions, although this has never been
+tested.] BSWITCH will interpolate the values given in the file
+so that a course estimate of the revision may be entered, say if the
+deltas at U, B, V, R, and I are known.
+.PP
+BEWARE that the extinction correction is performed assuming the
+header parameters used for airmass refer to a "mean" airmass value
+for the exposure. In general the header value is wrong! It usually
+refers to the beginning, middle, or end of the exposure. I have
+never seen a header airmass value which was an equivalent airmass
+for the duration of the exposure. This is partly because there is
+no way to compute a single effective airmass; it is a function
+of wavelength, telescope position as a function of time, and
+the extinction function. Fortunately, for most observations
+this is not very significant. But anyone taking a one hour exposure near
+3500 Angstroms at airmass values greater than 2, should not complain
+when the fluxes look a bit odd.
+.sp 1
+.SH
+CALIBRATE
+.PP
+Having a system sensitivity function allows the data to be
+placed on an absolute flux scale. CALIBRATE performs this
+correction using the output sensitivity function from SENSFUNC. Operations are
+keyed to the instrument aperture, and a system sensitivity
+function is required for each observing aperture, although
+this requirement may be overriden.
+.PP
+A valid exposure time is required (a value of 1.0 should
+probably be assumed if not present) to compute the observed
+count rate. Input counts are transformed to units of
+ergs/cm2/sec/Angstrom (or optionally ergs/cm2/sec/Hz).
+CALIBRATE will calibrate two dimensional images as well, applying the
+sensitivity function to all image lines.
+.PP
+The operation is performed on a pixel-by-pixel basis so that
+the defined sensitivity function should overlap precisely
+with data in terms of wavelength.
+.sp 1
+.SH
+COINCOR
+.PP
+This task applies a statistical correction to each pixel
+to account for undetected photoevents as a result of
+coincidental arrival of photons. This is a detector
+specific correcton, although the photoelectric detector
+model provides a reasonable correction for many detectors
+when a judicious value for the deadtime parameter is chosen.
+This model assumes that the correction follows the
+typical procedures applied to photoelectric photometer data:
+.sp 1
+.DS L
+ Ic = Io * exp [Io * dt / T]
+.DE
+.sp 1
+where Ic is the corrected count rate in a pixel, Io is the
+observed count rate in that pixel, dt is the detector deadtime,
+and T is the observation integration time.
+.PP
+In addition to the photoelectric model, a more accurate model
+is available for the IIDS and is included in COINCOR. This
+model is taken from Goad (1979, SPIE Vol 172, 86.) and the correction
+is applied as:
+.sp 1
+.DS L
+ Ic = ln [1 - Io * t] / t
+.DE
+.sp 1
+where t is sweep time between pixel samples (t=1.424 msec).
+The IIDS differs from a photomultiplier detector, in that
+there is a fixed rate at which each pixel is sampled due to
+time required for the dissector to sweep across the image tube
+phospher whether a photoevent has occurred in a pixel or not.
+The photomultiplier plus discriminator system
+assumes that once a photoevent has been recorded, the detector is
+dead until a fixed interval has elapsed.
+.sp 1
+.SH
+DISPCOR
+.PP
+If a relation is known linking pixel coordinate to user coordinate
+(i.e. wavelength as a function of pixel number), then any non-linearities
+can be removed by remapping the pixels to a linear wavelength coordinate.
+This procedure, dispersion correction, is complicated by the
+lack of a wavelength-pixel solution which is derived from data simultaneously
+obtained with the object data. Any drifts in the detector then require
+an interpolation among solutions for the solution appropriate to
+the object observations. Depending on the detector, this interpolation
+may be a function of the time of observation, temperature, or some telescope
+parameter such as airmass.
+When multiple solutions are available, DISPCOR will linearly interpolate
+the solution in any available header parameter known to ONEDSPEC (see
+section 3).
+.PP
+Each solution is read from the database file created by the IDENTIFY
+task (in TWODSPEC$LONGSLIT), and the image name leading to that solution
+is also read from the database file. The image is opened to extract
+the header parameter to be used in the above interpolation.
+A null name for the interpolation parameter indicates that none
+is to be used. In this case, one of the options on the "guide"
+parameter should be set to indicate what solution should be used.
+The guide may be "precede", "follow", or "nearest" to select
+the most appropriate choice for each spectrum.
+.PP
+If an explicit wavelength solution is to be used, the parameter
+"reference" may be used to specify the image name of a comparison
+spectrum to be used as the reference for the wavelength solution.
+In this case all spectra will be corrected using a single solution -
+no flexure correction will be applied.
+.PP
+If the parameter to be used for interpolation is a "time-like"
+variable, such as RA, UT, ST, then the variable is discontinuous
+at 24|0 hours. If UT is the chosen parameter (as has been the
+case for IIDS and IRS spectra), the discontinuity occurs at
+5 PM local Kitt Peak time. A comparison spectrum taken at 4:59PM
+(=23:59h UT, =just before dinner), will be treated as an "end of
+the night" observation rather than a beginning of the night
+observation. To circumvent this error, the parameter, "time_wrap",
+can be specified to a time at which a true zero should be assigned.
+For UT at Kitt Peak, a choice like 17h UT (=10AM local, =asleep),
+is an unlikely hour for nighttime observations to be made. Then for
+a given night's observations, 17h UT becomes the new zero point in time.
+.PP
+Each solution in the database may be any of the forms legal
+to IDENTIFY: legendre, chebyshev, spline3, or spline1 - the form
+is encoded in the database and will automatically be recalled.
+The interpolation in the solution is performed by locating the
+pixel location for each required wavelength for the two
+solutions bounding each observation and linearly interpolating
+for the appropriate pixel location. One cannot simply interpolate
+across the coefficients of the solutions to derive a new
+single solution because the solutions may have different forms
+or orders, so that the coefficients may have quite different
+meanings.
+.PP
+Dispersion correction requires that there be equal intervals
+of wavelength between pixels. The wavelength solution
+is of a form describing the wavelength for a given pixel location,
+not a pixel location for a given wavelength. So the solution
+must be inverted.
+.PP
+The inversion to pixel location for wavelength is done in the
+following way: The pixel coordinate in the solution is incremented
+until the desired wavelength is bounded. The pixel value for the
+desired wavelength is obtained by linearly interpolating across these
+two bounding pixel locations. A linear approximation appears to be
+very good for typical solutions, providing proper pixel locations to
+better than 0.01 pixels. An improvement may be obtained by
+increasing the order of the interpolation, but the improvement
+is generally not warranted because the wavelength solutions
+are rarely known to this accuracy. [Note that the use of real
+and not double precision limits the precision of this technique!
+For spectra longer than 50,000 pixels, the errors due to
+the precision of reals can be serious.]
+.PP
+Note that a transformation to
+a wavelength coordinate which is linear in the logarithm of
+wavelength only requires that the inversion occur at wavelengths
+selected by equal increments in the logarithm of wavelength.
+.PP
+During the actual remapping, 5 possible techniques are available.
+Actually there are only two techniques: re-interpolation in 4 flavors,
+and rebinning by partial pixel summation. The re-interpolation
+may be performed with polynomials of order 1 (=linear), 3, or 5,
+or by a cubic spline. The 3rd and 5th order polynomials may introduce
+some ringing in the wings of strong, sharp, features, but the 5th order
+is good at preserving the high frequency component of the data.
+The linear and spline interpolators introduce significant smoothing.
+The rebinning algorithm offers conservation of flux but also smooths
+the data. In fact, rebinning to a course grid offers a good smoothing
+algorithm.
+.PP
+At some future date, it would be a good idea to include a "synch"
+function interpolator in the image interpolator package. This would
+be a little slower to process, but results in very good frequency
+response.
+.PP
+Other options in DISPCOR include "ids_mode" which forces spectra
+from all apertures to a single output mapping (starting wavelength
+and pixel-to-pixel increment), and "cols_out" forces the output spectra
+to a specified length, zero-filling if necessary.
+.PP
+DISPCOR will correct two-dimensional data by applying the
+remapping to all lines in the image. If the input two-dimensional
+spectrum has only one line, the output spectrum will be written as
+a one-dimensional spectrum.
+.sp 1
+.SH
+EXTINCT
+.PP
+Extinction is currently only available as a script file which drives
+BSWITCH. This is possible by suppressing all options: weighting,
+ids_mode, statistics file, and setting the subset pair size to the
+number of instrument apertures.
+.sp 1
+.SH
+FLATDIV
+.PP
+This task divides the specified spectra by their flat field spectra.
+This is not much more than an "en mass" spectrum divider, with the
+exceptions that the header elements are used to key on the
+aperture number so that the appropriate flat field spectrum is used,
+and that the header processing flags are checked to prevent
+double divisions and subsequently set after the division. Also,
+division by zero is guarded by setting any zeroes in the flat field
+spectrum to 1.0 prior to the division.
+.sp 1
+.SH
+FLATFIT
+.PP
+Pixel-to-pixel variations in the detector response can be removed
+by dividing all observations by a flat field spectrum.
+Flat field spectra are generally obtained by observing a source
+having a continuous energy distribution, such as a tungsten filament
+lamp. This is sometimes called a "quartz" lamp when the enclosing
+glass bulb is made with quartz rather than silicon. The quartz
+enclosure transmits ultraviolet light much better than glass.
+.PP
+If the color temperature of the source is very low (or very high, though
+this is extremely unlikely), then a color term would be introduced
+into the data when the flat is divided into the data.
+Large scale variations in the system sensitivity also introduce a
+color term into the flat - the same variations that are introduced into
+any spectrum taken with the system. [Large scale variations are
+evaluated by STANDARD and SENSFUNC, and removed by CALIBRATE.]
+This is not of any particular importance except that counting
+statistics are destroyed by the division.
+.PP
+To preserve the statistics, many find it desirable to divide by a flat
+field spectrum which has been filtered to remove any large scale variations
+but in which the pixel-to-pixel variations have been retained.
+A filtered flat can be obtained by fitting a low order polynomial
+through the spectrum and dividing the spectrum by the polynomial.
+The result is a spectrum normalized to 1.0 and having high frequency
+variations only. If one does not care to preserve the statistics,
+then this procedure is not required. In fact, for certain instruments
+(the IRS), the fitting and normalizing procedure is not recommended
+because some intermediate order curvature can be introduced.
+.PP
+The purpose of FLATFIT is to find the combination of parameters
+which produces a well flattened flat with a minimum of wiggles.
+The usual curve fitting package is used to fit a function (chebyshev,
+legendre, spline3, spline1) to the flats. Pixel rejection is
+user selectable by a choice of cutoff sigmas, both above and below
+the mean, and an optional growing region [A growing region is the number
+of pixels on either side of one rejected which will also be rejected -
+Growing regions are not recommended for most spectral applications].
+Any number of iterations may be used to further reject discrepant
+pixels. The fitting may be performed interactively and controlled by cursor
+keystrokes to select the fitting order, and other fit parameters.
+.PP
+Prior to the fit, the specified spectra are read, optionally corrected
+for coincidence losses, and added to accumulators appropriate to
+their instrument apertures. Each aperture is treated independently,
+except that, the interactive fitting mode may be selected to operate
+on the first aperture only, and then apply the same fitting parameters
+to all other aperture accumulations. Or the interactive procedure
+may be selected to operate on all apertures or none.
+.PP
+After the fit has been done, the fit is divided into the accumulation
+and written as a new spectrum having a specified root name and a trailing
+index indicating the aperture.
+.sp 1
+.SH
+IDENTIFY
+.PP
+This task (written by Frank Valdes) is used to identify features
+in the comparison arcs to be used in the solution for a wavelength calibration.
+The solution is performed interactively for at least one spectrum
+and then optionally in a batch mode using REIDENTIFY.
+IDENTIFY writes to a database file which will contain the solutions
+generated from each input comparison spectrum. The database is
+later used by DISPCOR to correct spectra according to the solution.
+.sp 1
+.SH
+IIDS
+.PP
+This script file initializes several hidden parameters in a
+variety of tasks to values appropriate to the IIDS instrument.
+There is also a script for the IRS. There should probably be
+a script for resetting the parameters to a default instrument.
+These parameters are:
+.RS
+.IP 1.
+onedspec.calib_file - the package parameter indicating which file
+should be used for standard star calibration data and the atmospheric
+extinction table (=onedspec$iids.cl.)
+.IP 2.
+addsets.subset - the number of instrument apertures (=2).
+.IP 3.
+bswitch.ids_mode - assume and check for data taken in beam-switched
+quadruple mode (=yes).
+.IP 4.
+coincor.ccmode - coincidence correction model (=iids).
+.IP 5.
+coincor.deadtime - detector deadtime (=1.424e-3 seconds)
+.IP 6.
+dispcor.flex_par - the name of the parameter to be used as the
+guide to removing flexure during the observations (=ut).
+.IP 7.
+dispcor.time_wrap - the zero point to be adopted for the
+flexure parameter if it is a time-like variable having a discontinuity
+at 0/24 hours (=17).
+.IP 8.
+dispcor.idsmode - should data from all instrument apertures be dispersion
+corrected to a uniform wavelength scale? (=yes).
+.IP 9.
+dispcor.cols_out - the number of columns (row length of the spectrum)
+to which the output corrected spectrum should be forced during
+mapping (=1024).
+.IP 10.
+extinct.nr_aps - the number of instrument apertures (=2).
+.IP 11.
+flatfit.order - the order of the fit to be used when fitting to
+the flat field spectra (=6).
+.IP 12.
+flatfit.coincor - apply coincidence correction to the flat field
+spectra during accumulations (=yes).
+.IP 13.
+flatdiv.coincor - apply coincidence correction to all spectra during
+the flat field division process (=yes).
+.IP 14.
+identify.function - the fitting function to be used during the wavelength
+solution process (=chebyshev).
+.IP 15.
+identify.order - the order of the fit to be used during the wavelength
+solution process (=6).
+.RE
+.sp 1
+.SH
+IRS
+.PP
+This script file initializes several hidden parameters in a
+variety of tasks to values appropriate to the IRS instrument.
+These parameters are:
+.RS
+.IP 1.
+onedspec.calib_file - the package parameter indicating which file
+should be used for standard star calibration data and the atmospheric
+extinction table (=onedspec$irs.cl.)
+.IP 2.
+addsets.subset - the number of instrument apertures (=2).
+.IP 3.
+bswitch.ids_mode - assume and check for data taken in beam-switched
+quadruple mode (=yes).
+.IP 4.
+coincor.ccmode - coincidence correction model (=iids).
+.IP 5.
+coincor.deadtime - detector deadtime (=1.424e-3 seconds)
+.IP 6.
+dispcor.flex_par - the name of the parameter to be used as the
+guide to removing flexure during the observations (=ut).
+.IP 7.
+dispcor.time_wrap - the zero point to be adopted for the
+flexure parameter if it is a time-like variable having a discontinuity
+at 0/24 hours (=17).
+.IP 8.
+dispcor.idsmode - should data from all instrument apertures be dispersion
+corrected to a uniform wavelength scale? (=yes).
+.IP 9.
+dispcor.cols_out - the number of columns (row length of the spectrum)
+to which the output corrected spectrum should be forced during
+mapping (=1024).
+.IP 10.
+extinct.nr_aps - the number of instrument apertures (=2).
+.IP 11.
+flatfit.order - the order of the fit to be used when fitting to
+the flat field spectra. IRS users have frequently found that
+any curvature in the fit introduces wiggles in the resulting
+calibrations and a straight divide by the flat normalized to the
+mean works best (=1).
+.IP 12.
+flatfit.coincor - apply coincidence correction to the flat field
+spectra during accumulations (=no).
+.IP 13.
+flatdiv.coincor - apply coincidence correction to all spectra during
+the flat field division process (=no).
+.IP 14.
+identify.function - the fitting function to be used during the wavelength
+solution process (=chebyshev).
+.IP 15.
+identify.order - the order of the fit to be used during the wavelength
+solution process. The IRS has strong deviations from linearity
+in the dispersion and a fairly high order is required to correct
+the dispersion solution (=8).
+.RE
+.sp 1
+.SH
+ONEDUTIL
+.PP
+This is a group of utility operators for the ONEDSPEC package. They
+are documented separately after the ONEDSPEC operators. ONEDUTIL
+is a "pseudo-package" - it acts like a package under ONEDSPEC, but
+many of its logical tasks are physically a part of ONEDSPEC. This
+is done to minimize disk storage requirements, and to logically
+separate some of the functions from the main ONEDSPEC menu which
+was getting too large to visually handle.
+.sp 1
+.SH
+PROCESS
+.PP
+This task generally does not exist until the user executes the
+script task BATCHRED which creates PROCESS.CL, a secondary script
+file containing a CL command stream to batch process spectra.
+The task is defined so that the CL is aware of its potential
+existence. It is not declared as a hidden task so that the
+user is also aware of its existence and may execute PROCESS
+in the foreground or background.
+.sp 1
+.SH
+REIDENTIFY
+.PP
+This task (written b Frank Valdes) is intended to be used after
+IDENTIFY has been executed. Once a wavelength solution has been
+found for one comparison spectrum, it may be used as a starting point
+for subsequent spectra having similar wavelength characteristics.
+REIDENTIFY provides a batch-like means of performing wavelength solutions
+for many spectra. The output solution is directed to a database text file
+used by DISPCOR.
+.sp 1
+.SH
+SENSFUNC
+.PP
+This task solves for the system sensitivity function across
+the wavelength region of the spectra by comparison of observations
+of standard stars to their (assumed) known energy distribution.
+Each instrument aperture is treated completely independently
+with one exception discussed later. SENSFUNC is probably the
+largest task in the ONEDSPEC package due to heavy use of
+interactive graphics which represents more than half of the
+actual coding.
+.PP
+Input to SENFUNC is the "std" text file produced by STANDARD
+containing the ratio of the count rate adjusted for atmospheric extinction
+to the flux of the star in ergs/cm2/s/Angstrom. Both the count rates and
+fluxes are the average values in the pre-defined bandpasses tabulated
+in the calibration file (indicated by the parameter onedspec.calib_file).
+.PP
+Each entry is the "std" file may have an independent set of wavelength sampling
+points. After all entries have been loaded, a table containing all sampled
+wavelengths is built (a "composite" wavelength table) and all sensitivity
+values are reinterpolated onto this sampling grid. This allows the inclusion
+of standards in which the observational samples are not uniform.
+.PP
+When multiple measurements are available, one of two corrections may
+be applied to the data to account for either clouds or an additive extinction
+term. The effect of clouds is assumed to be grey. Each contributing
+observation is compared to the one producing the highest count rate ratio
+at each wavelength sample. The deviation averaged over all wavelengths
+for a given observation is derived and added back to
+each wavelength sample for that observation. This produces a shift
+(in magnitudes) which, on the average across the spectrum, accounts
+for an extinction due to clouds. This process is called "fudge"
+primarily for historical reasons (from the IPPS, R.I.P.) and also
+because there is questionable justification to apply this correction.
+One reason is so that one can better assess the errors
+in the data after a zero-point correction has been made.
+Another is that the sensitivity function is that closest to a cloud-free
+sky so that claibrations may approach a true flux system if one
+standard was observed during relatively clear conditions.
+Alsom there are claims that the "color solution" is improved by "fudging", but
+I admit that I don't fully understand this argument.
+.PP
+[Perhaps it goes as follows:
+Although a grey scale correction is applied to each observation,
+a color term is introduced in the overall solution. Consider the
+case where 5 magnitudes of cloud extinction obscure one standard
+relative to another. This star generates a sensitivity curve which
+is a factor of 100 smaller. When averaged with the other curve,
+any variations are lost, and the net curve will be
+very similar to the first curve divided by 2. Now apply a "fudge"
+of 5 magnitudes to the second curve. On the average, both curves have
+similar amplitudes, so variations in the second now influence the
+average. The net curve then has color dependent variations not
+in the "un-fudged" net curve. If we assume that the variations in
+the individual observations are not systematic, then "fudge" will
+improve the net color solution. Amazing, isn't it?
+End of hypothesis.]
+.PP
+The second form of correction is much more justifiable. In ONEDSPEC
+it is referred to as a "grey shift" and accounts for possible
+changes in the standard atmospheric extinction model due to
+a constant offset. SENSFUNC will optionally solve for this constant
+provided the observations sample a range of airmass values.
+The constant is computed in terms of magnitudes per airmass, so
+if the airmass range is small, then a large error is likely.
+To solve for this value, a list of pairs of delta magnitude (from the
+observation having the greatest sensitivity) as a function of
+delta airmass (relative to the same observation) is generated
+for all observations. The list is fit using a least squares solution
+of the form:
+.sp 1
+.DS L
+ delta_mag = delta_airmass * grey_shift
+.DE
+.sp 1
+Note that this is a restricted least-squares in the sense that there
+is no zero-point term. The standard curve fit package in IRAF
+does not support this option and the code to perform this is included
+in SENSFUNC.
+.PP
+Because the atmosphere is likely to be the same one for observations
+with each instrument aperture, it is not appropriate to limit
+the least-squares solution to the individual apertures, but rather
+to combine all the data to improve the solution. This would mean
+that the user could not view the effects of applying the grey term
+until all apertures had been analyzed. So, although each aperture is
+solved independently to derive a preliminary value, a final value is
+computed at the end when all data have been reviewed. This is the
+one exception to the independent aperture equals independent
+instrument philosophy.
+.PP
+When "fudging" is applied, the sensitivity function that is generated
+is altered to account for the shifts to the observations. But when
+the "grey shift" is computed, it cannot be directly applied to
+the sensitivity function because it must be modified by the
+observing airmass for each individual object. So the grey shift
+constant is written into the image headers of the generated
+sensitivity functions (which are IRAF images), and also placed
+into the task parameter "add_const" to be used later by BSWITCH.
+.PP
+SENSFUNC can be run in an interactive mode to allow editing
+of the sensitivity data. There are two phases of interaction:
+(1) a review of the individual observations in which every data
+element can be considered and edited, and (2) a review of the
+composite sensitivity table and the calculated fit to the table.
+In the interactive mode, both phases are executed for every instrument
+aperture.
+.PP
+At both phases of the interactive modes there will be a plot of the
+error in the input values for each wavelength. This is an RMS
+error. [The IPPS plotted standard error which is always a smaller number
+and represents the error in the mean; the RMS represents the error
+in the sample. I'm not sure which is better to use, but RMS is easier
+to understand. RMS is the same as the standard deviation.]
+During phase one, the rms is computed as the standard deviation of
+the sensitivity in magnitudes; but during phase two, it is computed
+as the standard deviation in raw numbers
+and then converted to a magnitude equivalent. The latter is more
+correct but both converge for small errors.
+.PP
+There is one option in SENSFUNC which has never been tried and it won't
+work - the option to enter a predefined table of sensitivities as
+a function of wavelength as a simple text file. This option may
+be useful a some time and should probably be fixed. I think the
+only problem with it is a lack of consistency in the units.
+.PP
+An additional option has been requested but it is not clear that it
+is a high priority item - the ability to compute the extinction
+function. There may be instances when the mean extinction table
+is not appropriate, or is not known. If sufficient data are
+available (many observations of high precision over a range of airmasses
+during a photometric night), then the extinction function is
+calculable. Presently SENSFUNC can only compute a constant offset to
+the extinction function, but the same algorithm used may be applied
+at each wavelength for which observations are made to compute a
+correction to an adopted extinction function (which may be zero),
+and the correction can then be written out to the revised extinction
+table file. This file will then be read by BSWITCH during the
+extinction correction process.
+So at each wavelength, pairs of delta magnitude as a function of
+delta airmass are tabulated and fit as above:
+.sp 1
+.DS L
+ delta_mag[lambda] = delta_airmass * delta_extinction[lambda]
+.DE
+.sp 1
+Because the data have been heavily subdivided into wavelength bins,
+there are only a few measurements available for solving this
+least-squares problem and the uncertainties are large unless many
+observations have been taken. Experience has shown that at least
+7-8 measurements are needed to come close, and 15 measurements are
+about the minimum to get a good solution. Unless the data are of
+high quality, the uncertainty in the solution is comparable to
+the error in assuming a constant offset to the mean extinction function.
+Nevertheless, the option should be installed at some time since
+some observers do obtain the necessary data.
+.sp 1
+.SH
+SLIST
+.PP
+The spectrum specific header elements are listed in either a short
+or long form. See the discussion on headers (section 3) for an explanation
+of the terms. Values for airmass are printed if present in the header;
+otherwise, the value is given as the string "?????" to indicate no
+value present (even if one can be calculated from the telescope
+pointing information elsewhere in the header).
+.PP
+The short form header lists only the image name, whether it is
+an object or sky observation, the spectrum length, and the title.
+.sp 1
+.SH
+SPLOT
+.PP
+This is probably the second largest task in the ONEDSPEC package. It continues
+to grow as users provide suggestions for enhancement, although
+the growth rate appears to be slowing. SPLOT is an interactive
+plot program with spectroscopy in mind, although it can be used
+to plot two dimensional images as well.
+.PP
+SPLOT should still be considered a prototype - many of the algortihms
+used in the analysis functions are crude, provided as interim
+software to get results from the data until a more elaborate package
+is written. It would probably be best to create an analysis specific
+package - SPLOT is reasonably general, and to enhance it further
+would complicate the keystroke sequences.
+.PP
+Ideally it should be possible to do anything to a spectrum with
+a single keystroke. In reality, several keystrokes are required.
+And after 15 or 20 functions have been installed, the keystroke
+nomenclature becomes obscure - all the best keys are used up, and
+you have to resort to things like '(' which is rather less
+mneumonic than a letter. So some of the functionality in SPLOT
+has been assigned to the "function" submenu invoked by 'f' and
+exited by 'q' keystrokes. These include the arithmetic operators:
+add, multiply by a constant, add, subtract, multiply, divide by
+a spectrum, and logarithms, square root, inverse, and absolute
+value of a spectrum.
+.PP
+Some of the analysis functions include: equivalent width, line centers,
+flux integration under a line, smoothing, spectrum flattening,
+and deblending of lines.
+.PP
+The deblender has serious limitations but handles about half the
+cases that IIDS/IRS users are interested in. It fits only
+Gaussian models to the blends, and only a single width parameter.
+The fit is a non-linear least-squares problem, so starting values
+present some difficulties. All starting values are initialized to 1.0 -
+this includes the width, relative strengths of the lines, and deviation
+from intial marked centers. The iterative solution usually converges
+for high signal-to-noise data, but may go astray, resulting in
+a numerical abort for noisy data. If this occurs, it is often
+possible to find a solution by fitting to a single strong line
+to force a better approximation to the starting values, and then refit
+the blend of interest.
+.PP
+The non-linear least-squares routine is one obtained from an industrial
+source. The code is very poorly written and in FORTRAN. No one should
+attempt to understand it. The basic algorithm is an unconstrained simplex
+minization search combined with a parabolic linear least-squares approximation
+when in the region of a local minimum.
+A test was made comparing this to the algorithm in Bevington, and the
+Bevington algorithm appeared less likely to converge on noisy data.
+Only one test case was used, so this is hardly a fair benchmark.
+.PP
+The problem with non-convergence is that a floating point error is
+almost surely to arise. This is usually a floating point over/under
+flow while computing an exponential (as required for a Gaussian).
+In UNIX, there is apparently no easy way to discriminate from
+FORTRAN which floating point exception has occurred, and so there
+is no easy way to execute a fix up and continue. This is most
+unfortunate because the nature of these non-linear techniques is
+that given a chance, they will often recover from searching
+down the wrong alley. A VMS version of the same routines seems to
+survive the worst data because the error recovery is handled
+somewhat better. [The VMS version also seems to run much faster,
+presumably because the floating point library support is better
+optimized.]
+.PP
+The net result of all this, is that a weird undocumented subroutine
+is used which provides no error estimate. The Bevington routines
+do provide an error estimate which is why I wanted to use them.
+[In fact, there is no way to exactly compute the errors in the
+fit of a non-linear least-squares fit. One can however apply
+an approximation theory which assumes the hypersurface can be
+treated locally as a linear function.]
+.PP
+There are several methods for computing equivalent widths in SPLOT.
+The first method for measuring equivalent width is simply to integrate the
+flux above/under a user defined continuum level. Partial pixels
+are considered at the marked endpoints. A correction for the pixel size,
+in Angstroms, is applied because the units of equivalent width are Angstroms.
+You will probably get a different answer when doing equivalent
+width measurements in channel mode ('$' keystroke) as compared to
+wavelength mode ('p').
+.PP
+Centering is performed as a weighted first moment of the region:
+.sp 1
+.DS L
+ int1 = integral [ (I-Ic) * sqrt (I-Ic) * w]
+ int2 = integral [ (I-Ic) * sqrt (I-Ic) ]
+ xc = int1 / int2
+.DE
+.sp 1
+where I is the intensity at the pixel at wavelength w, and Ic is
+the estimated continuum intensity. The square root term provides
+the weighting assuming photon statistics [sigma = sqrt(I)], and xc
+is the derived center of the region.
+.PP
+An alternative method for equivalent widths was supplied by Caty
+Pilachowski and is described in some detail in the help file for
+SPLOT. This method is fast and insensitive to cursor settings, so
+the user can really zip through a spectrum quickly.
+.PP
+Smoothing is performed using a simple boxcar smooth of user specified
+size (in pixels). To handle edge effects, the boxcar size is
+dynamically reduced as the edge is approached, thereby reducing
+the smoothing size in those regions.
+.PP
+The flattening operator is a preliminary one, written before the
+curve fitting package was available in IRAF. This operator
+should probably be re-written to include the interactive
+style used in FLATFIT. Currently the flattening is done
+using classic polynomial least-squares with pixel rejection
+chosen to preferentially reject absorption lines and strong
+emission lines. The rejection process is repeated through
+a number of iterations specifiable as a hidden parameter to SPLOT.
+This is poorly done - the order of the fit and the number of
+iterations should be controllable while in SPLOT. However,
+experimentation has shown that for a given series of spectra,
+the combination of rejection criteria, order, and iteration count
+which works well on one spectrum will generally work well
+on the other spectra. Note that the flatten operator attempts to
+find a continuum level and normalize to that continuum, not to the
+average value of the spectrum.
+.PP
+There are also the usual host of support operators - expansion,
+overplotting, and so forth. There is also a pixel modifer mode
+which connects two cursor positions. This forces a replot of the entire
+spectrum after each pair of points has been entered. This should
+probably be changed to inhibit auto-replot.
+.PP
+Some users have requested that all two cursor operators allow
+an option to escape from the second setting in case the wrong
+key was typed. I think this is a good idea, and might be implemented
+using the "esc" key (although I could not seem to get this keystroke
+through the GIO interface).
+.PP
+Another user request is the option to overplot many spectra with
+autoscaling operational on the entire range. This is also a good
+idea. Yet another improvement could be made by allowing the user
+to specify the x and y range of the plot, rather than autoscaling.
+.PP
+There is one serious problem with respect to plotting spectra
+corrected to a logarithmic wavelength scale. It would be nice to
+plot these spectra using the logarithmic axis option, but this
+option in GIO requires that at least one entire decade of x axis
+be plotted. So for optical data, the x axis runs from 1000 Angstroms
+to 10,000 Angstroms. Imagine a high dispersion plot having only 100
+Angstroms of coverage - the plot will look like a delta function!
+The current version of SPLOT uses a linear axis but plots in
+the log10 of wavelength. Not very good, is it.
+.sp 1
+.SH
+STANDARD
+.PP
+This task computes the sensitivity factor of the instrument
+at each wavelength for which an a priori measured flux value is known
+and within the wavelength range of the observations.
+Sensitivity is defined as
+[average counts/sec/Angstrom]/[average ergs/cm2/sec/Angstrom]
+over the specified bandpass for which the star has been measured.
+Both numerator and denominator refer to quantities above the
+Earth's atmosphere and so the count rates must be corrected for
+extinction.
+The wavelengths of known measurements, the bandpasses, the
+fluxes (in magnitudes), and the mean extinction table
+are read from a calibration file whose name is specified
+by the calib_file parameter (see LCALIB for a description of this
+file). If a magnitude is exactly 0.0, it is assumed
+that no magnitude is known for this star at the wavelength
+having a 0.0 magnitude. This allows entries having incomplete
+information.
+.PP
+As each observation is read, it is added into an accumulator for
+its aperture. Or subtracted if it is a sky measurement. After
+a pair of object and sky observations have been added, the
+difference is corrected for extinction (as in BSWITCH), converted
+to counts per second, and integrations performed over the bandpasses
+for which flux measures are known. The bandpasses must be completely
+contained within the spectrum - partial coverage of a bandpass
+disqualifies it from consideration. The integrations are compared
+with the known flux values and the ratio is written to a text
+file (the "std" file) along with the wavelength of the measurement
+and the total counts in the bandpass. The total counts value may
+be used by SENSFUNC for weighting the measurements during averaging.
+.PP
+Many users are surprised by the order of the spectral names
+printed out as STANDARD executes since the order is not necessarily
+ascending through the spectrum list. This is because the name
+printed is the name of the object spectrum most recently associated
+with an object-sky pair. So if a sky pair is several spectra down the
+list, an intervening object-sky pair taken through a different
+instrument aperture may be processed in the meantime.
+For example, say spectra 1-8 are taken so that object spectra
+numbers 1 and 7 and sky spectra 3 and 5 are taken through aperture 0,
+object spectra 4 and 6 and sky spectra 2 and 8 are taken through
+aperture 1. [This is a very common pattern for IIDS/IRS users.]
+Then spectrum 1 and 3 will pair up and be processed first (spectrum
+name 1 will be printed). Then 4 and 2 (name 4 printed), then
+7 and 5 (name 7 printed), and then 6 and 8 (name 6 printed).
+So the order of names printed will be 1,4,7,6. Simple, isn't it?
+.PP
+If the input spectra are not taken in a beam-switched mode
+then the parameter "beam_switch" should be set to no.
+Then no sky subtraction will be attempted.
+.PP
+The user may enter sensitivity values directly into a file and use
+it as the "std" file for a correction.
+See the help file for STANDARD for a description of the entries in
+the file, and see a typical file.
+.PP
+STANDARD offers a limited interactive mode. The first sky subtracted
+spectrum is displayed and the bandpasses at which sensitivity
+measurements are made will be shown as boxes. This provides a means
+to see where the measurements are falling on the observational
+data and to assess whether a bandpass may be including some
+absorption edge which may be affecting the measurement. While it
+is true that the wavelengths of the reference measurements should
+fall in the same place, the effects of instrument resolution and
+inaccuracies in the wavelength calibration may shift the positions
+of the apparent bandpasses. The samples may then be biased.
+.PP
+The second purpose of the interactive mode is to allow the user
+to artificially create new bandpasses on the fly. By placing the
+cursor to bound a new wavelength region, STANDARD will interpolate
+in the magnitude table of the reference star to estimate the magnitude
+of the star at the bounded wavelength. The sensitivity will be calculated
+at that wavelength just as if the bandpass had come from the calibration
+file. This option should be exercised with care. Obviously, points
+should not be generated between reference wavelengths falling on
+strong absorption lines, or on a line either. This option is most useful
+when at a high dispersion and few samples happen to fall in the
+limited wavelength region. Sufficient space is allocated for 10
+artificial samples to be inserted. Once the artificial bandpasses
+have been designated, they are applied to the entire sequence of
+spectra for the current invocation of STANDARD. Once STANDARD
+completes, the added bandpasses are forgotten. This prevents
+an accidental usage of newly created bandpasses on stars of different
+spectral types where a bandpass may fall in a region of continuum
+for one star, but on an absorption line in another.
+.sp 1
+.SH
+SUBSETS
+.PP
+This is a simple task to subtract the second spectrum from the
+first in a series of spectra. So if spectra 1-10 are input,
+5 new spectra will be created from 1 minus 2, 3 minus 4, and so on.
+This is a straight subtraction, pixel for pixel, with no
+compensation for exposure time differences.
+The header from the first spectrum of the pair is applied to the
+output spectrum.
+.sp 1
+.SH
+The ONEDUTIL tasks
+.PP
+These utility tasks are logically separated from the ONEDSPEC
+package.
+.sp 1
+.SH
+COEFS
+.PP
+This task reads the header parameters contained in comparison arc spectra
+describing the wavelength solution generated by the mountain reduction
+program and re-writes the solution parameters into a database
+text file for use by DISPCOR. Otherwise those solutions would be
+lost. COEFS assumes that the coefficients represent a Legendre
+polynomial which is what the mountain reduction programs use.
+.sp 1
+.SH
+COMBINE
+.PP
+When an object has been observed over a wide range of wavelength
+coverage by using more than one instrumental setup (such as
+a blue and a red setting) or with different instruments (such
+as IUE and the IRS), it is often desirable to combine the
+spectra into a single spectrum. COMBINE will rebin a group of
+spectra to new spectra having a single dispersion and average the
+new spectra to create a single long spectrum.
+If there are gaps in the composite spectrum, zeroes are used
+as fillers. Ideally those pixels which have no known value
+should be considered blank pixels. IRAF does not currently
+support blank pixels, so zeroes are used for now. [One
+might suggest using INDEF, but then all other routines will
+have to check for this value.]
+A side effect of choosing 0.0 is that during the averaging
+of overlapping spectra, a true 0.0 will be ignored by COMBINE.
+The basic rebinning algorithms used in DISPCOR are used in COMBINE
+(and also REBIN).
+.PP
+The averaging can be weighted by exposure time, or by user assigned weights.
+It would be better if each spectrum had an associated vector of
+weights (one weight at each wavelength) so that the weighted averaging
+could be done on a pixel basis. This is very expensive in terms
+of both storage and file access overhead since each spectrum would
+require twice the storage and number of files.
+[Actually weights could be small 4 bit integers and take up very little space.]
+.PP
+A less ideal alternative would be to place a small number
+(about 16) of weight parameters
+in the header file which represent the approximate weights of that many
+regions of the spectrum, and then one could interpolate in these parameters
+for a weight appropriate to the pixel of interest.
+.PP
+A third solution (and even less ideal)
+is to place a single parameter in the header which
+represents an average weight of the entire spectrum. For the latter two cases,
+the header weights could be derived from the average counts per
+wavelength region - the region being the entire spectrum in the last case.
+The weights must be entered into the header during the BSWITCH operation
+since that is the last time that true counts are seen. [An implicit
+assumption is that counts are proportional to photons. If data from
+two different instruments are to be averaged, then the weights should be
+expressed in photons because the ratio of counts to photons is highly
+instrument dependent.]
+.PP
+COMBINE suffers from a partial pixel problem at the end points.
+Interpolation at the ends can lead to an underestimate of the flux
+in the last pixels because the final pixel is not filled. When averaging
+in data from another spectrum or instrument, these pixels show up
+as sharp drops in the spectrum. The problem appears due to the
+rebinning algorithm and should be corrected someday (also in DISPCOR
+and REBIN).
+.sp 1
+.SH
+LCALIB
+.PP
+This utility provides a means of checking the calibration files
+containing the standard star fluxes and extinction table.
+Any of the entries in the file may be listed out - the bandpasses,
+extinction, standard star names, standard star fluxes in either
+magnitudes, flambda, or fnu. For a description of the calibration
+file format, see the help documentation for LCALIB.
+.PP
+The primary uses for LCALIB are to verify that new entries in
+the tables are correct, to generate a list of standard star names
+in a calibration file, and to produce a table of fluxes for a given standard
+star. The table may then be used to generate a spectrum over a specified
+wavelength region using SINTERP and overplotted with observational
+data to check the accuracy of the reductions.
+.sp 1
+.SH
+MKSPEC
+.PP
+MKSPEC provides a way to generate a limited set of artificial
+spectra. Noise generation is not available. The current options
+are to generate a spectrum which is either a constant, a ramp,
+or a black body. The spectrum may be two dimensional, but
+all image lines will be the same.
+.sp 1
+.SH
+NAMES
+.PP
+This is the simplest task in the ONEDSPEC package. It
+generates the image file names which are implied by a
+root name and record string. The primary use for this
+task is to generate a list of image names to be used
+as input for some other program such as WFITS.
+The output from NAMES can be redirected to file
+and that file used with the "@file" notation for image
+name input. An optional parameter allows an additional
+string to be appended to the generated file name
+to allow a subraster specification.
+.sp 1
+.SH
+REBIN
+.PP
+Spectra are rebinned to the wavelength parameters specified
+by either matching to a reference spectrum or by user input.
+The algorithms are those used by DISPCOR and the same options
+for the interpolation method are available. REBIN is useful
+when data are obtained with different instruments or setups
+producing roughly comparable wavelength ranges and possibly
+different dispersions, and the data are to be compared.
+REBIN may also be used as a shift operator by specifying a
+new starting wavelength. Or it may be used as a smoothing operator
+by specifying a course dispersion. It may also be used
+to convert between the two formats - linear in wavelength and
+linear in the logarithm of wavelength. This latter option has
+not been thoroughly exercised - proceed with caution.
+.sp 1
+.SH
+RIDSMTN
+.PP
+This task was stolen from the DATAIO package to make the following
+modification: IIDS and IRS data are both written as 1024 pixel
+spectra at the mountain. But the detectors do not produce a full
+1024 pixels of acceptable data. In fact the IRS only has 936 pixels.
+The data are written this way to conform to the IIDS ideal spectrum
+which does have 1024 pixels, but the first few (about 6) are not usable.
+To signal the good pixels, the IIDS/IRS header words NP1 and NP2 are
+set to the beginning and ending good pixels. Actually NP1 points to
+the first good pixel minus one. [Really actually NP1 and NP2 may be reversed,
+but one is big and the other small so you can tell them apart.]
+.PP
+The version of RIDSMTN in ONEDUTIL keys off these parameters and writes
+images containing only good pixels which means that the images will be
+smaller than 1024 pixels. The user has the option of overriding the
+header values with the task parameters "np1" and "np2". These may be
+specified as 1 and 1024 to capture the entire set of pixels written to
+tape or any other subset. Beware that np1 and np2 as task parameters
+refer to the starting pixel and ending pixel respectively. None of this
+nonsense about possible role reversals or "first good minus one" is
+perpetuated.
+.sp 1
+.SH
+SINTERP
+.PP
+I think this is a handy little program. It provides a way to make
+an IRAF spectral image from a table of values in a text file.
+The table is interpolated out to any length and at any sampling
+rate. A user can create a table of corrections to be applied to
+a set of spectra, for example, use SINTERP to build a spectrum,
+and run CALIBRATE to multiply a group of spectra by the correction.
+.PP
+The original raison d'etre for SINTERP was to create spectra of
+standard stars from the listing of fluxes generated by LCALIB.
+Using SPLOT the created spectrum can be overplotted with calibrated
+observations to compare the true tabulated fluxes with the observed
+fluxes.
+.PP
+SINTERP grew out of the task INTERP in the UTILITIES package
+and works pretty much the same way. One major change is that
+the table containing the x-y pairs is now stored in a dynamically
+allocated array and can be as large as the user requests. The
+default size is 1024 pairs, but the parameter tbl_size can
+be set to a larger value. This then allows one to create a spectrum
+from its tabulated values of wavelength and flux even if the
+the table is several thousand elements long.
+Note that the option to route the output from INTERP to
+STDOUT has been retained if a new table is to be generated rather
+than an IRAF image.
+.PP
+Another major change from INTERP is the use of the IRAF curve fitting
+routines as an option. These were not originally available.
+The choices now include linear or curvey interpolators, Legendre
+or Chebyshev polynomial fits, and cubic or linear splines.
+.sp 1
+.SH
+WIDSTAPE
+.PP
+This task has vague origins in the DATAIO task WIDSOUT which writes
+a tape having the format of the IDSOUT package which ran on the
+CYBER (R.I.P.). For convenience to users this format has been
+maintained for spectra having lengths up to 1024 pixels.
+The version in DATAIO requires that the user enter all the header
+parameters as task parameters. For several hundred spectra, this
+approach is unwieldy. Because the ONEDSPEC package uses the header
+parameters heavily, it is able to read them directly and write the
+values to the tape file without user intervention.
+.PP
+The output tape (or diskfile) may be in either ASCII or EBCDIC format.
+Spectra shorter than 1024 are zero filled. Each invocation of
+the task write a new tape file followed by a tape mark (EOF).
+.LP
+.SH
+3. Image Header Parameters
+.PP
+The ONEDSPEC package uses the extended image header to extract
+information required to direct processing of spectra. If the
+header information were to be ignored, the user would need to
+enter observing parameters to the program at the risk of
+typographical errors, and with the burden of supplying the
+data. For more than a few spectra this is a tedious job,
+and the image header information provides the means to eliminate
+almost all the effort and streamline the processing.
+.PP
+However, this requires that the header information be present,
+correct, and in a recognizable format. To meet the goal of
+providing a functional package in May 1985, the first iteration
+of the header format was to simply adopt the IIDS/IRS headers.
+This allowed for processing of the data which would be first
+used heavily on the system, but would need to be augmented at
+a later date. The header elements may be present in any order,
+but must be in a FITS-like format and have the following names
+and formats for the value fields:
+.sp 1
+.TS
+l c l
+l l l.
+Parameter Value Type Definition
+
+HA SX Hour angle (+ for west, - for east)
+RA SX Right Ascension
+DEC SX Declination
+UT SX Universal time
+ST SX Sidereal time
+AIRMASS R Observing airmass (effective)
+W0 R Wavelength at center of pixel 1
+WPC R Pixel-to-pixel wavelength difference
+NP1 I Index to first pixel containing good data (actually first-1)
+NP2 I Index to last pixel containing good data (last really)
+EXPOSURE I Exposure time in seconds (ITIME is an accepted alias)
+BEAM-NUM I Instrument aperture used for this data (0-49)
+SMODE I Number of apertures in instrument minus one (IIDS only)
+OFLAG I Object or sky flag (0=sky, 1=object)
+DF-FLAG I Dispersion fit made on this spectrum (I=nr coefs in fit)
+SM-FLAG I Smoothing operation performed on this spectrum (I=box size)
+QF-FLAG I Flat field fit performed on this spectrum (0=yes)
+DC-FLAG I Spectrum has been dispersion corrected (0=linear, 1=logarithmic)
+QD-FLAG I Spectrum has been flat fielded (0=yes)
+EX-FLAG I Spectrum has been extinction corrected (0=yes)
+BS-FLAG I Spectrum is derived from a beam-switch operation (0=yes)
+CA-FLAG I Spectrum has been calibrated to a flux scale (0=yes)
+CO-FLAG I Spectrum has been coincidence corrected (0=yes)
+DF1 I If DF-FLAG is set, then coefficients DF1-DFn (n <= 25) exist
+.TE
+.PP
+The values for the parameters follow the guidelines adopted for
+FITS format tapes. All keywords occupy 8 columns and contain
+trailing blanks. Column 9 is an "=" followed by a space. The value field
+begins in column 11. Comments to the parameter may follow a "/" after
+the value field. The value type code is as follows:
+.RS
+.IP SX
+This is a sexigesimal string of the form '12:34:56 ' where the first
+quote appears in column 11 and the last in column 30.
+.IP R
+This is a floating point ("real") value beginning in column 11 and
+extending to column 30 with leading blanks.
+.IP I
+This is an integer value beginning in column 11 and extending to
+column 30 with leading blanks.
+.RE
+.sp 1
+.PP
+The parameters having FLAG designations all default to -1 to indicate
+that an operation has not been performed.
+The ONEDSPEC subroutines "load_ids_hdr" and "store_keywords" follow
+these rules when reading and writing spectral header fields.
+If not present in a header, load_ids_hdr will assume a value of zero
+except that all flags are set to -1, and the object flag parameter
+defaults to object.
+.PP
+When writing an image, only the above parameters are stored by store_keywords.
+Other header information is lost. This needs to be improved.
+.PP
+Not all programs need all the header elements. The following table
+indicates who needs what. Tasks not listed generally do not require
+any header information. Header elements not listed are not used.
+The task SLIST requires all the elements listed above.
+The task WIDTAPE requires almost all (except NP1 and NP2).
+The headings are abbreviated task names as follows:
+.sp 1
+.nr PS 8
+.ps 8
+.TS
+center;
+l l | l l | l l.
+ADD addsets COI coincor FIT flatfit
+BSW bswitch COM combine REB rebin
+CAL calibrate DIS dispcor SPL splot
+COE coefs FDV flatdiv STA standard
+.TE
+.sp 1
+.TS
+center, tab(/);
+l | l | l | l | l | l | l | l | l | l | l | l | l.
+Key/ADD/BSW/CAL/COE/COI/COM/DIS/FDV/FIT/REB/SPL/STA
+_
+HA// X////////// X/
+RA// X////////// X/
+DEC// X////////// X/
+ST// X////////// X/
+UT// X////////// X/
+AIRMASS// X////////// X/
+W0// X/ X/// X//// X/ X/ X/
+WPC// X/ X/// X//// X/ X/ X/
+NP1/////////// X///
+NP2/////////// X///
+EXPOSURE/ X/ X/// X/ X///// X///
+BEAM-NUM// X/ X//// X/ X/ X// X/ X//
+OFLAG// X////////// X/
+DF-FLAG//// X
+DC-FLAG// X//// X//// X/ X/ X/
+QD-FLAG//////// X/
+EX-FLAG// X/
+BS-FLAG// X/
+CA-FLAG/ X// X//////// X/
+CO-FLAG///// X//
+DFn//// X/
+.TE
+.nr PS 11
+.ps 11
+.bp
+.SH
+Headers From Other Instruments
+.PP
+The header elements listed above are currently created only when reading
+IIDS and IRS data from one of the specific readers: RIDSMTN and RIDSFILE.
+The time-like parameters, (RA, DEC, UT, ST, HA), are created in a
+compatible fashion by RCAMERA and RFITS (when the FITS tape is written
+by the KPNO CCD systems).
+.PP
+For any other header information, the ONEDSPEC package is at a loss
+unless the necessary information is edited into the headers with
+an editing task such as HEDIT. This is not an acceptable long term
+mode of operation, and the following suggestion is one approach to
+the header problem.
+.PP
+A translation table can be created as a text file which outlines
+the mapping of existing header elements to those required by the
+ONEDSPEC package. A mapping line is needed for each parameter
+and may take the form:
+.sp 1
+.RS
+.DC
+1D_param default hdr_param key_start value_start type conversion
+.DE
+.RE
+.sp 1
+where the elements of an entry have the following definitions:
+.sp 1
+.TS
+center, tab( );
+l lw(5i).
+1D_param T{
+The name of the parameter expected by the ONEDSPEC package,
+such as EXPOSURE, OFLAG, BEAM-NUM.
+T}
+
+default T{
+A value to be used if no entry is found for this parameter or if
+no mapping exists.
+T}
+
+hdr_param T{
+The string actually present in the existing image header to be
+associated with the ONEDSPEC parameter.
+T}
+
+key_start T{
+The starting column number at which the string starts
+in the header.
+T}
+
+value_start T{
+The starting column number at which the string describing the
+value of the parameter starts in the header.
+T}
+
+type T{
+The format type of the parameter: integer, real, string, boolean,
+sexigesimal.
+T}
+
+conversion T{
+If the format type is string, a further conversion may
+optionally be made to one of the formats listed under type.
+The conversion may requires some expression evaluation.
+T}
+.TE
+.sp 1
+.PP
+Consider the example where the starting wavelength of a
+spectrum is contained in a FITS-like comment and the object-
+sky flag in a similar fashion:
+.sp 1
+.DS
+ COMMENT = START-WAVE 4102.345 / Starting wavelength
+ COMMENT = OBJECT/SKY 'SKY '/ Object or Sky observation
+.DE
+.sp 1
+The translation file entries for this would be:
+.sp 1
+.DS
+ W0 0.0 START-WAVE 12 24 R
+ OFLAG 0 OBJECT/SKY 12 25 S SKY=0;OBJECT=1
+.DE
+.sp 1
+The first entry is fairly simple. The second requires an expression
+evaluation and second conversion.
+.PP
+A translation file can be built for each instrument and its
+special header format, and the file name can be associated with a
+ONEDSPEC package parameter. The two subroutines in ONEDSPEC dealing
+directly with the headers (load_ids_hdr and store_keywords)
+can be modified or replaced to access this file and
+translate the header elements.
diff --git a/noao/onedspec/doc/sys/onedv210.ms b/noao/onedspec/doc/sys/onedv210.ms
new file mode 100644
index 00000000..431c84f5
--- /dev/null
+++ b/noao/onedspec/doc/sys/onedv210.ms
@@ -0,0 +1,680 @@
+.nr PS 9
+.nr VS 11
+.de LS
+.RT
+.if \\n(1T .sp \\n(PDu
+.ne 1.1
+.if !\\n(IP .nr IP +1
+.if \\n(.$-1 .nr I\\n(IR \\$2n
+.in +\\n(I\\n(IRu
+.ta \\n(I\\n(IRu
+.if \\n(.$ \{\
+.ds HT \&\\$1
+.ti -\\n(I\\n(IRu
+\\*(HT
+.br
+..
+.ND
+.TL
+ONEDSPEC/IMRED Package Revisions Summary: IRAF Version 2.10
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+May 1992
+.NH
+Introduction
+.LP
+The IRAF NOAO spectroscopy software, except for the \fBlongslit\fR
+package, has undergone major revisions. The revisions to the aperture
+extraction package, \fBapextract\fR, are described in a separate
+document. This paper addresses the revisions in the \fBonedspec\fR
+package and the spectroscopic image reduction packages in the
+\fBimred\fR package. In addition to the revisions summary given here
+there is a new help topic covering general aspects of the new
+\fBonedspec\fR package such as image formats, coordinate systems, and
+units. This help topic is referenced under the name
+"onedspec.package".
+.LP
+There are a large number of revisions both minor and major. To avoid
+obscuring the basic themes and the major revisions in a wealth of minor
+detail, this document is organized into sections of increasing detail. The
+most important aspects of the revisions are described in a major highlight
+section followed by a minor highlight section. Then a reorganization chart
+for the \fBonedspec\fR package is presented showing where various
+tasks have been moved, which have been deleted, and which are new.
+Finally, a summary of the revisions to each task is presented.
+.LP
+I hope that the many new capabilities, particularly as presented in the
+highlight section, will outweigh any disruption in accomodating to so
+many changes.
+.NH
+Major Highlights
+.LP
+The major highlights of the revisions to the NOAO spectroscopy software
+are listed and then discussed below.
+
+.DS
+\(bu Non-linear dispersion calibration
+\(bu Integration of dispersion coordinates with the core system
+\(bu Sinc interpolation
+\(bu Plotting in user selected units including velocity
+\(bu Integration of long slit spectra and 1D formats
+\(bu New \fBimred\fR packages featuring streamlined reductions
+.DE
+
+Possibly the most significant revision is the generalization allowing
+non-linear dispersion calibration. What this means is that spectra do
+not need to be interpolated to a uniform sampling in wavelength or
+logarithmic wavelength. The dispersion functions determined from
+calibration arc lines by \fBidentify\fR, \fBreidentify\fR,
+\fBecidentify\fR, or \fBecreidentify\fR can be simply assigned to the
+spectra and used throughout the package. It is also possible to assign
+a dispersion table or vector giving the wavelengths at some or all of
+the pixels. Note, however, that it is still perfectly acceptible to
+resample spectra to a uniform linear or log-linear dispersion as was
+done previously.
+.LP
+For data which does not require geometric corrections, combining, or
+separate sky subtraction the observed sampling need never be changed
+from the original detector sampling, thus avoiding any concerns over
+interpolation errors. In other cases it is possible to just
+interpolate one spectrum, say a sky spectrum, to the dispersion of
+another spectrum, say an object spectrum, before operating on the two
+spectra. There are several new tasks that perform interpolations to a
+common dispersion, not necessarily linear, when operating on more than
+one spectrum. In particular, the new task \fBsarith\fR and the older
+task \fBsplot\fR now do arithmetic on spectra in wavelength space.
+Thus, one no longer need be concerned about having all spectra
+interpolated to the same sampling before doing arithmetic operations as
+was the case previously.
+.LP
+The trade-off in using non-linear dispersion functions is a more complex
+image header structure. This will make it difficult to import to non-IRAF
+software or to pre-V2.10 IRAF systems. However, one may resample to a
+linear coordinate system in those cases before transfering the spectra as
+FITS images having standard linear coordinate keywords.
+.LP
+On the subject of interpolation, another important addition is the
+implementation of sinc interpolation. This is generally considered
+the best interpolation method for spectra, however, it must be used
+with care as described below.
+Sinc interpolation approximates applying a phase shift to the fourier
+transform of the spectrum. Thus, repeated interpolations do not accumulate
+errors (or nearly so) and, in particular, a forward and reverse
+interpolation will recover the original spectrum much more closely than
+other interpolation methods. However, for undersampled (where the fourier
+transform is no longer completely represented), strong features, such as
+cosmic rays or narrow emission or absorption lines, the ringing can be much
+more severe than the polynomial interpolations. The ringing is especially
+a concern because it extends a long way from the feature causing the
+ringing; 30 pixels with the truncated algorithm that has been added. Note
+that it is not the truncation of the interpolation function which is at
+fault but the undersampling of the narrow features!
+.LP
+Because of the problems seen with sinc interpolation it should be used with
+care. Specifically, if there are no undersampled, narrow features it is a
+good choice but when there are such features the contamination of the
+spectrum by ringing is more severe, corrupting more of the spectrum,
+than with other interpolation types.
+.LP
+The dispersion coordinates are now interfaced through the IRAF WCS
+(world coordinate system) interface. This is important to users for
+two reasons. First, operations performed on spectral images by IRAF
+core system tasks and the IRAF image I/O system will have access to the
+dispersion coordinates and will properly modify them as necessary. The
+most common such operation is applying an image section to a spectrum
+either during an image copy or as input to another task. In this case
+the relation between the pixels in the image section and their
+wavelengths is preserved. For example one may \fBsplot\fR a section of
+a large spectrum and get the correct wavelengths. The second reason is
+to allow use of proper dispersion coordinates in such IRAF tasks as
+\fBlistpixels\fR, \fBimplot\fR, and \fBgraph\fR.
+.LP
+The new package supports a variety of spectral image formats. The
+older formats are understood when reading them. In particular the one
+dimensional "onedspec" and the two dimensional "multispec" format will
+still be acceptable as input. Note that the image naming syntax for
+the "onedspec" format using record number extensions is a separate
+issue and is still provided but only in the \fBimred.iids\fR and
+\fBimred.irs\fR packages. Any new spectra created are either a one
+dimensional format using relatively simple keywords and a two or three
+dimensional format which treats each line of the image as a separate
+spectrum and uses a more complex world coordinate system and keywords.
+For the sake of discussion the two formats are still called "onedspec"
+and "multispec" though they are not equivalent to the earlier formats.
+.LP
+In addition, the one dimensional spectral tasks may also now operate on
+two dimensional images directly. This is done by using the DISPAXIS
+keyword in the image header or a package dispaxis parameter if the
+keyword is missing to define the dispersion axis. In addition there is
+a summing parameter in the packages to allow summing a number of lines
+or columns. If the spectra are wavelength calibrated long slit
+spectra, the product of the \fBlongslit.transform\fR task, the
+wavelength information will also be properly handled. Thus, one may
+use \fBsplot\fR or \fBspecplot\fR for plotting such data without having
+to extract them to another format. If one wants to extract one
+dimensional spectra by summing columns or lines, as opposed to using
+the more complex \fBapextract\fR package, then one can simply use
+\fBscopy\fR (this effectively replaces \fBproto.toonedspec\fR).
+.LP
+The tasks \fBsplot\fR and \fBspecplot\fR allow use of and changes
+between various dispersion units. Spectra may be plotted in units all
+the way from Hertz to Mev. The units may also be inverted to plot in
+wavenumbers, such as inverse centimeters, and the decimal log may be
+applied, to plot something like log wavelength or log frequency. One
+special "unit" which is available is a velocity computed about a
+specified wavelength/frequency. The multiple unit capability was one
+of the last major changes made before the V2.10 release so the complete
+generalization to arbitrary units has not been completed. Dispersion
+calibration and image world coordinate system generally must still be
+done in Angstroms, particularly if flux calibration is to be done. The
+generalization to other units throughout the package is planned for a
+later release.
+.LP
+The last of the changes catagorized as a major highlight is the
+addition of a number of special packages for generic or specific
+types of instruments and data in the \fBimred\fR package. Most of these
+package include a highly streamlined reduction task that combines
+all of the reduction operations into a single task. For example,
+the \fBspectred.doslit\fR task can extract object, standard star, and
+arc spectra from long slit images, apply a consistent dispersion
+function based on only a single interactively performed dispersion
+solution, compute a sensitivity function and end up with flux
+calibrated spectra. Another example, is \fBhydra.dohydra\fR for
+extracting, flatfielding, dispersion calibrating, and sky subtracting
+spectra from the NOAO Hydra multifiber spectrograph. There are user's
+guides for each of these new reduction tasks.
+.NH
+Minor Highlights
+.LP
+There are some further highlights which are also quite important
+but which are secondary to the previous highlights. These are listed
+and discussed below.
+
+.DS
+\(bu Greater use of package parameters
+\(bu An observatory database
+\(bu A more flexible \fBidentify/reidentify\fR
+\(bu Only one \fBdispcor\fR
+\(bu Spatial interpolation of dispersion solutions
+\(bu Deblending of arbitrary number of gaussian components
+\(bu Manipulating spectral formats
+\(bu Improved fitting of the continuum and related features
+\(bu Various new tasks
+.DE
+
+There is an even greater use of package parameters than in the previous
+release. Package parameters are those which are common to many of the
+the tasks in the package and which one usually wants to change in
+one place. The new package parameters are the default observatory for
+the data if the observatory is not identified in the image header
+(discussed further below), the interpolation type used
+when spectra need to be resampled either for dispersion calibration
+or when operating on pairs of spectra with different wavelength
+calibration, and the default dispersion axis and summing parameters
+for long slit and general 2D images (as discussed in the last section).
+You will find these parameters not only in the \fBonedspec\fR package but in
+all the spectroscopic packages in the \fBimred\fR package.
+.LP
+A number of spectroscopic tasks require information about the location
+of the observation. Typically this is the observatory latitude for
+computing air masses if not defined in the header. Radial velocity
+tasks, and possible future tasks, may require additional information
+such as longitude and altitude. The difficulty is that if such
+parameters are specified in parameter files the default may well be
+inappropriate and even if the users set then once, they may forget to
+update them in later reductions of data from a different observatory.
+In other words this approach is prone to error.
+.LP
+To address this concern observatory parameters are now obtained from an
+observatory database keyed by an observatory identifier. If the image data
+contains an observatory keyword, OBSERVAT, the tasks will look up the
+required parameters from the observatory database. Thus, if the images
+contain the observatory identifier, as does data from the NOAO
+observatories, they will always be correctly reduced regardless of the
+setting of any parameters. Of course one has to deal with data from
+observatories which may not include the observatory identifier and may not
+have an entry in the observatory database. There are provisions for sites
+and individual users to define local database files and to set the default
+observatory parameters. This is all discussed in the help for the
+\fBobservatory\fR task.
+.LP
+The dispersion function fitting tasks \fBidentify\fR and
+\fBreidentify\fR have been improved in a number of important ways.
+These tasks now treat the input images as units. So for long slit and
+multispectrum images one can move about the image with a few
+keystrokes, transfer solutions, and so on. When transfering solutions
+between a multispectrum reference image and another multispectrum image
+with the same apertures using \fBreidentify\fR, the features and
+dispersion solutions are transfered aperture by aperture. This avoids
+problems encountered by having to trace successively between apertures
+and having the apertures be in the same order.
+.LP
+On the subject of tracing in \fBreidentify\fR, in some cases it is
+desirable to use the same reference spectrum with all other sampled
+lines or columns in a long slit spectrum or apertures in a
+multispectrum image rather than propagating solutions across the
+image. The latter method is necessary if there is a continuous and
+progress shift in the features. But if this is not the situation then
+the loss of features when tracing can be a problem. In this case the
+alternative of reidentifying against the same starting reference is now
+possible and there will not be the problem of an increasing loss of
+features. On the other hand, the problem of lost features, whether
+tracing or not, can also be addressed using another new feature of
+\fBreidentify\fR, the ability to add features from a line list. For
+both tracing and nontracing reidentifications, another useful new
+feature is automatic iterative rejection of poorly fitting lines in
+determining a new dispersion function noninteractively.
+.LP
+The nontracing reidentifications, the automatic addition of new lines, and
+the iterative rejection of poorly fitting lines in determining a new
+dispersion function are all responses to make the reidentification process
+work better without intervention. However, as a last resort there is also
+a new interactive feature of \fBreidentify\fR. By monitoring the log output of
+the reidentification process one can have a query be made after the
+automatic reidentification and function fitting to allow selectively
+entering the interactive feature identification and dispersion function
+fitting based on the logged output. Thus if a fit has a particularly large
+RMS or a large number of features are not found one can chose to intervene
+in the reidentification process.
+.LP
+Dispersion calibration is now done exclusively by the task
+\fBdispcor\fR regardless of the spectrum format or dispersion solution
+type; i.e. solutions from \fBidentify\fR or \fBecidentify\fR. In addition to
+allowing assignment of non-linear dispersion functions, as described
+earlier, \fBdispcor\fR has other new features. One is that, in
+addition to interpolating dispersion solutions between two calibration
+images (usually weighted by time), it is now possible to interpolate
+zero point shifts spatially when multiple spectra taken simultaneously
+include arc spectra. This is mostly intended for the new generation of
+multifiber spectrographs which include some fibers assigned to an arc
+lamp source. However, it can be used for the classic photographic case
+of calibration spectra on the same plate.
+.LP
+The limitation to four lines on the number of gaussian components which
+can be deblended by the deblending option in \fBsplot\fR has been removed.
+A new feature is that line positions may be input from a line list as
+well as the original cursor marking or terminal input.
+In addition an option to simultaneously determine a linear background
+has been added. As a spinoff of the deblending option a new, noninteractive
+task, called FITPROFS, has been added. This task takes a list of initial
+line positions and sigmas and simultaneously fits gaussians with a
+linear background. One can constrain various combination of parameters
+and output various parameters of the fitting. While it can be used to
+fit an entire spectrum it becomes prohibitively slow beyond a number like
+30. A banded matrix approach is required in that case.
+.LP
+As mentioned earlier there is a new task called \fBscopy\fR for manipulating
+spectra. It allows changing between various formats such as producing
+the separate, simple keyword structure, one dimensional images from multispec
+format images, combining multiple one dimensional spectra into the
+more compact multispec format, and extracting line or column averaged one
+dimensional spectra from two dimensional images. It can also be
+used to select any subset of apertures from a multispec format,
+merge multiple multispec format spectra, and extract regions of spectra
+by wavelength.
+.LP
+The \fBcontinuum\fR task has been revised to allow independent
+continuum fits for each aperture, order, line, or column in images
+containing multiple spectra. Instead of being based on the
+\fBimages.fit1d\fR task it is based on the new task \fBsfit\fR.
+\fBSfit\fR allows fitting the \fBicfit\fR functions to spectra and
+outputing the results in several ways such as the ratio (continuum
+normalization), difference (continuum subtraction), and the actual
+function fit. In addition it allows outputing the input data with
+points found to be deviant by the iterative rejection algorithm of
+\fBicfit\fR replaced by the fitted value. This is similar to
+\fBimages.lineclean\fR. In all cases, this is may be done
+independently and interactively or noninteractively when there are
+multiple spectra in an image.
+.LP
+A number of useful new tasks have already been mentioned:
+\fBfitprofs\fR, \fBsarith\fR, \fBscombine\fR, \fBscopy\fR, and
+\fBsfit\fR. There are two more new tasks of interest. The task \fBdopcor\fR
+applies doppler shifts to spectra. It applies the shift purely to the
+dispersion coordinates by adding a redshift factor which is applied by
+the coordinate system interface. This eliminates reinterpolation and
+preserves both the shift applied and the original observed dispersion
+function (either linear or nonlinear). The task can also modify the
+pixel values for various relativistic and geometric factors. This task
+is primarily useful for shifting spectra at high redshifts to the local
+rest frame. The second new task is called \fBderedden\fR. It applies
+corrections for interstellar reddening given some measure of the
+extinction along the line of site.
+.NH
+ONEDSPEC Package Task Reorganization
+.LP
+The \fBonedspec\fR package dates back to the earliest versions of IRAF. Some of
+its heritage is tied to the reduction of IRS and IIDS spectra. One of
+the revisions made for this release has been to reorganize the various
+tasks and packages. A few tasks have been obsoleted by new tasks or
+the functionality of the new dispersion coordinate system, a number
+of new tasks have been added, and a number of IRS and IIDS specific
+tasks have been moved to the \fBimred\fR packages for those instruments.
+While these packages are organized for those particular instruments they may
+also be used by data having similar characteristics of beam switching,
+coincidence corrections, and the requirement of sequential numeric
+extensions.
+.LP
+The table below provides the road map to the reorganization showing
+tasks which have disappeared, been moved, been replaced, or are new.
+
+.DS
+.TS
+center;
+r l l l r l l.
+V2.9 V2.10 ALTERNATIVE V2.9 V2.10 ALTERNATIVE
+
+addsets irs/iids process irs/iids
+batchred irs/iids rebin scopy/dispcor
+bplot bplot refspectra refspectra
+bswitch irs/iids reidentify reidentify
+calibrate calibrate sapertures
+coincor iids sarith
+combine scombine scombine
+continuum continuum scopy
+ deredden sensfunc sensfunc
+dispcor dispcor setdisp hedit
+ dopcor sextract scopy
+ fitprofs sfit
+flatdiv irs/iids sflip scopy/imcopy [-*,*]
+flatfit irs/iids shedit hedit
+identify identify sinterp sinterp
+lcalib lcalib slist slist
+mkspec mkspec specplot specplot
+names names splot splot
+ ndprep standard standard
+observatory noao subsets irs/iids
+powercor iids sums irs/iids
+.TE
+.DE
+.NH
+IMRED Packages
+.LP
+Many of the \fBonedspec\fR tasks from the previous release have been
+moved to the \fBiids\fR and \fBirs\fR packages, as indicated above,
+since they were applicable only to these and similar instruments.
+.LP
+A number of new specialized spectroscopic instrument reduction packages
+have been added to the \fBimred\fR package. Many of these have been in
+use in somewhat earlier forms in the IRAF external package called
+\fBnewimred\fR. In addition the other spectroscopic package have been
+updated based on the revisions to the \fBonedspec\fR and
+\fBapextract\fR packages. Below is a table showing the changes between
+the two version and describing the purpose of the spectroscopic
+packages. Note that while many of these package are named for and
+specialized for various NOAO instruments these packages may be applied
+fairly straightforwardly to similar instruments from other
+observatories. In addition the same tools for multifiber and slit
+spectra are collected in a generic package called \fBspecred\fR.
+
+.DS
+.TS
+center;
+r l l s
+r l l l.
+V2.9 V2.10 SPECTROSCOPY PACKAGE
+ argus Fiber: CTIO Argus Reductions
+specphot ctioslit Slit: CTIO Slit Instruments
+echelle echelle Fiber Slit: Generic Echelle
+ hydra Fiber: KPNO Hydra (and Nessie) Reductions
+iids iids Scanner: KPNO IIDS Reductions
+irs irs Scanner: KPNO IRS Reductions
+coude kpnocoude Fiber/Slit: KPNO Coude (High Res.) Reductions
+ kpnoslit Slit: KPNO Slit Instruments
+msred specred Fiber/Slit: Generic fiber and slit reductions
+observatory -> noao
+setairmass
+.TE
+.DE
+.LP
+An important feature of most of the spectroscopic packages are specialized
+routines for combining and streamlining the different reduction operations
+for a particular instrument or type of instrument. These tasks are:
+
+.DS
+.TS
+center;
+r r r.
+argus.doargus ctioslit.doslit echelle.doecslit
+echelle.dofoe hydra.dohydra iids.batchred
+irs.batchred kpnocoude.do3fiber kpnocoude.doslit
+kpnoslit.doslit specred.dofibers specred.doslit
+.TE
+.DE
+.NH
+ONEDSPEC Task Revisions in V2.10
+.LS ADDSETS 2
+Moved to the \fBiids/irs\fR packages.
+.LS BATCHRED
+Moved to the \fBiids/irs\fR packages.
+.LS BPLOT
+The APERTURES and BAND parameters been added to select
+apertures from multiple spectra and long slit images, and bands
+from 3D images. Since the task is a script calling \fBsplot\fR, the
+many revisions to that task also apply. The version in the
+\fBiids/irs\fR packages selects spectra using the record number
+extension syntax.
+.LS BSWITCH
+Moved to the \fBiids/irs\fR packages.
+.LS CALIBRATE
+This task was revised to operate on nonlinear dispersion
+corrected spectra and 3D images (the \fBapextract\fR "extras"). The
+aperture selection parameter was eliminated (since the header
+structure does not allow mixing calibrated and uncalibrated
+spectra) and the latitude parameter was replaced by the
+observatory parameter. The observatory mechanism insures that
+if the observatory latitude is needed for computing an airmass
+and the observatory is specified in the image header the
+correct calibration will be applied. The record format syntax
+is available in the \fBiids/irs\fR packages. The output spectra are
+coerced to have real pixel datatype.
+.LS COINCOR
+Moved to the \fBiids\fR package.
+.LS COMBINE
+Replaced by \fBscombine\fR.
+.LS CONTINUUM
+This task was changed from a script based on \fBimages.fit1d\fR to a
+script based on \fBsfit\fR. This provides for individual independent
+continuum fitting in multiple spectra images and for additional
+flexibility and record keeping. The parameters have been
+largely changed.
+.LS DEREDDEN
+This task is new.
+.LS DISPCOR
+This is a new version with many differences. It replaces the
+previous three tasks \fBdispcor\fR, \fBecdispcor\fR and \fBmsdispcor\fR. It
+applies both one dimensional and echelle dispersion functions.
+The new parameter LINEARIZE selects whether to interpolate the
+spectra to a uniform linear dispersion (the only option
+available previously) or to assign a nonlinear dispersion
+function to the image without any interpolation. The
+interpolation function parameter has been eliminated and the
+package parameter INTERP is used to select the interpolation
+function. The new interpolation type "sinc" may be used but
+care should be exercised. The new task supports applying a
+secondary zero point shift spectrum to a master dispersion
+function and a spatial interpolation of the shifts when
+calibration spectra are taken at the same time on a different
+region of the same 2D image. The optional wavelength table may
+now also be an image to match dispersion parameters. The
+APERTURES and REBIN parameters have been eliminated. If an
+input spectrum has been previously dispersion corrected it will
+be resampled as desired. Verbose and log file parameters have
+been added to log the dispersion operations as desired. The
+record format syntax is available in the \fBiids/irs\fR packages.
+.LS DOPCOR
+This task is new.
+.LS FITPROFS
+This task is new.
+.LS FLATDIV
+Moved to the \fBiids/irs\fR packages.
+.LS FLATFIT
+Moved to the \fBiids/irs\fR packages.
+.LS IDENTIFY
+The principle revision is to allow multiple aperture images and
+long slit spectra to be treated as a unit. New keystrokes
+allow jumping or scrolling within multiple spectra in a single
+image. For aperture spectra the database entries are
+referenced by image name and aperture number and not with image
+sections. Thus, \fBidentify\fR solutions are not tied to specific
+image lines in this case. There is a new autowrite parameter
+which may be set to eliminate the save to database query upon
+exiting. The new colon command "add" may be used to add
+features based on some other spectrum or arc type and then
+apply the fit to the combined set of features.
+.LS LCALIB
+This task has a more compact listing for the "stars" option and
+allows paging a list of stars when the star name query is not
+recognized.
+.LS MKSPEC
+This task is unchanged.
+.LS NAMES
+This task is unchanged.
+.LS NDPREP
+This task was moved from the \fBproto\fR package. It was originally
+written at CTIO for CTIO data. It's functionality is largely
+unchanged though it has been updated for changes in the
+\fBonedspec\fR package.
+.LS OBSERVATORY
+New version of this task moved to \fBnoao\fR root package.
+.LS POWERCOR
+Moved to the \fBiids\fR package.
+.LS PROCESS
+Moved to the \fBiids/irs\fR package.
+.LS REBIN
+This task has been eliminated. Use \fBscopy\fR or \fBdispcor\fR.
+.LS REFSPECTRA
+A group parameter was added to allow restricting assignments by
+observing period; for example by night. The record format
+option was removed and the record format syntax is available in
+the \fBiids/irs\fR packages.
+.LS REIDENTIFY
+This task is a new version with many new features. The new
+features include an interactive options for reviewing
+identifications, iterative rejection of features during
+fitting, automatic addition of new features from a line list,
+and the choice of tracing or using a single master reference
+when reidentifying features in other vectors of a reference
+spectrum. Reidentifications from a reference image to another
+image is done by matching apertures rather than tracing. New
+apertures not present in the reference image may be added.
+.LS SAPERTURES
+This task is new.
+.LS SARITH
+This task is new.
+.LS SCOMBINE
+This task is new.
+.LS SCOPY
+This task is new.
+.LS SENSFUNC
+The latitude parameter has been replaced by the observatory
+parameter. The 'i' flux calibrated graph type now shows flux
+in linear scaling while the new graph type 'l' shows flux in
+log scaling. A new colon command allows fixing the flux limits
+for the flux calibrated graphs.
+.LS SETDISP
+This task has been eliminated. Use \fBhedit\fR or the package
+DISPAXIS parameter.
+.LS SEXTRACT
+Replaced by \fBscopy\fR.
+.LS SFIT
+This task is new.
+.LS SFLIP
+This task has been eliminated. Use image sections.
+.LS SHEDIT
+This task has been eliminated. Use \fBhedit\fR if needed.
+.LS SINTERP
+This task is unchanged.
+.LS SLIST
+This task was revised to be relevant for the current spectral
+image formats. The old version is still available in the
+\fBiids/irs\fR package.
+.LS SPECPLOT
+New parameters were added to select apertures and bands, plot
+additional dimensions (for example the additional output from
+the extras option in \fBapextract\fR), suppress the system ID banner,
+suppress the Y axis scale, output a logfile, and specify the
+plotting units. The PTYPE parameter now allows negative
+numbers to select histogram style lines. Interactively, the
+plotting units may be changed and the 'v' key allows setting a
+velocity scale zero point with the cursor. The new version
+supports the new spectral WCS features including nonlinear
+dispersion functions.
+.LS SPLOT
+This is a new version with a significant number of changes. In
+addition to the task changes the other general changes to the
+spectroscopy packages also apply. In particular, long slit
+spectra and spectra with nonlinear dispersion functions may be
+used with this task. The image header or package dispaxis and
+nsum parameters allow automatically extracting spectra from 2D
+image. The task parameters have been modified primarily to
+obtain the desired initial graph without needing to do it
+interactively. In particular, the new band parameter selects
+the band in 3D images, the units parameter selects the
+dispersion units, and the new histogram, nosysid, and xydraw
+options select histogram line type, whether to include a system
+ID banner, and allow editing a spectrum using different
+endpoint criteria.
+.LS
+Because nearly every key is used there has been some shuffling,
+consolidating, or elimination of keys. One needs to check the
+run time '?' help or the help to determine the key changes.
+.LS
+Deblending may now use any number of components and
+simultaneous fitting of a linear background. A new simplified
+version of gaussian fitting for a single line has been added in
+the 'k' key. The old 'k', 'h', and 'v' equivalent width
+commands are all part of the single 'h' command using a second
+key to select a specific option. The gaussian line model from
+these modes may now be subtracted from the spectrum in the same
+way as the gaussian fitting. The one-sided options, in
+particular, are interesting in this regard as a new capability.
+.LS
+The arithmetic functions between two spectra are now done in
+wavelength with resampling to a common dispersion done
+automatically. The 't' key now provides for the full power of
+the ICFIT package to be used on a spectrum for continuum
+normalization, subtraction, or line and cosmic ray removal.
+The 'x' editing key may now use the nearest pixel values rather
+than only the y cursor position to replace regions by straight
+line segments. The mode is selected by the task option
+parameter "xydraw".
+.LS
+Control over the graph window (plotting limits) is better
+integrated so that redrawing, zooming, shifting, and the \fBgtools\fR
+window commands all work well together. The new 'c' key resets
+the window to the full spectrum allowing the 'r' redraw key to
+redraw the current window to clean up overplots from the
+gaussian fits or spectrum editing.
+.LS
+The dispersion units may now be selected and changed to be from
+hertz to Mev and the log or inverse (for wave numbers) of units
+taken. As part of the units package the 'v' key or colon
+commands may be used to plot in velocity relative to some
+origin. The $ key now easily toggles between the dispersion
+units (whatever they may be) and pixels coordinates.
+.LS
+Selection of spectra has become more complex with multiaperture
+and long slit spectra. New keys allow selecting apertures,
+lines, columns, and bands as well as quickly scrolling through
+the lines in multiaperture spectra. Overplotting is also more
+general and consistent with other tasks by using the 'o' key to
+toggle the next plot to be overplotted. Overplots, including
+those of the gaussian line models, are now done in a different
+line type.
+.LS
+There are new colon commands to change the dispersion axis and
+summing parameters for 2D image, to toggle logging, and also to
+put comments into the log file.
+.LS STANDARD
+Giving an unrecognized standard star name will page a list of
+standard stars available in the calibration directory and then
+repeat the query.
+.LS SUBSETS
+Moved to the \fBiids/irs\fR packages.
+.LS SUMS
+Moved to the \fBiids/irs\fR packages.
diff --git a/noao/onedspec/doc/sys/revisions.v3.ms b/noao/onedspec/doc/sys/revisions.v3.ms
new file mode 100644
index 00000000..1c3da8be
--- /dev/null
+++ b/noao/onedspec/doc/sys/revisions.v3.ms
@@ -0,0 +1,382 @@
+.nr PS 9
+.nr VS 11
+.RP
+.ND
+.TL
+ONEDSPEC Package Revisions Summary: IRAF Version 2.10
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+July 1990
+.AB
+This paper summarizes the changes in Version 3 of the IRAF \fBonedspec\fR
+package which is part of IRAF Version 2.10. The major new features and
+changes are:
+
+.IP \(bu
+\fBIdentify\fR and \fBreidentify\fR now treat multispec format spectra
+and two dimensional images as a unit.
+.IP \(bu
+\fBReidentify\fR supports both tracing (the old method) and always starting
+with the primary reference vector when reidentifying other vectors in a
+two dimensional reference image.
+.IP \(bu
+\fBReidentify\fR matches reference lines or apertures when reidentifying
+those vectors in different images rather than tracing.
+.IP \(bu
+\fBReidentify\fR has an interactive capability to review
+suspect reidentifications.
+.IP \(bu
+\fBReidentify\fR provides the capability to add new features.
+.IP \(bu
+The task \fBmsdispcor\fR provides for spatial interpolation of wavelength
+zero point shifts from simultaneous arc spectra.
+.IP \(bu
+The new task \fBscopy\fR copies subsets of apertures and does format
+conversions between the different spectrum formats.
+.IP \(bu
+The new task \fBsapertures\fR adds or modifies beam numbers and
+aperture titles for selected apertures based on an aperture
+identification file.
+.IP \(bu
+The new task \fBsfit\fR fits spectra and outputs the fits in various ways.
+Apertures in multispec and echelle format are fit independently.
+.IP \(bu
+The task \fBcontinuum\fR now does independent fits for multispec and
+echelle format spectra.
+.IP \(bu
+\fBSplot\fR now allows deblending of any number of components and
+allows simultaneous fitting of a linear background.
+.IP \(bu
+The new task \fBfitprofs\fR fits 1D gaussian profiles in images.
+.AE
+.NH
+Introduction
+.PP
+Though most of the ONEDSPEC package is unchanged there have been
+significant changes to a number of the commonly used tasks in IRAF
+Version 2.10. The changes will be made available as part of an
+external package prior to the release of V2.10. This paper summarizes
+the changes and new features. The changes primarily apply to multispec
+or echelle format spectra.
+.PP
+Tables 1 and 2 summarize most of the major and minor changes in the package.
+
+.ce
+TABLE 1: Summary of Major New Features and Changes
+
+.IP \(bu
+\fBIdentify\fR and \fBreidentify\fR now treat multispec format spectra
+and two dimensional images as a unit allowing easy movement between
+different image lines or columns. The database is only updated upon
+exiting the image.
+.IP \(bu
+\fBReidentify\fR supports both tracing (the old method) and always starting
+with the primary reference vector when reidentifying other vectors in a
+two dimensional reference image.
+.IP \(bu
+\fBReidentify\fR matches reference lines or apertures when reidentifying
+those vectors in different images rather than tracing.
+.IP \(bu
+\fBReidentify\fR has an interactive capability to review
+suspect reidentifications.
+.IP \(bu
+\fBReidentify\fR provides the capability to add new features.
+.IP \(bu
+The task \fBmsdispcor\fR allows using
+auxilary reference spectra to provide a shift in the wavelength
+zero point to the primary dispersion functions. This includes
+spatial interpolation of simultaneous arc spectra in multifiber
+spectrographs.
+.IP \(bu
+The new task \fBscopy\fR copies subsets of apertures and does format
+conversions between the different spectrum formats.
+.IP \(bu
+The new task \fBsapertures\fR adds or modifies beam numbers and
+aperture titles for selected apertures based on an aperture
+identification file.
+.IP \(bu
+The new task \fBsfit\fR fits spectra and outputs the fits in various ways.
+This includes a new feature to replace deviant points by the fit.
+Apertures in multispec and echelle format are fit independently.
+.IP \(bu
+The task \fBcontinuum\fR now does independent fits for multispec and
+echelle format spectra.
+.IP \(bu
+\fBSplot\fR now allows deblending of any number of components and
+allows simultaneous fitting of a linear background.
+.IP \(bu
+The new task \fBfitprofs\fR fits 1D gaussian profiles to spectral lines or
+features in an image line or column. This is done noniteractively and
+driven by an input list of feature positions.
+.bp
+.LP
+.ce
+TABLE 2: Summary of Other New Features and Changes
+
+.IP \(bu
+The \fBidentify\fR database format uses aperture numbers rather than
+image sections for multispec format spectra.
+.IP \(bu
+The apertures in multispec format images need not be in the same order
+or even the same number of apertures as the reference image in
+\fBreidentify\fR or \fBmsdispcor\fR.
+.IP \(bu
+An automatic write parameter has been added to \fBidentify\fR.
+.IP \(bu
+The tasks \fBmsdispcor\fR and \fBspecplot\fR support the extra information
+in the third dimension of multispec format spectra which is optionally
+output by the \fBapextract\fR package.
+.IP \(bu
+\fBMsdispcor\fR and \fBspecplot\fR now include a logfile.
+.IP \(bu
+\fBSplot\fR selects spectra from multispec or echelle format by their
+aperture number. Also a new keystroke was added to select a new
+line/aperture without having to enter the image name again.
+.IP \(bu
+The task \fBspecplot\fR may select apertures from a multispec or
+echelle format spectrum.
+.IP \(bu
+The aperture identification in multispec format is used, if present,
+for labeling in \fBsplot\fR, \fBspecplot\fR, and \fBstandard\fR.
+.NH
+IDENTIFY and REIDENTIFY
+.PP
+These tasks have been modified for greater flexibility when dealing with
+two dimensional images and multispec format spectra in particular. These
+tasks were initially designed primarily to work on one dimensional images
+with provisions for two dimensional images through image sections and
+tracing to other parts of the image. Now these tasks treat such images
+as a unit.
+.PP
+The task \fBidentify\fR has three added keystrokes, 'j', 'k', and 'o'.
+These provide for moving between lines and columns of a two dimensional
+image and different apertures in a multispec format spectrum. When
+changing vectors the last set of features and fit are recalled, if they
+have been previously defined, or the last set of features and fit are
+inherited. For efficiency and to minimize queries, the feature
+information from all the lines or apertures is not written to the
+database until you quit the image (or explicitly write it) rather than
+one at a time. A new parameter was also added, \fIautowrite\fR, which
+may be set to automatically write the results to the database rather
+than querying as is currently done.
+.PP
+The format of the database entries have also been slightly modified in
+the case of multispec format images. Instead of using image sections
+as part of the image name to define different vectors in the image
+(this is still the case for regular two dimensional images) the aperture
+number is recorded. This decouples the solutions for an aperture from
+the specific image line allowing reference images to have a different
+aperture order and additional or missing apertures.
+.PP
+While the changes to \fBidentify\fR are minor as far as usage, the task
+\fBreidentify\fR is quite different and is essentially a new program.
+Much of the complexity in this task relates to two dimensional images.
+Two additions that apply to both one and two dimensional images is the
+capability to add features from a coordinate list and to interactively
+review the reidentifications using \fBidentify\fR. The addition of new
+features may be useful in cases where the signal-to-noise varies or to
+compensate for lost features when tracing across an image. The review
+capability first prints the statistical results and then ask the user if
+they want to examine the results interactively . This allows
+basing the decision to interactively examine the features and fit based
+on this information. Ideally, only a few of the worst cases need be
+examined interactively.
+.PP
+There are two phases of reidentifications which apply to two
+dimensional and multispec format images. In the first phase, one needs
+to expand the identifications in the reference image from an initial,
+interactively defined line, column, or aperture to other parts of the
+reference image. A very important change is that there are now two
+ways to transfer the features list; by successive steps (tracing) using
+the previous results as a starting point (the only method provided in
+the previous version) or always starting from the original reference
+list. The first method is suitable for long slit spectra which have
+significant positional trends across the image. If a feature is lost,
+however, the feature remains missing (barring automatic addition as
+mentioned above) for all following lines or columns. The latter method
+is best if there are only small variations relative to the initial
+reference or in multispec format spectra where there is no inherent
+relation between apertures.
+.PP
+The second phase of reidentifications is between the reference image
+and other images. In the previous version the primary reference vector
+was transferred to the new image and then tracing would be applied
+again. This compounds the problem with losing features during tracing
+and prevents any possible reidentifications from multispec images in
+which the wavelength range may vary greatly. In the new version there
+is a direct reidentification from the same line, column, or aperture in
+the reference to that of the next image. In the case where different
+apertures may have significantly different wavelength coverage, as
+occurs with aperture masks, it will at least be possible to
+interactively identify features and coordinate functions for each
+aperture, using the scrolling capability in the new \fBidentify\fR, in
+just a single image and then correctly transfer the features to
+additional images.
+.PP
+For multispec format spectra the database information is organized by
+aperture number independent of image line number. Thus, it is possible
+to reidentify features in multispec format spectra even if the aperture
+order is different. If there is only a partial overlap in the aperture
+set only those apertures having an entry in the reference image will be
+done.
+.NH
+MSDISPCOR
+.PP
+The task \fBmsdispcor\fR dispersion corrects (rebins to a linear
+dispersion function) multispec format spectra. It was introduced in
+V2.8 of IRAF in the prototype \fBimred.msred\fR package. A number of
+changes have been made in this task as summarized here.
+.PP
+The most fundamental change is support for spatial interpolation of
+reference dispersion functions from a subset of apertures to other
+apertures originating at different positions in a two dimensional
+image. This is primarily intended for the case of comparison arc
+spectra which are interspersed with object spectra in multifiber
+spectrographs. It would also be useful in digitized photographic
+spectra having calibration spectra exposed next to the object
+spectrum. While usable directly, this feature is intended for the
+processing scripts in the new \fBimred\fR fiber instrument packages.
+.PP
+The interpolation is only for a wavelength zero point shift, as determined
+by \fBreidentify\fR with \fIrefit\fR=no. The full dispersion function
+is still provided by a calibration image covering all apertures. Thus,
+the simultaneous arc apertures are used to monitor shifts in the
+detector relative to the full calibration which includes the relative
+differences between each aperture and the arc monitoring apertures.
+.PP
+The multispec spectra containing the apertures used for the spatial
+wavelength zero point corrections are specified in the image header
+using the keywords REFSHFT1 and REFSHFT2. These are analogous to
+the REFSPEC keywords used to define the reference dispersion functions
+for the apertures.
+.PP
+As part of the general theme of multispec format support the
+multispec dispersion reference spectra may have additional spectra and
+need not be in the same order. However, all aperture in the
+images being dispersion corrected must have dispersion relations
+in the database. Multispec format spectra may include additional
+data in the 3rd image dimension produced by the new
+\fBapextract\fR package. \fBMsdispcor\fR rebins this information
+in the same way as the spectra, thus, preserving the information
+but now in linear wavelength sampling.
+.PP
+A new parameter, \fIlogfile\fR, has been added to capture information
+about the dispersion correction process.
+.NH
+SCOPY and SAPERTURES
+.PP
+The task \fBscopy\fR is intended to bridge the gap between the various
+spectrum formats and provide a tool to flexibly manipulate multispec
+format spectra. It replaces the more primitve tasks
+\fBmsred.msselect\fR and \fBechelle.ecselect\fR. Basically, this task
+copies all or selected spectra from one format to a new image or images
+of the same or different format. The typical uses are:
+
+.IP \(bu
+Extract selected spectra from a multispec format image.
+.IP \(bu
+Allow converting the voluminous onedspec format from previous reductions
+done before the multispec format was introduced into the more compact
+multispec format.
+.IP \(bu
+Splice selected apertures from different multispec images into a new
+multispec image.
+.IP \(bu
+Provide a quick way to convert lines or columns from two dimensional
+long slit images into one dimensional spectra. This replaces
+the task \fBproto.toonedspec\fR.
+.PP
+Because \fBscopy\fR can easily change the number and order of apertures
+in the multispec image format it is important that the other tasks which
+use the multispec format have been modified to be insensitive to which
+line a spectrum is in and generally key off the aperture number.
+.PP
+The task \fBsapertures\fR is a simple way to set the aperture identifications,
+APID keyword, and beam number, second field of APNUM keyword, based on
+the aperture number and a simple text file. The text file contains lines
+with aperture number, beam number, and (optional) title. This file is
+used by the \fBapextract\fR package as well. Its likely usage is
+to change image titles which might be wrong because of being inherited
+from an aperture reference image during extraction.
+.NH
+SFIT, CONTINUUM, and ECCONTINUUM
+.PP
+The original version of \fBcontinuum\fR was a simple script based on
+the task \fBfit1d\fR. The problem is that \fBfit1d\fR is intended to
+process all the lines or columns in a two dimensional image
+noninteractively. To do this it applies the same fitting parameters to
+every line or column. The interactive step in this task is simply to
+adjust fitting parameters. For spectra, particularly multispec and
+echelle format spectra, one often needs to fit each spectrum
+interactively and independently. When this problem was encountered for
+the \fBechelle\fR package Rob Seaman wrote a nice program,
+\fBeccontinuum\fR, which allows fitting a set of orders and keeps track
+of which orders have been fit.
+.PP
+The general feature of the continuum fitting tasks is that they fit
+spectra using the \fBicfit\fR interactive function fitting interface.
+The results of the fit may be output as the fit itself, the difference
+or residuals, the ratio, or the input data with rejected points replaced
+by the fitted values. The last feature is new an provides a useful
+spectrum cleaning option. The general equivalent to \fBfit1d\fR is
+the new task \fBsfit\fR which provides the same independent fitting and
+image line selection capabilites as \fBeccontinuum\fR. Note this task
+is line oriented and does not select by aperture or order number. The
+revised version of \fBcontinuum\fR is now based on \fBsfit\fR and
+provides the independent continuum fitting capability for onedspec and
+multispec format spectra that \fBeccontinuum\fR provides for echelle
+format spectra. Technically what has been done is that \fBsfit\fR,
+\fBcontinuum\fR, and \fBeccontinuum\fR are the same task; essentially
+the task written by Seaman for echelle data. They differ in the
+default parameters with the continuum fitting task having default
+parameters providing continuum normalization (ratio) output and
+iterative rejection values for excluding lines.
+.NH
+SPLOT, FITPROFS, and SPECPLOT
+.PP
+\fBSplot\fR has been modified to better support multispec and echelle
+format images. The line selection for multispec and echelle format
+spectra is now in terms of the aperture number rather than the image
+line. The aperture title is used in place of the image title
+if present.
+.PP
+The restriction to a maximum of four lines in the gaussian fitting and
+deblending option of \fBsplot\fR has been lifted. Any number of
+lines may be fit simultaneously, though execution time will become
+long for a large number. In addition the fitting allows determining
+a simultaneous linear background as well as using the cursor defined
+points. The positions of the lines to be fit may be marked with
+the cursor, typed in, or read from a file. The last choice is a new
+feature.
+.PP
+In the past many people have used \fBsplot\fR for bulk, noninteractive
+gaussian fitting by going through the trouble of redirecting the cursor
+input, ukey input, text output, and graphics output. The main reason
+this has been done is the lack of a one dimensional gaussian fitting
+task. The task \fBfitprofs\fR has been added to provide simultaneous
+gaussian fitting. This task takes a list of positions and optional
+sigmas and fits gaussians to a list of images or spectra. The lines,
+columns, or apertures may be selected. In addition a linear
+background may be specified or included in the fitting. The output
+consists of any combination of text similiar to the \fBsplot\fR
+logfile, plots showing the data and fit, and image output of the fit or
+the difference. This task is noninteractive; the interactive version
+is the deblend command of \fBsplot\fR. The multiparameter, nonlinear
+fitting software is the same as used in \fBsplot\fR.
+.PP
+\fBFitprofs\fR complements the task \fBstsdas.fitting.ngaussfit\fR from
+the \fBstsdas\fR package (available from the Space Telescope Science
+Institute). This task is similar in character to \fBfit1d\fR and has
+an interactive one dimensional nonlinear function fitting interface
+similar to \fBicfit\fR.
+.PP
+The task \fBspecplot\fR has a new parameter to select apertures to
+plot. Previously there was no way to limit the apertures plotted other
+than with image sections. All associated lines of a multispec
+spectrum (those in the third dimension) are also plotted for the
+selected apertures. This extra information is a new option of the
+\fBapextract\fR package.
diff --git a/noao/onedspec/doc/sys/revisions.v31.ms b/noao/onedspec/doc/sys/revisions.v31.ms
new file mode 100644
index 00000000..f9d6c24f
--- /dev/null
+++ b/noao/onedspec/doc/sys/revisions.v31.ms
@@ -0,0 +1,329 @@
+.nr PS 10
+.nr VS 12
+.RP
+.ND
+.TL
+NOAO Spectroscopy Packages Revisions: IRAF Version 2.10.3
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+March 1993
+.AB
+This paper summarizes the changes in Version 3.1 of the IRAF/NOAO
+spectroscopy packages, \fBonedspec\fR, \fBlongslit\fR, \fBapextract\fR, and
+those in \fBimred\fR. These changes are part of IRAF Version 2.10.3. A
+list of the revisions is:
+
+.in +2
+.nf
+\(bu A simplified \fIequispec\fR image header format
+\(bu \fIEquispec\fR format allows a larger number of apertures in an image
+\(bu Extensions to allow tasks to work on 3D images
+\(bu New task \fBspecshift\fR for applying a zeropoint dispersion shift
+\(bu Revised \fBsapertures\fR to edit spectrum coordinate parameters
+\(bu Revised \fBdispcor\fR to easily apply multiple dispersion corrections
+\(bu Revised \fBscombine\fR weighting and scaling options
+\(bu Revised \fBscopy\fR to better handle bands in 3D images
+\(bu Revised \fBcalibrate, deredden, dopcor\fR, and \fBspecshift\fR to work on 2D/3D images
+\(bu Extended \fBidentify\fR and \fBreidentify\fR to work on 3D images
+\(bu New color graphics capabilities in \fBsplot, specplot, sensfunc\fR, and \fBidentify\fR
+\(bu All spectral tasks use a common package dispersion axis parameter
+\(bu A more complete suite of tasks in the \fBlongslit\fR package
+\(bu The \fBimred\fR reductions scripts can now be used with any image format
+\(bu A \fIdatamax\fR parameter in the \fBimred\fR reduction scripts for better cleaning
+\(bu Revised the \fBimred\fR reduction scripts to abort on non-CCD processed data
+\(bu Revised fiber reduction tasks to include a scattered light subtraction option
+\(bu Revised fiber reduction tasks to allow as many sky apertures as desired
+\(bu Revised \fBdoslit\fR to take the reference arc aperture from the first object
+\(bu Bug fixes
+.fi
+.in -2
+.AE
+.NH
+Spectral Image Formats and Dispersion World Coordinate Systems
+.LP
+As with the original release of V2.10 IRAF, the primary changes in the
+NOAO spectroscopy
+software in V2.10.3 are in the area of spectral image formats and dispersion
+world coordinate systems (WCS). A great deal was learned from experience
+with the first release and the changes in this release attempt to
+address problems encountered by users. The main revisions are:
+
+.in +2
+.nf
+\(bu A new WCS format called \fIequispec\fR.
+\(bu Extensions to allow use of 3D images with arbitrary dispersion axis.
+\(bu Elimination of limits on the number of apertures in an image under certain conditions.
+\(bu Improved tools for manipulating the spectral coordinate systems.
+\(bu Bug fixes and solutions to problems found in the previous release.
+.fi
+.in -2
+
+In the previous version all images with multiple spectra used a coordinate
+system called \fImultispec\fR. This type of WCS is complex and difficult
+to manipulate by image header editing tools. Only the case of a single
+linearized spectrum per image, sometimes called \fIonedspec\fR format,
+provided a simple header format. However, the \fBapextract\fR package
+used the \fImultispec\fR format even in the case of extracting a single
+spectrum so to get to the simple format required use of \fBscopy\fR.
+.LP
+In many cases all the spectra in a multispectrum image have the same linear
+dispersion function. The new \fIequispec\fR format uses a simple linear
+coordinate system for the entire image. This format is produced by the
+spectral software whenever possible. In addition to being simple and
+compatible with the standard FITS coordinate representation, the
+\fIequispec\fR format also avoids a limitation of the \fImultispec\fR WCS
+on the number of spectra in a single image. This has specific application
+to multifiber spectrographs with more than 250 fibers.
+.LP
+For multiple spectrum data in which the spectra have differing
+dispersion functions (such as echelle orders) or when the spectra are
+not linearized but use nonlinear dispersion functions, the \fImultispec\fR
+format is still used. It is the most general WCS representation.
+The difficulties with modifying this coordinate system, \fBhedit\fR
+cannot be used, are addressed by enhancing the \fBsapertures\fR task
+and by the new task \fBspecshift\fR which covers the common case of
+modifying the dispersion zeropoint.
+.LP
+A feature of the spectral tasks which operate on one dimensional spectra
+is that they can operate on two dimensional long slit spectra by
+specifying a dispersion axis and a summing factor. This feature has
+been extended to three dimensional spectra such as occur with
+Fabry-Perot and multichannel radio synthesis instruments. The
+dispersion axis may be along any axis as specified by the DISPAXIS
+image header keyword or by the \fIdispaxis\fR package parameter. The
+summing factor parameter \fInsum\fR is now a string which may have
+one or two values to allow separate summing factors along two spatial
+axes. Also, some additional tasks which previously did not support this
+feature are \fBcalibrate\fR, \fBderedden\fR, \fBdopcor\fR, and \fBspecshift\fR.
+.LP
+The gory details of the spectral image formats and world coordinate
+systems are laid out in the new help topic \fIspecwcs\fR (also
+available in a postscript version in the IRAF network documentation
+archive as iraf/docs/specwcs.ps.Z).
+.LP
+Some of the bug fixes and solutions to problems found in the previous
+release concerning the image formats and WCS are a problem with the WCS
+dimensionality (WCSDIM keyword) with 3D images and problems reading various
+imported nonstandard formats. It is hoped that all such formats, including
+previous IRAF spectral formats will all be allowed by the software in the
+latest release.
+.NH
+DISPCOR
+.LP
+The previous versions of \fBdispcor\fR, the dispersion correction task, was
+designed to prevent accidental repeated application; it is incorrect to
+apply the dispersion function from the original data to a linearized
+spectrum. However, it is valid to determine a new dispersion solution, say
+from a dispersion calibrated arc, and apply that as a second correction.
+\fBDispcor\fR would not use a new dispersion function, as specified by the
+REFSPEC keywords, if the dispersion calibration flag was set. In order to
+override this the user needed to manually change this flag to indicate the
+spectrum was uncorrected. The problem was that it was difficult to do this
+with \fImultispec\fR format spectra because the flag is part of the complex
+WCS attribute strings.
+.LP
+\fBDispcor\fR was revised to use a different logic to prevent accidental
+recalibration using an unintended dispersion function. The logic is as
+follows. Previously \fBdispcor\fR would simply change the dispersion
+calibration flag after correcting a spectrum while leaving the dispersion
+function reference spectrum keywords alone as a record. The revised
+\fBdispcor\fR keeps this useful record but moves this it to a new keyword
+DCLOGn (where n is a sequential integer). Because the REFSPEC keyword is
+removed after each application of \fBdispcor\fR it now takes an explicit
+act by the user to assign another dispersion function to a spectrum and so
+it is not possible to accidentally reapply the same dispersion function
+twice. Thus this version will apply additional dispersion functions by
+simply adding new REFSPEC keywords. If they are absent the task resamples
+the spectra based on the current dispersion relation as was the case
+before.
+.LP
+The new version can also tell whether the data was calibrated by the
+previous version. In this case the check on the dispersion calibration
+flag is still used so that during the transition users are still protected
+against accidentally applying the same reference dispersion function
+twice. The new task \fBsapertures\fR can now be used to change the
+dispersion calibration flag to override this checking more easily than was
+the case previously.
+.NH
+New Tasks
+.LP
+In this release there is only one completely new task and one task which
+was significantly redesigned. The new task is \fBspecshift\fR. It is
+relatively simple, it adds a zero point shift to the dispersion coordinates
+of spectra. This was the most common request for manipulating the spectral
+world coordinate system. In this regard there was a common confusion about
+the distinction between shifting the coordinate system and shifting the
+pixel data. Generally what people want is to apply a shift such that
+features in the spectrum move to the desired wavelength. One thought is to
+apply the tasks \fBimshift\fR or \fBshiftlines\fR. The surprise is that
+this does not to work. The pixels are actually shifted in the image array,
+but these tasks also apply the same shift to the coordinate system so that
+features in the spectrum remain at the same wavelength. What is really
+required is to leave the pixel data alone and shift only the coordinate
+system. That is what \fBspecshift\fR does.
+.LP
+While one hopefully does not need to directly manipulate the image header
+keywords describing the coordinate system or other aspects of the spectra,
+instead using such tasks as \fBspecshift\fR, there always seem to be cases
+where this is needed or desired. In the V2.10 release of the spectral
+software this was difficult because the general \fImultispec\fR format was
+the norm and it has information encoded in the complex WCS attribute
+strings. As mentioned previously several changes have been made reduce the
+complexity. Now \fIequispec\fR format will generally be the rule and this
+format has keywords which are more easily manipulated with \fBhedit\fR and
+\fBwcsedit\fR. However, the task \fBsapertures\fR was revised to provide
+an editing capability specifically for spectral images, in either
+\fImultispec\fR or \fIequispec\fR format, with options to change various
+parameters globally or aperture-by-aperture.
+.NH
+New Features
+.LP
+There were a number of miscellaneous minor revisions and bug fixes. One of
+the major new capabilities available with V2.10.3 is support for color
+graphics if the graphics device supports it. \fBXgterm\fR supports color
+on X-window systems with color monitors. Several of the spectral tasks
+were modified to use different colors for marks and overplots. These tasks
+include \fBsplot\fR, \fBspecplot\fR, \fBidentify\fR, and \fBsensfunc\fR.
+In the case of \fBsensfunc\fR the user controls the various color
+assignments with a task parameter or \fBgtools\fR colon command while in
+other cases the next available color is used.
+.LP
+There were several changes to \fBscombine\fR equivalent to those in
+\fBimcombine\fR. The weighting, when selected, was changed from the square
+root of the exposure time or spectrum statistics to the value with no
+square root. This corresponds to the more commonly used variance
+weighting. Other options were added to specify the scaling and weighting
+factors. These allow specifying an image header keyword or a file
+containing the scale or weighting factors. A new parameter, "nkeep" has
+been added to allow controlling the maximum number of pixels rejected by the
+clipping algorithms. Previously it was possible to reject all pixels even
+when some of the data was good though with a higher scatter than estimated;
+i.e. all pixels might be greater than 3 sigma from the mean without being
+cosmic rays or other bad values. Finally a parameter \fIsnoise\fR was
+added to include a sensitivity or scale noise component to a Poisson noise
+model.
+.LP
+In \fBsplot\fR the 'p' and 'u' keys which assign and modify the dispersion
+coordinates now include options for applying a zero point shift or a
+doppler shift in addition to defining an absolute wavelength for a feature
+or starting and ending wavelengths. There are also bug fixes to the
+equivalent width calculations, it did not handle flux calibrated data, and
+the scroll keys '(' and ')'.
+.LP
+There were several changes to make it easier to deal with with three
+dimensional \fImultispec\fR and \fIequispec\fR data; that is the additional
+data from the "extras" option in the \fBapextract\fR tasks. One was to fix
+problems associated with an incorrect WCSDIM keyword. This allows use of
+image sections or \fBimcopy\fR for extracting specific bands and
+apertures. Another was to add a "bands" parameter in \fBscopy/sarith\fR to
+allow selection of bands. Also the "onedspec" output format in \fBscopy\fR
+copies any selected bands to separate one dimensional images.
+.LP
+As mentioned earlier, many of the \fBonedspec\fR tasks have been extended
+to work on 2D and 3D spatial spectra. Some tasks which now have this
+capability in this version and not the previous one are \fBcalibrate\fR and
+\fBdopcor\fR. \fBIdentify\fR and \fBredentify\fR were extended to operate
+on 3D images. This involved extending the syntax for the section parameter
+selecting the image vector and the parameter specifying any summing
+across the vector direction.
+.NH
+LONGSLIT
+.LP
+With the applicability of more \fBonedspec\fR tasks to long slit data
+the \fBlongslit\fR package was modified to add many new tasks.
+This required adding additional package parameters. One new task
+to point out is \fBcalibrate\fR. This task is now the preferred one
+to use for extinction and flux calibration of long slit spectra
+rather than the obsolete \fBextinction\fR and \fBfluxcalib\fR.
+The obsolete tasks are still present in this release.
+.NH
+APEXTRACT
+.LP
+The \fBapextract\fR package had a few, mostly transparent, changes. In
+the previous version the output image header format was always \fImultispec\fR
+even when there was a single spectrum, either because only one aperture
+was defined or because the output format parameter was "onedspec".
+In this release the default WCS format is the simpler \fIequispec\fR.
+.LP
+In the \fBonedspec\fR and \fBimred\fR spectral reduction packages there is
+a dispersion axis package parameter which is used to defined the dispersion
+axis for images without a DISPAXIS keyword. This applies to all tasks.
+However, the \fBapextract\fR tasks had the dispersion axis defined by their
+own task parameters resulting in some confusion. To make things consistent
+the dispersion axis parameter in \fBapextract\fR has been moved from the
+tasks to a package parameter. Now in the \fBimred\fR spectral reduction
+packages, there is just one dispaxis parameter in the package parameters
+which applies to all tasks in those packages, both those from
+\fBonedspec\fR and those from \fBapextract\fR.
+.LP
+Some hidden algorithm parameters were adjusted so that the cleaning and
+variance weighting options perform better in some problem cases without
+requiring a great deal of knowledge about things to tweak.
+.NH
+IMRED Spectroscopic Reduction Tasks
+.LP
+The various spectroscopic reductions tasks, those beginning with "do", have
+had some minor revisions and enhancements in addition to those which apply
+to the individual tasks which make up these scripts. In the latter class
+is the output WCS format is \fBequispec\fR except for the echelle tasks and
+when dispersion linearization is not done. Related to this is that the
+multifiber tasks can operate on data with more than 250 fibers which was a
+limitation of the \fBmultispec\fR format.
+.LP
+In the previous version only the OIF format images were allowed (the ".imh"
+extensions). This has been generalized to allow selecting the image format
+by setting the environment parameter \fIimtype\fR. Only images with the
+specified extension will be processed and created.
+.LP
+The dispersion axis parameter in the reduction tasks and in the other tasks
+in the \fBimred\fR spectroscopy packages, such as the \fBapextract\fR
+tasks, is now solely a package parameter.
+.LP
+All the scripts now check the input spectra for the presence of the CCDPROC
+keyword and abort if it is not found. This keyword indicates that the data
+have been processed for basic CCD calibrations, though it does not check
+the operations themselves. For data reduced using \fBccdproc\fR this
+keyword will be present. If these tasks are used on data not processed by
+\fBccdproc\fR then it is a simple matter to add this keyword with
+\fBhedit\fR. Obviously, the purpose of this change is to avoid
+inadvertently operating on raw data.
+.LP
+All the "do" tasks now have a parameter "datamax". This minimizes the
+effects of very strong cosmic rays during the extraction of object spectra;
+it does not apply to flat field or arc spectra. When there is a very large
+difference between data pixel values and cosmic ray pixel values,
+especially true for very weak spectra, the cosmic ray cleaning operation
+does not always work well. If it is possible to specify a threshold value
+between the maximum real data value and cosmic rays then the cosmic ray
+cleaning can be significantly improved by immediately rejecting those
+pixels above the threshold. Of course the user must be careful that real
+data does not exceed this value since such data will be excluded.
+.LP
+The fiber reduction tasks, \fBdoargus, dohydra, dofibers, dofoe\fR, and
+\fBdo3fiber\fR have a new processing option for subtracting scattered
+light. This is particularly useful if there is significant scattered light
+in producing uniform sky spectra for sky subtraction since the fiber
+throughput calibration does not generally correct for this.
+.LP
+The fiber reduction tasks also had a limit on the number of sky fibers
+which could be used with the interactive sky editing. This limit has
+been eliminated so that it is possible, for example, to have one object
+fiber and 99 sky fibers.
+.LP
+The slit reduction task \fBdoslit\fR previously required that the spectrum
+for the reference arc cover the middle of the input data images. There
+were cases of instrument configurations where this was not true requiring
+additional manipulation to use this task. This requirement has been
+eliminated. Instead when the reference arc needs to be extracted it uses
+the aperture definition from one of the input object spectra since
+definition of the object apertures occurs prior to setting up the
+dispersion calibration.
+.LP
+In addition the task \fBdoslit\fR and \fBdoecslit\fR had a bug in which
+the order of the arcs specified by the user was ignored and alphabetical
+order was used instead. This has been fixes so that the first arc
+specified by the use is the reference arc.
diff --git a/noao/onedspec/doc/sys/revisions.v31.ms.bak b/noao/onedspec/doc/sys/revisions.v31.ms.bak
new file mode 100644
index 00000000..1c7c3b31
--- /dev/null
+++ b/noao/onedspec/doc/sys/revisions.v31.ms.bak
@@ -0,0 +1,307 @@
+.nr PS 9
+.nr VS 11
+.RP
+.ND
+.TL
+NOAO Spectroscopy Packages Revision Summary: IRAF Version 2.10.3
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+March 1993
+.AB
+This paper summarizes the changes in Version 3.1 of the IRAF/NOAO
+spectroscopy packages, \fBonedspec\fR, \fBlongslit\fR, \fBapextract\fR,
+and those in \fBimred\fR. These changes are part of
+part of IRAF Version 2.10.3. A list of the revisions is:
+
+.nf
+\(bu A simplified \fIequispec\fR image header format
+\(bu \fIEquispec\fR format allows a larger number of apertures in an image
+\(bu Extensions to allow tasks to work on 3D images
+\(bu New task \fBspecshift\fR for applying a zeropoint dispersion shift
+\(bu Revised \fBsapertures\fR to edit spectrum coordinate parameters
+\(bu Revised \fBdispcor\fR to easily apply multiple dispersion corrections
+\(bu Revised \fBscombine\fR weighting and scaling options
+\(bu Revised \fBscopy\fR to better handle bands in 3D images
+\(bu Revised \fBcalibrate, dopcor\fR, and \fBspecshift\fR to work on 2D/3D images
+\(bu New color graphics capabilities in \fBsplot, specplot, sensfunc\fR, and \fBidentify\fR
+\(bu All spectral tasks use a common package dispersion axis parameter
+\(bu A more complete suite of tasks in the \fBlongslit\fR package
+\(bu A \fIdatamax\fR parameter in the \fBimred\fR reduction scripts for better cleaning
+\(bu Revised the \fBimred\fR reduction scripts to abort on non-CCD processed data
+\(bu Revised fiber reduction tasks to include a scattered light subtraction option
+\(bu Revised \fBdoslit\fR to take the reference arc aperture from the first object
+\(bu Bug fixes
+.fi
+.AE
+.NH
+Spectral Image Formats and Dispersion World Coordinate Systems
+.LP
+As with the original release of V2.10 IRAF, the primary changes in the
+NOAO spectroscopy
+software in V2.10.3 are in the area of spectral image formats and dispersion
+world coordinate systems (WCS). A great deal was learned from experience
+with the first release and the changes in this release attempt to
+address problems encountered by users. The main revisions are:
+
+.in +4
+.nf
+\(bu A new WCS format called \fIequispec\fR.
+\(bu Extensions to allow use of 3D images with arbitrary dispersion axis.
+\(bu Elimination of limits on the number of apertures in an image under certain conditions.
+\(bu Improved tools for manipulating the spectral coordinate systems.
+\(bu Bug fixes and solutions to problems found in the previous release.
+.fi
+.in - 4
+
+In the previous version all images with multiple spectra used a coordinate
+system called \fImultispec\fR. This type of WCS is complex and difficult
+to manipulate by image header editting tools. Only the case of a single
+linearized spectrum per image, sometimes called \fIonedspec\fR format,
+provided a simple header format. However, the \fBapextract\fR package
+used the \fImultispec\fR format even in the case of extracting a single
+spectrum so to get to the simple format required use of \fBscopy\fR.
+.LP
+In many cases all the spectra in a multispectrum image have the same linear
+dispersion function. The new \fIequispec\fR format uses a simple linear
+coordinate system for the entire image. This format is produced by the
+spectral software whenever possible. In addition to being simple and
+compatible with the standard FITS coordinate representation, the
+\fIequispec\fR format also avoids a limitation of the \fImultispec\fR WCS
+on the number of spectra in a single image. This has specific application
+to multifiber spectrographs with more than 250 fibers.
+.LP
+For multiple spectrum data in which the spectra have differing
+dispersion functions (such as echelle orders) or when the spectra are
+not linearized but use nonlinear dispersion functions, the \fImultispec\fR
+format is still used. It is the most general WCS representation.
+The difficulties with modifying this coordinate system, \fBhedit\fR
+cannot be used, are addressed by enhancing the \fBsapertures\fR task
+and by the new task \fBspecshift\fR which covers the common case of
+modifying the dispersion zeropoint.
+.LP
+A feature of the spectral tasks which operate on one dimensional spectra
+is that they can operate on two dimensional long slit spectra by
+specifying a dispersion axis and a summing factor. This feature has
+been extended to three dimensional spectra such as occur with
+Fabry-Perot and multichannel radio synthesis instruments. The
+dispersion axis may be along any axis as specified by the DISPAXIS
+image header keyword or by the \fIdispaxis\fR package parameter. The
+summing factor parameter \fInsum\fR is now a string which may have
+one or two values to allow separate summing factors along two spatial
+axes. Also, some additional tasks which previously did not support this
+feature are \fBcalibrate\fR, \fBdopcor\fR, and \fBspecshift\fR.
+.LP
+The gory details of the spectral image formats and world coordinate
+systems are laid out in the new help topic \fIspecwcs\fR (also
+available in a postscript version in the IRAF network documentation
+archive as iraf/docs/specwcs.ps.Z).
+.LP
+Some of the bug fixes and solutions to problems found in the previous
+release concerning the image formats and WCS are a problem with the WCS
+dimensionality (WCSDIM keyword) with 3D images and problems reading various
+imported nonstandard formats. It is hoped that all such formats, including
+previous IRAF spectral formats will all be allowed by the software in the
+latest release.
+.NH
+DISPCOR
+.LP
+The previous versions of \fBdispcor\fR, the dispersion correction task, was
+designed to prevent accidental repeated application; it is incorrect to
+apply the dispersion function from the original data to a linearized
+spectrum. However, it is valid to determine a new dispersion solution, say
+from a dispersion calibrated arc, and apply that as a second correction.
+\fBDispcor\fR would not use a new dispersion function, as specified by the
+REFSPEC keywords, if the dispersion calibration flag was set. In order to
+override this the user needed to manually change this flag to indicate the
+spectrum was uncorrected. The problem was that it was difficult to do this
+with \fImultispec\fR format spectra because the flag is part of the complex
+WCS attribute strings.
+.LP
+\fBDispcor\fR was revised to use a different logic to prevent accidental
+recalibration using an unintended dispersion function. The logic is as
+follows. Previously \fBdispcor\fR would simply change the dispersion
+calibration flag after correcting a spectrum while leaving the dispersion
+function reference spectrum keywords alone as a record. The revised
+\fBdispcor\fR keeps this useful record but moves this it to a new keyword
+DCLOGn (where n is a sequential integer). Because the REFSPEC keyword is
+removed after each application of \fBdispcor\fR it now takes an explicit
+act by the user to assign another dispersion function to a spectrum and so
+it is not possible to accidentally reapply the same dispersion function
+twice. Thus this version will apply additional dispersion functions by
+simply adding new REFSPEC keywords. If they are absent the task resamples
+the spectra based on the current dispersion relation as was the case
+before.
+.LP
+The new version can also tell whether the data was calibrated by the
+previous version. In this case the check on the dispersion calibration
+flag is still used so that during the transition users are still protected
+against accidentally applying the same reference dispersion function
+twice. The new task \fBsapertures\fR can now be used to change the
+dispersion calibration flag to override this checking more easily than was
+the case previously.
+.NH
+New Tasks
+.LP
+In this release there is only one completely new task and one task which
+was significantly redesigned. The new task is \fBspecshift\fR. It is
+relatively simple, it adds a zero point shift to the dispersion coordinates
+of spectra. This was the most common request for manipulating the spectral
+world coordinate system. In this regard there was a common confusion about
+the distinction between shifting the coordinate system and shifting the
+pixel data. Generally what people want is to apply a shift such that
+features in the spectrum move to the desired wavelength. One thought is to
+apply the tasks \fBimshift\fR or \fBshiftlines\fR. The surprise is that
+this does not to work. The pixels are actually shifted in the image array,
+but these tasks also apply the same shift to the coordinate system so that
+features in the spectrum remain at the same wavelength. What is really
+required is to leave the pixel data alone and shift only the coordinate
+system. That is what \fBspecshift\fR does.
+.LP
+While one hopefully does not need to directly manipulate the image header
+keywords describing the coordinate system or other aspects of the spectra,
+instead using such tasks as \fBspecshift\fR, there always seem to be cases
+where this is needed or desired. In the V2.10 release of the spectral
+software this was difficult because the general \fImultispec\fR format was
+the norm and it has information encoded in the complex WCS attribute
+strings. As mentioned previously several changes have been made reduce the
+complexity. Now \fIequispec\fR format will generally be the rule and this
+format has keywords which are more easily manipulated with \fBhedit\fR and
+\fBwcsedit\fR. However, the task \fBsapertures\fR was revised to provide
+an editing cabability specifically for spectral images, in either
+\fImultispec\fR or \fIequispec\fR format, with options to change various
+parameters globally or aperture-by-aperture.
+.NH
+New Features
+.LP
+There were a number of miscellaneous minor revisions and bug fixes. One of
+the major new capabilities available with V2.10.3 is support for color
+graphics if the graphics device supports it. \fBXgterm\fR supports color
+on X-window systems with color monitors. Several of the spectral tasks
+were modified to use different colors for marks and overplots. These tasks
+include \fBsplot\fR, \fBspecplot\fR, \fBidentify\fR, and \fBsensfunc\fR.
+In the case of \fBsensfunc\fR the user controls the various color
+assignments with a task parameter or \fBgtools\fR colon command while in
+other cases the next available color is used.
+.LP
+There were several changes to \fBscombine\fR equivalent to those in
+\fBimcombine\fR. The weighting, when selected, was changed from the square
+root of the exposure time or spectrum statistics to the value with no
+square root. This corresponds to the more commonly used variance
+weighting. Other options were added to specify the scaling and weighting
+factors. These allow specifying an image header keyword or a file
+containing the scale or weighting factors. A new parameter, "nkeep" has
+been added to allow controling the maximum number of pixels rejected by the
+clipping algorithms. Previously it was possible to reject all pixels even
+when some of the data was good though with a higher scatter than estimated;
+i.e. all pixels might be greater than 3 sigma from the mean without being
+cosmic rays or other bad values. Finally a parameter \fIsnoise\fR was
+added to include a sensitivity or scale noise component to a Poisson noise
+model.
+.LP
+In \fBsplot\fR the 'p' and 'u' keys which assign and modify the dispersion
+coordinates now include options for applying a zero point shift or a
+doppler shift in addition to defining an absolute wavelength for a feature
+or starting and ending wavelengths. There are also bug fixes to the
+equivalent width calculations, it did not handle flux calibrated data, and
+the scroll keys '(' and ')'.
+.LP
+There were several changes to make it easier to deal with with three
+dimensional \fImultispec\fR and \fIequispec\fR data; that is the additional
+data from the "extras" option in the \fBapextract\fR tasks. One was to fix
+problems associated with an incorrect WCSDIM keyword. This allows use of
+image sections or \fBimcopy\fR for extracting specific bands and
+apertures. Another was to add a "bands" parameter in \fBscopy/sarith\fR to
+allow selection of bands. Also the "onedspec" output format in \fBscopy\fR
+copies any selected bands to separate one dimensional images.
+.LP
+As mentioned earlier, many of the \fBonedspec\fR tasks have been extended
+to work on 2D and 3D spatial spectra. Some tasks which now have this
+capability in this version and not the previous one are \fBcalibrate\fR and
+\fBdopcor\fR. \fBIdentify\fR and \fBredentify\fR were extended to operate
+on 3D images.
+.NH
+LONGSLIT
+.LP
+With the applicablity of more \fBonedspec\fR tasks to long slit data
+the \fBlongslit\fR package was modified to add many new tasks.
+This required adding additional package parameters. One new task
+to point out is \fBcalibrate\fR. This task is now the prefered one
+to use for extinction and flux calibration of long slit spectra
+rather than the obsolete \fBextinction\fR and \fBfluxcalib\fR.
+The obsolete tasks are still present in this release.
+.NH
+APEXTRACT
+.LP
+The \fBapextract\fR package had a few, mostly transparent, changes. In
+the previous version the output image header format was always \fImultispec\fR
+even when there was a single spectrum, either because only one aperture
+was defined or because the output format parameter was "onedspec".
+In this release the default WCS format is the simpler \fIequispec\fR.
+.LP
+In the \fBonedspec\fR and \fBimred\fR spectral reduction packages there is
+a dispersion axis package parameter which is used to defined the dispersion
+axis for images without a DISPAXIS keyword. This applies to all tasks.
+However, the \fBapextract\fR tasks had the dispersion axis defined by their
+own task parameters resulting in some confusion. To make things consistent
+the dispersion axis parameter in \fBapextract\fR has been moved from the
+tasks to a package parameter. Now in the \fBimred\fR spectral reduction
+packages, there is just one dispaxis parameter in the package parameters
+which applies to all tasks in those packages, both those from
+\fBonedspec\fR and those from \fBapextract\fR.
+.LP
+Some hidden algorithm parameters were adjusted so that the cleaning and
+variance weighting options perform better in some problem cases without
+requiring a great deal of knowledge about things to tweak.
+.NH
+IMRED Spectroscopic Reduction Tasks
+.LP
+The various spectroscopic reductions tasks, those beginning with "do", have
+had some minor revisions and enhancements in addition to those which apply
+to the individual tasks which make up these scripts. In the latter class
+is the output WCS format is \fBequispec\fR except for the echelle tasks and
+when dispersion linearization is not done. Related to this is that the
+multifiber tasks can operate on data with more than 250 fibers which was a
+limitation of the \fBmultispec\fR format.
+.LP
+The dispersion axis parameter in the reduction tasks and in the other tasks
+in the \fBimred\fR spectroscopy packages, such as the \fBapextract\fR
+tasks, is now solely a package parameter.
+.LP
+All the scripts now check the input spectra for the presence of the CCDPROC
+keyword and abort if it is not found. This keyword indicates that the data
+have been processed for basic CCD calibrations, though it does not check
+the operations themselves. For data reduced using \fBccdproc\fR this
+keyword will be present. If these tasks are used on data not processed by
+\fBccdproc\fR then it is a simple matter to add this keyword with
+\fBhedit\fR. Obviously, the purpose of this change is to avoid
+inadvertently operating on raw data.
+.LP
+All the "do" tasks now have a parameter "datamax". This minimizes the
+effects of very strong cosmic rays during the extraction of object spectra;
+it does not apply to flat field or arc spectra. When there is a very large
+difference between data pixel values and cosmic ray pixel values,
+especially true for very weak spectra, the cosmic ray cleanning operation
+does not always work well. If it is possible to specify a threshold value
+between the maximum real data value and cosmic rays then the cosmic ray
+cleanning can be significantly improved by immediately rejecting those
+pixels above the threshold. Of course the user must be careful that real
+data does not exceed this value since such data will be excluded.
+.LP
+The fiber reduction tasks, \fBdoargus, dohydra, dofibers, dofoe\fR, and
+\fBdo3fiber\fR have a new processing option for subtracting scattered
+light. This is particularly useful if there is significant scattered light
+in producing uniform sky spectra for sky subtraction since the fiber
+throughput calibration does not generally correct for this.
+.LP
+The slit reduction task \fBdoslit\fR previously required that the spectrum
+for the reference arc cover the middle of the input data images. There
+were cases of instrument configurations where this was not true requiring
+additional manipulation to use this task. This requirement has been
+eliminated. Instead when the reference arc needs to be extracted it uses
+the aperture definition from one of the input object spectra since
+definition of the object apertures occurs prior to setting up the
+dispersion calibration.
diff --git a/noao/onedspec/doc/sys/rvidentify.ms b/noao/onedspec/doc/sys/rvidentify.ms
new file mode 100644
index 00000000..dadab882
--- /dev/null
+++ b/noao/onedspec/doc/sys/rvidentify.ms
@@ -0,0 +1,304 @@
+.RP
+.TL
+Radial Velocity Measurements with IDENTIFY
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+P.O. Box 26732, Tucson, Arizona 85726
+.AB
+The IRAF task \fBidentify\fR may be used to measure radial velocities.
+This is done using the classical method of determining the doppler shifted
+wavelengths of emission and absorption lines. This paper covers many of
+the features and techniques available through this powerful and versatile
+task which are not immediately evident to a new user.
+.AE
+.NH
+Introduction
+.PP
+The task \fBidentify\fR is very powerful and versatile. It can be used
+to measure wavelengths and wavelength shifts for doing radial velocity
+measurements from emission and absorption lines. When combined with
+the CL's ability to redirect input and output both from the standard
+text streams and the cursor and graphics streams virtually anything may
+be accomplished either interactively or automatically. This, of
+course, requires quite a bit of expertise and experience with
+\fBidentify\fR and with the CL which a new user is not expected to be
+aware of initially. This paper attempts to convey some of the
+possibilities. There are many variations on these methods which the
+user will learn through experience.
+.PP
+I want to make a caveat about the suggestions made in this paper. I wrote
+the \fBidentify\fR task and so I am an expert in its use. However, I am not
+a spectroscopist, I have not been directly involved in the science of
+measuring astronomical radial velocities, and I am not very familiar with
+the literature. Thus, the suggestions contained in this paper are based
+on my understanding of the basic principles and the abilities of the
+\fBidentify\fR task.
+.PP
+The task \fBidentify\fR is used to measure radial velocities by
+determining the wavelengths of individual emission and absorption
+lines. The user must compute the radial velocities separately by
+relating the observed wavelengths to the known rest wavelengths via the
+Doppler formula. This is a good method when the lines are strong, when
+there are only one or two features, and when there are many, possibly,
+weaker lines. The accuracy of this method is determined by the
+accuracy of the line centering algorithm.
+.PP
+The alternative method is to compare an observed
+spectrum to a template spectrum of known radial velocity. This is done
+by correlation or fourier ratio methods. These methods have the
+advantage of using all of the spectrum and are good when there are many
+very weak and possibly broad features. Their disadvantages are
+confusion with telluric lines, they don't work well with just a few
+real features, and they require a fair amount of preliminary
+manipulation of the spectrum to remove continuum and interpolate the
+spectrum in logarithmic wavelength intervals. IRAF tasks for
+correlation and fourier ratio methods are under development at this
+time. Many people assume that these more abstract methods are inherently
+better than the classical method. This is not true, it depends on the
+quality and type of data.
+.PP
+Wavelength measurements are best done on the original data rather than
+after linearizing the wavelength intervals. This is because 1) it is
+not necessary as will be shown below and 2) the interpolation used to
+linearize the wavelength scale can change the shape of the lines,
+particularly strong, narrow emission lines which are the best ones for
+determining radial velocities. A second reason is that
+\fBidentify\fR currently does not recognize the linear wavelength parameters
+produced during linearization. This will be fixed soon but
+in the mean time the lines must be measured in pixels and converted
+later by the user. Alternatively one can determine a linear dispersion solution
+with \fBidentify\fR but this is more work than needed.
+.PP
+This paper is specifically about \fBidentify\fR but one should be aware of the
+task \fBsplot\fR which also may be used to measure radial velocities. It
+differs in several respects from \fBidentify\fR. \fBSplot\fR works only on linearized
+data; the wavelength and pixel coordinates are related by a zero point and
+wavelength interval. The line centering algorithms are different;
+the line centering is generally less robust (tolerant
+of error) and often less accurate. It has many nice features but is
+not designed for the specific purpose of measuring positions of lines
+and, thus, is not as easy to use for this purpose.
+.PP
+There are a number of sources of additional information relating to the
+use of the task \fBidentify\fR. The primary source is the manual pages for
+the task. As with all manual pages it is available online with the
+\fBhelp\fR command and in the \fIIRAF User Handbook\fR. The NOAO
+reduction guides or cookbooks for the echelle and IIDS/IRS include
+additional examples and discussion. The line centering algorithm
+is the most critical factor in determining dispersion solutions and
+radial velocities. It is described in more detail under the help
+topic \fBcenter1d\fR online or in the handbook.
+.NH
+Method 1
+.PP
+In this method arc calibration images are used to determine a wavelength
+scale. The dispersion solution is then transferred to the object spectrum
+and the wavelengths of emission and absorption lines are measured and
+recorded. This is relatively straightforward but some tricks will make
+this easier and more accurate.
+.NH 2
+Transferring Dispersion Solutions
+.PP
+There are several ways to transfer the dispersion solution from an arc
+spectrum to an object spectrum differing in the order in which things are
+done.
+.IP (1)
+One way is to determine the dispersion solution for all the arc images
+first. To do this interactively specify all the arc images as the
+input to \fBidentify\fR. After determining the dispersion solution for
+the first arc and quitting (\fIq\fR key) the next arc will be displayed
+with the previous dispersion solution and lines retained. Then use the
+cursor commands \fIa\fR and \fIc\fR (all center) to recenter and
+recompute the dispersion solution, \fIs\fR to shift to the cursor
+position, recenter, and recompute the dispersion solution, or \fIx\fR
+to correlate features, shift, recenter, and recompute the dispersion
+solution. These commands are relatively fast and simple.
+.IP
+A important reason for doing all the arc images first is that this same
+procedure can be done mostly noninteractively with the task
+\fBreidentify\fR. After determining a dispersion solution for one arc
+image \fBreidentify\fR does the recenter (\fIa\fR and \fIc\fR), shift
+and recenter (\fIs\fR), or correlation features, shift, and recenter
+(\fIx\fR) to transfer the dispersion solutions between arcs. This is
+usually done as a background task.
+.IP
+To transfer the solution to the object spectra specify the list of
+object spectra as input to \fBidentify\fR. For each image begin by
+entering the colon command \fI:read arc\fR where arc is the name of the
+arc image whose dispersion solution is to be applied; normally the one
+taken at the same time and telescope position as the object. This will
+read the dispersion solution and arc line positions. Delete the arc
+line positions with the \fIa\fR and \fId\fR (all delete) cursor keys.
+You can now measure the wavelengths of lines in the spectrum.
+.IP (2)
+An alternative method is to interactively alternate between arc and
+object spectra either in the input image list or with the \fI:image
+name\fR colon command.
+.NH 2
+Measuring Wavelengths
+.PP
+.IP (1)
+To record the feature positions at any time use the \fI:features file\fR
+colon command where file is where the feature information will be written.
+Repeating this with the same file appends to the file. Writing to
+the database with the \fI:write\fR colon command also records this information.
+Without an argument the results are put in a file with the same name as the
+image and a prefix of "id". You can use any name you like, however,
+with \fI:write name\fR. The \fI:features\fR command is probably preferable
+because it only records the line information while the database format
+includes the dispersion solution and other information not needed for
+computing radial velocities.
+.IP (2)
+Remember that when shifting between emission and absorption lines the
+parameter \fIftype\fR must be changed. This may be done interactively with
+the \fI:ftype emission\fR and \fI:ftype absorption\fR commands. This parameter
+does not need to be set except when changing between types of lines.
+.IP (3)
+Since the centering of the emission or absorption line is the most
+critical factor one should experiment with the parameter \fIfwidth\fR.
+To change this parameter type \fI:fwidth value\fR. The positions of the
+marked features are not changed until a center command (\fIc\fR) command
+is given. \fIWarning: The all center (\fIa\fR and \fIc') command automatically
+refits the dispersion solution to the lines which will lose your
+arc dispersion solution.\fR
+.IP
+A narrow \fIfwidth\fR is less influenced by blends and wings but has a larger
+uncertainty. A broad \fIfwidth\fR uses all of the line profile and is thus
+stable but may be systematically influenced by blending and wings. One
+possible approach is to measure the positions at several values of
+\fIfwidth\fR and decide which value to use or use some weighting of the
+various measurements. You can record each set of measurements with
+the \fI:fe file\fR command.
+.IP (4)
+For calibration of systematic effects from the centering one should obtain
+the spectrum of a similar object with a known radial velocity. The systematic
+effect is due to the fact that the centering algorithm is measuring a
+weighted function of the line profile which may not be the true center of
+the line as tabulated in the laboratory or in a velocity standard.
+By using the same centering method on an object with the same line profiles
+and known velocity this effect can be eliminated.
+.IP (5)
+Since the arcs are not obtained at precisely the same time as the object
+exposures there may be a wavelength shift relative to the arc dispersion
+solution. This may be calibrated from night sky lines in the object
+itself (the night sky lines are "good" in this case and should not be
+subtracted away). There are generally not enough night sky lines to act
+as the primary dispersion calibrator but just one can determine a possible
+wavelength zero point shift. Measure the night sky line positions at the same
+time the object lines are measured. Determine a zero point shift from
+the night sky to be taken out of the object lines.
+.NH
+Method 2
+.PP
+This method is similar to the correlation method in that a template
+spectrum is used and the average shift relative to the template measures the
+radial velocity. This has the advantage of not requiring the user to
+do a lot of calculations (the averaging of the line shifts is done by
+\fRidentify\fR) but is otherwise no better than method 1.
+The template spectrum must have the same features as the object spectrum.
+.IP (1)
+Determine a dispersion solution for the template spectrum either from
+the lines in the spectrum or from an arc calibration.
+.IP (2)
+Mark the features to be correlated in the template spectrum.
+.IP (3)
+Transfer the template dispersion solution and line positions to an object
+spectrum using one of the methods described earlier. Then for the
+current feature point the cursor near the same feature in the object
+spectrum and type \fIs\fR. The mean shift in pixels, wavelength, and
+fractional wavelength (like a radial velocity without the factor of
+the speed of light) for the object is determined and printed. A new
+dispersion solution is determined but you may ignore this.
+.IP (4)
+When doing additional object spectra remember to start over again with
+the template spectrum (using \fI:read template\fR) and not the solution
+from the last object spectrum.
+.IP (5)
+This procedure assumes that the dispersion solution between the template
+and object are the same. Checks for zero point shifts with night sky
+lines, as discussed earlier, should be made if possible. The systematic
+centering bias, however, is accounted for by using the same lines from
+the template radial velocity standard.
+.IP (6)
+One possible source of error is attempting to use very weak lines. The
+recentering may find the wrong lines and affect the results. The protections
+against this are the \fIthreshold\fR parameter (in Version 2.4 IRAF) and
+setting the centering error radius to be relatively small.
+.NH
+Method 3
+.PP
+This method uses only strong emission lines and works with linearized
+data without an \fBidentify\fR dispersion solution. \fBIdentify\fR has
+a failing when used with linearized data; it does not know about the
+wavelength parameters in the image header. This will eventually be
+fixed. However, if you have already linearized your spectra and wish
+to use them instead of the nonlinear spectra the following method will
+work. The recipe involves measuring the positions of emission lines in
+pixels which must then be converted to wavelength using the header
+information. The strongest emission lines are found automatically
+using the \fIy\fR cursor key. The number of emission lines to be
+identified is set by the \fImaxfeatures\fR parameter. The emission
+line positions are then written to a data file using the \fI:features
+file\fR colon command. This may be done interactively and takes only a
+few moments per spectrum. If done interactively the images may be
+chained by specifying an image template. The only trick required is
+than when proceeding to the next spectrum the previous features are
+deleted using the cursor key combination \fIa\fR and \fId\fR (all
+delete).
+.PP
+For a large number of images, on the order of hundreds, this may be automated
+as follows. A file containing the cursor commands is prepared.
+The cursor command format consists of the x and y positions, the window
+(usually window 1), and the key stroke or colon command. Because each new
+image form an image template does not restart the cursor command file the
+commands would have to be repeated for each image in the list. Thus, a CL
+loop calling the
+task each time with only one image is preferable. Besides redirecting
+the cursor input from a command file we must also redirect the standard
+input for the response to the database save query, the standard output
+to discard the status line information, and, possibly, the graphics
+to a metacode file which can then be reviewed later. The following
+steps indicate what is to be done.
+.IP (1)
+Prepare a file containing the images to be measured (one per line).
+This can usually be done using the sections command to expand a template
+and directing the output into a file.
+.IP (2)
+Prepare the a cursor command file (let's call it cmdfile) containing the
+following two lines.
+.nf
+ 1 1 1 y
+ 1 1 1 :fe positions.dat
+.fi
+.IP (3)
+Enter the following commands.
+.nf
+ list="file"
+ while (fscan (list, s1) != EOF) {
+ print ("no") | identify (s1, maxfeatures=2, cursor="cmdfile",
+ >"dev$null", >G "plotfile")
+ }
+.fi
+.LP
+Note that these commands could be put in a CL script and executed using the
+command
+
+ on> cl <script.cl
+
+.PP
+The commands do the following. The first command initializes the image list
+for the loop. The second command is the loop to be run until the end of
+the image file is reached. The command in the loop directs the string
+"no" to the standard input of identify which will be the response to the
+database save query. The identify command uses the image name obtained
+from the list by the fscan procedure, sets the maximum number of features
+to be found to be 2 (this can be set using \fBeparam\fR instead), the cursor
+input is taken from the cursor command file, the standard output is
+discarded to the null device, and the STDGRAPH output is redirected to
+a plot file. If the plot file redirection is not used then the graphs
+will appear on the specified graphics device (usually the graphics terminal).
+The plot file can then be disposed of using the \fBgkimosaic\fR task to either
+the graphics terminal or a hardcopy device.
diff --git a/noao/onedspec/doc/sys/sensfunc.ms b/noao/onedspec/doc/sys/sensfunc.ms
new file mode 100644
index 00000000..67b6532d
--- /dev/null
+++ b/noao/onedspec/doc/sys/sensfunc.ms
@@ -0,0 +1,83 @@
+.EQ
+delim $$
+.EN
+.OM
+.TO
+IRAF ONEDSPEC Users
+.FR
+Frank Valdes
+.SU
+SENSFUNC Corrections
+.LP
+This memorandum describes the meaning of the corrections
+computed by the \fBonedspec\fR task \fBsensfunc\fR.
+The basic equation is
+
+.EQ (1)
+I( lambda )~=~I sub obs ( lambda )~10 sup {0.4~(s( lambda )~+
+~A~e( lambda )~+~roman {fudge~terms})}
+.EN
+
+where $I sub obs$ is the observed spectrum corrected to counts per second,
+$I$ is the flux calibrated spectrum, $s( lambda )$ is the sensitivity
+correction needed to produce
+flux calibrated intensities, $A$ is the air mass at the time of the
+observation, $e( lambda )$ is a standard extinction function, and,
+finally, additional terms appropriately called \fIfudge\fR terms. Expressed
+as a magnitude correction this equation is
+
+.EQ (2)
+DELTA m( lambda )~=s( lambda )~+~A~e( lambda )~+~roman {fudge~terms}
+.EN
+
+In \fBsensfunc\fR the standard extinction function is applied so that ideally
+the $DELTA m$ curves (defining the sensitivity function) obtained from
+observations of different stars and at different air masses are identical.
+However, at times this is not the case because the observations were taken
+through non-constant or nonstandard extinction.
+
+There are two types of fudge terms used in \fBsensfunc\fR, called \fIfudge\fR
+and \fIgrey\fR. The \fIfudge\fR correction is a separate constant,
+independent of wavelength or air mass, applied to each observation to shift
+the sensitivity curves to the same level on average. This is done to
+determine the shape of the sensitivity curve only.
+The fudge correction for each observation is obtained by determining
+the average magnitude shift over all wavelenths relative to the observation
+with the smallest sensitivity correction. A composite sensitivity curve
+is then determined from the average of all the fudged observations.
+The fudge terms are not incorporated in the sensitivity or extinction
+corrections applied to calibrate the spectra. Thus, after applying the
+sensitivity and extinction corrections to the standard star spectra there
+will be absolute flux scale errors due to the observing conditions.
+
+If the observer believes that there is an actual calibratible error in
+the standard extinction then \fBsensfunc\fR can be used to determine a
+correction which is a linear function of the air mass. This is done by
+relating the fudge values (the magnitude shifts needed to bring observations
+to the same sensitivity level) to the air mass of the observations.
+The \fIgrey\fR term is obtained by the least square fit to
+
+.EQ (3)
+f sub i~=~G~DELTA A sub i~=~G~A sub i~+~C
+.EN
+
+where the $f sub i$ are the fudge values relative to the observation with
+the smallest sensitivity correction and the $DELTA A sub i$ are the
+air mass differences relative to this same observation. The slope constant
+$G$ is what is refered to as the \fIgrey\fR term. The constant term,
+related to the air mass of the reference observation to which the other
+spectra are shifted, is absorbed in the sensitivity function.
+The modified equation (2) is
+
+.EQ (4)
+DELTA m( lambda )~=~s ( lambda ) + A~(e( lambda )~+~G)
+.EN
+
+It is important to realize that equation (3) can lead to erroneous results
+if there is no real relation to the air mass or the air mass range is
+too small. In other words applying the grey term correction will produce
+some number for $G$ but it may be worse than no correction. A plot of
+the individual fudge constants, $f sub i$, and the air mass or
+air mass differences would be useful to evaluate the validity of the
+grey correction. The actual magnitude of the correction is not $G$
+but $DELTA A~G$ where $DELTA A$ is the range of observed air mass.
diff --git a/noao/onedspec/doc/sys/specwcs.ms b/noao/onedspec/doc/sys/specwcs.ms
new file mode 100644
index 00000000..a9d90a41
--- /dev/null
+++ b/noao/onedspec/doc/sys/specwcs.ms
@@ -0,0 +1,612 @@
+.EQ
+delim $$
+gsize 10
+.EN
+.nr PS 11
+.nr VS 13
+.de V1
+.ft CW
+.ps -2
+.nf
+..
+.de V2
+.fi
+.ft R
+.ps +2
+..
+.ND March 1993
+.TL
+The IRAF/NOAO Spectral World Coordinate Systems
+.AU
+Francisco Valdes
+.AI
+IRAF Group - Central Computer Services
+.K2
+.DY
+
+.AB
+The image formats and world coordinate systems for dispersion calibrated
+spectra used in the IRAF/NOAO spectroscopy packages are described; in
+particular, the image header keywords defining the coordinates are given.
+These keywords appear both as part of the IRAF image structure and map
+directly to FITS format. The types of spectra include multidimensional
+images with one or more spatial axes and a linear or log-linear dispersion
+axis and special \fIequispec\fR and \fImultispec\fR formats having multiple
+independent one dimensional spectra in a single multidimensional image.
+The \fImultispec\fR format also includes general nonlinear dispersion
+coordinate systems using polynomial, spline, sampled table, and look-up
+table functions.
+.AE
+
+.NH
+Types of Spectral Data
+.LP
+Spectra are stored as one, two, or three dimensional images with one axis
+being the dispersion axis. A pixel value is the flux over
+some interval of wavelength and position. The simplest example of a
+spectrum is a one dimensional image which has pixel values as a
+function of wavelength.
+.LP
+There are two types of higher dimensional spectral image formats. One type
+has spatial axes for the other dimensions and the dispersion axis may be
+along any of the image axes. Typically this type of format is used for
+long slit (two dimensional) and Fabry-Perot (three dimensional) spectra.
+This type of spectra is referred to as \fIspatial\fR spectra and the
+world coordinate system (WCS) format is called \fIndspec\fR.
+The details of the world coordinate systems are discussed later.
+.LP
+The second type of higher dimensional spectral image consists of multiple,
+independent, one dimensional spectra stored in the higher dimensions with
+the first image axis being the dispersion axis; i.e. each line is a
+spectrum. This format allows associating many spectra and related
+parameters in a single data object. This type of spectra is referred to
+as \fImultispec\fR and the there are two coordinate system formats,
+\fIequispec\fR and \fImultispec\fR. The \fIequispec\fR format applies
+to the common case where all spectra have the same linear dispersion
+relation. The \fImultispec\fR format applies to the general case of spectra
+with differing dispersion relations or non-linear dispersion functions.
+These multi-spectrum formats are important since maintaining large numbers
+of spectra as individual one dimensional images is very unwieldy for the
+user and inefficient for the software.
+.LP
+Examples of multispec spectral images are spectra extracted from a
+multi-fiber or multi-aperture spectrograph or orders from an echelle
+spectrum. The second axis is some arbitrary indexing of the spectra,
+called \fIapertures\fR, and the third dimension is used for
+associated quantities. The IRAF \fBapextract\fR package may produce
+multiple spectra from a CCD image in successive image lines with an
+optimally weighted spectrum, a simple aperture sum spectrum, a background
+spectrum, and sigma spectrum as the associated quantities along the third
+dimension of the image.
+.LP
+Many \fBonedspec\fR package tasks which are designed to operate on
+individual one dimensional spectra may operate on spatial spectra by
+summing a number of neighboring spectra across the dispersion axis. This
+eliminates the need to "extract" one dimensional spectra from the natural
+format of this type of data in order to use tasks oriented towards the
+display and analysis of one dimensional spectra. The dispersion axis is
+either given in the image header by the keyword DISPAXIS or the package
+\fIdispaxis\fR parameter. The summing factors across the
+dispersion are specified by the \fInsum\fR package parameter.
+.LP
+One dimensional spectra, whether from multispec spatial spectra, have
+several associated quantities which may appear in the image header as part
+of the coordinate system description. The primary identification of a
+spectrum is an integer aperture number. This number must be unique within
+a single image. There is also an integer beam number used for various
+purposes such as discriminating object, sky, and arc spectra in
+multi-fiber/multi-aperture data or to identifying the order number in
+echelle data. For spectra summed from spatial spectra the aperture number
+is the central line, column, or band. In 3D images the aperture index
+wraps around the lowest non-dispersion axis. Since most one dimensional
+spectra are derived from an integration over one or more spatial axes, two
+additional aperture parameters record the aperture limits. These limits
+refer to the original pixel limits along the spatial axis. This
+information is primarily for record keeping but in some cases it is used
+for spatial interpolation during dispersion calibration. These values are
+set either by the \fBapextract\fR tasks or when summing neighboring vectors
+in spatial spectra.
+.LP
+An important task to be aware of for manipulating spectra between image
+formats is \fBscopy\fR. This task allows selecting spectra from multispec
+images and grouping them in various ways and also "extracts" apertures from
+long slit and 3D spectra simply and without resort to the more general
+\fBapextract\fR package.
+.NH
+World Coordinate Systems
+.LP
+IRAF images have three types of coordinate systems. The pixel array
+coordinates of an image or image section, i.e. the lines and
+columns, are called the \fIlogical\fR coordinates. The logical coordinates of
+individual pixels change as sections of the image are used or extracted.
+Pixel coordinates are tied to the data, i.e. are fixed to features
+in the image, are called \fIphysical\fR coordinates. Initially the logical
+and physical coordinates are the equivalent but differ when image sections
+or other tasks which modify the sampling of the pixels are applied.
+.LP
+The last type of coordinate system is called the \fIworld\fR coordinate
+system. Like the physical coordinates, the world coordinates are tied to
+the features in the image and remain unchanged when sections of the image
+are used or extracted. If a world coordinate system is not defined for an
+image, the physical coordinate system is considered to be the world
+coordinate system. In spectral images the world coordinate system includes
+dispersion coordinates such as wavelengths. In many tasks outside the
+spectroscopy packages, for example the \fBplot\fR, \fBtv\fR and
+\fBimages\fR packages, one may select the type of coordinate system to be
+used. To make plots and get coordinates in dispersion units for spectra
+with these tasks one selects the "world" system. The spectral tasks always
+use world coordinates.
+.LP
+The coordinate systems are defined in the image headers using a set of
+reserved keywords which are set, changed, and updated by various tasks.
+Some of the keywords consist of simple single values following the FITS
+convention. Others, the WAT keywords, encode long strings of information,
+one for each coordinate axis and one applying to all axes, into a set of
+sequential keywords. The values of these keywords must then be pasted
+together to recover the string. The long strings contain multiple pieces
+called WCS \fIattributes\fR. In general the WCS keywords should be left to
+IRAF tasks to modify. However, if one wants modify them directly some
+tasks which may be used are \fBhedit\fR, \fBhfix\fR, \fBwcsedit\fR,
+\fBwcsreset\fR, \fBspecshift\fR, \fBdopcor\fR, and \fBsapertures\fR. The
+first two are useful for the simple keywords, the two "wcs" tasks are
+useful for the linear ndspec and equispec formats, the next two are for the
+common cases of shifting the coordinate zero point or applying a doppler
+correction, and the last one is the one to use for the more complex
+multispec format attributes.
+.NH
+Physical Coordinate System
+.LP
+The physical coordinate system is used by the spectral tasks when there is
+no dispersion coordinate information (such as before dispersion
+calibration), to map the physical dispersion axis to the logical dispersion
+axis, and in the multispec world coordinate system dispersion functions
+which are defined in terms of physical coordinates.
+.LP
+The transformation between logical and physical coordinates is defined by
+the header keywords LTVi, LTMi_j (where i and j are axis numbers) through
+the vector equation
+
+.EQ I
+ l vec~=~|m| cdot p vec + v vec
+.EN
+
+where $l vec$ is a logical coordinate vector, $p vec$ is a physical
+coordinate vector, $v vec$ is the origin translation vector specified by
+the LTV keywords and $|m|$ is the scale/rotation matrix
+specified by the LTM keywords. For spectra rotation terms (nondiagonal
+matrix elements) generally do not make sense (in fact many tasks will not
+work if there is a rotation) so the transformations along each axis are
+given by the linear equation
+
+.EQ I
+ l sub i~=~LTMi_i cdot p sub i + LTVi.
+.EN
+
+If all the LTM/LTV keywords are missing they are assumed to have zero
+values except that the diagonal matrix terms, LTMi_i, are assumed to be 1.
+Note that if some of the keywords are present then a missing LTMi_i will
+take the value zero which generally causes an arithmetic or matrix
+inversion error in the IRAF tasks.
+.LP
+The dimensional mapping between logical and physical axes is given by the
+keywords WCSDIM and WAXMAP01. The WCSDIM keyword gives the dimensionality
+of the physical and world coordinate system. There must be coordinate
+information for that many axes in the header (though some may be missing
+and take their default values). If the WCSDIM keyword is missing it is
+assumed to be the same as the logical image dimensionality.
+.LP
+The syntax of the WAXMAP keyword are pairs of integer values,
+one for each physical axis. The first number of each pair indicates which
+current \fIlogical\fR axis corresponds to the original \fIphysical\fR axis
+(in order) or zero if that axis is missing. When the first number is zero
+the second number gives the offset to the element of the original axis
+which is missing. As an example consider a three dimensional image in
+which the second plane is extracted (an IRAF image section of [*,2,*]).
+The keyword would then appear as WAXMAP01 = '1 0 0 1 2 0'. If this keyword
+is missing the mapping is 1:1; i.e. the dimensionality and order of the
+axes are the same.
+.LP
+The dimensional mapping is important because the dispersion axis for
+the nspec spatial spectra as specified by the DISPAXIS keyword or task
+parameter, or the axis definitions for the equispec and or multispec
+formats are always in terms of the original physical axes.
+.NH
+Linear Spectral World Coordinate Systems
+.LP
+When there is a linear or logarithmic relation between pixels and
+dispersion coordinates which is the same for all spectra the WCS header
+format is simple and uses the FITS convention (with the CD matrix keywords
+proposed by Hanisch and Wells 1992) for the logical pixel to world
+coordinate transformation. This format applies to one, two, and three
+dimensional data. The higher dimensional data may have either linear
+spatial axes or the equispec format where each one dimensional spectrum
+stored along the lines of the image has the same dispersion.
+.LP
+The FITS image header keywords describing the spectral world coordinates
+are CTYPEi, CRPIXi, CRVALi, and CDi_j where i and j are axis numbers. As
+with the physical coordinate transformation the nondiagonal or rotation
+terms are not expected in the spectral WCS and may cause problems if they
+are not zero. The CTYPEi keywords will have the value LINEAR to identify
+the type of of coordinate system. The transformation between dispersion
+coordinate, $w sub i$, and logical pixel coordinate, $l sub i$, along axis i is given by
+
+.EQ I
+ w sub i~=~CRVALi + CDi_i cdot (l sub i - CRPIXi)
+.EN
+
+If the keywords are missing then the values are assumed to be zero except
+for the diagonal elements of the scale/rotation matrix, the CDi_i, which
+are assumed to be 1. If only some of the keywords are present then any
+missing CDi_i keywords will take the value 0 which will cause IRAF tasks to
+fail with arithmetic or matrix inversion errors. If the CTYPEi keyword is
+missing it is assumed to be "LINEAR".
+.LP
+If the pixel sampling is logarithmic in the dispersion coordinate, as
+required for radial velocity cross-correlations, the WCS coordinate values
+are logarithmic and $w sub i$ (above) is the logarithm of the dispersion
+coordinate. The spectral tasks (though not other tasks) will recognize
+this case and automatically apply the anti-log. The two types of pixel
+sampling are identified by the value of the keyword DC-FLAG. A value of 0
+defines a linear sampling of the dispersion and a value of 1 defines a
+logarithmic sampling of the dispersion. Thus, in all cases the spectral
+tasks will display and analyze the spectra in the same dispersion units
+regardless of the pixel sampling.
+.LP
+Other keywords which may be present are DISPAXIS for 2 and 3 dimensional
+spatial spectra, and the WCS attributes "system", "wtype", "label", and
+"units". The system attribute will usually have the value "world" for
+spatial spectra and "equispec" for equispec spectra. The wtype attribute
+will have the value "linear". Currently the label will be either "Pixel"
+or "Wavelength" and the units will be "Angstroms" for dispersion corrected
+spectra. In the future there will be more generality in the units
+for dispersion calibrated spectra.
+.LP
+Figure 1 shows the WCS keywords for a two dimensional long slit spectrum.
+The coordinate system is defined to be a generic "world" system and the
+wtype attributes and CTYPE keywords define the axes to be linear. The
+other attributes define a label and unit for the second axis, which is the
+dispersion axis as indicated by the DISPAXIS keyword. The LTM/LTV keywords
+in this example show that a subsection of the original image has been
+extracted with a factor of 2 block averaging along the dispersion axis.
+The dispersion coordinates are given in terms of the \fIlogical\fR pixel
+coordinates by the FITS keywords as defined previously.
+
+.DS
+.ce
+Figure 1: Long Slit Spectrum
+
+.V1
+WAT0_001= 'system=world'
+WAT1_001= 'wtype=linear'
+WAT2_001= 'wtype=linear label=Wavelength units=Angstroms'
+WCSDIM = 2
+DISPAXIS= 2
+DC-FLAG = 0
+
+CTYPE1 = 'LINEAR '
+LTV1 = -10.
+LTM1_1 = 1.
+CRPIX1 = -9.
+CRVAL1 = 19.5743865966797
+CD1_1 = 1.01503419876099
+
+CTYPE2 = 'LINEAR '
+LTV2 = -49.5
+LTM2_2 = 0.5
+CRPIX2 = -49.
+CRVAL2 = 4204.462890625
+CD2_2 = 12.3337936401367
+.V2
+.DE
+
+Figure 2 shows the WCS keywords for a three dimensional image where each
+line is an independent spectrum or associated data but where all spectra
+have the same linear dispersion. This type of coordinate system has the
+system name "equispec". The ancillary information about each aperture is
+found in the APNUM keywords. These give the aperture number, beam number,
+and extraction limits. In this example the LTM/LTV keywords have their
+default values; i.e. the logical and physical coordinates are the same.
+
+.DS
+.ce
+Figure 2: Equispec Spectrum
+
+.V1
+WAT0_001= 'system=equispec'
+WAT1_001= 'wtype=linear label=Wavelength units=Angstroms'
+WAT2_001= 'wtype=linear'
+WAT3_001= 'wtype=linear'
+WCSDIM = 3
+DC-FLAG = 0
+APNUM1 = '41 3 7.37 13.48'
+APNUM2 = '15 1 28.04 34.15'
+APNUM3 = '33 2 43.20 49.32'
+
+CTYPE1 = 'LINEAR '
+LTM1_1 = 1.
+CRPIX1 = 1.
+CRVAL1 = 4204.463
+CD1_1 = 6.16689700000001
+
+CTYPE2 = 'LINEAR '
+LTM2_2 = 1.
+CD2_2 = 1.
+
+CTYPE3 = 'LINEAR '
+LTM3_3 = 1.
+CD3_3 = 1.
+.V2
+.DE
+.NH
+Multispec Spectral World Coordinate System
+.LP
+The \fImultispec\fR spectral world coordinate system applies only to one
+dimensional spectra; i.e. there is no analog for the spatial type spectra.
+It is used either when there are multiple 1D spectra with differing
+dispersion functions in a single image or when the dispersion functions are
+nonlinear.
+.LP
+The multispec coordinate system is always two dimensional though there may
+be an independent third axis. The two axes are coupled and they both have
+axis type "multispec". When the image is one dimensional the physical line
+is given by the dimensional reduction keyword WAXMAP. The second, line
+axis, has world coordinates of aperture number. The aperture numbers are
+integer values and need not be in any particular order but do need to be
+unique. This aspect of the WCS is not of particular user interest but
+applications use the inverse world to physical transformation to select a
+spectrum line given a specified aperture.
+.LP
+The dispersion functions are specified by attribute strings with the
+identifier \fIspecN\fR where N is the \fIphysical\fR image line. The
+attribute strings contain a series of numeric fields. The fields are
+indicated symbolically as follows.
+
+.EQ I
+ specN~=~ap~beam~dtype~w1~dw~nw~z~aplow~aphigh~[functions sub i ]
+.EN
+
+where there are zero or more functions having the following fields,
+
+.EQ I
+ function sub i~=~ wt sub i~w0 sub i~ftype sub i~[parameters]~[coefficients]
+.EN
+
+The first nine fields in the attribute are common to all the dispersion
+functions. The first field of the WCS attribute is the aperture number,
+the second field is the beam number, and the third field is the dispersion
+type with the same function as DC-FLAG in the \fInspec\fR and
+\fIequispec\fR formats. A value of -1 indicates the coordinates are not
+dispersion coordinates (the spectrum is not dispersion calibrated), a value
+of 0 indicates linear dispersion sampling, a value of 1 indicates
+log-linear dispersion sampling, and a value of 2 indicates a nonlinear
+dispersion.
+.LP
+The next two fields are the dispersion coordinate of the first
+\fIphysical\fR pixel and the average dispersion interval per \fIphysical\fR
+pixel. For linear and log-linear dispersion types the dispersion
+parameters are exact while for the nonlinear dispersion functions they are
+approximate. The next field is the number of valid pixels, hence it is
+possible to have spectra with varying lengths in the same image. In that
+case the image is as big as the biggest spectrum and the number of pixels
+selects the actual data in each image line. The next (seventh) field is a
+doppler factor. This doppler factor is applied to all dispersion
+coordinates by multiplying by $1/(1+z)$ (assuming wavelength dispersion
+units). Thus a value of 0 is no doppler correction. The last two fields
+are extraction aperture limits as discussed previously.
+.LP
+Following these fields are zero or more function descriptions. For linear
+or log-linear dispersion coordinate systems there are no function fields.
+For the nonlinear dispersion systems the function fields specify a weight,
+a zero point offset, the type of dispersion function, and the parameters
+and coefficients describing it. The function type codes, $ftype sub i$,
+are 1 for a chebyshev polynomial, 2 for a legendre polynomial, 3 for a
+cubic spline, 4 for a linear spline, 5 for a pixel coordinate array, and 6
+for a sampled coordinate array. The number of fields before the next
+function and the number of functions are determined from the parameters of
+the preceding function until the end of the attribute is reached.
+.LP
+The equation below shows how the final wavelength is computed based on
+the $nfunc$ individual dispersion functions $W sub i (p)$. Note that this
+is completely general in that different function types may be combined.
+However, in practice when multiple functions are used they are generally of
+the same type and represent a calibration before and after the actual
+object observation with the weights based on the relative time difference
+between the calibration dispersion functions and the object observation.
+
+.EQ I
+w~=~sum from i=1 to nfunc {wt sub i cdot (w0 sub i + W sub i (p)) / (1 + z)}
+.EN
+
+The multispec coordinate systems define a transformation between physical
+pixel, $p$, and world coordinates, $w$. Generally there is an intermediate
+coordinate system used. The following equations define these coordinates.
+The first one shows the transformation between logical, $l$, and physical,
+$p$, coordinates based on the LTM/LTV keywords. The polynomial functions
+are defined in terms of a normalized coordinate, $n$, as shown in the
+second equation. The normalized coordinates run between -1 and 1 over the
+range of physical coordinates, $p sub min$ and $p sub max$ which are
+parameters of the function, upon which the coefficients were defined. The
+spline functions map the physical range into an index over the number of
+evenly divided spline pieces, $npieces$, which is a parameter of the
+function. This mapping is shown in the third and fourth equations where
+$s$ is the continuous spline coordinate and $j$ is the nearest integer less
+than or equal to $s$.
+
+.EQ I
+ p mark~=~(l - LTV1) / LTM1_1
+.EN
+.EQ I
+ n lineup~=~(p - p sub middle ) / (2 cdot p sub range )
+.EN
+.EQ I
+ lineup~=~(p - (p sub max + p sub min )/2) / (2 cdot (p sub max - p sub min ))
+.EN
+.EQ I
+ s lineup~=~(p - p sub min ) / (p sub max - p sub min ) cdot npieces
+.EN
+.EQ I
+ j lineup~=~roman "int" (s)
+.EN
+.NH 2
+Linear and Log Linear Dispersion Function
+.LP
+The linear and log-linear dispersion functions are described by a
+wavelength at the first \fIphysical\fR pixel and a wavelength increment per
+\fIphysical\fR pixel. A doppler correction may also be applied. The
+equations below show the two forms. Note that the coordinates returned are
+always wavelength even though the pixel sampling and the dispersion
+parameters may be log-linear.
+
+.EQ I
+ w mark~=~(w1 + dw cdot (p - 1)) / (1 + z)
+.EN
+.EQ I
+ w lineup~=~10 sup {(w1 + dw cdot (p - 1)) / (1 + z)}
+.EN
+
+Figure 3 shows an example from a multispec image with
+independent linear dispersion coordinates. This is a linearized echelle
+spectrum where each order (identified by the beam number) is stored as a
+separate image line.
+
+.DS
+.ce
+Figure 3: Echelle Spectrum with Linear Dispersion Function
+
+.V1
+WAT0_001= 'system=multispec'
+WAT1_001= 'wtype=multispec label=Wavelength units=Angstroms'
+WAT2_001= 'wtype=multispec spec1 = "1 113 0 4955.44287109375 0.05...
+WAT2_002= '5 256 0. 23.22 31.27" spec2 = "2 112 0 4999.0810546875...
+WAT2_003= '58854293 256 0. 46.09 58.44" spec3 = "3 111 0 5043.505...
+WAT2_004= '928358078002 256 0. 69.28 77.89"
+WCSDIM = 2
+
+CTYPE1 = 'MULTISPE'
+LTM1_1 = 1.
+CD1_1 = 1.
+
+CTYPE2 = 'MULTISPE'
+LTM2_2 = 1.
+CD2_2 = 1.
+.V2
+.DE
+.NH 2
+Chebyshev Polynomial Dispersion Function
+.LP
+The parameters for the chebyshev polynomial dispersion function are the
+$order$ (number of coefficients) and the normalizing range of physical
+coordinates, $p sub min$ and $p sub max$, over which the function is
+defined and which are used to compute $n$. Following the parameters are
+the $order$ coefficients, $c sub i$. The equation below shows how to
+evaluate the function using an iterative definition where $x sub 1 = 1$,
+$x sub 2 = n$, and $x sub i = 2 cdot n cdot x sub {i-1} - x sub {i-2}$.
+
+.EQ I
+ W~=~sum from i=1 to order {c sub i cdot x sub i}
+.EN
+.NH 2
+Legendre Polynomial Dispersion Function
+.LP
+The parameters for the legendre polynomial dispersion function are the
+order (number of coefficients) and the normalizing range of physical
+coordinates, pmin and pmax, over which the function is defined
+and which are used to compute n. Following the parameters are the
+order coefficients, c sub i. The equation below shows how to evaluate the
+function using an iterative definition where $x sub 1 = 1$, $x sub 2 = n$, and
+$x sub i = ((2i-3) cdot n cdot x sub {i-1} - (i-2) cdot x sub {i-2}) / (i-1)$.
+
+.EQ I
+ W~=~sum from i=1 to order {c sub i cdot x sub i}
+.EN
+.LP
+Figure 4 shows an example from a multispec image with independent nonlinear
+dispersion coordinates. This is again from an echelle spectrum. Note that
+the IRAF \fBechelle\fR package determines a two dimensional dispersion
+function, in this case a bidimensional legendre polynomial, with the
+independent variables being the order number and the extracted pixel
+coordinate. To assign and store this function in the image is simply a
+matter of collapsing the two dimensional dispersion function by fixing the
+order number and combining all the terms with the same order.
+
+.DS
+.ce
+Figure 4: Echelle Spectrum with Legendre Polynomial Function
+
+.V1
+WAT0_001= 'system=multispec'
+WAT1_001= 'wtype=multispec label=Wavelength units=Angstroms'
+WAT2_001= 'wtype=multispec spec1 = "1 113 2 4955.442888635351 0.05...
+WAT2_002= '83 256 0. 23.22 31.27 1. 0. 2 4 1. 256. 4963.0163112090...
+WAT2_003= '976664 -0.3191636898579552 -0.8169352858733255" spec2 =...
+WAT2_004= '9.081188912082 0.06387049476832223 256 0. 46.09 58.44 1...
+WAT2_005= '56. 5007.401409453303 8.555959076467951 -0.176732458267...
+WAT2_006= '09935064388" spec3 = "3 111 2 5043.505764869474 0.07097...
+WAT2_007= '256 0. 69.28 77.89 1. 0. 2 4 1. 256. 5052.586239197408 ...
+WAT2_008= '271 -0.03173489817897474 -7.190562320405975E-4"
+WCSDIM = 2
+
+CTYPE1 = 'MULTISPE'
+LTM1_1 = 1.
+CD1_1 = 1.
+
+CTYPE2 = 'MULTISPE'
+LTM2_2 = 1.
+CD2_2 = 1.
+.V2
+.DE
+.NH 2
+Linear Spline Dispersion Function
+.LP
+The parameters for the linear spline dispersion function are the number of
+spline pieces, $npieces$, and the range of physical coordinates, $p sub min$
+and $p sub max$, over which the function is defined and which are used to
+compute the spline coordinate $s$. Following the parameters are the
+$npieces+1$ coefficients, $c sub i$. The two coefficients used in a linear
+combination are selected based on the spline coordinate, where $a$ and $b$
+are the fractions of the interval in the spline piece between the spline
+knots, $a=(j+1)-s$, $b=s-j$, and $x sub 0 =a$, and $x sub 1 =b$.
+
+.EQ I
+ W~=~sum from i=0 to 1 {c sub (i+j) cdot x sub i}
+.EN
+.NH 2
+Cubic Spline Dispersion Function
+.LP
+The parameters for the cubic spline dispersion function are the number of
+spline pieces, $npieces$, and the range of physical coordinates, $p sub min$
+and $p sub max$, over which the function is defined and which are used
+to compute the spline coordinate $s$. Following the parameters are the
+$npieces+3$ coefficients, $c sub i$. The four coefficients used are
+selected based on the spline coordinate. The fractions of the interval
+between the integer spline knots are given by $a$ and $b$, $a=(j+1)-s$,
+b=$s-j$, and $x sub 0 =a sup 3$, $x sub 1 =(1+3 cdot a cdot (1+a cdot b))$,
+$x sub 2 =(1+3 cdot b cdot (1+a cdot b))$, and $x sub 3 =b sup 3$.
+
+.EQ I
+ W~=~sum from i=0 to 3 {c sub (i+j) cdot x sub i}
+.EN
+.NH 2
+Pixel Array Dispersion Function
+.LP
+The parameters for the pixel array dispersion function consists of just the
+number of coordinates $ncoords$. Following this are the wavelengths at
+integer physical pixel coordinates starting with 1. To evaluate a
+wavelength at some physical coordinate, not necessarily an integer, a
+linear interpolation is used between the nearest integer physical coordinates
+and the desired physical coordinate where $a$ and $b$ are the usual
+fractional intervals $k= roman "int" (p)$, $a=(k+1)-p$, $b=p-k$,
+and $x sub 0 =a$, and $x sub 1 =b$.
+
+.EQ I
+ W~=~sum from i=0 to 1 {c sub (i+j) cdot x sub i}
+.EN
+.NH 2
+Sampled Array Dispersion Function
+.LP
+The parameters for the sampled array dispersion function consists of
+the number of coordinate pairs, $ncoords$, and a dummy field.
+Following these are the physical coordinate and wavelength pairs
+which are in increasing order. The nearest physical coordinates to the
+desired physical coordinate are located and a linear interpolation
+is computed between the two sample points.
diff --git a/noao/onedspec/doc/telluric.hlp b/noao/onedspec/doc/telluric.hlp
new file mode 100644
index 00000000..f0bfe597
--- /dev/null
+++ b/noao/onedspec/doc/telluric.hlp
@@ -0,0 +1,350 @@
+.help telluric Mar97 noao.onedspec
+.ih
+NAME
+telluric -- remove telluric features from 1D spectra
+.ih
+SUMMARY
+Telluric calibration spectra are shifted and scaled to best divide out
+telluric features from data spectra. This may be done non-interactively to
+minimize the RMS in some region or regions of the data spectra and
+interactively with a graphically search.
+.ih
+USAGE
+telluric input output cal
+.ih
+PARAMETERS
+.ls input
+List of input data images containing one dimensional spectra to be
+corrected. All spectra in each image are corrected. The spectra need not
+be wavelength calibrated.
+.le
+.ls output
+List of output corrected images. The list must either match the input list
+or be an empty list. If an empty list is specified the input spectra will
+be replaced by the corrected spectra. The input spectra will also be
+replaced if the input and output image names are the same. Any other image
+name must be for a new image otherwise a warning message will be given and
+the task will proceed to the next input image.
+.le
+.ls cal
+List of telluric calibration images. If a single image is specified it
+will apply to all the input images. Otherwise the list of calibration
+images must match the list of input images.
+.le
+.ls ignoreaps = no
+Ignore aperture numbers between the input spectra and the calibration
+spectra? If "no" then the calibration image must contain a spectrum
+with the same aperture number as each spectrum in the input image.
+Otherwise the first spectrum in the calibration image will be used
+for all spectra in the input image.
+.le
+.ls xcorr = yes
+Cross-correlate each input spectrum with the calibration spectrum to
+determine an shift for the calibration spectrum? Only regions specified by
+the sample regions parameter will be used in the cross-correlation.
+.le
+.ls tweakrms = yes
+Search for the minimum RMS in the corrected spectrum by adjusting the
+shifts and scales between the input spectrum and the calibration spectrum?
+The RMS is minimized in the specified sample regions.
+.le
+.ls interactive = yes
+Enter an interactive graphical mode to search for the best shift
+and scale between the input spectra and calibration spectra? This
+is done after the optional automatic cross-correlation and RMS minimization
+step. A query is made for each input spectrum so that the interactive
+step may be skipped during the execution of the task.
+.le
+.ls sample = "*"
+Sample regions to use for cross-correlation, automatic RMS minimization,
+and RMS values. The sample regions are specified by a list of comma
+separated ranges. The ranges are colon separate coordinate values.
+For dispersion calibrated spectra the coordinate values are in the
+dispersion units otherwise they are in pixel coordinates. The string "*"
+selects the entire spectrum. The sample regions may be changed
+interactively either with the cursor or with a colon command.
+.le
+.ls threshold = 0.
+Since the calibration consists of division by the scaled calibration data
+it is possible for totally saturated lines to have zero or negative values.
+The task will quit if detects negative or zero calibration values. The
+\fIthreshold\fR allows applying a minimum threshold to the calibration
+values so the task may continue.
+.le
+.ls lag = 10
+The cross-correlation lag to use when \fIxcorr\fR = yes. The lag
+is given in pixels. This is the distance to either side of the
+initial shift over which the cross-correlation profile is computed.
+If a value of zero is given then the cross-correlation step is not done.
+.le
+.ls shift = 0., dshift = 1.
+The initial shift and shift step in pixels. This initializes the shift
+search parameters for the first spectrum. If \fIdshift\fR is zero then
+there will be no search for a new shift and the 'x' interactive function is
+disabled. These parameters may be changed interactively. After the
+first spectrum subsequent spectra begin with the values from the last
+spectrum.
+.le
+.ls scale = 1., dscale = 0.2
+The initial scale and scale step. This initializes the scale
+search parameters for the first spectrum. If \fIdscale\fR is zero then
+there will be no search for a new scale and the 'y' interactive function is
+disabled. These parameters may be changed interactively. After the
+first spectrum subsequent spectra begin with the values from the last
+spectrum.
+.le
+.ls offset = 1.
+The interactive search displays three candidate corrected spectra which
+have been normalized to a mean of one. The offset is added and subtracted
+to separate the three candidates. The value may be changed interactively.
+.le
+.ls smooth = 1
+The displayed candidate corrected spectra are smoothed by a moving
+boxcar average with a box size specified by this parameter. The smoothing
+only applies to the displayed spectra and does not affect the measured
+RMS or the output corrected spectra. The value may be changed interactively.
+.le
+.ls cursor = ""
+Input cursor for the interactive graphics. A null value selects the
+graphics cursor otherwise a file of cursor values may be specified.
+.le
+.ls airmass
+Query parameter for the airmass. If the airmass is not in the image
+header under the keyword AIRMASS the user is queried for the airmass.
+This parameter should not be specified on the command line.
+.le
+.ls answer
+Query parameter for responding to the interactive question. This parameter
+should not be specified on the command line.
+.le
+.ls interp = poly5
+The \fBpackage\fR parameter specifying the interpolation function for shifting
+the calibration spectra to match the input spectra.
+.le
+.ih
+DESCRIPTION
+Input one dimensional spectra are corrected to remove telluric features by
+dividing by shifted and scaled calibration spectra. The calibration
+spectra are generally of hot, nearly featureless stars; hence this procedure
+is sometimes referred to as a B-star correction. The shifting
+allows for possible small shifts or errors in the dispersion zeropoints.
+The intensity scaling allows for differences in the airmass and variations
+in the abundance of the telluric species. The intensity scaling
+uses Beer's law which is the approximation that the change in absorption
+with abundance is an exponential relation.
+
+The following describes the correction. Let J(x_i) be the calibration
+spectrum at a set of pixels x_i. An interpolation function is fit to this
+spectrum to give J(x). The shifted and scaled calibration function
+is then
+
+.nf
+ (1) J'(x) = max (threshold, J(x+dx)) ** (A / A_cal * scale)
+.fi
+
+where dx is the pixel shift parameter, A is the airmass of the input
+spectrum, A_cal is the airmass of the calibration spectrum, and
+scale is the scale parameter. The operator "**" is exponentiation.
+The max operation limits the calibration spectrum to be greater
+than or equal to the specified threshold value. If the calibration
+value is ever less than or equal to zero then the task will quit
+with a warning error.
+
+The output corrected spectrum is then computed as
+
+.nf
+ (2) I'(x_i) = I(x_i) / (J'(x_i) / <J'>)
+.fi
+
+where I' is the corrected spectrum, I is the input spectrum, and <J'> is
+the mean of the shifted and scaled calibration spectrum to keep the output
+intensities comparable to the input spectrum. The value of <J'> is
+printed in the output as the "normalization". If the spectra are
+dispersion calibrated, possibly with different dispersion parameters, then
+the x values in (2) from the input spectrum are converted to matching
+pixels in the calibration spectrum using the dispersion functions of the
+two spectra.
+
+The purpose of this task is to determine the best values of the
+shift and scale parameters dx and scale. There
+are automatic and interactive methods provided. The automatic
+methods are cross-correlation of the calibration and input spectra
+to find a shift and an iterative search for the in both
+shift and scale that minimizes the RMS of I' in some region.
+The automatic methods are performed first, if selected, followed
+by the interactive, graphical step. The following describes
+the steps in the order in which they occur.
+
+The initial values of the shift and scale are set by the parameters
+\fIshift\fR and \fIscale\fR for the first spectrum. After that the values
+determined for the previous spectrum, those actually applied to correcting
+that spectrum, are used as the initial values for the next spectrum. The
+search steps and sample regions are also initialized by task parameters but
+may be modified during the interactive step and the modified values apply
+to subsequent spectra.
+
+If the \fIxcorr\fR parameter is yes and the \fIlag\fR parameter is
+not zero the calibration spectrum is cross-correlated against the input
+spectrum. Each spectrum is prepared as follows. A large scale continuum
+is fit by a quadratic chebyshev using 5 iterations of sigma clipping with a
+clipping factor of 3 sigma below the fit and 1 sigma above the fit and
+rejecting the deviant points along with one pixel on either side. This
+attempts to eliminate the effects of absorption lines. The continuum fit
+is subtracted from the spectrum and the spectrum is extended and tapered by
+a cosine function of length given by the \fIlag\fR parameter.
+
+The prepared spectra are then cross-correlated by shifting the calibration
+spectrum plus and minus the specified \fIlag\fR amount about the current
+shift value. Only the regions in the input spectrum specified by the
+sample regions parameter are used in the correlation. This produces a
+correlation profile whose peak defines the relative shift between the two
+spectra. The current shift value is updated. This method assumes the
+common telluric features dominate within the specified sample regions. The
+lag size should be roughly the profile widths of the telluric features.
+
+If the \fItweakrms\fR parameter is yes and \fIdshift\fR is greater than
+zero trial corrections at the current shift value and plus and minus one
+shift step with the scale value fixed at its current value are made and the
+RMS in the sample regions computed. If the RMS is smallest at the current
+shift value the shift step is divided in half otherwise the current shift
+value is set to the shift with the lowest RMS. The process is then
+repeated with the new shift and shift step values. This continues until
+either the shift step is less than 0.01 pixels or the shift is more than
+two pixels from the initial shift. In the latter case the final shift is
+reset to the original shift.
+
+The scale factor is then varied if \fIdscale\fR is greater than zero by the
+scale step at a fixed shift in the same way as above to search for a
+smaller RMS in the sample regions. This search terminates when the scale
+step is less than 0.01 or if the scale value has departed by 100% of the
+initial value. In the latter case the scale value is left unchanged.
+
+The search over the shifts and scales is repeated a second time after which
+the tweak algorithm terminates.
+
+After the optional cross-correlation and tweak steps the interactive search
+mode may be entered. This occurs if \fIinteractive\fR = yes. A query is
+asking whether to search interactively. The answers may be "no", "yes",
+"NO", or "YES". The lower case answers apply to the current spectrum and
+the upper case answers apply to all subsequent spectra. This means that if
+an answer of "NO" or "YES" is given then there will be no further queries
+for the remaining input spectra.
+
+If the interactive step is selected a graph of three candidate corrections
+for the input spectrum is displayed. There also may be a graph of the
+calibration or input spectrum shown for reference. Initially the
+calibration spectrum is displayed. The additional graph may be toggled off
+and on and between the input and calibration spectra with the 'c' and 'd'
+keys. The three candidate corrected spectra will be with the current shift
+and scale in the middle and plus or minus one step in either the shift or
+scale. Initially the spectra will be at different scale values.
+Information about the current shift and scale and the step used is given in
+the graph title.
+
+One may toggle between shift steps and scale steps with the 'x' (for shift)
+or 'y' (for scale) keys. The RMS in the title is the RMS within the
+currently defined sample regions. If one of the step values is zero then a
+display of different values of that parameter will not be selected. The
+step size will need to be set with a colon command to search in that
+parameter.
+
+If 'x' is typed when the three spectra are at different shifts then the
+nearest spectrum to the y cursor at the x cursor position will be
+selected. If the central spectrum is selected the step size is divided in
+half otherwise the current shift is changed and the selected spectrum
+becomes the middle spectrum. Three new spectra are then shown. The same
+applies if 'y' is typed when the three spectra are at different scales.
+This allows an interactive search similar to the iterative tweakrms method
+described previously except the user can use whatever criteria is desired
+to search for the best scale and shift.
+
+There are additional keystrokes and colon commands to set or change sample
+regions, reset the current shift, scale, and step sizes, expand the step
+size in the current mode, adjust the offsets between the spectra, and
+get help. The 'w' key and GTOOLS colon commands are available to window
+the graphs. Any changes in the x limits apply to both graphs while y limit
+adjustments apply to the graph pointed to by the cursor.
+
+Two other commands require a short explanation. The 'a' key may
+be used to run the tweakrms algorithm starting from the current
+shift, scale, and steps and the current sample regions. This allows
+one to graphically set or reset the sample regions before doing
+the RMS minimization. The ":smooth" command and associated
+\fIsmooth\fR task parameter allow the corrected spectra to be
+displayed with a boxcar smoothing to better see faint features in
+noise. It is important to realize that the smoothing is only
+done on the displayed spectra. The telluric correction and computed RMS
+are done in the unsmoothed data.
+
+After the interactive step is quit with 'q' or if the interactive
+step is not done then the final output spectrum is computed and
+written to the output image. A brief log output is printed for
+each spectrum.
+.ih
+CURSOR KEYS AND COLON COMMANDS
+.nf
+? - print help
+a - automatic RMS minimization within sample regions
+c - toggle calibration spectrum display
+d - toggle data spectrum display
+e - expand (double) the step for the current selection
+q - quit
+r - redraw the graphs
+s - add or reset sample regions
+w - window commands (see :/help for additional information)
+x - graph and select from corrected shifted candidates
+y - graph and select from corrected scaled candidates
+
+:help - print help
+:shift [value] - print or reset the current shift
+:scale [value] - print or reset the current scale
+:dshift [value] - print or reset the current shift step
+:dscale [value] - print or reset the current scale step
+:offset [value] - print or reset the current offset between spectra
+:sample [value] - print or reset the sample regions
+:smooth [value] - print or reset the smoothing box size
+.fi
+.ih
+EXAMPLES
+1. To interactively search for a best correction with the default
+cross-correlation and tweak steps:
+
+.nf
+ cl> telluric spec001.ms telspec001.ms spec005.ms
+.fi
+
+2. To search only for a scale factor:
+
+.nf
+ cl> telluric spec001.ms telspec001.ms spec005.ms xcorr- dshift=0.
+.fi
+
+3. To processes a set of spectra non-interactively with the same calibration
+spectrum and to replace the input spectra with the corrected spectra and
+log the processing:
+
+.nf
+ cl> telluric spec* "" calspec inter- > log
+.fi
+
+4. To apply the simplest scaling by the ratio of the airmasses alone:
+
+.nf
+ cl> telluric spec* tel//spec* calspec inter- xcorr- tweak- inter- \
+ >>> scale=1. shift=0.
+.fi
+.ih
+REVISIONS
+.ls TELLURIC V2.12.3
+The normalization is printed.
+.le
+.ls TELLURIC V2.11.2
+Threshold parameter added.
+.le
+.ls TELLURIC V2.11
+This task is new in this version.
+.le
+.ih
+SEE ALSO
+skytweak
+.endhelp
diff --git a/noao/onedspec/doc/telluric.key b/noao/onedspec/doc/telluric.key
new file mode 100644
index 00000000..11a42cc3
--- /dev/null
+++ b/noao/onedspec/doc/telluric.key
@@ -0,0 +1,35 @@
+ TELLURIC COMMAND SUMMARY
+
+? - print help
+a - automatic RMS minimization within sample regions
+c - toggle calibration spectrum display
+d - toggle data spectrum display
+e - expand (double) the step for the current selection
+q - quit
+r - redraw the graphs
+s - add or reset sample regions
+w - window commands (see :/help for additional information)
+x - graph and select from corrected shifted candidates
+y - graph and select from corrected scaled candidates
+
+:help - print help
+:shift [value] - print or reset the current shift
+:scale [value] - print or reset the current scale
+:dshift [value] - print or reset the current shift step
+:dscale [value] - print or reset the current scale step
+:offset [value] - print or reset the current offset between spectra
+:sample [value] - print or reset the sample regions
+:smooth [value] - print or reset the smoothing box size
+
+
+The stacked display shows three corrected candidate spectra. The center
+one is for the current shift and scale and the other two are one step
+higher or lower in the shift or scale. The current values and the
+step is shown in the title. Toggle between the shift and scale candidates
+with 'x' or 'y'. Select the best spectrum with the cursor and typing
+'x' or 'y'. Selecting the middle spectrum with 'x' in the shift display
+divides the shift step in half. Selecting one of the other spectra
+changes the current shift. Selecting the middle spectrum with 'y'
+in the scale display divides the scale step in half. Selecting one of
+the other spectra changes the current scale. When 'q' is typed the
+final shift and scale will be that of the middle spectrum.
diff --git a/noao/onedspec/doc/wspectext.hlp b/noao/onedspec/doc/wspectext.hlp
new file mode 100644
index 00000000..9840b7b4
--- /dev/null
+++ b/noao/onedspec/doc/wspectext.hlp
@@ -0,0 +1,96 @@
+.help wspectext Oct93 onedspec
+.ih
+NAME
+wspectext -- convert 1D image spectra to an ascii text spectra
+.ih
+USAGE
+wspectext input output
+.ih
+PARAMETERS
+.ls input
+Input list of 1D image spectra to be converted. If the image is
+not one dimensional an warning will be given and the image will be skipped.
+.le
+.ls output
+Output list of ascii text spectra filenames. The list must match the
+input list.
+.le
+.ls header = yes
+This parameter determines whether or not a descriptive header precedes the
+wavelength and flux values written to the text file. When \fIheader =
+no\fR, only a two column list of wavelengths and fluxes is output.
+.le
+.ls wformat = ""
+The wavelength coordinate output format. If it is undefined the formatting
+option stored with the WCS in the image header is used. If the WCS
+formatting option is not defined then a free format is used. See
+\fBlistpixels\fR for a description of the format syntax.
+.le
+.ih
+DESCRIPTION
+IRAF one dimensional spectra are converted to ascii text files. The
+text files consist of an optional FITS type header followed by a two
+column list of wavelengths and flux values. The format of the wavelengths
+can be set but the flux values are given in free format. This task
+is a combination of \fBwtextimage\fR and \fBlistpixels\fR. The output
+of this task may be converted back to an image spectrum with the
+task \fBrspectext\fR.
+
+Spectra which are not in 1D images such as multispec format or long slit
+may first be converted to 1D images using \fBscopy\fR with format="onedspec".
+.ih
+EXAMPLES
+1. Write a text file with a header.
+
+.nf
+ cl> wspectext spec001 text001 header+ wformat="%0.2f"
+ cl> type text001
+ BITPIX = 8 / 8-bit ASCII characters
+ NAXIS = 1 / Number of Image Dimensions
+ NAXIS1 = 100 / Length of axis
+ ORIGIN = 'NOAO-IRAF: WTEXTIMAGE' /
+ IRAF-MAX= 0. / Max image pixel (out of date)
+ IRAF-MIN= 0. / Min image pixel (out of date)
+ IRAF-B/P= 32 / Image bits per pixel
+ IRAFTYPE= 'REAL FLOATING ' / Image datatype
+ OBJECT = 'TITLE ' /
+ FILENAME= 'TEST ' / IRAF filename
+ FORMAT = '5G14.7 ' / Text line format
+ APNUM1 = '1 1 '
+ DC-FLAG = 0
+ WCSDIM = 1
+ CTYPE1 = 'LINEAR '
+ CRVAL1 = 4000.
+ CRPIX1 = 1.
+ CDELT1 = 10.1010101010101
+ CD1_1 = 10.1010101010101
+ LTM1_1 = 1.
+ WAT0_001= 'system=equispec '
+ WAT1_001= 'wtype=linear label=Wavelength units=Angstroms '
+ END
+
+ 4000.00 1000.
+ 4010.10 1005.54
+ 4020.20 1011.05
+ ...
+.fi
+
+2. Write a simple text file with two columns of wavelength and flux.
+
+.nf
+ cl> wspectext spec001 text002 header- wformat="%0.2f"
+ cl> type text002
+ 4000.00 1000.
+ 4010.10 1005.54
+ 4020.20 1011.05
+ ...
+.fi
+.ih
+REVISIONS
+.ls WSPECTEXT V2.10.3
+This is a new task with this version.
+.le
+.ih
+SEE ALSO
+rspectext, wtextimage, listpixels, scopy, imspec
+.endhelp
diff --git a/noao/onedspec/dopcor.par b/noao/onedspec/dopcor.par
new file mode 100644
index 00000000..95f7bb1b
--- /dev/null
+++ b/noao/onedspec/dopcor.par
@@ -0,0 +1,10 @@
+input,s,a,,,,List of input spectra
+output,s,a,,,,List of output spectra
+redshift,s,a,,,,Redshift or velocity (Km/s)
+isvelocity,b,h,no,,,Is the redshift parameter a velocity?
+add,b,h,no,,,Add to previous dispersion correction?
+dispersion,b,h,yes,,,Apply dispersion correction?
+flux,b,h,no,,,Apply flux correction?
+factor,r,h,3.,,,Flux correction factor (power of 1+z)
+apertures,s,h,"",,,List of apertures to correct
+verbose,b,h,no,,,Print corrections performed?
diff --git a/noao/onedspec/ecidentify.par b/noao/onedspec/ecidentify.par
new file mode 100644
index 00000000..102fcfee
--- /dev/null
+++ b/noao/onedspec/ecidentify.par
@@ -0,0 +1,26 @@
+# Parameters for ECIDENTIFY task.
+
+images,s,a,,,,Images containing features to be identified
+database,f,h,database,,,Database in which to record feature data
+coordlist,f,h,linelists$thar.dat,,,User coordinate list
+units,s,h,"",,,Coordinate units
+match,r,h,1.,,,Coordinate list matching limit in user units
+maxfeatures,i,h,100,,,Maximum number of features for automatic identification
+zwidth,r,h,10.,,,Zoom graph width in user units
+
+ftype,s,h,"emission","emission|absorption",,Feature type
+fwidth,r,h,4.,,,Feature width in pixels
+cradius,r,h,5.,,,Centering radius in pixels
+threshold,r,h,10.,0.,,Feature threshold for centering
+minsep,r,h,2.,0.,,Minimum pixel separation
+
+function,s,h,"chebyshev","legendre|chebyshev",,Coordinate function
+xorder,i,h,2,1,,Order of coordinate function along dispersion
+yorder,i,h,2,1,,Order of coordinate function across dispersion
+niterate,i,h,0,0,,Rejection iterations
+lowreject,r,h,3.,0.,,Lower rejection sigma
+highreject,r,h,3.,0.,,Upper rejection sigma
+
+autowrite,b,h,no,,,Automatically write to database?
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Graphics cursor input
diff --git a/noao/onedspec/ecidentify/eccenter.x b/noao/onedspec/ecidentify/eccenter.x
new file mode 100644
index 00000000..730ad2a8
--- /dev/null
+++ b/noao/onedspec/ecidentify/eccenter.x
@@ -0,0 +1,34 @@
+include "ecidentify.h"
+
+# EC_CENTER -- Locate the center of a feature.
+
+double procedure ec_center (ec, x, width, type)
+
+pointer ec # EC pointer
+double x # Initial guess
+real width # Feature width
+int type # Feature type
+
+double dvalue
+real value
+
+real center1d()
+double smw_c1trand()
+
+begin
+ if (IS_INDEFD(x))
+ return (x)
+
+ dvalue = smw_c1trand (EC_PL(ec), x)
+ if (IS_INDEFD(dvalue))
+ return (dvalue)
+
+ value = dvalue
+ value = center1d (value, IMDATA(ec,1), EC_NPTS(ec), width,
+ abs (type), EC_CRADIUS(ec), EC_THRESHOLD(ec))
+
+ if (IS_INDEF(value))
+ return (INDEFD)
+ else
+ return (smw_c1trand (EC_LP(ec), double(value)))
+end
diff --git a/noao/onedspec/ecidentify/eccolon.x b/noao/onedspec/ecidentify/eccolon.x
new file mode 100644
index 00000000..0fe22af5
--- /dev/null
+++ b/noao/onedspec/ecidentify/eccolon.x
@@ -0,0 +1,243 @@
+include <gset.h>
+include <error.h>
+include <pkg/center1d.h>
+include "ecidentify.h"
+
+# List of colon commands.
+define CMDS "|show|features|image|database|read|write|coordlist|match|\
+ |maxfeatures|minsep|zwidth|labels|fwidth|ftype|cradius|threshold|"
+
+define SHOW 1 # Show parameters
+define FEATURES 2 # Show list of features
+define IMAGE 3 # Set new image
+define DATABASE 4 # Set new database
+define READ 5 # Read database entry
+define WRITE 6 # Write database entry
+define COORDLIST 7 # Set new coordinate list
+define MATCH 8 # Set coordinate list matching distance
+# newline 9
+define MAXFEATURES 10 # Set maximum number of features for auto find
+define MINSEP 11 # Set minimum separation distance
+define ZWIDTH 12 # Set zoom window width
+define LABEL 13 # Set label type
+define WIDTH 14 # Set centering width
+define TYPE 15 # Set centering type
+define RADIUS 16 # Set centering radius
+define THRESHOLD 17 # Set the centering threshold
+
+# EC_COLON -- Respond to colon command.
+
+procedure ec_colon (ec, cmdstr, newimage, prfeature)
+
+pointer ec # ID pointer
+char cmdstr[ARB] # Colon command
+char newimage[ARB] # New image name
+int prfeature # Print current feature on status line
+
+char cmd[SZ_LINE]
+int i, ncmd, ival
+real rval[2]
+pointer im
+
+int nscan(), strdic(), ec_next()
+pointer immap()
+errchk immap, ec_dbread, ec_dbwrite, ec_log
+
+begin
+ # Scan the command string and get the first word.
+ call sscan (cmdstr)
+ call gargwrd (cmd, SZ_LINE)
+ ncmd = strdic (cmd, cmd, SZ_LINE, CMDS)
+
+ switch (ncmd) {
+ case SHOW: # :show - show values of parameters
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call gdeactivate (EC_GP(ec), AW_CLEAR)
+ call ec_show (ec, "STDOUT")
+ call greactivate (EC_GP(ec), AW_PAUSE)
+ } else {
+ iferr (call ec_show (ec, cmd)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case FEATURES: # :features - list features
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call gdeactivate (EC_GP(ec), AW_CLEAR)
+ call ec_log (ec, "STDOUT")
+ call greactivate (EC_GP(ec), AW_PAUSE)
+ } else {
+ iferr (call ec_log (ec, cmd)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case IMAGE: # :image - set image
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("image %s\n")
+ call pargstr (Memc[EC_IMAGE(ec)])
+ prfeature = NO
+ } else {
+ call strcpy (cmd, newimage, SZ_FNAME)
+ iferr {
+ im = immap (newimage, READ_ONLY, 0)
+ call imunmap (im)
+ } then {
+ newimage[1] = EOS
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case DATABASE: # :database - set database
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("database %s\n")
+ call pargstr (Memc[EC_DATABASE(ec)])
+ prfeature = NO
+ } else {
+ call strcpy (cmd, Memc[EC_DATABASE(ec)], SZ_FNAME)
+ EC_NEWDBENTRY(ec) = YES
+ }
+ case READ: # :read - read database entry
+ prfeature = NO
+ iferr {
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1)
+ call ec_dbread (ec, Memc[EC_IMAGE(ec)], YES)
+ else {
+ call xt_stripwhite (cmd)
+ if (cmd[1] == EOS)
+ call ec_dbread (ec, Memc[EC_IMAGE(ec)], YES)
+ else
+ call ec_dbread (ec, cmd, YES)
+ }
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ } then
+ call erract (EA_WARN)
+ case WRITE: # :write - write database entry
+ prfeature = NO
+ iferr {
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1)
+ call ec_dbwrite (ec, Memc[EC_IMAGE(ec)], YES)
+ else {
+ call xt_stripwhite (cmd)
+ if (cmd[1] == EOS)
+ call ec_dbwrite (ec, Memc[EC_IMAGE(ec)], YES)
+ else
+ call ec_dbwrite (ec, cmd, YES)
+ }
+ } then
+ call erract (EA_WARN)
+ case COORDLIST: # :coordlist - set coordinate list
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("coordlist %s\n")
+ call pargstr (Memc[EC_COORDLIST(ec)])
+ prfeature = NO
+ } else {
+ call strcpy (cmd, Memc[EC_COORDLIST(ec)], SZ_FNAME)
+ call ec_unmapll (ec)
+ call ec_mapll (ec)
+ }
+ case MATCH: # :match - set matching distance for coordinate list
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("match %g\n")
+ call pargr (EC_MATCH(ec))
+ prfeature = NO
+ } else
+ EC_MATCH(ec) = rval[1]
+ case MAXFEATURES: # :maxfeatures - set max num features for auto find
+ call gargi (ival)
+ if (nscan() == 1) {
+ call printf ("maxfeatures %d\n")
+ call pargi (EC_MAXFEATURES(ec))
+ prfeature = NO
+ } else
+ EC_MAXFEATURES(ec) = ival
+ case MINSEP: # :minsep - set minimum feature separation allowed
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("minsep %g\n")
+ call pargr (EC_MINSEP(ec))
+ prfeature = NO
+ } else
+ EC_MINSEP(ec) = rval[1]
+ case ZWIDTH: # :zwidth - set zoom window width
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("zwidth %g\n")
+ call pargr (EC_ZWIDTH(ec))
+ prfeature = NO
+ } else {
+ EC_ZWIDTH(ec) = rval[1]
+ if (EC_GTYPE(ec) == 2)
+ EC_NEWGRAPH(ec) = YES
+ }
+ case LABEL: # :labels - set label type
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ switch (EC_LABELS(ec)) {
+ case 2:
+ call printf ("labels index\n")
+ case 3:
+ call printf ("labels pixel\n")
+ case 4:
+ call printf ("labels user\n")
+ default:
+ call printf ("labels none\n")
+ }
+ prfeature = NO
+ } else {
+ EC_LABELS(ec) = strdic (cmd, cmd, SZ_LINE, LABELS)
+ do i = 1, EC_NFEATURES(ec) {
+ if (APN(ec,i) == EC_AP(ec))
+ call ec_mark (ec, i)
+ }
+ }
+ case WIDTH: # :fwidth - set centering width
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("fwidth %g\n")
+ call pargr (EC_FWIDTH(ec))
+ prfeature = NO
+ } else
+ EC_FWIDTH(ec) = rval[1]
+ case TYPE: # :ftype - set centering type
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ switch (EC_FTYPE(ec)) {
+ case EMISSION:
+ call printf ("ftype emission\n")
+ case ABSORPTION:
+ call printf ("ftype absorption\n")
+ }
+ prfeature = NO
+ } else
+ EC_FTYPE(ec) = strdic (cmd, cmd, SZ_LINE, FTYPES)
+ case RADIUS: # :cradius - set centering radius
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("cradius %g\n")
+ call pargr (EC_CRADIUS(ec))
+ prfeature = NO
+ } else
+ EC_CRADIUS(ec) = rval[1]
+ case THRESHOLD: # :threshold - set centering threshold
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("threshold %g\n")
+ call pargr (EC_THRESHOLD(ec))
+ prfeature = NO
+ } else
+ EC_THRESHOLD(ec) = rval[1]
+ default:
+ call printf ("Unrecognized or ambiguous command\007")
+ prfeature = NO
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecdb.x b/noao/onedspec/ecidentify/ecdb.x
new file mode 100644
index 00000000..f6e02526
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecdb.x
@@ -0,0 +1,268 @@
+include <math/gsurfit.h>
+include <smw.h>
+include <units.h>
+include "ecidentify.h"
+
+# EC_DBREAD -- Read features data from the database.
+
+procedure ec_dbread (ec, name, verbose)
+
+pointer ec # ID pointer
+char name[SZ_LINE]
+int verbose
+
+pointer dt
+int i, j, k, ncoeffs, rec, slope, offset, niterate
+double shift, low, high
+pointer sp, coeffs, line, cluster, un
+
+int ec_line()
+int dtgeti(), dgsgeti(), dtlocate(), dtscan(), nscan()
+real dtgetr()
+bool un_compare()
+double dgsgetd(), smw_c1trand()
+pointer dtmap1(), un_open()
+
+errchk dtmap1, dtlocate, dtgeti, dtgad, un_open
+
+begin
+ call smark (sp)
+ call salloc (cluster, SZ_LINE, TY_CHAR)
+ call salloc (line, SZ_LINE, TY_CHAR)
+
+ call imgcluster (name, Memc[cluster], SZ_LINE)
+ call sprintf (Memc[line], SZ_LINE, "ec%s")
+ call pargstr (Memc[cluster])
+ dt = dtmap1 (Memc[EC_DATABASE(ec)], Memc[line], READ_ONLY)
+
+ call sprintf (Memc[line], SZ_LINE, "ecidentify %s")
+ call pargstr (Memc[cluster])
+
+ rec = dtlocate (dt, Memc[line])
+ if (rec == EOF)
+ call error (0, "Entry not found")
+
+ i = dtgeti (dt, rec, "features")
+
+ EC_NALLOC(ec) = i
+ call realloc (EC_APNUM(ec), i, TY_INT)
+ call realloc (EC_LINENUM(ec), i, TY_INT)
+ call realloc (EC_ORD(ec), i, TY_INT)
+ call realloc (EC_PIX(ec), i, TY_DOUBLE)
+ call realloc (EC_FIT(ec), i, TY_DOUBLE)
+ call realloc (EC_USER(ec), i, TY_DOUBLE)
+ call realloc (EC_FWIDTHS(ec), i, TY_REAL)
+ call realloc (EC_FTYPES(ec), i, TY_INT)
+
+ j = 1
+ do i = 1, EC_NALLOC(ec) {
+ k = dtscan (dt)
+ call gargi (APN(ec,j))
+ call gargi (ORDER(ec,j))
+ call gargd (PIX(ec,j))
+ call gargd (FIT(ec,j))
+ call gargd (USER(ec,j))
+ call gargr (FWIDTH(ec,j))
+ call gargi (FTYPE(ec,j))
+ call gargi (k)
+ if (nscan() == 8 && k == 0)
+ FTYPE(ec,j) = -FTYPE(ec,j)
+ iferr (LINE(ec,j) = ec_line (ec, APN(ec,j)))
+ next
+ shift = smw_c1trand (EC_PL(ec), PIX(ec,j))
+ low = 0.5
+ high = SN(SH(ec,LINE(ec,j))) + 0.5
+ if (shift < low || shift > high)
+ next
+ j = j + 1
+ }
+ EC_NFEATURES(ec) = j - 1
+
+ iferr (shift = dtgetr (dt, rec, "shift"))
+ shift = 0.
+ iferr (offset = dtgeti (dt, rec, "offset"))
+ offset = 0
+ iferr (slope = dtgeti (dt, rec, "slope"))
+ slope = 1
+ call ecf_setd ("shift", shift)
+ call ecf_seti ("offset", offset)
+ call ecf_seti ("slope", slope)
+
+ iferr {
+ ncoeffs = dtgeti (dt, rec, "coefficients")
+ call salloc (coeffs, ncoeffs, TY_DOUBLE)
+ call dtgad (dt, rec, "coefficients", Memd[coeffs], ncoeffs, ncoeffs)
+
+ if (EC_ECF(ec) != NULL)
+ call dgsfree (EC_ECF(ec))
+ call dgsrestore (EC_ECF(ec), Memd[coeffs])
+
+ call ecf_setd ("xmin", dgsgetd (EC_ECF(ec), GSXMIN))
+ call ecf_setd ("xmax", dgsgetd (EC_ECF(ec), GSXMAX))
+ call ecf_setd ("ymin", dgsgetd (EC_ECF(ec), GSYMIN))
+ call ecf_setd ("ymax", dgsgetd (EC_ECF(ec), GSYMAX))
+ call ecf_seti ("xorder", dgsgeti (EC_ECF(ec), GSXORDER))
+ call ecf_seti ("yorder", dgsgeti (EC_ECF(ec), GSYORDER))
+
+ switch (dgsgeti (EC_ECF(ec), GSTYPE)) {
+ case GS_LEGENDRE:
+ call ecf_sets ("function", "legendre")
+ case GS_CHEBYSHEV:
+ call ecf_sets ("function", "chebyshev")
+ }
+
+ ifnoerr (niterate = dtgeti (dt, rec, "niterate"))
+ call ecf_seti ("niterate", niterate)
+ ifnoerr (low = dtgetr (dt, rec, "lowreject"))
+ call ecf_setd ("low", low)
+ ifnoerr (high = dtgeti (dt, rec, "highreject"))
+ call ecf_setd ("high", high)
+
+ EC_NEWECF(ec) = YES
+ EC_CURRENT(ec) = min (1, EC_NFEATURES(ec))
+ } then
+ ;
+
+ ifnoerr (call dtgstr (dt, rec, "units", Memc[line], SZ_LINE)) {
+ if (EC_UN(ec) == NULL)
+ EC_UN(ec) = un_open (Memc[line])
+ else {
+ un = un_open (Memc[line])
+ if (!un_compare (un, EC_UN(ec))) {
+ call ec_unitsll (ec, Memc[line])
+ call un_close (EC_UN(ec))
+ EC_UN(ec) = un
+ } else
+ call un_close (un)
+ }
+ }
+
+ call dtunmap (dt)
+ call sfree (sp)
+
+ if (EC_NFEATURES(ec) > 0) {
+ EC_NEWGRAPH(ec) = YES
+ EC_NEWFEATURES(ec) = YES
+ EC_CURRENT(ec) = 1
+ } else
+ EC_CURRENT(ec) = 0
+
+ if (verbose == YES) {
+ call printf ("ecidentify %s\n")
+ call pargstr (Memc[cluster])
+ }
+end
+
+
+# EC_DBWRITE -- Write features data to the database.
+
+procedure ec_dbwrite (ec, name, verbose)
+
+pointer ec # ID pointer
+char name[ARB]
+int verbose
+
+int i, ncoeffs
+pointer dt, sp, coeffs, root, cluster
+
+int dgsgeti(), ecf_geti()
+double ecf_getd()
+pointer dtmap1(), immap()
+
+errchk dtmap1, immap
+
+begin
+ call smark (sp)
+ call salloc (cluster, SZ_FNAME, TY_CHAR)
+ call salloc (root, SZ_FNAME, TY_CHAR)
+
+ call imgcluster (name, Memc[cluster], SZ_FNAME)
+ call sprintf (Memc[root], SZ_FNAME, "ec%s")
+ call pargstr (Memc[cluster])
+ dt = dtmap1 (Memc[EC_DATABASE(ec)], Memc[root], APPEND)
+
+ call dtptime (dt)
+ call dtput (dt, "begin\tecidentify %s\n")
+ call pargstr (Memc[cluster])
+ call dtput (dt, "\tid\t%s\n")
+ call pargstr (Memc[cluster])
+ call dtput (dt, "\ttask\tecidentify\n")
+ call dtput (dt, "\timage\t%s\n")
+ call pargstr (Memc[EC_IMAGE(ec)])
+
+ if (EC_UN(ec) != NULL) {
+ call dtput (dt, "\tunits\t%s\n")
+ call pargstr (UN_UNITS(EC_UN(ec)))
+ }
+ call dtput (dt, "\tfeatures\t%d\n")
+ call pargi (EC_NFEATURES(ec))
+ do i = 1, EC_NFEATURES(ec) {
+ call dtput (dt,
+ "\t\t%3d %3d %7.2f %10.9g %10.9g %4.1f %d %d\n")
+ call pargi (APN(ec,i))
+ call pargi (ORDER(ec,i))
+ call pargd (PIX(ec,i))
+ call pargd (FIT(ec,i))
+ call pargd (USER(ec,i))
+ call pargr (FWIDTH(ec,i))
+ call pargi (abs (FTYPE(ec,i)))
+ if (FTYPE(ec,i) > 0)
+ call pargi (1)
+ else
+ call pargi (0)
+ }
+
+ if (ecf_getd ("shift") != 0.) {
+ call dtput (dt, "\tshift\t%g\n")
+ call pargd (ecf_getd ("shift"))
+ }
+ if (ecf_geti ("offset") != 0) {
+ call dtput (dt, "\toffset\t%d\n")
+ call pargi (ecf_geti ("offset"))
+ }
+ if (ecf_geti ("slope") != 1) {
+ call dtput (dt, "\tslope\t%d\n")
+ call pargi (ecf_geti ("slope"))
+ }
+
+ if (EC_ECF(ec) != NULL) {
+ call dtput (dt, "\tniterate %d\n")
+ call pargi (ecf_geti ("niterate"))
+ call dtput (dt, "\tlowreject %g\n")
+ call pargd (ecf_getd ("low"))
+ call dtput (dt, "\thighreject %g\n")
+ call pargd (ecf_getd ("high"))
+
+ ncoeffs = dgsgeti (EC_ECF(ec), GSNSAVE)
+ call salloc (coeffs, ncoeffs, TY_DOUBLE)
+ call dgssave (EC_ECF(ec), Memd[coeffs])
+ call dtput (dt, "\tcoefficients\t%d\n")
+ call pargi (ncoeffs)
+ do i = 1, ncoeffs {
+ call dtput (dt, "\t\t%g\n")
+ call pargd (Memd[coeffs+i-1])
+ }
+ }
+
+ call dtput (dt, "\n")
+ call dtunmap (dt)
+
+ EC_NEWFEATURES(ec) = NO
+ EC_NEWECF(ec) = NO
+ EC_NEWDBENTRY(ec) = NO
+
+ if (verbose == YES) {
+ call printf ("ecidentify %s\n")
+ call pargstr (Memc[cluster])
+ }
+
+ # Enter reference spectrum name in image header.
+ call imgcluster (Memc[EC_IMAGE(ec)], Memc[root], SZ_FNAME)
+ dt = immap (Memc[root], READ_WRITE, 0)
+ call imastr (dt, "REFSPEC1", Memc[cluster])
+ iferr (call imdelf (dt, "REFSPEC2"))
+ ;
+ call imunmap (dt)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecdelete.x b/noao/onedspec/ecidentify/ecdelete.x
new file mode 100644
index 00000000..b729d326
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecdelete.x
@@ -0,0 +1,28 @@
+include "ecidentify.h"
+
+# EC_DELETE -- Delete a feature.
+
+procedure ec_delete (ec, feature)
+
+pointer ec # ID pointer
+int feature # Feature to be deleted
+
+int i
+
+begin
+ if (feature == 0)
+ return
+
+ do i = feature + 1, EC_NFEATURES(ec) {
+ APN(ec,i-1) = APN(ec,i)
+ LINE(ec,i-1) = LINE(ec,i)
+ ORDER(ec,i-1) = ORDER(ec,i)
+ PIX(ec,i-1) = PIX(ec,i)
+ FIT(ec,i-1) = FIT(ec,i)
+ USER(ec,i-1) = USER(ec,i)
+ FWIDTH(ec,i-1) = FWIDTH(ec,i)
+ FTYPE(ec,i-1) = FTYPE(ec,i)
+ }
+ EC_NFEATURES(ec) = EC_NFEATURES(ec) - 1
+ EC_NEWFEATURES(ec) = YES
+end
diff --git a/noao/onedspec/ecidentify/ecdofit.x b/noao/onedspec/ecidentify/ecdofit.x
new file mode 100644
index 00000000..14dcea54
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecdofit.x
@@ -0,0 +1,128 @@
+include <smw.h>
+include "ecidentify.h"
+
+# EC_DOFIT -- Fit an echelle function to the features. Eliminate INDEF points.
+
+procedure ec_dofit (ec, interactive, fixedorder)
+
+pointer ec # EC pointer
+int interactive # Interactive fit?
+int fixedorder # Fixed order?
+
+int i, j, k, nfit
+double xmin, xmax, ymin, ymax
+pointer gt1, ecf
+pointer sp, x, y, z, w, gt_init()
+errchk ecf_fit
+
+begin
+ # Count number of points and determine the order range.
+ j = ORDER(ec,1)
+ k = ORDER(ec,1)
+ nfit = 0
+ for (i=1; i<=EC_NFEATURES(ec); i=i+1) {
+ if (IS_INDEFD (PIX(ec,i)) || IS_INDEFD (USER(ec,i)))
+ next
+ j = min (j, ORDER(ec,i))
+ k = max (k, ORDER(ec,i))
+ nfit = nfit + 1
+ }
+
+ # Require at least 4 points and more than one order.
+ if (nfit < 4 || j == k) {
+ if (EC_ECF(ec) != NULL) {
+ call dgs_free (EC_ECF(ec))
+ call ecf_setd ("shift", 0.D0)
+ EC_NEWGRAPH(ec) = YES
+ EC_NEWECF(ec) = YES
+ }
+ return
+ }
+
+ # Allocate arrays for points to be fit and fill them in.
+ call smark (sp)
+ call salloc (x, nfit, TY_DOUBLE)
+ call salloc (y, nfit, TY_DOUBLE)
+ call salloc (z, nfit, TY_DOUBLE)
+ call salloc (w, nfit, TY_DOUBLE)
+
+ nfit = 0
+ do i = 1, EC_NFEATURES(ec) {
+ if (IS_INDEFD (PIX(ec,i)) || IS_INDEFD (USER(ec,i)))
+ next
+ Memd[x+nfit] = PIX(ec,i)
+ Memd[y+nfit] = APN(ec,i)
+ Memd[z+nfit] = USER(ec,i)
+ Memd[w+nfit] = 1.
+ nfit = nfit + 1
+ }
+
+ # Initialize fit limits.
+ ymin = APS(ec,1)
+ ymax = ymin
+ do i = 2, EC_NLINES(ec) {
+ xmin = APS(ec,i)
+ if (xmin < ymin)
+ ymin = xmin
+ if (xmin > ymax)
+ ymax = xmin
+ }
+ xmin = 1
+ xmax = EC_NPTS(ec)
+
+ call ecf_setd ("xmin", xmin)
+ call ecf_setd ("xmax", xmax)
+ call ecf_setd ("ymin", ymin)
+ call ecf_setd ("ymax", ymax)
+
+ # Fit the echelle dispersion function.
+ ecf = EC_ECF(ec)
+ if (interactive == YES) {
+ gt1 = gt_init()
+ call ecf_fit (ecf, EC_GP(ec), gt1, Memd[x], Memd[y],
+ Memd[z], Memd[w], nfit, fixedorder)
+ call gt_free (gt1)
+ } else
+ call ecf_fit (ecf, NULL, NULL, Memd[x], Memd[y], Memd[z],
+ Memd[w], nfit, fixedorder)
+ EC_ECF(ec) = ecf
+
+ # Remove any deleted points.
+ j = 0
+ k = 0
+ do i = 1, EC_NFEATURES(ec) {
+ if (IS_INDEFD (PIX(ec,i)) || IS_INDEFD (USER(ec,i))) {
+ j = j + 1
+ APN(ec,j) = APN(ec,i)
+ LINE(ec,j) = LINE(ec,i)
+ ORDER(ec,j) = ORDER(ec,i)
+ PIX(ec,j) = PIX(ec,i)
+ FIT(ec,j) = FIT(ec,i)
+ USER(ec,j) = USER(ec,i)
+ FWIDTH(ec,j) = FWIDTH(ec,i)
+ FTYPE(ec,j) = abs (FTYPE(ec,i))
+ } else {
+ if (Memd[w+k] != 0.) {
+ j = j + 1
+ APN(ec,j) = APN(ec,i)
+ LINE(ec,j) = LINE(ec,i)
+ ORDER(ec,j) = ORDER(ec,i)
+ PIX(ec,j) = PIX(ec,i)
+ FIT(ec,j) = FIT(ec,i)
+ USER(ec,j) = USER(ec,i)
+ FWIDTH(ec,j) = FWIDTH(ec,i)
+ FTYPE(ec,j) = abs (FTYPE(ec,i))
+ if (Memd[w+k] < 0.)
+ FTYPE(ec,j) = -FTYPE(ec,j)
+ }
+ k = k + 1
+ }
+ }
+ EC_NFEATURES(ec) = j
+
+ # Set flags.
+ EC_NEWECF(ec) = YES
+ EC_NEWGRAPH(ec) = YES
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecdoshift.x b/noao/onedspec/ecidentify/ecdoshift.x
new file mode 100644
index 00000000..1689bc92
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecdoshift.x
@@ -0,0 +1,44 @@
+include "ecidentify.h"
+
+# EC_DOSHIFT -- Minimize residuals by constant shift.
+
+procedure ec_doshift (ec, interactive)
+
+pointer ec # ID pointer
+int interactive # Called interactively?
+
+int i, j
+double shft, delta, rms, ec_fitpt(), ecf_getd()
+
+begin
+ shft = 0.
+ rms = 0.
+ j = 0
+ for (i=1; i <= EC_NFEATURES(ec); i = i + 1) {
+ if (IS_INDEFD (USER(ec,i)))
+ next
+ delta = USER(ec,i) - ec_fitpt (ec, APN(ec,i), PIX(ec,i))
+ delta = delta * ORDER(ec,i)
+ shft = shft + delta
+ rms = rms + delta * delta
+ j = j + 1
+ }
+
+ if (j > 0) {
+ shft = shft / j
+ rms = rms / j
+ if (interactive == YES) {
+ i = EC_ORDER(ec)
+ call printf ("Coordinate shift=%5f, rms=%5f")
+ call pargd (shft / i)
+ if (j == 1)
+ call pargd (INDEFD)
+ else
+ call pargd (sqrt (rms - shft ** 2) / i)
+ }
+ shft = shft + ecf_getd ("shift")
+ call ecf_setd ("shift", shft)
+ EC_NEWECF(ec) = YES
+ EC_NEWGRAPH(ec) = YES
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfcolon.x b/noao/onedspec/ecidentify/ecffit/ecfcolon.x
new file mode 100644
index 00000000..4307335b
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfcolon.x
@@ -0,0 +1,102 @@
+include <error.h>
+include <gset.h>
+
+# List of colon commands
+define CMDS "|show|function|xorder|yorder|niterate|lowreject|highreject|"
+
+define SHOW 1 # Show parameters
+define FUNCTION 2 # Set or show function type
+define XORDER 3 # Set or show x order of function
+define YORDER 4 # Set or show y order of function
+define NITERATE 5 # Set or show rejection iterations
+define LOW 6 # Set or show low rejection threshold
+define HIGH 7 # Set or show high rejection threshold
+
+# ECF_COLON -- Processes colon commands.
+
+procedure ecf_colon (cmdstr, gp)
+
+char cmdstr[ARB] # Command string
+pointer gp # GIO pointer
+
+double dval
+int ncmd, ival
+int nscan(), strdic()
+include "ecffit.com"
+
+begin
+ # Use formated scan to parse the command string.
+ # The first word is the command and it may be minimum match
+ # abbreviated with the list of commands.
+
+ call sscan (cmdstr)
+ call gargwrd (ecfstr, SZ_LINE)
+ ncmd = strdic (ecfstr, ecfstr, SZ_LINE, CMDS)
+
+ switch (ncmd) {
+ case SHOW: # :show - Show the values of the fitting parameters.
+ call gdeactivate (gp, AW_CLEAR)
+ call printf ("function %s\nxorder %d\nyorder %d\n")
+ call pargstr (function)
+ call pargi (xorder)
+ call pargi (yorder)
+ call printf ("niterate %d\nlowreject %g\nhighreject\nnreject %d\n")
+ call pargi (niterate)
+ call pargd (low)
+ call pargd (high)
+ call pargi (nreject)
+ call printf ("slope %d\noffset %d\nshift %g\n")
+ call pargi (slope)
+ call pargi (offset)
+ call pargd (shift)
+ call printf ("rms %g\n")
+ call pargd (rms)
+ call greactivate (gp, AW_PAUSE)
+ case FUNCTION: # :function - List or set the fitting function.
+ call gargwrd (ecfstr, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("function = %s\n")
+ call pargstr (function)
+ } else {
+ iferr (call ecf_sets ("function", ecfstr))
+ call erract (EA_WARN)
+ }
+ case XORDER: # xorder: List or set the function order.
+ call gargi (ival)
+ if (nscan() == 1) {
+ call printf ("xorder %d\n")
+ call pargi (xorder)
+ } else
+ xorder = ival
+ case YORDER: # yorder: List or set the function order.
+ call gargi (ival)
+ if (nscan() == 1) {
+ call printf ("yorder %d\n")
+ call pargi (yorder)
+ } else
+ yorder = ival
+ case NITERATE: # niterate: List or set rejection iterations.
+ call gargi (ival)
+ if (nscan() == 1) {
+ call printf ("niterate %d\n")
+ call pargi (niterate)
+ } else
+ niterate = ival
+ case LOW: # low: List or set low rejection threshold.
+ call gargd (dval)
+ if (nscan() == 1) {
+ call printf ("lowreject %g\n")
+ call pargd (low)
+ } else
+ low = dval
+ case HIGH: # highreject: List or set high rejection threshold.
+ call gargd (dval)
+ if (nscan() == 1) {
+ call printf ("highreject %g\n")
+ call pargd (high)
+ } else
+ high = dval
+ default:
+ call printf ("Unrecognized or ambiguous command\007")
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfeval.x b/noao/onedspec/ecidentify/ecffit/ecfeval.x
new file mode 100644
index 00000000..1901522f
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfeval.x
@@ -0,0 +1,68 @@
+# ECF_EVAL -- Evaluate wavelength at a given order and pixel position.
+
+double procedure ecf_eval (ecf, order, x)
+
+pointer ecf # GSURFIT pointer
+int order # Order
+double x # X point
+
+int ecf_oeval()
+double y, dgseval()
+include "ecffit.com"
+
+begin
+ y = ecf_oeval (ecf, order)
+ if (ecf == NULL)
+ return (x + shift / y)
+ else
+ return ((dgseval (ecf, x, y) + shift) / y)
+end
+
+
+# ECF_VECTOR -- Evaluate echelle dispersion function for a vector of points of
+# the same order.
+
+procedure ecf_vector (ecf, order, x, fit, npts)
+
+pointer ecf # GSURFIT pointer
+int order # Order
+double x[npts] # X points
+double fit[npts] # Fitted points
+int npts # Number of points
+
+double yval
+pointer sp, y
+int ecf_oeval()
+include "ecffit.com"
+
+begin
+ call smark (sp)
+ call salloc (y, npts, TY_DOUBLE)
+
+ yval = ecf_oeval (ecf, order)
+ if (ecf == NULL)
+ call amovd (x, fit, npts)
+ else {
+ call amovkd (yval, Memd[y], npts)
+ call dgsvector (ecf, x, Memd[y], fit, npts)
+ call adivkd (fit, yval, fit, npts)
+ }
+ if (shift != 0.)
+ call aaddkd (fit, shift / yval, fit, npts)
+
+ call sfree (sp)
+end
+
+
+# ECF_OEVAL -- Evaluate the fit order.
+
+int procedure ecf_oeval (ecf, order)
+
+pointer ecf # GSURFIT pointer
+int order # User order
+
+include "ecffit.com"
+
+begin
+ return (slope * order + offset)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecffit.com b/noao/onedspec/ecidentify/ecffit/ecffit.com
new file mode 100644
index 00000000..61f3104a
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecffit.com
@@ -0,0 +1,23 @@
+# Common parameters.
+
+char function[SZ_FNAME] # Fitting function
+char ecfstr[SZ_LINE] # Working char string
+int gstype # Surface function type
+int xorder # X order of surface function
+int yorder # Y order of surface function
+int niterate # Number of rejection iterations
+int nreject # Number of rejected points
+int xtype # X axis type
+int ytype # Y axis type
+int slope # Slope of order
+int offset # Order offset of fit
+double low, high # Low and high rejection thresholds
+double xmin, xmax # X range
+double ymin, ymax # Y range
+double shift # First order shift
+double rms # RMS of fit
+
+
+common /ecfcom/ low, high, xmin, xmax, ymin, ymax, shift, rms, gstype,
+ xorder, yorder, niterate, nreject, xtype, ytype, slope, offset,
+ function, ecfstr
diff --git a/noao/onedspec/ecidentify/ecffit/ecffit.h b/noao/onedspec/ecidentify/ecffit/ecffit.h
new file mode 100644
index 00000000..20825c71
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecffit.h
@@ -0,0 +1,20 @@
+define IGSPARAMS 7
+
+define FEATURE 1
+define X 2
+define Y 3
+define Z 4
+define W 5
+define S 6
+define R 7
+
+define IGS_FUNCTION 1
+define IGS_XORDER 2
+define IGS_YORDER 3
+define IGS_XMIN 4
+define IGS_XMAX 5
+define IGS_YMIN 6
+define IGS_YMAX 7
+define IGS_OFFSET 8
+
+define SFTYPES "|chebyshev|legendre|" # Surface types
diff --git a/noao/onedspec/ecidentify/ecffit/ecffit.key b/noao/onedspec/ecidentify/ecffit/ecffit.key
new file mode 100644
index 00000000..f24407b9
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecffit.key
@@ -0,0 +1,53 @@
+ ECHELLE DISPERSION FUNCTION FITTING KEYS
+
+
+CURSOR KEY SUMMARY
+
+? Help c Print coordinates d Delete point
+f Fit dispersion o Fit with fixed order offset q Quit
+r Redraw graph u Undelete point w Window graph
+x Set ordinate y Set abscissa I Interrupt
+
+
+COLON COMMAND SUMMARY
+
+:show :function [value] :highreject [value] :lowreject [value]
+:niterate [value] :xorder [value] :yorder [value]
+
+
+CURSOR KEYS
+
+? Print this list of cursor keys
+c Print cursor coordinates
+d Delete the nearest undeleted point to the cursor
+f Fit dispersion function including determining the order offset
+o Fit dispersion function with the order offset fixed
+q Quit and return to the spectrum display
+r Redraw the graph
+u Undelete the nearest deleted point to the cursor (may be outside the window)
+w Window the graph (type ? to the window prompt for more help)
+x Set the quantity plotted along the ordinate (x axis)
+y Set the quantity plotted along the abscissa (y axis)
+I Interrupt the task immediately
+
+
+COLON COMMANDS
+
+:show Print current function and orders
+:function [value] Print or set the function type (chebyshev|legendre)
+:highreject [value] Print or set high rejection limit
+:lowreject [value] Print or set high rejection limit
+:niterate [value] Print or set number of rejection iterations
+:xorder [value] Print or set the order for the dispersion dependence
+:yorder [value] Print or set the order for the echelle order dependence
+
+
+The dispersion function fitted is given by a two dimensional function
+(either chebyshev or legendre) of the pixel position along the
+dispersion of an order (called x) and the order number (called y). The
+order number is determined from the aperture number by an offset and
+direction of increasing order number. The basic order dependence is
+separated from the surface function as given below.
+
+ y = offset +/- aperture
+ wavelength = f (x, y) / y
diff --git a/noao/onedspec/ecidentify/ecffit/ecffit.x b/noao/onedspec/ecidentify/ecffit/ecffit.x
new file mode 100644
index 00000000..408a1b77
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecffit.x
@@ -0,0 +1,193 @@
+include <error.h>
+include <pkg/gtools.h>
+
+define HELP "noao$onedspec/ecidentify/ecffit/ecffit.key"
+define PROMPT "fitcoords surface fitting options"
+
+# EC_FIT -- Echelle dispersion fitting.
+#
+# X - Pixel coordinates along dispersion
+# Y - Relative order number
+# Z - Wavelength
+
+procedure ecf_fit (ecf, gp, gt, xd, yd, zd, wd, npts, fixedorder)
+
+pointer ecf # GSURFIT pointer
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+double xd[npts] # Pixel coordinates along dispersion
+double yd[npts] # Order number
+double zd[npts] # Wavelength
+double wd[npts] # Weights
+int npts # Number of points
+int fixedorder # Fixed order?
+
+real wx, wy
+int wcs, key
+int i, newgraph
+pointer sp, wd1, rd, xr, yr
+char cmd[SZ_LINE]
+
+int ecf_nearest()
+int clgcur(), scan(), nscan()
+errchk ecf_solve()
+include "ecffit.com"
+
+begin
+ # Allocate residuals and weights with rejected points arrays
+ call smark (sp)
+ call salloc (wd1, npts, TY_DOUBLE)
+ call salloc (rd, npts, TY_DOUBLE)
+ call amovd (wd, Memd[wd1], npts)
+
+ # Compute a solution and return if not interactive.
+ if (gp == NULL) {
+ call ecf_solve (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ fixedorder)
+ call ecf_reject (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ fixedorder)
+ do i = 1, npts
+ if (Memd[wd1+i-1] != wd[i])
+ wd[i] = -1.
+ call sfree (sp)
+ return
+ }
+
+ # Allocate real graph vectors.
+ call salloc (xr, npts, TY_REAL)
+ call salloc (yr, npts, TY_REAL)
+
+ # Read cursor commands.
+ key = 'f'
+ repeat {
+ switch (key) {
+ case 'o':
+ call printf ("Order offset (%d): ")
+ call pargi (offset)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargi (i)
+ if (nscan() == 1)
+ offset = i
+ call amovd (wd, Memd[wd1], npts)
+ call ecf_solve (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ YES)
+ call ecf_reject (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ YES)
+ call ecf_gdata (ecf, xtype, xd, yd, zd, Memd[rd],
+ Memr[xr], npts)
+ call ecf_gdata (ecf, ytype, xd, yd, zd, Memd[rd],
+ Memr[yr], npts)
+ call ecf_title (gt)
+ newgraph = YES
+ }
+
+ case '?': # Print help text.
+ call gpagefile (gp, HELP, PROMPT)
+
+ case ':': # List or set parameters
+ if (cmd[1] == '/')
+ call gt_colon (cmd, gp, gt, newgraph)
+ else
+ call ecf_colon (cmd, gp)
+
+ case 'x': # Set ordinate
+ call printf ("Ordinate - ")
+ call printf (
+ "(p)ixel, (o)rder, (w)avelength, (r)esidual, (v)elocity: ")
+ if (clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+ break
+
+ if (key != xtype) {
+ if (key=='p'||key=='o'||key=='w'||key=='r'||key=='v') {
+ xtype = key
+ call gt_setr (gt, GTXMIN, INDEF)
+ call gt_setr (gt, GTXMAX, INDEF)
+ call ecf_gdata (ecf, xtype, xd, yd, zd, Memd[rd],
+ Memr[xr], npts)
+ call ecf_title (gt)
+ newgraph = YES
+ } else
+ call printf ("\007")
+ }
+
+ case 'y': # Set abscissa
+ call printf ("Abscissa - ")
+ call printf (
+ "(p)ixel, (o)rder, (w)avelength, (r)esidual, (v)elocity: ")
+ if(clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+ break
+
+ if (key != ytype) {
+ if (key=='p'||key=='o'||key=='w'||key=='r'||key=='v') {
+ ytype = key
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ call ecf_gdata (ecf, ytype, xd, yd, zd, Memd[rd],
+ Memr[yr], npts)
+ call ecf_title (gt)
+ newgraph = YES
+ } else
+ call printf ("\007")
+ }
+
+ case 'r': # Redraw
+ newgraph = YES
+
+ case 'c': # Cursor coordinates
+ i = ecf_nearest (gp, gt, wx, wy, wcs, key, Memr[xr], Memr[yr],
+ wd, npts)
+ call printf ("%10.2g %d %10.8g\n")
+ call pargd (xd[i])
+ call pargd (yd[i])
+ call pargd (zd[i])
+
+ case 'd': # Delete
+ i = ecf_nearest (gp, gt, wx, wy, wcs, key, Memr[xr], Memr[yr],
+ wd, npts)
+ if (i > 0)
+ Memd[wd1+i-1] = wd[i]
+
+ case 'u': # Undelete
+ i = ecf_nearest (gp, gt, wx, wy, wcs, key, Memr[xr], Memr[yr],
+ wd, npts)
+ if (i > 0)
+ Memd[wd1+i-1] = wd[i]
+
+ case 'f': # Fit
+ call amovd (wd, Memd[wd1], npts)
+ call ecf_solve (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ fixedorder)
+ call ecf_reject (ecf, xd, yd, zd, Memd[wd1], Memd[rd], npts,
+ fixedorder)
+ call ecf_gdata (ecf, xtype, xd, yd, zd, Memd[rd],
+ Memr[xr], npts)
+ call ecf_gdata (ecf, ytype, xd, yd, zd, Memd[rd],
+ Memr[yr], npts)
+ call ecf_title (gt)
+ newgraph = YES
+
+ case 'w': # Window graph
+ call gt_window (gt, gp, "cursor", newgraph)
+
+ case 'q': # Quit
+ break
+
+ case 'I': # Interrupt
+ call fatal (0, "Interrupt")
+
+ default: # Ring the bell.
+ call printf ("\07\n")
+ }
+
+ if (newgraph == YES) {
+ call ecf_graph (gp, gt, Memr[xr], Memr[yr], wd, Memd[wd1], npts)
+ newgraph = NO
+ }
+ } until (clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+
+ do i = 1, npts
+ if (Memd[wd1+i-1] != wd[i])
+ wd[i] = -1.
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfgdata.x b/noao/onedspec/ecidentify/ecffit/ecfgdata.x
new file mode 100644
index 00000000..eebb34d6
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfgdata.x
@@ -0,0 +1,37 @@
+include <pkg/gtools.h>
+
+# ECF_GDATA -- Get graph data for the specified axis type from the fitting data.
+
+procedure ecf_gdata (ecf, type, x, y, z, r, data, npts)
+
+pointer ecf # GSURFIT pointer
+int type # Axis type
+double x[npts] # X fit data
+double y[npts] # Y fit data
+double z[npts] # Z fit data
+double r[npts] # Residuals
+real data[npts] # Graph data
+int npts # Number of points
+
+pointer sp, v
+include "ecffit.com"
+
+begin
+ switch (type) {
+ case 'p':
+ call achtdr (x, data, npts)
+ case 'o':
+ call achtdr (y, data, npts)
+ case 'w':
+ call achtdr (z, data, npts)
+ case 'r':
+ call achtdr (r, data, npts)
+ case 'v':
+ call smark (sp)
+ call salloc (v, npts, TY_DOUBLE)
+ call adivd (r, z, Memd[v], npts)
+ call amulkd (Memd[v], 300000.D0, Memd[v], npts)
+ call achtdr (Memd[v], data, npts)
+ call sfree (sp)
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfget.x b/noao/onedspec/ecidentify/ecffit/ecfget.x
new file mode 100644
index 00000000..025059df
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfget.x
@@ -0,0 +1,84 @@
+# ECF_GETI -- Get the value of an integer parameter.
+
+int procedure ecf_geti (param)
+
+char param[ARB] # ECF parameter
+
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, ecfstr, SZ_LINE,
+ "|slope|offset|xorder|yorder|niterate|")
+ switch (i) {
+ case 1:
+ return (slope)
+ case 2:
+ return (offset)
+ case 3:
+ return (xorder)
+ case 4:
+ return (yorder)
+ case 5:
+ return (niterate)
+ default:
+ call error (0, "ecf_geti: Unknown parameter")
+ }
+end
+
+
+# ECF_GETS -- Get the value of a string parameter.
+
+procedure ecf_gets (param, str, maxchar)
+
+char param[ARB] # ECF parameter
+char str[maxchar] # String
+int maxchar # Maximum number of characters
+
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, ecfstr, SZ_LINE, "|function|")
+ switch (i) {
+ case 1:
+ call strcpy (function, str, maxchar)
+ default:
+ call error (0, "ecf_gets: Unknown parameter")
+ }
+end
+
+
+# ECF_GETD -- Get the values of double valued fitting parameters.
+
+double procedure ecf_getd (param)
+
+char param[ARB] # ECF parameter
+
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, ecfstr, SZ_LINE,
+ "|xmin|xmax|ymin|ymax|shift|rms|low|high|")
+ switch (i) {
+ case 1:
+ return (xmin)
+ case 2:
+ return (xmax)
+ case 3:
+ return (ymin)
+ case 4:
+ return (ymax)
+ case 5:
+ return (shift)
+ case 6:
+ return (rms)
+ case 7:
+ return (low)
+ case 8:
+ return (high)
+ default:
+ call error (0, "ecf_gets: Unknown parameter")
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfgraph.x b/noao/onedspec/ecidentify/ecffit/ecfgraph.x
new file mode 100644
index 00000000..22749527
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfgraph.x
@@ -0,0 +1,50 @@
+include <gset.h>
+include <mach.h>
+include <pkg/gtools.h>
+
+# ECF_GRAPH -- Graph the fitted data.
+
+procedure ecf_graph (gp, gt, x, y, w, rej, npts)
+
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+real x[npts] # X data
+real y[npts] # Y data
+double w[npts] # Weights
+double rej[npts] # Rejected points
+int npts # Number of pts points
+
+int i
+real xsize, ysize, ymin, ymax, gt_getr()
+
+begin
+ xsize = gt_getr (gt, GTXSIZE)
+ ysize = gt_getr (gt, GTYSIZE)
+
+ call gclear (gp)
+
+ ymin = MAX_REAL
+ ymax = -MAX_REAL
+ do i = 1, npts
+ if (w[i] > 0.) {
+ ymin = min (ymin, y[i])
+ ymax = max (ymax, y[i])
+ }
+
+ call gascale (gp, x, npts, 1)
+ call gswind (gp, INDEF, INDEF, ymin, ymax)
+ call gt_swind (gp, gt)
+ call gt_labax (gp, gt)
+
+ do i = 1, npts {
+ if (rej[i] == 0.) {
+ if (y[i] >= ymin && y[i] <= ymax) {
+ if (w[i] == 0.)
+ call gmark (gp, x[i], y[i], GM_CROSS, xsize, ysize)
+ else
+ call gmark (gp, x[i], y[i], GM_DIAMOND, xsize, ysize)
+ }
+ } else
+ call gmark (gp, x[i], y[i], GM_PLUS, xsize, ysize)
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfnearest.x b/noao/onedspec/ecidentify/ecffit/ecfnearest.x
new file mode 100644
index 00000000..af1b1f78
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfnearest.x
@@ -0,0 +1,85 @@
+include <mach.h>
+include <gset.h>
+include <pkg/gtools.h>
+
+# ECF_NEAREST -- Find nearest point to the cursor.
+
+int procedure ecf_nearest (gp, gt, wx, wy, wcs, key, x, y, w, npts)
+
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+real wx, wy # Cursor coordinates
+int wcs # WCS
+int key # Nearest key
+real x[npts] # Data points
+real y[npts] # Data points
+double w[npts] # Weight
+int npts # Number of data points
+
+int i, j
+real r2, r2min, x0, y0, xsize, ysize, gt_getr()
+
+begin
+ call gctran (gp, wx, wy, wx, wy, wcs, 0)
+ r2min = MAX_REAL
+ j = 0
+
+ switch (key) {
+ case 'c':
+ do i = 1, npts {
+ call gctran (gp, x[i], y[i], x0, y0, wcs, 0)
+ r2 = (x0 - wx) ** 2 + (y0 - wy) ** 2
+ if (r2 < r2min) {
+ r2min = r2
+ j = i
+ }
+ }
+ call gscur (gp, x[j], y[j])
+ case 'd':
+ do i = 1, npts {
+ if (w[i] == 0.)
+ next
+ call gctran (gp, x[i], y[i], x0, y0, wcs, 0)
+ r2 = (x0 - wx) ** 2 + (y0 - wy) ** 2
+ if (r2 < r2min) {
+ r2min = r2
+ j = i
+ }
+ }
+ if (j > 0) {
+ xsize = gt_getr (gt, GTXSIZE)
+ ysize = gt_getr (gt, GTYSIZE)
+
+ call gseti (gp, G_PMLTYPE, 0)
+ call gmark (gp, x[j], y[j], GM_PLUS, xsize, ysize)
+ call gseti (gp, G_PMLTYPE, 1)
+ call gmark (gp, x[j], y[j], GM_CROSS, xsize, ysize)
+ w[j] = 0.
+ call gscur (gp, x[j], y[j])
+ }
+ case 'u':
+ do i = 1, npts {
+ if (w[i] != 0.)
+ next
+ call gctran (gp, x[i], y[i], x0, y0, wcs, 0)
+ r2 = (x0 - wx) ** 2 + (y0 - wy) ** 2
+ if (r2 < r2min) {
+ r2min = r2
+ j = i
+ }
+ }
+ if (j > 0) {
+ xsize = gt_getr (gt, GTXSIZE)
+ ysize = gt_getr (gt, GTYSIZE)
+
+ call gseti (gp, G_PMLTYPE, 0)
+ call gmark (gp, x[j], y[j], GM_CROSS, xsize, ysize)
+ call gseti (gp, G_PMLTYPE, 1)
+ call gmark (gp, x[j], y[j], GM_PLUS, xsize, ysize)
+ w[j] = 1.
+ call gscur (gp, x[j], y[j])
+ }
+ }
+
+ return (j)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfreject.x b/noao/onedspec/ecidentify/ecffit/ecfreject.x
new file mode 100644
index 00000000..a772069e
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfreject.x
@@ -0,0 +1,53 @@
+include <mach.h>
+
+# ECF_REJECT -- Reject points with large residuals from the fit.
+
+procedure ecf_reject (ecf, x, y, z, w, r, npts, fixedorder)
+
+pointer ecf # GSURFIT pointer
+double x[npts] # X points
+double y[npts] # Y points
+double z[npts] # Z points
+double w[npts] # Weights
+double r[npts] # Residuals
+int npts # Number of points
+int fixedorder # Fixed order?
+
+int i, j, newreject
+double low_cut, high_cut
+include "ecffit.com"
+
+begin
+ # Return if rejection is not desired.
+ nreject = 0
+ if (niterate == 0 || (low == 0. && high == 0.))
+ return
+
+ # Reject points.
+ do i = 1, niterate {
+ if (low > 0.)
+ low_cut = -low * rms
+ else
+ low_cut = -MAX_REAL
+ if (high > 0.)
+ high_cut = high * rms
+ else
+ high_cut = MAX_REAL
+
+ newreject = 0
+ do j = 1, npts {
+ if (w[j] == 0.)
+ next
+ if ((r[j] > high_cut) || (r[j] < low_cut)) {
+ w[j] = 0.
+ newreject = newreject + 1
+ }
+ }
+
+ if (newreject == 0)
+ break
+
+ call ecf_solve (ecf, x, y, z, w, r, npts, fixedorder)
+ nreject = nreject + newreject
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfrms.x b/noao/onedspec/ecidentify/ecffit/ecfrms.x
new file mode 100644
index 00000000..1140dc29
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfrms.x
@@ -0,0 +1,26 @@
+# ECF_RMS -- Compute the rms with deleted points ignored.
+
+double procedure ecf_rms (r, w, npts)
+
+double r[npts] # Residuals
+double w[npts] # Weights
+int npts # Number of points
+
+int i, n
+double rms
+
+begin
+ n = 0
+ rms = 0.
+ do i = 1, npts {
+ if (w[i] == 0.)
+ next
+ n = n + 1
+ rms = rms + r[i] * r[i]
+ }
+ if (n > 0)
+ rms = sqrt (rms / n)
+ else
+ rms = INDEFD
+ return (rms)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfset.x b/noao/onedspec/ecidentify/ecffit/ecfset.x
new file mode 100644
index 00000000..4b6402b1
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfset.x
@@ -0,0 +1,92 @@
+# ECF_SETS -- Set the values of string valued fitting parameters.
+
+procedure ecf_sets (param, str)
+
+char param[ARB] # Parameter to be set
+char str[ARB] # String value
+
+char temp[10]
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, temp, 10, "|function|")
+ switch (i) {
+ case 1:
+ i = strdic (str, str, SZ_FNAME, "|chebyshev|legendre|")
+ if (i == 0)
+ call error (0, "Unknown function type")
+ call strcpy (str, function, SZ_LINE)
+ gstype = i
+ default:
+ call error (0, "ecf_sets: Unknown parameter")
+ }
+end
+
+
+# ECF_SETI -- Set the values of integer valued fitting parameters.
+
+procedure ecf_seti (param, ival)
+
+char param[ARB] # Parameter to be set
+int ival # Integer value
+
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, ecfstr, SZ_LINE,
+ "|slope|offset|xorder|yorder|xtype|ytype|niterate|")
+ switch (i) {
+ case 1:
+ slope = ival
+ case 2:
+ offset = ival
+ case 3:
+ xorder = ival
+ case 4:
+ yorder = ival
+ case 5:
+ xtype = ival
+ case 6:
+ ytype = ival
+ case 7:
+ niterate = max (0, ival)
+ default:
+ call error (0, "ecf_seti: Unknown parameter")
+ }
+end
+
+
+# ECF_SETD -- Set the values of double valued fitting parameters.
+
+procedure ecf_setd (param, dval)
+
+char param[ARB] # Parameter to be set
+double dval # Double value
+
+int i, strdic()
+include "ecffit.com"
+
+begin
+ i = strdic (param, ecfstr, SZ_LINE,
+ "|xmin|xmax|ymin|ymax|shift|low|high|")
+ switch (i) {
+ case 1:
+ xmin = dval
+ case 2:
+ xmax = dval
+ case 3:
+ ymin = dval
+ case 4:
+ ymax = dval
+ case 5:
+ shift = dval
+ case 6:
+ low = max (0.D0, dval)
+ case 7:
+ high = max (0.D0, dval)
+ default:
+ call error (0, "ecf_setd: Unknown parameter")
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfshift.x b/noao/onedspec/ecidentify/ecffit/ecfshift.x
new file mode 100644
index 00000000..75655703
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfshift.x
@@ -0,0 +1,55 @@
+# ECF_GSHIFT -- Return the shift for the given order.
+
+double procedure ecf_gshift (ecf, order)
+
+pointer ecf # GSURFIT pointer
+int order # User order
+
+include "ecffit.com"
+
+begin
+ return (shift / (slope * order + offset))
+end
+
+
+# ECF_PSHIFT -- Put the shift for the given order.
+
+procedure ecf_pshift (ecf, order, shft)
+
+pointer ecf # GSURFIT pointer
+int order # User order
+double shft # Shift at given order
+
+include "ecffit.com"
+
+begin
+ shift = shft * (slope * order + offset)
+end
+
+
+procedure ecf_vector (ecf, order, x, fit, npts)
+
+pointer ecf # GSURFIT pointer
+int order # Order
+double x[npts] # X points
+double fit[npts] # Fitted points
+int npts # Number of points
+
+double yval
+pointer sp, y
+
+include "ecffit.com"
+
+begin
+ call smark (sp)
+ call salloc (y, npts, TY_DOUBLE)
+
+ yval = slope * order + offset
+ call amovkd (yval, Memd[y], npts)
+ call dgsvector (ecf, x, Memd[y], fit, npts)
+ call adivkd (fit, yval, fit, npts)
+ if (shift != 0.)
+ call aaddkd (fit, shift / yval, fit, npts)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecfsolve.x b/noao/onedspec/ecidentify/ecffit/ecfsolve.x
new file mode 100644
index 00000000..1c844e76
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecfsolve.x
@@ -0,0 +1,196 @@
+include <mach.h>
+include <math/gsurfit.h>
+
+define ECFTYPES "|chebyshev|legendre|" # Fit types
+
+
+# ECF_SOLVE -- Fit
+#
+# f(x, slope*y+offset) = (y+slope*offset)*z
+#
+# with offset minimizing the RMS.
+
+procedure ecf_solve (ecf, x, y, z, w, r, npts, fixedorder)
+
+pointer ecf # GSURFIT pointer
+double x[npts] # X points
+double y[npts] # Y points
+double z[npts] # Z points
+double w[npts] # Weights
+double r[npts] # Residuals
+int npts # Number of points
+int fixedorder # Fixed order?
+
+int i, j, k, err
+double ya, yb, newrms, ecf_rms()
+pointer sp, y1, ecf1
+errchk ecf_solve1
+include "ecffit.com"
+define fit_ 99
+
+begin
+ if (fixedorder == YES) {
+ call ecf_solve1 (ecf, x, y, z, w, r, npts)
+ return
+ }
+
+ call smark (sp)
+ call salloc (y1, npts, TY_DOUBLE)
+
+ # Determine if the orders are reversed.
+ j = 1
+ k = 1
+ do i = 1, npts {
+ if (z[i] < z[j])
+ j = i
+ if (z[i] > z[k])
+ k = i
+ }
+ if (y[j] >= y[k]) {
+ slope = 1
+ offset = max (offset, int(1. - ymin))
+ } else {
+ slope = -1
+ offset = max (offset, int(1. + ymax))
+ }
+
+ call dgsfree (ecf)
+ shift = 0.
+
+ rms = MAX_DOUBLE
+ j = 1
+ k = 0
+
+ for (i=offset;;i=i+j) {
+ if (slope == 1) {
+ ya = i + ymin
+ yb = i + ymax
+ } else {
+ ya = i - ymax
+ yb = i - ymin
+ }
+ if (ya < 1.)
+ break
+
+ call altmd (y, Memd[y1], npts, double(slope), double(i))
+ call amuld (Memd[y1], z, r, npts)
+
+fit_ call dgsinit (ecf1, gstype, xorder, yorder, YES, xmin, xmax, ya, yb)
+ call dgsfit (ecf1, x, Memd[y1], r, w, npts, WTS_USER, err)
+
+ if (err != OK) {
+ if (xorder > 2 || yorder > 2) {
+ call dgsfree (ecf)
+ xorder = max (2, xorder - 1)
+ yorder = max (2, yorder - 1)
+ goto fit_
+ }
+
+ switch (err) {
+ case SINGULAR:
+ call dgsfree (ecf)
+ ecf = ecf1
+ call eprintf ("Singular solution\n")
+ case NO_DEG_FREEDOM:
+ call sfree (sp)
+ call error (0, "No degrees of freedom")
+ }
+ }
+
+ call dgsvector (ecf1, x, Memd[y1], r, npts)
+ call adivd (r, Memd[y1], r, npts)
+ call asubd (z, r, r, npts)
+
+ newrms = ecf_rms (r, w, npts)
+ k = k + 1
+
+ if (newrms / rms < 0.999) {
+ call dgsfree (ecf)
+ ecf = ecf1
+ offset = i
+ rms = newrms
+ } else {
+ call dgsfree (ecf1)
+ if (k > 2)
+ break
+ i = offset
+ j = -j
+ }
+ }
+
+ call altmd (y, Memd[y1], npts, double(slope), double(offset))
+ call dgsvector (ecf, x, Memd[y1], r, npts)
+ call adivd (r, Memd[y1], r, npts)
+ call asubd (z, r, r, npts)
+
+ call sfree (sp)
+
+end
+
+
+# ECF_SOLVE1 -- Fit f(x, y+offset) = (y+offset)*z with offset fixed.
+
+procedure ecf_solve1 (ecf, x, y, z, w, r, npts)
+
+pointer ecf # GSURFIT pointer
+double x[npts] # X points
+double y[npts] # Y points
+double z[npts] # Z points
+double w[npts] # Weights
+double r[npts] # Residuals
+int npts # Number of points
+
+int err
+pointer sp, y1
+double ya, yb, ecf_rms()
+include "ecffit.com"
+define fit_ 99
+
+begin
+ call smark (sp)
+ call salloc (y1, npts, TY_DOUBLE)
+
+ call dgsfree (ecf)
+ shift = 0.
+
+ if (slope == 1) {
+ offset = max (offset, int(1. - ymin))
+ ya = offset + ymin
+ yb = offset + ymax
+ } else {
+ offset = max (offset, int(1. + ymax))
+ ya = offset - ymax
+ yb = offset - ymin
+ }
+
+ call altmd (y, Memd[y1], npts, double (slope), double (offset))
+ call amuld (Memd[y1], z, r, npts)
+
+fit_ call dgsinit (ecf, gstype, xorder, yorder, YES, xmin, xmax,
+ min (ya, yb), max (ya, yb))
+ call dgsfit (ecf, x, Memd[y1], r, w, npts, WTS_USER, err)
+
+ if (err != OK) {
+ if (xorder > 2 || yorder > 2) {
+ call dgsfree (ecf)
+ xorder = max (2, xorder - 1)
+ yorder = max (2, yorder - 1)
+ goto fit_
+ }
+
+ switch (err) {
+ case SINGULAR:
+ call eprintf ("Singular solution\n")
+ case NO_DEG_FREEDOM:
+ call sfree (sp)
+ call error (0, "No degrees of freedom")
+ }
+ }
+
+ call dgsvector (ecf, x, Memd[y1], r, npts)
+ call adivd (r, Memd[y1], r, npts)
+ call asubd (z, r, r, npts)
+ rms = ecf_rms (r, w, npts)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecffit/ecftitle.x b/noao/onedspec/ecidentify/ecffit/ecftitle.x
new file mode 100644
index 00000000..3b754f31
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/ecftitle.x
@@ -0,0 +1,48 @@
+include <pkg/gtools.h>
+
+# ECF_TITLE -- Set the GTOOLS parameter string.
+
+procedure ecf_title (gt)
+
+pointer gt # GTOOLS pointer
+
+include "ecffit.com"
+
+begin
+ call sprintf (ecfstr, SZ_LINE,
+ "Function=%s, xorder=%d, yorder=%d, slope=%d, offset=%d, rms=%6g")
+ call pargstr (function)
+ call pargi (xorder)
+ call pargi (yorder)
+ call pargi (slope)
+ call pargi (offset)
+ call pargd (rms)
+ call gt_sets (gt, GTPARAMS, ecfstr)
+ call gt_sets (gt, GTTITLE, "Echelle Dispersion Function Fitting")
+
+ switch (xtype) {
+ case 'p':
+ call gt_sets (gt, GTXLABEL, "Pixel")
+ case 'o':
+ call gt_sets (gt, GTXLABEL, "Order")
+ case 'w':
+ call gt_sets (gt, GTXLABEL, "Wavelength")
+ case 'r':
+ call gt_sets (gt, GTXLABEL, "Residual")
+ case 'v':
+ call gt_sets (gt, GTXLABEL, "Velocity")
+ }
+
+ switch (ytype) {
+ case 'p':
+ call gt_sets (gt, GTYLABEL, "Pixel")
+ case 'o':
+ call gt_sets (gt, GTYLABEL, "Order")
+ case 'w':
+ call gt_sets (gt, GTYLABEL, "Wavelength")
+ case 'r':
+ call gt_sets (gt, GTYLABEL, "Residual")
+ case 'v':
+ call gt_sets (gt, GTYLABEL, "Velocity")
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecffit/mkpkg b/noao/onedspec/ecidentify/ecffit/mkpkg
new file mode 100644
index 00000000..40324cb8
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecffit/mkpkg
@@ -0,0 +1,21 @@
+# Echelle Dispersion Fitting Package
+
+$checkout libpkg.a ../../
+$update libpkg.a
+$checkin libpkg.a ../../
+$exit
+
+libpkg.a:
+ ecfcolon.x ecffit.com <error.h> <gset.h>
+ ecfeval.x ecffit.com
+ ecffit.x ecffit.com <error.h> <pkg/gtools.h>
+ ecfgdata.x ecffit.com <pkg/gtools.h>
+ ecfget.x ecffit.com
+ ecfgraph.x <gset.h> <mach.h> <pkg/gtools.h>
+ ecfnearest.x <gset.h> <mach.h> <pkg/gtools.h>
+ ecfreject.x ecffit.com <mach.h>
+ ecfrms.x
+ ecfset.x ecffit.com
+ ecfsolve.x ecffit.com <mach.h> <math/gsurfit.h>
+ ecftitle.x ecffit.com <pkg/gtools.h>
+ ;
diff --git a/noao/onedspec/ecidentify/ecfitdata.x b/noao/onedspec/ecidentify/ecfitdata.x
new file mode 100644
index 00000000..998f5057
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecfitdata.x
@@ -0,0 +1,146 @@
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include "ecidentify.h"
+
+# EC_FITDATA -- Compute fit coordinates from pixel coordinates.
+
+procedure ec_fitdata (ec)
+
+pointer ec # ID pointer
+
+int i, ecf_oeval()
+
+begin
+ call mfree (EC_FITDATA(ec), TY_DOUBLE)
+ call malloc (EC_FITDATA(ec), EC_NCOLS(ec)*EC_NLINES(ec), TY_DOUBLE)
+
+ do i = 1, EC_NLINES(ec) {
+ call ec_gline (ec, i)
+ if (EC_ECF(ec) == NULL) {
+ if (DC(EC_SH(ec)) != DCNO && EC_UN(ec) != NULL)
+ iferr (call shdr_units (EC_SH(ec), UN_UNITS(EC_UN(ec))))
+ ;
+ call achtrd (Memr[SX(EC_SH(ec))], FITDATA(ec,1), EC_NPTS(ec))
+ call gt_sets (EC_GT(ec), GTXLABEL, LABEL(EC_SH(ec)))
+ call gt_sets (EC_GT(ec), GTXUNITS, UNITS(EC_SH(ec)))
+ } else {
+ ORDERS(ec,i) = ecf_oeval (EC_ECF(ec), APS(ec,i))
+ call ecf_vector (EC_ECF(ec), APS(ec,i), PIXDATA(ec,1),
+ FITDATA(ec,1), EC_NPTS(ec))
+ if (EC_UN(ec) == NULL) {
+ call gt_sets (EC_GT(ec), GTXLABEL, LABEL(EC_SH(ec)))
+ call gt_sets (EC_GT(ec), GTXUNITS, UNITS(EC_SH(ec)))
+ } else {
+ call gt_sets (EC_GT(ec), GTXLABEL, UN_LABEL(EC_UN(ec)))
+ call gt_sets (EC_GT(ec), GTXUNITS, UN_UNITS(EC_UN(ec)))
+ }
+ }
+ }
+
+ call ec_gline (ec, EC_LINE(ec))
+ EC_ORDER(ec) = ORDERS(ec,EC_LINE(ec))
+end
+
+
+# EC_FITFEATURES -- Compute fit coordinates for features.
+
+procedure ec_fitfeatures (ec)
+
+pointer ec # ID pointer
+
+int i, ec_line()
+double ec_fitpt()
+
+begin
+ if (EC_NFEATURES(ec) < 1)
+ return
+
+ do i = 1, EC_NFEATURES(ec) {
+ LINE(ec,i) = ec_line (ec, APN(ec,i))
+ ORDER(ec,i) = ORDERS(ec,LINE(ec,i))
+ FIT(ec,i) = ec_fitpt (ec, APN(ec,i), PIX(ec,i))
+ }
+end
+
+
+# EC_FITPT -- Compute fit coordinates from pixel coordinates.
+
+double procedure ec_fitpt (ec, order, pix)
+
+pointer ec # ID pointer
+int order # Order
+double pix # Pixel coordinate
+
+double fit, ecf_eval(), smw_c1trand(), shdr_lw()
+
+begin
+ if (EC_ECF(ec) == NULL) {
+ fit = smw_c1trand (EC_PL(ec), pix)
+ fit = shdr_lw (EC_SH(ec), fit)
+ } else
+ fit = ecf_eval (EC_ECF(ec), order, pix)
+
+ return (fit)
+end
+
+
+# EC_FITTOPIX -- Transform fit coordinate to pixel coordinate.
+
+define DXMIN .01
+
+double procedure ec_fittopix (ec, fitcoord)
+
+pointer ec # ID pointer
+double fitcoord # Fit coordinate to be transformed
+double pixcoord # Pixel coordinate returned
+
+int i, n
+double dx
+
+double ec_fitpt(), smw_c1trand()
+
+begin
+ n = EC_NPTS(ec)
+ if (FITDATA(ec,1) < FITDATA(ec,n)) {
+ if ((fitcoord<FITDATA(ec,1)) || (fitcoord>FITDATA(ec,n)))
+ return (INDEFD)
+
+ for (i = 1; fitcoord > FITDATA(ec,i); i = i + 1)
+ ;
+
+ if (FITDATA(ec,i) == fitcoord)
+ return (double (i))
+
+ pixcoord = smw_c1trand (EC_LP(ec), double(i-.5))
+ dx = smw_c1trand (EC_LP(ec), double(i+.5)) - pixcoord
+ while (dx > DXMIN) {
+ dx = dx / 2
+ if (ec_fitpt (ec, EC_AP(ec), pixcoord) < fitcoord)
+ pixcoord = pixcoord + dx
+ else
+ pixcoord = pixcoord - dx
+ }
+ } else {
+ if ((fitcoord<FITDATA(ec,n)) || (fitcoord>FITDATA(ec,1)))
+ return (INDEFD)
+
+ for (i = 1; fitcoord < FITDATA(ec,i); i = i + 1)
+ ;
+
+ if (FITDATA(ec,i) == fitcoord)
+ return (double (i))
+
+ pixcoord = smw_c1trand (EC_LP(ec), double(i-.5))
+ dx = smw_c1trand (EC_LP(ec), double(i+.5)) - pixcoord
+ while (dx > DXMIN) {
+ dx = dx / 2
+ if (ec_fitpt (ec, EC_AP(ec), pixcoord) < fitcoord)
+ pixcoord = pixcoord - dx
+ else
+ pixcoord = pixcoord + dx
+ }
+ }
+
+ return (pixcoord)
+end
diff --git a/noao/onedspec/ecidentify/ecgdata.x b/noao/onedspec/ecidentify/ecgdata.x
new file mode 100644
index 00000000..1087d38c
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecgdata.x
@@ -0,0 +1,74 @@
+include <imhdr.h>
+include <imio.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include "ecidentify.h"
+
+# EC_GDATA -- Get image data.
+
+procedure ec_gdata (ec)
+
+pointer ec # ID pointer
+
+int i, j
+pointer im, mw, sh, sp, str1, str2
+
+double smw_c1trand()
+pointer immap(), smw_openim(), smw_sctran()
+errchk immap, smw_openim, shdr_open
+
+begin
+ # Map the image.
+ im = immap (Memc[EC_IMAGE(ec)], READ_ONLY, 0)
+
+ # Free previous data
+ do i = 1, EC_NLINES(ec)
+ call shdr_close (SH(ec,i))
+ call mfree (EC_SHS(ec), TY_POINTER)
+ call mfree (EC_PIXDATA(ec), TY_DOUBLE)
+
+ # Set MWCS
+ mw = smw_openim (im)
+ EC_LP(ec) = smw_sctran (mw, "logical", "physical", 1)
+ EC_PL(ec) = smw_sctran (mw, "physical", "logical", 1)
+
+ # Allocate new vectors.
+ EC_NCOLS(ec) = IM_LEN(im, 1)
+ EC_NLINES(ec) = IM_LEN(im, 2)
+ call calloc (EC_SHS(ec), EC_NLINES(ec), TY_POINTER)
+ call malloc (EC_PIXDATA(ec), EC_NCOLS(ec)*EC_NLINES(ec), TY_DOUBLE)
+
+ # Set the coordinates.
+ sh = NULL
+ do j = 1, EC_NLINES(ec) {
+ call shdr_open (im, mw, j, 1, INDEFI, SHDATA, sh)
+ if (EC_UN(ec) != NULL)
+ iferr (call shdr_units (sh, UN_UNITS(EC_UN(ec))))
+ ;
+ if (j != EC_NLINES(ec))
+ call shdr_copy (sh, SH(ec,j), NO)
+ else
+ SH(ec,j) = sh
+ call ec_gline (ec, j)
+ do i = 1, EC_NPTS(ec)
+ PIXDATA(ec,i) = smw_c1trand (EC_LP(ec), double(i))
+ }
+ EC_LINE(ec) = 1
+ call ec_gline (ec, EC_LINE(ec))
+ EC_AP(ec) = APS(ec,EC_LINE(ec))
+ EC_ORDER(ec) = ORDERS(ec,EC_LINE(ec))
+
+ # Set graph title.
+ call smark (sp)
+ call salloc (str1, SZ_LINE, TY_CHAR)
+ call salloc (str2, SZ_LINE, TY_CHAR)
+
+ call sprintf (Memc[str1], SZ_LINE, "ecidentify %s: %s")
+ call pargstr (Memc[EC_IMAGE(ec)])
+ call pargstr (IM_TITLE(im))
+ call gt_sets (EC_GT(ec), GTTITLE, Memc[str1])
+
+ call imunmap (im)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecgetim.x b/noao/onedspec/ecidentify/ecgetim.x
new file mode 100644
index 00000000..cbcb244e
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecgetim.x
@@ -0,0 +1,17 @@
+# EC_GETIM -- Get next image name with standard image extensions removed.
+
+int procedure ec_getim (list, image, maxchar)
+
+int list # Image list
+char image[maxchar] # Image name
+int maxchar # Maximum number of chars in image name
+
+int stat, imtgetim()
+
+begin
+ stat = imtgetim (list, image, maxchar)
+ if (stat != EOF)
+ call xt_imroot (image, image, maxchar)
+
+ return (stat)
+end
diff --git a/noao/onedspec/ecidentify/ecgline.x b/noao/onedspec/ecidentify/ecgline.x
new file mode 100644
index 00000000..7d3f9e16
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecgline.x
@@ -0,0 +1,20 @@
+include <smw.h>
+include "ecidentify.h"
+
+# EC_GLINE -- Get line of data.
+
+procedure ec_gline (ec, line)
+
+pointer ec # EC pointer
+int line # Image line
+
+begin
+ if (IS_INDEFI(line))
+ return
+
+ EC_SH(ec) = SH(ec,line)
+ EC_NPTS(ec) = SN(EC_SH(ec))
+ EC_IMLINE(ec) = SY(EC_SH(ec))
+ EC_PIXLINE(ec) = EC_PIXDATA(ec) + (line - 1) * EC_NCOLS(ec)
+ EC_FITLINE(ec) = EC_FITDATA(ec) + (line - 1) * EC_NCOLS(ec)
+end
diff --git a/noao/onedspec/ecidentify/ecgraph.x b/noao/onedspec/ecidentify/ecgraph.x
new file mode 100644
index 00000000..9eaeaa5f
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecgraph.x
@@ -0,0 +1,155 @@
+include <gset.h>
+include <pkg/gtools.h>
+include "ecidentify.h"
+
+# EC_GRAPH -- Graph image vector in which features are to be ecentified.
+
+procedure ec_graph (ec, gtype)
+
+pointer ec # ID pointer
+int gtype # Graph type
+
+begin
+ switch (gtype) {
+ case 1:
+ if (IS_INDEFI (EC_AP(ec)))
+ call ec_graph3(ec)
+ else
+ call ec_graph1 (ec)
+ case 2:
+ call ec_graph2 (ec)
+ default:
+ call ec_graph1 (ec)
+ }
+end
+
+
+procedure ec_graph1 (ec)
+
+pointer ec # ID pointer
+
+int i
+real xmin, xmax, ymin, ymax, dy
+pointer sp, str, x, y
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (x, EC_NPTS(ec), TY_REAL)
+ y = EC_IMLINE(ec)
+
+ call sprintf (Memc[str], SZ_LINE,
+ "Aperture %d, Image line %d, Order %d")
+ call pargi (EC_AP(ec))
+ call pargi (EC_LINE(ec))
+ call pargi (EC_ORDER(ec))
+ call gt_sets (EC_GT(ec), GTPARAMS, Memc[str])
+ call achtdr (FITDATA(ec,1), Memr[x], EC_NPTS(ec))
+
+ call gclear (EC_GP(ec))
+ xmin = min (Memr[x], Memr[x+EC_NPTS(ec)-1])
+ xmax = max (Memr[x], Memr[x+EC_NPTS(ec)-1])
+ call alimr (Memr[y], EC_NPTS(ec), ymin, ymax)
+ dy = ymax - ymin
+ call gswind (EC_GP(ec), xmin, xmax, ymin - .2 * dy, ymax + .2 * dy)
+ call gt_swind (EC_GP(ec), EC_GT(ec))
+ call gt_labax (EC_GP(ec), EC_GT(ec))
+ call gt_plot (EC_GP(ec), EC_GT(ec), Memr[x], Memr[y], EC_NPTS(ec))
+
+ do i = 1, EC_NFEATURES(ec)
+ if (APN(ec,i) == EC_AP(ec))
+ call ec_mark (ec, i)
+
+ call sfree (sp)
+end
+
+
+# EC_GRAPH2 -- Make review graph for current feature.
+
+procedure ec_graph2 (ec)
+
+pointer ec # ID pointer
+
+int i, j, k
+real xmin, xmax, ymin, ymax, dy
+pointer sp, str, x, y
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (x, EC_NPTS(ec), TY_REAL)
+ y = EC_IMLINE(ec)
+
+ call sprintf (Memc[str], SZ_LINE, "Order %d")
+ call pargi (EC_AP(ec))
+ call gt_sets (EC_GT(ec), GTPARAMS, Memc[str])
+ call achtdr (FITDATA(ec,1), Memr[x], EC_NPTS(ec))
+
+ xmin = real (FIT(ec,EC_CURRENT(ec))) - EC_ZWIDTH(ec) / 2.
+ xmax = real (FIT(ec,EC_CURRENT(ec))) + EC_ZWIDTH(ec) / 2.
+
+ i = 0
+ do k = 1, EC_NPTS(ec) {
+ if ((Memr[x+k-1] < xmin) || (Memr[x+k-1] > xmax))
+ next
+ if (i == 0)
+ i = k
+ j = k
+ }
+ k = j - i + 1
+
+ call alimr (Memr[y+i-1], k, ymin, ymax)
+ dy = ymax - ymin
+
+ call gclear (EC_GP(ec))
+ call gswind (EC_GP(ec), xmin, xmax, ymin - .2 * dy, ymax + .2 * dy)
+ call gt_labax (EC_GP(ec), EC_GT(ec))
+ call gt_plot (EC_GP(ec), EC_GT(ec), Memr[x], Memr[y], EC_NPTS(ec))
+
+ do i = 1, EC_NFEATURES(ec)
+ if (APN(ec,i) == EC_AP(ec))
+ call ec_mark (ec, i)
+
+ call sfree (sp)
+end
+
+
+procedure ec_graph3 (ec)
+
+pointer ec # ID pointer
+
+int i, npts
+real xmin, xmax, ymin, ymax, dy
+pointer sp, str, x, y
+
+begin
+ npts = EC_NPTS(ec) * EC_NLINES(ec)
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (x, npts, TY_REAL)
+ y = EC_IMLINE(ec)
+
+ call sprintf (Memc[str], SZ_LINE, "All orders")
+ call gt_sets (EC_GT(ec), GTPARAMS, Memc[str])
+ call achtdr (Memd[EC_FITDATA(ec)], Memr[x], npts)
+
+ call gclear (EC_GP(ec))
+ xmin = min (Memr[x], Memr[x+npts-1])
+ xmax = max (Memr[x], Memr[x+npts-1])
+ call alimr (Memr[y], npts, ymin, ymax)
+ dy = ymax - ymin
+ call gswind (EC_GP(ec), xmin, xmax, ymin - .2 * dy, ymax + .2 * dy)
+ call gt_swind (EC_GP(ec), EC_GT(ec))
+ call gt_labax (EC_GP(ec), EC_GT(ec))
+ do i = 1, EC_NLINES(ec) {
+ call gt_plot (EC_GP(ec), EC_GT(ec), Memr[x], Memr[y], EC_NPTS(ec))
+ x = x + EC_NPTS(ec)
+ y = y + EC_NPTS(ec)
+ }
+
+ do i = 1, EC_NFEATURES(ec)
+ call ec_mark (ec, i)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/ecidentify.h b/noao/onedspec/ecidentify/ecidentify.h
new file mode 100644
index 00000000..63e4c6bd
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecidentify.h
@@ -0,0 +1,94 @@
+# Task parameters
+
+define LEN_EC 52 # Length ID structure
+
+define EC_IMAGE Memi[$1] # Image name (pointer)
+define EC_MAXFEATURES Memi[$1+1] # Maximum number of features
+define EC_FTYPE Memi[$1+2] # Feature type
+define EC_MINSEP Memr[P2R($1+3)] # Minimum pixel separation
+define EC_MATCH Memr[P2R($1+4)] # Maximum matching separation
+define EC_FWIDTH Memr[P2R($1+5)] # Feature width in pixels
+define EC_CRADIUS Memr[P2R($1+6)] # Centering radius in pixels
+define EC_THRESHOLD Memr[P2R($1+7)] # Centering threshold
+define EC_ZWIDTH Memr[P2R($1+8)] # Zoom window width in fit units
+define EC_DATABASE Memi[$1+9] # Name of database (pointer)
+define EC_COORDLIST Memi[$1+10] # Name of coordinate list (pointer)
+define EC_LABELS Memi[$1+11] # Type of feature labels
+define EC_LOGFILES Memi[$1+12] # List of logfiles
+
+# Common image data
+
+define EC_NCOLS Memi[$1+13] # Number of columns
+define EC_NLINES Memi[$1+14] # Number of lines/apertures/orders
+define EC_SHS Memi[$1+15] # Pointer to SHDR pointers
+define EC_PIXDATA Memi[$1+16] # Pixel coordinates (pointer)
+define EC_FITDATA Memi[$1+17] # Fit coordinates (pointer)
+
+define EC_IMLINE Memi[$1+18] # Image data (pointer)
+define EC_PIXLINE Memi[$1+19] # Pixel coordinates (pointer)
+define EC_FITLINE Memi[$1+20] # Fit coordinates (pointer)
+define EC_NPTS Memi[$1+21] # Number of points
+
+define EC_SHIFT Memd[P2D($1+22)]# Wavelength shift
+
+# Features
+
+define EC_NFEATURES Memi[$1+24] # Number of features
+define EC_NALLOC Memi[$1+25] # Length of allocated feature arrays
+define EC_APNUM Memi[$1+26] # Aperture number (pointer)
+define EC_LINENUM Memi[$1+27] # Image line number (pointer)
+define EC_ORD Memi[$1+28] # Feature order number (pointer)
+define EC_PIX Memi[$1+29] # Feature pixel coordinates (pointer)
+define EC_FIT Memi[$1+30] # Feature fit coordinates (pointer)
+define EC_USER Memi[$1+31] # Feature user coordinates (pointer)
+define EC_FWIDTHS Memi[$1+32] # Feature width (pointer)
+define EC_FTYPES Memi[$1+33] # Feature type (pointer)
+
+# Current status
+
+define EC_CURRENT Memi[$1+34] # Current feature
+define EC_SH Memi[$1+35] # Current SHDR pointer
+define EC_AP Memi[$1+36] # Current aperture
+define EC_LINE Memi[$1+37] # Current line
+define EC_ORDER Memi[$1+38] # Current order
+
+# Pointers for other packages
+
+define EC_LP Memi[$1+39] # Logical to physical transformation
+define EC_PL Memi[$1+40] # Physical to logical transformation
+define EC_LL Memi[$1+41] # Linelist pointer
+define EC_ECF Memi[$1+42] # Curfit pointer
+define EC_GP Memi[$1+43] # GIO pointer
+define EC_GT Memi[$1+44] # Gtools pointer
+define EC_UN Memi[$1+45] # Units pointer
+
+# Flags
+
+define EC_NEWFEATURES Memi[$1+46] # Has feature list changed?
+define EC_NEWECF Memi[$1+47] # Has fitting function changed?
+define EC_NEWGRAPH Memi[$1+48] # Has graph changed?
+define EC_NEWDBENTRY Memi[$1+49] # Has database entry changed?
+define EC_REFIT Memi[$1+50] # Refit feature data?
+define EC_GTYPE Memi[$1+51] # Graph type
+
+# End of structure ----------------------------------------------------------
+
+define LABELS "|none|index|pixel|user|"
+define FTYPES "|emission|absorption|"
+
+define IMDATA Memr[EC_IMLINE($1)+$2-1]
+define PIXDATA Memd[EC_PIXLINE($1)+$2-1]
+define FITDATA Memd[EC_FITLINE($1)+$2-1]
+
+define SH Memi[EC_SHS($1)+$2-1]
+define APS AP(SH($1,$2))
+define ORDERS BEAM(SH($1,$2))
+
+define APN Memi[EC_APNUM($1)+$2-1]
+define LINE Memi[EC_LINENUM($1)+$2-1]
+define ORDER Memi[EC_ORD($1)+$2-1]
+define PIX Memd[EC_PIX($1)+$2-1]
+define FIT Memd[EC_FIT($1)+$2-1]
+define USER Memd[EC_USER($1)+$2-1]
+define FWIDTH Memr[EC_FWIDTHS($1)+$2-1]
+define FTYPE Memi[EC_FTYPES($1)+$2-1]
diff --git a/noao/onedspec/ecidentify/ecidentify.key b/noao/onedspec/ecidentify/ecidentify.key
new file mode 100644
index 00000000..c19698ef
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecidentify.key
@@ -0,0 +1,76 @@
+1. ECIDENTIFY CURSOR KEY SUMMARY
+
+? Help a Affect all features c Center feature(s)
+d Delete feature(s) f Fit dispersion g Fit zero point shift
+i Initialize j Go to previous order k Go to next order
+l Match coordinate list m Mark feature n Next feature
+o Go to specified order p Pan graph q Quit
+r Redraw graph s Shift feature t Reset position
+u Enter user coordinate w Window graph x Crosscorrelate peaks
+y Find peaks z Zoom graph . Nearest feature
++ Next feature - Previous feature I Interrupt
+
+
+2. ECIDENTIFY COLON COMMAND SUMMARY
+
+:show [file] :features [file] :coordlist [file]
+:cradius [value] :threshold [value] :database [file]
+:ftype [type] :fwidth [value] :image [image]
+:labels [type] :match [value] :maxfeatures [value]
+:minsep [value] :read [image] :write [image]
+:zwidth [value]
+
+
+3. ECIDENTIFY CURSOR KEYS
+
+? Clear the screen and print menu of options
+a Apply next (c)enter or (d)elete operation to (a)ll features
+c (C)enter the feature nearest the cursor
+d (D)elete the feature nearest the cursor
+f (F)it a function of pixel coordinate to the user coordinates
+g Fit a zero point shift to the user coordinates
+i (I)nitialize (delete features and coordinate fit)
+j Go to the previous order
+k Go to the next order
+l Match coordinates in the coordinate (l)ist to features in the data
+m (M)ark a new feature near the cursor
+n Move the cursor or zoom to the (n)ext feature (same as +)
+o Go to the specified (o)rder
+p (P)an to user defined window after (z)ooming on a feature
+q (Q)uit and continue with next image (also carriage return)
+r (R)edraw the graph
+s (S)hift the current feature to the position of the cursor
+t Reset (move) the position of a feature without centering
+u Enter a new (u)ser coordinate for the current feature
+w (W)indow the graph. Use '?' to window prompt for more help.
+x Crosscorrelate features with the data peaks and reregister
+y Automatically find "maxfeatures" strongest peaks and identify them
+z (Z)oom on the feature nearest the cursor
+. Move the cursor or zoom to the feature nearest the cursor (also space bar)
++ Move the cursor or zoom to the next feature
+- Move the cursor or zoom to the previous feature
+I Interrupt task immediately. Database information is not saved.
+
+
+4. ECIDENTIFY COLON COMMANDS
+
+The parameters are listed or set with the following commands which may be
+abbreviated. To list the value of a parameter type the command alone.
+
+:show file Show the values of all the parameters
+:features file Write feature list to file (default is STDOUT)
+
+:coordlist file Coordinate list file
+:cradius value Centering radius in pixels
+:threshold value Detection threshold for feature centering
+:database name Database for recording feature records
+:ftype value Feature type (emission or absorption)
+:fwidth value Feature width in pixels
+:image imagename Set a new image or show the current image
+:labels value Feature label type (none, index, pixel, or user)
+:match value Coordinate list matching distance
+:maxfeatures value Maximum number of features automatically found
+:minsep value Minimum separation allowed between features
+:read name Read a record from the database (name defaults to image)
+:write name Write a record to the database (name defaults to image)
+:zwidth value Zoom width in user units
diff --git a/noao/onedspec/ecidentify/ecidentify.x b/noao/onedspec/ecidentify/ecidentify.x
new file mode 100644
index 00000000..827568d1
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecidentify.x
@@ -0,0 +1,535 @@
+include <error.h>
+include <imhdr.h>
+include <gset.h>
+include <smw.h>
+include "ecidentify.h"
+
+define HELP "noao$onedspec/ecidentify/ecidentify.key"
+define PROMPT "ecidentify options"
+
+define PAN 1 # Pan graph
+define ZOOM 2 # Zoom graph
+
+# EC_IDENTIFY -- Identify echelle features in an image.
+# This is the basic interactive loop.
+
+procedure ec_identify (ec)
+
+pointer ec # EC pointer
+
+real wx, wy
+int wcs, key
+char cmd[SZ_LINE]
+
+char newimage[SZ_FNAME]
+int i, j, last, all, prfeature, nfeatures1, npeaks
+bool answer
+double pix, fit, user, shift, pix_shift, z_shift
+pointer peaks
+
+bool clgetb()
+int clgcur(), scan(), nscan(), find_peaks(), ec_next(), ec_previous()
+int ec_line()
+double ec_center(), ec_fittopix(), ec_fitpt(), ec_shift(), ec_rms()
+double ecf_getd()
+errchk ec_gdata(), ec_graph(), ec_dbread(), xt_mk1d(), ec_line()
+
+define newim_ 10
+define newkey_ 20
+define beep_ 99
+
+begin
+newim_ # Start here for each new image.
+
+ # Get the image data. Return if there is an error.
+ iferr (call ec_gdata (ec)) {
+ call erract (EA_WARN)
+ return
+ }
+
+ # Look for a database entry for the image.
+ iferr {
+ call ec_dbread (ec, Memc[EC_IMAGE(ec)], NO)
+ EC_NEWDBENTRY(ec) = NO
+ } then
+ if ((EC_NFEATURES(ec) > 0) || (EC_ECF(ec) != NULL))
+ EC_NEWDBENTRY(ec) = YES
+
+ # Set the coordinate array and the feature data.
+ iferr (call ec_fitdata (ec))
+ call erract (EA_WARN)
+ call ec_fitfeatures (ec)
+
+ # Begin with the first image line.
+ EC_LINE(ec) = 1
+ EC_AP(ec) = APS(ec,EC_LINE(ec))
+ EC_ORDER(ec) = ORDERS(ec,EC_LINE(ec))
+ call ec_gline (ec, EC_LINE(ec))
+
+ # Initialize.
+ EC_GTYPE(ec) = PAN
+ EC_REFIT(ec) = NO
+ EC_NEWFEATURES(ec) = NO
+ EC_NEWECF(ec) = NO
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ last = EC_CURRENT(ec)
+ all = 0
+ newimage[1] = EOS
+ key = 'r'
+
+ repeat {
+ prfeature = YES
+ if (all != 0)
+ all = mod (all + 1, 3)
+
+ switch (key) {
+ case '?': # Page help
+ call gpagefile (EC_GP(ec), HELP, PROMPT)
+ case ':': # Execute colon commands
+ if (cmd[1] == '/')
+ call gt_colon (cmd, EC_GP(ec), EC_GT(ec), EC_NEWGRAPH(ec))
+ else
+ call ec_colon (ec, cmd, newimage, prfeature)
+ case ' ': # Go to the current feature
+ case '.': # Go to the nearest feature
+ if (EC_NFEATURES(ec) == 0)
+ goto beep_
+ call ec_nearest (ec, double (wx))
+ case '-': # Go to the previous feature
+ if (ec_previous (ec, EC_CURRENT(ec)) == EOF)
+ goto beep_
+ case '+', 'n': # Go to the next feature
+ if (ec_next (ec, EC_CURRENT(ec)) == EOF)
+ goto beep_
+ case 'a': # Set the all flag for the next key
+ all = 1
+ case 'c': # Center features on data
+ if (all != 0) {
+ call eprintf ("Recentering features ...\n")
+ for (i = 1; i <= EC_NFEATURES(ec); i = i + 1) {
+ call ec_gline (ec, LINE(ec,i))
+ call gseti (EC_GP(ec), G_PLTYPE, 0)
+ call ec_mark (ec, i)
+ call gseti (EC_GP(ec), G_PLTYPE, 1)
+ FWIDTH(ec,i) = EC_FWIDTH(ec)
+ PIX(ec,i) = ec_center (ec, PIX(ec,i), FWIDTH(ec,i),
+ FTYPE(ec,i))
+ if (!IS_INDEFD (PIX(ec,i))) {
+ FIT(ec,i) = ec_fitpt (ec, APN(ec,i), PIX(ec,i))
+ call ec_mark (ec, i)
+ } else {
+ call ec_delete (ec, i)
+ i = i - 1
+ }
+ }
+ call ec_gline (ec, EC_LINE(ec))
+ EC_NEWFEATURES(ec) = YES
+ } else {
+ if (EC_NFEATURES(ec) == 0)
+ goto beep_
+
+ call ec_nearest (ec, double (wx))
+ pix = PIX(ec,EC_CURRENT(ec))
+ pix = ec_center (ec, pix, EC_FWIDTH(ec),
+ FTYPE(ec,EC_CURRENT(ec)))
+ if (!IS_INDEFD (pix)) {
+ call gseti (EC_GP(ec), G_PLTYPE, 0)
+ call ec_mark (ec, EC_CURRENT(ec))
+ PIX(ec,EC_CURRENT(ec)) = pix
+ FWIDTH(ec,EC_CURRENT(ec)) = EC_FWIDTH(ec)
+ FIT(ec,EC_CURRENT(ec)) =
+ ec_fitpt (ec, APN(ec,EC_CURRENT(ec)), pix)
+ call gseti (EC_GP(ec), G_PLTYPE, 1)
+ call ec_mark (ec, EC_CURRENT(ec))
+ EC_NEWFEATURES(ec) = YES
+ } else {
+ call eprintf ("Centering failed\n")
+ prfeature = NO
+ }
+ }
+ case 'd': # Delete features
+ if (all != 0) {
+ EC_NFEATURES(ec) = 0
+ EC_CURRENT(ec) = 0
+ EC_NEWFEATURES(ec) = YES
+ EC_NEWGRAPH(ec) = YES
+ } else {
+ if (EC_NFEATURES(ec) == 0)
+ goto beep_
+
+ call ec_nearest (ec, double (wx))
+ call gseti (EC_GP(ec), G_PLTYPE, 0)
+ call ec_mark (ec, EC_CURRENT(ec))
+ call gseti (EC_GP(ec), G_PLTYPE, 1)
+ call ec_delete (ec, EC_CURRENT(ec))
+ call ec_nearest (ec, double (wx))
+ last = 0
+ }
+ case 'f': # Fit dispersion function
+ iferr (call ec_dofit (ec, YES, NO)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ goto beep_
+ }
+ case 'g': # Fit shift
+ call ec_doshift (ec, YES)
+ prfeature = NO
+ case 'i': # Initialize
+ call dgsfree (EC_ECF(ec))
+ call ecf_setd ("shift", 0.D0)
+ EC_NEWECF(ec) = YES
+ EC_NFEATURES(ec) = 0
+ EC_CURRENT(ec) = 0
+ EC_NEWFEATURES(ec) = YES
+ EC_NEWGRAPH(ec) = YES
+ case 'j': # Go to the previous order
+ EC_LINE(ec) =
+ mod (EC_LINE(ec)+EC_NLINES(ec)-2, EC_NLINES(ec)) + 1
+ EC_AP(ec) = APS(ec,EC_LINE(ec))
+ EC_ORDER(ec) = ORDERS(ec,EC_LINE(ec))
+ call ec_gline (ec, EC_LINE(ec))
+ EC_NEWGRAPH(ec) = YES
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ case 'k': # Go to the next order
+ EC_LINE(ec) = mod (EC_LINE(ec), EC_NLINES(ec)) + 1
+ EC_AP(ec) = APS(ec,EC_LINE(ec))
+ EC_ORDER(ec) = ORDERS(ec,EC_LINE(ec))
+ call ec_gline (ec, EC_LINE(ec))
+ EC_NEWGRAPH(ec) = YES
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ case 'l': # Find features using a line list
+ if (EC_ECF(ec) == NULL) {
+ call eprintf ("Doing initial fit ...\n")
+ iferr (call ec_dofit (ec, NO, NO)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ goto beep_
+ }
+ if (EC_NEWECF(ec) == YES) {
+ iferr (call ec_fitdata (ec)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ call ec_fitfeatures (ec)
+ EC_NEWECF(ec) = NO
+ }
+ }
+
+ call eprintf ("Searching coordinate list ...\n")
+ call ec_linelist (ec)
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ if (EC_NEWFEATURES(ec) == YES)
+ EC_NEWGRAPH(ec) = YES
+ case 'm': # Mark a new feature
+ fit = wx
+ pix = ec_fittopix (ec, fit)
+ pix = ec_center (ec, pix, EC_FWIDTH(ec), EC_FTYPE(ec))
+ if (IS_INDEFD (pix))
+ goto beep_
+ fit = ec_fitpt (ec, EC_AP(ec), pix)
+ user = fit
+ call ec_newfeature (ec, EC_AP(ec), pix, fit, user,
+ EC_FWIDTH(ec), EC_FTYPE(ec))
+ USER(ec,EC_CURRENT(ec)) = INDEFD
+ call ec_match (ec, FIT(ec,EC_CURRENT(ec)),
+ USER(ec,EC_CURRENT(ec)))
+ call ec_mark (ec, EC_CURRENT(ec))
+ call printf ("%3d %10.2f %10.8g (%10.8g): ")
+ call pargi (APN(ec,EC_CURRENT(ec)))
+ call pargd (PIX(ec,EC_CURRENT(ec)))
+ call pargd (FIT(ec,EC_CURRENT(ec)))
+ call pargd (USER(ec,EC_CURRENT(ec)))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ if (nscan() == 1) {
+ USER(ec,EC_CURRENT(ec)) = user
+ call ec_match (ec, user, USER(ec,EC_CURRENT(ec)))
+ }
+ }
+ case 'o': # Go to a specified order
+ call printf ("Aperture (%d): ")
+ call pargi (EC_AP(ec))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargi (j)
+ if (nscan() == 1) {
+ if (j != EC_AP(ec)) {
+ iferr {
+ i = ec_line (ec, j)
+ call ec_gline (ec, i)
+ EC_LINE(ec) = i
+ EC_AP(ec) = j
+ EC_ORDER(ec) = ORDERS(ec,i)
+ EC_NEWGRAPH(ec) = YES
+ EC_CURRENT(ec) = 0
+ i = ec_next (ec, EC_CURRENT(ec))
+ } then
+ goto beep_
+ }
+ }
+ }
+ case 'p': # Go to pan graph mode
+ if (EC_GTYPE(ec) == PAN)
+ goto beep_
+
+ EC_GTYPE(ec) = PAN
+ EC_NEWGRAPH(ec) = YES
+ case 'q': # Quit
+ break
+ case 'r': # Redraw the current graph
+ EC_NEWGRAPH(ec) = YES
+ case 's', 'x': # Shift or cross correlate features
+ # Get coordinate shift.
+ switch (key) {
+ case 's':
+ call printf ("User coordinate (%10.8g): ")
+ call pargr (wx)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ if (nscan() == 1)
+ shift = (wx - user) * EC_ORDER(ec)
+ } else
+ shift = 0.
+ case 'x':
+ if (EC_NFEATURES(ec) > 5) {
+ call eprintf (
+ "Cross correlating features with peaks ...\n")
+ shift = ec_shift (ec)
+ } else
+ goto beep_
+ }
+
+ EC_NEWFEATURES(ec) = YES
+ EC_NEWECF(ec) = YES
+ EC_NEWGRAPH(ec) = YES
+ prfeature = NO
+
+ if (EC_NFEATURES(ec) < 1) {
+ call printf ("User coordinate shift=%5f")
+ call pargd (shift / EC_ORDER(ec))
+ call ecf_setd ("shift", ecf_getd ("shift") - shift)
+ goto newkey_
+ }
+
+ # Recenter features.
+ call eprintf ("Recentering features ...\n")
+ pix_shift = 0.
+ z_shift = 0.
+ nfeatures1 = EC_NFEATURES(ec)
+
+ j = 0.
+ do i = 1, EC_NFEATURES(ec) {
+ call ec_gline (ec, LINE(ec,i))
+ pix = ec_fittopix (ec, FIT(ec,i) + shift/ORDER(ec,i))
+ pix = ec_center (ec, pix, FWIDTH(ec,i), FTYPE(ec,i))
+ if (IS_INDEFD (pix)) {
+ if (EC_CURRENT(ec) == i)
+ EC_CURRENT(ec) = 0
+ next
+ }
+ fit = ec_fitpt (ec, APN(ec,i), pix)
+
+ pix_shift = pix_shift + pix - PIX(ec,i)
+ if (FIT(ec,i) != 0.)
+ z_shift = z_shift + (fit - FIT(ec,i)) / FIT(ec,i)
+
+ j = j + 1
+ APN(ec,j) = APN(ec,i)
+ LINE(ec,j) = LINE(ec,i)
+ ORDER(ec,j) = ORDER(ec,i)
+ PIX(ec,j) = pix
+ FIT(ec,j) = FIT(ec,i)
+ USER(ec,j) = USER(ec,i)
+ FWIDTH(ec,j) = FWIDTH(ec,i)
+ FTYPE(ec,j) = FTYPE(ec,i)
+ if (EC_CURRENT(ec) == i)
+ EC_CURRENT(ec) = j
+ }
+ call ec_gline (ec, EC_LINE(ec))
+ EC_NFEATURES(ec) = j
+ if (EC_CURRENT(ec) == 0)
+ i = ec_next (ec, EC_CURRENT(ec))
+
+ if (EC_NFEATURES(ec) < 1) {
+ call printf ("User coordinate shift=%5f")
+ call pargd (shift / EC_ORDER(ec))
+ call printf (", No features found during recentering")
+ call ecf_setd ("shift", ecf_getd ("shift") - shift)
+ goto newkey_
+ }
+
+ # Adjust shift.
+ pix = ecf_getd ("shift")
+ call ec_doshift (ec, NO)
+ call ec_fitfeatures (ec)
+
+ # Print results.
+ call printf ("Recentered=%d/%d")
+ call pargi (EC_NFEATURES(ec))
+ call pargi (nfeatures1)
+ call printf (
+ ", pixel shift=%.2f, user shift=%5f, z=%7.3g, rms=%5g")
+ call pargd (pix_shift / EC_NFEATURES(ec))
+ call pargd ((pix - ecf_getd ("shift")) / EC_ORDER(ec))
+ call pargd (z_shift / EC_NFEATURES(ec))
+ call pargd (ec_rms(ec))
+ case 't': # Move current feature
+ if (EC_CURRENT(ec) == 0)
+ goto beep_
+
+ call gseti (EC_GP(ec), G_PLTYPE, 0)
+ call ec_mark (ec, EC_CURRENT(ec))
+ pix = ec_fittopix (ec, double (wx))
+ PIX(ec,EC_CURRENT(ec)) = pix
+ FIT(ec,EC_CURRENT(ec)) =
+ ec_fitpt (ec, APN(ec,EC_CURRENT(ec)), pix)
+ call gseti (EC_GP(ec), G_PLTYPE, 1)
+ call ec_mark (ec, EC_CURRENT(ec))
+ EC_NEWFEATURES(ec) = YES
+ case 'u': # Set uesr coordinate value
+ if (EC_NFEATURES(ec) == 0)
+ goto beep_
+
+ call printf ("%3d %10.2f %10.8g (%10.8g): ")
+ call pargi (APN(ec,EC_CURRENT(ec)))
+ call pargd (PIX(ec,EC_CURRENT(ec)))
+ call pargd (FIT(ec,EC_CURRENT(ec)))
+ call pargd (USER(ec,EC_CURRENT(ec)))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ if (nscan() == 1) {
+ USER(ec,EC_CURRENT(ec)) = user
+ EC_NEWFEATURES(ec) = YES
+ }
+ }
+ case 'w': # Window graph
+ call gt_window (EC_GT(ec), EC_GP(ec), "cursor", EC_NEWGRAPH(ec))
+ case 'y': # Find peaks in order
+ call malloc (peaks, EC_NPTS(ec), TY_REAL)
+ npeaks = find_peaks (IMDATA(ec,1), Memr[peaks],
+ EC_NPTS(ec), 0., int (EC_MINSEP(ec)), 0, EC_MAXFEATURES(ec),
+ 0., false)
+ for (j = 1; j <= EC_NFEATURES(ec); j = j + 1) {
+ for (i = 1; i <= npeaks; i = i + 1) {
+ if (!IS_INDEF(pix)) {
+ pix = Memr[peaks + i - 1]
+ if (abs (pix - PIX(ec,j)) < EC_MINSEP(ec))
+ Memr[peaks + i - 1] = INDEF
+ }
+ }
+ }
+ for (i = 1; i <= npeaks; i = i + 1) {
+ pix = Memr[peaks+i-1]
+ pix = ec_center (ec, pix, EC_FWIDTH(ec), EC_FTYPE(ec))
+ if (IS_INDEFD (pix))
+ next
+ fit = ec_fitpt (ec, EC_AP(ec), pix)
+ user = INDEFD
+ call ec_match (ec, fit, user)
+ call ec_newfeature (ec, EC_AP(ec), pix, fit, user,
+ EC_FWIDTH(ec), EC_FTYPE(ec))
+ call ec_mark (ec, EC_CURRENT(ec))
+ }
+ call mfree (peaks, TY_REAL)
+ case 'z': # Go to zoom mode
+ if (EC_CURRENT(ec) == 0)
+ goto beep_
+
+ if (EC_GTYPE(ec) == PAN)
+ EC_NEWGRAPH(ec) = YES
+ EC_GTYPE(ec) = ZOOM
+ call ec_nearest (ec, double (wx))
+ case 'I': # Interrupt
+ call fatal (0, "Interrupt")
+ default: # Beep
+beep_ call printf ("\007\n")
+ }
+
+newkey_
+ # Set database update flag if there has been a change.
+ if ((EC_NEWFEATURES(ec) == YES) || (EC_NEWECF(ec) == YES))
+ EC_NEWDBENTRY(ec) = YES
+
+ # Exit loop and then start new image.
+ if (newimage[1] != EOS)
+ break
+
+ # Refit the dispersion function if needed.
+ if (EC_REFIT(ec) == YES) {
+ iferr (call ec_dofit (ec, NO, NO)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ EC_REFIT(ec) = NO
+ }
+
+ # Recompute the coordinate information.
+ if (EC_NEWECF(ec) == YES) {
+ iferr (call ec_fitdata (ec)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ call ec_fitfeatures (ec)
+ EC_NEWECF(ec) = NO
+ }
+
+ # Redraw new feature in zoom mode.
+ if ((EC_GTYPE(ec) == ZOOM) && (last != EC_CURRENT(ec)))
+ EC_NEWGRAPH(ec) = YES
+
+ # Redraw graph.
+ if (EC_NEWGRAPH(ec) == YES) {
+ call ec_graph (ec, EC_GTYPE(ec))
+ EC_NEWGRAPH(ec) = NO
+ }
+
+ # Set cursor and print current feature on status (unless canceled).
+ if (EC_CURRENT(ec) > 0) {
+ call gscur (EC_GP(ec), real (FIT(ec,EC_CURRENT(ec))), wy)
+ if (prfeature == YES) {
+ call printf ("%d %10.2f %10.8g %10.8g\n")
+ call pargi (APN(ec,EC_CURRENT(ec)))
+ call pargd (PIX(ec,EC_CURRENT(ec)))
+ call pargd (FIT(ec,EC_CURRENT(ec)))
+ call pargd (USER(ec,EC_CURRENT(ec)))
+ }
+ }
+
+ last = EC_CURRENT(ec)
+ } until (clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+
+ # Warn user that feature data is newer than database entry.
+ if (EC_NEWDBENTRY(ec) == YES) {
+ answer = true
+ if (!clgetb ("autowrite")) {
+ call printf ("Write feature data to the database (yes)? ")
+ call flush (STDOUT)
+ if (scan() != EOF)
+ call gargb (answer)
+ }
+ if (answer)
+ call ec_dbwrite (ec, Memc[EC_IMAGE(ec)], NO)
+ }
+
+ call flush (STDOUT)
+
+ # Free image data and MWCS
+ call mfree (EC_PIXDATA(ec), TY_DOUBLE)
+ call mfree (EC_FITDATA(ec), TY_DOUBLE)
+ call smw_close (MW(EC_SH(ec)))
+ do i = 1, EC_NLINES(ec)
+ MW(SH(ec,i)) = NULL
+
+ # If a new image was specified by a colon command don't return.
+ if (newimage[1] != EOS) {
+ call strcpy (newimage, Memc[EC_IMAGE(ec)], SZ_FNAME)
+ goto newim_
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecinit.x b/noao/onedspec/ecidentify/ecinit.x
new file mode 100644
index 00000000..8b3b7b62
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecinit.x
@@ -0,0 +1,64 @@
+include <gset.h>
+include "ecidentify.h"
+
+# EC_INIT -- Allocate and initialize the identify structure.
+
+procedure ec_init (ec)
+
+pointer ec # ID pointer
+
+begin
+ call calloc (ec, LEN_EC, TY_STRUCT)
+
+ EC_NALLOC(ec) = 20
+ EC_NFEATURES(ec) = 0
+ EC_CURRENT(ec) = 0
+ EC_NLINES(ec) = 0
+ EC_LL(ec) = NULL
+ EC_ECF(ec) = NULL
+ EC_LABELS(ec) = 1
+
+ call malloc (EC_IMAGE(ec), SZ_FNAME, TY_CHAR)
+ call malloc (EC_DATABASE(ec), SZ_FNAME, TY_CHAR)
+ call malloc (EC_COORDLIST(ec), SZ_FNAME, TY_CHAR)
+
+ call malloc (EC_APNUM(ec), EC_NALLOC(ec), TY_INT)
+ call malloc (EC_LINENUM(ec), EC_NALLOC(ec), TY_INT)
+ call malloc (EC_PIX(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call malloc (EC_ORD(ec), EC_NALLOC(ec), TY_INT)
+ call malloc (EC_FIT(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call malloc (EC_USER(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call malloc (EC_FWIDTHS(ec), EC_NALLOC(ec), TY_REAL)
+ call malloc (EC_FTYPES(ec), EC_NALLOC(ec), TY_INT)
+end
+
+
+# EC_FREE -- Free identify structure.
+
+procedure ec_free (ec)
+
+pointer ec # ID pointer
+int i
+
+begin
+ if (EC_UN(ec) != NULL)
+ call un_close (EC_UN(ec))
+ do i = 1, EC_NLINES(ec)
+ call shdr_close (SH(ec,i))
+ call mfree (EC_SHS(ec), TY_POINTER)
+
+ call mfree (EC_IMAGE(ec), TY_CHAR)
+ call mfree (EC_DATABASE(ec), TY_CHAR)
+ call mfree (EC_COORDLIST(ec), TY_CHAR)
+
+ call mfree (EC_APNUM(ec), TY_INT)
+ call mfree (EC_LINENUM(ec), TY_INT)
+ call mfree (EC_PIX(ec), TY_DOUBLE)
+ call mfree (EC_ORD(ec), TY_INT)
+ call mfree (EC_FIT(ec), TY_DOUBLE)
+ call mfree (EC_USER(ec), TY_DOUBLE)
+ call mfree (EC_FWIDTHS(ec), TY_REAL)
+ call mfree (EC_FTYPES(ec), TY_INT)
+
+ call mfree (ec, TY_STRUCT)
+end
diff --git a/noao/onedspec/ecidentify/ecline.x b/noao/onedspec/ecidentify/ecline.x
new file mode 100644
index 00000000..63e55072
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecline.x
@@ -0,0 +1,22 @@
+include <smw.h>
+include "ecidentify.h"
+
+# EC_LINE -- Get line corresponding to aperture.
+
+int procedure ec_line (ec, ap)
+
+pointer ec # EC pointer
+int ap # Aperture
+
+int i
+
+begin
+ if (IS_INDEFI (ap))
+ return (INDEFI)
+
+ do i = 1, EC_NLINES(ec)
+ if (ap == APS(ec,i))
+ return (i)
+
+ call error (0, "Image line for aperture number not found")
+end
diff --git a/noao/onedspec/ecidentify/eclinelist.x b/noao/onedspec/ecidentify/eclinelist.x
new file mode 100644
index 00000000..6653dd4b
--- /dev/null
+++ b/noao/onedspec/ecidentify/eclinelist.x
@@ -0,0 +1,281 @@
+include <error.h>
+include <mach.h>
+include <smw.h>
+include <units.h>
+include "ecidentify.h"
+
+# EC_MAPLL -- Read the line list into memory.
+
+procedure ec_mapll (ec)
+
+pointer ec # Echelle pointer
+
+int fd, nalloc, nlines, open(), fscan(), nscan()
+double value, lastval
+pointer ec_ll
+pointer sp, str, units, un_open()
+bool streq()
+errchk open, fscan, malloc, realloc, un_open
+
+begin
+ EC_LL(ec) = NULL
+
+ call xt_stripwhite (Memc[EC_COORDLIST(ec)])
+ if (Memc[EC_COORDLIST(ec)] == EOS)
+ return
+ iferr (fd = open (Memc[EC_COORDLIST(ec)], READ_ONLY, TEXT_FILE))
+ return
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (units, SZ_LINE, TY_CHAR)
+ call strcpy ("Angstroms", Memc[units], SZ_LINE)
+
+ lastval = -MAX_DOUBLE
+ nalloc = 0
+ nlines = 0
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() != 1)
+ next
+ if (Memc[str] == '#') {
+ call gargwrd (Memc[str], SZ_LINE)
+ call strlwr (Memc[str])
+ if (streq (Memc[str], "units")) {
+ call gargstr (Memc[units], SZ_LINE)
+ call xt_stripwhite (Memc[units])
+ }
+ next
+ }
+ call reset_scan ()
+
+ call gargd (value)
+ if (nscan() != 1)
+ next
+
+ if (nalloc == 0) {
+ nalloc = 100
+ call malloc (ec_ll, nalloc, TY_DOUBLE)
+ } else if (nlines == nalloc) {
+ nalloc = nalloc + 100
+ call realloc (ec_ll, nalloc, TY_DOUBLE)
+ }
+
+ if (value < lastval) {
+ call close (fd)
+ call mfree (ec_ll, TY_DOUBLE)
+ call error (0, "Line list not sorted in increasing order")
+ }
+
+ Memd[ec_ll+nlines] = value
+ nlines = nlines + 1
+ }
+ call close (fd)
+
+ if (nlines > 0) {
+ call realloc (ec_ll, nlines + 1, TY_DOUBLE)
+ Memd[ec_ll+nlines] = INDEFD
+ EC_LL(ec) = ec_ll
+
+ if (EC_UN(ec) == NULL && Memc[units] != EOS)
+ EC_UN(ec) = un_open (Memc[units])
+ call ec_unitsll (ec, Memc[units])
+ }
+
+ call sfree (sp)
+end
+
+
+# EC_UNMAPLL -- Unmap the linelist.
+
+procedure ec_unmapll (ec)
+
+pointer ec # Line list pointer
+
+begin
+ call mfree (EC_LL(ec), TY_DOUBLE)
+end
+
+
+# EC_UNITSLL -- Change the line list units from the input units to the
+# units given by EC_UN. This may involve reversing the order of the list.
+
+procedure ec_unitsll (ec, units)
+
+pointer ec # Identify structure
+char units[ARB] # Input units
+
+int i, nll
+double value
+pointer un, ll, llend, un_open()
+bool un_compare()
+errchk un_open
+
+begin
+ if (EC_LL(ec) == NULL)
+ return
+ if (IS_INDEFD(Memd[EC_LL(ec)]))
+ return
+ if (units[1] == EOS || EC_UN(ec) == NULL)
+ return
+ if (UN_CLASS(EC_UN(ec)) == UN_UNKNOWN)
+ return
+
+ un = un_open (units)
+ if (un_compare (un, EC_UN(ec))) {
+ call un_close (un)
+ return
+ }
+
+ ll = EC_LL(ec)
+ do i = 0, ARB
+ if (IS_INDEFD(Memd[ll+i])) {
+ nll = i
+ break
+ }
+ call un_ctrand (un, EC_UN(ec), Memd[ll], Memd[ll], nll)
+ call un_close (un)
+
+ if (Memd[ll] > Memd[ll+nll-1]) {
+ llend = ll + nll - 1
+ do i = 0, nll / 2 - 1 {
+ value = Memd[ll+i]
+ Memd[ll+i] = Memd[llend-i]
+ Memd[llend-i] = value
+ }
+ }
+end
+
+
+
+# EC_MATCH -- Match current feature against a line list.
+
+procedure ec_match (ec, in, out)
+
+pointer ec # Echelle pointer
+double in # Coordinate to be matched
+double out # Matched coordinate
+
+double match, alpha, delta, delta1, delta2, out1
+pointer ll
+
+begin
+ if (EC_LL(ec) == NULL) {
+ out = in
+ return
+ }
+
+ match = EC_MATCH(ec)
+ alpha = 1.25
+ delta1 = MAX_REAL
+
+ # Find nearest match.
+ for (ll=EC_LL(ec); !IS_INDEFD(Memd[ll]); ll = ll + 1) {
+ delta = abs (in - Memd[ll])
+ if (delta < delta1) {
+ delta2 = delta1
+ delta1 = delta
+ if (delta1 <= match)
+ out1 = Memd[ll]
+ }
+ }
+
+ # Only return match if no other candidate is also possible.
+ if (delta1 > match)
+ return
+ if (delta2 < alpha * delta1)
+ return
+
+ out = out1
+end
+
+# EC_LINELIST -- Add features from a line list.
+
+procedure ec_linelist (ec)
+
+pointer ec # Echelle pointer
+
+int i, line, ap, nfound, nextpix
+double pix, fit, user, peak, minval, match, fit1, fit2
+pointer sp, aps, pixes, fits, users, peaks, ll
+
+double ec_center(), ec_fittopix(), ec_fitpt(), ec_peak()
+
+begin
+ if (EC_LL(ec) == NULL)
+ return
+
+ call smark (sp)
+ call salloc (aps, EC_MAXFEATURES(ec), TY_INT)
+ call salloc (pixes, EC_MAXFEATURES(ec), TY_DOUBLE)
+ call salloc (fits, EC_MAXFEATURES(ec), TY_DOUBLE)
+ call salloc (users, EC_MAXFEATURES(ec), TY_DOUBLE)
+ call salloc (peaks, EC_MAXFEATURES(ec), TY_DOUBLE)
+
+ nfound = 0
+ minval = MAX_REAL
+
+ do line = 1, EC_NLINES(ec) {
+ call ec_gline (ec, line)
+ ap = APS(ec,line)
+ fit1 = min (FITDATA(ec,1), FITDATA(ec,EC_NPTS(ec)))
+ fit2 = max (FITDATA(ec,1), FITDATA(ec,EC_NPTS(ec)))
+ for (ll=EC_LL(ec); !IS_INDEFD(Memd[ll]); ll = ll + 1) {
+ user = Memd[ll]
+ if (user < fit1)
+ next
+ if (user > fit2)
+ break
+
+ pix = ec_center (ec, ec_fittopix (ec, user), EC_FWIDTH(ec),
+ EC_FTYPE(ec))
+ if (!IS_INDEFD(pix)) {
+ fit = ec_fitpt (ec, ap, pix)
+ match = abs (fit - user)
+ if (match > EC_MATCH(ec))
+ next
+
+ peak = abs (ec_peak (ec, pix))
+ if (nfound < EC_MAXFEATURES(ec)) {
+ nfound = nfound + 1
+ if (peak < minval) {
+ nextpix = nfound
+ minval = peak
+ }
+ Memi[aps+nfound-1] = ap
+ Memd[pixes+nfound-1] = pix
+ Memd[fits+nfound-1] = fit
+ Memd[users+nfound-1] = user
+ Memd[peaks+nfound-1] = peak
+ } else if (peak > minval) {
+ Memi[aps+nextpix-1] = ap
+ Memd[pixes+nextpix-1] = pix
+ Memd[fits+nextpix-1] = fit
+ Memd[users+nextpix-1] = user
+ Memd[peaks+nextpix-1] = peak
+
+ minval = MAX_REAL
+ do i = 1, nfound {
+ peak = Memd[peaks+i-1]
+ if (peak < minval) {
+ nextpix = i
+ minval = peak
+ }
+ }
+ }
+ }
+ }
+ }
+ call ec_gline (ec, EC_LINE(ec))
+
+ do i = 1, nfound {
+ ap = Memi[aps+i-1]
+ pix = Memd[pixes+i-1]
+ fit = Memd[fits+i-1]
+ user = Memd[users+i-1]
+ call ec_newfeature (ec, ap, pix, fit, user, EC_FWIDTH(ec),
+ EC_FTYPE(ec))
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecidentify/eclog.x b/noao/onedspec/ecidentify/eclog.x
new file mode 100644
index 00000000..e2730ca0
--- /dev/null
+++ b/noao/onedspec/ecidentify/eclog.x
@@ -0,0 +1,77 @@
+include <time.h>
+include "ecidentify.h"
+
+# EC_LOG -- Write log
+
+procedure ec_log (ec, file)
+
+pointer ec # ID pointer
+char file[ARB] # Log file
+
+char str[SZ_TIME]
+int i, fd, nrms
+double resid, rms
+
+int open()
+long clktime()
+errchk open()
+
+begin
+ if (EC_NFEATURES(ec) == 0)
+ return
+
+ fd = open (file, APPEND, TEXT_FILE)
+
+ call cnvtime (clktime (0), str, SZ_TIME)
+ call fprintf (fd, "\n%s\n")
+ call pargstr (str)
+ call fprintf (fd, "Features identified in image %s.\n")
+ call pargstr (Memc[EC_IMAGE(ec)])
+
+ call fprintf (fd, " %3s %4s %5s %8s %10s %10s %10s %6s %6d\n")
+ call pargstr ("Ap")
+ call pargstr ("Line")
+ call pargstr ("Order")
+ call pargstr ("Pixel")
+ call pargstr ("Fit")
+ call pargstr ("User")
+ call pargstr ("Residual")
+ call pargstr ("Fwidth")
+ call pargstr ("Reject")
+
+ rms = 0.
+ nrms = 0
+ do i = 1, EC_NFEATURES(ec) {
+ call fprintf (fd,
+ "%5d %3d %4d %5d %8.2f %10.8g %10.8g %10.8g %6.2f %6b\n")
+ call pargi (i)
+ call pargi (APN(ec,i))
+ call pargi (LINE(ec,i))
+ call pargi (ORDER(ec,i))
+ call pargd (PIX(ec,i))
+ call pargd (FIT(ec,i))
+ call pargd (USER(ec,i))
+ if (IS_INDEFD (USER(ec,i)))
+ call pargd (USER(ec,i))
+ else {
+ resid = FIT(ec,i) - USER(ec,i)
+ call pargd (resid)
+ if (FTYPE(ec,i) > 0) {
+ rms = rms + resid ** 2
+ nrms = nrms + 1
+ }
+ }
+ call pargr (FWIDTH(ec,i))
+ if (FTYPE(ec,i) > 0)
+ call pargb (false)
+ else
+ call pargb (true)
+ }
+
+ if (nrms > 1) {
+ call fprintf (fd, "RMS = %0.8g\n")
+ call pargd (sqrt (rms / nrms))
+ }
+
+ call close (fd)
+end
diff --git a/noao/onedspec/ecidentify/ecmark.x b/noao/onedspec/ecidentify/ecmark.x
new file mode 100644
index 00000000..58b02d0f
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecmark.x
@@ -0,0 +1,71 @@
+include <gset.h>
+include <pkg/center1d.h>
+include "ecidentify.h"
+
+procedure ec_mark (ec, feature)
+
+pointer ec # ID pointer
+int feature
+
+int pix
+real x, y
+real mx, my, x1, x2, y1, y2, tick, gap
+pointer sp, format, label
+double smw_c1trand()
+
+define TICK .03 # Tick size in NDC
+define GAP .02 # Gap size in NDC
+
+begin
+ call ggwind (EC_GP(ec), x1, x2, y1, y2)
+
+ x = FIT(ec,feature)
+
+ if ((x < min (x1, x2)) || (x > max (x1, x2)))
+ return
+
+ pix = smw_c1trand (EC_PL(ec), PIX(ec,feature))
+ pix = max (1, min (pix, EC_NPTS(ec) - 1))
+
+ call smark (sp)
+ call salloc (format, SZ_LINE, TY_CHAR)
+ call salloc (label, SZ_LINE, TY_CHAR)
+ switch (EC_FTYPE(ec)) {
+ case EMISSION:
+ y = max (IMDATA(ec,pix), IMDATA(ec,pix+1))
+ tick = TICK
+ gap = GAP
+ call strcpy ("u=180;h=c;v=b;s=0.5", Memc[format], SZ_LINE)
+ case ABSORPTION:
+ y = min (IMDATA(ec,pix), IMDATA(ec,pix+1))
+ tick = -TICK
+ gap = -GAP
+ call strcpy ("u=0;h=c;v=t;s=0.5", Memc[format], SZ_LINE)
+ }
+
+ call gctran (EC_GP(ec), x, y, mx, my, 1, 0)
+ call gctran (EC_GP(ec), mx, my + gap, x1, y1, 0, 1)
+ call gctran (EC_GP(ec), mx, my + gap + tick, x1, y2, 0, 1)
+ call gline (EC_GP(ec), x1, y1, x1, y2)
+
+ call gctran (EC_GP(ec), mx, my + tick + 2 * gap, x1, y2, 0, 1)
+ switch (EC_LABELS(ec)) {
+ case 2:
+ call sprintf (Memc[label], SZ_LINE, "%d")
+ call pargi (feature)
+ call gtext (EC_GP(ec), x1, y2, Memc[label], Memc[format])
+ case 3:
+ call sprintf (Memc[label], SZ_LINE, "%0.2f")
+ call pargd (PIX(ec,feature))
+ call gtext (EC_GP(ec), x1, y2, Memc[label], Memc[format])
+ case 4:
+ if (!IS_INDEFD (USER(ec,feature))) {
+ call sprintf (Memc[label], SZ_LINE, "%0.4f")
+ call pargd (USER(ec,feature))
+ call gtext (EC_GP(ec), x1, y2, Memc[label], Memc[format])
+ }
+ }
+
+ call sfree (sp)
+ call gflush (EC_GP(ec))
+end
diff --git a/noao/onedspec/ecidentify/ecnearest.x b/noao/onedspec/ecidentify/ecnearest.x
new file mode 100644
index 00000000..7b061472
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecnearest.x
@@ -0,0 +1,26 @@
+include <mach.h>
+include "ecidentify.h"
+
+# EC_NEAREST -- Find the nearest feature to a given coordinate.
+
+procedure ec_nearest (ec, fitnear)
+
+pointer ec # ID pointer
+double fitnear # Coordinate to find nearest feature
+
+int i, ec_next()
+double delta, delta1
+
+begin
+ EC_CURRENT(ec) = 0
+
+ i = 0
+ delta = MAX_REAL
+ while (ec_next (ec, i) != EOF) {
+ delta1 = abs (FIT(ec,i) - fitnear)
+ if (delta1 < delta) {
+ EC_CURRENT(ec) = i
+ delta = delta1
+ }
+ }
+end
diff --git a/noao/onedspec/ecidentify/ecnewfeature.x b/noao/onedspec/ecidentify/ecnewfeature.x
new file mode 100644
index 00000000..525c034a
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecnewfeature.x
@@ -0,0 +1,91 @@
+include <mach.h>
+include <smw.h>
+include "ecidentify.h"
+
+# EC_NEWFEATURE -- Allocate and initialize memory for a new feature.
+
+procedure ec_newfeature (ec, ap, pix, fit, user, width, type)
+
+pointer ec # ID pointer
+int ap # Order
+double pix # Pixel coordinate
+double fit # Fit coordinate
+double user # User coordinate
+real width # Feature width
+int type # Feature type
+
+int i, j, ec_line()
+double delta
+
+define NALLOC 20 # Length of additional allocations
+
+begin
+ if (IS_INDEFD (pix))
+ return
+
+ delta = MAX_REAL
+ do i = 1, EC_NFEATURES(ec) {
+ if (APN(ec,i) != ap)
+ next
+ if (abs (pix - PIX(ec,i)) < delta) {
+ delta = abs (pix - PIX(ec,i))
+ j = i
+ }
+ }
+
+ if (delta >= EC_MINSEP(ec)) {
+ EC_NFEATURES(ec) = EC_NFEATURES(ec) + 1
+ if (EC_NALLOC(ec) < EC_NFEATURES(ec)) {
+ EC_NALLOC(ec) = EC_NALLOC(ec) + NALLOC
+ call realloc (EC_APNUM(ec), EC_NALLOC(ec), TY_INT)
+ call realloc (EC_LINENUM(ec), EC_NALLOC(ec), TY_INT)
+ call realloc (EC_ORD(ec), EC_NALLOC(ec), TY_INT)
+ call realloc (EC_PIX(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call realloc (EC_FIT(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call realloc (EC_USER(ec), EC_NALLOC(ec), TY_DOUBLE)
+ call realloc (EC_FWIDTHS(ec), EC_NALLOC(ec), TY_REAL)
+ call realloc (EC_FTYPES(ec), EC_NALLOC(ec), TY_INT)
+ }
+ for (j=EC_NFEATURES(ec); (j>1)&&(ap<APN(ec,j-1)); j=j-1) {
+ APN(ec,j) = APN(ec,j-1)
+ LINE(ec,j) = LINE(ec,j-1)
+ ORDER(ec,j) = ORDER(ec,j-1)
+ PIX(ec,j) = PIX(ec,j-1)
+ FIT(ec,j) = FIT(ec,j-1)
+ USER(ec,j) = USER(ec,j-1)
+ FWIDTH(ec,j) = FWIDTH(ec,j-1)
+ FTYPE(ec,j) = FTYPE(ec,j-1)
+ }
+ for (; (j>1)&&(ap==APN(ec,j-1))&&(pix<PIX(ec,j-1)); j=j-1) {
+ APN(ec,j) = APN(ec,j-1)
+ LINE(ec,j) = LINE(ec,j-1)
+ ORDER(ec,j) = ORDER(ec,j-1)
+ PIX(ec,j) = PIX(ec,j-1)
+ FIT(ec,j) = FIT(ec,j-1)
+ USER(ec,j) = USER(ec,j-1)
+ FWIDTH(ec,j) = FWIDTH(ec,j-1)
+ FTYPE(ec,j) = FTYPE(ec,j-1)
+ }
+ APN(ec,j) = ap
+ LINE(ec,j) = ec_line (ec, ap)
+ ORDER(ec,j) = ORDERS(ec,LINE(ec,j))
+ PIX(ec,j) = pix
+ FIT(ec,j) = fit
+ USER(ec,j) = user
+ FWIDTH(ec,j) = width
+ FTYPE(ec,j) = type
+ EC_NEWFEATURES(ec) = YES
+ } else if (abs (fit-user) < abs (FIT(ec,j)-USER(ec,j))) {
+ APN(ec,j) = ap
+ LINE(ec,j) = ec_line (ec, ap)
+ ORDER(ec,j) = ORDERS(ec,LINE(ec,j))
+ PIX(ec,j) = pix
+ FIT(ec,j) = fit
+ USER(ec,j) = user
+ FWIDTH(ec,j) = width
+ FTYPE(ec,j) = type
+ EC_NEWFEATURES(ec) = YES
+ }
+
+ EC_CURRENT(ec) = j
+end
diff --git a/noao/onedspec/ecidentify/ecnext.x b/noao/onedspec/ecidentify/ecnext.x
new file mode 100644
index 00000000..1028371d
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecnext.x
@@ -0,0 +1,23 @@
+include "ecidentify.h"
+
+# EC_NEXT -- Return the next feature.
+
+int procedure ec_next (ec, feature)
+
+pointer ec # ID pointer
+int feature # Starting feature (input), next feature (returned)
+
+int i
+
+begin
+ for (i=feature+1; i<=EC_NFEATURES(ec); i=i+1)
+ if (APN(ec,i) == EC_AP(ec))
+ break
+
+ if (i <= EC_NFEATURES(ec))
+ feature = i
+ else
+ i = EOF
+
+ return (i)
+end
diff --git a/noao/onedspec/ecidentify/ecpeak.x b/noao/onedspec/ecidentify/ecpeak.x
new file mode 100644
index 00000000..f797fbac
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecpeak.x
@@ -0,0 +1,24 @@
+include "ecidentify.h"
+
+# EC_PEAK -- Find the peak value above continuum.
+
+double procedure ec_peak (ec, pix)
+
+pointer ec # ID pointer
+double pix # Pixel position
+double peak # Peak value
+
+int c, l, u
+
+begin
+ if (IS_INDEFD(pix))
+ return (INDEFD)
+
+ c = nint (pix)
+ l = max (1, nint (pix - EC_FWIDTH(ec)))
+ u = min (EC_NPTS(ec), nint (pix + EC_FWIDTH(ec)))
+ peak = IMDATA(ec,c) - (IMDATA(ec,l) +
+ IMDATA(ec,u)) / 2.
+
+ return (peak)
+end
diff --git a/noao/onedspec/ecidentify/ecprevious.x b/noao/onedspec/ecidentify/ecprevious.x
new file mode 100644
index 00000000..4301b722
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecprevious.x
@@ -0,0 +1,23 @@
+include "ecidentify.h"
+
+# EC_PREVIOUS -- Return the previous feature.
+
+int procedure ec_previous (ec, feature)
+
+pointer ec # ID pointer
+int feature # Starting feature (input), previous feature (returned)
+
+int i
+
+begin
+ for (i=feature-1; i>0; i=i-1)
+ if (APN(ec,i) == EC_AP(ec))
+ break
+
+ if (i > 0)
+ feature = i
+ else
+ i = EOF
+
+ return (i)
+end
diff --git a/noao/onedspec/ecidentify/ecrms.x b/noao/onedspec/ecidentify/ecrms.x
new file mode 100644
index 00000000..de84ae26
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecrms.x
@@ -0,0 +1,28 @@
+include "ecidentify.h"
+
+# EC_RMS -- Compute RMS of fit about the user coordinates
+
+double procedure ec_rms (ec)
+
+pointer ec # ID pointer
+
+int i, nrms
+double rms
+
+begin
+ rms = 0.
+ nrms = 0
+ for (i=1; i<=EC_NFEATURES(ec); i=i+1) {
+ if (!IS_INDEFD (USER(ec,i)) && FTYPE(ec,i) > 0) {
+ rms = rms + (FIT(ec,i) - USER(ec,i)) ** 2
+ nrms = nrms + 1
+ }
+ }
+
+ if (nrms > 0)
+ rms = sqrt (rms / nrms)
+ else
+ rms = INDEFD
+
+ return (rms)
+end
diff --git a/noao/onedspec/ecidentify/ecshift.x b/noao/onedspec/ecidentify/ecshift.x
new file mode 100644
index 00000000..22b050a7
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecshift.x
@@ -0,0 +1,77 @@
+include <smw.h>
+include "ecidentify.h"
+
+define NBIN 10 # Bin parameter for mode determination
+
+# EC_SHIFT -- Determine a shift by correlating feature user positions
+# with peaks in the image data.
+
+double procedure ec_shift (ec)
+
+pointer ec # EC pointer
+
+int i, j, k, ap, order, nx, ndiff, find_peaks()
+real d, dmin
+double pix, ec_center(), ec_fitpt()
+pointer x, y, diff
+errchk malloc, realloc, find_peaks
+
+begin
+ ndiff = 0
+ call malloc (x, EC_NCOLS(ec), TY_REAL)
+ call malloc (y, EC_NCOLS(ec), TY_DOUBLE)
+ do k = 1, EC_NLINES(ec) {
+ call ec_gline (ec, k)
+ ap = APS(ec,k)
+ order = ORDERS(ec,k)
+
+ # Find the peaks in the image data.
+ i = max (5, EC_MAXFEATURES(ec) / EC_NLINES(ec))
+ nx = find_peaks (IMDATA(ec,1), Memr[x], EC_NPTS(ec), 0.,
+ int (EC_MINSEP(ec)), 0, i, 0., false)
+
+ # Center the peaks and convert to user coordinates.
+ j = 0
+ do i = 1, nx {
+ pix = Memr[x+i-1]
+ pix = ec_center (ec, pix, EC_FWIDTH(ec), EC_FTYPE(ec))
+ if (!IS_INDEFD (pix)) {
+ Memd[y+j] = ec_fitpt (ec, ap, pix)
+ j = j + 1
+ }
+ }
+ nx = j
+
+ # Compute differences with feature list.
+ do i = 1, EC_NFEATURES(ec) {
+ if (APN(ec,i) != ap)
+ next
+ if (ndiff == 0)
+ call malloc (diff, nx, TY_REAL)
+ else
+ call realloc (diff, ndiff+nx, TY_REAL)
+ do j = 1, nx {
+ Memr[diff+ndiff] = (Memd[y+j-1] - FIT(ec,i)) * order
+ ndiff = ndiff + 1
+ }
+ }
+ }
+ call mfree (x, TY_REAL)
+ call mfree (y, TY_DOUBLE)
+
+ # Sort the differences and find the mode.
+ call asrtr (Memr[diff], Memr[diff], ndiff)
+
+ dmin = Memr[diff+ndiff-1] - Memr[diff]
+ do i = 0, ndiff-NBIN-1 {
+ j = i + NBIN
+ d = Memr[diff+j] - Memr[diff+i]
+ if (d < dmin) {
+ dmin = d
+ pix = Memr[diff+i] + d / 2.
+ }
+ }
+ call mfree (diff, TY_REAL)
+
+ return (pix)
+end
diff --git a/noao/onedspec/ecidentify/ecshow.x b/noao/onedspec/ecidentify/ecshow.x
new file mode 100644
index 00000000..e8fb5acc
--- /dev/null
+++ b/noao/onedspec/ecidentify/ecshow.x
@@ -0,0 +1,78 @@
+include <pkg/center1d.h>
+include "ecidentify.h"
+
+# EC_SHOW -- Show parameter information.
+
+procedure ec_show (ec, file)
+
+pointer ec # ID pointer
+char file[ARB] # File
+
+char line[SZ_LINE]
+int fd
+
+int open(), ecf_geti()
+double ecf_getd()
+errchk open()
+
+begin
+ fd = open (file, APPEND, TEXT_FILE)
+
+ call sysid (line, SZ_LINE)
+ call fprintf (fd, "%s\n")
+ call pargstr (line)
+
+ call fprintf (fd, "image %s\n")
+ call pargstr (Memc[EC_IMAGE(ec)])
+ switch (EC_FTYPE(ec)) {
+ case EMISSION:
+ call fprintf (fd, "ftype emission\n")
+ case ABSORPTION:
+ call fprintf (fd, "ftype absorption\n")
+ }
+ switch (EC_LABELS(ec)) {
+ case 2:
+ call fprintf (fd, "labels index\n")
+ case 3:
+ call fprintf (fd, "labels pixel\n")
+ case 4:
+ call fprintf (fd, "labels user\n")
+ default:
+ call fprintf (fd, "labels none\n")
+ }
+ call fprintf (fd, "maxfeatures %d\n")
+ call pargi (EC_MAXFEATURES(ec))
+ call fprintf (fd, "match %g\n")
+ call pargr (EC_MATCH(ec))
+ call fprintf (fd, "zwidth %g\n")
+ call pargr (EC_ZWIDTH(ec))
+ call fprintf (fd, "fwidth %g\n")
+ call pargr (EC_FWIDTH(ec))
+ call fprintf (fd, "database %s\n")
+ call pargstr (Memc[EC_DATABASE(ec)])
+ call fprintf (fd, "coordlist %s\n")
+ call pargstr (Memc[EC_COORDLIST(ec)])
+ call fprintf (fd, "cradius %g\n")
+ call pargr (EC_CRADIUS(ec))
+ call fprintf (fd, "threshold %g\n")
+ call pargr (EC_THRESHOLD(ec))
+ call fprintf (fd, "minsep %g\n")
+ call pargr (EC_MINSEP(ec))
+ if (EC_ECF(ec) != NULL) {
+ call fprintf (fd, "function = %s\n")
+ call ecf_gets ("function", line, SZ_LINE)
+ call pargstr (line)
+ call fprintf (fd, "xorder = %d, yorder = %d\n")
+ call pargi (ecf_geti ("xorder"))
+ call pargi (ecf_geti ("yorder"))
+ call fprintf (fd,
+ "niterate = %d, lowreject = %g, highreject = %g\n")
+ call pargi (ecf_geti ("niterate"))
+ call pargd (ecf_getd ("low"))
+ call pargd (ecf_getd ("high"))
+ call fprintf (fd, "Fit at first pixel = %0.8g\n")
+ call pargd (Memd[EC_FITDATA(ec)])
+ }
+
+ call close (fd)
+end
diff --git a/noao/onedspec/ecidentify/mkpkg b/noao/onedspec/ecidentify/mkpkg
new file mode 100644
index 00000000..1c8664a7
--- /dev/null
+++ b/noao/onedspec/ecidentify/mkpkg
@@ -0,0 +1,39 @@
+# ECIDENTIFY Task
+
+$checkout libpkg.a ..
+$update libpkg.a
+$checkin libpkg.a ..
+$exit
+
+libpkg.a:
+ @ecffit
+
+ eccenter.x ecidentify.h
+ eccolon.x ecidentify.h <error.h> <gset.h> <pkg/center1d.h>
+ ecdb.x ecidentify.h <math/gsurfit.h> <smw.h> <units.h>
+ ecdelete.x ecidentify.h
+ ecdofit.x ecidentify.h <smw.h>
+ ecdoshift.x ecidentify.h
+ ecfitdata.x ecidentify.h <pkg/gtools.h> <smw.h> <units.h>
+ ecgdata.x ecidentify.h <imhdr.h> <imio.h> <pkg/gtools.h> <smw.h>\
+ <units.h>
+ ecgetim.x
+ ecgline.x ecidentify.h <smw.h>
+ ecgraph.x ecidentify.h <gset.h> <pkg/gtools.h>
+ ecidentify.x ecidentify.h <error.h> <gset.h> <imhdr.h> <smw.h>
+ ecinit.x ecidentify.h <gset.h>
+ ecline.x ecidentify.h <smw.h>
+ eclinelist.x ecidentify.h <error.h> <mach.h> <smw.h> <units.h>
+ eclog.x ecidentify.h <time.h>
+ ecmark.x ecidentify.h <gset.h> <pkg/center1d.h>
+ ecnearest.x ecidentify.h <mach.h>
+ ecnewfeature.x ecidentify.h <mach.h> <smw.h>
+ ecnext.x ecidentify.h
+ ecpeak.x ecidentify.h
+ ecprevious.x ecidentify.h
+ ecrms.x ecidentify.h
+ ecshift.x ecidentify.h <smw.h>
+ ecshow.x ecidentify.h <pkg/center1d.h>
+ t_eciden.x ecidentify.h <mach.h> <pkg/center1d.h> <pkg/gtools.h>
+ t_ecreid.x ecidentify.h <error.h> <smw.h>
+ ;
diff --git a/noao/onedspec/ecidentify/t_eciden.x b/noao/onedspec/ecidentify/t_eciden.x
new file mode 100644
index 00000000..7590dc17
--- /dev/null
+++ b/noao/onedspec/ecidentify/t_eciden.x
@@ -0,0 +1,68 @@
+include <mach.h>
+include <pkg/gtools.h>
+include <pkg/center1d.h>
+include "ecidentify.h"
+
+# T_ECIDENTIFY -- Identify features in echelle format data.
+#
+# The input data must be in the echelle format produced by APEXTRACT.
+
+procedure t_ecidentify ()
+
+int images
+pointer ec, gopen(), gt_init(), un_open()
+int clgeti(), clgwrd(), imtopenp(), ec_getim()
+real clgetr()
+double clgetd()
+
+begin
+ # Allocate the basic data structure.
+ call ec_init (ec)
+
+ # Get task parameters.
+ images = imtopenp ("images")
+ EC_MAXFEATURES(ec) = clgeti ("maxfeatures")
+ EC_MINSEP(ec) = clgetr ("minsep")
+ EC_MATCH(ec) = clgetr ("match")
+ EC_ZWIDTH(ec) = clgetr ("zwidth")
+ EC_FTYPE(ec) = clgwrd ("ftype", Memc[EC_IMAGE(ec)], SZ_FNAME, FTYPES)
+ EC_FWIDTH(ec) = clgetr ("fwidth")
+ EC_CRADIUS(ec) = clgetr ("cradius")
+ EC_THRESHOLD(ec) = clgetr ("threshold")
+ call clgstr ("database", Memc[EC_DATABASE(ec)], SZ_FNAME)
+ call clgstr ("coordlist", Memc[EC_COORDLIST(ec)], SZ_FNAME)
+
+ # Get the line list.
+ call clgstr ("units", Memc[EC_IMAGE(ec)], SZ_FNAME)
+ call xt_stripwhite (Memc[EC_IMAGE(ec)])
+ if (Memc[EC_IMAGE(ec)] != EOS)
+ EC_UN(ec) = un_open (Memc[EC_IMAGE(ec)])
+ call ec_mapll (ec)
+
+ # Initialize graphics and fitting.
+ call clgstr ("function", Memc[EC_IMAGE(ec)], SZ_FNAME)
+ call ecf_sets ("function", Memc[EC_IMAGE(ec)])
+ call ecf_seti ("xorder", clgeti ("xorder"))
+ call ecf_seti ("yorder", clgeti ("yorder"))
+ call ecf_seti ("niterate", clgeti ("niterate"))
+ call ecf_setd ("low", clgetd ("lowreject"))
+ call ecf_setd ("high", clgetd ("highreject"))
+ call ecf_seti ("xtype", 'p')
+ call ecf_seti ("ytype", 'r')
+ call clgstr ("graphics", Memc[EC_IMAGE(ec)], SZ_FNAME)
+ EC_GP(ec) = gopen (Memc[EC_IMAGE(ec)], NEW_FILE, STDGRAPH)
+ EC_GT(ec) = gt_init()
+ call gt_sets (EC_GT(ec), GTTYPE, "line")
+
+ # Identify features in each image.
+ while (ec_getim (images, Memc[EC_IMAGE(ec)], SZ_FNAME) != EOF)
+ call ec_identify (ec)
+
+ # Finish up.
+ call gclose (EC_GP(ec))
+ call gt_free (EC_GT(ec))
+ call dgsfree (EC_ECF(ec))
+ call imtclose (images)
+ call ec_unmapll (ec)
+ call ec_free (ec)
+end
diff --git a/noao/onedspec/ecidentify/t_ecreid.x b/noao/onedspec/ecidentify/t_ecreid.x
new file mode 100644
index 00000000..5e9769ff
--- /dev/null
+++ b/noao/onedspec/ecidentify/t_ecreid.x
@@ -0,0 +1,181 @@
+include <error.h>
+include <smw.h>
+include "ecidentify.h"
+
+# T_ECREIDENTIFY -- Reidentify echelle features starting from reference.
+# If no initial shift is given then the procedure ec_shift computes a
+# shift between the reference features and the features in the image.
+# The purpose of the shift is to get the feature positions from the
+# reference image close enough to those of the image being identified
+# that the centering algorithm will determine the exact positions of the
+# features. The recentered features are then fit with either a shift
+# of a full echelle function and written to database.
+
+procedure t_ecreidentify ()
+
+int images # List of images
+pointer ref # Reference image
+double shift # Initial shift
+
+int i, j, fd, nfeatures1, nfeatures2
+double shift1, pix, fit, pix_shift, fit_shift, z_shift
+pointer sp, log, ec
+
+int imtopenp(), ec_getim(), clpopnu(), clgfil(), open(), btoi()
+double ec_fitpt(), ec_fittopix(), ec_shift(), ec_center(), ec_rms()
+double clgetd()
+bool clgetb()
+real clgetr()
+errchk ec_dbread(), ec_gdata(), ec_fitdata()
+
+begin
+ call smark (sp)
+ call salloc (ref, SZ_FNAME, TY_CHAR)
+ call salloc (log, SZ_FNAME, TY_CHAR)
+
+ # Allocate the basic data structure.
+ call ec_init (ec)
+
+ # Initialize fitting
+ call ecf_seti ("niterate", 0)
+ call ecf_setd ("low", 3.D0)
+ call ecf_setd ("high", 3.D0)
+
+ # Get task parameters.
+ images = imtopenp ("images")
+ call clgstr ("reference", Memc[ref], SZ_FNAME)
+ shift = clgetd ("shift")
+ call clgstr ("database", Memc[EC_DATABASE(ec)], SZ_FNAME)
+ EC_CRADIUS(ec) = clgetr ("cradius")
+ EC_THRESHOLD(ec) = clgetr ("threshold")
+ EC_LOGFILES(ec) = clpopnu ("logfiles")
+ EC_REFIT(ec) = btoi (clgetb ("refit"))
+
+ # Write logfile header.
+ while (clgfil (EC_LOGFILES(ec), Memc[log], SZ_FNAME) != EOF) {
+ iferr (fd = open (Memc[log], APPEND, TEXT_FILE)) {
+ call erract (EA_WARN)
+ next
+ }
+ call sysid (Memc[log], SZ_LINE)
+ call fprintf (fd, "\nECREIDENTIFY: %s\n")
+ call pargstr (Memc[log])
+ call fprintf (fd,
+ " Reference image = %s, Refit = %b\n")
+ call pargstr (Memc[ref])
+ call pargb (EC_REFIT(ec) == YES)
+ call fprintf (fd, "%20s %7s %7s %9s %10s %7s %7s\n")
+ call pargstr ("Image")
+ call pargstr ("Found")
+ call pargstr ("Fit")
+ call pargstr ("Pix Shift")
+ call pargstr ("User Shift")
+ call pargstr ("Z Shift")
+ call pargstr ("RMS")
+ call close (fd)
+ }
+
+ # Reidentify features in each spectrum.
+ while (ec_getim (images, Memc[EC_IMAGE(ec)], SZ_FNAME) != EOF) {
+ call ec_gdata (ec)
+ call ec_dbread (ec, Memc[ref], NO)
+ call ec_fitdata (ec)
+ call ec_fitfeatures (ec)
+
+ if (IS_INDEFD (shift)) {
+ EC_FWIDTH(ec) = FWIDTH(ec,1)
+ EC_FTYPE(ec) = abs (FTYPE(ec,1))
+ EC_MINSEP(ec) = 1.
+ EC_MAXFEATURES(ec) = 20
+ shift1 = ec_shift (ec)
+ } else
+ shift1 = shift
+
+ # Recenter features.
+ pix_shift = 0.
+ fit_shift = 0.
+ z_shift = 0.
+ nfeatures1 = EC_NFEATURES(ec)
+
+ j = 0.
+ do i = 1, EC_NFEATURES(ec) {
+ call ec_gline (ec, LINE(ec,i))
+ pix = ec_fittopix (ec, FIT(ec,i) + shift1/ORDER(ec,i))
+ pix = ec_center (ec, pix, FWIDTH(ec,i), FTYPE(ec,i))
+ if (IS_INDEFD (pix))
+ next
+ fit = ec_fitpt (ec, APN(ec,i), pix)
+
+ pix_shift = pix_shift + pix - PIX(ec,i)
+ fit_shift = fit_shift + (fit - FIT(ec,i)) * ORDER(ec,i)
+ if (FIT(ec,i) != 0.)
+ z_shift = z_shift + (fit - FIT(ec,i)) / FIT(ec,i)
+
+ j = j + 1
+ APN(ec,j) = APN(ec,i)
+ LINE(ec,j) = LINE(ec,i)
+ ORDER(ec,j) = ORDER(ec,i)
+ PIX(ec,j) = pix
+ FIT(ec,j) = FIT(ec,i)
+ USER(ec,j) = USER(ec,i)
+ FWIDTH(ec,j) = FWIDTH(ec,i)
+ FTYPE(ec,j) = abs (FTYPE(ec,i))
+ }
+ EC_NFEATURES(ec) = j
+
+ # If refitting the coordinate function is requested and there
+ # is more than one feature and there is a previously defined
+ # coordinate function then refit. Otherwise compute a coordinate
+ # shift.
+
+ if ((EC_REFIT(ec)==YES)&&(EC_NFEATURES(ec)>1)&&(EC_ECF(ec)!=NULL)) {
+ iferr (call ec_dofit (ec, NO, YES)) {
+ call erract (EA_WARN)
+ next
+ }
+ } else
+ call ec_doshift (ec, NO)
+ if (EC_NEWECF(ec) == YES)
+ call ec_fitfeatures (ec)
+
+ nfeatures2 = 0
+ do i = 1, EC_NFEATURES(ec)
+ if (FTYPE(ec,i) > 0)
+ nfeatures2 = nfeatures2 + 1
+
+ # Write a database entry for the reidentified image.
+ if (EC_NFEATURES(ec) > 0)
+ call ec_dbwrite (ec, Memc[EC_IMAGE(ec)], NO)
+
+ # Record log information if a log file descriptor is given.
+ call clprew (EC_LOGFILES(ec))
+ while (clgfil (EC_LOGFILES(ec), Memc[log], SZ_FNAME) != EOF) {
+ iferr (fd = open (Memc[log], APPEND, TEXT_FILE)) {
+ call erract (EA_WARN)
+ next
+ }
+ call fprintf (fd,
+ "%20s %3d/%-3d %3d/%-3d %9.3g %10.3g %7.3g %7.3g\n")
+ call pargstr (Memc[EC_IMAGE(ec)])
+ call pargi (EC_NFEATURES(ec))
+ call pargi (nfeatures1)
+ call pargi (nfeatures2)
+ call pargi (EC_NFEATURES(ec))
+ call pargd (pix_shift / max (1, EC_NFEATURES(ec)))
+ call pargd (fit_shift / max (1, EC_NFEATURES(ec)))
+ call pargd (z_shift / max (1, EC_NFEATURES(ec)))
+ call pargd (ec_rms(ec))
+ call close (fd)
+ }
+
+ call smw_close (MW(EC_SH(ec)))
+ do i = 1, EC_NLINES(ec)
+ MW(SH(ec,i)) = NULL
+ }
+
+ call dgsfree (EC_ECF(ec))
+ call clpcls (EC_LOGFILES(ec))
+ call ec_free (ec)
+ call imtclose (images)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/ecreidentify.par b/noao/onedspec/ecreidentify.par
new file mode 100644
index 00000000..251675a7
--- /dev/null
+++ b/noao/onedspec/ecreidentify.par
@@ -0,0 +1,11 @@
+# Parameters for ECREIDENTIFY task.
+
+images,s,a,,,,Spectra to be reidentified
+reference,f,a,,,,Reference spectrum
+shift,r,h,0.,,,Shift to add to reference features
+cradius,r,h,5.,,,Centering radius
+threshold,r,h,10.,0.,,Feature threshold for centering
+refit,b,h,yes,,,Refit coordinate function?
+
+database,f,h,database,,,Database
+logfiles,s,h,"STDOUT,logfile",,,List of log files
diff --git a/noao/onedspec/fitprofs.par b/noao/onedspec/fitprofs.par
new file mode 100644
index 00000000..475996ef
--- /dev/null
+++ b/noao/onedspec/fitprofs.par
@@ -0,0 +1,29 @@
+input,s,a,,,,List of input images
+lines,s,h,"",,,List of lines/columns/apertures
+bands,s,h,"",,,Bands in 3D image
+dispaxis,i,h,)_.dispaxis,,,"Image axis for 2D images"
+nsum,s,h,)_.nsum,,,"Number of lines/columns to sum for 2D images
+"
+region,s,h,,,,Fitting region
+positions,f,h,,,,File of positions/sigmas
+background,s,h,"",,,Default background
+profile,s,h,"gaussian","gaussian|lorentzian|voigt",,Default profile type
+gfwhm,r,h,20.,,,Default Gaussian FWHM
+lfwhm,r,h,20.,,,Default Lorentzian FWHM
+fitbackground,b,h,yes,,,Fit background?
+fitpositions,s,h,"all","fixed|single|all",,Fit positions
+fitgfwhm,s,h,"all","fixed|single|all",,"Fit Gaussian FWHM"
+fitlfwhm,s,h,"all","fixed|single|all",,"Fit Lorentzian FWHM
+"
+nerrsample,i,h,0,0,,"Number of error samples (<10 for no errors)"
+sigma0,r,h,INDEF,,,"Constant gaussian noise term (INDEF for no errors)"
+invgain,r,h,INDEF,,,"Inverse gain term (INDEF for no errors)
+"
+components,s,h,"",,,Components to output
+verbose,b,h,yes,,,Standard output?
+logfile,f,h,"logfile",,,Logfile output
+plotfile,f,h,"",,,Plotfile output
+output,s,h,"",,,Image output (list)
+option,s,h,"fit","fit|difference",,Image output option
+clobber,b,h,no,,,Modify output images?
+merge,b,h,no,,,Merge with existing output images?
diff --git a/noao/onedspec/fortran/mkpkg b/noao/onedspec/fortran/mkpkg
new file mode 100644
index 00000000..91b19945
--- /dev/null
+++ b/noao/onedspec/fortran/mkpkg
@@ -0,0 +1,10 @@
+# Fortran subroutines for ONEDSPEC package.
+
+$checkout libpkg.a ../
+$update libpkg.a
+$checkin libpkg.a ../
+$exit
+
+libpkg.a:
+ polft1.f
+ ;
diff --git a/noao/onedspec/fortran/nlcfit.f b/noao/onedspec/fortran/nlcfit.f
new file mode 100644
index 00000000..80aff616
--- /dev/null
+++ b/noao/onedspec/fortran/nlcfit.f
@@ -0,0 +1,400 @@
+ SUBROUTINE NLCFIT(IM,INN,IN,INTA,XEPSI,XV,XYD)
+C+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+C NONLINEAR LEAST SQUARES FITTING USING SIMPLEX
+C METHOD AND QUADRATIC APPROXIMATION.
+C WITH LINEAR PARAMETER ELIMINATION.
+C-------------------------------------------------------------
+ INTEGER IM,INN,IN,INTA
+ REAL XEPSI,XV(IM),XYD(IM)
+ COMMON /NLC/ EPSI,IFLAG,IL,IQ,INDEX,F(15,120),M,N
+ COMMON /NLC/ SOLD,Y(20),YVAL,XF(11),X(11,11),V(120),YD(120,10)
+ COMMON /NLC/ GG(11,11),GINV(11,11),EM(15,120),BB(5),NT
+ COMMON /NLC/ GA(120,5),NN
+ COMMON /NLCOUT/ FF(120),PARS(10),BPARS(10)
+ DIMENSION SUMC(11),XC(11),XE(11),XCO(11),XR(11)
+ REAL LERROR
+C-----
+C RESET ERROR HANDLER
+c...UNIX has general handler only!
+c call trpfpe (0, 0d0)
+C-----
+C FLOAT OVERFLOW
+c CALL ERRSET(72,.TRUE.,.FALSE.,.FALSE.,.FALSE.,15)
+C FLOAT UNDERFLOW
+c CALL ERRSET(74,.TRUE.,.FALSE.,.FALSE.,.FALSE.,15)
+C EXP TOO SMALL
+c CALL ERRSET(89,.TRUE.,.FALSE.,.FALSE.,.FALSE.,15)
+C EXP TOO LRGE
+c CALL ERRSET(88,.TRUE.,.FALSE.,.FALSE.,.FALSE.,15)
+C-----
+ LERROR=1.E30
+ IFLAG=0
+C COEFFICIENTS
+C-----
+C ASSIGN EXTERNAL PARAMETERS
+ M=IM
+ NN=INN
+ N=IN
+ NT=INTA
+ EPSI=XEPSI
+ DO 8100 I=1,M
+ V(I)=XV(I)
+ YD(I,1)=XYD(I)
+8100 CONTINUE
+C-----
+ T=1.0
+ A=1.0
+ B=0.5
+ G=2.0
+ ICOUNT=0
+ INDEX=1
+ IQ=3*N
+ DO 140 J=1,N
+140 X(1,J)=1.0
+160 DO 172 J=1,N
+172 XF(J)=X(1,J)
+ CALL FVAL
+ Y(1)=YVAL
+ SOLD=YVAL
+C---- CONSTRUCT SIMPLEX
+ EN=N
+ PN=(SQRT(EN+1.0)-1.0+EN)/(EN*SQRT(2.0))*T
+ QN=(SQRT(EN+1.0)-1.0)/(EN*SQRT(2.0))*T
+ NP1=N+1
+ DO 305 I=2,NP1
+ INDEX=I
+ DO 300 J=1,N
+ EJ=0.0
+ EI=0.0
+ IF(I-1.NE.J) EJ=1.0
+ IF(I-1.EQ.J) EI=1.0
+ X(I,J)=X(1,J)+EI*PN+EJ*QN
+300 XF(J)=X(I,J)
+ CALL FVAL
+305 Y(I)=YVAL
+C---- DETERMINE MAX XH
+310 IH=1
+ DO 350 J=1,NP1
+ IF(Y(IH).GE.Y(J)) GOTO 350
+ IH=J
+350 CONTINUE
+C---- DETERMINE SECOND MAX XS
+ IS=1
+ IF(IH.NE.1) GOTO 470
+ IS=2
+470 CONTINUE
+ DO 420 J=1,NP1
+ IF(J.EQ.IH) GOTO 420
+ IF(Y(IS).GE.Y(J)) GOTO 420
+ IS=J
+420 CONTINUE
+C---- DETERMINE MIN XL
+ IL=1
+ DO 480 J=1,NP1
+ IF(Y(IL).LE.Y(J)) GOTO 480
+ IL=J
+480 CONTINUE
+C---- COMPUTE CENTROID
+ DO 510 J=1,N
+510 SUMC(J)=0.0
+ EN=N
+ DO 570 J=1,N
+ DO 560 I=1,NP1
+ IF(I.EQ.IH) GOTO 560
+ SUMC(J)=SUMC(J)+X(I,J)
+560 CONTINUE
+570 XC(J)=1.0/EN*SUMC(J)
+ DO 573 J=1,N
+573 XF(J)=XC(J)
+ CALL FVAL
+ YBAR=YVAL
+ SUM=0.0
+ DO 577 I=1,NP1
+ 577 SUM = SUM + ((Y(I)-YBAR)/YBAR)**2
+ ICOUNT=ICOUNT+1
+ ERROR=SQRT(SUM/EN)
+ IQ=IQ-1
+ IF(IQ.EQ.-1) CALL QADFIT
+ IF(IFLAG.EQ.1) GOTO 1990
+ IF(ERROR.LE.EPSI) GOTO 1990
+ IF(ABS(LERROR-ERROR).LT.EPSI) GO TO 1990
+ LERROR=ERROR
+C---- DO A REFLECTION
+ DO 600 J=1,N
+600 XR(J)=(1.0+A)*XC(J)-A*X(IH,J)
+ DO 610 J=1,N
+610 XF(J)=XR(J)
+ INDEX=N+2
+ CALL FVAL
+ YXR=YVAL
+ IF(YXR.GE.Y(IL)) GOTO 750
+C---- DO A EXPANSION
+ DO 660 J=1,N
+660 XE(J)=G*XR(J)+(1.0-G)*XC(J)
+ DO 680 J=1,N
+680 XF(J)=XE(J)
+ INDEX=N+3
+ CALL FVAL
+ YXE=YVAL
+ IF(YXE.GT.Y(IL)) GOTO 760
+ DO 730 J=1,N
+730 X(IH,J)=XE(J)
+ Y(IH)=YXE
+ NP3=N+3
+ DO 735 K=1,M
+735 F(IH,K)=F(NP3,K)
+ GOTO 310
+750 IF(YXR.GT.Y(IS)) GOTO 800
+760 DO 780 J=1,N
+780 X(IH,J)=XR(J)
+ Y(IH)=YXR
+ NP2=N+2
+ DO 785 K=1,M
+785 F(IH,K)=F(NP2,K)
+ GOTO 310
+800 IF(YXR.GT.Y(IH)) GOTO 830
+ DO 820 J=1,N
+820 X(IH,J)=XR(J)
+C---- DO A CONTRACTION
+830 DO 840 J=1,N
+840 XCO(J)=B*X(IH,J)+(1.0-B)*XC(J)
+ DO 860 J=1,N
+860 XF(J)=XCO(J)
+ INDEX=N+2
+ CALL FVAL
+ YXCO=YVAL
+ IF(YXCO.GT.Y(IH)) GOTO 930
+ DO 910 J=1,N
+910 X(IH,J)=XCO(J)
+ Y(IH)=YXCO
+ NP2=N+2
+ DO 915 K=1,M
+915 F(IH,K)=F(NP2,K)
+ GOTO 310
+930 DO 960 I=1,NP1
+ INDEX=I
+ DO 955 J=1,N
+950 X(I,J)=0.5*(X(I,J)+X(IL,J))
+955 XF(J)=X(I,J)
+ CALL FVAL
+960 Y(I)=YVAL
+C---- HAS A MIN BEEN REACHED?
+ GOTO 310
+1990 DO 1594 J=1,N
+ PARS(J)=X(IL,J)
+1594 XF(J)=X(IL,J)
+ CALL FVAL
+ DO 1595 I=1,NT
+1595 BPARS(I)=BB(I)
+ CALL INDEXD
+ RETURN
+ END
+C---------------------------------------------------------------------
+ SUBROUTINE MATIN
+C---- DETERMINE INVERSE OF MATRIX
+ COMMON /NLC/ EPSI,IFLAG,IL,IQ,INDEX,F(15,120),M,N
+ COMMON /NLC/ SOLD,Y(20),YVAL,XF(11),X(11,11),V(120),YD(120,10)
+ COMMON /NLC/ GG(11,11),GINV(11,11),EM(15,120),BB(5),NT
+ COMMON /NLC/ GA(120,5),NN
+ DIMENSION E(15,120),EN(20),T(20),Z(11,11),YY(20)
+ EQUIVALENCE (EM(1,1),E(1,1))
+ DO 20 I=1,N
+ DO 20 J=1,N
+ IF(I.EQ.J) GOTO 10
+ Z(I,J)=0.0
+ GOTO 20
+10 Z(I,J)=1.0
+20 CONTINUE
+ DO 120 J0=1,N
+ I0=J0
+ DO 30 I=1,N
+30 YY(I)=GG(I,J0)
+ DO 40 I=1,N
+ EN(I) = 0.
+ T(I)=0.0
+ DO 40 J=1,N
+40 T(I)=T(I)+Z(I,J)*YY(J)
+ IF(T(J0).EQ.0.) GO TO 65
+ DO 60 J=1,N
+ IF(J.EQ.J0) GOTO 50
+ EN(J)=-T(J)/T(J0)
+ GOTO 60
+50 EN(J)=1./T(J0)
+60 CONTINUE
+ 65 DO 80 I = 1,N
+ DO 80 J=1,N
+ IF (I.EQ.J) GOTO 70
+ E(I,J)=0.0
+ GOTO 80
+70 E(I,J)=1.0
+80 CONTINUE
+ DO 90 J=1,N
+90 E(J,J0)=EN(J)
+ DO 100 K=1,N
+ DO 100 I=1,N
+ GINV(K,I)=0.0
+ DO 100 J=1,N
+100 GINV(K,I)=GINV(K,I)+E(K,J)*Z(J,I)
+ DO 110 J=1,N
+ DO 110 I=1,N
+110 Z(I,J)=GINV(I,J)
+120 CONTINUE
+ RETURN
+ END
+C-------------------------------------------------------------------------
+ SUBROUTINE QADFIT
+ COMMON /NLC/ EPSI,IFLAG,IL,IQ,INDEX,F(15,120),M,N
+ COMMON /NLC/ SOLD,Y(20),YVAL,XF(11),X(11,11),V(120),YD(120,10)
+ COMMON /NLC/ GG(11,11),GINV(11,11),EM(15,120),BB(5),NT
+ COMMON /NLC/ GA(120,5),NN
+ DIMENSION A(11,11),DELX(20),E(20),F0(20)
+ NP1=N+1
+C---- QUADRATIC COEFFICIENTS
+ II=0
+ DO 30 K=1,M
+ II=0
+ DO 30 I=1,NP1
+ IF(I.EQ.IL) GOTO 30
+ II=II+1
+ EM(II,K)=F(I,K)-F(IL,K)
+30 CONTINUE
+ DO 50 I=1,N
+ F0(I)=0.0
+ DO 50 K=1,M
+50 F0(I)=F0(I)-F(IL,K)*EM(I,K)
+C---- ELEMENTS OF THE MATRIX GAMMA,G
+ DO 70 I=1,N
+ DO 70 J=1,N
+ GG(I,J)=0.0
+ DO 70 K=1,M
+70 GG(I,J)=GG(I,J)+EM(I,K)*EM(J,K)
+ CALL MATIN
+ DO 80 I=1,N
+ E(I)=0.0
+ DO 80 J=1,N
+80 E(I)=E(I)+GINV(I,J)*F0(J)
+C---- DEFINE THE SCALING MATRIX A
+ II=0
+ DO 101 I=1,NP1
+ IF(I.EQ.IL) GOTO 101
+ II=II+1
+ DO 100 J=1,N
+ A(II,J)=X(I,J)-X(IL,J)
+100 CONTINUE
+101 CONTINUE
+C---- DETERMINE DEL X
+ DO 110 I=1,N
+ DELX(I)=0.0
+ DO 110 J=1,N
+110 DELX(I)=DELX(I)+A(J,I)*E(J)
+ DO 120 J=1,N
+120 XF(J)=X(IL,J)+DELX(J)
+ INDEX=N+2
+ CALL FVAL
+ IF(Y(IL).LT.YVAL) GOTO 140
+ TEMP=ABS(1-SOLD/YVAL)
+ IF(TEMP.EQ.1) GOTO 150
+ IF(TEMP.LE.EPSI) GOTO 150
+ SOLD=YVAL
+ DO 130 J=1,N
+130 X(IL,J)=XF(J)
+ NP2=N+2
+ DO 135 K=1,M
+135 F(IL,K)=F(NP2,K)
+ IFLAG=2
+ IQ=(3*N)/2
+ GOTO 160
+140 IFLAG=2
+ IQ=3*N
+ GOTO 160
+150 IFLAG=1
+ DO 155 J=1,N
+155 X(IL,J)=XF(J)
+ Y(IL)=YVAL
+160 RETURN
+ END
+C----------------------------------------------------------------------
+ SUBROUTINE INDEXD
+ COMMON /NLC/ EPSI,IFLAG,IL,IQ,INDEX,F(15,120),M,N
+ COMMON /NLC/ SOLD,Y(20),YVAL,XF(11),X(11,11),V(120),YD(120,10)
+ COMMON /NLC/ GG(11,11),GINV(11,11),EM(15,120),BB(5),NT
+ COMMON /NLC/ GA(120,5),NN
+ COMMON /NLCOUT/ FF(120),PARS(10),BPARS(10)
+ SUM=0.0
+ DO 200 I=1,M
+200 SUM=SUM+V(I)
+ XM=M
+ YBAR=SUM/XM
+ SST=0.0
+ DO 240 I=1,M
+240 SST=SST+(V(I)-YBAR)**2
+ SSR=0.0
+ DO 280 I=1,M
+ FF(I)=0.0
+ DO 260 J=1,NT
+260 FF(I)=BB(J)*GA(I,J)+FF(I)
+280 SSR=SSR+(FF(I)-V(I))**2
+ XINDX=1-SSR/SST
+ SIGMAR=SQRT(SSR/XM)
+ DO 300 I=1,M
+ DIFF=FF(I)-V(I)
+ IF(V(I).EQ.0.) GO TO 295
+ DIFF = DIFF*100./V(I)
+ GO TO 300
+295 DIFF=0.
+300 CONTINUE
+C
+C---- WRITE(1) (FF(I),I=1,M)
+C---- WRITE(1) (V(I),I=1,M)
+ RETURN
+ END
+C---------------------------------------------------------------------------
+ SUBROUTINE FVAL
+ COMMON /NLC/ EPSI,IFLAG,IL,IQ,INDEX,F(15,120),M,N
+ COMMON /NLC/ SOLD,Y(20),YVAL,XF(11),X(11,11),V(120),YD(120,10)
+ COMMON /NLC/ GG(11,11),GINV(11,11),EM(15,120),BB(5),NT
+ COMMON /NLC/ GA(120,5),NN
+ DIMENSION GTGA(11,11),GT(5,120),GGG(5,120),B(5)
+ DIMENSION G(120,5),A(11),TR(5),XP(11)
+ EQUIVALENCE (GG(1,1),GTGA(1,1)),(BB(1),B(1)),(XF(1),A(1)),
+ *(G(1,1),GA(1,1))
+ DO 200 I=1,M
+ DO 100 J=1,NN
+100 XP(J)=YD(I,J)
+C
+C---- LOCATION OF TRANSFORMS
+ CALL TRANS(TR,A,XP)
+C
+ DO 110 J=1,NT
+110 GA(I,J)=TR(J)
+200 CONTINUE
+ DO 230 J=1,NT
+ DO 230 I=1,M
+230 GT(J,I)=GA(I,J)
+ DO 280 K=1,NT
+ DO 280 I=1,NT
+ GTGA(K,I)=0.0
+ DO 280 J=1,M
+280 GTGA(K,I)=GTGA(K,I)+GT(K,J)*GA(J,I)
+ HOLD=N
+ N=NT
+ CALL MATIN
+ N=HOLD
+ DO 350 K=1,NT
+ DO 350 I=1,M
+ GGG(K,I)=0.0
+ DO 350 J=1,NT
+350 GGG(K,I)=GGG(K,I)+GINV(K,J)*GT(J,I)
+ DO 400 K=1,NT
+ B(K)=0.0
+ DO 400 J=1,M
+400 B(K)=B(K)+GGG(K,J)*V(J)
+ YVAL=0.0
+ DO 460 I=1,M
+ FF=0.0
+ DO 240 J=1,NT
+240 FF=B(J)*GA(I,J)+FF
+ F(INDEX,I)=V(I)-FF
+460 YVAL=(V(I)-FF)**2+YVAL
+ RETURN
+ END
diff --git a/noao/onedspec/fortran/polft1.f b/noao/onedspec/fortran/polft1.f
new file mode 100644
index 00000000..625b69c7
--- /dev/null
+++ b/noao/onedspec/fortran/polft1.f
@@ -0,0 +1,205 @@
+C+
+C
+C SUBROUTINE POLFT1
+C
+C PURPOSE
+C MAKE A LEAST SQUARES FIT TO DATA WITH A POLYNOMIAL CURVE
+C Y = A(1) + A(2)*X + A(3)*X**2 + ...
+C
+C USAGE
+C CALL POLFIT(X,Y,SIGMAY,NPTS,NTERMS,MODE,A,CHISQR,ARR,IER)
+C
+C DESCRIPTION OF PARAMETERS
+C Y - ARRAY OF DATA POINTS FOR DEPENDENT VARIABLE
+C SIGMAY - ARRAY OF STANDARD DEVIATIONS FOR Y-DATA POINTS
+C (OR - IN CASE MODE = 4 - WEIGHTS FOR POINTS)
+C NPTS - NO. OF PAIRS OF DATA POINTS
+C NTERMS - NO. OF COEFFICIENTS (DEGREE OF POLYNOMIAL + 1)
+C MODE - DETERMINES METHOD OF WEIGHTING LEAST SQUARES FIT
+C 4 (USER DEFINED) WEIGHT(I) = SIGMAY(I)
+C 3 (INSTRUMENTAL) WEIGHT(I) = 1./SIGMAY(I)**2
+C 2 (NO WEIGHTING) WEIGHT(I) = 1.
+C 1 (STATISTICAL) WEIGHT(I) = 1./Y(I)
+C A - ARRAY OF COEFFICIENTS OF POLYNOMIAL
+C CHISQR - REDUCED CHISQUARE FOR FIT
+C ARR - DOUBLE PRECISION WORK BUFFER; MUST BE AT LEAST
+C 400 WORDS LONG IN THE CALLING ROUTINE
+C IER - ERROR PARAMETER
+C -1 DET=0
+C 0 O.K.
+C +1 NOT ENOUGH POINTS
+C
+C SUBROUTINES AND FUNCTION SUBPROGRAMS REQUIRED
+C DET(ARR,NORD) - EVALUATES THE DETERMINANT OF A SYMMETRIC
+C TWO DIMENSIONAL MATRIX OF ORDER NORD
+C
+C COMMENTS
+C DIMENSION STATEMENT VALID FOR NTERMS UP TO 10
+C FOR DETAILS OF ALGORITHM SEE "DATA REDUCTION AND ERROR
+C ANALYSIS FOR THE PHYSICAL SCIENCES" - PH. R. BEVINGTON
+C MC GRAW-HILL BOOK COMPANY
+C
+C IN THIS SPECIAL VERSION THE ELEMENTS OF COEFFICIENT MATRIX
+C ARR ARE NORMALIZED WITH RESPECT TO A VALUE COMPUTED VIA
+C LOGARITHMIC INTERPOLATION BETWEEN THE SMALLEST AND LARGEST
+C MATRIX ELEMENT
+C
+C MODIFICATIONS BY A. SCHERMANN - INST. FOR ASTRONOMY,
+C UNIV. OF VIENNA, AUSTRIA
+C
+C Modified to assume x-variable is index of Y array
+C-
+ SUBROUTINE POLFT1(Y,SIGMAY,NPTS,NTERMS,MODE,A,CHISQR,ARR,IER)
+ DOUBLE PRECISION SUMX(19),SUMY(10),ARR(10,10),XTERM,YTERM,
+ 1 CHISQ
+ DIMENSION Y(1),SIGMAY(1),A(1)
+ IER=0
+C
+C CHECK DEGREE OF FREEDOM
+ FREE=NPTS-NTERMS
+ IF(FREE.GT.0.) GOTO 11
+ IER=1
+ GOTO 80
+C
+C ACCUMULATE WEIGHTED SUMS
+11 NMAX=2*NTERMS-1
+ DO 13 N=1,NMAX
+13 SUMX(N)=0.
+ DO 15 J=1,NTERMS
+15 SUMY(J)=0.
+ CHISQ=0.
+21 DO 50 I=1,NPTS
+ XI=I
+ YI=Y(I)
+31 GOTO(32,37,38,39),MODE
+32 IF(YI)35,37,33
+33 WEIGHT=1./YI
+ GOTO 41
+35 WEIGHT=-1./YI
+ GOTO 41
+37 WEIGHT=1.
+ GOTO 41
+38 WEIGHT=1./SIGMAY(I)**2
+ GOTO 41
+39 WEIGHT=SIGMAY(I)
+41 XTERM=WEIGHT
+ DO 44 N=1,NMAX
+ SUMX(N)=SUMX(N)+XTERM
+44 XTERM=XTERM*XI
+45 YTERM=WEIGHT*YI
+ DO 48 N=1,NTERMS
+ SUMY(N)=SUMY(N)+YTERM
+48 YTERM=YTERM*XI
+49 CHISQ=CHISQ+WEIGHT*YI**2
+50 CONTINUE
+C
+C GET LARGEST AND SMALLEST MATRIX ELEMENT (FOR NORMALIZATION)
+ XTERM=SUMX(1)
+ YTERM=XTERM
+ DO 100 I=2,NMAX
+ IF(SUMX(I).GT.XTERM) XTERM=SUMX(I)
+ IF(SUMX(I).LT.YTERM) YTERM=SUMX(I)
+100 CONTINUE
+ DO 110 I=1,NTERMS
+ IF(SUMY(I).GT.XTERM) XTERM=SUMY(I)
+ IF(SUMY(I).LT.YTERM) YTERM=SUMY(I)
+110 CONTINUE
+ IF(YTERM.LE.0.) YTERM=1.D0
+C
+C LOGARITHMIC INTERPOLATION OF NORMALIZATION VALUE
+ XTERM=1.D1**((DLOG10(XTERM)+DLOG10(YTERM))/2.)
+C
+C CONSTRUCT MATRICES AND CALCULATE COEFFICIENTS
+51 DO 54 J=1,NTERMS
+ DO 54 K=1,NTERMS
+ N=J+K-1
+54 ARR(J,K)=SUMX(N)/XTERM
+ DELTA=DET(ARR,NTERMS)
+ IF(DELTA.NE.0.) GOTO 61
+57 CHISQR=0.
+ DO 59 J=1,NTERMS
+59 A(J)=0.
+ IER=-1
+ GOTO 80
+61 DO 70 L=1,NTERMS
+62 DO 66 J=1,NTERMS
+ DO 65 K=1,NTERMS
+ N=J+K-1
+65 ARR(J,K)=SUMX(N)/XTERM
+66 ARR(J,L)=SUMY(J)/XTERM
+70 A(L)=DET(ARR,NTERMS)/DELTA
+C
+C CALCULATE CHISQUARE
+71 DO 75 J=1,NTERMS
+ CHISQ=CHISQ-2.*A(J)*SUMY(J)
+ DO 75 K=1,NTERMS
+ N=J+K-1
+75 CHISQ=CHISQ+A(J)*A(K)*SUMX(N)
+77 CHISQR=CHISQ/FREE
+80 RETURN
+ END
+c-----------------------------------------------------------
+ FUNCTION POLYNO (COE,NPOL,IX)
+C
+C EVALUATE A POLYNOMIAL OF ORDER NPOL WITH COEFFICIENTS COE(1),...,
+C COE (NPOL+1) FOR AN INDEX IX
+C
+ DIMENSION COE(*)
+C
+ IF(NPOL.GT.0) GO TO 10
+ POLYNO=COE(1)
+ RETURN
+C
+10 POLYNO=COE(NPOL+1)
+ DO 20 I=NPOL,1,-1
+20 POLYNO=POLYNO*IX+COE(I)
+C
+ RETURN
+ END
+c-----------------------------------------------------------
+C+
+C
+C FUNCTION DET
+C
+C PURPOSE
+C CALCULATE DETERMINANT OF A SQUARE MATRIX
+C
+C USAGE
+C DET = DET (ARR,NORDER)
+C
+C DESCRIPTION OF PARAMETERS
+C ARRAY - MATRIX
+C NORDER - DEGREE OF MATRIX
+C
+C SUBROUTINES AND FUNCTION SUBPROGRAMS REQUIRED
+C NONE
+C
+C COMMENTS
+C THIS SUBPROGRAM DESTROYS THE INPUT MATRIX ARRAY
+C
+ FUNCTION DET(ARRAY,NORDER)
+ DOUBLE PRECISION ARRAY(10,10),SAVE
+ DET=1.
+ DO 90 K=1,NORDER
+C INTERCHANGE COLUMNS, IF DIAGONAL ELEMENT IS ZERO
+ IF(ARRAY(K,K).NE.0.) GOTO 40
+ DO 10 J=K,NORDER
+ IF(ARRAY(K,J).NE.0.) GOTO 20
+10 CONTINUE
+ DET=0.
+ GOTO 100
+20 DO 30 I=K,NORDER
+ SAVE=ARRAY(I,J)
+ ARRAY(I,J)=ARRAY(I,K)
+30 ARRAY(I,K)=SAVE
+ DET=-DET
+C SUBTRACT ROW K FROM LOWER ROWS TO GET DIAGONAL MATRIX
+40 DET=DET*ARRAY(K,K)
+ IF(K-NORDER.GE.0) GOTO 90
+ K1=K+1
+ DO 50 I=K1,NORDER
+ DO 50 J=K1,NORDER
+50 ARRAY(I,J)=ARRAY(I,J)-ARRAY(I,K)*ARRAY(K,J)/ARRAY(K,K)
+90 CONTINUE
+100 RETURN
+ END
diff --git a/noao/onedspec/fortran/trans.f b/noao/onedspec/fortran/trans.f
new file mode 100644
index 00000000..038384ba
--- /dev/null
+++ b/noao/onedspec/fortran/trans.f
@@ -0,0 +1,21 @@
+ SUBROUTINE TRANS(Y,A,X)
+ DIMENSION Y(10), A(10), X(10)
+ COMMON /NLCPAR/XC(10), N, FIXSEP
+ LOGICAL FIXSEP
+C----- TRANSOFRMATION FOR GAUSSIAN LINES
+C
+C----- 'N' GAUSSIAN LINES
+C
+ Y(1)=EXP(-0.5*((X(1)-XC(1)-A(2))/A(1))**2)
+ DO 1000 I=2,N
+ IF(FIXSEP) THEN
+ DELTA=A(2)
+ ELSE
+ DELTA=A(2*I)
+ ENDIF
+ Y(1)=Y(1)+ABS(A(2*I-1)*EXP(-0.5*((X(1)-XC(I)-DELTA)/
+ * A(1))**2))
+1000 CONTINUE
+C
+ RETURN
+ END
diff --git a/noao/onedspec/gcurval.dat b/noao/onedspec/gcurval.dat
new file mode 100644
index 00000000..3e8ae075
--- /dev/null
+++ b/noao/onedspec/gcurval.dat
@@ -0,0 +1 @@
+0 0 1 q
diff --git a/noao/onedspec/getairm.x b/noao/onedspec/getairm.x
new file mode 100644
index 00000000..5226702d
--- /dev/null
+++ b/noao/onedspec/getairm.x
@@ -0,0 +1,54 @@
+# GET_AIRM -- Derive airmass value from telescope data if possible
+# Otherwise return INDEF
+#
+# Note that HA must be negative to the East.
+# If HA is not reasonable, then ST-RA is used
+
+procedure get_airm (ra, dec, ha, st, latitude, airm)
+
+real ra, dec, ha, st, latitude, airm
+
+begin
+ # Verify realistic value for HA
+
+ if (IS_INDEF (ha)) {
+ if (IS_INDEF (st) || IS_INDEF (ra))
+ call error (0, "Can't determine airmass")
+
+ ha = st - ra
+ }
+
+ # Now verify DEC
+ if (IS_INDEF (dec))
+ call error (0, "Can't determine airmass")
+
+ # Everything should be just fine now
+ # Compute airmass using method of John Ball
+
+ call airmass (dec, ha, latitude, airm)
+end
+
+# AIRMASS -- Compute airmass from RA, DEC, and HA
+#
+# Airmass formulation from Allen "Astrophysical Quantities" 1973 p.125,133.
+# and John Ball's book on Algorithms for the HP-45
+
+procedure airmass (dec, ha, latitude, airm)
+
+real dec, ha, latitude, airm
+
+real scale, rads, cos_zd, sin_elev
+real x
+
+data rads /57.29577951/ # Degrees per radian
+data scale/750.0 / # Atmospheric scale height approx
+
+begin
+ cos_zd = sin (latitude/rads) * sin (dec/rads) +
+ cos (latitude/rads) * cos (dec/rads) * cos (ha*15/rads)
+
+ sin_elev = cos_zd # SIN of elev = cos of Zenith dist
+
+ x = scale * sin_elev
+ airm = sqrt (x**2 + 2*scale + 1) - x
+end
diff --git a/noao/onedspec/getcalib.x b/noao/onedspec/getcalib.x
new file mode 100644
index 00000000..6d7d77c4
--- /dev/null
+++ b/noao/onedspec/getcalib.x
@@ -0,0 +1,415 @@
+include <ctype.h>
+include <error.h>
+include <mach.h>
+
+define STD_TYPES "|star|blackbody|"
+define UNKNOWN 0 # Unknown calibration file type
+define STAR 1 # Standard star calibration file
+define BLACKBODY 2 # Blackbody calibration file
+
+define NALLOC 128 # Allocation block size
+
+# GETCALIB -- Get flux data.
+# This is either for a blackbody of specified magnitude and type or
+# a specified standard star with calibration data in a database directory.
+
+procedure getcalib (waves, dwaves, mags, nwaves)
+
+pointer waves #O Pointer to calibration wavelengths
+pointer dwaves #O Pointer to calibration bandpasses
+pointer mags #O Pointer to calibration magnitudes
+int nwaves #O Number of calibration points
+
+real weff, wave, mag, dwave, wave1, wave2
+int i, j, fd, nalloc
+pointer sp, dir, star, name, file, type, units, band, str
+pointer un, unang
+
+bool streq()
+int open(), fscan(), nscan(), getline(), strdic()
+pointer un_open()
+errchk getbbcal, open, un_open, un_ctranr
+define getstd_ 10
+
+begin
+ call smark (sp)
+ call salloc (dir, SZ_FNAME, TY_CHAR)
+ call salloc (star, SZ_FNAME, TY_CHAR)
+ call salloc (name, SZ_LINE, TY_CHAR)
+ call salloc (file, SZ_LINE, TY_CHAR)
+ call salloc (type, SZ_LINE, TY_CHAR)
+ call salloc (units, SZ_LINE, TY_CHAR)
+ call salloc (band, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ Memc[str] = EOS
+
+ # Convert the star name to a file name and open the file.
+ # If an error occurs print a list of files.
+
+getstd_ call clgstr ("caldir", Memc[dir], SZ_FNAME)
+ call clgstr ("star_name", Memc[star], SZ_FNAME)
+
+ call strcpy (Memc[star], Memc[name], SZ_LINE)
+ call strlwr (Memc[name])
+ j = name
+ for (i=name; Memc[i]!=EOS; i=i+1) {
+ if (IS_WHITE(Memc[i]) || Memc[i]=='+' || Memc[i]=='-')
+ next
+ Memc[j] = Memc[i]
+ j = j + 1
+ }
+ Memc[j] = EOS
+
+ # Check if this is an alternate name.
+ call sprintf (Memc[file], SZ_LINE, "%snames.men")
+ call pargstr (Memc[dir])
+ ifnoerr (fd = open (Memc[file], READ_ONLY, TEXT_FILE)) {
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[file], SZ_LINE)
+ if (streq (Memc[file], Memc[name])) {
+ call gargwrd (Memc[name], SZ_LINE)
+ break
+ }
+ }
+ }
+
+ call sprintf (Memc[file], SZ_LINE, "%s%s.dat")
+ call pargstr (Memc[dir])
+ call pargstr (Memc[name])
+
+ iferr (fd = open (Memc[file], READ_ONLY, TEXT_FILE)) {
+ if (streq (Memc[file], Memc[str]))
+ call erract (EA_ERROR)
+ call strcpy (Memc[file], Memc[str], SZ_LINE)
+ call sprintf (Memc[file], SZ_LINE, "%sstandards.men")
+ call pargstr (Memc[dir])
+ fd = open (Memc[file], READ_ONLY, TEXT_FILE)
+ while (getline (fd, Memc[file]) != EOF)
+ call putline (STDERR, Memc[file])
+ call close (fd)
+ Memc[star] = EOS
+ goto getstd_
+ }
+
+ # Read the calibration data.
+ type = STAR
+ call strcpy ("angstroms", Memc[units], SZ_LINE)
+ Memc[band] = EOS
+ weff = INDEFR
+
+ nalloc = 0
+ nwaves = 0
+ while (fscan (fd) != EOF) {
+
+ # Check for comments and parameters.
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() != 1)
+ next
+ if (Memc[str] == '#') {
+ call gargwrd (Memc[str], SZ_LINE)
+ call strlwr (Memc[str])
+ if (streq (Memc[str], "type")) {
+ call gargwrd (Memc[str], SZ_LINE)
+ type = strdic (Memc[str], Memc[str], SZ_LINE, STD_TYPES)
+ } else if (streq (Memc[str], "units"))
+ call gargwrd (Memc[units], SZ_LINE)
+ else if (streq (Memc[str], "band"))
+ call gargwrd (Memc[band], SZ_LINE)
+ else if (streq (Memc[str], "weff"))
+ call gargr (weff)
+ next
+ }
+ call reset_scan ()
+
+ # Read data.
+ call gargr (wave)
+ call gargr (mag)
+ call gargr (dwave)
+ if (nscan() != 3)
+ next
+
+ if (nalloc == 0) {
+ nalloc = nalloc + NALLOC
+ call malloc (waves, nalloc, TY_REAL)
+ call malloc (mags, nalloc, TY_REAL)
+ call malloc (dwaves, nalloc, TY_REAL)
+ } else if (nwaves == nalloc) {
+ nalloc = nalloc + NALLOC
+ call realloc (waves, nalloc, TY_REAL)
+ call realloc (mags, nalloc, TY_REAL)
+ call realloc (dwaves, nalloc, TY_REAL)
+ }
+
+ Memr[waves+nwaves] = wave
+ Memr[mags+nwaves] = mag
+ Memr[dwaves+nwaves] = dwave
+ nwaves = nwaves + 1
+ }
+ call close (fd)
+
+ if (nwaves == 0)
+ call error (1, "No calibration data found")
+
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (mags, nwaves, TY_REAL)
+ call realloc (dwaves, nwaves, TY_REAL)
+
+ # This routine returns wavelengths in Angstroms.
+ un = un_open (Memc[units])
+ unang = un_open ("Angstroms")
+ call un_ctranr (un, unang, weff, weff, 1)
+ do i = 1, nwaves {
+ wave = Memr[waves+i-1]
+ dwave = Memr[dwaves+i-1]
+ wave1 = wave - dwave / 2
+ wave2 = wave + dwave / 2
+ call un_ctranr (un, unang, wave1, wave1, 1)
+ call un_ctranr (un, unang, wave2, wave2, 1)
+ wave = (wave1 + wave2) / 2.
+ dwave = abs (wave1 - wave2)
+ Memr[waves+i-1] = wave
+ Memr[dwaves+i-1] = dwave
+ }
+ call un_close (un)
+ call un_close (unang)
+
+ switch (type) {
+ case UNKNOWN:
+ call freecalib (waves, dwaves, mags)
+ call error (1, "Unknown calibration type")
+ case BLACKBODY:
+ call getbbcal (Memr[waves], Memr[mags], nwaves, Memc[band],
+ weff, Memc[dir])
+ }
+
+ call sfree (sp)
+end
+
+
+# GETBBCAL -- Get blackbody calibration data.
+
+procedure getbbcal (waves, mags, nwaves, band, weff, caldir)
+
+real waves[nwaves] #I Calibration wavelengths
+real mags[nwaves] #I Calibration magnitudes
+int nwaves #I Number of calibration points
+char band[ARB] #I Bandpass of data
+real weff #I Effective wavelength
+char caldir[ARB] #I Calibration directory
+
+int i, j, col1, col2, fd
+real mag, m1, m2, dm, teff, t, dt
+pointer sp, bands, magband, sptype, default, fname, str
+
+bool streq(), strne()
+int clgwrd(), nowhite(), ctor(), strdic(), strncmp()
+int open(), fscan(), nscan()
+real clgetr()
+errchk open
+
+begin
+ if (band [1] == EOS || IS_INDEFR(weff))
+ call error (1,
+ "Blackbody calibration file has no band or effective wavelength")
+
+ call smark (sp)
+ call salloc (bands, SZ_LINE, TY_CHAR)
+ call salloc (magband, SZ_LINE, TY_CHAR)
+ call salloc (sptype, SZ_LINE, TY_CHAR)
+ call salloc (default, SZ_LINE, TY_CHAR)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Create list of acceptable magnitudes.
+ call sprintf (Memc[bands], SZ_LINE, "|")
+ call sprintf (Memc[fname], SZ_FNAME, "%sparams.dat")
+ call pargstr (caldir)
+ ifnoerr (fd = open (Memc[fname], READ_ONLY, TEXT_FILE)) {
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (Memc[str] != '#')
+ next
+ call gargwrd (Memc[str], SZ_LINE)
+ if (strne (Memc[str], "Type"))
+ next
+
+ call gargwrd (Memc[str], SZ_LINE)
+ j = nscan()
+ repeat {
+ i = j
+ call gargwrd (Memc[str], SZ_LINE)
+ j = nscan()
+ if (i == j)
+ break
+ call strcat (Memc[str], Memc[bands], SZ_LINE)
+ call strcat ("|", Memc[bands], SZ_LINE)
+ }
+ break
+ }
+ call close (fd)
+ }
+ col1 = strdic (band, Memc[str], SZ_LINE, Memc[bands]) + 2
+ if (col1 == 2) {
+ call strcat (band, Memc[bands], SZ_LINE)
+ call strcat ("|", Memc[bands], SZ_LINE)
+ }
+ col1 = strdic (band, Memc[str], SZ_LINE, Memc[bands]) + 2
+ call clpstr ("magband.p_min", Memc[bands])
+
+ # Get blackbody parameters.
+ mag = clgetr ("mag")
+ col2 = clgwrd ("magband", Memc[magband], SZ_LINE, Memc[bands]) + 2
+ call clgstr ("teff", Memc[sptype], SZ_LINE)
+ i = nowhite (Memc[sptype], Memc[sptype], SZ_LINE)
+
+ # Convert spectral type to effective temperature.
+ i = 1
+ if (ctor (Memc[sptype], i, teff) == 0) {
+ teff = INDEFR
+ call sprintf (Memc[fname], SZ_FNAME, "%sparams.dat")
+ call pargstr (caldir)
+ fd = open (Memc[fname], READ_ONLY, TEXT_FILE)
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_FNAME)
+ if (strncmp (Memc[str], Memc[sptype], 2) != 0)
+ next
+ call gargr (t)
+ if (nscan() < 2)
+ next
+
+ call strcpy (Memc[str], Memc[default], SZ_LINE)
+ teff = t
+
+ if (streq (Memc[default], Memc[sptype]))
+ break
+ }
+ call close (fd)
+
+ if (IS_INDEFR(teff))
+ call error (1, "Failed to determine effective temperature")
+ if (strne (Memc[default], Memc[sptype])) {
+ call eprintf ("WARNING: Effective temperature for %s not found")
+ call pargstr (Memc[sptype])
+ call eprintf (" - using %s\n")
+ call pargstr (Memc[default])
+ call strcpy (Memc[default], Memc[sptype], SZ_LINE)
+ }
+ } else
+ Memc[sptype] = EOS
+
+ # Transform the input magnitude from the input passband to the
+ # data passband if necessary.
+ if (strne (Memc[magband], band)) {
+
+ # Get spectral type if necessary.
+ if (Memc[sptype] == EOS) {
+ dt = MAX_REAL
+ call sprintf (Memc[fname], SZ_FNAME, "%sparams.dat")
+ call pargstr (caldir)
+ fd = open (Memc[fname], READ_ONLY, TEXT_FILE)
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_FNAME)
+ if (Memc[str+2] != 'V')
+ next
+ call gargr (t)
+ if (nscan() < 2)
+ next
+ if (abs (t - teff) < dt) {
+ dt = abs (t - teff)
+ call strcpy (Memc[str], Memc[sptype], SZ_LINE)
+ }
+ }
+ call close (fd)
+
+ if (Memc[sptype] == EOS)
+ call error (1, "Failed to determine spectral type")
+ call eprintf ("WARNING: Assuming spectral type of %s\n")
+ call pargstr (Memc[sptype])
+ }
+
+ # Get magnitude correction.
+ dm = INDEFR
+ call sprintf (Memc[fname], SZ_FNAME, "%sparams.dat")
+ call pargstr (caldir)
+ fd = open (Memc[fname], READ_ONLY, TEXT_FILE)
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (strncmp (Memc[str], Memc[sptype], 2) != 0)
+ next
+
+ call gargr (t)
+
+ m1 = INDEFR
+ m2 = INDEFR
+ do i = 1, max (col1, col2) {
+ call gargr (t)
+ if (i == col1)
+ m1 = t
+ if (i == col2)
+ m2 = t
+ }
+
+ if (!IS_INDEFR(m1) && !IS_INDEFR(m2)) {
+ call strcpy (Memc[str], Memc[default], SZ_LINE)
+ dm = m1 - m2
+ if (streq (Memc[default], Memc[sptype]))
+ break
+ }
+ }
+ call close (fd)
+
+ if (IS_INDEFR(dm)) {
+ call sprintf (Memc[str], SZ_LINE,
+ "No information in %s to convert %s mag to %s mag for %s star")
+ call pargstr (Memc[fname])
+ call pargstr (Memc[magband])
+ call pargstr (band)
+ call pargstr (Memc[sptype])
+ call error (1, Memc[str])
+ }
+ if (strne (Memc[default], Memc[sptype])) {
+ call eprintf (
+ "WARNING: Converting %s mag to %s mag using spectral type %s\n")
+ call pargstr (Memc[magband])
+ call pargstr (band)
+ call pargstr (Memc[default])
+ }
+
+ mag = mag + dm
+
+ call eprintf ("Blackbody: %s = %.2f, %s = %.2f, Teff = %d\n")
+ call pargstr (Memc[magband])
+ call pargr (mag - dm)
+ call pargstr (band)
+ call pargr (mag)
+ call pargr (teff)
+
+ } else {
+ call eprintf ("Blackbody: %s = %.2f, Teff = %d\n")
+ call pargstr (band)
+ call pargr (mag)
+ call pargr (teff)
+ }
+
+ # Convert the calibration magnitudes to the specified magnitude and
+ # apply the blackbody function.
+ m1 = -2.5 * log10 (weff**3 * (exp(1.4387E8/(weff*teff)) - 1))
+ do i = 1, nwaves
+ mags[i] = mags[i] + mag + m1 +
+ 2.5 * log10 (waves[i]**3 * (exp(1.4387E8/(waves[i]*teff)) - 1))
+
+ call sfree (sp)
+end
+
+
+# FREECALIB -- Free calibration data arrays.
+
+procedure freecalib (waves, dwaves, mags)
+
+pointer waves, dwaves, mags
+
+begin
+ call mfree (waves, TY_REAL)
+ call mfree (dwaves, TY_REAL)
+ call mfree (mags, TY_REAL)
+end
diff --git a/noao/onedspec/getextn.x b/noao/onedspec/getextn.x
new file mode 100644
index 00000000..82640152
--- /dev/null
+++ b/noao/onedspec/getextn.x
@@ -0,0 +1,209 @@
+include <error.h>
+include <syserr.h>
+
+define EXTN_LOOKUP 10 # Interp index for de-extinction
+define DEXTN_LOOKUP 11 # Interp index for differential extn table
+define TEMP_SPACE 100 # Amount of temporary space to allocate
+
+# GET_EXTN -- Get extinction from calibration file and
+# any update as indicated from the SENSITIVITY
+# computation
+
+procedure get_extn (wave_tbl, extn_tbl, nwaves)
+
+pointer wave_tbl, extn_tbl
+int nwaves
+
+pointer waves, extns
+
+begin
+ # Get standard extinction values
+ call ext_load (waves, extns, nwaves)
+
+ # Copy values to external pointer.
+ # Use of salloc is incorrect but this is a hack on old code. FV
+ call salloc (extn_tbl, nwaves, TY_REAL)
+ call salloc (wave_tbl, nwaves, TY_REAL)
+ call amovr (Memr[waves], Memr[wave_tbl], nwaves)
+ call amovr (Memr[extns], Memr[extn_tbl], nwaves)
+ call mfree (waves, TY_REAL)
+ call mfree (extns, TY_REAL)
+end
+
+
+# DE_EXT_SPEC -- Apply extinction correction to a spectrum
+
+procedure de_ext_spec (spec, airm, w0, wpc, wave_tbl, extn_tbl, nwaves, len)
+
+real spec[ARB], wave_tbl[ARB], extn_tbl[ARB]
+real airm, w0, wpc
+int nwaves, len
+
+int i, ierr
+real wave, ext
+bool lin_log
+
+begin
+ # Assume linear dispersion, but possibly in LOG10
+ if (w0 < 5.0 && wpc < 0.05)
+ lin_log = true
+ else
+ lin_log = false
+
+ # Initialize interpolator
+ call intrp0 (EXTN_LOOKUP)
+
+ do i = 1, len {
+ wave = w0 + (i-1) * wpc
+ if (lin_log)
+ wave = 10.0 ** wave
+
+ # Table must be in wavelength, not log[]
+ call intrp (EXTN_LOOKUP, wave_tbl, extn_tbl, nwaves,
+ wave, ext, ierr)
+
+ spec[i] = spec[i] * 10.0 ** (0.4 * airm * ext)
+ }
+end
+
+# SUM_SPEC -- Add up counts within a specified region of a spectrum
+# denoted by a wavelength range.
+# The summation is active only over those pixels which
+# are completely within the range specification.
+# Data referenced outside the spectrum is ignored.
+
+procedure sum_spec (spec, w1, w2, w0, wpc, counts, len)
+
+real spec[ARB], w1, w2, w0, wpc, counts
+int len
+
+int i, pix1, pix2
+
+real pix_index()
+
+begin
+ # Compute pixel numbers from w1 to w2
+ pix1 = max (int (pix_index (w0, wpc, w1) + 1.0), 1)
+ pix2 = max (int (pix_index (w0, wpc, w2) ), pix1)
+ pix2 = min (pix2, len)
+
+ counts = 0.0
+
+ do i = pix1, pix2
+ counts = counts + spec[i]
+
+ # Guarantee that there are no negative counts
+ if (counts < 0.0)
+ counts = 0.0
+end
+
+# PIX_INDEX -- Returns the pixel index at wavelength for linearly
+# dispersion corrected spectra
+#
+# The "Guess" is made that if the start wavelength for the
+# spectrum is less than 5.0 and the dispersion is less than
+# 0.05, the spectrum has been linearized in LOG10 space.
+#
+# Note that in IRAF, a pixel index effectively refers to the center of a pixel.
+# So a spectrum must actually extend from w0-0.5*wpc to w0+(len+0.5)*wpc
+
+real procedure pix_index (w0, wpc, w)
+
+real w0, wpc, w
+real xw
+
+begin
+ # Check for LOG10 dispersion
+
+ if (w0 < 5.0 && wpc < 0.05)
+ xw = log10 (w)
+ else
+ xw = w
+
+ pix_index = (xw - w0) / wpc + 1.0
+end
+
+
+define NALLOC 128 # Allocation block size
+
+# EXT_LOAD -- Read extinction data from database directory.
+
+procedure ext_load (waves, extns, nwaves)
+
+pointer waves, extns
+int nwaves
+
+real wave, extn
+int fd, nalloc
+pointer sp, file
+
+int open(), fscan(), nscan(), errcode()
+
+begin
+ call smark (sp)
+ call salloc (file, SZ_FNAME, TY_CHAR)
+
+ # Get the extinction file and open it.
+ call clgstr ("extinction", Memc[file], SZ_FNAME)
+ iferr (fd = open (Memc[file], READ_ONLY, TEXT_FILE)) {
+ switch (errcode()) {
+ case SYS_FNOFNAME:
+ nwaves = 2
+ call malloc (waves, nwaves, TY_REAL)
+ call malloc (extns, nwaves, TY_REAL)
+ Memr[waves] = 1000.
+ Memr[extns] = 0.
+ Memr[waves+1] = 10000.
+ Memr[extns+1] = 0.
+ call eprintf ("No extinction correction applied\n")
+ return
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+
+ # Read the extinction data.
+ nalloc = 0
+ nwaves = 0
+ while (fscan (fd) != EOF) {
+ call gargr (wave)
+ call gargr (extn)
+ if (nscan() != 2)
+ next
+
+ if (nalloc == 0) {
+ nalloc = nalloc + NALLOC
+ call malloc (waves, nalloc, TY_REAL)
+ call malloc (extns, nalloc, TY_REAL)
+ } else if (nwaves == nalloc) {
+ nalloc = nalloc + NALLOC
+ call realloc (waves, nalloc, TY_REAL)
+ call realloc (extns, nalloc, TY_REAL)
+ }
+
+ Memr[waves+nwaves] = wave
+ Memr[extns+nwaves] = extn
+ nwaves = nwaves + 1
+ }
+ call close (fd)
+
+ if (nwaves == 0)
+ call error (1, "No extinction data found")
+
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (extns, nwaves, TY_REAL)
+
+ call sfree (sp)
+end
+
+
+# EXT_FREE -- Free extinction data arrays.
+
+procedure ext_free (waves, extns)
+
+pointer waves, extns
+
+begin
+ call mfree (waves, TY_REAL)
+ call mfree (extns, TY_REAL)
+end
diff --git a/noao/onedspec/hireswcal.cl b/noao/onedspec/hireswcal.cl
new file mode 100644
index 00000000..f4d41986
--- /dev/null
+++ b/noao/onedspec/hireswcal.cl
@@ -0,0 +1,68 @@
+# HIRESWCAL -- Apply HIRES wavelengths to flux data to produce an IRAF file.
+#
+# This script requires the onedspec, proto, and artdata packages be loaded.
+
+procedure hireswcal (input, waves, output)
+
+file input {prompt="Input hires data file"}
+file waves {prompt="Input matching hires wavelength file"}
+file output {prompt="Output IRAF file"}
+
+struct *fd # Required to loop through a list.
+
+begin
+ file in, win, out
+ file im, wim, out1, tmp
+ int ap
+
+ # Set query parameters.
+ in = input
+ win = waves
+ out = output
+
+ # Check if output is already present.
+ if (imaccess(out))
+ error (1, "Output already exists ("//out//")")
+
+ # Define a temporary file rootname.
+ tmp = mktemp ("tmp")
+
+ # Expand input into a list of spectra.
+ slist (in, apertures="", long-, > tmp)
+
+ # For each spectrum in the list create an IRAF 1D spectrum.
+ fd = tmp
+ while (fscan (fd, im, ap) != EOF) {
+
+ # Form names for each spectrum.
+ printf ("%s[*,%d]\n", in, ap) | scan (im)
+ printf ("%s[*,%d]\n", win, ap) | scan (wim)
+ printf ("%s_%d\n", tmp, ap) | scan (out1)
+
+ # Dump the wavelengths and flux and put together into
+ # a file for rspectext.
+
+ listpix (wim, v-, > tmp//"waves")
+ listpix (im, v-, > tmp//"flux")
+ joinlines (tmp//"waves", tmp//"flux") |
+ fields ("STDIN", "2,4", > tmp//"join")
+
+ # Create the IRAF spectrum.
+ rspectext (tmp//"join", out1, title="", flux-, dtype="interp")
+
+ # Delete working files.
+ delete (tmp//"[wfj]*", v-)
+ print (out1, >> tmp//".list")
+ }
+ fd = ""; delete (tmp, v-)
+
+ # Put the 1D spectrum into a multispec file.
+ scopy ("@"//tmp//".list", out, format="multispec", renumber+)
+
+ # Add the input header for what its worth.
+ mkhead (out, in, append+, verbose-)
+
+ # Finish up.
+ imdelete ("@"//tmp//".list", v-)
+ delete (tmp//"*", v-)
+end
diff --git a/noao/onedspec/identify.par b/noao/onedspec/identify.par
new file mode 100644
index 00000000..04d76993
--- /dev/null
+++ b/noao/onedspec/identify.par
@@ -0,0 +1,33 @@
+# Parameters for identify task.
+
+images,s,a,,,,Images containing features to be identified
+section,s,h,"middle line",,,Section to apply to two dimensional images
+database,f,h,database,,,Database in which to record feature data
+coordlist,f,h,linelists$idhenear.dat,,,User coordinate list
+units,s,h,"",,,Coordinate units
+nsum,s,h,"10",,,Number of lines/columns/bands to sum in 2D images
+match,r,h,-3.,,,Coordinate list matching limit
+maxfeatures,i,h,50,,,Maximum number of features for automatic identification
+zwidth,r,h,100.,,,Zoom graph width in user units
+
+ftype,s,h,"emission","emission|absorption",,Feature type
+fwidth,r,h,4.,,,Feature width in pixels
+cradius,r,h,5.,,,Centering radius in pixels
+threshold,r,h,0.,0.,,Feature threshold for centering
+minsep,r,h,2.,0.,,Minimum pixel separation
+
+function,s,h,"spline3","legendre|chebyshev|spline1|spline3",,Coordinate function
+order,i,h,1,1,,Order of coordinate function
+sample,s,h,"*",,,Coordinate sample regions
+niterate,i,h,0,0,,Rejection iterations
+low_reject,r,h,3.,0.,,Lower rejection sigma
+high_reject,r,h,3.,0.,,Upper rejection sigma
+grow,r,h,0.,0.,,Rejection growing radius
+
+autowrite,b,h,no,,,"Automatically write to database"
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Graphics cursor input
+
+crval,s,q,,,,"Approximate coordinate (at reference pixel)"
+cdelt,s,q,,,,"Approximate dispersion"
+aidpars,pset,h,,,,"Automatic identification algorithm parameters"
diff --git a/noao/onedspec/identify/autoid/aidautoid.x b/noao/onedspec/identify/autoid/aidautoid.x
new file mode 100644
index 00000000..7c213b4a
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidautoid.x
@@ -0,0 +1,314 @@
+include <mach.h>
+include <gset.h>
+include <math/iminterp.h>
+include <smw.h>
+include "../identify.h"
+include "autoid.h"
+
+
+# List of debug key letters.
+# Debug a: Print candidate line assignments.
+# Debug b: Print search limits.
+# Debug c: Print list of line ratios.
+# Debug d: Graph dispersions.
+# Debug f: Print final result.
+# Debug i: Show ICFIT iterations.
+# Debug l: Graph lines and spectra.
+# Debug m: Print miscellaneous debug information
+# Debug n: Show non-linearity constraint
+# Debug r: Print list of reference lines.
+# Debug s: Print search iterations.
+# Debug t: Print list of target lines.
+# Debug v: Print vote array.
+# Debug w: Print wavelength bin limits.
+
+
+# AID_AUTOID -- Automatically identify spectral features.
+# This routine is main entry to the autoidentify algorithms.
+# The return value is true if the ID pointer contains a new solution
+# and false if the ID pointer is the original solution.
+
+bool procedure aid_autoid (id, aid)
+
+pointer id #I ID pointer
+pointer aid #U AID pointer
+
+bool cdflip
+int i, j, k, l, iev, nbins, bin, nbest
+double best, dval1, dval2
+pointer sp, str, idr, ev, evf, sid
+
+bool streq(), strne()
+int stridxs()
+double dcveval(), aid_eval()
+pointer gopen(), aid_evalloc(), id_getid()
+errchk id_mapll, aid_reference, aid_target, aid_autoid1, aid_evalutate
+
+define done_ 10
+define redo_ 20
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Save original data.
+ call id_saveid (id, "autoidentify backup")
+
+ # Initialize.
+ AID_IDT(aid) = id
+ call ic_putr (AID_IC1(aid), "xmin", real (PIXDATA(id,1)))
+ call ic_putr (AID_IC1(aid), "xmax", real (PIXDATA(id,ID_NPTS(id))))
+ AID_IC2(aid) = ID_IC(id)
+
+ if (stridxs ("ild", AID_DEBUG(aid,1)) != 0 && ID_GP(id) == NULL) {
+ call clgstr ("graphics", Memc[str], SZ_LINE)
+ ID_GP(id) = gopen (Memc[str], NEW_FILE+AW_DEFER, STDGRAPH)
+ } else if (AID_DEBUG(aid,1) != EOS && ID_GP(id) != NULL)
+ call gdeactivate (ID_GP(id), 0)
+
+ idr = AID_IDR(aid)
+ if (idr == NULL) {
+ call id_init (AID_IDR(aid))
+ idr = AID_IDR(aid)
+ }
+
+ # Set reference coordinate list.
+ if (strne (AID_REFLIST(aid), ID_COORDLIST(idr)) ||
+ streq (AID_REFLIST(aid), "FEATURES")) {
+ call id_unmapll (idr)
+ ID_COORDLIST(idr) = EOS
+
+ if (streq (AID_REFLIST(aid), "FEATURES")) {
+ if (ID_NFEATURES(id) >= 10) {
+ call strcpy (AID_REFLIST(aid), ID_COORDLIST(idr),
+ ID_LENSTRING)
+ i = ID_NFEATURES(id)
+ ID_NLL(idr) = i
+ call calloc (ID_LL(idr), i+1, TY_DOUBLE)
+ call calloc (ID_LLL(idr), i+1, TY_POINTER)
+ call amovd (USER(id,1), Memd[ID_LL(idr)], i)
+ Memd[ID_LL(idr)+i] = INDEFD
+ }
+ } else if (AID_REFLIST(aid) != EOS) {
+ call strcpy (AID_REFLIST(aid), ID_COORDLIST(idr), ID_LENSTRING)
+ call id_mapll (idr)
+ }
+ }
+
+ # Get reference spectrum.
+ if (AID_REFSPEC(aid) != EOS)
+ call strcpy (AID_REFSPEC(aid), ID_COORDSPEC(idr), ID_LENSTRING)
+ else if (ID_COORDSPEC(idr) == EOS)
+ call strcpy (ID_COORDSPEC(id), ID_COORDSPEC(idr), ID_LENSTRING)
+ if (strne (ID_COORDSPEC(idr), ID_IMAGE(idr))) {
+ if (ID_SH(idr) != NULL) {
+ call smw_close (MW(ID_SH(idr)))
+ call imunmap (IM(ID_SH(idr)))
+ call shdr_close (ID_SH(idr))
+ }
+ call strcpy (ID_COORDSPEC(idr), ID_IMAGE(idr), ID_LENSTRING)
+ ifnoerr (call id_map (idr))
+ call id_gdata(idr)
+ else {
+ ID_COORDSPEC(idr) = EOS
+ ID_IMAGE(idr) = EOS
+ }
+ }
+
+ ID_MAXFEATURES(idr) = AID_NRMAX(aid)
+ ID_MINSEP(idr) = ID_MINSEP(id)
+ ID_FTYPE(idr) = ID_FTYPE(id)
+ ID_FWIDTH(idr) = ID_FWIDTH(id)
+ ID_CRADIUS(idr) = ID_CRADIUS(id)
+ ID_THRESHOLD(idr) = ID_THRESHOLD(id)
+ ID_MATCH(idr) = ID_MATCH(id)
+
+ # Use faster, less accurate centering for the search.
+ call c1d_params (II_LINEAR, 0.02)
+
+ # Set target lines and dispersion limits.
+ call aid_target (aid)
+ cdflip = (AID_CDDIR(aid) == CDUNKNOWN ||
+ (IS_INDEFD(AID_CDELT(aid)) && AID_CDDIR(aid) == CDSIGN))
+
+ # Now search for the dispersion function and line identifications.
+ # The reference spectrum is broken up into a varying number of
+ # pieces and each is searched. The order in which the reference
+ # spectrum is divided is from the middle outward and overlapping
+ # bins are tried as a second pass. The hope is to find a
+ # piece that is close enough to the target spectrum as quickly
+ # as possible.
+
+ AID_BEST(aid) = MAX_REAL
+ nbest = 0
+ iev = 0
+redo_
+ do i = 0, 1 {
+ do j = 1, AID_NB(aid) {
+ if (j == 1)
+ nbins = (AID_NB(aid) + 2) / 2
+ else if (mod (j, 2) == 0)
+ nbins = (AID_NB(aid) + 2 - j) / 2
+ else
+ nbins = (AID_NB(aid) + 1 + j) / 2
+ nbins = 2 * nbins - 1
+ do k = 1, nbins {
+ if (k == 1)
+ bin = (nbins + 2) / 2
+ else if (mod (k, 2) == 0)
+ bin = (nbins + 2 - k) / 2
+ else
+ bin = (nbins + 1 + k) / 2
+ if (mod ((nbins-1)/2, 2) == 0) {
+ if (mod (bin, 2) == i)
+ next
+ } else {
+ if (mod (bin, 2) != i)
+ next
+ }
+
+ iferr {
+ iev = iev + 1
+ ev = aid_evalloc (aid, iev)
+ AID_BIN1(ev) = nbins
+ AID_BIN2(ev) = bin
+ call aid_reference (aid, ev, NO)
+ call aid_autoid1 (aid, ev)
+ } then {
+ AID_ND(ev) = 0
+ }
+ if (cdflip) {
+ iferr {
+ iev = iev + 1
+ evf = aid_evalloc (aid, iev)
+ AID_BIN1(evf) = nbins
+ AID_BIN2(evf) = bin
+ call aid_reference (aid, evf, YES)
+ call aid_autoid1 (aid, evf)
+ } then {
+ AID_ND(evf) = 0
+ }
+ }
+
+ # Search the candidates with the highest weights.
+ # Terminate the search if the number of best fit values
+ # less than 1. is equal to the specified number.
+ do l = 1, 5 {
+ best = aid_eval (aid, ev, l)
+ if (!IS_INDEFD(best) && best < 1.) {
+ nbest = nbest + 1
+ if (nbest >= AID_NBEST(aid))
+ goto done_
+ }
+ if (cdflip) {
+ best = aid_eval (aid, evf, l)
+ if (!IS_INDEFD(best) && best < 1.) {
+ nbest = nbest + 1
+ if (nbest >= AID_NBEST(aid))
+ goto done_
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Go back and evaluate candidates with lower weights.
+ # Terminate the search if the number of best fit values
+ # less than 1. is equal to the specified number.
+ do j = 6, AID_ND(ev) {
+ do i = 1, iev {
+ ev = aid_evalloc (aid, i)
+ best = aid_eval (aid, ev, j)
+ if (!IS_INDEFD(best) && best < 1.) {
+ nbest = nbest + 1
+ if (nbest >= AID_NBEST(aid))
+ goto done_
+ }
+ }
+ }
+
+ # Add other loops here.
+ if (AID_BEST(aid) > 1.) {
+ if (AID_NP(aid) > 3) {
+ AID_NP(aid) = AID_NP(aid) - 1
+ goto redo_
+ }
+ }
+
+done_
+ do i = 1, iev
+ call aid_evfree (aid, i)
+
+ # Evaluate the final solution with the full dispersion function.
+ # Save the final solution. If the final solution has a merit
+ # greater than one restore the original solution.
+
+ sid = id_getid (id, "autoidentify")
+ if (sid != NULL) {
+ call dcvfree (ID_CV(id))
+ iferr (call aid_dofitf (aid, id))
+ ;
+ call id_sid (id, sid)
+ } else {
+ ID_NFEATURES(id) = 0
+ call dcvfree (ID_CV(id))
+ call id_saveid (id, "autoidentify")
+ }
+
+ # Debug f: Print final result.
+ if (stridxs ("f", AID_DEBUG(aid,1)) != 0) {
+ if (AID_BEST(aid) == MAX_REAL) {
+ call eprintf ("%s %8.5g %8.3g No solution found\n")
+ call pargstr (ID_IMAGE(id))
+ call pargd (AID_CRVAL(aid))
+ call pargd (AID_CDELT(aid))
+ } else {
+ call eprintf (
+ "%s %8.5g %8.3g %8.5g %8.3g %3d %3d %6.3f %5.2f\n")
+ call pargstr (ID_IMAGE(id))
+ call pargd (AID_CRVAL(aid))
+ call pargd (AID_CDELT(aid))
+ if (ID_CV(id) == NULL) {
+ dval1 = FITDATA(id,1)
+ dval2 = FITDATA(id,2) - FITDATA(id,1)
+ } else {
+ dval1 = dcveval (ID_CV(id), AID_CRPIX(aid)+1D0)
+ dval2 = dcveval (ID_CV(id), AID_CRPIX(aid)-1D0)
+ dval2 = (dval1 - dval2) / 2D0
+ dval1 = dcveval (ID_CV(id), AID_CRPIX(aid))
+ }
+ call pargd (dval1)
+ call pargd (FITDATA(id,2) - FITDATA(id,1))
+ call pargi (nint(100.*AID_FMATCH(aid)))
+ call pargi (nint(100.*AID_FTMATCH(aid)))
+ call pargr (AID_RMS(aid))
+ call pargr (AID_BEST(aid))
+ call eprintf (
+ " dCRVAL = %.4g (%.3g), dCDELT = %.4g (%.3g)\n")
+ call pargd (dval1 - AID_CRVAL(aid))
+ call pargd (abs((dval1-AID_CRVAL(aid))/
+ (ID_NPTS(id)*AID_CDELT(aid))))
+ call pargd (dval2 - AID_CDELT(aid))
+ call pargd (abs((dval2-AID_CDELT(aid))/AID_CDELT(aid)))
+ }
+ }
+
+ if (AID_BEST(aid) > 1.) {
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ call dcvfree (ID_CV(id))
+ sid = id_getid (id, "autoidentify backup")
+ ID_NEWFEATURES(id) = NO
+ ID_NEWCV(id) = NO
+ ID_NEWGRAPH(id) = NO
+ }
+ call id_fitdata (id)
+
+ # Restore centering.
+ call c1d_params (II_SPLINE3, 0.001)
+
+ call sfree (sp)
+
+ return (AID_BEST(aid) <= 1.)
+end
diff --git a/noao/onedspec/identify/autoid/aidget.x b/noao/onedspec/identify/autoid/aidget.x
new file mode 100644
index 00000000..ba3c9342
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidget.x
@@ -0,0 +1,21 @@
+include "autoid.h"
+
+define AIDGET "|best|"
+
+
+# AID_GETR -- Get AID parameters by name.
+
+real procedure aid_getr (aid, param)
+
+pointer aid #I AID object
+char param[ARB] #I Parameter name
+
+char temp[10]
+int strdic()
+
+begin
+ switch (strdic (param, temp, 10, AIDGET)) {
+ case 1:
+ return (AID_BEST(aid))
+ }
+end
diff --git a/noao/onedspec/identify/autoid/aidgraph.x b/noao/onedspec/identify/autoid/aidgraph.x
new file mode 100644
index 00000000..35494004
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidgraph.x
@@ -0,0 +1,240 @@
+include <mach.h>
+include <gset.h>
+include <pkg/gtools.h>
+include <smw.h>
+include "../identify.h"
+include "autoid.h"
+
+
+# AID_LGRAPH -- Graph target and reference spectra and associated lines.
+# This is only used for debugging.
+
+procedure aid_lgraph (aid, x1, n1, x2, n2)
+
+pointer aid #I AID pointer
+double x1[n1] #I Reference lines
+int n1 #I Number of reference lines
+double x2[n2] #I Target lines
+int n2 #I Number of target lines
+
+int i, wcs, key, nr, nt, redraw, clgcur(), stridxs()
+real wx, wy, wz, a, b, c, d, dy, ytmin, ytmax
+pointer sp, cmd, id, sht, shr, gp, gt, xr, yr, yt, y, gt_init()
+double shdr_lw()
+
+begin
+ call smark (sp)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+
+ id = AID_IDT(aid)
+ sht = ID_SH(id)
+ shr = ID_SH(AID_IDR(aid))
+
+ gp = ID_GP(id)
+ if (gp == NULL)
+ return
+ gt = gt_init()
+ call gt_sets (gt, GTTYPE, "line")
+ call gt_seti (gt, GTSYSID, NO)
+ if (DC(sht) == DCNO || WP(sht) * AID_CDSIGN(aid) < 0.) {
+ call gt_setr (gt, GTXMIN, AID_W1(aid))
+ call gt_setr (gt, GTXMAX, AID_W2(aid))
+ } else {
+ call gt_setr (gt, GTXMIN, W0(sht))
+ call gt_setr (gt, GTXMAX, W1(sht))
+ }
+
+ if (shr != NULL) {
+ xr = SX(shr) + AID_X1R(aid) - 1
+ yr = AID_SPECR(aid)
+ nr = AID_X2R(aid) - AID_X1R(aid) + 1
+ }
+
+ nt = ID_NPTS(id)
+ yt = ID_IMDATA(id)
+ call alimr (Memr[yt], nt, ytmin, ytmax)
+
+ call malloc (y, max(nr,nt), TY_REAL)
+
+ key = 'r'
+ repeat {
+ switch (key) {
+ case ':':
+ call gt_colon (Memc[cmd], gp, gt, redraw)
+ case 'Q':
+ i = stridxs ("l", AID_DEBUG(aid,1))
+ AID_DEBUG(aid,i) = ' '
+ break
+ case 'q':
+ break
+ case 'r':
+ redraw = YES
+ case 'w':
+ call gt_window (gt, gp, "gcur", redraw)
+ }
+
+ if (redraw == YES) {
+ call gclear (gp)
+ call gseti (gp, G_YDRAWTICKS, NO)
+ if (shr != NULL) {
+ call gascale (gp, Memr[xr], nr, 1)
+ call gascale (gp, Memr[yr], nr, 2)
+ } else {
+ call gswind (gp, real(x1[1]), real(x1[n1]), 0., 1.)
+ }
+ call gt_swind (gp, gt)
+ call ggwind (gp, a, b, c, d)
+ dy = 2 * (d - c)
+ call gswind (gp, a, b, c, c + dy)
+ call gt_labax(gp, gt)
+
+ if (shr != NULL) {
+ call aminkr (Memr[yr], c + 0.44 * dy, Memr[y], nr)
+ call gt_plot (gp, gt, Memr[xr], Memr[y], nr)
+ }
+
+ wy = c + 0.46 * dy
+ wz = c + 0.49 * dy
+ do i = 1, n1 {
+ wx = x1[i]
+ if (wx < min (a,b) || wx > max (a,b))
+ next
+ call gline (gp, wx, wy, wx, wz)
+ }
+
+ call amapr (Memr[yt], Memr[y], nt,
+ ytmin, ytmax, c+0.55*dy, c+0.99*dy)
+ wy = c + 0.50 * dy
+ wz = c + 0.53 * dy
+
+ if (DC(sht) == DCNO || WP(sht) * AID_CDSIGN(aid) < 0.) {
+ call gvline (gp, Memr[y], nt, a, b)
+ do i = 1, n2 {
+ wx = a + (x2[i] - 1) / (nt - 1) * (b - a)
+ call gline (gp, wx, wy, wx, wz)
+ }
+ } else {
+ call gpline (gp, Memr[SX(sht)], Memr[y], nt)
+ do i = 1, n2 {
+ wx = shdr_lw (sht, double (x2[i]))
+ call gline (gp, wx, wy, wx, wz)
+ }
+ }
+
+ redraw = NO
+ }
+ } until (clgcur ("gcur", wx, wy, wcs, key, Memc[cmd], SZ_LINE) == EOF)
+
+ call gdeactivate (gp, 0)
+ call mfree (y, TY_REAL)
+ call gt_free (gt)
+ call sfree (sp)
+end
+
+
+# AID_DGRAPH -- Graph candidate dispersions.
+# This routine is only used for debugging.
+
+procedure aid_dgraph (aid, x, y, n, w1, dw, nd)
+
+pointer aid #I AID pointer
+real x[n] #I Array of candidate reference coordinates (sorted)
+real y[n] #I Array of candidate target coordinates
+int n #I Number of candidate pairs
+real w1[nd] #I Dispersion origin
+real dw[nd] #I Dispersion slope
+int nd #I Number of dispersions
+
+int i, ndplot, wcs, key, redraw, clgcur(), stridxs(), ctoi()
+real wx, wy, a, b, c, d, dy, crpix, crval, cdelt
+pointer sp, cmd, sh, gp, gt, gt_init()
+
+begin
+ call smark (sp)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+
+ sh = ID_SH(AID_IDT(aid))
+ gp = ID_GP(AID_IDT(aid))
+ if (gp == NULL)
+ return
+ gt = gt_init()
+ call gt_seti (gt, GTSYSID, NO)
+ if (DC(sh) != DCNO) {
+ call gt_setr (gt, GTXMIN, W0(sh))
+ call gt_setr (gt, GTXMAX, W1(sh))
+ call gt_setr (gt, GTYMIN, 1.)
+ call gt_setr (gt, GTYMAX, real(SN(sh)))
+ }
+
+ ndplot = nd
+ key = 'r'
+ repeat {
+ switch (key) {
+ case ':':
+ if (Memc[cmd] == '/')
+ call gt_colon (Memc[cmd], gp, gt, redraw)
+ else {
+ i = 1
+ if (ctoi (Memc[cmd], i, ndplot) == 0)
+ ndplot = nd
+ }
+ case 'Q':
+ i = stridxs ("d", AID_DEBUG(aid,1))
+ AID_DEBUG(aid,i) = ' '
+ break
+ case 'q':
+ break
+ case 'r':
+ redraw = YES
+ case 'w':
+ call gt_window (gt, gp, "gcur", redraw)
+ }
+
+ if (redraw == YES) {
+ call gclear (gp)
+ call gascale (gp, x, n, 1)
+ call gascale (gp, y, n, 2)
+ call gt_swind (gp, gt)
+ call gt_labax(gp, gt)
+
+ call gt_plot (gp, gt, x, y, n)
+
+ call ggwind (gp, a, b, c, d)
+ dy = (b - a) / 500.
+ do i = 1, ndplot {
+ crval = w1[i]
+ cdelt = dw[i]
+ wy = c
+ wx = crval + wy * cdelt
+ call gamove (gp, wx, wy)
+ for (wy=wy+dy; wy<d+dy; wy=wy+dy) {
+ wx = crval + wy * cdelt
+ call gadraw (gp, wx, wy)
+ }
+ }
+
+ if (AID_CRMIN(aid) > -MAX_DOUBLE / 10. &&
+ AID_CRMAX(aid) < MAX_DOUBLE / 10.) {
+ crpix = AID_CRPIX(aid)
+ crval = AID_CDSIGN(aid) * AID_CDMIN(aid)
+ cdelt = AID_CDSIGN(aid) * AID_CDMAX(aid)
+ for (wy=c; wy<d+dy; wy=wy+dy) {
+ wx = AID_CRMIN(aid) +
+ min ((wy-crpix)*crval, (wy-crpix)*cdelt)
+ call gmark (gp, wx, wy, GM_POINT, 2, 2)
+ }
+ for (wy=c; wy<d+dy; wy=wy+dy) {
+ wx = AID_CRMAX(aid) +
+ max ((wy-crpix)*crval, (wy-crpix)*cdelt)
+ call gmark (gp, wx, wy, GM_POINT, 2, 2)
+ }
+ }
+
+ redraw = NO
+ }
+ } until (clgcur ("gcur", wx, wy, wcs, key, Memc[cmd], SZ_LINE) == EOF)
+
+ call gdeactivate (gp, 0)
+ call gt_free (gt)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/autoid/aidinit.x b/noao/onedspec/identify/autoid/aidinit.x
new file mode 100644
index 00000000..ac86b34d
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidinit.x
@@ -0,0 +1,93 @@
+include <smw.h>
+include "../identify.h"
+include "autoid.h"
+
+
+# AID_INIT -- Create AID object and initialize algorithm parameters.
+
+procedure aid_init (aid, pset)
+
+pointer aid #O AID object
+char pset[ARB] #I Pset for parameters
+
+pointer pp, clopset()
+int clgpseti(), strdic()
+double clgpsetd()
+
+begin
+ call calloc (aid, AID_LEN, TY_STRUCT)
+
+ # Set default parameters. This can be overridden later by
+ # the application.
+
+ pp = clopset (pset)
+
+ #call clgpseta (pp, "crval", AID_CR(aid), AID_SZLINE)
+ #call clgpseta (pp, "cdelt", AID_CD(aid), AID_SZLINE)
+ call strcpy ("INDEF", AID_CR(aid), AID_SZLINE)
+ call strcpy ("INDEF", AID_CD(aid), AID_SZLINE)
+
+ call clgpseta (pp, "reflist", AID_REFLIST(aid), AID_SZLINE)
+ call clgpseta (pp, "refspec", AID_REFSPEC(aid), AID_SZLINE)
+ call clgpseta (pp, "crpix", AID_CP(aid), AID_SZLINE)
+ call clgpseta (pp, "crquad", AID_CQ(aid), AID_SZLINE)
+ call clgpseta (pp, "cddir", AID_DEBUG(aid,1), AID_SZLINE)
+ AID_CDDIR(aid) = strdic (AID_DEBUG(aid,1), AID_DEBUG(aid,1),
+ AID_SZLINE, CDDIR)
+ call clgpseta (pp, "crsearch", AID_CRS(aid), AID_SZLINE)
+ call clgpseta (pp, "cdsearch", AID_CDS(aid), AID_SZLINE)
+ AID_NTMAX(aid) = clgpseti (pp, "ntarget")
+ #AID_NRMAX(aid) = clgpseti (pp, "nreference")
+ AID_NRMAX(aid) = 2 * AID_NTMAX(aid)
+ AID_ORD(aid) = clgpseti (pp, "aidord")
+ AID_MAXNL(aid) = clgpsetd (pp, "maxnl")
+ AID_NB(aid) = clgpseti (pp, "nbins")
+ AID_NN(aid) = clgpseti (pp, "nneighbors")
+ AID_NP(aid) = clgpseti (pp, "npattern")
+ AID_SIG(aid) = clgpsetd (pp, "sigma")
+ AID_NFOUND(aid) = clgpseti (pp, "nfound")
+ AID_RMSG(aid) = clgpsetd (pp, "rms")
+ AID_FMATCHG(aid) = clgpsetd (pp, "fmatch")
+ AID_FTMATCHG(aid) = clgpsetd (pp, "fmatch")
+ AID_MINRATIO(aid) = clgpsetd (pp, "minratio")
+ AID_NDMAX(aid) = clgpseti (pp, "ndmax")
+ call clgpseta (pp, "debug", AID_DEBUG(aid,1), AID_SZLINE)
+ AID_NBEST(aid) = 3
+ AID_WRMS(aid) = 0.34
+ AID_WFMATCH(aid) = 0.33
+ AID_WFTMATCH(aid) = 0.33
+ call clcpset (pp)
+
+ call ic_open (AID_IC1(aid))
+ call ic_pstr (AID_IC1(aid), "function", "chebyshev")
+ call ic_puti (AID_IC1(aid), "order", AID_ORD(aid))
+ call ic_puti (AID_IC1(aid), "niterate", 5)
+ call ic_putr (AID_IC1(aid), "low", 2.)
+ call ic_putr (AID_IC1(aid), "high", 2.)
+end
+
+
+# AID_FREE -- Free memory associated with the AID algorithms.
+
+procedure aid_free (aid)
+
+pointer aid #U AID object
+
+begin
+ if (AID_IDR(aid) != NULL) {
+ if (ID_SH(AID_IDR(aid)) != NULL) {
+ call smw_close (MW(ID_SH(AID_IDR(aid))))
+ call imunmap (IM(ID_SH(AID_IDR(aid))))
+ call shdr_close (ID_SH(AID_IDR(aid)))
+ }
+ }
+
+ call ic_closed (AID_IC1(aid))
+ call mfree (AID_SPECR(aid), TY_REAL)
+ call mfree (AID_XR(aid), TY_DOUBLE)
+ call mfree (AID_XT(aid), TY_DOUBLE)
+ call mfree (AID_XTF(aid), TY_DOUBLE)
+ call id_free (AID_IDR(aid))
+ call mfree (AID_EVS(aid), TY_POINTER)
+ call mfree (aid, TY_STRUCT)
+end
diff --git a/noao/onedspec/identify/autoid/aidlog.x b/noao/onedspec/identify/autoid/aidlog.x
new file mode 100644
index 00000000..b0247d00
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidlog.x
@@ -0,0 +1,57 @@
+include "../identify.h"
+
+
+# AID_LOG -- Log final solution.
+
+procedure aid_log (id, fd, hdr)
+
+pointer id #I ID object
+int fd #I Log file descriptor
+int hdr #U Print header?
+
+double wc, dw, id_fitpt(), id_rms()
+pointer str
+bool fp_equald()
+
+begin
+ if (fd == NULL)
+ return
+
+ if (fd == STDOUT && ID_GP(id) != NULL)
+ call gdeactivate (ID_GP(id), 0)
+
+ if (hdr == YES) {
+ call malloc (str, SZ_LINE, TY_CHAR)
+ call sysid (Memc[str], SZ_LINE)
+ call fprintf (fd, "\nAUTOIDENTIFY: %s\n")
+ call pargstr (Memc[str])
+ call mfree (str, TY_CHAR)
+
+ call fprintf (fd, " %-20s %10s %10s %10s %10s\n")
+ call pargstr ("Spectrum")
+ call pargstr ("# Found")
+ call pargstr ("Midpoint")
+ call pargstr ("Dispersion")
+ call pargstr ("RMS")
+
+ hdr = NO
+ }
+
+ call fprintf (fd, " %s%s%24t ")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ if (ID_CV(id) == NULL)
+ call fprintf (fd, " No solution found\n")
+ else {
+ wc = id_fitpt (id, (ID_NPTS(id) + 1D0) / 2D0)
+ dw = wc - id_fitpt (id, (ID_NPTS(id) - 1D0) / 2D0)
+ if (!fp_equald (dw, 0D0)) {
+ call fprintf (fd, "%10d %10.*g %10.3g %10.3g\n")
+ call pargi (ID_NFEATURES(id))
+ call pargi (int (log10 (abs (wc / dw)) + 3))
+ call pargd (wc)
+ call pargd (dw)
+ call pargd (id_rms(id))
+ }
+ }
+end
diff --git a/noao/onedspec/identify/autoid/aidset.x b/noao/onedspec/identify/autoid/aidset.x
new file mode 100644
index 00000000..5905002b
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidset.x
@@ -0,0 +1,162 @@
+include "autoid.h"
+
+define AIDSET "|reflist|refspec|crval|cdelt|crpix|crquad|crsearch|cdsearch\
+ |cddir|ntarget|nreference|aidord|maxnl|nbins|nneighbors\
+ |npattern|sigma|nfound|rms|fmatch|ftmatch|minratio|ndmax\
+ |debug|nbest|wrms|wfmatch|wftmatch|"
+
+
+# AID_SETS -- Set AID parameters by name.
+# If the first word of the value field is "CL" or "ENV" then the second
+# word is the CL parameter name or environment variable name to use
+# for the value.
+
+procedure aid_sets (aid, param, value)
+
+pointer aid #I AID object
+char param[ARB] #I Parameter name
+char value[ARB] #I Value
+
+int i, j, strdic(), strncmp(), envfind(), nowhite(), ctoi(), ctor(), ctod()
+pointer sp, str
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ i = strdic (param, Memc[str], SZ_LINE, AIDSET)
+
+ if (strncmp ("CL ", value, 3) == 0)
+ call clgstr (value[4], Memc[str], SZ_LINE)
+ else if (strncmp ("ENV ", value, 4) == 0) {
+ if (envfind (value[5], Memc[str], SZ_LINE) <= 0)
+ Memc[str] = EOS
+ } else
+ call strcpy (value, Memc[str], SZ_LINE)
+ j = nowhite (Memc[str], Memc[str], SZ_LINE)
+
+ j = 1
+ switch (i) {
+ case 1:
+ call strcpy (Memc[str], AID_REFLIST(aid), AID_SZLINE)
+ case 2:
+ call strcpy (Memc[str], AID_REFSPEC(aid), AID_SZLINE)
+ case 3:
+ call strcpy (Memc[str], AID_CR(aid), AID_SZLINE)
+ case 4:
+ call strcpy (Memc[str], AID_CD(aid), AID_SZLINE)
+ case 5:
+ call strcpy (Memc[str], AID_CP(aid), AID_SZLINE)
+ case 6:
+ i = ctod (Memc[str], j, AID_CRQUAD(aid))
+ case 7:
+ call strcpy (Memc[str], AID_CRS(aid), AID_SZLINE)
+ case 8:
+ call strcpy (Memc[str], AID_CDS(aid), AID_SZLINE)
+ case 9:
+ AID_CDDIR(aid) = strdic (Memc[str], Memc[str], SZ_LINE, CDDIR)
+ if (AID_CDDIR(aid) == 0)
+ AID_CDDIR(aid) = CDUNKNOWN
+ case 10:
+ i = ctoi (Memc[str], j, AID_NTMAX(aid))
+ case 11:
+ i = ctoi (Memc[str], j, AID_NRMAX(aid))
+ case 12:
+ i = ctoi (Memc[str], j, AID_ORD(aid))
+ call ic_puti (AID_IC1(aid), "order", AID_ORD(aid))
+ case 13:
+ i = ctor (Memc[str], j, AID_MAXNL(aid))
+ case 14:
+ i = ctoi (Memc[str], j, AID_NB(aid))
+ case 15:
+ i = ctoi (Memc[str], j, AID_NN(aid))
+ case 16:
+ i = ctoi (Memc[str], j, AID_NP(aid))
+ case 17:
+ i = ctor (Memc[str], j, AID_SIG(aid))
+ case 18:
+ i = ctoi (Memc[str], j, AID_NFOUND(aid))
+ case 19:
+ i = ctor (Memc[str], j, AID_RMSG(aid))
+ case 20:
+ i = ctor (Memc[str], j, AID_FMATCHG(aid))
+ case 21:
+ i = ctor (Memc[str], j, AID_FTMATCHG(aid))
+ case 22:
+ i = ctor (Memc[str], j, AID_MINRATIO(aid))
+ case 23:
+ i = ctoi (Memc[str], j, AID_NDMAX(aid))
+ case 24:
+ call strcpy (Memc[str], AID_DEBUG(aid,1), AID_SZLINE)
+ case 25:
+ i = ctoi (Memc[str], j, AID_NBEST(aid))
+ case 26:
+ i = ctor (Memc[str], j, AID_WRMS(aid))
+ case 27:
+ i = ctor (Memc[str], j, AID_WFMATCH(aid))
+ case 28:
+ i = ctor (Memc[str], j, AID_WFTMATCH(aid))
+ }
+
+ call sfree (sp)
+end
+
+
+# AID_SETI -- Set integer AID parameters.
+
+procedure aid_seti (aid, param, ival)
+
+pointer aid #I AID object
+char param[ARB] #I Parameter name
+int ival #I Value
+
+pointer sp, str
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call sprintf (Memc[str], SZ_FNAME, "%d")
+ call pargi (ival)
+ call aid_sets (aid, param, Memc[str])
+ call sfree (sp)
+end
+
+
+# AID_SETR -- Set real AID parameters.
+
+procedure aid_setr (aid, param, rval)
+
+pointer aid #I AID object
+char param[ARB] #I Parameter name
+real rval #I Value
+
+pointer sp, str
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call sprintf (Memc[str], SZ_FNAME, "%g")
+ call pargr (rval)
+ call aid_sets (aid, param, Memc[str])
+ call sfree (sp)
+end
+
+
+# AID_SETD -- Set double AID parameters.
+
+procedure aid_setd (aid, param, dval)
+
+pointer aid #I AID object
+char param[ARB] #I Parameter name
+double dval #I Value
+
+pointer sp, str
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call sprintf (Memc[str], SZ_FNAME, "%g")
+ call pargd (dval)
+ call aid_sets (aid, param, Memc[str])
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/autoid/aidshift.x b/noao/onedspec/identify/autoid/aidshift.x
new file mode 100644
index 00000000..1b910338
--- /dev/null
+++ b/noao/onedspec/identify/autoid/aidshift.x
@@ -0,0 +1,67 @@
+include "../identify.h"
+
+
+# AID_SHIFT -- Find a new shifted dispersion solution assuming (nearly) the
+# same dispersion per pixel and the same dispersion direction. The shift is
+# assumed to be less than or equal to the dispersion range of the input
+# dispersion. The input is an ID pointer have the previous dispersion
+# solution and features but with the new spectrum. If there are more than 10
+# features then the list of user feature coordinates is used as the reference
+# list. If there are not enough features or the previous search fails then
+# the the coordinate list is used as the reference. The returned result is a
+# new ID pointer if the algorithm succeeds or the original ID pointer if it
+# fails along with an error status.
+
+procedure aid_shift (id, crsearch, cdsearch)
+
+pointer id #I ID object
+double crsearch #I Search range
+double cdsearch #I Search range
+
+pointer aid
+bool found, aid_autoid()
+double crpix, crval, cdelt, id_fitpt()
+
+begin
+ # Set approximate dispersion from input dispersion solution.
+ crpix = ID_NPTS(id) / 2 + 1
+ crval = id_fitpt (id, crpix)
+ cdelt = (FITDATA(id,ID_NPTS(id)) - FITDATA(id,1)) /
+ (ID_NPTS(id) - 1)
+
+ # Initialize AUTOID.
+ call aid_init (aid, "aidpars")
+ call aid_setd (aid, "crval", crval)
+ call aid_setd (aid, "cdelt", cdelt)
+ call aid_setd (aid, "crpix", crpix)
+ call aid_sets (aid, "cddir", "sign")
+ call aid_setd (aid, "crsearch", crsearch)
+ call aid_setd (aid, "cdsearch", cdsearch)
+ call aid_seti (aid, "nbest", 5)
+
+ found = false
+ if (ID_NFEATURES(id) > 10) {
+ # Try shift using features.
+ call aid_seti (aid, "ntarget", ID_NFEATURES(id))
+ call aid_seti (aid, "nreference", ID_NFEATURES(id))
+ call aid_setr (aid, "wrms", 0.5)
+ call aid_setr (aid, "wfmatch", 0.5)
+ call aid_setr (aid, "wftmatch", 0.)
+ call aid_sets (aid, "refspec", "FEATURES")
+ found = aid_autoid (id, aid)
+ }
+ if (!found) {
+ # Try shift using coordinate list.
+ call aid_seti (aid, "ntarget", max (ID_NFEATURES(id),20))
+ call aid_seti (aid, "nreference", max (ID_NFEATURES(id),40))
+ call aid_setr (aid, "wrms", 0.5)
+ call aid_setr (aid, "wfmatch", 0.25)
+ call aid_setr (aid, "wftmatch", 0.25)
+ call aid_sets (aid, "refspec", "COORDLIST")
+ found = aid_autoid (id, aid)
+ }
+
+ call aid_free (aid)
+ if (!found)
+ call error (1, "No solution not found")
+end
diff --git a/noao/onedspec/identify/autoid/autoid.h b/noao/onedspec/identify/autoid/autoid.h
new file mode 100644
index 00000000..304d675a
--- /dev/null
+++ b/noao/onedspec/identify/autoid/autoid.h
@@ -0,0 +1,90 @@
+# AUTOIDENTIFY data structure.
+
+define AID_SZLINE 99
+define AID_LEN 512
+
+# Algorithm input parameters.
+define AID_REFLIST Memc[P2C($1)] # Reference coordinate list
+define AID_REFSPEC Memc[P2C($1+50)] # Reference spectrum
+define AID_CR Memc[P2C($1+100)] # Coordinate reference value
+define AID_CD Memc[P2C($1+150)] # Coordinate reference value
+define AID_CP Memc[P2C($1+200)] # Coordinate reference value
+define AID_CQ Memc[P2C($1+250)] # Coordinate quad distortion
+define AID_CRS Memc[P2C($1+300)] # Coordinate reference value
+define AID_CDS Memc[P2C($1+350)] # Coordinate reference value
+define AID_DEBUG Memc[P2C($1+400)+ 2-1] # Debug flags (19 chars)
+define AID_CDDIR Memi[$1+450] # Coordinate direction
+define AID_NTMAX Memi[$1+451] # Maximum number of target lines
+define AID_NRMAX Memi[$1+452] # Maximum number of reference lines
+define AID_ORD Memi[$1+453] # Maximum fitting order
+define AID_MAXNL Memr[P2R($1+454)] # Maximum non-linearity
+define AID_NB Memi[$1+455] # Number of sub-bins
+define AID_NN Memi[$1+456] # Number of neighbor lines
+define AID_NP Memi[$1+457] # Number of lines in pattern
+define AID_SIG Memr[P2R($1+458)] # Target line centering sigma
+define AID_NFOUND Memi[$1+459] # Minimum number to be found
+define AID_RMSG Memr[P2R($1+460)] # Pixel RMS (goal)
+define AID_FMATCHG Memr[P2R($1+461)] # Frac of unmatched lines (goal)
+define AID_FTMATCHG Memr[P2R($1+462)] # Frac of unmatched target lines (goal)
+
+define AID_IDT Memi[$1+463] # Target ID pointer
+define AID_IDR Memi[$1+464] # Reference ID pointer
+define AID_IC1 Memi[$1+465] # ICFIT pointer
+define AID_IC2 Memi[$1+466] # ICFIT pointer
+
+define AID_XR Memi[$1+467] # Reference lines (ptr)
+define AID_NR Memi[$1+468] # Number of reference lines
+define AID_XTF Memi[$1+469] # Full target lines sorted by peak
+define AID_NTF Memi[$1+470] # Full number of target lines
+define AID_XT Memi[$1+471] # Target lines to use sorted by pix
+define AID_XTL Memi[$1+472] # Linearized target lines sort by pix
+define AID_NT Memi[$1+473] # Number of target lines to use
+
+define AID_CDSIGN Memi[$1+474] # Sign of coordinate interval
+define AID_CRVAL Memd[P2D($1+476)] # Reference coordinate value
+define AID_CDELT Memd[P2D($1+478)] # Coordinate interval per pixel
+define AID_CRPIX Memd[P2D($1+480)] # Reference pixel
+define AID_CRQUAD Memd[P2D($1+482)] # Quadratic distortion
+define AID_CRSEARCH Memd[P2D($1+484)] # Search radius for ref value
+define AID_CDSEARCH Memd[P2D($1+486)] # Search radius for coord int
+define AID_CRMIN Memd[P2D($1+488)] # Min for central coordinate
+define AID_CRMAX Memd[P2D($1+490)] # Max for central coordinate
+define AID_CDMIN Memd[P2D($1+492)] # Min for coordinate interval
+define AID_CDMAX Memd[P2D($1+494)] # Max for coordinate interval
+
+define AID_MINRATIO Memr[P2R($1+496)] # Minimum ratio
+define AID_NDMAX Memi[$1+497] # Max number of dispersions to check
+define AID_RMS Memr[P2R($1+498)] # Pixel RMS (best)
+define AID_FMATCH Memr[P2R($1+499)] # Fraction of unmatched linelist lines
+define AID_FTMATCH Memr[P2R($1+500)] # Fraction of unmatched target lines
+define AID_WRMS Memr[P2R($1+501)] # Weight for RMS
+define AID_WFMATCH Memr[P2R($1+502)] # Weight for FMATCH
+define AID_WFTMATCH Memr[P2R($1+503)] # Weight for FTMATCH
+define AID_NBEST Memi[$1+504] # Number of best values < 1 to check
+define AID_BEST Memr[P2R($1+505)] # Best fit parameter
+define AID_EVS Memi[$1+506] # Evaluate structure
+
+define AID_SPECR Memi[$1+507] # Reference spectrum (ptr)
+define AID_X1R Memi[$1+508] # First pixel of full ref spectrum
+define AID_X2R Memi[$1+509] # Last pixel of full ref spectrum
+define AID_W1 Memr[P2R($1+510)] # Tentative wavelength of first pixel
+define AID_W2 Memr[P2R($1+511)] # Tentative wavelength of last pixel
+
+
+# Evaluation structure.
+define AID_EVLEN 8
+define AID_BIN1 Memi[$1] # Reference sample bin
+define AID_BIN2 Memi[$1+1] # Reference sample bin
+define AID_X Memi[$1+2] # Pixel coordinates
+define AID_Y Memi[$1+3] # Dispersion coordinates
+define AID_N Memi[$1+4] # Number of coordinate pairs
+define AID_A Memi[$1+5] # Trial dispersion start
+define AID_B Memi[$1+6] # Trial dispersion step
+define AID_ND Memi[$1+7] # Number of trial dispersions
+
+# Dispersion direction options.
+define CDDIR "|sign|increasing|decreasing|unknown|"
+define CDSIGN 1
+define CDINC 2
+define CDDEC 3
+define CDUNKNOWN 4
diff --git a/noao/onedspec/identify/autoid/autoid.x b/noao/onedspec/identify/autoid/autoid.x
new file mode 100644
index 00000000..3f169ca7
--- /dev/null
+++ b/noao/onedspec/identify/autoid/autoid.x
@@ -0,0 +1,1600 @@
+include <mach.h>
+include <error.h>
+include <smw.h>
+include "../identify.h"
+include "autoid.h"
+
+
+# AID_TARGET -- Select target lines and the dispersion limits to be searched.
+# The dispersion limits may be based on header parameters.
+
+procedure aid_target (aid)
+
+pointer aid #I AID pointer
+
+int i, j, l, nt, n
+double dw, dwmin, dwmax, pix, aid_imgd(), id_center()
+pointer sp, x, y, idt, idr, im, xt, xtl
+int id_upeaks(), stridxs()
+errchk id_upeaks, id_center
+
+begin
+ call smark (sp)
+ call salloc (x, ID_NPTS(AID_IDT(aid)), TY_REAL)
+
+ idt = AID_IDT(aid)
+ idr = AID_IDR(aid)
+ im = IM(ID_SH(idt))
+ nt = ID_NPTS(idt)
+
+ # Set the approximate coordinate information.
+ AID_CRVAL(aid) = aid_imgd (im, AID_CR(aid))
+ AID_CDELT(aid) = aid_imgd (im, AID_CD(aid))
+ AID_CRPIX(aid) = aid_imgd (im, AID_CP(aid))
+ AID_CRQUAD(aid) = aid_imgd (im, AID_CQ(aid))
+ AID_CRSEARCH(aid) = aid_imgd (im, AID_CRS(aid))
+ AID_CDSEARCH(aid) = aid_imgd (im, AID_CDS(aid))
+
+ if (IS_INDEFD(AID_CRPIX(aid)))
+ AID_CRPIX(aid) = (nt+1) / 2.
+
+ if (IS_INDEFD(AID_CRQUAD(aid)))
+ AID_CRQUAD(aid) = 0D0
+
+ if (!IS_INDEFD(AID_CRVAL(aid)) && !IS_INDEFD(AID_CDELT(aid))) {
+ dw = nt * AID_CDELT(aid)
+ if (IS_INDEFD(AID_CRSEARCH(aid)))
+ AID_CRSEARCH(aid) = abs (0.1 * dw)
+ else if (AID_CRSEARCH(aid) < 0.)
+ AID_CRSEARCH(aid) = abs (AID_CRSEARCH(aid) * dw)
+ if (IS_INDEFD(AID_CDSEARCH(aid)))
+ AID_CDSEARCH(aid) = abs (0.1 * AID_CDELT(aid))
+ else if (AID_CDSEARCH(aid) < 0.)
+ AID_CDSEARCH(aid) = abs (AID_CDSEARCH(aid) * AID_CDELT(aid))
+ AID_CRSEARCH(aid) = max (abs (0.0001 * dw),
+ AID_CRSEARCH(aid))
+ AID_CDSEARCH(aid) = max (abs (0.0001 * AID_CDELT(aid)),
+ AID_CDSEARCH(aid))
+ dwmax = 2 * AID_CRSEARCH(aid) + (nt - 1) *
+ (abs (AID_CDELT(aid)) + AID_CDSEARCH(aid))
+ dwmin = (abs (AID_CDELT(aid)) - AID_CDSEARCH(aid)) * (nt - 1)
+ dwmin = max (1.0D-1, dwmin / dwmax)
+ AID_NB(aid) = nint (1. / dwmin)
+ }
+
+ # Find the peaks in the target spectrum.
+ if (ID_FTYPE(idt) == ABSORPTION) {
+ call anegr (IMDATA(idt,1), IMDATA(idt,1), nt)
+ n = id_upeaks (idt, IMDATA(idt,1), Memr[x], nt, INDEF,
+ int (ID_MINSEP(idt)), 0, AID_NTMAX(aid), 5, INDEF, false)
+ call anegr (IMDATA(idt,1), IMDATA(idt,1), nt)
+ } else {
+ n = id_upeaks (idt, IMDATA(idt,1), Memr[x], nt, INDEF,
+ int (ID_MINSEP(idt)), 0, AID_NTMAX(aid), 5, INDEF, false)
+ }
+ call salloc (y, n, TY_REAL)
+ do i = 1, n
+ Memr[y+i-1] = -IMDATA(idt,nint(Memr[x+i-1]))
+ call xt_sort2 (Memr[y], Memr[x], n)
+
+ # Center and sort the lines.
+ if (AID_XTF(aid) == NULL)
+ call malloc (AID_XTF(aid), n, TY_DOUBLE)
+ else
+ call realloc (AID_XTF(aid), n, TY_DOUBLE)
+ xt = AID_XTF(aid)
+
+ j = 0
+ do i = 1, n {
+ pix = Memr[x+i-1]
+ pix = id_center (idt, pix, ID_FWIDTH(idt), ID_FTYPE(idt))
+ if (IS_INDEFD(pix))
+ next
+ if (IS_INDEFD(pix))
+ next
+ do l = 1, j {
+ if (abs (pix-Memd[xt+l-1]) < 1.)
+ break
+ }
+ if (l <= j)
+ next
+ Memd[xt+j] = pix
+ j = j + 1
+ }
+ AID_NTF(aid) = j
+
+ # Sort the lines.
+ if (AID_XT(aid) == NULL)
+ call malloc (AID_XT(aid), j, TY_DOUBLE)
+ else
+ call realloc (AID_XT(aid), j, TY_DOUBLE)
+ xt = AID_XT(aid)
+ if (j > 0)
+ call asrtd (Memd[AID_XTF(aid)], Memd[xt], j)
+ else {
+ call salloc (x, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[x], SZ_LINE, "No target lines found in `%s'")
+ call pargstr (ID_IMAGE(idt))
+ call error (1, Memc[x])
+ }
+
+ # Linearize the lines.
+ if (AID_XTL(aid) == NULL)
+ call malloc (AID_XTL(aid), j, TY_DOUBLE)
+ else
+ call realloc (AID_XTL(aid), j, TY_DOUBLE)
+ xt = AID_XT(aid)
+ xtl = AID_XTL(aid)
+ do i = 1, j
+ Memd[xtl+i-1] = Memd[xt+i-1] +
+ AID_CRQUAD(aid) * (Memd[xt+i-1]-AID_CRPIX(aid))**2
+
+ # Debug t: Print list of target lines.
+ if (stridxs ("t", AID_DEBUG(aid,1)) != 0) {
+ call eprintf ("# Selected target lines:\n")
+ call eprintf ("#%10s %11s\n")
+ call pargstr ("Measured")
+ call pargstr ("Undistorted")
+ do i = 1, j {
+ call eprintf ("%11.6g %11.6g\n")
+ call pargd (Memd[xt+i-1])
+ call pargd (Memd[xtl+i-1])
+ }
+ call eprintf ("\n")
+ }
+
+ call sfree (sp)
+end
+
+
+# AID_REFERENCE -- Set reference lines from spectrum or line list.
+
+procedure aid_reference (aid, ev, flip)
+
+pointer aid #I AID pointer
+pointer ev #I EV pointer
+int flip #I Flip dispersion?
+
+int i, j, i1, i2, npts, nr, nt, nll, id_peaks(), stridxs()
+double w, w0, w1, wp, cdelt, wa, wb
+real sig, wt, center1d()
+pointer sp, x, idt, idr, specr, xr, sh, label, ll
+double shdr_wl(), shdr_lw()
+errchk id_peaks, center1d
+
+begin
+ call smark (sp)
+
+ idr = AID_IDR(aid)
+ npts = ID_NPTS(idr)
+ sh = ID_SH(idr)
+ specr = AID_SPECR(aid)
+ idt = AID_IDT(aid)
+ nt = ID_NPTS(idt)
+
+ # Set reference parameters.
+ if (sh != NULL) {
+ w0 = min (W0(sh), W1(sh))
+ w1 = max (W0(sh), W1(sh))
+ wp = abs (WP(sh))
+ } else {
+ ll = ID_LL(idr)
+ nll = ID_NLL(idr)
+ if (ll == NULL) {
+ ll = ID_LL(idt)
+ nll = ID_NLL(idt)
+ }
+ x = ll
+ w0 = Memd[x]
+ w1 = Memd[x+nll-1]
+ wp = INDEFD
+ }
+
+ # Set limits for reference coordinate and dispersion values.
+ AID_CRMIN(aid) = -MAX_DOUBLE
+ AID_CRMAX(aid) = MAX_DOUBLE
+ AID_CDMIN(aid) = 0D0
+ AID_CDMAX(aid) = MAX_DOUBLE
+ if (IS_INDEFD(AID_CDELT(aid))) {
+ switch (AID_CDDIR(aid)) {
+ case CDINC:
+ AID_CDSIGN(aid) = 1
+ case CDDEC:
+ AID_CDSIGN(aid) = -1
+ default:
+ if (flip == YES)
+ AID_CDSIGN(aid) = -1
+ else
+ AID_CDSIGN(aid) = 1
+ }
+
+ if (!IS_INDEFD(AID_CRVAL(aid))) {
+ AID_CRMIN(aid) = AID_CRVAL(aid) - AID_CRSEARCH(aid)
+ AID_CRMAX(aid) = AID_CRVAL(aid) + AID_CRSEARCH(aid)
+ }
+
+ if (sh != NULL) {
+ i1 = 1
+ i2 = npts
+ sig = 0.
+ } else {
+ wa = -MAX_DOUBLE
+ wb = MAX_DOUBLE
+ }
+
+ AID_W1(aid) = INDEF
+ AID_W2(aid) = INDEF
+ } else if (IS_INDEFD(AID_CRVAL(aid))) {
+ switch (AID_CDDIR(aid)) {
+ case CDINC:
+ cdelt = abs (AID_CDELT(aid))
+ AID_CDSIGN(aid) = 1
+ case CDDEC:
+ cdelt = -abs (AID_CDELT(aid))
+ AID_CDSIGN(aid) = -1
+ default:
+ if (flip == YES)
+ cdelt = -AID_CDELT(aid)
+ else
+ cdelt = AID_CDELT(aid)
+ if (cdelt < 0.)
+ AID_CDSIGN(aid) = -1
+ else
+ AID_CDSIGN(aid) = 1
+ }
+
+ AID_CDMIN(aid) = abs (cdelt) - AID_CDSEARCH(aid)
+ AID_CDMAX(aid) = abs (cdelt) + AID_CDSEARCH(aid)
+
+ if (sh != NULL) {
+ i1 = 1
+ i2 = npts
+ sig = abs (AID_CDELT(aid)) / wp
+ } else {
+ wa = -MAX_DOUBLE
+ wb = MAX_DOUBLE
+ }
+
+ AID_W1(aid) = INDEF
+ AID_W2(aid) = INDEF
+ } else {
+ switch (AID_CDDIR(aid)) {
+ case CDINC:
+ cdelt = abs (AID_CDELT(aid))
+ AID_CDSIGN(aid) = 1
+ case CDDEC:
+ cdelt = -abs (AID_CDELT(aid))
+ AID_CDSIGN(aid) = -1
+ default:
+ if (flip == YES)
+ cdelt = -AID_CDELT(aid)
+ else
+ cdelt = AID_CDELT(aid)
+ if (cdelt < 0.)
+ AID_CDSIGN(aid) = -1
+ else
+ AID_CDSIGN(aid) = 1
+ }
+
+ AID_CRMIN(aid) = AID_CRVAL(aid) - AID_CRSEARCH(aid)
+ AID_CRMAX(aid) = AID_CRVAL(aid) + AID_CRSEARCH(aid)
+ AID_CDMIN(aid) = abs (cdelt) - AID_CDSEARCH(aid)
+ AID_CDMAX(aid) = abs (cdelt) + AID_CDSEARCH(aid)
+
+ if (cdelt > 0.) {
+ wa = AID_CRMIN(aid) + (cdelt + AID_CDSEARCH(aid)) *
+ (1 - AID_CRPIX(aid))
+ wb = AID_CRMAX(aid) + (cdelt + AID_CDSEARCH(aid)) *
+ (nt - AID_CRPIX(aid))
+ } else {
+ wa = AID_CRMIN(aid) + (cdelt - AID_CDSEARCH(aid)) *
+ (nt - AID_CRPIX(aid))
+ wb = AID_CRMAX(aid) + (cdelt - AID_CDSEARCH(aid)) *
+ (1 - AID_CRPIX(aid))
+ }
+
+ if (stridxs ("m", AID_DEBUG(aid,1)) != 0) {
+ call eprintf ("wa=%g wb=%g\n")
+ call pargd (wa)
+ call pargd (wb)
+ }
+
+ if (sh != NULL) {
+ i1 = max (1, min (npts, nint (shdr_wl (sh, wa))))
+ i2 = max (1, min (npts, nint (shdr_wl (sh, wb))))
+ sig = abs (AID_CDELT(aid)) / wp
+ }
+
+ AID_W1(aid) = AID_CRVAL(aid) + (1-AID_CRPIX(aid)) * cdelt
+ AID_W2(aid) = AID_CRVAL(aid) + (nt-AID_CRPIX(aid)) * cdelt
+ }
+
+ # Select lines from line list.
+ if (ID_IMAGE(idr) == EOS) {
+ ll = ID_LL(idr)
+ if (ll == NULL)
+ ll = ID_LL(idt)
+ x = ll
+ npts = 0
+ while (!IS_INDEFD(Memd[x])) {
+ if (Memd[x] > wb)
+ break
+ if (Memd[x] >= wa)
+ npts = npts + 1
+ x = x + 1
+ }
+ x = x - npts
+ if (npts == 0) {
+ call salloc (x, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[x], SZ_LINE, "No reference lines found")
+ call error (1, Memc[x])
+ }
+
+ wa = Memd[x]
+ wb = Memd[x+npts-1] - Memd[x]
+ wb = wb / ((AID_BIN1(ev) + 1) / 2)
+ wa = wa + wb / 2 * (AID_BIN2(ev) - 1)
+ wb = wa + wb
+
+ x = ll
+ npts = 0
+ while (!IS_INDEFD(Memd[x])) {
+ if (Memd[x] > wb)
+ break
+ if (Memd[x] >= wa)
+ npts = npts + 1
+ x = x + 1
+ }
+ x = x - npts
+ if (npts == 0) {
+ call salloc (x, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[x], SZ_LINE, "No reference lines found")
+ call error (1, Memc[x])
+ }
+
+ AID_NRMAX(aid) = npts
+ nr = AID_NRMAX(aid)
+ AID_NR(aid) = nr
+ if (AID_XR(aid) == NULL)
+ call malloc (AID_XR(aid), nr, TY_DOUBLE)
+ else
+ call realloc (AID_XR(aid), nr, TY_DOUBLE)
+ xr = AID_XR(aid)
+
+ if (nr < npts) {
+ w = real (npts) / nr
+ do i = 0, nr {
+ j = i * w
+ Memd[xr+i] = Memd[x+j]
+ }
+ } else {
+ do i = 0, nr-1
+ Memd[xr+i] = Memd[x+i]
+ }
+
+ # Select lines using reference spectrum.
+ } else {
+ wb = (i2 - i1) / ((AID_BIN1(ev) + 1) / 2)
+ i1 = max (i1, nint (i1 + wb / 2 * (AID_BIN2(ev) - 1)))
+ i2 = min (i2, nint (i1 + wb))
+
+ if (i2 - i1 + 1 < 100) {
+ i1 = 1
+ i2 = npts
+ }
+ npts = i2 - i1 + 1
+
+ if (specr == NULL)
+ call malloc (specr, npts, TY_REAL)
+ else
+ call realloc (specr, npts, TY_REAL)
+ AID_SPECR(aid) = specr
+ AID_X1R(aid) = i1
+ AID_X2R(aid) = i2
+ wa = Memr[SX(sh)+i1-1]
+ wb = Memr[SX(sh)+i2-1]
+ call amovr (IMDATA(idr,i1), Memr[specr], npts)
+
+ if (sig > 1.) {
+ ID_MINSEP(idr) = sig * ID_MINSEP(idt)
+ ID_FWIDTH(idr) = sig * ID_FWIDTH(idt)
+ sig = sig / 1.1774
+ j = nint (3 * sig)
+ call malloc (x, npts, TY_REAL)
+ call malloc (xr, npts+2*j+1, TY_REAL)
+ xr = xr + j
+ call amovr (Memr[specr], Memr[xr], npts)
+ do i = 1, j {
+ wt = exp (-0.5 * (i / sig) ** 2)
+ call amulkr (Memr[specr], wt, Memr[x], npts)
+ call aaddr (Memr[x], Memr[xr+i], Memr[xr+i], npts)
+ call aaddr (Memr[x], Memr[xr-i], Memr[xr-i], npts)
+ }
+ call amovr (Memr[xr], Memr[specr], npts)
+ call mfree (x, TY_REAL)
+ call mfree (xr-j, TY_REAL)
+ }
+
+ call salloc (x, npts, TY_REAL)
+
+ # Find the peaks in the reference spectrum.
+ AID_NRMAX(aid) = 2 * AID_NTF(aid)
+ if (ID_FTYPE(idr) == ABSORPTION) {
+ call anegr (Memr[specr], Memr[specr], nt)
+ nr = id_peaks (idr, Memr[specr], Memr[x], npts, INDEF,
+ int (ID_MINSEP(idr)), 0, AID_NRMAX(aid), INDEF, false)
+ call anegr (Memr[specr], Memr[specr], nt)
+ } else {
+ nr = id_peaks (idr, Memr[specr], Memr[x], npts, INDEF,
+ int (ID_MINSEP(idr)), 0, AID_NRMAX(aid), INDEF, false)
+ }
+
+ # Center and sort the lines.
+ if (AID_XR(aid) == NULL)
+ call malloc (AID_XR(aid), nr, TY_DOUBLE)
+ else
+ call realloc (AID_XR(aid), nr, TY_DOUBLE)
+ xr = AID_XR(aid)
+
+ j = 0
+ label = NULL
+ do i = 1, nr {
+ wt = center1d (Memr[x+i-1], Memr[specr], npts, ID_FWIDTH(idr),
+ ID_FTYPE(idr), ID_CRADIUS(idt), 0.)
+ if (IS_INDEF(wt))
+ next
+ w = shdr_lw (sh, double(wt+i1-1))
+ Memd[xr+j] = w
+ call id_match (idt, w, Memd[xr+j], label, -2.)
+ if (IS_INDEFD(Memd[xr+j]) || (j>0 && Memd[xr+j]==Memd[xr+j-1]))
+ next
+ j = j + 1
+ }
+ call mfree (label, TY_CHAR)
+ nr = j
+ AID_NR(aid) = nr
+
+ # Sort the lines.
+ if (j > 0)
+ call asrtd (Memd[xr], Memd[xr], nr)
+ else {
+ call salloc (x, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[x], SZ_LINE,
+ "No reference lines found in `%s'")
+ call pargstr (ID_IMAGE(idr))
+ call error (1, Memc[x])
+ }
+ }
+
+ #AID_NT(aid) = min (2 * AID_NR(aid), AID_NTF(aid))
+ AID_NT(aid) = AID_NTF(aid)
+ call asrtd (Memd[AID_XTF(aid)], Memd[AID_XT(aid)], AID_NT(aid))
+
+ # Debug w: Print wavelength bin limits.
+ if (stridxs ("w", AID_DEBUG(aid,1)) != 0) {
+ call eprintf ("%2d/%-2d %g %g\n")
+ call pargi (AID_BIN1(ev))
+ call pargi (AID_BIN2(ev))
+ call pargd (wa)
+ call pargd (wb)
+ }
+
+ # Debug b: Print search limits.
+ if (stridxs ("b", AID_DEBUG(aid,1)) != 0) {
+ if (ev == AID_EVS(aid)) {
+ call eprintf ("Search: CRVAL = %.8g - %.8g, CDELT = %.5g - %.5g\n\n")
+ call pargd (AID_CRMIN(aid))
+ call pargd (AID_CRMAX(aid))
+ call pargd (AID_CDMIN(aid))
+ call pargd (AID_CDMAX(aid))
+ }
+ }
+
+ # Debug r: Print list of reference lines.
+ if (stridxs ("r", AID_DEBUG(aid,1)) != 0) {
+ call eprintf ("# Selected reference lines:\n")
+ do i = 1, nr {
+ call eprintf ("%10.6g\n")
+ call pargd (Memd[xr+i-1])
+ }
+ call eprintf ("\n")
+ }
+
+ call sfree (sp)
+end
+
+
+# AID_AUTOID1 -- Automatically identify lines.
+# This routine takes preset target and reference line lists and tries to
+# find correspondences. It returns lists of possible correspondences
+# and dispersions.
+
+procedure aid_autoid1 (aid, ev)
+
+pointer aid #I AID pointer
+pointer ev #I EV pointer
+
+int i, nn, n1, n2, nr1, nr2, n, nd
+pointer sp, idt, x1, x2, x3, r1, s1, r2, s2, votes, svotes
+pointer x, y, w, w1, dw, nw, nv
+
+int aid_rsort(), aid_vsort(), stridxs()
+extern aid_rsort, aid_vsort
+errchk aid_select, aid_disp
+
+begin
+ call smark (sp)
+
+ idt = AID_IDT(aid)
+ nn = AID_NN(aid)
+ x1 = AID_XR(aid)
+ n1 = AID_NR(aid)
+ x2 = AID_XTL(aid)
+ x3 = AID_XT(aid)
+ n2 = AID_NT(aid)
+
+ # Debug l: Graph lines and spectra.
+ if (stridxs ("l", AID_DEBUG(aid,1)) != 0)
+ call aid_lgraph (aid, Memd[x1], n1, Memd[x2], n2)
+
+ # Make ratio lists.
+ i = min (nn, n1-1)
+ nr1 = (n1-i) * i * (i - 1) / 2 + i * (i - 1) * (i - 2) / 6
+ call salloc (r1, nr1, TY_REAL)
+ call aid_ratios (aid, Memd[x1], n1, 1, Memr[r1], nr1, 1)
+ call salloc (s1, nr1, TY_INT)
+ do i = 1, nr1
+ Memi[s1+i-1] = i
+ call gqsort (Memi[s1], nr1, aid_rsort, r1)
+
+ i = min (nn, n2-1)
+ nr2 = (n2-i) * i * (i - 1) / 2 + i * (i - 1) * (i - 2) / 6
+ call salloc (r2, 2*nr2, TY_REAL)
+ call aid_ratios (aid, Memd[x2], n2, AID_CDSIGN(aid), Memr[r2], nr2, 2)
+ call salloc (s2, nr2, TY_INT)
+ do i = 1, nr2
+ Memi[s2+i-1] = i
+ call gqsort (Memi[s2], nr2, aid_rsort, r2)
+
+ call salloc (votes, n1 * n2, TY_INT)
+ call aid_votes (aid, Memr[r1], Memi[s1], nr1, Memr[r2], Memi[s2],
+ nr2, Memd[x1], Memd[x2], Memi[votes], n1, n2)
+
+ call salloc (svotes, n1 * n2, TY_INT)
+ do i = 1, n1 * n2
+ Memi[svotes+i-1] = i
+ call gqsort (Memi[svotes], n1*n2, aid_vsort, votes)
+
+ do n = 1, n1 * n2
+ if (Memi[votes+Memi[svotes+n-1]-1] < 1)
+ break
+ n = max (3 * n2, n-1)
+
+ call malloc (x, n, TY_REAL)
+ call malloc (y, n, TY_REAL)
+ call salloc (w, n, TY_REAL)
+ iferr (call aid_select (aid, Memd[x1], Memd[x2], Memd[x3], Memi[votes],
+ Memi[svotes], n1, n2, Memr[x], Memr[y], Memr[w], n)) {
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+
+ nd = AID_NDMAX(aid)
+ call malloc (w1, nd, TY_REAL)
+ call malloc (dw, nd, TY_REAL)
+ call salloc (nw, nd, TY_INT)
+ call salloc (nv, nd, TY_INT)
+ call aid_disp (aid, Memr[y], Memr[x], Memr[w], n, Memr[w1], Memr[dw],
+ Memi[nw], Memi[nv], nd)
+
+ AID_X(ev) = x
+ AID_Y(ev) = y
+ AID_N(ev) = n
+ AID_A(ev) = w1
+ AID_B(ev) = dw
+ AID_ND(ev) = nd
+
+ call sfree (sp)
+end
+
+
+# AID_RATIOS -- Generate list of spacing ratios from list of lines.
+
+procedure aid_ratios (aid, x, n, cdsign, r, nr, nv)
+
+pointer aid #I AID pointer
+double x[n] #I Line positions (sorted)
+int n #I Number of lines
+int cdsign #I Sign of dispersion
+real r[nr,nv] #O Ratios
+int nr #O Number of ratios
+int nv #I Number of values
+
+int i, j, k, l, nn, stridxs()
+real minr, maxr, xi, xj, xk, xij, xjk, sig, ratio, err
+
+begin
+ nn = AID_NN(aid)
+ sig = AID_SIG(aid)
+ minr = AID_MINRATIO(aid)
+ maxr = 1 / AID_MINRATIO(aid)
+
+ # Compute ratios.
+ l = 0
+ if (cdsign == 1) {
+ do i = 1, n-2 {
+ xi = x[i]
+ do j = i+1, min (i+nn-1, n-1) {
+ xj = x[j]
+ xij = xj - xi
+ do k = j+1, min (i+nn, n) {
+ xk = x[k]
+ xjk = xk - xj
+ ratio = xij / xjk
+
+ l = l + 1
+ if (nv == 1) {
+ if (ratio < minr || ratio > maxr)
+ r[l,1] = 1000.
+ else
+ r[l,1] = ratio
+ } else {
+ if (ratio < minr || ratio > maxr) {
+ r[l,1] = 1000.
+ r[l,2] = 1000.
+ } else {
+ err = sig * sqrt (2*(1+ratio+ratio**2)) / xjk
+ r[l,1] = ratio - err
+ r[l,2] = ratio + err
+ }
+ }
+ }
+ }
+ }
+ } else {
+ do i = n, 3, -1 {
+ xi = x[i]
+ do j = i-1, max (i-nn+1, 2), -1 {
+ xj = x[j]
+ xij = xi - xj
+ do k = j-1, max (i-nn, 1), -1 {
+ xk = x[k]
+ xjk = xj - xk
+ ratio = xij / xjk
+
+ l = l + 1
+ if (nv == 1) {
+ if (ratio < minr || ratio > maxr)
+ r[l,1] = 1000.
+ else
+ r[l,1] = ratio
+ } else {
+ if (ratio < minr || ratio > maxr) {
+ r[l,1] = 1000.
+ r[l,2] = 1000.
+ } else {
+ err = sig * sqrt (2*(1+ratio+ratio**2)) / xjk
+ r[l,1] = ratio - err
+ r[l,2] = ratio + err
+ }
+ }
+ }
+ }
+ }
+ }
+ nr = l
+
+ # Debug c: Print list of line ratios.
+ if (stridxs ("c", AID_DEBUG(aid,1)) != 0) {
+ do l = 1, nr {
+ call aid_lines (l, n, nn, i, j, k)
+ if (nv == 1)
+ call printf ("%2d %2d %2d %8.2f %8.2f %8.2f %6.4f\n")
+ else
+ call printf ("%2d %2d %2d %8.2f %8.2f %8.2f %6.4f %6.4f\n")
+ call pargi (i)
+ call pargi (j)
+ call pargi (k)
+ if (cdsign == 1) {
+ call pargd (x[i])
+ call pargd (x[j])
+ call pargd (x[k])
+ } else {
+ call pargd (x[n-i+1])
+ call pargd (x[n-j+1])
+ call pargd (x[n-k+1])
+ }
+ call pargr (r[l,1])
+ if (nv == 2)
+ call pargr (r[l,2])
+ }
+ }
+end
+
+
+# AID_LINES -- Convert ratio index to line indices.
+
+procedure aid_lines (s, n, nn, i, j, k)
+
+int s # Index into ratio array
+int n # Number of lines
+int nn # Number of neigbhors
+int i #O Index of first line
+int j #O Index of second line
+int k #O Index of third line
+
+int l
+
+begin
+ k = s
+ for (i=1;;i=i+1) {
+ l = min (nn, n-i)
+ l = l * (l-1) / 2
+ if (k <= l)
+ break
+ k = k - l
+ }
+ for (j=i+1;;j=j+1) {
+ l = min (nn-1, n-j)
+ if (k <= l)
+ break
+ k = k - l
+ }
+ k = k + j
+end
+
+
+# AID_RSORT -- Compare ratio array with smallest first.
+
+int procedure aid_rsort (ptr, i, j)
+
+pointer ptr #I Pointer to array to be sorted.
+int i #I Index 1
+int j #I Index 2
+
+real a, b
+
+begin
+ a = Memr[ptr+i-1]
+ b = Memr[ptr+j-1]
+
+ if (a < b)
+ return (-1)
+ else if (b < a)
+ return (1)
+ else
+ return (0)
+end
+
+
+# AID_VSORT -- Compare vote array with biggest first.
+
+int procedure aid_vsort (ptr, i, j)
+
+pointer ptr #I Pointer to array to be sorted.
+int i #I Index 1
+int j #I Index 2
+
+int a, b
+
+begin
+ a = Memi[ptr+i-1]
+ b = Memi[ptr+j-1]
+
+ if (a < b)
+ return (1)
+ else if (b < a)
+ return (-1)
+ else
+ return (0)
+end
+
+
+# AID_VOTES -- Find ratio matches and increment the vote array.
+
+procedure aid_votes (aid, r1, s1, nr1, r2, s2, nr2, x1, x2, votes, n1, n2)
+
+pointer aid #I AID pointer
+real r1[nr1] #I Ratio array (reference)
+int s1[nr1] #I Sort array
+int nr1 #I Number of ratios
+real r2[nr2,2] #I Ratio array (target)
+int s2[nr2] #I Sort array
+int nr2 #I Number of ratios
+double x1[n1] #I Reference lines
+double x2[n2] #I Target lines
+int votes[n1,n2] #O Votes
+int n1, n2 #I Size of votes array
+
+int i, j, nn, np, start, stridxs()
+real maxr, ra, rb1, rb2
+pointer sp, a, b
+
+begin
+ nn = AID_NN(aid)
+ np = max (3, min (AID_NP(aid), n1 - 5))
+ maxr = 1. / AID_MINRATIO(aid)
+
+ call smark (sp)
+ call salloc (a, np, TY_INT)
+ call salloc (b, np, TY_INT)
+
+ call aclri (votes, n1*n2)
+
+ start = 1
+ do j = 1, nr2 {
+ rb1 = r2[s2[j],1]
+ if (rb1 > maxr)
+ break
+ rb2 = r2[s2[j],2]
+ do i = start, nr1 {
+ ra = r1[s1[i]]
+ if (ra > rb2)
+ break
+ if (ra < rb1) {
+ start = i + 1
+ next
+ }
+ call aid_lines (s1[i], n1, nn, Memi[a], Memi[a+1], Memi[a+2])
+ call aid_lines (s2[j], n2, nn, Memi[b], Memi[b+1], Memi[b+2])
+ call aid_addlines (aid, r1, nr1, s1[i], r2, nr2, s2[j], nn,
+ Memi[a], Memi[b], np, votes, n1, n2)
+ }
+ }
+
+ # Debug v: Print vote array.
+ if (stridxs ("v", AID_DEBUG(aid,1)) != 0) {
+ call printf ("%4w")
+ do i = 1, n2 {
+ call printf (" %3d")
+ call pargi (nint (x2[i]))
+ }
+ call printf ("\n")
+ do i = 1, n1 {
+ call printf ("%4d")
+ call pargi (nint (x1[i]))
+ do j = 1, n2 {
+ call printf (" %3d")
+ call pargi (votes[i,j])
+ }
+ call printf ("\n")
+ }
+ call printf ("\n")
+ call flush (STDOUT)
+ }
+
+ call sfree (sp)
+end
+
+
+# AID_ADDLINES -- Starting with a matching triplets add more lines.
+# The lines are added recursively. To avoid recursive calls this
+# routine is repeated to a maximum depth. The indentation is intentionally
+# non-standard.
+
+procedure aid_addlines (aid, r1, nr1, s1, r2, nr2, s2, nn, a, b, npattern,
+ votes, n1, n2)
+
+pointer aid #I AID pointer
+real r1[nr1] #I Reference ratios
+int nr1 #I Number of ratios
+int s1 #I Ratio index
+real r2[nr2,2] #I Target ratios
+int nr2 #I Number of ratios
+int s2 #I Ratio index
+int nn #I Number of neighbors
+int a[npattern] #I Reference lines (indices)
+int b[npattern] #I Target lines (indices)
+int npattern #I Number of lines in pattern
+int votes[n1,n2] #O Vote array
+int n1, n2 #O Number of reference and target lines
+
+int i, j, i1, j1, na, nb
+
+begin
+ na = min (a[1] + nn, n1)
+ nb = min (b[1] + nn, n2)
+ i1 = s1 - a[3]
+ j1 = s2 - b[3]
+
+ if (npattern > 3) {
+ for (a[4]=a[3]+1; a[4]<=na; a[4]=a[4]+1) {
+ for (b[4]=b[3]+1; b[4]<=nb; b[4]=b[4]+1) {
+ i = i1 + a[4]
+ j = j1 + b[4]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 4) {
+ for (a[5]=a[4]+1; a[5]<=na; a[5]=a[5]+1) {
+ for (b[5]=b[4]+1; b[5]<=nb; b[5]=b[5]+1) {
+ i = i1 + a[5]
+ j = j1 + b[5]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 5) {
+ for (a[6]=a[5]+1; a[6]<=na; a[6]=a[6]+1) {
+ for (b[6]=b[5]+1; b[6]<=nb; b[6]=b[6]+1) {
+ i = i1 + a[6]
+ j = j1 + b[6]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 6) {
+ for (a[7]=a[6]+1; a[7]<=na; a[7]=a[7]+1) {
+ for (b[7]=b[6]+1; b[7]<=nb; b[7]=b[7]+1) {
+ i = i1 + a[7]
+ j = j1 + b[7]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 7) {
+ for (a[8]=a[7]+1; a[8]<=na; a[8]=a[8]+1) {
+ for (b[8]=b[7]+1; b[8]<=nb; b[8]=b[8]+1) {
+ i = i1 + a[8]
+ j = j1 + b[8]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 8) {
+ for (a[9]=a[8]+1; a[9]<=na; a[9]=a[9]+1) {
+ for (b[9]=b[8]+1; b[9]<=nb; b[9]=b[9]+1) {
+ i = i1 + a[9]
+ j = j1 + b[9]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ if (npattern > 9) {
+ for (a[10]=a[9]+1; a[10]<=na; a[10]=a[10]+1) {
+ for (b[10]=b[9]+1; b[10]<=nb; b[10]=b[10]+1) {
+ i = i1 + a[10]
+ j = j1 + b[10]
+ if (r1[i] < r2[j,1] || r1[i] > r2[j,2])
+ next
+ call aid_vote (aid, a, b, 10, votes, n1, n2)
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+ }
+ }
+ } else {
+ call aid_vote (aid, a, b, npattern, votes, n1, n2)
+ }
+end
+
+
+# AID_VOTE -- Add votes for the lines in the pattern to the vote array.
+
+procedure aid_vote (aid, a, b, npattern, votes, n1, n2)
+
+pointer aid #I AID pointer
+int a[npattern] #I Reference lines (indices)
+int b[npattern] #I Target lines (indices)
+int npattern #I Number of lines in pattern
+int votes[n1,n2] #O Vote array
+int n1, n2 #O Number of reference and target lines
+
+int i, stridxs()
+pointer xr, xt
+
+begin
+ if (AID_CDSIGN(aid) == 1) {
+ do i = 1, npattern
+ votes[a[i],b[i]] = votes[a[i],b[i]] + 1
+ } else {
+ do i = 1, npattern
+ votes[a[i],n2-b[i]+1] = votes[a[i],n2-b[i]+1] + 1
+ }
+
+ # Debug a: Print line assignments.
+ if (stridxs ("a", AID_DEBUG(aid,1)) != 0) {
+ xr = AID_XR(aid)-1
+ xt = AID_XT(aid)-1
+ if (AID_CDSIGN(aid) == 1) {
+ do i = 1, npattern {
+ call eprintf (" %6g %6g %5d")
+ call pargd (Memd[xr+a[i]])
+ call pargd (Memd[xt+b[i]])
+ call pargi (b[i])
+ }
+ } else {
+ xt = xt+n2+1
+ do i = 1, npattern {
+ call eprintf (" %6g %6g %5d")
+ call pargd (Memd[xr+a[i]])
+ call pargd (Memd[xt-b[i]])
+ call pargi (n2-b[i]+1)
+ }
+ }
+ call eprintf ("\n")
+ }
+end
+
+
+# AID_SELECT -- Select top vote getters.
+
+procedure aid_select (aid, x1, x2, x3, votes, svotes, n1, n2, x, y, w, ns)
+
+pointer aid #I AID pointer
+double x1[n1] #I Reference lines
+double x2[n2] #I Linearized target lines
+double x3[n2] #I Target lines
+int votes[n1,n2] #I Vote array
+int svotes[ARB] #I Sort indices for vote array
+int n1, n2 #I Number of lines
+real x[ns] #O Selected target coordinates
+real y[ns] #O Selected reference coordinates
+real w[ns] #O Weight (votes)
+int ns #U Maximum number on input, number selected on output
+
+int i, j, k, n
+double a, b
+bool check
+
+begin
+ check = (AID_CRMIN(aid) > -MAX_DOUBLE / 10. &&
+ AID_CRMAX(aid) < MAX_DOUBLE / 10.)
+
+ # Select the highest votes.
+ n = 0
+ for (k=1; k<=n1*n2 && n<ns; k=k+1) {
+ i = mod (svotes[k]-1, n1) + 1
+ j = (svotes[k]-1) / n1 + 1
+ if (votes[i,j] < 1)
+ break
+ if (check) {
+ a = (x2[j] - AID_CRPIX(aid)) * AID_CDSIGN(aid) * AID_CDMIN(aid)
+ b = (x2[j] - AID_CRPIX(aid)) * AID_CDSIGN(aid) * AID_CDMAX(aid)
+ if (x1[i] < AID_CRMIN(aid) + min (a,b))
+ next
+ if (x1[i] > AID_CRMAX(aid) + max (a,b))
+ next
+ }
+ n = n + 1
+ x[n] = x3[j]
+ y[n] = x1[i]
+ w[n] = votes[i,j]
+ }
+ ns = n
+
+ if (ns < 1)
+ call error (1, "No matches found")
+end
+
+
+# AID_DISP -- Given a set of candidate identifications (pixel, wavelength)
+# find all linear dispersions between two or more identifications which
+# satisfy the dispersion constraints. The list of ranked dispersions with
+# higher rankings for higher number of points the dispersion goes through
+# higher total votes for the points. Hopefully the true dispersion will be
+# in the highest ranked dispersions.
+
+procedure aid_disp (aid, x, y, v, n, w1, dw, nw, nv, nd)
+
+pointer aid #I AID pointer
+real x[n] #I Array of candidate reference coordinates
+real y[n] #I Array of candidate target coordinates
+real v[n] #I Votes
+int n #I Number of candidate pairs
+real w1[nd] #O Dispersion origin
+real dw[nd] #O Dispersion slope
+int nw[nd] #O Number of points
+int nv[nd] #O Sum of votes
+int nd #U Number of dispersions
+
+bool debug, skip
+int i, j, k, l, m, ii, sumn, sumv, stridxs()
+double aw, bw, cw, sumx, sumy, sumyy, sumxy
+pointer iii
+
+begin
+ # Sort the candidates by reference coordinate.
+ call xt_sort2 (x, y, n)
+
+ debug = (stridxs ("m", AID_DEBUG(aid,1)) != 0)
+ if (debug) {
+ call eprintf ("# Selected pairs with votes.\n")
+ do i = 1, n {
+ call eprintf ("%4d %8.6g %8.6g %d\n")
+ call pargi (i)
+ call pargr (x[i])
+ call pargr (y[i])
+ call pargr (v[i])
+ }
+ call eprintf ("# Dispersions to check up to %d.\n")
+ call pargi (nd)
+ }
+
+ m = 0
+ ii = 0
+ call malloc (iii, nd, TY_INT)
+ do i = 1, n-2 {
+ do j = i+1, n-1 {
+ if (x[j] == x[i] || y[j] == y[i])
+ next
+
+ bw = (x[j] - x[i]) / (y[j] - y[i])
+ aw = x[i] - bw * y[i]
+ cw = aw + bw * AID_CRPIX(aid)
+
+ # Check dispersion ranges.
+ skip = false
+ if (abs (bw) < AID_CDMIN(aid) || abs (bw) > AID_CDMAX(aid))
+ skip = true
+ else if (cw < AID_CRMIN(aid) || cw > AID_CRMAX(aid))
+ skip = true
+ if (AID_CDSIGN(aid) * bw < 0.)
+ skip = true
+ if (skip)
+ next
+
+ sumn = 2
+ sumv = v[i] + v[j]
+ sumx = x[i] + x[j]
+ sumy = y[i] + y[j]
+ sumyy = y[i]*y[i] + y[j]*y[j]
+ sumxy = x[i]*y[i] + x[j]*y[j]
+
+ do k = j+1, n {
+ if (abs ((x[k] - aw - bw * y[k]) / bw) > 2.)
+ next
+
+ sumn = sumn + 1
+ sumv = sumv + v[k]
+ sumx = sumx + x[k]
+ sumy = sumy + y[k]
+ sumyy = sumyy + y[k]*y[k]
+ sumxy = sumxy + x[k]*y[k]
+ }
+
+ aw = (sumx*sumyy - sumy*sumxy) / (sumn * sumyy - sumy * sumy)
+ bw = (sumn*sumxy - sumx*sumy) / (sumn * sumyy - sumy * sumy)
+ cw = aw + bw * AID_CRPIX(aid)
+ ii = ii + 1
+
+ if (debug) {
+ call eprintf (" %4d %4d %4d %8.5g %8.3g %8d %8d")
+ call pargi (ii)
+ call pargi (i)
+ call pargi (j)
+ call pargd (aw+bw*(ID_NPTS(AID_IDT(aid))/2.+1))
+ call pargd (bw)
+ call pargi (sumn)
+ call pargi (sumv)
+ }
+
+ # Check if already found.
+ for (k = 1; k <= m; k = k + 1)
+ if (abs ((x[1]-aw)/bw - (x[1]-w1[k])/dw[k]) < 2. &&
+ abs ((x[n]-aw)/bw - (x[n]-w1[k])/dw[k]) < 2.)
+ break
+ if (k <= m) {
+ if (sumn > nw[k] || (sumn == nw[k] && sumv > nv[k])) {
+ for (l = k; l > 1; l = l - 1) {
+ if (sumn<nw[l-1] || (sumn==nw[l-1] && sumv<nv[l-1]))
+ break
+ w1[l] = w1[l-1]
+ dw[l] = dw[l-1]
+ nw[l] = nw[l-1]
+ nv[l] = nv[l-1]
+ Memi[iii+l-1] = Memi[iii+l-2]
+ }
+ if (debug) {
+ call eprintf (" replace %4d\n")
+ call pargi (Memi[iii+l-1])
+ }
+ w1[l] = aw
+ dw[l] = bw
+ nw[l] = sumn
+ nv[l] = sumv
+ Memi[iii+l-1] = ii
+ } else if (debug) {
+ call eprintf (" use %4d\n")
+ call pargi (Memi[iii+k-1])
+ }
+ next
+ }
+
+ # Check dispersion ranges.
+ if (abs (bw) < AID_CDMIN(aid) || abs (bw) > AID_CDMAX(aid))
+ skip = true
+ else if (cw < AID_CRMIN(aid) || cw > AID_CRMAX(aid))
+ skip = true
+ if (AID_CDSIGN(aid) * bw < 0.)
+ skip = true
+ if (skip) {
+ if (debug)
+ call eprintf (" out of range\n")
+ next
+ }
+
+ # Add to ordered list.
+ for (k = 1; k <= m; k = k + 1)
+ if (sumn > nw[k] || (sumn == nw[k] && sumv > nv[k]))
+ break
+ if (k <= nd) {
+ if (m < nd) {
+ m = m + 1
+ if (debug)
+ call eprintf (" add\n")
+ } else if (debug) {
+ call eprintf (" bump %4d\n")
+ call pargi (Memi[iii+m-1])
+ }
+ for (l = m; l > k; l = l - 1) {
+ w1[l] = w1[l-1]
+ dw[l] = dw[l-1]
+ nw[l] = nw[l-1]
+ nv[l] = nv[l-1]
+ Memi[iii+l-1] = Memi[iii+l-2]
+ }
+ w1[k] = aw
+ dw[k] = bw
+ nw[k] = sumn
+ nv[k] = sumv
+ Memi[iii+k-1] = ii
+ } else if (debug)
+ call eprintf (" failed\n")
+ }
+ }
+
+ nd = m
+
+ if (debug) {
+ call eprintf ("# Final ordered dispersions to try.\n")
+ do i = 1, nd {
+ call eprintf (" %4d %8.5g %8.3g %8d %8d\n")
+ call pargi (Memi[iii+i-1])
+ call pargr (w1[i]+dw[i]*(ID_NPTS(AID_IDT(aid))/2.+1))
+ call pargr (dw[i])
+ call pargi (nw[i])
+ call pargi (nv[i])
+ }
+ }
+ call mfree (iii, TY_INT)
+
+ # Debug d: Graph dispersions.
+ if (stridxs ("d", AID_DEBUG(aid,1)) != 0)
+ call aid_dgraph (aid, x, y, n, w1, dw, nd)
+end
+
+
+# AID_EVAL -- Evaluate possible solutions.
+
+double procedure aid_eval (aid, ev, nd)
+
+pointer aid #I AID pointer
+pointer ev #I EV pointer
+int nd #I Dispersion candidate to evaluate
+double best #O Best statistic
+
+int i, n
+pointer idt, x, y
+double a, b, c, d, rms, fmatch, ftmatch
+int stridxs()
+
+int ncandidate, nmatch1, nmatch2
+common /llstat/ ncandidate, nmatch1, nmatch2
+
+define done_ 90
+
+begin
+ best = INDEFD
+ if (nd > AID_ND(ev))
+ return (best)
+
+ idt = AID_IDT(aid)
+ x = AID_X(ev) - 1
+ y = AID_Y(ev) - 1
+ n = AID_N(ev)
+
+ a = Memr[AID_A(ev)+nd-1]
+ b = Memr[AID_B(ev)+nd-1]
+ c = ID_NPTS(AID_IDT(aid)) / 2. + 1
+ if (IS_INDEFD(AID_CDELT(aid)))
+ d = b
+ else
+ d = AID_CDELT(aid)
+
+ ID_IC(idt) = AID_IC1(aid)
+ ID_NFEATURES(idt) = 0
+ do i = 1, n {
+ if (abs ((Memr[y+i] - a - b * Memr[x+i]) / b) < 2.)
+ call id_newfeature (idt, double(Memr[x+i]), double(Memr[x+i]),
+ double(Memr[y+i]), 1.0D0, ID_FWIDTH(idt), ID_FTYPE(idt),
+ NULL)
+ }
+ if (ID_NFEATURES(idt) <= 1)
+ goto done_
+
+ call dcvfree (ID_CV(idt))
+ iferr (call aid_dofit (aid, idt, d, rms, fmatch, ftmatch, best))
+ goto done_
+
+ # Debug s: Print search iterations.
+ if (stridxs ("s", AID_DEBUG(aid,1)) != 0) {
+ call eprintf (
+ "%2d/%-2d %8.2f %8.3f %3d %3d/%-3d %3d/%-3d %3d %3d %6.3f %5.2f\n")
+ call pargi (AID_BIN1(ev))
+ call pargi (AID_BIN2(ev))
+ call pargd (a+c*b)
+ call pargd (b)
+ call pargi (ID_NFEATURES(idt))
+ call pargi (nmatch2)
+ call pargi (ncandidate)
+ call pargi (nint(min (ncandidate, AID_NT(aid))*(1-ftmatch)))
+ call pargi (min (ncandidate, AID_NT(aid)))
+ call pargi (nint(100.*fmatch))
+ call pargi (nint(100.*ftmatch))
+ call pargd (rms)
+ call pargd (best)
+ }
+
+ if (best < AID_BEST(aid)) {
+ AID_FMATCH(aid) = fmatch
+ AID_FTMATCH(aid) = ftmatch
+ AID_RMS(aid) = rms
+ AID_BEST(aid) = best
+ ID_IC(idt) = AID_IC2(aid)
+ call id_saveid (idt, "autoidentify")
+ }
+
+done_
+ ID_IC(idt) = AID_IC2(aid)
+ return (best)
+end
+
+
+# AID_DOFIT -- From a set of candidate identifications fit and evaluate
+# a dispersion solution.
+
+procedure aid_dofit (aid, id, cdelt, rms, fmatch, ftmatch, best)
+
+pointer aid #I AID pointer
+pointer id #I ID pointer
+double cdelt #I Dispersion to use in pixel rms conversion
+double rms #O Final RMS in pixels
+double fmatch #O Line list non-matching fraction
+double ftmatch #O Target line non-matching fraction
+double best #O Best fit parameter
+
+int i, j, k, l, nmin, nfound, nt, ntmatch, maxfeatures, stridxs()
+double fit, user, dcveval(), id_fitpt()
+pointer cv, xt, label
+
+int ncandidate, nmatch1, nmatch2
+common /llstat/ ncandidate, nmatch1, nmatch2
+
+errchk id_dofit, id_fitdata, id_fitfeatures, id_linelist, id_match
+
+begin
+ maxfeatures = ID_MAXFEATURES(id)
+ ID_MAXFEATURES(id) = 1000
+ iferr {
+ do k = 1, 3 {
+ if (ID_NFEATURES(id) < 2)
+ call error (0, "aid_dofit: not enough features")
+ if (k > 1)
+ call id_linelist (id)
+
+ if (stridxs ("i", AID_DEBUG(aid,1)) != 0)
+ call id_dofit (id, YES)
+ else
+ call id_dofit (id, NO)
+ do l = AID_ORD(aid)-1, 2, -1 {
+ cv = ID_CV(id)
+ user = dcveval (cv, 1D0)
+ fit = (dcveval (cv, double (ID_NPTS(id)/2)) - user) /
+ (dcveval (cv, double (ID_NPTS(id))) - user)
+ if (abs (fit - 0.5) <= AID_MAXNL(aid))
+ break
+ if (stridxs ("n", AID_DEBUG(aid,1)) != 0) {
+ call eprintf (
+ "order %d: non-linearity of %.1f%% > %.1f%%\n")
+ call pargi (l+1)
+ call pargd (100*abs(fit-0.5))
+ call pargr (100*AID_MAXNL(aid))
+ }
+ call ic_puti (ID_IC(id), "order", l)
+ if (stridxs ("i", AID_DEBUG(aid,1)) != 0)
+ call id_dofit (id, YES)
+ else
+ call id_dofit (id, NO)
+ call ic_puti (ID_IC(id), "order", AID_ORD(aid))
+ }
+ call id_fitdata (id)
+ call id_fitfeatures (id)
+
+ j = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD(USER(id,i)) || WTS(id,i) != 0.) {
+ j = j + 1
+ PIX(id,j) = PIX(id,i)
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ }
+ }
+ ID_NFEATURES(id) = j
+ }
+ ID_MAXFEATURES(id) = maxfeatures
+ } then {
+ ID_MAXFEATURES(id) = maxfeatures
+ call erract (EA_ERROR)
+ }
+ if (IS_INDEFD(cdelt))
+ return
+
+ nmin = 2
+ nfound = AID_NFOUND(aid)
+ if (ID_NFEATURES(id) < nfound)
+ call error (0, "aid_dofit: not enough features")
+
+ # Compute fwidth rms.
+ rms = 0.
+ for (i=1; i<=ID_NFEATURES(id); i=i+1)
+ rms = rms + (FIT(id,i) - USER(id,i)) ** 2
+ rms = sqrt (rms/ max (1, ID_NFEATURES(id)-nmin)) / abs (cdelt)
+ rms = rms / ID_FWIDTH(id)
+
+ # Compute line list matching fraction.
+ ncandidate = max (nfound, (ncandidate-(nmatch1-nmatch2)))
+ fmatch = 1 - real (nmatch2) / ncandidate
+
+ # Compute target line matching fraction.
+ xt = AID_XT(aid)
+ nt = AID_NT(aid)
+ label = NULL
+ ntmatch = 0
+ do i = 1, nt {
+ fit = id_fitpt (id, Memd[xt+i-1])
+ user = INDEFD
+ call id_match (id, fit, user, label, ID_MATCH(id))
+ if (!IS_INDEFD(user))
+ ntmatch = ntmatch + 1
+ }
+ ftmatch = 1 - real (ntmatch) / min (nt, ncandidate)
+ call mfree (label, TY_CHAR)
+
+ if (AID_RMSG(aid) > 0. && AID_FMATCHG(aid) > 0.) {
+ best = AID_WRMS(aid) * rms / AID_RMSG(aid)
+ best = best + AID_WFMATCH(aid) * fmatch / AID_FMATCHG(aid)
+ best = best + AID_WFTMATCH(aid) * ftmatch / AID_FMATCHG(aid)
+ } else
+ best = MAX_DOUBLE
+end
+
+
+# AID_DOFITF -- From a set of candidate identifications fit and evaluate
+# a final dispersion solution.
+
+procedure aid_dofitf (aid, id)
+
+pointer aid #I AID pointer
+pointer id #I ID pointer
+
+int i, j, k, maxfeatures
+
+errchk id_dofit, id_fitdata, id_fitfeatures, id_linelist
+
+begin
+ maxfeatures = ID_MAXFEATURES(id)
+ ID_MAXFEATURES(id) = 1000
+ iferr {
+ do k = 1, 3 {
+ if (ID_NFEATURES(id) < 2)
+ call error (0, "aid_dofit: not enough features")
+ if (k > 1)
+ call id_linelist (id)
+
+ call id_dofit (id, NO)
+ call id_fitdata (id)
+ call id_fitfeatures (id)
+ if (k < 3) {
+ j = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD(USER(id,i)) || WTS(id,i) != 0.) {
+ j = j + 1
+ PIX(id,j) = PIX(id,i)
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ }
+ }
+ ID_NFEATURES(id) = j
+ }
+ }
+ ID_MAXFEATURES(id) = maxfeatures
+ } then {
+ ID_MAXFEATURES(id) = maxfeatures
+ call erract (EA_ERROR)
+ }
+end
+
+
+# AID_EVALLOC -- Allocate memory to save the candidate identifications
+# and dispersions to be evaluated.
+
+pointer procedure aid_evalloc (aid, index)
+
+pointer aid #I AID pointer
+int index #I Reference sample index
+
+begin
+ if (AID_EVS(aid) == NULL)
+ call calloc (AID_EVS(aid), (index+49)*AID_EVLEN, TY_POINTER)
+ else if (index > 1 && mod (index-1, 50) == 0) {
+ call realloc (AID_EVS(aid), (index+49)*AID_EVLEN, TY_POINTER)
+ call aclri (Memi[AID_EVS(aid)+(index-1)*AID_EVLEN], 50*AID_EVLEN)
+ }
+ return (AID_EVS(aid)+(index-1)*AID_EVLEN)
+end
+
+
+# AID_EVFREE -- Free memory from the evaluation step.
+
+procedure aid_evfree (aid, index)
+
+pointer aid #I AID pointer
+int index #I Reference sample index
+
+pointer ev, aid_evalloc()
+
+begin
+ ev = aid_evalloc (aid, index)
+ call mfree (AID_X(ev), TY_REAL)
+ call mfree (AID_Y(ev), TY_REAL)
+ call mfree (AID_A(ev), TY_REAL)
+ call mfree (AID_B(ev), TY_REAL)
+end
+
+
+# AID_IMGD -- Get value from image header or parameter string.
+
+double procedure aid_imgd (im, param)
+
+pointer im #I IMIO pointer
+char param[ARB] #I Parameter
+
+int i, ctod()
+double dval, imgetd()
+
+begin
+ if (param[1] == '!') {
+ iferr (dval = imgetd (im, param[2]))
+ dval = INDEFD
+ } else {
+ iferr (dval = imgetd (im, param)) {
+ i = 1
+ if (ctod (param, i, dval) == 0)
+ dval = INDEFD
+ }
+ }
+ return (dval)
+end
diff --git a/noao/onedspec/identify/autoid/mkpkg b/noao/onedspec/identify/autoid/mkpkg
new file mode 100644
index 00000000..7d46d183
--- /dev/null
+++ b/noao/onedspec/identify/autoid/mkpkg
@@ -0,0 +1,17 @@
+$checkout libpkg.a ../../
+$update libpkg.a
+$checkin libpkg.a ../../
+$exit
+
+libpkg.a:
+ aidautoid.x autoid.h <gset.h> ../identify.h <mach.h>\
+ <math/iminterp.h> <smw.h>
+ aidget.x autoid.h
+ aidgraph.x autoid.h <gset.h> ../identify.h <mach.h> <pkg/gtools.h>\
+ <smw.h>
+ aidinit.x autoid.h ../identify.h <smw.h>
+ aidlog.x ../identify.h
+ aidset.x autoid.h
+ aidshift.x ../identify.h
+ autoid.x autoid.h <error.h> ../identify.h <mach.h> <smw.h>
+ ;
diff --git a/noao/onedspec/identify/idcenter.x b/noao/onedspec/identify/idcenter.x
new file mode 100644
index 00000000..6b6dba06
--- /dev/null
+++ b/noao/onedspec/identify/idcenter.x
@@ -0,0 +1,37 @@
+include <smw.h>
+include "identify.h"
+
+# ID_CENTER -- Locate the center of a feature.
+
+double procedure id_center (id, x, width, type)
+
+pointer id # ID pointer
+double x # Initial guess
+real width # Feature width
+int type # Feature type
+
+int np1
+real value
+double dvalue
+
+real center1d()
+double smw_c1trand()
+
+begin
+ if (IS_INDEFD(x))
+ return (x)
+
+ dvalue = smw_c1trand (ID_PL(id), x)
+ if (IS_INDEFD(dvalue))
+ return (dvalue)
+
+ np1 = NP1(ID_SH(id)) - 1
+ value = dvalue - np1
+ value = center1d (value, IMDATA(id,1), ID_NPTS(id),
+ width, type, ID_CRADIUS(id), ID_THRESHOLD(id))
+
+ if (IS_INDEF(value))
+ return (INDEFD)
+ else
+ return (smw_c1trand (ID_LP(id), double(value+np1)))
+end
diff --git a/noao/onedspec/identify/idcolon.x b/noao/onedspec/identify/idcolon.x
new file mode 100644
index 00000000..0bd68042
--- /dev/null
+++ b/noao/onedspec/identify/idcolon.x
@@ -0,0 +1,284 @@
+include <gset.h>
+include <error.h>
+include <smw.h>
+include "identify.h"
+
+# List of colon commands.
+define CMDS "|show|features|image|nsum|database|read|write|add|coordlist|match\
+ |maxfeatures|minsep|zwidth|labels|fwidth|ftype|cradius|threshold|"
+
+define SHOW 1 # Show parameters
+define FEATURES 2 # Show list of features
+define IMAGE 3 # Set new image
+define NSUM 4 # Set the number of lines or columns to sum
+define DATABASE 5 # Set new database
+define READ 6 # Read database entry
+define WRITE 7 # Write database entry
+define ADD 8 # Add features from database
+define COORDLIST 9 # Set new coordinate list
+define MATCH 10 # Set coordinate list matching distance
+define MAXFEATURES 11 # Set maximum number of features for auto find
+define MINSEP 12 # Set minimum separation distance
+define ZWIDTH 13 # Set zoom window width
+define LABEL 14 # Set label type
+define WIDTH 15 # Set centering width
+define TYPE 16 # Set centering type
+define RADIUS 17 # Set centering radius
+define THRESHOLD 18 # Set the centering threshold
+
+# ID_COLON -- Respond to colon command.
+
+procedure id_colon (id, cmdstr, newimage, prfeature)
+
+pointer id # ID pointer
+char cmdstr[ARB] # Colon command
+char newimage[ARB] # New image name
+int prfeature # Print current feature on status line
+
+char cmd[SZ_LINE]
+int i, ncmd, ival[2]
+real rval[2]
+pointer im
+
+int nscan(), strdic()
+pointer immap()
+errchk immap, id_dbread, id_dbwrite, id_log
+
+begin
+ # Scan the command string and get the first word.
+ call sscan (cmdstr)
+ call gargwrd (cmd, SZ_LINE)
+ ncmd = strdic (cmd, cmd, SZ_LINE, CMDS)
+
+ switch (ncmd) {
+ case SHOW: # :show - show values of parameters
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call gdeactivate (ID_GP(id), AW_CLEAR)
+ call id_show (id, "STDOUT")
+ call greactivate (ID_GP(id), AW_PAUSE)
+ } else {
+ iferr (call id_show (id, cmd)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case FEATURES: # :features - list features
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call gdeactivate (ID_GP(id), AW_CLEAR)
+ call id_log (id, "STDOUT")
+ call greactivate (ID_GP(id), AW_PAUSE)
+ } else {
+ iferr (call id_log (id, cmd)) {
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case IMAGE: # :image - set image to identify
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("image %s\n")
+ call pargstr (ID_IMAGE(id))
+ prfeature = NO
+ } else {
+ call strcpy (cmd, newimage, SZ_FNAME)
+ iferr {
+ im = immap (newimage, READ_ONLY, 0)
+ call imunmap (im)
+ } then {
+ newimage[1] = EOS
+ call erract (EA_WARN)
+ prfeature = NO
+ }
+ }
+ case NSUM: # :nsum - set number of lines or columns to sum in image
+ call gargi (ival[1])
+ if (nscan() == 1) {
+ call printf ("nsum %d %d\n")
+ call pargi (ID_NSUM(id,1))
+ call pargi (ID_NSUM(id,2))
+ prfeature = NO
+ } else {
+ ID_NSUM(id,1) = ival[1]
+ call gargi (ival[2])
+ if (nscan() == 3)
+ ID_NSUM(id,2) = ival[2]
+ call smw_daxis (NULL, NULL, SMW_PAXIS(MW(ID_SH(id)),1),
+ ID_NSUM(id,1), ID_NSUM(id,2))
+ }
+ case DATABASE: # :database - set database
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("database %s\n")
+ call pargstr (ID_DATABASE(id))
+ prfeature = NO
+ } else {
+ call strcpy (cmd, ID_DATABASE(id), ID_LENSTRING)
+ ID_NEWDBENTRY(id) = YES
+ }
+ case READ: # :read - read database entry
+ prfeature = NO
+ iferr {
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1)
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1),
+ NO, YES)
+ else {
+ call gargi (ival[1])
+ if (nscan() < 3)
+ ival[1] = ID_AP(id,1)
+ call gargi (ival[2])
+ if (nscan() < 4)
+ ival[2] = ID_AP(id,2)
+ call id_dbread (id, cmd, ival, NO, YES)
+ }
+ } then
+ call erract (EA_WARN)
+ case WRITE: # :write - write database entry
+ prfeature = NO
+ iferr {
+ ival[1] = ID_AP(id,1)
+ ival[2] = ID_AP(id,2)
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1)
+ call id_dbwrite (id, ID_IMAGE(id), ival, YES)
+ else {
+ call gargi (ival[1])
+ if (nscan() < 3)
+ ival[1] = ID_AP(id,1)
+ call gargi (ival[2])
+ if (nscan() < 4)
+ ival[2] = ID_AP(id,2)
+ call id_dbwrite (id, cmd, ival, YES)
+ }
+ } then
+ call erract (EA_WARN)
+ case ADD: # :add - add features from database entry
+ prfeature = NO
+ iferr {
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1)
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1),
+ YES, YES)
+ else {
+ call gargi (ival[1])
+ if (nscan() < 3)
+ ival[1] = ID_AP(id,1)
+ call gargi (ival[2])
+ if (nscan() < 4)
+ ival[2] = ID_AP(id,2)
+ call id_dbread (id, cmd, ival, YES, YES)
+ }
+ } then
+ call erract (EA_WARN)
+ case COORDLIST: # :coordlist - set coordinate list
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ call printf ("coordlist %s\n")
+ call pargstr (ID_COORDLIST(id))
+ prfeature = NO
+ } else {
+ call strcpy (cmd, ID_COORDLIST(id), ID_LENSTRING)
+ call id_unmapll (id)
+ call id_mapll (id)
+ }
+ case MATCH: # :match - set matching distance for coordinate list
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("match %g\n")
+ call pargr (ID_MATCH(id))
+ prfeature = NO
+ } else
+ ID_MATCH(id) = rval[1]
+ case MAXFEATURES: # :maxfeatures - set max num features for auto find
+ call gargi (ival[1])
+ if (nscan() == 1) {
+ call printf ("maxfeatures %d\n")
+ call pargi (ID_MAXFEATURES(id))
+ prfeature = NO
+ } else
+ ID_MAXFEATURES(id) = ival[1]
+ case MINSEP: # :minsep - set minimum feature separation allowed
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("minsep %g\n")
+ call pargr (ID_MINSEP(id))
+ prfeature = NO
+ } else
+ ID_MINSEP(id) = rval[1]
+ case ZWIDTH: # :zwidth - set zoom window width
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("zwidth %g\n")
+ call pargr (ID_ZWIDTH(id))
+ prfeature = NO
+ } else {
+ ID_ZWIDTH(id) = rval[1]
+ if (ID_GTYPE(id) == 2)
+ ID_NEWGRAPH(id) = YES
+ }
+ case LABEL: # :labels - set label type
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ switch (ID_LABELS(id)) {
+ case 2:
+ call printf ("labels index\n")
+ case 3:
+ call printf ("labels pixel\n")
+ case 4:
+ call printf ("labels coord\n")
+ case 5:
+ call printf ("labels user\n")
+ case 6:
+ call printf ("labels both\n")
+ default:
+ call printf ("labels none\n")
+ }
+ prfeature = NO
+ } else {
+ ID_LABELS(id) = strdic (cmd, cmd, SZ_LINE, LABELS)
+ do i = 1, ID_NFEATURES(id)
+ call id_mark (id, i)
+ }
+ case WIDTH: # :fwidth - set centering width
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("fwidth %g\n")
+ call pargr (ID_FWIDTH(id))
+ prfeature = NO
+ } else
+ ID_FWIDTH(id) = rval[1]
+ case TYPE: # :ftype - set centering type
+ call gargwrd (cmd, SZ_LINE)
+ if (nscan() == 1) {
+ switch (ID_FTYPE(id)) {
+ case EMISSION:
+ call printf ("ftype emission\n")
+ case ABSORPTION:
+ call printf ("ftype absorption\n")
+ }
+ prfeature = NO
+ } else
+ ID_FTYPE(id) = strdic (cmd, cmd, SZ_LINE, FTYPES)
+ case RADIUS: # :cradius - set centering radius
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("cradius %g\n")
+ call pargr (ID_CRADIUS(id))
+ prfeature = NO
+ } else
+ ID_CRADIUS(id) = rval[1]
+ case THRESHOLD: # :threshold - set centering threshold
+ call gargr (rval[1])
+ if (nscan() == 1) {
+ call printf ("threshold %g\n")
+ call pargr (ID_THRESHOLD(id))
+ prfeature = NO
+ } else
+ ID_THRESHOLD(id) = rval[1]
+ default:
+ call printf ("Unrecognized or ambiguous command\007")
+ prfeature = NO
+ }
+end
diff --git a/noao/onedspec/identify/iddb.x b/noao/onedspec/identify/iddb.x
new file mode 100644
index 00000000..e354d1c4
--- /dev/null
+++ b/noao/onedspec/identify/iddb.x
@@ -0,0 +1,515 @@
+include <imset.h>
+include <math/curfit.h>
+include <smw.h>
+include <units.h>
+include "identify.h"
+include <pkg/dttext.h>
+
+
+# ID_DBREAD -- Read features data from the database.
+
+procedure id_dbread (id, name, ap, add, verbose)
+
+pointer id # ID pointer
+char name[SZ_LINE] # Image name
+int ap[2] # Aperture number
+int add # Add features?
+int verbose # Verbose flag
+
+int rec, dtlocate()
+pointer sp, line, str
+errchk dtremap, dtlocate, id_dbread_rec
+
+begin
+ call smark (sp)
+ call salloc (line, SZ_LINE, TY_CHAR)
+
+ call strcpy ("id", Memc[line], SZ_LINE)
+ call imgcluster (name, Memc[line+2], SZ_LINE)
+ call dtremap (ID_DT(id), ID_DATABASE(id), Memc[line], READ_ONLY)
+
+ call id_dbsection (id, name, ap, ID_SECTION(id), ID_LENSTRING)
+ call sprintf (Memc[line], SZ_LINE, "identify %s%s")
+ call pargstr (name)
+ call pargstr (ID_SECTION(id))
+
+ iferr (rec = dtlocate (ID_DT(Id), Memc[line])) {
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[str], SZ_LINE, "Entry not found: %s")
+ call pargstr (Memc[line])
+ call error (0, Memc[str])
+ }
+
+ call id_dbread_rec (id, rec, add)
+
+ if (ID_NFEATURES(id) > 0) {
+ ID_NEWGRAPH(id) = YES
+ ID_NEWFEATURES(id) = YES
+ ID_CURRENT(id) = 1
+ } else
+ ID_CURRENT(id) = 0
+
+ if (verbose == YES) {
+ call printf ("identify %s%s\n")
+ call pargstr (name)
+ call pargstr (ID_SECTION(id))
+ }
+
+ call sfree (sp)
+end
+
+
+# ID_DBSAVE -- Read all entries from a database and save.
+
+procedure id_dbsave (id, name)
+
+pointer id # ID pointer
+char name[SZ_LINE] # Image name
+
+int rec, dtgeti()
+pointer sp, line, dt
+errchk dtremap, dtgeti, id_dbread_rec, id_saveap
+
+begin
+ call smark (sp)
+ call salloc (line, SZ_FNAME, TY_CHAR)
+
+ call strcpy ("id", Memc[line], SZ_FNAME)
+ call imgcluster (name, Memc[line+2], SZ_FNAME)
+ call dtremap (ID_DT(id), ID_DATABASE(id), Memc[line], READ_ONLY)
+
+ dt = ID_DT(id)
+ do rec = 1, DT_NRECS(dt) {
+ ID_AP(id,1) = dtgeti (dt, rec, "aperture")
+ ID_AP(id,2) = 1
+ call id_dbread_rec (id, rec, NO)
+ call id_saveap (id)
+ }
+
+ call sfree (sp)
+end
+
+
+# ID_DBREAD_REC -- Read specified record from the database.
+
+procedure id_dbread_rec (id, rec, add)
+
+pointer id # ID pointer
+int rec # Database record
+int add # Add features?
+
+double pix
+int i, j, k, ncoeffs
+pointer dt, sh, un, sp, line, coeffs
+
+int dtgeti(), dcvstati(), dtscan(), nscan()
+real dtgetr()
+double dcvstatd()
+bool un_compare()
+pointer un_open()
+errchk un_open, dtgeti(), dtgad()
+
+begin
+ call smark (sp)
+ call salloc (line, SZ_LINE, TY_CHAR)
+
+ dt = ID_DT(id)
+ sh = ID_SH(id)
+
+ if (add == YES) {
+ j = dtgeti (dt, rec, "features")
+ k = j + ID_NFEATURES(id)
+ ID_NALLOC(id) = k
+
+ call realloc (ID_PIX(id), k, TY_DOUBLE)
+ call realloc (ID_FIT(id), k, TY_DOUBLE)
+ call realloc (ID_USER(id), k, TY_DOUBLE)
+ call realloc (ID_WTS(id), k, TY_DOUBLE)
+ call realloc (ID_FWIDTHS(id), k, TY_REAL)
+ call realloc (ID_FTYPES(id), k, TY_INT)
+ call realloc (ID_LABEL(id), k, TY_POINTER)
+
+ do i = 1, j {
+ k = dtscan (dt)
+ call gargd (pix)
+
+ ID_NFEATURES(id) = ID_NFEATURES(id) + 1
+ for (k=ID_NFEATURES(id); (k>1)&&(pix<PIX(id,k-1)); k=k-1) {
+ PIX(id,k) = PIX(id,k-1)
+ FIT(id,k) = FIT(id,k-1)
+ USER(id,k) = USER(id,k-1)
+ WTS(id,k) = WTS(id,k-1)
+ FWIDTH(id,k) = FWIDTH(id,k-1)
+ FTYPE(id,k) = FTYPE(id,k-1)
+ Memi[ID_LABEL(id)+k-1] = Memi[ID_LABEL(id)+k-2]
+ }
+ PIX(id,k) = pix
+ call gargd (FIT(id,k))
+ call gargd (USER(id,k))
+ call gargr (FWIDTH(id,k))
+ call gargi (FTYPE(id,k))
+ call gargd (WTS(id,k))
+ call gargstr (Memc[line], SZ_LINE)
+ Memi[ID_LABEL(id)+k-1] = NULL
+ call id_label (Memc[line], Memi[ID_LABEL(id)+k-1])
+
+ # The following initialization is for backwards compatibility.
+ if (nscan() < 5) {
+ FWIDTH(id,k) = ID_FWIDTH(id)
+ FTYPE(id,k) = ID_FTYPE(id)
+ } else if (nscan() < 6)
+ WTS(id,k) = 1.
+ }
+
+ if (ID_UN(id) != NULL) {
+ ifnoerr (call dtgstr (dt, rec, "units", Memc[line], SZ_LINE)) {
+ un = un_open (Memc[line])
+ if (!un_compare (un, ID_UN(id)) && j > 0) {
+ k = ID_NFEATURES(id) - j
+ call un_ctrand (un, ID_UN(id), FIT(id,k), FIT(id,k), j)
+ call un_ctrand (un, ID_UN(id), USER(id,k), USER(id,k),j)
+ }
+ call un_close (un)
+ }
+ }
+
+ } else {
+ if (sh != NULL) {
+ if (SMW_FORMAT(MW(sh))==SMW_ES || SMW_FORMAT(MW(sh))==SMW_MS) {
+ iferr (APLOW(sh,1) = dtgetr (dt, rec, "aplow"))
+ APLOW(sh,1) = INDEF
+ iferr (APHIGH(sh,1) = dtgetr (dt, rec, "aphigh"))
+ APHIGH(sh,1) = INDEF
+ }
+ }
+
+ do i = 1, ID_NFEATURES(id)
+ call mfree (Memi[ID_LABEL(id)+i-1], TY_CHAR)
+
+ k = dtgeti (dt, rec, "features")
+ ID_NFEATURES(id) = k
+ ID_NALLOC(id) = k
+ call realloc (ID_PIX(id), k, TY_DOUBLE)
+ call realloc (ID_FIT(id), k, TY_DOUBLE)
+ call realloc (ID_USER(id), k, TY_DOUBLE)
+ call realloc (ID_WTS(id), k, TY_DOUBLE)
+ call realloc (ID_FWIDTHS(id), k, TY_REAL)
+ call realloc (ID_FTYPES(id), k, TY_INT)
+ call realloc (ID_LABEL(id), k, TY_POINTER)
+
+ do i = 1, ID_NFEATURES(id) {
+ k = dtscan (dt)
+ call gargd (PIX(id,i))
+ call gargd (FIT(id,i))
+ call gargd (USER(id,i))
+ call gargr (FWIDTH(id,i))
+ call gargi (FTYPE(id,i))
+ call gargd (WTS(id,i))
+ call gargstr (Memc[line], SZ_LINE)
+ Memi[ID_LABEL(id)+i-1] = NULL
+ call id_label (Memc[line], Memi[ID_LABEL(id)+i-1])
+
+ # The following initialization is for backwards compatibility.
+ if (nscan() < 5) {
+ FWIDTH(id,i) = ID_FWIDTH(id)
+ FTYPE(id,i) = ID_FTYPE(id)
+ } else if (nscan() < 6)
+ WTS(id,i) = 1.
+ }
+
+ iferr (ID_SHIFT(id) = dtgetr (dt, rec, "shift"))
+ ID_SHIFT(id) = 0.
+
+ iferr {
+ ncoeffs = dtgeti (dt, rec, "coefficients")
+ call salloc (coeffs, ncoeffs, TY_DOUBLE)
+ call dtgad (dt, rec, "coefficients", Memd[coeffs], ncoeffs,
+ ncoeffs)
+
+ if (ID_CV(id) != NULL)
+ call dcvfree (ID_CV(id))
+ call dcvrestore (ID_CV(id), Memd[coeffs])
+
+ call ic_putr (ID_IC(id), "xmin", real (dcvstatd(ID_CV(id),
+ CVXMIN)))
+ call ic_putr (ID_IC(id), "xmax", real (dcvstatd(ID_CV(id),
+ CVXMAX)))
+ ifnoerr (call dtgstr (dt,rec,"function",Memc[line],SZ_LINE)) {
+ call ic_pstr (ID_IC(id), "function", Memc[line])
+ call ic_puti (ID_IC(id), "order", dtgeti (dt, rec, "order"))
+ call dtgstr (dt, rec, "sample", Memc[line], SZ_LINE)
+ call ic_pstr (ID_IC(id), "sample", Memc[line])
+ call ic_puti (ID_IC(id), "naverage",
+ dtgeti (dt, rec, "naverage"))
+ call ic_puti (ID_IC(id), "niterate",
+ dtgeti (dt, rec, "niterate"))
+ call ic_putr (ID_IC(id), "low",
+ dtgetr (dt, rec, "low_reject"))
+ call ic_putr (ID_IC(id), "high",
+ dtgetr (dt, rec, "high_reject"))
+ call ic_putr (ID_IC(id), "grow", dtgetr (dt, rec, "grow"))
+ } else {
+ call ic_puti (ID_IC(id), "order", dcvstati (ID_CV(id),
+ CVORDER))
+ switch (dcvstati (ID_CV(id), CVTYPE)) {
+ case LEGENDRE:
+ call ic_pstr (ID_IC(id), "function", "legendre")
+ case CHEBYSHEV:
+ call ic_pstr (ID_IC(id), "function", "chebyshev")
+ case SPLINE1:
+ call ic_pstr (ID_IC(id), "function", "spline1")
+ case SPLINE3:
+ call ic_pstr (ID_IC(id), "function", "spline3")
+ }
+ }
+
+ ID_NEWCV(id) = YES
+ ID_CURRENT(id) = min (1, ID_NFEATURES(id))
+ } then
+ ;
+
+ ifnoerr (call dtgstr (dt, rec, "units", Memc[line], SZ_LINE)) {
+ if (ID_UN(id) == NULL)
+ ID_UN(id) = un_open (Memc[line])
+ else {
+ un = un_open (Memc[line])
+ if (!un_compare (un, ID_UN(id))) {
+ call id_unitsll (id, Memc[line])
+ call un_close (ID_UN(id))
+ ID_UN(id) = un
+ } else
+ call un_close (un)
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# ID_DBWRITE -- Write features data to the database.
+
+procedure id_dbwrite (id, name, ap, verbose)
+
+pointer id # ID pointer
+char name[ARB] # Image name
+int ap[2] # Aperture number
+int verbose # Verbose flag
+
+int i, ncoeffs
+pointer dt, sp, coeffs, root, sh, im
+
+int dcvstati(), ic_geti()
+real ic_getr()
+
+errchk dtremap
+
+begin
+ call smark (sp)
+ call salloc (root, SZ_FNAME, TY_CHAR)
+
+ call strcpy ("id", Memc[root], SZ_FNAME)
+ call imgcluster (name, Memc[root+2], SZ_FNAME)
+ call dtremap (ID_DT(id), ID_DATABASE(id), Memc[root], APPEND)
+
+ call id_dbsection (id, name, ap, ID_SECTION(id), ID_LENSTRING)
+
+ sh = ID_SH(id)
+ dt = ID_DT(id)
+ call dtptime (dt)
+ call dtput (dt, "begin\tidentify %s%s\n")
+ call pargstr (name)
+ call pargstr (ID_SECTION(id))
+ call dtput (dt, "\tid\t%s\n")
+ call pargstr (name)
+ call dtput (dt, "\ttask\tidentify\n")
+ call dtput (dt, "\timage\t%s%s\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ if (SMW_FORMAT(MW(sh)) == SMW_ES || SMW_FORMAT(MW(sh)) == SMW_MS) {
+ call dtput (dt, "\taperture\t%d\n")
+ call pargi (ID_AP(id,1))
+ call dtput (dt, "\taplow\t%g\n")
+ call pargr (APLOW(sh,1))
+ call dtput (dt, "\taphigh\t%g\n")
+ call pargr (APHIGH(sh,1))
+ }
+
+ if (ID_UN(id) != NULL) {
+ call dtput (dt, "\tunits\t%s\n")
+ call pargstr (UN_USER(ID_UN(id)))
+ }
+ call dtput (dt, "\tfeatures\t%d\n")
+ call pargi (ID_NFEATURES(id))
+ do i = 1, ID_NFEATURES(id) {
+ call dtput (dt, "\t %10.2f %10.9g %10.9g %5.1f %d %d %s\n")
+ call pargd (PIX(id,i))
+ call pargd (FIT(id,i))
+ call pargd (USER(id,i))
+ call pargr (FWIDTH(id,i))
+ call pargi (FTYPE(id,i))
+ call pargd (WTS(id,i))
+ if (Memi[ID_LABEL(id)+i-1] != NULL)
+ call pargstr (Memc[Memi[ID_LABEL(id)+i-1]])
+ else
+ call pargstr ("")
+ }
+
+ if (ID_SHIFT(id) != 0.) {
+ call dtput (dt, "\tshift\t%g\n")
+ call pargd (ID_SHIFT(id))
+ }
+
+ if (ID_CV(id) != NULL) {
+ call dtput (dt, "\tfunction %s\n")
+ call ic_gstr (ID_IC(id), "function", Memc[root], SZ_FNAME)
+ call pargstr (Memc[root])
+ call dtput (dt, "\torder %d\n")
+ call pargi (ic_geti (ID_IC(id), "order"))
+ call dtput (dt, "\tsample %s\n")
+ call ic_gstr (ID_IC(id), "sample", Memc[root], SZ_FNAME)
+ call pargstr (Memc[root])
+ call dtput (dt, "\tnaverage %d\n")
+ call pargi (ic_geti (ID_IC(id), "naverage"))
+ call dtput (dt, "\tniterate %d\n")
+ call pargi (ic_geti (ID_IC(id), "niterate"))
+ call dtput (dt, "\tlow_reject %g\n")
+ call pargr (ic_getr (ID_IC(id), "low"))
+ call dtput (dt, "\thigh_reject %g\n")
+ call pargr (ic_getr (ID_IC(id), "high"))
+ call dtput (dt, "\tgrow %g\n")
+ call pargr (ic_getr (ID_IC(id), "grow"))
+
+ ncoeffs = dcvstati (ID_CV(id), CVNSAVE)
+ call salloc (coeffs, ncoeffs, TY_DOUBLE)
+ call dcvsave (ID_CV(id), Memd[coeffs])
+ call dtput (dt, "\tcoefficients\t%d\n")
+ call pargi (ncoeffs)
+ do i = 1, ncoeffs {
+ call dtput (dt, "\t\t%g\n")
+ call pargd (Memd[coeffs+i-1])
+ }
+ }
+
+ call dtput (dt, "\n")
+
+ ID_NEWFEATURES(id) = NO
+ ID_NEWCV(id) = NO
+ ID_NEWDBENTRY(id) = NO
+
+ if (verbose == YES) {
+ call printf ("identify %s%s\n")
+ call pargstr (name)
+ call pargstr (ID_SECTION(id))
+ }
+
+ # Enter reference spectrum name in image header.
+ im = IM(sh)
+ call imseti (im, IM_WHEADER, YES)
+ call imastr (im, "REFSPEC1", ID_IMAGE(id))
+ iferr (call imdelf (im, "REFSPEC2"))
+ ;
+
+ call sfree (sp)
+end
+
+
+# ID_DBCHECK -- Check if there is an entry in the database.
+# This does not actually read the database entry. It also assumes that
+# if a database is already open it is for the same image (the image
+# names are not checked) and the database has been scanned.
+
+int procedure id_dbcheck (id, name, ap)
+
+pointer id # ID pointer
+char name[SZ_LINE] # Image name
+int ap[2] # Aperture number
+
+int rec, stat
+pointer sp, line, sec
+
+int dtlocate()
+
+errchk dtremap(), dtlocate()
+
+begin
+ call smark (sp)
+ call salloc (line, SZ_LINE, TY_CHAR)
+ call salloc (sec, SZ_LINE, TY_CHAR)
+
+ if (ID_DT(id) == NULL) {
+ call strcpy ("id", Memc[line], SZ_LINE)
+ call imgcluster (name, Memc[line+2], SZ_LINE)
+ iferr (call dtremap (ID_DT(id), ID_DATABASE(id), Memc[line],
+ READ_ONLY)) {
+ call sfree (sp)
+ return (NO)
+ }
+ }
+
+ call id_dbsection (id, name, ap, Memc[sec], SZ_LINE)
+ call sprintf (Memc[line], SZ_LINE, "identify %s%s")
+ call pargstr (name)
+ call pargstr (Memc[sec])
+
+ iferr (rec = dtlocate (ID_DT(id), Memc[line]))
+ stat = NO
+ else
+ stat = YES
+
+ call sfree (sp)
+ return (stat)
+end
+
+
+# ID_DBSECTION -- Make the IDENTIFY section.
+
+procedure id_dbsection (id, name, ap, section, sz_section)
+
+pointer id #I ID pointer
+char name[SZ_LINE] #I Image name
+int ap[2] #I Aperture number
+char section[sz_section] #O IDENTIFY section
+int sz_section #I Size of section string
+
+pointer sh, smw
+bool streq()
+
+begin
+ sh = ID_SH(id)
+ smw = MW(sh)
+
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ section[1] = EOS
+ if (streq (name, ID_IMAGE(id))) {
+ switch (SMW_LDIM(smw)) {
+ case 2:
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (section, sz_section, "[*,%d]")
+ case 2:
+ call sprintf (section, sz_section, "[%d,*]")
+ }
+ #call pargi (LINDEX(sh,1))
+ call pargi (ap[1])
+ case 3:
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (section, sz_section, "[*,%d,%d]")
+ case 2:
+ call sprintf (section, sz_section, "[%d,*,%d]")
+ case 3:
+ call sprintf (section, sz_section, "[%d,%d,*]")
+ }
+ #call pargi (LINDEX(sh,1))
+ #call pargi (LINDEX(sh,2))
+ call pargi (ap[1])
+ call pargi (ap[2])
+ }
+ }
+ case SMW_ES, SMW_MS:
+ call sprintf (section, sz_section, " - Ap %d")
+ call pargi (ap[1])
+ }
+end
diff --git a/noao/onedspec/identify/iddelete.x b/noao/onedspec/identify/iddelete.x
new file mode 100644
index 00000000..cd96abb1
--- /dev/null
+++ b/noao/onedspec/identify/iddelete.x
@@ -0,0 +1,26 @@
+include "identify.h"
+
+# ID_DELETE -- Delete a feature.
+
+procedure id_delete (id, feature)
+
+pointer id # ID pointer
+int feature # Feature to be deleted
+
+int i
+
+begin
+ call mfree (Memi[ID_LABEL(id)+feature-1], TY_CHAR)
+ do i = feature + 1, ID_NFEATURES(id) {
+ PIX(id,i-1) = PIX(id,i)
+ FIT(id,i-1) = FIT(id,i)
+ USER(id,i-1) = USER(id,i)
+ WTS(id,i-1) = WTS(id,i)
+ FWIDTH(id,i-1) = FWIDTH(id,i)
+ FTYPE(id,i-1) = FTYPE(id,i)
+ Memi[ID_LABEL(id)+i-2] = Memi[ID_LABEL(id)+i-1]
+ }
+ Memi[ID_LABEL(id)+ID_NFEATURES(id)-1] = NULL
+ ID_NFEATURES(id) = ID_NFEATURES(id) - 1
+ ID_NEWFEATURES(id) = YES
+end
diff --git a/noao/onedspec/identify/iddofit.x b/noao/onedspec/identify/iddofit.x
new file mode 100644
index 00000000..8e6558e9
--- /dev/null
+++ b/noao/onedspec/identify/iddofit.x
@@ -0,0 +1,108 @@
+include <units.h>
+include "identify.h"
+
+# ID_DOFIT -- Fit a function to the features. Eliminate INDEF points.
+
+procedure id_dofit (id, interactive)
+
+pointer id # ID pointer
+int interactive # Interactive fit?
+
+int i, j, k, nfit, ic_geti()
+pointer gt1, sp, x, y, wts, rejpts, str, gt_init()
+
+begin
+ if (ID_NFEATURES(id) == 0) {
+ if (ID_CV(id) != NULL) {
+ call dcvfree (ID_CV(id))
+ ID_SHIFT(id) = 0.
+ ID_NEWGRAPH(id) = YES
+ ID_NEWCV(id) = YES
+ }
+ return
+ }
+
+ call smark (sp)
+ call salloc (x, ID_NFEATURES(id), TY_DOUBLE)
+ call salloc (y, ID_NFEATURES(id), TY_DOUBLE)
+ call salloc (wts, ID_NFEATURES(id), TY_DOUBLE)
+
+ nfit = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD (PIX(id,i)) || IS_INDEFD (USER(id,i)))
+ next
+ Memd[x+nfit] = PIX(id,i)
+ Memd[y+nfit] = USER(id,i)
+ Memd[wts+nfit] = max (1D0, WTS(id,i))
+ nfit = nfit + 1
+ }
+
+ if (nfit > 1) {
+ if (ID_UN(id) != NULL) {
+ call ic_pstr (ID_IC(id), "ylabel", UN_LABEL(ID_UN(id)))
+ call ic_pstr (ID_IC(id), "yunits", UN_UNITS(ID_UN(id)))
+ }
+ if (interactive == YES) {
+ call salloc (str, SZ_LINE, TY_CHAR)
+ gt1 = gt_init()
+ call icg_fitd (ID_IC(id), ID_GP(id), "cursor", gt1, ID_CV(id),
+ Memd[x], Memd[y], Memd[wts], nfit)
+ call gt_free (gt1)
+ } else
+ call ic_fitd (ID_IC(id), ID_CV(id), Memd[x], Memd[y], Memd[wts],
+ nfit, YES, YES, YES, YES)
+
+ if (ic_geti (ID_IC(id), "nreject") > 0 &&
+ ic_geti (ID_IC(id), "nfit") == nfit)
+ rejpts = ic_geti (ID_IC(id), "rejpts")
+ else
+ rejpts = NULL
+
+ j = 0
+ k = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD (PIX(id,i)) || IS_INDEFD (USER(id,i))) {
+ j = j + 1
+ PIX(id,j) = PIX(id,i)
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ call mfree (Memi[ID_LABEL(id)+j-1], TY_CHAR)
+ Memi[ID_LABEL(id)+j-1] = Memi[ID_LABEL(id)+i-1]
+ } else {
+ if (Memd[wts+k] != 0.) {
+ j = j + 1
+ PIX(id,j) = Memd[x+k]
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = Memd[y+k]
+ WTS(id,j) = Memd[wts+k]
+ if (rejpts != NULL)
+ if (Memi[rejpts+k] == YES)
+ WTS(id,j) = 0.
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ Memi[ID_LABEL(id)+j-1] = Memi[ID_LABEL(id)+i-1]
+ }
+ k = k + 1
+ }
+ }
+ do i = j+1, ID_NFEATURES(id)
+ Memi[ID_LABEL(id)+i-1] = NULL
+ ID_NFEATURES(id) = j
+
+ ID_SHIFT(id) = 0.
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ } else {
+ if (ID_CV(id) != NULL) {
+ call dcvfree (ID_CV(id))
+ ID_SHIFT(id) = 0.
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/iddoshift.x b/noao/onedspec/identify/iddoshift.x
new file mode 100644
index 00000000..2dfdff74
--- /dev/null
+++ b/noao/onedspec/identify/iddoshift.x
@@ -0,0 +1,41 @@
+include "identify.h"
+
+# ID_DOSHIFT -- Minimize residuals by constant shift.
+
+procedure id_doshift (id, interactive)
+
+pointer id # ID pointer
+int interactive # Called interactively?
+
+int i, j
+double shft, delta, rms, id_fitpt()
+
+begin
+ shft = 0.
+ rms = 0.
+ j = 0
+ for (i=1; i <= ID_NFEATURES(id); i = i + 1) {
+ if (IS_INDEFD (USER(id,i)) || WTS(id,i) == 0.)
+ next
+ delta = USER(id,i) - id_fitpt (id, PIX(id,i))
+ shft = shft + delta
+ rms = rms + delta * delta
+ j = j + 1
+ }
+
+ if (j > 0) {
+ shft = shft / j
+ rms = rms / j
+ if (interactive == YES) {
+ call printf ("%s%s: Coordinate shift=%5f, rms=%5f, npts=%3d\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargd (shft)
+ call pargd (sqrt (rms - shft ** 2))
+ call pargi (j)
+ }
+ ID_SHIFT(id) = ID_SHIFT(id) + shft
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ }
+end
diff --git a/noao/onedspec/identify/identify.h b/noao/onedspec/identify/identify.h
new file mode 100644
index 00000000..0af2d58b
--- /dev/null
+++ b/noao/onedspec/identify/identify.h
@@ -0,0 +1,90 @@
+# Task parameters
+
+define ID_LENSTRING 99 # Length of strings in ID structure
+define ID_LENSTRUCT 354 # Length ID structure
+
+define ID_IMAGE Memc[P2C($1)] # Image
+define ID_SECTION Memc[P2C($1+50)] # Section for 2D and 3D images
+define ID_DATABASE Memc[P2C($1+100)] # Name of database
+define ID_COORDLIST Memc[P2C($1+150)] # Name of coordinate list
+define ID_COORDSPEC Memc[P2C($1+200)] # Name of coordinate spectrum
+define ID_SAVEID Memc[P2C($1+250)] # ID for save structure
+define ID_LINE Memi[$1+$2+299] # Image line or column [2]
+define ID_MAXLINE Memi[$1+$2+301] # Maximum line or column [2]
+define ID_AP Memi[$1+$2+303] # Aperture if appropriate [2]
+define ID_APS Memi[$1+306] # Array of apertures (pointer)
+define ID_NSUM Memi[$1+$2+306] # Number of lines to sum [2]
+define ID_MAXFEATURES Memi[$1+309] # Maximum number of features
+define ID_FTYPE Memi[$1+310] # Feature type
+define ID_MINSEP Memr[P2R($1+311)] # Minimum pixel separation
+define ID_MATCH Memr[P2R($1+312)] # Maximum matching separation
+define ID_FWIDTH Memr[P2R($1+313)] # Feature width in pixels
+define ID_CRADIUS Memr[P2R($1+314)] # Centering radius in pixels
+define ID_THRESHOLD Memr[P2R($1+315)] # Centering threshold
+define ID_ZWIDTH Memr[P2R($1+316)] # Zoom window width in fit units
+define ID_LL Memi[$1+317] # Pointer to coordinate list lines
+define ID_LLL Memi[$1+318] # Pointer to coordinate list labels
+define ID_NLL Memi[$1+319] # Number of coordinate list lines
+define ID_LABELS Memi[$1+320] # Type of feature labels
+define ID_LOGFILES Memi[$1+321] # List of logfiles
+
+# Common image data
+
+define ID_SHIFT Memd[P2D($1+322)]# Wavelength shift
+define ID_IMDATA Memi[$1+324] # Image data (pointer)
+define ID_PIXDATA Memi[$1+325] # Pixel coordinates (pointer)
+define ID_FITDATA Memi[$1+326] # Fit coordinates (pointer)
+define ID_NPTS Memi[$1+327] # Number of points
+
+# Features
+
+define ID_NFEATURES Memi[$1+328] # Number of features
+define ID_NALLOC Memi[$1+329] # Length of allocated feature arrays
+define ID_PIX Memi[$1+330] # Feature pixel coordinates (pointer)
+define ID_FIT Memi[$1+331] # Feature fit coordinates (pointer)
+define ID_USER Memi[$1+332] # Feature user coordinates (pointer)
+define ID_WTS Memi[$1+333] # Feature weights (pointer)
+define ID_FWIDTHS Memi[$1+334] # Feature width (pointer)
+define ID_FTYPES Memi[$1+335] # Feature type (pointer)
+define ID_LABEL Memi[$1+336] # Feature label (pointer)
+define ID_CURRENT Memi[$1+337] # Current feature
+
+# Pointers for other packages and to save data
+
+define ID_SH Memi[$1+338] # SHDR pointer
+define ID_LP Memi[$1+339] # Logical to physical transformation
+define ID_PL Memi[$1+340] # Physical to logical transformation
+define ID_IC Memi[$1+341] # ICFIT pointer
+define ID_CV Memi[$1+342] # Curfit pointer
+define ID_GP Memi[$1+343] # GIO pointer
+define ID_GT Memi[$1+344] # Gtools pointer
+define ID_STP Memi[$1+345] # Symbol table of saved data
+define ID_DT Memi[$1+346] # Database pointer
+define ID_UN Memi[$1+347] # Units pointer
+
+# Flags
+
+define ID_NEWFEATURES Memi[$1+348] # Has feature list changed?
+define ID_NEWCV Memi[$1+349] # Has fitting function changed?
+define ID_NEWGRAPH Memi[$1+350] # Has graph changed?
+define ID_NEWDBENTRY Memi[$1+351] # Has database entry changed?
+define ID_REFIT Memi[$1+352] # Refit feature data?
+define ID_GTYPE Memi[$1+353] # Graph type
+
+# End of structure ----------------------------------------------------------
+
+define LABELS "|none|index|pixel|coord|user|both|"
+define FTYPES "|emission|absorption|"
+define EMISSION 1 # Emission feature
+define ABSORPTION 2 # Absorption feature
+
+define IMDATA Memr[ID_IMDATA($1)+$2-1]
+define PIXDATA Memd[ID_PIXDATA($1)+$2-1]
+define FITDATA Memd[ID_FITDATA($1)+$2-1]
+
+define PIX Memd[ID_PIX($1)+$2-1]
+define FIT Memd[ID_FIT($1)+$2-1]
+define USER Memd[ID_USER($1)+$2-1]
+define WTS Memd[ID_WTS($1)+$2-1]
+define FWIDTH Memr[ID_FWIDTHS($1)+$2-1]
+define FTYPE Memi[ID_FTYPES($1)+$2-1]
diff --git a/noao/onedspec/identify/identify.key b/noao/onedspec/identify/identify.key
new file mode 100644
index 00000000..95b44c32
--- /dev/null
+++ b/noao/onedspec/identify/identify.key
@@ -0,0 +1,90 @@
+1. IDENTIFY CURSOR KEY SUMMARY
+
+? Help k Next line u Enter coordinate
+a Affect all features l Match list (refit) v Weight
+b Auto identification m Mark feature w Window graph
+c Center feature(s) n Next feature x Find shift
+d Delete feature(s) o Go to line y Find peaks
+e Add lines (no refit) p Pan graph z Zoom graph
+f Fit positions q Quit . Nearest feature
+g Fit zero point shift r Redraw graph + Next feature
+i Initialize s Shift feature - Previous feature
+j Preceding line t Reset position I Interrupt
+
+2. IDENTIFY COLON COMMAND SUMMARY
+
+:add [image [ap]] :fwidth [value] :read [image [ap]]
+:coordlist [file] :image [image] :show [file]
+:cradius [value] :labels [type] :threshold [value]
+:database [file] :match [value] :write [image [ap]]
+:features [file] :maxfeatures [value] :zwidth [value]
+:ftype [type] :minsep [value]
+
+3. IDENTIFY CURSOR KEYS
+
+? Clear the screen and print menu of options
+a Apply next (c)enter or (d)elete operation to (a)ll features
+b Automatic line identifications: queries for approx. coordinate and dispersion
+c (C)enter the feature nearest the cursor
+d (D)elete the feature nearest the cursor
+e Add features from coordinate list with no automatic refit
+f (F)it a function of pixel coordinate to the user coordinates
+g Fit a zero point shift to the user coordinates
+i (I)nitialize (delete features and coordinate fit)
+j Go to the preceding image line or column in a 2D or multispec image
+k Go to the next image line or column in a 2D or multispec image
+l Add features from coordinate (l)ist with automatic refit
+m (M)ark a new feature near the cursor and enter coordinate and label
+n Move the cursor or zoom to the (n)ext feature (same as +)
+o Go to the specified image line or column in a 2D or multispec image
+p (P)an to user defined window after (z)ooming on a feature
+q (Q)uit and continue with next image (also carriage return)
+r (R)edraw the graph
+s (S)hift the current feature to the position of the cursor
+t Reset the position of a feature without centering
+u Enter a new (u)ser coordinate and label for the current feature
+v Modify weight of line in fitting
+w (W)indow the graph. Use '?' to window prompt for more help.
+x Find zero point shift by matching lines with peaks
+y Automatically find "maxfeatures" strongest peaks and identify them
+z (Z)oom on the feature nearest the cursor
+. Move the cursor or zoom to the feature nearest the cursor
++ Move the cursor or zoom to the next feature
+- Move the cursor or zoom to the previous feature
+I Interrupt task and exit immediately. Database information is not saved.
+
+
+4. IDENTIFY COLON COMMANDS
+
+The parameters are listed or set with the following commands which may be
+abbreviated. To list the value of a parameter type the command alone.
+
+:show file Show the values of all the parameters
+:features file Write feature list to file (default is STDOUT)
+
+:coordlist file Coordinate list file
+:cradius value Centering radius in pixels
+:threshold value Detection threshold for feature centering
+:database name Database for recording feature records
+:ftype value Feature type (emission or absorption)
+:fwidth value Feature width in pixels
+:image imagename Set a new image or show the current image
+:labels value Feature label type (none|index|pixel|coord|user|both)
+:match value Coordinate list matching distance
+:maxfeatures value Maximum number of features automatically found
+:minsep value Minimum separation allowed between features
+:read name ap Read a record from the database
+ (name and ap default to the current spectrum)
+:write name ap Write a record to the database
+ (name and ap default to the current spectrum)
+:add name ap Add features from the database
+ (name and ap default to the current spectrum)
+:zwidth value Zoom width in user units
+
+Labels:
+ none - No labels
+ index - Sequential numbers in order of increasing pixel position
+ pixel - Pixel coordinates
+ coord - User coordinates such as wavelength
+ user - User labels
+ both - Combination of coord and user
diff --git a/noao/onedspec/identify/idfitdata.x b/noao/onedspec/identify/idfitdata.x
new file mode 100644
index 00000000..2d86163c
--- /dev/null
+++ b/noao/onedspec/identify/idfitdata.x
@@ -0,0 +1,177 @@
+include <math/curfit.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include "identify.h"
+
+# ID_FITDATA -- Compute fit coordinates from pixel coordinates.
+
+procedure id_fitdata (id)
+
+pointer id # ID pointer
+int i
+
+begin
+ if (ID_SH(id) == NULL || ID_PIXDATA(id) == NULL)
+ return
+
+ call mfree (ID_FITDATA(id), TY_DOUBLE)
+ call malloc (ID_FITDATA(id), ID_NPTS(id), TY_DOUBLE)
+
+ if (ID_CV(id) == NULL) {
+ if (DC(ID_SH(id)) != DCNO && ID_UN(id) != NULL)
+ iferr (call shdr_units (ID_SH(id), UN_UNITS(ID_UN(id))))
+ ;
+ call achtrd (Memr[SX(ID_SH(id))], FITDATA(id,1), ID_NPTS(id))
+ call gt_sets (ID_GT(id), GTXLABEL, LABEL(ID_SH(id)))
+ call gt_sets (ID_GT(id), GTXUNITS, UNITS(ID_SH(id)))
+ } else {
+ call dcvvector (ID_CV(id), PIXDATA(id,1), FITDATA(id,1),
+ ID_NPTS(id))
+ if (FITDATA(id,2) > FITDATA(id,1)) {
+ do i = 3, ID_NPTS(id)
+ if (FITDATA(id,i) < FITDATA(id,i-1))
+ call error (1, "Coordinate solution is not monotonic")
+ } else {
+ do i = 3, ID_NPTS(id)
+ if (FITDATA(id,i) > FITDATA(id,i-1))
+ call error (1, "Coordinate solution is not monotonic")
+ }
+ if (ID_UN(id) == NULL) {
+ call gt_sets (ID_GT(id), GTXLABEL, LABEL(ID_SH(id)))
+ call gt_sets (ID_GT(id), GTXUNITS, UNITS(ID_SH(id)))
+ } else {
+ call gt_sets (ID_GT(id), GTXLABEL, UN_LABEL(ID_UN(id)))
+ call gt_sets (ID_GT(id), GTXUNITS, UN_UNITS(ID_UN(id)))
+ }
+ }
+ if (ID_SHIFT(id) != 0.)
+ call aaddkd (FITDATA(id,1), ID_SHIFT(id), FITDATA(id,1),ID_NPTS(id))
+
+end
+
+
+# ID_FITFEATURES -- Compute fit coordinates for features.
+
+procedure id_fitfeatures (id)
+
+pointer id # ID pointer
+int i
+
+double id_fitpt()
+
+begin
+ if (ID_NFEATURES(id) < 1)
+ return
+
+ if (ID_CV(id) == NULL)
+ do i = 1, ID_NFEATURES(id)
+ FIT(id,i) = id_fitpt (id, PIX(id,i))
+ else {
+ call dcvvector (ID_CV(id), PIX(id,1), FIT(id,1), ID_NFEATURES(id))
+ if (ID_SHIFT(id) != 0.)
+ call aaddkd (FIT(id,1), ID_SHIFT(id), FIT(id,1), ID_NFEATURES(id))
+ }
+end
+
+
+# ID_FITPT -- Compute fit coordinates from pixel coordinates.
+
+double procedure id_fitpt (id, pix)
+
+pointer id # ID pointer
+double pix # Pixel coordinate
+
+double fit
+
+double smw_c1trand(), shdr_lw(), dcveval()
+
+begin
+ if (ID_CV(id) == NULL) {
+ fit = smw_c1trand (ID_PL(id), pix)
+ fit = shdr_lw (ID_SH(id), fit)
+ } else
+ fit = dcveval (ID_CV(id), pix)
+ fit = fit + ID_SHIFT(id)
+
+ return (fit)
+end
+
+
+# FIT_TO_PIX -- Transform fit coordinate to pixel coordinate.
+
+define DXMIN .01
+
+double procedure fit_to_pix (id, fitcoord)
+
+pointer id # ID pointer
+double fitcoord # Fit coordinate to be transformed
+double pixcoord # Pixel coordinate returned
+
+int i, np1
+double dx
+
+int dcvstati()
+double shdr_wl(), smw_c1trand(), id_fitpt()
+
+begin
+ if (ID_CV(id) == NULL) {
+ pixcoord = fitcoord - ID_SHIFT(id)
+ pixcoord = shdr_wl (ID_SH(id), pixcoord)
+ pixcoord = smw_c1trand (ID_LP(id), pixcoord)
+ return (pixcoord)
+ }
+
+ np1 = NP1(ID_SH(id)) - 1
+ if (dcvstati (ID_CV(id), CVORDER) == 2) {
+ i = dcvstati (ID_CV(id), CVTYPE)
+ if (i == LEGENDRE || i == CHEBYSHEV) {
+ dx = FITDATA(id,1)
+ pixcoord = (fitcoord - dx) / (FITDATA(id,2) - dx) + 1 + np1
+ pixcoord = smw_c1trand (ID_LP(id), pixcoord)
+ return (pixcoord)
+ }
+ }
+
+ if (FITDATA(id,1) < FITDATA(id,ID_NPTS(id))) {
+ if ((fitcoord<FITDATA(id,1)) || (fitcoord>FITDATA(id,ID_NPTS(id))))
+ return (INDEFD)
+
+ for (i = 1; fitcoord > FITDATA(id,i); i = i + 1)
+ ;
+
+ if (FITDATA(id,i) == fitcoord)
+ return (PIXDATA(id,i))
+
+ pixcoord = smw_c1trand (ID_LP(id), double(i+np1-.5))
+ dx = smw_c1trand (ID_LP(id), double(i+np1+.5)) - pixcoord
+ while (dx > DXMIN) {
+ dx = dx / 2
+ if (id_fitpt (id, pixcoord) < fitcoord)
+ pixcoord = pixcoord + dx
+ else
+ pixcoord = pixcoord - dx
+ }
+ } else {
+ if ((fitcoord<FITDATA(id,ID_NPTS(id))) || (fitcoord>FITDATA(id,1)))
+ return (INDEFD)
+
+ for (i = 1; fitcoord < FITDATA(id,i); i = i + 1)
+ ;
+
+ if (FITDATA(id,i) == fitcoord)
+ return (PIXDATA(id,i))
+
+ pixcoord = smw_c1trand (ID_LP(id), double(i+np1-.5))
+ dx = smw_c1trand (ID_LP(id), double(i+np1+.5)) - pixcoord
+ while (dx > DXMIN) {
+ dx = dx / 2
+ if (id_fitpt (id, pixcoord) < fitcoord)
+ pixcoord = pixcoord - dx
+ else
+ pixcoord = pixcoord + dx
+ }
+ }
+
+ return (pixcoord)
+end
diff --git a/noao/onedspec/identify/idgdata.x b/noao/onedspec/identify/idgdata.x
new file mode 100644
index 00000000..92bd65eb
--- /dev/null
+++ b/noao/onedspec/identify/idgdata.x
@@ -0,0 +1,67 @@
+include <imhdr.h>
+include <imio.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include "identify.h"
+
+define SZ_TITLE 320 # Size of long string for title.
+
+# ID_GDATA -- Get image data.
+
+procedure id_gdata (id)
+
+pointer id # ID pointer
+
+int i, np1
+pointer sp, str, im, mw, sh
+
+double smw_c1trand()
+errchk shdr_open
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_TITLE, TY_CHAR)
+
+ sh = ID_SH(id)
+ im = IM(sh)
+ mw = MW(sh)
+
+ # If format is multispec then header info depends on line.
+ if (SMW_FORMAT(mw) == SMW_ES || SMW_FORMAT(mw) == SMW_MS)
+ ID_LINE(id,2) = 1
+ call shdr_open (im, mw, ID_LINE(id,1), ID_LINE(id,2),
+ INDEFI, SHDATA, sh)
+ if (ID_UN(id) != NULL) {
+ iferr (call shdr_units (sh, UN_UNITS(ID_UN(id))))
+ ;
+ }
+ ID_AP(id,1) = AP(sh)
+ ID_AP(id,2) = ID_LINE(id,2)
+ ID_NPTS(id) = SN(sh)
+ call id_dbsection (id, ID_IMAGE(id), ID_AP(id,1),
+ ID_SECTION(id), ID_LENSTRING)
+ call sprintf (Memc[str], SZ_TITLE, "identify %s%s\n%s")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargstr (TITLE(sh))
+ call gt_sets (ID_GT(id), GTTITLE, Memc[str])
+
+ # Free previous vectors and allocate new vectors.
+ call mfree (ID_PIXDATA(id), TY_DOUBLE)
+
+ call malloc (ID_PIXDATA(id), ID_NPTS(id), TY_DOUBLE)
+
+ # Set the physical coordinates.
+ np1 = NP1(sh) - 1
+ do i = 1, ID_NPTS(id)
+ PIXDATA(id,i) = smw_c1trand (ID_LP(id), double(i+np1))
+
+ # Set the image data
+ ID_IMDATA(id) = SY(sh)
+
+ ID_NEWGRAPH(id) = YES
+ ID_NEWCV(id) = YES
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/idgraph.x b/noao/onedspec/identify/idgraph.x
new file mode 100644
index 00000000..2c38efb4
--- /dev/null
+++ b/noao/onedspec/identify/idgraph.x
@@ -0,0 +1,111 @@
+include <gset.h>
+include <pkg/gtools.h>
+include <smw.h>
+include "identify.h"
+
+# ID_GRAPH -- Graph image vector in which features are to be identified.
+
+procedure id_graph (id, gtype)
+
+pointer id # ID pointer
+int gtype # Graph type
+
+begin
+ switch (gtype) {
+ case 1:
+ call id_graph1 (id)
+ case 2:
+ call id_graph2 (id)
+ default:
+ call id_graph1 (id)
+ }
+end
+
+
+procedure id_graph1 (id)
+
+pointer id # ID pointer
+
+int i, n
+real xmin, xmax, ymin, ymax, dy, gt_getr()
+pointer sh, x, y
+
+begin
+ sh = ID_SH(id)
+ call malloc (x, SN(sh), TY_REAL)
+ y = SY(sh)
+ n = SN(sh)
+
+ call achtdr (FITDATA(id,1), Memr[x], n)
+
+ call gclear (ID_GP(id))
+ xmin = min (Memr[x], Memr[x+n-1])
+ xmax = max (Memr[x], Memr[x+n-1])
+ ymin = gt_getr (ID_GT(id), GTXMIN)
+ ymax = gt_getr (ID_GT(id), GTXMAX)
+ if ((!IS_INDEF(ymin) && xmax<ymin) || (!IS_INDEF(ymax) && xmin>ymax)) {
+ call gt_setr (ID_GT(id), GTXMIN, INDEF)
+ call gt_setr (ID_GT(id), GTXMAX, INDEF)
+ }
+ call alimr (Memr[y], n, ymin, ymax)
+ dy = ymax - ymin
+ call gswind (ID_GP(id), xmin, xmax, ymin - .2 * dy, ymax + .2 * dy)
+ call gt_swind (ID_GP(id), ID_GT(id))
+ call gt_labax (ID_GP(id), ID_GT(id))
+ call gt_plot (ID_GP(id), ID_GT(id), Memr[x], Memr[y], n)
+
+ do i = 1, ID_NFEATURES(id)
+ call id_mark (id, i)
+
+ call mfree (x, TY_REAL)
+end
+
+
+# ID_GRAPH2 -- Make review graph for current feature.
+
+procedure id_graph2 (id)
+
+pointer id # ID pointer
+
+int i, j, k, n
+real xmin, xmax, ymin, ymax, dy
+pointer sh, x, y
+
+begin
+ sh = ID_SH(id)
+ call malloc (x, SN(sh), TY_REAL)
+ y = SY(sh)
+ n = SN(sh)
+
+ call achtdr (FITDATA(id,1), Memr[x], n)
+
+ xmin = real (FIT(id,ID_CURRENT(id))) - ID_ZWIDTH(id) / 2.
+ xmax = real (FIT(id,ID_CURRENT(id))) + ID_ZWIDTH(id) / 2.
+
+ i = 0
+ do k = 1, n {
+ if ((Memr[x+k-1] < xmin) || (Memr[x+k-1] > xmax))
+ next
+ if (i == 0)
+ i = k
+ j = k
+ }
+ k = j - i + 1
+
+ call alimr (Memr[y+i-1], k, ymin, ymax)
+ dy = ymax - ymin
+
+ call gclear (ID_GP(id))
+ call gswind (ID_GP(id), xmin, xmax, ymin - .2 * dy, ymax + .2 * dy)
+# if (ID_GT(id) != NULL) {
+# call gseti (ID_GP(id), G_XTRAN, GT_XTRAN(ID_GT(id)))
+# call gseti (ID_GP(id), G_YTRAN, GT_YTRAN(ID_GT(id)))
+# }
+ call gt_labax (ID_GP(id), ID_GT(id))
+ call gt_plot (ID_GP(id), ID_GT(id), Memr[x], Memr[y], n)
+
+ do i = 1, ID_NFEATURES(id)
+ call id_mark (id, i)
+
+ call mfree (x, TY_REAL)
+end
diff --git a/noao/onedspec/identify/ididentify.x b/noao/onedspec/identify/ididentify.x
new file mode 100644
index 00000000..1b13643c
--- /dev/null
+++ b/noao/onedspec/identify/ididentify.x
@@ -0,0 +1,631 @@
+include <error.h>
+include <imhdr.h>
+include <gset.h>
+include <smw.h>
+include "identify.h"
+
+define HELP "noao$onedspec/identify/identify.key"
+define ICFITHELP "noao$lib/scr/idicgfit.key"
+define PROMPT "identify options"
+
+define PAN 1 # Pan graph
+define ZOOM 2 # Zoom graph
+
+# ID_IDENTIFY -- Identify features in an image.
+# This is the main interactive loop.
+
+procedure id_identify (id)
+
+pointer id # ID pointer
+
+real wx, wy
+int wcs, key
+char cmd[SZ_LINE]
+
+char newimage[SZ_FNAME]
+int i, j, last, all, prfeature, nfeatures1, npeaks, newline[2]
+bool answer
+double pix, fit, user, shift, pix_shift, z_shift
+pointer peaks, label, aid, stp, sid
+
+bool clgetb(), aid_autoid()
+pointer gopen(), id_getap(), sthead(), stnext()
+int clgcur(), scan(), nscan(), id_peaks(), errcode(), strncmp
+double id_center(), fit_to_pix(), id_fitpt(), id_shift(), id_rms()
+errchk id_gdata(), id_graph(), id_dbread(), xt_mk1d()
+
+define newim_ 10
+define newkey_ 20
+define beep_ 99
+
+begin
+newim_
+ # Open the image and return if there is an error.
+ iferr (call id_map (id)) {
+ call erract (EA_WARN)
+ return
+ }
+
+ # Get the image data and return if there is an error.
+ iferr (call id_gdata (id)) {
+ call erract (EA_WARN)
+ return
+ }
+
+ # Get the database entry for the image if it exists.
+ iferr {
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1), NO, YES)
+ ID_NEWDBENTRY(id) = NO
+ } then
+ if ((ID_NFEATURES(id) > 0) || (ID_CV(id) != NULL))
+ ID_NEWDBENTRY(id) = YES
+
+ # Set the coordinate information.
+ iferr (call id_fitdata (id))
+ ;
+
+ # Set fitting limits.
+ call ic_putr (ID_IC(id), "xmin", real (PIXDATA(id,1)))
+ call ic_putr (ID_IC(id), "xmax", real (PIXDATA(id,ID_NPTS(id))))
+ call ic_pstr (ID_IC(id), "help", ICFITHELP)
+
+ # Open graphics.
+ call clgstr ("graphics", newimage, SZ_FNAME)
+ ID_GP(id) = gopen (newimage, NEW_FILE, STDGRAPH)
+
+ # Initialize.
+ ID_GTYPE(id) = PAN
+ all = 0
+ last = ID_CURRENT(id)
+ newimage[1] = EOS
+ newline[1] = ID_LINE(id,1)
+ newline[2] = ID_LINE(id,2)
+ ID_REFIT(id) = NO
+ ID_NEWFEATURES(id) = NO
+ ID_NEWCV(id) = NO
+ wy = INDEF
+ key = 'r'
+
+ repeat {
+ prfeature = YES
+ if (all != 0)
+ all = mod (all + 1, 3)
+
+ switch (key) {
+ case '?': # Print help
+ call gpagefile (ID_GP(id), HELP, PROMPT)
+ case ':': # Process colon commands
+ if (cmd[1] == '/')
+ call gt_colon (cmd, ID_GP(id), ID_GT(id), ID_NEWGRAPH(id))
+ else
+ call id_colon (id, cmd, newimage, prfeature)
+ case ' ': # Go to current feature
+ case '.': # Go to nearest feature
+ if (ID_NFEATURES(id) == 0)
+ goto beep_
+ call id_nearest (id, double (wx))
+ case '-': # Go to previous feature
+ if (ID_CURRENT(id) == 1)
+ goto beep_
+ ID_CURRENT(id) = ID_CURRENT(id) - 1
+ case '+', 'n': # Go to next feature
+ if (ID_CURRENT(id) == ID_NFEATURES(id))
+ goto beep_
+ ID_CURRENT(id) = ID_CURRENT(id) + 1
+ case 'a': # Set all flag for next key
+ all = 1
+ case 'b': # Autoidentify
+ call aid_init (aid, "aidpars")
+ call aid_sets (aid, "crval", "CL crval")
+ call aid_sets (aid, "cdelt", "CL cdelt")
+ if (aid_autoid (id, aid)) {
+ ID_NEWCV(id) = YES
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ } else {
+ prfeature = 0
+ call printf ("No solution found\n")
+ }
+ call aid_free (aid)
+ case 'c': # Recenter features
+ if (all != 0) {
+ for (i = 1; i <= ID_NFEATURES(id); i = i + 1) {
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, i)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ FWIDTH(id,i) = ID_FWIDTH(id)
+ PIX(id,i) = id_center (id, PIX(id,i), FWIDTH(id,i),
+ FTYPE(id,i))
+ if (!IS_INDEFD (PIX(id,i))) {
+ FIT(id,i) = id_fitpt (id, PIX(id,i))
+ call id_mark (id, i)
+ } else {
+ call id_delete (id, i)
+ i = i - 1
+ }
+ }
+ ID_NEWFEATURES(id) = YES
+ } else {
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call id_nearest (id, double (wx))
+ pix = PIX(id,ID_CURRENT(id))
+ pix = id_center (id, pix, ID_FWIDTH(id),
+ FTYPE(id,ID_CURRENT(id)))
+ if (!IS_INDEFD (pix)) {
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ PIX(id,ID_CURRENT(id)) = pix
+ FWIDTH(id,ID_CURRENT(id)) = ID_FWIDTH(id)
+ FIT(id,ID_CURRENT(id)) = id_fitpt (id, pix)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_mark (id, ID_CURRENT(id))
+ ID_NEWFEATURES(id) = YES
+ } else {
+ call printf ("Centering failed\n")
+ prfeature = NO
+ }
+ }
+ case 'd': # Delete features
+ if (all != 0) {
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ } else {
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call id_nearest (id, double (wx))
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_delete (id, ID_CURRENT(id))
+ ID_CURRENT(id) = min (ID_NFEATURES(id), ID_CURRENT(id))
+ last = 0
+ }
+ case 'e': # Find features from line list with no fitting
+ call id_linelist (id)
+ if (ID_NEWFEATURES(id) == YES)
+ ID_NEWGRAPH(id) = YES
+ case 'f': # Fit dispersion function
+ call id_dofit (id, YES)
+ case 'g': # Fit shift
+ call id_doshift (id, YES)
+ prfeature = NO
+ case 'i': # Initialize
+ call dcvfree (ID_CV(id))
+ ID_SHIFT(id) = 0.
+ ID_NEWCV(id) = YES
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ case 'j': # Go to previous line
+ newline[1] = ID_LINE(id,1) - ID_NSUM(id,1)
+ if (newline[1] < 1) {
+ newline[1] = newline[1] + ID_MAXLINE(id,1)
+ newline[2] = ID_LINE(id,2) - ID_NSUM(id,2)
+ if (newline[2] < 1)
+ newline[2] = newline[2] + ID_MAXLINE(id,2)
+ }
+ case 'k': # Go to next line
+ newline[1] = ID_LINE(id,1) + ID_NSUM(id,1)
+ if (newline[1] > ID_MAXLINE(id,1)) {
+ newline[1] = newline[1] - ID_MAXLINE(id,1)
+ newline[2] = ID_LINE(id,2) + ID_NSUM(id,2)
+ if (newline[2] > ID_MAXLINE(id,2))
+ newline[2] = newline[2] - ID_MAXLINE(id,2)
+ }
+ case 'l': # Find features from line list
+ if (ID_NFEATURES(id) >= 2)
+ call id_dofit (id, NO)
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata(id))
+ ;
+ call id_fitfeatures(id)
+ ID_NEWCV(id) = NO
+ }
+ call id_linelist (id)
+ if (ID_NEWFEATURES(id) == YES)
+ ID_REFIT(id) = YES
+ case 'm': # Mark new feature
+ fit = wx
+ pix = fit_to_pix (id, fit)
+ pix = id_center (id, pix, ID_FWIDTH(id), ID_FTYPE(id))
+ if (IS_INDEFD (pix)) {
+ prfeature = NO
+ call printf ("Center not found: check cursor position")
+ if (ID_THRESHOLD(id) > 0.)
+ call printf (" and threshold value")
+ goto beep_
+ }
+ fit = id_fitpt (id, pix)
+ user = fit
+ call id_newfeature (id, pix, fit, user, 1.0D0, ID_FWIDTH(id),
+ ID_FTYPE(id), NULL)
+ USER(id,ID_CURRENT(id)) = INDEFD
+ call id_match (id, FIT(id,ID_CURRENT(id)),
+ USER(id,ID_CURRENT(id)),
+ Memi[ID_LABEL(id)+ID_CURRENT(id)-1], ID_MATCH(id))
+ call id_mark (id, ID_CURRENT(id))
+ call printf ("%10.2f %10.8g (%10.8g %s): ")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ label = Memi[ID_LABEL(id)+ID_CURRENT(id)-1]
+ if (label != NULL)
+ call pargstr (Memc[label])
+ else
+ call pargstr ("")
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ call gargwrd (cmd, SZ_LINE)
+ i = nscan()
+ if (i > 0) {
+ USER(id,ID_CURRENT(id)) = user
+ call id_match (id, user, USER(id,ID_CURRENT(id)),
+ Memi[ID_LABEL(id)+ID_CURRENT(id)-1], ID_MATCH(id))
+ }
+ if (i > 1) {
+ call reset_scan ()
+ call gargd (user)
+ call gargstr (cmd, SZ_LINE)
+ call id_label (cmd, Memi[ID_LABEL(id)+ID_CURRENT(id)-1])
+ }
+ }
+ case 'o': # Go to a specified line
+ call printf ("Line/Column/Band (%d %d): ")
+ call pargi (ID_LINE(id,1))
+ call pargi (ID_LINE(id,2))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargi (j)
+ if (nscan() == 1) {
+ if (j < 1 || j > ID_MAXLINE(id,1))
+ goto beep_
+ newline[1] = j
+ call gargi (j)
+ if (nscan() == 2) {
+ if (j < 1 || j > ID_MAXLINE(id,2))
+ goto beep_
+ newline[2] = j
+ }
+ }
+ }
+ case 'p': # Switch to pan mode
+ if (ID_GTYPE(id) != PAN) {
+ ID_GTYPE(id) = PAN
+ ID_NEWGRAPH(id) = YES
+ }
+ case 'q': # Exit loop
+ break
+ case 'r': # Redraw the graph
+ ID_NEWGRAPH(id) = YES
+ case 's', 'x': # Shift or correlate features
+ # Get coordinate shift.
+ switch (key) {
+ case 's':
+ call printf ("User coordinate (%10.8g): ")
+ call pargr (wx)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ if (nscan() == 1)
+ shift = wx - user
+ } else
+ shift = 0.
+ case 'x':
+ shift = id_shift (id, -1D0, -0.05D0)
+ if (IS_INDEFD(shift)) {
+ call printf ("No solution found\n")
+ goto beep_
+ }
+ }
+
+ ID_NEWFEATURES(id) = YES
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ prfeature = NO
+
+ if (ID_NFEATURES(id) < 1) {
+ call printf ("User coordinate shift=%5f\n")
+ call pargd (shift)
+ ID_SHIFT(id) = ID_SHIFT(id) + shift
+ goto newkey_
+ }
+
+ # Recenter features.
+ pix_shift = 0.
+ z_shift = 0.
+ nfeatures1 = ID_NFEATURES(id)
+
+ j = 0.
+ do i = 1, ID_NFEATURES(id) {
+ pix = fit_to_pix (id, FIT(id,i) + shift)
+ pix = id_center (id, pix, FWIDTH(id,i), FTYPE(id,i))
+ if (IS_INDEFD (pix)) {
+ if (ID_CURRENT(id) == i)
+ ID_CURRENT(id) = i + 1
+ next
+ }
+ fit = id_fitpt (id, pix)
+
+ pix_shift = pix_shift + pix - PIX(id,i)
+ if (FIT(id,i) != 0.)
+ z_shift = z_shift + (fit - FIT(id,i)) / FIT(id,i)
+
+ j = j + 1
+ PIX(id,j) = pix
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ if (ID_CURRENT(id) == i)
+ ID_CURRENT(id) = j
+ }
+ if (j != ID_NFEATURES(id)) {
+ ID_NFEATURES(id) = j
+ ID_CURRENT(id) = min (ID_CURRENT(id), ID_NFEATURES(id))
+ }
+
+ if (ID_NFEATURES(id) < 1) {
+ call printf ("User coordinate shift=%5f")
+ call pargd (shift)
+ call printf (", No features found during recentering\n")
+ ID_SHIFT(id) = ID_SHIFT(id) + shift
+ goto newkey_
+ }
+
+ # Adjust shift.
+ pix = ID_SHIFT(id)
+ call id_doshift (id, NO)
+ call id_fitfeatures (id)
+
+ # Print results.
+ call printf ("Recentered=%d/%d")
+ call pargi (ID_NFEATURES(id))
+ call pargi (nfeatures1)
+ call printf (
+ ", pixel shift=%.2f, user shift=%5f, z=%7.3g, rms=%5g\n")
+ call pargd (pix_shift / ID_NFEATURES(id))
+ call pargd (pix - ID_SHIFT(id))
+ call pargd (z_shift / ID_NFEATURES(id))
+ call pargd (id_rms(id))
+ case 't': # Move the current feature
+ if (ID_CURRENT(id) < 1)
+ goto beep_
+ pix = fit_to_pix (id, double (wx))
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ PIX(id,ID_CURRENT(id)) = pix
+ FIT(id,ID_CURRENT(id)) = id_fitpt (id, pix)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_mark (id, ID_CURRENT(id))
+ ID_NEWFEATURES(id) = YES
+ case 'u': # Set user coordinate
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call printf ("%10.2f %10.8g (%10.8g %s): ")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ label = Memi[ID_LABEL(id)+ID_CURRENT(id)-1]
+ if (label != NULL)
+ call pargstr (Memc[label])
+ else
+ call pargstr ("")
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ call gargwrd (cmd, SZ_LINE)
+ i = nscan()
+ if (i > 0) {
+ USER(id,ID_CURRENT(id)) = user
+ ID_NEWFEATURES(id) = YES
+ }
+ if (i > 1) {
+ call reset_scan ()
+ call gargd (user)
+ call gargstr (cmd, SZ_LINE)
+ call id_label (cmd, Memi[ID_LABEL(id)+ID_CURRENT(id)-1])
+ }
+ }
+ case 'v': # Modify weight
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call printf ("Weight (%d): ")
+ call pargd (WTS(id,ID_CURRENT(id)))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargi (i)
+ if (nscan() > 0) {
+ WTS(id,ID_CURRENT(id)) = i
+ ID_NEWFEATURES(id) = YES
+ }
+ }
+ case 'w': # Window graph
+ call gt_window (ID_GT(id), ID_GP(id), "cursor", ID_NEWGRAPH(id))
+ case 'y': # Find peaks
+ call malloc (peaks, ID_NPTS(id), TY_REAL)
+ npeaks = id_peaks (id, IMDATA(id,1), Memr[peaks], ID_NPTS(id),
+ 0., int (ID_MINSEP(id)), 0, ID_MAXFEATURES(id), 0., false)
+ for (j = 1; j <= ID_NFEATURES(id); j = j + 1) {
+ for (i = 1; i <= npeaks; i = i + 1) {
+ if (!IS_INDEF (Memr[peaks+i-1])) {
+ pix = Memr[peaks+i-1]
+ if (abs (pix - PIX(id,j)) < ID_MINSEP(id))
+ Memr[peaks+i-1] = INDEF
+ }
+ }
+ }
+ for (i = 1; i <= npeaks; i = i + 1) {
+ if (IS_INDEF(Memr[peaks+i-1]))
+ next
+ pix = Memr[peaks+i-1]
+ pix = id_center (id, pix, ID_FWIDTH(id), ID_FTYPE(id))
+ if (IS_INDEFD (pix))
+ next
+ fit = id_fitpt (id, pix)
+ user = INDEFD
+ call id_match (id, fit, user, label, ID_MATCH(id))
+ call id_newfeature (id, pix, fit, user, 1.0D0,
+ ID_FWIDTH(id), ID_FTYPE(id), label)
+ call id_mark (id, ID_CURRENT(id))
+ }
+ call mfree (peaks, TY_REAL)
+ case 'z': # Go to zoom mode
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ if (ID_GTYPE(id) == PAN)
+ ID_NEWGRAPH(id) = YES
+ ID_GTYPE(id) = ZOOM
+ call id_nearest (id, double (wx))
+ case 'I':
+ call fatal (0, "Interrupt")
+ default:
+beep_ call printf ("\007")
+ }
+
+newkey_
+ # Set update flag if anything has changed.
+ if ((ID_NEWFEATURES(id) == YES) || (ID_NEWCV(id) == YES))
+ ID_NEWDBENTRY(id) = YES
+
+ # If a new image exit loop, update database, and start over.
+ if (newimage[1] != EOS)
+ break
+
+ # If a new line, save features and set new line.
+ if (newline[1] != ID_LINE(id,1) || newline[2] != ID_LINE(id,2)) {
+ call id_saveap (id)
+ ID_LINE(id,1) = newline[1]
+ ID_LINE(id,2) = newline[2]
+ call id_gdata (id)
+ if (id_getap (id) == NULL) {
+ iferr {
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1),
+ NO, NO)
+ ID_NEWDBENTRY(id) = NO
+ ID_NEWFEATURES(id) = NO
+ } then
+ if ((ID_NFEATURES(id) > 0) || (ID_CV(id) != NULL))
+ ID_NEWDBENTRY(id) = YES
+ }
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ wy = INDEF
+ }
+
+ # Refit dispersion function
+ if (ID_REFIT(id) == YES) {
+ call id_dofit (id, NO)
+ ID_REFIT(id) = NO
+ }
+
+ # If there is a new dispersion solution evaluate the coordinates
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata (id))
+ ;
+ call id_fitfeatures (id)
+ ID_NEWCV(id) = NO
+ }
+
+ # Draw new graph in zoom mode if current feature has changed.
+ if ((ID_GTYPE(id) == ZOOM) && (last != ID_CURRENT(id)))
+ ID_NEWGRAPH(id) = YES
+
+ # Draw new graph.
+ if (ID_NEWGRAPH(id) == YES) {
+ call id_graph (id, ID_GTYPE(id))
+ ID_NEWGRAPH(id) = NO
+ }
+
+ # Set cursor and print status of current feature (unless canceled).
+ if (ID_CURRENT(id) > 0) {
+ if (IS_INDEF (wy)) {
+ i = max (1, min (ID_NPTS(id), int (PIX(id,ID_CURRENT(id)))))
+ wy = IMDATA(id,i)
+ }
+
+ call gscur (ID_GP(id), real (FIT(id,ID_CURRENT(id))), wy)
+ if (errcode() == OK && prfeature == YES) {
+ call printf ("%10.2f %10.8g %10.8g %s\n")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ if (Memi[ID_LABEL(id)+ID_CURRENT(id)-1] != NULL)
+ call pargstr (
+ Memc[Memi[ID_LABEL(id)+ID_CURRENT(id)-1]])
+ else
+ call pargstr ("")
+ }
+ }
+
+ # Print delayed error message
+ if (errcode() != OK)
+ call erract (EA_WARN)
+
+ last = ID_CURRENT(id)
+ } until (clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+ call gclose (ID_GP(id))
+
+ # Warn user that feature data is newer than database entry.
+ if (ID_NEWDBENTRY(id) == YES)
+ answer = true
+ else {
+ answer = false
+ stp = ID_STP(id)
+ for (sid=sthead(stp); sid!=NULL; sid=stnext(stp,sid)) {
+ if (strncmp (ID_SAVEID(sid), "aperture", 8) != 0)
+ next
+ if (ID_NEWDBENTRY(sid) == YES) {
+ answer = true
+ break
+ }
+ }
+ }
+ if (answer) {
+ if (!clgetb ("autowrite")) {
+ call printf ("Write feature data to the database (yes)? ")
+ call flush (STDOUT)
+ if (scan() != EOF)
+ call gargb (answer)
+ }
+ if (answer) {
+ newline[1] = ID_LINE(id,1)
+ newline[2] = ID_LINE(id,2)
+ if (ID_NEWDBENTRY(id) == YES)
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ stp = ID_STP(id)
+ for (sid=sthead(stp); sid!=NULL; sid=stnext(stp, sid)) {
+ if (strncmp (ID_SAVEID(sid), "aperture", 8) != 0)
+ next
+ if (ID_NEWDBENTRY(sid) == YES &&
+ (ID_LINE(sid,1) != newline[1] ||
+ ID_LINE(sid,2) != newline[2])) {
+ call id_gid (id, sid)
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ }
+ }
+ }
+ }
+
+ call flush (STDOUT)
+
+ # Free image data.
+ call mfree (ID_PIXDATA(id), TY_DOUBLE)
+ call mfree (ID_FITDATA(id), TY_DOUBLE)
+ call id_free1 (id)
+
+ call smw_close (MW(ID_SH(id)))
+ call imunmap (IM(ID_SH(id)))
+ call shdr_close (ID_SH(id))
+
+ # If a new image was requested with colon command start over.
+ if (newimage[1] != EOS) {
+ call strcpy (newimage, ID_IMAGE(id), ID_LENSTRING)
+ goto newim_
+ }
+end
diff --git a/noao/onedspec/identify/idinit.x b/noao/onedspec/identify/idinit.x
new file mode 100644
index 00000000..128f0cc0
--- /dev/null
+++ b/noao/onedspec/identify/idinit.x
@@ -0,0 +1,368 @@
+include <gset.h>
+include <math/curfit.h>
+include "identify.h"
+
+# ID_INIT -- Allocate identify structure
+
+procedure id_init (id)
+
+pointer id #O ID pointer
+
+pointer stopen()
+errchk stopen
+
+begin
+ call calloc (id, ID_LENSTRUCT, TY_STRUCT)
+
+ ID_NALLOC(id) = 20
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ ID_DT(id) = NULL
+ ID_STP(id) = stopen ("identify", 100, 10*ID_LENSTRUCT, 10*SZ_LINE)
+
+ if (ID_NALLOC(id) > 0) {
+ call malloc (ID_PIX(id), ID_NALLOC(id), TY_DOUBLE)
+ call malloc (ID_FIT(id), ID_NALLOC(id), TY_DOUBLE)
+ call malloc (ID_USER(id), ID_NALLOC(id), TY_DOUBLE)
+ call malloc (ID_WTS(id), ID_NALLOC(id), TY_DOUBLE)
+ call malloc (ID_FWIDTHS(id), ID_NALLOC(id), TY_REAL)
+ call malloc (ID_FTYPES(id), ID_NALLOC(id), TY_INT)
+ call calloc (ID_LABEL(id), ID_NALLOC(id), TY_POINTER)
+ }
+end
+
+
+# ID_FREE -- Free identify structure.
+
+procedure id_free (id)
+
+pointer id #I ID pointer
+
+int i
+pointer ptr
+
+begin
+ if (id == NULL)
+ return
+
+ call id_free1 (id)
+
+ call mfree (ID_APS(id), TY_INT)
+
+ ptr = ID_LABEL(id)
+ do i = 1, ID_NFEATURES(id) {
+ call mfree (Memi[ptr], TY_CHAR)
+ ptr = ptr + 1
+ }
+
+ call mfree (ID_PIX(id), TY_DOUBLE)
+ call mfree (ID_FIT(id), TY_DOUBLE)
+ call mfree (ID_USER(id), TY_DOUBLE)
+ call mfree (ID_WTS(id), TY_DOUBLE)
+ call mfree (ID_FWIDTHS(id), TY_REAL)
+ call mfree (ID_FTYPES(id), TY_INT)
+ call mfree (ID_LABEL(id), TY_POINTER)
+
+ if (ID_DT(id) != NULL)
+ call dtunmap (ID_DT(id))
+ call id_unmapll (id)
+ call stclose (ID_STP(id))
+ call gt_free (ID_GT(id))
+ call dcvfree (ID_CV(id))
+ call ic_closed (ID_IC(id))
+ if (ID_UN(id) != NULL)
+ call un_close (ID_UN(id))
+
+ call mfree (id, TY_STRUCT)
+end
+
+
+# ID_FREE1 -- Free saved identify structures.
+
+procedure id_free1 (id)
+
+pointer id # ID pointer
+
+int i
+pointer stp, sid, ptr, sthead(), stnext(), stopen()
+
+begin
+ stp = ID_STP(id)
+ for (sid = sthead(stp); sid != NULL; sid = stnext (stp, sid)) {
+ ptr = ID_LABEL(sid)
+ do i = 1, ID_NFEATURES(sid) {
+ call mfree (Memi[ptr], TY_CHAR)
+ ptr = ptr + 1
+ }
+
+ call mfree (ID_PIX(sid), TY_DOUBLE)
+ call mfree (ID_FIT(sid), TY_DOUBLE)
+ call mfree (ID_USER(sid), TY_DOUBLE)
+ call mfree (ID_WTS(sid), TY_DOUBLE)
+ call mfree (ID_FWIDTHS(sid), TY_REAL)
+ call mfree (ID_FTYPES(sid), TY_INT)
+ call mfree (ID_LABEL(sid), TY_POINTER)
+ if (ID_CV(sid) != NULL)
+ call dcvfree (ID_CV(sid))
+ if (ID_IC(sid) != NULL)
+ call ic_closed (ID_IC(sid))
+ }
+ if (sthead(stp) != NULL) {
+ call stclose (stp)
+ ID_STP(id) = stopen ("identify", 100, 10*ID_LENSTRUCT, 10*SZ_LINE)
+ }
+end
+
+
+# ID_SAVEID -- Save identify information by key.
+
+procedure id_saveid (id, key)
+
+pointer id #I IDENTIFY structure
+char key[ARB] #I Key to use in saving information
+
+pointer sid, stfind(), stenter()
+
+begin
+ sid = stfind (ID_STP(id), key)
+ if (sid == NULL) {
+ sid = stenter (ID_STP(id), key, ID_LENSTRUCT)
+ call aclri (Memi[sid], ID_LENSTRUCT)
+ }
+ call strcpy (key, ID_SAVEID(id), ID_LENSTRING)
+ call id_sid (id, sid)
+end
+
+
+# ID_GETID -- Get saved identify information by key.
+# Return NULL if not found.
+
+pointer procedure id_getid (id, key)
+
+pointer id #I IDENTIFY structure
+char key[ARB] #I Key to use in saving information
+
+int sid, stfind()
+
+begin
+ sid = stfind (ID_STP(id), key)
+ if (sid != NULL)
+ call id_gid (id, sid)
+
+ return (sid)
+end
+
+
+# ID_SAVEAP -- Save identify information by aperture.
+
+procedure id_saveap (id)
+
+pointer id # IDENTIFY structure
+
+begin
+ call sprintf (ID_SAVEID(id), ID_LENSTRING, "aperture %d %d")
+ call pargi (ID_AP(id,1))
+ call pargi (ID_AP(id,2))
+ call id_saveid (id, ID_SAVEID(id))
+end
+
+
+# ID_GETAP -- Get saved identify information by aperture.
+# Return NULL if not found.
+
+pointer procedure id_getap (id)
+
+pointer id # IDENTIFY structure
+
+int sid, stfind()
+
+begin
+ call sprintf (ID_SAVEID(id), ID_LENSTRING, "aperture %d %d")
+ call pargi (ID_AP(id,1))
+ call pargi (ID_AP(id,2))
+
+ # Check if saved.
+ sid = stfind (ID_STP(id), ID_SAVEID(id))
+ if (sid != NULL)
+ call id_gid (id, sid)
+
+ return (sid)
+end
+
+
+# ID_SID -- Save parts of IDENTIFY structure.
+
+procedure id_sid (id, sid)
+
+pointer id #I IDENTIFY structure
+pointer sid #I IDENTIFY save structure
+
+int i, j, dcvstati(), strlen()
+pointer sp, coeffs, ptr1, ptr2
+
+begin
+ if (sid == NULL)
+ return
+
+ # Allocate or reallocate memory for features and copy them.
+ if (ID_NFEATURES(id) > 0) {
+ if (ID_NALLOC(sid) == 0) {
+ call malloc (ID_PIX(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call malloc (ID_FIT(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call malloc (ID_USER(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call malloc (ID_WTS(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call malloc (ID_FWIDTHS(sid), ID_NFEATURES(id), TY_REAL)
+ call malloc (ID_FTYPES(sid), ID_NFEATURES(id), TY_INT)
+ call calloc (ID_LABEL(sid), ID_NFEATURES(id), TY_POINTER)
+ } else if (ID_NALLOC(sid) != ID_NFEATURES(id)) {
+ call realloc (ID_PIX(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call realloc (ID_FIT(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call realloc (ID_USER(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call realloc (ID_WTS(sid), ID_NFEATURES(id), TY_DOUBLE)
+ call realloc (ID_FWIDTHS(sid), ID_NFEATURES(id), TY_REAL)
+ call realloc (ID_FTYPES(sid), ID_NFEATURES(id), TY_INT)
+ call realloc (ID_LABEL(sid), ID_NFEATURES(id), TY_POINTER)
+
+ j = ID_NALLOC(sid)
+ i = ID_NFEATURES(id) - j
+ if (i > 0)
+ call aclri (Memi[ID_LABEL(sid)+j], i)
+ }
+ call amovd (PIX(id,1), PIX(sid,1), ID_NFEATURES(id))
+ call amovd (FIT(id,1), FIT(sid,1), ID_NFEATURES(id))
+ call amovd (USER(id,1), USER(sid,1), ID_NFEATURES(id))
+ call amovd (WTS(id,1), WTS(sid,1), ID_NFEATURES(id))
+ call amovr (FWIDTH(id,1), FWIDTH(sid,1), ID_NFEATURES(id))
+ call amovi (FTYPE(id,1), FTYPE(sid,1), ID_NFEATURES(id))
+
+ ptr1 = ID_LABEL(id)
+ ptr2 = ID_LABEL(sid)
+ do i = 1, ID_NFEATURES(id) {
+ call mfree (Memi[ptr2], TY_CHAR)
+ if (Memi[ptr1] != NULL) {
+ j = strlen (Memc[Memi[ptr1]])
+ call malloc (Memi[ptr2], j, TY_CHAR)
+ call strcpy (Memc[Memi[ptr1]], Memc[Memi[ptr2]], j)
+ }
+ ptr1 = ptr1 + 1
+ ptr2 = ptr2 + 1
+ }
+
+ ID_NALLOC(sid) = ID_NFEATURES(id)
+ }
+
+ # Use a SAVE and RESTORE to copy the CURFIT data.
+ if (ID_CV(sid) != NULL)
+ call dcvfree (ID_CV(sid))
+ if (ID_CV(id) != NULL) {
+ call smark (sp)
+ i = dcvstati (ID_CV(id), CVNSAVE)
+ call salloc (coeffs, i, TY_DOUBLE)
+ call dcvsave (ID_CV(id), Memd[coeffs])
+ call dcvrestore (ID_CV(sid), Memd[coeffs])
+ call sfree (sp)
+
+ if (ID_IC(sid) == NULL)
+ call ic_open (ID_IC(sid))
+ call ic_copy (ID_IC(id), ID_IC(sid))
+ }
+
+ call strcpy (ID_SAVEID(id), ID_SAVEID(sid), ID_LENSTRING)
+ ID_LINE(sid,1) = ID_LINE(id,1)
+ ID_LINE(sid,2) = ID_LINE(id,2)
+ ID_AP(sid,1) = ID_AP(id,1)
+ ID_AP(sid,2) = ID_AP(id,2)
+ ID_NFEATURES(sid) = ID_NFEATURES(id)
+ ID_SHIFT(sid) = ID_SHIFT(id)
+ ID_CURRENT(sid) = ID_CURRENT(id)
+
+ ID_NEWFEATURES(sid) = ID_NEWFEATURES(id)
+ ID_NEWCV(sid) = ID_NEWCV(id)
+ ID_NEWDBENTRY(sid) = ID_NEWDBENTRY(id)
+end
+
+
+# ID_GID -- Restore saved identify information.
+
+procedure id_gid (id, sid)
+
+pointer id #I IDENTIFY structure
+int sid #I IDENTIFY save structure
+
+int i, j, dcvstati(), strlen()
+pointer sp, coeffs, ptr1, ptr2
+
+begin
+ if (sid == NULL)
+ return
+
+ # Reallocate memory for features and copy them.
+ if (ID_NFEATURES(sid) > 0) {
+ if (ID_NALLOC(sid) != ID_NALLOC(id)) {
+ call realloc (ID_PIX(id), ID_NALLOC(sid), TY_DOUBLE)
+ call realloc (ID_FIT(id), ID_NALLOC(sid), TY_DOUBLE)
+ call realloc (ID_USER(id), ID_NALLOC(sid), TY_DOUBLE)
+ call realloc (ID_WTS(id), ID_NALLOC(sid), TY_DOUBLE)
+ call realloc (ID_FWIDTHS(id), ID_NALLOC(sid), TY_REAL)
+ call realloc (ID_FTYPES(id), ID_NALLOC(sid), TY_INT)
+ call realloc (ID_LABEL(id), ID_NALLOC(sid), TY_POINTER)
+
+ j = ID_NALLOC(id)
+ i = ID_NALLOC(sid) - j
+ if (i > 0)
+ call aclri (Memi[ID_LABEL(id)+j], i)
+ }
+ call amovd (PIX(sid,1), PIX(id,1), ID_NFEATURES(sid))
+ call amovd (FIT(sid,1), FIT(id,1), ID_NFEATURES(sid))
+ call amovd (USER(sid,1), USER(id,1), ID_NFEATURES(sid))
+ call amovd (WTS(sid,1), WTS(id,1), ID_NFEATURES(sid))
+ call amovr (FWIDTH(sid,1), FWIDTH(id,1), ID_NFEATURES(sid))
+ call amovi (FTYPE(sid,1), FTYPE(id,1), ID_NFEATURES(sid))
+
+ ptr1 = ID_LABEL(sid)
+ ptr2 = ID_LABEL(id)
+ do i = 1, ID_NFEATURES(sid) {
+ call mfree (Memi[ptr2], TY_CHAR)
+ if (Memi[ptr1] != NULL) {
+ j = strlen (Memc[Memi[ptr1]])
+ call malloc (Memi[ptr2], j, TY_CHAR)
+ call strcpy (Memc[Memi[ptr1]], Memc[Memi[ptr2]], j)
+ }
+ ptr1 = ptr1 + 1
+ ptr2 = ptr2 + 1
+ }
+
+ ID_NALLOC(id) = ID_NALLOC(sid)
+ ID_NFEATURES(id) = ID_NFEATURES(sid)
+ ID_NEWFEATURES(id) = ID_NEWFEATURES(sid)
+ ID_CURRENT(id) = ID_CURRENT(sid)
+ ID_NEWDBENTRY(id) = ID_NEWDBENTRY(sid)
+ }
+
+ # Use a SAVE and RESTORE to copy the CURFIT data.
+ ID_SHIFT(id) = ID_SHIFT(sid)
+ if (ID_CV(sid) != NULL) {
+ if (ID_CV(id) != NULL)
+ call dcvfree (ID_CV(id))
+ call smark (sp)
+ i = dcvstati (ID_CV(sid), CVNSAVE)
+ call salloc (coeffs, i, TY_DOUBLE)
+ call dcvsave (ID_CV(sid), Memd[coeffs])
+ call dcvrestore (ID_CV(id), Memd[coeffs])
+ call sfree (sp)
+
+ call ic_copy (ID_IC(sid), ID_IC(id))
+
+ ID_NEWCV(id) = ID_NEWCV(sid)
+ ID_NEWDBENTRY(id) = ID_NEWDBENTRY(sid)
+
+ call id_fitdata (id)
+ call id_fitfeatures (id)
+ }
+
+ call strcpy (ID_SAVEID(sid), ID_SAVEID(id), ID_LENSTRING)
+ ID_LINE(id,1) = ID_LINE(sid,1)
+ ID_LINE(id,2) = ID_LINE(sid,2)
+ ID_AP(id,1) = ID_AP(sid,1)
+ ID_AP(id,2) = ID_AP(sid,2)
+end
diff --git a/noao/onedspec/identify/idlabel.x b/noao/onedspec/identify/idlabel.x
new file mode 100644
index 00000000..cb5fa439
--- /dev/null
+++ b/noao/onedspec/identify/idlabel.x
@@ -0,0 +1,30 @@
+define SKIP ($1==' '||$1=='\t'||$1=='"'||$1=='\'')
+
+# ID_LABEL -- Set label
+
+procedure id_label (str, label)
+
+char str[ARB] # String to be set
+pointer label # Label pointer to be set
+
+int i, j, strlen()
+pointer cp
+
+begin
+ call mfree (label, TY_CHAR)
+
+ for (i=1; str[i]!=EOS && SKIP(str[i]); i=i+1)
+ ;
+ for (j=strlen(str); j>=i && SKIP(str[j]); j=j-1)
+ ;
+
+ if (i <= j) {
+ call malloc (label, j-i+1, TY_CHAR)
+ cp = label
+ for (; i<=j; i=i+1) {
+ Memc[cp] = str[i]
+ cp = cp + 1
+ }
+ Memc[cp] = EOS
+ }
+end
diff --git a/noao/onedspec/identify/idlinelist.x b/noao/onedspec/identify/idlinelist.x
new file mode 100644
index 00000000..d7772a40
--- /dev/null
+++ b/noao/onedspec/identify/idlinelist.x
@@ -0,0 +1,385 @@
+include <error.h>
+include <mach.h>
+include <units.h>
+include "identify.h"
+
+# ID_MAPLL -- Read the line list into memory.
+# Convert to desired units.
+
+procedure id_mapll (id)
+
+pointer id # Identify structure
+
+int i, j, fd, nalloc, nlines
+pointer ll, lll, ill
+pointer sp, str, units
+double value
+
+bool streq(), fp_equald()
+int open(), fscan(), nscan(), nowhite(), id_compare()
+pointer un_open()
+errchk open, fscan, malloc, realloc, un_open
+extern id_compare()
+
+begin
+ call id_unmapll (id)
+
+ if (nowhite (ID_COORDLIST(id), ID_COORDLIST(id), ID_LENSTRING) == 0)
+ return
+ iferr (fd = open (ID_COORDLIST(id), READ_ONLY, TEXT_FILE)) {
+ call erract (EA_WARN)
+ return
+ }
+
+ ID_COORDSPEC(id) = EOS
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (units, SZ_LINE, TY_CHAR)
+ call strcpy ("Angstroms", Memc[units], SZ_LINE)
+ nalloc = 0
+ nlines = 0
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() != 1)
+ next
+ if (Memc[str] == '#') {
+ call gargwrd (Memc[str], SZ_LINE)
+ call strlwr (Memc[str])
+ if (streq (Memc[str], "spectrum"))
+ call gargwrd (ID_COORDSPEC(id), ID_LENSTRING)
+ if (streq (Memc[str], "units")) {
+ call gargstr (Memc[units], SZ_LINE)
+ call xt_stripwhite (Memc[units])
+ }
+ next
+ }
+ call reset_scan ()
+
+ call gargd (value)
+ if (nscan() != 1)
+ next
+
+ if (nalloc == 0) {
+ nalloc = 100
+ call malloc (ll, nalloc, TY_DOUBLE)
+ call calloc (lll, nalloc, TY_POINTER)
+ } else if (nlines == nalloc) {
+ nalloc = nalloc + 100
+ call realloc (ll, nalloc, TY_DOUBLE)
+ call realloc (lll, nalloc, TY_POINTER)
+ call aclri (Memi[lll+nalloc-100], 100)
+ }
+
+ Memd[ll+nlines] = value
+ call gargstr (Memc[str], SZ_LINE)
+ call id_label (Memc[str], Memi[lll+nlines])
+
+ nlines = nlines + 1
+ }
+ call close (fd)
+
+ # Sort the lines, eliminate identical lines, and convert units.
+ if (nlines > 0) {
+ call malloc (ID_LL(id), nlines + 1, TY_DOUBLE)
+ call malloc (ID_LLL(id), nlines + 1, TY_POINTER)
+
+ call malloc (ill, nlines, TY_INT)
+ do i = 0, nlines-1
+ Memi[ill+i] = i
+ call gqsort (Memi[ill], nlines, id_compare, ll)
+
+ Memd[ID_LL(id)] = Memd[ll+Memi[ill]]
+ Memi[ID_LLL(id)] = Memi[lll+Memi[ill]]
+ j = 1
+ do i = 1, nlines-1 {
+ if (fp_equald (Memd[ll+Memi[ill+i]], Memd[ID_LL(id)+j-1]))
+ next
+ Memd[ID_LL(id)+j] = Memd[ll+Memi[ill+i]]
+ Memi[ID_LLL(id)+j] = Memi[lll+Memi[ill+i]]
+ j = j + 1
+ }
+ Memd[ID_LL(id)+j] = INDEFD
+ ID_NLL(id) = j
+
+ call mfree (ll, TY_DOUBLE)
+ call mfree (lll, TY_POINTER)
+ call mfree (ill, TY_INT)
+
+ if (ID_UN(id) == NULL && Memc[units] != EOS)
+ ID_UN(id) = un_open (Memc[units])
+ call id_unitsll (id, Memc[units])
+ }
+
+ call sfree (sp)
+end
+
+
+# ID_UNMAPLL -- Unmap the linelist.
+
+procedure id_unmapll (id)
+
+pointer id # Identify structure
+
+pointer lll
+
+begin
+ if (ID_LL(id) == NULL)
+ return
+
+ do lll = ID_LLL(id), ID_LLL(id)+ID_NLL(id)-1
+ call mfree (Memi[lll], TY_CHAR)
+
+ call mfree (ID_LL(id), TY_DOUBLE)
+ call mfree (ID_LLL(id), TY_POINTER)
+end
+
+
+# ID_UNITSLL -- Change the line list units from the input units to the
+# units given by ID_UN. This may involve reversing the order of the list.
+
+procedure id_unitsll (id, units)
+
+pointer id # Identify structure
+char units[ARB] # Input units
+
+int i, nll
+double value
+pointer un, ll, lll, llend, lllend, un_open()
+bool un_compare()
+errchk un_open
+
+begin
+ if (ID_LL(id) == NULL)
+ return
+ if (ID_NLL(id) < 1)
+ return
+ if (units[1] == EOS || ID_UN(id) == NULL)
+ return
+ if (UN_CLASS(ID_UN(id)) == UN_UNKNOWN)
+ return
+
+ un = un_open (units)
+ if (un_compare (un, ID_UN(id))) {
+ call un_close (un)
+ return
+ }
+
+ ll = ID_LL(id)
+ lll = ID_LLL(id)
+ nll = ID_NLL(id)
+ call un_ctrand (un, ID_UN(id), Memd[ll], Memd[ll], nll)
+ call un_close (un)
+
+ if (Memd[ll] > Memd[ll+nll-1]) {
+ llend = ll + nll - 1
+ lllend = lll + nll - 1
+ do i = 0, nll / 2 - 1 {
+ value = Memd[ll+i]
+ Memd[ll+i] = Memd[llend-i]
+ Memd[llend-i] = value
+ un = Memi[lll+i]
+ Memi[lll+i] = Memi[lllend-i]
+ Memi[lllend-i] = un
+ }
+ }
+end
+
+
+
+# ID_MATCH -- Match current feature against a line list.
+#
+# This is extremely inefficient. It can be greatly improved.
+
+procedure id_match (id, in, out, label, diff)
+
+pointer id # Identify structure
+double in # Coordinate to be matched
+double out # Matched coordinate
+pointer label # Pointer to label
+real diff # Maximum difference
+
+int i, j, nll
+double delta
+pointer ll
+int strlen()
+
+begin
+ call mfree (label, TY_CHAR)
+
+ if (ID_LL(id) == NULL) {
+ out = in
+ return
+ }
+
+ if (diff < 0.)
+ delta = abs (diff * (FITDATA(id,1) - FITDATA(id,ID_NPTS(id))) /
+ (ID_NPTS(id) - 1))
+ else
+ delta = diff
+
+ ll = ID_LL(id)
+ nll = ID_NLL(id)
+ j = max (1, nint (sqrt (real (nll))))
+ for (i = 0; i < nll && in > Memd[ll+i]; i = i + j)
+ ;
+ for (i = max (0, min (i-1, nll-1)); i > 0 && in < Memd[ll+i]; i = i - 1)
+ ;
+
+ ll = ll + i
+ if (i < nll-1) {
+ if (abs (in - Memd[ll]) > abs (in - Memd[ll+1])) {
+ i = i + 1
+ ll = ll + 1
+ }
+ }
+
+ if (abs (in - Memd[ll]) <= delta) {
+ out = Memd[ll]
+ ll = Memi[ID_LLL(id)+i]
+ if (ll != NULL) {
+ call malloc (label, strlen (Memc[ll]), TY_CHAR)
+ call strcpy (Memc[ll], Memc[label], ARB)
+ }
+ }
+end
+
+# ID_LINELIST -- Add features from a line list.
+
+procedure id_linelist (id)
+
+pointer id # Identify structure
+
+int i, nfound, nextpix, lastpix, cursave
+double cd, pix, fit, fit1, fit2, user, peak, minval, diff, diff1
+pointer sp, pixes, fits, users, labels, ll, lll, label
+
+double id_center(), fit_to_pix(), id_fitpt(), id_peak(), smw_c1trand()
+
+int ncandidate, nmatch1, nmatch2
+common /llstat/ ncandidate, nmatch1, nmatch2
+
+begin
+ if (ID_LL(id) == NULL)
+ return
+
+ call smark (sp)
+ call salloc (pixes, ID_MAXFEATURES(id), TY_DOUBLE)
+ call salloc (fits, ID_MAXFEATURES(id), TY_DOUBLE)
+ call salloc (users, ID_MAXFEATURES(id), TY_DOUBLE)
+ call salloc (labels, ID_MAXFEATURES(id), TY_POINTER)
+
+ ncandidate = 0
+ nmatch1 = 0
+ nmatch2 = 0
+ nfound = 0
+ lastpix = 0
+ minval = MAX_REAL
+
+ if (ID_MATCH(id) < 0.)
+ cd = (FITDATA(id,1) - FITDATA(id,ID_NPTS(id))) / (ID_NPTS(id) - 1)
+ else
+ cd = 1
+
+ fit1 = min (FITDATA(id,1), FITDATA(id,ID_NPTS(id)))
+ fit2 = max (FITDATA(id,1), FITDATA(id,ID_NPTS(id)))
+ ll = ID_LL(id)
+ lll = ID_LLL(id)
+ while (!IS_INDEFD(Memd[ll])) {
+ user = Memd[ll]
+ label = Memi[lll]
+ ll = ll + 1
+ lll = lll + 1
+ if (user < fit1)
+ next
+ if (user > fit2)
+ break
+
+ ncandidate = ncandidate + 1
+ pix = id_center (id, fit_to_pix (id, user), ID_FWIDTH(id),
+ ID_FTYPE(id))
+ if (!IS_INDEFD(pix)) {
+ fit = id_fitpt (id, pix)
+ diff = abs ((fit - user) / cd)
+ if (diff > abs (ID_MATCH(id)))
+ next
+
+ nmatch1 = nmatch1 + 1
+ if (lastpix > 0) {
+ if (abs (pix - Memd[pixes+lastpix-1]) < 0.01) {
+ diff1 = abs (Memd[fits+lastpix-1]-Memd[users+lastpix-1])
+ if (diff < diff1) {
+ Memd[pixes+lastpix-1] = pix
+ Memd[fits+lastpix-1] = fit
+ Memd[users+lastpix-1] = user
+ Memi[labels+lastpix-1] = label
+ }
+ next
+ }
+ }
+
+ nmatch2 = nmatch2 + 1
+ peak = abs (id_peak (id, smw_c1trand (ID_PL(id), pix)))
+ if (nfound < ID_MAXFEATURES(id)) {
+ nfound = nfound + 1
+ if (peak < minval) {
+ nextpix = nfound
+ minval = peak
+ }
+ Memd[pixes+nfound-1] = pix
+ Memd[fits+nfound-1] = fit
+ Memd[users+nfound-1] = user
+ Memi[labels+nfound-1] = label
+ lastpix = nfound
+ } else if (peak > minval) {
+ Memd[pixes+nextpix-1] = pix
+ Memd[fits+nextpix-1] = fit
+ Memd[users+nextpix-1] = user
+ Memi[labels+nextpix-1] = label
+ lastpix = nextpix
+
+ minval = MAX_REAL
+ do i = 1, nfound {
+ pix = Memd[pixes+i-1]
+ peak = abs (id_peak (id, smw_c1trand (ID_PL(id), pix)))
+ peak = abs (id_peak (id, pix))
+ if (peak < minval) {
+ nextpix = i
+ minval = peak
+ }
+ }
+ }
+ }
+ }
+
+ do i = 1, nfound {
+ pix = Memd[pixes+i-1]
+ fit = Memd[fits+i-1]
+ user = Memd[users+i-1]
+ label = Memi[labels+i-1]
+ call id_newfeature (id, pix, fit, user, 1.0D0, ID_FWIDTH(id),
+ ID_FTYPE(id), label)
+ if (i == 1)
+ cursave = ID_CURRENT(id)
+ }
+ ID_CURRENT(id) = cursave
+
+ call sfree (sp)
+end
+
+
+# ID_COMPARE - Routine to compare line list coordinates for sorting.
+# Zero indexing is used.
+
+int procedure id_compare (ll, x1, x2)
+
+pointer ll #I Pointer to array of line list coordinates
+int x1, x2 #I Indices to array of line list coordinates
+
+begin
+ if (Memd[ll+x1] < Memd[ll+x2])
+ return (-1)
+ else if (Memd[ll+x1] > Memd[ll+x2])
+ return (1)
+ else
+ return (0)
+end
diff --git a/noao/onedspec/identify/idlog.x b/noao/onedspec/identify/idlog.x
new file mode 100644
index 00000000..d893f671
--- /dev/null
+++ b/noao/onedspec/identify/idlog.x
@@ -0,0 +1,72 @@
+include <time.h>
+include "identify.h"
+
+# ID_LOG -- Write log
+
+procedure id_log (id, file)
+
+pointer id # ID pointer
+char file[ARB] # Log file
+
+char str[SZ_TIME]
+int i, fd, nrms
+double resid, rms
+
+int open()
+long clktime()
+errchk open()
+
+begin
+ if (ID_NFEATURES(id) == 0)
+ return
+
+ fd = open (file, APPEND, TEXT_FILE)
+
+ call cnvtime (clktime (0), str, SZ_TIME)
+ call fprintf (fd, "\n%s\n")
+ call pargstr (str)
+ call fprintf (fd, "Features identified in image %s.\n")
+ call pargstr (ID_IMAGE(id))
+
+ call fprintf (fd, " %8s %10s %10s %10s %6s %2s %s\n")
+ call pargstr ("Pixel")
+ call pargstr ("Fit")
+ call pargstr ("User")
+ call pargstr ("Residual")
+ call pargstr ("Fwidth")
+ call pargstr ("Wt")
+ call pargstr ("Label")
+
+ rms = 0.
+ nrms = 0
+ do i = 1, ID_NFEATURES(id) {
+ call fprintf (fd, "%2d %8.2f %10.8g %10.8g %10.8g %6.2f %2d %s\n")
+ call pargi (i)
+ call pargd (PIX(id,i))
+ call pargd (FIT(id,i))
+ call pargd (USER(id,i))
+ if (IS_INDEFD (USER(id,i)))
+ call pargd (USER(id,i))
+ else {
+ resid = FIT(id,i) - USER(id,i)
+ call pargd (resid)
+ if (WTS(id,i) > 0.) {
+ rms = rms + resid ** 2
+ nrms = nrms + 1
+ }
+ }
+ call pargr (FWIDTH(id,i))
+ call pargd (WTS(id,i))
+ if (Memi[ID_LABEL(id)+i-1] != NULL)
+ call pargstr (Memc[Memi[ID_LABEL(id)+i-1]])
+ else
+ call pargstr ("")
+ }
+
+ if (nrms > 1) {
+ call fprintf (fd, "RMS = %0.8g\n")
+ call pargd (sqrt (rms / nrms))
+ }
+
+ call close (fd)
+end
diff --git a/noao/onedspec/identify/idmap.x b/noao/onedspec/identify/idmap.x
new file mode 100644
index 00000000..c5f113ff
--- /dev/null
+++ b/noao/onedspec/identify/idmap.x
@@ -0,0 +1,375 @@
+include <ctype.h>
+include <imhdr.h>
+include <smw.h>
+include <units.h>
+include "identify.h"
+
+# Sepcial section words.
+define SPECIAL "|first|middle|x|y|z|last|column|line|band|"
+define FIRST 1
+define MIDDLE 2
+define X 3
+define Y 4
+define Z 5
+define LAST 6
+define COLUMN 7
+define LINE 8
+define BAND 9
+
+# ID_MAP -- Map an image for IDENTIFY/REIDENTIFY
+# The image must 1, 2, or 3 dimensional. An image section may be given with
+# the image name or with the CL parameter "section". The CL parameter can
+# have one of the following formats:
+# 1. An IMIO image section
+# 2. [line|column|x|y|z] [#|middle|last] [#|middle|last]
+# 3. [#|middle|last] [#|middle|last] [line|column|x|y|z]
+# where # is a line or column number. The strings may be abbreviated.
+# The task returns and error if it cannot map the image or determine
+# the 1D line or column desired.
+
+procedure id_map (id)
+
+pointer id # IDENTIFY data structure pointer
+
+int i, j, k, l, a, b, c, x1[3], x2[3], xs[3]
+pointer sp, wrd1, wrd2, wrd3, im
+
+int imaccess(), strdic(), ctoi(), nscan()
+pointer immap()
+errchk immap, id_maphdr
+
+begin
+ # Separate the image name and image section and map the full image.
+ call imgsection (ID_IMAGE(id), ID_SECTION(id), ID_LENSTRING)
+ call imgimage (ID_IMAGE(id), ID_IMAGE(id), ID_LENSTRING)
+ im = immap (ID_IMAGE(id), READ_ONLY, 0)
+
+ # If no image section is found use the "section" parameter.
+ if (ID_SECTION(id) == EOS && IM_NDIM(im) > 1) {
+ call clgstr ("section", ID_SECTION(id), ID_LENSTRING)
+ call xt_stripwhite (ID_SECTION(id))
+
+ # If not an image section construct one.
+ if (ID_SECTION(id) != '[') {
+ call smark (sp)
+ call salloc (wrd1, SZ_FNAME, TY_CHAR)
+ call salloc (wrd2, SZ_FNAME, TY_CHAR)
+ call salloc (wrd3, SZ_FNAME, TY_CHAR)
+
+ call sscan (ID_SECTION(id))
+
+ # Parse axis and elements.
+ call gargwrd (Memc[wrd1], SZ_FNAME)
+ call gargwrd (Memc[wrd2], SZ_FNAME)
+ call gargwrd (Memc[wrd3], SZ_FNAME)
+ switch (nscan()) {
+ case 0:
+ a = X
+ b = MIDDLE
+ c = MIDDLE
+ case 1:
+ a = strdic (Memc[wrd1], Memc[wrd1], SZ_FNAME, SPECIAL)
+ b = MIDDLE
+ c = MIDDLE
+ case 2:
+ a = strdic (Memc[wrd1], Memc[wrd1], SZ_FNAME, SPECIAL)
+ if (a >= X)
+ b = strdic (Memc[wrd2], Memc[wrd2], SZ_FNAME, SPECIAL)
+ else {
+ b = a
+ a = strdic (Memc[wrd2], Memc[wrd2], SZ_FNAME, SPECIAL)
+ call strcpy (Memc[wrd1], Memc[wrd2], SZ_FNAME)
+ }
+ c = MIDDLE
+ call strcpy (Memc[wrd2], Memc[wrd3], SZ_FNAME)
+ case 3:
+ a = strdic (Memc[wrd1], Memc[wrd1], SZ_FNAME, SPECIAL)
+ if (a >= X) {
+ b = strdic (Memc[wrd2], Memc[wrd2], SZ_FNAME, SPECIAL)
+ c = strdic (Memc[wrd3], Memc[wrd3], SZ_FNAME, SPECIAL)
+ } else {
+ b = a
+ a = strdic (Memc[wrd2], Memc[wrd2], SZ_FNAME, SPECIAL)
+ if (a >= X) {
+ c = strdic (Memc[wrd3], Memc[wrd3],SZ_FNAME,SPECIAL)
+ call strcpy (Memc[wrd1], Memc[wrd2], SZ_FNAME)
+ } else {
+ c = b
+ b = a
+ a = strdic (Memc[wrd3], Memc[wrd3],SZ_FNAME,SPECIAL)
+ call strcpy (Memc[wrd2], Memc[wrd3], SZ_FNAME)
+ call strcpy (Memc[wrd1], Memc[wrd2], SZ_FNAME)
+ }
+ }
+ }
+
+ switch (a) {
+ case X, LINE:
+ i = 1
+ j = 2
+ k = 3
+ case Y, COLUMN:
+ i = 2
+ j = 1
+ k = 3
+ case Z, BAND:
+ i = 3
+ j = 1
+ k = 2
+ default:
+ call imunmap (im)
+ call error (1,
+ "Error in section specification or non-unique abbreviation")
+ }
+
+ switch (b) {
+ case FIRST:
+ ID_LINE(id,1) = 1
+ case MIDDLE:
+ ID_LINE(id,1) = (1 + IM_LEN(im,j)) / 2
+ case LAST:
+ ID_LINE(id,1) = IM_LEN(im,j)
+ default:
+ l = 1
+ if (ctoi (Memc[wrd2], l, ID_LINE(id,1)) == 0)
+ call error (1, "Error in section specification")
+ }
+
+ switch (c) {
+ case FIRST:
+ ID_LINE(id,2) = 1
+ case MIDDLE:
+ ID_LINE(id,2) = (1 + IM_LEN(im,k)) / 2
+ case LAST:
+ ID_LINE(id,2) = IM_LEN(im,k)
+ default:
+ l = 1
+ if (ctoi (Memc[wrd3], l, ID_LINE(id,2)) == 0)
+ call error (1, "Error in section specification")
+ }
+
+ # Format section.
+ switch (IM_NDIM(im)) {
+ case 2:
+ switch (i) {
+ case 1:
+ call sprintf (ID_SECTION(id), ID_LENSTRING, "[*,%d]")
+ case 2:
+ call sprintf (ID_SECTION(id), ID_LENSTRING, "[%d,*]")
+ default:
+ call error (1, "Error in section specification")
+ }
+ call pargi (ID_LINE(id,1))
+ case 3:
+ switch (i) {
+ case 1:
+ call sprintf (ID_SECTION(id), ID_LENSTRING, "[*,%d,%d]")
+ case 2:
+ call sprintf (ID_SECTION(id), ID_LENSTRING, "[%d,*,%d]")
+ case 3:
+ call sprintf (ID_SECTION(id), ID_LENSTRING, "[%d,%d,*]")
+ }
+ call pargi (ID_LINE(id,1))
+ call pargi (ID_LINE(id,2))
+ case 4:
+ call error (1, "Image dimension greater than 3 not allowed")
+ }
+ }
+ }
+
+ # Parse the image section.
+ x1[1] = 1; x2[1] = IM_LEN(im,1); xs[1] = 1
+ x1[2] = 1; x2[2] = IM_LEN(im,2); xs[2] = 1
+ x1[3] = 1; x2[3] = IM_LEN(im,3); xs[3] = 1
+ call id_section (ID_SECTION(id), x1, x2, xs, 3)
+
+ # Set the axes. The axis to be identified is the longest one.
+ i = 1
+ if (IM_NDIM(im) > 1 && abs (x1[2]-x2[2]) >= abs (x1[i]-x2[i]))
+ i = 2
+ if (IM_NDIM(im) > 2 && abs (x1[3]-x2[3]) >= abs (x1[i]-x2[i]))
+ i = 3
+ if (IM_NDIM(im) > 3)
+ call error (1, "Image dimension greater than 3 not allowed")
+
+ switch (i) {
+ case 1:
+ j = 2
+ k = 3
+ case 2:
+ j = 1
+ k = 3
+ case 3:
+ j = 1
+ k = 2
+ }
+
+ ID_LINE(id,1) = (x1[j] + x2[j]) / 2
+ ID_LINE(id,2) = (x1[k] + x2[k]) / 2
+ ID_MAXLINE(id,1) = IM_LEN(im, j)
+ ID_MAXLINE(id,2) = IM_LEN(im, k)
+ ID_NSUM(id,1) = min (ID_MAXLINE(id,1), ID_NSUM(id,1))
+ ID_NSUM(id,2) = min (ID_MAXLINE(id,2), ID_NSUM(id,2))
+ call smw_daxis (NULL, NULL, i, ID_NSUM(id,1), ID_NSUM(id,2))
+
+ call id_maphdr (id, im)
+
+ # Open the image READ_WRITE if possible in order to add REFSPEC.
+ # This is not done earlier to avoid updating of the WCS.
+
+ call imunmap (im)
+ if (imaccess (ID_IMAGE(id), READ_WRITE) == YES)
+ im = immap (ID_IMAGE(id), READ_WRITE, 0)
+ else
+ im = immap (ID_IMAGE(id), READ_ONLY, 0)
+ call id_noextn (ID_IMAGE(id))
+ IM(ID_SH(id)) = im
+end
+
+
+# ID_MAPHDR -- Map image header.
+
+procedure id_maphdr (id, im)
+
+pointer id # ID pointer
+pointer im # IMIO pointer
+
+int i
+pointer mw, sh, smw_openim(), smw_sctran()
+errchk smw_openim(), shdr_open(), smw_sctran
+
+begin
+ mw = smw_openim (im)
+ if (SMW_TRANS(mw) == YES) {
+ if (SMW_PAXIS(mw,1) == 1)
+ call smw_daxis (mw, im, 2, INDEFI, INDEFI)
+ else
+ call smw_daxis (mw, im, 1, INDEFI, INDEFI)
+ call smw_saxes (mw, NULL, im)
+ }
+ call shdr_open (im, mw, ID_LINE(id,1), ID_LINE(id,2),
+ INDEFI, SHHDR, ID_SH(id))
+ if (ID_UN(id) != NULL)
+ iferr (call shdr_units (ID_SH(id), UN_UNITS(ID_UN(id))))
+ ;
+ sh = ID_SH(id)
+
+ if (SMW_FORMAT(mw) == SMW_MS || SMW_FORMAT(mw) == SMW_ES) {
+ ID_MAXLINE(id,1) = IM_LEN(im,2)
+ ID_MAXLINE(id,2) = IM_LEN(im,3)
+ ID_NSUM(id,1) = 1
+ ID_NSUM(id,2) = 1
+ ID_LINE(id,1) = max (1, min (ID_MAXLINE(id,1), ID_LINE(id,1)))
+ ID_LINE(id,2) = 1
+ call mfree (ID_APS(id), TY_INT)
+ call malloc (ID_APS(id), ID_MAXLINE(id,1), TY_INT)
+ do i = 1, ID_MAXLINE(id,1) {
+ call shdr_open (im, mw, i, 1, INDEFI, SHHDR, sh)
+ Memi[ID_APS(id)+i-1] = AP(sh)
+ }
+ ID_AP(id,1) = Memi[ID_APS(id)+ID_LINE(id,1)-1]
+ ID_AP(id,2) = 1
+ } else {
+ call mfree (ID_APS(id), TY_INT)
+ ID_AP(id,1) = ID_LINE(id,1)
+ ID_AP(id,2) = ID_LINE(id,2)
+ }
+ ID_NPTS(id) = IM_LEN(im, SMW_LAXIS(mw,1))
+
+ # Set logical / physical transformations
+ i = 2 ** (SMW_PAXIS(mw,1) - 1)
+ ID_LP(id) = smw_sctran (mw, "logical", "physical", i)
+ ID_PL(id) = smw_sctran (mw, "physical", "logical", i)
+end
+
+
+# ID_SECTION -- Parse an image section into its elements.
+# 1. The default values must be set by the caller.
+# 2. A null image section is OK.
+# 3. The first nonwhitespace character must be '['.
+# 4. The last interpreted character must be ']'.
+#
+# This procedure should be replaced with an IMIO procedure at some
+# point.
+
+procedure id_section (section, x1, x2, xs, ndim)
+
+char section[ARB] # Image section
+int x1[ndim] # Starting pixel
+int x2[ndim] # Ending pixel
+int xs[ndim] # Step
+int ndim # Number of dimensions
+
+int i, ip, a, b, c, temp, ctoi()
+define error_ 99
+
+begin
+ # Decode the section string.
+ ip = 1
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == '[')
+ ip = ip + 1
+ else if (section[ip] == EOS)
+ return
+ else
+ goto error_
+
+ do i = 1, ndim {
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ']')
+ break
+
+ # Default values
+ a = x1[i]
+ b = x2[i]
+ c = xs[i]
+
+ # Get a:b:c. Allow notation such as "-*:c"
+ # (or even "-:c") where the step is obviously negative.
+
+ if (ctoi (section, ip, temp) > 0) { # a
+ a = temp
+ if (section[ip] == ':') {
+ ip = ip + 1
+ if (ctoi (section, ip, b) == 0) # a:b
+ goto error_
+ } else
+ b = a
+ } else if (section[ip] == '-') { # -*
+ temp = a
+ a = b
+ b = temp
+ ip = ip + 1
+ if (section[ip] == '*')
+ ip = ip + 1
+ } else if (section[ip] == '*') # *
+ ip = ip + 1
+ if (section[ip] == ':') { # ..:step
+ ip = ip + 1
+ if (ctoi (section, ip, c) == 0)
+ goto error_
+ else if (c == 0)
+ goto error_
+ }
+ if (a > b && c > 0)
+ c = -c
+
+ x1[i] = a
+ x2[i] = b
+ xs[i] = c
+
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ',')
+ ip = ip + 1
+ }
+
+ if (section[ip] != ']')
+ goto error_
+
+ return
+error_
+ call error (0, "Error in image section specification")
+end
diff --git a/noao/onedspec/identify/idmark.x b/noao/onedspec/identify/idmark.x
new file mode 100644
index 00000000..ac888c91
--- /dev/null
+++ b/noao/onedspec/identify/idmark.x
@@ -0,0 +1,98 @@
+include <gset.h>
+include <smw.h>
+include "identify.h"
+
+procedure id_mark (id, feature)
+
+pointer id # ID pointer
+int feature
+
+int pix, color, markcolor, gstati()
+real x, y
+real mx, my, x1, x2, y1, y2, tick, gap
+pointer sp, format, label, ptr
+double smw_c1trand()
+
+define TICK .03 # Tick size in NDC
+define GAP .02 # Gap size in NDC
+
+begin
+ call ggwind (ID_GP(id), x1, x2, y1, y2)
+
+ x = FIT(id,feature)
+
+ if ((x < min (x1, x2)) || (x > max (x1, x2)))
+ return
+
+ pix = smw_c1trand (ID_PL(id), PIX(id,feature)) - NP1(ID_SH(id)) + 1
+ pix = max (1, min (pix, ID_NPTS(id)-1))
+
+ call smark (sp)
+ call salloc (format, SZ_LINE, TY_CHAR)
+ call salloc (label, SZ_LINE, TY_CHAR)
+ switch (FTYPE(id,feature)) {
+ case EMISSION:
+ y = max (IMDATA(id,pix), IMDATA(id,pix+1))
+ tick = TICK
+ gap = GAP
+ call strcpy ("u=180;h=c;v=b;s=0.5", Memc[format], SZ_LINE)
+ case ABSORPTION:
+ y = min (IMDATA(id,pix), IMDATA(id,pix+1))
+ tick = -TICK
+ gap = -GAP
+ call strcpy ("u=0;h=c;v=t;s=0.5", Memc[format], SZ_LINE)
+ }
+
+ call gctran (ID_GP(id), x, y, mx, my, 1, 0)
+ call gctran (ID_GP(id), mx, my + gap, x1, y1, 0, 1)
+ call gctran (ID_GP(id), mx, my + gap + tick, x1, y2, 0, 1)
+ color = gstati (ID_GP(id), G_PLCOLOR)
+ markcolor = gstati (ID_GP(id), G_TICKLABELCOLOR)
+ call gseti (ID_GP(id), G_PLCOLOR, markcolor)
+ call gline (ID_GP(id), x1, y1, x1, y2)
+ call gseti (ID_GP(id), G_PLCOLOR, color)
+
+ call gctran (ID_GP(id), mx, my + tick + 2 * gap, x1, y2, 0, 1)
+ color = gstati (ID_GP(id), G_TXCOLOR)
+ call gseti (ID_GP(id), G_TXCOLOR, markcolor)
+ switch (ID_LABELS(id)) {
+ case 2:
+ call sprintf (Memc[label], SZ_LINE, "%d")
+ call pargi (feature)
+ call gtext (ID_GP(id), x1, y2, Memc[label], Memc[format])
+ case 3:
+ call sprintf (Memc[label], SZ_LINE, "%0.2f")
+ call pargd (PIX(id,feature))
+ call gtext (ID_GP(id), x1, y2, Memc[label], Memc[format])
+ case 4:
+ if (!IS_INDEFD (USER(id,feature))) {
+ call sprintf (Memc[label], SZ_LINE, "%0.4f")
+ call pargd (USER(id,feature))
+ call gtext (ID_GP(id), x1, y2, Memc[label], Memc[format])
+ }
+ case 5:
+ label = Memi[ID_LABEL(id)+feature-1]
+ if (label != NULL)
+ call gtext (ID_GP(id), x1, y2, Memc[label], Memc[format])
+ case 6:
+ Memc[label] = EOS
+ ptr = Memi[ID_LABEL(id)+feature-1]
+ if (!IS_INDEFD (USER(id,feature))) {
+ if (ptr != NULL) {
+ call sprintf (Memc[label], SZ_LINE, "%0.4f %s")
+ call pargd (USER(id,feature))
+ call pargstr (Memc[ptr])
+ } else {
+ call sprintf (Memc[label], SZ_LINE, "%0.4f")
+ call pargd (USER(id,feature))
+ }
+ } else if (ptr != NULL)
+ call strcpy (Memc[ptr], Memc[label], SZ_LINE)
+ if (Memc[label] != EOS)
+ call gtext (ID_GP(id), x1, y2, Memc[label], Memc[format])
+ }
+ call gseti (ID_GP(id), G_TXCOLOR, color)
+
+ call sfree (sp)
+ call gflush (ID_GP(id))
+end
diff --git a/noao/onedspec/identify/idnearest.x b/noao/onedspec/identify/idnearest.x
new file mode 100644
index 00000000..41aa4c61
--- /dev/null
+++ b/noao/onedspec/identify/idnearest.x
@@ -0,0 +1,29 @@
+include "identify.h"
+
+# ID_NEAREST -- Find the nearest feature to a given coordinate.
+
+procedure id_nearest (id, fitnear)
+
+pointer id # ID pointer
+double fitnear # Coordinate to find nearest feature
+
+int i
+double delta, delta1
+
+begin
+ if (ID_NFEATURES(id) < 1) {
+ ID_CURRENT(id) = 0
+ return
+ }
+
+ ID_CURRENT(id) = 1
+ delta = abs (FIT(id,1) - fitnear)
+
+ do i = 2, ID_NFEATURES(id) {
+ delta1 = abs (FIT(id,i) - fitnear)
+ if (delta1 < delta) {
+ ID_CURRENT(id) = i
+ delta = delta1
+ }
+ }
+end
diff --git a/noao/onedspec/identify/idnewfeature.x b/noao/onedspec/identify/idnewfeature.x
new file mode 100644
index 00000000..efa489b4
--- /dev/null
+++ b/noao/onedspec/identify/idnewfeature.x
@@ -0,0 +1,87 @@
+include <mach.h>
+include "identify.h"
+
+# ID_NEWFEATURE -- Allocate and initialize memory for a new feature.
+
+procedure id_newfeature (id, pix, fit, user, wt, width, type, label)
+
+pointer id # ID pointer
+double pix # Pixel coordinate
+double fit # Fit coordinate
+double user # User coordinate
+double wt # Feature weight
+real width # Feature width
+int type # Feature type
+pointer label # Pointer to feature label
+
+int i, current, strlen()
+double delta
+
+define NALLOC 20 # Length of additional allocations
+
+begin
+ if (IS_INDEFD (pix))
+ return
+
+ delta = MAX_REAL
+ do i = 1, ID_NFEATURES(id) {
+ if (abs (pix - PIX(id,i)) < delta) {
+ delta = abs (pix - PIX(id,i))
+ current = i
+ }
+ }
+
+ if (delta >= ID_MINSEP(id)) {
+ ID_NFEATURES(id) = ID_NFEATURES(id) + 1
+ if (ID_NALLOC(id) < ID_NFEATURES(id)) {
+ ID_NALLOC(id) = ID_NALLOC(id) + NALLOC
+ call realloc (ID_PIX(id), ID_NALLOC(id), TY_DOUBLE)
+ call realloc (ID_FIT(id), ID_NALLOC(id), TY_DOUBLE)
+ call realloc (ID_USER(id), ID_NALLOC(id), TY_DOUBLE)
+ call realloc (ID_WTS(id), ID_NALLOC(id), TY_DOUBLE)
+ call realloc (ID_FWIDTHS(id), ID_NALLOC(id), TY_REAL)
+ call realloc (ID_FTYPES(id), ID_NALLOC(id), TY_INT)
+ call realloc (ID_LABEL(id), ID_NALLOC(id), TY_POINTER)
+ call aclri (Memi[ID_LABEL(id)+ID_NALLOC(id)-NALLOC], NALLOC)
+ }
+ for (current=ID_NFEATURES(id); (current>1)&&(pix<PIX(id,current-1));
+ current=current-1) {
+ PIX(id,current) = PIX(id,current-1)
+ FIT(id,current) = FIT(id,current-1)
+ USER(id,current) = USER(id,current-1)
+ WTS(id,current) = WTS(id,current-1)
+ FWIDTH(id,current) = FWIDTH(id,current-1)
+ FTYPE(id,current) = FTYPE(id,current-1)
+ Memi[ID_LABEL(id)+current-1] = Memi[ID_LABEL(id)+current-2]
+ }
+ PIX(id,current) = pix
+ FIT(id,current) = fit
+ USER(id,current) = user
+ WTS(id,current) = wt
+ FWIDTH(id,current) = width
+ FTYPE(id,current) = type
+ if (label != NULL) {
+ i = strlen (Memc[label])
+ call malloc (Memi[ID_LABEL(id)+current-1], i, TY_CHAR)
+ call strcpy (Memc[label], Memc[Memi[ID_LABEL(id)+current-1]], i)
+ } else
+ Memi[ID_LABEL(id)+current-1] = NULL
+ ID_NEWFEATURES(id) = YES
+ } else if (abs (fit-user) < abs (FIT(id,current)-USER(id,current))) {
+ PIX(id,current) = pix
+ FIT(id,current) = fit
+ USER(id,current) = user
+ WTS(id,current) = wt
+ FWIDTH(id,current) = width
+ FTYPE(id,current) = type
+ if (label != NULL) {
+ i = strlen (Memc[label])
+ call malloc (Memi[ID_LABEL(id)+current-1], i, TY_CHAR)
+ call strcpy (Memc[label], Memc[Memi[ID_LABEL(id)+current-1]], i)
+ } else
+ Memi[ID_LABEL(id)+current-1] = NULL
+ ID_NEWFEATURES(id) = YES
+ }
+
+ ID_CURRENT(id) = current
+end
diff --git a/noao/onedspec/identify/idnoextn.x b/noao/onedspec/identify/idnoextn.x
new file mode 100644
index 00000000..6c82d778
--- /dev/null
+++ b/noao/onedspec/identify/idnoextn.x
@@ -0,0 +1,11 @@
+# ID_NOEXTN -- Remove standard image extensions.
+
+procedure id_noextn (image)
+
+char image[ARB] # Image name
+
+int strlen()
+
+begin
+ call xt_imroot (image, image, strlen (image))
+end
diff --git a/noao/onedspec/identify/idpeak.x b/noao/onedspec/identify/idpeak.x
new file mode 100644
index 00000000..c3e7559d
--- /dev/null
+++ b/noao/onedspec/identify/idpeak.x
@@ -0,0 +1,95 @@
+include <smw.h>
+include "identify.h"
+
+# ID_PEAK -- Find the peak value above continuum.
+
+double procedure id_peak (id, pix)
+
+pointer id # ID pointer
+double pix # Pixel position
+double peak # Peak value
+
+int c, l, u
+
+begin
+ if (IS_INDEFD(pix))
+ return (INDEFD)
+
+ c = nint (pix)
+ l = max (1, nint (pix - ID_FWIDTH(id)))
+ u = min (ID_NPTS(id), nint (pix + ID_FWIDTH(id)))
+ peak = IMDATA(id,c) - (IMDATA(id,l) + IMDATA(id,u)) / 2.
+
+ return (peak)
+end
+
+
+# ID_PEAKS -- Find peaks in the data. This just calls find_peaks but does
+# the logical to physical pixel conversion.
+
+int procedure id_peaks (id, data, x, npoints, contrast, separation, edge, nmax,
+ threshold, debug)
+
+pointer id #I Identify pointer
+real data[npoints] #I Input data array
+real x[npoints] #O Output peak position array
+int npoints #I Number of data points
+real contrast #I Maximum contrast between strongest and weakest
+int separation #I Minimum separation between peaks
+int edge #I Minimum distance from the edge
+int nmax #I Maximum number of peaks to be returned
+real threshold #I Minimum threshold level for peaks
+bool debug #I Print diagnostic information?
+
+int i, n, np1, find_peaks()
+double smw_c1trand()
+errchk find_peaks
+
+begin
+ # Find the peaks in logical coordinates.
+ n = find_peaks (data, x, npoints, contrast, separation, edge,
+ nmax, threshold, debug)
+
+ # Convert to physical coordinates.
+ np1 = NP1(ID_SH(id)) - 1
+ do i = 1, n
+ x[i] = smw_c1trand (ID_LP(id), double (x[i]+np1))
+
+ return (n)
+end
+
+
+# ID_UPEAKS -- Find uniformly distributed peaks in the data. This just calls
+# find_upeaks but does the logical to physical pixel conversion.
+
+int procedure id_upeaks (id, data, x, npoints, contrast, separation, edge,
+ nmax, nbins, threshold, debug)
+
+pointer id #I Identify pointer
+real data[npoints] #I Input data array
+real x[npoints] #O Output peak position array
+int npoints #I Number of data points
+real contrast #I Maximum contrast between strongest and weakest
+int separation #I Minimum separation between peaks
+int edge #I Minimum distance from the edge
+int nmax #I Maximum number of peaks to be returned
+int nbins #I Number of bins across the data array
+real threshold #I Minimum threshold level for peaks
+bool debug #I Print diagnostic information?
+
+int i, n, np1, find_upeaks()
+double smw_c1trand()
+errchk find_upeaks
+
+begin
+ # Find the peaks in logical coordinates.
+ n = find_upeaks (data, x, npoints, contrast, separation, edge,
+ nmax, nbins, threshold, debug)
+
+ # Convert to physical coordinates.
+ np1 = NP1(ID_SH(id)) - 1
+ do i = 1, n
+ x[i] = smw_c1trand (ID_LP(id), double (x[i]+np1))
+
+ return (n)
+end
diff --git a/noao/onedspec/identify/idrms.x b/noao/onedspec/identify/idrms.x
new file mode 100644
index 00000000..82916f1a
--- /dev/null
+++ b/noao/onedspec/identify/idrms.x
@@ -0,0 +1,28 @@
+include "identify.h"
+
+# ID_RMS -- Compute RMS of fit about the user coordinates
+
+double procedure id_rms (id)
+
+pointer id # ID pointer
+
+int i, nrms
+double rms
+
+begin
+ rms = 0.
+ nrms = 0
+ for (i=1; i<=ID_NFEATURES(id); i=i+1) {
+ if (!IS_INDEFD (USER(id,i)) && WTS(id,i) != 0.) {
+ rms = rms + (FIT(id,i) - USER(id,i)) ** 2
+ nrms = nrms + 1
+ }
+ }
+
+ if (nrms > 0)
+ rms = sqrt (rms / nrms)
+ else
+ rms = INDEFD
+
+ return (rms)
+end
diff --git a/noao/onedspec/identify/idshift.x b/noao/onedspec/identify/idshift.x
new file mode 100644
index 00000000..1aedad69
--- /dev/null
+++ b/noao/onedspec/identify/idshift.x
@@ -0,0 +1,106 @@
+include "identify.h"
+
+define NBIN 10 # Bin parameter for mode determination
+
+# ID_SHIFT1 -- Determine a shift by correlating feature user positions
+# with peaks in the image data.
+
+double procedure id_shift1 (id)
+
+pointer id # ID pointer
+
+int i, j, npeaks, ndiff, id_peaks()
+real d, dmin
+double pix, id_center(), id_fitpt()
+pointer x, y, diff
+errchk malloc, id_peaks
+
+begin
+ # Find the peaks in the image data and center.
+ call malloc (x, ID_NPTS(id), TY_REAL)
+ npeaks = id_peaks (id, IMDATA(id,1), Memr[x], ID_NPTS(id), 0.,
+ int (ID_MINSEP(id)), 0, ID_MAXFEATURES(id), 0., false)
+
+ # Center the peaks and convert to user coordinates.
+ call malloc (y, npeaks, TY_DOUBLE)
+ j = 0
+ do i = 1, npeaks {
+ pix = id_center (id, double(Memr[x+i-1]), ID_FWIDTH(id),
+ ID_FTYPE(id))
+ if (!IS_INDEFD (pix)) {
+ Memd[y+j] = id_fitpt (id, pix)
+ j = j + 1
+ }
+ }
+ npeaks = j
+
+ # Compute differences with feature list.
+ ndiff = npeaks * ID_NFEATURES(id)
+ call malloc (diff, ndiff, TY_REAL)
+ ndiff = 0
+ do i = 1, ID_NFEATURES(id) {
+ do j = 1, npeaks {
+ Memr[diff+ndiff] = Memd[y+j-1] - FIT(id,i)
+ ndiff = ndiff + 1
+ }
+ }
+ call mfree (x, TY_REAL)
+ call mfree (y, TY_DOUBLE)
+
+ # Sort the differences and find the mode.
+ call asrtr (Memr[diff], Memr[diff], ndiff)
+
+ dmin = Memr[diff+ndiff-1] - Memr[diff]
+ do i = 0, ndiff-NBIN-1 {
+ j = i + NBIN
+ d = Memr[diff+j] - Memr[diff+i]
+ if (d < dmin) {
+ dmin = d
+ pix = Memr[diff+i] + d / 2.
+ }
+ }
+ call mfree (diff, TY_REAL)
+
+ return (pix)
+end
+
+
+# ID_SHIFT -- Determine a shift using the AID_SHIFT algorithm. This
+# differs from AID_SHIFT in that the input ID pointer is unchanged
+# (same dispersion function and features) but a shift is computed and
+# returned.
+
+double procedure id_shift (id, crsearch cdsearch)
+
+pointer id #I ID pointer
+double crsearch #I Search range
+double cdsearch #I Search range
+
+int marker
+double shift, asumd()
+pointer new, id_getid()
+errchk aid_shift
+
+begin
+ call stmark (ID_STP(id), marker)
+ call id_saveid (id, "backup")
+
+ # Find the shift.
+ shift = INDEFD
+ iferr {
+ call aid_shift (id, crsearch, cdsearch)
+ call malloc (new, ID_NPTS(id), TY_DOUBLE)
+ call amovd (FITDATA(id,1), Memd[new], ID_NPTS(id))
+ if (id_getid (id, "backup") == NULL)
+ call error (1, "Error getting saved record")
+ call asubd (FITDATA(id,1), Memd[new], Memd[new], ID_NPTS(id))
+ shift = asumd (Memd[new], ID_NPTS(id)) / ID_NPTS(id)
+ call mfree (new, TY_DOUBLE)
+ } then {
+ if (id_getid (id, "backup") == NULL)
+ call error (1, "Error getting saved record")
+ }
+
+ call stfree (ID_STP(id), marker)
+ return (shift)
+end
diff --git a/noao/onedspec/identify/idshow.x b/noao/onedspec/identify/idshow.x
new file mode 100644
index 00000000..16f4d9df
--- /dev/null
+++ b/noao/onedspec/identify/idshow.x
@@ -0,0 +1,79 @@
+include "identify.h"
+
+# ID_SHOW -- Show parameter information.
+
+procedure id_show (id, file)
+
+pointer id # ID pointer
+char file[ARB] # File
+
+char line[SZ_LINE]
+int fd
+
+int open(), ic_geti()
+errchk open()
+
+begin
+ fd = open (file, APPEND, TEXT_FILE)
+
+ call sysid (line, SZ_LINE)
+ call fprintf (fd, "%s\n")
+ call pargstr (line)
+
+ call fprintf (fd, "image %s\n")
+ call pargstr (ID_IMAGE(id))
+ call fprintf (fd, "nsum %d\n")
+ call pargi (ID_NSUM(id,1))
+ switch (ID_FTYPE(id)) {
+ case EMISSION:
+ call fprintf (fd, "ftype emission\n")
+ case ABSORPTION:
+ call fprintf (fd, "ftype absorption\n")
+ }
+ switch (ID_LABELS(id)) {
+ case 2:
+ call fprintf (fd, "labels index\n")
+ case 3:
+ call fprintf (fd, "labels pixel\n")
+ case 4:
+ call fprintf (fd, "labels coords\n")
+ case 5:
+ call fprintf (fd, "labels user\n")
+ case 6:
+ call fprintf (fd, "labels both\n")
+ default:
+ call fprintf (fd, "labels none\n")
+ }
+ call fprintf (fd, "maxfeatures %d\n")
+ call pargi (ID_MAXFEATURES(id))
+ call fprintf (fd, "match %g\n")
+ call pargr (ID_MATCH(id))
+ call fprintf (fd, "zwidth %g\n")
+ call pargr (ID_ZWIDTH(id))
+ call fprintf (fd, "fwidth %g\n")
+ call pargr (ID_FWIDTH(id))
+ call fprintf (fd, "database %s\n")
+ call pargstr (ID_DATABASE(id))
+ call fprintf (fd, "coordlist %s\n")
+ call pargstr (ID_COORDLIST(id))
+ call fprintf (fd, "cradius %g\n")
+ call pargr (ID_CRADIUS(id))
+ call fprintf (fd, "threshold %g\n")
+ call pargr (ID_THRESHOLD(id))
+ call fprintf (fd, "minsep %g\n")
+ call pargr (ID_MINSEP(id))
+ if (ID_CV(id) != NULL) {
+ call fprintf (fd, "function = %s\n")
+ call ic_gstr (ID_IC(id), "function", line, SZ_LINE)
+ call pargstr (line)
+ call fprintf (fd, "order = %d\n")
+ call pargi (ic_geti (ID_IC(id), "order"))
+ call fprintf (fd, "Fit at first pixel = %0.8g\n")
+ call pargd (FITDATA(id,1))
+ call fprintf (fd, "Average fit interval = %0.8g\n")
+ call pargd ((FITDATA(id,ID_NPTS(id))-FITDATA(id,1))/
+ (ID_NPTS(id)-1))
+ }
+
+ call close (fd)
+end
diff --git a/noao/onedspec/identify/mkpkg b/noao/onedspec/identify/mkpkg
new file mode 100644
index 00000000..7b568269
--- /dev/null
+++ b/noao/onedspec/identify/mkpkg
@@ -0,0 +1,48 @@
+# IDENTIFY Task
+
+$checkout libpkg.a ..
+$update libpkg.a
+$checkin libpkg.a ..
+$exit
+
+libpkg.a:
+ @autoid
+
+ $ifeq (USE_GENERIC, yes)
+ $ifolder (peaks.x, peaks.gx)
+ $generic -k peaks.gx -o peaks.x $endif $endif
+
+ idcenter.x identify.h <smw.h>
+ idcolon.x identify.h <error.h> <gset.h> <smw.h>
+ iddb.x identify.h <imset.h> <math/curfit.h> <pkg/dttext.h>\
+ <smw.h> <units.h>
+ iddelete.x identify.h
+ iddofit.x identify.h <units.h>
+ iddoshift.x identify.h
+ idfitdata.x identify.h <pkg/gtools.h> <smw.h> <units.h>\
+ <math/curfit.h>
+ idgdata.x identify.h <imhdr.h> <imio.h> <pkg/gtools.h> <smw.h>\
+ <units.h>
+ idgraph.x identify.h <gset.h> <pkg/gtools.h> <smw.h>
+ ididentify.x identify.h <error.h> <gset.h> <imhdr.h> <smw.h>
+ idinit.x identify.h <gset.h> <math/curfit.h>
+ idlabel.x
+ idlinelist.x identify.h <error.h> <mach.h> <units.h>
+ idlog.x identify.h <time.h>
+ idmap.x identify.h <ctype.h> <imhdr.h> <smw.h> <units.h>
+ idmark.x identify.h <gset.h> <smw.h>
+ idnearest.x identify.h
+ idnewfeature.x identify.h <mach.h>
+ idnoextn.x
+ idpeak.x identify.h <smw.h>
+ idrms.x identify.h
+ idshift.x identify.h
+ idshow.x identify.h
+ peaks.x
+ reidentify.x identify.h <error.h> <gset.h> <imhdr.h>
+ t_autoid.x identify.h <error.h> <fset.h> <gset.h> <pkg/gtools.h>\
+ <smw.h>
+ t_identify.x identify.h <mach.h> <pkg/gtools.h>
+ t_reidentify.x identify.h <error.h> <fset.h> <gset.h> <pkg/gtools.h>\
+ <smw.h>
+ ;
diff --git a/noao/onedspec/identify/peaks.gx b/noao/onedspec/identify/peaks.gx
new file mode 100644
index 00000000..571948c6
--- /dev/null
+++ b/noao/onedspec/identify/peaks.gx
@@ -0,0 +1,578 @@
+# PEAKS -- The following procedures are general numerical functions
+# dealing with finding peaks in a data array.
+#
+# FIND_PEAKS Find the NMAX peaks in the data array.
+# FIND_UPEAKS Find the uniformly distrib. peaks in the data array.
+# FIND_IPEAKS Find all the isolated peaks in the data array.
+# FIND_LOCAL_MAXIMA Find the local maxima in the data array.
+# IS_LOCAL_MAX Test a point to determine if it is a local maximum.
+# FIND_THRESHOLD Find the peaks with positions satisfying threshold
+# and contrast constraints.
+# FIND_ISOLATED Flag peaks which are within separation of a peak
+# with a higher peak value.
+# FIND_NMAX Select up to the nmax highest ranked peaks.
+# FIND_UNMAX Select up to the nmax ranked peaks in bins.
+# COMPARE Compare procedure for sort used in FIND_PEAKS.
+
+
+# FIND_PEAKS -- Find the NMAX peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Accept at most the nmax strongest peaks.
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+$for (r)
+int procedure find_peaks (data, x, npoints, contrast, separation, edge, nmax,
+ threshold, debug)
+
+# Procedure parameters:
+PIXEL data[npoints] # Input data array
+PIXEL x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+int nmax # Maximum number of peaks to be returned
+real threshold # Minimum threshold level for peaks
+bool debug # Print diagnostic information?
+
+int nrank, npeaks, find_nmax()
+pointer rank
+
+begin
+ # Find all isolated peaks and their rank.
+ call find_ipeaks (data, x, npoints, contrast, separation, edge,
+ threshold, rank, nrank, debug)
+
+ # Select the strongest nmax peaks.
+ npeaks = find_nmax (data, x, Memi[rank], nrank, nmax, debug)
+
+ call mfree (rank, TY_INT)
+ return (npeaks)
+end
+
+
+# FIND_UPEAKS -- Find the uniformly distrib. peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Accept at most the nmax uniformly distributed peaks.
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+int procedure find_upeaks (data, x, npoints, contrast, separation, edge,
+ nmax, nbins, threshold, debug)
+
+# Procedure parameters:
+PIXEL data[npoints] # Input data array
+PIXEL x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+int nmax # Maximum number of peaks to be returned
+int nbins # Number of bins across the data array
+real threshold # Minimum threshold level for peaks
+bool debug # Print diagnostic information?
+
+int npts, nrank, npeaks, find_unmax()
+pointer rank
+
+begin
+ npts = npoints
+
+ # Find all isolated peaks and their rank.
+ call find_ipeaks (data, x, npoints, contrast, separation, edge,
+ threshold, rank, nrank, debug)
+
+ # Select the peaks.
+ npeaks = find_unmax (data, npts, x, Memi[rank], nrank, nmax, nbins,
+ debug)
+
+ call mfree (rank, TY_INT)
+ return (npeaks)
+end
+
+
+# FIND_IPEAKS -- Find the all the isolated peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Return a rank array
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+procedure find_ipeaks (data, x, npoints, contrast, separation, edge, threshold,
+ rank, nrank, debug)
+
+# Procedure parameters:
+PIXEL data[npoints] # Input data array
+PIXEL x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+real threshold # Minimum threshold level for peaks
+pointer rank # Rank array
+int nrank # Size of rank array
+bool debug # Print diagnostic information?
+
+int i, j
+int nlmax, nisolated
+pointer sp, y
+
+int find_local_maxima(), find_threshold(), find_isolated()
+int compare()
+
+extern compare()
+
+common /sort/ y
+
+begin
+ # Find the local maxima in data and put column positions in x..
+ nlmax = find_local_maxima (data, x, npoints, debug)
+
+ # Reject local maxima near the edge.
+ if (edge > 0) {
+ j = 0
+ do i = 1, nlmax {
+ if ((x[i] > edge) && (x[i] <= npoints - edge)) {
+ j = j + 1
+ x[j] = x[i]
+ }
+ }
+ nlmax = j
+ }
+
+ # Allocate a working array y.
+ call smark (sp)
+ call salloc (y, npoints, TY_PIXEL)
+
+ # Reject the local maxima which do not satisfy the thresholds.
+ # The array y is set to the peak values of the remaining peaks.
+ nrank = find_threshold (data, x, Mem$t[y], nlmax,
+ contrast, threshold, debug)
+
+ # Rank the peaks by peak value.
+ call malloc (rank, nrank, TY_INT)
+ do i = 1, nrank
+ Memi[rank + i - 1] = i
+ call qsort (Memi[rank], nrank, compare)
+
+ # Reject the weaker peaks within sep of a stronger peak.
+ nisolated = find_isolated (x, Memi[rank], nrank, separation, debug)
+
+ call sfree (sp)
+end
+
+
+# FIND_LOCAL_MAXIMA -- Find the local maxima in the data array.
+#
+# A data array is input and the local maxima positions array is output.
+# The number of local maxima found is returned.
+
+int procedure find_local_maxima (data, x, npoints, debug)
+
+PIXEL data[npoints] # Input data array
+PIXEL x[npoints] # Output local maxima positions array
+int npoints # Number of input points
+bool debug # Print debugging information?
+
+int i, nlmax
+
+bool is_local_max()
+
+begin
+ nlmax = 0
+ do i = 1, npoints {
+ if (is_local_max (i, data, npoints)) {
+ nlmax = nlmax + 1
+ x[nlmax] = i
+ }
+ }
+
+ if (debug) {
+ call printf (" Number of local maxima found = %d.\n")
+ call pargi (nlmax)
+ }
+
+ return (nlmax)
+end
+
+
+# IS_LOCAL_MAX -- Test a point to determine if it is a local maximum.
+#
+# Indefinite points are ignored.
+
+bool procedure is_local_max (index, data, npoints)
+
+# Procedure parameters:
+int index # Index to test for local maximum
+PIXEL data[npoints] # Data values
+int npoints # Number of points in the data vector
+
+int i, j, nright, nleft
+
+begin
+ # INDEF points cannot be local maxima.
+ if (IS_INDEF (data[index]))
+ return (FALSE)
+
+ # Find the left and right indices where data values change and the
+ # number of points with the same value. Ignore INDEF points.
+ nleft = 0
+ for (i = index - 1; i >= 1; i = i - 1) {
+ if (!IS_INDEF (data[i])) {
+ if (data[i] != data[index])
+ break
+ nleft = nleft + 1
+ }
+ }
+ nright = 0
+ for (j = index + 1; j <= npoints; j = j + 1) {
+ if (!IS_INDEF (data[j])) {
+ if (data[j] != data[index])
+ break
+ nright = nright + 1
+ }
+ }
+
+ # Test for failure to be a local maxima
+ if ((i == 0) && (j == npoints+1)) {
+ return (FALSE) # Data is constant
+ } else if (i == 0) {
+ if (data[j] > data[index])
+ return (FALSE) # Data increases to right
+ } else if (j == npoints+1) {
+ if (data[i] > data[index]) # Data increase to left
+ return (FALSE)
+ } else if ((data[i] > data[index]) || (data[j] > data[index])) {
+ return (FALSE) # Not a local maximum
+ } else if (!((nleft - nright == 0) || (nleft - nright == 1))) {
+ return (FALSE) # Not center of plateau
+ }
+
+ # Point is a local maxima
+ return (TRUE)
+end
+
+
+# FIND_THRESHOLD -- Find the peaks with positions satisfying threshold
+# and contrast constraints.
+#
+# The input is the data array, data, and the peak positions array, x.
+# The x array is resorted to the nthreshold peaks satisfying the constraints.
+# The corresponding nthreshold data values are returned the y array.
+# The number of peaks satisfying the constraints (nthreshold) is returned.
+
+int procedure find_threshold (data, x, y, npoints, contrast, threshold, debug)
+
+PIXEL data[ARB] # Input data values
+PIXEL x[npoints] # Input/Output peak positions
+PIXEL y[npoints] # Output peak data values
+int npoints # Number of peaks input
+real contrast # Contrast constraint
+real threshold # Threshold constraint
+bool debug # Print debugging information?
+
+int i, j, nthreshold
+PIXEL minval, maxval, lcut
+
+begin
+ # Set the y array to be the values at the peak positions.
+ do i = 1, npoints {
+ j = x[i]
+ y[i] = data[j]
+ }
+
+ # Determine the min and max values of the peaks.
+ call alim$t (y, npoints, minval, maxval)
+
+ # Set the threshold based on the max of the absolute threshold and the
+ # contrast. Use arlt to set peaks below threshold to INDEF.
+ if (!IS_INDEFR(threshold) || !IS_INDEFR(contrast)) {
+ if (IS_INDEFR(threshold))
+ lcut = PIXEL (contrast * maxval)
+ else if (IS_INDEFR(contrast))
+ lcut = PIXEL (threshold)
+ else
+ lcut = max (PIXEL (threshold), PIXEL (contrast * maxval))
+ call arlt$t (y, npoints, lcut, INDEFR)
+ }
+
+ if (debug) {
+ call printf (" Highest peak value = %g.\n")
+ call parg$t (maxval)
+ call printf (" Peak cutoff threshold = %g.\n")
+ call parg$t (lcut)
+ do i = 1, npoints {
+ if (IS_INDEF (y[i])) {
+ j = x[i]
+ call printf (
+ " Peak at column %d with value %g below threshold.\n")
+ call pargi (j)
+ call parg$t (data[j])
+ }
+ }
+ }
+
+ # Determine the number of acceptable peaks & resort the x and y arrays.
+ nthreshold = 0
+ do i = 1, npoints {
+ if (IS_INDEF (y[i]))
+ next
+ nthreshold = nthreshold + 1
+ x[nthreshold] = x[i]
+ y[nthreshold] = y[i]
+ }
+
+ if (debug) {
+ call printf (" Number of peaks above the threshold = %d.\n")
+ call pargi (nthreshold)
+ }
+
+ return (nthreshold)
+end
+
+# FIND_ISOLATED -- Flag peaks which are within separation of a peak
+# with a higher peak value.
+#
+# The peak positions, x, and their ranks, rank, are input.
+# The rank array contains the indices of the peak positions in order from
+# the highest peak value to the lowest peak value. Starting with
+# highest rank (rank[1]) all peaks of lower rank within separation
+# are marked by setting their positions to INDEF. The number of
+# unflaged peaks is returned.
+
+int procedure find_isolated (x, rank, npoints, separation, debug)
+
+# Procedure parameters:
+PIXEL x[npoints] # Positions of points
+int rank[npoints] # Rank of peaks
+int npoints # Number of peaks
+int separation # Minimum allowed separation
+bool debug # Print diagnostic information
+
+int i, j
+int nisolated
+
+begin
+ # Eliminate close neighbors. The eliminated
+ # peaks are marked by setting their positions to INDEF.
+ nisolated = 0
+ do i = 1, npoints {
+ if (IS_INDEF (x[rank[i]]))
+ next
+ nisolated = nisolated + 1
+ do j = i + 1, npoints {
+ if (IS_INDEF (x[rank[j]]))
+ next
+ if (abs (x[rank[i]] - x[rank[j]]) < separation) {
+ if (debug) {
+ call printf (
+ " Peak at column %d too near peak at column %d.\n")
+ call pargi (int (x[rank[j]]))
+ call pargi (int (x[rank[i]]))
+ }
+ x[rank[j]] = INDEF
+ }
+ }
+ }
+
+ if (debug) {
+ call printf (" Number of peaks separated by %d pixels = %d.\n")
+ call pargi (separation)
+ call pargi (nisolated)
+ }
+
+ # Return number of isolated peaks.
+ return (nisolated)
+end
+
+
+# FIND_NMAX -- Select up to the nmax highest ranked peaks.
+#
+# The data values, data, peak positions, x, and their ranks, rank, are input.
+# The data values are used only in printing debugging information.
+# Peak positions previously eliminated are flaged by the value INDEF.
+# The rank array contains the indices to the peak positions in order from
+# the highest peak value to the lowest peak value.
+# First all but the nmax highest ranked peaks (which have not been previously
+# eliminated) are eliminated by marking their positions with the value INDEF.
+# Then the remaining peaks are resorted to contain only the unflaged
+# peaks and the number of such peaks is returned.
+
+int procedure find_nmax (data, x, rank, npoints, nmax, debug)
+
+PIXEL data[ARB] # Input data values
+PIXEL x[npoints] # Peak positions
+int rank[npoints] # Ranks of peaks
+int npoints # Number of input peaks
+int nmax # Max number of peaks to be selected
+bool debug # Print debugging information?
+
+int i, j, npeaks
+
+begin
+ # Only mark peaks to reject if the number peaks is greater than nmax.
+ if (nmax < npoints) {
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEF (x[rank[i]]))
+ next
+ npeaks = npeaks + 1
+ if (npeaks > nmax) {
+ if (debug) {
+ j = x[rank[i]]
+ call printf (
+ " Reject peak at column %d with rank %d and value %g.\n")
+ call pargi (j)
+ call pargi (i)
+ call parg$t (data[j])
+ }
+ x[rank[i]] = INDEF
+ }
+ }
+ }
+
+ # Eliminate INDEF points and determine the number of spectra found.
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEF (x[i]))
+ next
+ npeaks = npeaks + 1
+ x[npeaks] = x[i]
+ }
+
+ return (npeaks)
+end
+
+
+# FIND_UNMAX -- Select up to the nmax highest ranked peaks in bins.
+#
+# The data values, data, peak positions, x, and their ranks, rank, are input.
+# The data values are used only in printing debugging information.
+# Peak positions previously eliminated are flaged by the value INDEF.
+# The rank array contains the indices to the peak positions in order from
+# the highest peak value to the lowest peak value.
+# First all but the nmax highest ranked peaks (which have not been previously
+# eliminated) are eliminated by marking their positions with the value INDEF.
+# Then the remaining peaks are resorted to contain only the unflaged
+# peaks and the number of such peaks is returned.
+
+int procedure find_unmax (data, npts, x, rank, npoints, nmax, nbins, debug)
+
+PIXEL data[npts] # Input data values
+int npts # Number of input data points
+PIXEL x[npoints] # Peak positions
+int rank[npoints] # Ranks of peaks
+int npoints # Number of input peaks
+int nmax # Max number of peaks to be selected
+int nbins # Number of sample bins
+bool debug # Print debugging information?
+
+int i, j, npeaks, width, x1, x2
+PIXEL a
+
+begin
+ # Only mark peaks to reject if the number peaks is greater than nmax.
+ if (nmax < npoints) {
+
+ # Set up circular bins and select highest peak in each bin
+ # until the desired number of peaks is selected.
+
+ width = min (npts-1, nint ((npts-1) / (nbins-.5)))
+ x2 = 1
+ npeaks = 0
+ repeat {
+ x1 = x2
+ x2 = mod (x1 + width, npts) + 1
+ j = 0
+ do i = 1, npoints {
+ a = x[rank[i]]
+ if (IS_INDEF (a) || a < 0) {
+ j = j + 1
+ next
+ }
+ if (x1 < x2) {
+ if (a >= x1 && a <= x2) {
+ x[rank[i]] = -a
+ npeaks = npeaks + 1
+ break
+ }
+ } else {
+ if (a <= x2 || a >= x1) {
+ x[rank[i]] = -a
+ npeaks = npeaks + 1
+ break
+ }
+ }
+ }
+ } until (npeaks >= nmax || j == npoints)
+
+ # Now eliminate all unused peaks and reset the selected peaks.
+ do i = 1, npoints {
+ if (!IS_INDEF (x[i]) && x[i] < 1)
+ x[i] = -x[i]
+ else
+ x[i] = INDEF
+ }
+ }
+
+ # Eliminate INDEF points and determine the number of peaks found.
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEF (x[i]))
+ next
+ npeaks = npeaks + 1
+ x[npeaks] = x[i]
+ }
+
+ return (npeaks)
+end
+
+
+# COMPARE -- Compare procedure for sort used in FIND_PEAKS.
+# Larger values are indexed first. INDEF values are indexed last.
+
+int procedure compare (index1, index2)
+
+# Procedure parameters:
+int index1 # Comparison index
+int index2 # Comparison index
+
+pointer y
+
+common /sort/ y
+
+begin
+ # INDEF points are considered to be smallest possible values.
+ if (IS_INDEF (Mem$t[y - 1 + index1]))
+ return (1)
+ else if (IS_INDEF (Mem$t[y - 1 + index2]))
+ return (-1)
+ else if (Mem$t[y - 1 + index1] < Mem$t[y - 1 + index2])
+ return (1)
+ else if (Mem$t[y - 1 + index1] > Mem$t[y - 1 + index2])
+ return (-1)
+ else
+ return (0)
+end
+$endfor
diff --git a/noao/onedspec/identify/peaks.x b/noao/onedspec/identify/peaks.x
new file mode 100644
index 00000000..0ebda9f7
--- /dev/null
+++ b/noao/onedspec/identify/peaks.x
@@ -0,0 +1,578 @@
+# PEAKS -- The following procedures are general numerical functions
+# dealing with finding peaks in a data array.
+#
+# FIND_PEAKS Find the NMAX peaks in the data array.
+# FIND_UPEAKS Find the uniformly distrib. peaks in the data array.
+# FIND_IPEAKS Find all the isolated peaks in the data array.
+# FIND_LOCAL_MAXIMA Find the local maxima in the data array.
+# IS_LOCAL_MAX Test a point to determine if it is a local maximum.
+# FIND_THRESHOLD Find the peaks with positions satisfying threshold
+# and contrast constraints.
+# FIND_ISOLATED Flag peaks which are within separation of a peak
+# with a higher peak value.
+# FIND_NMAX Select up to the nmax highest ranked peaks.
+# FIND_UNMAX Select up to the nmax ranked peaks in bins.
+# COMPARE Compare procedure for sort used in FIND_PEAKS.
+
+
+# FIND_PEAKS -- Find the NMAX peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Accept at most the nmax strongest peaks.
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+
+int procedure find_peaks (data, x, npoints, contrast, separation, edge, nmax,
+ threshold, debug)
+
+# Procedure parameters:
+real data[npoints] # Input data array
+real x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+int nmax # Maximum number of peaks to be returned
+real threshold # Minimum threshold level for peaks
+bool debug # Print diagnostic information?
+
+int nrank, npeaks, find_nmax()
+pointer rank
+
+begin
+ # Find all isolated peaks and their rank.
+ call find_ipeaks (data, x, npoints, contrast, separation, edge,
+ threshold, rank, nrank, debug)
+
+ # Select the strongest nmax peaks.
+ npeaks = find_nmax (data, x, Memi[rank], nrank, nmax, debug)
+
+ call mfree (rank, TY_INT)
+ return (npeaks)
+end
+
+
+# FIND_UPEAKS -- Find the uniformly distrib. peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Accept at most the nmax uniformly distributed peaks.
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+int procedure find_upeaks (data, x, npoints, contrast, separation, edge,
+ nmax, nbins, threshold, debug)
+
+# Procedure parameters:
+real data[npoints] # Input data array
+real x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+int nmax # Maximum number of peaks to be returned
+int nbins # Number of bins across the data array
+real threshold # Minimum threshold level for peaks
+bool debug # Print diagnostic information?
+
+int npts, nrank, npeaks, find_unmax()
+pointer rank
+
+begin
+ npts = npoints
+
+ # Find all isolated peaks and their rank.
+ call find_ipeaks (data, x, npoints, contrast, separation, edge,
+ threshold, rank, nrank, debug)
+
+ # Select the peaks.
+ npeaks = find_unmax (data, npts, x, Memi[rank], nrank, nmax, nbins,
+ debug)
+
+ call mfree (rank, TY_INT)
+ return (npeaks)
+end
+
+
+# FIND_IPEAKS -- Find the all the isolated peaks in the data array.
+#
+# The peaks are found using the following algorithm:
+#
+# 1. Find the local maxima.
+# 2. Reject peaks below the threshold.
+# 3. Determine the ranks of the remaining peaks.
+# 4. Flag weaker peaks within separation of a stronger peak.
+# 5. Return a rank array
+#
+# Indefinite points are ignored. The peak positions are returned in the
+# array x.
+
+procedure find_ipeaks (data, x, npoints, contrast, separation, edge, threshold,
+ rank, nrank, debug)
+
+# Procedure parameters:
+real data[npoints] # Input data array
+real x[npoints] # Output peak position array
+int npoints # Number of data points
+real contrast # Maximum contrast between strongest and weakest
+int separation # Minimum separation between peaks
+int edge # Minimum distance from the edge
+real threshold # Minimum threshold level for peaks
+pointer rank # Rank array
+int nrank # Size of rank array
+bool debug # Print diagnostic information?
+
+int i, j
+int nlmax, nisolated
+pointer sp, y
+
+int find_local_maxima(), find_threshold(), find_isolated()
+int compare()
+
+extern compare()
+
+common /sort/ y
+
+begin
+ # Find the local maxima in data and put column positions in x..
+ nlmax = find_local_maxima (data, x, npoints, debug)
+
+ # Reject local maxima near the edge.
+ if (edge > 0) {
+ j = 0
+ do i = 1, nlmax {
+ if ((x[i] > edge) && (x[i] <= npoints - edge)) {
+ j = j + 1
+ x[j] = x[i]
+ }
+ }
+ nlmax = j
+ }
+
+ # Allocate a working array y.
+ call smark (sp)
+ call salloc (y, npoints, TY_REAL)
+
+ # Reject the local maxima which do not satisfy the thresholds.
+ # The array y is set to the peak values of the remaining peaks.
+ nrank = find_threshold (data, x, Memr[y], nlmax,
+ contrast, threshold, debug)
+
+ # Rank the peaks by peak value.
+ call malloc (rank, nrank, TY_INT)
+ do i = 1, nrank
+ Memi[rank + i - 1] = i
+ call qsort (Memi[rank], nrank, compare)
+
+ # Reject the weaker peaks within sep of a stronger peak.
+ nisolated = find_isolated (x, Memi[rank], nrank, separation, debug)
+
+ call sfree (sp)
+end
+
+
+# FIND_LOCAL_MAXIMA -- Find the local maxima in the data array.
+#
+# A data array is input and the local maxima positions array is output.
+# The number of local maxima found is returned.
+
+int procedure find_local_maxima (data, x, npoints, debug)
+
+real data[npoints] # Input data array
+real x[npoints] # Output local maxima positions array
+int npoints # Number of input points
+bool debug # Print debugging information?
+
+int i, nlmax
+
+bool is_local_max()
+
+begin
+ nlmax = 0
+ do i = 1, npoints {
+ if (is_local_max (i, data, npoints)) {
+ nlmax = nlmax + 1
+ x[nlmax] = i
+ }
+ }
+
+ if (debug) {
+ call printf (" Number of local maxima found = %d.\n")
+ call pargi (nlmax)
+ }
+
+ return (nlmax)
+end
+
+
+# IS_LOCAL_MAX -- Test a point to determine if it is a local maximum.
+#
+# Indefinite points are ignored.
+
+bool procedure is_local_max (index, data, npoints)
+
+# Procedure parameters:
+int index # Index to test for local maximum
+real data[npoints] # Data values
+int npoints # Number of points in the data vector
+
+int i, j, nright, nleft
+
+begin
+ # INDEF points cannot be local maxima.
+ if (IS_INDEFR (data[index]))
+ return (FALSE)
+
+ # Find the left and right indices where data values change and the
+ # number of points with the same value. Ignore INDEF points.
+ nleft = 0
+ for (i = index - 1; i >= 1; i = i - 1) {
+ if (!IS_INDEFR (data[i])) {
+ if (data[i] != data[index])
+ break
+ nleft = nleft + 1
+ }
+ }
+ nright = 0
+ for (j = index + 1; j <= npoints; j = j + 1) {
+ if (!IS_INDEFR (data[j])) {
+ if (data[j] != data[index])
+ break
+ nright = nright + 1
+ }
+ }
+
+ # Test for failure to be a local maxima
+ if ((i == 0) && (j == npoints+1)) {
+ return (FALSE) # Data is constant
+ } else if (i == 0) {
+ if (data[j] > data[index])
+ return (FALSE) # Data increases to right
+ } else if (j == npoints+1) {
+ if (data[i] > data[index]) # Data increase to left
+ return (FALSE)
+ } else if ((data[i] > data[index]) || (data[j] > data[index])) {
+ return (FALSE) # Not a local maximum
+ } else if (!((nleft - nright == 0) || (nleft - nright == 1))) {
+ return (FALSE) # Not center of plateau
+ }
+
+ # Point is a local maxima
+ return (TRUE)
+end
+
+
+# FIND_THRESHOLD -- Find the peaks with positions satisfying threshold
+# and contrast constraints.
+#
+# The input is the data array, data, and the peak positions array, x.
+# The x array is resorted to the nthreshold peaks satisfying the constraints.
+# The corresponding nthreshold data values are returned the y array.
+# The number of peaks satisfying the constraints (nthreshold) is returned.
+
+int procedure find_threshold (data, x, y, npoints, contrast, threshold, debug)
+
+real data[ARB] # Input data values
+real x[npoints] # Input/Output peak positions
+real y[npoints] # Output peak data values
+int npoints # Number of peaks input
+real contrast # Contrast constraint
+real threshold # Threshold constraint
+bool debug # Print debugging information?
+
+int i, j, nthreshold
+real minval, maxval, lcut
+
+begin
+ # Set the y array to be the values at the peak positions.
+ do i = 1, npoints {
+ j = x[i]
+ y[i] = data[j]
+ }
+
+ # Determine the min and max values of the peaks.
+ call alimr (y, npoints, minval, maxval)
+
+ # Set the threshold based on the max of the absolute threshold and the
+ # contrast. Use arlt to set peaks below threshold to INDEF.
+ if (!IS_INDEFR(threshold) || !IS_INDEFR(contrast)) {
+ if (IS_INDEFR(threshold))
+ lcut = real (contrast * maxval)
+ else if (IS_INDEFR(contrast))
+ lcut = real (threshold)
+ else
+ lcut = max (real (threshold), real (contrast * maxval))
+ call arltr (y, npoints, lcut, INDEFR)
+ }
+
+ if (debug) {
+ call printf (" Highest peak value = %g.\n")
+ call pargr (maxval)
+ call printf (" Peak cutoff threshold = %g.\n")
+ call pargr (lcut)
+ do i = 1, npoints {
+ if (IS_INDEFR (y[i])) {
+ j = x[i]
+ call printf (
+ " Peak at column %d with value %g below threshold.\n")
+ call pargi (j)
+ call pargr (data[j])
+ }
+ }
+ }
+
+ # Determine the number of acceptable peaks & resort the x and y arrays.
+ nthreshold = 0
+ do i = 1, npoints {
+ if (IS_INDEFR (y[i]))
+ next
+ nthreshold = nthreshold + 1
+ x[nthreshold] = x[i]
+ y[nthreshold] = y[i]
+ }
+
+ if (debug) {
+ call printf (" Number of peaks above the threshold = %d.\n")
+ call pargi (nthreshold)
+ }
+
+ return (nthreshold)
+end
+
+# FIND_ISOLATED -- Flag peaks which are within separation of a peak
+# with a higher peak value.
+#
+# The peak positions, x, and their ranks, rank, are input.
+# The rank array contains the indices of the peak positions in order from
+# the highest peak value to the lowest peak value. Starting with
+# highest rank (rank[1]) all peaks of lower rank within separation
+# are marked by setting their positions to INDEF. The number of
+# unflaged peaks is returned.
+
+int procedure find_isolated (x, rank, npoints, separation, debug)
+
+# Procedure parameters:
+real x[npoints] # Positions of points
+int rank[npoints] # Rank of peaks
+int npoints # Number of peaks
+int separation # Minimum allowed separation
+bool debug # Print diagnostic information
+
+int i, j
+int nisolated
+
+begin
+ # Eliminate close neighbors. The eliminated
+ # peaks are marked by setting their positions to INDEF.
+ nisolated = 0
+ do i = 1, npoints {
+ if (IS_INDEFR (x[rank[i]]))
+ next
+ nisolated = nisolated + 1
+ do j = i + 1, npoints {
+ if (IS_INDEFR (x[rank[j]]))
+ next
+ if (abs (x[rank[i]] - x[rank[j]]) < separation) {
+ if (debug) {
+ call printf (
+ " Peak at column %d too near peak at column %d.\n")
+ call pargi (int (x[rank[j]]))
+ call pargi (int (x[rank[i]]))
+ }
+ x[rank[j]] = INDEFR
+ }
+ }
+ }
+
+ if (debug) {
+ call printf (" Number of peaks separated by %d pixels = %d.\n")
+ call pargi (separation)
+ call pargi (nisolated)
+ }
+
+ # Return number of isolated peaks.
+ return (nisolated)
+end
+
+
+# FIND_NMAX -- Select up to the nmax highest ranked peaks.
+#
+# The data values, data, peak positions, x, and their ranks, rank, are input.
+# The data values are used only in printing debugging information.
+# Peak positions previously eliminated are flaged by the value INDEF.
+# The rank array contains the indices to the peak positions in order from
+# the highest peak value to the lowest peak value.
+# First all but the nmax highest ranked peaks (which have not been previously
+# eliminated) are eliminated by marking their positions with the value INDEF.
+# Then the remaining peaks are resorted to contain only the unflaged
+# peaks and the number of such peaks is returned.
+
+int procedure find_nmax (data, x, rank, npoints, nmax, debug)
+
+real data[ARB] # Input data values
+real x[npoints] # Peak positions
+int rank[npoints] # Ranks of peaks
+int npoints # Number of input peaks
+int nmax # Max number of peaks to be selected
+bool debug # Print debugging information?
+
+int i, j, npeaks
+
+begin
+ # Only mark peaks to reject if the number peaks is greater than nmax.
+ if (nmax < npoints) {
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEFR (x[rank[i]]))
+ next
+ npeaks = npeaks + 1
+ if (npeaks > nmax) {
+ if (debug) {
+ j = x[rank[i]]
+ call printf (
+ " Reject peak at column %d with rank %d and value %g.\n")
+ call pargi (j)
+ call pargi (i)
+ call pargr (data[j])
+ }
+ x[rank[i]] = INDEFR
+ }
+ }
+ }
+
+ # Eliminate INDEF points and determine the number of spectra found.
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEFR (x[i]))
+ next
+ npeaks = npeaks + 1
+ x[npeaks] = x[i]
+ }
+
+ return (npeaks)
+end
+
+
+# FIND_UNMAX -- Select up to the nmax highest ranked peaks in bins.
+#
+# The data values, data, peak positions, x, and their ranks, rank, are input.
+# The data values are used only in printing debugging information.
+# Peak positions previously eliminated are flaged by the value INDEF.
+# The rank array contains the indices to the peak positions in order from
+# the highest peak value to the lowest peak value.
+# First all but the nmax highest ranked peaks (which have not been previously
+# eliminated) are eliminated by marking their positions with the value INDEF.
+# Then the remaining peaks are resorted to contain only the unflaged
+# peaks and the number of such peaks is returned.
+
+int procedure find_unmax (data, npts, x, rank, npoints, nmax, nbins, debug)
+
+real data[npts] # Input data values
+int npts # Number of input data points
+real x[npoints] # Peak positions
+int rank[npoints] # Ranks of peaks
+int npoints # Number of input peaks
+int nmax # Max number of peaks to be selected
+int nbins # Number of sample bins
+bool debug # Print debugging information?
+
+int i, j, npeaks, width, x1, x2
+real a
+
+begin
+ # Only mark peaks to reject if the number peaks is greater than nmax.
+ if (nmax < npoints) {
+
+ # Set up circular bins and select highest peak in each bin
+ # until the desired number of peaks is selected.
+
+ width = min (npts-1, nint ((npts-1) / (nbins-.5)))
+ x2 = 1
+ npeaks = 0
+ repeat {
+ x1 = x2
+ x2 = mod (x1 + width, npts) + 1
+ j = 0
+ do i = 1, npoints {
+ a = x[rank[i]]
+ if (IS_INDEFR (a) || a < 0) {
+ j = j + 1
+ next
+ }
+ if (x1 < x2) {
+ if (a >= x1 && a <= x2) {
+ x[rank[i]] = -a
+ npeaks = npeaks + 1
+ break
+ }
+ } else {
+ if (a <= x2 || a >= x1) {
+ x[rank[i]] = -a
+ npeaks = npeaks + 1
+ break
+ }
+ }
+ }
+ } until (npeaks >= nmax || j == npoints)
+
+ # Now eliminate all unused peaks and reset the selected peaks.
+ do i = 1, npoints {
+ if (!IS_INDEFR (x[i]) && x[i] < 1)
+ x[i] = -x[i]
+ else
+ x[i] = INDEFR
+ }
+ }
+
+ # Eliminate INDEF points and determine the number of peaks found.
+ npeaks = 0
+ do i = 1, npoints {
+ if (IS_INDEFR (x[i]))
+ next
+ npeaks = npeaks + 1
+ x[npeaks] = x[i]
+ }
+
+ return (npeaks)
+end
+
+
+# COMPARE -- Compare procedure for sort used in FIND_PEAKS.
+# Larger values are indexed first. INDEF values are indexed last.
+
+int procedure compare (index1, index2)
+
+# Procedure parameters:
+int index1 # Comparison index
+int index2 # Comparison index
+
+pointer y
+
+common /sort/ y
+
+begin
+ # INDEF points are considered to be smallest possible values.
+ if (IS_INDEFR (Memr[y - 1 + index1]))
+ return (1)
+ else if (IS_INDEFR (Memr[y - 1 + index2]))
+ return (-1)
+ else if (Memr[y - 1 + index1] < Memr[y - 1 + index2])
+ return (1)
+ else if (Memr[y - 1 + index1] > Memr[y - 1 + index2])
+ return (-1)
+ else
+ return (0)
+end
+
diff --git a/noao/onedspec/identify/reidentify.x b/noao/onedspec/identify/reidentify.x
new file mode 100644
index 00000000..e29fa163
--- /dev/null
+++ b/noao/onedspec/identify/reidentify.x
@@ -0,0 +1,482 @@
+include <error.h>
+include <imhdr.h>
+include <gset.h>
+include "identify.h"
+
+define HELP "noao$onedspec/identify/identify.key"
+define ICFITHELP "noao$lib/scr/idicgfit.key"
+define PROMPT "identify options"
+
+define PAN 1 # Pan graph
+define ZOOM 2 # Zoom graph
+
+# REIDENTIFY -- Reidentify features in an image.
+
+procedure reidentify (id)
+
+pointer id # ID pointer
+
+real wx, wy
+int wcs, key
+char cmd[SZ_LINE]
+
+char newimage[SZ_FNAME]
+int i, j, last, all, prfeature, nfeatures1, npeaks
+double pix, fit, user, shift, pix_shift, z_shift
+pointer peaks, label, aid
+
+bool aid_autoid()
+int clgcur(), scan(), nscan(), id_peaks(), errcode()
+double id_center(), fit_to_pix(), id_fitpt(), id_shift(), id_rms()
+errchk id_graph()
+
+define newim_ 10
+define newkey_ 20
+define beep_ 99
+
+begin
+ # Initialize.
+ if (ID_GP(id) == NULL)
+ return
+ ID_GTYPE(id) = PAN
+ all = 0
+ last = ID_CURRENT(id)
+ newimage[1] = EOS
+ ID_REFIT(id) = NO
+ wy = INDEF
+ key = 'r'
+
+ repeat {
+ prfeature = YES
+ if (all != 0)
+ all = mod (all + 1, 3)
+
+ switch (key) {
+ case '?': # Print help
+ call gpagefile (ID_GP(id), HELP, PROMPT)
+ case ':': # Process colon commands
+ if (cmd[1] == '/')
+ call gt_colon (cmd, ID_GP(id), ID_GT(id), ID_NEWGRAPH(id))
+ else
+ call id_colon (id, cmd, newimage, prfeature)
+ case ' ': # Go to current feature
+ case '.': # Go to nearest feature
+ if (ID_NFEATURES(id) == 0)
+ goto beep_
+ call id_nearest (id, double (wx))
+ case '-': # Go to previous feature
+ if (ID_CURRENT(id) == 1)
+ goto beep_
+ ID_CURRENT(id) = ID_CURRENT(id) - 1
+ case '+', 'n': # Go to next feature
+ if (ID_CURRENT(id) == ID_NFEATURES(id))
+ goto beep_
+ ID_CURRENT(id) = ID_CURRENT(id) + 1
+ case 'a': # Set all flag for next key
+ all = 1
+ case 'b': # Autoidentify
+ call aid_init (aid, "aidpars")
+ call aid_sets (aid, "crval", "CL crval")
+ call aid_sets (aid, "cdelt", "CL cdelt")
+ if (aid_autoid (id, aid)) {
+ ID_NEWCV(id) = YES
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ } else {
+ prfeature = 0
+ call printf ("No solution found\n")
+ }
+ call aid_free (aid)
+ case 'c': # Recenter features
+ if (all != 0) {
+ for (i = 1; i <= ID_NFEATURES(id); i = i + 1) {
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, i)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ FWIDTH(id,i) = ID_FWIDTH(id)
+ PIX(id,i) = id_center (id, PIX(id,i), FWIDTH(id,i),
+ FTYPE(id,i))
+ if (!IS_INDEFD (PIX(id,i))) {
+ FIT(id,i) = id_fitpt (id, PIX(id,i))
+ call id_mark (id, i)
+ } else {
+ call id_delete (id, i)
+ i = i - 1
+ }
+ }
+ ID_NEWFEATURES(id) = YES
+ } else {
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call id_nearest (id, double (wx))
+ pix = PIX(id,ID_CURRENT(id))
+ pix = id_center (id, pix, ID_FWIDTH(id),
+ FTYPE(id,ID_CURRENT(id)))
+ if (!IS_INDEFD (pix)) {
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ PIX(id,ID_CURRENT(id)) = pix
+ FWIDTH(id,ID_CURRENT(id)) = ID_FWIDTH(id)
+ FIT(id,ID_CURRENT(id)) = id_fitpt (id, pix)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_mark (id, ID_CURRENT(id))
+ ID_NEWFEATURES(id) = YES
+ } else {
+ call printf ("Centering failed\n")
+ prfeature = NO
+ }
+ }
+ case 'd': # Delete features
+ if (all != 0) {
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ } else {
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call id_nearest (id, double (wx))
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_delete (id, ID_CURRENT(id))
+ ID_CURRENT(id) = min (ID_NFEATURES(id), ID_CURRENT(id))
+ last = 0
+ }
+ case 'e': # Find features from line list with no fitting
+ call id_linelist (id)
+ if (ID_NEWFEATURES(id) == YES)
+ ID_NEWGRAPH(id) = YES
+ case 'f': # Fit dispersion function
+ call id_dofit (id, YES)
+ case 'g': # Fit shift
+ call id_doshift (id, YES)
+ prfeature = NO
+ case 'i': # Initialize
+ call dcvfree (ID_CV(id))
+ ID_SHIFT(id) = 0.
+ ID_NEWCV(id) = YES
+ ID_NFEATURES(id) = 0
+ ID_CURRENT(id) = 0
+ ID_NEWFEATURES(id) = YES
+ ID_NEWGRAPH(id) = YES
+ case 'j', 'k', 'o':
+ call printf ("Command not available in REIDENTIFY")
+ prfeature = NO
+ case 'l': # Find features from line list
+ if (ID_NFEATURES(id) >= 2)
+ call id_dofit (id, NO)
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata(id))
+ ;
+ call id_fitfeatures(id)
+ ID_NEWCV(id) = NO
+ }
+ call id_linelist (id)
+ if (ID_NEWFEATURES(id) == YES)
+ ID_REFIT(id) = YES
+ case 'm': # Mark new feature
+ fit = wx
+ pix = fit_to_pix (id, fit)
+ pix = id_center (id, pix, ID_FWIDTH(id), ID_FTYPE(id))
+ if (IS_INDEFD (pix)) {
+ prfeature = NO
+ call printf ("Center not found: check cursor position")
+ if (ID_THRESHOLD(id) > 0.)
+ call printf (" and threshold value")
+ goto beep_
+ }
+ fit = id_fitpt (id, pix)
+ user = fit
+ call id_newfeature (id, pix, fit, user, 1.0D0, ID_FWIDTH(id),
+ ID_FTYPE(id), NULL)
+ USER(id,ID_CURRENT(id)) = INDEFD
+ call id_match (id, FIT(id,ID_CURRENT(id)),
+ USER(id,ID_CURRENT(id)),
+ Memi[ID_LABEL(id)+ID_CURRENT(id)-1], ID_MATCH(id))
+ call id_mark (id, ID_CURRENT(id))
+ call printf ("%10.2f %10.8g (%10.8g %s): ")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ label = Memi[ID_LABEL(id)+ID_CURRENT(id)-1]
+ if (label != NULL)
+ call pargstr (Memc[label])
+ else
+ call pargstr ("")
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ call gargwrd (cmd, SZ_LINE)
+ i = nscan()
+ if (i > 0) {
+ USER(id,ID_CURRENT(id)) = user
+ call id_match (id, user, USER(id,ID_CURRENT(id)),
+ Memi[ID_LABEL(id)+ID_CURRENT(id)-1], ID_MATCH(id))
+ }
+ if (i > 1) {
+ call reset_scan ()
+ call gargd (user)
+ call gargstr (cmd, SZ_LINE)
+ call id_label (cmd, Memi[ID_LABEL(id)+ID_CURRENT(id)-1])
+ }
+ }
+ case 'p': # Switch to pan mode
+ if (ID_GTYPE(id) != PAN) {
+ ID_GTYPE(id) = PAN
+ ID_NEWGRAPH(id) = YES
+ }
+ case 'q': # Exit loop
+ break
+ case 'r': # Redraw the graph
+ ID_NEWGRAPH(id) = YES
+ case 's', 'x': # Shift or correlate features
+ # Get coordinate shift.
+ switch (key) {
+ case 's':
+ call printf ("User coordinate (%10.8g): ")
+ call pargr (wx)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ if (nscan() == 1)
+ shift = wx - user
+ } else
+ shift = 0.
+ case 'x':
+ shift = id_shift (id, -1D0, -0.05D0)
+ if (IS_INDEFD(shift)) {
+ call printf ("No solution found\n")
+ goto beep_
+ }
+ }
+
+ ID_NEWFEATURES(id) = YES
+ ID_NEWCV(id) = YES
+ ID_NEWGRAPH(id) = YES
+ prfeature = NO
+
+ if (ID_NFEATURES(id) < 1) {
+ call printf ("User coordinate shift=%5f\n")
+ call pargd (shift)
+ ID_SHIFT(id) = ID_SHIFT(id) + shift
+ goto newkey_
+ }
+
+ # Recenter features.
+ pix_shift = 0.
+ z_shift = 0.
+ nfeatures1 = ID_NFEATURES(id)
+
+ j = 0.
+ do i = 1, ID_NFEATURES(id) {
+ pix = fit_to_pix (id, FIT(id,i) + shift)
+ pix = id_center (id, pix, FWIDTH(id,i), FTYPE(id,i))
+ if (IS_INDEFD (pix)) {
+ if (ID_CURRENT(id) == i)
+ ID_CURRENT(id) = i + 1
+ next
+ }
+ fit = id_fitpt (id, pix)
+
+ pix_shift = pix_shift + pix - PIX(id,i)
+ if (FIT(id,i) != 0.)
+ z_shift = z_shift + (fit - FIT(id,i)) / FIT(id,i)
+
+ j = j + 1
+ PIX(id,j) = pix
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ if (ID_CURRENT(id) == i)
+ ID_CURRENT(id) = j
+ }
+ if (j != ID_NFEATURES(id)) {
+ ID_NFEATURES(id) = j
+ ID_CURRENT(id) = min (ID_CURRENT(id), ID_NFEATURES(id))
+ }
+
+ if (ID_NFEATURES(id) < 1) {
+ call printf ("User coordinate shift=%5f")
+ call pargd (shift)
+ call printf (", No features found during recentering\n")
+ ID_SHIFT(id) = ID_SHIFT(id) + shift
+ goto newkey_
+ }
+
+ # Adjust shift.
+ pix = ID_SHIFT(id)
+ call id_doshift (id, NO)
+ call id_fitfeatures (id)
+
+ # Print results.
+ call printf ("Recentered=%d/%d")
+ call pargi (ID_NFEATURES(id))
+ call pargi (nfeatures1)
+ call printf (
+ ", pixel shift=%.2f, user shift=%5f, z=%7.3g, rms=%5g\n")
+ call pargd (pix_shift / ID_NFEATURES(id))
+ call pargd (pix - ID_SHIFT(id))
+ call pargd (z_shift / ID_NFEATURES(id))
+ call pargd (id_rms(id))
+ case 't': # Move the current feature
+ if (ID_CURRENT(id) < 1)
+ goto beep_
+ pix = fit_to_pix (id, double (wx))
+ call gseti (ID_GP(id), G_PLTYPE, 0)
+ call id_mark (id, ID_CURRENT(id))
+ PIX(id,ID_CURRENT(id)) = pix
+ FIT(id,ID_CURRENT(id)) = id_fitpt (id, pix)
+ call gseti (ID_GP(id), G_PLTYPE, 1)
+ call id_mark (id, ID_CURRENT(id))
+ ID_NEWFEATURES(id) = YES
+ case 'u': # Set user coordinate
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call printf ("%10.2f %10.8g (%10.8g %s): ")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ label = Memi[ID_LABEL(id)+ID_CURRENT(id)-1]
+ if (label != NULL)
+ call pargstr (Memc[label])
+ else
+ call pargstr ("")
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargd (user)
+ call gargwrd (cmd, SZ_LINE)
+ i = nscan()
+ if (i > 0) {
+ USER(id,ID_CURRENT(id)) = user
+ ID_NEWFEATURES(id) = YES
+ }
+ if (i > 1) {
+ call reset_scan ()
+ call gargd (user)
+ call gargstr (cmd, SZ_LINE)
+ call id_label (cmd, Memi[ID_LABEL(id)+ID_CURRENT(id)-1])
+ }
+ }
+ case 'v': # Modify weight
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ call printf ("Weight (%d): ")
+ call pargd (WTS(id,ID_CURRENT(id)))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargi (i)
+ if (nscan() > 0) {
+ WTS(id,ID_CURRENT(id)) = i
+ ID_NEWFEATURES(id) = YES
+ }
+ }
+ case 'w': # Window graph
+ call gt_window (ID_GT(id), ID_GP(id), "cursor", ID_NEWGRAPH(id))
+ case 'y': # Find peaks
+ call malloc (peaks, ID_NPTS(id), TY_REAL)
+ npeaks = id_peaks (id, IMDATA(id,1), Memr[peaks], ID_NPTS(id),
+ 0., int (ID_MINSEP(id)), 0, ID_MAXFEATURES(id), 0., false)
+ for (j = 1; j <= ID_NFEATURES(id); j = j + 1) {
+ for (i = 1; i <= npeaks; i = i + 1) {
+ if (!IS_INDEF (Memr[peaks+i-1])) {
+ pix = Memr[peaks+i-1]
+ if (abs (pix - PIX(id,j)) < ID_MINSEP(id))
+ Memr[peaks+i-1] = INDEF
+ }
+ }
+ }
+ for (i = 1; i <= npeaks; i = i + 1) {
+ if (IS_INDEF(Memr[peaks+i-1]))
+ next
+ pix = Memr[peaks+i-1]
+ pix = id_center (id, pix, ID_FWIDTH(id), ID_FTYPE(id))
+ if (IS_INDEFD (pix))
+ next
+ fit = id_fitpt (id, pix)
+ user = INDEFD
+ call id_match (id, fit, user, label, ID_MATCH(id))
+ call id_newfeature (id, pix, fit, user, 1.0D0,
+ ID_FWIDTH(id), ID_FTYPE(id), label)
+ call id_mark (id, ID_CURRENT(id))
+ }
+ call mfree (peaks, TY_REAL)
+ case 'z': # Go to zoom mode
+ if (ID_NFEATURES(id) < 1)
+ goto beep_
+ if (ID_GTYPE(id) == PAN)
+ ID_NEWGRAPH(id) = YES
+ ID_GTYPE(id) = ZOOM
+ call id_nearest (id, double (wx))
+ case 'I':
+ call fatal (0, "Interrupt")
+ default:
+beep_ call printf ("\007")
+ }
+
+newkey_
+ # Set update flag if anything has changed.
+ if ((ID_NEWFEATURES(id) == YES) || (ID_NEWCV(id) == YES))
+ ID_NEWDBENTRY(id) = YES
+
+ # If a new image exit loop, update database, and start over.
+ if (newimage[1] != EOS) {
+ call printf ("Can't change image in REIDENTIFY")
+ newimage[1] = EOS
+ prfeature = NO
+ }
+
+ # Refit dispersion function
+ if (ID_REFIT(id) == YES) {
+ call id_dofit (id, NO)
+ ID_REFIT(id) = NO
+ }
+
+ # If there is a new dispersion solution evaluate the coordinates
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata (id))
+ ;
+ call id_fitfeatures (id)
+ ID_NEWCV(id) = NO
+ }
+
+ # Draw new graph in zoom mode if current feature has changed.
+ if ((ID_GTYPE(id) == ZOOM) && (last != ID_CURRENT(id)))
+ ID_NEWGRAPH(id) = YES
+
+ # Draw new graph.
+ if (ID_NEWGRAPH(id) == YES) {
+ call id_graph (id, ID_GTYPE(id))
+ ID_NEWGRAPH(id) = NO
+ }
+
+ # Set cursor and print status of current feature (unless canceled).
+ if (ID_CURRENT(id) > 0) {
+ if (IS_INDEF (wy)) {
+ i = max (1, min (ID_NPTS(id), int (PIX(id,ID_CURRENT(id)))))
+ wy = IMDATA(id,i)
+ }
+
+ call gscur (ID_GP(id), real (FIT(id,ID_CURRENT(id))), wy)
+ if (errcode() == OK && prfeature == YES) {
+ call printf ("%10.2f %10.8g %10.8g %s\n")
+ call pargd (PIX(id,ID_CURRENT(id)))
+ call pargd (FIT(id,ID_CURRENT(id)))
+ call pargd (USER(id,ID_CURRENT(id)))
+ if (Memi[ID_LABEL(id)+ID_CURRENT(id)-1] != NULL)
+ call pargstr (
+ Memc[Memi[ID_LABEL(id)+ID_CURRENT(id)-1]])
+ else
+ call pargstr ("")
+ }
+ }
+
+ # Print delayed error message
+ if (errcode() != OK)
+ call erract (EA_WARN)
+
+ last = ID_CURRENT(id)
+ } until (clgcur ("cursor", wx, wy, wcs, key, cmd, SZ_LINE) == EOF)
+end
diff --git a/noao/onedspec/identify/t_autoid.x b/noao/onedspec/identify/t_autoid.x
new file mode 100644
index 00000000..fbdaa0cd
--- /dev/null
+++ b/noao/onedspec/identify/t_autoid.x
@@ -0,0 +1,252 @@
+include <error.h>
+include <fset.h>
+include <gset.h>
+include <pkg/gtools.h>
+include <smw.h>
+include "identify.h"
+
+define ICFITHELP "noao$lib/scr/idicgfit.key"
+
+
+# T_AUTOIDENTIFY -- Automatically identify spectral features.
+
+procedure t_autoidentify ()
+
+int list # List of images
+int interactive # Examine identifications interactively?
+int dbwrite # Write database results?
+
+int i, fd, hdr, hdr1
+pointer sp, str, aid, id
+
+int clgeti(), clgwrd(), nscan(), open(), nowhite()
+int imtopenp(), imtgetim(), id_dbcheck()
+bool clgetb(), aid_autoid()
+real clgetr()
+pointer gopen(), gt_init(), un_open()
+errchk open, id_mapll, aid_autoid, aid_init, reidentify
+
+define done_ 10
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Initialize data structures. Note the AID structure is initialized
+ # with CL queries to the AIDPARS pset.
+
+ aid = NULL
+ call aid_init (aid, "aidpars")
+ call id_init (id)
+
+ # Get query parameters.
+ list = imtopenp ("images")
+ call aid_sets (aid, "crval", "CL crval")
+ call aid_sets (aid, "cdelt", "CL cdelt")
+
+ # Get other parameters and IDENITFY set data structures.
+ ID_NSUM(id,1) = clgeti ("nsum")
+ call gargi (ID_NSUM(id,2))
+ if (nscan() != 2)
+ ID_NSUM(id,2) = ID_NSUM(id,1)
+ ID_NSUM(id,1) = max (1, ID_NSUM(id,1))
+ ID_NSUM(id,2) = max (1, ID_NSUM(id,2))
+ ID_MAXFEATURES(id) = clgetr ("aidpars.ntarget")
+ ID_MINSEP(id) = clgetr ("minsep")
+ ID_FTYPE(id) = clgwrd ("ftype", Memc[str], SZ_LINE, FTYPES)
+ ID_FWIDTH(id) = clgetr ("fwidth")
+ ID_CRADIUS(id) = clgetr ("cradius")
+ ID_THRESHOLD(id) = clgetr ("threshold")
+ ID_MATCH(id) = clgetr ("match")
+ ID_ZWIDTH(id) = clgetr ("identify.zwidth")
+ ID_LABELS(id) = 1
+
+ call clgstr ("database", ID_DATABASE(id), ID_LENSTRING)
+ dbwrite = clgwrd ("dbwrite", Memc[str], SZ_FNAME, "|no|yes|NO|YES|")
+ if (dbwrite == 1)
+ dbwrite = 3
+
+ call clgstr ("coordlist", ID_COORDLIST(id), ID_LENSTRING)
+ if (nowhite (ID_COORDLIST(id), ID_COORDLIST(id), ID_LENSTRING) == 0) {
+ call clgstr ("coordlist.p_prompt", Memc[str], SZ_LINE)
+ call printf (Memc[str])
+ call flush (STDOUT)
+ call clgstr ("query", ID_COORDLIST(id), ID_LENSTRING)
+ }
+ call clgstr ("units", Memc[str], SZ_LINE)
+ call xt_stripwhite (Memc[str])
+ if (Memc[str] != EOS)
+ ID_UN(id) = un_open (Memc[str])
+ call id_mapll (id)
+ if (ID_LL(id) == NULL)
+ call error (0, "Required coordinate line list not found")
+
+ # Dispersion fitting parameters.
+ call ic_open (ID_IC(id))
+ call clgstr ("function", Memc[str], SZ_LINE)
+ call ic_pstr (ID_IC(id), "function", Memc[str])
+ call ic_puti (ID_IC(id), "order", clgeti ("order"))
+ call clgstr ("sample", Memc[str], SZ_LINE)
+ call ic_pstr (ID_IC(id), "sample", Memc[str])
+ call ic_puti (ID_IC(id), "naverage", 1)
+ call ic_puti (ID_IC(id), "niterate", clgeti ("niterate"))
+ call ic_putr (ID_IC(id), "low", clgetr ("low_reject"))
+ call ic_putr (ID_IC(id), "high", clgetr ("high_reject"))
+ call ic_putr (ID_IC(id), "grow", clgetr ("grow"))
+
+ call ic_pstr (ID_IC(id), "xlabel", "Feature positions")
+ call ic_pstr (ID_IC(id), "xunits", "pixels")
+ call ic_pstr (ID_IC(id), "ylabel", "")
+ call ic_pkey (ID_IC(id), 1, 'y', 'x')
+ call ic_pkey (ID_IC(id), 2, 'y', 'v')
+ call ic_pkey (ID_IC(id), 3, 'y', 'r')
+ call ic_pkey (ID_IC(id), 4, 'y', 'd')
+ call ic_pkey (ID_IC(id), 5, 'y', 'n')
+ call ic_puti (ID_IC(id), "key", 5)
+ call ic_pstr (ID_IC(id), "help", ICFITHELP)
+
+ # Interactive, graphics, and output parameters.
+ interactive = clgwrd ("interactive", Memc[str], SZ_FNAME,
+ "|no|yes|NO|YES|")
+ switch (interactive) {
+ case 1, 3:
+ ID_GP(id) = NULL
+ interactive = 3
+ case 2, 4:
+ # Open graphics
+ call clgstr ("graphics", Memc[str], SZ_LINE)
+ ID_GP(id) = gopen (Memc[str], NEW_FILE+AW_DEFER, STDGRAPH)
+ }
+
+ ID_GT(id) = gt_init()
+ call gt_sets (ID_GT(id), GTTYPE, "line")
+ call fseti (STDOUT, F_FLUSHNL, YES)
+ hdr = YES
+ hdr1 = YES
+
+ # Log and plot files.
+ call calloc (ID_LOGFILES(id), 4, TY_INT)
+ if (clgetb ("verbose"))
+ Memi[ID_LOGFILES(id)] = STDOUT
+ call clgstr ("logfile", Memc[str], SZ_LINE)
+ if (nowhite (Memc[str], Memc[str], SZ_FNAME) > 0) {
+ fd = open (Memc[str], APPEND, TEXT_FILE)
+ Memi[ID_LOGFILES(id)+1] = fd
+ }
+ call clgstr ("plotfile", Memc[str], SZ_LINE)
+ if (nowhite (Memc[str], Memc[str], SZ_FNAME) > 0) {
+ fd = open (Memc[str], APPEND, BINARY_FILE)
+ Memi[ID_LOGFILES(id)+2] = fd
+ }
+
+ # Expand the image template and identify features.
+ while (imtgetim (list, ID_IMAGE(id), ID_LENSTRING) != EOF) {
+ # Initialize.
+ iferr (call id_map(id)) {
+ call erract (EA_WARN)
+ next
+ }
+ if (!clgetb ("overwrite")) {
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES) {
+ if (Memi[ID_LOGFILES(id)] != NULL) {
+ if (ID_GP(id) != NULL)
+ call gdeactivate (ID_GP(id), 0)
+ call fprintf (Memi[ID_LOGFILES(id)],
+ " %s%s%24t Database entry already exists\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ }
+ goto done_
+ }
+ }
+
+ call id_gdata(id)
+ call id_fitdata(id)
+ call ic_putr (ID_IC(id), "xmin", real (PIXDATA(id,1)))
+ call ic_putr (ID_IC(id), "xmax", real (PIXDATA(id,ID_NPTS(id))))
+
+ call dcvfree (ID_CV(id))
+ ID_SHIFT(id) = 0.
+ ID_NFEATURES(id) = 0
+
+ # Automatically identify the features.
+ if (aid_autoid (id, aid))
+ ID_NEWDBENTRY(id) = YES
+ else if (Memi[ID_LOGFILES(id)] == NULL)
+ call aid_log (id, STDOUT, NO)
+ call aid_log (id, Memi[ID_LOGFILES(id)], hdr)
+ call aid_log (id, Memi[ID_LOGFILES(id)+1], hdr1)
+
+ # Enter interactive identification mode if desired.
+ if (interactive != 3) {
+ if (interactive != 4) {
+ repeat {
+ call clgstr ("interactive.p_prompt", Memc[str],
+ SZ_FNAME)
+ call printf ("%s%s: %s")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargstr (Memc[str])
+ call flush (STDOUT)
+ if (interactive == 1)
+ call clpstr ("query", "no")
+ else
+ call clpstr ("query", "yes")
+ ifnoerr (interactive = clgwrd ("query", Memc[str],
+ SZ_FNAME, "|no|yes|NO|YES|"))
+ break
+ }
+ }
+ if (interactive == 2 || interactive == 4) {
+ call reidentify (id)
+ call gdeactivate (ID_GP(id), 0)
+ }
+ }
+
+ # Write results to the database.
+ if (ID_NEWDBENTRY(id) == YES) {
+ if (dbwrite == 1 || dbwrite == 2) {
+ repeat {
+ call clgstr ("dbwrite.p_prompt", Memc[str], SZ_FNAME)
+ call printf ("%s%s: %s")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargstr (Memc[str])
+ call flush (STDOUT)
+ if (dbwrite == 1)
+ call clpstr ("query", "no")
+ else
+ call clpstr ("query", "yes")
+ ifnoerr (dbwrite = clgwrd ("query", Memc[str],
+ SZ_FNAME, "|no|yes|NO|YES|"))
+ break
+ }
+ }
+ if (dbwrite == 2 || dbwrite == 4)
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ }
+
+done_ # Close the database, image, and spectrum data structures.
+ if (ID_DT(id) != NULL)
+ call dtunmap (ID_DT(id))
+ call smw_close (MW(ID_SH(id)))
+ call imunmap (IM(ID_SH(id)))
+ call shdr_close (ID_SH(id))
+ }
+
+ # Finish up.
+ do i = 1, 3 {
+ fd = Memi[ID_LOGFILES(id)+i-1]
+ if (fd != NULL)
+ call close (fd)
+ }
+ call mfree (ID_LOGFILES(id), TY_INT)
+ if (ID_GP(id) != NULL)
+ call gclose (ID_GP(id))
+ call smw_daxis (NULL, NULL, 0, 0, 0)
+ call imtclose (list)
+ if (aid != NULL)
+ call aid_free (aid)
+ call id_free (id)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/t_identify.x b/noao/onedspec/identify/t_identify.x
new file mode 100644
index 00000000..96e5034e
--- /dev/null
+++ b/noao/onedspec/identify/t_identify.x
@@ -0,0 +1,89 @@
+include <mach.h>
+include <pkg/gtools.h>
+include "identify.h"
+
+# T_IDENTIFY -- Identify features
+
+procedure t_identify ()
+
+int list, clscan(), clgeti(), clgwrd(), nscan(), imtopenp(), imtgetim()
+real clgetr()
+pointer sp, str, id, gt_init(), un_open()
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Allocate the basic data structure.
+ call id_init (id)
+
+ # Get task parameters.
+ list = imtopenp ("images")
+ if (clscan ("nsum") != EOF) {
+ call gargi (ID_NSUM(id,1))
+ call gargi (ID_NSUM(id,2))
+ if (nscan() == 0)
+ call error (1, "Error in 'nsum' parameter")
+ if (nscan() == 1)
+ ID_NSUM(id,2) = ID_NSUM(id,1)
+ ID_NSUM(id,1) = max (1, ID_NSUM(id,1))
+ ID_NSUM(id,2) = max (1, ID_NSUM(id,2))
+ }
+ ID_MAXFEATURES(id) = clgeti ("maxfeatures")
+ ID_MINSEP(id) = clgetr ("minsep")
+ ID_MATCH(id) = clgetr ("match")
+ ID_ZWIDTH(id) = clgetr ("zwidth")
+ ID_FTYPE(id) = clgwrd ("ftype", Memc[str], SZ_LINE, FTYPES)
+ ID_FWIDTH(id) = clgetr ("fwidth")
+ ID_CRADIUS(id) = clgetr ("cradius")
+ ID_THRESHOLD(id) = clgetr ("threshold")
+ call clgstr ("database", ID_DATABASE(id), ID_LENSTRING)
+ call clgstr ("coordlist", ID_COORDLIST(id), ID_LENSTRING)
+ call clgstr ("units", Memc[str], SZ_LINE)
+ call xt_stripwhite (Memc[str])
+ if (Memc[str] != EOS)
+ ID_UN(id) = un_open (Memc[str])
+ ID_LABELS(id) = 1
+
+ # Initialize features data structure.
+ ID_GT(id) = gt_init()
+ call gt_sets (ID_GT(id), GTTYPE, "line")
+ ID_CV(id) = NULL
+ ID_CURRENT(id) = 0
+ ID_SHIFT(id) = 0.
+
+ # Initialize ICFIT
+ call ic_open (ID_IC(id))
+ call clgstr ("function", Memc[str], SZ_LINE)
+ call ic_pstr (ID_IC(id), "function", Memc[str])
+ call ic_puti (ID_IC(id), "order", clgeti ("order"))
+ call clgstr ("sample", Memc[str], SZ_LINE)
+ call ic_pstr (ID_IC(id), "sample", Memc[str])
+ call ic_puti (ID_IC(id), "naverage", 1)
+ call ic_puti (ID_IC(id), "niterate", clgeti ("niterate"))
+ call ic_putr (ID_IC(id), "low", clgetr ("low_reject"))
+ call ic_putr (ID_IC(id), "high", clgetr ("high_reject"))
+ call ic_putr (ID_IC(id), "grow", clgetr ("grow"))
+ call ic_pstr (ID_IC(id), "xlabel", "Feature positions")
+ call ic_pstr (ID_IC(id), "xunits", "pixels")
+ call ic_pstr (ID_IC(id), "ylabel", "")
+ call ic_pkey (ID_IC(id), 1, 'y', 'x')
+ call ic_pkey (ID_IC(id), 2, 'y', 'v')
+ call ic_pkey (ID_IC(id), 3, 'y', 'r')
+ call ic_pkey (ID_IC(id), 4, 'y', 'd')
+ call ic_pkey (ID_IC(id), 5, 'y', 'n')
+ call ic_puti (ID_IC(id), "key", 3)
+
+ # Get the line list.
+ call id_mapll (id)
+
+ # Expand the image template and identify features in each image.
+ while (imtgetim (list, ID_IMAGE(id), ID_LENSTRING) != EOF)
+ call id_identify (id)
+
+ # Finish up.
+ call smw_daxis (NULL, NULL, 0, 0, 0)
+ call id_free (id)
+ call imtclose (list)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/identify/t_reidentify.x b/noao/onedspec/identify/t_reidentify.x
new file mode 100644
index 00000000..e82951ee
--- /dev/null
+++ b/noao/onedspec/identify/t_reidentify.x
@@ -0,0 +1,1083 @@
+include <error.h>
+include <fset.h>
+include <gset.h>
+include <pkg/gtools.h>
+include <smw.h>
+include "identify.h"
+
+define ICFITHELP "noao$lib/scr/idicgfit.key"
+
+# T_REIDENTIFY -- Reidentify features starting from reference features.
+# A reference spectrum is specified and the same features are identified
+# in other images. Some lines may be lost due to bad centering. Additional
+# lines may be excluded from a new fit to the dispersion function. Instead
+# of refitting the dispersion function the user may elect to determine only
+# a shift in the reference dispersion function. Additional features may
+# be added given a coordinate list.
+#
+# In 2D images a starting line or column is selected. A number of lines
+# or columns may be averaged before identifying features. If a positive step
+# size is given then additional lines or columns may be reidentified in
+# the reference image. This may be done either by tracing or by reidentifying
+# starting from the same reference features. Reidentification between images
+# is done by taking the same line or column from the reference image.
+# The step and summing are ignored for multispec images.
+#
+# Multispec format images are matched by aperture number and the spectra
+# need not be in the same order in each image.
+
+procedure t_reidentify ()
+
+pointer reference # Reference image
+int list # List of images
+char ans[3] # Interactive?
+double crsearch # Search radius
+
+int i, fd, nlogfd
+pointer sp, logfile, str, id, logfd, pd
+
+int clscan(), clgeti(), clpopnu(), clgfil(), clgwrd()
+int nscan(), open(), btoi(), nowhite(), imtopenp(), imtgetim()
+bool clgetb(), strne()
+double clgetd()
+pointer gopen(), gt_init()
+
+begin
+ call smark (sp)
+ call salloc (reference, SZ_FNAME, TY_CHAR)
+ call salloc (logfile, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Allocate the basic data structures.
+ call id_init (id)
+ call ic_open (ID_IC(id))
+
+ # Get task parameters.
+ call clgstr ("reference", Memc[reference], SZ_FNAME)
+ list = imtopenp ("images")
+ i = nowhite (Memc[reference], Memc[reference], SZ_FNAME)
+
+ crsearch = clgetd ("search")
+ ID_REFIT(id) = btoi (clgetb ("refit"))
+
+ if (clscan ("nsum") != EOF) {
+ call gargi (ID_NSUM(id,1))
+ call gargi (ID_NSUM(id,2))
+ if (nscan() == 0)
+ call error (1, "Error in 'nsum' parameter")
+ if (nscan() == 1)
+ ID_NSUM(id,2) = ID_NSUM(id,1)
+ ID_NSUM(id,1) = max (1, ID_NSUM(id,1))
+ ID_NSUM(id,2) = max (1, ID_NSUM(id,2))
+ }
+ ID_MAXFEATURES(id) = clgeti ("maxfeatures")
+ ID_MINSEP(id) = clgetd ("minsep")
+ ID_MATCH(id) = clgetd ("match")
+ ID_ZWIDTH(id) = clgetd ("identify.zwidth")
+ ID_FTYPE(id) = clgwrd ("identify.ftype", Memc[str], SZ_LINE, FTYPES)
+ ID_FWIDTH(id) = clgetd ("identify.fwidth")
+ ID_CRADIUS(id) = clgetd ("cradius")
+ ID_THRESHOLD(id) = clgetd ("threshold")
+ call clgstr ("database", ID_DATABASE(id), ID_LENSTRING)
+ call clgstr ("coordlist", ID_COORDLIST(id), ID_LENSTRING)
+ ID_LABELS(id) = 1
+
+ call id_mapll (id)
+ ID_LOGFILES(id) = clpopnu ("logfiles")
+
+ switch (clgwrd ("interactive", ans, SZ_FNAME, "|no|yes|NO|YES|")) {
+ case 1, 3:
+ call strcpy ("NO", ans, 3)
+ ID_GP(id) = NULL
+ case 2, 4:
+ # Open graphics
+ call clgstr ("graphics", Memc[logfile], SZ_FNAME)
+ ID_GP(id) = gopen (Memc[logfile], NEW_FILE+AW_DEFER, STDGRAPH)
+ call ic_pstr (ID_IC(id), "help", ICFITHELP)
+ call ic_pstr (ID_IC(id), "xlabel", "Feature positions")
+ call ic_pstr (ID_IC(id), "xunits", "pixels")
+ call ic_pstr (ID_IC(id), "ylabel", "")
+ call ic_pkey (ID_IC(id), 1, 'y', 'x')
+ call ic_pkey (ID_IC(id), 2, 'y', 'v')
+ call ic_pkey (ID_IC(id), 3, 'y', 'r')
+ call ic_pkey (ID_IC(id), 4, 'y', 'd')
+ call ic_pkey (ID_IC(id), 5, 'y', 'n')
+ call ic_puti (ID_IC(id), "key", 3)
+ }
+
+ # Open log and plot files.
+ nlogfd = 0
+ if (clgetb ("verbose")) {
+ nlogfd = 1
+ call malloc (logfd, nlogfd, TY_INT)
+ Memi[logfd] = STDOUT
+ }
+ while (clgfil (ID_LOGFILES(id), Memc[logfile], SZ_FNAME) != EOF) {
+ fd = open (Memc[logfile], APPEND, TEXT_FILE)
+ call fseti (fd, F_FLUSHNL, YES)
+ nlogfd = nlogfd + 1
+ if (nlogfd == 1)
+ call malloc (logfd, nlogfd, TY_INT)
+ else
+ call realloc (logfd, nlogfd, TY_INT)
+ Memi[logfd+nlogfd-1] = fd
+ }
+ call ri_loghdr (id, Memc[reference], Memi[logfd], nlogfd, 1)
+
+ call clgstr ("plotfile", Memc[logfile], SZ_FNAME)
+ if (nowhite (Memc[logfile], Memc[logfile], SZ_FNAME) > 0) {
+ fd = open (Memc[logfile], APPEND, BINARY_FILE)
+ pd = gopen ("stdvdm", NEW_FILE, fd)
+ } else
+ pd = NULL
+
+ ID_GT(id) = gt_init()
+ call gt_sets (ID_GT(id), GTTYPE, "line")
+
+ # Get and trace the reference solutions.
+ call ri_reference (id, Memc[reference], crsearch, ans, Memi[logfd],
+ nlogfd, pd)
+
+ # Expand the image template and reidentify features.
+ while (imtgetim (list, ID_IMAGE(id), ID_LENSTRING) != EOF)
+ if (strne (Memc[reference], ID_IMAGE(id)))
+ call ri_image (id, Memc[reference], ID_IMAGE(id), crsearch, ans,
+ Memi[logfd], nlogfd, pd)
+
+ # Finish up.
+ if (nlogfd > 0) {
+ do i = 1, nlogfd
+ call close (Memi[logfd+i-1])
+ call mfree (logfd, TY_INT)
+ }
+
+ if (ID_GP(id) != NULL)
+ call gclose (ID_GP(id))
+ if (pd != NULL) {
+ call gclose (pd)
+ call close (fd)
+ }
+ call clpcls (ID_LOGFILES(id))
+ call imtclose (list)
+ call id_free (id)
+ call smw_daxis (NULL, NULL, 0, 0, 0)
+ call sfree (sp)
+end
+
+
+# RI_REFERENCE -- Set reference features. Trace if needed.
+
+procedure ri_reference (id, reference, crsearch, ans, logfd, nlogfd, pd)
+
+pointer id # ID pointer
+char reference[ARB] # Reference image
+double crsearch # Search radius
+char ans[3] # Interactive?
+int logfd[ARB] # Logfiles
+int nlogfd # Number of logfiles
+pointer pd # Plot file pointer
+
+int step[2]
+double shift[2]
+int nreid
+bool override
+bool trace
+
+int i, apstart[2], start[2], line[2], loghdr
+double fit_shift[2]
+pointer ic, ic1
+bool clgetb()
+int clscan(), clgeti(), nscan(), id_dbcheck()
+pointer id_getap()
+errchk id_dbread
+
+begin
+ # Open the image and return if there is an error.
+ call strcpy (reference, ID_IMAGE(id), ID_LENSTRING)
+ iferr (call id_map (id)) {
+ call erract (EA_WARN)
+ iferr (call id_dbsave (id, ID_IMAGE(id)))
+ call erract (EA_WARN)
+ return
+ }
+
+ # Get and save the reference database entry.
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1), NO, NO)
+ call id_saveap (id)
+
+ # Set parameters
+ start[1] = ID_LINE(id,1)
+ start[2] = ID_LINE(id,2)
+ apstart[1] = ID_AP(id,1)
+ apstart[2] = ID_AP(id,2)
+
+ if (clscan ("step") == EOF)
+ call error (1, "Error in 'step' parameter")
+ call gargi (step[1])
+ call gargi (step[2])
+ if (nscan() == 0)
+ call error (1, "Error in 'step' parameter")
+ if (nscan() == 1)
+ step[2] = step[1]
+ if (SMW_FORMAT(MW(ID_SH(id))) != SMW_ND) {
+ step[1] = min (step[1], 1)
+ step[2] = min (step[2], 1)
+ }
+ if (step[1] == 0)
+ step[1] = ID_MAXLINE(id,1)
+ if (step[2] == 0)
+ step[2] = ID_MAXLINE(id,2)
+
+ if (clscan ("shift") != EOF) {
+ call gargd (shift[1])
+ call gargd (shift[2])
+ if (nscan() == 0)
+ call error (1, "Error in 'shift' parameter")
+ if (nscan() == 1)
+ shift[2] = shift[1]
+ }
+
+ nreid = max (1, ID_NFEATURES(id) - clgeti ("nlost"))
+ override = clgetb ("override")
+ trace = clgetb ("trace")
+
+ # Get and save other entries.
+ if (!override) {
+ for (line[2]=start[2]; line[2]>0; line[2]=line[2]-step[2]) {
+ ID_LINE(id,2) = line[2]
+ ID_AP(id,2) = line[2]
+ for (line[1]=start[1]; line[1]>0; line[1]=line[1]-step[1]) {
+ if (line[1]==start[1] && line[2]==start[2])
+ next
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ ifnoerr (
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1),
+ NO, NO)) {
+ call id_saveap (id)
+ }
+ }
+ for (line[1]=start[1]+step[1]; line[1]<=ID_MAXLINE(id,1);
+ line[1]=line[1]+step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ ifnoerr (call id_dbread (id, ID_IMAGE(id),
+ ID_AP(id,1), NO, NO)) {
+ call id_saveap (id)
+ }
+ }
+ }
+ for (line[2]=start[2]+step[2]; line[2]<=ID_MAXLINE(id,2);
+ line[2]=line[2]+step[2]) {
+ ID_LINE(id,2) = line[2]
+ ID_AP(id,2) = line[2]
+ for (line[1]=start[1]-step[1]; line[1]>0;
+ line[1]=line[1]-step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ ifnoerr (
+ call id_dbread (id, ID_IMAGE(id), ID_AP(id,1),
+ NO, NO)) {
+ call id_saveap (id)
+ }
+ }
+ for (line[1]=start[1]+step[1]; line[1]<=ID_MAXLINE(id,1);
+ line[1]=line[1]+step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ ifnoerr (call id_dbread (id, ID_IMAGE(id),
+ ID_AP(id,1), NO, NO)) {
+ call id_saveap (id)
+ }
+ }
+ }
+ }
+
+ # Reidentify.
+ loghdr = 2
+ ic = ID_IC(id)
+ if (ans[1] == 'N')
+ ic1 = ic
+ else {
+ call ic_open (ic1)
+ call ic_copy (ic, ic1)
+ }
+
+ fit_shift[2] = shift[2]
+ for (line[2]=start[2]; line[2]>0; line[2]=line[2]-step[2]) {
+ ID_LINE(id,2) = line[2]
+ ID_AP(id,2) = line[2]
+ ID_IC(id) = ic
+
+ if (IS_INDEFD(shift[2]))
+ fit_shift[2] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[2] = fit_shift[2] - shift[2]
+ else
+ fit_shift[2] = -shift[2]
+ }
+
+ fit_shift[1] = fit_shift[2]
+ for (line[1]=start[1]; line[1]>0; line[1]=line[1]-step[1]) {
+ if (line[1]==start[1] && line[2]==start[2])
+ next
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ ID_IC(id) = ic
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ if (!trace) {
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ ID_LINE(id,1) = line[1]
+ ID_LINE(id,2) = line[2]
+ }
+
+ if (IS_INDEFD(shift[1]))
+ fit_shift[1] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[1] = fit_shift[1] - shift[1]
+ else
+ fit_shift[1] = -shift[1]
+ }
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) < nreid) {
+ call ri_loghdr (id, reference, logfd, nlogfd, 3)
+ ID_NFEATURES(id) = 0
+ if (trace)
+ break
+ }
+
+ if (ID_NFEATURES(id) > 0) {
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ call id_saveap (id)
+ }
+ }
+
+ ID_IC(id) = ic
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ fit_shift[1] = fit_shift[2]
+ for (line[1]=start[1]+step[1]; line[1]<=ID_MAXLINE(id,1);
+ line[1]=line[1]+step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ ID_IC(id) = ic
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ if (!trace) {
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ ID_LINE(id,1) = line[1]
+ ID_LINE(id,2) = line[2]
+ }
+
+ if (IS_INDEFD(shift[1]))
+ fit_shift[1] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[1] = fit_shift[1] + shift[1]
+ else
+ fit_shift[1] = shift[1]
+ }
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) < nreid) {
+ call ri_loghdr (id, reference, logfd, nlogfd, 3)
+ ID_NFEATURES(id) = 0
+ if (trace)
+ break
+ }
+
+ if (ID_NFEATURES(id) > 0) {
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ call id_saveap (id)
+ }
+ }
+ }
+
+
+ fit_shift[2] = 0.
+ for (line[2]=start[2]+step[2]; line[2]<=ID_MAXLINE(id,2);
+ line[2]=line[2]+step[2]) {
+ ID_LINE(id,2) = line[2]
+ ID_AP(id,2) = line[2]
+ ID_IC(id) = ic
+
+ if (IS_INDEFD(shift[2]))
+ fit_shift[2] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[2] = fit_shift[2] + shift[2]
+ else
+ fit_shift[2] = shift[2]
+ }
+
+ fit_shift[1] = fit_shift[2]
+ for (line[1]=start[1]; line[1]>0; line[1]=line[1]-step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ ID_IC(id) = ic
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ if (!trace) {
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ ID_LINE(id,1) = line[1]
+ ID_LINE(id,2) = line[2]
+ }
+
+ if (IS_INDEFD(shift[1]))
+ fit_shift[1] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[1] = fit_shift[1] - shift[1]
+ else
+ fit_shift[1] = -shift[1]
+ }
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) < nreid) {
+ call ri_loghdr (id, reference, logfd, nlogfd, 3)
+ ID_NFEATURES(id) = 0
+ if (trace)
+ break
+ }
+
+ if (ID_NFEATURES(id) > 0) {
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ call id_saveap (id)
+ }
+ }
+
+ ID_IC(id) = ic
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ fit_shift[1] = fit_shift[2]
+ for (line[1]=start[1]+step[1]; line[1]<=ID_MAXLINE(id,1);
+ line[1]=line[1]+step[1]) {
+ ID_LINE(id,1) = line[1]
+ ID_AP(id,1) = line[1]
+ ID_IC(id) = ic
+ if (ID_APS(id) != NULL)
+ ID_AP(id,1) = Memi[ID_APS(id)+line[1]-1]
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ if (!trace) {
+ ID_NFEATURES(id) = 0
+ ID_AP(id,1) = apstart[1]
+ ID_AP(id,2) = apstart[2]
+ i = id_getap (id)
+ ID_LINE(id,1) = line[1]
+ ID_LINE(id,2) = line[2]
+ }
+
+ if (IS_INDEFD(shift[1]))
+ fit_shift[1] = INDEFD
+ else {
+ if (!trace)
+ fit_shift[1] = fit_shift[1] + shift[1]
+ else
+ fit_shift[1] = shift[1]
+ }
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) < nreid) {
+ call ri_loghdr (id, reference, logfd, nlogfd, 3)
+ ID_NFEATURES(id) = 0
+ if (trace)
+ break
+ }
+
+ if (ID_NFEATURES(id) > 0) {
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ call id_saveap (id)
+ }
+ }
+ }
+
+ ID_IC(id) = ic
+ if (ic != ic1)
+ call ic_closed (ic1)
+
+ call smw_close (MW(ID_SH(id)))
+ call imunmap (IM(ID_SH(id)))
+ call shdr_close (ID_SH(id))
+end
+
+
+# RI_IMAGE -- Reidentify an image.
+
+procedure ri_image (id, reference, image, crsearch, ans, logfd, nlogfd, pd)
+
+pointer id # ID pointer
+char reference[ARB] # Reference image
+char image[ARB] # Image to be reidentified
+double crsearch # Search radius
+char ans[3] # Interactive?
+int logfd[ARB] # Logfiles
+int nlogfd # Number of logfiles
+pointer pd # Plot file pointer
+
+bool newaps # Add new apertures not in reference?
+bool override # Override previous identifications?
+bool verbose # Verbose output?
+
+int i, loghdr, id_dbcheck()
+double shift, fit_shift, clgetd()
+pointer sp, key, ic, ic1, stp, sid, stpmark
+pointer sthead(), stnext(), stname(), stfind(), id_getap()
+bool clgetb()
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+
+ # Open the image and return if there is an error.
+ call strcpy (image, ID_IMAGE(id), ID_LENSTRING)
+ iferr (call id_map (id)) {
+ call erract (EA_WARN)
+ return
+ }
+ if (ID_DT(id) != NULL)
+ call dtunmap (ID_DT(id))
+
+ newaps = clgetb ("newaps")
+ override = clgetb ("override")
+ verbose = clgetb ("verbose")
+
+ ic = ID_IC(id)
+ if (ans[1] == 'N')
+ ic1 = ic
+ else {
+ call ic_open (ic1)
+ call ic_copy (ic, ic1)
+ }
+
+ loghdr = 2
+ shift = clgetd ("shift")
+
+ # For MULTISPEC search the reference list of each aperture. If
+ # a reference of the same aperture is not found and the newaps
+ # flag is set use the initial reference and then add the
+ # reidentification to the reference list.
+ # For NDSPEC apply each reference to the image.
+
+ stp = ID_STP(id)
+ call stmark (stp, stpmark)
+ if (SMW_FORMAT(MW(ID_SH(id))) == SMW_ES ||
+ SMW_FORMAT(MW(ID_SH(id))) == SMW_MS) {
+ for (i=1; i<=ID_MAXLINE(id,1); i=i+1) {
+ ID_AP(id,1) = Memi[ID_APS(id)+i-1]
+ ID_AP(id,2) = 1
+ sid = id_getap (id)
+ if (sid == NULL) {
+ if (!newaps) {
+ if (verbose) {
+ call printf (
+ "%s: Reference for aperture %d not found\n")
+ call pargstr (image)
+ call pargi (ID_AP(id,1))
+ }
+ next
+ }
+ if (crsearch != 0.)
+ ID_NFEATURES(id) = 0
+ }
+ ID_LINE(id,1) = i
+
+ if (i == 1 && ic != ic1)
+ call ic_copy (ic, ic1)
+
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+
+ fit_shift = shift
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) > 0) {
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ if (sid == NULL && newaps) {
+ call id_saveap (id)
+ if (verbose) {
+ call printf (
+ "%s: New reference for aperture %d\n")
+ call pargstr (image)
+ call pargi (ID_AP(id,1))
+ }
+ }
+ }
+ ID_IC(id) = ic
+ }
+
+ } else {
+
+ # Go through the stored reference solutions.
+ # Because the symbol table might be changed in ri_reidentify
+ # save the key to restore the symbol pointer.
+
+ for (sid=sthead(stp); sid!=NULL; sid=stnext(stp,sid)) {
+ call strcpy (Memc[stname(stp,sid)], Memc[key], SZ_LINE)
+ call id_gid (id, sid)
+ if (i == 1 && ic != ic1)
+ call ic_copy (ic, ic1)
+
+ if (!override)
+ if (id_dbcheck (id, ID_IMAGE(id), ID_AP(id,1)) == YES)
+ next
+
+ ID_IC(id) = ic1
+ call id_gdata (id)
+ iferr (call id_fitdata (id))
+ ;
+
+ call ri_loghdr (id, reference, logfd, nlogfd, loghdr)
+ loghdr = 0
+
+ fit_shift = shift
+ call ri_reidentify (id, fit_shift, crsearch, ans, logfd,
+ nlogfd, pd)
+
+ if (ID_NFEATURES(id) > 0)
+ call id_dbwrite (id, ID_IMAGE(id), ID_AP(id,1), NO)
+ ID_IC(id) = ic
+ sid = stfind (stp, Memc[key])
+ }
+ if (sid == NULL)
+ ID_NFEATURES(id) = 0
+ }
+
+ ID_IC(id) = ic
+ if (ic != ic1)
+ call ic_closed (ic1)
+ call stfree (stp, stpmark)
+ call smw_close (MW(ID_SH(id)))
+ call imunmap (IM(ID_SH(id)))
+ call shdr_close (ID_SH(id))
+ call sfree (sp)
+end
+
+
+# RI_REIDENTIFY -- Reidentify features using a reference image database entry.
+
+procedure ri_reidentify (id, fit_shift, crsearch, ans, logfd, nlogfd, pd)
+
+pointer id # ID pointer
+double fit_shift # Shift in fit coords (input and output)
+double crsearch # Search radius
+char ans[3] # Interactive?
+int logfd[ARB] # Logfiles
+int nlogfd # Number of logfiles
+pointer pd # Plot file pointer
+
+int i, j, nfeatures1, nfeatures2, nfit, iden, mono, clgwrd()
+double shift, pix_shift, z_shift
+double clgetd(), id_fitpt(), fit_to_pix()
+double id_shift(), id_shift1(), id_center(), id_rms()
+pointer sp, str, pix, fit
+bool clgetb()
+errchk id_shift, id_shift1
+
+begin
+ call smark (sp)
+
+ # Add features or determine a shift.
+ nfeatures1 = ID_NFEATURES(id)
+ if (nfeatures1 == 0) {
+ call salloc (str, SZ_LINE, TY_CHAR)
+ ID_FTYPE(id) =
+ clgwrd ("identify.ftype", Memc[str], SZ_LINE, FTYPES)
+ ID_FWIDTH(id) = clgetd ("identify.fwidth")
+ if (crsearch != 0.)
+ shift = id_shift (id, crsearch, -0.05D0)
+ else if (clgetb ("addfeatures")) {
+ call id_linelist (id)
+ shift = 0.
+ }
+ } else if (IS_INDEFD(fit_shift)) {
+ ID_FWIDTH(id) = FWIDTH(id,1)
+ ID_FTYPE(id) = FTYPE(id,1)
+ if (IS_INDEFD(crsearch))
+ shift = id_shift1 (id)
+ else if (crsearch != 0.)
+ shift = id_shift (id, crsearch, -0.02D0)
+ else
+ shift = 0.
+ } else
+ shift = fit_shift
+
+ nfeatures1 = ID_NFEATURES(id)
+ if (nfeatures1 == 0)
+ call error (0, "No features in reference")
+ call salloc (pix, nfeatures1, TY_DOUBLE)
+ call salloc (fit, nfeatures1, TY_DOUBLE)
+ call amovd (PIX(id,1), Memd[pix], nfeatures1)
+ call amovd (FIT(id,1), Memd[fit], nfeatures1)
+
+ # For each reference feature a shift is added to bring the pixel
+ # position near that for the image being identified and then the
+ # centering algorithm is used. If the centering algorithm fails
+ # the feature is discarded. A mean shift is computed for the
+ # features which have been reidentified.
+
+ do i = 1, ID_NFEATURES(id) {
+ PIX(id,i) = fit_to_pix (id, FIT(id,i) + shift)
+ PIX(id,i) = id_center (id, PIX(id,i), FWIDTH(id,i), FTYPE(id,i))
+ if (!IS_INDEFD(PIX(id,i)))
+ FIT(id,i) = id_fitpt (id, PIX(id,i))
+ }
+ for (i=1; i<ID_NFEATURES(id); i=i+1) {
+ if (IS_INDEFD(PIX(id,i)))
+ next
+ for (j=i+1; j<=ID_NFEATURES(id); j=j+1) {
+ if (IS_INDEFD(PIX(id,j)))
+ next
+ if (abs (PIX(id,i)-PIX(id,j)) < ID_MINSEP(id)) {
+ if (abs (FIT(id,i)-USER(id,i)) < abs (FIT(id,j)-USER(id,j)))
+ PIX(id,j) = INDEFD
+ else {
+ PIX(id,i) = INDEFD
+ break
+ }
+ }
+ }
+ }
+
+ pix_shift = 0.
+ fit_shift = 0.
+ z_shift = 0.
+ j = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD(PIX(id,i)))
+ next
+
+ pix_shift = pix_shift + PIX(id,i) - Memd[pix+i-1]
+ fit_shift = fit_shift + FIT(id,i) - Memd[fit+i-1]
+ if (Memd[fit+i-1] != 0.)
+ z_shift = z_shift + (FIT(id,i) - Memd[fit+i-1]) / Memd[fit+i-1]
+
+ j = j + 1
+ PIX(id,j) = PIX(id,i)
+ FIT(id,j) = FIT(id,i)
+ USER(id,j) = USER(id,i)
+ WTS(id,j) = WTS(id,i)
+ FWIDTH(id,j) = FWIDTH(id,i)
+ FTYPE(id,j) = FTYPE(id,i)
+ }
+ ID_NFEATURES(id) = j
+
+ nfeatures2 = j
+ pix_shift = pix_shift / max (1, ID_NFEATURES(id))
+ fit_shift = fit_shift / max (1, ID_NFEATURES(id))
+ z_shift = z_shift / max (1, ID_NFEATURES(id))
+
+ # If refitting the coordinate function is requested and there is
+ # more than one feature and there is a previously defined
+ # coordinate function then refit. Otherwise compute a coordinate
+ # shift.
+
+ mono = YES
+ if (ID_REFIT(id)==YES && ID_CV(id)!=NULL && ID_NFEATURES(id)>1) {
+ if (clgetb("addfeatures") && abs(pix_shift) > 0.1*ID_NPTS(id)) {
+ call id_doshift (id, NO)
+ ID_NEWFEATURES(id) = YES
+ } else
+ call id_dofit (id, NO)
+ } else
+ call id_doshift (id, NO)
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata (id))
+ mono = NO
+ call id_fitfeatures (id)
+ }
+
+ if (clgetb ("addfeatures")) {
+ ID_FWIDTH(id) = FWIDTH(id,1)
+ ID_FTYPE(id) = FTYPE(id,1)
+ call id_linelist (id)
+ if (ID_NEWFEATURES(id) == YES) {
+ if (ID_REFIT(id) == YES && ID_CV(id) != NULL)
+ call id_dofit (id, NO)
+ else
+ call id_doshift (id, NO)
+ if (ID_NEWCV(id) == YES) {
+ iferr (call id_fitdata (id))
+ mono = NO
+ call id_fitfeatures (id)
+ }
+ }
+ }
+
+ # Enter fitting interactively.
+ iden = NO
+ if ((ID_NFEATURES(id)>1) && (ID_CV(id)!=NULL)) {
+ if (ans[1] != 'N') {
+ if (ans[1] != 'Y') {
+ nfit = 0
+ for (j=1; j<=ID_NFEATURES(id); j=j+1)
+ if (WTS(id,j) > 0.)
+ nfit = nfit + 1
+ call printf (
+ "%s%s%23t%3d/%-3d %3d/%-3d %9.3g %10.3g %7.3g %7.3g\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargi (nfeatures2)
+ call pargi (nfeatures1)
+ call pargi (nfit)
+ call pargi (ID_NFEATURES(id))
+ call pargd (pix_shift)
+ call pargd (fit_shift)
+ call pargd (z_shift)
+ call pargd (id_rms(id))
+ call flush (STDOUT)
+ repeat {
+ ifnoerr (i = clgwrd ("answer", ans, SZ_FNAME,
+ "|no|yes|NO|YES|"))
+ break
+ }
+ call clpstr ("answer", ans)
+ }
+ switch (ans[1]) {
+ case 'y', 'Y':
+ mono = YES
+ i = ID_REFIT(id)
+ call reidentify (id)
+ ID_REFIT(id) = i
+ iden = YES
+ }
+ if (ans[1] != 'Y')
+ call gdeactivate (ID_GP(id), 0)
+ }
+ }
+
+ # Record log information if a log file descriptor is given.
+ for (i = 1; i <= nlogfd; i = i + 1) {
+ if (ans[1] == 'n' && logfd[i] == STDOUT)
+ next
+ nfit = 0
+ for (j=1; j<=ID_NFEATURES(id); j=j+1)
+ if (WTS(id,j) > 0.)
+ nfit = nfit + 1
+ call fprintf (logfd[i],
+ "%s%s%23t%3d/%-3d %3d/%-3d %9.3g %10.3g %7.3g %7.3g\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargi (nfeatures2)
+ call pargi (nfeatures1)
+ call pargi (nfit)
+ call pargi (ID_NFEATURES(id))
+ call pargd (pix_shift)
+ call pargd (fit_shift)
+ call pargd (z_shift)
+ call pargd (id_rms(id))
+ if (mono == NO)
+ call fprintf (logfd[i], "Non-monotonic dispersion function")
+ call flush (logfd[i])
+ if (logfd[i] == STDOUT)
+ iden = NO
+ }
+ # Print log if STDOUT is not used but if the IDENTIFY is done.
+ if (iden == YES) {
+ call printf (
+ "%s%s%23t%3d/%-3d %3d/%-3d %9.3g %10.3g %7.3g %7.3g\n")
+ call pargstr (ID_IMAGE(id))
+ call pargstr (ID_SECTION(id))
+ call pargi (nfeatures2)
+ call pargi (nfeatures1)
+ call pargi (nfit)
+ call pargi (ID_NFEATURES(id))
+ call pargd (pix_shift)
+ call pargd (fit_shift)
+ call pargd (z_shift)
+ call pargd (id_rms(id))
+ if (mono == NO)
+ call printf ("Non-monotonic dispersion function")
+ call flush (STDOUT)
+ }
+
+ # Make log plot.
+ call ri_plot (id, pd)
+
+ call sfree (sp)
+end
+
+
+# RI_LOGHDR -- Print a log header in the log files.
+
+procedure ri_loghdr (id, reference, logfd, nlogfd, flag)
+
+pointer id # Identify structure
+char reference[ARB] # Reference image
+int logfd[ARB] # Log file descriptors
+int nlogfd # Number of log files
+int flag # Header type flag (1=banner, 2=Column labels, 3=Error)
+
+int i
+pointer str
+
+begin
+ for (i = 1; i <= nlogfd; i = i + 1) {
+ switch (flag) {
+ case 1: # Print ID
+ call malloc (str, SZ_LINE, TY_CHAR)
+ call sysid (Memc[str], SZ_LINE)
+ call fprintf (logfd[i], "\nREIDENTIFY: %s\n")
+ call pargstr (Memc[str])
+ call mfree (str, TY_CHAR)
+ case 2: # Print labels
+ call fprintf (logfd[i],
+ " Reference image = %s, New image = %s, Refit = %b\n")
+ call pargstr (reference)
+ call pargstr (ID_IMAGE(id))
+ call pargb (ID_REFIT(id) == YES)
+ call fprintf (logfd[i],
+ "%20s %7s %7s %9s %10s %7s %7s\n")
+ call pargstr ("Image Data")
+ call pargstr ("Found")
+ call pargstr ("Fit")
+ call pargstr ("Pix Shift")
+ call pargstr ("User Shift")
+ call pargstr ("Z Shift")
+ call pargstr ("RMS")
+ case 3: # Error
+ call fprintf (logfd[i], " ** Too many features lost **\n")
+ }
+ }
+end
+
+
+# RI_PLOT -- Plot residual graph of reidentified lines.
+
+procedure ri_plot (id, pd)
+
+pointer id # ID pointer
+pointer pd # GIO pointer
+
+int i, j
+pointer sp, str, x, y, gt, gt_init()
+
+begin
+ # Check if there is anything to plot.
+ if (pd == NULL || ID_NFEATURES(id) == 0)
+ return
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (x, ID_NFEATURES(id), TY_REAL)
+ call salloc (y, ID_NFEATURES(id), TY_REAL)
+
+ # Set plot points.
+ j = 0
+ do i = 1, ID_NFEATURES(id) {
+ if (IS_INDEFD(USER(id,i)))
+ break
+
+ Memr[x+j] = USER(id,i)
+ Memr[y+j] = FIT(id,i) - USER(id,i)
+ j = j + 1
+ }
+
+ if (j == 0) {
+ call sfree (sp)
+ return
+ }
+
+ # Make the plot.
+ call sprintf (Memc[str], SZ_LINE, "Reidentify: %s")
+ call pargstr (ID_IMAGE(id))
+ gt = gt_init ()
+ call gt_sets (gt, GTTYPE, "mark")
+ call gt_sets (gt, GTXLABEL, "user coordinates")
+ call gt_sets (gt, GTYLABEL, "residuals (fit - user)")
+ call gt_sets (gt, GTTITLE, Memc[str])
+ call gclear (pd)
+ call gascale (pd, Memr[x], j, 1)
+ call gascale (pd, Memr[y], j, 2)
+ call gt_swind (pd, gt)
+ call gt_labax (pd, gt)
+ call gt_plot (pd, gt, Memr[x], Memr[y], j)
+ call gt_free (gt)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/irsiids/addsets.par b/noao/onedspec/irsiids/addsets.par
new file mode 100644
index 00000000..1ad84e8b
--- /dev/null
+++ b/noao/onedspec/irsiids/addsets.par
@@ -0,0 +1,8 @@
+# ADDSETS parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+subset,i,h,2,1,,Number of spectra to add together within string
+weighting,b,h,yes,,,Apply integration time weighting to calibrated data
diff --git a/noao/onedspec/irsiids/batchred.cl b/noao/onedspec/irsiids/batchred.cl
new file mode 100644
index 00000000..1bd5a3a3
--- /dev/null
+++ b/noao/onedspec/irsiids/batchred.cl
@@ -0,0 +1,168 @@
+#{ BATCHRED -- Script file to generate another script file
+# which runs several ONEDSPEC tasks in an automated fashion.
+#
+# Currently the following procedures are automated:
+# 1. STANDARD
+# 2. SENSFUNC
+# 3. BSWITCH
+# 4. CALIBRATE
+# 5. ADDSETS
+#
+
+{
+# Say hello to the guy on the other side of the screen and check batch file.
+print ("\n----B A T C H I I D S / I R S F I L E G E N E R A T O R----\n")
+
+s2 = "process.cl" # Batch file to be created.
+if (access (s2)) {
+ print ("A batch file already exists - ")
+ if (query)
+ delete (s2, verify=no)
+}
+
+# Initialize
+rt = input # Root name for spectra
+ot = output # Output root name
+ttl = ">>&'" + ttylog + "')\n" # Log file for tty output
+
+out = rt
+s1 = ""
+st = ""
+sns = ""
+stat = ""
+print ("i = ", start_rec, >>s2)
+
+if (standard) { # STANDARD?
+ print ("\n#---STANDARD---\n")
+ print ("\n#---STANDARD---\n", >>s2)
+
+ st = std # STD file
+ if (access (st)) {
+ print (st, " - already exists")
+ if (query)
+ delete (st, verify=no)
+ }
+
+ # Loop over all stars
+ b1 = yes
+ while (b1) {
+ # Check that the last entry was different - otherwise end input
+ records = ""
+ s3 = records
+ if (s3 == "")
+ b1 = no
+ else {
+ print ("standard (input='",rt,"',output='",st,"',",>>s2)
+ print ("\trecords='",s3,"',",>>s2)
+ print ("\tstar_name='",star_name,"',beam_switch=yes,",>>s2)
+ print ("\tsamestar=yes,apertures='',bandwidth=INDEF,",>>s2)
+ print ("\tbandsep=INDEF,interact=no,",ttl,>>s2)
+ }
+ }
+
+ print ("")
+}
+
+
+if (sensfunc) { # SENSFUNC?
+ print ("\n#---SENSFUNC---\n")
+ print ("\n#---SENSFUNC---\n", >>s2)
+
+ if (st == "")
+ st = std # STD file
+ sns = sensitivity # Sensitivity image
+ stat = stats # Statistics file
+
+ print ("\nsensfunc (standards='",st,"',sensitivity='",sns,"',",>>s2)
+ print ("\tlogfile='",stat,"',apertures='',ignoreaps=no,",>>s2)
+ print ("\tfunction='",function,"',order=",order,",",>>s2)
+ print ("\tinteract=no,",ttl,>>s2)
+
+ print ("")
+}
+
+
+if (bswitch) { # BSWITCH?
+ print ("\n#---BSWITCH---\n")
+ print ("\n#---BSWITCH---\n", >>s2)
+
+ # Save starting output record number
+ in = out
+ out = "b" // ot
+ wt = weight # Weighting?
+ if (stat == "")
+ stat = stats # Statistics file
+
+ # Accumulate records
+ print ("next_rec = i", >>s2)
+ b1 = yes
+ while (b1) {
+ records = ""
+ s3 = records
+ if (s3 == "")
+ b1 = no
+ else {
+ print ("j = next_rec", >> s2)
+ print ("bswitch (input='",in,"',output='",out,"',",>>s2)
+ print ("\trecords='",s3,"',stats='",stat,"',",>>s2)
+ print ("\tweighting=",wt,",subset=",subset,",",>>s2)
+ print ("\tstart_rec=j,",>>s2)
+ print ("\twave1=",wave1,",wave2=",wave2,",",ttl,>>s2)
+ }
+ }
+
+ # Output records
+ print ("j = next_rec", >>s2)
+ s1 = "str (i) // '-' // str(j-1)"
+ print ("s1 = ", s1, >>s2)
+
+ print ("")
+}
+
+if (calibrate) { # CALIBRATE?
+ print ("\n#---CALIBRATE---\n")
+ print ("\n#---CALIBRATE---\n", >>s2)
+
+ in = out
+ out = "c" // ot
+ if (sns == "")
+ sns = sensitivity # Sensivity file name
+
+ if (s1 == "") {
+ records = ""
+ s1 = records
+ print ("s1 = '", s1, "'", >>s2)
+ }
+
+ print ("calibrate (input='",in,"',output='",out,"',records=s1,",>>s2)
+ print ("\tignoreaps=no,",>>s2)
+ print ("\textinct=no,flux=yes,",>>s2)
+ print ("\tsensitivity='",sns,"',fnu=",fnu,",",ttl,>>s2)
+
+ print ("")
+}
+
+if (addsets) { # ADDSETS?
+ print ("\n#---ADDSETS---\n")
+ print ("\n#---ADDSETS---\n", >>s2)
+
+ in = out
+ out = "a" // ot
+ if (s1 == "") {
+ records = ""
+ s1 = records
+ print ("s1 = '", s1, "'", >>s2)
+ }
+
+ print ("addsets (input='",in,"',output='",out,"',records=s1,",>>s2)
+ print ("\tstart_rec=i,subset=2,",ttl,>>s2)
+}
+
+# All done with generator. Ask whether to execute it.
+print ("File generation complete - filename=",s2)
+if (proceed == no) # Execute batch file?
+ bye
+}
+
+# Execute generated batch file
+process &
diff --git a/noao/onedspec/irsiids/batchred.par b/noao/onedspec/irsiids/batchred.par
new file mode 100644
index 00000000..dbb61b8c
--- /dev/null
+++ b/noao/onedspec/irsiids/batchred.par
@@ -0,0 +1,38 @@
+# BATCHRED -- Parameter file for batch reduction prep task
+
+input,s,a,,,,Input root name for spectra
+output,s,a,,,,Output root name for spectra
+start_rec,i,a,,0,9999,Starting record for output spectra
+ttylog,s,a,"ttylog",,,File name to contain a log of terminal output
+standard,b,a,yes,,,Generate commands for STANDARD
+sensfunc,b,a,yes,,,Generate commands for SENSFUNC
+bswitch,b,a,yes,,,Generate commands for BSWITCH
+calibrate,b,a,yes,,,Generate commands for CALIBRATE
+addsets,b,a,yes,,,Generate commands for ADDSETS
+
+std,s,a,"std",,,STANDARD and SENSFUNC standard star file
+star_name,s,q,,,,STANDARD star name
+stats,s,a,"stats",,,SENSFUNC and BSWITCH statistics file
+sensitivity,s,a,sens,,,SENSFUNC and CALIBRATE sensitivity spectra
+weight,b,a,no,,,BSWITCH weighted averages?
+function,s,h,"chebyshev",,,SENSFUNC fitting function
+order,i,h,7,1,,SENSFUNC fitting order
+
+records,s,q,,,,Record string to process
+proceed,b,q,yes,,,Begin batch processing
+query,b,q,no,,,Delete files(s)?
+
+fnu,b,h,no
+wave1,r,h,0.0
+wave2,r,h,0.0
+subset,i,h,32767
+
+rt,s,h
+ot,s,h
+in,s,h
+out,s,h
+stat,s,h
+sns,s,h
+st,s,h
+wt,b,h
+ttl,s,h
diff --git a/noao/onedspec/irsiids/bplot.cl b/noao/onedspec/irsiids/bplot.cl
new file mode 100644
index 00000000..53ce4cfc
--- /dev/null
+++ b/noao/onedspec/irsiids/bplot.cl
@@ -0,0 +1,35 @@
+# BPLOT -- Batch plotting of spectra with SPLOT
+
+procedure bplot (images, records)
+
+string images {prompt="List of images to plot"}
+string records = "" {prompt="List of records to plot"}
+string graphics = "stdgraph" {prompt="Graphics output device"}
+string cursor = "onedspec$gcurval.dat" {prompt="Cursor file(s)\n"}
+
+struct *ilist, *clist
+
+begin
+ int line, ap
+ file ifile, cfile, cur, image
+
+ ifile = mktemp ("bplot")
+ cfile = mktemp ("bplot")
+
+ names (images, records, >& ifile)
+ files (cursor, > cfile)
+ cur = ""
+
+ ilist = ifile; clist = cfile
+ while (fscan (ilist, image) != EOF) {
+ if ((cursor != "") && (fscan (clist, cur) == EOF)) {
+ clist = cfile
+ line = fscan (clist, cur)
+ }
+ splot (image, graphics=graphics, cursor=cur)
+ }
+ clist = ""; ilist = ""
+
+ delete (ifile, verify=no)
+ delete (cfile, verify=no)
+end
diff --git a/noao/onedspec/irsiids/bswitch.par b/noao/onedspec/irsiids/bswitch.par
new file mode 100644
index 00000000..3a8b7e8f
--- /dev/null
+++ b/noao/onedspec/irsiids/bswitch.par
@@ -0,0 +1,15 @@
+# BSWITCH parameter file
+
+input,s,a,,,,Input spectra file root name
+records,s,a,,,,Ranges of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+stats,s,a,"stats",,,File to contain statistics summary
+ids_mode,b,h,yes,,,Are data in quadruples
+extinct,b,h,yes,,,Apply de-extinction correction
+weighting,b,h,no,,,Apply statistical weights during addition
+subset,i,h,32767,1,,Generate sums at subset intervals
+wave1,r,h,0.0,,,Starting wavelength to accumulate stats
+wave2,r,h,0.0,,,Ending wavelength to accumulate stats
+observatory,s,h,"kpno",,,Observatory of data
+extinction,s,h,)_.extinction,,,Extinction file
diff --git a/noao/onedspec/irsiids/coefs.par b/noao/onedspec/irsiids/coefs.par
new file mode 100644
index 00000000..dbabec65
--- /dev/null
+++ b/noao/onedspec/irsiids/coefs.par
@@ -0,0 +1,3 @@
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+database,s,h,"database",,,IDENTIFY database
diff --git a/noao/onedspec/irsiids/coincor.par b/noao/onedspec/irsiids/coincor.par
new file mode 100644
index 00000000..a0f2e0bb
--- /dev/null
+++ b/noao/onedspec/irsiids/coincor.par
@@ -0,0 +1,9 @@
+# COINCOR parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+ccmode,s,h,)_.ccmode,,,Correction mode (photo|iids)
+deadtime,r,h,)_.deadtime,,,Deadtime in seconds
+power,r,h,)_.power,,,IIDS power law coefficient
diff --git a/noao/onedspec/irsiids/coincor.x b/noao/onedspec/irsiids/coincor.x
new file mode 100644
index 00000000..df572c02
--- /dev/null
+++ b/noao/onedspec/irsiids/coincor.x
@@ -0,0 +1,123 @@
+# Coincidence correction options
+define CC_PHOTO_MODE 1 # Photometer style correction
+define CC_IIDS_MODE 2 # IIDS style
+define CC_POWER_MODE 3 # Power law correction
+define CC_USER_MODE 4 # User supplies a function
+
+
+# COINCOR -- Coincidence correction for detector deadtime
+
+procedure coincor (input, output, npts, expo, coflag, dt, power, mode)
+
+real input[npts]
+real output[npts]
+real expo
+int coflag
+real dt
+real power
+int mode, npts
+
+begin
+ # Check that exposure time is legit
+ if (expo <= 0.0)
+ return
+
+ # Select the method by which the correction is performed
+ switch (mode) {
+ case CC_PHOTO_MODE:
+ # Photoelectric photometer
+ call ccphoto (input, output, npts, expo, coflag, dt)
+
+ case CC_IIDS_MODE:
+ # IIDS style correction
+ if (coflag == -1) {
+ call cciids (input, output, npts, expo, coflag, dt)
+ if (power != 1.0)
+ call ccpower (output, output, npts, expo, coflag,
+ power)
+ } else if ((coflag == 0) && (power != 1.0))
+ call ccpower (input, output, npts, expo, coflag, power)
+ else
+ call amovr (input, output, npts)
+
+ case CC_USER_MODE:
+ # Provided by the user
+ call ccuser (input, output, npts, expo, coflag, dt)
+ }
+end
+
+# CCPHOTO -- Photoelectric photometer coincidence correction
+
+procedure ccphoto (input, output, npts, expo, coflag, dt)
+
+real input[npts], output[npts], expo, dt
+int coflag
+int npts
+
+int i
+
+begin
+ do i = 1, npts
+ output[i] = input[i] * exp (input[i] * dt / expo)
+ coflag = 2
+end
+
+# CCUSER -- User supplied correction scheme
+
+procedure ccuser (input, output, npts, expo, coflag, dt)
+
+real input[npts], output[npts], expo, dt
+int coflag
+int npts
+
+begin
+ coflag = 3
+end
+
+# CCIIDS -- IIDS style correction scheme
+# From Instrumentation for Astronomy III (SPIE Vol 172) p.88 by Larry Goad
+#
+# Note that only the "Detect" mode of observation is supported.
+
+procedure cciids (input, output, npts, expo, coflag, dt)
+
+real input[npts], output[npts], expo, dt
+int npts, coflag
+
+int i
+real tsweep, value
+
+begin
+ # Allow tsweep to be the deadtime so that a different value
+ # may be entered for other instruments.
+ # For the IIDS, tsweep = 1.424e-3 sec
+ tsweep = dt
+
+ do i = 1, npts {
+ value = 1 - input[i] / expo * tsweep
+ if ((value < 0.) || (value > 1.))
+ output[i] = input[i]
+ else
+ output[i] = -expo * log (value)/ tsweep
+ }
+ coflag = 0
+end
+
+# CCPOWER -- Power law correction
+# Power law correction from Massey and De Veny, NOAO Newsletter #6.
+
+procedure ccpower (input, output, npts, expo, coflag, power)
+
+real input[npts], output[npts], expo, power
+int npts, coflag
+
+int i
+
+begin
+ do i = 1, npts
+ if (input[i] > 0.)
+ output[i] = expo * (input[i] / expo) ** power
+ else
+ output[i] = input[i]
+ coflag = 1
+end
diff --git a/noao/onedspec/irsiids/conversion.x b/noao/onedspec/irsiids/conversion.x
new file mode 100644
index 00000000..9d0b8c15
--- /dev/null
+++ b/noao/onedspec/irsiids/conversion.x
@@ -0,0 +1,213 @@
+define MAX_CHARS 256
+
+
+# ASCII_TO_EBCDIC -- Vector procedure to convert ASCII characters to EBCDIC
+# characters using the lookup table atoe.
+
+procedure ascii_to_ebcdic (inbuffer, outbuffer, nchars)
+
+char inbuffer[ARB]
+short outbuffer[ARB], atoe[MAX_CHARS]
+int l, nchars
+
+data (atoe[l], l = 1, 8) / 0b, 1b, 2b, 3b, '7' , '-' , '.' , '/' /
+data (atoe[l], l = 9, 16) /26b, 5b, '%' , 13b, 14b, 15b, 16b, 17b /
+data (atoe[l], l = 17, 24) /20b, 21b, 22b, 23b, '<' , '=' , '2' , '&' /
+data (atoe[l], l = 25, 32) /30b, 31b, '?' , '\'', 34b, 35b, 36b, 37b /
+data (atoe[l], l = 33, 40) /'@' , 'O' , 177b, '{' , '[' , 'l' , 'P' , '}' /
+data (atoe[l], l = 41, 48) /'M' , ']' , '\\' , 'N' , 'k' , '`' , 'K' , 'a'/
+data (atoe[l], l = 49, 56) /360b, 361b, 362b, 363b, 364b, 365b, 366b, 367b/
+data (atoe[l], l = 57, 64) /370b, 371b, 'z' , '^' , 'L' , '~' , 'n' , 'o' /
+data (atoe[l], l = 65, 72) /'|' , 301b, 302b, 303b, 304b, 305b, 306b, 307b/
+data (atoe[l], l = 73, 80) /310b, 311b, 321b, 322b, 323b, 324b, 325b, 326b/
+data (atoe[l], l = 81, 88) /327b, 330b, 331b, 342b, 343b, 344b, 345b, 346b/
+data (atoe[l], l = 89, 96) /347b, 350b, 351b, 'J' , 340b, 'Z' , '_' , 'm' /
+data (atoe[l], l = 97, 104) /'y' , 201b, 202b, 203b, 204b, 205b, 206b, 207b/
+data (atoe[l], l = 105, 112) /210b, 211b, 221b, 222b, 223b, 224b, 225b, 226b/
+data (atoe[l], l = 113, 120) /227b, 230b, 231b, 242b, 243b, 244b, 245b, 246b/
+data (atoe[l], l = 121, 128) /247b, 250b, 251b, 300b, 'j' , 320b, 241b, 7b/
+data (atoe[l], l = 129, 136) /' ' , '!' , '"' , '#' , '$' , 25b, 6b, 27b/
+data (atoe[l], l = 137, 144) /'(' , ')' , '*' , '+' , ',' , 11b, 12b, 33b/
+data (atoe[l], l = 145, 152) /'0' , '1' , 32b, '3' , '4' , '5' , '6' , 10b/
+data (atoe[l], l = 153, 160) /'8' , '9' , ':' , ';' , 4b, 24b, '>' , 341b/
+data (atoe[l], l = 161, 168) /'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' /
+data (atoe[l], l = 169, 176) /'I' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' /
+data (atoe[l], l = 177, 184) /'X' , 'Y' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' /
+data (atoe[l], l = 185, 192) /'h' , 'i' , 'p' , 'q' , 'r' , 's' , 't' , 'u' /
+data (atoe[l], l = 193, 200) /'v' , 'w' , 'x' , 200b, 212b, 213b, 214b, 215b/
+data (atoe[l], l = 201, 208) /216b, 217b, 220b, 232b, 233b, 234b, 235b, 236b/
+data (atoe[l], l = 209, 216) /237b, 240b, 252b, 253b, 254b, 255b, 256b, 257b/
+data (atoe[l], l = 217, 224) /260b, 261b, 262b, 263b, 264b, 265b, 266b, 267b/
+data (atoe[l], l = 225, 232) /270b, 271b, 272b, 273b, 274b, 275b, 276b, 277b/
+data (atoe[l], l = 233, 240) /312b, 313b, 314b, 315b, 316b, 317b, 332b, 333b/
+data (atoe[l], l = 241, 248) /334b, 335b, 336b, 337b, 352b, 353b, 354b, 355b/
+data (atoe[l], l = 249, 256) /356b, 357b, 372b, 373b, 374b, 375b, 376b, 377b/
+
+begin
+ call alutcs (inbuffer, outbuffer, nchars, atoe)
+end
+
+# EBCDIC_TO_ASCII -- Vector procedure to convert EBCDIC characters to ASCII
+# characters.
+
+procedure ebcdic_to_ascii (inbuffer, outbuffer, nchars)
+
+char outbuffer[ARB]
+short inbuffer[ARB], etoa[MAX_CHARS]
+int l, nchars
+
+data (etoa[l], l = 1, 8) / 0b, 1b, 2b, 3b, 234b, 11b, 206b, 177b /
+data (etoa[l], l = 9, 16) /227b, 215b, 216b, 13b, 14b, 15b, 16b, 17b/
+data (etoa[l], l = 17, 24) /20b, 21b, 22b, 23b, 235b, 205b, 10b, 207b /
+data (etoa[l], l = 25, 32) /30b, 31b, 222b, 217b, 34b, 35b, 36b, 37b /
+data (etoa[l], l = 33, 40) /200b, 201b, 202b, 203b, 204b, 12b, 27b, 33b/
+data (etoa[l], l = 41, 48) /210b, 211b, 212b, 213b, 214b, 5b, 6b, 7b/
+data (etoa[l], l = 49, 56) /220b, 221b, 26b, 223b, 224b, 225b, 226b, 4b/
+data (etoa[l], l = 57, 64) /230b, 231b, 232b, 233b, 24b, 25b, 236b, 32b/
+data (etoa[l], l = 65, 72) /' ' , 240b, 241b, 242b, 243b, 244b, 245b, 246b/
+data (etoa[l], l = 73, 80) /247b, 250b, '[' , '.' , '<' , '(' , '+' , '!' /
+data (etoa[l], l = 81, 88) /'&' , 251b, 252b, 253b, 254b, 255b, 256b, 257b/
+data (etoa[l], l = 89, 96) /260b, 261b, ']' , '$' , '*' , ')' , ';' , '^' /
+data (etoa[l], l = 97, 104) /'-' , '/' , 262b, 263b, 264b, 265b, 266b, 267b/
+data (etoa[l], l = 105, 112) /270b, 271b, '|' , ',' , '%' , '_' , '>' , '?' /
+data (etoa[l], l = 113, 120) /272b, 273b, 274b, 275b, 276b, 277b, 300b, 301b/
+data (etoa[l], l = 121, 128) /302b, '`' , ':' , '#' , '@' , '\'' , '=' , '"'/
+data (etoa[l], l = 129, 136) /303b, 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' /
+data (etoa[l], l = 137, 144) /'h' , 'i' , 304b, 305b, 306b, 307b, 310b, 311b/
+data (etoa[l], l = 145, 152) /312b, 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' /
+data (etoa[l], l = 153, 160) /'q' , 'r' , 313b, 314b, 315b, 316b, 317b, 320b/
+data (etoa[l], l = 161, 168) /321b, '~' , 's' , 't' , 'u' , 'v' , 'w' , 'x' /
+data (etoa[l], l = 169, 176) /'y' , 'z' , 322b, 323b, 324b, 325b, 326b, 327b/
+data (etoa[l], l = 177, 184) /330b, 331b, 332b, 333b, 334b, 335b, 336b, 337b/
+data (etoa[l], l = 185, 192) /340b, 341b, 342b, 343b, 344b, 345b, 346b, 347b/
+data (etoa[l], l = 193, 200) /'{' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' /
+data (etoa[l], l = 201, 208) /'H' , 'I' , 350b, 351b, 352b, 353b, 354b, 355b/
+data (etoa[l], l = 209, 216) /'}' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' /
+data (etoa[l], l = 217, 224) /'Q' , 'R' , 356b, 357b, 360b, 361b, 362b, 363b/
+data (etoa[l], l = 225, 232) /'\\', 237b, 'S' , 'T' , 'U' , 'V' , 'W' , 'X' /
+data (etoa[l], l = 233, 240) /'Y' , 'Z' , 364b, 365b, 366b, 367b, 370b, 371b/
+data (etoa[l], l = 241, 248) /'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' /
+data (etoa[l], l = 249, 256) /'8' , '9' , 372b, 373b, 374b, 375b, 376b, 377b/
+
+begin
+ call alutsc (inbuffer, outbuffer, nchars, etoa)
+end
+
+# IBM_TO_ASCII -- Vector procedure for converting IBM characters to ASCII
+# characters.
+
+procedure ibm_to_ascii (inbuffer, outbuffer, nchars)
+
+char outbuffer[ARB]
+short inbuffer[ARB], ibmtoa[MAX_CHARS]
+int l, nchars
+
+data (ibmtoa[l], l = 1, 8) /0b, 1b, 2b, 3b, 234b, 11b, 206b, 177b /
+data (ibmtoa[l], l = 9, 16) /1227b, 215b, 216b, 13b, 14b, 15b, 16b, 17b/
+data (ibmtoa[l], l = 17, 24) /20b, 21b, 22b, 23b, 235b, 205b, 10b, 207b /
+data (ibmtoa[l], l = 25, 32) /30b, 31b, 222b, 217b, 34b, 35b, 36b, 37b /
+data (ibmtoa[l], l = 33, 40) /200b, 201b, 202b, 203b, 204b, 12b, 27b, 33b/
+data (ibmtoa[l], l = 41, 48) /210b, 211b, 212b, 213b, 214b, 5b, 6b, 7b/
+data (ibmtoa[l], l = 49, 56) /220b, 221b, 26b, 223b, 224b, 225b, 226b, 4b/
+data (ibmtoa[l], l = 57, 64) /230b, 231b, 232b, 233b, 24b, 25b, 236b, 32b/
+data (ibmtoa[l], l = 65, 72) /' ' , 240b, 241b, 242b, 243b, 244b, 245b, 246b/
+data (ibmtoa[l], l = 73, 80) /247b, 250b, 0b, '.' , '<' , '(' , '+' , '|' /
+data (ibmtoa[l], l = 81, 88) /'&' , 251b, 252b, 253b, 254b, 255b, 256b, 257b/
+data (ibmtoa[l], l = 89, 96) /260b, 261b, '!' , '$' , '*' , ')' , ';' , '^' /
+data (ibmtoa[l], l = 97, 104) /'-' , '/' , 262b, 263b, 264b, 265b, 266b, 267b/
+data (ibmtoa[l], l = 105,112) /270b, 271b, 0b, ',' , '%' , '_' , '>' , '?' /
+data (ibmtoa[l], l = 113, 120) /272b, 273b, 274b, 275b, 276b, 277b, 300b, 301b/
+data (ibmtoa[l], l = 121, 128) /302b, '`' , ':' , '#' , '@' , '\'' , '=' , '"'/
+data (ibmtoa[l], l = 129, 136) /303b, 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' /
+data (ibmtoa[l], l = 137, 144) /'h' , 'i' , 304b, 305b, 306b, 307b, 310b, 311b/
+data (ibmtoa[l], l = 145, 152) /312b, 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' /
+data (ibmtoa[l], l = 153, 160) /'q' , 'r' , 313b, 314b, 315b, 316b, 317b, 320b/
+data (ibmtoa[l], l = 161, 168) /321b, '~' , 's' , 't' , 'u' , 'v' , 'w' , 'x' /
+data (ibmtoa[l], l = 169, 176) /'y' , 'z' , 322b, 323b, 324b, 325b, 326b, 327b/
+data (ibmtoa[l], l = 177, 184) /330b, 331b, 332b, 333b, 334b, 335b, 336b, 337b/
+data (ibmtoa[l], l = 185, 192) /340b, 341b, 342b, 343b, 344b, 345b, 346b, 347b/
+data (ibmtoa[l], l = 193, 200) /'{' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' /
+data (ibmtoa[l], l = 201, 208) /'H' , 'I' , 350b, 351b, 352b, 353b, 354b, 355b/
+data (ibmtoa[l], l = 209, 216) /'}' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' /
+data (ibmtoa[l], l = 217, 224) /'Q' , 'R' , 356b, 357b, 360b, 361b, 362b, 363b/
+data (ibmtoa[l], l = 225, 232) /'\\', 237b, 'S' , 'T' , 'U' , 'V' , 'W' , 'X' /
+data (ibmtoa[l], l = 233, 240) /'Y' , 'Z' , 364b, 365b, 366b, 367b, 370b, 371b/
+data (ibmtoa[l], l = 241, 248) /'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' /
+data (ibmtoa[l], l = 249, 256) /'8' , '9' , 372b, 373b, 374b, 375b, 376b, 377b /
+
+begin
+ call alutsc (inbuffer, outbuffer, nchars, ibmtoa)
+end
+
+# Vector procedure to convert ASCII characters to ibm characters
+
+procedure ascii_to_ibm (inbuffer, outbuffer, nchars)
+
+char inbuffer[ARB]
+short outbuffer[ARB], atoibm[MAX_CHARS]
+int l, nchars
+
+data (atoibm[l], l = 1, 8) /0b, 1b, 2b, 3b, '7' , '-' , '.' , '/' /
+data (atoibm[l], l = 9, 16) /26b, 5b, '%' , 13b, 14b, 15b, 16b, 17b /
+data (atoibm[l], l = 17, 24) /20b, 21b, 22b, 23b, '<' , '=' , '2' , '&' /
+data (atoibm[l], l = 25, 32) /30b, 31b, '?' , '\'', 34b, 35b, 36b, 37b /
+data (atoibm[l], l = 33, 40) /'@' , 'Z' , 177b, '{' , '[' , 'l' , 'P' , '}' /
+data (atoibm[l], l = 41, 48) /'M' , ']' , '\\', 'N' , 'k' , '`' , 'K' , 'a' /
+data (atoibm[l], l = 49, 56) /360b, 361b, 362b, 363b, 364b, 365b, 366b, 367b/
+data (atoibm[l], l = 57, 64) /370b, 371b, 'z' , '^' , 'L' , '~' , 'n' , 'o' /
+data (atoibm[l], l = 65, 72) /'|' , 301b, 302b, 303b, 304b, 305b, 306b, 307b/
+data (atoibm[l], l = 73, 80) /310b, 311b, 321b, 322b, 323b, 324b, 325b, 326b/
+data (atoibm[l], l = 81, 88) /327b, 330b, 331b, 342b, 343b, 344b, 345b, 346b/
+data (atoibm[l], l = 89, 96) /347b, 350b, 351b, 255b, 340b, 275b, '_' , 'm' /
+data (atoibm[l], l = 97, 104) /'y' , 201b, 202b, 203b, 204b, 205b, 206b, 207b/
+data (atoibm[l], l = 105, 112) /210b, 211b, 221b, 222b, 223b, 224b, 225b, 226b/
+data (atoibm[l], l = 113, 120) /227b, 230b, 231b, 242b, 243b, 244b, 245b, 246b/
+data (atoibm[l], l = 121, 128) /247b, 250b, 251b, 300b, 'O' , 320b, 241b, 7b/
+data (atoibm[l], l = 129, 136) /' ' , '!' , '"' , '#' , '$' , 25b, 6b, 27b/
+data (atoibm[l], l = 137, 144) /'(' , ')' , '*' , '+' , ',' , 11b, 12b, 33b/
+data (atoibm[l], l = 145, 152) /'0' , '1' , 32b, '3' , '4' , '5' , '6' , 10b/
+data (atoibm[l], l = 153, 160) /'8' , '9' , ':' , ';' , 4b, 24b, '>' , 341b/
+data (atoibm[l], l = 161, 168) /'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' /
+data (atoibm[l], l = 169, 176) /'I' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' /
+data (atoibm[l], l = 177, 184) /'X' , 'Y' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' /
+data (atoibm[l], l = 185, 192) /'h' , 'i' , 'p' , 'q' , 'r' , 's' , 't' , 'u' /
+data (atoibm[l], l = 193, 200) /'v' , 'w' , 'x' , 200b, 212b, 213b, 214b, 215b/
+data (atoibm[l], l = 201, 208) /216b, 217b, 220b, 232b, 233b, 234b, 235b, 236b/
+data (atoibm[l], l = 209, 216) /237b, 240b, 252b, 253b, 254b, 255b, 256b, 257b/
+data (atoibm[l], l = 217, 224) /260b, 261b, 262b, 263b, 264b, 265b, 266b, 267b/
+data (atoibm[l], l = 225, 232) /270b, 271b, 272b, 273b, 274b, 275b, 276b, 277b/
+data (atoibm[l], l = 233, 240) /312b, 313b, 314b, 315b, 316b, 317b, 332b, 333b/
+data (atoibm[l], l = 241, 248) /334b, 335b, 336b, 337b, 352b, 353b, 354b, 355b/
+data (atoibm[l], l = 249, 256) /356b, 357b, 372b, 373b, 374b, 375b, 376b, 377b/
+
+begin
+ call alutcs (inbuffer, outbuffer, nchars, atoibm)
+end
+
+# ALUTSC -- Vector operator to map one set of characters to another using a
+# lookup table.
+
+procedure alutsc (a, b, nchars, lut)
+
+char b[nchars]
+int nchars, i
+short a[nchars], lut[ARB]
+
+begin
+ do i = 1, nchars, 1
+ b[i] = lut[a[i] + 1]
+end
+
+# ALUTCS -- Vector operator to map one set of characters to another using
+# a lookup table.
+
+procedure alutcs (a, b, nchars, lut)
+
+char a[nchars]
+int nchars, i
+short b[nchars], lut[ARB]
+
+begin
+ do i = nchars, 1, -1
+ b[i] = lut[a[i] + 1]
+end
diff --git a/noao/onedspec/irsiids/doc/addsets.hlp b/noao/onedspec/irsiids/doc/addsets.hlp
new file mode 100644
index 00000000..6ce49122
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/addsets.hlp
@@ -0,0 +1,66 @@
+.help addsets Feb85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+addsets - Add subsets of a string of spectra
+.ih
+USAGE
+addsets input records
+.ih
+PARAMETERS
+.ls input
+The root file name for the input spectra in the string.
+.le
+.ls records
+The range of spectra indicating the elements of the string.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the names of the spectra which will
+be created by the addset operation.
+.le
+.ls start_rec = 1
+The starting record number to be appended to the root name of the
+created spectra.
+.le
+.ls subset = 2
+The length of the substring of spectra which will be added together.
+For IIDS/IRS data which has been processed through BSWITCH, this
+parameter should be 2. This implies that spectra will be taken
+2 at a time, added, and the sum written as a new spectrum.
+.le
+.ls weighting = yes
+If set to yes, an average of the substring of spectra is generated
+(if flux calibrated) weighted by the integration times of the
+individual spectra. If set to no, a simple average is generated.
+If not flux calibrated, this parameter has no effect - a simple
+sum is generated.
+.le
+.ih
+DESCRIPTION
+Every "subset" group of spectra will be accumulated and the sum will be
+written as a new spectrum. For example, if the input string contains
+100 spectra, and subset=2, then 50 new spectra will be created. Each
+new spectrum will be the sum of the consecutive pairs in the original string.
+
+If there are insufficient spectra to complete a subset accumulation,
+the sum is written out anyway and a warning printed. For example,
+if the input string contains 23 spectra, and subset=4, there will be
+6 new spectra created, but the last one will be based on only 3 spectra.
+
+Subset may be set to 1 to allow a copy operation although this is not
+a very efficient way to do so.
+.ih
+EXAMPLES
+The following three examples are those described above.
+
+.nf
+ cl> addsets nite1 2001-2100
+ cl> addsets nite1 2001-2023 subset=4
+ cl> addsets nite1 2001-2010 subset=1 output=nite2 \
+ >>> start_rec=2001
+.fi
+.ih
+SEE ALSO
+bswitch
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/batchred.hlp b/noao/onedspec/irsiids/doc/batchred.hlp
new file mode 100644
index 00000000..9301f8b0
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/batchred.hlp
@@ -0,0 +1,145 @@
+.help batchred Feb85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+batchred - Automated processing of IIDS/IRS spectra
+.ih
+USAGE
+batchred
+.ih
+PARAMETERS
+This script task has many parameters, but most are used as
+variables internal to the task and are not user parameters.
+There are 5 parameters having similar purposes: standard,
+sensfunc, bswitch, calibrate, and addsets. Each corresponds
+to the ONEDSPEC task of the same name and BATCHRED will generate
+the commands necessary to invoke those tasks if the associated
+parameter is set to yes (the default in all cases).
+
+.ls standard = yes
+.le
+.ls sensfunc = yes
+.le
+.ls bswitch = yes
+.le
+.ls calibrate = yes
+.le
+.ls addsets = yes
+.le
+.ls fnu = no
+This parameter is identical to the fnu parameter for CALIBRATE.
+.le
+.ls wave1 = 0.0
+This parameter is identical to the wave1 parameter for BSWITCH.
+.le
+.ls wave2 = 0.0
+This parameter is identical to the wave2 parameter for BSWITCH.
+.le
+.ls subset = 32767
+This parameter is identical to the subset parameter for BSWITCH.
+.le
+.ih
+DESCRIPTION
+Through a question and answer session, a series of commands to
+ONEDSPEC is generated which are then processed as a batch job
+to reduce "typical" spectra from the IIDS and IRS spectrographs.
+
+By setting the appropriate hidden parameters, the user may
+"turn off" command generation for any of the possible tasks.
+
+A script task is generated having the name "process.cl" which is
+submitted to the CL as the final command of BATCHRED.
+All terminal output which would normally appear during the course
+of running each of the individual tasks is redirected to a log file
+(default=ttylog).
+
+After the script has been generated, the user may suppress running
+the processing task. The script file remains on disk so that subsequent
+cases may be appended, such as when
+several independent runs of data are to be processed in one
+stream (e.g. several nights of data, each to be reduced separately).
+
+The questions which are asked are described below:
+
+"Root name for spectra file names:" This is the input root file name
+for all spectra which will be run through STANDARD and BSWITCH.
+
+"Root name for spectra to be created:" This is the output root file
+name which all newly created spectra will use. It is also the
+input file name for tasks CALIBRATE and ADDSETS since these tasks
+operate on spectra created by BSWITCH.
+
+"Starting record number for spectra to be created:" All created spectra
+will have a suffix number starting with this value and incremented
+by one for each new spectrum created.
+
+"File name to contain statistics information:" This file will contain
+informative output from SENSFUNC and BSWITCH. (default=stats)
+
+"File name to contain a log of terminal output:" All tasks talk back
+to let you know how things are proceding. The backtalk is saved
+in this file. (default=ttylog)
+
+"File name for output from STANDARD and input to SENSFUNC:" Just
+what it says. (default=std)
+
+"Record string to process:" The spectra are assumed to be representable
+by strings (try "help ranges" for details on the formats allowed).
+Both STANDARD and BSWITCH expect ranges of spectral record numbers
+which are appended to the root given in answer to the first question
+above. This question is asked repeatedly so that you can enter as
+many strings of spectra as you like and is ended by hitting return
+without entering a value. There is a short delay after entering
+each string of records while a check is made to verify that all
+your spectra actually exist.
+
+"Standard star name:" For each record string STANDARD expects
+the name of the standard star observed, but it must be given in
+a manner acceptable to STANDARD. (see STANDARD and LCALIB for
+more details).
+
+"Use weighted averages:" If answered yes, then SENSFUNC and BSWITCH
+will use their weighted averaging schemes.
+
+"Apply magnitude fudging:" If answered yes, then SENSFUNC will
+use its "fudge" option. (see SENSFUNC)
+
+"Solve for grey additive extinction constant:" If answered yes, then
+SENSFUNC will solve for this value.
+
+"File name for sensitivity image file:" This will be the root name
+for the output sensitivity spectra from SENSFUNC.
+
+At anytime during the processing phase, you can inquire about the
+progress by listing the latest contents of the file "ttylog"
+either by "type ttylog" or by "tail ttylog". The latter command
+lists the last 12 lines of the file.
+
+Be sure to have all your record strings, standard star names,
+and options well planned and written down so that you can enter
+the answers correctly. The batch reductions are not overly
+tolerant of incorrect entries although some preliminary checks
+are performed during the entry process.
+
+.ih
+EXAMPLES
+
+The following invokes the batch reductions using all task options;
+
+ cl> batchred
+
+The following inhibits the STANDARD and SENSFUNC tasks which must have
+been run previously. This is equivalent to the IPPS "autoreduce":
+
+ cl> batchred standard- sensfunc-
+.ih
+BUGS
+If you make an error while entering the requested information, there
+is no way to effect repairs other than to (1) start all over, or (2) edit
+the generated script file "process.cl" using the system editor.
+
+If a task encounters an irrecoverable error, the background job
+hangs until you kill it using "kill N" where N is the job number.
+.ih
+SEE ALSO
+mkscript, standard, sensfunc, bswitch, calibrate, addsets
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/bswitch.hlp b/noao/onedspec/irsiids/doc/bswitch.hlp
new file mode 100644
index 00000000..a50647b4
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/bswitch.hlp
@@ -0,0 +1,228 @@
+.help bswitch Sep87 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+bswitch - generate sky-subtracted accumulated spectra
+.ih
+USAGE
+bswitch input records
+.ih
+PARAMETERS
+.ls input
+The root name for the input spectra to be beam-switched.
+.le
+.ls records
+The range of spectra to be included in the beam-switch operation.
+Each range item will be appended to the root name to form an image
+name. For example, if "input" is "nite1" and records is "1011-1018",
+then spectra nite1.1011, nite.1012 ... nite1.1018 will be included.
+.le
+.ls output
+New spectra are created by the beam-switch operation. This parameter
+specifies the root name to be used for the created spectra.
+.le
+.ls start_rec = 1
+Each new spectrum created has "output" as its root name and a trailing
+number appended. The number begins with start_rec and is incremented
+for each new spectrum. For example, if "output" is given as "nite1b"
+and start_rec is given as 1001, then new spectra will be created as
+nite1b.1001, nite1b.1002 ...
+.le
+.ls stats = "stats"
+A file by this name will have statistical data appended to it, or created
+if necessary. If a null file name is given (""), no statistical output
+is given. For each aperture, a listing of countrates for each
+observation is given relative to the observation with the highest rate.
+.le
+.ls ids_mode = yes
+If the data are taken under the usual IIDS "beam-switch" mode, this
+parameter should be set to yes so that accumulations will be performed
+in pairs. But if the data are taken where there is no sky observation
+or different numbers of sky observations, ids_mode should be set to no.
+If weighting is in effect, ids_mode=yes implies weighting of the
+object-sky sum; if ids_mode=no, then weighting is applied to the
+object and sky independently because then there is no guarantee that
+an object and sky observation are related.
+.le
+.ls extinct = yes
+If set to yes, a correction for atmospheric extinction is applied.
+The image header must have either a valid entry for AIRMASS or
+for hour angle (or right ascension and sidereal time) and declination.
+.le
+.ls weighting = no
+If set to yes, the entire spectrum or a specified region will be used
+to obtain a countrate indicative of the statistical weight to be
+applied to the spectrum during the accumulations.
+.le
+.ls subset = 32767
+A subset value larger than the number of independent spectra to be
+added indicates that the operation is to produce a single spectrum
+for each aperture regardless of how many input spectra are entered.
+If subset is a smaller number, say 4, then the accumulations
+are written out after every 4 spectra and then re-initialized to zero
+for the next 4.
+.le
+.ls wave1 = 0.0
+If weighting=yes, this parameter indicates the starting point in the
+spectrum for the countrate to be assessed. For emission-line objects,
+this is particularly useful because the regime of information is then
+confined to a narrow spectral region rather than the entire spectrum.
+Defaults to the beginning of the spectrum.
+.le
+.ls wave2 = 0.0
+This provides the ending wavelength for the countrate determination.
+Defaults to the endpoint of the spectrum.
+.le
+.ls observatory = "observatory"
+Observatory at which the spectra were obtained if
+not specified in the image header by the keyword OBSERVAT. The
+observatory may be one of the observatories in the observatory
+database, "observatory" to select the observatory defined by the
+environment variable "observatory" or the task \fBobservatory\fR, or
+"obspars" to select the current parameters set in the \fBobservatory\fR
+task. See help for \fBobservatory\fR for additional information.
+.le
+.ls extinction = ")_.extinction"
+The the name of the file containing extinction values.
+Required if extinct=yes.
+.le
+.ih
+DESCRIPTION
+Data from multiaperture spectrographs are summed according to
+aperture number and sky subtracted if sky observations are available.
+Data for up to 50 apertures may be simultaneously accumulated.
+The accumulated spectra are written to new images.
+
+The exposure times for each observation may be different. All
+internal computations are performed in terms of count rates,
+and converted back to counts (for statistical analysis) prior to writing
+the new image. Therefore, the time on the sky and object may
+be different as well. When these extensions to the normal
+mode are required, the flag ids_mode must be set to no.
+Then object and sky accumulations are performed totally
+independently and a difference is derived at the conclusion
+of the operation.
+
+If ids_mode is set to yes, then the usual IIDS/IRS "beam-switch"
+observing mode is assumed. This implies that an equal number of
+sky and object spectra are obtained through each aperture
+after 2N spectra have been accumulated, where N is the number
+of instrument apertures (2 for the IIDS/IRS). It is also assumed
+that the object and sky exposure times are equal for each aperture.
+Note that the "nebular" mode (where all instrument apertures
+point at an extended object simultaneously, and then all apertures
+point at sky simultaneously) is an acceptable form for
+beam-switched data in ids_mode.
+
+The accumulations are optionally weighted by the countrate
+over a region of the spectrum to improve the statistics during
+variable conditions. The user may specify the region of spectrum
+by wavelength. In ids_mode, the statistics are obtained from
+object-sky differences; otherwise, the statistics are performed
+on object+sky and sky spectra separately.
+
+The spectra may be extinction corrected if this has not already
+been performed.
+In order to perform either the extinction correction or the
+weighting process, the spectra must have been placed on a linear
+wavelength scale (or linear in the base 10 logarithm).
+
+Strings of spectra are accumulated to produce a single
+summed spectrum for each observing aperture. But in some cases
+it is desirable to produce summed spectra from subsets of the
+entire string to evaluate the presence of variations either due
+to observing conditions or due to the physical nature of the
+object. A subset parameter may be set to the frequency at which
+spectra are to be summed.
+
+In order that the processing occur with minimal user interaction,
+elements from the extended image header are used to direct the
+flow of operation and to obtain key observing parameters.
+The required parameters are: object/sky flag (OFLAG=1/0), exposure
+time in seconds (ITM), beam (that is, aperture) number (BEAM-NUM), airmass (AIRMASS)
+or alternatively hour angle (HA) and declination (DEC), or
+right ascension (RA), sidereal time (ST), declination (DEC), and the
+observatory (OBSERVAT),
+starting wavelength (W0), and wavelength increment per channel (WPC),
+where the names in parenthesis are the expected keywords in the
+header. If the observatory is not specified in the image the
+observatory parameter is used. See \fBobservatory\fR for further
+details on the observatory database.
+
+The following header flags are used as well: DC_FLAG
+for dispersion corrected data (must=0), BS_FLAG for beam-switching
+(must not be 1 which indicates the operation was already done),
+EX_FLAG for extinction correction (if = 0 extinction is assumed already
+done).
+
+The headers may be listed with the IMHEADER task, setting
+the parameter "long" = yes. The values for the parameters follow
+the rules used for IIDS and IRS data.
+
+After the beam-switch operation, the newly created spectra will
+have header elements taken from the last object spectrum.
+A few parameters will be updated to reflect the operation
+(e.g. integration time, processing flags).
+
+.ih
+EXAMPLES
+The following example will accumulate a series of 16 spectra obtained
+in the normal beam-switched mode and create two new extinction corrected
+spectra having names nite1bs.1 and nite1bs.2:
+
+ cl> bswitch nite1 1011-1026 nite1bs 1
+
+The following example performs the same functions but accumulates the data
+to produce 8 new spectra representing the individual object-sky pairs:
+
+ cl> bswitch nite1 1011-1026 nite1bs 1 subset=4
+
+The following example produces an extinction corrected spectrum for every
+input spectrum. Note that ids_mode is set to off to generate separate object and
+sky sums, and subset is set to 2 so that every pair of spectra (one object and
+one sky) are written out as two new spectra:
+
+ cl> bswitch nite1 1011-1026 nite1bs 1 subset=2 ids_mode-
+
+The next example produces a pair of spectra for each of 3 independent
+objects observed, provided that each was observed for the same number
+of observations (16 in this case).
+
+.nf
+ cl> bswitch nite1 1011-1026,1051-1066,1081-1096 nite1bs 1 \
+ >>> subset=16
+.fi
+
+The next example shows how to use the weighting parameters where
+the indicative flux is derived from the region around the emission-line
+of 5007A.
+
+.nf
+ cl> bswitch nite1 1011-1026 nite1bs 1 weighting- \
+ >>> wave1=4990, wave2=5020
+.fi
+.ih
+TIME REQUIREMENTS
+The principle time expenditure goes toward extinction correcting the
+data. For IIDS type spectra (length=1024 pixels), approximately 30 cpu
+seconds are required to beam-switch a series of 16 spectra.
+.ih
+BUGS
+The number of apertures is restricted to 50 and must be labeled
+between 0 and 49 in the image header (the IIDS uses 0 and 1).
+
+Until an image header editor is available, BSWITCH
+can be applied only to data with properly prepared headers
+such as IIDS/IRS data read by RIDSMTN, RIDSFILE and some data via RFITS.
+
+When used to perform the function of extinction correction only (the
+third example above), the statistics file fails to note the output
+image name for the sky spectrum.
+
+The data must be on a linear wavelength scale.
+The starting wavelength, W0, and a wavelength
+per channel, WPC, are required header information, and the DC_FLAG
+must be set to 0.
+.ih
+SEE ALSO
+observatory, sensfunc, imheader, lcalib, ridsmtn, ridsfile, rfits
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/coefs.hlp b/noao/onedspec/irsiids/doc/coefs.hlp
new file mode 100644
index 00000000..777933bc
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/coefs.hlp
@@ -0,0 +1,57 @@
+.help coefs May85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+coefs -- Extract dispersion coefs from mtn HeNeAr headers
+.ih
+USAGE
+coefs input records database
+.ih
+PARAMETERS
+.ls input
+The input image root name for the spectral images containing the
+dispersion coefficients.
+.le
+.ls records
+The range of records for which the root name applies.
+.le
+.ls database
+The database file name which will contain the coefficients.
+.le
+.ih
+DESCRIPTION
+The spectra specified by the combination of the root name
+and the records are scanned for the presence of dispersion
+coefficients. If present, the coefficients and necessary
+information are written to the file indicated by the database
+parameter. This file an then be used by the linearization
+program DISPCOR to correct any spectra for which the
+database is appropriate.
+
+Each invocation of COEFS appends to the database file, or
+creates a new file if necessary.
+
+The following assumptions are made concerning the coefficients,
+which are always correct for IIDS and IRS mountain reduced
+data at Kitt Peak.
+.ls 5 (1)
+The coefficients represent Legendre polynomials.
+.le
+.ls (2)
+The coefficients apply to pixels 1 through 1024 in the original data.
+.le
+.ih
+EXAMPLES
+The following example reads the coefficients from the headers
+for nite1 arc spectra taken near the beginning and end of the
+night and creates a database file called nite1.db:
+
+ cl> coefs nite1 3-4,201-202 nite1.db
+
+.ih
+TIME REQUIREMENTS
+Approximately 1 second per spectrum is required. This is primarily
+overhead due to file access.
+.ih
+SEE ALSO
+dispcor, identify
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/coincor.hlp b/noao/onedspec/irsiids/doc/coincor.hlp
new file mode 100644
index 00000000..74e002f3
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/coincor.hlp
@@ -0,0 +1,101 @@
+.help coincor Feb87 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+coincor -- Correct detector count rates
+.ih
+USAGE
+coincor input records
+.ih
+PARAMETERS
+.ls input
+The root file name of the input spectra.
+.le
+.ls records
+The range of spectra.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the corrected spectra. If no root name
+is specified (specified with the null string "") then the operation
+is done in place.
+.le
+.ls start_rec = 1
+The starting record number to be appended to the root name of the
+created spectra.
+.le
+.ls ccmode = )_.ccmode
+The mode used to model the detector count rate corrections.
+In the following C(obs) is the observed count rate and C(cor) is the
+corrected count rate.
+.ls "photo"
+Photoelectric photometer with discriminator mode. The count rate
+correction is
+
+ C(cor) = C(obs) * exp (C(obs) * deadtime)
+
+where the parameter \fIdeadtime\fR is the representative deadtime in seconds.
+.le
+.ls "iids"
+IIDS correction given by
+
+ C(cor) = (-ln(1-C(obs)*deadtime)/deadtime)**power
+
+where \fBdeadtime\fR is a parameter related to the sweep time used to
+correct for coincidence losses and \fBpower\fR is a power law coefficient.
+.le
+.le
+.ls deadtime = )_.deadtime
+For the "photo" mode this parameter is the period, in seconds, during
+which no counts can be registered by the detector. Note that this is
+based on a per pixel basis. So if the discriminator dead period is of
+order 50 nanoseconds and 2000 pixels are observed per readout, the
+effective deadtime is about 10E-4 seconds. For the "iids" mode this
+parameter defines the sweep time correction and has a value of 1.424E-3
+seconds.
+.le
+.ls power = )_.power
+The IIDS power law coefficient. The standard value is 0.975.
+.le
+.ih
+DESCRIPTION
+The input spectra are corrected for detector count rate errors. If no
+output root name is given then the operation is done in place. The type
+of correction is specified by the parameter \fIccmode\fR. The available
+modes are for a general photomultiplier with discriminator coincidence
+correction, and the NOAO IIDS. The parameters for these modes are
+\fIdeadtime\fR and \fIpower\fR. The exposure time, in seconds, is a
+required image header parameter (keyword = EXPOSURE).
+
+The default mode is for the NOAO IIDS. The IIDS correction includes a
+power law correction for a nonlinear effect in the IIDS image tube chain
+which is not included by the mountain reduction software at the telescope.
+If the spectra have been coincidence corrected at the telescope
+then only the nonlinear power law correction is applied.
+
+The coincidence correction flag may take the values -1 for no correction,
+0 for the IIDS correction with \fIpower\fR = 1 (the correction
+applied by the mountain reduction software), 1 for the full IIDS
+correction, and 2 for the photomuliplier mode correction.
+.ih
+EXAMPLES
+The following example corrects a series of IIDS spectra:
+
+ cl> coincor nite1 1-250 output=nite1cc start_rec=1
+
+The following example corrects a series of spectra from the
+Lick ITS:
+
+.nf
+ cl> coincor its 1-250 output=itscc start=1 ccmode=photo \
+ >>> deadtime=2.4E-4 power=1
+.fi
+.ih
+TIME REQUIREMENTS
+\fBCoincor\fR requires approximately 1 second per spectrum of length 1024.
+.ih
+SEE ALSO
+.nf
+The \fBimred.iids\fR package is designed for reducing NOAO IIDS spectra.
+.fi
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/extinct.hlp b/noao/onedspec/irsiids/doc/extinct.hlp
new file mode 100644
index 00000000..66aca3d6
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/extinct.hlp
@@ -0,0 +1,49 @@
+.help extinct Apr85 noao.onedspec
+.ih
+NAME
+extinct -- Correct spectra for atmospheric extinction
+.ih
+USAGE
+extinct root records output
+.ih
+PARAMETERS
+.ls root
+The root name for the input spectra to be corrected.
+.le
+.ls records
+The range of spectra to be included in the extinction operation.
+.le
+.ls output
+The root name for the output corrected spectra
+.le
+.ls start_rec
+The starting record number for the output corrected spectra.
+.le
+.ls nr_aps = 2
+The number of instrument apertures for this data set.
+.le
+.ih
+DESCRIPTION
+The input spectra are corrected for atmospheric extinction.
+EXTINCT redirects the spectra through the task BSWITCH so all
+procedures are identical to those described for that task.
+
+Because BSWITCH attempts to perform a beam-switch operation
+unless the subset parameter is equal to the number of
+instrument apertures (in which case beam-switching degenerates
+to a copy operation), the hidden parameter nr_aps should be set
+appropriately to the instrument. For IIDS and IRS data, this
+is 2.
+.ih
+EXAMPLES
+
+ cl> extinct nite1 1001-1032 nite1ex
+.ih
+BUGS
+The input string of spectra must be ordered so that only
+one spectrum from each aperture is present among substrings
+of length nr_aps.
+.ih
+SEE ALSO
+bswitch
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/flatdiv.hlp b/noao/onedspec/irsiids/doc/flatdiv.hlp
new file mode 100644
index 00000000..e6e8c22e
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/flatdiv.hlp
@@ -0,0 +1,94 @@
+.help flatdiv Dec86 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+flatdiv -- Divide spectra by flat field spectra
+.ih
+USAGE
+flatdiv input records
+.ih
+PARAMETERS
+.ls input
+The root file name for the input records to be divided.
+.le
+.ls records
+The range of spectra to be included in the divide operation.
+Each range item will be appended to the root name to form an
+image file name.
+.le
+.ls output
+New spectra are created by the flatdiv operation. This parameter
+specifies the root name to be used for the created spectra.
+.le
+.ls start_rec
+Each new spectrum created as "output" has its root name and a
+trailing number appended starting with "start_rec". Subsequent
+output images will have an incremented trailing number.
+Note that even if an output image is not created because the input
+image has already been flattened or the input image is not found the
+output record number is still incremented.
+.le
+.ls flat_file
+The root name for the sensitivity spectra as produced by FLATFIT.
+Normally with multi-aperture instruments, FLATFIT will produce a
+spectrum appropriate to each aperture and the file name will have
+"flat_file" as the file name root and the aperture number appended.
+.le
+.ls coincor = )_.coincor
+If set to yes, coincidence correction is applied to the data during
+the division, and the following three parameters are required.
+For more about this correction see \fBcoincor\fR.
+.ls ccmode = )_.ccmode
+The mode by which the coincidence correction is to be performed.
+This may be "iids" or "photo".
+.le
+.ls deadtime = )_.deadtime
+The detector deadtime in seconds.
+.le
+.ls power = )_.power
+Power law IIDS non-linear correction exponent.
+.le
+.le
+.ih
+DESCRIPTION
+The input spectra are divided by the flat fields which are
+represented by spectra produced by FLATFIT.
+
+To avoid possible division by zero, any zeroes in the flat field
+spectra generated by FLATFIT are replaced by 1.0.
+
+The input spectra may optionally be corrected for coincidence losses.
+
+If the input and output spectra (after appending the record numbers) are
+the same then the division is performed in-place; i.e. the flattened spectra
+replace the original input spectra.
+Note that even if an output image is not created because the input
+image has already been flattened or the input image is not found the
+output record number is still incremented. This is to insure that if
+in-place division is desired that the input and output names remain
+matched.
+.ih
+EXAMPLES
+The following example divides a series of spectra to produce 20 new
+spectra having names nite1.1221 ... nite1.1240.
+
+ cl> flatdiv nite1 1201-1220 nite1 1221
+
+The same spectra as above are simultaneously corrected for
+coincidence losses.
+
+ cl> flatdiv nite1 1201-1220 nite1 1221 coincor=yes
+
+The flattened spectra replace the unflattened spectra.
+
+ cl> flatdiv nite1 1201-1220 nite1 1201
+
+Note that the input record numbers must be contiguous and the starting
+output record number must be the same as the first input record number.
+.ih
+TIME REQUIREMENTS
+Approximately 1 second is required to correct a spectrum of length
+1024 points.
+.ih
+SEE ALSO
+coincor, flatfit
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/flatfit.hlp b/noao/onedspec/irsiids/doc/flatfit.hlp
new file mode 100644
index 00000000..af84cb3c
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/flatfit.hlp
@@ -0,0 +1,188 @@
+.help flatfit Dec86 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+flatfit -- Sum and normalize flat field spectra
+.ih
+USAGE
+flatfit root records
+.ih
+PARAMETERS
+.ls root
+The root file name for the input names of the flat field
+spectra to be accumulated and fit for normalization.
+.le
+.ls records
+The range of spectra indicating the elements of the string.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the names of the spectra which will
+be created during normalization. The aperture number for the observation
+will be appended to the root in form "root.nnnn" where nnnn is the aperture
+number with leading 0's.
+.le
+.ls function = "chebyshev"
+The accumulated spectra are fit by this function type - either
+chebyshev or legendre polynomials, or spline3 or spline1 interpolators.
+.le
+.ls order = 4
+The order of the fit using the above function. This should generally be
+a low order fit to avoid introduction of high spatial frequency wiggles.
+.le
+.ls niter = 1
+The number of iterations to reject discrepant pixels upon initial
+startup of the solution.
+.le
+.ls lower = 2.0
+The number of sigmas for which data values less than this cutoff are
+rejected.
+.le
+.ls upper = 2.0
+The number of sigmas for which data values greater than this cutoff are
+rejected.
+.le
+.ls ngrow = 0
+The number of pixels on either side of a rejected pixel to also be rejected.
+.le
+.ls div_min = 1.0
+During the normalization process, a division by zero will produce
+this value as a result.
+.le
+.ls interact = yes
+If set to yes, graphical interaction with the normalization process
+is provided for at least the first aperture for which sums are available.
+If set to no, no interaction is provided.
+.le
+.ls all_interact = no
+If set to yes, then interaction will be provided for all apertures
+for which sums have been accumulated. If set to no then the parameter interact
+will determine if the first aperture data is to be interactive.
+.le
+.ls coincor = )_.coincor
+If set to yes, coincidence correction is applied to the data during
+the summation process, and the following three parameters are required.
+See \fBcoincor\fR for more about this correction.
+.ls ccmode = )_.ccmode
+The mode by which the coincidence correction is to be performed.
+This may be "iids" or "photo".
+.le
+.ls deadtime = )_.deadtime
+The detector deadtime in seconds.
+.le
+.ls power = )_.power
+Power law IIDS non-linear correction exponent.
+.le
+.le
+.ls cursor = ""
+Graphics cursor input. When null the standard cursor is used otherwise
+the specified file is used.
+.le
+.ih
+DESCRIPTION
+The specified spectra are added by aperture number to produce
+summations which are then fit by a specified fitting function.
+The fitting function is then divided into the sum to produce a
+normalized (to 1.0) sum in which the low frequency spatial
+response has been removed.
+
+The resultant normalized images may then be divided into all other
+data to remove the pixel-to-pixel variations without introducing
+any color terms. The spectra may be used directly if they happen
+to be object spectra in which the low frequency response is to be
+removed.
+
+During the accumulation process the spectra may be corrected for
+coincidence losses if the detector is subject to the phenomenon.
+
+After accumulating all input spectra, the pixels in each sum are
+fit according to
+the specified function. If the interactive switches are set, then
+graphical interaction is made available. If only the interact parameter
+is set to yes, then only the data from the first aperture will
+be available for interaction. Data from subsequent apertures will
+be fit using the same parameters and number of iterations as the
+first. If the all_interact parameter is also
+set, then data from each aperture will be presented for interaction.
+
+At each step in the fit, pixels which are discrepant by more than
+"upper" sigmas above the fit, or "lower" sigmas below the fit, are
+rejected. The rejection process may be applied many times (iterations)
+to continue rejecting pixels. If the upper and lower sigmas are
+not equal, the resulting fit will be biased slightly above the mean
+(for lower < upper) or below the mean (upper < lower). This is useful
+when the spectrum being fit is that of a star having either absorption
+or emission lines.
+
+A display is presented of the sum and the fit through the data.
+A status line is printed containing the fit type, the order of
+the fit, the rms residual from the fit, and the number of data
+points in the fit after one iteration of rejection.
+
+The following cursor keystrokes are then active:
+.ls ?
+Clear the screen and display the active keystrokes
+.le
+.ls /
+Indicate active keystrokes on the status line
+.le
+.ls e
+Change plot mode to an error plot. This display is defined
+as the deviation from the fit divided by the data values [ (data - fit)/ data]
+at each pixel
+.le
+.ls f
+Change plot mode back to the fit through the data display
+.le
+.ls o
+Change the order of the fit.
+.le
+.ls l
+Change the lower rejection criterion (in units of sigma).
+.le
+.ls u
+Change the upper rejection criterion.
+.le
+.ls s
+Change both rejection criteria to the same value.
+.le
+.ls r
+Reinstate rejected pixels.
+.le
+.ls i
+Iterate one more time.
+.le
+.ls n
+Iterate several more times - the user is prompted for the count.
+.le
+.ls q
+Quit and accept the solution
+.le
+.ls <CR>
+RETURN is the same as 'q' but a confirmation request to exit must be
+answered as yes.
+.le
+
+All keystrokes but ?,/,e,f, and q force another iteration which will
+reject additional pixels. To fully inhibit pixel rejection, the sigmas
+should be set to a large value (e.g. 100).
+.ih
+EXAMPLES
+The following example will accumulate 8 spectra and fit the first
+aperture data interactively but not the second, and apply coincidence
+corrections to the sums. The upper and lower rejection criteria
+have been altered to bias the seventh order fit to a higher level.
+
+ cl> flatfit nite1 1-4,201-204 coin+ low=1.4 up=3 order=7
+.ih
+BUGS
+For some reason, the error plot is supposed to have a zero level line
+drawn, but none appears.
+
+As in most of the IRAF software, the order of a fit refers to the number
+of terms in the fit, so that a fit of order 1 implies a constant and order
+2 implies a linear fit.
+.ih
+SEE ALSO
+coincor, flatdiv
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/powercor.hlp b/noao/onedspec/irsiids/doc/powercor.hlp
new file mode 100644
index 00000000..e1f9c70e
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/powercor.hlp
@@ -0,0 +1,62 @@
+.help powercor Oct86 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+powercor -- Apply power law correction to mountain reduced spectra
+.ih
+USAGE
+powercor input records
+.ih
+PARAMETERS
+.ls input
+The root file name of the input spectra.
+.le
+.ls records
+The range of spectra.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the corrected spectra.
+.le
+.ls start_rec = 1
+The starting record number to be appended to the root name of the
+created spectra.
+.le
+.ls power = )iids.power
+The power law coefficient.
+.le
+.ih
+DESCRIPTION
+A power law correction to the IIDS count rates is applied to the input
+spectra. The mountain reduction software applies a coincidence correction
+to the observed IIDS count rates but does not correct for a nonlinear effect
+in the image tube chain. This second correction takes the form of a
+power law
+
+ C(out) = C(in) ** power
+
+where C(in) is the input, coincidence corrected, count rate and C(out)
+is the corrected count rate. The power is a parameter of the task
+which defaults to the \fBiids\fR package parameter set to the appropriate
+value for the IIDS. The exposure time, in seconds, is a required
+image header parameter (keyword = EXPOSURE) used to convert the
+total counts to count rates.
+
+Note that if the original raw spectra are being reduced then the either
+\fBcoincor\fR or \fBpowercor\fR may be used to apply both the coincidence
+correction and the power law correction at the same time. In other words,
+the tasks apply the coincidence correction if the coincidence flag (CO-FLAG) is
+-1 (uncorrected) and the power law correction alone if the flag is zero
+(coincidence corrected only). The flag is 1 when both the coincidence and
+nonlinear correction have been applied.
+
+This task is a script calling \fBcoincor\fR with \fIccmode\fR = "iids".
+.ih
+EXAMPLES
+The following example corrects a series of IIDS spectra:
+
+ cl> powercor nite1 1-250 output=nite1cc start_rec=1
+.ih
+SEE ALSO
+coincor
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/process.hlp b/noao/onedspec/irsiids/doc/process.hlp
new file mode 100644
index 00000000..5cedcde3
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/process.hlp
@@ -0,0 +1,20 @@
+.help process Oct85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+process -- A task generated by BATCHRED
+.ih
+USAGE
+process
+.ih
+DESCRIPTION
+The task \fBbatchred\fR creates a script called process.cl for batch
+reductions. \fBBatchred\fR also has an option to automatically run
+this script.
+.ih
+EXAMPLES
+The task \fBbatchred\fR is run to setup a set of beam switching operations.
+It creates the script \fBprocess.cl\fR which the user runs as a background
+process as follows:
+
+ cl> process&
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/slist1d.hlp b/noao/onedspec/irsiids/doc/slist1d.hlp
new file mode 100644
index 00000000..6c7d2702
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/slist1d.hlp
@@ -0,0 +1,59 @@
+.help slist1d Jan92 noao.imred.irs/iids
+.ih
+NAME
+slist1d -- List spectral header information
+.ih
+USAGE
+slist1d input records
+.ih
+PARAMETERS
+.ls input
+The image root name for the spectra to be listed.
+.le
+.ls records
+The record string for the spectra to be listed. The records will be appended
+to the root name to form image names of the type "root.xxxx".
+.le
+.ls long_header = no
+If set to yes, then a complete listing of the header elements
+is given. If set to no, then a single line per spectrum is given which lists
+in the following order: the image name, object or sky spectrum, exposure
+time, spectrum length, and image title.
+.le
+.ih
+DESCRIPTION
+Each spectrum in the list implied by the root name and the record string
+is opened and the header is read. The pixel file is not accessed in order
+to save time. The header listing is directed to STDOUT and may be
+redirected for printing.
+
+A warning message is issued if
+a requested image is not found, but otherwise proceeds.
+.ih
+EXAMPLES
+The following example lists 8 spectral headers in long form on the printer:
+
+.nf
+ cl> slist1d nite1 1001-1008 | lprint
+.fi
+
+The next example lists the same spectral headers but in short form
+on the terminal
+
+.nf
+ cl> slist1d nite1 1001-1008 long-
+.fi
+.ih
+REVISIONS
+.ls SLIST1D V2.10
+This task is the same as V2.9 \fBslist\fR and applies only to the older
+IRS/IIDS record extension spectra. In V2.10 \fBslist\fR
+has been revised for multiaperture spectra.
+.le
+.ih
+BUGS
+SLIST1D does not inform the user if the pixel file can or cannot be read.
+.ih
+SEE ALSO
+slist, imheader
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/subsets.hlp b/noao/onedspec/irsiids/doc/subsets.hlp
new file mode 100644
index 00000000..a9f0ae68
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/subsets.hlp
@@ -0,0 +1,49 @@
+.help subsets May85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+subsets - Subtract pairs of spectra in a string
+.ih
+USAGE
+subsets input records
+.ih
+PARAMETERS
+.ls input
+The root file name for the input spectra in the string.
+.le
+.ls records
+The range of spectra indicating the elements of the string.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the names of the spectra which will
+be created by the subtraction operation.
+.le
+.ls start_rec
+The starting record number to be appended to the root name of the
+created spectra.
+.le
+.ih
+DESCRIPTION
+Pairs of spectra are formed from the input string in the order that
+the record numbers would suggest.
+The first spectrum in the pair is assumed to be the
+principle spectrum and the second spectrum in the pair is subtracted
+from the first. The result is written out as a new spectrum.
+
+No compensation is made for exposure time during the subtraction.
+The header from the principle spectrum is assigned to the output
+spectrum.
+
+.ih
+EXAMPLES
+The following example forms 50 new spectra from nite1.2001-nite1.2002,
+nite1.2003-nite1.2004, ...
+
+ cl> subsets nite1 2001-2100
+
+The following example creates new spectra from the pairs nite2.2001-nite2.2002,
+nite2.2003-nite2.2004 in spite of the order of the record numbers entered.
+
+ cl> subsets nite2 2001,2003,2002,2004
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/sums.hlp b/noao/onedspec/irsiids/doc/sums.hlp
new file mode 100644
index 00000000..0d8b27e9
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/sums.hlp
@@ -0,0 +1,44 @@
+.help sums Jul85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+sums -- Generate sums of the sky and object spectra for each aperture
+.ih
+USAGE
+sums input records
+.ih
+PARAMETERS
+.ls input
+The root file name for the input spectra in the string.
+.le
+.ls records
+The range of spectra indicating the elements of the string.
+The names of the spectra will be formed by appending the range
+elements to the input root name.
+.le
+.ls output
+This is the root file name for the names of the spectra which will
+be created by the summation operation.
+.le
+.ls start_rec
+The starting record number to be appended to the root name of the
+created spectra.
+.le
+.ih
+DESCRIPTION
+All the object spectra for each aperture are summed, and the
+sky spectra are also summed to produce two new spectra for
+each observing aperture. Exposure times are accumulated.
+No tests are made to check whether the object is consistent
+among the specified spectra. This could be accomplished by
+checking the titles or telescope positions, but it isn't.
+
+The header parameters OFLAG and BEAM-NUM must be properly
+set in the headers.
+.ih
+EXAMPLES
+The following example forms 4 new spectra from nite1.2001-nite1.2002,
+nite1.2003-nite1.2004, ... assuming this string is derived from
+IIDS spectra.
+
+ cl> sums nite1 2001-2100
+.endhelp
diff --git a/noao/onedspec/irsiids/doc/widstape.hlp b/noao/onedspec/irsiids/doc/widstape.hlp
new file mode 100644
index 00000000..855f223d
--- /dev/null
+++ b/noao/onedspec/irsiids/doc/widstape.hlp
@@ -0,0 +1,90 @@
+.help widstape Mar85 noao.imred.iids/noao.imred.irs
+.ih
+NAME
+widstape -- Write a Cyber style IDSOUT tape
+.ih
+USAGE
+widstape idsout input records
+.ih
+PARAMETERS
+.ls idsout
+The output file name to receive the card-image data. This may be a
+magtape specification (e.g. mta, mtb) or disk file name.
+.le
+.ls input
+The input root file name for the spectra to be written
+.le
+.ls records
+The record string to be appended to the root name to create the image
+names of the spectra to be written.
+.le
+.ls new_tape = no
+If set to yes, the tape is rewound and output begins at BOT. If no,
+output begins at EOT unless an explicit file specification is given
+as part of the magtape file name for parameter "idsout" (e.g. mta[2]).
+If idsout contains a file specification of [1], then writing begins
+at BOT regardless of the value for new_tape.
+.le
+.ls block_size = 3200
+The tape block size in bytes. This must be an integral factor of 80.
+.le
+.ls ebcdic = no
+The default character code is ASCII, but if this parameter is set to yes,
+the output character will be in EBCDIC.
+.le
+.ih
+DESCRIPTION
+The specified spectra are copied to the output file in a card-image format
+defined in the IPPS-IIDS/IRS Reduction Manual. Values from the extended
+image header are used to fill in the observational parameters.
+
+The basic format consists of 4 - 80 byte header cards, 128 data cards
+having 8 data elements per card in 1PE10.3 FORTRAN equivalent format,
+and a trailing blank card for a total of 133 cards.
+Thus spectra up to 1024 points may be contained in the IDSOUT format.
+The format is outlined below:
+
+.nf
+ Line Column Type
+ 1 1-5 Integer Record number within IDSOUT text file
+ 6-10 Integer Integration time
+ 11-25 Real Wavelength of first bin
+ 26-40 Real Dispersion
+ 41-45 Integer 0 (Index of first pixel)
+ 46-50 Integer Line length - 1 (Index of last pixel)
+ 71-80 Integer UT time
+ 2 1-10 Real Siderial time
+ 11-25 Real Right Ascension
+ 26-40 Real Declination
+ 3 21-35 Real Hour Angle
+ 36-50 Real Air mass
+ 51-58 Integer UT date
+ 60-76 String Image title
+ 78-80 String END
+ 4 1-64 String Record label
+ 78-80 String END
+5-132 Real 1024 pixel values, 8 per line
+ 133 Blank line
+.fi
+
+The data of type real are in exponent format; i.e FORTRAN 'E' format (1.234e3).
+
+There are no special marks between spectral images,
+and when multiple spectra are written with a single command, the first card
+of a subsequent spectrum may be within the same physical tape block
+as the last card of the previous spectrum. This assures that all tape
+blocks (except the very last one in the tape file) are all the same
+length. A double end-of-mark is written after the last spectrum.
+.ih
+EXAMPLES
+The following example writes an IDSOUT format tape starting at the
+beginning of the tape.
+
+ cl> widstape mta nite1 1001-1200 new_tape+
+.ih
+TIME REQUIREMENTS: UNIX/VAX 11/750
+Each spectrum of 1024 points requires about 2 second.
+.ih
+SEE ALSO
+rcardimage, ridsout
+.endhelp
diff --git a/noao/onedspec/irsiids/extinct.cl b/noao/onedspec/irsiids/extinct.cl
new file mode 100644
index 00000000..68c5a2de
--- /dev/null
+++ b/noao/onedspec/irsiids/extinct.cl
@@ -0,0 +1,22 @@
+#{ EXTINCT -- Use the BSWITCH task to perform the correction for
+# atmospheric extinction.
+
+{
+# Root name
+rt = root
+
+# Records
+rec = records
+
+# Output root
+out = output
+
+# Output starting record
+strt = start_rec
+
+# Do operation
+# Inhibit weighting and statisitic file generation
+
+bswitch (input=rt, records=rec, output=out, start_rec=strt, subset=nr_aps,
+ weighting=no, ids_mode=no, stats="")
+}
diff --git a/noao/onedspec/irsiids/extinct.par b/noao/onedspec/irsiids/extinct.par
new file mode 100644
index 00000000..a605dae0
--- /dev/null
+++ b/noao/onedspec/irsiids/extinct.par
@@ -0,0 +1,11 @@
+# EXINCT
+
+root,s,a,,,,Root name for spectra file names
+records,s,a,,,,Record string to process
+output,s,a,,,,Root name for spectra to be created
+start_rec,i,a,1,0,9999,Next starting spectral record
+nr_aps,i,h,2
+strt,i,h
+rt,s,h
+out,s,h
+rec,s,h
diff --git a/noao/onedspec/irsiids/flatdiv.par b/noao/onedspec/irsiids/flatdiv.par
new file mode 100644
index 00000000..84de42d4
--- /dev/null
+++ b/noao/onedspec/irsiids/flatdiv.par
@@ -0,0 +1,12 @@
+
+# FLATDIV parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+flat_file,s,a,,,,Image root name for output flat field spectra
+coincor,b,h,)_.coincor,,,Apply coincidence correction to spectra
+ccmode,s,h,)_.ccmode,,,Correction mode (photo|iids)
+deadtime,r,h,)_.deadtime,,,Deadtime in seconds
+power,r,h,)_.power,,,IIDS power law coefficient
diff --git a/noao/onedspec/irsiids/flatfit.par b/noao/onedspec/irsiids/flatfit.par
new file mode 100644
index 00000000..3167697d
--- /dev/null
+++ b/noao/onedspec/irsiids/flatfit.par
@@ -0,0 +1,24 @@
+# FLATFIT parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+function,s,h,"chebyshev",,,Function to fit (chebyshev|legendre|spline3|spline1)
+order,i,h,6,1,,Fitting order (number of terms)
+niter,i,h,1,1,,Number of rejection iterations
+lower,r,h,2,0,,Lower rejection criterion in sigmas
+upper,r,h,2,0,,Upper rejection criterion in sigmas
+ngrow,i,h,0,,,Growing region
+div_min,r,h,1.0,,,Value to use if division by zero occurs
+interact,b,h,yes,,,Interact with the first accumulation?
+all_interact,b,h,no,,,Interact with all accumulations?
+coincor,b,h,)_.coincor,,,Apply coincidence correction to flats
+ccmode,s,h,)_.ccmode,,,Correction mode (photo|iids)
+deadtime,r,h,)_.deadtime,,,Deadtime in seconds
+power,r,h,)_.power,,,IIDS power law coefficient
+new_order,i,a,4,1,,enter order
+new_lower,r,a,,,,enter nr sigma
+new_upper,r,a,,,,enter nr sigma
+new_niter,i,a,,,,enter nr of iterations
+confirm,b,a,,,,Exit and save solution?
+cursor,*gcur,h,"",,,Graphics cursor input
diff --git a/noao/onedspec/irsiids/getnimage.x b/noao/onedspec/irsiids/getnimage.x
new file mode 100644
index 00000000..c232a85c
--- /dev/null
+++ b/noao/onedspec/irsiids/getnimage.x
@@ -0,0 +1,133 @@
+include <mach.h>
+
+
+# GET_NEXT_IMAGE -- Use root filename and ranges string (if any) to
+# generate the next image filename. Return EOF
+# when image list is exhausted.
+
+int procedure get_next_image (infile, records, nrecs, image, sz_name)
+
+int infile, records[ARB], nrecs, sz_name
+char image[sz_name]
+
+int next_num, stat
+int flag1, flag2, flag3
+char image_0[SZ_FNAME]
+
+int clgfil(), get_next_entry(), strlen()
+
+common /gnicom/ flag1, flag2
+
+data flag3/YES/
+
+begin
+ # Reset initializer, record counter, and get root name
+ if ((flag1 == YES) || (flag3 == YES)) {
+ next_num = -1
+ call rst_get_entry ()
+ }
+
+ # If no ranges specified, act like template expander
+ if (nrecs == MAX_INT) {
+ stat = clgfil (infile, image, sz_name)
+
+ # Otherwise append record numbers to first template expansion
+ } else {
+ if (flag1 == YES) {
+ stat = clgfil (infile, image_0, sz_name)
+ if (stat == EOF)
+ return (stat)
+ }
+
+ stat = get_next_entry (records, next_num)
+ if (stat != EOF) {
+ call strcpy (image_0, image, sz_name)
+ call sprintf (image[strlen(image)+1], sz_name, ".%04d")
+ call pargi (next_num)
+ }
+ }
+
+ flag1 = NO
+ flag3 = NO
+ return (stat)
+end
+
+
+# Reset the initialization parameter to TRUE
+
+procedure reset_next_image ()
+
+int flag1, flag2
+common /gnicom/ flag1, flag2
+
+begin
+ flag1 = YES
+end
+
+
+# GET_NEXT_ENTRY -- Given a list of ranges and the current file number,
+# find and return the next file number in order of entry.
+# EOF is returned at the end of the list.
+
+int procedure get_next_entry (ranges, number)
+
+int ranges[ARB] # Range array
+int number # Both input and output parameter
+
+int ip, first, last, step, next_number, remainder
+int flag1, flag2, flag3
+
+common /gnicom/ flag1, flag2
+
+data flag3/YES/
+
+begin
+ number = number + 1
+ next_number = MAX_INT
+ if ((flag2 == YES) || (flag3 == YES)) {
+ ip = 1
+ flag2 = NO
+ flag3 = NO
+ }
+
+ first = min (ranges[ip], ranges[ip+1])
+ last = max (ranges[ip], ranges[ip+1])
+ step = ranges[ip+2]
+
+ if (number >= first && number <= last) {
+ remainder = mod (number - first, step)
+ if (remainder == 0)
+ return (number)
+ if (number - remainder + step <= last)
+ next_number = number - remainder + step
+ else
+ go to 10
+
+ } else if (first > number)
+ next_number = min (next_number, first)
+
+ else {
+10 ip = ip + 3
+ if (ranges[ip] != -1 && ranges[ip+1] !=0 && ranges[ip+2] !=0)
+ next_number = min (ranges[ip], ranges[ip+1])
+ }
+
+ if (next_number == MAX_INT) {
+ ip = 1
+ flag2 = YES
+ return (EOF)
+
+ } else {
+ number = next_number
+ return (number)
+ }
+end
+
+procedure rst_get_entry ()
+
+int first, flag2
+common /gnicom/ first, flag2
+
+begin
+ flag2 = YES
+end
diff --git a/noao/onedspec/irsiids/idsmtn.h b/noao/onedspec/irsiids/idsmtn.h
new file mode 100644
index 00000000..5fa3c73e
--- /dev/null
+++ b/noao/onedspec/irsiids/idsmtn.h
@@ -0,0 +1,101 @@
+# Definitions for the Mountain format IDS tape reader:
+
+define MAX_RANGES 100
+define DUMMY 3 # Value returned if DUMMY IDS record is read
+
+define NBITS_CHAR (SZB_CHAR * NBITS_BYTE)
+define SZ_IDS_RECORD (2108 * 16 / NBITS_CHAR)
+define NPIX_IDS_REC 1024
+define LEN_USER_AREA 2880
+define SZ_IDS_ID 64
+define NBITS_VN_3WRD_FP 48
+define NBITS_VN_2WRD_FP 32
+define NBITS_VN_LONG_INT 32
+define NBITS_VN_INT 16
+define NBITS_FORTH_CHAR 8
+define DATA_BYTE 9 # First byte of data
+define NBYTES_DATA (1024 * 32 / NBITS_BYTE) # Number of data bytes
+define NBYTES_INT (NBITS_INT / NBITS_BYTE)
+define NBYTES_VN_3WRD_FP 6
+define NBYTES_VN_2WRD_FP 4
+define NBITS_2WRD_HIGH 8
+define WRD2_EXP_OFFSET 25
+define NBITS_2WRD_EXP 6
+define WRD2_MANT_SIGN 24
+define WRD2_EXP_SIGN 31
+define NSIG_VN_BITS 15
+define VN_LONG_SIGN 31
+define WRD3_MANT_SIGN 31
+define WRD3_EXP_SIGN 47
+define MAX_NCOEFF 25
+
+
+# The control parameter structure is defined below:
+
+define LEN_CP 10 + SZ_FNAME + 1
+
+define IS_REDUCED Memi[$1]
+define LONG_HEADER Memi[$1+1]
+define PRINT_PIXELS Memi[$1+2]
+define MAKE_IMAGE Memi[$1+3]
+define OFFSET Memi[$1+4]
+define DATA_TYPE Memi[$1+5]
+define IRAF_FILE Memc[P2C($1+10)]
+
+
+# The header structure is defined below:
+
+define LEN_IDS 40 + SZ_IDS_ID + 1
+
+define HA Memr[P2R($1)]
+define AIRMASS Memr[P2R($1+1)]
+define RA Memr[P2R($1+2)]
+define DEC Memr[P2R($1+3)]
+define W0 Memr[P2R($1+4)]
+define WPC Memr[P2R($1+5)]
+define LINE Memi[$1+6]
+define NP1 Memi[$1+7]
+define NP2 Memi[$1+8]
+define ITM Memr[P2R($1+9)]
+define BEAM Memi[$1+10]
+define W Memi[$1+11]
+define UT Memr[P2R($1+13)]
+define ST Memr[P2R($1+14)]
+define DF_FLAG Memi[$1+15]
+define SM_FLAG Memi[$1+16]
+define QF_FLAG Memi[$1+17]
+define DC_FLAG Memi[$1+18]
+define QD_FLAG Memi[$1+19]
+define EX_FLAG Memi[$1+20]
+define BS_FLAG Memi[$1+21]
+define CA_FLAG Memi[$1+22]
+define CO_FLAG Memi[$1+23]
+define OFLAG Memi[$1+24]
+define POINT Memi[$1+25]
+define DRA Memi[$1+26]
+define DDEC Memi[$1+27]
+define ALPHA_ID Memc[P2C($1+35)]
+define LABEL Memc[P2C($1+40)]
+
+
+# Bit offsets to various IDS header words are defined below:
+
+define NREC_OFFSET ((0 * 16) + 1)
+define RFLAGS_OFFSET ((1 * 16) + 1)
+define ITM_OFFSET ((2 * 16) + 1)
+define DATA_OFFSET ((4 * 16) + 1)
+define W0_OFFSET ((2052 * 16) + 1)
+define WPC_OFFSET ((2055 * 16) + 1)
+define NP1_OFFSET ((2058 * 16) + 1)
+define NP2_OFFSET ((2059 * 16) + 1)
+define OFLAG_OFFSET ((2060 * 16) + 1)
+define SMODE_OFFSET ((2061 * 16) + 1)
+define UT_OFFSET ((2062 * 16) + 1)
+define ST_OFFSET ((2064 * 16) + 1)
+define BEAM_OFFSET ((2066 * 16) + 1)
+define HA_OFFSET ((2067 * 16) + 1)
+define RA_OFFSET ((2070 * 16) + 1)
+define DEC_OFFSET ((2073 * 16) + 1)
+define DRA_OFFSET ((2076 * 16) + 1)
+define DDEC_OFFSET ((2077 * 16) + 1)
+define LABEL_OFFSET ((2078 * 16) + 1)
diff --git a/noao/onedspec/irsiids/irsiids.hd b/noao/onedspec/irsiids/irsiids.hd
new file mode 100644
index 00000000..d0a20d98
--- /dev/null
+++ b/noao/onedspec/irsiids/irsiids.hd
@@ -0,0 +1,18 @@
+# Help directory for the IRS/IIDS tasks.
+
+$doc = "./doc/"
+
+addsets hlp=doc$addsets.hlp, src=t_addsets.x
+batchred hlp=doc$batchred.hlp, src=batchred.cl
+bswitch hlp=doc$bswitch.hlp, src=t_bswitch.x
+coefs hlp=doc$coefs.hlp, src=t_coefs.x
+coincor hlp=doc$coincor.hlp, src=t_coincor.x
+extinct hlp=doc$extinct.hlp, src=extinct.cl
+flatdiv hlp=doc$flatdiv.hlp, src=t_flatdiv.x
+flatfit hlp=doc$flatfit.hlp, src=t_flatfit.x
+powercor hlp=doc$powercor.hlp, src=powercor.cl
+process hlp=doc$process.hlp, src=process.cl
+slist1d hlp=doc$slist1d.hlp, src=t_slist1d.x
+subsets hlp=doc$subsets.hlp, src=t_subsets.x
+sums hlp=doc$sums.hlp, src=t_sums.x
+widstape hlp=doc$widstape.hlp, src=x_widstape.x
diff --git a/noao/onedspec/irsiids/mkpkg b/noao/onedspec/irsiids/mkpkg
new file mode 100644
index 00000000..01ee3403
--- /dev/null
+++ b/noao/onedspec/irsiids/mkpkg
@@ -0,0 +1,22 @@
+# IRS/IIDS Tasks
+
+$checkout libpkg.a ..
+$update libpkg.a
+$checkin libpkg.a ..
+$exit
+
+libpkg.a:
+ coincor.x
+ conversion.x
+ getnimage.x <mach.h>
+ t_addsets.x <error.h> <imhdr.h>
+ t_bswitch.x <smw.h> <error.h> <imhdr.h> <mach.h> <time.h>
+ t_coefs.x <error.h>
+ t_coincor.x <error.h> <imhdr.h>
+ t_flatdiv.x <error.h> <imhdr.h>
+ t_flatfit.x <gset.h> <imhdr.h> <math/curfit.h>
+ t_slist1d.x <error.h> <fset.h> <imhdr.h> <smw.h>
+ t_subsets.x <error.h> <imhdr.h>
+ t_sums.x <error.h> <imhdr.h>
+ t_widstape.x <error.h> <imhdr.h> <mach.h> <smw.h>
+ ;
diff --git a/noao/onedspec/irsiids/powercor.cl b/noao/onedspec/irsiids/powercor.cl
new file mode 100644
index 00000000..a89e2478
--- /dev/null
+++ b/noao/onedspec/irsiids/powercor.cl
@@ -0,0 +1,4 @@
+#{ Apply nonlinear IIDS correction
+
+coincor (input, records, output, start_rec=start_rec, ccmode="iids",
+ power=power)
diff --git a/noao/onedspec/irsiids/powercor.par b/noao/onedspec/irsiids/powercor.par
new file mode 100644
index 00000000..e4e8bb90
--- /dev/null
+++ b/noao/onedspec/irsiids/powercor.par
@@ -0,0 +1,7 @@
+# POWERCOR parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+power,r,h,)_.power,,,Power law coefficient
diff --git a/noao/onedspec/irsiids/slist1d.par b/noao/onedspec/irsiids/slist1d.par
new file mode 100644
index 00000000..ae091a20
--- /dev/null
+++ b/noao/onedspec/irsiids/slist1d.par
@@ -0,0 +1,3 @@
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+long_header,b,h,no,,,List header in long format
diff --git a/noao/onedspec/irsiids/subsets.par b/noao/onedspec/irsiids/subsets.par
new file mode 100644
index 00000000..5368ca06
--- /dev/null
+++ b/noao/onedspec/irsiids/subsets.par
@@ -0,0 +1,6 @@
+# SUBSETS parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
diff --git a/noao/onedspec/irsiids/sums.par b/noao/onedspec/irsiids/sums.par
new file mode 100644
index 00000000..bbeab466
--- /dev/null
+++ b/noao/onedspec/irsiids/sums.par
@@ -0,0 +1,8 @@
+
+# SUMS parameter file
+
+input,s,a,,,,Input image root file name
+records,s,a,,,,Range of spectral records
+output,s,a,,,,Output file root name for new spectra
+start_rec,i,a,1,0,9999,Next starting spectral record
+newoutput,s,q,,,,New output file root name
diff --git a/noao/onedspec/irsiids/t_addsets.x b/noao/onedspec/irsiids/t_addsets.x
new file mode 100644
index 00000000..cd145b4a
--- /dev/null
+++ b/noao/onedspec/irsiids/t_addsets.x
@@ -0,0 +1,195 @@
+include <error.h>
+include <imhdr.h>
+
+
+# T_ADDSETS -- Add a series of spectra by subsets. A single spectrum
+# is produced for every "subset" number of input spectra. The input
+# list is accumulated until "subset" number of spectra have been
+# encountered. The result is then written out.
+#
+# If the input data are calibrated (CA_FLAG = 0) then the result
+# is an average over the subset size, but the header exposure
+# time is updated.
+#
+# If the data is uncalibrated then the resulting spectrum is a sum
+# of the total counts.
+
+procedure t_addsets ()
+
+pointer image
+pointer recstr, ofile
+int root, start_rec, subset
+int nrecs
+int nrem, ifile, ca_flag
+real itm, expo, wt, wtsum
+bool weight
+pointer sp, recs, im, cur_pix, sp_sum
+
+real imgetr()
+int clpopni(), clgeti(), imgeti()
+int get_next_image(), decode_ranges()
+bool clgetb()
+pointer immap(), imgl1r()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (recstr, SZ_LINE, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+
+ # Open input file name template
+ root = clpopni ("input")
+
+ # Get range specification if any
+ call clgstr ("records", Memc[recstr], SZ_LINE)
+ if (decode_ranges (Memc[recstr], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # Get rootname for output files and starting record
+ call clgstr ("output", Memc[ofile], SZ_FNAME)
+ start_rec = clgeti ("start_rec")
+
+ # Get subset size
+ subset = clgeti ("subset")
+
+ # Apply integration time weighting?
+ weight = clgetb ("weighting")
+
+ # Initialize range decoder
+ call reset_next_image ()
+
+ #Initialize file counter
+ ifile = 0
+ wtsum = 0.0
+
+ # Loop over all input images by subsets
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+
+ # Open image
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+
+ # Allocate space for current subset
+ if (mod (ifile, subset) == 0) {
+ call calloc (sp_sum, IM_LEN (im,1), TY_REAL)
+
+ # Zero exposure counter
+ expo = 0.0
+ }
+
+ # Add in current spectrum
+ iferr (itm = imgetr (im, "EXPOSURE"))
+ iferr (itm = imgetr (im, "ITIME"))
+ iferr (itm = imgetr (im, "EXPTIME"))
+ itm = 1
+ iferr (ca_flag = imgeti (im, "CA-FLAG"))
+ ca_flag = -1
+ cur_pix = imgl1r (im)
+
+ # Apply integration time weighting
+ if (weight)
+ wt = itm
+ else
+ wt = 1.0
+
+ if (ca_flag != 0)
+ wt = 1.0
+
+ wtsum = wtsum + wt
+ call amulkr (Memr[cur_pix], wt, Memr[cur_pix], IM_LEN(im,1))
+ call aaddr (Memr[cur_pix], Memr[sp_sum], Memr[sp_sum], IM_LEN(im,1))
+ expo = expo + itm
+
+ # Issue status report
+ call printf ("[%s] added\n")
+ call pargstr (Memc[image])
+
+ ifile = ifile + 1
+ if (mod (ifile, subset) == 0) {
+ call wrt_set (Memr[sp_sum], subset, im, Memc[ofile], start_rec,
+ expo, wtsum, ca_flag)
+ wtsum = 0.0
+ call mfree (sp_sum, TY_REAL)
+ } else
+ call imunmap (im)
+
+ }
+ # Check that there are no remaining spectra in an unfulfilled subset
+ nrem = mod (ifile, subset)
+ if (nrem != 0) {
+ call wrt_set (Memr[sp_sum], nrem, im, Memc[ofile], start_rec,
+ expo, wtsum, ca_flag)
+ wtsum = 0.0
+ call mfree (sp_sum, TY_REAL)
+
+ call eprintf ("Unfulfilled subset accumulation written - ")
+ call eprintf ("missing %d spectra\n")
+ call pargi (subset - nrem)
+ }
+
+ # Update record number
+ call clputi ("next_rec", start_rec)
+
+ # Free space
+ call sfree (sp)
+ call clpcls (root)
+end
+
+# WRT_SET -- Write spectra ccumulated from the set
+
+procedure wrt_set (sp_sum, subset, im, ofile, start_rec, expo, wtsum, ca_flag)
+
+real sp_sum[ARB]
+int subset, start_rec, ca_flag
+pointer im
+char ofile[SZ_FNAME]
+real expo, wtsum
+
+char newfile[SZ_FNAME]
+pointer imnew, newpix
+
+pointer impl1r(), immap()
+int strlen()
+
+begin
+ # Create new spectrum - first make up a name
+ call strcpy (ofile, newfile, SZ_FNAME)
+ call sprintf (newfile[strlen (newfile) + 1], SZ_FNAME, ".%04d")
+ call pargi (start_rec)
+
+ imnew = immap (newfile, NEW_COPY, im)
+
+ IM_NDIM (imnew) = 1
+ IM_LEN (imnew,1) = IM_LEN (im,1)
+ IM_PIXTYPE (imnew) = TY_REAL
+ call strcpy (IM_TITLE(im), IM_TITLE(imnew), SZ_LINE)
+
+ call imunmap (im)
+
+ newpix = impl1r (imnew)
+
+ # If this spectrum is calibrated, perform an average
+ # weighted by integration time and copy new pixels into image
+ if (ca_flag == 0)
+ call adivkr (sp_sum, real (wtsum), Memr[newpix], IM_LEN(imnew,1))
+ else
+ call amovr (sp_sum, Memr[newpix], IM_LEN(imnew,1))
+
+ # Update keyword
+ call imaddr (imnew, "EXPOSURE", expo)
+
+ # Send user report
+ call printf ("writing [%s]: %s\n")
+ call pargstr (newfile)
+ call pargstr (IM_TITLE(imnew))
+ call flush (STDOUT)
+
+ call imunmap (imnew)
+
+ # Update record counter
+ start_rec = start_rec + 1
+end
diff --git a/noao/onedspec/irsiids/t_bswitch.x b/noao/onedspec/irsiids/t_bswitch.x
new file mode 100644
index 00000000..0c7b71a5
--- /dev/null
+++ b/noao/onedspec/irsiids/t_bswitch.x
@@ -0,0 +1,924 @@
+include <error.h>
+include <imhdr.h>
+include <mach.h>
+include <time.h>
+include <smw.h>
+
+define MAX_NR_BEAMS 100 # Max number of instrument apertures
+define MIN_RANGES 100 # Minimum spectra per beam if not given
+
+# T_BSWITCH -- Beam switch a series of spectra to produce a single
+# sky subtracted spectrum.
+#
+# The spectra may be extinction corrected if not already done.
+#
+# The summation may include an optional statistical weighting
+# based on the total countrate summed over a user definable
+# piece of the spectrum. If the countrate is <= 0, the
+# spectrum is given zero weight.
+#
+# The data may be organized as data from the IIDS/IRS are usually
+# obtained - where the telescope is beam-switched so that the
+# object is first in one aperture while sky is observed in the other,
+# and then the process is reversed.
+#
+# If the instrument offers many apertures, "nebular" mode can be used
+# to obtain the same effect. Here all apertures observe the object(s)
+# at one time; then the telescope is moved so all apertures are observing
+# sky.
+#
+# Both these methods are considered "idsmode". But if there are a different
+# number of sky observations than object, an imbalance exists.
+# To account for this possibility, all summations are performed by computing
+# an average countrate over all observations. Sky countrates can then be
+# subtracted from the object. Later the differential countrate is returned
+# to an "equivalent" count by multiplying by the exposure time.
+#
+# Spectra must be dispersion corrected to employ either
+# weighting or extinction correction.
+#
+# The series of spectra may be accumulated in subsets rather than
+# over the entire series by specifying a subset rate. (E.g. for
+# IIDS data a subset rate of 4 would produce a summed pair for
+# every quadruple.)
+
+# Revisions made for WCS support and change from idsmtn.h structure to shdr.h
+# structure. Because this program is an awful mess the changes were made a
+# small as possible without altering the structure. (5/1/91, Valdes)
+
+procedure t_bswitch ()
+
+char image[SZ_FNAME,MAX_NR_BEAMS+1]
+char rec_numbers[SZ_LINE], title[SZ_LINE,MAX_NR_BEAMS]
+char ofile[SZ_FNAME], stat_fil[SZ_FNAME]
+int sfd, nrecsx
+int i, infile, nrecs, def_beam, start_rec, nimage, sub_rate
+int records[300], beam_stat[MAX_NR_BEAMS], ncols[MAX_NR_BEAMS]
+bool idsmode, extinct, stat, weight, eof_test
+pointer ids[MAX_NR_BEAMS+1]
+pointer imnames[MAX_NR_BEAMS] # Hold pointers to pointers of image names
+pointer imin, sp, obs
+
+# The following arrays are suffixed by either 'o' for object or 's' for sky
+
+int ico [MAX_NR_BEAMS], ics [MAX_NR_BEAMS] # nr obs in beam
+real expo [MAX_NR_BEAMS], exps [MAX_NR_BEAMS] # exposure times
+pointer accumo[MAX_NR_BEAMS+1], accums[MAX_NR_BEAMS+1] # beam accumulators
+pointer counto[MAX_NR_BEAMS], counts[MAX_NR_BEAMS] # counts in each obs
+
+int clpopni(), clgeti(), get_next_image(), decode_ranges()
+int open(), mod()
+pointer immap()
+bool clgetb(), streq()
+
+begin
+ call smark (sp)
+ call aclri (ids, MAX_NR_BEAMS+1)
+
+ # Open input filename template
+ infile = clpopni ("input")
+
+ # Get range specification
+ call clgstr ("records", rec_numbers, SZ_LINE)
+ if (decode_ranges (rec_numbers, records, 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # If no ranges is given, filename expansion will occur, so
+ # we must will need some indication of the number of spectra.
+ if (nrecs == MAX_INT)
+ nrecsx = MIN_RANGES
+ else
+ nrecsx = nrecs
+
+ # Get root name for new records and starting record number
+ call clgstr ("output", ofile, SZ_FNAME)
+ start_rec = clgeti ("start_rec")
+
+ # Get filename for statistics
+ call clgstr ("stats", stat_fil, SZ_FNAME)
+
+ # Assume spectra are in quadruples?
+ idsmode = clgetb ("ids_mode")
+
+ # Perform de-extinction?
+ extinct = clgetb ("extinct")
+
+ # Use weighting?
+ weight = clgetb ("weighting")
+
+ # Accumulate by subsets? - A very large number implies no subsetting
+ sub_rate = clgeti ("subset")
+
+ # Open statistics file if any
+ if (streq (stat_fil, "")) {
+ sfd = NULL
+ stat = false
+ } else {
+ stat = true
+ sfd = open (stat_fil, APPEND, TEXT_FILE)
+ }
+
+ # Initialize beam-switch status
+ obs = NULL
+ call init_file (extinct, def_beam, ico, ics, beam_stat)
+
+
+ # Begin cycling through all images - accumulate if possible
+ # by beam number
+
+ # Initialize range decoder
+ call reset_next_image ()
+
+ # Set up for subsets
+ nimage = 0
+ eof_test = false
+
+ repeat {
+
+ while (get_next_image (infile, records, nrecs, image[1,def_beam],
+ SZ_FNAME) != EOF) {
+
+ # Attempt to open image with extended header -
+ iferr (imin = immap (image[1,def_beam], READ_ONLY, 0)) {
+ call eprintf ("[%s]")
+ call pargstr (image[1,def_beam])
+ call error (0, "Image not found or header info not available")
+ }
+
+ # Add in to accumlators
+ call accum_image (imin, ids, accumo, accums, counto, counts,
+ ico, ics, expo, exps, image, beam_stat, idsmode, extinct,
+ weight, nrecsx, ncols, title, imnames, sfd, obs)
+
+ call printf ("[%s] added\n")
+ call pargstr (image[1,def_beam])
+ call flush (STDOUT)
+
+ # Close current image
+ call imunmap (imin)
+
+ # Test for subsets
+ nimage = nimage + 1
+ if (mod (nimage, sub_rate) == 0)
+ go to 10
+ }
+
+ # Get here by running out of data
+ eof_test = true
+
+ # Must be careful not to write out the last sums if subsets are
+ # in effect because the subset check would already have done so
+ # We can check because "nimage" will not have been bumped
+ # if EOF was encountered.
+
+ if (mod (nimage, sub_rate) != 0) {
+
+ # All data has been summed - generate spectra of the accumlations
+10 call wrt_accum (ids, image, title, accumo, accums, ico, ics,
+ counto, counts, expo, exps, ncols, beam_stat, idsmode, weight,
+ extinct, ofile, start_rec, sub_rate)
+
+ # Generate statistics output for this beam
+ if (stat)
+ call wrt_stats (sfd, accumo, accums, ico, ics, counto, counts,
+ expo, exps, image, beam_stat, title, imnames, weight)
+
+ # Clear counters and accumulators
+ call reset_beams (accumo, accums, expo, exps, ico, ics, beam_stat,
+ ncols)
+ }
+
+ } until (eof_test)
+
+ # Put current record counter back into the parameter file for
+ # subsequent invocations
+ call clputi ("next_rec", start_rec)
+
+ # Close out inputs, outputs, and space
+ do i = 1, MAX_NR_BEAMS+1
+ call shdr_close (ids[i])
+ if (obs != NULL)
+ call obsclose (obs)
+ call clpcls (infile)
+ call close (sfd)
+ call sfree (sp)
+end
+
+# ACCUM_IMAGE -- Opens current pixel file, loads header elements,
+# adds current spectrum to accumulator array(s),
+# and updates the accumulator status array.
+# If not in IDSMODE, then returns both object and
+# sky sums for further consideration.
+# IDSMODE requires an equal number of each, object and sky, in
+# a sequence of OSSO-OSSO or OSSO-SOOS groups.
+
+procedure accum_image (imin, ids, accumo, accums, counto, counts, ico, ics,
+ expo, exps, image, beam_stat, idsmode, extinct, weight, nrecs,
+ ncols, title, imnames, sfd, obs)
+
+pointer imin, ids[ARB]
+pointer imnames[ARB] # Saved image names for stat printout
+pointer sfd # Statistics file
+pointer obs # Observatory
+
+pointer accumo[ARB], accums[ARB] # Object and sky accumlators
+pointer counto[ARB], counts[ARB] # counting stats
+real expo [ARB], exps [ARB] # total exposure times
+int ico [ARB], ics [ARB] # number of observations
+
+char image[SZ_FNAME, MAX_NR_BEAMS+1], title[SZ_LINE,MAX_NR_BEAMS]
+char observatory[SZ_FNAME]
+int beam_stat[ARB], ncols[ARB]
+int dum_beam
+bool idsmode, extinct, weight, exflag, newobs, obshead
+real latitude
+
+int last_len[MAX_NR_BEAMS], name_nr[MAX_NR_BEAMS]
+int ifile, nr_beams, i, j, def_beam, beam_nr
+int nwaves, ic, nrecs
+real airm, wave1, wave2, wt
+pointer wave_tbl, extn_tbl, ipacc, ipc, mw
+
+real clgetr(), obsgetr()
+pointer smw_openim()
+errchk smw_openim, shdr_open, obsimopen
+
+begin
+ # Bump image file counter
+ ifile = ifile + 1
+
+ # Load header area
+ mw = smw_openim (imin)
+ call shdr_open (imin, mw, 1, 1, INDEFI, SHDATA, ids[def_beam])
+ call smw_close (MW(ids[def_beam]))
+
+ accumo[def_beam] = SY(ids[def_beam])
+
+ # Check for proper flags
+ call flag_chk (ids[def_beam], exflag)
+
+ if (ifile == 1) {
+
+ # Get region for statistics to operate over -
+ # Currently only one set of wavelengths is available, but
+ # at some point, it may be desirable to extend this to
+ # provide a start and ending wavelength for each aperture
+ # since an aperture must be considered as an independent
+ # instrument.
+
+ # Insert defaults --> entire spectrum
+ # Now ask user for start and end - if =0.0, use defaults
+ wave1 = clgetr ("wave1")
+ wave2 = clgetr ("wave2")
+
+ if (wave1 == 0.0)
+ wave1 = W0(ids[def_beam])
+ if (wave2 == 0.0)
+ wave2 = W0(ids[def_beam]) + (IM_LEN(imin,1)-1) *
+ WP(ids[def_beam])
+
+ }
+
+ # Determine beam number and add/sub in pixels
+ # Remember that IIDS/IRS "beams" are 0-indexed
+
+ beam_nr = BEAM(ids[def_beam]) + 1
+ if (beam_nr > MAX_NR_BEAMS || beam_nr < 1)
+ call error (0, "Illegal beam number")
+
+ # Allocate space for this aperture if not already done
+ # Space must be allocated for 2 lines of spectra for
+ # each aperture - Line 1 is used to sum up the most
+ # recent object-sky spectra to maintain the local
+ # statistics. Line 2 is used for the net accumulation
+ # over the entire sequence. The statistics from Line 1
+ # may be used to weigh the observations as they are
+ # added into the Line 2 accumulation.
+ #
+ # For non-IDSMODE the two lines are used for separate
+ # object and sky sums
+
+ if (IS_INDEFI (beam_stat[beam_nr])) {
+ beam_stat[beam_nr] = 0
+
+ # Allocate space for the accumulators for this beam nr
+ call salloc (accumo[beam_nr], IM_LEN(imin,1), TY_REAL)
+ call salloc (accums[beam_nr], IM_LEN(imin,1), TY_REAL)
+
+ # Zero object and sky accumulators
+ call amovkr (0.0, Memr[accumo[beam_nr]], IM_LEN(imin,1))
+ call amovkr (0.0, Memr[accums[beam_nr]], IM_LEN(imin,1))
+
+
+ # Allocate space for statistics array - For each beam,
+ # a series of up to 'nrecs' spectra may be read, and we
+ # want to keep track of the stats (=countrates) for each
+ # observation. For non-idsmode, need sky rates too.
+ call salloc (counto[beam_nr], nrecs, TY_REAL)
+ if (!idsmode)
+ call salloc (counts[beam_nr], nrecs, TY_REAL)
+
+ # Allocate space for the image names
+ call salloc (imnames[beam_nr], nrecs, TY_INT)
+ name_nr[beam_nr] = 1
+ do j = 1, nrecs
+ call salloc (Memi[imnames[beam_nr]+j-1], SZ_LINE, TY_CHAR)
+
+ # Save number of points for checking purposes
+ last_len[beam_nr] = IM_LEN(imin,1)
+ ncols[beam_nr] = last_len[beam_nr]
+
+ # Initialize exposure time
+ expo[beam_nr] = 0.0
+ exps[beam_nr] = 0.0
+
+ nr_beams = nr_beams + 1
+ }
+
+ # If this is an object observation, save the image name
+ if (OFLAG(ids[def_beam]) == 1) {
+ call strcpy (image[1,def_beam], Memc[Memi[imnames[beam_nr]+
+ name_nr[beam_nr]-1]], SZ_LINE)
+ name_nr[beam_nr] = name_nr[beam_nr] + 1
+ }
+
+ # If an object observation, save the header elements --
+ # NOTE that if we get >1 objects before getting a sky, only
+ # the last observation header is saved!
+
+ # The pixel data will be the sum of all objects until the
+ # |object-sky| count = 0 -- Thus, beam switching does not
+ # necessarily accumulate by pairs, but depends on how the
+ # sequence of observations are presented to the program.
+
+ # The following test has been deleted so that headers
+ # will be saved for sky frames as well. This is necessary
+ # if BSWITCH is to perform the function of EXTINCTION
+ # only when sky frames are to be written out as well.
+
+ if (OFLAG(ids[def_beam]) == 1 || !idsmode) {
+ # Save headers - could probably be done faster by AMOV
+ call shdr_copy (ids[def_beam], ids[beam_nr], NO)
+
+ # Fix airmass if necessary
+ if (extinct && IS_INDEF (AM(ids[beam_nr]))) {
+ call clgstr ("observatory", observatory, SZ_FNAME)
+ call obsimopen (obs, imin, observatory, NO, newobs, obshead)
+ if (newobs) {
+ call obslog (obs, "BSWITCH", "latitude", STDOUT)
+ if (sfd != NULL)
+ call obslog (obs, "BSWITCH", "latitude", sfd)
+ }
+ latitude = obsgetr (obs, "latitude")
+ call get_airm (RA(ids[beam_nr]), DEC(ids[beam_nr]),
+ HA(ids[beam_nr]), ST(ids[beam_nr]), latitude,
+ AM(ids[beam_nr]))
+ }
+
+ call strcpy (image[1,def_beam], image[1,beam_nr], SZ_FNAME)
+
+ # Save length - Each beam may be independent sizes
+ ncols[beam_nr] = IM_LEN(imin,1)
+
+ # Save title, too for same reason
+ call strcpy (IM_TITLE(imin), title[1,beam_nr], SZ_LINE)
+ }
+
+ # Verify length
+ if (last_len[beam_nr] != ncols[beam_nr]) {
+ call eprintf ("[%s] -- Length not consistent %d\n")
+ call pargstr (image[1,beam_nr])
+ call pargi (ncols[beam_nr])
+ ncols[beam_nr] = min (ncols[beam_nr], last_len[beam_nr])
+ }
+ last_len[beam_nr] = ncols[beam_nr]
+
+
+ # Check to see if a pair is obtained - then perform statistics
+ # and add into global accumulator
+
+ if (idsmode) {
+
+ # Add spectrum to local accumulation buffer --> Use SKY buffer
+ # At this point of deriving a sequentially local sum, weighting
+ # is not used.
+
+ call add_spec (Memr[accumo[def_beam]], Memr[accums[beam_nr]],
+ beam_stat[beam_nr], OFLAG(ids[def_beam]), last_len[beam_nr])
+
+ # IDSMODE requires that every 2N observations produce an
+ # OBJECT-SKY pair
+ if (mod (ifile, 2*nr_beams) == 0)
+
+ # Review all beams in use for non-zero pairings
+ do i = 1, MAX_NR_BEAMS
+ if (!IS_INDEFI (beam_stat[i]) && beam_stat[i] != 0)
+ call error (0, "Spectra are not in quadruples")
+
+
+ # Object and sky exposure times must be equal.
+ if (OFLAG(ids[def_beam]) == 1) {
+ expo[beam_nr] = expo[beam_nr] + IT(ids[def_beam])
+
+ # Increment number of object observations for this beam
+ ico[beam_nr] = ico[beam_nr] + 1
+ }
+
+
+ if (beam_stat[beam_nr] == 0) {
+ # Add up all counts within a region for statistics of objects
+ # This must be kept separately for each beam number and for
+ # each observation
+
+ # First convert to counts per second (CPS)
+ call adivkr (Memr[accums[beam_nr]], IT(ids[def_beam]),
+ Memr[accums[beam_nr]], last_len[beam_nr])
+
+ # Sum CPS in statistics region
+ call sum_spec (Memr[accums[beam_nr]], wave1, wave2,
+ W0(ids[def_beam]), WP(ids[def_beam]), Memr[counto[beam_nr]+
+ ico[beam_nr]-1], last_len[beam_nr])
+
+ # De-extinct spectrum
+ if (extinct && !exflag) {
+ airm = AM(ids[beam_nr])
+ call de_ext_spec (Memr[accums[beam_nr]], airm,
+ W0(ids[def_beam]), WP(ids[def_beam]), Memr[wave_tbl],
+ Memr[extn_tbl], nwaves, last_len[beam_nr])
+ }
+
+ # Add to global accumulator
+ # Use weights which are proportional to countrate, if desired
+ if (weight) {
+ wt = Memr[counto[beam_nr]+ico[beam_nr]-1]
+ call amulkr (Memr[accums[beam_nr]], wt,
+ Memr[accums[beam_nr]], last_len[beam_nr])
+ }
+
+ # And add into global sum
+ call aaddr (Memr[accums[beam_nr]], Memr[accumo[beam_nr]],
+ Memr[accumo[beam_nr]], last_len[beam_nr])
+ }
+
+ } else {
+ # Non IDSMODE -accumulate separate object and sky CPS sums
+
+ # Set pointers and update obj-sky parameters
+ if (OFLAG(ids[def_beam]) == 1) {
+ beam_stat[beam_nr] = beam_stat[beam_nr] + 1
+ ipacc = accumo[beam_nr]
+ ipc = counto[beam_nr]
+ ico[beam_nr] = ico[beam_nr] + 1
+ ic = ico[beam_nr]
+ expo[beam_nr] = expo[beam_nr] + IT(ids[def_beam])
+ } else {
+ beam_stat[beam_nr] = beam_stat[beam_nr] - 1
+ ipacc = accums[beam_nr]
+ ipc = counts[beam_nr]
+ ics[beam_nr] = ics[beam_nr] + 1
+ ic = ics[beam_nr]
+ exps[beam_nr] = exps[beam_nr] + IT(ids[def_beam])
+ }
+
+ # First convert to counts per second (CPS)
+ call adivkr (Memr[accumo[def_beam]], IT(ids[def_beam]),
+ Memr[accumo[def_beam]], last_len[beam_nr])
+
+ # Get counting stats
+ call sum_spec (Memr[accumo[def_beam]], wave1, wave2,
+ W0(ids[def_beam]), WP(ids[def_beam]), Memr[ipc+ic-1],
+ last_len[beam_nr])
+
+ # De-extinct spectrum
+ if (extinct && !exflag) {
+ airm = AM(ids[beam_nr])
+ call de_ext_spec (Memr[accumo[def_beam]], airm,
+ W0(ids[def_beam]), WP(ids[def_beam]), Memr[wave_tbl],
+ Memr[extn_tbl], nwaves, last_len[beam_nr])
+ }
+
+ if (weight) {
+ wt = Memr[ipc+ic-1]
+ call amulkr (Memr[accumo[def_beam]], wt, Memr[accumo[def_beam]],
+ last_len[beam_nr])
+ }
+
+ # Add into appropriate accumulator
+ call aaddr (Memr[accumo[def_beam]], Memr[ipacc], Memr[ipacc],
+ last_len[beam_nr])
+ }
+
+ return
+
+# INIT_FILE -- Zero the file initializer, the beam counter, beam stats
+# and read the extinction data if necessary
+
+entry init_file (extinct, dum_beam, ico, ics, beam_stat)
+
+ ifile = 0
+ nr_beams = 0
+ def_beam = MAX_NR_BEAMS + 1
+ dum_beam = def_beam
+
+ do i = 1, MAX_NR_BEAMS {
+ beam_stat[i] = INDEFI
+ ico[i] = 0
+ ics[i] = 0
+ }
+
+ # If extinction required, read in extinction file, and sensitivity file
+ if (extinct)
+ call get_extn (wave_tbl, extn_tbl, nwaves)
+
+ return
+
+# INIT_NAME -- Reset name index counter for a beam number
+
+entry init_name (dum_beam)
+
+ name_nr[dum_beam] = 1
+ return
+end
+
+# ACCUM_OUT -- Checks accumulator flags and writes out a new summed
+# image if the count is zero
+
+procedure accum_out (accum, image, ncols, title, root, rec, beam_nr,
+ bsflag, itm, exflag)
+
+real accum[ARB], itm
+char image[SZ_FNAME], title[SZ_LINE], root[SZ_FNAME]
+int ncols, rec, beam_nr
+int bsflag, exflag
+
+pointer imin, imout, spec
+char bs_image[SZ_FNAME]
+
+pointer immap(), impl1r()
+
+begin
+ # Create new image with user area
+ # Use ROOT for spectrum name and increment starting record number
+
+ call sprintf (bs_image, SZ_FNAME, "%s.%04d")
+ call pargstr (root)
+ call pargi (rec)
+
+ rec = rec + 1
+
+ # Provide user info
+ call printf ("writing: [%s] %s\n")
+ call pargstr (bs_image)
+ call pargstr (title)
+ call flush (STDOUT)
+
+ imin = immap (image, READ_ONLY, 0)
+ imout = immap (bs_image, NEW_COPY, imin)
+
+ # Add standard image header
+ IM_NDIM(imout) = 1
+ IM_LEN(imout,1) = ncols
+ IM_PIXTYPE(imout) = TY_REAL
+ call strcpy (title, IM_TITLE(imout), SZ_LINE)
+
+ # Write out pixels
+ spec = impl1r (imout)
+ call amovr (accum, Memr[spec], ncols)
+
+ # Update changed parameters
+ if(bsflag == 1)
+ call imaddi (imout, "BS-FLAG", bsflag)
+ call imaddr (imout, "EXPTIME", itm)
+ call imaddi (imout, "EX-FLAG", exflag)
+
+ call imunmap (imin)
+ call imunmap (imout)
+
+ # Store new image name back into image
+ call strcpy (bs_image, image, SZ_FNAME)
+end
+
+# ACCUM_NORM - Normalize weighted rate and convert to counts
+
+procedure accum_norm (accum, nr, counts, exp, ncols, weight)
+
+real accum[ARB], counts[ARB], exp
+int nr, ncols
+bool weight
+
+real sum_wt
+int i
+
+begin
+ # The accumulation is an array weighted by non-normalized weights
+ # Normalize to total weight to produce a true weighted average
+ # and multiply by the total exposure to produce
+ # an equivalent sum
+
+ # Add up all weighting factors
+ if (weight) {
+ sum_wt = 0.0
+ do i = 1, nr
+ sum_wt = sum_wt + counts[i]
+ } else
+ sum_wt = real (nr)
+
+ if (sum_wt == 0.0)
+ sum_wt = 1.0
+
+ # Correct for exposure time
+ sum_wt = exp / sum_wt
+
+ call amulkr (accum, sum_wt, accum, ncols)
+end
+
+# WRT_ACCUM -- Write out accumulations as spectra
+
+procedure wrt_accum (ids, image, title, accumo, accums, ico, ics,
+ counto, counts, expo, exps, ncols, beam_stat, idsmode, weight,
+ extinct, ofile, start_rec, sub_rate)
+
+pointer ids[ARB]
+char image[SZ_FNAME,MAX_NR_BEAMS+1], title[SZ_LINE,MAX_NR_BEAMS]
+
+pointer accumo[ARB], accums[ARB]
+pointer counto[ARB], counts[ARB]
+int ico [ARB], ics [ARB]
+real expo [ARB], exps [ARB]
+int ncols[ARB]
+int beam_stat[ARB]
+bool idsmode, weight, extinct
+char ofile[SZ_FNAME]
+int start_rec, sub_rate, bsflag
+
+int i, nr_beams
+real exp_ratio
+
+begin
+ # First compute number of beams
+ nr_beams = 0
+ do i = 1, MAX_NR_BEAMS
+ if (!IS_INDEFI (beam_stat[i]) && ((ico[i] > 0) || (ics[i] > 0)))
+ nr_beams = nr_beams + 1
+
+ # For all present apertures, write out a spectrum
+ do i = 1, MAX_NR_BEAMS {
+
+ if (!IS_INDEFI (beam_stat[i]) && ((ico[i] > 0) || (ics[i] > 0))) {
+ if (beam_stat[i] != 0 && idsmode) {
+ call eprintf ("Non-equal number of obj-sky observations")
+ call eprintf (" beam: %d - residual: %d\n")
+ call pargi (i-1)
+ call pargi (beam_stat[i])
+
+ # Reset to 0 and force output
+ beam_stat[i] = 0
+ }
+
+ # The accumulator has a total CPS using non-normalized
+ # weights - apply normalization and exposure time to
+ # generate an equivalent COUNT sum.
+ call accum_norm (Memr[accumo[i]], ico[i], Memr[counto[i]],
+ expo[i], ncols[i], weight)
+
+ if (!idsmode) {
+ # Separate object and sky sums require sky info
+ call accum_norm (Memr[accums[i]], ics[i], Memr[counts[i]],
+ exps[i], ncols[i], weight)
+
+ # Then normalize sky exposure time to that of object
+ if (exps[i] != 0.0)
+ exp_ratio = expo[i]/exps[i]
+ else
+ exp_ratio = 1.0
+
+ # Check that some object observtion was made
+ # If not, then we only have sky data so multiply by -1
+ # so that the subsequent subtraction will produce a
+ # positive sky
+ if (expo[i] == 0.0)
+ exp_ratio = -1.0
+
+ if (exp_ratio != 1.0)
+ call amulkr (Memr[accums[i]], exp_ratio,
+ Memr[accums[i]], ncols[i])
+
+ # Finally subtract sky from object equivalent counts
+ call asubr (Memr[accumo[i]], Memr[accums[i]],
+ Memr[accumo[i]], ncols[i])
+
+ }
+ # Set header flags
+ # BS flag is not set if the subset rate equals the
+ # number of apertures since each record in is copied out
+ if (sub_rate > nr_beams)
+ bsflag = 1
+ else
+ bsflag = -1
+
+ if (OFLAG(ids[i]) == 1)
+ IT(ids[i]) = expo[i]
+ else
+ IT(ids[i]) = exps[i]
+
+ if (extinct)
+ EC(ids[i]) = 0
+
+ # And write out spectrum, at last
+ call accum_out (Memr[accumo[i]], image[1,i],
+ ncols[i], title[1,i], ofile, start_rec, i,
+ bsflag, IT(ids[i]), EC(ids[i]))
+
+ # Reset name entry counter
+ call init_name (i)
+ }
+
+ }
+end
+
+# RESET_BEAMS -- Zeroes the counters and accumulators for additional
+# cases
+
+procedure reset_beams (accumo, accums, expo, exps, ico, ics, beam_stat, ncols)
+
+pointer accumo[ARB], accums[ARB]
+real expo [ARB], exps [ARB]
+int ico [ARB], ics [ARB]
+int beam_stat[ARB], ncols[ARB]
+
+int i
+
+begin
+ do i = 1, MAX_NR_BEAMS
+ if (!IS_INDEFI (beam_stat[i])) {
+
+ expo[i] = 0.0
+ exps[i] = 0.0
+ ico[i] = 0
+ ics[i] = 0
+ call amovkr (0.0, Memr[accumo[i]], ncols[i])
+ call amovkr (0.0, Memr[accums[i]], ncols[i])
+ }
+end
+
+# WRT_STATS -- Write out statistics file
+
+procedure wrt_stats (fd, accumo, accums, ico, ics, counto, counts,
+ expo, exps, image, beam_stat, title, imnames, weight)
+
+int fd
+pointer accumo[ARB], accums[ARB], counto[ARB], counts[ARB]
+real expo[ARB], exps[ARB]
+int ico[ARB], ics[ARB], beam_stat[ARB]
+char image[SZ_FNAME,MAX_NR_BEAMS+1]
+char title[ARB]
+pointer imnames[ARB]
+bool weight
+
+int i, j
+real cmaxo, cmaxs
+char ctime[SZ_TIME]
+
+long clktime()
+
+begin
+ # Issue time stamp
+ call cnvtime (clktime (long(0)), ctime, SZ_TIME)
+ call fprintf (fd, "%s\n\n")
+ call pargstr (ctime)
+
+ # Issue message if weighted sums in effect
+ if (weight)
+ call fprintf (fd, "--> Using weighted averages <--\n\n")
+
+ # Cycle over beams
+ do i = 1, MAX_NR_BEAMS {
+ if (!IS_INDEFI (beam_stat[i])) {
+
+ # Write out Object stats if any
+ if (ico[i] > 0) {
+ call fprintf (fd, "Object statistics for beam %d -->[%s]\n")
+ call pargi (i-1)
+ call pargstr (image[1,i])
+ call fprintf (fd, "Title: %s\n")
+ call pargstr (title)
+
+ # Find maximum count value for this beam
+ cmaxo = Memr[counto[i]]
+
+ do j = 1, ico[i]
+ cmaxo = max (cmaxo, Memr[counto[i]+j-1])
+
+ call fprintf (fd,
+ "Obs Relative CPS Image%12wPeak CPS = %10.3g\n")
+ call pargr (cmaxo)
+
+ if (cmaxo == 0.0)
+ cmaxo = 1.0
+
+ do j = 1, ico[i] {
+ call fprintf (fd, "%3d %8.2f [%s]\n")
+ call pargi (j)
+ call pargr (Memr[counto[i]+j-1] / cmaxo)
+ call pargstr (Memc[Memi[imnames[i]+j-1]])
+ }
+ }
+
+ # Write out sky stats if any
+ if (ics[i] > 0) {
+ call fprintf (fd, "Sky statistics for beam %d\n")
+ call pargi (i-1)
+
+ cmaxs = Memr[counts[i]]
+
+ do j = 1, ics[i]
+ cmaxs = max (cmaxs, Memr[counts[i]+j-1])
+
+ call fprintf (fd, "Obs Relative CPS Peak CPS = %10.3g\n")
+ call pargr (cmaxs)
+
+ if (cmaxs == 0.0)
+ cmaxs = 1.0
+
+ do j = 1, ics[i] {
+ call fprintf (fd, "%3d %8.2f\n")
+ call pargi (j)
+ call pargr (Memr[counts[i]+j-1] / cmaxs)
+ }
+ }
+
+ call fprintf (fd, "\n\n")
+ }
+ }
+end
+
+
+# ADD_SPEC -- Accumulate spectrum into array - either add or subtract
+# Returns status = net number of object - sky apectra
+# = 0 for equal numbers to indicate further
+# processing may take place
+
+procedure add_spec (inspec, accum, stat, flag, len)
+
+real inspec[ARB], accum[ARB]
+int stat, flag, len
+
+int i, add_sub
+
+begin
+ add_sub = 0
+
+ # Is this an Object or Sky?
+ # If flag is neither 0 or 1, spectrum is ignored
+ if (flag == 0)
+ add_sub = -1
+ if (flag == 1)
+ add_sub = +1
+
+ if (add_sub == 0) {
+ stat = INDEFI
+ return
+ }
+
+ # Is accumulator to be cleared?
+ if (IS_INDEFI (stat) || stat == 0) {
+ call amulkr (inspec, real (add_sub), accum, len)
+ stat = add_sub
+
+ } else {
+ # Add into accumulator
+ do i = 1, len
+ accum[i] = accum[i] + add_sub * inspec[i]
+
+ stat = stat + add_sub
+ }
+end
+
+# FLAG_CHK -- Check header flags prior to beam switching
+
+procedure flag_chk (ids, exflag)
+
+pointer ids
+bool exflag
+
+int bsflag, imgeti()
+
+begin
+ # BS requires
+ # 1. dispersion corrected spectra
+ # 2. non-beam switched
+ # 3. may be either extinction corrected or not
+
+ if (DC(ids) != DCLINEAR)
+ call error (0, "Spectrum not dispersion corrected")
+
+ iferr (bsflag = imgeti (IM(ids), "BS-FLAG"))
+ bsflag = -1
+ if (bsflag == 1)
+ call error (0, "Spectrum already beam-switched")
+
+ if (EC(ids) == ECYES)
+ exflag = true
+ else
+ exflag = false
+end
diff --git a/noao/onedspec/irsiids/t_coefs.x b/noao/onedspec/irsiids/t_coefs.x
new file mode 100644
index 00000000..656e777d
--- /dev/null
+++ b/noao/onedspec/irsiids/t_coefs.x
@@ -0,0 +1,88 @@
+include <error.h>
+
+# COEFS -- Convert IIDS/IRS coeffients to IDENTIFY database entry.
+
+procedure t_coefs ()
+
+int root # List of input root names
+pointer database # Output database directory
+
+int i, nrecs, ncoefs
+real coef
+pointer sp, image, dtname, recs, im, dt
+
+real imgetr()
+int clpopni(), imgeti(), get_next_image(), decode_ranges()
+pointer immap(), dtmap1()
+errchk imgetr, dtmap1
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_LINE, TY_CHAR)
+ call salloc (database, SZ_FNAME, TY_CHAR)
+ call salloc (dtname, SZ_FNAME, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+
+ root = clpopni ("input")
+ call clgstr ("records", Memc[image], SZ_LINE)
+ call clgstr ("database", Memc[database], SZ_LINE)
+
+ if (decode_ranges (Memc[image], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # Loop over all input images - print name on STDOUT
+ call reset_next_image ()
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_LINE) != EOF) {
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+
+ iferr (ncoefs = imgeti (im, "DF-FLAG"))
+ ncoefs = -1
+ if (ncoefs > 1) {
+ call strcpy ("id", Memc[dtname], SZ_FNAME)
+ call imgcluster (Memc[image], Memc[dtname+2], SZ_FNAME)
+ dt = dtmap1 (Memc[database], Memc[dtname], APPEND)
+
+ call dtptime (dt)
+ call dtput (dt, "begin\tidentify %s\n")
+ call pargstr (Memc[image])
+ call dtput (dt, "\tid\t%s\n")
+ call pargstr (Memc[image])
+ call dtput (dt, "\ttask\tidentify\n")
+ call dtput (dt, "\timage\t%s\n")
+ call pargstr (Memc[image])
+
+ # Convert coefficients
+ call dtput (dt, "\tcoefficients\t%d\n")
+ call pargi (ncoefs+4)
+ call dtput (dt, "\t\t2\n")
+ call dtput (dt, "\t\t%1d\n")
+ call pargi (ncoefs)
+ call dtput (dt, "\t\t1\n")
+ call dtput (dt, "\t\t1024\n")
+
+ do i = 1, ncoefs {
+ call sprintf (Memc[dtname], SZ_FNAME, "DF%d")
+ call pargi (i)
+ coef = imgetr (im, Memc[dtname])
+ call dtput (dt, "\t\t%10.4f\n")
+ call pargr (coef)
+ }
+
+ call dtput (dt, "\n")
+ call dtunmap (dt)
+ }
+
+ call printf ("[%s] %d coefficients written\n")
+ call pargstr (Memc[image])
+ call pargi (max (0, ncoefs))
+ call flush (STDOUT)
+ call imunmap (im)
+ }
+
+ call clpcls (root)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/irsiids/t_coincor.x b/noao/onedspec/irsiids/t_coincor.x
new file mode 100644
index 00000000..ad2d8bd4
--- /dev/null
+++ b/noao/onedspec/irsiids/t_coincor.x
@@ -0,0 +1,102 @@
+include <error.h>
+include <imhdr.h>
+
+
+# T_COINCOR -- Apply coincidence corrections to spectra
+
+procedure t_coincor ()
+
+int root, start_rec, ccmode, npts, nrecs, coflag
+real dtime, power, expo
+pointer sp, image, ofile, str, recs, imin, imout, pixin, pixout
+
+int clpopni(), clgeti(), clgwrd(), imgeti()
+int get_next_image(), decode_ranges()
+real clgetr(), imgetr()
+pointer immap(), imgl1r(), impl1r()
+errchk coincor
+
+begin
+ # Allocate memory
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+
+ # Get parameters
+ root = clpopni ("input")
+ call clgstr ("output", Memc[ofile], SZ_FNAME)
+ if (Memc[ofile] != EOS)
+ start_rec = clgeti ("start_rec")
+ ccmode = clgwrd ("ccmode", Memc[str], SZ_LINE, ",photo,iids,")
+ dtime = clgetr ("deadtime")
+ power = clgetr ("power")
+ call clgstr ("records", Memc[str], SZ_LINE)
+
+ # Initialize
+ if (decode_ranges (Memc[str], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+ call reset_next_image ()
+
+ # Loop over all input images by subsets
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+
+ # Open input image and check coincidence flag
+ iferr (imin = immap (Memc[image], READ_WRITE, 0)) {
+ call erract (EA_WARN)
+ start_rec = start_rec + 1
+ next
+ }
+ iferr (coflag = imgeti (imin, "CO-FLAG"))
+ coflag = -1
+ if (coflag > 0) {
+ call printf ("[%s] already coincidence corrected\n")
+ call pargstr (IM_HDRFILE(imin))
+ call flush (STDOUT)
+ call imunmap (imin)
+ next
+ }
+
+ # Open output image
+ if (Memc[ofile] != EOS) {
+ call sprintf (Memc[str], SZ_LINE, "%s.%04d")
+ call pargstr (Memc[ofile])
+ call pargi (start_rec)
+ start_rec = start_rec + 1
+
+ imout = immap (Memc[str], NEW_COPY, imin)
+ IM_PIXTYPE (imout) = TY_REAL
+ } else
+ imout = imin
+
+ # Apply coincidence correction
+ pixin = imgl1r (imin)
+ pixout = impl1r (imout)
+ npts = IM_LEN (imin, 1)
+ iferr (expo = imgetr (imin, "EXPOSURE"))
+ iferr (expo = imgetr (imin, "ITIME"))
+ iferr (expo = imgetr (imin, "EXPTIME"))
+ expo = 1
+ call coincor (Memr[pixin], Memr[pixout], npts, expo, coflag,
+ dtime, power, ccmode)
+
+ # Update flag and write status
+ call imaddi (imout, "CO-FLAG", coflag)
+ call printf ("[%s] --> [%s] %s\n")
+ call pargstr (IM_HDRFILE(imin))
+ call pargstr (IM_HDRFILE(imout))
+ call pargstr (IM_TITLE(imout))
+ call flush (STDOUT)
+
+ # Close images
+ if (imout != imin)
+ call imunmap (imout)
+ call imunmap (imin)
+ }
+
+ call clputi ("next_rec", start_rec)
+ call clpcls (root)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/irsiids/t_flatdiv.x b/noao/onedspec/irsiids/t_flatdiv.x
new file mode 100644
index 00000000..37186878
--- /dev/null
+++ b/noao/onedspec/irsiids/t_flatdiv.x
@@ -0,0 +1,276 @@
+include <imhdr.h>
+include <error.h>
+
+define MAX_NR_BEAMS 100 # Max number of instrument apertures
+
+# T_FLATDIV -- Divide by a flat field spectrum. This is basically
+# a simple division of two vectors but with the following
+# additional functions:
+#
+# 1. Check the processing flag of the input spectra to avoid
+# double processing, and set the flag if the processing is
+# performed.
+# 2. Trap division by zero errors
+# 3. Optionally apply coincidence corrections
+
+procedure t_flatdiv ()
+
+int root, start_rec
+int nrecs
+int len_flat
+int ccmode, qd_flag
+real dtime
+real power
+bool coincidence
+pointer sp, image, str, ofile, flat, recs, bstat, flatsp, im
+
+int clpopni(), clgeti(), clgwrd(), imgeti()
+int get_next_image(), decode_ranges()
+real clgetr()
+bool clgetb()
+pointer immap()
+errchk get_flatsp
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (flat, SZ_FNAME, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+ call salloc (bstat, MAX_NR_BEAMS, TY_INT)
+
+ # Open input file name template
+ root = clpopni ("input")
+
+ # Get range specification if any
+ call clgstr ("records", Memc[str], SZ_LINE)
+ if (decode_ranges (Memc[str], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # Get rootname for output files and starting record
+ # Subtract 1 from start_rec because 1 will be added later.
+ call clgstr ("output", Memc[ofile], SZ_FNAME)
+ start_rec = clgeti ("start_rec") - 1
+
+ # Get flat field spectrum root name
+ call clgstr ("flat_file", Memc[flat], SZ_FNAME)
+
+ # Apply coincidence corrections?
+ coincidence = clgetb ("coincor")
+ if (coincidence) {
+ ccmode = clgwrd ("ccmode", Memc[str], SZ_LINE, ",photo,iids,")
+ dtime = clgetr ("deadtime")
+ power = clgetr ("power")
+ }
+
+ # Initialize beam number status
+ call init_bs (Memi[bstat])
+
+ # Initialize range decoder
+ call reset_next_image ()
+
+ # Loop over all input images - divide and make new image.
+ # The output record number is incremented in all cases.
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+ start_rec = start_rec + 1
+
+ # Open image
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+
+ # Get header
+ iferr (qd_flag = imgeti (im, "QD-FLAG"))
+ qd_flag = -1
+
+ # Verify divide flag
+ if (qd_flag != 0) {
+
+ # Get flat field spectrum if needed
+ call get_flatsp (im, flatsp, Memc[flat], Memi[bstat], len_flat)
+
+ # Calibrate the current spectrum and make a calibrated version
+ call divide (im, flatsp, len_flat, Memc[image], Memc[ofile],
+ start_rec, coincidence, ccmode, dtime, power)
+
+ } else {
+ call eprintf ("[%s] already divided - ignored\n")
+ call pargstr (Memc[image])
+ }
+ }
+
+ # Update record number
+ call clputi ("next_rec", start_rec)
+
+ # Free space
+ call sfree (sp)
+ call clpcls (root)
+end
+
+# GET_FLATSP -- Load flat field spectrum for the current beam number
+
+procedure get_flatsp (im, sp, flat_file, beam_stat, len_flat)
+
+pointer im, sp
+char flat_file[SZ_FNAME]
+int beam_stat[ARB], len_flat
+
+int i
+int beam, len[MAX_NR_BEAMS]
+char sfname[SZ_FNAME]
+pointer flatsp[MAX_NR_BEAMS], imflat
+
+int strlen(), imgeti()
+pointer imgl1r(), immap()
+errchk immap
+
+begin
+ # Determine beam number.
+
+ iferr (beam = imgeti (im, "BEAM-NUM"))
+ beam = 0
+ beam = beam + 1
+
+ # Validate beam number
+ if (beam < 1 || beam > MAX_NR_BEAMS) {
+ call eprintf (" Beam number out of range: %d - using 0\n")
+ call pargi (beam)
+ beam = 1
+ }
+
+ # Has this beam already been loaded?
+ if (IS_INDEFI (beam_stat[beam])) {
+
+ # Create file name
+ call strcpy (flat_file, sfname, SZ_FNAME)
+
+ # Flat field file names have beam number appended
+ call sprintf (sfname[strlen(sfname)+1], SZ_FNAME, ".%04d")
+ call pargi (beam-1)
+
+ # Open spectrum
+ imflat = immap (sfname, READ_ONLY, 0)
+
+ # Allocate space for this beam's sensitivity spectrum
+ call salloc (flatsp[beam], IM_LEN(imflat,1), TY_REAL)
+
+ # Copy pixels into space
+ call amovr (Memr[imgl1r(imflat)], Memr[flatsp[beam]],
+ IM_LEN(imflat,1))
+
+ # Must be careful that no division by zero occurs.
+ do i = 1, IM_LEN(imflat,1) {
+ if (Memr[flatsp[beam]+i-1] == 0.0)
+ Memr[flatsp[beam]+i-1] = 1.0
+ }
+
+ # Mark this beam accounted for
+ beam_stat[beam] = 1
+ len[beam] = IM_LEN(imflat,1)
+
+ call imunmap (imflat)
+ }
+
+ # Point to the spectrum
+ sp = flatsp[beam]
+ len_flat = len[beam]
+
+end
+
+# DIVIDE -- Perform the division and create new spectrum
+
+procedure divide (im, flat, len_flat, ifile, ofile, rec,
+ coincidence, ccmode, dtime, power)
+
+pointer im, flat
+int len_flat, rec, ccmode
+real dtime, power
+char ifile[ARB], ofile[ARB]
+bool coincidence
+
+real itm, imgetr()
+int i, co_flag, imgeti()
+int ncols, nlines
+char calfname[SZ_FNAME], original[SZ_FNAME]
+pointer imcal, rawpix, calpix
+
+pointer immap(), impl2r(), imgl2r()
+
+begin
+ # Find smallest length of the two possible spectra
+ ncols = min (IM_LEN (im, 1), len_flat)
+
+ # Create new spectrum. Make up a name
+ call sprintf (calfname, SZ_FNAME, "%s.%04d")
+ call pargstr (ofile)
+ call pargi (rec)
+
+ call xt_mkimtemp (ifile, calfname, original, SZ_FNAME)
+ imcal = immap (calfname, NEW_COPY, im)
+
+ IM_NDIM(imcal) = IM_NDIM(im)
+ IM_LEN (imcal,1) = ncols
+ IM_PIXTYPE(imcal) = TY_REAL
+
+ # Check for 2D spectrum
+ if (IM_NDIM(im) > 1)
+ nlines = IM_LEN(im,2)
+ else
+ nlines = 1
+
+ # Copy across the image title
+ call strcpy (IM_TITLE(im), IM_TITLE(imcal), SZ_LINE)
+
+ # Operate on the pixels
+ do i = 1, nlines {
+ rawpix = imgl2r (im,i)
+ calpix = impl2r (imcal,i)
+
+ # Apply coincidence correction if needed
+ co_flag = -1
+ if (coincidence) {
+ iferr (co_flag = imgeti (im, "CO-FLAG"))
+ ;
+ if (co_flag < 1) {
+ iferr (itm = imgetr (im, "EXPOSURE"))
+ iferr (itm = imgetr (im, "ITIME"))
+ iferr (itm = imgetr (im, "EXPTIME"))
+ itm = 1.
+ call coincor (Memr[rawpix], Memr[rawpix], ncols,
+ itm, co_flag, dtime, power, ccmode)
+ }
+ }
+
+ call adivr (Memr[rawpix], Memr[flat], Memr[calpix], ncols)
+ }
+
+ call imaddi (imcal, "QD-FLAG", 0)
+ if (co_flag != -1)
+ call imaddi (imcal, "CO-FLAG", co_flag)
+
+ # Send user report
+ call printf ("writing [%s]: %s\n")
+ call pargstr (original)
+ call pargstr (IM_TITLE(imcal))
+ call flush (STDOUT)
+
+ call imunmap (im)
+ call imunmap (imcal)
+ call xt_delimtemp (calfname, original)
+end
+
+# INIT_BS -- Initialize beam status flags
+
+procedure init_bs (beam_stat)
+
+int beam_stat[ARB]
+
+int i
+
+begin
+ do i = 1, MAX_NR_BEAMS
+ beam_stat[i] = INDEFI
+end
diff --git a/noao/onedspec/irsiids/t_flatfit.x b/noao/onedspec/irsiids/t_flatfit.x
new file mode 100644
index 00000000..c3558afc
--- /dev/null
+++ b/noao/onedspec/irsiids/t_flatfit.x
@@ -0,0 +1,740 @@
+include <imhdr.h>
+include <math/curfit.h>
+include <gset.h>
+
+define MAX_NR_BEAMS 100 # Max number of instrument apertures
+
+define KEY "noao$lib/scr/flatfit.key"
+define PROMPT "flatfit cursor options"
+
+# Definitions for Plotting modes
+define PLT_FIT 1 # Plot the direct fit
+define PLT_ERR 2 # Plot the errors in the fit
+define PLT_LIN 3 # Plot the fit minus the linear part
+
+# T_FLATFIT -- Accumulate a series of flat field spectra to produce
+# a grand sum and fit a function to the sum to produce a normalized
+# flat containing the pixel-to-pixel variations.
+# User interaction via the graphics cursor is provided. The following
+# cursor commands are recognized:
+#
+# ? - Screen help
+# / - Status line help
+# e - Plot in residual error mode
+# f - Plot in fit to the data mode
+# o - Change order of fit
+# l - Change lower rejection sigma
+# u - Change upper rejection sigma
+# r - Reset fit to include rejected pixels
+# s - Change upper and lower sigmas to same value
+# i - Iterate again
+# n - Iterate N times
+# q - Quit and accept current solution (also RETURN)
+#
+
+procedure t_flatfit ()
+
+pointer image # Image name to be fit
+pointer images # Image name to be fit
+pointer ofile # Output image file name
+int function # Fitting function
+int order # Order of fitting function
+int recs # Spectral record numbers
+int root, nrecs # CL and ranges flags
+real expo # Exposure time
+real dtime # Deadtime
+real power # Power law coin. correction
+real lower # Lower rejection sigma
+real upper # Upper threshold sigma
+int ngrow # Rejection radius
+real div_min # Division min for option RESP
+bool coincidence, all # Apply coincidence correction
+bool interact # Interactive levels
+pointer bstat # Status of each aperture
+pointer npts # Length of spectrum
+pointer esum # Accumulated exposure time
+pointer accum # Pointers to beam accumulators
+pointer title
+int ccmode, beam
+int niter
+
+int i
+pointer sp, str, im
+
+int clgeti(), clgwrd(), clpopni(), imgeti()
+int get_next_image(), decode_ranges()
+real clgetr(), imgetr()
+bool clgetb()
+pointer immap()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (images, MAX_NR_BEAMS, TY_POINTER)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+ call salloc (bstat, MAX_NR_BEAMS, TY_INT)
+ call salloc (npts, MAX_NR_BEAMS, TY_INT)
+ call salloc (esum, MAX_NR_BEAMS, TY_REAL)
+ call salloc (accum, MAX_NR_BEAMS, TY_POINTER)
+ call salloc (title, MAX_NR_BEAMS, TY_POINTER)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call amovki (NULL, Memi[images], MAX_NR_BEAMS)
+
+ # Get task parameters.
+ root = clpopni ("input")
+
+ # Get input record numbers
+ call clgstr ("records", Memc[str], SZ_LINE)
+ if (decode_ranges (Memc[str], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ call clgstr ("output", Memc[ofile], SZ_LINE)
+
+ call clgcurfit ("function", "order", function, order)
+
+ lower = clgetr ("lower")
+ upper = clgetr ("upper")
+ ngrow = clgeti ("ngrow")
+ div_min = clgetr ("div_min")
+
+ # Determine desired level of activity
+ interact = clgetb ("interact")
+ all = clgetb ("all_interact")
+
+ niter = clgeti ("niter")
+
+ # Is coincidence correction to be performed?
+ coincidence = clgetb ("coincor")
+
+ if (coincidence) {
+ ccmode = clgwrd ("ccmode", Memc[str], SZ_LINE, ",photo,iids,")
+ dtime = clgetr ("deadtime")
+ power = clgetr ("power")
+ }
+
+ call reset_next_image ()
+
+ # Clear all beam status flags
+ call amovki (INDEFI, Memi[bstat], MAX_NR_BEAMS)
+ call aclrr (Memr[esum], MAX_NR_BEAMS)
+
+ call printf ("Accumulating spectra --\n")
+ call flush (STDOUT)
+
+10 while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call eprintf ("Header info not available for [%s]\n")
+ call pargstr (Memc[image])
+ goto 10
+ }
+
+ iferr (beam = imgeti (im, "BEAM-NUM"))
+ beam = 0
+ if (beam < 0 || beam > MAX_NR_BEAMS-1)
+ call error (0, "Invalid aperture number")
+
+ iferr (expo = imgetr (im, "EXPOSURE"))
+ iferr (expo = imgetr (im, "ITIME"))
+ iferr (expo = imgetr (im, "EXPTIME"))
+ expo = 1
+
+ # Add spectrum into accumulator
+ if (IS_INDEFI (Memi[bstat+beam])) {
+ Memi[npts+beam] = IM_LEN (im,1)
+ call salloc (Memi[accum+beam], Memi[npts+beam], TY_REAL)
+ call aclrr (Memr[Memi[accum+beam]], Memi[npts+beam])
+ Memi[bstat+beam] = 0
+
+ call salloc (Memi[title+beam], SZ_LINE, TY_CHAR)
+ call strcpy (IM_TITLE(im), Memc[Memi[title+beam]], SZ_LINE)
+ }
+
+ call ff_accum_spec (im, Memi[npts], expo, Memi[bstat], beam+1,
+ Memi[accum], Memr[esum], coincidence, ccmode, dtime, power,
+ Memi[title])
+
+ call printf ("[%s] added to aperture %1d\n")
+ call pargstr (Memc[image])
+ call pargi (beam)
+ call flush (STDOUT)
+ if (Memi[images+beam] == NULL)
+ call salloc (Memi[images+beam], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[images+beam]], SZ_FNAME)
+
+ call imunmap (im)
+ }
+
+ # Review all apertures containing data and perform fits.
+ # Act interactively if desired
+ do i = 0, MAX_NR_BEAMS-1 {
+ if (!IS_INDEFI (Memi[bstat+i])) {
+ call fit_spec (Memr[Memi[accum+i]], Memi[npts+i], Memr[esum+i],
+ interact, function, order, niter, lower, upper, ngrow,
+ div_min, i)
+ if (interact & !all)
+ interact = false
+ call wrt_fit_spec (Memc[Memi[images+i]], Memr[Memi[accum+i]],
+ Memr[esum+i], Memc[ofile], i, Memc[Memi[title+i]],
+ Memi[npts+i], order)
+ }
+ }
+
+ call sfree (sp)
+ call clpcls (root)
+end
+
+# ACCUM_SPEC -- Accumulate spectra by beams
+
+procedure ff_accum_spec (im, len, expo, beam_stat, beam, accum, expo_sum,
+ coincidence, ccmode, dtime, power, title)
+
+pointer im, accum[ARB], title[ARB]
+real expo, expo_sum[ARB]
+int beam_stat[ARB], beam, len[ARB]
+bool coincidence
+int ccmode
+real dtime, power
+
+int npts, co_flag, imgeti()
+pointer pix
+
+pointer imgl1r()
+
+begin
+ npts = IM_LEN (im, 1)
+
+ # Map pixels and optionally correct for coincidence
+ pix = imgl1r (im)
+ if (coincidence) {
+ iferr (co_flag = imgeti (im, "CO-FLAG"))
+ co_flag = -1
+ if (co_flag < 1) {
+ call coincor (Memr[pix], Memr[pix], npts, expo, co_flag,
+ dtime, power, ccmode)
+ }
+ }
+
+ # Add in the current data
+ npts = min (npts, len[beam])
+
+ call aaddr (Memr[pix], Memr[accum[beam]], Memr[accum[beam]], npts)
+
+ beam_stat[beam] = beam_stat[beam] + 1
+ expo_sum [beam] = expo_sum [beam] + expo
+end
+
+# WRT_FIT_SPEC -- Write out normalized spectrum
+
+procedure wrt_fit_spec (image, accum, expo_sum, ofile, beam, title, npts, order)
+
+char image[SZ_FNAME]
+real accum[ARB], expo_sum
+int beam, npts, order
+char ofile[SZ_FNAME]
+char title[SZ_LINE]
+
+char output[SZ_FNAME], temp[SZ_LINE]
+pointer im, imnew, newpix
+
+pointer immap(), impl1r()
+int strlen()
+
+begin
+ im = immap (image, READ_ONLY, 0)
+10 call strcpy (ofile, output, SZ_FNAME)
+ call sprintf (output[strlen (output) + 1], SZ_FNAME, ".%04d")
+ call pargi (beam)
+
+ # Create new image with a user area
+ # If an error occurs, ask user for another name to try
+ # since many open errors result from trying to overwrite an
+ # existing image.
+
+ iferr (imnew = immap (output, NEW_COPY, im)) {
+ call eprintf ("Cannot create [%s] -- Already exists??\07\n")
+ call pargstr (output)
+ call clgstr ("output", ofile, SZ_FNAME)
+ go to 10
+ }
+
+ call strcpy ("Normalized flat:", temp, SZ_LINE)
+ call sprintf (temp[strlen (temp) + 1], SZ_LINE, "%s")
+ call pargstr (title)
+ call strcpy (temp, IM_TITLE (imnew), SZ_LINE)
+ IM_PIXTYPE (imnew) = TY_REAL
+
+ newpix = impl1r (imnew)
+ call amovr (accum, Memr[newpix], npts)
+
+ call imaddr (imnew, "EXPOSURE", expo_sum)
+ call imaddi (imnew, "QF-FLAG", order)
+ call imunmap (im)
+ call imunmap (imnew)
+
+ call printf ("Fit for aperture %1d --> [%s]\n")
+ call pargi (beam)
+ call pargstr (output)
+ call flush (STDOUT)
+end
+
+# FIT_SPEC -- Fit a line through the spectrum with user interaction
+
+procedure fit_spec (accum, npts, expo_sum, interact, function,
+ order, niter, lower, upper, ngrow, div_min, beam)
+
+real accum[ARB], expo_sum
+bool interact
+int function, order, niter, ngrow, npts, beam
+real lower, upper, div_min
+
+int cc, key, gp, plt_mode
+int i, initer, sum_niter, newgraph
+real x1, y1, sigma, temp
+pointer sp, wts, x, y, cv
+bool first
+char gtitle[SZ_LINE], command[SZ_FNAME]
+
+int clgcur(), clgeti()
+pointer gopen()
+real clgetr(), cveval()
+
+data plt_mode/PLT_FIT/
+
+begin
+ # Perform initial fit
+ call smark (sp)
+ call salloc (wts, npts, TY_REAL)
+ call salloc (x , npts, TY_REAL)
+ call salloc (y , npts, TY_REAL)
+
+ first = true
+ if (!interact) {
+ sum_niter = 0
+ do i = 1, niter
+ call linefit (accum, npts, function, order, lower, upper,
+ ngrow, cv, first, Memr[wts], Memr[x])
+ sum_niter = niter
+
+ } else {
+ gp = gopen ("stdgraph", NEW_FILE, STDGRAPH)
+ call sprintf (gtitle, SZ_LINE, "Flat Field Sum - %f seconds ap:%1d")
+ call pargr (expo_sum)
+ call pargi (beam)
+
+ key = 'r'
+ repeat {
+ switch (key) {
+ case 'e': # Plot errors
+ plt_mode = PLT_ERR
+ newgraph = YES
+
+ case 'f': # Plot fit
+ plt_mode = PLT_FIT
+ newgraph = YES
+
+ case 'o': # Change order
+ order = clgeti ("new_order")
+ # Reinstate all pixels
+ first = true
+ newgraph = YES
+
+ case 'l': # Change lower sigma
+ lower = clgetr ("new_lower")
+ newgraph = YES
+
+ case 'u': # Change upper sigma
+ upper = clgetr ("new_upper")
+ newgraph = YES
+
+ case 'r': # Reset fit parameters
+ first = true
+ newgraph = YES
+
+ case 's': # Change both rejection sigmas
+ lower = clgetr ("new_lower")
+ upper = lower
+ call clputr ("new_upper", upper)
+ newgraph = YES
+
+ case 'i': # Iterate again - Drop thru
+ initer = 1
+ newgraph = YES
+
+ case 'n': # Iterate n times
+ initer = clgeti ("new_niter")
+ newgraph = YES
+
+ case 'q': # Quit
+ break
+
+ case '?': # Clear and help
+ call gpagefile (gp, KEY, PROMPT)
+
+ case '/': # Status line help
+ call ff_sts_help
+
+ case 'I': # Interrupt
+ call fatal (0, "Interrupt")
+
+ default:
+ call printf ("\07\n")
+ }
+
+ if (newgraph == YES) {
+ # Suppress an iteration if plot mode change requested
+ if (key != 'e' && key != 'f') {
+ if (first) {
+ sum_niter = 0
+ initer = niter
+ call cvfree (cv)
+ }
+ do i = 1, initer
+ call linefit (accum, npts, function, order, lower,
+ upper, ngrow, cv, first, Memr[wts], Memr[x])
+ sum_niter = sum_niter + initer
+ }
+
+ switch (plt_mode) {
+ case PLT_FIT:
+ call plot_fit (gp, accum, cv, function, order, npts,
+ gtitle, Memr[wts], Memr[x], Memr[y], sigma)
+ case PLT_ERR:
+ call plot_fit_er (gp, accum, cv, function, order, npts,
+ gtitle, Memr[wts], Memr[x], Memr[y], sigma)
+ }
+
+ newgraph = NO
+ }
+ } until (clgcur ("cursor",x1,y1,cc,key,command,SZ_FNAME) == EOF)
+ call gclose (gp)
+ }
+
+ # Replace original data with the data/fit
+ do i = 1, npts {
+ temp = cveval (cv, real (i))
+ if (temp == 0.0)
+ temp = max (temp, div_min)
+ accum[i] = accum[i] / temp
+ }
+
+ call cvfree (cv)
+ call sfree (sp)
+
+ # Save iteration count for next time
+ niter = sum_niter
+end
+
+# LINEFIT -- Fit desired function thru data
+
+procedure linefit (pix, npts, function, order, lower, upper, ngrow, cv,
+ first, wts, x)
+
+real pix[ARB] # Data array to fit
+int npts # Elements in array
+int function # Type of fitting function
+int order # Order of fitting function
+real lower # Lower rejection threshold
+real upper # Upper rejection threshold
+int ngrow # Rejection growing radius
+pointer cv
+real wts[ARB] # Array weights
+real x[ARB]
+bool first
+
+int ier, i, nreject
+
+int reject()
+
+begin
+10 if (first) {
+ do i = 1, npts {
+ x[i] = i
+ wts[i] = 1.0
+ }
+
+ # Initialize curve fitting.
+ call cvinit (cv, function, order, 1., real (npts))
+ call cvfit (cv, x, pix, wts, npts, WTS_USER, ier)
+ nreject = 0
+ first = false
+ }
+
+ # Do pixel rejection if desired.
+ if ((lower > 0.) || (upper > 0.))
+ nreject = reject (cv, x, pix, wts, npts, lower, upper, ngrow)
+ else
+ nreject = 0
+
+ if (nreject == ERR) {
+ call eprintf ("Cannot fit data -- too many points rejected??\n")
+ call cvfree (cv)
+ first = true
+ go to 10
+ }
+end
+
+# REJECT -- Reject points with large residuals from the fit.
+#
+# The sigma of the input to the fit is calculated. The rejection thresholds
+# are set at -lower*sigma and upper*sigma. Points outside the rejection
+# thresholds are rejected from the fit and flaged by setting their
+# weights to zero. Finally, the remaining points are refit and a new
+# fit line evaluated. The number of points rejected is returned.
+
+int procedure reject (cv, x, y, w, npoints, lower, upper, ngrow)
+
+pointer cv # Curve descriptor
+real x[ARB] # Input ordinates
+real y[ARB] # Input data values
+real w[ARB] # Weights
+int npoints # Number of input points
+real lower # Lower rejection sigma
+real upper # Upper rejection sigma
+int ngrow # Rejection radius
+
+int i, j, n, i_min, i_max, nreject
+real sigma, residual, resid_min, resid_max
+
+real cveval()
+
+begin
+ # Determine sigma of fit and set rejection limits.
+ sigma = 0.
+ n = 0
+ do i = 1, npoints {
+ if (w[i] == 0.)
+ next
+ sigma = sigma + (y[i] - cveval (cv, x[i])) ** 2
+ n = n + 1
+ }
+
+ sigma = sqrt (sigma / (n - 1))
+ resid_min = -lower * sigma
+ resid_max = upper * sigma
+
+ # Reject the residuals exceeding the rejection limits.
+ nreject = 0
+ for (i = 1; i <= npoints; i = i + 1) {
+ if (w[i] == 0.)
+ next
+ residual = y[i] - cveval (cv, x[i])
+ if ((residual < resid_min) || (residual > resid_max)) {
+ i_min = max (1, i - ngrow)
+ i_max = min (npoints, i + ngrow)
+
+ # Reject points from the fit and flag them with zero weight.
+ do j = i_min, i_max {
+ call cvrject (cv, x[j], y[j], w[j])
+ w[j] = 0.
+ nreject = nreject + 1
+ }
+ i = i_max
+ }
+ }
+
+ # Refit if points have been rejected.
+ if (nreject > 0) {
+ call cvsolve (cv, i)
+ if (i != OK)
+ return (ERR)
+ }
+
+ return (nreject)
+end
+
+# PLOT_FIT -- Plot the fit to the image line and data
+
+procedure plot_fit (gp, pix, cv, function, order, npts, gtitle, wts, xfit,
+ yfit, sigma)
+
+int gp, npts, function, order
+real pix[ARB], wts[ARB], xfit[ARB], yfit[ARB]
+pointer cv
+real sigma
+char gtitle[SZ_LINE]
+
+real x1, x2
+int i
+
+begin
+ # Set up plot
+ x1 = 1.0
+ x2 = npts
+
+ call gseti (gp, G_NMINOR, 0)
+ call gclear (gp)
+ call gsview (gp, 0.15, 0.95, 0.20, 0.9)
+ call gploto (gp, pix, npts, x1, x2, gtitle)
+
+ # Now plot the fit
+ do i = 1, npts
+ xfit[i] = i
+
+ call cvvector (cv, xfit, yfit, npts)
+ call gvline (gp, yfit, npts, x1, x2)
+
+ # Compute sigma and write it out
+ call get_sigma (pix, yfit, wts, npts, sigma)
+ call show_status (function, order, sigma, npts, wts)
+end
+
+# PLOT_FIT_ER -- Plot the error in the fit to the image line and data
+
+procedure plot_fit_er (gp, pix, cv, function, order, npts, gtitle, wts, xfit,
+ yfit, sigma)
+
+int gp, npts, function, order
+real pix[ARB], wts[ARB], xfit[ARB], yfit[ARB]
+pointer cv
+real sigma
+char gtitle[SZ_LINE]
+
+real x1, x2, y[2]
+int i
+
+begin
+ # Set up plot
+ x1 = 1.0
+ x2 = npts
+ y[1] = -0.0001
+ y[2] = +0.0001
+
+ call cvvector (cv, xfit, yfit, npts)
+
+ # Compute percentage errors
+ do i = 1, npts
+ if (pix[i] != 0.0)
+ yfit[i] = (pix[i] - yfit[i]) / pix[i]
+ else
+ yfit[i] = 0.0
+
+ call gseti (gp, G_NMINOR, 0)
+ call gclear (gp)
+ call gsview (gp, 0.15, 0.95, 0.20, 0.9)
+
+ call gploto (gp, yfit, npts, x1, x2,
+ "Flat field fractional error in fit")
+
+ # Draw a zero error line
+ call gline (gp, x1, y[1], x2, y[2])
+
+ # Compute sigma
+ call get_sigma0 (yfit, wts, npts, sigma)
+ call show_status (function, order, sigma, npts, wts)
+end
+
+# SHOW_STATUS -- Show the fit status on status line
+
+procedure show_status (function, order, sigma, npts, wts)
+
+int function, order, npts
+real sigma, wts[ARB]
+
+int i, nvals
+
+begin
+ # Count non-rejected points
+ nvals = 0
+ do i = 1, npts
+ if (wts[i] != 0.0)
+ nvals = nvals + 1
+
+ call printf ("Fit type: %s order: %2d rms: %6.3f")
+ switch (function) {
+ case LEGENDRE:
+ call pargstr ("Legendre")
+ case CHEBYSHEV:
+ call pargstr ("Chebyshev")
+ case SPLINE3:
+ call pargstr ("Spline3")
+ case SPLINE1:
+ call pargstr ("Spline1")
+ default:
+ call pargstr ("???")
+ }
+
+ call pargi (order)
+ call pargr (sigma)
+
+ call printf (" points: %d out of %d")
+ call pargi (nvals)
+ call pargi (npts)
+
+ call flush (STDOUT)
+end
+
+# GET_SIGMA -- Compute rms error between two vectors whose average difference
+# is zero.
+
+procedure get_sigma (y1, y2, wts, n, sigma)
+
+real y1[ARB], y2[ARB], wts[ARB], sigma
+int n
+
+int i, nval
+real sum
+
+begin
+ sum = 0.0
+ nval = 0
+ do i = 1, n
+ if (wts[i] != 0.0) {
+ sum = sum + (y1[i] - y2[i]) ** 2
+ nval = nval + 1
+ }
+
+ sigma = sqrt (sum / (nval-1))
+ return
+end
+
+# GET_SIGMA0 -- Compute rms error of a vector
+
+procedure get_sigma0 (y1, wts, n, sigma)
+
+real y1[ARB], wts[ARB], sigma
+int n
+
+int i, nval
+real sum
+
+begin
+ sum = 0.0
+ nval = 0
+ do i = 1, n
+ if (wts[i] != 0.0) {
+ sum = sum + y1[i]**2
+ nval = nval + 1
+ }
+
+ sigma = sqrt (sum / (nval-1))
+ return
+end
+
+# FF_STS_HELP -- Status line help for Flat Fit
+
+procedure ff_sts_help ()
+
+int linenr, maxline
+
+data linenr/1/
+data maxline/2/
+
+begin
+ switch (linenr) {
+ case 1:
+ call printf ("e=err plot f=data plot o=order l=lower sigma ")
+ call printf ("u=upper sigma s=both sigmas")
+
+ case 2:
+ call printf ("r=incl reject i=iterate n=niterate q=quit ")
+ call printf ("?=help /=linehelp <CR>=quit")
+ }
+
+ call flush (STDOUT)
+
+ linenr = linenr + 1
+ if (linenr > maxline)
+ linenr = 1
+end
diff --git a/noao/onedspec/irsiids/t_slist1d.x b/noao/onedspec/irsiids/t_slist1d.x
new file mode 100644
index 00000000..75837d50
--- /dev/null
+++ b/noao/onedspec/irsiids/t_slist1d.x
@@ -0,0 +1,163 @@
+include <error.h>
+include <imhdr.h>
+include <fset.h>
+include <smw.h>
+
+
+# SLIST1D -- Lists header information from IIDS/IRS format header
+# This is the original T_SLIST.
+
+procedure t_slist1d ()
+
+int root
+int long_header
+pointer sp, image, im, mw, sh, ptr
+int i, nl, df, sm, qf, qd, bs, co
+
+int btoi(), imtgetim(), imgeti()
+bool clgetb()
+pointer imtopenp(), immap(), smw_openim()
+errchk immap, smw_openim, shdr_open
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_LINE, TY_CHAR)
+
+ # Parameters
+ root = imtopenp ("input")
+ call clgstr ("records", Memc[image], SZ_LINE)
+ call odr_openp (root, Memc[image])
+ long_header = btoi (clgetb ("long_header"))
+
+ # Initialize
+ call fseti (STDOUT, F_FLUSHNL, YES)
+
+ # Loop over all input images by subsets
+ while (imtgetim (root, Memc[image], SZ_FNAME) != EOF) {
+
+ # Open image
+ iferr {
+ im = NULL
+ mw = NULL
+ ptr = immap (Memc[image], READ_ONLY, 0); im = ptr
+ ptr = smw_openim (im); mw = ptr
+ call shdr_open (im, mw, 1, 1, INDEFI, SHHDR, sh)
+ } then {
+ if (mw != NULL) {
+ call smw_close (mw)
+ if (sh != NULL)
+ MW(sh) = NULL
+ }
+ if (im != NULL)
+ call imunmap (im)
+ call erract (EA_WARN)
+ next
+ }
+
+ nl = IM_LEN(im,2)
+ do i = 1, nl {
+ call shdr_open (im, mw, i, 1, INDEFI, SHHDR, sh)
+
+ if (long_header == YES) {
+ call printf ("[%s] %4dpts %s\n")
+ call pargstr (IMNAME(sh))
+ call pargi (SN(sh))
+ call pargstr (TITLE(sh))
+
+ if (OFLAG(sh) == 1) {
+ call printf ("oflag = OBJECT, beam_number = %d")
+ call pargi (BEAM(sh))
+ } else if (OFLAG (sh) == 0) {
+ call printf ("oflag = SKY, beam_number = %d")
+ call pargi (BEAM(sh))
+ }
+ call printf (",\n")
+
+ iferr (df = imgeti (im, "DF-FLAG"))
+ df = -1
+ iferr (sm = imgeti (im, "SM-FLAG"))
+ sm = -1
+ iferr (qf = imgeti (im, "QF-FLAG"))
+ qf = -1
+ iferr (qd = imgeti (im, "QD-FLAG"))
+ qd = -1
+ iferr (bs = imgeti (im, "BS-FLAG"))
+ bs = -1
+ iferr (co = imgeti (im, "CO-FLAG"))
+ co = -1
+
+ # Airmass may not be in header. It could be computed if
+ # if the observatory latitude were available.
+
+ call printf ("airmass = %5.3f,%25tW0 = %0.3f,")
+ call pargr (AM(sh))
+ call pargr (W0(sh))
+ call printf (" WPC = %0.5g, ITM = %.2f,\n")
+ call pargr (WP(sh))
+ call pargr (IT(sh))
+ call printf ("NP1 = %d, NP2 = %d,")
+ call pargi (NP1(sh))
+ call pargi (NP2(sh))
+ call printf (" UT = %0.1h, ST = %0.1h,\n")
+ call pargr (UT(sh))
+ call pargr (ST(sh))
+ call printf ("HA = %0.2h,")
+ call pargr (HA(sh))
+ call printf (" RA = %0.2h, DEC = %0.1h,\n")
+ call pargr (RA(sh))
+ call pargr (DEC(sh))
+ call printf (
+ "df = %d, sm = %d, qf = %d, dc = %d, qd = %d, ")
+ call pargi (df)
+ call pargi (sm)
+ call pargi (qf)
+ call pargi (DC(sh))
+ call pargi (qd)
+ call printf ("ex = %d, bs = %d, ca = %d, co = %d")
+ call pargi (EC(sh))
+ call pargi (bs)
+ call pargi (FC(sh))
+ call pargi (co)
+
+ call printf ("\n\n")
+ } else {
+ if (nl == 1) {
+ call printf ("[%s]:%s %4ds %4dpts %s\n")
+ call pargstr (IMNAME(sh))
+ if (OFLAG(sh) == 1)
+ call pargstr ("o")
+ else
+ call pargstr ("s")
+ call pargr (IT(sh))
+ call pargi (SN(sh))
+ call pargstr (TITLE(sh))
+ } else {
+ call printf ("[%s]:%s %6.2fs %4dpts %dspectra %s\n")
+ call pargstr (IMNAME(sh))
+ if (OFLAG(sh) == 1)
+ call pargstr ("o")
+ else
+ call pargstr ("s")
+ call pargr (IT(sh))
+ call pargi (SN(sh))
+ call pargi (nl)
+ call pargstr (TITLE(sh))
+ }
+ break
+ }
+ }
+
+ call smw_close (mw)
+ if (sh != NULL)
+ MW(sh) = NULL
+ call imunmap (im)
+ }
+
+ # Null out record string to avoid learn mode
+ call clpstr ("records", "")
+
+ # Free space
+ call shdr_close (sh)
+ call imtclose (root)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/irsiids/t_subsets.x b/noao/onedspec/irsiids/t_subsets.x
new file mode 100644
index 00000000..6a2a61bf
--- /dev/null
+++ b/noao/onedspec/irsiids/t_subsets.x
@@ -0,0 +1,121 @@
+include <error.h>
+include <imhdr.h>
+
+
+# T_SUBSETS -- Sub a series of spectra by pairs. A single spectrum
+# is produced for every pair.
+#
+
+procedure t_subsets ()
+
+pointer image
+pointer recstr, ofile
+int root, start_rec, subset
+int nrecs
+int npts, nrem, ifile, tog
+real expo, wtsum
+pointer sp, recs, im[2], cur_pix, sp_sum
+
+real imgetr()
+int clpopni(), clgeti()
+int get_next_image(), decode_ranges()
+pointer immap(), imgl1r()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (recstr, SZ_LINE, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+
+ # Open input file name template
+ root = clpopni ("input")
+
+ # Get range specification if any
+ call clgstr ("records", Memc[recstr], SZ_LINE)
+ if (decode_ranges (Memc[recstr], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # Get rootname for output files and starting record
+ call clgstr ("output", Memc[ofile], SZ_FNAME)
+ start_rec = clgeti ("start_rec")
+
+ # Initialize range decoder
+ call reset_next_image ()
+
+ #Initialize file counter
+ ifile = 0
+
+ # Set weighting value needed by spectrum writer
+ wtsum = 1.0
+
+ # Define subset of operation is a pair
+ subset = 2
+
+ # Loop over all input images by subsets
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+
+ # Get toggle value
+ tog = mod (ifile, 2) + 1
+
+ # Open image
+ iferr (im[tog] = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+
+ # Load data
+ cur_pix = imgl1r (im[tog])
+
+ # Allocate space for the sum
+ if (mod (ifile,2) == 0) {
+ npts = IM_LEN (im[tog],1)
+ call calloc (sp_sum, npts, TY_REAL)
+
+ # Zero exposure counter
+ expo = 0.0
+
+ # Add first spectrum
+ call amovr (Memr[cur_pix], Memr[sp_sum], npts)
+
+ iferr (expo = imgetr (im[tog], "EXPOSURE"))
+ iferr (expo = imgetr (im[tog], "ITIME"))
+ iferr (expo = imgetr (im[tog], "EXPTIME"))
+ expo = 1
+
+ call printf ("[%s] added\n")
+ call pargstr (Memc[image])
+ call flush (STDOUT)
+
+ } else {
+ # Subtract second spectrum
+ call asubr (Memr[sp_sum], Memr[cur_pix], Memr[sp_sum],
+ min (npts, IM_LEN(im[tog],1)))
+ call printf ("[%s] subtracted\n")
+ call pargstr (Memc[image])
+ call flush (STDOUT)
+ call imunmap (im[2])
+
+ call wrt_set (Memr[sp_sum], subset, im[1], Memc[ofile],
+ start_rec, expo, wtsum, -1)
+ call mfree (sp_sum, TY_REAL)
+ }
+
+ ifile = ifile + 1
+ }
+ # Check that there are no remaining spectra in an unfulfilled subset
+ nrem = mod (ifile, 2)
+ if (nrem != 0) {
+ call mfree (sp_sum, TY_REAL)
+
+ call eprintf ("Unfulfilled pair ignored\n")
+ }
+
+ # Update record number
+ call clputi ("next_rec", start_rec)
+
+ # Free space
+ call sfree (sp)
+ call clpcls (root)
+end
diff --git a/noao/onedspec/irsiids/t_sums.x b/noao/onedspec/irsiids/t_sums.x
new file mode 100644
index 00000000..e28ebb35
--- /dev/null
+++ b/noao/onedspec/irsiids/t_sums.x
@@ -0,0 +1,239 @@
+include <error.h>
+include <imhdr.h>
+
+define MAX_NR_BEAMS 100 # Max number of instrument apertures
+
+# T_SUMS -- Compute sums of strings of spectra according to
+# Aperture number and object/sky flag. So for IIDS/IRS
+# type spectra, 4 sums will be generated.
+# In general, there will be 2N sums where N is the number
+# apertures.
+
+procedure t_sums ()
+
+pointer image # Image name to be added
+pointer images # Image name to be added
+pointer ofile # Output image file name
+pointer recstr # Record number string
+int recs # Spectral record numbers
+int root, nrecs # CL and ranges flags
+real expo # Exposure time
+pointer bstat[2] # Status of each aperture
+pointer npts[2] # Length of spectrum
+pointer esum[2] # Accumulated exposure time
+pointer accum[2] # Pointers to beam accumulators
+pointer title[2]
+int beam, object
+int start_rec
+
+int i, j
+pointer sp, work, im
+
+real imgetr()
+int clgeti(), clpopni(), imgeti()
+int get_next_image(), decode_ranges()
+pointer immap()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (images, MAX_NR_BEAMS, TY_POINTER)
+ call salloc (ofile, SZ_FNAME, TY_CHAR)
+ call salloc (recstr, SZ_LINE, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+ call salloc (accum, MAX_NR_BEAMS, TY_POINTER)
+ call salloc (title, MAX_NR_BEAMS, TY_POINTER)
+ call amovki (NULL, Memi[images], MAX_NR_BEAMS)
+ call salloc (work, 2*5*MAX_NR_BEAMS, TY_STRUCT)
+ bstat[1] = work
+ bstat[2] = work + MAX_NR_BEAMS
+ npts[1] = work + 2 * MAX_NR_BEAMS
+ npts[2] = work + 3 * MAX_NR_BEAMS
+ esum[1] = work + 4 * MAX_NR_BEAMS
+ esum[2] = work + 5 * MAX_NR_BEAMS
+ accum[1] = work + 6 * MAX_NR_BEAMS
+ accum[2] = work + 7 * MAX_NR_BEAMS
+ title[1] = work + 8 * MAX_NR_BEAMS
+ title[2] = work + 9 * MAX_NR_BEAMS
+
+ # Get task parameters.
+ root = clpopni ("input")
+
+ # Get input record numbers
+ call clgstr ("records", Memc[recstr], SZ_LINE)
+ if (decode_ranges (Memc[recstr], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ call clgstr ("output", Memc[ofile], SZ_LINE)
+
+ start_rec = clgeti ("start_rec")
+
+ call reset_next_image ()
+
+ # Clear all beam status flags
+ call amovki (INDEFI, Memi[bstat[1]], MAX_NR_BEAMS*2)
+ call aclrr (Memr[esum[1]], MAX_NR_BEAMS*2)
+
+ call printf ("Accumulating spectra --\n")
+ call flush (STDOUT)
+
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_FNAME) != EOF) {
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+
+ # Load header
+ iferr (beam = imgeti (im, "BEAM-NUM"))
+ beam = 0
+ if (beam < 0 || beam > MAX_NR_BEAMS-1)
+ call error (0, "Invalid aperture number")
+
+ # Select array: Object = array 2; sky = array 1
+ iferr (object = imgeti (im, "OFLAG"))
+ object = 1
+ if (object == 1)
+ object = 2
+ else
+ object = 1
+
+ iferr (expo = imgetr (im, "EXPOSURE"))
+ iferr (expo = imgetr (im, "ITIME"))
+ iferr (expo = imgetr (im, "EXPTIME"))
+ expo = 1
+
+ # Add spectrum into accumulator
+ if (IS_INDEFI (Memi[bstat[object]+beam])) {
+ Memi[npts[object]+beam] = IM_LEN (im,1)
+ call salloc (Memi[accum[object]+beam], IM_LEN(im,1), TY_REAL)
+ call aclrr (Memr[Memi[accum[object]+beam]], IM_LEN(im,1))
+ Memi[bstat[object]+beam] = 0
+
+ call salloc (Memi[title[object]+beam], SZ_LINE, TY_CHAR)
+ call strcpy (IM_TITLE(im), Memc[Memi[title[object]+beam]],
+ SZ_LINE)
+ }
+
+ call su_accum_spec (im, Memi[npts[1]], expo, Memi[bstat[1]],
+ beam+1, Memi[accum[1]], Memr[esum[1]], Memi[title[1]], object)
+
+ call printf ("[%s] %s spectrum added to aperture %1d\n")
+ call pargstr (Memc[image])
+ if (object == 2)
+ call pargstr ("object")
+ else
+ call pargstr ("sky ")
+ call pargi (beam)
+ call flush (STDOUT)
+
+ if (Memi[images+beam] == NULL)
+ call salloc (Memi[images+beam], SZ_FNAME, TY_CHAR)
+ call strcpy (Memc[image], Memc[Memi[images+beam]], SZ_FNAME)
+ call imunmap (im)
+ }
+
+ # Review all apertures containing data and write sums
+ do i = 0, MAX_NR_BEAMS-1
+ do j = 1, 2
+ if (!IS_INDEFI (Memi[bstat[j]+i])) {
+ call wrt_spec (Memc[Memi[images+i]], Memr[Memi[accum[j]+i]],
+ Memr[esum[j]+i], Memc[ofile], start_rec,
+ Memc[Memi[title[j]+i]], Memi[npts[j]+i], i, j)
+
+ start_rec = start_rec + 1
+ }
+
+ call clputi ("next_rec", start_rec)
+ call sfree (sp)
+ call clpcls (root)
+end
+
+# ACCUM_SPEC -- Accumulate spectra by beams
+
+procedure su_accum_spec (im, len, expo, beam_stat, beam, accum, expo_sum,
+ title, object)
+
+pointer im, accum[MAX_NR_BEAMS,2], title[MAX_NR_BEAMS,2]
+real expo, expo_sum[MAX_NR_BEAMS,2]
+int beam_stat[MAX_NR_BEAMS,2], beam, len[MAX_NR_BEAMS,2]
+int object
+
+int npts
+pointer pix
+
+pointer imgl1r()
+
+begin
+ npts = IM_LEN (im, 1)
+
+ # Map pixels and optionally correct for coincidence
+ pix = imgl1r (im)
+
+ # Add in the current data
+ npts = min (npts, len[beam, object])
+
+ call aaddr (Memr[pix], Memr[accum[beam, object]],
+ Memr[accum[beam, object]], npts)
+
+ beam_stat[beam, object] = beam_stat[beam, object] + 1
+ expo_sum [beam, object] = expo_sum [beam, object] + expo
+end
+
+# WRT_SPEC -- Write out normalized spectrum
+
+procedure wrt_spec (image, accum, expo_sum, ofile, start, title, npts, object,
+ beam)
+
+char image[SZ_FNAME]
+real accum[ARB], expo_sum
+int start, npts
+char ofile[SZ_FNAME]
+char title[SZ_LINE]
+int object, beam
+
+char output[SZ_FNAME], temp[SZ_LINE]
+pointer im, imnew, newpix
+
+pointer immap(), impl1r()
+int strlen()
+
+begin
+ im = immap (image, READ_ONLY, 0)
+10 call strcpy (ofile, output, SZ_FNAME)
+ call sprintf (output[strlen (output) + 1], SZ_FNAME, ".%04d")
+ call pargi (start)
+
+ # Create new image with a user area
+ # If an error occurs, ask user for another name to try
+ # since many open errors result from trying to overwrite an
+ # existing image.
+
+ iferr (imnew = immap (output, NEW_COPY, im)) {
+ call eprintf ("Cannot create [%s] -- Already exists??\07\n")
+ call pargstr (output)
+ call clgstr ("newoutput", ofile, SZ_FNAME)
+ go to 10
+ }
+
+ call strcpy ("Summation:", temp, SZ_LINE)
+ call sprintf (temp[strlen (temp) + 1], SZ_LINE, "%s")
+ call pargstr (title)
+ call strcpy (temp, IM_TITLE (imnew), SZ_LINE)
+
+ newpix = impl1r (imnew)
+ call amovr (accum, Memr[newpix], npts)
+
+ call imaddr (imnew, "EXPOSURE", expo_sum)
+ call imunmap (im)
+ call imunmap (imnew)
+
+ call printf ("%s sum for aperture %1d --> [%s]\n")
+ if (object == 1)
+ call pargstr ("Object")
+ else
+ call pargstr ("Sky ")
+ call pargi (beam)
+ call pargstr (output)
+ call flush (STDOUT)
+end
diff --git a/noao/onedspec/irsiids/t_widstape.x b/noao/onedspec/irsiids/t_widstape.x
new file mode 100644
index 00000000..1f96d146
--- /dev/null
+++ b/noao/onedspec/irsiids/t_widstape.x
@@ -0,0 +1,343 @@
+include <mach.h>
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+
+define SZ_IDSTITLE 64 # Length of IDSOUT title
+define SZ_CARD 80 # Columns on a card
+
+# T_WIDSTAPE -- Convert each line of an IRAF image to IDSOUT text format.
+# Each image line is treated as a one dimensional spectrum.
+# A maximum IDSOUT length of 1024 points is enforced silently.
+#
+# There are two types of output:
+# single -- All image lines are appended to a single IDSOUT file.
+# multiple -- Each image line is appended to a different IDSOUT file.
+
+procedure t_widstape ()
+
+pointer image # Image to be converted
+pointer recs # Record numbers
+pointer idsout # IDSOUT file or root name to be written
+int block_size # Block size
+bool ebcdic # ASCII or EBCDIC
+
+int i, mfd, root, nrecs
+pointer sp, im, mw, sh, ptr
+
+int open(), mtopen(), clgeti(), clpopni()
+int get_next_image(), decode_ranges(), mtfile(), mtneedfileno()
+bool clgetb()
+pointer immap(), smw_openim()
+errchk immap, smw_openim, shdr_open, wrt_ids_rec
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_LINE, TY_CHAR)
+ call salloc (idsout, SZ_FNAME, TY_CHAR)
+ call salloc (recs, 300, TY_INT)
+
+ # Parameters
+ root = clpopni ("input")
+ call clgstr ("records", Memc[image], SZ_LINE)
+ call clgstr ("idsout", Memc[idsout], SZ_FNAME)
+ block_size = clgeti ("block_size")
+ ebcdic = clgetb ("ebcdic")
+
+ # Set record numbers
+ if (decode_ranges (Memc[image], Memi[recs], 100, nrecs) == ERR)
+ call error (0, "Bad range specification")
+
+ # Check that a realistic block size was requested
+ if (mod (block_size, SZ_CARD) == 0)
+ block_size = block_size / SZB_CHAR
+ else
+ call error (0, "Blocks not integral number of cards")
+
+ # Open output tape file
+ # First determine if a file number was specified
+ if (mtfile (Memc[idsout]) == YES) {
+
+ # If no file, check if new_tape was specified and if so,
+ # force file=1; otherwise force file=EOT
+
+ if (mtneedfileno (Memc[idsout]) == YES) {
+ if (!clgetb("new_tape"))
+ call mtfname (Memc[idsout], EOT, Memc[idsout], SZ_FNAME)
+
+ else
+ call mtfname (Memc[idsout], 1, Memc[idsout], SZ_FNAME)
+ }
+ mfd = mtopen (Memc[idsout], WRITE_ONLY, block_size)
+ } else
+ mfd = open (Memc[idsout], NEW_FILE, BINARY_FILE)
+
+ # Loop over all files
+ call reset_next_image ()
+ while (get_next_image (root, Memi[recs], nrecs, Memc[image],
+ SZ_LINE) != EOF) {
+ iferr {
+ im = NULL
+ mw = NULL
+ ptr = immap (Memc[image], READ_ONLY, 0); im = ptr
+ ptr = smw_openim (im); mw = ptr
+
+ # Write out a spectrum for each line in the image
+ do i = 1, IM_LEN (im,2) {
+ call shdr_open (im, mw, i, 1, INDEFI, SHDATA, sh)
+ call wrt_ids_rec (mfd, sh, Memc[image], ebcdic)
+ }
+
+ call printf ("copied - [%s]: %s\n")
+ call pargstr (IMNAME(sh))
+ call pargstr (TITLE(sh))
+ call flush (STDOUT)
+ } then
+ call erract (EA_WARN)
+
+ if (mw != NULL)
+ call smw_close (mw)
+ if (im != NULL)
+ call imunmap (im)
+ }
+
+ call shdr_close (sh)
+ call close (mfd)
+ call sfree (sp)
+end
+
+
+# WRT_IDS_REC -- Write one IIDS/IRS format record in IDSOUT form
+
+procedure wrt_ids_rec (mfd, sh, image, ebcdic)
+
+int mfd
+pointer sh
+char image[SZ_FNAME]
+bool ebcdic
+
+# IDSOUT header parameters
+char label[SZ_IDSTITLE] # Record label
+int record # Record number
+int uttime # UT time in seconds
+int st # Siderial time in seconds
+real ra # Right Ascension in hours
+real dec # Declination in degrees
+real ha # Hour angle in hours
+real airmass # Air mass
+int itime # Integration time
+real wavelen1 # Wavelength of first pixel
+real dispersion # Dispersion per pixel
+
+int i, rec_no, df, sm, qf, qd, bs, co
+pointer sp, padline, bufline
+
+int strmatch(), imgeti()
+
+begin
+ call smark (sp)
+ call salloc (padline, SZ_LINE, TY_CHAR)
+ call salloc (bufline, SZ_LINE, TY_CHAR)
+
+ # Fill in header parameters.
+
+ call strcpy (TITLE(sh), label, SZ_IDSTITLE)
+
+ # The following two calculations were causing floating overflows
+ # when the header values were indefinite. SEH 7-23-86
+ if (IS_INDEF(UT(sh)))
+ uttime = INDEFI
+ else
+ uttime = UT(sh) * 3600.
+
+ if (IS_INDEF(ST(sh)))
+ st = INDEFI
+ else
+ st = ST(sh) * 3600.
+
+ ra = RA(sh)
+ dec = DEC(sh)
+ ha = HA(sh)
+ airmass = AM(sh)
+ itime = IT(sh)
+ wavelen1 = W0(sh)
+ dispersion = WP(sh)
+
+ iferr (df = imgeti (IM(sh), "DF-FLAG"))
+ df = -1
+ iferr (sm = imgeti (IM(sh), "SM-FLAG"))
+ sm = -1
+ iferr (qf = imgeti (IM(sh), "QF-FLAG"))
+ qf = -1
+ iferr (qd = imgeti (IM(sh), "QD-FLAG"))
+ qd = -1
+ iferr (bs = imgeti (IM(sh), "BS-FLAG"))
+ bs = -1
+ iferr (co = imgeti (IM(sh), "CO-FLAG"))
+ co = -1
+
+ # Create a padding line to fill the IDSOUT block to 1024 points.
+
+ call sprintf (Memc[padline], SZ_LINE,
+ "%10.4e%10.4e%10.4e%10.4e%10.4e%10.4e%10.4e%10.4e\n")
+ do i = 1, 8
+ call pargr (0.)
+
+ # Line 1 -- Record number, etc.
+ rec_no = strmatch (image, ".")
+ call sscan (image[rec_no])
+ call gargi (record)
+
+ call sprintf (Memc[bufline], SZ_LINE,
+ "%5d%5d%15.7e%15.7e%5d%5d%5d%5d%5d%5d%10d")
+ call pargi (record)
+ call pargi (itime)
+ call pargr (wavelen1)
+ call pargr (dispersion)
+ call pargi (0)
+ call pargi (SN(sh))
+ call pargi (BEAM(sh))
+ call pargi (-1)
+ call pargi (-1)
+ call pargi (0)
+ call pargi (uttime)
+
+ call putcard (mfd, Memc[bufline], ebcdic)
+
+ # Line 2 -- Siderial time, RA, and Dec.
+
+ call sprintf (Memc[bufline], SZ_LINE,
+ "%10d%15.7e%15.7e%5d%5d%5d%5d%5d%5d%5d%5d")
+ call pargi (st)
+ call pargr (ra)
+ call pargr (dec)
+ call pargi (0)
+ call pargi (df)
+ call pargi (sm)
+ call pargi (qf)
+ call pargi (DC(sh))
+ call pargi (qd)
+ call pargi (EC(sh))
+ call pargi (bs)
+
+ call putcard (mfd, Memc[bufline], ebcdic)
+
+ # Line 3 -- Hour angle, air mass, UT date, and exposure title.
+
+ call sprintf (Memc[bufline], SZ_LINE,
+ "%5d%5d%2w%-3.3s%5d%15.7e%15.7e%27wEND")
+ call pargi (FC(sh))
+ call pargi (co)
+ call pargstr ("IRF")
+ call pargi (OFLAG(sh))
+ call pargr (ha)
+ call pargr (airmass)
+
+ call putcard (mfd, Memc[bufline], ebcdic)
+
+ # Line 4 -- Record label.
+ call sprintf (Memc[bufline], SZ_LINE, "%-77sEND")
+ call pargstr (TITLE(sh))
+
+ call putcard (mfd, Memc[bufline], ebcdic)
+
+ # Lines 5 to 132
+
+ call putdata (mfd, Memr[SY(sh)], SN(sh), Memc[padline],
+ Memc[bufline], ebcdic)
+
+ # Line 133 -- Blank line
+
+ call sprintf (Memc[bufline], SZ_LINE, "%80w")
+ call putcard (mfd, Memc[bufline], ebcdic)
+end
+
+
+# PUTDATA -- Format and output extraction data to IDSOUT length of 1024 points.
+# Special effort is made to make the zero padding efficient.
+
+procedure putdata (mfd, data, npts, padline, bufline, ebcdic)
+
+int mfd # IDSOUT file descriptor
+real data[npts] # Data
+int npts # Number of data points
+char padline[ARB] # Padding string
+char bufline[ARB] # Output buffer string
+bool ebcdic # Convert to ebcdic
+
+int i, j, k, l, n
+int index
+double ddata
+
+int dtoc3()
+
+begin
+ j = min (1024, npts) # Maximum number of data points
+ k = j / 8 * 8 # Index of last data point in last complete line
+ if (k < j)
+ l = k + 8 # Index of last point in last line with data
+ else
+ l = k
+
+ # Write all complete data lines.
+
+ index = 1
+ do i = 1, k {
+ ddata = double (data[i])
+ n = dtoc3 (ddata, bufline[index], 10, 4, 'e', 10)
+ while (n < 10) {
+ bufline[index+n] = ' '
+ n = n + 1
+ }
+ index = index + 10
+ if (mod (i, 8) == 0) {
+ call putcard (mfd, bufline, ebcdic)
+ index = 1
+ }
+ }
+
+ # Write partial data line.
+
+ index = 1
+ do i = k + 1, l {
+ if (i <= j) {
+ ddata = double (data[i])
+ n = dtoc3 (ddata, bufline[index], 11, 5, 'e', 10)
+ } else
+ n = dtoc3 (0.D0, bufline[index], 11, 5, 'e', 10)
+ while (n < 10) {
+ bufline[index+n] = ' '
+ n = n + 1
+ }
+ index = index + 10
+ if (mod (i, 8) == 0) {
+ call putcard (mfd, bufline, ebcdic)
+ index = 1
+ }
+ }
+
+ # Write remaining padding lines.
+
+ do i = l + 1, 1024, 8
+ call putcard (mfd, padline, ebcdic)
+end
+
+# PUTCARD -- Convert to ebcdic if desired and write out card
+
+procedure putcard (mfd, bufline, ebcdic)
+
+int mfd
+char bufline[ARB]
+bool ebcdic
+
+char packline[SZ_LINE]
+
+begin
+ if (ebcdic) {
+ call ascii_to_ebcdic (bufline, packline, SZ_CARD)
+ call achtsb (packline, packline, SZ_CARD)
+ } else
+ call chrpak (bufline, 1, packline, 1, SZ_CARD)
+
+ call write (mfd, packline, SZ_CARD/SZB_CHAR)
+end
diff --git a/noao/onedspec/irsiids/widstape.par b/noao/onedspec/irsiids/widstape.par
new file mode 100644
index 00000000..33dee906
--- /dev/null
+++ b/noao/onedspec/irsiids/widstape.par
@@ -0,0 +1,8 @@
+# IDSOUT parameter file -- write a CYBER style IDSOUT tape
+
+idsout,s,a,,,,Output file or magtape
+input,s,a,,,,Image root name to write
+records,s,a,,,,Records to write
+block_size,i,h,3200,80,10640,Tape block size in bytes
+new_tape,b,h,no,,,Is this a new (blank) tape
+ebcdic,b,h,no,,,Convert character code to ebcdic
diff --git a/noao/onedspec/lcalib.par b/noao/onedspec/lcalib.par
new file mode 100644
index 00000000..cb7fc931
--- /dev/null
+++ b/noao/onedspec/lcalib.par
@@ -0,0 +1,10 @@
+# CALIBLIST parameter file
+
+option,s,a,,,,"List option (bands, ext, mags, fnu, flam, stars)"
+star_name,s,a,,,,Star name in calibration list
+mag,r,q,,,,Magnitude of star
+magband,s,q,,"U|B|V|R|I|J|H|K|L|Lprime|M",,"Magnitude type"
+teff,s,q,,,,Effective temperature or spectral type
+extinction,s,h,,,,Extinction file
+caldir,s,h,,,,Directory containing calibration data
+fnuzero,r,h,3.68e-20,,,Absolute flux zero point
diff --git a/noao/onedspec/mkpkg b/noao/onedspec/mkpkg
new file mode 100644
index 00000000..0c2e260c
--- /dev/null
+++ b/noao/onedspec/mkpkg
@@ -0,0 +1,72 @@
+# ONEDSPEC package.
+
+$call relink
+$exit
+
+update:
+ $call update@scombine
+ $call relink
+ $call install
+ ;
+
+relink:
+ $update libpkg.a
+ $omake x_onedspec.x
+ $link x_onedspec.o libpkg.a -lsmw\
+ -lxtools -lcurfit -lgsurfit -liminterp -lllsq -o xx_onedspec.e
+ ;
+
+install:
+ $move xx_onedspec.e noaobin$x_onedspec.e
+ ;
+
+smw:
+ $checkout libsmw.a noaolib$
+ $update libsmw.a
+ $checkin libsmw.a noaolib$
+ ;
+
+libsmw.a:
+ @smw
+ ;
+
+libpkg.a:
+ @dispcor
+ @ecidentify
+ @fortran
+ @identify
+ @irsiids
+ @odcombine
+ @sensfunc
+ @splot
+
+ getairm.x
+ getcalib.x <error.h> <ctype.h> <mach.h>
+ getextn.x <error.h>
+ odropenp.x <ctype.h>
+ t_calibrate.x <error.h> <imhdr.h> <imset.h> <math/iminterp.h> <smw.h>
+ t_deredden.x <error.h> <imhdr.h> <smw.h>
+ t_dopcor.x <error.h> <imhdr.h> <smw.h>
+ t_fitprofs.x <ctotok.h> <error.h> <gset.h> <imhdr.h> <smw.h> <time.h>
+ t_lcalib.x <ctype.h>
+ t_mkspec.x <imhdr.h>
+ t_names.x
+ t_rstext.x
+ t_sapertures.x <error.h> <imhdr.h> <smw.h>
+ t_sarith.x <error.h> <imhdr.h> <mach.h> <smw.h>
+ t_sbands.x <error.h> <smw.h>
+ t_scoords.x <error.h> <imhdr.h>
+ t_sfit.x <error.h> <imhdr.h> <smw.h> <math/curfit.h>\
+ <pkg/gtools.h> <pkg/rg.h>
+ t_sflip.x <error.h> <imhdr.h> <smw.h>
+ t_sinterp.x <imhdr.h> <math/curfit.h>
+ t_slist.x <error.h> <fset.h> <imhdr.h> <smw.h>
+ t_specplot.x specplot.h <ctype.h> <error.h> <gset.h> <imhdr.h>\
+ <mach.h> <pkg/gtools.h> <smw.h> <units.h>
+ t_specshift.x <error.h> <smw.h>
+ t_standard.x <error.h> <gset.h> <imhdr.h> <imset.h> <mach.h>\
+ <pkg/gtools.h> <smw.h>
+ t_tweak.x <error.h> <gset.h> <imhdr.h> <imset.h> <math.h>\
+ <math/iminterp.h> <pkg/gtools.h> <pkg/xtanswer.h>\
+ <smw.h> <units.h>
+ ;
diff --git a/noao/onedspec/mkspec.par b/noao/onedspec/mkspec.par
new file mode 100644
index 00000000..d836b8ad
--- /dev/null
+++ b/noao/onedspec/mkspec.par
@@ -0,0 +1,11 @@
+image_name,s,a,,,,Name of image file to be created
+image_title,s,a,,,,Title of image
+ncols,i,a,,,,Length of image
+nlines,i,a,,,,Number of lines (rows) in the image
+function,i,a,1,1,3,Function type (1=flat;2=ramp;3=Black body)
+constant,r,a,0.0,,,Flat image level
+start_level,r,a,,,,Ramp image first value
+end_level,r,a,,,,Ramp image last value
+start_wave,r,a,,0.0,,Starting wavelength for BB - Angstroms
+end_wave,r,a,,0.0,,Ending wavelength
+temperature,r,a,,0.0,,Black body temperature - Deg.K
diff --git a/noao/onedspec/names.par b/noao/onedspec/names.par
new file mode 100644
index 00000000..57341508
--- /dev/null
+++ b/noao/onedspec/names.par
@@ -0,0 +1,7 @@
+
+# NAMES parameter file
+
+input,s,a,,,,List of root file names
+records,s,a,,,,Range of spectral records
+append,s,h,"",,,String to append to generated image names
+check,b,h,no,,,Verify that image header exists
diff --git a/noao/onedspec/ndprep.cl b/noao/onedspec/ndprep.cl
new file mode 100644
index 00000000..8031a2a8
--- /dev/null
+++ b/noao/onedspec/ndprep.cl
@@ -0,0 +1,65 @@
+# NDPREP -- Generate an ND filter correction image for use over a specified
+# wavelength range from a filter file. The output correction image may
+# be 1D or 2D.
+
+procedure ndprep (filter_curve, output)
+
+file filter_curve {prompt="Input ND filter curve"}
+file output {prompt="Output calibration image"}
+real w0 {prompt="Starting wavelength (Angstroms)"}
+real dw {prompt="Wavelength increment (Angstroms)"}
+int nw {prompt="Number of wavelength points"}
+int nspace=0 {prompt="Number of spatial points (0 for 1D)"}
+bool logarithm=no {prompt="Use logarithmic wavelengths?"}
+bool flux=yes {prompt="Conserve flux when log rebinning?"}
+int dispaxis=2 {prompt="Dispersion axis"}
+file directory="onedstds$ctio/" {prompt="ND filter directory"}
+
+begin
+ file in, out, temp
+ bool log
+
+ # Page list of filters if '?'.
+ in = filter_curve
+ if (in == "?") {
+ page (directory // "ndfilters.men")
+ in = filter_curve
+ if (in == "?")
+ return
+ }
+
+ # Check if filter curve exists.
+ in = directory // in
+ if (!access (in))
+ error (0, "Filter curve "// in // " not found")
+
+ # Convert the filter curve to a 1D image.
+ out = output
+ sinterp (in, "", out, w0, dx=dw, npts=nw, make_image=yes,
+ interp_mode="curve")
+ hedit (out, "dc-flag", 0, add=yes, show=no, verify=no)
+
+ # Convert to log if desired.
+ if (logarithm == yes) {
+ temp = mktemp ("tmp")
+ dispcor (out, temp, linearize=yes, table="", w1=INDEF,
+ w2=INDEF, dw=INDEF, nw=INDEF, log=yes, flux=flux,
+ confirm=no, listonly=no, verbose=no, logfile="")
+ imdelete (out, verify=no)
+ imrename (temp, out, verbose=no)
+ }
+
+ # Convert to a 2D image if the number of spacial points is > 0.
+ if (nspace > 0) {
+ temp = mktemp ("tmp")
+ imstack (out, temp)
+ imdelete (out, verify=no)
+ imrename (temp, out, verbose=no)
+ if (dispaxis == 1) {
+ blkrep (out, out, 1, nspace)
+ } else {
+ imtranspose (out, out)
+ blkrep (out, out, nspace, 1)
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine.par b/noao/onedspec/odcombine.par
new file mode 100644
index 00000000..c2506352
--- /dev/null
+++ b/noao/onedspec/odcombine.par
@@ -0,0 +1,54 @@
+# SCOMBINE -- Spectrum combine parameters
+
+input,s,a,,,,List of images to combine
+output,s,a,,,,List of output images
+headers,s,h,"",,,List of header files (optional)
+bpmasks,s,h,"",,,List of bad pixel masks (optional)
+rejmasks,s,h,"",,,List of rejection masks (optional)
+nrejmasks,s,h,"",,,List of number rejected masks (optional)
+expmasks,s,h,"",,,List of exposure masks (optional)
+sigmas,s,h,"",,,List of sigma images (optional)
+logfile,s,h,"STDOUT",,,"Log file
+"
+apertures,s,h,"",,,Apertures to combine
+group,s,h,"apertures","all|images|apertures",,Grouping option
+first,b,h,no,,,Use first spectrum for dispersion?
+w1,r,h,INDEF,,,Starting wavelength of output spectra
+w2,r,h,INDEF,,,Ending wavelength of output spectra
+dw,r,h,INDEF,,,Wavelength increment of output spectra
+nw,i,h,INDEF,,,Length of output spectra
+log,b,h,no,,,"Logarithmic increments?
+"
+combine,s,h,"average","average|median|sum",,Type of combine operation
+reject,s,h,"none","none|minmax|ccdclip|crreject|sigclip|avsigclip|pclip",,Type of rejection
+outtype,s,h,"real","short|ushort|integer|long|real|double",,Output image pixel datatype
+outlimits,s,h,"",,,Output limits (x1 x2 y1 y2 ...)
+smaskformat,s,h,"bpmspectrum","bpmpixel|bpmspectrum",,Mask format
+smasktype,s,h,"none","none|goodvalue|badvalue|goodbits|badbits",,Mask type
+smaskvalue,r,h,0,,,Mask value
+blank,r,h,0.,,,"Value if there are no pixels
+"
+scale,s,h,"none",,,Image scaling
+zero,s,h,"none",,,Image zero point offset
+weight,s,h,"none",,,Image weights
+statsec,s,h,"",,,Image section for computing statistics
+expname,s,h,"",,,"Image header exposure time keyword
+"
+lthreshold,r,h,INDEF,,,Lower threshold
+hthreshold,r,h,INDEF,,,Upper threshold
+nlow,i,h,1,0,,minmax: Number of low pixels to reject
+nhigh,i,h,1,0,,minmax: Number of high pixels to reject
+nkeep,i,h,1,,,Minimum to keep (pos) or maximum to reject (neg)
+mclip,b,h,yes,,,Use median in sigma clipping algorithms?
+lsigma,r,h,3.,0.,,Lower sigma clipping factor
+hsigma,r,h,3.,0.,,Upper sigma clipping factor
+rdnoise,s,h,"0.",,,ccdclip: CCD readout noise (electrons)
+gain,s,h,"1.",,,ccdclip: CCD gain (electrons/DN)
+snoise,s,h,"0.",,,ccdclip: Sensitivity noise (fraction)
+sigscale,r,h,0.1,0.,,Tolerance for sigma clipping scaling corrections
+pclip,r,h,-0.5,,,pclip: Percentile clipping parameter
+grow,r,h,0.,0.,,"Radius for neighbor rejection
+"
+offsets,f,h,"physical","physical"
+masktype,s,h,"none"
+maskvalue,r,h,0
diff --git a/noao/onedspec/odcombine/mkpkg b/noao/onedspec/odcombine/mkpkg
new file mode 100644
index 00000000..d0b76b89
--- /dev/null
+++ b/noao/onedspec/odcombine/mkpkg
@@ -0,0 +1,18 @@
+$checkout libpkg.a ../
+$update libpkg.a
+$checkin libpkg.a ../
+$exit
+
+standalone:
+ $set LIBS1 = "-lsmw -liminterp -lxtools"
+ $update libpkg.a
+ $omake x_odcombine.x
+ $link x_odcombine.o libpkg.a $(LIBS1) -o xx_odcombine.e
+ ;
+
+libpkg.a:
+ @src
+
+ t_odcombine.x src/icombine.h src/icombine.com <imhdr.h> <error.h> \
+ <mach.h> <mwset.h> <smw.h>
+ ;
diff --git a/noao/onedspec/odcombine/odcombine.par b/noao/onedspec/odcombine/odcombine.par
new file mode 100644
index 00000000..b7b6e856
--- /dev/null
+++ b/noao/onedspec/odcombine/odcombine.par
@@ -0,0 +1,54 @@
+# SCOMBINE -- Spectrum combine parameters
+
+input,s,a,,,,List of images to combine
+output,s,a,,,,List of output images
+headers,s,h,"",,,List of header files (optional)
+bpmasks,s,h,"",,,List of bad pixel masks (optional)
+rejmasks,s,h,"",,,List of rejection masks (optional)
+nrejmasks,s,h,"",,,List of number rejected masks (optional)
+expmasks,s,h,"",,,List of exposure masks (optional)
+sigmas,s,h,"",,,List of sigma images (optional)
+logfile,s,h,"STDOUT",,,"Log file
+"
+apertures,s,h,"",,,Apertures to combine
+group,s,h,"apertures","all|images|apertures",,Grouping option
+first,b,h,no,,,Use first spectrum for dispersion?
+w1,r,h,INDEF,,,Starting wavelength of output spectra
+w2,r,h,INDEF,,,Ending wavelength of output spectra
+dw,r,h,INDEF,,,Wavelength increment of output spectra
+nw,i,h,INDEF,,,Length of output spectra
+log,b,h,no,,,"Logarithmic increments?
+"
+combine,s,h,"average","average|median|sum",,Type of combine operation
+reject,s,h,"none","none|minmax|ccdclip|crreject|sigclip|avsigclip|pclip",,Type of rejection
+outtype,s,h,"real","short|ushort|integer|long|real|double",,Output image pixel datatype
+outlimits,s,h,"",,,Output limits (x1 x2 y1 y2 ...)
+smaskformat,s,h,"bpmspectrum","bpmpixel|bpmspectrum",,Mask format
+smasktype,s,h,"none","none|goodvalue|badvalue|goodbits|badbits",,Mask type
+smaskvalue,r,h,0,,,Mask value
+blank,r,h,0.,,,"Value if there are no pixels
+"
+scale,s,h,"none",,,Image scaling
+zero,s,h,"none",,,Image zero point offset
+weight,s,h,"none",,,Image weights
+statsec,s,h,"",,,Image section for computing statistics
+expname,s,h,"",,,"Image header exposure time keyword
+"
+lthreshold,r,h,INDEF,,,Lower threshold
+hthreshold,r,h,INDEF,,,Upper threshold
+nlow,i,h,1,0,,minmax: Number of low pixels to reject
+nhigh,i,h,1,0,,minmax: Number of high pixels to reject
+nkeep,i,h,1,,,Minimum to keep (pos) or maximum to reject (neg)
+mclip,b,h,yes,,,Use median in sigma clipping algorithms?
+lsigma,r,h,3.,0.,,Lower sigma clipping factor
+hsigma,r,h,3.,0.,,Upper sigma clipping factor
+rdnoise,s,h,"0.",,,ccdclip: CCD readout noise (electrons)
+gain,s,h,"1.",,,ccdclip: CCD gain (electrons/DN)
+snoise,s,h,"0.",,,ccdclip: Sensitivity noise (fraction)
+sigscale,r,h,0.1,0.,,Tolerance for sigma clipping scaling corrections
+pclip,r,h,-0.5,,,pclip: Percentile clipping parameter
+grow,r,h,0.,0.,,"Radius (pixels) for neighbor rejection
+"
+offsets,f,h,"physical","physical"
+masktype,s,h,"none"
+maskvalue,r,h,0
diff --git a/noao/onedspec/odcombine/src/generic/icaclip.x b/noao/onedspec/odcombine/src/generic/icaclip.x
new file mode 100644
index 00000000..97c12346
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icaclip.x
@@ -0,0 +1,2206 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number of images for this algorithm
+
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclips (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclips (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Mems[d[1]+k]
+ else {
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Mems[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Mems[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Mems[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipi (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipi (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memi[d[1]+k]
+ else {
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memi[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memi[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memi[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+ else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipd (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, s1, r, one
+data one /1.0D0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipd (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+double med, low, high, r, s, s1, one
+data one /1.0D0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memd[d[1]+k]
+ else {
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memd[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memd[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memd[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/generic/icaverage.x b/noao/onedspec/odcombine/src/generic/icaverage.x
new file mode 100644
index 00000000..fc9f16da
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icaverage.x
@@ -0,0 +1,406 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averages (d, m, n, wts, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mems[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mems[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mems[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mems[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averagei (d, m, n, wts, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memi[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memi[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memi[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memi[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averager (d, m, n, wts, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averaged (d, m, n, wts, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+double average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+double sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memd[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memd[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memd[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memd[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
diff --git a/noao/onedspec/odcombine/src/generic/iccclip.x b/noao/onedspec/odcombine/src/generic/iccclip.x
new file mode 100644
index 00000000..bf655477
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/iccclip.x
@@ -0,0 +1,1790 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 2 # Mininum number of images for algorithm
+
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclips (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Mems[d[1]+k]
+ sum = sum + Mems[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclips (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Mems[d[n3-1]+k]
+ med = (med + Mems[d[n3]+k]) / 2.
+ } else
+ med = Mems[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipi (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memi[d[1]+k]
+ sum = sum + Memi[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipi (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memi[d[n3-1]+k]
+ med = (med + Memi[d[n3]+k]) / 2.
+ } else
+ med = Memi[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipr (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memr[d[1]+k]
+ sum = sum + Memr[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipr (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memr[d[n3-1]+k]
+ med = (med + Memr[d[n3]+k]) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipd (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, r, zero
+data zero /0.0D0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memd[d[1]+k]
+ sum = sum + Memd[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipd (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+double med, zero
+data zero /0.0D0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memd[d[n3-1]+k]
+ med = (med + Memd[d[n3]+k]) / 2.
+ } else
+ med = Memd[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/generic/icgdata.x b/noao/onedspec/odcombine/src/generic/icgdata.x
new file mode 100644
index 00000000..5cefcf5a
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icgdata.x
@@ -0,0 +1,1207 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+include "../icombine.h"
+
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatas (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnls()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnls
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnls (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnls (in[i], i, buf, v2, v1[2])
+ call amovs (Mems[buf+k-1], Mems[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Mems[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Mems[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Mems[d[k]+j-1] = Mems[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Mems[d[k]+j-1] = Mems[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_SHORT)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sorts (d, Mems[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sorts (d, Mems[dp], n, npts)
+ call mfree (dp, TY_SHORT)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatai (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnli()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnli
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnli (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnli (in[i], i, buf, v2, v1[2])
+ call amovi (Memi[buf+k-1], Memi[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memi[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memi[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memi[d[k]+j-1] = Memi[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memi[d[k]+j-1] = Memi[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_INT)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sorti (d, Memi[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sorti (d, Memi[dp], n, npts)
+ call mfree (dp, TY_INT)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatar (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnlr()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnlr
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnlr (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnlr (in[i], i, buf, v2, v1[2])
+ call amovr (Memr[buf+k-1], Memr[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memr[d[k]+j-1] = Memr[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memr[d[k]+j-1] = Memr[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_REAL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sortr (d, Memr[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sortr (d, Memr[dp], n, npts)
+ call mfree (dp, TY_REAL)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatad (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnld()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnld
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnld (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnld (in[i], i, buf, v2, v1[2])
+ call amovd (Memd[buf+k-1], Memd[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memd[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memd[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memd[d[k]+j-1] = Memd[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memd[d[k]+j-1] = Memd[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_DOUBLE)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sortd (d, Memd[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sortd (d, Memd[dp], n, npts)
+ call mfree (dp, TY_DOUBLE)
+ }
+end
+
diff --git a/noao/onedspec/odcombine/src/generic/icgrow.x b/noao/onedspec/odcombine/src/generic/icgrow.x
new file mode 100644
index 00000000..1ccb7885
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icgrow.x
@@ -0,0 +1,263 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <pmset.h>
+include "../icombine.h"
+
+# IC_GROW -- Mark neigbors of rejected pixels.
+# The rejected pixels (original plus grown) are saved in pixel masks.
+
+procedure ic_grow (out, v, m, n, buf, nimages, npts, pms)
+
+pointer out # Output image pointer
+long v[ARB] # Output vector
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[npts,nimages] # Working buffer
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k, l, line, nl, rop, igrow, nset, ncompress, or()
+real grow2, i2
+pointer mp, pm, pm_newmask()
+errchk pm_newmask()
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE || grow == 0.)
+ return
+
+ line = v[2]
+ nl = IM_LEN(out,2)
+ rop = or (PIX_SRC, PIX_DST)
+
+ igrow = grow
+ grow2 = grow**2
+ do l = 0, igrow {
+ i2 = grow2 - l * l
+ call aclri (buf, npts*nimages)
+ nset = 0
+ do j = 1, npts {
+ do k = n[j]+1, nimages {
+ mp = Memi[m[k]+j-1]
+ if (mp == 0)
+ next
+ do i = 0, igrow {
+ if (i**2 > i2)
+ next
+ if (j > i)
+ buf[j-i,mp] = 1
+ if (j+i <= npts)
+ buf[j+i,mp] = 1
+ nset = nset + 1
+ }
+ }
+ }
+ if (nset == 0)
+ return
+
+ if (pms == NULL) {
+ call malloc (pms, nimages, TY_POINTER)
+ do i = 1, nimages
+ Memi[pms+i-1] = pm_newmask (out, 1)
+ ncompress = 0
+ }
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ v[2] = line - l
+ if (v[2] > 0)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ if (l > 0) {
+ v[2] = line + l
+ if (v[2] <= nl)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ }
+ }
+ }
+ v[2] = line
+
+ if (ncompress > 10) {
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ call pm_compress (pm)
+ }
+ ncompress = 0
+ } else
+ ncompress = ncompress + 1
+end
+
+
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_grows (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Mems[d[j]+i-1] = Mems[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growi (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memi[d[j]+i-1] = Memi[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growr (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memr[d[j]+i-1] = Memr[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growd (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memd[d[j]+i-1] = Memd[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/src/generic/icmedian.x b/noao/onedspec/odcombine/src/generic/icmedian.x
new file mode 100644
index 00000000..1a2ed72d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icmedian.x
@@ -0,0 +1,692 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_medians (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+short temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Mems[d[j1]+k]
+ val2 = Mems[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mems[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Mems[d[j1]+k]
+ val2 = Mems[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mems[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mems[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Mems[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Mems[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mems[d[lo1]+k]
+ Mems[d[lo1]+k] = Mems[d[up1]+k]
+ Mems[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Mems[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mems[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Mems[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Mems[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mems[d[lo1]+k]
+ Mems[d[lo1]+k] = Mems[d[up1]+k]
+ Mems[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Mems[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Mems[d[1]+k]
+ val2 = Mems[d[2]+k]
+ val3 = Mems[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Mems[d[1]+k]
+ val2 = Mems[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Mems[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_mediani (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+int temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memi[d[j1]+k]
+ val2 = Memi[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memi[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memi[d[j1]+k]
+ val2 = Memi[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memi[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memi[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memi[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memi[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memi[d[lo1]+k]
+ Memi[d[lo1]+k] = Memi[d[up1]+k]
+ Memi[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memi[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memi[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memi[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memi[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memi[d[lo1]+k]
+ Memi[d[lo1]+k] = Memi[d[up1]+k]
+ Memi[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memi[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memi[d[1]+k]
+ val2 = Memi[d[2]+k]
+ val3 = Memi[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memi[d[1]+k]
+ val2 = Memi[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memi[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_medianr (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+real temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memr[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memr[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memr[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memr[d[lo1]+k]
+ Memr[d[lo1]+k] = Memr[d[up1]+k]
+ Memr[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memr[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memr[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memr[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memr[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memr[d[lo1]+k]
+ Memr[d[lo1]+k] = Memr[d[up1]+k]
+ Memr[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memr[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ val3 = Memr[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_mediand (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+double median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+double val1, val2, val3
+double temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memd[d[j1]+k]
+ val2 = Memd[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memd[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memd[d[j1]+k]
+ val2 = Memd[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memd[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memd[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memd[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memd[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memd[d[lo1]+k]
+ Memd[d[lo1]+k] = Memd[d[up1]+k]
+ Memd[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memd[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memd[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memd[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memd[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memd[d[lo1]+k]
+ Memd[d[lo1]+k] = Memd[d[up1]+k]
+ Memd[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memd[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memd[d[1]+k]
+ val2 = Memd[d[2]+k]
+ val3 = Memd[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memd[d[1]+k]
+ val2 = Memd[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memd[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
diff --git a/noao/onedspec/odcombine/src/generic/icmm.x b/noao/onedspec/odcombine/src/generic/icmm.x
new file mode 100644
index 00000000..5b2b13bf
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icmm.x
@@ -0,0 +1,644 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mms (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+short d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Mems[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Mems[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Mems[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Mems[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Mems[kmax] = d2
+ else
+ Mems[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Mems[kmin] = d1
+ else
+ Mems[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Mems[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Mems[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Mems[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Mems[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmi (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+int d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memi[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memi[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memi[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memi[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memi[kmax] = d2
+ else
+ Memi[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memi[kmin] = d1
+ else
+ Memi[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memi[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memi[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memi[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memi[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmr (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+real d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memr[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memr[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memr[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memr[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memr[kmax] = d2
+ else
+ Memr[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memr[kmin] = d1
+ else
+ Memr[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memr[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memr[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memr[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memr[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmd (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+double d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memd[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memd[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memd[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memd[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memd[kmax] = d2
+ else
+ Memd[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memd[kmin] = d1
+ else
+ Memd[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memd[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memd[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memd[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memd[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
diff --git a/noao/onedspec/odcombine/src/generic/icomb.x b/noao/onedspec/odcombine/src/generic/icomb.x
new file mode 100644
index 00000000..96138646
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icomb.x
@@ -0,0 +1,1917 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <pmset.h>
+include <error.h>
+include <syserr.h>
+include <mach.h>
+include "../icombine.h"
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+
+# ICOMBINE -- Combine images
+#
+# The memory and open file descriptor limits are checked and an attempt
+# to recover is made either by setting the image pixel files to be
+# closed after I/O or by notifying the calling program that memory
+# ran out and the IMIO buffer size should be reduced. After the checks
+# a procedure for the selected combine option is called.
+# Because there may be several failure modes when reaching the file
+# limits we first assume an error is due to the file limit, except for
+# out of memory, and close some pixel files. If the error then repeats
+# on accessing the pixels the error is passed back.
+
+
+procedure icombines (in, out, scales, zeros, wts, offsets, nimages, bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnls()
+pointer sp, d, id, n, m, lflag, v, dbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnls, impl1i, ic_combines
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_SHORT)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 1)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_SHORT)
+ }
+ call amovki (NULL, Memi[dbuf], nimages)
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnls (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combines (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts, nimages, npts)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combines (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli()
+pointer impnlr(), imgnlr()
+errchk immap, ic_scale, imgetr, ic_grow, ic_grows, ic_rmasks, ic_gdatas
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatas (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclips (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclips (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mms (d, id, n, npts)
+ case PCLIP:
+ call ic_pclips (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclips (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclips (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclips (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclips (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averages (d, id, n, wts, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medians (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averages (d, id, n, wts, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmas (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatas (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_grows (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averages (d, id, n, wts, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medians (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averages (d, id, n, wts, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmas (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ call sfree (sp)
+end
+
+procedure icombinei (in, out, scales, zeros, wts, offsets, nimages, bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnli()
+pointer sp, d, id, n, m, lflag, v, dbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnli, impl1i, ic_combinei
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_INT)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 1)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_INT)
+ }
+ call amovki (NULL, Memi[dbuf], nimages)
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnli (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combinei (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts, nimages, npts)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combinei (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli()
+pointer impnlr(), imgnlr()
+errchk immap, ic_scale, imgetr, ic_grow, ic_growi, ic_rmasks, ic_gdatai
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatai (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipi (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclipi (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mmi (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipi (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipi (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclipi (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipi (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclipi (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averagei (d, id, n, wts, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_mediani (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averagei (d, id, n, wts, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmai (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatai (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_growi (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averagei (d, id, n, wts, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_mediani (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averagei (d, id, n, wts, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmai (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ call sfree (sp)
+end
+
+procedure icombiner (in, out, scales, zeros, wts, offsets, nimages, bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnlr()
+pointer sp, d, id, n, m, lflag, v, dbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnlr, impl1i, ic_combiner
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 1)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_REAL)
+ }
+ call amovki (NULL, Memi[dbuf], nimages)
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnlr (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combiner (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts, nimages, npts)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combiner (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli()
+pointer impnlr(), imgnlr
+errchk immap, ic_scale, imgetr, ic_grow, ic_growr, ic_rmasks, ic_gdatar
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatar (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mmr (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipr (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averager (d, id, n, wts, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medianr (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averager (d, id, n, wts, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmar (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatar (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_growr (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averager (d, id, n, wts, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medianr (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averager (d, id, n, wts, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmar (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ call sfree (sp)
+end
+
+procedure icombined (in, out, scales, zeros, wts, offsets, nimages, bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnld()
+pointer sp, d, id, n, m, lflag, v, dbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnld, impl1i, ic_combined
+pointer impl1d()
+errchk impl1d
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_DOUBLE)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 1)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_DOUBLE)
+ }
+ call amovki (NULL, Memi[dbuf], nimages)
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1d (out[1])
+ call aclrd (Memd[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1d (out[3])
+ call aclrd (Memd[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnld (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combined (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts, nimages, npts)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combined (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli()
+pointer impnld(), imgnld
+errchk immap, ic_scale, imgetr, ic_grow, ic_growd, ic_rmasks, ic_gdatad
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnld (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatad (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipd (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memd[outdata])
+ else
+ call ic_accdclipd (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memd[outdata])
+ case MINMAX:
+ call ic_mmd (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipd (d, id, n, nimages, npts, Memd[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipd (d, id, n, scales, zeros, nimages, npts,
+ Memd[outdata])
+ else
+ call ic_asigclipd (d, id, n, scales, zeros, nimages, npts,
+ Memd[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipd (d, id, n, scales, zeros, nimages,
+ npts, Memd[outdata])
+ else
+ call ic_aavsigclipd (d, id, n, scales, zeros, nimages,
+ npts, Memd[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averaged (d, id, n, wts, npts, YES, YES,
+ Memd[outdata])
+ case MEDIAN:
+ call ic_mediand (d, n, npts, YES, Memd[outdata])
+ case SUM:
+ call ic_averaged (d, id, n, wts, npts, YES, NO,
+ Memd[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnld (out[3], buf, Meml[v1])
+ call ic_sigmad (d, id, n, wts, npts, Memd[outdata],
+ Memd[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnld (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatad (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_growd (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnld (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovd (Memd[buf], Memd[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averaged (d, id, n, wts, npts, NO, YES,
+ Memd[outdata])
+ case MEDIAN:
+ call ic_mediand (d, n, npts, NO, Memd[outdata])
+ case SUM:
+ call ic_averaged (d, id, n, wts, npts, NO, NO,
+ Memd[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnld (out[3], buf, Meml[v1])
+ call ic_sigmad (d, id, n, wts, npts, Memd[outdata],
+ Memd[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/generic/icpclip.x b/noao/onedspec/odcombine/src/generic/icpclip.x
new file mode 100644
index 00000000..237d9686
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icpclip.x
@@ -0,0 +1,878 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number for clipping
+
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclips (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Mems[d[n2-1]+j]
+ med = (med + Mems[d[n2]+j]) / 2.
+ } else
+ med = Mems[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Mems[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Mems[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Mems[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Mems[d[n5-1]+j]
+ med = (med + Mems[d[n5]+j]) / 2.
+ } else
+ med = Mems[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+j] = Mems[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+j] = Mems[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipi (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memi[d[n2-1]+j]
+ med = (med + Memi[d[n2]+j]) / 2.
+ } else
+ med = Memi[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memi[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memi[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memi[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memi[d[n5-1]+j]
+ med = (med + Memi[d[n5]+j]) / 2.
+ } else
+ med = Memi[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+j] = Memi[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+j] = Memi[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipr (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memr[d[n2-1]+j]
+ med = (med + Memr[d[n2]+j]) / 2.
+ } else
+ med = Memr[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memr[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memr[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memr[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memr[d[n5-1]+j]
+ med = (med + Memr[d[n5]+j]) / 2.
+ } else
+ med = Memr[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipd (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+double med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memd[d[n2-1]+j]
+ med = (med + Memd[d[n2]+j]) / 2.
+ } else
+ med = Memd[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memd[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memd[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memd[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memd[d[n5-1]+j]
+ med = (med + Memd[d[n5]+j]) / 2.
+ } else
+ med = Memd[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+j] = Memd[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+j] = Memd[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/generic/icsclip.x b/noao/onedspec/odcombine/src/generic/icsclip.x
new file mode 100644
index 00000000..a0188d72
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icsclip.x
@@ -0,0 +1,1922 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Mininum number of images for algorithm
+
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclips (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mems[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclips (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Mems[d[n3-1]+k] + Mems[d[n3]+k]) / 2.
+ else
+ med = Mems[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Mems[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Mems[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipi (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memi[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipi (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memi[d[n3-1]+k] + Memi[d[n3]+k]) / 2.
+ else
+ med = Memi[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memi[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memi[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memr[d[n3-1]+k] + Memr[d[n3]+k]) / 2.
+ else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memr[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memr[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipd (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, r, one
+data one /1.0D0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memd[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipd (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+double med, one
+data one /1.0D0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memd[d[n3-1]+k] + Memd[d[n3]+k]) / 2.
+ else
+ med = Memd[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memd[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memd[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/generic/icsigma.x b/noao/onedspec/odcombine/src/generic/icsigma.x
new file mode 100644
index 00000000..b9c9a781
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icsigma.x
@@ -0,0 +1,434 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmas (d, m, n, wts, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mems[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mems[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mems[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mems[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmai (d, m, n, wts, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memi[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memi[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memi[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memi[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmar (d, m, n, wts, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memr[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memr[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memr[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memr[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmad (d, m, n, wts, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+double average[npts] # Average
+double sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+double a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memd[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memd[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memd[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memd[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
diff --git a/noao/onedspec/odcombine/src/generic/icsort.x b/noao/onedspec/odcombine/src/generic/icsort.x
new file mode 100644
index 00000000..3ec1d27e
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icsort.x
@@ -0,0 +1,1096 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+define LOGPTR 32 # log2(maxpts) (4e9)
+
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sorts (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+short b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+short pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Mems[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Mems[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Mems[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sorts (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+short b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+short pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Mems[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Mems[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sorti (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+int b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+int pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memi[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memi[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memi[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sorti (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+int b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+int pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memi[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memi[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sortr (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memr[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sortr (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memr[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memr[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sortd (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+double b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+double pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memd[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memd[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memd[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sortd (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+double b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+double pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memd[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memd[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/src/generic/icstat.x b/noao/onedspec/odcombine/src/generic/icstat.x
new file mode 100644
index 00000000..3a0ed49c
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/icstat.x
@@ -0,0 +1,892 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+define NMAX 100000 # Maximum number of pixels to sample
+
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stats (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnls()
+
+real asums()
+short ic_modes()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_SHORT)
+ dp = data
+ while (imgnls (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Mems[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mems[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Mems[dp] = Mems[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Mems[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mems[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Mems[dp] = Mems[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrts (Mems[data], Mems[data], n)
+ mode = ic_modes (Mems[data], n)
+ median = Mems[data+n/2-1]
+ }
+ if (domean)
+ mean = asums (Mems[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+short procedure ic_modes (a, n)
+
+short a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+short mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stati (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnli()
+
+real asumi()
+int ic_modei()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_INT)
+ dp = data
+ while (imgnli (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memi[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memi[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memi[dp] = Memi[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memi[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memi[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memi[dp] = Memi[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrti (Memi[data], Memi[data], n)
+ mode = ic_modei (Memi[data], n)
+ median = Memi[data+n/2-1]
+ }
+ if (domean)
+ mean = asumi (Memi[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+int procedure ic_modei (a, n)
+
+int a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+int mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_statr (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnlr()
+
+real asumr()
+real ic_moder()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_REAL)
+ dp = data
+ while (imgnlr (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrtr (Memr[data], Memr[data], n)
+ mode = ic_moder (Memr[data], n)
+ median = Memr[data+n/2-1]
+ }
+ if (domean)
+ mean = asumr (Memr[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+real procedure ic_moder (a, n)
+
+real a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+real mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_statd (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnld()
+
+double asumd()
+double ic_moded()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_DOUBLE)
+ dp = data
+ while (imgnld (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memd[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memd[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memd[dp] = Memd[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memd[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memd[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memd[dp] = Memd[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrtd (Memd[data], Memd[data], n)
+ mode = ic_moded (Memd[data], n)
+ median = Memd[data+n/2-1]
+ }
+ if (domean)
+ mean = asumd (Memd[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+double procedure ic_moded (a, n)
+
+double a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+double mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
diff --git a/noao/onedspec/odcombine/src/generic/mkpkg b/noao/onedspec/odcombine/src/generic/mkpkg
new file mode 100644
index 00000000..b05b48a6
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/mkpkg
@@ -0,0 +1,25 @@
+# Make IMCOMBINE.
+
+$checkout libpkg.a ../../../../
+$update libpkg.a
+$checkin libpkg.a ../../../../
+$exit
+
+libpkg.a:
+ icaclip.x ../icombine.com ../icombine.h
+ icaverage.x ../icombine.com ../icombine.h <imhdr.h>
+ iccclip.x ../icombine.com ../icombine.h
+ icgdata.x ../icombine.com ../icombine.h <imhdr.h> <mach.h>
+ icgrow.x ../icombine.com ../icombine.h <imhdr.h> <pmset.h>
+ icmedian.x ../icombine.com ../icombine.h
+ icmm.x ../icombine.com ../icombine.h
+ icomb.x ../icombine.com ../icombine.h <error.h> <imhdr.h>\
+ <imset.h> <mach.h> <pmset.h> <syserr.h>
+ icpclip.x ../icombine.com ../icombine.h
+ icsclip.x ../icombine.com ../icombine.h
+ icsigma.x ../icombine.com ../icombine.h <imhdr.h>
+ icsort.x
+ icstat.x ../icombine.com ../icombine.h <imhdr.h>
+
+ xtimmap.x ../xtimmap.com <config.h> <error.h> <imhdr.h> <imset.h>
+ ;
diff --git a/noao/onedspec/odcombine/src/generic/xtimmap.x b/noao/onedspec/odcombine/src/generic/xtimmap.x
new file mode 100644
index 00000000..9e86e44d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/generic/xtimmap.x
@@ -0,0 +1,1080 @@
+include <syserr.h>
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <config.h>
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+# These routines maintain an arbitrary number of indexed "open" images which
+# must be READ_ONLY. The calling program may use the returned pointer for
+# header accesses but must call xt_opix before I/O. Subsequent calls to
+# xt_opix may invalidate the pointer. The xt_imunmap call will free memory.
+
+define MAX_OPENIM (LAST_FD-16) # Maximum images kept open
+define MAX_OPENPIX 45 # Maximum pixel files kept open
+
+define XT_SZIMNAME 299 # Size of IMNAME string
+define XT_LEN 179 # Structure length
+define XT_IMNAME Memc[P2C($1)] # Image name
+define XT_ARG Memi[$1+150] # IMMAP header argument
+define XT_IM Memi[$1+151] # IMIO pointer
+define XT_HDR Memi[$1+152] # Copy of IMIO pointer
+define XT_CLOSEFD Memi[$1+153] # Close FD?
+define XT_FLAG Memi[$1+154] # Flag
+define XT_BUFSIZE Memi[$1+155] # Buffer size
+define XT_BUF Memi[$1+156] # Data buffer
+define XT_BTYPE Memi[$1+157] # Data buffer type
+define XT_VS Memi[$1+157+$2] # Start vector (10)
+define XT_VE Memi[$1+167+$2] # End vector (10)
+
+# Options
+define XT_MAPUNMAP 1 # Map and unmap images.
+
+# XT_IMMAP -- Map an image and save it as an indexed open image.
+# The returned pointer may be used for header access but not I/O.
+# The indexed image is closed by xt_imunmap.
+
+pointer procedure xt_immap (imname, acmode, hdr_arg, index)
+
+char imname[ARB] #I Image name
+int acmode #I Access mode
+int hdr_arg #I Header argument
+int index #I Save index
+pointer im #O Image pointer (returned)
+
+int i, envgeti()
+pointer xt, xt_opix()
+errchk xt_opix
+
+int first_time
+data first_time /YES/
+
+include "../xtimmap.com"
+
+begin
+ if (acmode != READ_ONLY)
+ call error (1, "XT_IMMAP: Only READ_ONLY allowed")
+
+ # Initialize once per process.
+ if (first_time == YES) {
+ iferr (option = envgeti ("imcombine_option"))
+ option = 1
+ min_open = 1
+ nopen = 0
+ nopenpix = 0
+ nalloc = MAX_OPENIM
+ call calloc (ims, nalloc, TY_POINTER)
+ first_time = NO
+ }
+
+ # Free image if needed.
+ call xt_imunmap (NULL, index)
+
+ # Allocate structure.
+ if (index > nalloc) {
+ i = nalloc
+ nalloc = index + MAX_OPENIM
+ call realloc (ims, nalloc, TY_STRUCT)
+ call amovki (NULL, Memi[ims+i], nalloc-i)
+ }
+ call calloc (xt, XT_LEN, TY_STRUCT)
+ Memi[ims+index-1] = xt
+
+ # Initialize.
+ call strcpy (imname, XT_IMNAME(xt), XT_SZIMNAME)
+ XT_ARG(xt) = hdr_arg
+ XT_IM(xt) = NULL
+ XT_HDR(xt) = NULL
+
+ # Open image.
+ last_flag = 0
+ im = xt_opix (NULL, index, 0)
+
+ # Make copy of IMIO pointer for header keyword access.
+ call malloc (XT_HDR(xt), LEN_IMDES+IM_HDRLEN(im)+1, TY_STRUCT)
+ call amovi (Memi[im], Memi[XT_HDR(xt)], LEN_IMDES)
+ call amovi (IM_MAGIC(im), IM_MAGIC(XT_HDR(xt)), IM_HDRLEN(im)+1)
+
+ return (XT_HDR(xt))
+end
+
+
+# XT_OPIX -- Open the image for I/O.
+# If the image has not been mapped return the default pointer.
+
+pointer procedure xt_opix (imdef, index, flag)
+
+int index #I index
+pointer imdef #I Default pointer
+int flag #I Flag
+
+int i, open(), imstati()
+pointer im, xt, xt1, immap()
+errchk open, immap, imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imdef)
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Return pointer for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (im)
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || flag == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ if (!IS_INDEFI(XT_BUFSIZE(xt)))
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ else
+ XT_BUFSIZE(xt) = imstati (im, IM_BUFSIZE)
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (im)
+end
+
+
+# XT_CPIX -- Close image.
+
+procedure xt_cpix (index)
+
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL)
+ return
+
+ if (XT_IM(xt) != NULL) {
+ call imunmap (XT_IM(xt))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ }
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+end
+
+
+# XT_IMSETI -- Set IMIO value.
+
+procedure xt_imseti (index, param, value)
+
+int index #I index
+int param #I IMSET parameter
+int value #I Value
+
+pointer xt
+bool streq()
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL) {
+ if (streq (param, "option"))
+ option = value
+ } else {
+ if (streq (param, "bufsize")) {
+ XT_BUFSIZE(xt) = value
+ if (XT_IM(xt) != NULL) {
+ call imseti (XT_IM(xt), IM_BUFFRAC, 0)
+ call imseti (XT_IM(xt), IM_BUFSIZE, value)
+ }
+ }
+ }
+end
+
+
+# XT_IMUNMAP -- Unmap indexed open image.
+# The header pointer is set to NULL to indicate the image has been closed.
+
+procedure xt_imunmap (im, index)
+
+int im #U IMIO header pointer
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Check for an indexed image. If it is not unmap the pointer
+ # as a regular IMIO pointer.
+
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+ if (xt == NULL) {
+ if (im != NULL)
+ call imunmap (im)
+ return
+ }
+
+ # Close indexed image.
+ if (XT_IM(xt) != NULL) {
+ iferr (call imunmap (XT_IM(xt))) {
+ XT_IM(xt) = NULL
+ call erract (EA_WARN)
+ }
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ if (index == min_open)
+ min_open = 1
+ }
+
+ # Free any buffered memory.
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+
+ # Free header pointer. Note that if the supplied pointer is not
+ # header pointer then it is not set to NULL.
+ if (XT_HDR(xt) == im)
+ im = NULL
+ call mfree (XT_HDR(xt), TY_STRUCT)
+
+ # Free save structure.
+ call mfree (Memi[ims+index-1], TY_STRUCT)
+ Memi[ims+index-1] = NULL
+end
+
+
+# XT_REINDEX -- Reindex open images.
+# This is used when some images are closed by xt_imunmap. It is up to
+# the calling program to reindex the header pointers and to subsequently
+# use the new index values.
+
+procedure xt_reindex ()
+
+int old, new
+
+include "../xtimmap.com"
+
+begin
+ new = 0
+ do old = 0, nalloc-1 {
+ if (Memi[ims+old] == NULL)
+ next
+ Memi[ims+new] = Memi[ims+old]
+ new = new + 1
+ }
+ do old = new, nalloc-1
+ Memi[ims+old] = NULL
+end
+
+
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnls (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnls(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggss()
+errchk open, immap, imgnls, imggss, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnls (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnls (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_SHORT)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_SHORT) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_SHORT
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggss (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovs (Mems[ptr], Mems[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnls (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnli (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnli(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsi()
+errchk open, immap, imgnli, imggsi, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnli (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnli (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_INT)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_INT) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_INT
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsi (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovi (Memi[ptr], Memi[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnli (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnlr (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnlr(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsr()
+errchk open, immap, imgnlr, imggsr, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnlr (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnlr (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_REAL)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_REAL) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_REAL
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsr (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovr (Memr[ptr], Memr[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnlr (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnld (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnld(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsd()
+errchk open, immap, imgnld, imggsd, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnld (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnld (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_DOUBLE)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_DOUBLE) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_DOUBLE
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsd (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovd (Memd[ptr], Memd[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnld (im, buf, v))
+end
+
diff --git a/noao/onedspec/odcombine/src/icaclip.gx b/noao/onedspec/odcombine/src/icaclip.gx
new file mode 100644
index 00000000..696402b2
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icaclip.gx
@@ -0,0 +1,575 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number of images for this algorithm
+
+$for (sird)
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclip$t (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+$else
+PIXEL d1, low, high, sum, a, s, s1, r, one
+data one /1$f/
+$endif
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclip$t (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med, low, high, r, s, s1, one
+data one /1.0/
+$else
+PIXEL med, low, high, r, s, s1, one
+data one /1$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Mem$t[d[1]+k]
+ else {
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Mem$t[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Mem$t[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Mem$t[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icaverage.gx b/noao/onedspec/odcombine/src/icaverage.gx
new file mode 100644
index 00000000..a95b7673
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icaverage.gx
@@ -0,0 +1,114 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_average$t (d, m, n, wts, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+$if (datatype == sil)
+real average[npts] # Average (returned)
+$else
+PIXEL average[npts] # Average (returned)
+$endif
+
+int i, j, k
+real sumwt, wt
+$if (datatype == sil)
+real sum
+$else
+PIXEL sum
+$endif
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mem$t[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mem$t[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mem$t[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mem$t[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/iccclip.gx b/noao/onedspec/odcombine/src/iccclip.gx
new file mode 100644
index 00000000..609b3448
--- /dev/null
+++ b/noao/onedspec/odcombine/src/iccclip.gx
@@ -0,0 +1,471 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 2 # Mininum number of images for algorithm
+
+$for (sird)
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclip$t (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+$else
+PIXEL d1, low, high, sum, a, s, r, zero
+data zero /0$f/
+$endif
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Mem$t[d[1]+k]
+ sum = sum + Mem$t[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclip$t (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med, zero
+data zero /0.0/
+$else
+PIXEL med, zero
+data zero /0$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Mem$t[d[n3-1]+k]
+ med = (med + Mem$t[d[n3]+k]) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icemask.x b/noao/onedspec/odcombine/src/icemask.x
new file mode 100644
index 00000000..e60b8ab7
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icemask.x
@@ -0,0 +1,114 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+
+
+# IC_EMASK -- Create exposure mask.
+
+procedure ic_emask (pm, v, id, nimages, n, wts, npts)
+
+pointer pm #I Pixel mask
+long v[ARB] #I Output vector
+pointer id[nimages] #I Image id pointers
+int nimages #I Number of images
+int n[npts] #I Number of good pixels
+real wts[npts] #I Weights
+int npts #I Number of output pixels per line
+
+int i, j, k, impnli()
+real exp
+pointer buf
+
+pointer exps # Exposure times
+pointer ev # IMIO coordinate vector
+real ezero # Integer to real zero
+real escale # Integer to real scale
+int einit # Initialization flag
+common /emask/ exps, ev, ezero, escale, einit
+
+begin
+ # Write scaling factors to the header.
+ if (einit == NO) {
+ if (ezero != 0. || escale != 1.) {
+ call imaddr (pm, "MASKZERO", ezero)
+ call imaddr (pm, "MASKSCAL", escale)
+ }
+ einit = YES
+ }
+
+ call amovl (v, Meml[ev], IM_MAXDIM)
+ i = impnli (pm, buf, Meml[ev])
+ call aclri (Memi[buf], npts)
+ do i = 1, npts {
+ exp = 0.
+ do j = 1, n[i] {
+ k = Memi[id[j]+i-1]
+ if (wts[k] > 0.)
+ exp = exp + Memr[exps+k-1]
+ }
+ Memi[buf] = nint((exp-ezero)/escale)
+ buf = buf + 1
+ }
+end
+
+
+# IC_EINIT -- Initialize exposure mask.
+
+procedure ic_einit (in, nimages, key, default, maxval)
+
+int in[nimages] #I Image pointers
+int nimages #I Number of images
+char key[ARB] #I Exposure time keyword
+real default #I Default exposure time
+int maxval #I Maximum mask value
+
+int i
+real exp, emin, emax, efrac, imgetr()
+
+pointer exps # Exposure times
+pointer ev # IMIO coordinate vector
+real ezero # Integer to real zero
+real escale # Integer to real scale
+int einit # Initialization flag
+common /emask/ exps, ev, ezero, escale, einit
+
+begin
+ call malloc (ev, IM_MAXDIM, TY_LONG)
+ call malloc (exps, nimages, TY_REAL)
+
+ emax = 0.
+ emin = MAX_REAL
+ efrac = 0
+ do i = 1, nimages {
+ iferr (exp = imgetr (in[i], key))
+ exp = default
+ exp = max (0., exp)
+ emax = emax + exp
+ if (exp > 0.)
+ emin = min (exp, emin)
+ efrac = max (abs(exp-nint(exp)), efrac)
+ Memr[exps+i-1] = exp
+ }
+
+ # Set scaling.
+ ezero = 0.
+ escale = 1.
+ if (emin < 1.) {
+ escale = emin
+ emin = emin / escale
+ emax = emax / escale
+ } else if (emin == MAX_REAL)
+ emin = 0.
+ if (efrac > 0.001 && emax-emin < 1000.) {
+ escale = escale / 1000.
+ emin = emin * 1000.
+ emax = emax * 1000.
+ }
+ while (emax > maxval) {
+ escale = escale * 10.
+ emin = emin / 10.
+ emax = emax / 10.
+ }
+ einit = NO
+end
diff --git a/noao/onedspec/odcombine/src/icgdata.gx b/noao/onedspec/odcombine/src/icgdata.gx
new file mode 100644
index 00000000..27f51ec5
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icgdata.gx
@@ -0,0 +1,307 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnl$t()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnl$t
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnl$t (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnl$t (in[i], i, buf, v2, v1[2])
+ call amov$t (Mem$t[buf+k-1], Mem$t[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_PIXEL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sort$t (d, Mem$t[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sort$t (d, Mem$t[dp], n, npts)
+ call mfree (dp, TY_PIXEL)
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icgrow.gx b/noao/onedspec/odcombine/src/icgrow.gx
new file mode 100644
index 00000000..caf7dd29
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icgrow.gx
@@ -0,0 +1,135 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <pmset.h>
+include "../icombine.h"
+
+# IC_GROW -- Mark neigbors of rejected pixels.
+# The rejected pixels (original plus grown) are saved in pixel masks.
+
+procedure ic_grow (out, v, m, n, buf, nimages, npts, pms)
+
+pointer out # Output image pointer
+long v[ARB] # Output vector
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[npts,nimages] # Working buffer
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k, l, line, nl, rop, igrow, nset, ncompress, or()
+real grow2, i2
+pointer mp, pm, pm_newmask()
+errchk pm_newmask()
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE || grow == 0.)
+ return
+
+ line = v[2]
+ nl = IM_LEN(out,2)
+ rop = or (PIX_SRC, PIX_DST)
+
+ igrow = grow
+ grow2 = grow**2
+ do l = 0, igrow {
+ i2 = grow2 - l * l
+ call aclri (buf, npts*nimages)
+ nset = 0
+ do j = 1, npts {
+ do k = n[j]+1, nimages {
+ mp = Memi[m[k]+j-1]
+ if (mp == 0)
+ next
+ do i = 0, igrow {
+ if (i**2 > i2)
+ next
+ if (j > i)
+ buf[j-i,mp] = 1
+ if (j+i <= npts)
+ buf[j+i,mp] = 1
+ nset = nset + 1
+ }
+ }
+ }
+ if (nset == 0)
+ return
+
+ if (pms == NULL) {
+ call malloc (pms, nimages, TY_POINTER)
+ do i = 1, nimages
+ Memi[pms+i-1] = pm_newmask (out, 1)
+ ncompress = 0
+ }
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ v[2] = line - l
+ if (v[2] > 0)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ if (l > 0) {
+ v[2] = line + l
+ if (v[2] <= nl)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ }
+ }
+ }
+ v[2] = line
+
+ if (ncompress > 10) {
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ call pm_compress (pm)
+ }
+ ncompress = 0
+ } else
+ ncompress = ncompress + 1
+end
+
+
+$for (sird)
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_grow$t (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Mem$t[d[j]+i-1] = Mem$t[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icgscale.x b/noao/onedspec/odcombine/src/icgscale.x
new file mode 100644
index 00000000..570697ad
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icgscale.x
@@ -0,0 +1,88 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "icombine.h"
+
+
+# IC_GSCALE -- Get scale values as directed by CL parameter.
+# Only those values which are INDEF are changed.
+# The values can be one of those in the dictionary, from a file specified
+# with a @ prefix, or from an image header keyword specified by a ! prefix.
+
+int procedure ic_gscale (param, name, dic, in, exptime, values, nimages)
+
+char param[ARB] #I CL parameter name
+char name[SZ_FNAME] #O Parameter value
+char dic[ARB] #I Dictionary string
+pointer in[nimages] #I IMIO pointers
+real exptime[nimages] #I Exposure times
+real values[nimages] #O Values
+int nimages #I Number of images
+
+int type #O Type of value
+
+int fd, i, nowhite(), open(), fscan(), nscan(), strdic()
+real rval, imgetr()
+pointer errstr
+errchk open, imgetr
+
+include "icombine.com"
+
+begin
+ call clgstr (param, name, SZ_FNAME)
+ if (nowhite (name, name, SZ_FNAME) == 0)
+ type = S_NONE
+ else if (name[1] == '@') {
+ type = S_FILE
+ do i = 1, nimages
+ if (IS_INDEFR(values[i]))
+ break
+ if (i <= nimages) {
+ fd = open (name[2], READ_ONLY, TEXT_FILE)
+ i = 0
+ while (fscan (fd) != EOF) {
+ call gargr (rval)
+ if (nscan() != 1)
+ next
+ if (i == nimages) {
+ call eprintf (
+ "Warning: Ignoring additional %s values in %s\n")
+ call pargstr (param)
+ call pargstr (name[2])
+ break
+ }
+ i = i + 1
+ if (IS_INDEFR(values[i]))
+ values[i] = rval
+ }
+ call close (fd)
+ if (i < nimages) {
+ call salloc (errstr, SZ_LINE, TY_CHAR)
+ call sprintf (errstr, SZ_FNAME,
+ "Insufficient %s values in %s")
+ call pargstr (param)
+ call pargstr (name[2])
+ call error (1, errstr)
+ }
+ }
+ } else if (name[1] == '!') {
+ type = S_KEYWORD
+ do i = 1, nimages {
+ if (IS_INDEFR(values[i]))
+ values[i] = imgetr (in[i], name[2])
+ if (project) {
+ call amovkr (values, values, nimages)
+ break
+ }
+ }
+ } else {
+ type = strdic (name, name, SZ_FNAME, dic)
+ if (type == 0)
+ call error (1, "Unknown scale, zero, or weight type")
+ if (type==S_EXPOSURE)
+ do i = 1, nimages
+ if (IS_INDEFR(values[i]))
+ values[i] = max (0.001, exptime[i])
+ }
+
+ return (type)
+end
diff --git a/noao/onedspec/odcombine/src/ichdr.x b/noao/onedspec/odcombine/src/ichdr.x
new file mode 100644
index 00000000..2d19c5bd
--- /dev/null
+++ b/noao/onedspec/odcombine/src/ichdr.x
@@ -0,0 +1,55 @@
+include <imset.h>
+
+
+# IC_HDR -- Set output header.
+
+procedure ic_hdr (in, out, nimages)
+
+pointer in[nimages] #I Input images
+pointer out[ARB] #I Output images
+int nimages #I Number of images
+
+int i, imgnfn()
+pointer sp, key, str, list, imofnlu()
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ # Set new PROCID.
+ call xt_procid (out)
+
+ # Set input PROCIDs.
+ if (nimages < 100) {
+ list = imofnlu (out, "PROCID[0-9][0-9]")
+ while (imgnfn (list, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (list)
+ do i = 1, nimages {
+ call sprintf (Memc[key], 8, "PROCID%02d")
+ call pargi (i)
+ iferr (call imgstr (in[i], "PROCID", Memc[str], SZ_LINE)) {
+ iferr (call imgstr (in[i], "OBSID", Memc[str], SZ_LINE))
+ Memc[str] = EOS
+ }
+ if (Memc[str] != EOS)
+ call imastr (out, Memc[key], Memc[str])
+ }
+
+ # Set input image names.
+ list = imofnlu (out, "IMCMB[0-9][0-9][0-9]")
+ while (imgnfn (list, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (list)
+ do i = 1, nimages {
+ iferr (call imgstr (in[i], "ICFNAME", Memc[str], SZ_LINE))
+ call imstats (in[i], IM_IMAGENAME, Memc[str], SZ_LINE)
+ call sprintf (Memc[key], SZ_LINE, "IMCMB%03d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[str])
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/icimstack.x b/noao/onedspec/odcombine/src/icimstack.x
new file mode 100644
index 00000000..d5628694
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icimstack.x
@@ -0,0 +1,186 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <error.h>
+include <imhdr.h>
+
+
+# IC_IMSTACK -- Stack images into a single image of higher dimension.
+
+procedure ic_imstack (list, output, mask)
+
+int list #I List of images
+char output[ARB] #I Name of output image
+char mask[ARB] #I Name of output mask
+
+int i, j, npix
+long line_in[IM_MAXDIM], line_out[IM_MAXDIM], line_outbpm[IM_MAXDIM]
+pointer sp, input, bpmname, key, in, out, inbpm, outbpm, buf_in, buf_out, ptr
+
+int imtgetim(), imtlen(), errget()
+int imgnls(), imgnli(), imgnll(), imgnlr(), imgnld(), imgnlx()
+int impnls(), impnli(), impnll(), impnlr(), impnld(), impnlx()
+pointer immap(), pm_newmask()
+errchk immap
+errchk imgnls, imgnli, imgnll, imgnlr, imgnld, imgnlx
+errchk impnls, impnli, impnll, impnlr, impnld, impnlx
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (bpmname, SZ_FNAME, TY_CHAR)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ iferr {
+ # Add each input image to the output image.
+ out = NULL; outbpm = NULL
+ i = 0
+ while (imtgetim (list, Memc[input], SZ_FNAME) != EOF) {
+
+ i = i + 1
+ in = NULL; inbpm = NULL
+ ptr = immap (Memc[input], READ_ONLY, 0)
+ in = ptr
+
+ # For the first input image map the output image as a copy
+ # and increment the dimension. Set the output line counter.
+
+ if (i == 1) {
+ ptr = immap (output, NEW_COPY, in)
+ out = ptr
+ IM_NDIM(out) = IM_NDIM(out) + 1
+ IM_LEN(out, IM_NDIM(out)) = imtlen (list)
+ npix = IM_LEN(out, 1)
+ call amovkl (long(1), line_out, IM_MAXDIM)
+
+ if (mask[1] != EOS) {
+ ptr = immap (mask, NEW_COPY, in)
+ outbpm = ptr
+ IM_NDIM(outbpm) = IM_NDIM(outbpm) + 1
+ IM_LEN(outbpm, IM_NDIM(outbpm)) = imtlen (list)
+ call amovkl (long(1), line_outbpm, IM_MAXDIM)
+ }
+ }
+
+ # Check next input image for consistency with the output image.
+ if (IM_NDIM(in) != IM_NDIM(out) - 1)
+ call error (0, "Input images not consistent")
+ do j = 1, IM_NDIM(in) {
+ if (IM_LEN(in, j) != IM_LEN(out, j))
+ call error (0, "Input images not consistent")
+ }
+
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[input])
+
+ # Copy the input lines from the image to the next lines of
+ # the output image. Switch on the output data type to optimize
+ # IMIO.
+
+ call amovkl (long(1), line_in, IM_MAXDIM)
+ switch (IM_PIXTYPE (out)) {
+ case TY_SHORT:
+ while (imgnls (in, buf_in, line_in) != EOF) {
+ if (impnls (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovs (Mems[buf_in], Mems[buf_out], npix)
+ }
+ case TY_INT:
+ while (imgnli (in, buf_in, line_in) != EOF) {
+ if (impnli (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovi (Memi[buf_in], Memi[buf_out], npix)
+ }
+ case TY_USHORT, TY_LONG:
+ while (imgnll (in, buf_in, line_in) != EOF) {
+ if (impnll (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovl (Meml[buf_in], Meml[buf_out], npix)
+ }
+ case TY_REAL:
+ while (imgnlr (in, buf_in, line_in) != EOF) {
+ if (impnlr (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovr (Memr[buf_in], Memr[buf_out], npix)
+ }
+ case TY_DOUBLE:
+ while (imgnld (in, buf_in, line_in) != EOF) {
+ if (impnld (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovd (Memd[buf_in], Memd[buf_out], npix)
+ }
+ case TY_COMPLEX:
+ while (imgnlx (in, buf_in, line_in) != EOF) {
+ if (impnlx (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovx (Memx[buf_in], Memx[buf_out], npix)
+ }
+ default:
+ while (imgnlr (in, buf_in, line_in) != EOF) {
+ if (impnlr (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovr (Memr[buf_in], Memr[buf_out], npix)
+ }
+ }
+
+ # Copy mask.
+ if (mask[1] != EOS) {
+ iferr (call imgstr (in, "bpm", Memc[bpmname], SZ_FNAME)) {
+ Memc[bpmname] = EOS
+ ptr = pm_newmask (in, 27)
+ } else
+ ptr = immap (Memc[bpmname], READ_ONLY, 0)
+ inbpm = ptr
+
+ if (IM_NDIM(inbpm) != IM_NDIM(outbpm) - 1)
+ call error (0, "Input images not consistent")
+ do j = 1, IM_NDIM(inbpm) {
+ if (IM_LEN(inbpm, j) != IM_LEN(outbpm, j))
+ call error (0, "Masks not consistent")
+ }
+
+ call amovkl (long(1), line_in, IM_MAXDIM)
+ while (imgnli (inbpm, buf_in, line_in) != EOF) {
+ if (impnli (outbpm, buf_out, line_outbpm) == EOF)
+ call error (0, "Error writing output mask")
+ call amovi (Memi[buf_in], Memi[buf_out], npix)
+ }
+
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[bpmname])
+
+ call imunmap (inbpm)
+ }
+
+ call imunmap (in)
+ }
+ } then {
+ i = errget (Memc[key], SZ_FNAME)
+ call erract (EA_WARN)
+ if (outbpm != NULL) {
+ call imunmap (outbpm)
+ iferr (call imdelete (mask))
+ ;
+ }
+ if (out != NULL) {
+ call imunmap (out)
+ iferr (call imdelete (output))
+ ;
+ }
+ if (inbpm != NULL)
+ call imunmap (inbpm)
+ if (in != NULL)
+ call imunmap (in)
+ call sfree (sp)
+ call error (i, "Can't make temporary stack images")
+ }
+
+ # Finish up.
+ if (outbpm != NULL) {
+ call imunmap (outbpm)
+ call imastr (out, "bpm", mask)
+ }
+ call imunmap (out)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/iclog.x b/noao/onedspec/odcombine/src/iclog.x
new file mode 100644
index 00000000..43ab37ab
--- /dev/null
+++ b/noao/onedspec/odcombine/src/iclog.x
@@ -0,0 +1,422 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <mach.h>
+include "icombine.h"
+include "icmask.h"
+
+# IC_LOG -- Output log information is a log file has been specfied.
+
+procedure ic_log (in, out, ncombine, exptime, sname, zname, wname,
+ mode, median, mean, scales, zeros, wts, offsets, nimages,
+ dozero, nout)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int ncombine[nimages] # Number of previous combined images
+real exptime[nimages] # Exposure times
+char sname[ARB] # Scale name
+char zname[ARB] # Zero name
+char wname[ARB] # Weight name
+real mode[nimages] # Modes
+real median[nimages] # Medians
+real mean[nimages] # Means
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Image offsets
+int nimages # Number of images
+bool dozero # Zero flag
+int nout # Number of images combined in output
+
+int i, j, stack, ctor()
+real rval, imgetr()
+long clktime()
+bool prncombine, prexptime, prmode, prmedian, prmean, prmask
+bool prrdn, prgain, prsn
+pointer sp, fname, bpname, key
+errchk imgetr
+
+include "icombine.com"
+
+begin
+ if (logfd == NULL)
+ return
+
+ call smark (sp)
+ call salloc (fname, SZ_LINE, TY_CHAR)
+ call salloc (bpname, SZ_LINE, TY_CHAR)
+
+ stack = NO
+ if (project) {
+ ifnoerr (call imgstr (in[1], "stck0001", Memc[fname], SZ_LINE))
+ stack = YES
+ }
+ if (stack == YES)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ # Time stamp the log and print parameter information.
+
+ call cnvdate (clktime(0), Memc[fname], SZ_LINE)
+ call fprintf (logfd, "\n%s: %s\n")
+ call pargstr (Memc[fname])
+ if (ictask != NULL)
+ call pargstr (Memc[ictask])
+ else
+ call pargstr ("IMCOMBINE")
+ switch (combine) {
+ case AVERAGE:
+ call fprintf (logfd, " combine = average, ")
+ case MEDIAN:
+ call fprintf (logfd, " combine = median, ")
+ case SUM:
+ call fprintf (logfd, " combine = sum, ")
+ }
+ call fprintf (logfd, "scale = %s, zero = %s, weight = %s\n")
+ call pargstr (sname)
+ call pargstr (zname)
+ call pargstr (wname)
+
+ switch (reject) {
+ case MINMAX:
+ call fprintf (logfd, " reject = minmax, nlow = %d, nhigh = %d\n")
+ call pargi (nint (flow * nimages))
+ call pargi (nint (fhigh * nimages))
+ case CCDCLIP:
+ call fprintf (logfd, " reject = ccdclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, sigma = %g, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case CRREJECT:
+ call fprintf (logfd,
+ " reject = crreject, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (hsigma)
+ case PCLIP:
+ call fprintf (logfd, " reject = pclip, nkeep = %d\n")
+ call pargi (nkeep)
+ call fprintf (logfd, " pclip = %g, lsigma = %g, hsigma = %g\n")
+ call pargr (pclip)
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case SIGCLIP:
+ call fprintf (logfd, " reject = sigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case AVSIGCLIP:
+ call fprintf (logfd,
+ " reject = avsigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ }
+ if (reject != NONE && grow >= 1.) {
+ call fprintf (logfd, " grow = %g\n")
+ call pargr (grow)
+ }
+ if (dothresh) {
+ if (lthresh > -MAX_REAL && hthresh < MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g, hthreshold = %g\n")
+ call pargr (lthresh)
+ call pargr (hthresh)
+ } else if (lthresh > -MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g\n")
+ call pargr (lthresh)
+ } else {
+ call fprintf (logfd, " hthreshold = %g\n")
+ call pargr (hthresh)
+ }
+ }
+ call fprintf (logfd, " blank = %g\n")
+ call pargr (blank)
+ if (Memc[statsec] != EOS) {
+ call fprintf (logfd, " statsec = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (ICM_TYPE(icm) != M_NONE) {
+ switch (ICM_TYPE(icm)) {
+ case M_BOOLEAN, M_GOODVAL:
+ call fprintf (logfd, " masktype = goodval, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_BADVAL:
+ call fprintf (logfd, " masktype = badval, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_GOODBITS:
+ call fprintf (logfd, " masktype = goodbits, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_BADBITS:
+ call fprintf (logfd, " masktype = badbits, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ }
+ }
+
+ # Print information pertaining to individual images as a set of
+ # columns with the image name being the first column. Determine
+ # what information is relevant and print the appropriate header.
+
+ prncombine = false
+ prexptime = false
+ prmode = false
+ prmedian = false
+ prmean = false
+ prmask = false
+ prrdn = false
+ prgain = false
+ prsn = false
+ do i = 1, nimages {
+ if (ncombine[i] != ncombine[1])
+ prncombine = true
+ if (exptime[i] != exptime[1])
+ prexptime = true
+ if (mode[i] != mode[1])
+ prmode = true
+ if (median[i] != median[1])
+ prmedian = true
+ if (mean[i] != mean[1])
+ prmean = true
+ if (ICM_TYPE(icm) != M_NONE) {
+ if (project)
+ bpname = Memi[ICM_LOGNAMES(icm)]
+ else
+ bpname = Memi[ICM_LOGNAMES(icm)+i-1]
+ if (Memc[bpname] != EOS)
+ prmask = true
+ }
+ if (reject == CCDCLIP || reject == CRREJECT) {
+ j = 1
+ if (ctor (Memc[rdnoise], j, rval) == 0)
+ prrdn = true
+ j = 1
+ if (ctor (Memc[gain], j, rval) == 0)
+ prgain = true
+ j = 1
+ if (ctor (Memc[snoise], j, rval) == 0)
+ prsn = true
+ }
+ }
+
+ call fprintf (logfd, " %20s ")
+ call pargstr ("Images")
+ if (prncombine) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("N")
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Exp")
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mode")
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Median")
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mean")
+ }
+ if (prrdn) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Rdnoise")
+ }
+ if (prgain) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Gain")
+ }
+ if (prsn) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Snoise")
+ }
+ if (doscale) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Scale")
+ }
+ if (dozero) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Zero")
+ }
+ if (dowts) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Weight")
+ }
+ if (!aligned) {
+ call fprintf (logfd, " %9s")
+ call pargstr ("Offsets")
+ }
+ if (prmask) {
+ call fprintf (logfd, " %s")
+ call pargstr ("Maskfile")
+ }
+ call fprintf (logfd, "\n")
+
+ do i = 1, nimages {
+ if (stack == YES) {
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ ifnoerr (call imgstr (in[i], Memc[key], Memc[fname], SZ_LINE)) {
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ } else {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %16s[%3d]")
+ call pargstr (Memc[fname])
+ call pargi (i)
+ }
+ } else if (project) {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %16s[%3d]")
+ call pargstr (Memc[fname])
+ call pargi (i)
+ } else ifnoerr (call imgstr (in[i],"ICFNAME",Memc[fname],SZ_LINE)) {
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ } else {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ }
+ if (prncombine) {
+ call fprintf (logfd, " %6d")
+ call pargi (ncombine[i])
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6.1f")
+ call pargr (exptime[i])
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mode[i])
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (median[i])
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mean[i])
+ }
+ if (prrdn) {
+ rval = imgetr (in[i], Memc[rdnoise])
+ call fprintf (logfd, " %7g")
+ call pargr (rval)
+ }
+ if (prgain) {
+ rval = imgetr (in[i], Memc[gain])
+ call fprintf (logfd, " %6g")
+ call pargr (rval)
+ }
+ if (prsn) {
+ rval = imgetr (in[i], Memc[snoise])
+ call fprintf (logfd, " %6g")
+ call pargr (rval)
+ }
+ if (doscale) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (1./scales[i])
+ }
+ if (dozero) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (-zeros[i])
+ }
+ if (dowts) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (wts[i])
+ }
+ if (!aligned) {
+ if (IM_NDIM(out[1]) == 1) {
+ call fprintf (logfd, " %9d")
+ call pargi (offsets[i,1])
+ } else {
+ do j = 1, IM_NDIM(out[1]) {
+ call fprintf (logfd, " %4d")
+ call pargi (offsets[i,j])
+ }
+ }
+ }
+ if (prmask) {
+ if (stack == YES) {
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ ifnoerr (call imgstr (in[i], Memc[key], Memc[fname],
+ SZ_LINE)) {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[fname])
+ } else {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[bpname])
+ }
+ } else if (ICM_TYPE(icm) != M_NONE) {
+ if (project)
+ bpname = Memi[ICM_LOGNAMES(icm)]
+ else
+ bpname = Memi[ICM_LOGNAMES(icm)+i-1]
+ if (Memc[bpname] != EOS) {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[bpname])
+ }
+ }
+ }
+ call fprintf (logfd, "\n")
+ }
+
+ # Log information about the output images.
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, "\n Output image = %s, ncombine = %d")
+ call pargstr (Memc[fname])
+ call pargi (nout)
+ call fprintf (logfd, "\n")
+
+ if (out[2] != NULL) {
+ call imstats (out[2], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Bad pixel mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[4] != NULL) {
+ call imstats (out[4], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Rejection mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[5] != NULL) {
+ call imstats (out[5], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Number rejected mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[6] != NULL) {
+ call imstats (out[6], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Exposure mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[3] != NULL) {
+ call imstats (out[3], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Sigma image = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ call flush (logfd)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/icmask.com b/noao/onedspec/odcombine/src/icmask.com
new file mode 100644
index 00000000..baba6f6a
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icmask.com
@@ -0,0 +1,8 @@
+# IMCMASK -- Common for IMCOMBINE mask interface.
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+common /imcmask/ mtype, mvalue, bufs, pms
diff --git a/noao/onedspec/odcombine/src/icmask.h b/noao/onedspec/odcombine/src/icmask.h
new file mode 100644
index 00000000..533c601d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icmask.h
@@ -0,0 +1,9 @@
+# ICMASK -- Data structure for IMCOMBINE mask interface.
+
+define ICM_LEN 6 # Structure length
+define ICM_TYPE Memi[$1] # Mask type
+define ICM_VALUE Memi[$1+1] # Mask value
+define ICM_BUFS Memi[$1+2] # Pointer to data line buffers
+define ICM_PMS Memi[$1+3] # Pointer to array of PMIO pointers
+define ICM_NAMES Memi[$1+4] # Pointer to array of mask names
+define ICM_LOGNAMES Memi[$1+5] # Pointer to array of mask log names
diff --git a/noao/onedspec/odcombine/src/icmask.x b/noao/onedspec/odcombine/src/icmask.x
new file mode 100644
index 00000000..9242405d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icmask.x
@@ -0,0 +1,499 @@
+include <imhdr.h>
+include <pmset.h>
+include "icombine.h"
+include "icmask.h"
+
+# IC_MASK -- ICOMBINE mask interface
+#
+# IC_MOPEN -- Initialize mask interface
+# IC_MCLOSE -- Close the mask interface
+# IC_MGET -- Get lines of mask pixels for all the images
+# IC_MGET1 -- Get a line of mask pixels for the specified image
+# IC_MCLOSE1-- Close a mask for the specified image index
+
+
+# IC_MOPEN -- Initialize mask interface.
+
+procedure ic_mopen (in, out, nimages, offsets)
+
+pointer in[nimages] #I Input images
+pointer out[ARB] #I Output images
+int nimages #I Number of images
+int offsets[nimages,ARB] #I Offsets to output image
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+pointer names # Pointer to array of string pointers
+pointer lognames # Pointer to array of string pointers
+
+int i, j, k, nin, nout, npix, npms, nowhite(), strdic()
+int clgeti()
+pointer sp, key, fname, logname, title, pm, pm_open()
+bool invert, pm_empty()
+errchk calloc, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ icm = NULL
+ if (IM_NDIM(out[1]) == 0)
+ return
+
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (title, SZ_FNAME, TY_CHAR)
+
+ # Determine the mask parameters and allocate memory.
+ # The mask buffers are initialize to all excluded so that
+ # output points outside the input data are always excluded
+ # and don't need to be set on a line-by-line basis.
+
+ mtype = M_NONE
+ call clgstr ("masktype", Memc[key], SZ_FNAME)
+ if (nowhite (Memc[key], Memc[key], SZ_FNAME) > 0) {
+ if (Memc[key] == '!') {
+ mtype = M_GOODVAL
+ call strcpy (Memc[key+1], Memc[key], SZ_FNAME)
+ } else {
+ mtype = strdic (Memc[key], Memc[title], SZ_FNAME, MASKTYPES)
+ if (mtype == 0) {
+ call sprintf (Memc[title], SZ_FNAME,
+ "Invalid or ambiguous masktype (%s)")
+ call pargstr (Memc[key])
+ call error (1, Memc[title])
+ }
+ call strcpy ("BPM", Memc[key], SZ_FNAME)
+ }
+ }
+ mvalue = clgeti ("maskvalue")
+ npix = IM_LEN(out[1],1)
+ call calloc (pms, nimages, TY_POINTER)
+ call calloc (bufs, nimages, TY_POINTER)
+ call calloc (names, nimages, TY_POINTER)
+ call calloc (lognames, nimages, TY_POINTER)
+ do i = 1, nimages {
+ call malloc (Memi[bufs+i-1], npix, TY_INT)
+ call amovki (1, Memi[Memi[bufs+i-1]], npix)
+ }
+
+ # Check for special cases. The BOOLEAN type is used when only
+ # zero and nonzero are significant; i.e. the actual mask values are
+ # not important. The invert flag is used to indicate that
+ # empty masks are all bad rather the all good.
+
+ if (mtype == 0)
+ mtype = M_NONE
+ if (mtype == M_BADBITS && mvalue == 0)
+ mtype = M_NONE
+ if (mvalue == 0 && (mtype == M_GOODVAL || mtype == M_GOODBITS))
+ mtype = M_BOOLEAN
+ if ((mtype == M_BADVAL && mvalue == 0) ||
+ (mtype == M_GOODVAL && mvalue != 0) ||
+ (mtype == M_GOODBITS && mvalue == 0))
+ invert = true
+ else
+ invert = false
+
+ # If mask images are to be used, get the mask name from the image
+ # header and open it saving the descriptor in the pms array.
+ # Empty masks (all good) are treated as if there was no mask image.
+
+ nout = IM_LEN(out[1],1)
+ npms = 0
+ do i = 1, nimages {
+ if (mtype != M_NONE) {
+ call malloc (Memi[names+i-1], SZ_FNAME, TY_CHAR)
+ call malloc (Memi[lognames+i-1], SZ_FNAME, TY_CHAR)
+ fname = Memi[names+i-1]
+ logname = Memi[lognames+i-1]
+ ifnoerr (call imgstr (in[i],Memc[key],Memc[fname],SZ_FNAME)) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ Memc[fname] = EOS
+ else {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], Memc[title],
+ SZ_FNAME))
+ call pm_loadim (pm, Memc[fname], Memc[title],
+ SZ_FNAME)
+ call pm_seti (pm, P_REFIM, in[i])
+ if (pm_empty (pm) && !invert)
+ Memc[fname] = EOS
+ else {
+ if (project)
+ npms = nimages
+ else
+ npms = npms + 1
+ }
+ call pm_close (pm)
+
+ ifnoerr (call imgstr (in[i], "ICBPM", Memc[title],
+ SZ_FNAME))
+ call strcpy (Memc[title], Memc[logname], SZ_FNAME)
+ else
+ call strcpy (Memc[fname], Memc[logname], SZ_FNAME)
+ }
+ if (project)
+ break
+ } else {
+ Memc[fname] = EOS
+ Memc[logname] = EOS
+ }
+ }
+ }
+
+ # If no mask images are found and the mask parameters imply that
+ # good values are 0 then use the special case of no masks.
+
+ if (npms == 0) {
+ if (!invert)
+ mtype = M_NONE
+ }
+
+ # Set up mask structure.
+ call calloc (icm, ICM_LEN, TY_STRUCT)
+ ICM_TYPE(icm) = mtype
+ ICM_VALUE(icm) = mvalue
+ ICM_BUFS(icm) = bufs
+ ICM_PMS(icm) = pms
+ ICM_NAMES(icm) = names
+ ICM_LOGNAMES(icm) = lognames
+
+ call sfree (sp)
+end
+
+
+# IC_MCLOSE -- Close the mask interface.
+
+procedure ic_mclose (nimages)
+
+int nimages # Number of images
+
+int i
+include "icombine.com"
+
+begin
+ if (icm == NULL)
+ return
+
+ do i = 1, nimages {
+ call mfree (Memi[ICM_NAMES(icm)+i-1], TY_CHAR)
+ call mfree (Memi[ICM_BUFS(icm)+i-1], TY_INT)
+ }
+ do i = 1, nimages {
+ if (Memi[ICM_PMS(icm)+i-1] != NULL)
+ call pm_close (Memi[ICM_PMS(icm)+i-1])
+ if (project)
+ break
+ }
+ call mfree (ICM_NAMES(icm), TY_POINTER)
+ call mfree (ICM_BUFS(icm), TY_POINTER)
+ call mfree (ICM_PMS(icm), TY_POINTER)
+ call mfree (icm, TY_STRUCT)
+end
+
+
+# IC_MGET -- Get lines of mask pixels in the output coordinate system.
+# This converts the mask format to an array where zero is good and nonzero
+# is bad. This has special cases for optimization.
+
+procedure ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+
+pointer in[nimages] # Input image pointers
+pointer out[ARB] # Output image pointer
+int offsets[nimages,ARB] # Offsets to output image
+long v1[IM_MAXDIM] # Data vector desired in output image
+long v2[IM_MAXDIM] # Data vector in input image
+pointer m[nimages] # Pointer to mask pointers
+int lflag[nimages] # Line flags
+int nimages # Number of images
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+char title[1]
+int i, j, k, ndim, nin, nout, npix
+pointer buf, pm, names, fname, pm_open()
+bool pm_linenotempty()
+errchk pm_glpi, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ # Determine if masks are needed at all. Note that the threshold
+ # is applied by simulating mask values so the mask pointers have to
+ # be set.
+
+ dflag = D_ALL
+ if (icm == NULL)
+ return
+ if (ICM_TYPE(icm) == M_NONE && aligned && !dothresh)
+ return
+
+ mtype = ICM_TYPE(icm)
+ mvalue = ICM_VALUE(icm)
+ bufs = ICM_BUFS(icm)
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ # Set the mask pointers and line flags and apply offsets if needed.
+
+ ndim = IM_NDIM(out[1])
+ nout = IM_LEN(out[1],1)
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+
+ m[i] = Memi[bufs+i-1]
+ buf = Memi[bufs+i-1] + j
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+i-1]
+ fname = Memi[names+i-1]
+ }
+
+ if (npix < 1)
+ lflag[i] = D_NONE
+ else if (npix == nout)
+ lflag[i] = D_ALL
+ else
+ lflag[i] = D_MIX
+
+ if (lflag[i] != D_NONE) {
+ v2[1] = 1 + j - offsets[i,1]
+ do j = 2, ndim {
+ v2[j] = v1[j] - offsets[i,j]
+ if (v2[j] < 1 || v2[j] > IM_LEN(in[i],j)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ }
+ if (project)
+ v2[ndim+1] = i
+
+ if (lflag[i] == D_NONE) {
+ if (pm != NULL && !project) {
+ call pm_close (pm)
+ Memi[pms+i-1] = NULL
+ }
+ next
+ }
+
+ if (fname == NULL) {
+ call aclri (Memi[buf], npix)
+ next
+ } else if (Memc[fname] == EOS) {
+ call aclri (Memi[buf], npix)
+ next
+ }
+
+ # Do mask I/O and convert to appropriate values in order of
+ # expected usage.
+
+ if (pm == NULL) {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], title, 1))
+ call pm_loadim (pm, Memc[fname], title, 1)
+ call pm_seti (pm, P_REFIM, in[i])
+ if (project)
+ Memi[pms] = pm
+ else
+ Memi[pms+i-1] = pm
+ }
+
+ if (pm_linenotempty (pm, v2)) {
+ call pm_glpi (pm, v2, Memi[buf], 32, npix, 0)
+
+ if (mtype == M_BOOLEAN)
+ ;
+ else if (mtype == M_BADBITS)
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_BADVAL)
+ call abeqki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_GOODBITS) {
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ call abeqki (Memi[buf], 0, Memi[buf], npix)
+ } else if (mtype == M_GOODVAL)
+ call abneki (Memi[buf], mvalue, Memi[buf], npix)
+
+ lflag[i] = D_NONE
+ do j = 1, npix
+ if (Memi[buf+j-1] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ } else {
+ if (mtype == M_BOOLEAN || mtype == M_BADBITS) {
+ call aclri (Memi[buf], npix)
+ } else if ((mtype == M_BADVAL && mvalue != 0) ||
+ (mtype == M_GOODVAL && mvalue == 0)) {
+ call aclri (Memi[buf], npix)
+ } else {
+ call amovki (1, Memi[buf], npix)
+ lflag[i] = D_NONE
+ }
+ }
+ }
+
+ # Set overall data flag
+ dflag = lflag[1]
+ do i = 2, nimages {
+ if (lflag[i] != dflag) {
+ dflag = D_MIX
+ break
+ }
+ }
+end
+
+
+# IC_MGET1 -- Get line of mask pixels from a specified image.
+# This is used by the IC_STAT procedure. This procedure converts the
+# stored mask format to an array where zero is good and nonzero is bad.
+# The data vector and returned mask array are in the input image pixel system.
+
+procedure ic_mget1 (in, image, nimages, offset, v, m)
+
+pointer in # Input image pointer
+int image # Image index
+int nimages # Number of images
+int offset # Column offset
+long v[IM_MAXDIM] # Data vector desired
+pointer m # Pointer to mask
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+char title[1]
+int i, npix
+pointer buf, pm, names, fname, pm_open()
+bool pm_linenotempty()
+errchk pm_glpi, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ dflag = D_ALL
+ if (icm == NULL)
+ return
+ if (ICM_TYPE(icm) == M_NONE)
+ return
+
+ mtype = ICM_TYPE(icm)
+ mvalue = ICM_VALUE(icm)
+ bufs = ICM_BUFS(icm)
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ npix = IM_LEN(in,1)
+ m = Memi[bufs+image-1] + offset
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+image-1]
+ fname = Memi[names+image-1]
+ }
+
+ if (fname == NULL)
+ return
+ if (Memc[fname] == EOS)
+ return
+
+ if (pm == NULL) {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], title, 1))
+ call pm_loadim (pm, Memc[fname], title, 1)
+ call pm_seti (pm, P_REFIM, in)
+ if (project)
+ Memi[pms] = pm
+ else
+ Memi[pms+image-1] = pm
+ }
+
+ # Do mask I/O and convert to appropriate values in order of
+ # expected usage.
+
+ buf = m
+ if (pm_linenotempty (pm, v)) {
+ call pm_glpi (pm, v, Memi[buf], 32, npix, 0)
+
+ if (mtype == M_BOOLEAN)
+ ;
+ else if (mtype == M_BADBITS)
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_BADVAL)
+ call abeqki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_GOODBITS) {
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ call abeqki (Memi[buf], 0, Memi[buf], npix)
+ } else if (mtype == M_GOODVAL)
+ call abneki (Memi[buf], mvalue, Memi[buf], npix)
+
+ dflag = D_NONE
+ do i = 1, npix
+ if (Memi[buf+i-1] == 0) {
+ dflag = D_MIX
+ break
+ }
+ } else {
+ if (mtype == M_BOOLEAN || mtype == M_BADBITS) {
+ ;
+ } else if ((mtype == M_BADVAL && mvalue != 0) ||
+ (mtype == M_GOODVAL && mvalue == 0)) {
+ ;
+ } else
+ dflag = D_NONE
+ }
+end
+
+
+# IC_MCLOSE1 -- Close mask by index.
+
+procedure ic_mclose1 (image, nimages)
+
+int image # Image index
+int nimages # Number of images
+
+pointer pms, names, pm, fname
+include "icombine.com"
+
+begin
+ if (icm == NULL)
+ return
+
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+image-1]
+ fname = Memi[names+image-1]
+ }
+
+ if (fname == NULL || pm == NULL)
+ return
+ if (Memc[fname] == EOS || pm == NULL)
+ return
+
+ call pm_close (pm)
+ if (project)
+ Memi[pms] = NULL
+ else
+ Memi[pms+image-1] = NULL
+end
diff --git a/noao/onedspec/odcombine/src/icmedian.gx b/noao/onedspec/odcombine/src/icmedian.gx
new file mode 100644
index 00000000..4ac51ae6
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icmedian.gx
@@ -0,0 +1,231 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+$for (sird)
+# IC_MEDIAN -- Median of lines
+
+procedure ic_median$t (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+$if (datatype == silx)
+real val1, val2, val3
+$else
+PIXEL val1, val2, val3
+$endif
+PIXEL temp, wtemp
+$if (datatype == x)
+real abs_temp
+$endif
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Mem$t[d[j1]+k]
+ val2 = Mem$t[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mem$t[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Mem$t[d[j1]+k]
+ val2 = Mem$t[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mem$t[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mem$t[d[j]+k]; lo1 = lo; up1 = up
+ $if (datatype == x)
+ abs_temp = abs (temp)
+ $endif
+
+ repeat {
+ $if (datatype == x)
+ while (abs (Mem$t[d[lo1]+k]) < abs_temp)
+ $else
+ while (Mem$t[d[lo1]+k] < temp)
+ $endif
+ lo1 = lo1 + 1
+ $if (datatype == x)
+ while (abs_temp < abs (Mem$t[d[up1]+k]))
+ $else
+ while (temp < Mem$t[d[up1]+k])
+ $endif
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mem$t[d[lo1]+k]
+ Mem$t[d[lo1]+k] = Mem$t[d[up1]+k]
+ Mem$t[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Mem$t[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mem$t[d[j]+k]; lo1 = lo; up1 = up
+ $if (datatype == x)
+ abs_temp = abs (temp)
+ $endif
+
+ repeat {
+ $if (datatype == x)
+ while (abs (Mem$t[d[lo1]+k]) < abs_temp)
+ $else
+ while (Mem$t[d[lo1]+k] < temp)
+ $endif
+ lo1 = lo1 + 1
+ $if (datatype == x)
+ while (abs_temp < abs (Mem$t[d[up1]+k]))
+ $else
+ while (temp < Mem$t[d[up1]+k])
+ $endif
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mem$t[d[lo1]+k]
+ Mem$t[d[lo1]+k] = Mem$t[d[up1]+k]
+ Mem$t[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Mem$t[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ $if (datatype == x)
+ val1 = abs (Mem$t[d[1]+k])
+ val2 = abs (Mem$t[d[2]+k])
+ val3 = abs (Mem$t[d[3]+k])
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = Mem$t[d[2]+k]
+ else if (val1 < val3) # acb
+ median[i] = Mem$t[d[3]+k]
+ else # cab
+ median[i] = Mem$t[d[1]+k]
+ } else {
+ if (val2 > val3) # cba
+ median[i] = Mem$t[d[2]+k]
+ else if (val1 > val3) # bca
+ median[i] = Mem$t[d[3]+k]
+ else # bac
+ median[i] = Mem$t[d[1]+k]
+ }
+ $else
+ val1 = Mem$t[d[1]+k]
+ val2 = Mem$t[d[2]+k]
+ val3 = Mem$t[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+ $endif
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Mem$t[d[1]+k]
+ val2 = Mem$t[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Mem$t[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icmm.gx b/noao/onedspec/odcombine/src/icmm.gx
new file mode 100644
index 00000000..16505588
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icmm.gx
@@ -0,0 +1,189 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+$for (sird)
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mm$t (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+PIXEL d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Mem$t[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Mem$t[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Mem$t[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Mem$t[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Mem$t[kmax] = d2
+ else
+ Mem$t[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Mem$t[kmin] = d1
+ else
+ Mem$t[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Mem$t[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Mem$t[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Mem$t[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Mem$t[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icomb.gx b/noao/onedspec/odcombine/src/icomb.gx
new file mode 100644
index 00000000..6c6e56c9
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icomb.gx
@@ -0,0 +1,674 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <pmset.h>
+include <error.h>
+include <syserr.h>
+include <mach.h>
+include "../icombine.h"
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+
+# ICOMBINE -- Combine images
+#
+# The memory and open file descriptor limits are checked and an attempt
+# to recover is made either by setting the image pixel files to be
+# closed after I/O or by notifying the calling program that memory
+# ran out and the IMIO buffer size should be reduced. After the checks
+# a procedure for the selected combine option is called.
+# Because there may be several failure modes when reaching the file
+# limits we first assume an error is due to the file limit, except for
+# out of memory, and close some pixel files. If the error then repeats
+# on accessing the pixels the error is passed back.
+
+$for (sird)
+procedure icombine$t (in, out, scales, zeros, wts, offsets, nimages, bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnl$t()
+pointer sp, d, id, n, m, lflag, v, dbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnl$t, impl1i, ic_combine$t
+$if (datatype == sil)
+pointer impl1r()
+errchk impl1r
+$else
+pointer impl1$t()
+errchk impl1$t
+$endif
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_PIXEL)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 1)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_PIXEL)
+ }
+ call amovki (NULL, Memi[dbuf], nimages)
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ $if (datatype == sil)
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ $else
+ buf = impl1$t (out[1])
+ call aclr$t (Mem$t[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1$t (out[3])
+ call aclr$t (Mem$t[buf], npts)
+ }
+ $endif
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnl$t (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combine$t (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts, nimages, npts)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combine$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli()
+$if (datatype == sil)
+pointer impnlr(), imgnlr()
+$else
+pointer impnl$t(), imgnl$t
+$endif
+errchk immap, ic_scale, imgetr, ic_grow, ic_grow$t, ic_rmasks, ic_gdata$t
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ $if (datatype == sil)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mm$t (d, id, n, npts)
+ case PCLIP:
+ call ic_pclip$t (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $else
+ while (impnl$t (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Mem$t[outdata])
+ else
+ call ic_accdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Mem$t[outdata])
+ case MINMAX:
+ call ic_mm$t (d, id, n, npts)
+ case PCLIP:
+ call ic_pclip$t (d, id, n, nimages, npts, Mem$t[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Mem$t[outdata])
+ else
+ call ic_asigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Mem$t[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Mem$t[outdata])
+ else
+ call ic_aavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Mem$t[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, npts, YES, YES,
+ Mem$t[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, YES, Mem$t[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, npts, YES, NO,
+ Mem$t[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnl$t (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, npts, Mem$t[outdata],
+ Mem$t[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $endif
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ $if (datatype == sil)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_grow$t (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $else
+ while (impnl$t (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ call ic_grow$t (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnl$t (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amov$t (Mem$t[buf], Mem$t[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, npts, NO, YES,
+ Mem$t[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, NO, Mem$t[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, npts, NO, NO,
+ Mem$t[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnl$t (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, npts, Mem$t[outdata],
+ Mem$t[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $endif
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icombine.com b/noao/onedspec/odcombine/src/icombine.com
new file mode 100644
index 00000000..7fa34287
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icombine.com
@@ -0,0 +1,45 @@
+# ICOMBINE Common
+
+int combine # Combine algorithm
+int reject # Rejection algorithm
+bool project # Combine across the highest dimension?
+real blank # Blank value
+pointer ictask # Task name for log
+pointer expkeyword # Exposure time keyword
+pointer statsec # Statistics section
+pointer rdnoise # CCD read noise
+pointer gain # CCD gain
+pointer snoise # CCD sensitivity noise
+real lthresh # Low threshold
+real hthresh # High threshold
+int nkeep # Minimum to keep
+real lsigma # Low sigma cutoff
+real hsigma # High sigma cutoff
+real pclip # Number or fraction of pixels from median
+real flow # Fraction of low pixels to reject
+real fhigh # Fraction of high pixels to reject
+real grow # Grow radius
+bool mclip # Use median in sigma clipping?
+real sigscale # Sigma scaling tolerance
+int logfd # Log file descriptor
+
+# These flags allow special conditions to be optimized.
+
+int dflag # Data flag (D_ALL, D_NONE, D_MIX)
+bool aligned # Are the images aligned?
+bool doscale # Do the images have to be scaled?
+bool doscale1 # Do the sigma calculations have to be scaled?
+bool dothresh # Check pixels outside specified thresholds?
+bool dowts # Does the final average have to be weighted?
+bool keepids # Keep track of the image indices?
+bool docombine # Call the combine procedure?
+bool sort # Sort data?
+bool verbose # Verbose?
+
+pointer icm # Mask data structure
+
+common /imccom/ combine, reject, blank, ictask, expkeyword, statsec, rdnoise,
+ gain, snoise, lsigma, hsigma, lthresh, hthresh, nkeep,
+ pclip, flow, fhigh, grow, logfd, dflag, sigscale, project,
+ mclip, aligned, doscale, doscale1, dothresh, dowts,
+ keepids, docombine, sort, verbose, icm
diff --git a/noao/onedspec/odcombine/src/icombine.h b/noao/onedspec/odcombine/src/icombine.h
new file mode 100644
index 00000000..016172de
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icombine.h
@@ -0,0 +1,53 @@
+# ICOMBINE Definitions
+
+# Memory management parameters;
+define MAXMEMORY 250000000 # maximum memory
+define FUDGE 0.8 # fudge factor
+
+# Rejection options:
+define REJECT "|none|ccdclip|crreject|minmax|pclip|sigclip|avsigclip|"
+define NONE 1 # No rejection algorithm
+define CCDCLIP 2 # CCD noise function clipping
+define CRREJECT 3 # CCD noise function clipping
+define MINMAX 4 # Minmax rejection
+define PCLIP 5 # Percentile clip
+define SIGCLIP 6 # Sigma clip
+define AVSIGCLIP 7 # Sigma clip with average poisson sigma
+
+# Combine options:
+define COMBINE "|average|median|sum|"
+define AVERAGE 1
+define MEDIAN 2
+define SUM 3
+
+# Scaling options:
+define STYPES "|none|mode|median|mean|exposure|"
+define ZTYPES "|none|mode|median|mean|"
+define WTYPES "|none|mode|median|mean|exposure|"
+define S_NONE 1
+define S_MODE 2
+define S_MEDIAN 3
+define S_MEAN 4
+define S_EXPOSURE 5
+define S_FILE 6
+define S_KEYWORD 7
+define S_SECTION "|input|output|overlap|"
+define S_INPUT 1
+define S_OUTPUT 2
+define S_OVERLAP 3
+
+# Mask options
+define MASKTYPES "|none|goodvalue|badvalue|goodbits|badbits|"
+define M_NONE 1 # Don't use mask images
+define M_GOODVAL 2 # Value selecting good pixels
+define M_BADVAL 3 # Value selecting bad pixels
+define M_GOODBITS 4 # Bits selecting good pixels
+define M_BADBITS 5 # Bits selecting bad pixels
+define M_BOOLEAN -1 # Ignore mask values
+
+# Data flag
+define D_ALL 0 # All pixels are good
+define D_NONE 1 # All pixels are bad or rejected
+define D_MIX 2 # Mixture of good and bad pixels
+
+define TOL 0.001 # Tolerance for equal residuals
diff --git a/noao/onedspec/odcombine/src/icombine.x b/noao/onedspec/odcombine/src/icombine.x
new file mode 100644
index 00000000..d7b1d1e7
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icombine.x
@@ -0,0 +1,476 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <error.h>
+include <syserr.h>
+include "icombine.h"
+
+
+# ICOMBINE -- Combine input list or image.
+# This procedure maps the images, sets the output dimensions and datatype,
+# opens the logfile, and sets IMIO parameters. It attempts to adjust
+# buffer sizes and memory requirements for maximum efficiency.
+
+procedure icombine (list, output, headers, bmask, rmask, nrmask, emask,
+ sigma, logfile, scales, zeros, wts, stack, delete)
+
+int list #I List of input images
+char output[ARB] #I Output image
+char headers[ARB] #I Output header rootname
+char bmask[ARB] #I Bad pixel mask
+char rmask[ARB] #I Rejection mask
+char nrmask[ARB] #I Nreject mask
+char emask[ARB] #I Exposure mask
+char sigma[ARB] #I Sigma image (optional)
+char logfile[ARB] #I Logfile (optional)
+real scales[ARB] #I Scale factors
+real zeros[ARB] #I Offset factors
+real wts[ARB] #I Weights
+int stack #I Stack input images?
+int delete #I Delete input images?
+
+bool proj
+char input[SZ_FNAME], errstr[SZ_LINE]
+int i, j, nimages, intype, bufsize, maxsize, memory, oldsize, stack1, err
+pointer sp, im, in1, in, out[6], offsets, key, tmp, bpmstack
+
+char clgetc()
+int clgwrd(), imtlen(), imtgetim(), imtrgetim(), getdatatype()
+int begmem(), errget(), open(), ty_max(), sizeof(), strmatch()
+pointer immap(), xt_immap(), ic_pmmap()
+errchk ic_imstack, immap, imunmap, xt_immap, ic_pmmap, ic_setout
+
+include "icombine.com"
+
+define retry_ 98
+define err_ 99
+
+begin
+ nimages = imtlen (list)
+ if (nimages == 0)
+ call error (1, "No images to combine")
+
+ if (project) {
+ if (imtgetim (list, input, SZ_FNAME) == EOF)
+ call error (1, "No image to project")
+ }
+
+ bufsize = 0
+# if (nimages > LAST_FD - 15)
+# stack1 = YES
+# else
+ stack1 = stack
+
+retry_
+ iferr {
+ call smark (sp)
+ call salloc (in, 1, TY_POINTER)
+
+ nimages = 0
+ in1 = NULL; Memi[in] = NULL; logfd = NULL
+ out[1] = NULL; out[2] = NULL; out[3] = NULL
+ out[4] = NULL; out[5] = NULL; out[6] = NULL
+
+ # Stack the input images.
+ if (stack1 == YES) {
+ proj = project
+ project = true
+ call salloc (bpmstack, SZ_FNAME, TY_CHAR)
+ i = clgwrd ("masktype", Memc[bpmstack], SZ_FNAME, MASKTYPES)
+ if (i == M_NONE)
+ Memc[bpmstack] = EOS
+ else {
+ call mktemp ("tmp", Memc[bpmstack], SZ_FNAME)
+ call strcat (".pl", Memc[bpmstack], SZ_FNAME)
+ }
+ call mktemp ("tmp", input, SZ_FNAME)
+ call imtrew (list)
+ call ic_imstack (list, input, Memc[bpmstack])
+ }
+
+ # Open the input image(s).
+ if (project) {
+ tmp = immap (input, READ_ONLY, 0); out[1] = tmp
+ if (IM_NDIM(out[1]) == 1)
+ call error (1, "Can't project one dimensional images")
+ nimages = IM_LEN(out[1],IM_NDIM(out[1]))
+ call salloc (in, nimages, TY_POINTER)
+ call amovki (out[1], Memi[in], nimages)
+ } else {
+ call salloc (in, imtlen(list), TY_POINTER)
+ call amovki (NULL, Memi[in], imtlen(list))
+ call imtrew (list)
+ while (imtgetim (list, input, SZ_FNAME)!=EOF) {
+ nimages = nimages + 1
+ tmp = xt_immap (input, READ_ONLY, 0, nimages)
+ Memi[in+nimages-1] = tmp
+ }
+
+ # Check sizes and set I/O option.
+ intype = 0
+ tmp = Memi[in]
+ do i = 2, nimages {
+ do j = 1, IM_NDIM(tmp) {
+ if (IM_LEN(tmp,j) != IM_LEN(Memi[in+i-1],j))
+ intype = 1
+ }
+ if (intype == 1)
+ break
+ }
+ if (intype == 1)
+ call xt_imseti (0, "option", intype)
+ }
+
+ # Check if there are no images.
+ if (nimages == 0)
+ call error (1, "No images to combine")
+
+ # Convert the pclip parameter to a number of pixels rather than
+ # a fraction. This number stays constant even if pixels are
+ # rejected. The number of low and high pixel rejected, however,
+ # are converted to a fraction of the valid pixels.
+
+ if (reject == PCLIP) {
+ i = nimages / 2.
+ if (abs (pclip) < 1.)
+ pclip = pclip * i
+ if (pclip < 0.)
+ pclip = min (-1, max (-i, int (pclip)))
+ else
+ pclip = max (1, min (i, int (pclip)))
+ }
+
+ if (reject == MINMAX) {
+ if (flow >= 1)
+ flow = flow / nimages
+ if (fhigh >= 1)
+ fhigh = fhigh / nimages
+ i = flow * nimages
+ j = fhigh * nimages
+ if (i + j == 0)
+ reject = NONE
+ else if (i + j >= nimages)
+ call error (1, "Bad minmax rejection parameters")
+ }
+
+ # Map the output image and set dimensions and offsets.
+ if (stack1 == YES) {
+ call imtrew (list)
+ i = imtgetim (list, errstr, SZ_LINE)
+ in1 = immap (errstr, READ_ONLY, 0)
+ tmp = immap (output, NEW_COPY, in1); out[1] = tmp
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ do i = 1, nimages {
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ iferr (call imdelf (out[1], Memc[key]))
+ ;
+ if (Memc[bpmstack] != EOS) {
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ iferr (call imdelf (out[1], Memc[key]))
+ ;
+ }
+ }
+ } else {
+ tmp = immap (output, NEW_COPY, Memi[in]); out[1] = tmp
+ if (project) {
+ IM_LEN(out[1],IM_NDIM(out[1])) = 1
+ IM_NDIM(out[1]) = IM_NDIM(out[1]) - 1
+ }
+ }
+ call salloc (offsets, nimages*IM_NDIM(out[1]), TY_INT)
+ iferr (call ic_setout (Memi[in], out, Memi[offsets], nimages)) {
+ call erract (EA_WARN)
+ call error (1, "Can't set output geometry")
+ }
+ call ic_hdr (Memi[in], out, nimages)
+ iferr (call imdelf (out, "BPM"))
+ ;
+ iferr (call imdelf (out, "ICFNAME"))
+ ;
+
+ # Determine the highest precedence datatype and set output datatype.
+ intype = IM_PIXTYPE(Memi[in])
+ do i = 2, nimages
+ intype = ty_max (intype, IM_PIXTYPE(Memi[in+i-1]))
+ IM_PIXTYPE(out[1]) = getdatatype (clgetc ("outtype"))
+ if (IM_PIXTYPE(out[1]) == ERR)
+ IM_PIXTYPE(out[1]) = intype
+
+ # Open rejection masks
+ if (rmask[1] != EOS) {
+ tmp = ic_pmmap (rmask, NEW_COPY, out[1]); out[4] = tmp
+ IM_NDIM(out[4]) = IM_NDIM(out[4]) + 1
+ IM_LEN(out[4],IM_NDIM(out[4])) = nimages
+ if (!project) {
+ if (key == NULL)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ do i = 100, nimages {
+ j = imtrgetim (list, i, input, SZ_FNAME)
+ if (i < 999)
+ call sprintf (Memc[key], SZ_FNAME, "imcmb%d")
+ else if (i < 9999)
+ call sprintf (Memc[key], SZ_FNAME, "imcm%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "imc%d")
+ call pargi (i)
+ call imastr (out[4], Memc[key], input)
+ }
+ }
+ } else
+ out[4] = NULL
+
+ # Open bad pixel pixel list file if given.
+ if (bmask[1] != EOS) {
+ tmp = ic_pmmap (bmask, NEW_COPY, out[1]); out[2] = tmp
+ } else
+ out[2] = NULL
+
+ # Open nreject pixel list file if given.
+ if (nrmask[1] != EOS) {
+ tmp = ic_pmmap (nrmask, NEW_COPY, out[1]); out[5] = tmp
+ } else
+ out[5] = NULL
+
+ # Open exposure mask if given.
+ if (emask[1] != EOS) {
+ tmp = ic_pmmap (emask, NEW_COPY, out[1]); out[6] = tmp
+ } else
+ out[6] = NULL
+
+ # Open the sigma image if given.
+ if (sigma[1] != EOS) {
+ tmp = immap (sigma, NEW_COPY, out[1]); out[3] = tmp
+ IM_PIXTYPE(out[3]) = ty_max (TY_REAL, IM_PIXTYPE(out[1]))
+ call sprintf (IM_TITLE(out[3]), SZ_IMTITLE,
+ "Combine sigma images for %s")
+ call pargstr (output)
+ } else
+ out[3] = NULL
+
+ # Open masks.
+ call ic_mopen (Memi[in], out, nimages, Memi[offsets])
+
+ # Open the log file.
+ logfd = NULL
+ if (logfile[1] != EOS) {
+ iferr (logfd = open (logfile, APPEND, TEXT_FILE)) {
+ logfd = NULL
+ call erract (EA_WARN)
+ }
+ }
+
+ if (bufsize == 0) {
+ # Set initial IMIO buffer size based on the number of images
+ # and maximum amount of working memory available. The buffer
+ # size may be adjusted later if the task runs out of memory.
+ # The FUDGE factor is used to allow for the size of the
+ # program, memory allocator inefficiencies, and any other
+ # memory requirements besides IMIO.
+
+ memory = begmem (0, oldsize, maxsize)
+ memory = min (memory, maxsize, MAXMEMORY)
+ bufsize = FUDGE * memory / (nimages + 1) / sizeof (intype)
+ }
+
+ # Combine the images. If an out of memory error occurs close all
+ # images and files, divide the IMIO buffer size in half and try
+ # again.
+
+ switch (ty_max (intype, IM_PIXTYPE(out[1]))) {
+ case TY_SHORT:
+ call icombines (Memi[in], out, scales, zeros,
+ wts, Memi[offsets], nimages, bufsize)
+ case TY_USHORT, TY_INT, TY_LONG:
+ call icombinei (Memi[in], out, scales, zeros,
+ wts, Memi[offsets], nimages, bufsize)
+ case TY_DOUBLE:
+ call icombined (Memi[in], out, scales, zeros,
+ wts, Memi[offsets], nimages, bufsize)
+ case TY_COMPLEX:
+ call error (1, "Complex images not allowed")
+ default:
+ call icombiner (Memi[in], out, scales, zeros,
+ wts, Memi[offsets], nimages, bufsize)
+ }
+ } then {
+ err = errget (errstr, SZ_LINE)
+ if (err == SYS_IKIOPIX && nimages < 250)
+ err = SYS_MFULL
+ call ic_mclose (nimages)
+ if (!project) {
+ do j = 2, nimages {
+ if (Memi[in+j-1] != NULL)
+ call xt_imunmap (Memi[in+j-1], j)
+ }
+ }
+ if (out[2] != NULL) {
+ call imunmap (out[2])
+ iferr (call imdelete (bmask))
+ ;
+ }
+ if (out[3] != NULL) {
+ call imunmap (out[3])
+ iferr (call imdelete (sigma))
+ ;
+ }
+ if (out[4] != NULL) {
+ call imunmap (out[4])
+ iferr (call imdelete (rmask))
+ ;
+ }
+ if (out[5] != NULL) {
+ call imunmap (out[5])
+ iferr (call imdelete (nrmask))
+ ;
+ }
+ if (out[6] != NULL) {
+ call imunmap (out[6])
+ iferr (call imdelete (emask))
+ ;
+ }
+ if (out[1] != NULL) {
+ call imunmap (out[1])
+ iferr (call imdelete (output))
+ ;
+ }
+ if (Memi[in] != NULL)
+ call xt_imunmap (Memi[in], 1)
+ if (in1 != NULL)
+ call imunmap (in1)
+ if (logfd != NULL)
+ call close (logfd)
+
+ switch (err) {
+ case SYS_MFULL:
+ if (project)
+ goto err_
+
+ if (bufsize < 10000) {
+ call strcat ("- Maybe min_lenuserarea is too large",
+ errstr, SZ_LINE)
+ goto err_
+ }
+
+ bufsize = bufsize / 2
+ call sfree (sp)
+ goto retry_
+ case SYS_FTOOMANYFILES, SYS_IKIOPEN, SYS_IKIOPIX, SYS_FOPEN, SYS_FWTNOACC:
+ if (project)
+ goto err_
+ stack1 = YES
+ call sfree (sp)
+ goto retry_
+ default:
+err_
+ if (stack1 == YES) {
+ iferr (call imdelete (input))
+ ;
+ if (Memc[bpmstack] != EOS) {
+ iferr (call imdelete (Memc[bpmstack]))
+ ;
+ }
+ }
+ call fixmem (oldsize)
+ while (imtgetim (list, input, SZ_FNAME)!=EOF)
+ ;
+ call sfree (sp)
+ call error (err, errstr)
+ }
+ }
+
+ # Unmap all the images, close the log file, and restore memory.
+ if (out[2] != NULL)
+ iferr (call imunmap (out[2]))
+ call erract (EA_WARN)
+ if (out[3] != NULL)
+ iferr (call imunmap (out[3]))
+ call erract (EA_WARN)
+ if (out[4] != NULL) {
+ # Close the output first so that there is no confusion with
+ # inheriting the output header. Then update the WCS for the
+ # extra dimension. Note that this may not be correct with
+ # axis reduced WCS.
+ iferr {
+ call imunmap (out[4])
+ out[4] = immap (rmask, READ_WRITE, 0)
+ i = IM_NDIM(out[4])
+ call imaddi (out[4], "WCSDIM", i)
+ call sprintf (errstr, SZ_LINE, "LTM%d_%d")
+ call pargi (i)
+ call pargi (i)
+ call imaddr (out[4], errstr, 1.)
+ call sprintf (errstr, SZ_LINE, "CD%d_%d")
+ call pargi (i)
+ call pargi (i)
+ call imaddr (out[4], errstr, 1.)
+ call imunmap (out[4])
+ } then
+ call erract (EA_WARN)
+ }
+ if (out[5] != NULL)
+ iferr (call imunmap (out[5]))
+ call erract (EA_WARN)
+ if (out[6] != NULL)
+ iferr (call imunmap (out[6]))
+ call erract (EA_WARN)
+ if (out[1] != NULL) {
+ call imunmap (out[1])
+ if (headers[1] != EOS) {
+ # Write input headers to a multiextension file if desired.
+ # This might be the same as the output image.
+ iferr {
+ do i = 1, nimages {
+ im = Memi[in+i-1]
+ call imstats (im, IM_IMAGENAME, input, SZ_FNAME)
+ if (strmatch (headers, ".fits$") == 0) {
+ call sprintf (errstr, SZ_LINE, "%s.fits[append]")
+ call pargstr (headers)
+ } else {
+ call sprintf (errstr, SZ_LINE, "%s[append]")
+ call pargstr (headers)
+ }
+ tmp = immap (errstr, NEW_COPY, im)
+ IM_NDIM(tmp) = 0
+ do j = 1, IM_NDIM(im) {
+ call sprintf (errstr, SZ_LINE, "AXLEN%d")
+ call pargi (j)
+ call imaddi (tmp, errstr, IM_LEN(im,j))
+ }
+ call imastr (tmp, "INIMAGE", input)
+ call imastr (tmp, "OUTIMAGE", output)
+ call imastr (tmp, "EXTNAME", input)
+ call imunmap (tmp)
+ }
+ if (logfd != NULL) {
+ call eprintf (" Headers = %s\n")
+ call pargstr (headers)
+ }
+ } then
+ call erract (EA_WARN)
+ }
+ }
+ if (!project) {
+ do i = 2, nimages {
+ if (Memi[in+i-1] != NULL)
+ call xt_imunmap (Memi[in+i-1], i)
+ }
+ }
+ if (Memi[in] != NULL)
+ call xt_imunmap (Memi[in], 1)
+ if (in1 != NULL)
+ call imunmap (in1)
+ if (stack1 == YES) {
+ call imdelete (input)
+ if (Memc[bpmstack] != EOS)
+ call imdelete (Memc[bpmstack])
+ project = proj
+ }
+ if (logfd != NULL)
+ call close (logfd)
+ call ic_mclose (nimages)
+ call fixmem (oldsize)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/icpclip.gx b/noao/onedspec/odcombine/src/icpclip.gx
new file mode 100644
index 00000000..f0c76369
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icpclip.gx
@@ -0,0 +1,233 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number for clipping
+
+$for (sird)
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclip$t (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med
+$else
+PIXEL med
+$endif
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Mem$t[d[n2-1]+j]
+ med = (med + Mem$t[d[n2]+j]) / 2.
+ } else
+ med = Mem$t[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Mem$t[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Mem$t[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Mem$t[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Mem$t[d[n5-1]+j]
+ med = (med + Mem$t[d[n5]+j]) / 2.
+ } else
+ med = Mem$t[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+j] = Mem$t[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+j] = Mem$t[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icpmmap.x b/noao/onedspec/odcombine/src/icpmmap.x
new file mode 100644
index 00000000..1afeedd7
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icpmmap.x
@@ -0,0 +1,34 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <pmset.h>
+
+
+# IC_PMMAP -- Map pixel mask.
+
+pointer procedure ic_pmmap (fname, mode, refim)
+
+char fname[ARB] # Mask name
+int mode # Image mode
+pointer refim # Reference image
+pointer pm # IMIO pointer (returned)
+
+int i, fnextn()
+pointer sp, extn, immap()
+bool streq()
+
+begin
+ call smark (sp)
+ call salloc (extn, SZ_FNAME, TY_CHAR)
+
+ i = fnextn (fname, Memc[extn], SZ_FNAME)
+ if (streq (Memc[extn], "pl"))
+ pm = immap (fname, mode, refim)
+ else {
+ call strcpy (fname, Memc[extn], SZ_FNAME)
+ call strcat (".pl", Memc[extn], SZ_FNAME)
+ pm = immap (Memc[extn], mode, refim)
+ }
+
+ call sfree (sp)
+ return (pm)
+end
diff --git a/noao/onedspec/odcombine/src/icrmasks.x b/noao/onedspec/odcombine/src/icrmasks.x
new file mode 100644
index 00000000..8b9a0c3d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icrmasks.x
@@ -0,0 +1,41 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+
+
+# IC_RMASKS -- Set pixels for rejection mask.
+
+procedure ic_rmasks (pm, v, id, nimages, n, npts)
+
+pointer pm #I Pixel mask
+long v[ARB] #I Output vector (input)
+pointer id[nimages] #I Image id pointers
+int nimages #I Number of images
+int n[npts] #I Number of good pixels
+int npts #I Number of output points per line
+
+int i, j, k, ndim, impnls()
+long v1[IM_MAXDIM]
+pointer buf
+
+begin
+ ndim = IM_NDIM(pm)
+ do k = 1, nimages {
+ call amovl (v, v1, ndim-1)
+ v1[ndim] = k
+ i = impnls (pm, buf, v1)
+ do j = 1, npts {
+ if (n[j] == nimages)
+ Mems[buf+j-1] = 0
+ else {
+ Mems[buf+j-1] = 1
+ do i = 1, n[j] {
+ if (Memi[id[i]+j-1] == k) {
+ Mems[buf+j-1] = 0
+ break
+ }
+ }
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/src/icscale.x b/noao/onedspec/odcombine/src/icscale.x
new file mode 100644
index 00000000..42d62f8d
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icscale.x
@@ -0,0 +1,351 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include "icombine.h"
+
+
+# IC_SCALE -- Get and set the scaling factors.
+#
+# If the scaling parameters have been set earlier then this routine
+# just normalizes the factors and writes the log output.
+# When dealing with individual images using image statistics for scaling
+# factors this routine determines the image statistics rather than being
+# done earlier since the input images have all been mapped at this stage.
+
+procedure ic_scale (in, out, offsets, scales, zeros, wts, nimages)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+int nimages # Number of images
+
+int stype, ztype, wtype
+int i, j, k, l, nout
+real mode, median, mean, sumwts
+pointer sp, ncombine, exptime, modes, medians, means
+pointer section, str, sname, zname, wname, im, imref
+bool domode, domedian, domean, dozero, dos, doz, dow, snorm, znorm, wflag
+
+int imgeti(), strdic(), ic_gscale()
+real imgetr(), asumr(), asumi()
+pointer xt_opix()
+errchk ic_gscale, xt_opix, ic_statr
+
+include "icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (ncombine, nimages, TY_INT)
+ call salloc (exptime, nimages, TY_REAL)
+ call salloc (modes, nimages, TY_REAL)
+ call salloc (medians, nimages, TY_REAL)
+ call salloc (means, nimages, TY_REAL)
+ call salloc (section, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (sname, SZ_FNAME, TY_CHAR)
+ call salloc (zname, SZ_FNAME, TY_CHAR)
+ call salloc (wname, SZ_FNAME, TY_CHAR)
+
+ # Get the number of images previously combined and the exposure times.
+ # The default combine number is 1 and the default exposure is 0.
+
+ do i = 1, nimages {
+ iferr (Memi[ncombine+i-1] = imgeti (in[i], "ncombine"))
+ Memi[ncombine+i-1] = 1
+ if (Memc[expkeyword] != EOS) {
+ iferr (Memr[exptime+i-1] = imgetr (in[i], Memc[expkeyword]))
+ Memr[exptime+i-1] = 0.
+ } else
+ Memr[exptime+i-1] = 0.
+ if (project) {
+ call amovki (Memi[ncombine], Memi[ncombine], nimages)
+ call amovkr (Memr[exptime], Memr[exptime], nimages)
+ break
+ }
+ }
+
+ # Set scaling type and factors.
+ stype = ic_gscale ("scale", Memc[sname], STYPES, in, Memr[exptime],
+ scales, nimages)
+ ztype = ic_gscale ("zero", Memc[zname], ZTYPES, in, Memr[exptime],
+ zeros, nimages)
+ wtype = ic_gscale ("weight", Memc[wname], WTYPES, in, Memr[exptime],
+ wts, nimages)
+
+ # Get image statistics if needed.
+ dos = ((stype==S_MODE)||(stype==S_MEDIAN)||(stype==S_MEAN))
+ doz = ((ztype==S_MODE)||(ztype==S_MEDIAN)||(ztype==S_MEAN))
+ dow = ((wtype==S_MODE)||(wtype==S_MEDIAN)||(wtype==S_MEAN))
+ if (dos) {
+ dos = false
+ do i = 1, nimages
+ if (IS_INDEFR(scales[i])) {
+ dos = true
+ break
+ }
+ }
+ if (doz) {
+ doz = false
+ do i = 1, nimages
+ if (IS_INDEFR(zeros[i])) {
+ doz = true
+ break
+ }
+ }
+ if (dow) {
+ dow = false
+ do i = 1, nimages
+ if (IS_INDEFR(wts[i])) {
+ dow = true
+ break
+ }
+ }
+
+ if (dos || doz || dow) {
+ domode = ((stype==S_MODE)||(ztype==S_MODE)||(wtype==S_MODE))
+ domedian = ((stype==S_MEDIAN)||(ztype==S_MEDIAN)||(wtype==S_MEDIAN))
+ domean = ((stype==S_MEAN)||(ztype==S_MEAN)||(wtype==S_MEAN))
+
+ Memc[section] = EOS
+ Memc[str] = EOS
+ call sscan (Memc[statsec])
+ call gargwrd (Memc[section], SZ_FNAME)
+ call gargwrd (Memc[str], SZ_LINE)
+
+ i = strdic (Memc[section], Memc[section], SZ_FNAME, S_SECTION)
+ switch (i) {
+ case S_INPUT:
+ call strcpy (Memc[str], Memc[section], SZ_FNAME)
+ imref = NULL
+ case S_OUTPUT:
+ call strcpy (Memc[str], Memc[section], SZ_FNAME)
+ imref = out[1]
+ case S_OVERLAP:
+ call strcpy ("[", Memc[section], SZ_FNAME)
+ do i = 1, IM_NDIM(out[1]) {
+ k = offsets[1,i] + 1
+ l = offsets[1,i] + IM_LEN(in[1],i)
+ do j = 2, nimages {
+ k = max (k, offsets[j,i]+1)
+ l = min (l, offsets[j,i]+IM_LEN(in[j],i))
+ }
+ if (i < IM_NDIM(out[1]))
+ call sprintf (Memc[str], SZ_LINE, "%d:%d,")
+ else
+ call sprintf (Memc[str], SZ_LINE, "%d:%d]")
+ call pargi (k)
+ call pargi (l)
+ call strcat (Memc[str], Memc[section], SZ_FNAME)
+ }
+ imref = out[1]
+ default:
+ imref = NULL
+ }
+
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (imref != out[1])
+ imref = im
+ if ((dos && IS_INDEFR(scales[i])) ||
+ (doz && IS_INDEFR(zeros[i])) ||
+ (dow && IS_INDEFR(wts[i]))) {
+ call ic_statr (im, imref, Memc[section], offsets, i,
+ nimages, domode, domedian, domean, mode, median, mean)
+ if (domode) {
+ if (stype == S_MODE && IS_INDEFR(scales[i]))
+ scales[i] = mode
+ if (ztype == S_MODE && IS_INDEFR(zeros[i]))
+ zeros[i] = mode
+ if (wtype == S_MODE && IS_INDEFR(wts[i]))
+ wts[i] = mode
+ }
+ if (domedian) {
+ if (stype == S_MEDIAN && IS_INDEFR(scales[i]))
+ scales[i] = median
+ if (ztype == S_MEDIAN && IS_INDEFR(zeros[i]))
+ zeros[i] = median
+ if (wtype == S_MEDIAN && IS_INDEFR(wts[i]))
+ wts[i] = median
+ }
+ if (domean) {
+ if (stype == S_MEAN && IS_INDEFR(scales[i]))
+ scales[i] = mean
+ if (ztype == S_MEAN && IS_INDEFR(zeros[i]))
+ zeros[i] = mean
+ if (wtype == S_MEAN && IS_INDEFR(wts[i]))
+ wts[i] = mean
+ }
+ }
+ }
+ }
+
+ # Save the image statistics if computed.
+ call amovkr (INDEFR, Memr[modes], nimages)
+ call amovkr (INDEFR, Memr[medians], nimages)
+ call amovkr (INDEFR, Memr[means], nimages)
+ if (stype == S_MODE)
+ call amovr (scales, Memr[modes], nimages)
+ if (stype == S_MEDIAN)
+ call amovr (scales, Memr[medians], nimages)
+ if (stype == S_MEAN)
+ call amovr (scales, Memr[means], nimages)
+ if (ztype == S_MODE)
+ call amovr (zeros, Memr[modes], nimages)
+ if (ztype == S_MEDIAN)
+ call amovr (zeros, Memr[medians], nimages)
+ if (ztype == S_MEAN)
+ call amovr (zeros, Memr[means], nimages)
+ if (wtype == S_MODE)
+ call amovr (wts, Memr[modes], nimages)
+ if (wtype == S_MEDIAN)
+ call amovr (wts, Memr[medians], nimages)
+ if (wtype == S_MEAN)
+ call amovr (wts, Memr[means], nimages)
+
+ # If nothing else has set the scaling factors set them to defaults.
+ do i = 1, nimages {
+ if (IS_INDEFR(scales[i]))
+ scales[i] = 1.
+ if (IS_INDEFR(zeros[i]))
+ zeros[i] = 0.
+ if (IS_INDEFR(wts[i]))
+ wts[i] = 1.
+ }
+
+ do i = 1, nimages
+ if (scales[i] <= 0.) {
+ call eprintf ("WARNING: Negative scale factors")
+ call eprintf (" -- ignoring scaling\n")
+ call amovkr (1., scales, nimages)
+ break
+ }
+
+ # Convert to factors relative to the first image.
+ snorm = (stype == S_FILE || stype == S_KEYWORD)
+ znorm = (ztype == S_FILE || ztype == S_KEYWORD)
+ wflag = (wtype == S_FILE || wtype == S_KEYWORD)
+ if (snorm)
+ call arcpr (1., scales, scales, nimages)
+ mean = scales[1]
+ call adivkr (scales, mean, scales, nimages)
+ call adivr (zeros, scales, zeros, nimages)
+
+ if (wtype != S_NONE) {
+ do i = 1, nimages {
+ if (wts[i] < 0.) {
+ call eprintf ("WARNING: Negative weights")
+ call eprintf (" -- using only NCOMBINE weights\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1]
+ break
+ }
+ if (ztype == S_NONE || znorm || wflag)
+ wts[i] = Memi[ncombine+i-1] * wts[i]
+ else {
+ if (zeros[i] <= 0.) {
+ call eprintf ("WARNING: Negative zero offsets")
+ call eprintf (" -- ignoring zero weight adjustments\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1] * wts[j]
+ break
+ }
+ wts[i] = Memi[ncombine+i-1] * wts[i] * zeros[1] / zeros[i]
+ }
+ }
+ }
+
+ if (znorm)
+ call anegr (zeros, zeros, nimages)
+ else {
+ # Because of finite arithmetic it is possible for the zero offsets
+ # to be nonzero even when they are all equal. Just for the sake of
+ # a nice log set the zero offsets in this case.
+
+ mean = zeros[1]
+ call asubkr (zeros, mean, zeros, nimages)
+ for (i=2; (i<=nimages)&&(zeros[i]==zeros[1]); i=i+1)
+ ;
+ if (i > nimages)
+ call aclrr (zeros, nimages)
+ }
+ mean = asumr (wts, nimages)
+ if (mean > 0.)
+ call adivkr (wts, mean, wts, nimages)
+ else {
+ call eprintf ("WARNING: Mean weight is zero -- using no weights\n")
+ call amovkr (1., wts, nimages)
+ mean = 1.
+ }
+
+ # Set flags for scaling, zero offsets, sigma scaling, weights.
+ # Sigma scaling may be suppressed if the scales or zeros are
+ # different by a specified tolerance.
+
+ doscale = false
+ dozero = false
+ doscale1 = false
+ dowts = false
+ do i = 2, nimages {
+ if (snorm || scales[i] != scales[1])
+ doscale = true
+ if (znorm || zeros[i] != zeros[1])
+ dozero = true
+ if (wts[i] != wts[1])
+ dowts = true
+ }
+ if (doscale && sigscale != 0.) {
+ do i = 1, nimages {
+ if (abs (scales[i] - 1) > sigscale) {
+ doscale1 = true
+ break
+ }
+ }
+ }
+
+ # Set the output header parameters.
+ nout = asumi (Memi[ncombine], nimages)
+ call imaddi (out[1], "ncombine", nout)
+ mean = 0.
+ sumwts = 0.
+ do i = 1, nimages {
+ ifnoerr (mode = imgetr (in[i], "ccdmean")) {
+ mean = mean + wts[i] * mode / scales[i]
+ sumwts = sumwts + wts[i]
+ }
+ }
+ if (sumwts > 0.) {
+ mean = mean / sumwts
+ ifnoerr (mode = imgetr (out[1], "ccdmean")) {
+ call imaddr (out[1], "ccdmean", mean)
+ iferr (call imdelf (out[1], "ccdmeant"))
+ ;
+ }
+ }
+ if (out[2] != NULL) {
+ call imstats (out[2], IM_IMAGENAME, Memc[str], SZ_FNAME)
+ call imastr (out[1], "BPM", Memc[str])
+ }
+
+ # Start the log here since much of the info is only available here.
+ if (verbose) {
+ i = logfd
+ logfd = STDOUT
+ call ic_log (in, out, Memi[ncombine], Memr[exptime], Memc[sname],
+ Memc[zname], Memc[wname], Memr[modes], Memr[medians],
+ Memr[means], scales, zeros, wts, offsets, nimages, dozero,
+ nout)
+
+ logfd = i
+ }
+ call ic_log (in, out, Memi[ncombine], Memr[exptime], Memc[sname],
+ Memc[zname], Memc[wname], Memr[modes], Memr[medians], Memr[means],
+ scales, zeros, wts, offsets, nimages, dozero, nout)
+
+ doscale = (doscale || dozero)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/icsclip.gx b/noao/onedspec/odcombine/src/icsclip.gx
new file mode 100644
index 00000000..1b1c5de9
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icsclip.gx
@@ -0,0 +1,504 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Mininum number of images for algorithm
+
+$for (sird)
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclip$t (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+$else
+PIXEL d1, low, high, sum, a, s, r, one
+data one /1$f/
+$endif
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclip$t (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+$if (datatype == sil)
+real med, one
+data one /1.0/
+$else
+PIXEL med, one
+data one /1$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Mem$t[d[n3-1]+k] + Mem$t[d[n3]+k]) / 2.
+ else
+ med = Mem$t[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Mem$t[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Mem$t[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icsection.x b/noao/onedspec/odcombine/src/icsection.x
new file mode 100644
index 00000000..746c1f51
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icsection.x
@@ -0,0 +1,94 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <ctype.h>
+
+# IC_SECTION -- Parse an image section into its elements.
+# 1. The default values must be set by the caller.
+# 2. A null image section is OK.
+# 3. The first nonwhitespace character must be '['.
+# 4. The last interpreted character must be ']'.
+#
+# This procedure should be replaced with an IMIO procedure at some
+# point.
+
+procedure ic_section (section, x1, x2, xs, ndim)
+
+char section[ARB] # Image section
+int x1[ndim] # Starting pixel
+int x2[ndim] # Ending pixel
+int xs[ndim] # Step
+int ndim # Number of dimensions
+
+int i, ip, a, b, c, temp, ctoi()
+define error_ 99
+
+begin
+ # Decode the section string.
+ ip = 1
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == '[')
+ ip = ip + 1
+ else if (section[ip] == EOS)
+ return
+ else
+ goto error_
+
+ do i = 1, ndim {
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ']')
+ break
+
+ # Default values
+ a = x1[i]
+ b = x2[i]
+ c = xs[i]
+
+ # Get a:b:c. Allow notation such as "-*:c"
+ # (or even "-:c") where the step is obviously negative.
+
+ if (ctoi (section, ip, temp) > 0) { # a
+ a = temp
+ if (section[ip] == ':') {
+ ip = ip + 1
+ if (ctoi (section, ip, b) == 0) # a:b
+ goto error_
+ } else
+ b = a
+ } else if (section[ip] == '-') { # -*
+ temp = a
+ a = b
+ b = temp
+ ip = ip + 1
+ if (section[ip] == '*')
+ ip = ip + 1
+ } else if (section[ip] == '*') # *
+ ip = ip + 1
+ if (section[ip] == ':') { # ..:step
+ ip = ip + 1
+ if (ctoi (section, ip, c) == 0)
+ goto error_
+ else if (c == 0)
+ goto error_
+ }
+ if (a > b && c > 0)
+ c = -c
+
+ x1[i] = a
+ x2[i] = b
+ xs[i] = c
+
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ',')
+ ip = ip + 1
+ }
+
+ if (section[ip] != ']')
+ goto error_
+
+ return
+error_
+ call error (0, "Error in image section specification")
+end
diff --git a/noao/onedspec/odcombine/src/icsetout.x b/noao/onedspec/odcombine/src/icsetout.x
new file mode 100644
index 00000000..51e1fe90
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icsetout.x
@@ -0,0 +1,322 @@
+include <imhdr.h>
+include <imset.h>
+include <mwset.h>
+
+define OFFTYPES "|none|wcs|world|physical|grid|"
+define FILE 0
+define NONE 1
+define WCS 2
+define WORLD 3
+define PHYSICAL 4
+define GRID 5
+
+# IC_SETOUT -- Set output image size and offsets of input images.
+
+procedure ic_setout (in, out, offsets, nimages)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int offsets[nimages,ARB] # Offsets
+int nimages # Number of images
+
+int i, j, indim, outdim, mwdim, a, b, amin, bmax, fd, offtype
+real val
+bool proj, reloff, flip, streq(), fp_equald()
+pointer sp, str, fname
+pointer ltv, lref, wref, cd, ltm, coord, shift, axno, axval, section
+pointer mw, ct, mw_openim(), mw_sctran(), xt_immap()
+int open(), fscan(), nscan(), mw_stati(), strlen(), strdic()
+errchk mw_openim, mw_gwtermd, mw_gltermd, mw_gaxmap
+errchk mw_sctran, mw_ctrand, open, xt_immap
+
+include "icombine.com"
+define newscan_ 10
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (ltv, IM_MAXDIM, TY_DOUBLE)
+ call salloc (ltm, IM_MAXDIM*IM_MAXDIM, TY_DOUBLE)
+ call salloc (lref, IM_MAXDIM, TY_DOUBLE)
+ call salloc (wref, IM_MAXDIM, TY_DOUBLE)
+ call salloc (cd, IM_MAXDIM*IM_MAXDIM, TY_DOUBLE)
+ call salloc (coord, IM_MAXDIM, TY_DOUBLE)
+ call salloc (shift, IM_MAXDIM, TY_REAL)
+ call salloc (axno, IM_MAXDIM, TY_INT)
+ call salloc (axval, IM_MAXDIM, TY_INT)
+
+ # Check and set the image dimensionality.
+ indim = IM_NDIM(in[1])
+ outdim = IM_NDIM(out[1])
+ proj = (indim != outdim)
+ if (!proj) {
+ do i = 1, nimages
+ if (IM_NDIM(in[i]) != outdim) {
+ call sfree (sp)
+ call error (1, "Image dimensions are not the same")
+ }
+ }
+
+ # Set the reference point to that of the first image.
+ mw = mw_openim (in[1])
+ call mw_seti (mw, MW_USEAXMAP, NO)
+ mwdim = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gwtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[lref], mwdim)
+ call mw_ctfree (ct)
+ if (proj)
+ Memd[lref+outdim] = 1
+
+ # Parse the user offset string. If "none" then there are no offsets.
+ # If "world" or "wcs" then set the offsets based on the world WCS.
+ # If "physical" then set the offsets based on the physical WCS.
+ # If "grid" then set the offsets based on the input grid parameters.
+ # If a file scan it.
+
+ call clgstr ("offsets", Memc[fname], SZ_FNAME)
+ call sscan (Memc[fname])
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (nscan() == 0)
+ offtype = NONE
+ else {
+ offtype = strdic (Memc[fname], Memc[str], SZ_FNAME, OFFTYPES)
+ if (offtype > 0 && !streq (Memc[fname], Memc[str]))
+ offtype = 0
+ }
+ if (offtype == 0)
+ offtype = FILE
+
+ switch (offtype) {
+ case NONE:
+ call aclri (offsets, outdim*nimages)
+ reloff = true
+ case WORLD, WCS:
+ do j = 1, outdim
+ offsets[1,j] = 0
+ if (proj) {
+ ct = mw_sctran (mw, "world", "logical", 0)
+ do i = 2, nimages {
+ Memd[wref+outdim] = i
+ call mw_ctrand (ct, Memd[wref], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ }
+ call mw_ctfree (ct)
+ call mw_close (mw)
+ } else {
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[lref], indim)
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ call mw_ctfree (ct)
+ }
+ }
+ reloff = true
+ case PHYSICAL:
+ call salloc (section, SZ_FNAME, TY_CHAR)
+
+ call mw_gltermd (mw, Memd[ltm], Memd[coord], indim)
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ call mw_gltermd (mw, Memd[cd], Memd[coord], indim)
+ call strcpy ("[", Memc[section], SZ_FNAME)
+ flip = false
+ do j = 0, indim*indim-1, indim+1 {
+ if (Memd[ltm+j] * Memd[cd+j] >= 0.)
+ call strcat ("*,", Memc[section], SZ_FNAME)
+ else {
+ call strcat ("-*,", Memc[section], SZ_FNAME)
+ flip = true
+ }
+ }
+ Memc[section+strlen(Memc[section])-1] = ']'
+ if (flip) {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call strcat (Memc[section], Memc[fname], SZ_FNAME)
+ call xt_imunmap (in[i], i)
+ in[i] = xt_immap (Memc[fname], READ_ONLY, TY_CHAR, i)
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ call mw_gltermd (mw, Memd[cd], Memd[coord], indim)
+ do j = 0, indim*indim-1
+ if (!fp_equald (Memd[ltm+j], Memd[cd+j]))
+ call error (1,
+ "Cannot match physical coordinates")
+ }
+ }
+
+ call mw_close (mw)
+ mw = mw_openim (in[1])
+ ct = mw_sctran (mw, "logical", "physical", 0)
+ call mw_ctrand (ct, Memd[lref], Memd[ltv], indim)
+ call mw_ctfree (ct)
+ do j = 1, outdim
+ offsets[1,j] = 0
+ if (proj) {
+ ct = mw_sctran (mw, "physical", "logical", 0)
+ do i = 2, nimages {
+ Memd[ltv+outdim] = i
+ call mw_ctrand (ct, Memd[ltv], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ }
+ call mw_ctfree (ct)
+ call mw_close (mw)
+ } else {
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ ct = mw_sctran (mw, "physical", "logical", 0)
+ call mw_ctrand (ct, Memd[ltv], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ call mw_ctfree (ct)
+ }
+ }
+ reloff = true
+ case GRID:
+ amin = 1
+ do j = 1, outdim {
+ call gargi (a)
+ call gargi (b)
+ if (nscan() < 1+2*j) {
+ a = 1
+ b = 0
+ }
+ do i = 1, nimages
+ offsets[i,j] = mod ((i-1)/amin, a) * b
+ amin = amin * a
+ }
+ reloff = true
+ case FILE:
+ reloff = true
+ fd = open (Memc[fname], READ_ONLY, TEXT_FILE)
+ do i = 1, nimages {
+newscan_ if (fscan (fd) == EOF)
+ call error (1, "IMCOMBINE: Offset list too short")
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (Memc[fname] == '#') {
+ call gargwrd (Memc[fname], SZ_FNAME)
+ call strlwr (Memc[fname])
+ if (streq (Memc[fname], "absolute"))
+ reloff = false
+ else if (streq (Memc[fname], "relative"))
+ reloff = true
+ goto newscan_
+ }
+ call reset_scan ()
+ do j = 1, outdim {
+ call gargr (val)
+ offsets[i,j] = nint (val)
+ }
+ if (nscan() < outdim)
+ call error (1, "IMCOMBINE: Error in offset list")
+ }
+ call close (fd)
+ }
+
+ # Set the output image size and the aligned flag
+ aligned = true
+ do j = 1, outdim {
+ a = offsets[1,j]
+ b = IM_LEN(in[1],j) + a
+ amin = a
+ bmax = b
+ do i = 2, nimages {
+ a = offsets[i,j]
+ b = IM_LEN(in[i],j) + a
+ if (a != amin || b != bmax || !reloff)
+ aligned = false
+ amin = min (a, amin)
+ bmax = max (b, bmax)
+ }
+ IM_LEN(out[1],j) = bmax
+ if (reloff || amin < 0) {
+ do i = 1, nimages
+ offsets[i,j] = offsets[i,j] - amin
+ IM_LEN(out[1],j) = IM_LEN(out[1],j) - amin
+ }
+ }
+
+ # Get the output limits.
+ call clgstr ("outlimits", Memc[fname], SZ_FNAME)
+ call sscan (Memc[fname])
+ do j = 1, outdim {
+ call gargi (a)
+ call gargi (b)
+ if (nscan() < 2*j)
+ break
+ if (!IS_INDEFI(a)) {
+ do i = 1, nimages {
+ offsets[i,j] = offsets[i,j] - a + 1
+ if (offsets[i,j] != 0)
+ aligned = false
+ }
+ IM_LEN(out[1],j) = IM_LEN(out[1],j) - a + 1
+ }
+ if (!IS_INDEFI(a) && !IS_INDEFI(b))
+ IM_LEN(out[1],j) = min (IM_LEN(out[1],j), b - a + 1)
+ }
+
+ # Update the WCS.
+ if (proj || !aligned || !reloff) {
+ call mw_close (mw)
+ mw = mw_openim (out[1])
+ mwdim = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gaxmap (mw, Memi[axno], Memi[axval], mwdim)
+ if (!aligned || !reloff) {
+ call mw_gltermd (mw, Memd[cd], Memd[lref], mwdim)
+ do i = 1, mwdim {
+ j = Memi[axno+i-1]
+ if (j > 0 && j <= indim)
+ Memd[lref+i-1] = Memd[lref+i-1] + offsets[1,j]
+ }
+ if (proj)
+ Memd[lref+mwdim-1] = 0.
+ call mw_sltermd (mw, Memd[cd], Memd[lref], mwdim)
+ }
+ if (proj) {
+ # Apply dimensional reduction.
+ do i = 1, mwdim {
+ j = Memi[axno+i-1]
+ if (j <= outdim)
+ next
+ else if (j > outdim+1)
+ Memi[axno+i-1] = j - 1
+ else {
+ Memi[axno+i-1] = 0
+ Memi[axval+i-1] = 0
+ }
+ }
+ call mw_saxmap (mw, Memi[axno], Memi[axval], mwdim)
+ }
+
+ # Reset physical coordinates.
+ if (offtype == WCS || offtype == WORLD) {
+ call mw_gltermd (mw, Memd[ltm], Memd[ltv], mwdim)
+ call mw_gwtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ call mwvmuld (Memd[ltm], Memd[lref], Memd[lref], mwdim)
+ call aaddd (Memd[lref], Memd[ltv], Memd[lref], mwdim)
+ call mwinvertd (Memd[ltm], Memd[ltm], mwdim)
+ call mwmmuld (Memd[cd], Memd[ltm], Memd[cd], mwdim)
+ call mw_swtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ call aclrd (Memd[ltv], mwdim)
+ call aclrd (Memd[ltm], mwdim*mwdim)
+ do i = 1, mwdim
+ Memd[ltm+(i-1)*(mwdim+1)] = 1.
+ call mw_sltermd (mw, Memd[ltm], Memd[ltv], mwdim)
+ }
+ call mw_saveim (mw, out)
+ }
+ call mw_close (mw)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/src/icsigma.gx b/noao/onedspec/odcombine/src/icsigma.gx
new file mode 100644
index 00000000..1304d940
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icsigma.gx
@@ -0,0 +1,122 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigma$t (d, m, n, wts, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+$else
+PIXEL average[npts] # Average
+PIXEL sigma[npts] # Sigma line (returned)
+$endif
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+$if (datatype == sil)
+real a, sum
+$else
+PIXEL a, sum
+$endif
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mem$t[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mem$t[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icsort.gx b/noao/onedspec/odcombine/src/icsort.gx
new file mode 100644
index 00000000..e124da15
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icsort.gx
@@ -0,0 +1,386 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+define LOGPTR 32 # log2(maxpts) (4e9)
+
+$for (sird)
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sort$t (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+PIXEL b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+PIXEL pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Mem$t[a[i]+l]
+
+ # Special cases
+ $if (datatype == x)
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (abs (temp) < abs (pivot)) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (abs (temp) < abs (pivot)) { # bac|bca|cba
+ if (abs (temp) < abs (temp3)) { # bac|bca
+ b[1] = temp
+ if (abs (pivot) < abs (temp3)) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (abs (temp3) < abs (temp)) { # acb|cab
+ b[3] = temp
+ if (abs (pivot) < abs (temp3)) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $else
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $endif
+
+ # General case
+ do i = 1, npix
+ b[i] = Mem$t[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ $if (datatype == x)
+ for (i=i+1; abs(b[i]) < abs(pivot); i=i+1)
+ $else
+ for (i=i+1; b[i] < pivot; i=i+1)
+ $endif
+ ;
+ for (j=j-1; j > i; j=j-1)
+ $if (datatype == x)
+ if (abs(b[j]) <= abs(pivot))
+ $else
+ if (b[j] <= pivot)
+ $endif
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Mem$t[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sort$t (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+PIXEL b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+PIXEL pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Mem$t[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ $if (datatype == x)
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (abs (temp) < abs (pivot)) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (abs (temp) < abs (pivot)) { # bac|bca|cba
+ if (abs (temp) < abs (temp3)) { # bac|bca
+ b[1] = temp
+ if (abs (pivot) < abs (temp3)) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (abs (temp3) < abs (temp)) { # acb|cab
+ b[3] = temp
+ if (abs (pivot) < abs (temp3)) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $else
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $endif
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ $if (datatype == x)
+ for (i=i+1; abs(b[i]) < abs(pivot); i=i+1)
+ $else
+ for (i=i+1; b[i] < pivot; i=i+1)
+ $endif
+ ;
+ for (j=j-1; j > i; j=j-1)
+ $if (datatype == x)
+ if (abs(b[j]) <= abs(pivot))
+ $else
+ if (b[j] <= pivot)
+ $endif
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Mem$t[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/icstat.gx b/noao/onedspec/odcombine/src/icstat.gx
new file mode 100644
index 00000000..c594182b
--- /dev/null
+++ b/noao/onedspec/odcombine/src/icstat.gx
@@ -0,0 +1,238 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+define NMAX 100000 # Maximum number of pixels to sample
+
+$for (sird)
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stat$t (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnl$t()
+
+$if (datatype == csir)
+real asum$t()
+$else $if (datatype == ld)
+double asum$t()
+$else
+PIXEL asum$t()
+$endif $endif
+PIXEL ic_mode$t()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_PIXEL)
+ dp = data
+ while (imgnl$t (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Mem$t[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mem$t[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Mem$t[dp] = Mem$t[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Mem$t[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mem$t[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Mem$t[dp] = Mem$t[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrt$t (Mem$t[data], Mem$t[data], n)
+ mode = ic_mode$t (Mem$t[data], n)
+ median = Mem$t[data+n/2-1]
+ }
+ if (domean)
+ mean = asum$t (Mem$t[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+PIXEL procedure ic_mode$t (a, n)
+
+PIXEL a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+PIXEL mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ $if (datatype == sil)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+ $endif
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/mkpkg b/noao/onedspec/odcombine/src/mkpkg
new file mode 100644
index 00000000..2ed3d8cb
--- /dev/null
+++ b/noao/onedspec/odcombine/src/mkpkg
@@ -0,0 +1,62 @@
+ Make the IMCOMBINE Task.
+
+$checkout libpkg.a ../../../../
+$update libpkg.a
+$checkin libpkg.a ../../../../
+$exit
+
+generic:
+ $set GEN = "$$generic -k"
+
+ $ifolder (generic/icaclip.x, icaclip.gx)
+ $(GEN) icaclip.gx -o generic/icaclip.x $endif
+ $ifolder (generic/icaverage.x, icaverage.gx)
+ $(GEN) icaverage.gx -o generic/icaverage.x $endif
+ $ifolder (generic/iccclip.x, iccclip.gx)
+ $(GEN) iccclip.gx -o generic/iccclip.x $endif
+ $ifolder (generic/icgdata.x, icgdata.gx)
+ $(GEN) icgdata.gx -o generic/icgdata.x $endif
+ $ifolder (generic/icgrow.x, icgrow.gx)
+ $(GEN) icgrow.gx -o generic/icgrow.x $endif
+ $ifolder (generic/icmedian.x, icmedian.gx)
+ $(GEN) icmedian.gx -o generic/icmedian.x $endif
+ $ifolder (generic/icmm.x, icmm.gx)
+ $(GEN) icmm.gx -o generic/icmm.x $endif
+ $ifolder (generic/icomb.x, icomb.gx)
+ $(GEN) icomb.gx -o generic/icomb.x $endif
+ $ifolder (generic/icpclip.x, icpclip.gx)
+ $(GEN) icpclip.gx -o generic/icpclip.x $endif
+ $ifolder (generic/icsclip.x, icsclip.gx)
+ $(GEN) icsclip.gx -o generic/icsclip.x $endif
+ $ifolder (generic/icsigma.x, icsigma.gx)
+ $(GEN) icsigma.gx -o generic/icsigma.x $endif
+ $ifolder (generic/icsort.x, icsort.gx)
+ $(GEN) icsort.gx -o generic/icsort.x $endif
+ $ifolder (generic/icstat.x, icstat.gx)
+ $(GEN) icstat.gx -o generic/icstat.x $endif
+
+ $ifolder (generic/xtimmap.x, xtimmap.gx)
+ $(GEN) xtimmap.gx -o generic/xtimmap.x $endif
+ ;
+
+libpkg.a:
+ $ifeq (USE_GENERIC, yes) $call generic $endif
+
+ @generic
+
+ icemask.x <imhdr.h> <mach.h>
+ icgscale.x icombine.com icombine.h
+ ichdr.x <imset.h>
+ icimstack.x <error.h> <imhdr.h>
+ iclog.x icmask.h icombine.com icombine.h <imhdr.h> <imset.h>\
+ <mach.h>
+ icmask.x icmask.h icombine.com icombine.h <imhdr.h> <pmset.h>
+ icombine.x icombine.com icombine.h <error.h> <imhdr.h> <imset.h>
+ icpmmap.x <pmset.h>
+ icrmasks.x <imhdr.h>
+ icscale.x icombine.com icombine.h <imhdr.h> <imset.h>
+ icsection.x <ctype.h>
+ icsetout.x icombine.com <imhdr.h> <imset.h> <mwset.h>
+ tymax.x <mach.h>
+ xtprocid.x
+ ;
diff --git a/noao/onedspec/odcombine/src/tymax.x b/noao/onedspec/odcombine/src/tymax.x
new file mode 100644
index 00000000..a7f4f469
--- /dev/null
+++ b/noao/onedspec/odcombine/src/tymax.x
@@ -0,0 +1,27 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <mach.h>
+
+
+# TY_MAX -- Return the datatype of highest precedence.
+
+int procedure ty_max (type1, type2)
+
+int type1, type2 # Datatypes
+
+int i, j, type, order[8]
+data order/TY_SHORT,TY_USHORT,TY_INT,TY_LONG,TY_REAL,TY_DOUBLE,TY_COMPLEX,TY_REAL/
+
+begin
+ for (i=1; (i<=7) && (type1!=order[i]); i=i+1)
+ ;
+ for (j=1; (j<=7) && (type2!=order[j]); j=j+1)
+ ;
+ type = order[max(i,j)]
+
+ # Special case of mixing short and unsigned short.
+ if (type == TY_USHORT && type1 != type2)
+ type = TY_INT
+
+ return (type)
+end
diff --git a/noao/onedspec/odcombine/src/xtimmap.com b/noao/onedspec/odcombine/src/xtimmap.com
new file mode 100644
index 00000000..61bf314a
--- /dev/null
+++ b/noao/onedspec/odcombine/src/xtimmap.com
@@ -0,0 +1,8 @@
+int option
+int nopen
+int nopenpix
+int nalloc
+int last_flag
+int min_open
+pointer ims
+common /xtimmapcom/ option, ims, nopen, nopenpix, nalloc, last_flag, min_open
diff --git a/noao/onedspec/odcombine/src/xtimmap.gx b/noao/onedspec/odcombine/src/xtimmap.gx
new file mode 100644
index 00000000..c0ae26a6
--- /dev/null
+++ b/noao/onedspec/odcombine/src/xtimmap.gx
@@ -0,0 +1,552 @@
+include <syserr.h>
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <config.h>
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+# These routines maintain an arbitrary number of indexed "open" images which
+# must be READ_ONLY. The calling program may use the returned pointer for
+# header accesses but must call xt_opix before I/O. Subsequent calls to
+# xt_opix may invalidate the pointer. The xt_imunmap call will free memory.
+
+define MAX_OPENIM (LAST_FD-16) # Maximum images kept open
+define MAX_OPENPIX 45 # Maximum pixel files kept open
+
+define XT_SZIMNAME 299 # Size of IMNAME string
+define XT_LEN 179 # Structure length
+define XT_IMNAME Memc[P2C($1)] # Image name
+define XT_ARG Memi[$1+150] # IMMAP header argument
+define XT_IM Memi[$1+151] # IMIO pointer
+define XT_HDR Memi[$1+152] # Copy of IMIO pointer
+define XT_CLOSEFD Memi[$1+153] # Close FD?
+define XT_FLAG Memi[$1+154] # Flag
+define XT_BUFSIZE Memi[$1+155] # Buffer size
+define XT_BUF Memi[$1+156] # Data buffer
+define XT_BTYPE Memi[$1+157] # Data buffer type
+define XT_VS Memi[$1+157+$2] # Start vector (10)
+define XT_VE Memi[$1+167+$2] # End vector (10)
+
+# Options
+define XT_MAPUNMAP 1 # Map and unmap images.
+
+# XT_IMMAP -- Map an image and save it as an indexed open image.
+# The returned pointer may be used for header access but not I/O.
+# The indexed image is closed by xt_imunmap.
+
+pointer procedure xt_immap (imname, acmode, hdr_arg, index)
+
+char imname[ARB] #I Image name
+int acmode #I Access mode
+int hdr_arg #I Header argument
+int index #I Save index
+pointer im #O Image pointer (returned)
+
+int i, envgeti()
+pointer xt, xt_opix()
+errchk xt_opix
+
+int first_time
+data first_time /YES/
+
+include "../xtimmap.com"
+
+begin
+ if (acmode != READ_ONLY)
+ call error (1, "XT_IMMAP: Only READ_ONLY allowed")
+
+ # Initialize once per process.
+ if (first_time == YES) {
+ iferr (option = envgeti ("imcombine_option"))
+ option = 1
+ min_open = 1
+ nopen = 0
+ nopenpix = 0
+ nalloc = MAX_OPENIM
+ call calloc (ims, nalloc, TY_POINTER)
+ first_time = NO
+ }
+
+ # Free image if needed.
+ call xt_imunmap (NULL, index)
+
+ # Allocate structure.
+ if (index > nalloc) {
+ i = nalloc
+ nalloc = index + MAX_OPENIM
+ call realloc (ims, nalloc, TY_STRUCT)
+ call amovki (NULL, Memi[ims+i], nalloc-i)
+ }
+ call calloc (xt, XT_LEN, TY_STRUCT)
+ Memi[ims+index-1] = xt
+
+ # Initialize.
+ call strcpy (imname, XT_IMNAME(xt), XT_SZIMNAME)
+ XT_ARG(xt) = hdr_arg
+ XT_IM(xt) = NULL
+ XT_HDR(xt) = NULL
+
+ # Open image.
+ last_flag = 0
+ im = xt_opix (NULL, index, 0)
+
+ # Make copy of IMIO pointer for header keyword access.
+ call malloc (XT_HDR(xt), LEN_IMDES+IM_HDRLEN(im)+1, TY_STRUCT)
+ call amovi (Memi[im], Memi[XT_HDR(xt)], LEN_IMDES)
+ call amovi (IM_MAGIC(im), IM_MAGIC(XT_HDR(xt)), IM_HDRLEN(im)+1)
+
+ return (XT_HDR(xt))
+end
+
+
+# XT_OPIX -- Open the image for I/O.
+# If the image has not been mapped return the default pointer.
+
+pointer procedure xt_opix (imdef, index, flag)
+
+int index #I index
+pointer imdef #I Default pointer
+int flag #I Flag
+
+int i, open(), imstati()
+pointer im, xt, xt1, immap()
+errchk open, immap, imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imdef)
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Return pointer for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (im)
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || flag == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ if (!IS_INDEFI(XT_BUFSIZE(xt)))
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ else
+ XT_BUFSIZE(xt) = imstati (im, IM_BUFSIZE)
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (im)
+end
+
+
+# XT_CPIX -- Close image.
+
+procedure xt_cpix (index)
+
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL)
+ return
+
+ if (XT_IM(xt) != NULL) {
+ call imunmap (XT_IM(xt))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ }
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+end
+
+
+# XT_IMSETI -- Set IMIO value.
+
+procedure xt_imseti (index, param, value)
+
+int index #I index
+int param #I IMSET parameter
+int value #I Value
+
+pointer xt
+bool streq()
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL) {
+ if (streq (param, "option"))
+ option = value
+ } else {
+ if (streq (param, "bufsize")) {
+ XT_BUFSIZE(xt) = value
+ if (XT_IM(xt) != NULL) {
+ call imseti (XT_IM(xt), IM_BUFFRAC, 0)
+ call imseti (XT_IM(xt), IM_BUFSIZE, value)
+ }
+ }
+ }
+end
+
+
+# XT_IMUNMAP -- Unmap indexed open image.
+# The header pointer is set to NULL to indicate the image has been closed.
+
+procedure xt_imunmap (im, index)
+
+int im #U IMIO header pointer
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Check for an indexed image. If it is not unmap the pointer
+ # as a regular IMIO pointer.
+
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+ if (xt == NULL) {
+ if (im != NULL)
+ call imunmap (im)
+ return
+ }
+
+ # Close indexed image.
+ if (XT_IM(xt) != NULL) {
+ iferr (call imunmap (XT_IM(xt))) {
+ XT_IM(xt) = NULL
+ call erract (EA_WARN)
+ }
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ if (index == min_open)
+ min_open = 1
+ }
+
+ # Free any buffered memory.
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+
+ # Free header pointer. Note that if the supplied pointer is not
+ # header pointer then it is not set to NULL.
+ if (XT_HDR(xt) == im)
+ im = NULL
+ call mfree (XT_HDR(xt), TY_STRUCT)
+
+ # Free save structure.
+ call mfree (Memi[ims+index-1], TY_STRUCT)
+ Memi[ims+index-1] = NULL
+end
+
+
+# XT_REINDEX -- Reindex open images.
+# This is used when some images are closed by xt_imunmap. It is up to
+# the calling program to reindex the header pointers and to subsequently
+# use the new index values.
+
+procedure xt_reindex ()
+
+int old, new
+
+include "../xtimmap.com"
+
+begin
+ new = 0
+ do old = 0, nalloc-1 {
+ if (Memi[ims+old] == NULL)
+ next
+ Memi[ims+new] = Memi[ims+old]
+ new = new + 1
+ }
+ do old = new, nalloc-1
+ Memi[ims+old] = NULL
+end
+
+
+$for(sird)
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnl$t (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnl$t(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggs$t()
+errchk open, immap, imgnl$t, imggs$t, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnl$t (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnl$t (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_PIXEL)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_PIXEL) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_PIXEL
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggs$t (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amov$t (Mem$t[ptr], Mem$t[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnl$t (im, buf, v))
+end
+$endfor
diff --git a/noao/onedspec/odcombine/src/xtprocid.x b/noao/onedspec/odcombine/src/xtprocid.x
new file mode 100644
index 00000000..0a82d81b
--- /dev/null
+++ b/noao/onedspec/odcombine/src/xtprocid.x
@@ -0,0 +1,38 @@
+# XT_PROCID -- Set or ppdate PROCID keyword.
+
+procedure xt_procid (im)
+
+pointer im #I Image header
+
+int i, j, ver, patmake(), gpatmatch(), strlen(), ctoi()
+pointer sp, pat, str
+
+begin
+ call smark (sp)
+ call salloc (pat, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ # Get current ID.
+ iferr (call imgstr (im, "PROCID", Memc[str], SZ_LINE)) {
+ iferr (call imgstr (im, "OBSID", Memc[str], SZ_LINE)) {
+ call sfree (sp)
+ return
+ }
+ }
+
+ # Set new PROCID.
+ ver = 0
+ i = patmake ("V[0-9]*$", Memc[pat], SZ_LINE)
+ if (gpatmatch (Memc[str], Memc[pat], i, j) == 0)
+ ;
+ if (j > 0) {
+ j = i+1
+ if (ctoi (Memc[str], j, ver) == 0)
+ ver = 0
+ i = i - 1
+ } else
+ i = strlen (Memc[str])
+ call sprintf (Memc[str+i], SZ_LINE, "V%d")
+ call pargi (ver+1)
+ call imastr (im, "PROCID", Memc[str])
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icaclip.x b/noao/onedspec/odcombine/srcwt/generic/icaclip.x
new file mode 100644
index 00000000..97c12346
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icaclip.x
@@ -0,0 +1,2206 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number of images for this algorithm
+
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclips (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclips (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Mems[d[1]+k]
+ else {
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Mems[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Mems[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Mems[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mems[d[n3-1]+k]
+ high = Mems[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mems[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipi (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipi (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memi[d[1]+k]
+ else {
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memi[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memi[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memi[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memi[d[n3-1]+k]
+ high = Memi[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memi[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+ else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipd (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, s1, r, one
+data one /1.0D0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipd (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+double med, low, high, r, s, s1, one
+data one /1.0D0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memd[d[1]+k]
+ else {
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memd[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memd[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memd[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memd[d[n3-1]+k]
+ high = Memd[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memd[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icaverage.x b/noao/onedspec/odcombine/srcwt/generic/icaverage.x
new file mode 100644
index 00000000..4b464e91
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icaverage.x
@@ -0,0 +1,522 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averages (d, m, n, wts, w, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL && w[1] == NULL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mems[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mems[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mems[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mems[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = Mems[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + Mems[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Mems[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mems[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averagei (d, m, n, wts, w, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL && w[1] == NULL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memi[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memi[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memi[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memi[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = Memi[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + Memi[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memi[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memi[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averager (d, m, n, wts, w, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL && w[1] == NULL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = Memr[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + Memr[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_averaged (d, m, n, wts, w, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+double average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+double sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL && w[1] == NULL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memd[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memd[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memd[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memd[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = Memd[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + Memd[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memd[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memd[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/generic/iccclip.x b/noao/onedspec/odcombine/srcwt/generic/iccclip.x
new file mode 100644
index 00000000..bf655477
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/iccclip.x
@@ -0,0 +1,1790 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 2 # Mininum number of images for algorithm
+
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclips (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Mems[d[1]+k]
+ sum = sum + Mems[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclips (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Mems[d[n3-1]+k]
+ med = (med + Mems[d[n3]+k]) / 2.
+ } else
+ med = Mems[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipi (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memi[d[1]+k]
+ sum = sum + Memi[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipi (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memi[d[n3-1]+k]
+ med = (med + Memi[d[n3]+k]) / 2.
+ } else
+ med = Memi[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipr (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memr[d[1]+k]
+ sum = sum + Memr[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipr (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memr[d[n3-1]+k]
+ med = (med + Memr[d[n3]+k]) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipd (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, r, zero
+data zero /0.0D0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memd[d[1]+k]
+ sum = sum + Memd[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipd (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+double med, zero
+data zero /0.0D0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memd[d[n3-1]+k]
+ med = (med + Memd[d[n3]+k]) / 2.
+ } else
+ med = Memd[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icgdata.x b/noao/onedspec/odcombine/srcwt/generic/icgdata.x
new file mode 100644
index 00000000..1350ad21
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icgdata.x
@@ -0,0 +1,1558 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+include "../icombine.h"
+
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatas (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer wtp[nimages] # Weight images
+pointer wbuf[nimages] # Weight buffers
+pointer w[nimages] # Weight pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnls()
+int xt_imgnlr()
+long v3[IM_MAXDIM]
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnls
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2)) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ j = xt_imgnlr (wtp[i], nimages+i, w[i], v3, v1[2])
+ }
+ j = xt_imgnls (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ l = xt_imgnlr (wtp[i], nimages+i, buf, v3, v1[2])
+ call amovr (Memr[buf+k-1], Memr[wbuf[i]+j], npix)
+ w[i] = wbuf[i]
+ }
+ l = xt_imgnls (in[i], i, buf, v2, v1[2])
+ call amovs (Mems[buf+k-1], Mems[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Mems[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Mems[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Mems[dp] = Mems[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Compute weights from sigmas.
+ if (wtype == S_SIGMAP) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Mems[d[k]+j-1] = Mems[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Mems[d[k]+j-1] = Mems[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_SHORT)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sorts (d, Mems[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sorts (d, Mems[dp], n, npts)
+ call mfree (dp, TY_SHORT)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatai (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer wtp[nimages] # Weight images
+pointer wbuf[nimages] # Weight buffers
+pointer w[nimages] # Weight pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnli()
+int xt_imgnlr()
+long v3[IM_MAXDIM]
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnli
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2)) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ j = xt_imgnlr (wtp[i], nimages+i, w[i], v3, v1[2])
+ }
+ j = xt_imgnli (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ l = xt_imgnlr (wtp[i], nimages+i, buf, v3, v1[2])
+ call amovr (Memr[buf+k-1], Memr[wbuf[i]+j], npix)
+ w[i] = wbuf[i]
+ }
+ l = xt_imgnli (in[i], i, buf, v2, v1[2])
+ call amovi (Memi[buf+k-1], Memi[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memi[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memi[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memi[dp] = Memi[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Compute weights from sigmas.
+ if (wtype == S_SIGMAP) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memi[d[k]+j-1] = Memi[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memi[d[k]+j-1] = Memi[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_INT)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sorti (d, Memi[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sorti (d, Memi[dp], n, npts)
+ call mfree (dp, TY_INT)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatar (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer wtp[nimages] # Weight images
+pointer wbuf[nimages] # Weight buffers
+pointer w[nimages] # Weight pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnlr()
+long v3[IM_MAXDIM]
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnlr
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2)) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ j = xt_imgnlr (wtp[i], nimages+i, w[i], v3, v1[2])
+ }
+ j = xt_imgnlr (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ l = xt_imgnlr (wtp[i], nimages+i, buf, v3, v1[2])
+ call amovr (Memr[buf+k-1], Memr[wbuf[i]+j], npix)
+ w[i] = wbuf[i]
+ }
+ l = xt_imgnlr (in[i], i, buf, v2, v1[2])
+ call amovr (Memr[buf+k-1], Memr[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Compute weights from sigmas.
+ if (wtype == S_SIGMAP) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memr[d[k]+j-1] = Memr[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memr[d[k]+j-1] = Memr[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_REAL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sortr (d, Memr[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sortr (d, Memr[dp], n, npts)
+ call mfree (dp, TY_REAL)
+ }
+end
+
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdatad (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer wtp[nimages] # Weight images
+pointer wbuf[nimages] # Weight buffers
+pointer w[nimages] # Weight pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnld()
+int xt_imgnlr()
+long v3[IM_MAXDIM]
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnld
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2)) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ j = xt_imgnlr (wtp[i], nimages+i, w[i], v3, v1[2])
+ }
+ j = xt_imgnld (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ l = xt_imgnlr (wtp[i], nimages+i, buf, v3, v1[2])
+ call amovr (Memr[buf+k-1], Memr[wbuf[i]+j], npix)
+ w[i] = wbuf[i]
+ }
+ l = xt_imgnld (in[i], i, buf, v2, v1[2])
+ call amovd (Memd[buf+k-1], Memd[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Memd[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memd[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memd[dp] = Memd[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Compute weights from sigmas.
+ if (wtype == S_SIGMAP) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memd[d[k]+j-1] = Memd[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memd[d[k]+j-1] = Memd[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_DOUBLE)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sortd (d, Memd[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sortd (d, Memd[dp], n, npts)
+ call mfree (dp, TY_DOUBLE)
+ }
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/generic/icgrow.x b/noao/onedspec/odcombine/srcwt/generic/icgrow.x
new file mode 100644
index 00000000..1ccb7885
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icgrow.x
@@ -0,0 +1,263 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <pmset.h>
+include "../icombine.h"
+
+# IC_GROW -- Mark neigbors of rejected pixels.
+# The rejected pixels (original plus grown) are saved in pixel masks.
+
+procedure ic_grow (out, v, m, n, buf, nimages, npts, pms)
+
+pointer out # Output image pointer
+long v[ARB] # Output vector
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[npts,nimages] # Working buffer
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k, l, line, nl, rop, igrow, nset, ncompress, or()
+real grow2, i2
+pointer mp, pm, pm_newmask()
+errchk pm_newmask()
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE || grow == 0.)
+ return
+
+ line = v[2]
+ nl = IM_LEN(out,2)
+ rop = or (PIX_SRC, PIX_DST)
+
+ igrow = grow
+ grow2 = grow**2
+ do l = 0, igrow {
+ i2 = grow2 - l * l
+ call aclri (buf, npts*nimages)
+ nset = 0
+ do j = 1, npts {
+ do k = n[j]+1, nimages {
+ mp = Memi[m[k]+j-1]
+ if (mp == 0)
+ next
+ do i = 0, igrow {
+ if (i**2 > i2)
+ next
+ if (j > i)
+ buf[j-i,mp] = 1
+ if (j+i <= npts)
+ buf[j+i,mp] = 1
+ nset = nset + 1
+ }
+ }
+ }
+ if (nset == 0)
+ return
+
+ if (pms == NULL) {
+ call malloc (pms, nimages, TY_POINTER)
+ do i = 1, nimages
+ Memi[pms+i-1] = pm_newmask (out, 1)
+ ncompress = 0
+ }
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ v[2] = line - l
+ if (v[2] > 0)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ if (l > 0) {
+ v[2] = line + l
+ if (v[2] <= nl)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ }
+ }
+ }
+ v[2] = line
+
+ if (ncompress > 10) {
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ call pm_compress (pm)
+ }
+ ncompress = 0
+ } else
+ ncompress = ncompress + 1
+end
+
+
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_grows (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Mems[d[j]+i-1] = Mems[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growi (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memi[d[j]+i-1] = Memi[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growr (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memr[d[j]+i-1] = Memr[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_growd (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Memd[d[j]+i-1] = Memd[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icmedian.x b/noao/onedspec/odcombine/srcwt/generic/icmedian.x
new file mode 100644
index 00000000..1a2ed72d
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icmedian.x
@@ -0,0 +1,692 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_medians (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+short temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Mems[d[j1]+k]
+ val2 = Mems[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mems[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Mems[d[j1]+k]
+ val2 = Mems[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mems[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mems[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Mems[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Mems[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mems[d[lo1]+k]
+ Mems[d[lo1]+k] = Mems[d[up1]+k]
+ Mems[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Mems[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mems[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Mems[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Mems[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mems[d[lo1]+k]
+ Mems[d[lo1]+k] = Mems[d[up1]+k]
+ Mems[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Mems[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Mems[d[1]+k]
+ val2 = Mems[d[2]+k]
+ val3 = Mems[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Mems[d[1]+k]
+ val2 = Mems[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Mems[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_mediani (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+int temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memi[d[j1]+k]
+ val2 = Memi[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memi[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memi[d[j1]+k]
+ val2 = Memi[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memi[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memi[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memi[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memi[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memi[d[lo1]+k]
+ Memi[d[lo1]+k] = Memi[d[up1]+k]
+ Memi[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memi[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memi[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memi[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memi[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memi[d[lo1]+k]
+ Memi[d[lo1]+k] = Memi[d[up1]+k]
+ Memi[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memi[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memi[d[1]+k]
+ val2 = Memi[d[2]+k]
+ val3 = Memi[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memi[d[1]+k]
+ val2 = Memi[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memi[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_medianr (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+real median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+real val1, val2, val3
+real temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memr[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memr[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memr[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memr[d[lo1]+k]
+ Memr[d[lo1]+k] = Memr[d[up1]+k]
+ Memr[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memr[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memr[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memr[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memr[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memr[d[lo1]+k]
+ Memr[d[lo1]+k] = Memr[d[up1]+k]
+ Memr[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memr[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ val3 = Memr[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_mediand (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+double median[npts] # Median
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+double val1, val2, val3
+double temp, wtemp
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memd[d[j1]+k]
+ val2 = Memd[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memd[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memd[d[j1]+k]
+ val2 = Memd[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memd[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memd[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memd[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memd[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memd[d[lo1]+k]
+ Memd[d[lo1]+k] = Memd[d[up1]+k]
+ Memd[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Memd[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Memd[d[j]+k]; lo1 = lo; up1 = up
+
+ repeat {
+ while (Memd[d[lo1]+k] < temp)
+ lo1 = lo1 + 1
+ while (temp < Memd[d[up1]+k])
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Memd[d[lo1]+k]
+ Memd[d[lo1]+k] = Memd[d[up1]+k]
+ Memd[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Memd[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ val1 = Memd[d[1]+k]
+ val2 = Memd[d[2]+k]
+ val3 = Memd[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Memd[d[1]+k]
+ val2 = Memd[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Memd[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icmm.x b/noao/onedspec/odcombine/srcwt/generic/icmm.x
new file mode 100644
index 00000000..5b2b13bf
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icmm.x
@@ -0,0 +1,644 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mms (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+short d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Mems[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Mems[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Mems[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Mems[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Mems[kmax] = d2
+ else
+ Mems[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Mems[kmin] = d1
+ else
+ Mems[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Mems[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Mems[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Mems[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Mems[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Mems[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Mems[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmi (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+int d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memi[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memi[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memi[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memi[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memi[kmax] = d2
+ else
+ Memi[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memi[kmin] = d1
+ else
+ Memi[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memi[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memi[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memi[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memi[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memi[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memi[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmr (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+real d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memr[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memr[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memr[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memr[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memr[kmax] = d2
+ else
+ Memr[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memr[kmin] = d1
+ else
+ Memr[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memr[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memr[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memr[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memr[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmd (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+double d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memd[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Memd[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memd[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Memd[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memd[kmax] = d2
+ else
+ Memd[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memd[kmin] = d1
+ else
+ Memd[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memd[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Memd[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memd[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memd[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memd[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Memd[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icomb.x b/noao/onedspec/odcombine/srcwt/generic/icomb.x
new file mode 100644
index 00000000..df4290b8
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icomb.x
@@ -0,0 +1,2054 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <pmset.h>
+include <error.h>
+include <syserr.h>
+include <mach.h>
+include "../icombine.h"
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+
+# ICOMBINE -- Combine images
+#
+# The memory and open file descriptor limits are checked and an attempt
+# to recover is made either by setting the image pixel files to be
+# closed after I/O or by notifying the calling program that memory
+# ran out and the IMIO buffer size should be reduced. After the checks
+# a procedure for the selected combine option is called.
+# Because there may be several failure modes when reaching the file
+# limits we first assume an error is due to the file limit, except for
+# out of memory, and close some pixel files. If the error then repeats
+# on accessing the pixels the error is passed back.
+
+
+procedure icombines (in, out, scales, zeros, wts, wtp, offsets, nimages,
+ bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnls()
+pointer sp, d, w, id, n, m, lflag, v, dbuf, wbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnls, impl1i, ic_combines
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (wbuf, nimages, TY_POINTER)
+ call salloc (w, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (NULL, Memi[dbuf], nimages)
+ call amovki (NULL, Memi[d], nimages)
+ call amovki (NULL, Memi[wbuf], nimages)
+ call amovki (NULL, Memi[w], nimages)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_SHORT)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_SHORT)
+ }
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnls (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combines (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts,
+ wtp, Memi[wbuf], Memi[w], nimages, npts)
+
+ call sfree (sp)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combines (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, wtp, wbuf, w, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+pointer wtp[nimages] # Combining weight image pointers
+pointer wbuf[nimages] # Weight buffers for nonaligned images
+pointer w[nimages] # Weight pointers
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli(), xt_opix()
+pointer impnlr(), imgnlr()
+errchk immap, ic_scale, xt_opix, imgetr, ic_grow, ic_rmasks
+errchk ic_grows, ic_gdatas
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, wtp, nimages, npts)
+
+ # Allocate weight buffers if needed.
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ if (!aligned) {
+ do i = 1, nimages
+ call salloc (wbuf[i], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ if (wtp[i] != xt_opix (wtp[i], nimages+i, 0))
+ call salloc (wbuf[i], npts, TY_REAL)
+ }
+ }
+ }
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatas (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclips (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclips (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mms (d, id, n, npts)
+ case PCLIP:
+ call ic_pclips (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclips (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclips (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclips (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclips (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averages (d, id, n, wts, w, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medians (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averages (d, id, n, wts, w, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmas (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatas (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_grows (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averages (d, id, n, wts, w, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medians (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averages (d, id, n, wts, w, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmas (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ }
+ call sfree (sp)
+end
+
+procedure icombinei (in, out, scales, zeros, wts, wtp, offsets, nimages,
+ bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnli()
+pointer sp, d, w, id, n, m, lflag, v, dbuf, wbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnli, impl1i, ic_combinei
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (wbuf, nimages, TY_POINTER)
+ call salloc (w, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (NULL, Memi[dbuf], nimages)
+ call amovki (NULL, Memi[d], nimages)
+ call amovki (NULL, Memi[wbuf], nimages)
+ call amovki (NULL, Memi[w], nimages)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_INT)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_INT)
+ }
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnli (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combinei (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts,
+ wtp, Memi[wbuf], Memi[w], nimages, npts)
+
+ call sfree (sp)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combinei (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, wtp, wbuf, w, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+pointer wtp[nimages] # Combining weight image pointers
+pointer wbuf[nimages] # Weight buffers for nonaligned images
+pointer w[nimages] # Weight pointers
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli(), xt_opix()
+pointer impnlr(), imgnlr()
+errchk immap, ic_scale, xt_opix, imgetr, ic_grow, ic_rmasks
+errchk ic_growi, ic_gdatai
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, wtp, nimages, npts)
+
+ # Allocate weight buffers if needed.
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ if (!aligned) {
+ do i = 1, nimages
+ call salloc (wbuf[i], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ if (wtp[i] != xt_opix (wtp[i], nimages+i, 0))
+ call salloc (wbuf[i], npts, TY_REAL)
+ }
+ }
+ }
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatai (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipi (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclipi (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mmi (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipi (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipi (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclipi (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipi (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclipi (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averagei (d, id, n, wts, w, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_mediani (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averagei (d, id, n, wts, w, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmai (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatai (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_growi (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averagei (d, id, n, wts, w, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_mediani (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averagei (d, id, n, wts, w, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmai (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ }
+ call sfree (sp)
+end
+
+procedure icombiner (in, out, scales, zeros, wts, wtp, offsets, nimages,
+ bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnlr()
+pointer sp, d, w, id, n, m, lflag, v, dbuf, wbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnlr, impl1i, ic_combiner
+pointer impl1r()
+errchk impl1r
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (wbuf, nimages, TY_POINTER)
+ call salloc (w, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (NULL, Memi[dbuf], nimages)
+ call amovki (NULL, Memi[d], nimages)
+ call amovki (NULL, Memi[wbuf], nimages)
+ call amovki (NULL, Memi[w], nimages)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_REAL)
+ }
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnlr (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combiner (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts,
+ wtp, Memi[wbuf], Memi[w], nimages, npts)
+
+ call sfree (sp)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combiner (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, wtp, wbuf, w, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+pointer wtp[nimages] # Combining weight image pointers
+pointer wbuf[nimages] # Weight buffers for nonaligned images
+pointer w[nimages] # Weight pointers
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli(), xt_opix()
+pointer impnlr(), imgnlr
+errchk immap, ic_scale, xt_opix, imgetr, ic_grow, ic_rmasks
+errchk ic_growr, ic_gdatar
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, wtp, nimages, npts)
+
+ # Allocate weight buffers if needed.
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ if (!aligned) {
+ do i = 1, nimages
+ call salloc (wbuf[i], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ if (wtp[i] != xt_opix (wtp[i], nimages+i, 0))
+ call salloc (wbuf[i], npts, TY_REAL)
+ }
+ }
+ }
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatar (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mmr (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipr (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averager (d, id, n, wts, w, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medianr (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_averager (d, id, n, wts, w, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmar (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatar (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_growr (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averager (d, id, n, wts, w, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_medianr (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_averager (d, id, n, wts, w, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigmar (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ }
+ call sfree (sp)
+end
+
+procedure icombined (in, out, scales, zeros, wts, wtp, offsets, nimages,
+ bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnld()
+pointer sp, d, w, id, n, m, lflag, v, dbuf, wbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnld, impl1i, ic_combined
+pointer impl1d()
+errchk impl1d
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (wbuf, nimages, TY_POINTER)
+ call salloc (w, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (NULL, Memi[dbuf], nimages)
+ call amovki (NULL, Memi[d], nimages)
+ call amovki (NULL, Memi[wbuf], nimages)
+ call amovki (NULL, Memi[w], nimages)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_DOUBLE)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_DOUBLE)
+ }
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ buf = impl1d (out[1])
+ call aclrd (Memd[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1d (out[3])
+ call aclrd (Memd[buf], npts)
+ }
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnld (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combined (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts,
+ wtp, Memi[wbuf], Memi[w], nimages, npts)
+
+ call sfree (sp)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combined (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, wtp, wbuf, w, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+pointer wtp[nimages] # Combining weight image pointers
+pointer wbuf[nimages] # Weight buffers for nonaligned images
+pointer w[nimages] # Weight pointers
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli(), xt_opix()
+pointer impnld(), imgnld
+errchk immap, ic_scale, xt_opix, imgetr, ic_grow, ic_rmasks
+errchk ic_growd, ic_gdatad
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, wtp, nimages, npts)
+
+ # Allocate weight buffers if needed.
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ if (!aligned) {
+ do i = 1, nimages
+ call salloc (wbuf[i], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ if (wtp[i] != xt_opix (wtp[i], nimages+i, 0))
+ call salloc (wbuf[i], npts, TY_REAL)
+ }
+ }
+ }
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ while (impnld (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatad (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipd (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memd[outdata])
+ else
+ call ic_accdclipd (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memd[outdata])
+ case MINMAX:
+ call ic_mmd (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipd (d, id, n, nimages, npts, Memd[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipd (d, id, n, scales, zeros, nimages, npts,
+ Memd[outdata])
+ else
+ call ic_asigclipd (d, id, n, scales, zeros, nimages, npts,
+ Memd[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipd (d, id, n, scales, zeros, nimages,
+ npts, Memd[outdata])
+ else
+ call ic_aavsigclipd (d, id, n, scales, zeros, nimages,
+ npts, Memd[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averaged (d, id, n, wts, w, npts, YES, YES,
+ Memd[outdata])
+ case MEDIAN:
+ call ic_mediand (d, n, npts, YES, Memd[outdata])
+ case SUM:
+ call ic_averaged (d, id, n, wts, w, npts, YES, NO,
+ Memd[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnld (out[3], buf, Meml[v1])
+ call ic_sigmad (d, id, n, wts, w, npts, Memd[outdata],
+ Memd[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ while (impnld (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdatad (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_growd (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnld (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovd (Memd[buf], Memd[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_averaged (d, id, n, wts, w, npts, NO, YES,
+ Memd[outdata])
+ case MEDIAN:
+ call ic_mediand (d, n, npts, NO, Memd[outdata])
+ case SUM:
+ call ic_averaged (d, id, n, wts, w, npts, NO, NO,
+ Memd[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnld (out[3], buf, Meml[v1])
+ call ic_sigmad (d, id, n, wts, w, npts, Memd[outdata],
+ Memd[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ }
+ call sfree (sp)
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/generic/icpclip.x b/noao/onedspec/odcombine/srcwt/generic/icpclip.x
new file mode 100644
index 00000000..237d9686
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icpclip.x
@@ -0,0 +1,878 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number for clipping
+
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclips (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Mems[d[n2-1]+j]
+ med = (med + Mems[d[n2]+j]) / 2.
+ } else
+ med = Mems[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Mems[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Mems[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Mems[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Mems[d[n5-1]+j]
+ med = (med + Mems[d[n5]+j]) / 2.
+ } else
+ med = Mems[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+j] = Mems[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+j] = Mems[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipi (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memi[d[n2-1]+j]
+ med = (med + Memi[d[n2]+j]) / 2.
+ } else
+ med = Memi[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memi[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memi[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memi[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memi[d[n5-1]+j]
+ med = (med + Memi[d[n5]+j]) / 2.
+ } else
+ med = Memi[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+j] = Memi[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+j] = Memi[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipr (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memr[d[n2-1]+j]
+ med = (med + Memr[d[n2]+j]) / 2.
+ } else
+ med = Memr[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memr[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memr[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memr[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memr[d[n5-1]+j]
+ med = (med + Memr[d[n5]+j]) / 2.
+ } else
+ med = Memr[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipd (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+double med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memd[d[n2-1]+j]
+ med = (med + Memd[d[n2]+j]) / 2.
+ } else
+ med = Memd[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memd[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memd[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memd[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memd[d[n5-1]+j]
+ med = (med + Memd[d[n5]+j]) / 2.
+ } else
+ med = Memd[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+j] = Memd[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+j] = Memd[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icsclip.x b/noao/onedspec/odcombine/srcwt/generic/icsclip.x
new file mode 100644
index 00000000..a0188d72
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icsclip.x
@@ -0,0 +1,1922 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Mininum number of images for algorithm
+
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclips (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mems[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mems[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Mems[d[1]+k]
+ high = Mems[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mems[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mems[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mems[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Mems[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mems[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mems[dp1]
+ Mems[dp1] = Mems[dp2]
+ Mems[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mems[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclips (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Mems[d[n3-1]+k] + Mems[d[n3]+k]) / 2.
+ else
+ med = Mems[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Mems[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Mems[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mems[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mems[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mems[d[l]+k] = Mems[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipi (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memi[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memi[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memi[d[1]+k]
+ high = Memi[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memi[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memi[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memi[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memi[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memi[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memi[dp1]
+ Memi[dp1] = Memi[dp2]
+ Memi[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memi[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipi (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memi[d[n3-1]+k] + Memi[d[n3]+k]) / 2.
+ else
+ med = Memi[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memi[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memi[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memi[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memi[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memi[d[l]+k] = Memi[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memr[d[n3-1]+k] + Memr[d[n3]+k]) / 2.
+ else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memr[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memr[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipd (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+double d1, low, high, sum, a, s, r, one
+data one /1.0D0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memd[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memd[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memd[d[1]+k]
+ high = Memd[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memd[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memd[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memd[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memd[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memd[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memd[dp1]
+ Memd[dp1] = Memd[dp2]
+ Memd[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memd[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipd (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+double median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+double med, one
+data one /1.0D0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memd[d[n3-1]+k] + Memd[d[n3]+k]) / 2.
+ else
+ med = Memd[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memd[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memd[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memd[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memd[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memd[d[l]+k] = Memd[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icsigma.x b/noao/onedspec/odcombine/srcwt/generic/icsigma.x
new file mode 100644
index 00000000..c66faba9
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icsigma.x
@@ -0,0 +1,562 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmas (d, m, n, wts, w, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL && w[1] == NULL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mems[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mems[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mems[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mems[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = (Mems[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + (Mems[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Mems[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mems[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmai (d, m, n, wts, w, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL && w[1] == NULL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memi[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memi[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memi[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memi[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = (Memi[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + (Memi[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memi[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memi[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmar (d, m, n, wts, w, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+real a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL && w[1] == NULL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memr[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memr[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memr[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memr[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = (Memr[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + (Memr[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memr[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memr[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigmad (d, m, n, wts, w, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+double average[npts] # Average
+double sigma[npts] # Sigma line (returned)
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+double a, sum
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL && w[1] == NULL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memd[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memd[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Memd[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Memd[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = (Memd[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + (Memd[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Memd[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Memd[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/generic/icsort.x b/noao/onedspec/odcombine/srcwt/generic/icsort.x
new file mode 100644
index 00000000..3ec1d27e
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icsort.x
@@ -0,0 +1,1096 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+define LOGPTR 32 # log2(maxpts) (4e9)
+
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sorts (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+short b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+short pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Mems[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Mems[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Mems[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sorts (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+short b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+short pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Mems[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Mems[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sorti (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+int b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+int pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memi[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memi[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memi[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sorti (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+int b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+int pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memi[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memi[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sortr (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memr[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sortr (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memr[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memr[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sortd (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+double b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+double pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memd[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memd[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memd[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sortd (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+double b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+double pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memd[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memd[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/srcwt/generic/icstat.x b/noao/onedspec/odcombine/srcwt/generic/icstat.x
new file mode 100644
index 00000000..3a0ed49c
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/icstat.x
@@ -0,0 +1,892 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+define NMAX 100000 # Maximum number of pixels to sample
+
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stats (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnls()
+
+real asums()
+short ic_modes()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_SHORT)
+ dp = data
+ while (imgnls (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Mems[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mems[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Mems[dp] = Mems[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Mems[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mems[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Mems[dp] = Mems[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrts (Mems[data], Mems[data], n)
+ mode = ic_modes (Mems[data], n)
+ median = Mems[data+n/2-1]
+ }
+ if (domean)
+ mean = asums (Mems[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+short procedure ic_modes (a, n)
+
+short a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+short mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stati (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnli()
+
+real asumi()
+int ic_modei()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_INT)
+ dp = data
+ while (imgnli (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memi[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memi[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memi[dp] = Memi[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memi[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memi[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memi[dp] = Memi[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrti (Memi[data], Memi[data], n)
+ mode = ic_modei (Memi[data], n)
+ median = Memi[data+n/2-1]
+ }
+ if (domean)
+ mean = asumi (Memi[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+int procedure ic_modei (a, n)
+
+int a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+int mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_statr (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnlr()
+
+real asumr()
+real ic_moder()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_REAL)
+ dp = data
+ while (imgnlr (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrtr (Memr[data], Memr[data], n)
+ mode = ic_moder (Memr[data], n)
+ median = Memr[data+n/2-1]
+ }
+ if (domean)
+ mean = asumr (Memr[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+real procedure ic_moder (a, n)
+
+real a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+real mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_statd (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnld()
+
+double asumd()
+double ic_moded()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_DOUBLE)
+ dp = data
+ while (imgnld (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Memd[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memd[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Memd[dp] = Memd[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Memd[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memd[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Memd[dp] = Memd[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrtd (Memd[data], Memd[data], n)
+ mode = ic_moded (Memd[data], n)
+ median = Memd[data+n/2-1]
+ }
+ if (domean)
+ mean = asumd (Memd[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+double procedure ic_moded (a, n)
+
+double a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+double mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/generic/mkpkg b/noao/onedspec/odcombine/srcwt/generic/mkpkg
new file mode 100644
index 00000000..632b61c8
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/mkpkg
@@ -0,0 +1,25 @@
+# Make IMCOMBINE.
+
+$checkout libpkg.a ../../../../
+$update libpkg.a
+$checkin libpkg.a ../../../../
+$exit
+
+libpkg.a:
+ icaclip.x ../icombine.com ../icombine.h
+ icaverage.x ../icombine.com ../icombine.h <imhdr.h>
+ iccclip.x ../icombine.com ../icombine.h
+ icgdata.x ../icombine.com ../icombine.h <imhdr.h> <mach.h>
+ icgrow.x ../icombine.com ../icombine.h <imhdr.h> <pmset.h>
+ icmedian.x ../icombine.com ../icombine.h
+ icmm.x ../icombine.com ../icombine.h
+ icomb.x ../icombine.com ../icombine.h <error.h> <imhdr.h>\
+ <imset.h> <mach.h> <pmset.h> <syserr.h>
+ icpclip.x ../icombine.com ../icombine.h
+ icsclip.x ../icombine.com ../icombine.h
+ icsigma.x ../icombine.com ../icombine.h <imhdr.h>
+ icsort.x
+ icstat.x ../icombine.com ../icombine.h <imhdr.h>
+
+ xtimmap.x xtimmap.com <config.h> <error.h> <imhdr.h> <imset.h>
+ ;
diff --git a/noao/onedspec/odcombine/srcwt/generic/xtimmap.x b/noao/onedspec/odcombine/srcwt/generic/xtimmap.x
new file mode 100644
index 00000000..1ab72c6f
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/generic/xtimmap.x
@@ -0,0 +1,1079 @@
+include <syserr.h>
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <config.h>
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+# These routines maintain an arbitrary number of indexed "open" images which
+# must be READ_ONLY. The calling program may use the returned pointer for
+# header accesses but must call xt_opix before I/O. Subsequent calls to
+# xt_opix may invalidate the pointer. The xt_imunmap call will free memory.
+
+define MAX_OPENIM (LAST_FD-16) # Maximum images kept open
+define MAX_OPENPIX 45 # Maximum pixel files kept open
+
+define XT_SZIMNAME 299 # Size of IMNAME string
+define XT_LEN 179 # Structure length
+define XT_IMNAME Memc[P2C($1)] # Image name
+define XT_ARG Memi[$1+150] # IMMAP header argument
+define XT_IM Memi[$1+151] # IMIO pointer
+define XT_HDR Memi[$1+152] # Copy of IMIO pointer
+define XT_CLOSEFD Memi[$1+153] # Close FD?
+define XT_FLAG Memi[$1+154] # Flag
+define XT_BUFSIZE Memi[$1+155] # Buffer size
+define XT_BUF Memi[$1+156] # Data buffer
+define XT_BTYPE Memi[$1+157] # Data buffer type
+define XT_VS Memi[$1+157+$2] # Start vector (10)
+define XT_VE Memi[$1+167+$2] # End vector (10)
+
+# Options
+define XT_MAPUNMAP 1 # Map and unmap images.
+
+# XT_IMMAP -- Map an image and save it as an indexed open image.
+# The returned pointer may be used for header access but not I/O.
+# The indexed image is closed by xt_imunmap.
+
+pointer procedure xt_immap (imname, acmode, hdr_arg, index)
+
+char imname[ARB] #I Image name
+int acmode #I Access mode
+int hdr_arg #I Header argument
+int index #I Save index
+pointer im #O Image pointer (returned)
+
+int i, envgeti()
+pointer xt, xt_opix()
+errchk xt_opix
+
+int first_time
+data first_time /YES/
+
+include "../xtimmap.com"
+
+begin
+ if (acmode != READ_ONLY)
+ call error (1, "XT_IMMAP: Only READ_ONLY allowed")
+
+ # Initialize once per process.
+ if (first_time == YES) {
+ iferr (option = envgeti ("imcombine_option"))
+ option = 1
+ min_open = 1
+ nopen = 0
+ nopenpix = 0
+ nalloc = MAX_OPENIM
+ call calloc (ims, nalloc, TY_POINTER)
+ first_time = NO
+ }
+
+ # Free image if needed.
+ call xt_imunmap (NULL, index)
+
+ # Allocate structure.
+ if (index > nalloc) {
+ i = nalloc
+ nalloc = index + MAX_OPENIM
+ call realloc (ims, nalloc, TY_STRUCT)
+ call amovki (NULL, Memi[ims+i], nalloc-i)
+ }
+ call calloc (xt, XT_LEN, TY_STRUCT)
+ Memi[ims+index-1] = xt
+
+ # Initialize.
+ call strcpy (imname, XT_IMNAME(xt), XT_SZIMNAME)
+ XT_ARG(xt) = hdr_arg
+ XT_IM(xt) = NULL
+ XT_HDR(xt) = NULL
+
+ # Open image.
+ last_flag = 0
+ im = xt_opix (NULL, index, 0)
+
+ # Make copy of IMIO pointer for header keyword access.
+ call malloc (XT_HDR(xt), LEN_IMDES+IM_HDRLEN(im)+1, TY_STRUCT)
+ call amovi (Memi[im], Memi[XT_HDR(xt)], LEN_IMDES)
+ call amovi (IM_MAGIC(im), IM_MAGIC(XT_HDR(xt)), IM_HDRLEN(im)+1)
+
+ return (XT_HDR(xt))
+end
+
+
+# XT_OPIX -- Open the image for I/O.
+# If the image has not been mapped return the default pointer.
+
+pointer procedure xt_opix (imdef, index, flag)
+
+int index #I index
+pointer imdef #I Default pointer
+int flag #I Flag
+
+int i, open(), imstati()
+pointer im, xt, xt1, immap()
+errchk open, immap, imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imdef)
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Return pointer for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (im)
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || flag == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ if (!IS_INDEFI(XT_BUFSIZE(xt)))
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ else
+ XT_BUFSIZE(xt) = imstati (im, IM_BUFSIZE)
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (im)
+end
+
+
+# XT_CPIX -- Close image.
+
+procedure xt_cpix (index)
+
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL)
+ return
+
+ if (XT_IM(xt) != NULL) {
+ call imunmap (XT_IM(xt))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ }
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+end
+
+
+# XT_IMSETI -- Set IMIO value.
+
+procedure xt_imseti (index, param, value)
+
+int index #I index
+int param #I IMSET parameter
+int value #I Value
+
+pointer xt
+bool streq()
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL) {
+ if (streq (param, "option"))
+ option = value
+ } else {
+ if (streq (param, "bufsize")) {
+ XT_BUFSIZE(xt) = value
+ if (XT_IM(xt) != NULL) {
+ call imseti (XT_IM(xt), IM_BUFFRAC, 0)
+ call imseti (XT_IM(xt), IM_BUFSIZE, value)
+ }
+ }
+ }
+end
+
+
+# XT_IMUNMAP -- Unmap indexed open image.
+# The header pointer is set to NULL to indicate the image has been closed.
+
+procedure xt_imunmap (im, index)
+
+int im #U IMIO header pointer
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Check for an indexed image. If it is not unmap the pointer
+ # as a regular IMIO pointer.
+
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+ if (xt == NULL) {
+ if (im != NULL)
+ call imunmap (im)
+ return
+ }
+
+ # Close indexed image.
+ if (XT_IM(xt) != NULL) {
+ iferr (call imunmap (XT_IM(xt))) {
+ XT_IM(xt) = NULL
+ call erract (EA_WARN)
+ }
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ if (index == min_open)
+ min_open = 1
+ }
+
+ # Free any buffered memory.
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+
+ # Free header pointer. Note that if the supplied pointer is not
+ # header pointer then it is not set to NULL.
+ if (XT_HDR(xt) == im)
+ im = NULL
+ call mfree (XT_HDR(xt), TY_STRUCT)
+
+ # Free save structure.
+ call mfree (Memi[ims+index-1], TY_STRUCT)
+end
+
+
+# XT_REINDEX -- Reindex open images.
+# This is used when some images are closed by xt_imunmap. It is up to
+# the calling program to reindex the header pointers and to subsequently
+# use the new index values.
+
+procedure xt_reindex ()
+
+int old, new
+
+include "../xtimmap.com"
+
+begin
+ new = 0
+ do old = 0, nalloc-1 {
+ if (Memi[ims+old] == NULL)
+ next
+ Memi[ims+new] = Memi[ims+old]
+ new = new + 1
+ }
+ do old = new, nalloc-1
+ Memi[ims+old] = NULL
+end
+
+
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnls (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnls(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggss()
+errchk open, immap, imgnls, imggss, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnls (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnls (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_SHORT)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_SHORT) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_SHORT
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggss (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovs (Mems[ptr], Mems[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnls (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnli (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnli(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsi()
+errchk open, immap, imgnli, imggsi, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnli (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnli (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_INT)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_INT) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_INT
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsi (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovi (Memi[ptr], Memi[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnli (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnlr (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnlr(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsr()
+errchk open, immap, imgnlr, imggsr, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnlr (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnlr (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_REAL)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_REAL) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_REAL
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsr (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovr (Memr[ptr], Memr[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnlr (im, buf, v))
+end
+
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnld (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnld(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggsd()
+errchk open, immap, imgnld, imggsd, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnld (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnld (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_DOUBLE)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_DOUBLE) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_DOUBLE
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggsd (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amovd (Memd[ptr], Memd[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnld (im, buf, v))
+end
+
diff --git a/noao/onedspec/odcombine/srcwt/icaclip.gx b/noao/onedspec/odcombine/srcwt/icaclip.gx
new file mode 100644
index 00000000..696402b2
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icaclip.gx
@@ -0,0 +1,575 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number of images for this algorithm
+
+$for (sird)
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclip$t (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+$else
+PIXEL d1, low, high, sum, a, s, s1, r, one
+data one /1$f/
+$endif
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclip$t (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med, low, high, r, s, s1, one
+data one /1.0/
+$else
+PIXEL med, low, high, r, s, s1, one
+data one /1$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Mem$t[d[1]+k]
+ else {
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Mem$t[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else {
+ call sfree (sp)
+ return
+ }
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Mem$t[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Mem$t[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Mem$t[d[n3-1]+k]
+ high = Mem$t[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icaverage.gx b/noao/onedspec/odcombine/srcwt/icaverage.gx
new file mode 100644
index 00000000..26ebd3a4
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icaverage.gx
@@ -0,0 +1,143 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_AVERAGE -- Compute the average (or summed) image line.
+# Options include a weighted average/sum.
+
+procedure ic_average$t (d, m, n, wts, w, npts, doblank, doaverage, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+int doblank # Set blank values?
+int doaverage # Do average?
+$if (datatype == sil)
+real average[npts] # Average (returned)
+$else
+PIXEL average[npts] # Average (returned)
+$endif
+
+int i, j, k
+real sumwt, wt
+$if (datatype == sil)
+real sum
+$else
+PIXEL sum
+$endif
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average/sum without checking
+ # the number of points and using the fact that the weights are
+ # normalized. If all the data has been excluded set the average/sum
+ # to the blank value if requested.
+
+ if (dflag == D_ALL && w[1] == NULL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mem$t[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mem$t[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ }
+ }
+ } else if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ average[i] = blank
+ }
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Mem$t[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Mem$t[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = Mem$t[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + Mem$t[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ if (doaverage == YES) {
+ if (sumwt > 0)
+ average[i] = sum / sumwt
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ } else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Mem$t[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Mem$t[d[j]+k]
+ if (doaverage == YES)
+ average[i] = sum / n[i]
+ else
+ average[i] = sum
+ } else if (doblank == YES)
+ average[i] = blank
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/iccclip.gx b/noao/onedspec/odcombine/srcwt/iccclip.gx
new file mode 100644
index 00000000..609b3448
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/iccclip.gx
@@ -0,0 +1,471 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 2 # Mininum number of images for algorithm
+
+$for (sird)
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclip$t (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+$else
+PIXEL d1, low, high, sum, a, s, r, zero
+data zero /0$f/
+$endif
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Mem$t[d[1]+k]
+ sum = sum + Mem$t[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclip$t (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med, zero
+data zero /0.0/
+$else
+PIXEL med, zero
+data zero /0$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Mem$t[d[n3-1]+k]
+ med = (med + Mem$t[d[n3]+k]) / 2.
+ } else
+ med = Mem$t[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icemask.x b/noao/onedspec/odcombine/srcwt/icemask.x
new file mode 100644
index 00000000..37b19636
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icemask.x
@@ -0,0 +1,128 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+
+
+# IC_EMASK -- Create exposure mask.
+
+procedure ic_emask (pm, v, id, nimages, n, wts, w, npts)
+
+pointer pm #I Pixel mask
+long v[ARB] #I Output vector
+pointer id[nimages] #I Image id pointers
+int nimages #I Number of images
+int n[npts] #I Number of good pixels
+real wts[npts] #I Weights
+pointer w[npts] #I Weight data pointers
+int npts #I Number of output pixels per line
+
+int i, j, k, impnli()
+real exp
+pointer buf
+
+pointer exps # Exposure times
+pointer ev # IMIO coordinate vector
+real ezero # Integer to real zero
+real escale # Integer to real scale
+int einit # Initialization flag
+common /emask/ exps, ev, ezero, escale, einit
+
+begin
+ # Write scaling factors to the header.
+ if (einit == NO) {
+ if (ezero != 0. || escale != 1.) {
+ call imaddr (pm, "MASKZERO", ezero)
+ call imaddr (pm, "MASKSCAL", escale)
+ }
+ einit = YES
+ }
+
+ call amovl (v, Meml[ev], IM_MAXDIM)
+ i = impnli (pm, buf, Meml[ev])
+ call aclri (Memi[buf], npts)
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ exp = 0.
+ do j = 1, n[i] {
+ k = Memi[id[j]+i-1]
+ if (wts[k] > 0.)
+ exp = exp + Memr[exps+k-1]
+ }
+ Memi[buf] = nint((exp-ezero)/escale)
+ buf = buf + 1
+ }
+ } else {
+ do i = 1, npts {
+ exp = 0.
+ do j = 1, n[i] {
+ k = Memi[id[j]+i-1]
+ if (Memr[w[id[j]+i-1]+i-1] > 0.)
+ exp = exp + Memr[exps+k-1]
+ }
+ Memi[buf] = nint((exp-ezero)/escale)
+ buf = buf + 1
+ }
+ }
+end
+
+
+# IC_EINIT -- Initialize exposure mask.
+
+procedure ic_einit (in, nimages, key, default, maxval)
+
+int in[nimages] #I Image pointers
+int nimages #I Number of images
+char key[ARB] #I Exposure time keyword
+real default #I Default exposure time
+int maxval #I Maximum mask value
+
+int i
+real exp, emin, emax, efrac, imgetr()
+
+pointer exps # Exposure times
+pointer ev # IMIO coordinate vector
+real ezero # Integer to real zero
+real escale # Integer to real scale
+int einit # Initialization flag
+common /emask/ exps, ev, ezero, escale, einit
+
+begin
+ call malloc (ev, IM_MAXDIM, TY_LONG)
+ call malloc (exps, nimages, TY_REAL)
+
+ emax = 0.
+ emin = MAX_REAL
+ efrac = 0
+ do i = 1, nimages {
+ iferr (exp = imgetr (in[i], key))
+ exp = default
+ exp = max (0., exp)
+ emax = emax + exp
+ if (exp > 0.)
+ emin = min (exp, emin)
+ efrac = max (abs(exp-nint(exp)), efrac)
+ Memr[exps+i-1] = exp
+ }
+
+ # Set scaling.
+ ezero = 0.
+ escale = 1.
+ if (emin < 1.) {
+ escale = emin
+ emin = emin / escale
+ emax = emax / escale
+ } else if (emin == MAX_REAL)
+ emin = 0.
+ if (efrac > 0.001 && emax-emin < 1000.) {
+ escale = escale / 1000.
+ emin = emin * 1000.
+ emax = emax * 1000.
+ }
+ while (emax > maxval) {
+ escale = escale * 10.
+ emin = emin / 10.
+ emax = emax / 10.
+ }
+ einit = NO
+end
diff --git a/noao/onedspec/odcombine/srcwt/icgdata.gx b/noao/onedspec/odcombine/srcwt/icgdata.gx
new file mode 100644
index 00000000..d4273e13
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icgdata.gx
@@ -0,0 +1,397 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdata$t (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag, offsets,
+ scales, zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer wtp[nimages] # Weight images
+pointer wbuf[nimages] # Weight buffers
+pointer w[nimages] # Weight pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnl$t()
+$if (datatype != r)
+int xt_imgnlr()
+$endif
+long v3[IM_MAXDIM]
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnl$t
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2)) {
+ call xt_cpix (i)
+ call xt_cpix (nimages+i)
+ }
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ j = xt_imgnlr (wtp[i], nimages+i, w[i], v3, v1[2])
+ }
+ j = xt_imgnl$t (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ if (wtp[i] != NULL) {
+ call amovl (v2, v3, IM_MAXDIM)
+ l = xt_imgnlr (wtp[i], nimages+i, buf, v3, v1[2])
+ call amovr (Memr[buf+k-1], Memr[wbuf[i]+j], npix)
+ w[i] = wbuf[i]
+ }
+ l = xt_imgnl$t (in[i], i, buf, v2, v1[2])
+ call amov$t (Mem$t[buf+k-1], Mem$t[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ if (wtype == S_SIGMAP) {
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Memr[dp] = Memr[dp] / a
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+ }
+
+ # Compute weights from sigmas.
+ if (wtype == S_SIGMAP) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = w[i]
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = w[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Memr[dp]
+ if (a > 0.)
+ Memr[dp] = 1. / (a**2)
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_PIXEL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sort$t (d, Mem$t[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sort$t (d, Mem$t[dp], n, npts)
+ call mfree (dp, TY_PIXEL)
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icgdata.gxBAK b/noao/onedspec/odcombine/srcwt/icgdata.gxBAK
new file mode 100644
index 00000000..27f51ec5
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icgdata.gxBAK
@@ -0,0 +1,307 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <mach.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_GDATA -- Get line of image and mask data and apply threshold and scaling.
+# Entirely empty lines are excluded. The data are compacted within the
+# input data buffers. If it is required, the connection to the original
+# image index is kept in the returned m data pointers.
+
+procedure ic_gdata$t (in, out, dbuf, d, id, n, m, lflag, offsets, scales,
+ zeros, nimages, npts, v1, v2)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+pointer dbuf[nimages] # Data buffers
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of input images
+int npts # NUmber of output points per line
+long v1[ARB], v2[ARB] # Line vectors
+
+int i, j, k, l, n1, n2, npix, nin, nout, ndim, nused, xt_imgnl$t()
+real a, b
+pointer buf, dp, ip, mp
+errchk xt_cpix, xt_imgnl$t
+
+include "../icombine.com"
+
+begin
+ # Get masks and return if there is no data
+ call ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+ if (dflag == D_NONE) {
+ call aclri (n, npts)
+ return
+ }
+
+ # Close images which are not needed.
+ nout = IM_LEN(out[1],1)
+ ndim = IM_NDIM(out[1])
+ if (!project) {
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ call xt_cpix (i)
+ if (ndim > 1) {
+ j = v1[2] - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+ }
+ }
+
+ # Get data and fill data buffers. Correct for offsets if needed.
+ do i = 1, nimages {
+ if (lflag[i] == D_NONE)
+ next
+ if (dbuf[i] == NULL) {
+ call amovl (v1, v2, IM_MAXDIM)
+ if (project)
+ v2[ndim+1] = i
+ j = xt_imgnl$t (in[i], i, d[i], v2, v1[2])
+ } else {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ k = 1 + j - offsets[i,1]
+ v2[1] = k
+ do l = 2, ndim {
+ v2[l] = v1[l] - offsets[i,l]
+ if (v2[l] < 1 || v2[l] > IM_LEN(in[i],l)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ if (lflag[i] == D_NONE)
+ next
+ if (project)
+ v2[ndim+1] = i
+ l = xt_imgnl$t (in[i], i, buf, v2, v1[2])
+ call amov$t (Mem$t[buf+k-1], Mem$t[dbuf[i]+j], npix)
+ d[i] = dbuf[i]
+ }
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ a = Mem$t[dp]
+ if (a < lthresh || a > hthresh) {
+ Memi[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+
+ # Check for completely empty lines
+ lflag[i] = D_NONE
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ dp = d[i]
+ do j = 1, npts {
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0)
+ Mem$t[dp] = Mem$t[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ ip = id[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow >= 1.) {
+ do j = 1, npts {
+ do i = n[j]+1, nimages
+ Memi[id[i]+j-1] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ l = lflag[i]
+ nin = IM_LEN(in[l],1)
+ j = max (0, offsets[l,1])
+ k = min (nout, nin + offsets[l,1])
+ npix = k - j
+ n1 = 1 + j
+ n2 = n1 + npix - 1
+ dp = d[i] + n1 - 1
+ mp = m[i] + n1 - 1
+ do j = n1, n2 {
+ if (Memi[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Mem$t[d[k]+j-1] = Mem$t[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_PIXEL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sort$t (d, Mem$t[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sort$t (d, Mem$t[dp], n, npts)
+ call mfree (dp, TY_PIXEL)
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icgrow.gx b/noao/onedspec/odcombine/srcwt/icgrow.gx
new file mode 100644
index 00000000..caf7dd29
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icgrow.gx
@@ -0,0 +1,135 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <pmset.h>
+include "../icombine.h"
+
+# IC_GROW -- Mark neigbors of rejected pixels.
+# The rejected pixels (original plus grown) are saved in pixel masks.
+
+procedure ic_grow (out, v, m, n, buf, nimages, npts, pms)
+
+pointer out # Output image pointer
+long v[ARB] # Output vector
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[npts,nimages] # Working buffer
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k, l, line, nl, rop, igrow, nset, ncompress, or()
+real grow2, i2
+pointer mp, pm, pm_newmask()
+errchk pm_newmask()
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE || grow == 0.)
+ return
+
+ line = v[2]
+ nl = IM_LEN(out,2)
+ rop = or (PIX_SRC, PIX_DST)
+
+ igrow = grow
+ grow2 = grow**2
+ do l = 0, igrow {
+ i2 = grow2 - l * l
+ call aclri (buf, npts*nimages)
+ nset = 0
+ do j = 1, npts {
+ do k = n[j]+1, nimages {
+ mp = Memi[m[k]+j-1]
+ if (mp == 0)
+ next
+ do i = 0, igrow {
+ if (i**2 > i2)
+ next
+ if (j > i)
+ buf[j-i,mp] = 1
+ if (j+i <= npts)
+ buf[j+i,mp] = 1
+ nset = nset + 1
+ }
+ }
+ }
+ if (nset == 0)
+ return
+
+ if (pms == NULL) {
+ call malloc (pms, nimages, TY_POINTER)
+ do i = 1, nimages
+ Memi[pms+i-1] = pm_newmask (out, 1)
+ ncompress = 0
+ }
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ v[2] = line - l
+ if (v[2] > 0)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ if (l > 0) {
+ v[2] = line + l
+ if (v[2] <= nl)
+ call pmplpi (pm, v, buf[1,i], 1, npts, rop)
+ }
+ }
+ }
+ v[2] = line
+
+ if (ncompress > 10) {
+ do i = 1, nimages {
+ pm = Memi[pms+i-1]
+ call pm_compress (pm)
+ }
+ ncompress = 0
+ } else
+ ncompress = ncompress + 1
+end
+
+
+$for (sird)
+# IC_GROW$T -- Reject pixels.
+
+procedure ic_grow$t (v, d, m, n, buf, nimages, npts, pms)
+
+long v[ARB] # Output vector
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[ARB] # Number of good pixels
+int buf[ARB] # Buffer of npts
+int nimages # Number of images
+int npts # Number of output points per line
+pointer pms # Pointer to array of pixel masks
+
+int i, j, k
+pointer pm
+bool pl_linenotempty()
+
+include "../icombine.com"
+
+begin
+ do k = 1, nimages {
+ pm = Memi[pms+k-1]
+ if (!pl_linenotempty (pm, v))
+ next
+ call pmglpi (pm, v, buf, 1, npts, PIX_SRC)
+ do i = 1, npts {
+ if (buf[i] == 0)
+ next
+ for (j = 1; j <= n[i]; j = j + 1) {
+ if (Memi[m[j]+i-1] == k) {
+ if (j < n[i]) {
+ Mem$t[d[j]+i-1] = Mem$t[d[n[i]]+i-1]
+ Memi[m[j]+i-1] = Memi[m[n[i]]+i-1]
+ }
+ n[i] = n[i] - 1
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icgscale.x b/noao/onedspec/odcombine/srcwt/icgscale.x
new file mode 100644
index 00000000..afcc8fd0
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icgscale.x
@@ -0,0 +1,92 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "icombine.h"
+
+
+# IC_GSCALE -- Get scale values as directed by CL parameter.
+# Only those values which are INDEF are changed.
+# The values can be one of those in the dictionary, from a file specified
+# with a @ prefix, or from an image header keyword specified by a ! prefix.
+
+int procedure ic_gscale (param, name, dic, in, exptime, values, nimages)
+
+char param[ARB] #I CL parameter name
+char name[SZ_FNAME] #O Parameter value
+char dic[ARB] #I Dictionary string
+pointer in[nimages] #I IMIO pointers
+real exptime[nimages] #I Exposure times
+real values[nimages] #O Values
+int nimages #I Number of images
+
+int type #O Type of value
+
+int fd, i, nowhite(), open(), fscan(), nscan(), strdic(), strncmp()
+real rval, imgetr()
+pointer errstr
+errchk open, imgetr
+
+include "icombine.com"
+
+begin
+ call clgstr (param, name, SZ_FNAME)
+ if (nowhite (name, name, SZ_FNAME) == 0)
+ type = S_NONE
+ else if (strncmp (name, "wtmap!", 6) == 0) {
+ type = S_WTMAP
+ } else if (strncmp (name, "sigmap!", 7) == 0) {
+ type = S_SIGMAP
+ } else if (name[1] == '@') {
+ type = S_FILE
+ do i = 1, nimages
+ if (IS_INDEFR(values[i]))
+ break
+ if (i <= nimages) {
+ fd = open (name[2], READ_ONLY, TEXT_FILE)
+ i = 0
+ while (fscan (fd) != EOF) {
+ call gargr (rval)
+ if (nscan() != 1)
+ next
+ if (i == nimages) {
+ call eprintf (
+ "Warning: Ignoring additional %s values in %s\n")
+ call pargstr (param)
+ call pargstr (name[2])
+ break
+ }
+ i = i + 1
+ if (IS_INDEFR(values[i]))
+ values[i] = rval
+ }
+ call close (fd)
+ if (i < nimages) {
+ call salloc (errstr, SZ_LINE, TY_CHAR)
+ call sprintf (errstr, SZ_FNAME,
+ "Insufficient %s values in %s")
+ call pargstr (param)
+ call pargstr (name[2])
+ call error (1, errstr)
+ }
+ }
+ } else if (name[1] == '!') {
+ type = S_KEYWORD
+ do i = 1, nimages {
+ if (IS_INDEFR(values[i]))
+ values[i] = imgetr (in[i], name[2])
+ if (project) {
+ call amovkr (values, values, nimages)
+ break
+ }
+ }
+ } else {
+ type = strdic (name, name, SZ_FNAME, dic)
+ if (type == 0)
+ call error (1, "Unknown scale, zero, or weight type")
+ if (type==S_EXPOSURE)
+ do i = 1, nimages
+ if (IS_INDEFR(values[i]))
+ values[i] = max (0.001, exptime[i])
+ }
+
+ return (type)
+end
diff --git a/noao/onedspec/odcombine/srcwt/ichdr.x b/noao/onedspec/odcombine/srcwt/ichdr.x
new file mode 100644
index 00000000..2d19c5bd
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/ichdr.x
@@ -0,0 +1,55 @@
+include <imset.h>
+
+
+# IC_HDR -- Set output header.
+
+procedure ic_hdr (in, out, nimages)
+
+pointer in[nimages] #I Input images
+pointer out[ARB] #I Output images
+int nimages #I Number of images
+
+int i, imgnfn()
+pointer sp, key, str, list, imofnlu()
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ # Set new PROCID.
+ call xt_procid (out)
+
+ # Set input PROCIDs.
+ if (nimages < 100) {
+ list = imofnlu (out, "PROCID[0-9][0-9]")
+ while (imgnfn (list, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (list)
+ do i = 1, nimages {
+ call sprintf (Memc[key], 8, "PROCID%02d")
+ call pargi (i)
+ iferr (call imgstr (in[i], "PROCID", Memc[str], SZ_LINE)) {
+ iferr (call imgstr (in[i], "OBSID", Memc[str], SZ_LINE))
+ Memc[str] = EOS
+ }
+ if (Memc[str] != EOS)
+ call imastr (out, Memc[key], Memc[str])
+ }
+
+ # Set input image names.
+ list = imofnlu (out, "IMCMB[0-9][0-9][0-9]")
+ while (imgnfn (list, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (list)
+ do i = 1, nimages {
+ iferr (call imgstr (in[i], "ICFNAME", Memc[str], SZ_LINE))
+ call imstats (in[i], IM_IMAGENAME, Memc[str], SZ_LINE)
+ call sprintf (Memc[key], SZ_LINE, "IMCMB%03d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[str])
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icimstack.x b/noao/onedspec/odcombine/srcwt/icimstack.x
new file mode 100644
index 00000000..d5628694
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icimstack.x
@@ -0,0 +1,186 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <error.h>
+include <imhdr.h>
+
+
+# IC_IMSTACK -- Stack images into a single image of higher dimension.
+
+procedure ic_imstack (list, output, mask)
+
+int list #I List of images
+char output[ARB] #I Name of output image
+char mask[ARB] #I Name of output mask
+
+int i, j, npix
+long line_in[IM_MAXDIM], line_out[IM_MAXDIM], line_outbpm[IM_MAXDIM]
+pointer sp, input, bpmname, key, in, out, inbpm, outbpm, buf_in, buf_out, ptr
+
+int imtgetim(), imtlen(), errget()
+int imgnls(), imgnli(), imgnll(), imgnlr(), imgnld(), imgnlx()
+int impnls(), impnli(), impnll(), impnlr(), impnld(), impnlx()
+pointer immap(), pm_newmask()
+errchk immap
+errchk imgnls, imgnli, imgnll, imgnlr, imgnld, imgnlx
+errchk impnls, impnli, impnll, impnlr, impnld, impnlx
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (bpmname, SZ_FNAME, TY_CHAR)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ iferr {
+ # Add each input image to the output image.
+ out = NULL; outbpm = NULL
+ i = 0
+ while (imtgetim (list, Memc[input], SZ_FNAME) != EOF) {
+
+ i = i + 1
+ in = NULL; inbpm = NULL
+ ptr = immap (Memc[input], READ_ONLY, 0)
+ in = ptr
+
+ # For the first input image map the output image as a copy
+ # and increment the dimension. Set the output line counter.
+
+ if (i == 1) {
+ ptr = immap (output, NEW_COPY, in)
+ out = ptr
+ IM_NDIM(out) = IM_NDIM(out) + 1
+ IM_LEN(out, IM_NDIM(out)) = imtlen (list)
+ npix = IM_LEN(out, 1)
+ call amovkl (long(1), line_out, IM_MAXDIM)
+
+ if (mask[1] != EOS) {
+ ptr = immap (mask, NEW_COPY, in)
+ outbpm = ptr
+ IM_NDIM(outbpm) = IM_NDIM(outbpm) + 1
+ IM_LEN(outbpm, IM_NDIM(outbpm)) = imtlen (list)
+ call amovkl (long(1), line_outbpm, IM_MAXDIM)
+ }
+ }
+
+ # Check next input image for consistency with the output image.
+ if (IM_NDIM(in) != IM_NDIM(out) - 1)
+ call error (0, "Input images not consistent")
+ do j = 1, IM_NDIM(in) {
+ if (IM_LEN(in, j) != IM_LEN(out, j))
+ call error (0, "Input images not consistent")
+ }
+
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[input])
+
+ # Copy the input lines from the image to the next lines of
+ # the output image. Switch on the output data type to optimize
+ # IMIO.
+
+ call amovkl (long(1), line_in, IM_MAXDIM)
+ switch (IM_PIXTYPE (out)) {
+ case TY_SHORT:
+ while (imgnls (in, buf_in, line_in) != EOF) {
+ if (impnls (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovs (Mems[buf_in], Mems[buf_out], npix)
+ }
+ case TY_INT:
+ while (imgnli (in, buf_in, line_in) != EOF) {
+ if (impnli (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovi (Memi[buf_in], Memi[buf_out], npix)
+ }
+ case TY_USHORT, TY_LONG:
+ while (imgnll (in, buf_in, line_in) != EOF) {
+ if (impnll (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovl (Meml[buf_in], Meml[buf_out], npix)
+ }
+ case TY_REAL:
+ while (imgnlr (in, buf_in, line_in) != EOF) {
+ if (impnlr (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovr (Memr[buf_in], Memr[buf_out], npix)
+ }
+ case TY_DOUBLE:
+ while (imgnld (in, buf_in, line_in) != EOF) {
+ if (impnld (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovd (Memd[buf_in], Memd[buf_out], npix)
+ }
+ case TY_COMPLEX:
+ while (imgnlx (in, buf_in, line_in) != EOF) {
+ if (impnlx (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovx (Memx[buf_in], Memx[buf_out], npix)
+ }
+ default:
+ while (imgnlr (in, buf_in, line_in) != EOF) {
+ if (impnlr (out, buf_out, line_out) == EOF)
+ call error (0, "Error writing output image")
+ call amovr (Memr[buf_in], Memr[buf_out], npix)
+ }
+ }
+
+ # Copy mask.
+ if (mask[1] != EOS) {
+ iferr (call imgstr (in, "bpm", Memc[bpmname], SZ_FNAME)) {
+ Memc[bpmname] = EOS
+ ptr = pm_newmask (in, 27)
+ } else
+ ptr = immap (Memc[bpmname], READ_ONLY, 0)
+ inbpm = ptr
+
+ if (IM_NDIM(inbpm) != IM_NDIM(outbpm) - 1)
+ call error (0, "Input images not consistent")
+ do j = 1, IM_NDIM(inbpm) {
+ if (IM_LEN(inbpm, j) != IM_LEN(outbpm, j))
+ call error (0, "Masks not consistent")
+ }
+
+ call amovkl (long(1), line_in, IM_MAXDIM)
+ while (imgnli (inbpm, buf_in, line_in) != EOF) {
+ if (impnli (outbpm, buf_out, line_outbpm) == EOF)
+ call error (0, "Error writing output mask")
+ call amovi (Memi[buf_in], Memi[buf_out], npix)
+ }
+
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ call imastr (out, Memc[key], Memc[bpmname])
+
+ call imunmap (inbpm)
+ }
+
+ call imunmap (in)
+ }
+ } then {
+ i = errget (Memc[key], SZ_FNAME)
+ call erract (EA_WARN)
+ if (outbpm != NULL) {
+ call imunmap (outbpm)
+ iferr (call imdelete (mask))
+ ;
+ }
+ if (out != NULL) {
+ call imunmap (out)
+ iferr (call imdelete (output))
+ ;
+ }
+ if (inbpm != NULL)
+ call imunmap (inbpm)
+ if (in != NULL)
+ call imunmap (in)
+ call sfree (sp)
+ call error (i, "Can't make temporary stack images")
+ }
+
+ # Finish up.
+ if (outbpm != NULL) {
+ call imunmap (outbpm)
+ call imastr (out, "bpm", mask)
+ }
+ call imunmap (out)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/iclog.x b/noao/onedspec/odcombine/srcwt/iclog.x
new file mode 100644
index 00000000..43ab37ab
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/iclog.x
@@ -0,0 +1,422 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <mach.h>
+include "icombine.h"
+include "icmask.h"
+
+# IC_LOG -- Output log information is a log file has been specfied.
+
+procedure ic_log (in, out, ncombine, exptime, sname, zname, wname,
+ mode, median, mean, scales, zeros, wts, offsets, nimages,
+ dozero, nout)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int ncombine[nimages] # Number of previous combined images
+real exptime[nimages] # Exposure times
+char sname[ARB] # Scale name
+char zname[ARB] # Zero name
+char wname[ARB] # Weight name
+real mode[nimages] # Modes
+real median[nimages] # Medians
+real mean[nimages] # Means
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+int offsets[nimages,ARB] # Image offsets
+int nimages # Number of images
+bool dozero # Zero flag
+int nout # Number of images combined in output
+
+int i, j, stack, ctor()
+real rval, imgetr()
+long clktime()
+bool prncombine, prexptime, prmode, prmedian, prmean, prmask
+bool prrdn, prgain, prsn
+pointer sp, fname, bpname, key
+errchk imgetr
+
+include "icombine.com"
+
+begin
+ if (logfd == NULL)
+ return
+
+ call smark (sp)
+ call salloc (fname, SZ_LINE, TY_CHAR)
+ call salloc (bpname, SZ_LINE, TY_CHAR)
+
+ stack = NO
+ if (project) {
+ ifnoerr (call imgstr (in[1], "stck0001", Memc[fname], SZ_LINE))
+ stack = YES
+ }
+ if (stack == YES)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ # Time stamp the log and print parameter information.
+
+ call cnvdate (clktime(0), Memc[fname], SZ_LINE)
+ call fprintf (logfd, "\n%s: %s\n")
+ call pargstr (Memc[fname])
+ if (ictask != NULL)
+ call pargstr (Memc[ictask])
+ else
+ call pargstr ("IMCOMBINE")
+ switch (combine) {
+ case AVERAGE:
+ call fprintf (logfd, " combine = average, ")
+ case MEDIAN:
+ call fprintf (logfd, " combine = median, ")
+ case SUM:
+ call fprintf (logfd, " combine = sum, ")
+ }
+ call fprintf (logfd, "scale = %s, zero = %s, weight = %s\n")
+ call pargstr (sname)
+ call pargstr (zname)
+ call pargstr (wname)
+
+ switch (reject) {
+ case MINMAX:
+ call fprintf (logfd, " reject = minmax, nlow = %d, nhigh = %d\n")
+ call pargi (nint (flow * nimages))
+ call pargi (nint (fhigh * nimages))
+ case CCDCLIP:
+ call fprintf (logfd, " reject = ccdclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, sigma = %g, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case CRREJECT:
+ call fprintf (logfd,
+ " reject = crreject, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (hsigma)
+ case PCLIP:
+ call fprintf (logfd, " reject = pclip, nkeep = %d\n")
+ call pargi (nkeep)
+ call fprintf (logfd, " pclip = %g, lsigma = %g, hsigma = %g\n")
+ call pargr (pclip)
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case SIGCLIP:
+ call fprintf (logfd, " reject = sigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case AVSIGCLIP:
+ call fprintf (logfd,
+ " reject = avsigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ }
+ if (reject != NONE && grow >= 1.) {
+ call fprintf (logfd, " grow = %g\n")
+ call pargr (grow)
+ }
+ if (dothresh) {
+ if (lthresh > -MAX_REAL && hthresh < MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g, hthreshold = %g\n")
+ call pargr (lthresh)
+ call pargr (hthresh)
+ } else if (lthresh > -MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g\n")
+ call pargr (lthresh)
+ } else {
+ call fprintf (logfd, " hthreshold = %g\n")
+ call pargr (hthresh)
+ }
+ }
+ call fprintf (logfd, " blank = %g\n")
+ call pargr (blank)
+ if (Memc[statsec] != EOS) {
+ call fprintf (logfd, " statsec = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (ICM_TYPE(icm) != M_NONE) {
+ switch (ICM_TYPE(icm)) {
+ case M_BOOLEAN, M_GOODVAL:
+ call fprintf (logfd, " masktype = goodval, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_BADVAL:
+ call fprintf (logfd, " masktype = badval, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_GOODBITS:
+ call fprintf (logfd, " masktype = goodbits, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ case M_BADBITS:
+ call fprintf (logfd, " masktype = badbits, maskval = %d\n")
+ call pargi (ICM_VALUE(icm))
+ }
+ }
+
+ # Print information pertaining to individual images as a set of
+ # columns with the image name being the first column. Determine
+ # what information is relevant and print the appropriate header.
+
+ prncombine = false
+ prexptime = false
+ prmode = false
+ prmedian = false
+ prmean = false
+ prmask = false
+ prrdn = false
+ prgain = false
+ prsn = false
+ do i = 1, nimages {
+ if (ncombine[i] != ncombine[1])
+ prncombine = true
+ if (exptime[i] != exptime[1])
+ prexptime = true
+ if (mode[i] != mode[1])
+ prmode = true
+ if (median[i] != median[1])
+ prmedian = true
+ if (mean[i] != mean[1])
+ prmean = true
+ if (ICM_TYPE(icm) != M_NONE) {
+ if (project)
+ bpname = Memi[ICM_LOGNAMES(icm)]
+ else
+ bpname = Memi[ICM_LOGNAMES(icm)+i-1]
+ if (Memc[bpname] != EOS)
+ prmask = true
+ }
+ if (reject == CCDCLIP || reject == CRREJECT) {
+ j = 1
+ if (ctor (Memc[rdnoise], j, rval) == 0)
+ prrdn = true
+ j = 1
+ if (ctor (Memc[gain], j, rval) == 0)
+ prgain = true
+ j = 1
+ if (ctor (Memc[snoise], j, rval) == 0)
+ prsn = true
+ }
+ }
+
+ call fprintf (logfd, " %20s ")
+ call pargstr ("Images")
+ if (prncombine) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("N")
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Exp")
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mode")
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Median")
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mean")
+ }
+ if (prrdn) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Rdnoise")
+ }
+ if (prgain) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Gain")
+ }
+ if (prsn) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Snoise")
+ }
+ if (doscale) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Scale")
+ }
+ if (dozero) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Zero")
+ }
+ if (dowts) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Weight")
+ }
+ if (!aligned) {
+ call fprintf (logfd, " %9s")
+ call pargstr ("Offsets")
+ }
+ if (prmask) {
+ call fprintf (logfd, " %s")
+ call pargstr ("Maskfile")
+ }
+ call fprintf (logfd, "\n")
+
+ do i = 1, nimages {
+ if (stack == YES) {
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ ifnoerr (call imgstr (in[i], Memc[key], Memc[fname], SZ_LINE)) {
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ } else {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %16s[%3d]")
+ call pargstr (Memc[fname])
+ call pargi (i)
+ }
+ } else if (project) {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %16s[%3d]")
+ call pargstr (Memc[fname])
+ call pargi (i)
+ } else ifnoerr (call imgstr (in[i],"ICFNAME",Memc[fname],SZ_LINE)) {
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ } else {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " %21s")
+ call pargstr (Memc[fname])
+ }
+ if (prncombine) {
+ call fprintf (logfd, " %6d")
+ call pargi (ncombine[i])
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6.1f")
+ call pargr (exptime[i])
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mode[i])
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (median[i])
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mean[i])
+ }
+ if (prrdn) {
+ rval = imgetr (in[i], Memc[rdnoise])
+ call fprintf (logfd, " %7g")
+ call pargr (rval)
+ }
+ if (prgain) {
+ rval = imgetr (in[i], Memc[gain])
+ call fprintf (logfd, " %6g")
+ call pargr (rval)
+ }
+ if (prsn) {
+ rval = imgetr (in[i], Memc[snoise])
+ call fprintf (logfd, " %6g")
+ call pargr (rval)
+ }
+ if (doscale) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (1./scales[i])
+ }
+ if (dozero) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (-zeros[i])
+ }
+ if (dowts) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (wts[i])
+ }
+ if (!aligned) {
+ if (IM_NDIM(out[1]) == 1) {
+ call fprintf (logfd, " %9d")
+ call pargi (offsets[i,1])
+ } else {
+ do j = 1, IM_NDIM(out[1]) {
+ call fprintf (logfd, " %4d")
+ call pargi (offsets[i,j])
+ }
+ }
+ }
+ if (prmask) {
+ if (stack == YES) {
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ ifnoerr (call imgstr (in[i], Memc[key], Memc[fname],
+ SZ_LINE)) {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[fname])
+ } else {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[bpname])
+ }
+ } else if (ICM_TYPE(icm) != M_NONE) {
+ if (project)
+ bpname = Memi[ICM_LOGNAMES(icm)]
+ else
+ bpname = Memi[ICM_LOGNAMES(icm)+i-1]
+ if (Memc[bpname] != EOS) {
+ call fprintf (logfd, " %s")
+ call pargstr (Memc[bpname])
+ }
+ }
+ }
+ call fprintf (logfd, "\n")
+ }
+
+ # Log information about the output images.
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, "\n Output image = %s, ncombine = %d")
+ call pargstr (Memc[fname])
+ call pargi (nout)
+ call fprintf (logfd, "\n")
+
+ if (out[2] != NULL) {
+ call imstats (out[2], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Bad pixel mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[4] != NULL) {
+ call imstats (out[4], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Rejection mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[5] != NULL) {
+ call imstats (out[5], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Number rejected mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[6] != NULL) {
+ call imstats (out[6], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Exposure mask = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ if (out[3] != NULL) {
+ call imstats (out[3], IM_IMAGENAME, Memc[fname], SZ_LINE)
+ call fprintf (logfd, " Sigma image = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ call flush (logfd)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icmask.com b/noao/onedspec/odcombine/srcwt/icmask.com
new file mode 100644
index 00000000..baba6f6a
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icmask.com
@@ -0,0 +1,8 @@
+# IMCMASK -- Common for IMCOMBINE mask interface.
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+common /imcmask/ mtype, mvalue, bufs, pms
diff --git a/noao/onedspec/odcombine/srcwt/icmask.h b/noao/onedspec/odcombine/srcwt/icmask.h
new file mode 100644
index 00000000..533c601d
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icmask.h
@@ -0,0 +1,9 @@
+# ICMASK -- Data structure for IMCOMBINE mask interface.
+
+define ICM_LEN 6 # Structure length
+define ICM_TYPE Memi[$1] # Mask type
+define ICM_VALUE Memi[$1+1] # Mask value
+define ICM_BUFS Memi[$1+2] # Pointer to data line buffers
+define ICM_PMS Memi[$1+3] # Pointer to array of PMIO pointers
+define ICM_NAMES Memi[$1+4] # Pointer to array of mask names
+define ICM_LOGNAMES Memi[$1+5] # Pointer to array of mask log names
diff --git a/noao/onedspec/odcombine/srcwt/icmask.x b/noao/onedspec/odcombine/srcwt/icmask.x
new file mode 100644
index 00000000..9242405d
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icmask.x
@@ -0,0 +1,499 @@
+include <imhdr.h>
+include <pmset.h>
+include "icombine.h"
+include "icmask.h"
+
+# IC_MASK -- ICOMBINE mask interface
+#
+# IC_MOPEN -- Initialize mask interface
+# IC_MCLOSE -- Close the mask interface
+# IC_MGET -- Get lines of mask pixels for all the images
+# IC_MGET1 -- Get a line of mask pixels for the specified image
+# IC_MCLOSE1-- Close a mask for the specified image index
+
+
+# IC_MOPEN -- Initialize mask interface.
+
+procedure ic_mopen (in, out, nimages, offsets)
+
+pointer in[nimages] #I Input images
+pointer out[ARB] #I Output images
+int nimages #I Number of images
+int offsets[nimages,ARB] #I Offsets to output image
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+pointer names # Pointer to array of string pointers
+pointer lognames # Pointer to array of string pointers
+
+int i, j, k, nin, nout, npix, npms, nowhite(), strdic()
+int clgeti()
+pointer sp, key, fname, logname, title, pm, pm_open()
+bool invert, pm_empty()
+errchk calloc, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ icm = NULL
+ if (IM_NDIM(out[1]) == 0)
+ return
+
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (title, SZ_FNAME, TY_CHAR)
+
+ # Determine the mask parameters and allocate memory.
+ # The mask buffers are initialize to all excluded so that
+ # output points outside the input data are always excluded
+ # and don't need to be set on a line-by-line basis.
+
+ mtype = M_NONE
+ call clgstr ("masktype", Memc[key], SZ_FNAME)
+ if (nowhite (Memc[key], Memc[key], SZ_FNAME) > 0) {
+ if (Memc[key] == '!') {
+ mtype = M_GOODVAL
+ call strcpy (Memc[key+1], Memc[key], SZ_FNAME)
+ } else {
+ mtype = strdic (Memc[key], Memc[title], SZ_FNAME, MASKTYPES)
+ if (mtype == 0) {
+ call sprintf (Memc[title], SZ_FNAME,
+ "Invalid or ambiguous masktype (%s)")
+ call pargstr (Memc[key])
+ call error (1, Memc[title])
+ }
+ call strcpy ("BPM", Memc[key], SZ_FNAME)
+ }
+ }
+ mvalue = clgeti ("maskvalue")
+ npix = IM_LEN(out[1],1)
+ call calloc (pms, nimages, TY_POINTER)
+ call calloc (bufs, nimages, TY_POINTER)
+ call calloc (names, nimages, TY_POINTER)
+ call calloc (lognames, nimages, TY_POINTER)
+ do i = 1, nimages {
+ call malloc (Memi[bufs+i-1], npix, TY_INT)
+ call amovki (1, Memi[Memi[bufs+i-1]], npix)
+ }
+
+ # Check for special cases. The BOOLEAN type is used when only
+ # zero and nonzero are significant; i.e. the actual mask values are
+ # not important. The invert flag is used to indicate that
+ # empty masks are all bad rather the all good.
+
+ if (mtype == 0)
+ mtype = M_NONE
+ if (mtype == M_BADBITS && mvalue == 0)
+ mtype = M_NONE
+ if (mvalue == 0 && (mtype == M_GOODVAL || mtype == M_GOODBITS))
+ mtype = M_BOOLEAN
+ if ((mtype == M_BADVAL && mvalue == 0) ||
+ (mtype == M_GOODVAL && mvalue != 0) ||
+ (mtype == M_GOODBITS && mvalue == 0))
+ invert = true
+ else
+ invert = false
+
+ # If mask images are to be used, get the mask name from the image
+ # header and open it saving the descriptor in the pms array.
+ # Empty masks (all good) are treated as if there was no mask image.
+
+ nout = IM_LEN(out[1],1)
+ npms = 0
+ do i = 1, nimages {
+ if (mtype != M_NONE) {
+ call malloc (Memi[names+i-1], SZ_FNAME, TY_CHAR)
+ call malloc (Memi[lognames+i-1], SZ_FNAME, TY_CHAR)
+ fname = Memi[names+i-1]
+ logname = Memi[lognames+i-1]
+ ifnoerr (call imgstr (in[i],Memc[key],Memc[fname],SZ_FNAME)) {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+ if (npix < 1)
+ Memc[fname] = EOS
+ else {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], Memc[title],
+ SZ_FNAME))
+ call pm_loadim (pm, Memc[fname], Memc[title],
+ SZ_FNAME)
+ call pm_seti (pm, P_REFIM, in[i])
+ if (pm_empty (pm) && !invert)
+ Memc[fname] = EOS
+ else {
+ if (project)
+ npms = nimages
+ else
+ npms = npms + 1
+ }
+ call pm_close (pm)
+
+ ifnoerr (call imgstr (in[i], "ICBPM", Memc[title],
+ SZ_FNAME))
+ call strcpy (Memc[title], Memc[logname], SZ_FNAME)
+ else
+ call strcpy (Memc[fname], Memc[logname], SZ_FNAME)
+ }
+ if (project)
+ break
+ } else {
+ Memc[fname] = EOS
+ Memc[logname] = EOS
+ }
+ }
+ }
+
+ # If no mask images are found and the mask parameters imply that
+ # good values are 0 then use the special case of no masks.
+
+ if (npms == 0) {
+ if (!invert)
+ mtype = M_NONE
+ }
+
+ # Set up mask structure.
+ call calloc (icm, ICM_LEN, TY_STRUCT)
+ ICM_TYPE(icm) = mtype
+ ICM_VALUE(icm) = mvalue
+ ICM_BUFS(icm) = bufs
+ ICM_PMS(icm) = pms
+ ICM_NAMES(icm) = names
+ ICM_LOGNAMES(icm) = lognames
+
+ call sfree (sp)
+end
+
+
+# IC_MCLOSE -- Close the mask interface.
+
+procedure ic_mclose (nimages)
+
+int nimages # Number of images
+
+int i
+include "icombine.com"
+
+begin
+ if (icm == NULL)
+ return
+
+ do i = 1, nimages {
+ call mfree (Memi[ICM_NAMES(icm)+i-1], TY_CHAR)
+ call mfree (Memi[ICM_BUFS(icm)+i-1], TY_INT)
+ }
+ do i = 1, nimages {
+ if (Memi[ICM_PMS(icm)+i-1] != NULL)
+ call pm_close (Memi[ICM_PMS(icm)+i-1])
+ if (project)
+ break
+ }
+ call mfree (ICM_NAMES(icm), TY_POINTER)
+ call mfree (ICM_BUFS(icm), TY_POINTER)
+ call mfree (ICM_PMS(icm), TY_POINTER)
+ call mfree (icm, TY_STRUCT)
+end
+
+
+# IC_MGET -- Get lines of mask pixels in the output coordinate system.
+# This converts the mask format to an array where zero is good and nonzero
+# is bad. This has special cases for optimization.
+
+procedure ic_mget (in, out, offsets, v1, v2, m, lflag, nimages)
+
+pointer in[nimages] # Input image pointers
+pointer out[ARB] # Output image pointer
+int offsets[nimages,ARB] # Offsets to output image
+long v1[IM_MAXDIM] # Data vector desired in output image
+long v2[IM_MAXDIM] # Data vector in input image
+pointer m[nimages] # Pointer to mask pointers
+int lflag[nimages] # Line flags
+int nimages # Number of images
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+char title[1]
+int i, j, k, ndim, nin, nout, npix
+pointer buf, pm, names, fname, pm_open()
+bool pm_linenotempty()
+errchk pm_glpi, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ # Determine if masks are needed at all. Note that the threshold
+ # is applied by simulating mask values so the mask pointers have to
+ # be set.
+
+ dflag = D_ALL
+ if (icm == NULL)
+ return
+ if (ICM_TYPE(icm) == M_NONE && aligned && !dothresh)
+ return
+
+ mtype = ICM_TYPE(icm)
+ mvalue = ICM_VALUE(icm)
+ bufs = ICM_BUFS(icm)
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ # Set the mask pointers and line flags and apply offsets if needed.
+
+ ndim = IM_NDIM(out[1])
+ nout = IM_LEN(out[1],1)
+ do i = 1, nimages {
+ nin = IM_LEN(in[i],1)
+ j = max (0, offsets[i,1])
+ k = min (nout, nin + offsets[i,1])
+ npix = k - j
+
+ m[i] = Memi[bufs+i-1]
+ buf = Memi[bufs+i-1] + j
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+i-1]
+ fname = Memi[names+i-1]
+ }
+
+ if (npix < 1)
+ lflag[i] = D_NONE
+ else if (npix == nout)
+ lflag[i] = D_ALL
+ else
+ lflag[i] = D_MIX
+
+ if (lflag[i] != D_NONE) {
+ v2[1] = 1 + j - offsets[i,1]
+ do j = 2, ndim {
+ v2[j] = v1[j] - offsets[i,j]
+ if (v2[j] < 1 || v2[j] > IM_LEN(in[i],j)) {
+ lflag[i] = D_NONE
+ break
+ }
+ }
+ }
+ if (project)
+ v2[ndim+1] = i
+
+ if (lflag[i] == D_NONE) {
+ if (pm != NULL && !project) {
+ call pm_close (pm)
+ Memi[pms+i-1] = NULL
+ }
+ next
+ }
+
+ if (fname == NULL) {
+ call aclri (Memi[buf], npix)
+ next
+ } else if (Memc[fname] == EOS) {
+ call aclri (Memi[buf], npix)
+ next
+ }
+
+ # Do mask I/O and convert to appropriate values in order of
+ # expected usage.
+
+ if (pm == NULL) {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], title, 1))
+ call pm_loadim (pm, Memc[fname], title, 1)
+ call pm_seti (pm, P_REFIM, in[i])
+ if (project)
+ Memi[pms] = pm
+ else
+ Memi[pms+i-1] = pm
+ }
+
+ if (pm_linenotempty (pm, v2)) {
+ call pm_glpi (pm, v2, Memi[buf], 32, npix, 0)
+
+ if (mtype == M_BOOLEAN)
+ ;
+ else if (mtype == M_BADBITS)
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_BADVAL)
+ call abeqki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_GOODBITS) {
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ call abeqki (Memi[buf], 0, Memi[buf], npix)
+ } else if (mtype == M_GOODVAL)
+ call abneki (Memi[buf], mvalue, Memi[buf], npix)
+
+ lflag[i] = D_NONE
+ do j = 1, npix
+ if (Memi[buf+j-1] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ } else {
+ if (mtype == M_BOOLEAN || mtype == M_BADBITS) {
+ call aclri (Memi[buf], npix)
+ } else if ((mtype == M_BADVAL && mvalue != 0) ||
+ (mtype == M_GOODVAL && mvalue == 0)) {
+ call aclri (Memi[buf], npix)
+ } else {
+ call amovki (1, Memi[buf], npix)
+ lflag[i] = D_NONE
+ }
+ }
+ }
+
+ # Set overall data flag
+ dflag = lflag[1]
+ do i = 2, nimages {
+ if (lflag[i] != dflag) {
+ dflag = D_MIX
+ break
+ }
+ }
+end
+
+
+# IC_MGET1 -- Get line of mask pixels from a specified image.
+# This is used by the IC_STAT procedure. This procedure converts the
+# stored mask format to an array where zero is good and nonzero is bad.
+# The data vector and returned mask array are in the input image pixel system.
+
+procedure ic_mget1 (in, image, nimages, offset, v, m)
+
+pointer in # Input image pointer
+int image # Image index
+int nimages # Number of images
+int offset # Column offset
+long v[IM_MAXDIM] # Data vector desired
+pointer m # Pointer to mask
+
+int mtype # Mask type
+int mvalue # Mask value
+pointer bufs # Pointer to data line buffers
+pointer pms # Pointer to array of PMIO pointers
+
+char title[1]
+int i, npix
+pointer buf, pm, names, fname, pm_open()
+bool pm_linenotempty()
+errchk pm_glpi, pm_open, pm_loadf, pm_loadim
+
+include "icombine.com"
+
+begin
+ dflag = D_ALL
+ if (icm == NULL)
+ return
+ if (ICM_TYPE(icm) == M_NONE)
+ return
+
+ mtype = ICM_TYPE(icm)
+ mvalue = ICM_VALUE(icm)
+ bufs = ICM_BUFS(icm)
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ npix = IM_LEN(in,1)
+ m = Memi[bufs+image-1] + offset
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+image-1]
+ fname = Memi[names+image-1]
+ }
+
+ if (fname == NULL)
+ return
+ if (Memc[fname] == EOS)
+ return
+
+ if (pm == NULL) {
+ pm = pm_open (NULL)
+ iferr (call pm_loadf (pm, Memc[fname], title, 1))
+ call pm_loadim (pm, Memc[fname], title, 1)
+ call pm_seti (pm, P_REFIM, in)
+ if (project)
+ Memi[pms] = pm
+ else
+ Memi[pms+image-1] = pm
+ }
+
+ # Do mask I/O and convert to appropriate values in order of
+ # expected usage.
+
+ buf = m
+ if (pm_linenotempty (pm, v)) {
+ call pm_glpi (pm, v, Memi[buf], 32, npix, 0)
+
+ if (mtype == M_BOOLEAN)
+ ;
+ else if (mtype == M_BADBITS)
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_BADVAL)
+ call abeqki (Memi[buf], mvalue, Memi[buf], npix)
+ else if (mtype == M_GOODBITS) {
+ call aandki (Memi[buf], mvalue, Memi[buf], npix)
+ call abeqki (Memi[buf], 0, Memi[buf], npix)
+ } else if (mtype == M_GOODVAL)
+ call abneki (Memi[buf], mvalue, Memi[buf], npix)
+
+ dflag = D_NONE
+ do i = 1, npix
+ if (Memi[buf+i-1] == 0) {
+ dflag = D_MIX
+ break
+ }
+ } else {
+ if (mtype == M_BOOLEAN || mtype == M_BADBITS) {
+ ;
+ } else if ((mtype == M_BADVAL && mvalue != 0) ||
+ (mtype == M_GOODVAL && mvalue == 0)) {
+ ;
+ } else
+ dflag = D_NONE
+ }
+end
+
+
+# IC_MCLOSE1 -- Close mask by index.
+
+procedure ic_mclose1 (image, nimages)
+
+int image # Image index
+int nimages # Number of images
+
+pointer pms, names, pm, fname
+include "icombine.com"
+
+begin
+ if (icm == NULL)
+ return
+
+ pms = ICM_PMS(icm)
+ names = ICM_NAMES(icm)
+
+ if (project) {
+ pm = Memi[pms]
+ fname = Memi[names]
+ } else {
+ pm = Memi[pms+image-1]
+ fname = Memi[names+image-1]
+ }
+
+ if (fname == NULL || pm == NULL)
+ return
+ if (Memc[fname] == EOS || pm == NULL)
+ return
+
+ call pm_close (pm)
+ if (project)
+ Memi[pms] = NULL
+ else
+ Memi[pms+image-1] = NULL
+end
diff --git a/noao/onedspec/odcombine/srcwt/icmedian.gx b/noao/onedspec/odcombine/srcwt/icmedian.gx
new file mode 100644
index 00000000..4ac51ae6
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icmedian.gx
@@ -0,0 +1,231 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+$for (sird)
+# IC_MEDIAN -- Median of lines
+
+procedure ic_median$t (d, n, npts, doblank, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+int doblank # Set blank values?
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, j1, j2, n1, lo, up, lo1, up1
+bool even
+$if (datatype == silx)
+real val1, val2, val3
+$else
+PIXEL val1, val2, val3
+$endif
+PIXEL temp, wtemp
+$if (datatype == x)
+real abs_temp
+$endif
+
+include "../icombine.com"
+
+begin
+ # If no data return after possibly setting blank values.
+ if (dflag == D_NONE) {
+ if (doblank == YES) {
+ do i = 1, npts
+ median[i]= blank
+ }
+ return
+ }
+
+ # If the data were previously sorted then directly compute the median.
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Mem$t[d[j1]+k]
+ val2 = Mem$t[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mem$t[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Mem$t[d[j1]+k]
+ val2 = Mem$t[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Mem$t[d[j1]+k]
+ } else if (doblank == YES)
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Compute the median.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+
+ # If there are more than 3 points use Wirth algorithm. This
+ # is the same as vops$amed.gx except for an even number of
+ # points it selects the middle two and averages.
+ if (n1 > 3) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2))
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mem$t[d[j]+k]; lo1 = lo; up1 = up
+ $if (datatype == x)
+ abs_temp = abs (temp)
+ $endif
+
+ repeat {
+ $if (datatype == x)
+ while (abs (Mem$t[d[lo1]+k]) < abs_temp)
+ $else
+ while (Mem$t[d[lo1]+k] < temp)
+ $endif
+ lo1 = lo1 + 1
+ $if (datatype == x)
+ while (abs_temp < abs (Mem$t[d[up1]+k]))
+ $else
+ while (temp < Mem$t[d[up1]+k])
+ $endif
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mem$t[d[lo1]+k]
+ Mem$t[d[lo1]+k] = Mem$t[d[up1]+k]
+ Mem$t[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+
+ median[i] = Mem$t[d[j]+k]
+
+ if (mod (n1,2) == 0) {
+ lo = 1
+ up = n1
+ j = max (lo, min (up, (up+1)/2)+1)
+
+ while (lo < up) {
+ if (! (lo < up))
+ break
+
+ temp = Mem$t[d[j]+k]; lo1 = lo; up1 = up
+ $if (datatype == x)
+ abs_temp = abs (temp)
+ $endif
+
+ repeat {
+ $if (datatype == x)
+ while (abs (Mem$t[d[lo1]+k]) < abs_temp)
+ $else
+ while (Mem$t[d[lo1]+k] < temp)
+ $endif
+ lo1 = lo1 + 1
+ $if (datatype == x)
+ while (abs_temp < abs (Mem$t[d[up1]+k]))
+ $else
+ while (temp < Mem$t[d[up1]+k])
+ $endif
+ up1 = up1 - 1
+ if (lo1 <= up1) {
+ wtemp = Mem$t[d[lo1]+k]
+ Mem$t[d[lo1]+k] = Mem$t[d[up1]+k]
+ Mem$t[d[up1]+k] = wtemp
+ lo1 = lo1 + 1; up1 = up1 - 1
+ }
+ } until (lo1 > up1)
+
+ if (up1 < j)
+ lo = lo1
+ if (j < lo1)
+ up = up1
+ }
+ median[i] = (median[i] + Mem$t[d[j]+k]) / 2
+ }
+
+ # If 3 points find the median directly.
+ } else if (n1 == 3) {
+ $if (datatype == x)
+ val1 = abs (Mem$t[d[1]+k])
+ val2 = abs (Mem$t[d[2]+k])
+ val3 = abs (Mem$t[d[3]+k])
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = Mem$t[d[2]+k]
+ else if (val1 < val3) # acb
+ median[i] = Mem$t[d[3]+k]
+ else # cab
+ median[i] = Mem$t[d[1]+k]
+ } else {
+ if (val2 > val3) # cba
+ median[i] = Mem$t[d[2]+k]
+ else if (val1 > val3) # bca
+ median[i] = Mem$t[d[3]+k]
+ else # bac
+ median[i] = Mem$t[d[1]+k]
+ }
+ $else
+ val1 = Mem$t[d[1]+k]
+ val2 = Mem$t[d[2]+k]
+ val3 = Mem$t[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+ $endif
+
+ # If 2 points average.
+ } else if (n1 == 2) {
+ val1 = Mem$t[d[1]+k]
+ val2 = Mem$t[d[2]+k]
+ median[i] = (val1 + val2) / 2
+
+ # If 1 point return the value.
+ } else if (n1 == 1)
+ median[i] = Mem$t[d[1]+k]
+
+ # If no points return with a possibly blank value.
+ else if (doblank == YES)
+ median[i] = blank
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icmm.gx b/noao/onedspec/odcombine/srcwt/icmm.gx
new file mode 100644
index 00000000..16505588
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icmm.gx
@@ -0,0 +1,189 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+$for (sird)
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mm$t (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+PIXEL d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Mem$t[kmax] = d2
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ } else {
+ Mem$t[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Mem$t[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ } else {
+ Mem$t[kmin] = d2
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ Memi[m[j]+i1] = k
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Mem$t[kmax] = d2
+ else
+ Mem$t[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Mem$t[kmin] = d1
+ else
+ Mem$t[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Mem$t[kmin] = d1
+ k = Memi[m[jmin]+i1]
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmin < n1)
+ Mem$t[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ $if (datatype == x)
+ d1 = abs (Mem$t[k])
+ $else
+ d1 = Mem$t[k]
+ $endif
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Mem$t[kmax] = d1
+ k = Memi[m[jmax]+i1]
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ Memi[m[n1]+i1] = k
+ }
+ } else {
+ if (jmax < n1)
+ Mem$t[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icomb.gx b/noao/onedspec/odcombine/srcwt/icomb.gx
new file mode 100644
index 00000000..ad572761
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icomb.gx
@@ -0,0 +1,711 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <pmset.h>
+include <error.h>
+include <syserr.h>
+include <mach.h>
+include "../icombine.h"
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+
+# ICOMBINE -- Combine images
+#
+# The memory and open file descriptor limits are checked and an attempt
+# to recover is made either by setting the image pixel files to be
+# closed after I/O or by notifying the calling program that memory
+# ran out and the IMIO buffer size should be reduced. After the checks
+# a procedure for the selected combine option is called.
+# Because there may be several failure modes when reaching the file
+# limits we first assume an error is due to the file limit, except for
+# out of memory, and close some pixel files. If the error then repeats
+# on accessing the pixels the error is passed back.
+
+$for (sird)
+procedure icombine$t (in, out, scales, zeros, wts, wtp, offsets, nimages,
+ bufsize)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int offsets[nimages,ARB] # Input image offsets
+int nimages # Number of input images
+int bufsize # IMIO buffer size
+
+char str[1]
+int i, j, k, npts, fd, stropen(), xt_imgnl$t()
+pointer sp, d, w, id, n, m, lflag, v, dbuf, wbuf
+pointer im, buf, xt_opix(), impl1i()
+errchk stropen, xt_cpix, xt_opix, xt_imgnl$t, impl1i, ic_combine$t
+$if (datatype == sil)
+pointer impl1r()
+errchk impl1r
+$else
+pointer impl1$t()
+errchk impl1$t
+$endif
+
+include "../icombine.com"
+
+begin
+ npts = IM_LEN(out[1],1)
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (dbuf, nimages, TY_POINTER)
+ call salloc (d, nimages, TY_POINTER)
+ call salloc (wbuf, nimages, TY_POINTER)
+ call salloc (w, nimages, TY_POINTER)
+ call salloc (id, nimages, TY_POINTER)
+ call salloc (n, npts, TY_INT)
+ call salloc (m, nimages, TY_POINTER)
+ call salloc (lflag, nimages, TY_INT)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call amovki (NULL, Memi[dbuf], nimages)
+ call amovki (NULL, Memi[d], nimages)
+ call amovki (NULL, Memi[wbuf], nimages)
+ call amovki (NULL, Memi[w], nimages)
+ call amovki (D_ALL, Memi[lflag], nimages)
+ call amovkl (1, Meml[v], IM_MAXDIM)
+
+ # If not aligned or growing create data buffers of output length
+ # otherwise use the IMIO buffers.
+
+ if (!aligned || grow >= 1.) {
+ do i = 1, nimages
+ call salloc (Memi[dbuf+i-1], npts, TY_PIXEL)
+ } else {
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (im != in[i])
+ call salloc (Memi[dbuf+i-1], npts, TY_PIXEL)
+ }
+ }
+
+ if (project) {
+ call imseti (in[1], IM_NBUFS, nimages)
+ call imseti (in[1], IM_BUFFRAC, 0)
+ call imseti (in[1], IM_BUFSIZE, bufsize)
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ } else {
+ # Reserve FD for string operations.
+ fd = stropen (str, 1, NEW_FILE)
+
+ # Do I/O to the images.
+ do i = 1, 6 {
+ if (out[i] != NULL) {
+ call imseti (out[i], IM_BUFFRAC, 0)
+ call imseti (out[i], IM_BUFSIZE, bufsize)
+ }
+ }
+ $if (datatype == sil)
+ buf = impl1r (out[1])
+ call aclrr (Memr[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1r (out[3])
+ call aclrr (Memr[buf], npts)
+ }
+ $else
+ buf = impl1$t (out[1])
+ call aclr$t (Mem$t[buf], npts)
+ if (out[3] != NULL) {
+ buf = impl1$t (out[3])
+ call aclr$t (Mem$t[buf], npts)
+ }
+ $endif
+ if (out[2] != NULL) {
+ buf = impl1i (out[2])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[4] != NULL) {
+ buf = impl1i (out[4])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[5] != NULL) {
+ buf = impl1i (out[5])
+ call aclri (Memi[buf], npts)
+ }
+ if (out[6] != NULL) {
+ buf = impl1i (out[6])
+ call aclri (Memi[buf], npts)
+ }
+
+ # Do I/O for first input image line.
+ if (!project) {
+ do i = 1, nimages {
+ call xt_imseti (i, "bufsize", bufsize)
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ call xt_cpix (i)
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ call xt_cpix (i)
+ }
+
+ do i = 1, nimages {
+ j = max (0, offsets[i,1])
+ k = min (npts, IM_LEN(in[i],1) + offsets[i,1])
+ if (k - j < 1)
+ next
+ j = 1 - offsets[i,2]
+ if (j < 1 || j > IM_LEN(in[i],2))
+ next
+ iferr {
+ Meml[v+1] = j
+ j = xt_imgnl$t (in[i], i, buf, Meml[v], 1)
+ } then {
+ call imseti (im, IM_PIXFD, NULL)
+ call sfree (sp)
+ call strclose (fd)
+ call erract (EA_ERROR)
+ }
+ }
+ }
+
+ call strclose (fd)
+ }
+
+ call ic_combine$t (in, out, Memi[dbuf], Memi[d], Memi[id], Memi[n],
+ Memi[m], Memi[lflag], offsets, scales, zeros, wts,
+ wtp, Memi[wbuf], Memi[w], nimages, npts)
+
+ call sfree (sp)
+end
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combine$t (in, out, dbuf, d, id, n, m, lflag, offsets,
+ scales, zeros, wts, wtp, wbuf, w, nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output image
+pointer dbuf[nimages] # Data buffers for nonaligned images
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+int offsets[nimages,ARB] # Input image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+pointer wtp[nimages] # Combining weight image pointers
+pointer wbuf[nimages] # Weight buffers for nonaligned images
+pointer w[nimages] # Weight pointers
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ext, ctor(), errcode()
+real r, imgetr()
+pointer sp, fname, imname, v1, v2, v3, work
+pointer outdata, buf, nm, pms
+pointer immap(), impnli(), xt_opix()
+$if (datatype == sil)
+pointer impnlr(), imgnlr()
+$else
+pointer impnl$t(), imgnl$t
+$endif
+errchk immap, ic_scale, xt_opix, imgetr, ic_grow, ic_rmasks
+errchk ic_grow$t, ic_gdata$t
+
+include "../icombine.com"
+data ext/0/
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (v3, IM_MAXDIM, TY_LONG)
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+
+ call ic_scale (in, out, offsets, scales, zeros, wts, wtp, nimages, npts)
+
+ # Allocate weight buffers if needed.
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ if (!aligned) {
+ do i = 1, nimages
+ call salloc (wbuf[i], npts, TY_REAL)
+ } else {
+ do i = 1, nimages {
+ if (wtp[i] != xt_opix (wtp[i], nimages+i, 0))
+ call salloc (wbuf[i], npts, TY_REAL)
+ }
+ }
+ }
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = imgetr (in[i], Memc[rdnoise])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[gain])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] =
+ max ((Memr[nm+3*(i-1)] / r) ** 2, 1e4 / MAX_REAL)
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = imgetr (in[i], Memc[snoise])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ case PCLIP:
+ mclip = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1)
+ keepids = true
+ case NONE:
+ mclip = false
+ }
+
+ if (out[4] != NULL)
+ keepids = true
+
+ if (out[6] != NULL) {
+ keepids = true
+ call ic_einit (in, nimages, Memc[expkeyword], 1., 2**27-1)
+ }
+
+ if (grow >= 1.) {
+ keepids = true
+ call salloc (work, npts * nimages, TY_INT)
+ }
+ pms = NULL
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ $if (datatype == sil)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ else
+ call ic_accdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[outdata])
+ case MINMAX:
+ call ic_mm$t (d, id, n, npts)
+ case PCLIP:
+ call ic_pclip$t (d, id, n, nimages, npts, Memr[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ else
+ call ic_asigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Memr[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ else
+ call ic_aavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Memr[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, w, npts, YES, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, YES, Memr[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, w, npts, YES, NO,
+ Memr[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $else
+ while (impnl$t (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, wtp, wbuf, w, id, n, m, lflag,
+ offsets, scales, zeros, nimages, npts, Meml[v2], Meml[v3])
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Mem$t[outdata])
+ else
+ call ic_accdclip$t (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Mem$t[outdata])
+ case MINMAX:
+ call ic_mm$t (d, id, n, npts)
+ case PCLIP:
+ call ic_pclip$t (d, id, n, nimages, npts, Mem$t[outdata])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Mem$t[outdata])
+ else
+ call ic_asigclip$t (d, id, n, scales, zeros, nimages, npts,
+ Mem$t[outdata])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Mem$t[outdata])
+ else
+ call ic_aavsigclip$t (d, id, n, scales, zeros, nimages,
+ npts, Mem$t[outdata])
+ }
+
+ if (pms == NULL || nkeep > 0) {
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, w, npts, YES, YES,
+ Mem$t[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, YES, Mem$t[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, w, npts, YES, NO,
+ Mem$t[outdata])
+ }
+ }
+ }
+
+ if (grow >= 1.)
+ call ic_grow (out, Meml[v2], id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (pms == NULL) {
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ buf = buf + 1
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnl$t (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, w, npts, Mem$t[outdata],
+ Mem$t[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+ }
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $endif
+
+ if (pms != NULL) {
+ if (nkeep > 0) {
+ call imstats (out[1], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call imunmap (out[1])
+ iferr (buf = immap (Memc[fname], READ_WRITE, 0)) {
+ switch (errcode()) {
+ case SYS_FXFOPNOEXTNV:
+ call imgcluster (Memc[fname], Memc[fname], SZ_FNAME)
+ ext = ext + 1
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext)
+ iferr (buf = immap (Memc[imname], READ_WRITE, 0)) {
+ buf = NULL
+ ext = 0
+ }
+ repeat {
+ call sprintf (Memc[imname], SZ_FNAME, "%s[%d]")
+ call pargstr (Memc[fname])
+ call pargi (ext+1)
+ iferr (outdata = immap (Memc[imname],READ_WRITE,0))
+ break
+ if (buf != NULL)
+ call imunmap (buf)
+ buf = outdata
+ ext = ext + 1
+ }
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ out[1] = buf
+ }
+
+ call amovkl (long(1), Meml[v1], IM_MAXDIM)
+ call amovkl (long(1), Meml[v2], IM_MAXDIM)
+ call amovkl (long(1), Meml[v3], IM_MAXDIM)
+ $if (datatype == sil)
+ while (impnlr (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_grow$t (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnlr (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amovr (Memr[buf], Memr[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, w, npts, NO, YES,
+ Memr[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, NO, Memr[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, w, npts, NO, NO,
+ Memr[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnlr (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, w, npts, Memr[outdata],
+ Memr[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $else
+ while (impnl$t (out[1], outdata, Meml[v1]) != EOF) {
+ call ic_gdata$t (in, out, dbuf, d, wtp, wbuf, w, id, n, m,
+ lflag, offsets, scales, zeros, nimages, npts,
+ Meml[v2], Meml[v3])
+
+ call ic_grow$t (Meml[v2], d, id, n, Memi[work], nimages, npts,
+ pms)
+
+ if (nkeep > 0) {
+ do i = 1, npts {
+ if (n[i] < nkeep) {
+ Meml[v1+1] = Meml[v1+1] - 1
+ if (imgnl$t (out[1], buf, Meml[v1]) == EOF)
+ ;
+ call amov$t (Mem$t[buf], Mem$t[outdata], npts)
+ break
+ }
+ }
+ }
+
+ switch (combine) {
+ case AVERAGE:
+ call ic_average$t (d, id, n, wts, w, npts, NO, YES,
+ Mem$t[outdata])
+ case MEDIAN:
+ call ic_median$t (d, n, npts, NO, Mem$t[outdata])
+ case SUM:
+ call ic_average$t (d, id, n, wts, w, npts, NO, NO,
+ Mem$t[outdata])
+ }
+
+ if (out[2] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[2], buf, Meml[v1])
+ do i = 1, npts {
+ if (n[i] == 0)
+ Memi[buf] = 1
+ else
+ Memi[buf] = 0
+ }
+ }
+
+ if (out[3] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnl$t (out[3], buf, Meml[v1])
+ call ic_sigma$t (d, id, n, wts, w, npts, Mem$t[outdata],
+ Mem$t[buf])
+ }
+
+ if (out[4] != NULL)
+ call ic_rmasks (out[4], Meml[v2], id, nimages, n, npts)
+
+ if (out[5] != NULL) {
+ call amovl (Meml[v2], Meml[v1], IM_MAXDIM)
+ i = impnli (out[5], buf, Meml[v1])
+ call amovki (nimages, Memi[buf], npts)
+ call asubi (Memi[buf], n, Memi[buf], npts)
+ }
+
+ if (out[6] != NULL)
+ call ic_emask (out[6], Meml[v2], id, nimages, n, wts, w,
+ npts)
+
+ call amovl (Meml[v1], Meml[v2], IM_MAXDIM)
+ }
+ $endif
+
+ do i = 1, nimages
+ call pm_close (Memi[pms+i-1])
+ call mfree (pms, TY_POINTER)
+ }
+
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ }
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icombine.com b/noao/onedspec/odcombine/srcwt/icombine.com
new file mode 100644
index 00000000..42ba4224
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icombine.com
@@ -0,0 +1,46 @@
+# ICOMBINE Common
+
+int combine # Combine algorithm
+int reject # Rejection algorithm
+bool project # Combine across the highest dimension?
+real blank # Blank value
+pointer ictask # Task name for log
+pointer expkeyword # Exposure time keyword
+pointer statsec # Statistics section
+pointer rdnoise # CCD read noise
+pointer gain # CCD gain
+pointer snoise # CCD sensitivity noise
+real lthresh # Low threshold
+real hthresh # High threshold
+int nkeep # Minimum to keep
+real lsigma # Low sigma cutoff
+real hsigma # High sigma cutoff
+real pclip # Number or fraction of pixels from median
+real flow # Fraction of low pixels to reject
+real fhigh # Fraction of high pixels to reject
+real grow # Grow radius
+bool mclip # Use median in sigma clipping?
+real sigscale # Sigma scaling tolerance
+int logfd # Log file descriptor
+
+# These flags allow special conditions to be optimized.
+
+int dflag # Data flag (D_ALL, D_NONE, D_MIX)
+int wtype # Weight type
+bool aligned # Are the images aligned?
+bool doscale # Do the images have to be scaled?
+bool doscale1 # Do the sigma calculations have to be scaled?
+bool dothresh # Check pixels outside specified thresholds?
+bool dowts # Does the final average have to be weighted?
+bool keepids # Keep track of the image indices?
+bool docombine # Call the combine procedure?
+bool sort # Sort data?
+bool verbose # Verbose?
+
+pointer icm # Mask data structure
+
+common /imccom/ combine, reject, blank, ictask, expkeyword, statsec, rdnoise,
+ gain, snoise, lsigma, hsigma, lthresh, hthresh, nkeep,
+ pclip, flow, fhigh, grow, logfd, dflag, wtype, sigscale,
+ project, mclip, aligned, doscale, doscale1, dothresh,
+ dowts, keepids, docombine, sort, verbose, icm
diff --git a/noao/onedspec/odcombine/srcwt/icombine.h b/noao/onedspec/odcombine/srcwt/icombine.h
new file mode 100644
index 00000000..277c79de
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icombine.h
@@ -0,0 +1,56 @@
+# ICOMBINE Definitions
+
+# Memory management parameters;
+define MAXMEMORY 250000000 # maximum memory
+define FUDGE 0.8 # fudge factor
+
+# Rejection options:
+define REJECT "|none|ccdclip|crreject|minmax|pclip|sigclip|avsigclip|"
+define NONE 1 # No rejection algorithm
+define CCDCLIP 2 # CCD noise function clipping
+define CRREJECT 3 # CCD noise function clipping
+define MINMAX 4 # Minmax rejection
+define PCLIP 5 # Percentile clip
+define SIGCLIP 6 # Sigma clip
+define AVSIGCLIP 7 # Sigma clip with average poisson sigma
+
+# Combine options:
+define COMBINE "|average|median|sum|"
+define AVERAGE 1
+define MEDIAN 2
+define SUM 3
+
+# Scaling options:
+define STYPES "|none|mode|median|mean|exposure|"
+define ZTYPES "|none|mode|median|mean|"
+define WTYPES "|none|mode|median|mean|exposure|"
+define S_NONE 1
+define S_MODE 2
+define S_MEDIAN 3
+define S_MEAN 4
+define S_EXPOSURE 5
+define S_FILE 6
+define S_KEYWORD 7
+define S_WTMAP 8
+define S_SIGMAP 9
+
+define S_SECTION "|input|output|overlap|"
+define S_INPUT 1
+define S_OUTPUT 2
+define S_OVERLAP 3
+
+# Mask options
+define MASKTYPES "|none|goodvalue|badvalue|goodbits|badbits|"
+define M_NONE 1 # Don't use mask images
+define M_GOODVAL 2 # Value selecting good pixels
+define M_BADVAL 3 # Value selecting bad pixels
+define M_GOODBITS 4 # Bits selecting good pixels
+define M_BADBITS 5 # Bits selecting bad pixels
+define M_BOOLEAN -1 # Ignore mask values
+
+# Data flag
+define D_ALL 0 # All pixels are good
+define D_NONE 1 # All pixels are bad or rejected
+define D_MIX 2 # Mixture of good and bad pixels
+
+define TOL 0.001 # Tolerance for equal residuals
diff --git a/noao/onedspec/odcombine/srcwt/icombine.x b/noao/onedspec/odcombine/srcwt/icombine.x
new file mode 100644
index 00000000..19add027
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icombine.x
@@ -0,0 +1,488 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <error.h>
+include <syserr.h>
+include "icombine.h"
+
+
+# ICOMBINE -- Combine input list or image.
+# This procedure maps the images, sets the output dimensions and datatype,
+# opens the logfile, and sets IMIO parameters. It attempts to adjust
+# buffer sizes and memory requirements for maximum efficiency.
+
+procedure icombine (list, output, headers, bmask, rmask, nrmask, emask,
+ sigma, logfile, scales, zeros, wts, stack, delete)
+
+int list #I List of input images
+char output[ARB] #I Output image
+char headers[ARB] #I Output header rootname
+char bmask[ARB] #I Bad pixel mask
+char rmask[ARB] #I Rejection mask
+char nrmask[ARB] #I Nreject mask
+char emask[ARB] #I Exposure mask
+char sigma[ARB] #I Sigma image (optional)
+char logfile[ARB] #I Logfile (optional)
+real scales[ARB] #I Scale factors
+real zeros[ARB] #I Offset factors
+real wts[ARB] #I Weights
+int stack #I Stack input images?
+int delete #I Delete input images?
+
+bool proj
+char input[SZ_FNAME], errstr[SZ_LINE]
+int i, j, nimages, intype, bufsize, maxsize, memory, oldsize, stack1, err
+pointer sp, im, in1, in, wtp, out[6], offsets, key, tmp, bpmstack
+
+char clgetc()
+int clgwrd(), imtlen(), imtgetim(), imtrgetim(), getdatatype()
+int begmem(), errget(), open(), ty_max(), sizeof(), strmatch()
+pointer immap(), xt_immap(), ic_pmmap()
+errchk ic_imstack, immap, imunmap, xt_immap, ic_pmmap, ic_setout
+
+include "icombine.com"
+
+define retry_ 98
+define err_ 99
+
+begin
+ nimages = imtlen (list)
+ if (nimages == 0)
+ call error (1, "No images to combine")
+
+ if (project) {
+ if (imtgetim (list, input, SZ_FNAME) == EOF)
+ call error (1, "No image to project")
+ }
+
+ bufsize = 0
+# if (nimages > LAST_FD - 15)
+# stack1 = YES
+# else
+ stack1 = stack
+
+retry_
+ iferr {
+ call smark (sp)
+ call salloc (in, 1, TY_POINTER)
+
+ nimages = 0
+ in1 = NULL; Memi[in] = NULL; logfd = NULL
+ out[1] = NULL; out[2] = NULL; out[3] = NULL
+ out[4] = NULL; out[5] = NULL; out[6] = NULL
+
+ # Stack the input images.
+ if (stack1 == YES) {
+ proj = project
+ project = true
+ call salloc (bpmstack, SZ_FNAME, TY_CHAR)
+ i = clgwrd ("masktype", Memc[bpmstack], SZ_FNAME, MASKTYPES)
+ if (i == M_NONE)
+ Memc[bpmstack] = EOS
+ else {
+ call mktemp ("tmp", Memc[bpmstack], SZ_FNAME)
+ call strcat (".pl", Memc[bpmstack], SZ_FNAME)
+ }
+ call mktemp ("tmp", input, SZ_FNAME)
+ call imtrew (list)
+ call ic_imstack (list, input, Memc[bpmstack])
+ }
+
+ # Open the input image(s).
+ if (project) {
+ tmp = immap (input, READ_ONLY, 0); out[1] = tmp
+ if (IM_NDIM(out[1]) == 1)
+ call error (1, "Can't project one dimensional images")
+ nimages = IM_LEN(out[1],IM_NDIM(out[1]))
+ call salloc (in, nimages, TY_POINTER)
+ call salloc (wtp, nimages, TY_POINTER)
+ call amovki (out[1], Memi[in], nimages)
+ call amovki (NULL, Memi[wtp], nimages)
+ } else {
+ call salloc (in, imtlen(list), TY_POINTER)
+ call salloc (wtp, imtlen(list), TY_POINTER)
+ call amovki (NULL, Memi[in], imtlen(list))
+ call amovki (NULL, Memi[wtp], imtlen(list))
+ call imtrew (list)
+ while (imtgetim (list, input, SZ_FNAME)!=EOF) {
+ nimages = nimages + 1
+ tmp = xt_immap (input, READ_ONLY, 0, nimages)
+ Memi[in+nimages-1] = tmp
+ }
+
+ # Check sizes and set I/O option.
+ intype = 0
+ tmp = Memi[in]
+ do i = 2, nimages {
+ do j = 1, IM_NDIM(tmp) {
+ if (IM_LEN(tmp,j) != IM_LEN(Memi[in+i-1],j))
+ intype = 1
+ }
+ if (intype == 1)
+ break
+ }
+ if (intype == 1)
+ call xt_imseti (0, "option", intype)
+ }
+
+ # Check if there are no images.
+ if (nimages == 0)
+ call error (1, "No images to combine")
+
+ # Convert the pclip parameter to a number of pixels rather than
+ # a fraction. This number stays constant even if pixels are
+ # rejected. The number of low and high pixel rejected, however,
+ # are converted to a fraction of the valid pixels.
+
+ if (reject == PCLIP) {
+ i = nimages / 2.
+ if (abs (pclip) < 1.)
+ pclip = pclip * i
+ if (pclip < 0.)
+ pclip = min (-1, max (-i, int (pclip)))
+ else
+ pclip = max (1, min (i, int (pclip)))
+ }
+
+ if (reject == MINMAX) {
+ if (flow >= 1)
+ flow = flow / nimages
+ if (fhigh >= 1)
+ fhigh = fhigh / nimages
+ i = flow * nimages
+ j = fhigh * nimages
+ if (i + j == 0)
+ reject = NONE
+ else if (i + j >= nimages)
+ call error (1, "Bad minmax rejection parameters")
+ }
+
+ # Map the output image and set dimensions and offsets.
+ if (stack1 == YES) {
+ call imtrew (list)
+ i = imtgetim (list, errstr, SZ_LINE)
+ in1 = immap (errstr, READ_ONLY, 0)
+ tmp = immap (output, NEW_COPY, in1); out[1] = tmp
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ do i = 1, nimages {
+ call sprintf (Memc[key], SZ_FNAME, "stck%04d")
+ call pargi (i)
+ iferr (call imdelf (out[1], Memc[key]))
+ ;
+ if (Memc[bpmstack] != EOS) {
+ call sprintf (Memc[key], SZ_FNAME, "bpm%04d")
+ call pargi (i)
+ iferr (call imdelf (out[1], Memc[key]))
+ ;
+ }
+ }
+ } else {
+ tmp = immap (output, NEW_COPY, Memi[in]); out[1] = tmp
+ if (project) {
+ IM_LEN(out[1],IM_NDIM(out[1])) = 1
+ IM_NDIM(out[1]) = IM_NDIM(out[1]) - 1
+ }
+ }
+ call salloc (offsets, nimages*IM_NDIM(out[1]), TY_INT)
+ iferr (call ic_setout (Memi[in], out, Memi[offsets], nimages)) {
+ call erract (EA_WARN)
+ call error (1, "Can't set output geometry")
+ }
+ call ic_hdr (Memi[in], out, nimages)
+ iferr (call imdelf (out, "BPM"))
+ ;
+ iferr (call imdelf (out, "ICFNAME"))
+ ;
+ iferr (call imdelf (out, "ICBPM"))
+ ;
+
+ # Determine the highest precedence datatype and set output datatype.
+ intype = IM_PIXTYPE(Memi[in])
+ do i = 2, nimages
+ intype = ty_max (intype, IM_PIXTYPE(Memi[in+i-1]))
+ IM_PIXTYPE(out[1]) = getdatatype (clgetc ("outtype"))
+ if (IM_PIXTYPE(out[1]) == ERR)
+ IM_PIXTYPE(out[1]) = intype
+
+ # Open rejection masks
+ if (rmask[1] != EOS) {
+ tmp = ic_pmmap (rmask, NEW_COPY, out[1]); out[4] = tmp
+ IM_NDIM(out[4]) = IM_NDIM(out[4]) + 1
+ IM_LEN(out[4],IM_NDIM(out[4])) = nimages
+ if (!project) {
+ if (key == NULL)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ do i = 100, nimages {
+ j = imtrgetim (list, i, input, SZ_FNAME)
+ if (i < 999)
+ call sprintf (Memc[key], SZ_FNAME, "imcmb%d")
+ else if (i < 9999)
+ call sprintf (Memc[key], SZ_FNAME, "imcm%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "imc%d")
+ call pargi (i)
+ call imastr (out[4], Memc[key], input)
+ }
+ }
+ } else
+ out[4] = NULL
+
+ # Open bad pixel pixel list file if given.
+ if (bmask[1] != EOS) {
+ tmp = ic_pmmap (bmask, NEW_COPY, out[1]); out[2] = tmp
+ } else
+ out[2] = NULL
+
+ # Open nreject pixel list file if given.
+ if (nrmask[1] != EOS) {
+ tmp = ic_pmmap (nrmask, NEW_COPY, out[1]); out[5] = tmp
+ } else
+ out[5] = NULL
+
+ # Open exposure mask if given.
+ if (emask[1] != EOS) {
+ tmp = ic_pmmap (emask, NEW_COPY, out[1]); out[6] = tmp
+ } else
+ out[6] = NULL
+
+ # Open the sigma image if given.
+ if (sigma[1] != EOS) {
+ tmp = immap (sigma, NEW_COPY, out[1]); out[3] = tmp
+ IM_PIXTYPE(out[3]) = ty_max (TY_REAL, IM_PIXTYPE(out[1]))
+ call sprintf (IM_TITLE(out[3]), SZ_IMTITLE,
+ "Combine sigma images for %s")
+ call pargstr (output)
+ } else
+ out[3] = NULL
+
+ # Open masks.
+ call ic_mopen (Memi[in], out, nimages, Memi[offsets])
+
+ # Open the log file.
+ logfd = NULL
+ if (logfile[1] != EOS) {
+ iferr (logfd = open (logfile, APPEND, TEXT_FILE)) {
+ logfd = NULL
+ call erract (EA_WARN)
+ }
+ }
+
+ if (bufsize == 0) {
+ # Set initial IMIO buffer size based on the number of images
+ # and maximum amount of working memory available. The buffer
+ # size may be adjusted later if the task runs out of memory.
+ # The FUDGE factor is used to allow for the size of the
+ # program, memory allocator inefficiencies, and any other
+ # memory requirements besides IMIO.
+
+ memory = begmem (0, oldsize, maxsize)
+ memory = min (memory, maxsize, MAXMEMORY)
+ bufsize = FUDGE * memory / (nimages + 1) / sizeof (intype)
+ }
+
+ # Combine the images. If an out of memory error occurs close all
+ # images and files, divide the IMIO buffer size in half and try
+ # again.
+
+ switch (ty_max (intype, IM_PIXTYPE(out[1]))) {
+ case TY_SHORT:
+ call icombines (Memi[in], out, scales, zeros,
+ wts, Memi[wtp], Memi[offsets], nimages, bufsize)
+ case TY_USHORT, TY_INT, TY_LONG:
+ call icombinei (Memi[in], out, scales, zeros,
+ wts, Memi[wtp], Memi[offsets], nimages, bufsize)
+ case TY_DOUBLE:
+ call icombined (Memi[in], out, scales, zeros,
+ wts, Memi[wtp], Memi[offsets], nimages, bufsize)
+ case TY_COMPLEX:
+ call error (1, "Complex images not allowed")
+ default:
+ call icombiner (Memi[in], out, scales, zeros,
+ wts, Memi[wtp], Memi[offsets], nimages, bufsize)
+ }
+ } then {
+ err = errget (errstr, SZ_LINE)
+ if (err == SYS_IKIOPIX && nimages < 250)
+ err = SYS_MFULL
+ call ic_mclose (nimages)
+ if (!project) {
+ do j = 2, nimages {
+ if (Memi[in+j-1] != NULL)
+ call xt_imunmap (Memi[in+j-1], j)
+ }
+ }
+ if (wtype == S_WTMAP || wtype == S_SIGMAP) {
+ do j = 1, nimages {
+ if (Memi[wtp+j-1] != NULL)
+ call xt_imunmap (Memi[wtp+j-1], nimages+j)
+ }
+ }
+ if (out[2] != NULL) {
+ call imunmap (out[2])
+ iferr (call imdelete (bmask))
+ ;
+ }
+ if (out[3] != NULL) {
+ call imunmap (out[3])
+ iferr (call imdelete (sigma))
+ ;
+ }
+ if (out[4] != NULL) {
+ call imunmap (out[4])
+ iferr (call imdelete (rmask))
+ ;
+ }
+ if (out[5] != NULL) {
+ call imunmap (out[5])
+ iferr (call imdelete (nrmask))
+ ;
+ }
+ if (out[6] != NULL) {
+ call imunmap (out[6])
+ iferr (call imdelete (emask))
+ ;
+ }
+ if (out[1] != NULL) {
+ call imunmap (out[1])
+ iferr (call imdelete (output))
+ ;
+ }
+ if (Memi[in] != NULL)
+ call xt_imunmap (Memi[in], 1)
+ if (in1 != NULL)
+ call imunmap (in1)
+ if (logfd != NULL)
+ call close (logfd)
+
+ switch (err) {
+ case SYS_MFULL:
+ if (project)
+ goto err_
+
+ if (bufsize < 10000) {
+ call strcat ("- Maybe min_lenuserarea is too large",
+ errstr, SZ_LINE)
+ goto err_
+ }
+
+ bufsize = bufsize / 2
+ call sfree (sp)
+ goto retry_
+ case SYS_FTOOMANYFILES, SYS_IKIOPEN, SYS_IKIOPIX, SYS_FOPEN, SYS_FWTNOACC:
+ if (project)
+ goto err_
+ stack1 = YES
+ call sfree (sp)
+ goto retry_
+ default:
+err_
+ if (stack1 == YES) {
+ iferr (call imdelete (input))
+ ;
+ if (Memc[bpmstack] != EOS) {
+ iferr (call imdelete (Memc[bpmstack]))
+ ;
+ }
+ }
+ call fixmem (oldsize)
+ while (imtgetim (list, input, SZ_FNAME)!=EOF)
+ ;
+ call sfree (sp)
+ call error (err, errstr)
+ }
+ }
+
+ # Unmap all the images, close the log file, and restore memory.
+ if (out[2] != NULL)
+ iferr (call imunmap (out[2]))
+ call erract (EA_WARN)
+ if (out[3] != NULL)
+ iferr (call imunmap (out[3]))
+ call erract (EA_WARN)
+ if (out[4] != NULL) {
+ # Close the output first so that there is no confusion with
+ # inheriting the output header. Then update the WCS for the
+ # extra dimension. Note that this may not be correct with
+ # axis reduced WCS.
+ iferr {
+ call imunmap (out[4])
+ out[4] = immap (rmask, READ_WRITE, 0)
+ i = IM_NDIM(out[4])
+ call imaddi (out[4], "WCSDIM", i)
+ call sprintf (errstr, SZ_LINE, "LTM%d_%d")
+ call pargi (i)
+ call pargi (i)
+ call imaddr (out[4], errstr, 1.)
+ call sprintf (errstr, SZ_LINE, "CD%d_%d")
+ call pargi (i)
+ call pargi (i)
+ call imaddr (out[4], errstr, 1.)
+ call imunmap (out[4])
+ } then
+ call erract (EA_WARN)
+ }
+ if (out[5] != NULL)
+ iferr (call imunmap (out[5]))
+ call erract (EA_WARN)
+ if (out[6] != NULL)
+ iferr (call imunmap (out[6]))
+ call erract (EA_WARN)
+ if (out[1] != NULL) {
+ call imunmap (out[1])
+ if (headers[1] != EOS) {
+ # Write input headers to a multiextension file if desired.
+ # This might be the same as the output image.
+ iferr {
+ do i = 1, nimages {
+ im = Memi[in+i-1]
+ call imstats (im, IM_IMAGENAME, input, SZ_FNAME)
+ if (strmatch (headers, ".fits$") == 0) {
+ call sprintf (errstr, SZ_LINE, "%s.fits[append]")
+ call pargstr (headers)
+ } else {
+ call sprintf (errstr, SZ_LINE, "%s[append]")
+ call pargstr (headers)
+ }
+ tmp = immap (errstr, NEW_COPY, im)
+ IM_NDIM(tmp) = 0
+ do j = 1, IM_NDIM(im) {
+ call sprintf (errstr, SZ_LINE, "AXLEN%d")
+ call pargi (j)
+ call imaddi (tmp, errstr, IM_LEN(im,j))
+ }
+ call imastr (tmp, "INIMAGE", input)
+ call imastr (tmp, "OUTIMAGE", output)
+ call imastr (tmp, "EXTNAME", input)
+ call imunmap (tmp)
+ }
+ if (logfd != NULL) {
+ call eprintf (" Headers = %s\n")
+ call pargstr (headers)
+ }
+ } then
+ call erract (EA_WARN)
+ }
+ }
+ if (!project) {
+ do i = 2, nimages {
+ if (Memi[in+i-1] != NULL)
+ call xt_imunmap (Memi[in+i-1], i)
+ }
+ }
+ if (Memi[in] != NULL)
+ call xt_imunmap (Memi[in], 1)
+ if (in1 != NULL)
+ call imunmap (in1)
+ if (stack1 == YES) {
+ call imdelete (input)
+ if (Memc[bpmstack] != EOS)
+ call imdelete (Memc[bpmstack])
+ project = proj
+ }
+ if (logfd != NULL)
+ call close (logfd)
+ call ic_mclose (nimages)
+ call fixmem (oldsize)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icpclip.gx b/noao/onedspec/odcombine/srcwt/icpclip.gx
new file mode 100644
index 00000000..f0c76369
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icpclip.gx
@@ -0,0 +1,233 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number for clipping
+
+$for (sird)
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclip$t (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+$if (datatype == sil)
+real med
+$else
+PIXEL med
+$endif
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Mem$t[d[n2-1]+j]
+ med = (med + Mem$t[d[n2]+j]) / 2.
+ } else
+ med = Mem$t[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Mem$t[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Mem$t[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Mem$t[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Mem$t[d[n5-1]+j]
+ med = (med + Mem$t[d[n5]+j]) / 2.
+ } else
+ med = Mem$t[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+j] = Mem$t[d[k]+j]
+ if (grow >= 1.) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+j] = Mem$t[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icpmmap.x b/noao/onedspec/odcombine/srcwt/icpmmap.x
new file mode 100644
index 00000000..1afeedd7
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icpmmap.x
@@ -0,0 +1,34 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <pmset.h>
+
+
+# IC_PMMAP -- Map pixel mask.
+
+pointer procedure ic_pmmap (fname, mode, refim)
+
+char fname[ARB] # Mask name
+int mode # Image mode
+pointer refim # Reference image
+pointer pm # IMIO pointer (returned)
+
+int i, fnextn()
+pointer sp, extn, immap()
+bool streq()
+
+begin
+ call smark (sp)
+ call salloc (extn, SZ_FNAME, TY_CHAR)
+
+ i = fnextn (fname, Memc[extn], SZ_FNAME)
+ if (streq (Memc[extn], "pl"))
+ pm = immap (fname, mode, refim)
+ else {
+ call strcpy (fname, Memc[extn], SZ_FNAME)
+ call strcat (".pl", Memc[extn], SZ_FNAME)
+ pm = immap (Memc[extn], mode, refim)
+ }
+
+ call sfree (sp)
+ return (pm)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icrmasks.x b/noao/onedspec/odcombine/srcwt/icrmasks.x
new file mode 100644
index 00000000..8b9a0c3d
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icrmasks.x
@@ -0,0 +1,41 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+
+
+# IC_RMASKS -- Set pixels for rejection mask.
+
+procedure ic_rmasks (pm, v, id, nimages, n, npts)
+
+pointer pm #I Pixel mask
+long v[ARB] #I Output vector (input)
+pointer id[nimages] #I Image id pointers
+int nimages #I Number of images
+int n[npts] #I Number of good pixels
+int npts #I Number of output points per line
+
+int i, j, k, ndim, impnls()
+long v1[IM_MAXDIM]
+pointer buf
+
+begin
+ ndim = IM_NDIM(pm)
+ do k = 1, nimages {
+ call amovl (v, v1, ndim-1)
+ v1[ndim] = k
+ i = impnls (pm, buf, v1)
+ do j = 1, npts {
+ if (n[j] == nimages)
+ Mems[buf+j-1] = 0
+ else {
+ Mems[buf+j-1] = 1
+ do i = 1, n[j] {
+ if (Memi[id[i]+j-1] == k) {
+ Mems[buf+j-1] = 0
+ break
+ }
+ }
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/odcombine/srcwt/icscale.x b/noao/onedspec/odcombine/srcwt/icscale.x
new file mode 100644
index 00000000..e38fc3fd
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icscale.x
@@ -0,0 +1,391 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include "icombine.h"
+
+
+# IC_SCALE -- Get and set the scaling factors.
+#
+# If the scaling parameters have been set earlier then this routine
+# just normalizes the factors and writes the log output.
+# When dealing with individual images using image statistics for scaling
+# factors this routine determines the image statistics rather than being
+# done earlier since the input images have all been mapped at this stage.
+
+procedure ic_scale (in, out, offsets, scales, zeros, wts, wtp nimages, npts)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int offsets[nimages,ARB] # Image offsets
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+pointer wtp[nimages] # Weight image pointers
+int nimages # Number of images
+int npts # Number of points per output line
+
+int stype, ztype
+int i, j, k, l, nout
+real mode, median, mean, sumwts
+pointer sp, ncombine, exptime, modes, medians, means
+pointer section, str, sname, zname, wname, im, imref
+bool domode, domedian, domean, dozero, dos, doz, dow, snorm, znorm, wflag
+
+int imgeti(), strdic(), ic_gscale()
+real imgetr(), asumr(), asumi()
+pointer xt_immap(), xt_opix()
+errchk ic_gscale, xt_immap, xt_opix, ic_statr
+
+include "icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (ncombine, nimages, TY_INT)
+ call salloc (exptime, nimages, TY_REAL)
+ call salloc (modes, nimages, TY_REAL)
+ call salloc (medians, nimages, TY_REAL)
+ call salloc (means, nimages, TY_REAL)
+ call salloc (section, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (sname, SZ_FNAME, TY_CHAR)
+ call salloc (zname, SZ_FNAME, TY_CHAR)
+ call salloc (wname, SZ_FNAME, TY_CHAR)
+
+ # Get the number of images previously combined and the exposure times.
+ # The default combine number is 1 and the default exposure is 0.
+
+ do i = 1, nimages {
+ iferr (Memi[ncombine+i-1] = imgeti (in[i], "ncombine"))
+ Memi[ncombine+i-1] = 1
+ if (Memc[expkeyword] != EOS) {
+ iferr (Memr[exptime+i-1] = imgetr (in[i], Memc[expkeyword]))
+ Memr[exptime+i-1] = 0.
+ } else
+ Memr[exptime+i-1] = 0.
+ if (project) {
+ call amovki (Memi[ncombine], Memi[ncombine], nimages)
+ call amovkr (Memr[exptime], Memr[exptime], nimages)
+ break
+ }
+ }
+
+ # Set scaling type and factors.
+ stype = ic_gscale ("scale", Memc[sname], STYPES, in, Memr[exptime],
+ scales, nimages)
+ ztype = ic_gscale ("zero", Memc[zname], ZTYPES, in, Memr[exptime],
+ zeros, nimages)
+ wtype = ic_gscale ("weight", Memc[wname], WTYPES, in, Memr[exptime],
+ wts, nimages)
+
+ # Check for syntax error.
+ if (stype == S_WTMAP || stype == S_SIGMAP ||
+ ztype == S_WTMAP || ztype == S_SIGMAP)
+ call error (1, "Unknown scale or zero type")
+
+ # Open maps if needed.
+ if (wtype == S_WTMAP) {
+ do i = 1, nimages {
+ call imgstr (in[i], Memc[wname+6], Memc[str], SZ_LINE)
+ wtp[i] = xt_immap (Memc[str], READ_ONLY, 0, nimages+i)
+ }
+ } else if (wtype == S_SIGMAP) {
+ do i = 1, nimages {
+ call imgstr (in[i], Memc[wname+7], Memc[str], SZ_LINE)
+ wtp[i] = xt_immap (Memc[str], READ_ONLY, 0, nimages+i)
+ }
+ }
+ if (wtp[1] != NULL) {
+ # Check maps match the input images.
+ iferr {
+ do i = 1, nimages {
+ k = IM_NDIM(in[i])
+ if (IM_NDIM(wtp[i]) != k)
+ call error (1, "Weight maps don't match images")
+ do j = 1, k {
+ if (IM_LEN(in[i],j) != IM_LEN(wtp[i],j))
+ call error (1, "Weight maps don't match images")
+ }
+ }
+ } then {
+ do i = 1, nimages
+ call xt_imunmap (wtp[i], nimages+i)
+ call error (1, "Weight maps don't match images")
+ }
+ }
+
+
+ # Get image statistics if needed.
+ dos = ((stype==S_MODE)||(stype==S_MEDIAN)||(stype==S_MEAN))
+ doz = ((ztype==S_MODE)||(ztype==S_MEDIAN)||(ztype==S_MEAN))
+ dow = ((wtype==S_MODE)||(wtype==S_MEDIAN)||(wtype==S_MEAN))
+ if (dos) {
+ dos = false
+ do i = 1, nimages
+ if (IS_INDEFR(scales[i])) {
+ dos = true
+ break
+ }
+ }
+ if (doz) {
+ doz = false
+ do i = 1, nimages
+ if (IS_INDEFR(zeros[i])) {
+ doz = true
+ break
+ }
+ }
+ if (dow) {
+ dow = false
+ do i = 1, nimages
+ if (IS_INDEFR(wts[i])) {
+ dow = true
+ break
+ }
+ }
+
+ if (dos || doz || dow) {
+ domode = ((stype==S_MODE)||(ztype==S_MODE)||(wtype==S_MODE))
+ domedian = ((stype==S_MEDIAN)||(ztype==S_MEDIAN)||(wtype==S_MEDIAN))
+ domean = ((stype==S_MEAN)||(ztype==S_MEAN)||(wtype==S_MEAN))
+
+ Memc[section] = EOS
+ Memc[str] = EOS
+ call sscan (Memc[statsec])
+ call gargwrd (Memc[section], SZ_FNAME)
+ call gargwrd (Memc[str], SZ_LINE)
+
+ i = strdic (Memc[section], Memc[section], SZ_FNAME, S_SECTION)
+ switch (i) {
+ case S_INPUT:
+ call strcpy (Memc[str], Memc[section], SZ_FNAME)
+ imref = NULL
+ case S_OUTPUT:
+ call strcpy (Memc[str], Memc[section], SZ_FNAME)
+ imref = out[1]
+ case S_OVERLAP:
+ call strcpy ("[", Memc[section], SZ_FNAME)
+ do i = 1, IM_NDIM(out[1]) {
+ k = offsets[1,i] + 1
+ l = offsets[1,i] + IM_LEN(in[1],i)
+ do j = 2, nimages {
+ k = max (k, offsets[j,i]+1)
+ l = min (l, offsets[j,i]+IM_LEN(in[j],i))
+ }
+ if (i < IM_NDIM(out[1]))
+ call sprintf (Memc[str], SZ_LINE, "%d:%d,")
+ else
+ call sprintf (Memc[str], SZ_LINE, "%d:%d]")
+ call pargi (k)
+ call pargi (l)
+ call strcat (Memc[str], Memc[section], SZ_FNAME)
+ }
+ imref = out[1]
+ default:
+ imref = NULL
+ }
+
+ do i = 1, nimages {
+ im = xt_opix (in[i], i, 0)
+ if (imref != out[1])
+ imref = im
+ if ((dos && IS_INDEFR(scales[i])) ||
+ (doz && IS_INDEFR(zeros[i])) ||
+ (dow && IS_INDEFR(wts[i]))) {
+ call ic_statr (im, imref, Memc[section], offsets, i,
+ nimages, domode, domedian, domean, mode, median, mean)
+ if (domode) {
+ if (stype == S_MODE && IS_INDEFR(scales[i]))
+ scales[i] = mode
+ if (ztype == S_MODE && IS_INDEFR(zeros[i]))
+ zeros[i] = mode
+ if (wtype == S_MODE && IS_INDEFR(wts[i]))
+ wts[i] = mode
+ }
+ if (domedian) {
+ if (stype == S_MEDIAN && IS_INDEFR(scales[i]))
+ scales[i] = median
+ if (ztype == S_MEDIAN && IS_INDEFR(zeros[i]))
+ zeros[i] = median
+ if (wtype == S_MEDIAN && IS_INDEFR(wts[i]))
+ wts[i] = median
+ }
+ if (domean) {
+ if (stype == S_MEAN && IS_INDEFR(scales[i]))
+ scales[i] = mean
+ if (ztype == S_MEAN && IS_INDEFR(zeros[i]))
+ zeros[i] = mean
+ if (wtype == S_MEAN && IS_INDEFR(wts[i]))
+ wts[i] = mean
+ }
+ }
+ }
+ }
+
+ # Save the image statistics if computed.
+ call amovkr (INDEFR, Memr[modes], nimages)
+ call amovkr (INDEFR, Memr[medians], nimages)
+ call amovkr (INDEFR, Memr[means], nimages)
+ if (stype == S_MODE)
+ call amovr (scales, Memr[modes], nimages)
+ if (stype == S_MEDIAN)
+ call amovr (scales, Memr[medians], nimages)
+ if (stype == S_MEAN)
+ call amovr (scales, Memr[means], nimages)
+ if (ztype == S_MODE)
+ call amovr (zeros, Memr[modes], nimages)
+ if (ztype == S_MEDIAN)
+ call amovr (zeros, Memr[medians], nimages)
+ if (ztype == S_MEAN)
+ call amovr (zeros, Memr[means], nimages)
+ if (wtype == S_MODE)
+ call amovr (wts, Memr[modes], nimages)
+ if (wtype == S_MEDIAN)
+ call amovr (wts, Memr[medians], nimages)
+ if (wtype == S_MEAN)
+ call amovr (wts, Memr[means], nimages)
+
+ # If nothing else has set the scaling factors set them to defaults.
+ do i = 1, nimages {
+ if (IS_INDEFR(scales[i]))
+ scales[i] = 1.
+ if (IS_INDEFR(zeros[i]))
+ zeros[i] = 0.
+ if (IS_INDEFR(wts[i]))
+ wts[i] = 1.
+ }
+
+ do i = 1, nimages
+ if (scales[i] <= 0.) {
+ call eprintf ("WARNING: Negative scale factors")
+ call eprintf (" -- ignoring scaling\n")
+ call amovkr (1., scales, nimages)
+ break
+ }
+
+ # Convert to factors relative to the first image.
+ snorm = (stype == S_FILE || stype == S_KEYWORD)
+ znorm = (ztype == S_FILE || ztype == S_KEYWORD)
+ wflag = (wtype == S_FILE || wtype == S_KEYWORD)
+ if (snorm)
+ call arcpr (1., scales, scales, nimages)
+ mean = scales[1]
+ call adivkr (scales, mean, scales, nimages)
+ call adivr (zeros, scales, zeros, nimages)
+
+ if (!(wtype == S_NONE || wtype == S_WTMAP || wtype == S_SIGMAP)) {
+ do i = 1, nimages {
+ if (wts[i] < 0.) {
+ call eprintf ("WARNING: Negative weights")
+ call eprintf (" -- using only NCOMBINE weights\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1]
+ break
+ }
+ if (ztype == S_NONE || znorm || wflag)
+ wts[i] = Memi[ncombine+i-1] * wts[i]
+ else {
+ if (zeros[i] <= 0.) {
+ call eprintf ("WARNING: Negative zero offsets")
+ call eprintf (" -- ignoring zero weight adjustments\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1] * wts[j]
+ break
+ }
+ wts[i] = Memi[ncombine+i-1] * wts[i] * zeros[1] / zeros[i]
+ }
+ }
+ }
+
+ if (znorm)
+ call anegr (zeros, zeros, nimages)
+ else {
+ # Because of finite arithmetic it is possible for the zero offsets
+ # to be nonzero even when they are all equal. Just for the sake of
+ # a nice log set the zero offsets in this case.
+
+ mean = zeros[1]
+ call asubkr (zeros, mean, zeros, nimages)
+ for (i=2; (i<=nimages)&&(zeros[i]==zeros[1]); i=i+1)
+ ;
+ if (i > nimages)
+ call aclrr (zeros, nimages)
+ }
+ mean = asumr (wts, nimages)
+ if (mean > 0.)
+ call adivkr (wts, mean, wts, nimages)
+ else {
+ call eprintf ("WARNING: Mean weight is zero -- using no weights\n")
+ call amovkr (1., wts, nimages)
+ mean = 1.
+ }
+
+ # Set flags for scaling, zero offsets, sigma scaling, weights.
+ # Sigma scaling may be suppressed if the scales or zeros are
+ # different by a specified tolerance.
+
+ doscale = false
+ dozero = false
+ doscale1 = false
+ dowts = false
+ do i = 2, nimages {
+ if (snorm || scales[i] != scales[1])
+ doscale = true
+ if (znorm || zeros[i] != zeros[1])
+ dozero = true
+ if (wts[i] != wts[1])
+ dowts = true
+ }
+ if (doscale && sigscale != 0.) {
+ do i = 1, nimages {
+ if (abs (scales[i] - 1) > sigscale) {
+ doscale1 = true
+ break
+ }
+ }
+ }
+
+ # Set the output header parameters.
+ nout = asumi (Memi[ncombine], nimages)
+ call imaddi (out[1], "ncombine", nout)
+ mean = 0.
+ sumwts = 0.
+ do i = 1, nimages {
+ ifnoerr (mode = imgetr (in[i], "ccdmean")) {
+ mean = mean + wts[i] * mode / scales[i]
+ sumwts = sumwts + wts[i]
+ }
+ }
+ if (sumwts > 0.) {
+ mean = mean / sumwts
+ ifnoerr (mode = imgetr (out[1], "ccdmean")) {
+ call imaddr (out[1], "ccdmean", mean)
+ iferr (call imdelf (out[1], "ccdmeant"))
+ ;
+ }
+ }
+ if (out[2] != NULL) {
+ call imstats (out[2], IM_IMAGENAME, Memc[str], SZ_FNAME)
+ call imastr (out[1], "BPM", Memc[str])
+ }
+
+ # Start the log here since much of the info is only available here.
+ if (verbose) {
+ i = logfd
+ logfd = STDOUT
+ call ic_log (in, out, Memi[ncombine], Memr[exptime], Memc[sname],
+ Memc[zname], Memc[wname], Memr[modes], Memr[medians],
+ Memr[means], scales, zeros, wts, offsets, nimages, dozero,
+ nout)
+
+ logfd = i
+ }
+ call ic_log (in, out, Memi[ncombine], Memr[exptime], Memc[sname],
+ Memc[zname], Memc[wname], Memr[modes], Memr[medians], Memr[means],
+ scales, zeros, wts, offsets, nimages, dozero, nout)
+
+ doscale = (doscale || dozero)
+ dowts = (dowts || (wtype == S_WTMAP) || (wtype == S_SIGMAP))
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icsclip.gx b/noao/onedspec/odcombine/srcwt/icsclip.gx
new file mode 100644
index 00000000..1b1c5de9
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icsclip.gx
@@ -0,0 +1,504 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Mininum number of images for algorithm
+
+$for (sird)
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclip$t (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+$else
+PIXEL average[npts] # Average
+$endif
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+$if (datatype == sil)
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+$else
+PIXEL d1, low, high, sum, a, s, r, one
+data one /1$f/
+$endif
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Mem$t[d[1]+k]
+ do j = 2, n1
+ sum = sum + Mem$t[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Mem$t[d[1]+k]
+ high = Mem$t[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Mem$t[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mem$t[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Mem$t[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Mem$t[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Mem$t[dp1]
+ Mem$t[dp1] = Mem$t[dp2]
+ Mem$t[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Mem$t[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclip$t (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+$if (datatype == sil)
+real median[npts] # Median
+$else
+PIXEL median[npts] # Median
+$endif
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+$if (datatype == sil)
+real med, one
+data one /1.0/
+$else
+PIXEL med, one
+data one /1$f/
+$endif
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Mem$t[d[n3-1]+k] + Mem$t[d[n3]+k]) / 2.
+ else
+ med = Mem$t[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Mem$t[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Mem$t[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Mem$t[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Mem$t[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow >= 1.)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ if (grow >= 1.) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Mem$t[d[l]+k] = Mem$t[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icsection.x b/noao/onedspec/odcombine/srcwt/icsection.x
new file mode 100644
index 00000000..746c1f51
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icsection.x
@@ -0,0 +1,94 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <ctype.h>
+
+# IC_SECTION -- Parse an image section into its elements.
+# 1. The default values must be set by the caller.
+# 2. A null image section is OK.
+# 3. The first nonwhitespace character must be '['.
+# 4. The last interpreted character must be ']'.
+#
+# This procedure should be replaced with an IMIO procedure at some
+# point.
+
+procedure ic_section (section, x1, x2, xs, ndim)
+
+char section[ARB] # Image section
+int x1[ndim] # Starting pixel
+int x2[ndim] # Ending pixel
+int xs[ndim] # Step
+int ndim # Number of dimensions
+
+int i, ip, a, b, c, temp, ctoi()
+define error_ 99
+
+begin
+ # Decode the section string.
+ ip = 1
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == '[')
+ ip = ip + 1
+ else if (section[ip] == EOS)
+ return
+ else
+ goto error_
+
+ do i = 1, ndim {
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ']')
+ break
+
+ # Default values
+ a = x1[i]
+ b = x2[i]
+ c = xs[i]
+
+ # Get a:b:c. Allow notation such as "-*:c"
+ # (or even "-:c") where the step is obviously negative.
+
+ if (ctoi (section, ip, temp) > 0) { # a
+ a = temp
+ if (section[ip] == ':') {
+ ip = ip + 1
+ if (ctoi (section, ip, b) == 0) # a:b
+ goto error_
+ } else
+ b = a
+ } else if (section[ip] == '-') { # -*
+ temp = a
+ a = b
+ b = temp
+ ip = ip + 1
+ if (section[ip] == '*')
+ ip = ip + 1
+ } else if (section[ip] == '*') # *
+ ip = ip + 1
+ if (section[ip] == ':') { # ..:step
+ ip = ip + 1
+ if (ctoi (section, ip, c) == 0)
+ goto error_
+ else if (c == 0)
+ goto error_
+ }
+ if (a > b && c > 0)
+ c = -c
+
+ x1[i] = a
+ x2[i] = b
+ xs[i] = c
+
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == ',')
+ ip = ip + 1
+ }
+
+ if (section[ip] != ']')
+ goto error_
+
+ return
+error_
+ call error (0, "Error in image section specification")
+end
diff --git a/noao/onedspec/odcombine/srcwt/icsetout.x b/noao/onedspec/odcombine/srcwt/icsetout.x
new file mode 100644
index 00000000..51e1fe90
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icsetout.x
@@ -0,0 +1,322 @@
+include <imhdr.h>
+include <imset.h>
+include <mwset.h>
+
+define OFFTYPES "|none|wcs|world|physical|grid|"
+define FILE 0
+define NONE 1
+define WCS 2
+define WORLD 3
+define PHYSICAL 4
+define GRID 5
+
+# IC_SETOUT -- Set output image size and offsets of input images.
+
+procedure ic_setout (in, out, offsets, nimages)
+
+pointer in[nimages] # Input images
+pointer out[ARB] # Output images
+int offsets[nimages,ARB] # Offsets
+int nimages # Number of images
+
+int i, j, indim, outdim, mwdim, a, b, amin, bmax, fd, offtype
+real val
+bool proj, reloff, flip, streq(), fp_equald()
+pointer sp, str, fname
+pointer ltv, lref, wref, cd, ltm, coord, shift, axno, axval, section
+pointer mw, ct, mw_openim(), mw_sctran(), xt_immap()
+int open(), fscan(), nscan(), mw_stati(), strlen(), strdic()
+errchk mw_openim, mw_gwtermd, mw_gltermd, mw_gaxmap
+errchk mw_sctran, mw_ctrand, open, xt_immap
+
+include "icombine.com"
+define newscan_ 10
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (ltv, IM_MAXDIM, TY_DOUBLE)
+ call salloc (ltm, IM_MAXDIM*IM_MAXDIM, TY_DOUBLE)
+ call salloc (lref, IM_MAXDIM, TY_DOUBLE)
+ call salloc (wref, IM_MAXDIM, TY_DOUBLE)
+ call salloc (cd, IM_MAXDIM*IM_MAXDIM, TY_DOUBLE)
+ call salloc (coord, IM_MAXDIM, TY_DOUBLE)
+ call salloc (shift, IM_MAXDIM, TY_REAL)
+ call salloc (axno, IM_MAXDIM, TY_INT)
+ call salloc (axval, IM_MAXDIM, TY_INT)
+
+ # Check and set the image dimensionality.
+ indim = IM_NDIM(in[1])
+ outdim = IM_NDIM(out[1])
+ proj = (indim != outdim)
+ if (!proj) {
+ do i = 1, nimages
+ if (IM_NDIM(in[i]) != outdim) {
+ call sfree (sp)
+ call error (1, "Image dimensions are not the same")
+ }
+ }
+
+ # Set the reference point to that of the first image.
+ mw = mw_openim (in[1])
+ call mw_seti (mw, MW_USEAXMAP, NO)
+ mwdim = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gwtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[lref], mwdim)
+ call mw_ctfree (ct)
+ if (proj)
+ Memd[lref+outdim] = 1
+
+ # Parse the user offset string. If "none" then there are no offsets.
+ # If "world" or "wcs" then set the offsets based on the world WCS.
+ # If "physical" then set the offsets based on the physical WCS.
+ # If "grid" then set the offsets based on the input grid parameters.
+ # If a file scan it.
+
+ call clgstr ("offsets", Memc[fname], SZ_FNAME)
+ call sscan (Memc[fname])
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (nscan() == 0)
+ offtype = NONE
+ else {
+ offtype = strdic (Memc[fname], Memc[str], SZ_FNAME, OFFTYPES)
+ if (offtype > 0 && !streq (Memc[fname], Memc[str]))
+ offtype = 0
+ }
+ if (offtype == 0)
+ offtype = FILE
+
+ switch (offtype) {
+ case NONE:
+ call aclri (offsets, outdim*nimages)
+ reloff = true
+ case WORLD, WCS:
+ do j = 1, outdim
+ offsets[1,j] = 0
+ if (proj) {
+ ct = mw_sctran (mw, "world", "logical", 0)
+ do i = 2, nimages {
+ Memd[wref+outdim] = i
+ call mw_ctrand (ct, Memd[wref], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ }
+ call mw_ctfree (ct)
+ call mw_close (mw)
+ } else {
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[lref], indim)
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ ct = mw_sctran (mw, "world", "logical", 0)
+ call mw_ctrand (ct, Memd[wref], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ call mw_ctfree (ct)
+ }
+ }
+ reloff = true
+ case PHYSICAL:
+ call salloc (section, SZ_FNAME, TY_CHAR)
+
+ call mw_gltermd (mw, Memd[ltm], Memd[coord], indim)
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ call mw_gltermd (mw, Memd[cd], Memd[coord], indim)
+ call strcpy ("[", Memc[section], SZ_FNAME)
+ flip = false
+ do j = 0, indim*indim-1, indim+1 {
+ if (Memd[ltm+j] * Memd[cd+j] >= 0.)
+ call strcat ("*,", Memc[section], SZ_FNAME)
+ else {
+ call strcat ("-*,", Memc[section], SZ_FNAME)
+ flip = true
+ }
+ }
+ Memc[section+strlen(Memc[section])-1] = ']'
+ if (flip) {
+ call imstats (in[i], IM_IMAGENAME, Memc[fname], SZ_FNAME)
+ call strcat (Memc[section], Memc[fname], SZ_FNAME)
+ call xt_imunmap (in[i], i)
+ in[i] = xt_immap (Memc[fname], READ_ONLY, TY_CHAR, i)
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ call mw_gltermd (mw, Memd[cd], Memd[coord], indim)
+ do j = 0, indim*indim-1
+ if (!fp_equald (Memd[ltm+j], Memd[cd+j]))
+ call error (1,
+ "Cannot match physical coordinates")
+ }
+ }
+
+ call mw_close (mw)
+ mw = mw_openim (in[1])
+ ct = mw_sctran (mw, "logical", "physical", 0)
+ call mw_ctrand (ct, Memd[lref], Memd[ltv], indim)
+ call mw_ctfree (ct)
+ do j = 1, outdim
+ offsets[1,j] = 0
+ if (proj) {
+ ct = mw_sctran (mw, "physical", "logical", 0)
+ do i = 2, nimages {
+ Memd[ltv+outdim] = i
+ call mw_ctrand (ct, Memd[ltv], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ }
+ call mw_ctfree (ct)
+ call mw_close (mw)
+ } else {
+ do i = 2, nimages {
+ call mw_close (mw)
+ mw = mw_openim (in[i])
+ ct = mw_sctran (mw, "physical", "logical", 0)
+ call mw_ctrand (ct, Memd[ltv], Memd[coord], indim)
+ do j = 1, outdim
+ offsets[i,j] = nint (Memd[lref+j-1] - Memd[coord+j-1])
+ call mw_ctfree (ct)
+ }
+ }
+ reloff = true
+ case GRID:
+ amin = 1
+ do j = 1, outdim {
+ call gargi (a)
+ call gargi (b)
+ if (nscan() < 1+2*j) {
+ a = 1
+ b = 0
+ }
+ do i = 1, nimages
+ offsets[i,j] = mod ((i-1)/amin, a) * b
+ amin = amin * a
+ }
+ reloff = true
+ case FILE:
+ reloff = true
+ fd = open (Memc[fname], READ_ONLY, TEXT_FILE)
+ do i = 1, nimages {
+newscan_ if (fscan (fd) == EOF)
+ call error (1, "IMCOMBINE: Offset list too short")
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (Memc[fname] == '#') {
+ call gargwrd (Memc[fname], SZ_FNAME)
+ call strlwr (Memc[fname])
+ if (streq (Memc[fname], "absolute"))
+ reloff = false
+ else if (streq (Memc[fname], "relative"))
+ reloff = true
+ goto newscan_
+ }
+ call reset_scan ()
+ do j = 1, outdim {
+ call gargr (val)
+ offsets[i,j] = nint (val)
+ }
+ if (nscan() < outdim)
+ call error (1, "IMCOMBINE: Error in offset list")
+ }
+ call close (fd)
+ }
+
+ # Set the output image size and the aligned flag
+ aligned = true
+ do j = 1, outdim {
+ a = offsets[1,j]
+ b = IM_LEN(in[1],j) + a
+ amin = a
+ bmax = b
+ do i = 2, nimages {
+ a = offsets[i,j]
+ b = IM_LEN(in[i],j) + a
+ if (a != amin || b != bmax || !reloff)
+ aligned = false
+ amin = min (a, amin)
+ bmax = max (b, bmax)
+ }
+ IM_LEN(out[1],j) = bmax
+ if (reloff || amin < 0) {
+ do i = 1, nimages
+ offsets[i,j] = offsets[i,j] - amin
+ IM_LEN(out[1],j) = IM_LEN(out[1],j) - amin
+ }
+ }
+
+ # Get the output limits.
+ call clgstr ("outlimits", Memc[fname], SZ_FNAME)
+ call sscan (Memc[fname])
+ do j = 1, outdim {
+ call gargi (a)
+ call gargi (b)
+ if (nscan() < 2*j)
+ break
+ if (!IS_INDEFI(a)) {
+ do i = 1, nimages {
+ offsets[i,j] = offsets[i,j] - a + 1
+ if (offsets[i,j] != 0)
+ aligned = false
+ }
+ IM_LEN(out[1],j) = IM_LEN(out[1],j) - a + 1
+ }
+ if (!IS_INDEFI(a) && !IS_INDEFI(b))
+ IM_LEN(out[1],j) = min (IM_LEN(out[1],j), b - a + 1)
+ }
+
+ # Update the WCS.
+ if (proj || !aligned || !reloff) {
+ call mw_close (mw)
+ mw = mw_openim (out[1])
+ mwdim = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gaxmap (mw, Memi[axno], Memi[axval], mwdim)
+ if (!aligned || !reloff) {
+ call mw_gltermd (mw, Memd[cd], Memd[lref], mwdim)
+ do i = 1, mwdim {
+ j = Memi[axno+i-1]
+ if (j > 0 && j <= indim)
+ Memd[lref+i-1] = Memd[lref+i-1] + offsets[1,j]
+ }
+ if (proj)
+ Memd[lref+mwdim-1] = 0.
+ call mw_sltermd (mw, Memd[cd], Memd[lref], mwdim)
+ }
+ if (proj) {
+ # Apply dimensional reduction.
+ do i = 1, mwdim {
+ j = Memi[axno+i-1]
+ if (j <= outdim)
+ next
+ else if (j > outdim+1)
+ Memi[axno+i-1] = j - 1
+ else {
+ Memi[axno+i-1] = 0
+ Memi[axval+i-1] = 0
+ }
+ }
+ call mw_saxmap (mw, Memi[axno], Memi[axval], mwdim)
+ }
+
+ # Reset physical coordinates.
+ if (offtype == WCS || offtype == WORLD) {
+ call mw_gltermd (mw, Memd[ltm], Memd[ltv], mwdim)
+ call mw_gwtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ call mwvmuld (Memd[ltm], Memd[lref], Memd[lref], mwdim)
+ call aaddd (Memd[lref], Memd[ltv], Memd[lref], mwdim)
+ call mwinvertd (Memd[ltm], Memd[ltm], mwdim)
+ call mwmmuld (Memd[cd], Memd[ltm], Memd[cd], mwdim)
+ call mw_swtermd (mw, Memd[lref], Memd[wref], Memd[cd], mwdim)
+ call aclrd (Memd[ltv], mwdim)
+ call aclrd (Memd[ltm], mwdim*mwdim)
+ do i = 1, mwdim
+ Memd[ltm+(i-1)*(mwdim+1)] = 1.
+ call mw_sltermd (mw, Memd[ltm], Memd[ltv], mwdim)
+ }
+ call mw_saveim (mw, out)
+ }
+ call mw_close (mw)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/srcwt/icsigma.gx b/noao/onedspec/odcombine/srcwt/icsigma.gx
new file mode 100644
index 00000000..b664fd24
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icsigma.gx
@@ -0,0 +1,154 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+$for (sird)
+# IC_SIGMA -- Compute the sigma image line.
+# The estimated sigma includes a correction for the finite population.
+# Weights are used if desired.
+
+procedure ic_sigma$t (d, m, n, wts, w, npts, average, sigma)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+pointer w[ARB] # Weight data pointers
+int npts # Number of output points per line
+$if (datatype == sil)
+real average[npts] # Average
+real sigma[npts] # Sigma line (returned)
+$else
+PIXEL average[npts] # Average
+PIXEL sigma[npts] # Sigma line (returned)
+$endif
+
+int i, j, k, n1
+real wt, sigcor, sumwt
+$if (datatype == sil)
+real a, sum
+$else
+PIXEL a, sum
+$endif
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_ALL && w[1] == NULL) {
+ n1 = n[1]
+ if (dowts) {
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mem$t[d[1]+k] - a) ** 2 * wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2 * wt
+ }
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ } else {
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ do i = 1, npts {
+ k = i - 1
+ a = average[i]
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sigma[i] = blank
+ } else {
+ if (dowts) {
+ if (w[1] == NULL) {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = wts[Memi[m[1]+k]]
+ sum = (Mem$t[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = real (n1) / real (n1 -1)
+ else
+ sigcor = 1
+ a = average[i]
+ wt = Memr[w[Memi[m[1]+k]]+k]
+ sum = (Mem$t[d[1]+k] - a) ** 2 * wt
+ sumwt = wt
+ do j = 2, n1 {
+ wt = Memr[w[Memi[m[j]+k]]+k]
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2 * wt
+ sumwt = sumwt + wt
+ }
+ if (sumwt > 0)
+ sigma[i] = sqrt (sum / sumwt * sigcor)
+ else {
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum / n1 * sigcor)
+ }
+ } else
+ sigma[i] = blank
+ }
+ }
+ } else {
+ do i = 1, npts {
+ n1 = n[i]
+ if (n1 > 0) {
+ k = i - 1
+ if (n1 > 1)
+ sigcor = 1. / real (n1 - 1)
+ else
+ sigcor = 1.
+ a = average[i]
+ sum = (Mem$t[d[1]+k] - a) ** 2
+ do j = 2, n1
+ sum = sum + (Mem$t[d[j]+k] - a) ** 2
+ sigma[i] = sqrt (sum * sigcor)
+ } else
+ sigma[i] = blank
+ }
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icsort.gx b/noao/onedspec/odcombine/srcwt/icsort.gx
new file mode 100644
index 00000000..e124da15
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icsort.gx
@@ -0,0 +1,386 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+define LOGPTR 32 # log2(maxpts) (4e9)
+
+$for (sird)
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sort$t (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+PIXEL b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+PIXEL pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Mem$t[a[i]+l]
+
+ # Special cases
+ $if (datatype == x)
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (abs (temp) < abs (pivot)) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (abs (temp) < abs (pivot)) { # bac|bca|cba
+ if (abs (temp) < abs (temp3)) { # bac|bca
+ b[1] = temp
+ if (abs (pivot) < abs (temp3)) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (abs (temp3) < abs (temp)) { # acb|cab
+ b[3] = temp
+ if (abs (pivot) < abs (temp3)) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $else
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $endif
+
+ # General case
+ do i = 1, npix
+ b[i] = Mem$t[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ $if (datatype == x)
+ for (i=i+1; abs(b[i]) < abs(pivot); i=i+1)
+ $else
+ for (i=i+1; b[i] < pivot; i=i+1)
+ $endif
+ ;
+ for (j=j-1; j > i; j=j-1)
+ $if (datatype == x)
+ if (abs(b[j]) <= abs(pivot))
+ $else
+ if (b[j] <= pivot)
+ $endif
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Mem$t[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sort$t (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+PIXEL b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+PIXEL pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Mem$t[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ $if (datatype == x)
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (abs (temp) < abs (pivot)) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (abs (temp) < abs (pivot)) { # bac|bca|cba
+ if (abs (temp) < abs (temp3)) { # bac|bca
+ b[1] = temp
+ if (abs (pivot) < abs (temp3)) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (abs (temp3) < abs (temp)) { # acb|cab
+ b[3] = temp
+ if (abs (pivot) < abs (temp3)) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $else
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ d[3] = d[1]
+ d[1] = itemp
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+ $endif
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ $if (datatype == x)
+ for (i=i+1; abs(b[i]) < abs(pivot); i=i+1)
+ $else
+ for (i=i+1; b[i] < pivot; i=i+1)
+ $endif
+ ;
+ for (j=j-1; j > i; j=j-1)
+ $if (datatype == x)
+ if (abs(b[j]) <= abs(pivot))
+ $else
+ if (b[j] <= pivot)
+ $endif
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Mem$t[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/icstat.gx b/noao/onedspec/odcombine/srcwt/icstat.gx
new file mode 100644
index 00000000..c594182b
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/icstat.gx
@@ -0,0 +1,238 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+define NMAX 100000 # Maximum number of pixels to sample
+
+$for (sird)
+# IC_STAT -- Compute image statistics within specified section.
+# The image section is relative to a reference image which may be
+# different than the input image and may have an offset. Only a
+# subsample of pixels is used. Masked and thresholded pixels are
+# ignored. Only the desired statistics are computed to increase
+# efficiency.
+
+procedure ic_stat$t (im, imref, section, offsets, image, nimages,
+ domode, domedian, domean, mode, median, mean)
+
+pointer im # Data image
+pointer imref # Reference image for image section
+char section[ARB] # Image section
+int offsets[nimages,ARB] # Image section offset from data to reference
+int image # Image index (for mask I/O)
+int nimages # Number of images in offsets.
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, j, ndim, n, nv
+real a
+pointer sp, v1, v2, dv, va, vb
+pointer data, mask, dp, lp, mp, imgnl$t()
+
+$if (datatype == csir)
+real asum$t()
+$else $if (datatype == ld)
+double asum$t()
+$else
+PIXEL asum$t()
+$endif $endif
+PIXEL ic_mode$t()
+
+include "../icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (v1, IM_MAXDIM, TY_LONG)
+ call salloc (v2, IM_MAXDIM, TY_LONG)
+ call salloc (dv, IM_MAXDIM, TY_LONG)
+ call salloc (va, IM_MAXDIM, TY_LONG)
+ call salloc (vb, IM_MAXDIM, TY_LONG)
+
+ # Determine the image section parameters. This must be in terms of
+ # the data image pixel coordinates though the section may be specified
+ # in terms of the reference image coordinates. Limit the number of
+ # pixels in each dimension to a maximum.
+
+ ndim = IM_NDIM(im)
+ if (project)
+ ndim = ndim - 1
+ call amovki (1, Memi[v1], IM_MAXDIM)
+ call amovki (1, Memi[va], IM_MAXDIM)
+ call amovki (1, Memi[dv], IM_MAXDIM)
+ call amovi (IM_LEN(imref,1), Memi[vb], ndim)
+ call ic_section (section, Memi[va], Memi[vb], Memi[dv], ndim)
+ if (im != imref)
+ do i = 1, ndim {
+ Memi[va+i-1] = Memi[va+i-1] - offsets[image,i]
+ Memi[vb+i-1] = Memi[vb+i-1] - offsets[image,i]
+ }
+
+ do j = 1, 10 {
+ n = 1
+ do i = 0, ndim-1 {
+ Memi[v1+i] = max (1, min (Memi[va+i], Memi[vb+i]))
+ Memi[v2+i] = min (IM_LEN(im,i+1), max (Memi[va+i], Memi[vb+i]))
+ Memi[dv+i] = j
+ nv = max (1, (Memi[v2+i] - Memi[v1+i]) / Memi[dv+i] + 1)
+ Memi[v2+i] = Memi[v1+i] + (nv - 1) * Memi[dv+i]
+ n = n * nv
+ }
+ if (n < NMAX)
+ break
+ }
+
+ call amovl (Memi[v1], Memi[va], IM_MAXDIM)
+ Memi[va] = 1
+ if (project)
+ Memi[va+ndim] = image
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+
+ # Accumulate the pixel values within the section. Masked pixels and
+ # thresholded pixels are ignored.
+
+ call salloc (data, n, TY_PIXEL)
+ dp = data
+ while (imgnl$t (im, lp, Memi[vb]) != EOF) {
+ call ic_mget1 (im, image, nimages, offsets[image,1], Memi[va], mask)
+ lp = lp + Memi[v1] - 1
+ if (dflag == D_ALL) {
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ a = Mem$t[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mem$t[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ Mem$t[dp] = Mem$t[lp]
+ dp = dp + 1
+ lp = lp + Memi[dv]
+ }
+ }
+ } else if (dflag == D_MIX) {
+ mp = mask + Memi[v1] - 1
+ if (dothresh) {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ a = Mem$t[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Mem$t[dp] = a
+ dp = dp + 1
+ }
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ } else {
+ do i = Memi[v1], Memi[v2], Memi[dv] {
+ if (Memi[mp] == 0) {
+ Mem$t[dp] = Mem$t[lp]
+ dp = dp + 1
+ }
+ mp = mp + Memi[dv]
+ lp = lp + Memi[dv]
+ }
+ }
+ }
+ for (i=2; i<=ndim; i=i+1) {
+ Memi[va+i-1] = Memi[va+i-1] + Memi[dv+i-1]
+ if (Memi[va+i-1] <= Memi[v2+i-1])
+ break
+ Memi[va+i-1] = Memi[v1+i-1]
+ }
+ if (i > ndim)
+ break
+ call amovl (Memi[va], Memi[vb], IM_MAXDIM)
+ }
+
+ # Close mask until it is needed again.
+ call ic_mclose1 (image, nimages)
+
+ n = dp - data
+ if (n < 1) {
+ call sfree (sp)
+ call error (1, "Image section contains no pixels")
+ }
+
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrt$t (Mem$t[data], Mem$t[data], n)
+ mode = ic_mode$t (Mem$t[data], n)
+ median = Mem$t[data+n/2-1]
+ }
+ if (domean)
+ mean = asum$t (Mem$t[data], n) / n
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.7 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+PIXEL procedure ic_mode$t (a, n)
+
+PIXEL a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+PIXEL mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+ $if (datatype == sil)
+ zstep = max (1., zstep)
+ zbin = max (1., zbin)
+ $endif
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/mkpkg b/noao/onedspec/odcombine/srcwt/mkpkg
new file mode 100644
index 00000000..2ed3d8cb
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/mkpkg
@@ -0,0 +1,62 @@
+ Make the IMCOMBINE Task.
+
+$checkout libpkg.a ../../../../
+$update libpkg.a
+$checkin libpkg.a ../../../../
+$exit
+
+generic:
+ $set GEN = "$$generic -k"
+
+ $ifolder (generic/icaclip.x, icaclip.gx)
+ $(GEN) icaclip.gx -o generic/icaclip.x $endif
+ $ifolder (generic/icaverage.x, icaverage.gx)
+ $(GEN) icaverage.gx -o generic/icaverage.x $endif
+ $ifolder (generic/iccclip.x, iccclip.gx)
+ $(GEN) iccclip.gx -o generic/iccclip.x $endif
+ $ifolder (generic/icgdata.x, icgdata.gx)
+ $(GEN) icgdata.gx -o generic/icgdata.x $endif
+ $ifolder (generic/icgrow.x, icgrow.gx)
+ $(GEN) icgrow.gx -o generic/icgrow.x $endif
+ $ifolder (generic/icmedian.x, icmedian.gx)
+ $(GEN) icmedian.gx -o generic/icmedian.x $endif
+ $ifolder (generic/icmm.x, icmm.gx)
+ $(GEN) icmm.gx -o generic/icmm.x $endif
+ $ifolder (generic/icomb.x, icomb.gx)
+ $(GEN) icomb.gx -o generic/icomb.x $endif
+ $ifolder (generic/icpclip.x, icpclip.gx)
+ $(GEN) icpclip.gx -o generic/icpclip.x $endif
+ $ifolder (generic/icsclip.x, icsclip.gx)
+ $(GEN) icsclip.gx -o generic/icsclip.x $endif
+ $ifolder (generic/icsigma.x, icsigma.gx)
+ $(GEN) icsigma.gx -o generic/icsigma.x $endif
+ $ifolder (generic/icsort.x, icsort.gx)
+ $(GEN) icsort.gx -o generic/icsort.x $endif
+ $ifolder (generic/icstat.x, icstat.gx)
+ $(GEN) icstat.gx -o generic/icstat.x $endif
+
+ $ifolder (generic/xtimmap.x, xtimmap.gx)
+ $(GEN) xtimmap.gx -o generic/xtimmap.x $endif
+ ;
+
+libpkg.a:
+ $ifeq (USE_GENERIC, yes) $call generic $endif
+
+ @generic
+
+ icemask.x <imhdr.h> <mach.h>
+ icgscale.x icombine.com icombine.h
+ ichdr.x <imset.h>
+ icimstack.x <error.h> <imhdr.h>
+ iclog.x icmask.h icombine.com icombine.h <imhdr.h> <imset.h>\
+ <mach.h>
+ icmask.x icmask.h icombine.com icombine.h <imhdr.h> <pmset.h>
+ icombine.x icombine.com icombine.h <error.h> <imhdr.h> <imset.h>
+ icpmmap.x <pmset.h>
+ icrmasks.x <imhdr.h>
+ icscale.x icombine.com icombine.h <imhdr.h> <imset.h>
+ icsection.x <ctype.h>
+ icsetout.x icombine.com <imhdr.h> <imset.h> <mwset.h>
+ tymax.x <mach.h>
+ xtprocid.x
+ ;
diff --git a/noao/onedspec/odcombine/srcwt/tymax.x b/noao/onedspec/odcombine/srcwt/tymax.x
new file mode 100644
index 00000000..a7f4f469
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/tymax.x
@@ -0,0 +1,27 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <mach.h>
+
+
+# TY_MAX -- Return the datatype of highest precedence.
+
+int procedure ty_max (type1, type2)
+
+int type1, type2 # Datatypes
+
+int i, j, type, order[8]
+data order/TY_SHORT,TY_USHORT,TY_INT,TY_LONG,TY_REAL,TY_DOUBLE,TY_COMPLEX,TY_REAL/
+
+begin
+ for (i=1; (i<=7) && (type1!=order[i]); i=i+1)
+ ;
+ for (j=1; (j<=7) && (type2!=order[j]); j=j+1)
+ ;
+ type = order[max(i,j)]
+
+ # Special case of mixing short and unsigned short.
+ if (type == TY_USHORT && type1 != type2)
+ type = TY_INT
+
+ return (type)
+end
diff --git a/noao/onedspec/odcombine/srcwt/xtimmap.com b/noao/onedspec/odcombine/srcwt/xtimmap.com
new file mode 100644
index 00000000..61bf314a
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/xtimmap.com
@@ -0,0 +1,8 @@
+int option
+int nopen
+int nopenpix
+int nalloc
+int last_flag
+int min_open
+pointer ims
+common /xtimmapcom/ option, ims, nopen, nopenpix, nalloc, last_flag, min_open
diff --git a/noao/onedspec/odcombine/srcwt/xtimmap.gx b/noao/onedspec/odcombine/srcwt/xtimmap.gx
new file mode 100644
index 00000000..c0ae26a6
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/xtimmap.gx
@@ -0,0 +1,552 @@
+include <syserr.h>
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <config.h>
+
+# The following is for compiling under V2.11.
+define IM_BUFFRAC IM_BUFSIZE
+include <imset.h>
+
+# These routines maintain an arbitrary number of indexed "open" images which
+# must be READ_ONLY. The calling program may use the returned pointer for
+# header accesses but must call xt_opix before I/O. Subsequent calls to
+# xt_opix may invalidate the pointer. The xt_imunmap call will free memory.
+
+define MAX_OPENIM (LAST_FD-16) # Maximum images kept open
+define MAX_OPENPIX 45 # Maximum pixel files kept open
+
+define XT_SZIMNAME 299 # Size of IMNAME string
+define XT_LEN 179 # Structure length
+define XT_IMNAME Memc[P2C($1)] # Image name
+define XT_ARG Memi[$1+150] # IMMAP header argument
+define XT_IM Memi[$1+151] # IMIO pointer
+define XT_HDR Memi[$1+152] # Copy of IMIO pointer
+define XT_CLOSEFD Memi[$1+153] # Close FD?
+define XT_FLAG Memi[$1+154] # Flag
+define XT_BUFSIZE Memi[$1+155] # Buffer size
+define XT_BUF Memi[$1+156] # Data buffer
+define XT_BTYPE Memi[$1+157] # Data buffer type
+define XT_VS Memi[$1+157+$2] # Start vector (10)
+define XT_VE Memi[$1+167+$2] # End vector (10)
+
+# Options
+define XT_MAPUNMAP 1 # Map and unmap images.
+
+# XT_IMMAP -- Map an image and save it as an indexed open image.
+# The returned pointer may be used for header access but not I/O.
+# The indexed image is closed by xt_imunmap.
+
+pointer procedure xt_immap (imname, acmode, hdr_arg, index)
+
+char imname[ARB] #I Image name
+int acmode #I Access mode
+int hdr_arg #I Header argument
+int index #I Save index
+pointer im #O Image pointer (returned)
+
+int i, envgeti()
+pointer xt, xt_opix()
+errchk xt_opix
+
+int first_time
+data first_time /YES/
+
+include "../xtimmap.com"
+
+begin
+ if (acmode != READ_ONLY)
+ call error (1, "XT_IMMAP: Only READ_ONLY allowed")
+
+ # Initialize once per process.
+ if (first_time == YES) {
+ iferr (option = envgeti ("imcombine_option"))
+ option = 1
+ min_open = 1
+ nopen = 0
+ nopenpix = 0
+ nalloc = MAX_OPENIM
+ call calloc (ims, nalloc, TY_POINTER)
+ first_time = NO
+ }
+
+ # Free image if needed.
+ call xt_imunmap (NULL, index)
+
+ # Allocate structure.
+ if (index > nalloc) {
+ i = nalloc
+ nalloc = index + MAX_OPENIM
+ call realloc (ims, nalloc, TY_STRUCT)
+ call amovki (NULL, Memi[ims+i], nalloc-i)
+ }
+ call calloc (xt, XT_LEN, TY_STRUCT)
+ Memi[ims+index-1] = xt
+
+ # Initialize.
+ call strcpy (imname, XT_IMNAME(xt), XT_SZIMNAME)
+ XT_ARG(xt) = hdr_arg
+ XT_IM(xt) = NULL
+ XT_HDR(xt) = NULL
+
+ # Open image.
+ last_flag = 0
+ im = xt_opix (NULL, index, 0)
+
+ # Make copy of IMIO pointer for header keyword access.
+ call malloc (XT_HDR(xt), LEN_IMDES+IM_HDRLEN(im)+1, TY_STRUCT)
+ call amovi (Memi[im], Memi[XT_HDR(xt)], LEN_IMDES)
+ call amovi (IM_MAGIC(im), IM_MAGIC(XT_HDR(xt)), IM_HDRLEN(im)+1)
+
+ return (XT_HDR(xt))
+end
+
+
+# XT_OPIX -- Open the image for I/O.
+# If the image has not been mapped return the default pointer.
+
+pointer procedure xt_opix (imdef, index, flag)
+
+int index #I index
+pointer imdef #I Default pointer
+int flag #I Flag
+
+int i, open(), imstati()
+pointer im, xt, xt1, immap()
+errchk open, immap, imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imdef)
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Return pointer for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (im)
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || flag == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ if (!IS_INDEFI(XT_BUFSIZE(xt)))
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ else
+ XT_BUFSIZE(xt) = imstati (im, IM_BUFSIZE)
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (im)
+end
+
+
+# XT_CPIX -- Close image.
+
+procedure xt_cpix (index)
+
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL)
+ return
+
+ if (XT_IM(xt) != NULL) {
+ call imunmap (XT_IM(xt))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ }
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+end
+
+
+# XT_IMSETI -- Set IMIO value.
+
+procedure xt_imseti (index, param, value)
+
+int index #I index
+int param #I IMSET parameter
+int value #I Value
+
+pointer xt
+bool streq()
+
+include "../xtimmap.com"
+
+begin
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ if (xt == NULL) {
+ if (streq (param, "option"))
+ option = value
+ } else {
+ if (streq (param, "bufsize")) {
+ XT_BUFSIZE(xt) = value
+ if (XT_IM(xt) != NULL) {
+ call imseti (XT_IM(xt), IM_BUFFRAC, 0)
+ call imseti (XT_IM(xt), IM_BUFSIZE, value)
+ }
+ }
+ }
+end
+
+
+# XT_IMUNMAP -- Unmap indexed open image.
+# The header pointer is set to NULL to indicate the image has been closed.
+
+procedure xt_imunmap (im, index)
+
+int im #U IMIO header pointer
+int index #I index
+
+pointer xt
+errchk imunmap
+
+include "../xtimmap.com"
+
+begin
+ # Check for an indexed image. If it is not unmap the pointer
+ # as a regular IMIO pointer.
+
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+ if (xt == NULL) {
+ if (im != NULL)
+ call imunmap (im)
+ return
+ }
+
+ # Close indexed image.
+ if (XT_IM(xt) != NULL) {
+ iferr (call imunmap (XT_IM(xt))) {
+ XT_IM(xt) = NULL
+ call erract (EA_WARN)
+ }
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt) == NO)
+ nopenpix = nopenpix - 1
+ if (index == min_open)
+ min_open = 1
+ }
+
+ # Free any buffered memory.
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+
+ # Free header pointer. Note that if the supplied pointer is not
+ # header pointer then it is not set to NULL.
+ if (XT_HDR(xt) == im)
+ im = NULL
+ call mfree (XT_HDR(xt), TY_STRUCT)
+
+ # Free save structure.
+ call mfree (Memi[ims+index-1], TY_STRUCT)
+ Memi[ims+index-1] = NULL
+end
+
+
+# XT_REINDEX -- Reindex open images.
+# This is used when some images are closed by xt_imunmap. It is up to
+# the calling program to reindex the header pointers and to subsequently
+# use the new index values.
+
+procedure xt_reindex ()
+
+int old, new
+
+include "../xtimmap.com"
+
+begin
+ new = 0
+ do old = 0, nalloc-1 {
+ if (Memi[ims+old] == NULL)
+ next
+ Memi[ims+new] = Memi[ims+old]
+ new = new + 1
+ }
+ do old = new, nalloc-1
+ Memi[ims+old] = NULL
+end
+
+
+$for(sird)
+# XT_IMGNL -- Return the next line for the indexed image.
+# Possibly unmap another image if too many files are open.
+# Buffer data when an image is unmmaped to minimize the mapping of images.
+# If the requested index has not been mapped use the default pointer.
+
+int procedure xt_imgnl$t (imdef, index, buf, v, flag)
+
+pointer imdef #I Default pointer
+int index #I index
+pointer buf #O Data buffer
+long v[ARB] #I Line vector
+int flag #I Flag (=output line)
+
+int i, j, nc, nl, open(), imgnl$t(), sizeof(), imloop()
+pointer im, xt, xt1, ptr, immap(), imggs$t()
+errchk open, immap, imgnl$t, imggs$t, imunmap
+
+long unit_v[IM_MAXDIM]
+data unit_v /IM_MAXDIM * 1/
+
+include "../xtimmap.com"
+
+begin
+ # Get index pointer.
+ xt = NULL
+ if (index <= nalloc && index > 0)
+ xt = Memi[ims+index-1]
+
+ # Use default pointer if index has not been mapped.
+ if (xt == NULL)
+ return (imgnl$t (imdef, buf, v))
+
+ # Close images not accessed during previous line.
+ # In normal usage this should only occur once per line over all
+ # indexed images.
+ if (flag != last_flag) {
+ do i = 1, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL || XT_FLAG(xt1) == last_flag)
+ next
+ call imunmap (XT_IM(xt1))
+ call mfree (XT_BUF(xt1), XT_BTYPE(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ }
+
+ # Optimize the file I/O.
+ do i = nalloc, 1, -1 {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+ min_open = i
+ if (nopenpix < MAX_OPENPIX) {
+ if (XT_CLOSEFD(xt1) == NO)
+ next
+ XT_CLOSEFD(xt1) = NO
+ call imseti (im, IM_CLOSEFD, NO)
+ nopenpix = nopenpix + 1
+ }
+ }
+ last_flag = flag
+ }
+
+ # Use IMIO for already opened images.
+ im = XT_IM(xt)
+ if (im != NULL) {
+ XT_FLAG(xt) = flag
+ return (imgnl$t (im, buf, v))
+ }
+
+ # If the image is not currently mapped use the stored header.
+ im = XT_HDR(xt)
+
+ # Check for EOF.
+ i = IM_NDIM(im)
+ if (v[i] > IM_LEN(im,i))
+ return (EOF)
+
+ # Check for buffered data.
+ if (XT_BUF(xt) != NULL) {
+ if (v[2] >= XT_VS(xt,2) && v[2] <= XT_VE(xt,2)) {
+ if (XT_BTYPE(xt) != TY_PIXEL)
+ call error (1, "Cannot mix data types")
+ nc = IM_LEN(im,1)
+ buf = XT_BUF(xt) + (v[2]-XT_VS(xt,2)) * IM_LEN(im,1)
+ XT_FLAG(xt) = flag
+ if (i == 1)
+ v[1] = nc + 1
+ else
+ j = imloop (v, unit_v, IM_LEN(im,1), unit_v, i)
+ return (nc)
+ }
+ }
+
+ # Handle more images than the maximum that can be open at one time.
+ if (nopen >= MAX_OPENIM) {
+ if (option == XT_MAPUNMAP || v[2] == 0) {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ im = XT_IM(xt1)
+ if (im == NULL)
+ next
+
+ # Buffer some number of lines.
+ nl = XT_BUFSIZE(xt1) / sizeof (TY_PIXEL) / IM_LEN(im,1)
+ if (nl > 1) {
+ nc = IM_LEN(im,1)
+ call amovl (v, XT_VS(xt1,1), IM_MAXDIM)
+ call amovl (v, XT_VE(xt1,1), IM_MAXDIM)
+ XT_VS(xt1,1) = 1
+ XT_VE(xt1,1) = nc
+ XT_VE(xt1,2) = min (XT_VS(xt1,2)+(nl-1), IM_LEN(im,2))
+ nl = XT_VE(xt1,2) - XT_VS(xt1,2) + 1
+ XT_BTYPE(xt1) = TY_PIXEL
+ call malloc (XT_BUF(xt1), nl*nc, XT_BTYPE(xt1))
+ ptr = imggs$t (im, XT_VS(xt1,1), XT_VE(xt1,1),
+ IM_NDIM(im))
+ call amov$t (Mem$t[ptr], Mem$t[XT_BUF(xt1)], nl*nc)
+ }
+
+ call imunmap (XT_IM(xt1))
+ nopen = nopen - 1
+ if (XT_CLOSEFD(xt1) == NO)
+ nopenpix = nopenpix - 1
+ min_open = i + 1
+ break
+ }
+ if (index <= min_open)
+ min_open = index
+ else {
+ do i = min_open, nalloc {
+ xt1 = Memi[ims+i-1]
+ if (xt1 == NULL)
+ next
+ if (XT_IM(xt1) == NULL)
+ next
+ min_open = i
+ break
+ }
+ }
+ } else {
+ # Check here because we can't catch error in immap.
+ i = open ("dev$null", READ_ONLY, BINARY_FILE)
+ call close (i)
+ if (i == LAST_FD - 1)
+ call error (SYS_FTOOMANYFILES, "Too many open files")
+ }
+ }
+
+ # Open image.
+ im = immap (XT_IMNAME(xt), READ_ONLY, XT_ARG(xt))
+ XT_IM(xt) = im
+ call imseti (im, IM_BUFSIZE, XT_BUFSIZE(xt))
+ call mfree (XT_BUF(xt), XT_BTYPE(xt))
+ nopen = nopen + 1
+ XT_CLOSEFD(xt) = YES
+ if (nopenpix < MAX_OPENPIX) {
+ XT_CLOSEFD(xt) = NO
+ nopenpix = nopenpix + 1
+ }
+ if (XT_CLOSEFD(xt) == YES)
+ call imseti (im, IM_CLOSEFD, YES)
+ XT_FLAG(xt) = flag
+
+ return (imgnl$t (im, buf, v))
+end
+$endfor
diff --git a/noao/onedspec/odcombine/srcwt/xtprocid.x b/noao/onedspec/odcombine/srcwt/xtprocid.x
new file mode 100644
index 00000000..0a82d81b
--- /dev/null
+++ b/noao/onedspec/odcombine/srcwt/xtprocid.x
@@ -0,0 +1,38 @@
+# XT_PROCID -- Set or ppdate PROCID keyword.
+
+procedure xt_procid (im)
+
+pointer im #I Image header
+
+int i, j, ver, patmake(), gpatmatch(), strlen(), ctoi()
+pointer sp, pat, str
+
+begin
+ call smark (sp)
+ call salloc (pat, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ # Get current ID.
+ iferr (call imgstr (im, "PROCID", Memc[str], SZ_LINE)) {
+ iferr (call imgstr (im, "OBSID", Memc[str], SZ_LINE)) {
+ call sfree (sp)
+ return
+ }
+ }
+
+ # Set new PROCID.
+ ver = 0
+ i = patmake ("V[0-9]*$", Memc[pat], SZ_LINE)
+ if (gpatmatch (Memc[str], Memc[pat], i, j) == 0)
+ ;
+ if (j > 0) {
+ j = i+1
+ if (ctoi (Memc[str], j, ver) == 0)
+ ver = 0
+ i = i - 1
+ } else
+ i = strlen (Memc[str])
+ call sprintf (Memc[str+i], SZ_LINE, "V%d")
+ call pargi (ver+1)
+ call imastr (im, "PROCID", Memc[str])
+end
diff --git a/noao/onedspec/odcombine/t_odcombine.x b/noao/onedspec/odcombine/t_odcombine.x
new file mode 100644
index 00000000..9f5a58d8
--- /dev/null
+++ b/noao/onedspec/odcombine/t_odcombine.x
@@ -0,0 +1,1071 @@
+include <imhdr.h>
+include <error.h>
+include <mach.h>
+include <mwset.h>
+include <smw.h>
+include "src/icombine.h"
+
+# Grouping options
+define GROUP "|all|images|apertures|"
+define GRP_ALL 1
+define GRP_IMAGES 2
+define GRP_APERTURES 3
+
+# Mask formats
+define MASKFORMATS "|bpmpixel|bpmspectrum|"
+define BPMPIX 1
+define BPMSPEC 2
+
+# Spectrum data structure
+define NS Memi[$1+$2-1] # Number of spec of given ap
+define SH Memi[Memi[$1+$2-1]+$3-1] # Spectrum header structure
+
+
+# T_ODCOMBINE - Combine spectra matched in world coordinates.
+#
+# The input spectra are combined by medianing, averaging or summing with
+# optional rejection, scaling and weighting. The combining algorithms and
+# other features are the same as those in IMCOMBINE.
+#
+# The main difference with IMCOMBINE is that the spectra are first resampled
+# to a common grid of pixels in dispersion. To do this each spectrum
+# is resampled to a temporary file which are then combined and deleted.
+# When bad pixels are used they are also resampled in the same way.
+#
+# Since there can be multiple spectra per file (each with different
+# aperture numbers) there are three ways to group the spectra for combining.
+# One is by image where all spectra in the file are combined. The second
+# is by aperture where the same aperture across multple files are combine.
+# The third is to combine all spectra independent of aperture or file.
+#
+# The structure of the program is to first internally collect all the
+# spectra from each input file. When combining by image this is done file
+# by file otherwise all the files are collected together. The reason for
+# this is to avoid opening, search, reading, and closing the same file
+# for each aperture Then the spectra for one output are rebinned to a
+# common dispersion and written to temporary files. The same is done for
+# bad pixel masks if used. The spectra in the files are then combined.
+# Finally the temporary files are deleted. The rebinning and combine are
+# repeated for each output spectrum.
+
+procedure t_odcombine()
+
+pointer aps # aperture ranges
+int group # grouping option
+
+int mformat, mtype, mvalue
+int i, j, index, naps
+pointer im, mw, refim, shout
+pointer sp, input, output, headers, bmask, rmask, nrmask, emask, sigma, logfile
+pointer tmp, str, s, b, ns
+int ilist1, ilist2, ilist, olist, hlist, blist, rlist, slist, nrlist, elist
+
+bool clgetb()
+int clgeti(), clgwrd()
+int imtopen(), imtopenp(), imtgetim(), imtlen()
+real clgetr()
+pointer rng_open()
+errchk shdr_open, odc_gspec, odc_rebin, odc_output, odc_combine
+
+include "src/icombine.com"
+
+begin
+ # Allocate stack memory. Note some of the variables are declared in
+ # the icombine common block but still need to be allocated here.
+
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (headers, SZ_FNAME, TY_CHAR)
+ call salloc (bmask, SZ_FNAME, TY_CHAR)
+ call salloc (rmask, SZ_FNAME, TY_CHAR)
+ call salloc (nrmask, SZ_FNAME, TY_CHAR)
+ call salloc (emask, SZ_FNAME, TY_CHAR)
+ call salloc (sigma, SZ_FNAME, TY_CHAR)
+ call salloc (logfile, SZ_FNAME, TY_CHAR)
+ call salloc (tmp, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (ictask, SZ_FNAME, TY_CHAR)
+ call salloc (expkeyword, SZ_FNAME, TY_CHAR)
+ call salloc (statsec, SZ_FNAME, TY_CHAR)
+ call salloc (gain, SZ_FNAME, TY_CHAR)
+ call salloc (rdnoise, SZ_FNAME, TY_CHAR)
+ call salloc (snoise, SZ_FNAME, TY_CHAR)
+
+ # Set the IMCOMBINE parameters.
+
+ call strcpy ("ODCOMBINE", Memc[ictask], SZ_FNAME)
+ ilist = imtopenp ("input")
+ olist = imtopenp ("output")
+ hlist = imtopenp ("headers")
+ blist = imtopenp ("bpmasks")
+ rlist = imtopenp ("rejmasks")
+ nrlist = imtopenp ("nrejmasks")
+ elist = imtopenp ("expmasks")
+ slist = imtopenp ("sigmas")
+ call clgstr ("logfile", Memc[logfile], SZ_FNAME)
+
+ project = false
+ combine = clgwrd ("combine", Memc[str], SZ_LINE, COMBINE)
+ reject = clgwrd ("reject", Memc[str], SZ_LINE, REJECT)
+ blank = clgetr ("blank")
+ call clgstr ("expname", Memc[expkeyword], SZ_FNAME)
+ call clgstr ("statsec", Memc[statsec], SZ_FNAME)
+ call clgstr ("gain", Memc[gain], SZ_FNAME)
+ call clgstr ("rdnoise", Memc[rdnoise], SZ_FNAME)
+ call clgstr ("snoise", Memc[snoise], SZ_FNAME)
+ lthresh = clgetr ("lthreshold")
+ hthresh = clgetr ("hthreshold")
+ lsigma = clgetr ("lsigma")
+ hsigma = clgetr ("hsigma")
+ pclip = clgetr ("pclip")
+ flow = clgetr ("nlow")
+ fhigh = clgetr ("nhigh")
+ nkeep = clgeti ("nkeep")
+ grow = clgetr ("grow")
+ mclip = clgetb ("mclip")
+ sigscale = clgetr ("sigscale")
+
+ # Check parameters, map INDEFs, and set threshold flag
+
+ if (pclip == 0. && reject == PCLIP)
+ call error (1, "Pclip parameter may not be zero")
+ if (IS_INDEFR (blank))
+ blank = 0.
+ if (IS_INDEFR (lsigma))
+ lsigma = MAX_REAL
+ if (IS_INDEFR (hsigma))
+ hsigma = MAX_REAL
+ if (IS_INDEFR (pclip))
+ pclip = -0.5
+ if (IS_INDEFR (flow))
+ flow = 0
+ if (IS_INDEFR (fhigh))
+ fhigh = 0
+ if (IS_INDEFR (grow))
+ grow = 0.
+ if (IS_INDEF (sigscale))
+ sigscale = 0.
+
+ if (IS_INDEF(lthresh) && IS_INDEF(hthresh))
+ dothresh = false
+ else {
+ dothresh = true
+ if (IS_INDEF(lthresh))
+ lthresh = -MAX_REAL
+ if (IS_INDEF(hthresh))
+ hthresh = MAX_REAL
+ }
+
+ # Get ODCOMBINE specific parameters.
+
+ call clgstr ("apertures", Memc[str], SZ_LINE)
+ group = clgwrd ("group", Memc[input], SZ_FNAME, GROUP)
+
+ # Expand aperture list.
+ iferr (aps = rng_open (Memc[str], INDEF, INDEF, INDEF))
+ call error (1, "Error in aperture list")
+
+ # We need to know about the mask in order to resample them.
+ # This does not support specifying a mask by name or keyword.
+
+ mformat = clgwrd ("smaskformat", Memc[str], SZ_LINE, MASKFORMATS)
+ mtype = clgwrd ("smasktype", Memc[str], SZ_LINE, MASKTYPES)
+ if (mtype == 0)
+ call error (1, "Unsupported masktype")
+ mvalue = clgeti ("smaskvalue")
+ if (mtype == M_BADBITS && mvalue == 0)
+ mtype = M_NONE
+ if (mtype == M_NONE)
+ call clpstr ("masktype", "none")
+ else
+ call clpstr ("masktype", "goodvalue")
+ call clputi ("maskvalue", 0)
+
+ # Check lists.
+ i = imtlen (ilist)
+ if (i == 0)
+ call error (1, "No input images to combine")
+ switch (group) {
+ case GRP_ALL, GRP_APERTURES:
+ if (imtlen (olist) != 1)
+ call error (1, "Wrong number of output images")
+ if (imtlen (hlist) > 1)
+ call error (1, "Wrong number of header files")
+ if (imtlen (blist) > 1)
+ call error (1, "Wrong number of bad pixel masks")
+ if (imtlen (rlist) > 1)
+ call error (1, "Wrong number of rejection masks")
+ if (imtlen (nrlist) > 1)
+ call error (1, "Wrong number of number rejected masks")
+ if (imtlen (elist) > 1)
+ call error (1, "Wrong number of exposure masks")
+ if (imtlen (slist) > 1)
+ call error (1, "Wrong number of sigma images")
+ case GRP_IMAGES:
+ if (imtlen (olist) != i)
+ call error (1, "Wrong number of output images")
+ if (imtlen (hlist) > 0 && imtlen (hlist) != i)
+ call error (1, "Wrong number of header files")
+ if (imtlen (blist) > 0 && imtlen (blist) != i)
+ call error (1, "Wrong number of bad pixel masks")
+ if (imtlen (rlist) > 0 && imtlen (rlist) != i)
+ call error (1, "Wrong number of rejection masks")
+ if (imtlen (nrlist) > 0 && imtlen (nrlist) != i)
+ call error (1, "Wrong number of number rejected masks")
+ if (imtlen (elist) > 0 && imtlen (elist) != i)
+ call error (1, "Wrong number of exposure masks")
+ if (imtlen (slist) > 1 && imtlen (slist) != i)
+ call error (1, "Wrong number of sigma images")
+ }
+
+ # Set temporary output rootname.
+ call mktemp ("tmp", Memc[tmp], SZ_FNAME)
+
+ # Loop through input images.
+ index = 0
+ while (imtgetim (ilist, Memc[input], SZ_FNAME) != EOF) {
+
+ # Get all requested apertures from an image. When not grouping
+ # by image go through all images and exhaust the input list.
+
+ naps = 0
+ repeat {
+ iferr (call odc_gspec (Memc[input], aps, group, mtype, mformat,
+ s, b, ns, naps)) {
+ if (group == GRP_IMAGES) {
+ call erract (EA_WARN)
+ next
+ } else {
+ call erract (EA_ERROR)
+ }
+ }
+ if (group == GRP_IMAGES)
+ break
+ } until (imtgetim (ilist, Memc[input], SZ_FNAME) == EOF)
+
+ if (naps < 1) {
+ call eprintf ("No input spectra to combine\n")
+ next
+ }
+
+ # Create each output spectrum. This involves rebinning to
+ # temporary files, combining, and cleaning up. The files are
+ # deleted in the odc_combine routine.
+
+ do i = 1, naps {
+
+ # Set the output dispersion in a temporary template image.
+ call odc_output (SH(s,i,1), NS(ns,i), Memc[tmp], im, mw, refim)
+ call shdr_open (im, mw, i, 1, INDEFI, SHDATA, shout)
+
+ # Rebin the spectra.
+ call odc_rebin (im, shout, SH(s,i,1), SH(b,i,1), NS(ns,i),
+ mformat, mtype, mvalue, Memc[tmp])
+
+ # Close and delete the template image.
+ call shdr_close (shout)
+ call smw_close (mw)
+ call imunmap (im)
+ call imunmap (refim)
+ iferr (call imdelete (Memc[tmp]))
+ ;
+
+ # Set lists to be combined.
+ call sprintf (Memc[str], SZ_LINE, "%s.*\\[^x]")
+ call pargstr (Memc[tmp])
+ ilist1 = imtopen (Memc[str])
+ if (mtype != NONE) {
+ call sprintf (Memc[str], SZ_LINE, "%sbpm.*\\[^x]")
+ call pargstr (Memc[tmp])
+ ilist2 = imtopen (Memc[str])
+ } else
+ ilist2 = imtopen ("")
+
+ # Set output names.
+ switch (group) {
+ case GRP_ALL:
+ index = 1
+ j = INDEFI
+ case GRP_IMAGES:
+ index = index + 1
+ j = INDEFI
+ case GRP_APERTURES:
+ index = 1
+ j = AP(SH(s,i,1))
+ }
+ call odc_imtgetim (olist, index, j, Memc[output], SZ_FNAME)
+ call odc_imtgetim (hlist, index, j, Memc[headers], SZ_FNAME)
+ call odc_imtgetim (blist, index, j, Memc[bmask], SZ_FNAME)
+ call odc_imtgetim (rlist, index, j, Memc[rmask], SZ_FNAME)
+ call odc_imtgetim (nrlist, index, j, Memc[nrmask], SZ_FNAME)
+ call odc_imtgetim (elist, index, j, Memc[emask], SZ_FNAME)
+ call odc_imtgetim (slist, index, j, Memc[sigma], SZ_FNAME)
+
+ # Combine and delete the lists.
+ iferr (call odc_combine (ilist1, ilist2, Memc[output],
+ Memc[headers], Memc[bmask], Memc[rmask], Memc[nrmask],
+ Memc[emask], Memc[sigma], Memc[logfile], YES))
+ call erract (EA_WARN)
+
+ call imtclose (ilist1)
+ call imtclose (ilist2)
+ }
+
+ # Free all the spectrum data structures.
+ call odc_fspec (s, b, ns, naps)
+ }
+
+ # Finish up.
+ call rng_close (aps)
+ call imtclose (ilist)
+ call imtclose (olist)
+ call imtclose (hlist)
+ call imtclose (blist)
+ call imtclose (rlist)
+ call imtclose (nrlist)
+ call imtclose (elist)
+ call imtclose (slist)
+ call sfree (sp)
+end
+
+
+# ODC_GSPEC -- Get spectra from an input image.
+#
+# This allocates and sets arrays of spectrum structures. There is an array
+# for each "output" aperture and the number of elements is given by another
+# array (ns). The number of output apertures is given by naps. Note that each
+# call to this accumulates new spectra.
+
+procedure odc_gspec (input, aps, group, mtype, mformat, s, b, ns, naps)
+
+char input[ARB] #I Input spectrum file
+pointer aps #I Apertures to select
+int group #I Grouping for combining
+int mtype #I Mask type
+int mformat #I Mask format
+pointer s #U Spectra data structure
+pointer b #U Spectra data structure for pixel masks
+int ns #U Number of spectra per group
+int naps #U Number of output apertures
+
+int i, j, k, n
+pointer im, mw, sh, sh1, bpm, err
+
+bool rng_elementi()
+pointer immap(), smw_openim()
+errchk immap, smw_openim, shdr_open
+
+begin
+ # Map the input spectrum file. Check format.
+ im = immap (input, READ_ONLY, 0)
+ mw = smw_openim (im)
+ if (SMW_FORMAT(mw) != SMW_ES && SMW_FORMAT(mw) != SMW_MS) {
+ call smw_close (mw)
+ call imunmap (im)
+ call salloc (err, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[err], SZ_LINE,
+ "Unsupported spectral format (%s)")
+ call pargstr (input)
+ call error (1, Memc[err])
+ }
+ sh = NULL
+
+ # Get the associated mask if requested. It is not an error if there
+ # is mask.
+
+ if (mtype == M_NONE)
+ bpm = NULL
+ else {
+ switch (mformat) {
+ case BPMPIX, BPMSPEC:
+ call malloc (bpm, SZ_FNAME, TY_CHAR)
+ iferr (call imgstr (im, "BPM", Memc[bpm], SZ_FNAME))
+ call mfree (bpm, TY_CHAR)
+ default:
+ bpm = NULL
+ }
+ }
+
+ # Select the requested apertures and group by output aperture.
+ do i = 1, SMW_NSPEC(mw) {
+ call shdr_open (im, mw, i, 1, INDEFI, SHDATA, sh)
+ if (!rng_elementi (aps, AP(sh)))
+ next
+
+ if (group == GRP_APERTURES) {
+ for (j=1; j<=naps; j=j+1)
+ if (AP(sh) == AP(SH(s,j,1)))
+ break
+ n = 10
+ } else {
+ j = 1
+ n = 1
+ }
+
+ if (naps == 0) {
+ call calloc (s, n, TY_POINTER)
+ call calloc (b, n, TY_POINTER)
+ call calloc (ns, n, TY_INT)
+ } else if (j > naps && mod (naps, n) == 0) {
+ call realloc (s, naps+n, TY_POINTER)
+ call realloc (b, naps+n, TY_POINTER)
+ call realloc (ns, naps+n, TY_INT)
+ call aclri (Memi[s+naps], n)
+ call aclri (Memi[b+naps], n)
+ call aclri (Memi[ns+naps], n)
+ }
+ if (j > naps)
+ naps = naps + 1
+ n = NS(ns,j)
+ if (n == 0) {
+ call malloc (Memi[s+j-1], 10, TY_POINTER)
+ call malloc (Memi[b+j-1], 10, TY_POINTER)
+ } else if (mod (n, 10) == 0) {
+ call realloc (Memi[s+j-1], n+10, TY_POINTER)
+ call realloc (Memi[b+j-1], n+10, TY_POINTER)
+ }
+
+ n = n + 1
+ SH(s,j,n) = NULL
+ SH(b,j,n) = NULL
+ call shdr_copy (sh, SH(s,j,n), NO)
+ NS(ns,j) = n
+ }
+
+ call imunmap (IM(sh))
+ MW(sh) = NULL
+ call shdr_close (sh)
+
+ # Get BPMs if defined.
+ if (bpm != NULL) {
+ im = immap (Memc[bpm], READ_ONLY, 0)
+ mw = smw_openim (im)
+ sh = NULL
+
+ switch (mformat) {
+ case BPMPIX:
+ do j = 1, naps {
+ n = NS(ns,j)
+ sh1 = SH(s,j,n)
+ if (sh1 == NULL)
+ next
+ k = LINDEX(sh1,1)
+ call shdr_open (im, mw, k, 1, INDEFI, SHDATA, sh)
+ if (LINDEX(sh,1) != k)
+ next
+ call shdr_copy (sh1, SH(b,j,n), YES)
+ sh1 = SH(b,j,n)
+ call strcpy (IMNAME(sh), IMNAME(sh1), LEN_SHDRS)
+ call strcpy (IMSEC(sh), IMSEC(sh1), LEN_SHDRS)
+ call strcpy (TITLE(sh), TITLE(sh1), LEN_SHDRS)
+ call amovi (LINDEX(sh,1), LINDEX(sh1,1), 2)
+ call amovi (PINDEX(sh,1), PINDEX(sh1,1), 2)
+ APINDEX(sh) = APINDEX(sh1)
+ call amovr (Memr[SY(sh)], Memr[SY(sh1)],
+ min (SN(sh), SN(sh1)))
+ }
+ call smw_close (mw)
+ case BPMSPEC:
+ do j = 1, naps {
+ n = NS(ns,j)
+ sh1 = SH(s,j,n)
+ if (sh1 == NULL)
+ next
+ k = AP(sh1)
+ call shdr_open (im, mw, 1, 1, k, SHDATA, sh)
+ if (AP(sh) != k)
+ next
+ call shdr_copy (sh, SH(b,j,n), NO)
+ }
+ }
+
+ call imunmap (IM(sh))
+ MW(sh) = NULL
+ call shdr_close (sh)
+ call mfree (bpm, TY_CHAR)
+ }
+end
+
+
+
+# ODC_FSPEC -- Free spectrum data structures.
+
+procedure odc_fspec (s, b, ns, naps)
+
+pointer s #U Spectrum data structures
+pointer b #U BPM data structures
+pointer ns #U Number of spectra per output aperture
+int naps #I Number of output apertures
+
+int i, j, k, l
+pointer sh, mw
+
+begin
+ # Find all the distinct SMW pointers and free them.
+ # Then free all the spectrum data pointers.
+
+ do j = 1, naps {
+ do i = 1, NS(ns,j) {
+ sh = SH(s,j,i)
+ if (sh == NULL)
+ next
+ mw = MW(sh)
+ if (mw != NULL) {
+ do k = 1, naps {
+ do l = 1, NS(ns,k) {
+ sh = SH(s,k,l)
+ if (sh == NULL)
+ next
+ if (MW(sh) == mw)
+ MW(sh) = NULL
+ }
+ }
+ call smw_close (mw)
+ }
+ }
+ }
+ do j = 1, naps {
+ do i = 1, NS(ns,j) {
+ sh = SH(s,j,i)
+ if (sh == NULL)
+ next
+ call shdr_close (sh)
+ }
+ call mfree (Memi[s+j-1], TY_POINTER)
+ }
+ call mfree (s, TY_POINTER)
+
+ do j = 1, naps {
+ do i = 1, NS(ns,j) {
+ sh = SH(b,j,i)
+ if (sh == NULL)
+ next
+ mw = MW(sh)
+ if (mw != NULL) {
+ do k = 1, naps {
+ do l = 1, NS(ns,k) {
+ sh = SH(b,k,l)
+ if (sh == NULL)
+ next
+ if (MW(sh) == mw)
+ MW(sh) = NULL
+ }
+ }
+ call smw_close (mw)
+ }
+ }
+ }
+ do j = 1, naps {
+ do i = 1, NS(ns,j) {
+ sh = SH(b,j,i)
+ if (sh == NULL)
+ next
+ call shdr_close (sh)
+ }
+ call mfree (Memi[b+j-1], TY_POINTER)
+ }
+ call mfree (b, TY_POINTER)
+
+ call mfree (ns, TY_INT)
+end
+
+
+# ODC_REBIN -- Rebin spectra and masks.
+
+procedure odc_rebin (refim, shout, s, b, n, mformat, mtype, mvalue, output)
+
+pointer refim #I Output reference image
+pointer shout #I Output spectrum structure
+pointer s[ARB] #I Array of spectrum structures
+pointer b[ARB] #I Array of BPM spectrum structures
+int n #I Number of spectra
+int mformat #I Mask format
+int mtype #I Mask type
+int mvalue #I Mask value
+char output[ARB] #I Output rootname
+
+int i, j, k, p1, p2, npts
+double c[3], d[3,3]
+pointer sh, bpm, im, mw
+pointer sp, str
+
+int mw_stati()
+double shdr_lw(), shdr_wl()
+pointer immap(), mw_openim(), impl1r()
+errchk immap, mw_openim, impl1r
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ j = 0
+ do i = 1, n {
+ sh = s[i]
+ bpm = b[i]
+
+ # Determine limits of input spectrum relative to the output
+ # spectrum.
+
+ c[1] = shdr_wl (shout, shdr_lw (sh, double(0.5)))
+ c[2] = shdr_wl (shout, shdr_lw (sh, double(SN(sh)+0.5)))
+ p1 = max (1, nint (min (c[1], c[2]) + 0.01))
+ p2 = min (SN(shout), nint (max (c[1], c[2]) - 0.01))
+ npts = p2 - p1 + 1
+ if (npts < 1)
+ next
+ p1 = 1 - p1
+
+ # Rebin the spectra and masks.
+ call shdr_rebin (sh, shout)
+ call odc_bpm (bpm, shout, mtype, mvalue)
+
+ # Write the results. We only write the part of the output
+ # contained by the input spectrum and then let the combining deal
+ # with the origin offsets. This is done by setting the physical
+ # pixel coordinate system to match the desired output system.
+ # The main reason for this it to make the output of bounds
+ # pixel implicitly bad or excluded.
+
+ j = j + 1
+ call sprintf (Memc[str], SZ_LINE, "%s.%04d")
+ call pargstr (output)
+ call pargi (j)
+ im = immap (Memc[str], NEW_COPY, refim)
+ call sprintf (Memc[str], SZ_LINE, "%s%s(%s)")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargi (AP(sh))
+ call imastr (im, "ICFNAME", Memc[str])
+ IM_LEN(im,1) = npts
+ if (p1 != 0) {
+ mw = mw_openim (im)
+ k = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gltermd (mw, d, c, k)
+ c[1] = c[1] + p1
+ call mw_sltermd (mw, d, c, k)
+ call mw_saveim (mw, im)
+ }
+ call amovr (Memr[SY(sh)-p1], Memr[impl1r(im)], npts)
+ if (bpm != NULL) {
+ switch (mformat) {
+ case BPMPIX:
+ call sprintf (Memc[str], SZ_LINE, "%s%s")
+ call pargstr (IMNAME(bpm))
+ call pargstr (IMSEC(bpm))
+ case BPMSPEC:
+ call sprintf (Memc[str], SZ_LINE, "%s%s(%s)")
+ call pargstr (IMNAME(bpm))
+ call pargstr (IMSEC(bpm))
+ call pargi (AP(bpm))
+ }
+ call imastr (im, "ICBPM", Memc[str])
+ call sprintf (Memc[str], SZ_LINE, "%sbpm.%04d")
+ call pargstr (output)
+ call pargi (j)
+ call imastr (im, "BPM", Memc[str])
+ } else iferr (call imdelf (im, "BPM"))
+ ;
+ call imunmap (im)
+
+ if (bpm == NULL)
+ next
+
+ im = immap (Memc[str], NEW_COPY, refim)
+ IM_PIXTYPE(im) = TY_INT
+ IM_LEN(im,1) = npts
+ if (p1 != 0) {
+ mw = mw_openim (im)
+ k = mw_stati (mw, MW_NPHYSDIM)
+ call mw_gltermd (mw, d, c, k)
+ c[1] = c[1] + p1
+ call mw_sltermd (mw, d, c, k)
+ call mw_saveim (mw, im)
+ }
+ call amovr (Memr[SY(bpm)-p1], Memr[impl1r(im)], npts)
+ iferr (call imdelf (im, "BPM"))
+ ;
+ call imunmap (im)
+ }
+
+ call sfree (sp)
+end
+
+
+# ODC_BPM -- Rebin the bad pixel masks.
+#
+# Even though the input mask can be specified by good or bad values or bits
+# the rebinned mask is created as a boolean mask. Note that the rebinning
+# is done by setting a large mask value and then values computed from good
+# and bad pixels will have some intermediate value which we then threshold
+# to define good and bad.
+
+procedure odc_bpm (sh, shout, mtype, mvalue)
+
+pointer sh #I SHDR pointer for mask spectrum
+pointer shout #I SHDR pointer for template output spectrum
+int mtype #I Mask type
+int mvalue #I Mask value
+
+int i, n, val, and()
+pointer ptr
+
+begin
+ if (sh == NULL)
+ return
+
+ n = SN(sh)
+ ptr = SY(sh)
+ switch (mtype) {
+ case M_GOODVAL:
+ do i = 1, n {
+ val = nint (Memr[ptr])
+ if (val == mvalue)
+ Memr[ptr] = 0
+ else
+ Memr[ptr] = 1000
+ ptr = ptr + 1
+ }
+ case M_BADVAL:
+ do i = 1, n {
+ val = nint (Memr[ptr])
+ if (val != mvalue)
+ Memr[ptr] = 0
+ else
+ Memr[ptr] = 1000
+ ptr = ptr + 1
+ }
+ case M_GOODBITS:
+ do i = 1, n {
+ val = nint (Memr[ptr])
+ if (and (val, mvalue) != 0)
+ Memr[ptr] = 0
+ else
+ Memr[ptr] = 1000
+ ptr = ptr + 1
+ }
+ case M_BADBITS:
+ do i = 1, n {
+ val = nint (Memr[ptr])
+ if (and (val, mvalue) == 0)
+ Memr[ptr] = 0
+ else
+ Memr[ptr] = 1000
+ ptr = ptr + 1
+ }
+ }
+
+ call shdr_rebin (sh, shout)
+
+ n = SN(sh)
+ ptr = SY(sh)
+ do i = 1, n {
+ val = nint (Memr[ptr])
+ if (val < 10)
+ Memr[ptr] = 0
+ else
+ Memr[ptr] = 1
+ ptr = ptr + 1
+ }
+end
+
+
+# ODC_OUTPUT - Set the output spectrum.
+
+procedure odc_output (sh, ns, output, im, mw, refim)
+
+pointer sh[ARB] # spectra structures
+int ns # number of spectra
+char output[SZ_FNAME] # output spectrum name
+pointer im # output IMIO pointer
+pointer mw # output MWCS pointer
+pointer refim # reference image for output image
+
+int ap, beam, dtype, nw, axis[2]
+double w1, dw, z
+real aplow[2], aphigh[2]
+pointer coeff
+pointer immap(), mw_open(), smw_openim()
+errchk immap, smw_openim
+data axis/1,2/
+
+begin
+ coeff = NULL
+
+ # Create output image using the first input image as a reference
+ refim = immap (IMNAME(sh[1]), READ_ONLY, 0)
+ im = immap (output, NEW_COPY, refim)
+
+ # Use smw_openim to clean up old keywords(?).
+ mw = smw_openim (im)
+ call smw_close (mw)
+
+ IM_NDIM(im) = 1
+ call imaddi (im, "SMW_NDIM", IM_NDIM(im))
+ if (IM_PIXTYPE(im) != TY_DOUBLE)
+ IM_PIXTYPE(im) = TY_REAL
+
+ # Set new header.
+ mw = mw_open (NULL, 2)
+ call mw_newsystem (mw, "multispec", 2)
+ call mw_swtype (mw, axis, 2, "multispec",
+ "label=Wavelength units=Angstroms")
+ call smw_open (mw, NULL, im)
+
+ call smw_gwattrs (MW(sh[1]), APINDEX(sh[1]), 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, coeff)
+ call odc_default (sh, ns, dtype, w1, dw, nw, z, Memc[coeff])
+ call smw_swattrs (mw, 1, 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, Memc[coeff])
+ call smw_sapid (mw, 1, 1, TITLE(sh[1]))
+
+ IM_LEN(im,1) = nw
+
+ # Set MWCS header.
+ call smw_saveim (mw, im)
+ call smw_close (mw)
+ mw = smw_openim (im)
+
+ call mfree (coeff, TY_CHAR)
+end
+
+
+# ODC_DEFAULT - Set default values for the starting wavelength, ending
+# wavelength, wavelength increment and spectrum length for the output
+# spectrum.
+
+procedure odc_default (shdr, ns, dtype, w1, dw, nw, z, coeff)
+
+pointer shdr[ARB] # spectra structures
+int ns # number of spectra
+int dtype # dispersion type
+double w1 # starting wavelength
+double dw # wavelength increment
+int nw # spectrum length
+double z # redshift
+char coeff[ARB] # nonlinear coefficient array
+
+bool clgetb()
+int i, nwa, clgeti()
+double w2, aux, w1a, w2a, dwa, clgetd()
+pointer sh
+
+begin
+ if (clgetb ("first")) {
+ # For now we don't allow non-linear dispersions because the
+ # generic combine routines don't understand multispec.
+ if (dtype == DCFUNC) {
+ dtype = DCLINEAR
+ coeff[1] = EOS
+ z = 0.
+ }
+
+ return
+ }
+
+ w1a = clgetd ("w1")
+ w2a = clgetd ("w2")
+ dwa = clgetd ("dw")
+ nwa = clgeti ("nw")
+ if (clgetb ("log"))
+ dtype = DCLOG
+ else
+ dtype = DCLINEAR
+ z = 0.
+ coeff[1] = EOS
+
+
+ # Dispersion type
+ if (dtype == DCLINEAR) {
+ do i = 1, ns {
+ if (DC(shdr[i]) == DCNO) {
+ dtype = DCNO
+ break
+ }
+ }
+ }
+
+ w1 = w1a
+ w2 = w2a
+ dw = dwa
+ nw = nwa
+
+ # Starting wavelength
+ if (IS_INDEFD (w1)) {
+ if (IS_INDEFD (dw) || dw > 0.) {
+ w1 = MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W0(sh)
+ else
+ aux = W1(sh)
+ if (aux < w1)
+ w1 = aux
+ }
+ } else {
+ w1 = -MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W1(sh)
+ else
+ aux = W0(sh)
+ if (aux > w1)
+ w1 = aux
+ }
+ }
+ }
+
+ # Ending wavelength
+ if (IS_INDEFD (w2)) {
+ if (IS_INDEFD (dw) || dw > 0.) {
+ w2 = -MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W1(sh)
+ else
+ aux = W0(sh)
+ if (aux > w2)
+ w2 = aux
+ }
+ } else {
+ w2 = MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W0(sh)
+ else
+ aux = W1(sh)
+ if (aux < w2)
+ w2 = aux
+ }
+ }
+ }
+
+ # Wavelength increment
+ if (IS_INDEFD (dw)) {
+ dw = MAX_REAL
+ do i = 1, ns {
+ aux = abs (WP(shdr[i]))
+ if (aux < dw)
+ dw = aux
+ }
+ }
+ if ((w2 - w1) / dw < 0.)
+ dw = -dw
+
+ # Spectrum length
+ if (IS_INDEFI (nw))
+ nw = int ((w2 - w1) / dw + 0.5) + 1
+
+ # Adjust the values.
+ if (IS_INDEFD (dwa))
+ dw = (w2 - w1) / (nw - 1)
+ else if (IS_INDEFD (w2a))
+ w2 = w1 + (nw - 1) * dw
+ else if (IS_INDEFD (w1a))
+ w1 = w2 - (nw - 1) * dw
+ else {
+ nw = int ((w2 - w1) / dw + 0.5) + 1
+ w2 = w1 + (nw - 1) * dw
+ }
+end
+
+
+# ODC_IMTGETIM -- Set output image from an list of root names.
+
+procedure odc_imtgetim (list, index, aperture, image, maxch)
+
+int list #I List of images
+int index #I List index
+int aperture #I Aperture
+char image[maxch] #O Image name
+int maxch #I Maximum character for image
+
+pointer sp, root, extn
+
+int imtrgetim()
+
+begin
+ if (imtrgetim (list, index, image, maxch) == EOF) {
+ image[1] = EOS
+ return
+ }
+
+ if (aperture == INDEFI)
+ return
+
+ call smark (sp)
+ call salloc (root, SZ_FNAME, TY_CHAR)
+ call salloc (extn, SZ_FNAME, TY_CHAR)
+
+ call iki_init()
+ call iki_parse (image, Memc[root], Memc[extn])
+ if (Memc[extn] == EOS) {
+ call sprintf (image, maxch, "%s.%04d")
+ call pargstr (Memc[root])
+ call pargi (aperture)
+ } else {
+ call sprintf (image, maxch, "%s.%04d.%s")
+ call pargstr (Memc[root])
+ call pargi (aperture)
+ call pargstr (Memc[extn])
+ }
+
+ call sfree (sp)
+end
+
+
+# ODC_COMBINE -- Combine the spectra by calling the IMCOMBINE source.
+
+procedure odc_combine (slist, blist, output, headers, bmask, rmask, nrmask,
+ emask, sigma, logfile, delete)
+
+int slist #I List of 1D spectra to combine
+int blist #I List of 1D bad pixel spectra
+char output[ARB] #I Output combined spectrum
+char headers[ARB] #I Output headers
+char bmask[ARB] #I Output bad pixel mask
+char rmask[ARB] #I Output rejection mask
+char nrmask[ARB] #I Output number rejected mask
+char emask[ARB] #I Ouput exposure time mask
+char sigma[ARB] #I Output sigma
+char logfile[ARB] #I Logfile
+int delete #I Delete input spectra?
+
+int n
+pointer sp, fname, scales, zeros, wts
+
+int imtlen(), imtgetim()
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+
+ # Allocate and initialize scaling factors.
+ n = imtlen (slist)
+ call salloc (scales, 3*n, TY_REAL)
+ zeros = scales + n
+ wts = scales + 2 * n
+ call amovkr (INDEFR, Memr[scales], 3*n)
+
+ # Combine.
+ iferr (call icombine (slist, output, headers, bmask, rmask,
+ nrmask, emask, sigma, logfile, Memr[scales], Memr[zeros],
+ Memr[wts], NO, NO))
+ call erract (EA_WARN)
+
+ # Delete the files.
+ if (delete == YES) {
+ call imtrew (slist)
+ while (imtgetim (slist, Memc[fname], SZ_FNAME) != EOF)
+ call imdelete (Memc[fname])
+ call imtrew (blist)
+ while (imtgetim (blist, Memc[fname], SZ_FNAME) != EOF)
+ call imdelete (Memc[fname])
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/odcombine/x_odcombine.x b/noao/onedspec/odcombine/x_odcombine.x
new file mode 100644
index 00000000..33943271
--- /dev/null
+++ b/noao/onedspec/odcombine/x_odcombine.x
@@ -0,0 +1 @@
+task scombine = t_scombine
diff --git a/noao/onedspec/odropenp.x b/noao/onedspec/odropenp.x
new file mode 100644
index 00000000..509cc75a
--- /dev/null
+++ b/noao/onedspec/odropenp.x
@@ -0,0 +1,92 @@
+include <ctype.h>
+
+define NALLOC 512 # Allocation increment
+define MAXRECS 1000 # Maximum number of records
+
+
+# ODR_OPENP -- Open onedspec record image pattern
+
+procedure odr_openp (list, records)
+
+int list # Image list
+char records[ARB] # Record string
+
+int i, n, nalloc, rec
+int decode_ranges(), imtgetim(), strlen(), get_next_number()
+pointer sp, fname, image, recs, images, imtopen()
+
+begin
+ call smark (sp)
+ call salloc (fname, SZ_LINE, TY_CHAR)
+ call salloc (image, SZ_LINE, TY_CHAR)
+
+ # Check for empty string.
+ for (i=1; IS_WHITE(records[i]); i=i+1)
+ ;
+ if (records[i] == EOS) {
+ call sfree (sp)
+ return
+ }
+
+ # Decode record string.
+ call salloc (recs, 300, TY_INT)
+ if (decode_ranges (records, Memi[recs], 100, i) == ERR)
+ call error (1, "Bad record specification")
+ if (i > MAXRECS)
+ call error (1, "Too many records")
+
+ n = 0
+ nalloc = NALLOC
+ call malloc (images, nalloc, TY_CHAR)
+ Memc[images] = EOS
+
+ rec = -1
+ repeat {
+ repeat {
+ if (rec < 0) {
+ i = imtgetim (list, Memc[fname], SZ_LINE)
+ if (i == EOF)
+ break
+
+ # Strip sections and extensions
+ call imgimage (Memc[fname], Memc[fname], SZ_LINE)
+ i = strlen (Memc[fname])
+ switch (Memc[fname+i-1]) {
+ case 'h':
+ if (i > 3 && Memc[fname+i-4] == '.')
+ Memc[fname+i-4] = EOS
+ case 'l':
+ if (i > 2 && Memc[fname+i-3] == '.')
+ Memc[fname+i-3] = EOS
+ }
+ }
+
+ i = get_next_number (Memi[recs], rec)
+ if (i != EOF) {
+ call sprintf (Memc[image], SZ_LINE, "%s.%04d")
+ call pargstr (Memc[fname])
+ call pargi (rec)
+ break
+ }
+ rec = -1
+ }
+
+ if (i == EOF)
+ break
+
+ n = n + strlen (Memc[image]) + 1
+ if (n > nalloc) {
+ nalloc = n + NALLOC
+ call realloc (images, nalloc, TY_CHAR)
+ }
+ if (Memc[images] != EOS)
+ call strcat (",", Memc[images], nalloc)
+ call strcat (Memc[image], Memc[images], nalloc)
+ }
+
+ call imtclose (list)
+ list = imtopen (Memc[images])
+
+ call mfree (images, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/onedspec.cl b/noao/onedspec/onedspec.cl
new file mode 100644
index 00000000..835905c2
--- /dev/null
+++ b/noao/onedspec/onedspec.cl
@@ -0,0 +1,57 @@
+#{ Package script task for the ONEDSPEC package.
+
+# Define necessary paths
+
+package onedspec
+
+task autoidentify,
+ calibrate,
+ continuum,
+ deredden,
+ dispcor,
+ disptrans,
+ dopcor,
+ fitprofs,
+ identify,
+ lcalib,
+ mkspec,
+ names,
+ refspectra,
+ reidentify,
+ rstext,
+ sapertures,
+ sarith,
+ sbands,
+ odcombine,
+ scoords,
+ sensfunc,
+ sfit,
+ sflip,
+ sinterp,
+ skytweak,
+ slist,
+ specplot,
+ specshift,
+ splot,
+ standard,
+ telluric = onedspec$x_onedspec.e
+
+task scombine = "onedspec$scombine/x_scombine.e"
+
+task setairmass,
+ setjd = astutil$x_astutil.e
+
+# Scripts and Psets
+
+task aidpars = onedspec$aidpars.par
+task bplot = onedspec$bplot.cl
+task ndprep = onedspec$ndprep.cl
+task scopy = onedspec$scopy.cl
+task rspectext = onedspec$rspectext.cl
+task wspectext = onedspec$wspectext.cl
+
+task $process = process.cl # Used by BATCHRED
+task dispcor1 = onedspec$dispcor1.par # Used by DISPCOR
+hidetask dispcor1,process,rstext
+
+clbye
diff --git a/noao/onedspec/onedspec.hd b/noao/onedspec/onedspec.hd
new file mode 100644
index 00000000..92f8face
--- /dev/null
+++ b/noao/onedspec/onedspec.hd
@@ -0,0 +1,58 @@
+# Help directory for the ONEDSPEC package.
+
+$dispcor = "./dispcor/
+$doc = "./doc/"
+$identify = "./identify/
+$irsiids = "./irsiids/"
+$sensfunc = "./sensfunc/
+$splot = "./splot/
+$linelists = "noao$lib/linelists/"
+$onedstds = "noao$lib/onedstds/"
+
+revisions sys=Revisions
+
+aidpars hlp=doc$aidpars.hlp, src=aidpars.par
+autoidentify hlp=doc$autoidentify.hlp, src=identify$t_autoid.x
+bplot hlp=doc$bplot.hlp, src=bplot.cl
+calibrate hlp=doc$calibrate.hlp, src=t_calibrate.x
+continuum hlp=doc$continuum.hlp, src=t_sfit.x
+deredden hlp=doc$deredden.hlp, src=t_deredden.x
+dispaxis hlp=doc$dispaxis.hlp, src=dispaxis.par
+dispcor hlp=doc$dispcor.hlp, src=dispcor$dispcor.x
+disptrans hlp=doc$disptrans.hlp, src=dispcor$disptrans.x
+dopcor hlp=doc$dopcor.hlp, src=t_dopcor.x
+fitprofs hlp=doc$fitprofs.hlp, src=t_fitprofs.x
+identify hlp=doc$identify.hlp, src=identify$t_identify.x
+lcalib hlp=doc$lcalib.hlp, src=t_lcalib.x
+mkspec hlp=doc$mkspec.hlp, src=x_mkspec.x
+names hlp=doc$names.hlp, src=t_names.x
+ndprep hlp=doc$ndprep.hlp, src=ndprep.cl
+odcombine hlp=doc$odcombine.hlp
+refspectra hlp=doc$refspectra.hlp, src=dispcor$refspectra.x
+reidentify hlp=doc$reidentify.hlp, src=identify$t_reidentify.x
+rspectext hlp=doc$rspectext.hlp, src=rspectext.cl
+sapertures hlp=doc$sapertures.hlp, src=t_sapertures.x
+sbands hlp=doc$sbands.hlp, src=t_sbands.x
+sarith hlp=doc$sarith.hlp, src=t_sarith.x
+scombine hlp=doc$scombine.hlp
+scoords hlp=doc$scoords.hlp, src=t_scoords.x
+scopy hlp=doc$scopy.hlp, src=scopy.cl
+sensfunc hlp=doc$sensfunc.hlp, src=sensfunc$t_sensfunc.x
+sfit hlp=doc$sfit.hlp, src=t_sfit.x
+sflip hlp=doc$sflip.hlp, src=t_sflip.x
+sinterp hlp=doc$sinterp.hlp, src=t_sinterp.x
+skytweak hlp=doc$skytweak.hlp, src=t_tweak.x
+slist hlp=doc$slist.hlp, src=t_slist.x
+specplot hlp=doc$specplot.hlp, src=t_specplot.x
+specshift hlp=doc$specshift.hlp, src=t_specshift.x
+splot hlp=doc$splot.hlp, src=splot$splot.x
+standard hlp=doc$standard.hlp, src=t_standard.x
+telluric hlp=doc$telluric.hlp, src=t_tweak.x
+wspectext hlp=doc$wspectext.hlp, src=wspectext.cl
+
+package hlp=doc$onedspec.hlp, src=onedspec.cl
+specwcs hlp=doc$specwcs.hlp
+linelists hlp=linelists$README
+onedstds hlp=onedstds$README
+
+irsiids pkg=irsiids$irsiids.hd
diff --git a/noao/onedspec/onedspec.men b/noao/onedspec/onedspec.men
new file mode 100644
index 00000000..7285e75a
--- /dev/null
+++ b/noao/onedspec/onedspec.men
@@ -0,0 +1,51 @@
+ aidpars - Automatic line identification parameters and algorithm
+ autoidentify - Automatically identify lines and fit dispersion
+ bplot - Batch plots of spectra
+ calibrate - Apply extinction and flux calibrations to spectra
+ continuum - Fit the continuum in spectra
+ deredden - Apply interstellar extinction correction
+ dispcor - Dispersion correct and resample spectra
+ disptrans - Transform dispersion units and apply air correction
+ dopcor - Apply doppler corrections
+ fitprofs - Fit gaussian profiles
+ identify - Identify features in spectrum for dispersion solution
+ lcalib - List calibration file data
+ mkspec - Generate an artificial spectrum (obsolete)
+ names - Generate a list of image names from a string
+ ndprep - Make neutral density filter calibration image
+ odcombine - Combine spectra having different wavelength ranges (new)
+ refspectra - Assign wavelength reference spectra to other spectra
+ reidentify - Automatically identify features in spectra
+ rspectext - Convert ascii text spectra to image spectra
+ sapertures - Set or change aperture header information
+ sarith - Spectrum arithmetic
+ sbands - Bandpass spectrophotometry of spectra
+ scombine - Combine spectra having different wavelength ranges
+ scoords - Set spectral coordinates as a pixel array (1D spectra only)
+ scopy - Select and copy apertures in different spectral formats
+ sensfunc - Create sensitivity function
+ setairmass - Compute effective airmass and middle UT for an exposure
+ setjd - Compute and set Julian dates in images
+ sfit - Fit spectra and output fit, ratio, or difference
+ sflip - Flip data and/or dispersion coordinates in spectra
+ sinterp - Interpolate a table of x,y pairs to create a spectrum
+ skytweak - Sky subtract 1D spectra after tweaking sky spectra
+ slist - List spectrum header parameters
+ specplot - Stack and plot multiple spectra
+ specshift - Shift spectral dispersion coordinate systems
+ splot - Preliminary spectral plot/analysis
+ standard - Identify standard stars to be used in sensitivity calc
+ telluric - Remove telluric features from 1D spectra
+ wspectext - Convert 1D image spectra to ascii text spectra
+
+ ADDITIONAL HELP TOPICS
+
+ package - Discussion and overview of package including sections on:
+ package parameters, units, and interpolation
+ specwcs - Discussion and description of the spectral image formats
+ and the dispersion world coordinate system
+ linelists - Description of the spectral line list library in
+ the directory linelists$.
+ onedstds - Description of the spectral calibration library in the
+ directory onedstds$: extinction, flux standards, etc.
+
diff --git a/noao/onedspec/onedspec.par b/noao/onedspec/onedspec.par
new file mode 100644
index 00000000..2264fe2c
--- /dev/null
+++ b/noao/onedspec/onedspec.par
@@ -0,0 +1,10 @@
+# Package parameter file for the ONEDSPEC package.
+
+observatory,s,h,"observatory",,,"Observatory for data"
+caldir,s,h,,,,"Standard star calibration directory"
+interp,s,h,"poly5","nearest|linear|poly3|poly5|spline3|sinc",,Interpolation type
+dispaxis,i,h,1,1,3,"Image axis for 2D/3D images"
+nsum,s,h,"1",,,"Number of lines/columns to sum for 2D/3D images"
+records,s,h,"",,,"Record number extensions
+"
+version,s,h,"ONEDSPEC: January 1996"
diff --git a/noao/onedspec/refspectra.par b/noao/onedspec/refspectra.par
new file mode 100644
index 00000000..51e613ee
--- /dev/null
+++ b/noao/onedspec/refspectra.par
@@ -0,0 +1,16 @@
+input,s,a,,,,"List of input spectra"
+references,s,h,"*.imh",,,"List of reference spectra"
+apertures,s,h,"",,,"Input aperture selection list"
+refaps,s,h,"",,,"Reference aperture selection list"
+ignoreaps,b,h,yes,,,Ignore input and reference apertures?
+select,s,h,"interp","match|nearest|preceding|following|interp|average",,"Selection method for reference spectra"
+sort,s,h,"jd",,,"Sort key"
+group,s,h,"ljd",,,"Group key"
+time,b,h,no,,,"Is sort key a time?"
+timewrap,r,h,17.,0.,24.,"Time wrap point for time sorting"
+override,b,h,no,,,"Override previous assignments?"
+confirm,b,h,yes,,,"Confirm reference spectrum assignments?"
+assign,b,h,yes,,,"Assign the reference spectra to the input spectrum?"
+logfiles,s,h,"STDOUT,logfile",,,"List of logfiles"
+verbose,b,h,no,,,"Verbose log output?"
+answer,s,q,,"no|yes|YES",,"Accept assignment?"
diff --git a/noao/onedspec/reidentify.par b/noao/onedspec/reidentify.par
new file mode 100644
index 00000000..de97bcd1
--- /dev/null
+++ b/noao/onedspec/reidentify.par
@@ -0,0 +1,36 @@
+# Parameters for reidentify task.
+
+reference,s,a,,,,Reference image
+images,s,a,,,,Images to be reidentified
+interactive,s,h,"no","no|yes|NO|YES",,Interactive fitting?
+section,s,h,"middle line",,,Section to apply to two dimensional images
+newaps,b,h,yes,,,Reidentify apertures in images not in reference?
+override,b,h,no,,,Override previous solutions?
+refit,b,h,yes,,,"Refit coordinate function?
+"
+trace,b,h,no,,,Trace reference image?
+step,s,h,"10",,,Step in lines/columns/bands for tracing an image
+nsum,s,h,"10",,,Number of lines/columns/bands to sum
+shift,s,h,"0.",,,Shift to add to reference features (INDEF to search)
+search,r,h,0.,,,Search radius
+nlost,i,h,0,0,,"Maximum number of features which may be lost
+"
+cradius,r,h,5.,,,Centering radius
+threshold,r,h,0.,0.,,Feature threshold for centering
+addfeatures,b,h,no,,,Add features from a line list?
+coordlist,f,h,linelists$idhenear.dat,,,User coordinate list
+match,r,h,-3.,,,Coordinate list matching limit
+maxfeatures,i,h,50,,,Maximum number of features for automatic identification
+minsep,r,h,2.,0.,,"Minimum pixel separation
+"
+database,f,h,database,,,Database
+logfiles,s,h,"logfile",,,List of log files
+plotfile,s,h,"",,,Plot file for residuals
+verbose,b,h,no,,,Verbose output?
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,"Graphics cursor input
+"
+answer,s,q,"yes","no|yes|NO|YES",,Fit dispersion function interactively?
+crval,s,q,,,,"Approximate coordinate (at reference pixel)"
+cdelt,s,q,,,,"Approximate dispersion"
+aidpars,pset,h,,,,"Automatic identification algorithm parameters"
diff --git a/noao/onedspec/rspectext.cl b/noao/onedspec/rspectext.cl
new file mode 100644
index 00000000..328ab59e
--- /dev/null
+++ b/noao/onedspec/rspectext.cl
@@ -0,0 +1,115 @@
+# RSPECTEXT -- Read a 1D ascii text spectrum into an image spectrum
+# The image is created from an optional header and the flux values
+# using RTEXTIMAGE. If there is no header the title, dispersion,
+# and flux calibration may be set. The dispersion can be defined
+# as linear, log linear, or from the wavelengths. The latter may be
+# used as a lookup table in the image header or used to interpolate
+# the spectrum to a linear wavelength dispersion.
+
+procedure rspectext (input, output)
+
+string input {prompt="Input list of text spectra"}
+string output {prompt="Output list of image spectra"}
+
+string title = "" {prompt="Spectrum title"}
+bool flux = no {prompt="Flux calibrated?"}
+string dtype = "linear" {prompt="Dispersion type",
+ enum="none|linear|log|nonlinear|interp"}
+real crval1 = 1. {prompt="Coordinate of first pixel"}
+real cdelt1 = 1. {prompt="Coordinate interval per pixel"}
+
+struct *fd1, *fd2
+
+begin
+ int dim
+ string specin, specout, spec, temp1, temp2, temp3, temp4
+ bool header=no
+ bool log=no
+
+ specin = mktemp ("tmp$iraf")
+ specout = mktemp ("tmp$iraf")
+ spec = mktemp ("tmp$iraf")
+ temp1 = mktemp ("tmp$iraf")
+ temp3 = mktemp ("iraf")
+ temp2 = "tmp$id"//temp3
+
+ # Expand the input and output lists.
+ files (input, sort=no, > specin)
+ files (output, sort=no, > specout)
+ join (specin, specout, output=spec, delim=" ", shortest=yes, verbose=yes)
+ delete (specin, verify-)
+ delete (specout, verify-)
+
+ # Go through each input and check for an existing output.
+ fd2 = spec
+ while (fscan (fd2, specin, specout) != EOF) {
+ if (access(specout)||access(specout//".imh")||access(specout//".hhh")) {
+ print ("Image "//specout//" already exists")
+ next
+ }
+
+ # Separate the header and flux values for RTEXTIMAGE and the
+ # wavelengths for later use.
+
+ rstext (specin, temp1, temp2, header=header) | scan (header, dim)
+
+ # Create the image from the header and flux values.
+ rtextimage (temp1, specout, otype="real", header=header, pixels=yes,
+ nskip=0, dim=dim)
+ fd1 = ""; delete (temp1, verify-)
+
+ # If there is no header setup the title, dispersion and flux.
+ # The dispersion may require using DISPCOR for nonlinear or
+ # resampled dispersion functions.
+
+ if (!header) {
+ hedit (specout, "title", title,
+ add+, addonly-, del-, update+, verify-, show-)
+ if (dtype == "linear") {
+ hedit (specout, "dc-flag", 0,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "crpix1", 1.,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "crval1", crval1,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "cdelt1", cdelt1,
+ add+, addonly-, del-, update+, verify-, show-)
+ } else if (dtype == "log") {
+ hedit (specout, "dc-flag", 1,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "crpix1", 1.,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "crval1", crval1,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "cdelt1", cdelt1,
+ add+, addonly-, del-, update+, verify-, show-)
+ } else if (dtype == "nonlinear") {
+ hedit (specout, "refspec1", temp3,
+ add+, addonly-, del-, update+, verify-, show-)
+ dispcor (specout, "", linearize=no, database="tmp$",
+ table="", w1=INDEF, w2=INDEF, dw=INDEF, nw=INDEF, log=log,
+ flux=no, samedisp=no, global=no, ignoreaps=no, confirm=no,
+ listonly=no, verbose=no, logfile="")
+ hedit (specout, "dclog1",
+ add-, addonly-, del+, update+, verify-, show-)
+ } else if (dtype == "interp") {
+ hedit (specout, "refspec1", temp3,
+ add+, addonly-, del-, update+, verify-, show-)
+ dispcor (specout, "", linearize=yes, database="tmp$",
+ table="", w1=INDEF, w2=INDEF, dw=INDEF, nw=INDEF, log=log,
+ flux=no, samedisp=no, global=no, ignoreaps=no, confirm=no,
+ listonly=no, verbose=no, logfile="")
+ hedit (specout, "dclog1",
+ add-, addonly-, del+, update+, verify-, show-)
+ }
+ if (flux) {
+ hedit (specout, "ca-flag", 0,
+ add+, addonly-, del-, update+, verify-, show-)
+ hedit (specout, "ex-flag", 0,
+ add+, addonly-, del-, update+, verify-, show-)
+ }
+ }
+ delete (temp2, verify-)
+ }
+ fd2=""; delete (spec, verify-)
+end
diff --git a/noao/onedspec/rstext.par b/noao/onedspec/rstext.par
new file mode 100644
index 00000000..07d78e31
--- /dev/null
+++ b/noao/onedspec/rstext.par
@@ -0,0 +1,4 @@
+input,f,a,,,,Input RSPECTEXT text file
+output1,f,a,,,,Output file for RTEXTIMAGE
+output2,f,a,,,,Output file for DISPCOR
+header,b,h,yes,,,Pass header?
diff --git a/noao/onedspec/sapertures.par b/noao/onedspec/sapertures.par
new file mode 100644
index 00000000..b14f1137
--- /dev/null
+++ b/noao/onedspec/sapertures.par
@@ -0,0 +1,16 @@
+input,s,a,,,,List of spectra
+apertures,s,h,"",,,List of apertures to change
+apidtable,s,h,"",,,Table of individual aperture values
+wcsreset,s,h,no,,,Reset WCS to pixels and ignore apidtable?
+verbose,b,h,no,,,"Print verbose information?
+
+# Defaults for apertures not in table
+# INDEF leaves value unchanged"
+beam,i,h,INDEF,,,Beam number
+dtype,i,h,INDEF,,,"Dispersion type (-1|0|1)"
+w1,r,h,INDEF,,,Coordinate of first physical pixel
+dw,r,h,INDEF,,,Coordinate step per physical pixel
+z,r,h,INDEF,,,Redshift factor
+aplow,r,h,INDEF,,,Lower extraction aperture position
+aphigh,r,h,INDEF,,,Upper extraction aperture position
+title,s,h,"INDEF",,,Spectrum title or ID
diff --git a/noao/onedspec/sarith.par b/noao/onedspec/sarith.par
new file mode 100644
index 00000000..bcb7b575
--- /dev/null
+++ b/noao/onedspec/sarith.par
@@ -0,0 +1,22 @@
+input1,s,a,,,,"List of input spectra"
+op,s,a,,"abs|copy|dex|exp|flam|fnu|inv|ln|log|lum|mag|sqrt|sextract|replace|+|-|*|/|^",,"Operation"
+input2,s,a,,,,"List of input spectra or constants"
+output,s,a,,,,"List of output spectra"
+w1,r,h,INDEF,,,"Starting wavelength"
+w2,r,h,INDEF,,,"Ending wavelength"
+apertures,s,h,"",,,"List of input apertures or columns/lines"
+bands,s,h,"",,,"List of input bands or lines/bands"
+beams,s,h,"",,,"List of input beams or echelle orders"
+apmodulus,i,h,0,,,"Input aperture modulus (0=none)
+"
+reverse,b,h,no,,,"Reverse order of operands in binary operation?"
+ignoreaps,b,h,no,,,"Ignore second operand aperture numbers?
+"
+format,s,h,"multispec","multispec|onedspec",,"Output spectral format"
+renumber,b,h,no,,,"Renumber output apertures?"
+offset,i,h,0,,,"Output aperture number offset"
+clobber,b,h,no,,,"Modify existing output images?"
+merge,b,h,no,,,"Merge with existing output images?"
+rebin,b,h,yes,,,"Rebin to exact wavelength region?"
+errval,r,h,0.,,,"Arithmetic error replacement value"
+verbose,b,h,no,,,"Print operations?"
diff --git a/noao/onedspec/sbands.par b/noao/onedspec/sbands.par
new file mode 100644
index 00000000..24fb01c0
--- /dev/null
+++ b/noao/onedspec/sbands.par
@@ -0,0 +1,8 @@
+input,s,a,"",,,Input list of spectra
+output,s,a,"",,,Output file name
+bands,s,a,"",,,Bandpass file
+apertures,s,h,"",,,Apertures
+normalize,b,h,yes,,,Normalize the bandpass response?
+mag,b,h,no,,,Output results in magnitudes?
+magzero,r,h,0.,,,Magnitude zero point
+verbose,b,h,yes,,,Verbose header?
diff --git a/noao/onedspec/scombine/README b/noao/onedspec/scombine/README
new file mode 100644
index 00000000..e4031e6a
--- /dev/null
+++ b/noao/onedspec/scombine/README
@@ -0,0 +1,17 @@
+SCOMBINE -- Combine spectra
+
+This routine is based in large part on IMCOMBINE. The routines in the
+generic directory are identical to those in that task except that they
+only contain routines for real data. The ic routines in this directory
+are similar though modified from IMCOMBINE.
+
+The iscombine files are for an interactive combine task based on work
+by CTIO. Because it is limited currently to linear spectra and is not
+organized to take advantage of the IMCOMBINE options it is not installed.
+A version of this may someday be added based on the current software.
+
+
+=======
+
+This version was renamed to OSCOMBINE. It is obsolete and may be removed
+at some future time. (4/14/04, Valdes)
diff --git a/noao/onedspec/scombine/generic/icaclip.x b/noao/onedspec/scombine/generic/icaclip.x
new file mode 100644
index 00000000..41432dd7
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icaclip.x
@@ -0,0 +1,555 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number of images for this algorithm
+
+
+# IC_AAVSIGCLIP -- Reject pixels using an average sigma about the average
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_aavsigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, s1, r, one
+data one /1.0/
+pointer sp, sums, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (sums, npts, TY_REAL)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Since the unweighted average is computed here possibly skip combining
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Compute the unweighted average with the high and low rejected and
+ # the poisson scaled average sigma. There must be at least three
+ # pixels at each point to define the average and contributions to
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ nin = n[1]
+ s = 0.
+ n2 = 0
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3)
+ next
+
+ # Unweighted average with the high and low rejected
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = max (one, (a + zeros[l]) / scales[l])
+ s = s + (d1 - a) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, a)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the average and sum for later.
+ average[i] = a
+ Memr[sums+k] = sum
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+
+ # Reject pixels and compute the final average (if needed).
+ # There must be at least three pixels at each point for rejection.
+ # Iteratively scale the mean sigma and reject pixels
+ # Compact the data and keep track of the image IDs if needed.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (2, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ a = average[i]
+ sum = Memr[sums+k]
+
+ repeat {
+ n2 = n1
+ if (s > 0.) {
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ s1 = s * sqrt (max (one, (a+zeros[l]) / scales[l]))
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ s1 = s * sqrt (max (one, a))
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s1
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MAVSIGCLIP -- Reject pixels using an average sigma about the median
+# The average sigma is normalized by the expected poisson sigma.
+
+procedure ic_mavsigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+pointer sp, resid, mp1, mp2
+real med, low, high, r, s, s1, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute the poisson scaled average sigma about the median.
+ # There must be at least three pixels at each point to define
+ # the mean sigma. Corrections for differences in the image
+ # scale factors are selected by the doscale1 flag.
+
+ s = 0.
+ n2 = 0
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 < 3) {
+ if (n1 == 0)
+ median[i] = blank
+ else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+ else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ median[i] = (low + high) / 2.
+ }
+ next
+ }
+
+ # Median
+ n3 = 1 + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ # Poisson scaled sigma accumulation
+ if (doscale1) {
+ do j = 1, n1 {
+ l = Memi[m[j]+k]
+ s1 = max (one, (med + zeros[l]) / scales[l])
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ } else {
+ s1 = max (one, med)
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - med) ** 2 / s1
+ }
+ n2 = n2 + n1
+
+ # Save the median for later.
+ median[i] = med
+ }
+
+ # Here is the final sigma.
+ if (n2 > 1)
+ s = sqrt (s / (n2 - 1))
+ else
+ return
+
+ # Compute individual sigmas and iteratively clip.
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 < max (3, maxkeep+1))
+ next
+ nl = 1
+ nh = n1
+ med = median[i]
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 >= max (MINCLIP, maxkeep+1) && s > 0.) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s1 = s * sqrt (max (one, (med+zeros[l])/scales[l]))
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ s1 = s * sqrt (max (one, med))
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s1
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s1
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many are rejected add some back in.
+ # Pixels with equal residuals are added together.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+
+ # Recompute median
+ if (n1 < n2) {
+ if (n1 > 0) {
+ n3 = nl + n1 / 2
+ if (mod (n1, 2) == 0) {
+ low = Memr[d[n3-1]+k]
+ high = Memr[d[n3]+k]
+ med = (low + high) / 2.
+ } else
+ med = Memr[d[n3]+k]
+ } else
+ med = blank
+ }
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow > 0)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow > 0) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
diff --git a/noao/onedspec/scombine/generic/icaverage.x b/noao/onedspec/scombine/generic/icaverage.x
new file mode 100644
index 00000000..6c5c870b
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icaverage.x
@@ -0,0 +1,84 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include "../icombine.h"
+
+
+# IC_AVERAGE -- Compute the average image line.
+# Options include a weight average.
+
+procedure ic_averager (d, m, n, wts, npts, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of points
+real wts[ARB] # Weights
+int npts # Number of output points per line
+real average[npts] # Average (returned)
+
+int i, j, k
+real sumwt, wt
+real sum
+
+include "../icombine.com"
+
+begin
+ # If no data has been excluded do the average without checking the
+ # number of points and using the fact that the weights are normalized.
+ # If all the data has been excluded set the average to the blank value.
+
+ if (dflag == D_ALL) {
+ if (dowts) {
+ do i = 1, npts {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ }
+ average[i] = sum
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n[i]
+ }
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ average[i] = blank
+ } else {
+ if (dowts) {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ wt = wts[Memi[m[1]+k]]
+ sum = Memr[d[1]+k] * wt
+ sumwt = wt
+ do j = 2, n[i] {
+ wt = wts[Memi[m[j]+k]]
+ sum = sum + Memr[d[j]+k] * wt
+ sumwt = sumwt + wt
+ }
+ average[i] = sum / sumwt
+ } else
+ average[i] = blank
+ }
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ sum = Memr[d[1]+k]
+ do j = 2, n[i]
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n[i]
+ } else
+ average[i] = blank
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/scombine/generic/iccclip.x b/noao/onedspec/scombine/generic/iccclip.x
new file mode 100644
index 00000000..26b17ba2
--- /dev/null
+++ b/noao/onedspec/scombine/generic/iccclip.x
@@ -0,0 +1,453 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 2 # Mininum number of images for algorithm
+
+
+# IC_ACCDCLIP -- Reject pixels using CCD noise parameters about the average
+
+procedure ic_accdclipr (d, m, n, scales, zeros, nm, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model parameters
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, zero
+data zero /0.0/
+pointer sp, resid, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are no pixels go on to the combining. Since the unweighted
+ # average is computed here possibly skip the combining later.
+
+ # There must be at least max (1, nkeep) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ } else if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # There must be at least two pixels for rejection. The initial
+ # average is the low/high rejected average except in the case of
+ # just two pixels. The rejections are iterated and the average
+ # is recomputed. Corrections for scaling may be performed.
+ # Depending on other flags the image IDs may also need to be adjusted.
+
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 <= max (MINCLIP-1, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ repeat {
+ if (n1 == 2) {
+ sum = Memr[d[1]+k]
+ sum = sum + Memr[d[2]+k]
+ a = sum / 2
+ } else {
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+ }
+ n2 = n1
+ if (doscale1) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+
+ l = Memi[mp1]
+ s = scales[l]
+ d1 = max (zero, s * (a + zeros[l]))
+ s = sqrt (nm[1,l] + d1/nm[2,l] + (d1*nm[3,l])**2) / s
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[n1] + k
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, a)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (j=1; j<=n1; j=j+1) {
+ if (keepids) {
+ l = Memi[m[j]+k]
+ s = max (zero, a)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs(r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+ }
+
+ n[i] = n1
+ if (!docombine)
+ if (n1 > 0)
+ average[i] = sum / n1
+ else
+ average[i] = blank
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_CCDCLIP -- Reject pixels using CCD noise parameters about the median
+
+procedure ic_mccdclipr (d, m, n, scales, zeros, nm, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+real nm[3,nimages] # Noise model
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, mp1, mp2
+real med, zero
+data zero /0.0/
+
+include "../icombine.com"
+
+begin
+ # There must be at least max (MINCLIP, nkeep+1) pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0) {
+ med = Memr[d[n3-1]+k]
+ med = (med + Memr[d[n3]+k]) / 2.
+ } else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ for (; nl <= n2; nl = nl + 1) {
+ l = Memi[m[nl]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ l = Memi[m[nh]+k]
+ s = scales[l]
+ r = max (zero, s * (med + zeros[l]))
+ s = sqrt (nm[1,l] + r/nm[2,l] + (r*nm[3,l])**2) / s
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ } else {
+ if (!keepids) {
+ s = max (zero, med)
+ s = sqrt (nm[1,1] + s/nm[2,1] + (s*nm[3,1])**2)
+ }
+ for (; nl <= n2; nl = nl + 1) {
+ if (keepids) {
+ l = Memi[m[nl]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ if (keepids) {
+ l = Memi[m[nh]+k]
+ s = max (zero, med)
+ s = sqrt (nm[1,l] + s/nm[2,l] + (s*nm[3,l])**2)
+ }
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow > 0)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow > 0) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median is computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
diff --git a/noao/onedspec/scombine/generic/icgrow.x b/noao/onedspec/scombine/generic/icgrow.x
new file mode 100644
index 00000000..074bd8c3
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icgrow.x
@@ -0,0 +1,76 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_GROW -- Reject neigbors of rejected pixels.
+# The rejected pixels are marked by having nonzero ids beyond the number
+# of included pixels. The pixels rejected here are given zero ids
+# to avoid growing of the pixels rejected here. The unweighted average
+# can be updated but any rejected pixels requires the median to be
+# recomputed. When the number of pixels at a grow point reaches nkeep
+# no further pixels are rejected. Note that the rejection order is not
+# based on the magnitude of the residuals and so a grow from a weakly
+# rejected image pixel may take precedence over a grow from a strongly
+# rejected image pixel.
+
+procedure ic_growr (d, m, n, nimages, npts, average)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i1, i2, j1, j2, k1, k2, l, is, ie, n2, maxkeep
+pointer mp1, mp2
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ do i1 = 1, npts {
+ k1 = i1 - 1
+ is = max (1, i1 - grow)
+ ie = min (npts, i1 + grow)
+ do j1 = n[i1]+1, nimages {
+ l = Memi[m[j1]+k1]
+ if (l == 0)
+ next
+ if (combine == MEDIAN)
+ docombine = true
+
+ do i2 = is, ie {
+ if (i2 == i1)
+ next
+ k2 = i2 - 1
+ n2 = n[i2]
+ if (nkeep < 0)
+ maxkeep = max (0, n2 + nkeep)
+ else
+ maxkeep = min (n2, nkeep)
+ if (n2 <= maxkeep)
+ next
+ do j2 = 1, n2 {
+ mp1 = m[j2] + k2
+ if (Memi[mp1] == l) {
+ if (!docombine && n2 > 1)
+ average[i2] =
+ (n2*average[i2] - Memr[d[j2]+k2]) / (n2-1)
+ mp2 = m[n2] + k2
+ if (j2 < n2) {
+ Memr[d[j2]+k2] = Memr[d[n2]+k2]
+ Memi[mp1] = Memi[mp2]
+ }
+ Memi[mp2] = 0
+ n[i2] = n2 - 1
+ break
+ }
+ }
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/scombine/generic/icmedian.x b/noao/onedspec/scombine/generic/icmedian.x
new file mode 100644
index 00000000..e7607340
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icmedian.x
@@ -0,0 +1,139 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MEDIAN -- Median of lines
+
+procedure ic_medianr (d, n, npts, median)
+
+pointer d[ARB] # Input data line pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j1, j2, j3, k, n1
+bool even
+real val1, val2, val3
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE) {
+ do i = 1, npts
+ median[i]= blank
+ return
+ }
+
+ # Check for previous sorting
+ if (mclip) {
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ even = (mod (n1, 2) == 0)
+ j1 = n1 / 2 + 1
+ j2 = n1 / 2
+ do i = 1, npts {
+ k = i - 1
+ if (even) {
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ }
+ } else {
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (n1 > 0) {
+ j1 = n1 / 2 + 1
+ if (mod (n1, 2) == 0) {
+ j2 = n1 / 2
+ val1 = Memr[d[j1]+k]
+ val2 = Memr[d[j2]+k]
+ median[i] = (val1 + val2) / 2.
+ } else
+ median[i] = Memr[d[j1]+k]
+ } else
+ median[i] = blank
+ }
+ }
+ return
+ }
+
+ # Repeatedly exchange the extreme values until there are three
+ # or fewer pixels.
+
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ while (n1 > 3) {
+ j1 = 1
+ j2 = 1
+ val1 = Memr[d[j1]+k]
+ val2 = val1
+ do j3 = 2, n1 {
+ val3 = Memr[d[j3]+k]
+ if (val3 > val1) {
+ j1 = j3
+ val1 = val3
+ } else if (val3 < val2) {
+ j2 = j3
+ val2 = val3
+ }
+ }
+ j3 = n1 - 1
+ if (j1 < j3 && j2 < j3) {
+ Memr[d[j1]+k] = val3
+ Memr[d[j2]+k] = Memr[d[j3]+k]
+ Memr[d[j3]+k] = val1
+ Memr[d[n1]+k] = val2
+ } else if (j1 < j3) {
+ if (j2 == j3) {
+ Memr[d[j1]+k] = val3
+ Memr[d[n1]+k] = val1
+ } else {
+ Memr[d[j1]+k] = Memr[d[j3]+k]
+ Memr[d[j3]+k] = val1
+ }
+ } else if (j2 < j3) {
+ if (j1 == j3) {
+ Memr[d[j2]+k] = val3
+ Memr[d[n1]+k] = val2
+ } else {
+ Memr[d[j2]+k] = Memr[d[j3]+k]
+ Memr[d[j3]+k] = val2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ if (n1 == 3) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ val3 = Memr[d[3]+k]
+ if (val1 < val2) {
+ if (val2 < val3) # abc
+ median[i] = val2
+ else if (val1 < val3) # acb
+ median[i] = val3
+ else # cab
+ median[i] = val1
+ } else {
+ if (val2 > val3) # cba
+ median[i] = val2
+ else if (val1 > val3) # bca
+ median[i] = val3
+ else # bac
+ median[i] = val1
+ }
+ } else if (n1 == 2) {
+ val1 = Memr[d[1]+k]
+ val2 = Memr[d[2]+k]
+ median[i] = (val1 + val2) / 2
+ } else if (n1 == 1)
+ median[i] = Memr[d[1]+k]
+ else
+ median[i] = blank
+ }
+end
diff --git a/noao/onedspec/scombine/generic/icmm.x b/noao/onedspec/scombine/generic/icmm.x
new file mode 100644
index 00000000..1c314241
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icmm.x
@@ -0,0 +1,152 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+
+# IC_MM -- Reject a specified number of high and low pixels
+
+procedure ic_mmr (d, m, n, npts)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image ID pointers
+int n[npts] # Number of good pixels
+int npts # Number of output points per line
+
+int n1, ncombine, npairs, nlow, nhigh, np
+int i, i1, j, jmax, jmin
+pointer k, kmax, kmin
+real d1, d2, dmin, dmax
+
+include "../icombine.com"
+
+begin
+ if (dflag == D_NONE)
+ return
+
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = n1 - nlow - nhigh
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ do i = 1, npts {
+ i1 = i - 1
+ n1 = n[i]
+ if (dflag == D_MIX) {
+ nlow = flow * n1 + 0.001
+ nhigh = fhigh * n1 + 0.001
+ ncombine = max (ncombine, n1 - nlow - nhigh)
+ npairs = min (nlow, nhigh)
+ nlow = nlow - npairs
+ nhigh = nhigh - npairs
+ }
+
+ # Reject the npairs low and high points.
+ do np = 1, npairs {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; dmin = d1; jmax = 1; jmin = 1; kmax = k; kmin = k
+ do j = 2, n1 {
+ d2 = d1
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ } else if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ j = n1 - 1
+ if (keepids) {
+ if (jmax < j) {
+ if (jmin != j) {
+ Memr[kmax] = d2
+ Memi[m[jmax]+i1] = Memi[m[j]+i1]
+ } else {
+ Memr[kmax] = d1
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ }
+ }
+ if (jmin < j) {
+ if (jmax != n1) {
+ Memr[kmin] = d1
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ } else {
+ Memr[kmin] = d2
+ Memi[m[jmin]+i1] = Memi[m[j]+i1]
+ }
+ }
+ } else {
+ if (jmax < j) {
+ if (jmin != j)
+ Memr[kmax] = d2
+ else
+ Memr[kmax] = d1
+ }
+ if (jmin < j) {
+ if (jmax != n1)
+ Memr[kmin] = d1
+ else
+ Memr[kmin] = d2
+ }
+ }
+ n1 = n1 - 2
+ }
+
+ # Reject the excess low points.
+ do np = 1, nlow {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmin = d1; jmin = 1; kmin = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 < dmin) {
+ dmin = d1; jmin = j; kmin = k
+ }
+ }
+ if (keepids) {
+ if (jmin < n1) {
+ Memr[kmin] = d1
+ Memi[m[jmin]+i1] = Memi[m[n1]+i1]
+ }
+ } else {
+ if (jmin < n1)
+ Memr[kmin] = d1
+ }
+ n1 = n1 - 1
+ }
+
+ # Reject the excess high points.
+ do np = 1, nhigh {
+ k = d[1] + i1
+ d1 = Memr[k]
+ dmax = d1; jmax = 1; kmax = k
+ do j = 2, n1 {
+ k = d[j] + i1
+ d1 = Memr[k]
+ if (d1 > dmax) {
+ dmax = d1; jmax = j; kmax = k
+ }
+ }
+ if (keepids) {
+ if (jmax < n1) {
+ Memr[kmax] = d1
+ Memi[m[jmax]+i1] = Memi[m[n1]+i1]
+ }
+ } else {
+ if (jmax < n1)
+ Memr[kmax] = d1
+ }
+ n1 = n1 - 1
+ }
+ n[i] = n1
+ }
+
+ if (dflag == D_ALL && npairs + nlow + nhigh > 0)
+ dflag = D_MIX
+end
diff --git a/noao/onedspec/scombine/generic/icpclip.x b/noao/onedspec/scombine/generic/icpclip.x
new file mode 100644
index 00000000..d9028a93
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icpclip.x
@@ -0,0 +1,224 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Minimum number for clipping
+
+
+# IC_PCLIP -- Percentile clip
+#
+# 1) Find the median
+# 2) Find the pixel which is the specified order index away
+# 3) Use the data value difference as a sigma and apply clipping
+# 4) Since the median is known return it so it does not have to be recomputed
+
+procedure ic_pclipr (d, m, n, nimages, npts, median)
+
+pointer d[ARB] # Data pointers
+pointer m[ARB] # Image id pointers
+int n[npts] # Number of good pixels
+int nimages # Number of input images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, n4, n5, nl, nh, nin, maxkeep
+bool even, fp_equalr()
+real sigma, r, s, t
+pointer sp, resid, mp1, mp2
+real med
+
+include "../icombine.com"
+
+begin
+ # There must be at least MINCLIP and more than nkeep pixels.
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+
+ # Set sign of pclip parameter
+ if (pclip < 0)
+ t = -1.
+ else
+ t = 1.
+
+ # If there are no rejected pixels compute certain parameters once.
+ if (dflag == D_ALL) {
+ n1 = n[1]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0.) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ nin = n1
+ }
+
+ # Now apply clipping.
+ do i = 1, npts {
+ # Compute median.
+ if (dflag == D_MIX) {
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ if (n1 == 0) {
+ if (combine == MEDIAN)
+ median[i] = blank
+ next
+ }
+ n2 = 1 + n1 / 2
+ even = (mod (n1, 2) == 0)
+ if (pclip < 0) {
+ if (even)
+ n3 = max (1, nint (n2 - 1 + pclip))
+ else
+ n3 = max (1, nint (n2 + pclip))
+ } else
+ n3 = min (n1, nint (n2 + pclip))
+ }
+
+ j = i - 1
+ if (even) {
+ med = Memr[d[n2-1]+j]
+ med = (med + Memr[d[n2]+j]) / 2.
+ } else
+ med = Memr[d[n2]+j]
+
+ if (n1 < max (MINCLIP, maxkeep+1)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Define sigma for clipping
+ sigma = t * (Memr[d[n3]+j] - med)
+ if (fp_equalr (sigma, 0.)) {
+ if (combine == MEDIAN)
+ median[i] = med
+ next
+ }
+
+ # Reject pixels and save residuals.
+ # Check if any pixels are clipped.
+ # If so recompute the median and reset the number of good pixels.
+ # Only reorder if needed.
+
+ for (nl=1; nl<=n1; nl=nl+1) {
+ r = (med - Memr[d[nl]+j]) / sigma
+ if (r < lsigma)
+ break
+ Memr[resid+nl] = r
+ }
+ for (nh=n1; nh>=1; nh=nh-1) {
+ r = (Memr[d[nh]+j] - med) / sigma
+ if (r < hsigma)
+ break
+ Memr[resid+nh] = r
+ }
+ n4 = nh - nl + 1
+
+ # If too many pixels are rejected add some back in.
+ # All pixels with the same residual are added.
+ while (n4 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n4 = nh - nl + 1
+ }
+
+ # If any pixels are rejected recompute the median.
+ if (nl > 1 || nh < n1) {
+ n5 = nl + n4 / 2
+ if (mod (n4, 2) == 0) {
+ med = Memr[d[n5-1]+j]
+ med = (med + Memr[d[n5]+j]) / 2.
+ } else
+ med = Memr[d[n5]+j]
+ n[i] = n4
+ }
+ if (combine == MEDIAN)
+ median[i] = med
+
+ # Reorder if pixels only if necessary.
+ if (nl > 1 && (combine != MEDIAN || grow > 0)) {
+ k = max (nl, n4 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ if (grow > 0) {
+ mp1 = m[l] + j
+ mp2 = m[k] + j
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+j] = Memi[m[k]+j]
+ k = k + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+j] = Memr[d[k]+j]
+ k = k + 1
+ }
+ }
+ }
+ }
+
+ # Check if data flag needs to be reset for rejected pixels.
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag whether the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/scombine/generic/icsclip.x b/noao/onedspec/scombine/generic/icsclip.x
new file mode 100644
index 00000000..e38f7935
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icsclip.x
@@ -0,0 +1,486 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "../icombine.h"
+
+define MINCLIP 3 # Mininum number of images for algorithm
+
+
+# IC_ASIGCLIP -- Reject pixels using sigma clipping about the average
+# The initial average rejects the high and low pixels. A correction for
+# different scalings of the images may be made. Weights are not used.
+
+procedure ic_asigclipr (d, m, n, scales, zeros, nimages, npts, average)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real average[npts] # Average
+
+int i, j, k, l, jj, n1, n2, nin, nk, maxkeep
+real d1, low, high, sum, a, s, r, one
+data one /1.0/
+pointer sp, resid, w, wp, dp1, dp2, mp1, mp2
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Flag whether returned average needs to be recomputed.
+ if (dowts || combine != AVERAGE)
+ docombine = true
+ else
+ docombine = false
+
+ # Save the residuals and the sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Do sigma clipping.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+
+ # If there are not enough pixels simply compute the average.
+ if (n1 < max (3, maxkeep)) {
+ if (!docombine) {
+ if (n1 == 0)
+ average[i] = blank
+ else {
+ sum = Memr[d[1]+k]
+ do j = 2, n1
+ sum = sum + Memr[d[j]+k]
+ average[i] = sum / n1
+ }
+ }
+ next
+ }
+
+ # Compute average with the high and low rejected.
+ low = Memr[d[1]+k]
+ high = Memr[d[2]+k]
+ if (low > high) {
+ d1 = low
+ low = high
+ high = d1
+ }
+ sum = 0.
+ do j = 3, n1 {
+ d1 = Memr[d[j]+k]
+ if (d1 < low) {
+ sum = sum + low
+ low = d1
+ } else if (d1 > high) {
+ sum = sum + high
+ high = d1
+ } else
+ sum = sum + d1
+ }
+ a = sum / (n1 - 2)
+ sum = sum + low + high
+
+ # Iteratively reject pixels and compute the final average if needed.
+ # Compact the data and keep track of the image IDs if needed.
+
+ repeat {
+ n2 = n1
+ if (doscale1) {
+ # Compute sigma corrected for scaling.
+ s = 0.
+ wp = w - 1
+ do j = 1, n1 {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ l = Memi[mp1]
+ r = sqrt (max (one, (a + zeros[l]) / scales[l]))
+ s = s + ((d1 - a) / r) ** 2
+ Memr[wp] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ wp = w - 1
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ wp = wp + 1
+
+ d1 = Memr[dp1]
+ r = (d1 - a) / (s * Memr[wp])
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ Memr[wp] = Memr[w+n1-1]
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ } else {
+ # Compute the sigma without scale correction.
+ s = 0.
+ do j = 1, n1
+ s = s + (Memr[d[j]+k] - a) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels. Save the residuals and data values.
+ if (s > 0.) {
+ for (j=1; j<=n1; j=j+1) {
+ dp1 = d[j] + k
+ d1 = Memr[dp1]
+ r = (d1 - a) / s
+ if (r < -lsigma || r > hsigma) {
+ Memr[resid+n1] = abs (r)
+ if (j < n1) {
+ dp2 = d[n1] + k
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[n1] + k
+ l = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = l
+ }
+ j = j - 1
+ }
+ sum = sum - d1
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ } until (n1 == n2 || n1 <= max (2, maxkeep))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ if (n1 < maxkeep) {
+ nk = maxkeep
+ if (doscale1) {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ mp1 = m[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ } else {
+ for (j=n1+1; j<=nk; j=j+1) {
+ dp1 = d[j] + k
+ r = Memr[resid+j]
+ jj = 0
+ do l = j+1, n2 {
+ s = Memr[resid+l]
+ if (s < r + TOL) {
+ if (s > r - TOL)
+ jj = jj + 1
+ else {
+ jj = 0
+ Memr[resid+l] = r
+ r = s
+ dp2 = d[l] + k
+ d1 = Memr[dp1]
+ Memr[dp1] = Memr[dp2]
+ Memr[dp2] = d1
+ if (keepids) {
+ mp1 = m[j] + k
+ mp2 = m[l] + k
+ s = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = s
+ }
+ }
+ }
+ }
+ sum = sum + Memr[dp1]
+ n1 = n1 + 1
+ nk = max (nk, j+jj)
+ }
+ }
+
+ # Recompute the average.
+ if (n1 > 1)
+ a = sum / n1
+ }
+
+ # Save the average if needed.
+ n[i] = n1
+ if (!docombine) {
+ if (n1 > 0)
+ average[i] = a
+ else
+ average[i] = blank
+ }
+ }
+
+ # Check if the data flag has to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# IC_MSIGCLIP -- Reject pixels using sigma clipping about the median
+
+procedure ic_msigclipr (d, m, n, scales, zeros, nimages, npts, median)
+
+pointer d[nimages] # Data pointers
+pointer m[nimages] # Image id pointers
+int n[npts] # Number of good pixels
+real scales[nimages] # Scales
+real zeros[nimages] # Zeros
+int nimages # Number of images
+int npts # Number of output points per line
+real median[npts] # Median
+
+int i, j, k, l, id, n1, n2, n3, nl, nh, nin, maxkeep
+real r, s
+pointer sp, resid, w, mp1, mp2
+real med, one
+data one /1.0/
+
+include "../icombine.com"
+
+begin
+ # If there are insufficient pixels go on to the combining
+ if (nkeep < 0)
+ maxkeep = max (0, nimages + nkeep)
+ else
+ maxkeep = min (nimages, nkeep)
+ if (nimages < max (MINCLIP, maxkeep+1) || dflag == D_NONE) {
+ docombine = true
+ return
+ }
+
+ # Save the residuals and sigma scaling corrections if needed.
+ call smark (sp)
+ call salloc (resid, nimages+1, TY_REAL)
+ if (doscale1)
+ call salloc (w, nimages, TY_REAL)
+
+ # Compute median and sigma and iteratively clip.
+ nin = n[1]
+ do i = 1, npts {
+ k = i - 1
+ n1 = n[i]
+ if (nkeep < 0)
+ maxkeep = max (0, n1 + nkeep)
+ else
+ maxkeep = min (n1, nkeep)
+ nl = 1
+ nh = n1
+
+ repeat {
+ n2 = n1
+ n3 = nl + n1 / 2
+
+ if (n1 == 0)
+ med = blank
+ else if (mod (n1, 2) == 0)
+ med = (Memr[d[n3-1]+k] + Memr[d[n3]+k]) / 2.
+ else
+ med = Memr[d[n3]+k]
+
+ if (n1 >= max (MINCLIP, maxkeep+1)) {
+ if (doscale1) {
+ # Compute the sigma with scaling correction.
+ s = 0.
+ do j = nl, nh {
+ l = Memi[m[j]+k]
+ r = sqrt (max (one, (med + zeros[l]) / scales[l]))
+ s = s + ((Memr[d[j]+k] - med) / r) ** 2
+ Memr[w+j-1] = r
+ }
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / (s * Memr[w+nl-1])
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / (s * Memr[w+nh-1])
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ } else {
+ # Compute the sigma without scaling correction.
+ s = 0.
+ do j = nl, nh
+ s = s + (Memr[d[j]+k] - med) ** 2
+ s = sqrt (s / (n1 - 1))
+
+ # Reject pixels and save the residuals.
+ if (s > 0.) {
+ for (; nl <= n2; nl = nl + 1) {
+ r = (med - Memr[d[nl]+k]) / s
+ if (r <= lsigma)
+ break
+ Memr[resid+nl] = r
+ n1 = n1 - 1
+ }
+ for (; nh >= nl; nh = nh - 1) {
+ r = (Memr[d[nh]+k] - med) / s
+ if (r <= hsigma)
+ break
+ Memr[resid+nh] = r
+ n1 = n1 - 1
+ }
+ }
+ }
+ }
+ } until (n1 == n2 || n1 < max (MINCLIP, maxkeep+1))
+
+ # If too many pixels are rejected add some back.
+ # All pixels with equal residuals are added back.
+ while (n1 < maxkeep) {
+ if (nl == 1)
+ nh = nh + 1
+ else if (nh == n[i])
+ nl = nl - 1
+ else {
+ r = Memr[resid+nl-1]
+ s = Memr[resid+nh+1]
+ if (r < s) {
+ nl = nl - 1
+ r = r + TOL
+ if (s <= r)
+ nh = nh + 1
+ if (nl > 1) {
+ if (Memr[resid+nl-1] <= r)
+ nl = nl - 1
+ }
+ } else {
+ nh = nh + 1
+ s = s + TOL
+ if (r <= s)
+ nl = nl - 1
+ if (nh < n2) {
+ if (Memr[resid+nh+1] <= s)
+ nh = nh + 1
+ }
+ }
+ }
+ n1 = nh - nl + 1
+ }
+
+ # Only set median and reorder if needed
+ n[i] = n1
+ if (n1 > 0 && nl > 1 && (combine != MEDIAN || grow > 0)) {
+ j = max (nl, n1 + 1)
+ if (keepids) {
+ do l = 1, min (n1, nl-1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ if (grow > 0) {
+ mp1 = m[l] + k
+ mp2 = m[j] + k
+ id = Memi[mp1]
+ Memi[mp1] = Memi[mp2]
+ Memi[mp2] = id
+ } else
+ Memi[m[l]+k] = Memi[m[j]+k]
+ j = j + 1
+ }
+ } else {
+ do l = 1, min (n1, nl - 1) {
+ Memr[d[l]+k] = Memr[d[j]+k]
+ j = j + 1
+ }
+ }
+ }
+
+ if (combine == MEDIAN)
+ median[i] = med
+ }
+
+ # Check if data flag needs to be reset for rejected pixels
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ if (n[i] != nin) {
+ dflag = D_MIX
+ break
+ }
+ }
+ }
+
+ # Flag that the median has been computed.
+ if (combine == MEDIAN)
+ docombine = false
+ else
+ docombine = true
+
+ call sfree (sp)
+end
+
diff --git a/noao/onedspec/scombine/generic/icsort.x b/noao/onedspec/scombine/generic/icsort.x
new file mode 100644
index 00000000..f3d2fb21
--- /dev/null
+++ b/noao/onedspec/scombine/generic/icsort.x
@@ -0,0 +1,275 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+define LOGPTR 32 # log2(maxpts) (4e9)
+
+
+# IC_SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially.
+
+procedure ic_sortr (a, b, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR]
+define swap {temp=$1;$1=$2;$2=temp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) # bac
+ b[2] = pivot
+ else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) # acb
+ b[2] = temp3
+ else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ do i = 1, npix
+ b[i] = Memr[a[i]+l]
+
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix
+ Memr[a[i]+l] = b[i]
+ }
+end
+
+
+# IC_2SORT -- Quicksort. This is based on the VOPS asrt except that
+# the input is an array of pointers to image lines and the sort is done
+# across the image lines at each point along the lines. The number of
+# valid pixels at each point is allowed to vary. The cases of 1, 2, and 3
+# pixels per point are treated specially. A second integer set of
+# vectors is sorted.
+
+procedure ic_2sortr (a, b, c, d, nvecs, npts)
+
+pointer a[ARB] # pointer to input vectors
+real b[ARB] # work array
+pointer c[ARB] # pointer to associated integer vectors
+int d[ARB] # work array
+int nvecs[npts] # number of vectors
+int npts # number of points in vectors
+
+real pivot, temp, temp3
+int i, j, k, l, p, npix, lv[LOGPTR], uv[LOGPTR], itemp
+define swap {temp=$1;$1=$2;$2=temp}
+define iswap {itemp=$1;$1=$2;$2=itemp}
+define copy_ 10
+
+begin
+ do l = 0, npts-1 {
+ npix = nvecs[l+1]
+ if (npix <= 1)
+ next
+
+ do i = 1, npix {
+ b[i] = Memr[a[i]+l]
+ d[i] = Memi[c[i]+l]
+ }
+
+ # Special cases
+ if (npix <= 3) {
+ pivot = b[1]
+ temp = b[2]
+ if (npix == 2) {
+ if (temp < pivot) {
+ b[1] = temp
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else
+ next
+ } else {
+ temp3 = b[3]
+ if (temp < pivot) { # bac|bca|cba
+ if (temp < temp3) { # bac|bca
+ b[1] = temp
+ if (pivot < temp3) { # bac
+ b[2] = pivot
+ iswap (d[1], d[2])
+ } else { # bca
+ b[2] = temp3
+ b[3] = pivot
+ itemp = d[2]
+ d[2] = d[3]
+ }
+ } else { # cba
+ b[1] = temp3
+ b[3] = pivot
+ iswap (d[1], d[3])
+ }
+ } else if (temp3 < temp) { # acb|cab
+ b[3] = temp
+ if (pivot < temp3) { # acb
+ b[2] = temp3
+ iswap (d[2], d[3])
+ } else { # cab
+ b[1] = temp3
+ b[2] = pivot
+ itemp = d[2]
+ d[2] = d[1]
+ d[1] = d[3]
+ d[3] = itemp
+ }
+ } else
+ next
+ }
+ goto copy_
+ }
+
+ # General case
+ lv[1] = 1
+ uv[1] = npix
+ p = 1
+
+ while (p > 0) {
+ if (lv[p] >= uv[p]) # only one elem in this subset
+ p = p - 1 # pop stack
+ else {
+ # Dummy do loop to trigger the Fortran optimizer.
+ do p = p, ARB {
+ i = lv[p] - 1
+ j = uv[p]
+
+ # Select as the pivot the element at the center of the
+ # array, to avoid quadratic behavior on an already
+ # sorted array.
+
+ k = (lv[p] + uv[p]) / 2
+ swap (b[j], b[k]); swap (d[j], d[k])
+ pivot = b[j] # pivot line
+
+ while (i < j) {
+ for (i=i+1; b[i] < pivot; i=i+1)
+ ;
+ for (j=j-1; j > i; j=j-1)
+ if (b[j] <= pivot)
+ break
+ if (i < j) { # out of order pair
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+ }
+ }
+
+ j = uv[p] # move pivot to position i
+ swap (b[i], b[j]) # interchange elements
+ swap (d[i], d[j])
+
+ if (i-lv[p] < uv[p] - i) { # stack so shorter done first
+ lv[p+1] = lv[p]
+ uv[p+1] = i - 1
+ lv[p] = i + 1
+ } else {
+ lv[p+1] = i + 1
+ uv[p+1] = uv[p]
+ uv[p] = i - 1
+ }
+
+ break
+ }
+ p = p + 1 # push onto stack
+ }
+ }
+
+copy_
+ do i = 1, npix {
+ Memr[a[i]+l] = b[i]
+ Memi[c[i]+l] = d[i]
+ }
+ }
+end
diff --git a/noao/onedspec/scombine/generic/mkpkg b/noao/onedspec/scombine/generic/mkpkg
new file mode 100644
index 00000000..4d371363
--- /dev/null
+++ b/noao/onedspec/scombine/generic/mkpkg
@@ -0,0 +1,16 @@
+$checkout libpkg.a ../
+$update libpkg.a
+$checkin libpkg.a ../
+$exit
+
+libpkg.a:
+ icaclip.x ../icombine.com ../icombine.h
+ icaverage.x ../icombine.com ../icombine.h <imhdr.h>
+ iccclip.x ../icombine.com ../icombine.h
+ icgrow.x ../icombine.com ../icombine.h
+ icmedian.x ../icombine.com ../icombine.h
+ icmm.x ../icombine.com ../icombine.h
+ icpclip.x ../icombine.com ../icombine.h
+ icsclip.x ../icombine.com ../icombine.h
+ icsort.x
+ ;
diff --git a/noao/onedspec/scombine/icgdata.x b/noao/onedspec/scombine/icgdata.x
new file mode 100644
index 00000000..907adc5e
--- /dev/null
+++ b/noao/onedspec/scombine/icgdata.x
@@ -0,0 +1,199 @@
+include <smw.h>
+include "icombine.h"
+
+
+# IC_GDATAR - Apply threshold, scaling, and masking
+
+procedure ic_gdatar (sh, d, id, n, m, lflag, scales, zeros, nimages, npts)
+
+pointer sh[nimages] # Input spectra structures
+pointer d[nimages] # Data pointers
+pointer id[nimages] # ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Empty mask flags
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+int nimages # Number of spectra
+int npts # NUmber of output points
+
+int i, j, k, l, nused
+real a, b
+pointer dp, ip, mp
+
+include "icombine.com"
+
+begin
+ # Set data vectors
+ do i = 1, nimages {
+ d[i] = SY(sh[i])
+ m[i] = SX(sh[i])
+ }
+
+ # Apply threshold if needed
+ if (dothresh) {
+ do i = 1, nimages {
+ dp = d[i]
+ if (lflag[i] == D_ALL) {
+ do j = 1, npts {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memr[m[i]+j-1] = 1
+ lflag[i] = D_MIX
+ dflag = D_MIX
+ }
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ mp = m[i]
+ do j = 1, npts {
+ if (Memr[mp] == 0) {
+ a = Memr[dp]
+ if (a < lthresh || a > hthresh) {
+ Memr[m[i]+j-1] = 1
+ dflag = D_MIX
+ }
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+
+ # Check for completely empty lines
+ if (lflag[i] == D_MIX) {
+ lflag[i] = D_NONE
+ mp = m[i]
+ do j = 1, npts {
+ if (Memr[mp] == 0) {
+ lflag[i] = D_MIX
+ break
+ }
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Apply scaling (avoiding masked pixels which might overflow?)
+ if (doscale) {
+ if (dflag == D_ALL) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ }
+ } else if (dflag == D_MIX) {
+ do i = 1, nimages {
+ dp = d[i]
+ a = scales[i]
+ b = -zeros[i]
+ if (lflag[i] == D_ALL) {
+ do j = 1, npts {
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ }
+ } else if (lflag[i] == D_MIX) {
+ mp = m[i]
+ do j = 1, npts {
+ if (Memr[mp] == 0)
+ Memr[dp] = Memr[dp] / a + b
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+ }
+
+ # Sort pointers to exclude unused images.
+ # Use the lflag array to keep track of the image index.
+
+ if (dflag == D_ALL)
+ nused = nimages
+ else {
+ nused = 0
+ do i = 1, nimages
+ if (lflag[i] != D_NONE) {
+ nused = nused + 1
+ d[nused] = d[i]
+ m[nused] = m[i]
+ lflag[nused] = i
+ }
+ if (nused == 0)
+ dflag = D_NONE
+ }
+
+ # Compact data to remove bad pixels
+ # Keep track of the image indices if needed
+ # If growing mark the end of the included image indices with zero
+
+ if (dflag == D_ALL) {
+ call amovki (nused, n, npts)
+ if (keepids)
+ do i = 1, nimages
+ call amovki (i, Memi[id[i]], npts)
+ } else if (dflag == D_NONE)
+ call aclri (n, npts)
+ else {
+ call aclri (n, npts)
+ if (keepids) {
+ do i = 1, nused {
+ l = lflag[i]
+ dp = d[i]
+ ip = id[i]
+ mp = m[i]
+ do j = 1, npts {
+ if (Memr[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i) {
+ Memr[d[k]+j-1] = Memr[dp]
+ Memi[id[k]+j-1] = l
+ } else
+ Memi[ip] = l
+ }
+ dp = dp + 1
+ ip = ip + 1
+ mp = mp + 1
+ }
+ }
+ if (grow > 0) {
+ do j = 0, npts-1 {
+ do i = n[i]+1, nimages
+ Memi[id[i]+j] = 0
+ }
+ }
+ } else {
+ do i = 1, nused {
+ dp = d[i]
+ mp = m[i]
+ do j = 1, npts {
+ if (Memr[mp] == 0) {
+ n[j] = n[j] + 1
+ k = n[j]
+ if (k < i)
+ Memr[d[k]+j-1] = Memr[dp]
+ }
+ dp = dp + 1
+ mp = mp + 1
+ }
+ }
+ }
+ }
+
+ # Sort the pixels and IDs if needed
+ if (mclip) {
+ call malloc (dp, nimages, TY_REAL)
+ if (keepids) {
+ call malloc (ip, nimages, TY_INT)
+ call ic_2sortr (d, Memr[dp], id, Memi[ip], n, npts)
+ call mfree (ip, TY_INT)
+ } else
+ call ic_sortr (d, Memr[dp], n, npts)
+ call mfree (dp, TY_REAL)
+ }
+end
diff --git a/noao/onedspec/scombine/iclog.x b/noao/onedspec/scombine/iclog.x
new file mode 100644
index 00000000..29002c0f
--- /dev/null
+++ b/noao/onedspec/scombine/iclog.x
@@ -0,0 +1,301 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <mach.h>
+include <smw.h>
+include "icombine.h"
+
+# IC_LOG -- Output log information is a log file has been specfied.
+
+procedure ic_log (sh, shout, ncombine, exptime, sname, zname, wname,
+ mode, median, mean, scales, zeros, wts, nimages,
+ dozero, nout, expname, exposure)
+
+pointer sh[nimages] # Input spectra
+pointer shout # Output spectrum
+int ncombine[nimages] # Number of previous combined images
+real exptime[nimages] # Exposure times
+char sname[ARB] # Scale name
+char zname[ARB] # Zero name
+char wname[ARB] # Weight name
+real mode[nimages] # Modes
+real median[nimages] # Medians
+real mean[nimages] # Means
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+int nimages # Number of images
+bool dozero # Zero flag
+int nout # Number of images combined in output
+char expname[ARB] # Exposure name
+real exposure # Output exposure
+
+int i, j, ctor()
+real rval
+long clktime()
+bool prncombine, prexptime, prmode, prmedian, prmean
+bool prrdn, prgain, prsn, prscale, przero, prwts, strne()
+pointer sp, fname
+
+include "icombine.com"
+
+begin
+ if (logfd == NULL)
+ return
+
+ call smark (sp)
+ call salloc (fname, SZ_LINE, TY_CHAR)
+
+ # Time stamp the log and print parameter information.
+
+ call cnvdate (clktime(0), Memc[fname], SZ_LINE)
+ call fprintf (logfd, "\n%s: SCOMBINE\n")
+ call pargstr (Memc[fname])
+ switch (combine) {
+ case AVERAGE:
+ call fprintf (logfd, " combine = average,")
+ case MEDIAN:
+ call fprintf (logfd, " combine = median,")
+ case SUM:
+ call fprintf (logfd, " combine = sum\n")
+ }
+ if (combine != SUM) {
+ call fprintf (logfd, " scale = %s, zero = %s, weight = %s\n")
+ call pargstr (sname)
+ call pargstr (zname)
+ call pargstr (wname)
+ }
+
+ switch (reject) {
+ case MINMAX:
+ call fprintf (logfd, " reject = minmax, nlow = %d, nhigh = %d\n")
+ call pargi (nint (flow * nimages))
+ call pargi (nint (fhigh * nimages))
+ case CCDCLIP:
+ call fprintf (logfd, " reject = ccdclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, sigma = %g, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case CRREJECT:
+ call fprintf (logfd,
+ " reject = crreject, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd,
+ " rdnoise = %s, gain = %s, snoise = %s, hsigma = %g\n")
+ call pargstr (Memc[rdnoise])
+ call pargstr (Memc[gain])
+ call pargstr (Memc[snoise])
+ call pargr (hsigma)
+ case PCLIP:
+ call fprintf (logfd, " reject = pclip, nkeep = %d\n")
+ call pargi (nkeep)
+ call fprintf (logfd, " pclip = %g, lsigma = %g, hsigma = %g\n")
+ call pargr (pclip)
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case SIGCLIP:
+ call fprintf (logfd, " reject = sigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ case AVSIGCLIP:
+ call fprintf (logfd,
+ " reject = avsigclip, mclip = %b, nkeep = %d\n")
+ call pargb (mclip)
+ call pargi (nkeep)
+ call fprintf (logfd, " lsigma = %g, hsigma = %g\n")
+ call pargr (lsigma)
+ call pargr (hsigma)
+ }
+ if (reject != NONE && grow > 0) {
+ call fprintf (logfd, " grow = %d\n")
+ call pargi (grow)
+ }
+ if (dothresh) {
+ if (lthresh > -MAX_REAL && hthresh < MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g, hthreshold = %g\n")
+ call pargr (lthresh)
+ call pargr (hthresh)
+ } else if (lthresh > -MAX_REAL) {
+ call fprintf (logfd, " lthreshold = %g\n")
+ call pargr (lthresh)
+ } else {
+ call fprintf (logfd, " hthreshold = %g\n")
+ call pargr (hthresh)
+ }
+ }
+ call fprintf (logfd, " blank = %g\n")
+ call pargr (blank)
+ call clgstr ("sample", Memc[fname], SZ_LINE)
+ if (Memc[fname] != EOS) {
+ call fprintf (logfd, " sample = %s\n")
+ call pargstr (Memc[fname])
+ }
+
+ # Print information pertaining to individual images as a set of
+ # columns with the image name being the first column. Determine
+ # what information is relevant and print the appropriate header.
+
+ prncombine = false
+ prexptime = (expname[1] != EOS)
+ prscale = (doscale || strne (sname, "none"))
+ przero = (dozero || strne (zname, "none"))
+ prwts = (dowts || strne (wname, "none"))
+ prmode = false
+ prmedian = false
+ prmean = false
+ prrdn = false
+ prgain = false
+ prsn = false
+ do i = 1, nimages {
+ if (ncombine[i] != ncombine[1])
+ prncombine = true
+ if (exptime[i] != exptime[1])
+ prexptime = true
+ if (mode[i] != mode[1])
+ prmode = true
+ if (median[i] != median[1])
+ prmedian = true
+ if (mean[i] != mean[1])
+ prmean = true
+ if (reject == CCDCLIP || reject == CRREJECT) {
+ j = 1
+ if (ctor (Memc[rdnoise], j, rval) == 0)
+ prrdn = true
+ j = 1
+ if (ctor (Memc[gain], j, rval) == 0)
+ prgain = true
+ j = 1
+ if (ctor (Memc[snoise], j, rval) == 0)
+ prsn = true
+ }
+ }
+
+ call fprintf (logfd, " %20s ")
+ call pargstr ("Images")
+ if (prncombine) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("N")
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Exp")
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mode")
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Median")
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Mean")
+ }
+ if (prrdn) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Rdnoise")
+ }
+ if (prgain) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Gain")
+ }
+ if (prsn) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Snoise")
+ }
+ if (prscale) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Scale")
+ }
+ if (przero) {
+ call fprintf (logfd, " %7s")
+ call pargstr ("Zero")
+ }
+ if (prwts) {
+ call fprintf (logfd, " %6s")
+ call pargstr ("Weight")
+ }
+ call fprintf (logfd, "\n")
+
+ do i = 1, nimages {
+ call fprintf (logfd, " %16s[%3d]")
+ call pargstr (IMNAME(sh[i]))
+ call pargi (AP(sh[i]))
+ if (prncombine) {
+ call fprintf (logfd, " %6d")
+ call pargi (ncombine[i])
+ }
+ if (prexptime) {
+ call fprintf (logfd, " %6.1f")
+ call pargr (exptime[i])
+ }
+ if (prmode) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mode[i])
+ }
+ if (prmedian) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (median[i])
+ }
+ if (prmean) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (mean[i])
+ }
+ if (prrdn) {
+ call fprintf (logfd, " %7g")
+ call pargr (RA(sh[i]))
+ }
+ if (prgain) {
+ call fprintf (logfd, " %6g")
+ call pargr (DEC(sh[i]))
+ }
+ if (prsn) {
+ call fprintf (logfd, " %6g")
+ call pargr (UT(sh[i]))
+ }
+ if (prscale) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (1./scales[i])
+ }
+ if (przero) {
+ call fprintf (logfd, " %7.5g")
+ call pargr (-zeros[i])
+ }
+ if (prwts) {
+ call fprintf (logfd, " %6.3f")
+ call pargr (wts[i])
+ }
+ call fprintf (logfd, "\n")
+ }
+
+ # Log information about the output images.
+ call fprintf (logfd, "\n Output image = %s, ncombine = %d")
+ call pargstr (IMNAME(shout))
+ call pargi (nout)
+ if (expname[1] != EOS) {
+ call fprintf (logfd, ", %s = %g")
+ call pargstr (expname)
+ call pargr (exposure)
+ }
+ call fprintf (logfd, "\n")
+ call fprintf (logfd,
+ " w1 = %g, w2 = %g, dw = %g, nw = %g, dtype = %d\n")
+ call pargr (W0(shout))
+ call pargr (W1(shout))
+ call pargr (WP(shout))
+ call pargi (SN(shout))
+ call pargi (DC(shout))
+
+ call flush (logfd)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/scombine/icombine.com b/noao/onedspec/scombine/icombine.com
new file mode 100644
index 00000000..771ada77
--- /dev/null
+++ b/noao/onedspec/scombine/icombine.com
@@ -0,0 +1,36 @@
+# SCOMBINE Common
+
+int combine # Combine algorithm
+int reject # Rejection algorithm
+real blank # Blank value
+pointer rdnoise # CCD read noise
+pointer gain # CCD gain
+pointer snoise # CCD sensitivity noise
+real lthresh # Low threshold
+real hthresh # High threshold
+int nkeep # Minimum to keep
+real lsigma # Low sigma cutoff
+real hsigma # High sigma cutoff
+real pclip # Number or fraction of pixels from median
+real flow # Fraction of low pixels to reject
+real fhigh # Fraction of high pixels to reject
+int grow # Grow radius
+bool mclip # Use median in sigma clipping?
+real sigscale # Sigma scaling tolerance
+int logfd # Log file descriptor
+
+# These flags allow special conditions to be optimized.
+
+int dflag # Data flag (D_ALL, D_NONE, D_MIX)
+bool doscale # Do the images have to be scaled?
+bool doscale1 # Do the sigma calculations have to be scaled?
+bool dothresh # Check pixels outside specified thresholds?
+bool dowts # Does the final average have to be weighted?
+bool keepids # Keep track of the image indices?
+bool docombine # Call the combine procedure?
+bool sort # Sort data?
+
+common /scbcom/ combine, reject, blank, rdnoise, gain, snoise, lsigma, hsigma,
+ lthresh, hthresh, nkeep, pclip, flow, fhigh, grow, logfd,
+ dflag, sigscale, mclip, doscale, doscale1,
+ dothresh, dowts, keepids, docombine, sort
diff --git a/noao/onedspec/scombine/icombine.h b/noao/onedspec/scombine/icombine.h
new file mode 100644
index 00000000..8a45a673
--- /dev/null
+++ b/noao/onedspec/scombine/icombine.h
@@ -0,0 +1,74 @@
+# SCOMBINE Definitions
+
+# Grouping options
+define GROUP "|all|images|apertures|"
+define GRP_ALL 1
+define GRP_IMAGES 2
+define GRP_APERTURES 3
+
+# Sorting options
+define SORT "|none|increasing|decreasing|"
+define SORT_NONE 1
+define SORT_INC 2
+define SORT_DEC 3
+
+# Combining modes in interactive mode
+define CMB_AGAIN 0
+define CMB_ALL 1
+define CMB_FIRST 2
+define CMB_NEXT 3
+define CMB_SKIP 4
+
+# Rejection options:
+define REJECT "|none|ccdclip|crreject|minmax|pclip|sigclip|avsigclip|"
+define NONE 1 # No rejection algorithm
+define CCDCLIP 2 # CCD noise function clipping
+define CRREJECT 3 # CCD noise function clipping
+define MINMAX 4 # Minmax rejection
+define PCLIP 5 # Percentile clip
+define SIGCLIP 6 # Sigma clip
+define AVSIGCLIP 7 # Sigma clip with average poisson sigma
+
+# Combine options:
+define COMBINE "|average|median|sum|"
+define AVERAGE 1
+define MEDIAN 2
+define SUM 3
+
+# Scaling options:
+define STYPES "|none|mode|median|mean|exposure|"
+define ZTYPES "|none|mode|median|mean|"
+define WTYPES "|none|mode|median|mean|exposure|"
+define S_NONE 1
+define S_MODE 2
+define S_MEDIAN 3
+define S_MEAN 4
+define S_EXPOSURE 5
+define S_FILE 6
+define S_KEYWORD 7
+define S_SECTION "|input|output|overlap|"
+define S_INPUT 1
+define S_OUTPUT 2
+define S_OVERLAP 3
+
+# Data flag
+define D_ALL 0 # All pixels are good
+define D_NONE 1 # All pixels are bad or rejected
+define D_MIX 2 # Mixture of good and bad pixels
+
+define TOL 0.001 # Tolerance for equal residuals
+
+# Spectrum data structure
+define NS Memi[$1+$2-1] # Number of spec of given ap
+define SH Memi[Memi[$1+$2-1]+$3-1] # Spectrum header structure
+
+# Combining options
+#define COMBINE "|average|sum|"
+#define CMB_AVERAGE 1
+#define CMB_SUM 2
+
+# Weighting options
+#define WT_TYPE "|none|expo|user|"
+#define WT_NONE 1
+#define WT_EXPO 2
+#define WT_USER 3
diff --git a/noao/onedspec/scombine/icombine.x b/noao/onedspec/scombine/icombine.x
new file mode 100644
index 00000000..5650d3ab
--- /dev/null
+++ b/noao/onedspec/scombine/icombine.x
@@ -0,0 +1,174 @@
+include <mach.h>
+include <smw.h>
+include "icombine.h"
+
+
+# IC_COMBINE -- Combine images.
+
+procedure ic_combiner (sh, shout, d, id, n, m, lflag, scales, zeros, wts,
+ nimages, npts)
+
+pointer sh[nimages] # Input spectra
+pointer shout # Output spectrum
+pointer d[nimages] # Data pointers
+pointer id[nimages] # Image index ID pointers
+int n[npts] # Number of good pixels
+pointer m[nimages] # Mask pointers
+int lflag[nimages] # Line flags
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero offset factors
+real wts[nimages] # Combining weights
+int nimages # Number of input images
+int npts # Number of points per output line
+
+int i, ctor()
+real r
+pointer sp, nm
+errchk ic_scale
+
+include "icombine.com"
+
+begin
+ call smark (sp)
+
+ # Rebin spectra and set mask arrays
+ call scb_rebin (sh, shout, lflag, nimages, npts)
+
+ # Set scale and weights and log
+ call ic_scale (sh, shout, lflag, scales, zeros, wts, nimages)
+
+ # Set combine parameters
+ switch (combine) {
+ case AVERAGE:
+ if (dowts)
+ keepids = true
+ else
+ keepids = false
+ case MEDIAN:
+ dowts = false
+ keepids = false
+ case SUM:
+ keepids = false
+ reject = NONE
+ grow = 0
+ }
+ docombine = true
+
+ # Set rejection algorithm specific parameters
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ call salloc (nm, 3*nimages, TY_REAL)
+ i = 1
+ if (ctor (Memc[rdnoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = r
+ } else {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)] = RA(sh[i])
+ }
+ i = 1
+ if (ctor (Memc[gain], i, r) > 0) {
+ do i = 1, nimages {
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] = (Memr[nm+3*(i-1)] / r) ** 2
+ }
+ } else {
+ do i = 1, nimages {
+ r = DEC(sh[i])
+ Memr[nm+3*(i-1)+1] = r
+ Memr[nm+3*(i-1)] = (Memr[nm+3*(i-1)] / r) ** 2
+ }
+ }
+ i = 1
+ if (ctor (Memc[snoise], i, r) > 0) {
+ do i = 1, nimages
+ Memr[nm+3*(i-1)+2] = r
+ } else {
+ do i = 1, nimages {
+ r = UT(sh[i])
+ Memr[nm+3*(i-1)+2] = r
+ }
+ }
+ if (!keepids) {
+ if (doscale1 || grow > 0)
+ keepids = true
+ else {
+ do i = 2, nimages {
+ if (Memr[nm+3*(i-1)] != Memr[nm] ||
+ Memr[nm+3*(i-1)+1] != Memr[nm+1] ||
+ Memr[nm+3*(i-1)+2] != Memr[nm+2]) {
+ keepids = true
+ break
+ }
+ }
+ }
+ }
+ if (reject == CRREJECT)
+ lsigma = MAX_REAL
+ case MINMAX:
+ mclip = false
+ if (grow > 0)
+ keepids = true
+ case PCLIP:
+ mclip = true
+ if (grow > 0)
+ keepids = true
+ case AVSIGCLIP, SIGCLIP:
+ if (doscale1 || grow > 0)
+ keepids = true
+ case NONE:
+ mclip = false
+ grow = 0
+ }
+
+ if (keepids) {
+ do i = 1, nimages
+ call salloc (id[i], npts, TY_INT)
+ }
+
+ call ic_gdatar (sh, d, id, n, m, lflag, scales, zeros, nimages, npts)
+
+ switch (reject) {
+ case CCDCLIP, CRREJECT:
+ if (mclip)
+ call ic_mccdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[SY(shout)])
+ else
+ call ic_accdclipr (d, id, n, scales, zeros, Memr[nm],
+ nimages, npts, Memr[SY(shout)])
+ case MINMAX:
+ call ic_mmr (d, id, n, npts)
+ case PCLIP:
+ call ic_pclipr (d, id, n, nimages, npts, Memr[SY(shout)])
+ case SIGCLIP:
+ if (mclip)
+ call ic_msigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[SY(shout)])
+ else
+ call ic_asigclipr (d, id, n, scales, zeros, nimages, npts,
+ Memr[SY(shout)])
+ case AVSIGCLIP:
+ if (mclip)
+ call ic_mavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[SY(shout)])
+ else
+ call ic_aavsigclipr (d, id, n, scales, zeros, nimages,
+ npts, Memr[SY(shout)])
+ }
+
+ if (grow > 0)
+ call ic_growr (d, id, n, nimages, npts, Memr[SY(shout)])
+
+ if (docombine) {
+ switch (combine) {
+ case AVERAGE:
+ call ic_averager (d, id, n, wts, npts, Memr[SY(shout)])
+ case MEDIAN:
+ call ic_medianr (d, n, npts, Memr[SY(shout)])
+ case SUM:
+ call ic_sumr (d, n, npts, Memr[SY(shout)])
+ }
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/scombine/icscale.x b/noao/onedspec/scombine/icscale.x
new file mode 100644
index 00000000..009b30c3
--- /dev/null
+++ b/noao/onedspec/scombine/icscale.x
@@ -0,0 +1,463 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <imhdr.h>
+include <imset.h>
+include <error.h>
+include <ctype.h>
+include <smw.h>
+include "icombine.h"
+
+# IC_SCALE -- Get the scale factors for the spectra.
+# 1. This procedure does CLIO to determine the type of scaling desired.
+# 2. The output header parameters for exposure time and NCOMBINE are set.
+
+procedure ic_scale (sh, shout, lflags, scales, zeros, wts, nimages)
+
+pointer sh[nimages] # Input spectra
+pointer shout # Output spectrum
+int lflags[nimages] # Data flags
+real scales[nimages] # Scale factors
+real zeros[nimages] # Zero or sky levels
+real wts[nimages] # Weights
+int nimages # Number of images
+
+int stype, ztype, wtype
+int i, j, nout
+real mode, median, mean, exposure, zmean
+pointer sp, ncombine, exptime, modes, medians, means, expname
+pointer str, sname, zname, wname, rg
+bool domode, domedian, domean, dozero
+
+int ic_gscale()
+real asumr(), asumi()
+pointer ic_wranges()
+errchk ic_gscale, ic_statr
+
+include "icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (ncombine, nimages, TY_INT)
+ call salloc (exptime, nimages, TY_REAL)
+ call salloc (modes, nimages, TY_REAL)
+ call salloc (medians, nimages, TY_REAL)
+ call salloc (means, nimages, TY_REAL)
+ call salloc (expname, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (sname, SZ_FNAME, TY_CHAR)
+ call salloc (zname, SZ_FNAME, TY_CHAR)
+ call salloc (wname, SZ_FNAME, TY_CHAR)
+
+ # Set the defaults.
+ call amovki (1, Memi[ncombine], nimages)
+ call amovkr (0., Memr[exptime], nimages)
+ call amovkr (INDEF, Memr[modes], nimages)
+ call amovkr (INDEF, Memr[medians], nimages)
+ call amovkr (INDEF, Memr[means], nimages)
+ call amovkr (1., scales, nimages)
+ call amovkr (0., zeros, nimages)
+ call amovkr (1., wts, nimages)
+
+ # Set scaling factors.
+ if (combine == SUM) {
+ stype = S_NONE
+ ztype = S_NONE
+ wtype = S_NONE
+ do i = 1, nimages
+ Memr[exptime+i-1] = IT(sh[i])
+ } else {
+ stype = ic_gscale ("scale", Memc[sname], STYPES, sh, Memr[exptime],
+ scales, nimages)
+ ztype = ic_gscale ("zero", Memc[zname], ZTYPES, sh, Memr[exptime],
+ zeros, nimages)
+ wtype = ic_gscale ("weight", Memc[wname], WTYPES, sh, Memr[exptime],
+ wts, nimages)
+ }
+
+ Memc[expname] = EOS
+ if (combine == SUM || stype == S_EXPOSURE || wtype == S_EXPOSURE) {
+ call strcpy ("exptime", Memc[expname], SZ_FNAME)
+ do i = 1, nimages
+ if (IS_INDEFR(Memr[exptime+i-1]))
+ Memc[expname] = EOS
+ }
+
+ # Get image statistics only if needed.
+ domode = ((stype==S_MODE)||(ztype==S_MODE)||(wtype==S_MODE))
+ domedian = ((stype==S_MEDIAN)||(ztype==S_MEDIAN)||(wtype==S_MEDIAN))
+ domean = ((stype==S_MEAN)||(ztype==S_MEAN)||(wtype==S_MEAN))
+ if (domode || domedian || domean) {
+ call clgstr ("sample", Memc[str], SZ_LINE)
+ rg = ic_wranges (Memc[str])
+ do i = 1, nimages {
+ call ic_statr (sh[i], lflags[i], rg, domode, domedian, domean,
+ mode, median, mean)
+ if (domode) {
+ Memr[modes+i-1] = mode
+ if (stype == S_MODE)
+ scales[i] = mode
+ if (ztype == S_MODE)
+ zeros[i] = mode
+ if (wtype == S_MODE)
+ wts[i] = mode
+ }
+ if (domedian) {
+ Memr[medians+i-1] = median
+ if (stype == S_MEDIAN)
+ scales[i] = median
+ if (ztype == S_MEDIAN)
+ zeros[i] = median
+ if (wtype == S_MEDIAN)
+ wts[i] = median
+ }
+ if (domean) {
+ Memr[means+i-1] = mean
+ if (stype == S_MEAN)
+ scales[i] = mean
+ if (ztype == S_MEAN)
+ zeros[i] = mean
+ if (wtype == S_MEAN)
+ wts[i] = mean
+ }
+ }
+ call mfree (rg, TY_REAL)
+ }
+
+ do i = 1, nimages
+ if (scales[i] <= 0.) {
+ call eprintf ("WARNING: Negative scale factors")
+ call eprintf (" -- ignoring scaling\n")
+ call amovkr (1., scales, nimages)
+ break
+ }
+
+ # Convert to relative factors.
+ mean = asumr (scales, nimages) / nimages
+ call adivkr (scales, mean, scales, nimages)
+ call adivr (zeros, scales, zeros, nimages)
+ zmean = asumr (zeros, nimages) / nimages
+
+ if (wtype != S_NONE) {
+ do i = 1, nimages {
+ if (wts[i] <= 0.) {
+ call eprintf ("WARNING: Negative weights")
+ call eprintf (" -- using only NCOMBINE weights\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1]
+ break
+ }
+ if (ztype == S_NONE)
+ wts[i] = Memi[ncombine+i-1] * wts[i]
+ else {
+ if (zeros[i] <= 0.) {
+ call eprintf ("WARNING: Negative zero offsets")
+ call eprintf (" -- ignoring zero weight adjustments\n")
+ do j = 1, nimages
+ wts[j] = Memi[ncombine+j-1] * wts[j]
+ break
+ }
+ wts[i] = Memi[ncombine+i-1] * wts[i] * zmean / zeros[i]
+ }
+ }
+ }
+
+ call asubkr (zeros, zmean, zeros, nimages)
+ mean = asumr (wts, nimages)
+ call adivkr (wts, mean, wts, nimages)
+
+ # Because of finite arithmetic it is possible for the zero offsets to
+ # be nonzero even when they are all equal. Just for the sake of
+ # a nice log set the zero offsets in this case.
+
+ for (i=2; (i<=nimages)&&(zeros[i]==zeros[1]); i=i+1)
+ ;
+ if (i > nimages)
+ call aclrr (zeros, nimages)
+
+ # Set flags for scaling, zero offsets, sigma scaling, weights.
+ # Sigma scaling may be suppressed if the scales or zeros are
+ # different by a specified tolerance.
+
+ doscale = false
+ dozero = false
+ doscale1 = false
+ dowts = false
+ do i = 2, nimages {
+ if (scales[i] != scales[1])
+ doscale = true
+ if (zeros[i] != zeros[1])
+ dozero = true
+ if (wts[i] != wts[1])
+ dowts = true
+ }
+ if (doscale && sigscale != 0.) {
+ do i = 1, nimages {
+ if (abs (scales[i] - 1) > sigscale) {
+ doscale1 = true
+ break
+ }
+ }
+ if (!doscale1 && zmean > 0.) {
+ do i = 1, nimages {
+ if (abs (zeros[i] / zmean) > sigscale) {
+ doscale1 = true
+ break
+ }
+ }
+ }
+ }
+
+ # Set the output header parameters.
+ nout = asumi (Memi[ncombine], nimages)
+ call imaddi (IM(shout), "ncombine", nout)
+ if (Memc[expname] != EOS) {
+ exposure = 0.
+ if (combine == SUM) {
+ do i = 1, nimages
+ exposure = exposure + Memr[exptime+i-1]
+ } else {
+ do i = 1, nimages
+ exposure = exposure + wts[i] * Memr[exptime+i-1] / scales[i]
+ }
+ call imaddr (IM(shout), Memc[expname], exposure)
+ } else
+ exposure = INDEF
+
+ # Start the log here since much of the info is only available here.
+ call ic_log (sh, shout, Memi[ncombine], Memr[exptime], Memc[sname],
+ Memc[zname], Memc[wname], Memr[modes], Memr[medians], Memr[means],
+ scales, zeros, wts, nimages, dozero, nout, Memc[expname], exposure)
+
+ doscale = (doscale || dozero)
+
+ call sfree (sp)
+end
+
+
+# IC_GSCALE -- Get scale values as directed by CL parameter
+# The values can be one of those in the dictionary, from a file specified
+# with a @ prefix, or from an image header keyword specified by a ! prefix.
+
+int procedure ic_gscale (param, name, dic, sh, exptime, values, nimages)
+
+char param[ARB] #I CL parameter name
+char name[SZ_FNAME] #O Parameter value
+char dic[ARB] #I Dictionary string
+pointer sh[nimages] #I SHDR pointers
+real exptime[nimages] #I Exposure times
+real values[nimages] #O Values
+int nimages #I Number of images
+
+int type #O Type of value
+
+int fd, i, nowhite(), open(), fscan(), nscan(), strdic()
+real rval
+pointer errstr
+errchk open
+
+include "icombine.com"
+
+begin
+ call clgstr (param, name, SZ_FNAME)
+ if (nowhite (name, name, SZ_FNAME) == 0)
+ type = S_NONE
+ else if (name[1] == '@') {
+ type = S_FILE
+ fd = open (name[2], READ_ONLY, TEXT_FILE)
+ i = 0
+ while (fscan (fd) != EOF) {
+ call gargr (rval)
+ if (nscan() != 1)
+ next
+ if (i == nimages) {
+ call eprintf (
+ "Warning: Ignoring additional %s values in %s\n")
+ call pargstr (param)
+ call pargstr (name[2])
+ break
+ }
+ i = i + 1
+ values[i] = rval
+ }
+ call close (fd)
+ if (i < nimages) {
+ call salloc (errstr, SZ_LINE, TY_CHAR)
+ call sprintf (Memc[errstr], SZ_FNAME,
+ "Insufficient %s values in %s")
+ call pargstr (param)
+ call pargstr (name[2])
+ call error (1, Memc[errstr])
+ }
+ } else if (name[1] == '!') {
+ type = S_KEYWORD
+ do i = 1, nimages {
+ switch (param[1]) {
+ case 's':
+ values[i] = ST(sh[i])
+ case 'z':
+ values[i] = HA(sh[i])
+ case 'w':
+ values[i] = AM(sh[i])
+ }
+ }
+ } else {
+ type = strdic (name, name, SZ_FNAME, dic)
+ if (type == 0)
+ call error (1, "Unknown scale, zero, or weight type")
+ if (type==S_EXPOSURE) {
+ do i = 1, nimages {
+ if (IS_INDEF(IT(sh[i])))
+ call error (1, "Exposure time not defined")
+ exptime[i] = IT(sh[i])
+ values[i] = max (0.001, exptime[i])
+ }
+ }
+ }
+
+ return (type)
+end
+
+
+# IC_WRANGES -- Parse wavelength range string.
+# A wavelength range string consists of colon delimited ranges with
+# multiple ranges separated by comma and/or whitespace.
+
+pointer procedure ic_wranges (rstr)
+
+char rstr[ARB] # Range string
+pointer rg # Range pointer
+
+int i, fd, strlen(), open(), getline()
+pointer sp, str, ptr
+errchk open, ic_wadd
+
+begin
+ call smark (sp)
+ call salloc (str, max (strlen (rstr), SZ_LINE), TY_CHAR)
+ call calloc (rg, 1, TY_REAL)
+
+ i = 1
+ while (rstr[i] != EOS) {
+
+ # Find beginning and end of a range and copy it to the work string
+ while (IS_WHITE(rstr[i]) || rstr[i]==',' || rstr[i]=='\n')
+ i = i + 1
+ if (rstr[i] == EOS)
+ break
+
+ ptr = str
+ while (!(IS_WHITE(rstr[i]) || rstr[i]==',' || rstr[i]=='\n' ||
+ rstr[i]==EOS)) {
+ Memc[ptr] = rstr[i]
+ i = i + 1
+ ptr = ptr + 1
+ }
+ Memc[ptr] = EOS
+
+ # Add range(s)
+ iferr {
+ if (Memc[str] == '@') {
+ fd = open (Memc[str+1], READ_ONLY, TEXT_FILE)
+ while (getline (fd, Memc[str]) != EOF) {
+ iferr (call ic_wadd (rg, Memc[str]))
+ call erract (EA_WARN)
+ }
+ call close (fd)
+ } else
+ call ic_wadd (rg, Memc[str])
+ } then
+ call erract (EA_WARN)
+ }
+
+ call sfree (sp)
+
+ # Set final structure
+ i = Memr[rg]
+ if (i == 0)
+ call mfree (rg, TY_REAL)
+ else
+ call realloc (rg, 1 + 2 * i, TY_REAL)
+ return (rg)
+end
+
+
+# IC_WADD -- Add a range
+
+procedure ic_wadd (rg, rstr)
+
+pointer rg # Range descriptor
+char rstr[ARB] # Range string
+
+int i, j, n, strlen(), ctor()
+real w1, w2
+pointer sp, str, ptr
+
+begin
+ call smark (sp)
+ call salloc (str, strlen (rstr), TY_CHAR)
+
+ i = 1
+ n = Memr[rg]
+ while (rstr[i] != EOS) {
+
+ # Find beginning and end of a range and copy it to the work string
+ while (IS_WHITE(rstr[i]) || rstr[i]==',' || rstr[i]=='\n')
+ i = i + 1
+ if (rstr[i] == EOS)
+ break
+
+ ptr = str
+ while (!(IS_WHITE(rstr[i]) || rstr[i]==',' || rstr[i]=='\n' ||
+ rstr[i]==EOS)) {
+ if (rstr[i] == ':')
+ Memc[ptr] = ' '
+ else
+ Memc[ptr] = rstr[i]
+ i = i + 1
+ ptr = ptr + 1
+ }
+ Memc[ptr] = EOS
+
+ # Parse range
+ if (Memc[str] == '@')
+ call error (1, "Cannot nest @files")
+ else {
+ # Get range
+ j = 1
+ if (ctor (Memc[str], j, w1) == 0)
+ call error (1, "Range syntax error")
+ if (ctor (Memc[str], j, w2) == 0)
+ call error (1, "Range syntax error")
+ }
+
+ if (mod (n, 10) == 0)
+ call realloc (rg, 1+2*(n+10), TY_REAL)
+ n = n + 1
+ Memr[rg+2*n-1] = min (w1, w2)
+ Memr[rg+2*n] = max (w1, w2)
+ }
+ Memr[rg] = n
+
+ call sfree (sp)
+end
+
+
+# IC_WISINRANGE -- Is wavelength in range?
+
+bool procedure ic_wisinrange (rg, w)
+
+pointer rg # Wavelength range array
+real w # Wavelength
+
+int i, n
+
+begin
+ if (rg == NULL)
+ return (true)
+
+ n = nint (Memr[rg])
+ do i = 1, 2*n, 2
+ if (w >= Memr[rg+i] && w <= Memr[rg+i+1])
+ return (true)
+ return (false)
+end
diff --git a/noao/onedspec/scombine/icstat.x b/noao/onedspec/scombine/icstat.x
new file mode 100644
index 00000000..3fce4165
--- /dev/null
+++ b/noao/onedspec/scombine/icstat.x
@@ -0,0 +1,160 @@
+include <smw.h>
+include "icombine.h"
+
+
+# IC_STATR -- Compute image statistics within spectrum.
+
+procedure ic_statr (sh, lflag, rg, domode, domedian, domean, mode, median, mean)
+
+pointer sh # Spectrum structure
+int lflag # Data flag
+pointer rg # Wavelength ranges
+bool domode, domedian, domean # Statistics to compute
+real mode, median, mean # Statistics
+
+int i, n, npts
+real a, w
+pointer sp, data, dp, lp, mp
+real ic_moder(), asumr()
+bool ic_wisinrange()
+double shdr_lw()
+
+include "icombine.com"
+
+begin
+ mp = SX(sh)
+ lp = SY(sh)
+ npts = SN(sh)
+
+ call smark (sp)
+ call salloc (data, npts, TY_REAL)
+
+ dp = data
+ if (lflag == D_ALL && rg == NULL) {
+ if (dothresh) {
+ do i = 1, npts {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ lp = lp + 1
+ }
+ } else {
+ do i = 1, npts {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ lp = lp + 1
+ }
+ }
+ } else if (lflag == D_MIX || rg != NULL) {
+ if (dothresh) {
+ do i = 1, npts {
+ if (Memr[mp] == 0) {
+ a = Memr[lp]
+ if (a >= lthresh && a <= hthresh) {
+ w = shdr_lw (sh, double (i))
+ if (ic_wisinrange (rg, w)) {
+ Memr[dp] = a
+ dp = dp + 1
+ }
+ }
+ }
+ mp = mp + 1
+ lp = lp + 1
+ }
+ } else {
+ do i = 1, npts {
+ if (Memr[mp] == 0) {
+ w = shdr_lw (sh, double (i))
+ if (ic_wisinrange (rg, w)) {
+ Memr[dp] = Memr[lp]
+ dp = dp + 1
+ }
+ }
+ mp = mp + 1
+ lp = lp + 1
+ }
+ }
+ }
+
+ n = dp - data
+ if (n > 0) {
+ # Compute only statistics needed.
+ if (domode || domedian) {
+ call asrtr (Memr[data], Memr[data], n)
+ mode = ic_moder (Memr[data], n)
+ median = Memr[data+n/2-1]
+ }
+ if (domean)
+ mean = asumr (Memr[data], n) / n
+ } else {
+ mode = INDEF
+ median = INDEF
+ mean = INDEF
+ }
+
+ call sfree (sp)
+end
+
+
+define NMIN 10 # Minimum number of pixels for mode calculation
+define ZRANGE 0.8 # Fraction of pixels about median to use
+define ZSTEP 0.01 # Step size for search for mode
+define ZBIN 0.1 # Bin size for mode.
+
+# IC_MODE -- Compute mode of an array. The mode is found by binning
+# with a bin size based on the data range over a fraction of the
+# pixels about the median and a bin step which may be smaller than the
+# bin size. If there are too few points the median is returned.
+# The input array must be sorted.
+
+real procedure ic_moder (a, n)
+
+real a[n] # Data array
+int n # Number of points
+
+int i, j, k, nmax
+real z1, z2, zstep, zbin
+real mode
+bool fp_equalr()
+
+begin
+ if (n < NMIN)
+ return (a[n/2])
+
+ # Compute the mode. The array must be sorted. Consider a
+ # range of values about the median point. Use a bin size which
+ # is ZBIN of the range. Step the bin limits in ZSTEP fraction of
+ # the bin size.
+
+ i = 1 + n * (1. - ZRANGE) / 2.
+ j = 1 + n * (1. + ZRANGE) / 2.
+ z1 = a[i]
+ z2 = a[j]
+ if (fp_equalr (z1, z2)) {
+ mode = z1
+ return (mode)
+ }
+
+ zstep = ZSTEP * (z2 - z1)
+ zbin = ZBIN * (z2 - z1)
+
+ z1 = z1 - zstep
+ k = i
+ nmax = 0
+ repeat {
+ z1 = z1 + zstep
+ z2 = z1 + zbin
+ for (; i < j && a[i] < z1; i=i+1)
+ ;
+ for (; k < j && a[k] < z2; k=k+1)
+ ;
+ if (k - i > nmax) {
+ nmax = k - i
+ mode = a[(i+k)/2]
+ }
+ } until (k >= j)
+
+ return (mode)
+end
diff --git a/noao/onedspec/scombine/icsum.x b/noao/onedspec/scombine/icsum.x
new file mode 100644
index 00000000..f038b37b
--- /dev/null
+++ b/noao/onedspec/scombine/icsum.x
@@ -0,0 +1,48 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include "icombine.h"
+
+
+# IC_SUM -- Compute the summed image line.
+
+procedure ic_sumr (d, n, npts, sum)
+
+pointer d[ARB] # Data pointers
+int n[npts] # Number of points
+int npts # Number of output points per line
+real sum[npts] # Average (returned)
+
+int i, j, k
+real s
+
+include "icombine.com"
+
+begin
+ # If no data has been excluded do the sum without checking the
+ # number of points. If all the data has been excluded set the
+ # sum to the blank value.
+
+ if (dflag == D_ALL) {
+ do i = 1, npts {
+ k = i - 1
+ s = Memr[d[1]+k]
+ do j = 2, n[i]
+ s = s + Memr[d[j]+k]
+ sum[i] = s
+ }
+ } else if (dflag == D_NONE) {
+ do i = 1, npts
+ sum[i] = blank
+ } else {
+ do i = 1, npts {
+ if (n[i] > 0) {
+ k = i - 1
+ s = Memr[d[1]+k]
+ do j = 2, n[i]
+ s = s + Memr[d[j]+k]
+ sum[i] = s
+ } else
+ sum[i] = blank
+ }
+ }
+end
diff --git a/noao/onedspec/scombine/iscombine.key b/noao/onedspec/scombine/iscombine.key
new file mode 100644
index 00000000..91d7876b
--- /dev/null
+++ b/noao/onedspec/scombine/iscombine.key
@@ -0,0 +1,23 @@
+ SCOMBINE CURSOR KEYS
+
+a - Mark scaling ranges in overlap region for 'v' key
+b - Cancel scaling ranges
+c - Print cursor position
+d - Replace range of pixels by straight cursor line
+e - Replace range of pixels by linear interpolation from the endpoint pixels
+f - Start over from the first spectrum
+j - Fudge a point to vertical cursor value
+n - Go on to next spectrum
+o - Reset data for current spectrum to initial values
+p - Don't include current spectrum in combined image and go on to next spectrum
+q - Quit and combine remaining spectra noninteractively (no|yes|YES)
+s - Mark accumulation ranges in current spectrum
+t - Cancel accumulation ranges
+v - Shift overlap average of spectrum vertically to accumulated spectrum
+w - Window the graph using gtools commands
+x - Shift spectrum horizontally to cursor position
+y - Shift spectrum vertically to cursor position
+z - Shift spectrum vertically to accumulated spectrum
++ - Set additive scaling for 'v' key
+* - Set multiplicative scaling
+? - This help page
diff --git a/noao/onedspec/scombine/iscombine.par b/noao/onedspec/scombine/iscombine.par
new file mode 100644
index 00000000..a9d8846e
--- /dev/null
+++ b/noao/onedspec/scombine/iscombine.par
@@ -0,0 +1,18 @@
+input,s,a,"",,,List of input spectra
+output,s,a,"",,,List of output spectra
+woutput,s,h,"",,,List of output weight spectra
+apertures,s,h,"",,,Apertures to combine
+group,s,h,"apertures","all|images|apertures",,Grouping option
+combine,s,h,"average","average|sum",,Combining option
+scale,s,h,"",,,Header keyword for scaling
+weight,s,h,"","",,"Header keyword for weighting
+"
+w1,r,h,INDEF,,,Starting wavelength of output spectra
+w2,r,h,INDEF,,,Ending wavelength of output spectra
+dw,r,h,INDEF,,,Wavelength increment of output spectra
+nw,i,h,INDEF,,,Length of output spectra
+log,b,h,no,,,"Logarithmic increments?
+"
+interactive,b,h,no,,,Adjust spectra interactively?
+sort,s,h,"none","none|increasing|decreasing",,Interactive combining order
+cursor,*gcur,h,"",,,Graphics cursor input
diff --git a/noao/onedspec/scombine/mkpkg b/noao/onedspec/scombine/mkpkg
new file mode 100644
index 00000000..ab60e45b
--- /dev/null
+++ b/noao/onedspec/scombine/mkpkg
@@ -0,0 +1,35 @@
+# SCOMBINE
+
+$call relink
+$exit
+
+update:
+ $call relink
+ $call install
+ ;
+
+relink:
+ $update libpkg.a
+ $omake x_scombine.x
+ $link x_scombine.o libpkg.a -lsmw -lxtools -liminterp \
+ -o xx_scombine.e
+ ;
+
+install:
+ $move xx_scombine.e noaobin$x_scombine.e
+ ;
+
+
+libpkg.a:
+ @generic
+
+ icgdata.x <smw.h> icombine.com icombine.h
+ iclog.x <smw.h> icombine.com icombine.h <mach.h>
+ icombine.x <smw.h> icombine.com <mach.h> icombine.h
+ icscale.x <smw.h> icombine.com icombine.h <ctype.h> <error.h>\
+ <imhdr.h> <imset.h>
+ icstat.x <smw.h> icombine.com icombine.h
+ icsum.x icombine.com icombine.h
+ t_scombine.x <smw.h> icombine.h icombine.com <error.h> <imhdr.h>\
+ <mach.h>
+ ;
diff --git a/noao/onedspec/scombine/scombine.par b/noao/onedspec/scombine/scombine.par
new file mode 100644
index 00000000..932e6e31
--- /dev/null
+++ b/noao/onedspec/scombine/scombine.par
@@ -0,0 +1,37 @@
+input,s,a,"",,,List of input spectra
+output,s,a,"",,,List of output spectra
+noutput,s,h,"",,,List of output number combined spectra
+logfile,s,h,"STDOUT",,,"Log file
+"
+apertures,s,h,"",,,Apertures to combine
+group,s,h,"apertures","all|images|apertures",,"Grouping option"
+combine,s,h,"average","average|median|sum",,Type of combine operation
+reject,s,h,"none","none|minmax|ccdclip|crreject|sigclip|avsigclip|pclip",,"Type of rejection
+"
+first,b,h,no,,,Use first spectrum for dispersion?
+w1,r,h,INDEF,,,Starting wavelength of output spectra
+w2,r,h,INDEF,,,Ending wavelength of output spectra
+dw,r,h,INDEF,,,Wavelength increment of output spectra
+nw,i,h,INDEF,,,Length of output spectra
+log,b,h,no,,,"Logarithmic increments?
+"
+scale,s,h,"none",,,Image scaling
+zero,s,h,"none",,,Image zero point offset
+weight,s,h,"none",,,Image weights
+sample,s,h,"",,,"Wavelength sample regions for statistics
+"
+lthreshold,r,h,INDEF,,,Lower threshold
+hthreshold,r,h,INDEF,,,Upper threshold
+nlow,i,h,1,0,,minmax: Number of low pixels to reject
+nhigh,i,h,1,0,,minmax: Number of high pixels to reject
+nkeep,i,h,1,,,Minimum to keep (pos) or maximum to reject (neg)
+mclip,b,h,yes,,,Use median in sigma clipping algorithms?
+lsigma,r,h,3.,0.,,Lower sigma clipping factor
+hsigma,r,h,3.,0.,,Upper sigma clipping factor
+rdnoise,s,h,"0.",,,ccdclip: CCD readout noise (electrons)
+gain,s,h,"1.",,,ccdclip: CCD gain (electrons/DN)
+snoise,s,h,"0.",,,ccdclip: Sensitivity noise (fraction)
+sigscale,r,h,0.1,0.,,Tolerance for sigma clipping scaling corrections
+pclip,r,h,-0.5,,,pclip: Percentile clipping parameter
+grow,i,h,0,,,Radius (pixels) for 1D neighbor rejection
+blank,r,h,0.,,,Value if there are no pixels
diff --git a/noao/onedspec/scombine/t_scombine.x b/noao/onedspec/scombine/t_scombine.x
new file mode 100644
index 00000000..774a5f87
--- /dev/null
+++ b/noao/onedspec/scombine/t_scombine.x
@@ -0,0 +1,630 @@
+include <imhdr.h>
+include <error.h>
+include <mach.h>
+include <smw.h>
+include "icombine.h"
+
+
+# T_SCOMBINE - Combine spectra
+# The input spectra are combined by medianing, averaging or summing
+# with optional rejection, scaling and weighting. The input may be
+# grouped by aperture or by image. The combining algorithms are
+# similar to those in IMCOMBINE.
+
+procedure t_scombine()
+
+int ilist # list of input images
+int olist # list of output images
+pointer nlist # image name for number combined
+pointer aps # aperture ranges
+int group # grouping option
+
+int reject1
+real flow1, fhigh1, pclip1, nkeep1
+
+real rval
+bool grdn, ggain, gsn
+int i, j, k, l, n, naps, npts
+pointer im, mw, nout, refim, shin, shout
+pointer sp, input, output, noutput, scale, zero, weight, str, logfile, sh, ns
+pointer sp1, d, id, nc, m, lflag, scales, zeros, wts
+
+real clgetr(), imgetr()
+bool clgetb(), rng_elementi()
+int clgeti(), clgwrd(), ctor()
+int imtopenp(), imtgetim(), open(), nowhite()
+pointer rng_open(), immap(), smw_openim(), impl2i(), impl2r()
+errchk open, immap, smw_openim, shdr_open, imgetr
+errchk scb_output, scb_combine, ic_combiner
+
+include "icombine.com"
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (noutput, SZ_FNAME, TY_CHAR)
+ call salloc (scale, SZ_FNAME, TY_CHAR)
+ call salloc (zero, SZ_FNAME, TY_CHAR)
+ call salloc (weight, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (gain, SZ_FNAME, TY_CHAR)
+ call salloc (snoise, SZ_FNAME, TY_CHAR)
+ call salloc (rdnoise, SZ_FNAME, TY_CHAR)
+ call salloc (logfile, SZ_FNAME, TY_CHAR)
+
+ # Get parameters
+ ilist = imtopenp ("input")
+ olist = imtopenp ("output")
+ nlist = imtopenp ("noutput")
+ call clgstr ("apertures", Memc[str], SZ_LINE)
+ group = clgwrd ("group", Memc[input], SZ_FNAME, GROUP)
+
+ # IMCOMBINE parameters
+ call clgstr ("logfile", Memc[logfile], SZ_FNAME)
+ combine = clgwrd ("combine", Memc[input], SZ_FNAME, COMBINE)
+ reject1 = clgwrd ("reject", Memc[input], SZ_FNAME, REJECT)
+ blank = clgetr ("blank")
+ call clgstr ("scale", Memc[scale], SZ_FNAME)
+ call clgstr ("zero", Memc[zero], SZ_FNAME)
+ call clgstr ("weight", Memc[weight], SZ_FNAME)
+ call clgstr ("gain", Memc[gain], SZ_FNAME)
+ call clgstr ("rdnoise", Memc[rdnoise], SZ_FNAME)
+ call clgstr ("snoise", Memc[snoise], SZ_FNAME)
+ lthresh = clgetr ("lthreshold")
+ hthresh = clgetr ("hthreshold")
+ lsigma = clgetr ("lsigma")
+ hsigma = clgetr ("hsigma")
+ pclip1 = clgetr ("pclip")
+ flow1 = clgetr ("nlow")
+ fhigh1 = clgetr ("nhigh")
+ nkeep1 = clgeti ("nkeep")
+ grow = clgeti ("grow")
+ mclip = clgetb ("mclip")
+ sigscale = clgetr ("sigscale")
+
+ i = nowhite (Memc[scale], Memc[scale], SZ_FNAME)
+ i = nowhite (Memc[zero], Memc[zero], SZ_FNAME)
+ i = nowhite (Memc[weight], Memc[weight], SZ_FNAME)
+
+ # Check parameters, map INDEFs, and set threshold flag
+ if (combine == SUM)
+ reject1 = NONE
+ if (pclip1 == 0. && reject1 == PCLIP)
+ call error (1, "Pclip parameter may not be zero")
+ if (IS_INDEFR (blank))
+ blank = 0.
+ if (IS_INDEFR (lsigma))
+ lsigma = MAX_REAL
+ if (IS_INDEFR (hsigma))
+ hsigma = MAX_REAL
+ if (IS_INDEFR (pclip1))
+ pclip1 = -0.5
+ if (IS_INDEFI (nkeep1))
+ nkeep1 = 0
+ if (IS_INDEFR (flow1))
+ flow1 = 0
+ if (IS_INDEFR (fhigh))
+ fhigh = 0
+ if (IS_INDEFI (grow))
+ grow = 0
+ if (IS_INDEF (sigscale))
+ sigscale = 0.
+
+ if (IS_INDEF(lthresh) && IS_INDEF(hthresh))
+ dothresh = false
+ else {
+ dothresh = true
+ if (IS_INDEF(lthresh))
+ lthresh = -MAX_REAL
+ if (IS_INDEF(hthresh))
+ hthresh = MAX_REAL
+ }
+
+ # Get read noise and gain?
+ grdn = false
+ ggain = false
+ gsn = false
+ if (reject1 == CCDCLIP || reject1 == CRREJECT) {
+ i = 1
+ if (ctor (Memc[rdnoise], i, rval) == 0)
+ grdn = true
+ i = 1
+ if (ctor (Memc[gain], i, rval) == 0)
+ ggain = true
+ i = 1
+ if (ctor (Memc[snoise], i, rval) == 0)
+ gsn = true
+ }
+
+ # Open the log file.
+ logfd = NULL
+ if (Memc[logfile] != EOS) {
+ iferr (logfd = open (Memc[logfile], APPEND, TEXT_FILE)) {
+ logfd = NULL
+ call erract (EA_WARN)
+ }
+ }
+
+ iferr (aps = rng_open (Memc[str], INDEF, INDEF, INDEF))
+ call error (1, "Error in aperture list")
+
+ # Loop through input images.
+ while (imtgetim (ilist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (olist, Memc[output], SZ_FNAME) == EOF) {
+ call eprintf ("No output image\n")
+ break
+ }
+ if (imtgetim (nlist, Memc[noutput], SZ_FNAME) == EOF)
+ Memc[noutput] = EOS
+
+ # Get spectra to combine.
+ # Because the input images are unmapped we must get all the
+ # data we need for combining into the spectrum data structures.
+ # In particular any header keyword parameters that will be
+ # used. We save the header values in unused elements of
+ # the spectrum data structure.
+
+ naps = 0
+ repeat {
+ iferr (im = immap (Memc[input], READ_ONLY, 0)) {
+ if (group == GRP_IMAGES) {
+ call erract (EA_WARN)
+ next
+ } else {
+ call erract (EA_ERROR)
+ }
+ }
+ mw = smw_openim (im)
+ shin = NULL
+
+ do i = 1, SMW_NSPEC(mw) {
+ call shdr_open (im, mw, i, 1, INDEFI, SHDATA, shin)
+ if (Memc[scale] == '!')
+ ST(shin) = imgetr (im, Memc[scale+1])
+ if (Memc[zero] == '!')
+ HA(shin) = imgetr (im, Memc[zero+1])
+ if (Memc[weight] == '!')
+ AM(shin) = imgetr (im, Memc[weight+1])
+ if (grdn)
+ RA(shin) = imgetr (im, Memc[rdnoise])
+ if (ggain)
+ DEC(shin) = imgetr (im, Memc[gain])
+ if (gsn)
+ UT(shin) = imgetr (im, Memc[snoise])
+ if (!rng_elementi (aps, AP(shin)))
+ next
+ if (group == GRP_APERTURES) {
+ for (j=1; j<=naps; j=j+1)
+ if (AP(shin) == AP(SH(sh,j,1)))
+ break
+ n = 10
+ } else {
+ j = 1
+ n = 1
+ }
+
+ if (naps == 0) {
+ call calloc (sh, n, TY_POINTER)
+ call calloc (ns, n, TY_INT)
+ } else if (j > naps && mod (naps, n) == 0) {
+ call realloc (sh, naps+n, TY_POINTER)
+ call realloc (ns, naps+n, TY_INT)
+ call aclri (Memi[sh+naps], n)
+ call aclri (Memi[ns+naps], n)
+ }
+ if (j > naps)
+ naps = naps + 1
+ n = NS(ns,j)
+ if (n == 0)
+ call malloc (Memi[sh+j-1], 10, TY_POINTER)
+ else if (mod (n, 10) == 0)
+ call realloc (Memi[sh+j-1], n+10, TY_POINTER)
+
+ n = n + 1
+ SH(sh,j,n) = NULL
+ NS(ns,j) = n
+ call shdr_copy (shin, SH(sh,j,n), NO)
+ }
+
+ call imunmap (IM(shin))
+ MW(shin) = NULL
+ call shdr_close (shin)
+
+ if (group == GRP_IMAGES)
+ break
+ } until (imtgetim (ilist, Memc[input], SZ_FNAME) == EOF)
+
+ if (naps < 1) {
+ call eprintf ("No input spectra to combine\n")
+ next
+ }
+
+ # Set the output and combine the spectra.
+ call scb_output (sh, ns, naps, Memc[output], Memc[noutput],
+ im, mw, nout, refim)
+
+ do j = 1, naps {
+ call shdr_open (im, mw, j, 1, INDEFI, SHHDR, shout)
+ npts = SN(shout)
+ n = NS(ns,j)
+
+ # Allocate additional memory
+ call smark (sp1)
+ call salloc (d, n, TY_POINTER)
+ call salloc (id, n, TY_POINTER)
+ call salloc (nc, npts, TY_INT)
+ call salloc (m, n, TY_POINTER)
+ call salloc (lflag, n, TY_INT)
+ call salloc (scales, n, TY_REAL)
+ call salloc (zeros, n, TY_REAL)
+ call salloc (wts, n, TY_REAL)
+ call calloc (SX(shout), npts, TY_REAL)
+ call calloc (SY(shout), npts, TY_REAL)
+ call amovki (D_ALL, Memi[lflag], n)
+
+ # Convert the pclip parameter to a number of pixels rather than
+ # a fraction. This number stays constant even if pixels are
+ # rejected. The number of low and high pixel rejected, however,
+ # are converted to a fraction of the valid pixels.
+
+ reject = reject1
+ nkeep = nkeep1
+ if (nkeep < 0)
+ nkeep = n + nkeep
+ if (reject == PCLIP) {
+ pclip = pclip1
+ i = (n - 1) / 2.
+ if (abs (pclip) < 1.)
+ pclip = pclip * i
+ if (pclip < 0.)
+ pclip = min (-1, max (-i, int (pclip)))
+ else
+ pclip = max (1, min (i, int (pclip)))
+ }
+ if (reject == MINMAX) {
+ flow = flow1
+ fhigh = fhigh1
+ if (flow >= 1)
+ flow = flow / n
+ if (fhigh >= 1)
+ fhigh = fhigh / n
+ i = flow * n + fhigh * n
+ if (i == 0)
+ reject = NONE
+ else if (i >= n) {
+ call eprintf ("Bad minmax rejection parameters\n")
+ call eprintf ("Using no rejection\n")
+ reject = NONE
+ }
+ }
+
+ # Combine spectra
+ call ic_combiner (SH(sh,j,1), shout, Memi[d], Memi[id],
+ Memi[nc], Memi[m], Memi[lflag], Memr[scales], Memr[zeros],
+ Memr[wts], n, npts)
+
+ # Write the results
+ call amovr (Memr[SY(shout)], Memr[impl2r(im,j)], npts)
+ if (nout != NULL)
+ call amovi (Memi[nc], Memi[impl2i(nout,j)], npts)
+ call sfree (sp1)
+ }
+
+ # Finish up
+ call shdr_close (shout)
+ call smw_close (mw)
+ call imunmap (im)
+ call imunmap (refim)
+ if (nout != NULL)
+ call imunmap (nout)
+
+ # Find all the distinct SMW pointers and free them.
+ do j = 1, naps {
+ do i = 1, NS(ns,j) {
+ mw = MW(SH(sh,j,i))
+ if (mw != NULL) {
+ do k = 1, naps {
+ do l = 1, NS(ns,k) {
+ shin = SH(sh,k,l)
+ if (MW(shin) == mw)
+ MW(shin) = NULL
+ }
+ }
+ call smw_close (mw)
+ }
+ }
+ }
+ do j = 1, naps {
+ do i = 1, NS(ns,j)
+ call shdr_close (SH(sh,j,i))
+ call mfree (Memi[sh+j-1], TY_POINTER)
+ }
+ call mfree (sh, TY_POINTER)
+ call mfree (ns, TY_INT)
+ }
+
+ call rng_close (aps)
+ call imtclose (ilist)
+ call imtclose (olist)
+ call imtclose (nlist)
+
+ call sfree (sp)
+end
+
+
+# SCB_REBIN - Rebin input spectra to output dispersion
+# Use the SX array as mask. If less than 1% of an input
+# pixel contributes to an output pixel then flag it as missing data.
+
+procedure scb_rebin (sh, shout, lflag, ns, npts)
+
+pointer sh[ns] # Input spectra structures
+pointer shout # Output spectrum structure
+int lflag[ns] # Empty mask flags
+int ns # Number of spectra
+int npts # NUmber of output points
+
+int i, j
+real a, b, c
+pointer shin
+double shdr_wl(), shdr_lw()
+
+include "icombine.com"
+
+begin
+ # Rebin to common dispersion
+ # Determine overlap with output and set mask arrays
+
+ do i = 1, ns {
+ shin = sh[i]
+ c = shdr_wl (shout, shdr_lw (shin, double(0.5)))
+ b = shdr_wl (shout, shdr_lw (shin, double(SN(shin)+0.5)))
+ a = max (1, nint (min (b, c) + 0.01))
+ b = min (npts, nint (max (b, c) - 0.01))
+ j = b - a + 1
+ if (j < 1) {
+ lflag[i] = D_NONE
+ next
+ }
+ else if (j < npts)
+ lflag[i] = D_MIX
+ else
+ lflag[i] = D_ALL
+
+ call shdr_rebin (shin, shout)
+ call aclrr (Memr[SX(shin)], SN(shin))
+ j = a - 1
+ if (j > 0)
+ call amovkr (1.0, Memr[SX(shin)], j)
+ j = SN(shin) - b
+ if (j > 0)
+ call amovkr (1.0, Memr[SX(shin)+SN(shin)-j], j)
+ }
+
+ dflag = lflag[1]
+ do i = 2, ns {
+ if (dflag != lflag[i]) {
+ dflag = D_MIX
+ break
+ }
+ }
+end
+
+
+# SCB_OUTPUT - Set the output spectrum
+
+procedure scb_output (sh, ns, naps, output, noutput, im, mw, nout, refim)
+
+pointer sh # spectra structures
+int ns # number of spectra
+int naps # number of apertures
+char output[SZ_FNAME] # output spectrum name
+char noutput[SZ_FNAME] # output number combined image name
+pointer im # output IMIO pointer
+pointer mw # output MWCS pointer
+pointer nout # output number combined IMIO pointer
+pointer refim # reference image for output image
+
+int i, ap, beam, dtype, nw, nmax, axis[2]
+double w1, dw, z
+real aplow[2], aphigh[2]
+pointer sp, key, coeff, sh1
+pointer immap(), mw_open(), smw_openim()
+errchk immap, smw_openim
+data axis/1,2/
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ coeff = NULL
+
+ # Create output image using the first input image as a reference
+ refim = immap (IMNAME(SH(sh,1,1)), READ_ONLY, 0)
+ im = immap (output, NEW_COPY, refim)
+
+ # Use smw_openim to clean up old keywords(?).
+ mw = smw_openim (im)
+ call smw_close (mw)
+
+ if (naps == 1)
+ IM_NDIM(im) = 1
+ else
+ IM_NDIM(im) = 2
+ call imaddi (im, "SMW_NDIM", IM_NDIM(im))
+ IM_LEN(im,2) = naps
+ if (IM_PIXTYPE(im) != TY_DOUBLE)
+ IM_PIXTYPE(im) = TY_REAL
+
+ # Set new header.
+ mw = mw_open (NULL, 2)
+ call mw_newsystem (mw, "multispec", 2)
+ call mw_swtype (mw, axis, 2, "multispec",
+ "label=Wavelength units=Angstroms")
+ call smw_open (mw, NULL, im)
+
+ nmax = 0
+ do i = 1, naps {
+ sh1 = SH(sh,i,1)
+ call smw_gwattrs (MW(sh1), APINDEX(sh1), 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, coeff)
+ call scb_default (SH(sh,i,1), NS(ns,i),
+ dtype, w1, dw, nw, z, Memc[coeff])
+ call smw_swattrs (mw, i, 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, Memc[coeff])
+ call smw_sapid (mw, i, 1, TITLE(sh1))
+ nmax = max (nmax, nw)
+ }
+
+ IM_LEN(im,1) = nmax
+
+ # Set MWCS header.
+ call smw_saveim (mw, im)
+ call smw_close (mw)
+ mw = smw_openim (im)
+
+ # Create number combined image
+ if (noutput[1] != EOS) {
+ nout = immap (noutput, NEW_COPY, im)
+ IM_PIXTYPE(nout) = TY_INT
+ call sprintf (IM_TITLE(nout), SZ_LINE, "Number combined for %s")
+ call pargstr (output)
+ }
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# SCB_DEFAULT - Set default values for the starting wavelength, ending
+# wavelength, wavelength increment and spectrum length for the output
+# spectrum.
+
+procedure scb_default (shdr, ns, dtype, w1, dw, nw, z, coeff)
+
+pointer shdr[ARB] # spectra structures
+int ns # number of spectra
+int dtype # dispersion type
+double w1 # starting wavelength
+double dw # wavelength increment
+int nw # spectrum length
+double z # redshift
+char coeff[ARB] # nonlinear coefficient array
+
+bool clgetb()
+int i, nwa, clgeti()
+double w2, aux, w1a, w2a, dwa, clgetd()
+pointer sh
+
+begin
+ if (clgetb ("first"))
+ return
+
+ w1a = clgetd ("w1")
+ w2a = clgetd ("w2")
+ dwa = clgetd ("dw")
+ nwa = clgeti ("nw")
+ if (clgetb ("log"))
+ dtype = DCLOG
+ else
+ dtype = DCLINEAR
+ z = 0.
+ coeff[1] = EOS
+
+ # Dispersion type
+ if (dtype == DCLINEAR) {
+ do i = 1, ns {
+ if (DC(shdr[i]) == DCNO) {
+ dtype = DCNO
+ break
+ }
+ }
+ }
+
+ w1 = w1a
+ w2 = w2a
+ dw = dwa
+ nw = nwa
+
+ # Starting wavelength
+ if (IS_INDEFD (w1)) {
+ if (IS_INDEFD (dw) || dw > 0.) {
+ w1 = MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W0(sh)
+ else
+ aux = W1(sh)
+ if (aux < w1)
+ w1 = aux
+ }
+ } else {
+ w1 = -MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W1(sh)
+ else
+ aux = W0(sh)
+ if (aux > w1)
+ w1 = aux
+ }
+ }
+ }
+
+ # Ending wavelength
+ if (IS_INDEFD (w2)) {
+ if (IS_INDEFD (dw) || dw > 0.) {
+ w2 = -MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W1(sh)
+ else
+ aux = W0(sh)
+ if (aux > w2)
+ w2 = aux
+ }
+ } else {
+ w2 = MAX_REAL
+ do i = 1, ns {
+ sh = shdr[i]
+ if (WP(sh) > 0.)
+ aux = W0(sh)
+ else
+ aux = W1(sh)
+ if (aux < w2)
+ w2 = aux
+ }
+ }
+ }
+
+ # Wavelength increment
+ if (IS_INDEFD (dw)) {
+ dw = MAX_REAL
+ do i = 1, ns {
+ aux = abs (WP(shdr[i]))
+ if (aux < dw)
+ dw = aux
+ }
+ }
+ if ((w2 - w1) / dw < 0.)
+ dw = -dw
+
+ # Spectrum length
+ if (IS_INDEFI (nw))
+ nw = int ((w2 - w1) / dw + 0.5) + 1
+
+ # Adjust the values.
+ if (IS_INDEFD (dwa))
+ dw = (w2 - w1) / (nw - 1)
+ else if (IS_INDEFD (w2a))
+ w2 = w1 + (nw - 1) * dw
+ else if (IS_INDEFD (w1a))
+ w1 = w2 - (nw - 1) * dw
+ else {
+ nw = int ((w2 - w1) / dw + 0.5) + 1
+ w2 = w1 + (nw - 1) * dw
+ }
+end
diff --git a/noao/onedspec/scombine/x_scombine.x b/noao/onedspec/scombine/x_scombine.x
new file mode 100644
index 00000000..33943271
--- /dev/null
+++ b/noao/onedspec/scombine/x_scombine.x
@@ -0,0 +1 @@
+task scombine = t_scombine
diff --git a/noao/onedspec/scoords.par b/noao/onedspec/scoords.par
new file mode 100644
index 00000000..320d4d54
--- /dev/null
+++ b/noao/onedspec/scoords.par
@@ -0,0 +1,5 @@
+images,f,a,,,,List of spectrum image names
+coords,f,a,,,,List of coordinate file names
+label,s,h,"",,,Coordinate axis label
+units,s,h,"",,,Coordinate axis units
+verbose,b,h,yes,,,Verbose output?
diff --git a/noao/onedspec/scopy.cl b/noao/onedspec/scopy.cl
new file mode 100644
index 00000000..5ac09c94
--- /dev/null
+++ b/noao/onedspec/scopy.cl
@@ -0,0 +1,30 @@
+# SCOPY -- Copy spectra
+
+procedure scopy (input, output)
+
+string input {prompt="List of input spectra"}
+string output {prompt="List of output spectra"}
+
+real w1 = INDEF {prompt="Starting wavelength"}
+real w2 = INDEF {prompt="Ending wavelength"}
+string apertures = "" {prompt="List of apertures or columns/lines"}
+string bands = "" {prompt="List of bands or lines/bands"}
+string beams = "" {prompt="List of beams or echelle orders"}
+int apmodulus = 0 {prompt="Input aperture modulus (0=none)\n"}
+
+string format = "multispec" {prompt="Output spectra format",
+ enum="multispec|onedspec"}
+bool renumber = no {prompt="Renumber output apertures?"}
+int offset = 0 {prompt="Output aperture number offset"}
+bool clobber = no {prompt="Modify existing output images?"}
+bool merge = no {prompt="Merge with existing output images?"}
+bool rebin = yes {prompt="Rebin to exact wavelength region?"}
+bool verbose = no {prompt="Print operations?"}
+
+begin
+ sarith (input, "copy", "", output, w1=w1, w2=w2, apertures=apertures,
+ bands=bands, beams=beams, apmodulus=apmodulus, reverse=no,
+ ignoreaps=no, format=format, renumber=renumber, offset=offset,
+ clobber=clobber, merge=merge, rebin=rebin, errval=0.,
+ verbose=verbose)
+end
diff --git a/noao/onedspec/scopy.par b/noao/onedspec/scopy.par
new file mode 100644
index 00000000..6caffa44
--- /dev/null
+++ b/noao/onedspec/scopy.par
@@ -0,0 +1,17 @@
+input,s,a,,,,"List of input spectra"
+output,s,a,,,,"List of output spectra"
+w1,r,h,INDEF,,,"Starting wavelength"
+w2,r,h,INDEF,,,"Ending wavelength"
+apertures,s,h,"",,,"List of input apertures or columns/lines"
+bands,s,h,"",,,"List of input bands or lines/bands"
+beams,s,h,"",,,"List of beams or echelle orders"
+apmodulus,i,h,0,,,"Input aperture modulus (0=none)
+"
+format,s,h,"multispec",multispec|onedspec,,"Output spectra format"
+renumber,b,h,no,,,"Renumber output apertures?"
+offset,i,h,0,,,"Output aperture number offset"
+clobber,b,h,no,,,"Modify existing output images?"
+merge,b,h,no,,,"Merge with existing output images?"
+rebin,b,h,yes,,,"Rebin to exact wavelength region?"
+verbose,b,h,no,,,"Print operations?"
+mode,s,h,"ql",,,
diff --git a/noao/onedspec/sensfunc.par b/noao/onedspec/sensfunc.par
new file mode 100644
index 00000000..6ff6930c
--- /dev/null
+++ b/noao/onedspec/sensfunc.par
@@ -0,0 +1,17 @@
+standards,s,a,std,,,Input standard star data file (from STANDARD)
+sensitivity,s,a,"sens",,,Output root sensitivity function imagename
+apertures,s,h,"",,,Aperture selection list
+ignoreaps,b,h,no,,,Ignore apertures and make one sensitivity function?
+logfile,f,h,"logfile",,,Output log for statistics information
+extinction,f,h,,,,Extinction file
+newextinction,f,h,"extinct.dat",,,Output revised extinction file
+observatory,s,h,)_.observatory,,,Observatory of data
+function,s,h,"spline3","chebyshev|legendre|spline3|spline1",,Fitting function
+order,i,h,6,1,,Order of fit
+interactive,b,h,yes,,,Determine sensitivity function interactively?
+graphs,s,h,"sr",,,Graphs per frame
+marks,s,h,"plus cross box",,,Data mark types (marks deleted added)
+colors,s,h,"2 1 3 4",,,Colors (lines marks deleted added)
+cursor,*gcur,h,"",,,Graphics cursor input
+device,s,h,"stdgraph",,,Graphics output device
+answer,s,q, yes,"no|yes|NO|YES",,"(no|yes|NO|YES)"
diff --git a/noao/onedspec/sensfunc/mkpkg b/noao/onedspec/sensfunc/mkpkg
new file mode 100644
index 00000000..4fdd11c0
--- /dev/null
+++ b/noao/onedspec/sensfunc/mkpkg
@@ -0,0 +1,38 @@
+# SENSFUNC
+
+$checkout libpkg.a ../
+$update libpkg.a
+$checkin libpkg.a ../
+$exit
+
+libpkg.a:
+ sfadd.x sensfunc.h <gset.h>
+ sfapertures.x sensfunc.h
+ sfcgraph.x sensfunc.h <gset.h>
+ sfcolon.x sensfunc.h <error.h> <gset.h>
+ sfcolors.x sensfunc.h <gset.h>
+ sfcomposite.x sensfunc.h
+ sfdata.x sensfunc.h
+ sfdelete.x sensfunc.h <gset.h>
+ sfeout.x <ctype.h> <error.h> <mach.h> <math/curfit.h>
+ sfextinct.x sensfunc.h <pkg/gtools.h>
+ sffit.x sensfunc.h <math/curfit.h>
+ sfginit.x sensfunc.h <gset.h>
+ sfgraph.x sensfunc.h <error.h> <gset.h> <math/curfit.h>
+ sfimage.x sensfunc.h <smw.h> <gset.h> <math/curfit.h>
+ sfmarks.x sensfunc.h <gset.h>
+ sfmove.x sensfunc.h <gset.h>
+ sfnearest.x sensfunc.h <gset.h> <mach.h>
+ sfoutput.x sensfunc.h <imhdr.h> <mach.h>
+ sfreset.x sensfunc.h
+ sfrms.x sensfunc.h
+ sfsensfunc.x sensfunc.h <error.h> <gset.h> <mach.h>
+ sfshift.x sensfunc.h
+ sfstats.x sensfunc.h
+ sfstds.x sensfunc.h
+ sftitle.x sensfunc.h
+ sfundelete.x sensfunc.h <gset.h>
+ sfvstats.x sensfunc.h
+ sfweights.x sensfunc.h
+ t_sensfunc.x sensfunc.h
+ ;
diff --git a/noao/onedspec/sensfunc/sensfunc.h b/noao/onedspec/sensfunc/sensfunc.h
new file mode 100644
index 00000000..9e3afb77
--- /dev/null
+++ b/noao/onedspec/sensfunc/sensfunc.h
@@ -0,0 +1,64 @@
+# SENSFUNC definitions.
+
+define SF_NGRAPHS 4 # Number of graphs per frame
+define SF_INCLUDE 1 # Include observation
+define SF_EXCLUDE 2 # Exclude observation
+define SF_DELETE 3 # Delete observation
+
+# SENSFUNC Standard Star Data Structure.
+
+define SZ_STDIMAGE 63 # Length of standard image name
+define SZ_STDTITLE 63 # Length of standard title
+
+define LEN_STD 115 # Length of standard obs. structure
+
+define STD_IMAGE Memc[P2C($1)] # Standard image name
+define STD_SKY Memc[P2C($1+32)] # Standard image sky
+define STD_TITLE Memc[P2C($1+64)] # Standard title
+define STD_FLAG Memi[$1+96] # Flag
+define STD_BEAM Memi[$1+97] # Beam number of spectrum
+define STD_NPTS Memi[$1+98] # Number of points in spectrum
+define STD_EXPTIME Memr[P2R($1+99)] # Exposure time
+define STD_AIRMASS Memr[P2R($1+100)] # Airmass of spectrum
+define STD_WSTART Memr[P2R($1+101)] # Starting wavelength of spectrum
+define STD_WEND Memr[P2R($1+102)] # Ending wavelength of spectrum
+define STD_SHIFT Memr[P2R($1+103)] # Added shift
+define STD_NWAVES Memi[$1+104] # Number of calibration wavelengths
+define STD_WAVES Memi[$1+105] # Pointer to wavelengths
+define STD_FLUXES Memi[$1+106] # Pointer to standard flux values
+define STD_DWAVES Memi[$1+107] # Pointer to flux bandwidths
+define STD_COUNTS Memi[$1+108] # Pointer to counts
+define STD_SENS Memi[$1+109] # Pointer to sensitivities
+define STD_FIT Memi[$1+110] # Pointer to fitted sensitivities
+define STD_WTS Memi[$1+111] # Pointer to weights
+define STD_IWTS Memi[$1+112] # Pointer to weights
+define STD_X Memi[$1+114] # Pointer to plotted x values
+define STD_Y Memi[$1+115] # Pointer to plotted y values
+
+# Graphics structure
+
+define GP_SZTITLE 79 # Size of title string
+
+define LEN_GP 75 # Length of structure
+
+define GP_GIO Memi[$1] # GIO pointer
+define GP_TITLE Memc[P2C($1+1)] # General title
+define GP_GRAPHS Memc[P2C($1+41)+$2-1] # Graphs
+define GP_IMAGES Memi[$1+44+$2-1] # Pointer to image names
+define GP_SKYS Memi[$1+48+$2-1] # Pointer to sky names
+define GP_MARK Memi[$1+52] # Mark type
+define GP_SZMARK Memr[P2R($1+53)] # Mark size
+define GP_CMARK Memi[$1+54] # Mark color
+define GP_MDEL Memi[$1+55] # Deleted mark
+define GP_SZMDEL Memr[P2R($1+56)] # Size of deleted mark
+define GP_CDEL Memi[$1+57] # Color of deleted mark
+define GP_MADD Memi[$1+58] # Mark type
+define GP_CADD Memi[$1+59] # Mark color
+define GP_PLCOLOR Memi[$1+60] # Line color
+define GP_WSTART Memr[P2R($1+61)] # Starting wavelength for plots
+define GP_WEND Memr[P2R($1+62)] # Ending wavelength for plots
+define GP_LOG Memi[$1+63] # Log flux plots?
+define GP_FMIN Memr[P2R($1+64)] # Minimum flux plot limit
+define GP_FMAX Memr[P2R($1+65)] # Maximum flux plot limit
+define GP_SHDR Memi[$1+65+$2] # SHDR pointer
+define GP_AIRMASS Memr[P2R($1+69+$2)] # Airmass range of plots
diff --git a/noao/onedspec/sensfunc/sensfunc.key b/noao/onedspec/sensfunc/sensfunc.key
new file mode 100644
index 00000000..5f09739f
--- /dev/null
+++ b/noao/onedspec/sensfunc/sensfunc.key
@@ -0,0 +1,81 @@
+ SENSFUNC: Determine Sensitivity Function
+
+SUMMARY:
+
+? Help a Add data c Composite data d Delete data
+e Extinction f Fit (overplot) g Fit (redraw) i Info
+m Move data o Original data q Quit r Redraw
+s Shift data u Undelete data w Change weights I Interrupt
+
+:function [type] :graphs [types] :images [images] :marks [types]
+:order [value] :skys [images] :stats [file] :vstats [file]
+:colors [colors]
+
+Graph types: a=(resid,airmass), c=(composite,lambda), e=(extinction,lambda)
+ i=(Fluxed image,lambda), l=(Log of fluxed image, lambda),
+ r=(resid, lambda), s=(Sensitivity,lambda)
+
+
+CURSOR KEYS:
+
+? Print help
+a Add a point at the cursor position
+c Toggle composite points
+d Delete point, star, or wavelength nearest the cursor
+e Toggle residual extinction correction
+f Fit data with a sensitivity function and overplot the fit
+g Fit data with a sensitivity function and redraw the graphs
+i Print information about point nearest the cursor
+m Move point, star, wavelength nearest the cursor to new sensitivity
+o Reset to original data
+q Quit and write sensitivity function for current aperture
+r Redraw graph(s)
+s Toggle shift of standard stars to eliminate mean deviations
+u Undelete point, star, or wavelength nearest the cursor
+w Change weights of point, star, or wavelength nearest the cursor
+I Interrupt task immediately
+
+
+COLON COMMANDS AND ARGUMENTS:
+
+:flux [min] [max] Limits for flux calibrated graphs (INDEF for autoscale)
+:function [type] Function to be fit to sensitivity data. The types are:
+ chebyshev - Chebyshev polynomial
+ legendre - Legendre polynomial
+ spline1 - Linear spline
+ spline3 - Cubic spline
+:graphs [types] Graphs to be displayed (up to four). The types are:
+ a - Residual sensitivity vs airmass
+ c - Composite residuals and error bars vs wavelength
+ e - Extinction (and revised extinction) vs wavelength
+ i - Flux calibrated image vs wavelength
+ l - Log of flux calibrated image vs wavelength
+ r - Residual sensitivity vs wavelength
+ s - Sensitivity vs wavelength
+:images [images] Images to flux calibrate and plot (up to four images)
+:colors [colors] Line and mark colors to use for line and included, deleted,
+ and added points. The colors are specified as four
+ whitespace separated integers between 1 and 9.
+:marks [marks] Mark types to use for included, deleted, and added points:
+ point, box, plus, cross, diamond, hline, vline,
+ hebar, vebar, circle
+:order [order] Order of function (polynomial terms or spline pieces)
+:skys [images] Sky images for flux calibration (optional, up to four images)
+:stats [file] Statistics about standard stars and sensitivity fit
+:vstats [file] Verbose statistics about standard stars and sensitivity fit
+
+
+EXAMPLES:
+
+:func spline3 Select cubic spline function
+:g srae Graph sensitivity, residuals, airmass, and extinction
+:g sii Graph sensitivity and two images
+:i n1.0004 n1.0008 Set first two images to graph (the defaults are
+ taken from the standard star list)
+:skys n1.0005 Subtract this sky image from first image for calibration
+:colors 2 Change only the line color to 2
+:colors 2 5 4 3 Change the line and mark colors
+:m plus Change the mark type for included points and don't
+ change the deleted or added point mark type
+:stats Print statistics to terminal
+:vstats stdstats Print verbose statistics to file
diff --git a/noao/onedspec/sensfunc/sfadd.x b/noao/onedspec/sensfunc/sfadd.x
new file mode 100644
index 00000000..c77694d7
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfadd.x
@@ -0,0 +1,105 @@
+include <gset.h>
+include "sensfunc.h"
+
+# SF_ADD -- Add a point to the added point observation structure.
+# The added star is the next to last of the standard stars.
+
+procedure sf_add (gp, stds, nstds, cv, wx, wy, wc)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star structures
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+real wx # Cursor X value
+real wy # Cursor Y value
+int wc # Cursor WCS
+
+int nwaves
+real wave, sen, fit, cveval()
+pointer std, waves, sens, fits, wts, iwts, x, y
+errchk malloc, realloc
+
+begin
+ # Convert from particular WCS to wavelength and sensitivity. In
+ # order to add a point the graph must be either sensitivity or
+ # residual verses wavelength. If not then return without adding
+ # a point.
+
+ switch (GP_GRAPHS(gp,wc)) {
+ case 's':
+ wave = wx
+ fit = cveval (cv, wx)
+ sen = wy
+ case 'r':
+ wave = wx
+ fit = cveval (cv, wx)
+ sen = wy + fit
+ default:
+ return
+ }
+
+ # Add the point to the next to last standard star. Allocate
+ # or reallocate memory as needed. Turn the added star on by
+ # setting INCLUDE flag.
+
+ std = stds[nstds-1]
+ nwaves = STD_NWAVES(std) + 1
+ waves = STD_WAVES(std)
+ if (waves == NULL) {
+ call malloc (waves, nwaves, TY_REAL)
+ call malloc (sens, nwaves, TY_REAL)
+ call malloc (fits, nwaves, TY_REAL)
+ call malloc (wts, nwaves, TY_REAL)
+ call malloc (iwts, nwaves, TY_REAL)
+ call malloc (x, nwaves, TY_REAL)
+ call malloc (y, nwaves, TY_REAL)
+ } else {
+ sens = STD_SENS(std)
+ fits = STD_FIT(std)
+ wts = STD_WTS(std)
+ iwts = STD_IWTS(std)
+ x = STD_X(std)
+ y = STD_Y(std)
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (sens, nwaves, TY_REAL)
+ call realloc (fits, nwaves, TY_REAL)
+ call realloc (wts, nwaves, TY_REAL)
+ call realloc (iwts, nwaves, TY_REAL)
+ call realloc (x, nwaves, TY_REAL)
+ call realloc (y, nwaves, TY_REAL)
+ }
+ STD_FLAG(std) = SF_INCLUDE
+ STD_NWAVES(std) = nwaves
+ STD_WAVES(std) = waves
+ STD_SENS(std) = sens
+ STD_FIT(std) = fits
+ STD_WTS(std) = wts
+ STD_IWTS(std) = iwts
+ STD_X(std) = x
+ STD_Y(std) = y
+
+ Memr[waves+nwaves-1] = wave
+ Memr[sens+nwaves-1] = sen
+ Memr[fits+nwaves-1] = fit
+ Memr[wts+nwaves-1] = 1
+ Memr[iwts+nwaves-1] = 1
+
+ # Mark the added point on all graphs.
+ for (wc = 1; GP_GRAPHS(gp,wc) != EOS; wc=wc+1) {
+ call gseti (GP_GIO(gp), G_WCS, wc)
+ call gseti (GP_GIO(gp), G_PLCOLOR, GP_CADD(gp))
+ switch (GP_GRAPHS(gp,wc)) {
+ case 's':
+ call gmark (GP_GIO(gp), wave, sen, GP_MADD(gp), GP_SZMARK(gp),
+ GP_SZMARK(gp))
+ case 'r':
+ wy = sen - cveval (cv, wave)
+ call gmark (GP_GIO(gp), wave, wy, GP_MADD(gp), GP_SZMARK(gp),
+ GP_SZMARK(gp))
+ case 'a':
+ wy = sen - cveval (cv, wave)
+ call gmark (GP_GIO(gp), STD_AIRMASS(std), wy, GP_MADD(gp),
+ GP_SZMARK(gp), GP_SZMARK(gp))
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfapertures.x b/noao/onedspec/sensfunc/sfapertures.x
new file mode 100644
index 00000000..7eb2b6f8
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfapertures.x
@@ -0,0 +1,27 @@
+include "sensfunc.h"
+
+# SF_APERTURES -- Determine the apertures in use.
+
+procedure sf_apertures (stds, nstds, apertures, napertures)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer apertures # Pointer to apertures (returned)
+int napertures # Number of apertures (returned)
+
+int i, j, aperture
+
+errchk malloc, realloc
+
+begin
+ call malloc (apertures, nstds, TY_INT)
+ napertures = 0
+ do i = 1, nstds {
+ aperture = STD_BEAM(stds[i])
+ for (j=1; (j<=napertures)&&(aperture!=Memi[apertures+j-1]); j=j+1)
+ ;
+ napertures = max (napertures, j)
+ Memi[apertures+j-1] = aperture
+ }
+ call realloc (apertures, napertures, TY_INT)
+end
diff --git a/noao/onedspec/sensfunc/sfcgraph.x b/noao/onedspec/sensfunc/sfcgraph.x
new file mode 100644
index 00000000..2f689d47
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfcgraph.x
@@ -0,0 +1,104 @@
+include <gset.h>
+include "sensfunc.h"
+
+# SF_CGRAPH -- Graph of composite points and errors
+
+procedure sf_cgraph (gp, stds, nstds, cv)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+
+int i, j, n, nwaves
+real w, s, ymin, ymax, cveval()
+double sum, sum2
+pointer sp, waves, sens, errors, xp, yp, zp, gio
+
+begin
+ nwaves = 0
+ do i = 1, nstds-2
+ if (STD_FLAG(stds[i]) != SF_EXCLUDE)
+ nwaves = nwaves + STD_NWAVES(stds[i])
+
+ call smark (sp)
+ call salloc (waves, nwaves, TY_REAL)
+ call salloc (sens, nwaves, TY_REAL)
+ call salloc (errors, nwaves, TY_REAL)
+
+ nwaves = 0
+ do i = 1, nstds-2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ xp = STD_WAVES(stds[i])
+ yp = STD_SENS(stds[i])
+ zp = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[zp] != 0.) {
+ Memr[waves+nwaves] = Memr[xp]
+ Memr[sens+nwaves] = Memr[yp]
+ nwaves = nwaves + 1
+ }
+ xp = xp + 1
+ yp = yp + 1
+ zp = zp + 1
+ }
+ }
+ call xt_sort2 (Memr[waves], Memr[sens], nwaves)
+
+ n = 0
+ sum = 0.
+ sum2 = 0.
+ ymin = 0.
+ ymax = 0.
+ j = 0
+ do i = 1, nwaves {
+ w = Memr[waves+i-1]
+ s = Memr[sens+i-1]
+ n = n + 1
+ sum = sum + s
+ sum2 = sum2 + s * s
+
+ if ((i < nwaves) && (w == Memr[waves+i]))
+ next
+
+ if (n > 2) {
+ sum = sum / n
+ sum2 = sum2 / n - sum * sum
+ if (sum2 > 0)
+ sum2 = sqrt (sum2 / n)
+ else
+ sum2 = 0.
+ sum = sum - cveval (cv, w)
+
+ Memr[waves+j] = w
+ Memr[sens+j] = sum
+ Memr[errors+j] = sum2
+ j = j + 1
+
+ if (sum + sum2 > ymax)
+ ymax = sum + sum2
+ if (sum - sum2 < ymin)
+ ymin = sum - sum2
+ }
+ n = 0
+ sum = 0.
+ sum2 = 0.
+ }
+ nwaves = j
+
+ if (j == 0) {
+ call printf ("No wavelength overlap for composite points")
+ } else {
+ gio = GP_GIO(gp)
+ call gswind (gio, GP_WSTART(gp), GP_WEND(gp), ymin, ymax)
+ call glabax (gio, "Composite Points vs Wavelength", "", "")
+ call gseti (gio, G_PLCOLOR, GP_CMARK(gp))
+ do i = 0, nwaves-1
+ call gmark (gio, Memr[waves+i], Memr[sens+i], GM_VEBAR,
+ 1., -Memr[errors+i])
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfcolon.x b/noao/onedspec/sensfunc/sfcolon.x
new file mode 100644
index 00000000..43a056e7
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfcolon.x
@@ -0,0 +1,193 @@
+include <error.h>
+include <gset.h>
+include "sensfunc.h"
+
+# SENSFUNC colon commands
+define CMDS "|stats|vstats|function|order|graphs|images|skys|marks\
+ |fluxlimits|colors|"
+define STATS 1 # Show results
+define VSTATS 2 # Show verbose results
+define FUNCTION 3 # Sensitivity function type
+define ORDER 4 # Function order
+define GRAPHS 5 # Select graphs
+define IMAGES 6 # Select images
+define SKYS 7 # Select skys
+define MARKS 8 # Set graph mark types
+define FLIMITS 9 # Flux graph limits
+define COLORS 10 # Flux graph limits
+
+# SF_COLON -- Process SENSFUNC colon commands.
+# This procedure has so many arguments because of the STATS option.
+
+procedure sf_colon (cmd, gp, stds, nstds, cv, wextn, extn, nextn, ecv, function,
+ order, npts, rms, newfit, newgraph)
+
+char cmd[ARB] # Colon command
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction table values
+pointer ecv # Residual extinction curve
+char function[ARB] # Function type
+int order # Function order
+int npts # Number of points in fit
+real rms # RMS in fit
+int newfit # New function?
+int newgraph # New graphs?
+
+int i, j, ncmd, ival, fd, nscan(), strdic(), open(), stridx()
+real rval1, rval2
+bool streq()
+pointer sp, str
+errchk open
+
+begin
+ # Match the command against a dictionary.
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call sscan (cmd)
+ call gargwrd (Memc[str], SZ_LINE)
+ ncmd = strdic (Memc[str], Memc[str], SZ_LINE, CMDS)
+
+ # Switch on the command.
+ switch (ncmd) {
+ case STATS:
+ call gargwrd (Memc[str], SZ_LINE)
+ iferr {
+ # If no argument write to STDOUT otherwise append to file.
+ if (nscan() == 1) {
+ call gdeactivate (GP_GIO(gp), AW_CLEAR)
+ call sf_stats (STDOUT, stds, nstds, function, order, npts,
+ rms)
+ call greactivate (GP_GIO(gp), AW_PAUSE)
+ } else {
+ fd = open (Memc[str], APPEND, TEXT_FILE)
+ call sf_stats (fd, stds, nstds, function, order, npts, rms)
+ call close (fd)
+ }
+ } then
+ call erract (EA_WARN)
+ case VSTATS:
+ call gargwrd (Memc[str], SZ_LINE)
+ iferr {
+ if (nscan() == 1) {
+ # If no argument page on STDOUT otherwise append to file.
+ # A temp file is used in order to page output.
+
+ call mktemp ("tmp$sf", Memc[str], SZ_LINE)
+ fd = open (Memc[str], NEW_FILE, TEXT_FILE)
+ call sf_stats (fd, stds, nstds, function, order, npts, rms)
+ call sf_vstats (fd, stds, nstds, cv, wextn, extn, nextn,
+ ecv)
+ call close (fd)
+ call gpagefile (GP_GIO(gp), Memc[str], "sensfunc")
+ call delete (Memc[str])
+ } else {
+ fd = open (Memc[str], APPEND, TEXT_FILE)
+ call sf_stats (fd, stds, nstds, function, order, npts, rms)
+ call sf_vstats (fd, stds, nstds, cv, wextn, extn, nextn,
+ ecv)
+ call close (fd)
+ }
+ } then
+ call erract (EA_WARN)
+ case FUNCTION:
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == 2) {
+ call strcpy (Memc[str], function, SZ_FNAME)
+ newfit = NO
+ } else {
+ call printf ("function %s")
+ call pargstr (function)
+ }
+ case ORDER:
+ call gargi (ival)
+ if (nscan() == 2) {
+ order = ival
+ newfit = NO
+ } else {
+ call printf ("order %d")
+ call pargi (order)
+ }
+ case GRAPHS:
+ call gargstr (Memc[str], SZ_LINE)
+ j = str
+ for (i=str; Memc[i] != EOS; i=i+1) {
+ switch (Memc[i]) {
+ case 'a','c','e','i','l','r','s':
+ Memc[j] = Memc[i]
+ j = j + 1
+ }
+ }
+ Memc[j] = EOS
+ if (Memc[str] != EOS) {
+ call strcpy (Memc[str], GP_GRAPHS(gp,1), SF_NGRAPHS)
+ newgraph = YES
+ } else {
+ call printf ("graphs %s")
+ call pargstr (GP_GRAPHS(gp,1))
+ }
+ case IMAGES:
+ # Note that changing the image automatically clears the sky.
+ do i = 1, SF_NGRAPHS {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == i + 1) {
+ call strcpy (Memc[str], Memc[GP_IMAGES(gp,i)], SZ_FNAME)
+ Memc[GP_SKYS(gp,i)] = EOS
+ do j = 1, nstds
+ if (streq (Memc[str], STD_IMAGE(stds[j])))
+ call strcpy (STD_SKY(stds[j]), Memc[GP_SKYS(gp,i)],
+ SZ_FNAME)
+ } else
+ break
+ }
+ if (nscan() == 1) {
+ call printf ("images %s %s %s %s")
+ call pargstr (Memc[GP_IMAGES(gp,1)])
+ call pargstr (Memc[GP_IMAGES(gp,2)])
+ call pargstr (Memc[GP_IMAGES(gp,3)])
+ call pargstr (Memc[GP_IMAGES(gp,4)])
+ }
+ case SKYS:
+ do i = 1, SF_NGRAPHS {
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == i + 1)
+ call strcpy (Memc[str], Memc[GP_SKYS(gp,i)], SZ_FNAME)
+ else
+ break
+ }
+ if (nscan() == 1) {
+ call printf ("skys %s %s %s %s")
+ call pargstr (Memc[GP_SKYS(gp,1)])
+ call pargstr (Memc[GP_SKYS(gp,2)])
+ call pargstr (Memc[GP_SKYS(gp,3)])
+ call pargstr (Memc[GP_SKYS(gp,4)])
+ }
+ case MARKS:
+ call gargstr (Memc[str], SZ_LINE)
+ call sf_marks (gp, Memc[str])
+ case FLIMITS:
+ call gargr (rval1)
+ call gargr (rval2)
+ if (nscan() == 3) {
+ GP_FMIN(gp) = rval1
+ GP_FMAX(gp) = rval2
+ if (stridx (GP_GRAPHS(gp,1), "il") != 0)
+ newgraph = YES
+ } else {
+ call printf ("fluxlimits %g %g")
+ call pargr (GP_FMIN(gp))
+ call pargr (GP_FMAX(gp))
+ }
+ case COLORS:
+ call gargstr (Memc[str], SZ_LINE)
+ call sf_colors (gp, Memc[str])
+ default:
+ call printf ("Unrecognized or ambiguous command\007")
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfcolors.x b/noao/onedspec/sensfunc/sfcolors.x
new file mode 100644
index 00000000..db3f69af
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfcolors.x
@@ -0,0 +1,28 @@
+include <gset.h>
+include "sensfunc.h"
+
+
+# SF_COLORS -- Set colors.
+
+procedure sf_colors (gp, colors)
+
+pointer gp
+char colors[ARB]
+
+int i, nscan()
+
+begin
+ call sscan (colors)
+ call gargi (i)
+ if (nscan() == 1)
+ GP_PLCOLOR(gp) = i
+ call gargi (i)
+ if (nscan() == 2)
+ GP_CMARK(gp) = i
+ call gargi (i)
+ if (nscan() == 3)
+ GP_CDEL(gp) = i
+ call gargi (i)
+ if (nscan() == 4)
+ GP_CADD(gp) = i
+end
diff --git a/noao/onedspec/sensfunc/sfcomposite.x b/noao/onedspec/sensfunc/sfcomposite.x
new file mode 100644
index 00000000..506416e3
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfcomposite.x
@@ -0,0 +1,147 @@
+include "sensfunc.h"
+
+# SF_COMPOSITE -- Create a composite standard structure.
+# The composite star is the last of the standard stars.
+# When the composite star is created the other stars are turned off.
+# The function toggles.
+
+procedure sf_composite (stds, nstds, cv)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity pointer
+
+int i, j, k, n, nwaves
+pointer std, waves, sens, fit, wts, iwts, x, y, z
+errchk malloc, realloc, xt_sort3
+
+begin
+ # If data is already composite toggle back to original data.
+ # Delete data points if composite point is deleted.
+ std = stds[nstds]
+ if (STD_FLAG(std) == SF_INCLUDE) {
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ STD_FLAG(stds[i]) = SF_INCLUDE
+ }
+ STD_FLAG(std) = SF_EXCLUDE
+
+ n = STD_NWAVES(std)
+ x = STD_WAVES(std)
+ z = STD_WTS(std)
+ do i = 1, n {
+ if (Memr[z] == 0.) {
+ do j = 1, nstds - 2 {
+ if (STD_FLAG(stds[j]) == SF_EXCLUDE)
+ next
+ nwaves = STD_NWAVES(stds[j])
+ waves = STD_WAVES(stds[j])
+ wts = STD_WTS(stds[j])
+ do k = 1, nwaves {
+ if (Memr[waves] == Memr[x])
+ Memr[wts] = 0.
+ waves = waves + 1
+ wts = wts + 1
+ }
+ }
+ }
+ x = x + 1
+ z = z + 1
+ }
+ call printf ("Individual star data")
+ return
+ }
+
+ # Initialize
+ if (STD_WAVES(std) != NULL) {
+ call mfree (STD_WAVES(std), TY_REAL)
+ call mfree (STD_SENS(std), TY_REAL)
+ call mfree (STD_WTS(std), TY_REAL)
+ call mfree (STD_IWTS(std), TY_REAL)
+ call mfree (STD_X(std), TY_REAL)
+ call mfree (STD_Y(std), TY_REAL)
+ }
+
+ # To bin the data we collect all the data and then sort by wavelength.
+ nwaves = 0
+ do i = 1, nstds - 2
+ if (STD_FLAG(stds[i]) == SF_INCLUDE)
+ nwaves = nwaves + STD_NWAVES(stds[i])
+
+ call malloc (waves, nwaves, TY_REAL)
+ call malloc (sens, nwaves, TY_REAL)
+ call malloc (wts, nwaves, TY_REAL)
+
+ nwaves = 0
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ x = STD_WAVES(stds[i])
+ y = STD_SENS(stds[i])
+ z = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[z] != 0.) {
+ Memr[waves+nwaves] = Memr[x]
+ Memr[sens+nwaves] = Memr[y]
+ Memr[wts+nwaves] = Memr[z]
+ nwaves = nwaves + 1
+ }
+ x = x + 1
+ y = y + 1
+ z = z + 1
+ }
+ STD_FLAG(stds[i]) = SF_DELETE
+ STD_BEAM(std) = STD_BEAM(stds[i])
+ STD_WSTART(std) = STD_WSTART(stds[i])
+ STD_WEND(std) = STD_WEND(stds[i])
+ }
+# STD_NWAVES(stds[nstds-1]) = 0
+
+ call xt_sort3 (Memr[waves], Memr[sens], Memr[wts], nwaves)
+
+ # Go through the wavelength sorted data and composite all points
+ # with the same wavelength.
+
+ n = 0
+ Memr[sens] = Memr[wts] * Memr[sens]
+ do i = 1, nwaves-1 {
+ if (Memr[waves+i] == Memr[waves+n]) {
+ Memr[sens+n] = Memr[sens+n] + Memr[wts+i] * Memr[sens+i]
+ Memr[wts+n] = Memr[wts+n] + Memr[wts+i]
+ } else {
+ n = n + 1
+ Memr[waves+n] = Memr[waves+i]
+ Memr[sens+n] = Memr[wts+i] * Memr[sens+i]
+ Memr[wts+n] = Memr[wts+i]
+ }
+ }
+
+ nwaves = n + 1
+ do i = 0, nwaves-1
+ Memr[sens+i] = Memr[sens+i] / Memr[wts+i]
+
+ # Store the composite data in the standard star structure.
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (sens, nwaves, TY_REAL)
+ call realloc (wts, nwaves, TY_REAL)
+ call malloc (iwts, nwaves, TY_REAL)
+ call malloc (fit, nwaves, TY_REAL)
+ call malloc (x, nwaves, TY_REAL)
+ call malloc (y, nwaves, TY_REAL)
+ call amovr (Memr[wts], Memr[iwts], nwaves)
+ call cvvector (cv, Memr[waves], Memr[fit], nwaves)
+
+ STD_FLAG(std) = SF_INCLUDE
+ STD_NWAVES(std) = nwaves
+ STD_WAVES(std) = waves
+ STD_SENS(std) = sens
+ STD_FIT(std) = fit
+ STD_WTS(std) = wts
+ STD_IWTS(std) = iwts
+ STD_X(std) = x
+ STD_Y(std) = y
+
+ call printf ("Composite star data")
+end
diff --git a/noao/onedspec/sensfunc/sfdata.x b/noao/onedspec/sensfunc/sfdata.x
new file mode 100644
index 00000000..94140049
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfdata.x
@@ -0,0 +1,59 @@
+include "sensfunc.h"
+
+# SF_DATA -- Compute the X and Y values for the particular graph.
+
+procedure sf_data (stds, nstds, graph)
+
+pointer stds[nstds] # Standard star structures
+int nstds # Number of standard stars
+char graph # Graph type
+
+real a
+int i, n
+pointer wp, sp, fp, xp, yp
+
+begin
+ switch (graph) {
+ case 's': # Sensitivity vs. Wavelength
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ wp = STD_WAVES(stds[i])
+ sp = STD_SENS(stds[i])
+ xp = STD_X(stds[i])
+ yp = STD_Y(stds[i])
+ call amovr (Memr[wp], Memr[xp], n)
+ call amovr (Memr[sp], Memr[yp], n)
+ }
+ case 'a': # Residuals vs. Airmass
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ wp = STD_WAVES(stds[i])
+ sp = STD_SENS(stds[i])
+ fp = STD_FIT(stds[i])
+ xp = STD_X(stds[i])
+ yp = STD_Y(stds[i])
+ call amovkr (a, Memr[xp], n)
+ call asubr (Memr[sp], Memr[fp], Memr[yp], n)
+ }
+ case 'r': # Residuals vs. Wavelength
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ wp = STD_WAVES(stds[i])
+ sp = STD_SENS(stds[i])
+ fp = STD_FIT(stds[i])
+ xp = STD_X(stds[i])
+ yp = STD_Y(stds[i])
+ call amovr (Memr[wp], Memr[xp], n)
+ call asubr (Memr[sp], Memr[fp], Memr[yp], n)
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfdelete.x b/noao/onedspec/sensfunc/sfdelete.x
new file mode 100644
index 00000000..ff2d267c
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfdelete.x
@@ -0,0 +1,127 @@
+include <gset.h>
+include "sensfunc.h"
+
+# SF_DELETE -- Delete point, star, or wavelength identified by the
+# star index and index within the array of values.
+
+procedure sf_delete (gp, stds, nstds, key, istd, ipt)
+
+pointer gp # GIO pointer
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+int key # Delete point, star, or wavelength
+int istd # Index of standard star
+int ipt # Index of point
+
+int i, j, n, wcs, mark, mdel, cdel, stridx()
+real wave, szmark, szmdel
+pointer x, y, z, w, gio
+
+begin
+ gio = GP_GIO(gp)
+ mdel = GP_MDEL(gp)
+ cdel = GP_CDEL(gp)
+ szmdel = GP_SZMDEL(gp)
+ szmark = GP_SZMARK(gp)
+
+ # Delete the point or points from each displayed graph.
+ # When deleting multiple points check if point already deleted.
+ for (wcs = 1; GP_GRAPHS(gp,wcs) != EOS; wcs = wcs + 1) {
+ if (stridx (GP_GRAPHS(gp,wcs), "ars") == 0)
+ next
+
+ call gseti (gio, G_WCS, wcs)
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wcs))
+ switch (key) {
+ case 'p':
+ if (istd != nstds-1)
+ mark = GP_MARK(gp)
+ else
+ mark = GP_MADD(gp)
+ x = STD_X(stds[istd])+ipt-1
+ y = STD_Y(stds[istd],1)+ipt-1
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark, szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ case 's':
+ if (istd != nstds-1)
+ mark = GP_MARK(gp)
+ else
+ mark = GP_MADD(gp)
+ n = STD_NWAVES(stds[istd])
+ x = STD_X(stds[istd])
+ y = STD_Y(stds[istd])
+ w = STD_WTS(stds[istd])
+ do i = 1, n {
+ if (Memr[w] != 0.) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark, szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ }
+ x = x + 1
+ y = y + 1
+ w = w + 1
+ }
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ if (i != nstds-1)
+ mark = GP_MARK(gp)
+ else
+ mark = GP_MADD(gp)
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i])
+ y = STD_Y(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ do j = 1, n {
+ if ((Memr[z] == wave) && (Memr[w] != 0.)) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark,
+ szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel,
+ szmdel)
+ }
+ x = x + 1
+ y = y + 1
+ z = z + 1
+ w = w + 1
+ }
+ }
+ }
+ }
+
+ # Mark the points as deleted by setting their weights to zero.
+ switch (key) {
+ case 'p':
+ w = STD_WTS(stds[istd])+ipt-1
+ Memr[w] = 0.
+ case 's':
+ n = STD_NWAVES(stds[istd])
+ w = STD_WTS(stds[istd])
+ call aclrr (Memr[w], n)
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[z] == wave)
+ Memr[w] = 0.
+ w = w + 1
+ z = z + 1
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfeout.x b/noao/onedspec/sensfunc/sfeout.x
new file mode 100644
index 00000000..8dae6301
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfeout.x
@@ -0,0 +1,114 @@
+include <error.h>
+include <ctype.h>
+include <mach.h>
+include <math/curfit.h>
+
+define NEPOINTS 100 # Number of points in extinction table
+
+# SF_EOUT -- Output a revised extinction table. This is only done if there
+# is at least one residual extinction curve. No assumption is made about
+# overlapping extinction curves. In the overlap the extinction corrections
+# are averaged.
+
+procedure sf_eout (wextn, extn, nextn, ecvs, necvs)
+
+real wextn[nextn] # Standard extinction wavelengths
+real extn[nextn] # Standard extinction values
+int nextn # Number of standard extinction points
+pointer ecvs[necvs] # Residual extinction curves (one for each beam)
+int necvs # Number of residual extinction curves
+
+int i, j, fd, open(), scan(), nscan()
+real w, ext, wmin, wmax, dw, xmin, xmax, cvstatr(), cveval()
+pointer sp, fname, waves, extns, navg, cv
+
+define newfile_ 99
+
+begin
+ # If there are no residual extinction values then do nothing.
+ for (i=1; (i<=necvs) && (ecvs[i]==NULL); i=i+1)
+ ;
+ if (i > necvs)
+ return
+
+ # The output table consists of NEPOINTS.
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+ call salloc (waves, NEPOINTS, TY_REAL)
+ call salloc (extns, NEPOINTS, TY_REAL)
+ call salloc (navg, NEPOINTS, TY_INT)
+ call aclrr (Memr[extns], NEPOINTS)
+ call aclri (Memi[navg], NEPOINTS)
+
+ # Open the extinction table. If it fails allow the user to
+ # enter a new name.
+
+ call clgstr ("newextinction", Memc[fname], SZ_FNAME)
+ for (i=fname; (Memc[i]!=EOS) && IS_WHITE(Memc[i]); i=i+1)
+ if (Memc[i] == EOS) {
+ call sfree (sp)
+ return
+ }
+
+newfile_
+ iferr (fd = open (Memc[i], NEW_FILE, TEXT_FILE)) {
+ call printf ("Cannot create %s -- Enter new extinction filename: ")
+ call pargstr (Memc[fname])
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (nscan() == 1)
+ goto newfile_
+ }
+ call sfree (sp)
+ call printf ("No revised extinction file created\n")
+ return
+ }
+
+ # Determine the range of the extinction table.
+ wmin = MAX_REAL
+ wmax = -MAX_REAL
+ do i = 1, necvs {
+ if (ecvs[i] == NULL)
+ next
+ wmin = min (wmin, cvstatr (ecvs[i], CVXMIN))
+ wmax = max (wmax, cvstatr (ecvs[i], CVXMAX))
+ }
+ dw = (wmax - wmin) / (NEPOINTS - 1)
+ do i = 1, NEPOINTS
+ Memr[waves+i-1] = wmin + (i - 1) * dw
+
+ # Average the residual extinctions and add the original extinction.
+ do j = 1, necvs {
+ if (ecvs[j] == NULL)
+ next
+ cv = ecvs[j]
+ xmin = cvstatr (cv, CVXMIN)
+ xmax = cvstatr (cv, CVXMAX)
+ do i = 1, NEPOINTS {
+ w = Memr[waves+i-1]
+ if ((w < xmin) || (w > xmax))
+ next
+ Memr[extns+i-1] = Memr[extns+i-1] + cveval (cv, w)
+ Memi[navg+i-1] = Memi[navg+i-1] + 1
+ }
+ }
+ do i = 1, NEPOINTS {
+ if (Memi[navg+i-1] > 0)
+ Memr[extns+i-1] = Memr[extns+i-1] / Memi[navg+i-1]
+ w = Memr[waves+i-1]
+ call intrp (1, wextn, extn, nextn, w, ext, j)
+ Memr[extns+i-1] = Memr[extns+i-1] + ext
+ }
+
+ # Output extinction table.
+ call fprintf (fd, "# Revised extinction table.\n")
+ do i = 1, NEPOINTS {
+ call fprintf (fd, "%7.2f %6.3f\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (Memr[extns+i-1])
+ }
+ call close (fd)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfextinct.x b/noao/onedspec/sensfunc/sfextinct.x
new file mode 100644
index 00000000..f7b95326
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfextinct.x
@@ -0,0 +1,226 @@
+include <pkg/gtools.h>
+include "sensfunc.h"
+
+define RANGE_AIRMASS 0.1 # Minimum airmass range
+define SIGMA_AIRMASS 0.05 # Minimum sigma in airmass
+
+# SF_EXINCT -- Determine a residual extinction curve. At each wavelength
+# for which there are multiple observations or neighboring wavelengths
+# such that there is a sufficient airmass range determine the slope
+# of the sensitivity vs airmass. Residual sensitivity is used to minimize
+# wavelength scatter when multiple wavelengths are needed because of
+# nonoverlapping standard star data. Each such slope is a measure of the
+# residual extinction at that wavelength. To make the residual extinction
+# curve fit the extinction vs. wavelength using ICFIT.
+
+procedure sf_extinct (gp, stds, nstds, cv, ecv, function, order)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+pointer ecv # Residual extinction curve
+char function[ARB] # Fitting function
+int order # Function order
+
+bool ans
+int i, j, n, nwaves, sum, npts, scan()
+real a, amin, amax, rms, rms1, r2, sig, cveval()
+double x, y, z, sumx, sumy, sumz, sumx2, sumxy
+pointer sp, waves, sens, airm, xp, yp, fp, wp, ic
+pointer gt, gt_init()
+errchk salloc, xt_sort3, icg_fit, ic_open
+
+define cancel_ 99
+
+begin
+ # Cancel previous extinction if defined.
+ if (ecv != NULL)
+ goto cancel_
+
+ # Check for minimum airmass range and determine number of points.
+ # Ignore added objects and composite data.
+ amin = 100.
+ amax = 0.
+ nwaves = 0
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ nwaves = nwaves + STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ amin = min (amin, a)
+ amax = max (amax, a)
+ }
+ if (amax - amin < RANGE_AIRMASS) {
+ call printf (
+ "Insufficient airmass coverage for extinction determination")
+ return
+ }
+
+ # Extract data to be used and sort by wavelength.
+ # The data is wavelength, airmass, and residual sensitivity.
+ call smark (sp)
+ call salloc (waves, nwaves, TY_REAL)
+ call salloc (sens, nwaves, TY_REAL)
+ call salloc (airm, nwaves, TY_REAL)
+
+ nwaves = 0
+ do i = 1, nstds-2 {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ xp = STD_WAVES(stds[i])
+ yp = STD_SENS(stds[i])
+ fp = STD_FIT(stds[i])
+ wp = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[wp] != 0.) {
+ Memr[airm+nwaves] = a
+ Memr[waves+nwaves] = Memr[xp]
+ Memr[sens+nwaves] = Memr[yp] - Memr[fp]
+ nwaves = nwaves + 1
+ }
+ xp = xp + 1
+ yp = yp + 1
+ fp = fp + 1
+ wp = wp + 1
+ }
+ }
+
+ call xt_sort3 (Memr[waves], Memr[sens], Memr[airm], nwaves)
+
+ # Bin points with common wavelengths or at least two points.
+ sum = 0
+ sumx = 0.
+ sumy = 0.
+ sumz = 0.
+ sumx2 = 0.
+ sumxy = 0.
+ n = 0
+ do i = 0, nwaves-2 {
+ x = Memr[airm+i]
+ y = Memr[sens+i]
+ z = Memr[waves+i]
+ sum = sum + 1
+ sumx = sumx + x
+ sumy = sumy + y
+ sumx2 = sumx2 + x * x
+ sumxy = sumxy + x * y
+ sumz = sumz + z
+
+ if ((z == Memr[waves+i+1]) || (sum < 2))
+ next
+
+ x = sumx2 - sumx * sumx / sum
+ if (x > SIGMA_AIRMASS) {
+ Memr[waves+n] = sumz / sum
+ Memr[sens+n] = (sumx * sumy / sum - sumxy) / x
+ Memr[airm+n] = 1.
+ n = n + 1
+ sum = 0
+ sumx = 0.
+ sumy = 0.
+ sumz = 0.
+ sumx2 = 0.
+ sumxy = 0.
+ }
+ }
+ if (sum > 1) {
+ x = sumx2 - sumx * sumx / sum
+ if (x > SIGMA_AIRMASS) {
+ Memr[waves+n] = sumz / sum
+ Memr[sens+n] = (sumx * sumy / sum - sumxy) / x
+ Memr[airm+n] = 1.
+ n = n + 1
+ }
+ }
+
+ if (n < 2) {
+ call printf ("Cannot determine residual extinction")
+ call sfree (sp)
+ return
+ }
+
+ # Fit residual extinction curve using ICFIT.
+ gt = gt_init()
+ call gt_sets (gt, GTTYPE, "mark")
+ call gt_seti (gt, GTCOLOR, GP_PLCOLOR(gp))
+ call ic_open (ic)
+ call ic_putr (ic, "xmin", min (STD_WSTART(stds[1]), STD_WEND(stds[1])))
+ call ic_putr (ic, "xmax", max (STD_WSTART(stds[1]), STD_WEND(stds[1])))
+ call ic_pstr (ic, "function", "chebyshev")
+ call ic_puti (ic, "order", 1)
+ call ic_pstr (ic, "xlabel", "wavelength")
+ call ic_pstr (ic, "ylabel", "residual extinction")
+ call ic_pstr (ic, "yunits", "mag")
+ call icg_fit (ic, GP_GIO(gp), "cursor", gt, ecv, Memr[waves],
+ Memr[sens], Memr[airm], n)
+ call ic_closer (ic)
+ call gt_free (gt)
+
+ # Determine significance of the fit.
+ call sf_fit (stds, nstds, cv, function, order,
+ min (GP_WSTART(gp), GP_WEND(gp)), max (GP_WSTART(gp), GP_WEND(gp)))
+ call sf_rms (stds, nstds, rms1, npts)
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ xp = STD_WAVES(stds[i])
+ yp = STD_SENS(stds[i])
+ call cvvector (ecv, Memr[xp], Memr[sens], n)
+ call amulkr (Memr[sens], a, Memr[sens], n)
+ call aaddr (Memr[yp], Memr[sens], Memr[yp], n)
+ }
+ call sf_fit (stds, nstds, cv, function, order,
+ min (GP_WSTART(gp), GP_WEND(gp)), max (GP_WSTART(gp), GP_WEND(gp)))
+ call sf_rms (stds, nstds, rms, npts)
+ do i = 1, SF_NGRAPHS
+ if (GP_SHDR(gp,i) != NULL)
+ call shdr_close (GP_SHDR(gp,i))
+
+ r2 = 1 - rms ** 2 / rms1 ** 2
+ sig = r2 * (nwaves - 2) / max (0.01, 1. - r2)
+ if (sig <= 0.0)
+ sig = 0.
+ else
+ sig = sqrt (sig)
+
+ # Apply to data if desired.
+ call printf (
+ "Significance = %4.1f sigma: Apply residual extinction correction? ")
+ call pargr (sig)
+ call flush (STDOUT)
+
+ ans = false
+ if (scan() != EOF)
+ call gargb (ans)
+
+ # Undo last fit if not applying correction.
+ if (!ans)
+ goto cancel_
+
+ call printf ("Residual extinction correction applied")
+ call sfree (sp)
+ return
+
+cancel_
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ a = STD_AIRMASS(stds[i])
+ xp = STD_WAVES(stds[i])
+ yp = STD_SENS(stds[i])
+ do j = 1, n {
+ Memr[yp] = Memr[yp] - a * cveval (ecv, Memr[xp])
+ xp = xp + 1
+ yp = yp + 1
+ }
+ }
+ call cvfree (ecv)
+ call printf ("Residual extinction correction canceled")
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sffit.x b/noao/onedspec/sensfunc/sffit.x
new file mode 100644
index 00000000..3be306ad
--- /dev/null
+++ b/noao/onedspec/sensfunc/sffit.x
@@ -0,0 +1,78 @@
+include <math/curfit.h>
+include "sensfunc.h"
+
+define FUNCTIONS "|chebyshev|legendre|spline3|spline1|"
+
+procedure sf_fit (stds, nstds, cv, function, order, xmin, xmax)
+
+pointer stds[nstds]
+int nstds
+pointer cv
+char function[ARB]
+int order
+real xmin
+real xmax
+
+int i, n, func, ord, strdic()
+pointer x, y, w
+
+int functions[4]
+data functions/CHEBYSHEV,LEGENDRE,SPLINE3,SPLINE1/
+
+begin
+ func = strdic (function, function, SZ_FNAME, FUNCTIONS)
+ func = functions[max (1, func)]
+ ord = order
+
+ while (ord > 0) {
+ call cvfree (cv)
+ call cvinit (cv, func, ord, xmin, xmax)
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) == SF_INCLUDE) {
+ n = STD_NWAVES(stds[i])
+ x = STD_WAVES(stds[i])
+ y = STD_SENS(stds[i])
+ w = STD_WTS(stds[i])
+ call cvacpts (cv, Memr[x], Memr[y], Memr[w], n, WTS_USER)
+ }
+ }
+ call cvsolve (cv, i)
+ if (i == OK)
+ break
+
+ switch (func) {
+ case CHEBYSHEV, LEGENDRE:
+ ord = ord - 1
+ case SPLINE3:
+ ord = ord - 1
+ if (ord == 0) {
+ func = CHEBYSHEV
+ ord = 2
+ }
+ case SPLINE1:
+ ord = ord - 1
+ if (ord == 0) {
+ func = CHEBYSHEV
+ ord = 1
+ }
+ }
+ }
+
+ switch (i) {
+ case SINGULAR:
+ call error (0, "Singular solution")
+ case NO_DEG_FREEDOM:
+ call error (0, "No degrees of freedom")
+ }
+
+ # Set fitted values
+ do i = 1, nstds
+ if (STD_FLAG(stds[i]) != SF_EXCLUDE) {
+ n = STD_NWAVES(stds[i])
+ if (n < 1)
+ next
+ x = STD_WAVES(stds[i])
+ y = STD_FIT(stds[i])
+ call cvvector (cv, Memr[x], Memr[y], n)
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfginit.x b/noao/onedspec/sensfunc/sfginit.x
new file mode 100644
index 00000000..0214c7a7
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfginit.x
@@ -0,0 +1,89 @@
+include <gset.h>
+include "sensfunc.h"
+
+# SF_GINIT -- Initialize graphics structure and open the graphics device.
+# This includes CL requests for the starting graphs (default is "sr"),
+# the mark types (default is "plus box"), and graphics device.
+
+procedure sf_ginit (gp)
+
+pointer gp # Graphics structure (returned)
+
+int i, j
+pointer sp, str, gopen()
+errchk malloc, gopen
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ call calloc (gp, LEN_GP, TY_STRUCT)
+ do i = 1, SF_NGRAPHS {
+ call malloc (GP_IMAGES(gp,i), SZ_FNAME, TY_CHAR)
+ Memc[GP_IMAGES(gp,i)] = EOS
+ call malloc (GP_SKYS(gp,i), SZ_FNAME, TY_CHAR)
+ Memc[GP_SKYS(gp,i)] = EOS
+ }
+
+ # Set the starting graph types.
+ call clgstr ("graphs", Memc[str], SZ_FNAME)
+ j = str
+ for (i=str; Memc[i] != EOS; i=i+1) {
+ switch (Memc[i]) {
+ case 'a','c','e','i','l','r','s':
+ Memc[j] = Memc[i]
+ j = j + 1
+ }
+ }
+ Memc[j] = EOS
+ if (Memc[str] != EOS)
+ call strcpy (Memc[str], GP_GRAPHS(gp,1), SF_NGRAPHS)
+ else
+ call strcpy ("sr", GP_GRAPHS(gp,1), SF_NGRAPHS)
+
+ # Set the starting mark types and colors.
+ GP_MARK(gp) = GM_PLUS
+ GP_MDEL(gp) = GM_CROSS
+ GP_MADD(gp) = GM_BOX
+ GP_PLCOLOR(gp) = 2
+ GP_CMARK(gp) = 1
+ GP_CDEL(gp) = 3
+ GP_CADD(gp) = 4
+ call clgstr ("marks", Memc[str], SZ_FNAME)
+ call sf_marks (gp, Memc[str])
+ call clgstr ("colors", Memc[str], SZ_FNAME)
+ call sf_colors (gp, Memc[str])
+
+ # Set flux limits
+ GP_FMIN(gp) = INDEF
+ GP_FMAX(gp) = INDEF
+
+ # Open the graphics device.
+ call clgstr ("device", Memc[str], SZ_FNAME)
+ GP_GIO(gp) = gopen (Memc[str], NEW_FILE, STDGRAPH)
+
+ call sfree (sp)
+end
+
+
+# SF_GFREE -- Free the graphics structure and close the graphics device.
+
+procedure sf_gfree (gp)
+
+pointer gp # Graphics structure
+
+int i
+
+begin
+ if (gp == NULL)
+ return
+
+ call gclose (GP_GIO(gp))
+ do i = 1, SF_NGRAPHS {
+ call mfree (GP_IMAGES(gp,i), TY_CHAR)
+ call mfree (GP_SKYS(gp,i), TY_CHAR)
+ if (GP_SHDR(gp,i) != NULL)
+ call shdr_close (GP_SHDR(gp,i))
+ }
+ call mfree (gp, TY_STRUCT)
+end
diff --git a/noao/onedspec/sensfunc/sfgraph.x b/noao/onedspec/sensfunc/sfgraph.x
new file mode 100644
index 00000000..bb6fb26f
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfgraph.x
@@ -0,0 +1,289 @@
+include <gset.h>
+include <error.h>
+include <math/curfit.h>
+include "sensfunc.h"
+
+define NCURVE 50 # Number of vectors in curve
+
+
+# SF_GRAPH -- Graph the desired data on the output graphics device.
+# This entry procedure determines the graph types, sets the device viewports
+# for each graph, and calls a procedure to make the graph.
+
+procedure sf_graph (gp, stds, nstds, cv, wextn, extn, nextn, ecv)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of values in extinction table
+pointer ecv # Residual extinction curve.
+
+int i, image, ngraphs, strlen()
+real fa[8]
+pointer sp, id, gio
+
+data fa/0.,1.,1.,0.,0.,0.,1.,1./
+
+begin
+ # Clear the graphs, write the title, and set the viewports based on
+ # the number of graphs.
+
+ call smark (sp)
+ call salloc (id, SZ_LINE, TY_CHAR)
+ call sysid (Memc[id], SZ_LINE)
+
+ gio = GP_GIO(gp)
+ call gclear (gio)
+ call gseti (gio, G_FACOLOR, 0)
+ call gseti (gio, G_NMINOR, 0)
+ ngraphs = strlen (GP_GRAPHS(gp,1))
+ switch (ngraphs) {
+ case 1:
+ call gseti (gio, G_WCS, 1)
+ call gsview (gio, .10, .97, .1, .9)
+ GP_SZMARK(gp) = 2.
+ GP_SZMDEL(gp) = 2.
+ case 2:
+ call gseti (gio, G_WCS, 1)
+ call gsview (gio, .10, .97, .55, .9)
+ call gseti (gio, G_WCS, 2)
+ call gsview (gio, .10, .97, .10, .45)
+ GP_SZMARK(gp) = 2.
+ GP_SZMDEL(gp) = 2.
+ case 3:
+ call gseti (gio, G_WCS, 1)
+ call gsview (gio, .10, .97, .55, .9)
+ call gseti (gio, G_WCS, 2)
+ call gsview (gio, .10, .50, .1, .45)
+ call gseti (gio, G_WCS, 3)
+ call gsview (gio, .57, .97, .1, .45)
+ GP_SZMARK(gp) = 2.
+ GP_SZMDEL(gp) = 2.
+ default:
+ call gseti (gio, G_WCS, 1)
+ call gsview (gio, .10, .50, .55, .9)
+ call gseti (gio, G_WCS, 2)
+ call gsview (gio, .57, .97, .55, .9)
+ call gseti (gio, G_WCS, 3)
+ call gsview (gio, .10, .50, .1, .45)
+ call gseti (gio, G_WCS, 4)
+ call gsview (gio, .57, .97, .1, .45)
+ GP_SZMARK(gp) = .01
+ GP_SZMDEL(gp) = .01
+ }
+
+ # For each graph select the viewport and call a procedure to make
+ # the graph.
+
+ image = 0
+ for (i = 1; GP_GRAPHS(gp,i) != EOS; i = i + 1) {
+ call gseti (gio, G_WCS, i)
+ if (i > 1)
+ call gfill (gio, fa, fa[5], 4, GF_SOLID)
+ switch (GP_GRAPHS(gp,i)) {
+ case 'a', 's', 'r':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,i))
+ call sf_graph1 (gp, stds, nstds, GP_GRAPHS(gp,i))
+ case 'e':
+ call sf_egraph (gp, wextn, extn, nextn, ecv)
+ case 'c':
+ call sf_cgraph (gp, stds, nstds, cv)
+ case 'i', 'l':
+ if (GP_GRAPHS(gp,i) == 'i')
+ GP_LOG(gp) = NO
+ else
+ GP_LOG(gp) = YES
+ image = image + 1
+ iferr (call sf_image (gp, image, stds, nstds, cv, wextn, extn,
+ nextn, ecv))
+ call erract (EA_WARN)
+ }
+ }
+
+ call gseti (gio, G_WCS, 0)
+ call gtext (gio, 0.5, 1., Memc[id], "h=c,v=t,f=b")
+ call gtext (gio, 0.5, 0.97, GP_TITLE(gp), "h=c,v=t,f=b")
+
+ call sfree (sp)
+end
+
+
+# SF_GRAPH1 -- Make graph of sensitivity or residual sensitivity vs wavelength.
+
+procedure sf_graph1 (gp, stds, nstds, graph)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+char graph # Graph type
+
+int i, j, n, mark, mdel, cdel, color
+real szmark, szmdel, ymin, ymax, y1, y2
+pointer x, y, w, gio
+
+begin
+ gio = GP_GIO(gp)
+
+ # Autoscale the included data in Y and set wavelength range.
+ j = 0
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ j = j + 1
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i])
+ y = STD_Y(stds[i])
+ if (j == 1)
+ call alimr (Memr[y], n, ymin, ymax)
+ else {
+ call alimr (Memr[y], n, y1, y2)
+ ymin = min (ymin, y1)
+ ymax = max (ymax, y2)
+ }
+ }
+ y1 = 0.05 * (ymax - ymin)
+ ymin = ymin - y1
+ ymax = ymax + y1
+
+ # Draw axes and title based on type of graph.
+ switch (graph) {
+ case 'a':
+ call gswind (gio, GP_AIRMASS(gp,1), GP_AIRMASS(gp,2), ymin, ymax)
+ call glabax (gio, "Sensitivity Residuals vs Airmass", "", "")
+ case 's':
+ call gswind (gio, GP_WSTART(gp), GP_WEND(gp), ymin, ymax)
+ call glabax (gio, "Sensitivity vs Wavelength", "", "")
+ case 'r':
+ call gswind (gio, GP_WSTART(gp), GP_WEND(gp), ymin, ymax)
+ call glabax (gio, "Sensitivity Residuals vs Wavelength", "", "")
+ }
+
+ # Plot the data with appropriate mark types and sizes.
+ mdel = GP_MDEL(gp)
+ cdel = GP_CDEL(gp)
+ szmdel = GP_SZMDEL(gp)
+ szmark = GP_SZMARK(gp)
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ if (i != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i]) - 1
+ y = STD_Y(stds[i]) - 1
+ w = STD_WTS(stds[i]) - 1
+ do j = 1, n {
+ if (Memr[w+j] == 0.) {
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x+j], Memr[y+j], mdel, szmdel, szmdel)
+ } else {
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x+j], Memr[y+j], mark, szmark, szmark)
+ }
+ }
+ }
+end
+
+
+# SF_EGRAPH -- Graph extinction curves with and without residual extinction
+# correction.
+
+procedure sf_egraph (gp, wextn, extn, nextn, ecv)
+
+pointer gp # Graphics structure
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction table values
+pointer ecv # Residual extinction curve
+
+int i, j
+real xmin, xmax, dx, x, cveval()
+pointer sp, ext, extnew, gio
+
+begin
+ call smark (sp)
+ call salloc (ext, NCURVE, TY_REAL)
+
+ # Interpolate extinction table to a grid of wavelengths within
+ # the range of the sensitivity data.
+
+ gio = GP_GIO(gp)
+ xmin = GP_WSTART(gp)
+ xmax = GP_WEND(gp)
+ dx = (xmax - xmin) / (NCURVE - 1)
+ x = xmin
+ do i = 0, NCURVE-1 {
+ call intrp (1, wextn, extn, nextn, x, Memr[ext+i], j)
+ x = x + dx
+ }
+ call gswind (gio, xmin, xmax, INDEF, INDEF)
+ call gascale (gio, Memr[ext], NCURVE, 2)
+
+ # If there is a residual extinction curve determine a new extinction
+ # curve.
+
+ if (ecv != NULL) {
+ call salloc (extnew, NCURVE, TY_REAL)
+ call amovr (Memr[ext], Memr[extnew], NCURVE)
+ x = xmin
+ do i = 0, NCURVE-1 {
+ Memr[extnew+i] = Memr[extnew+i] + cveval (ecv, x)
+ x = x + dx
+ }
+ call grscale (gio, Memr[extnew], NCURVE, 2)
+ }
+
+ # Draw the axes and title and extinction curves.
+ call glabax (gio, "Extinction vs Wavelength", "", "")
+ call gseti (gio, G_PLCOLOR, GP_PLCOLOR(gp))
+ call gvline (gio, Memr[ext], NCURVE, xmin, xmax)
+ if (ecv != NULL) {
+ call gseti (gio, G_PLTYPE, 2)
+ call gseti (gio, G_PLCOLOR, GP_PLCOLOR(gp)+1)
+ call gvline (gio, Memr[extnew], NCURVE, xmin, xmax)
+ call gseti (gio, G_PLTYPE, 1)
+ }
+
+ call sfree (sp)
+end
+
+
+# SF_FITGRAPH -- Overplot the fitted sensitivity curve.
+
+procedure sf_fitgraph (gp, cv)
+
+pointer gp # Graphics structure
+pointer cv # Sensitivity function curve
+
+int i, j
+real x1, x2, y1, y2, cveval()
+pointer gio
+
+begin
+ gio = GP_GIO(gp)
+ call gseti (gio, G_PLCOLOR, GP_PLCOLOR(gp))
+
+ # Only plot on sensitivity curve graph types.
+ for (j = 1; GP_GRAPHS(gp,j) != EOS; j = j + 1) {
+ if (GP_GRAPHS(gp,j) != 's')
+ next
+ call gseti (gio, G_WCS, j)
+ call ggwind (gio, x1, x2, y1, y2)
+ x2 = (x2 - x1) / NCURVE
+ y1 = cveval (cv, x1)
+ call gamove (gio, x1, y1)
+ do i = 1, NCURVE {
+ x1 = x1 + x2
+ y1 = cveval (cv, x1)
+ call gadraw (gio, x1, y1)
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfimage.x b/noao/onedspec/sensfunc/sfimage.x
new file mode 100644
index 00000000..71edc213
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfimage.x
@@ -0,0 +1,234 @@
+include <gset.h>
+include <math/curfit.h>
+include "sensfunc.h"
+include <smw.h>
+
+
+# SF_IMAGE -- Graph fluxed image data and possible standard flux points.
+# For efficiency the IMIO pointer, buffer, and associated data are kept
+# since a redraw is a common occurence and generating the data is slow.
+
+procedure sf_image (gp, wc, stds, nstds, cv, wextn, extn, nextn, ecv)
+
+pointer gp # Graphics structure
+int wc # WC of graph
+pointer stds[nstds] # Standard star data for flux points
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction table values
+pointer ecv # Residual extinction curve
+
+int scale[SF_NGRAPHS], log[SF_NGRAPHS]
+
+bool newobs, obshead
+int i, j, n, err
+real a, t, w, dw, e, sens, latitude, smin, smax, xmin, xmax
+pointer im, mw, sh, skyim, skymw, skysh, std, gio, sp, str, x, y, z, obs
+pointer immap(), smw_openim()
+real cveval(), obsgetr(), cvstatr()
+double shdr_lw()
+bool streq(), strne()
+errchk immap, smw_openim, obsimopen
+
+define plot_ 99
+
+begin
+ # Return if no image name.
+ if (Memc[GP_IMAGES(gp,wc)] == EOS)
+ return
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Get the spectrum and sky subtract if necessary.
+ sh = GP_SHDR(gp,wc)
+ if (sh != NULL) {
+ if (streq (Memc[GP_IMAGES(gp,wc)], IMNAME(sh))) {
+ if (GP_LOG(gp) == log[wc])
+ goto plot_
+ else
+ call shdr_close (sh)
+ }
+ }
+
+ # Determine a valid standard star to get aperture number.
+ do i = 1, nstds
+ if (STD_FLAG(stds[i]) != SF_EXCLUDE) {
+ std = stds[i]
+ break
+ }
+
+ im = immap (Memc[GP_IMAGES(gp,wc)], READ_ONLY, 0)
+ mw = smw_openim (im)
+ call shdr_open (im, mw, 1, 1, STD_BEAM(std), SHDATA, sh)
+
+ # Check for dispersion correction
+ if (DC(sh) == DCNO) {
+ call shdr_close (sh)
+ call smw_close (mw)
+ call imunmap (im)
+ GP_SHDR(gp,wc) = NULL
+ call sfree (sp)
+ call printf ("-%s must be dispersion corrected-")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ return
+ }
+
+ # Sky subtract if necessary
+ if (Memc[GP_SKYS(gp,wc)] != EOS) {
+ skyim = immap (Memc[GP_SKYS(gp,wc)], READ_ONLY, 0)
+ skymw = smw_openim (skyim)
+ call shdr_open (skyim, skymw, 1, 1, STD_BEAM(std), SHDATA, skysh)
+ call shdr_rebin (skysh, sh)
+ call asubr (Memr[SY(sh)], Memr[SY(skysh)], Memr[SY(sh)], SN(sh))
+ call shdr_close (skysh)
+ call smw_close (skymw)
+ call imunmap (skyim)
+ }
+
+ # Set airmass and exposure time
+ if (IS_INDEF (AM(sh))) {
+ obs = NULL
+ call clgstr ("observatory", Memc[str], SZ_LINE)
+ call obsimopen (obs, im, Memc[str], NO, newobs, obshead)
+ latitude = obsgetr (obs, "latitude")
+ call obsclose (obs)
+ call get_airm (RA(sh), DEC(sh), HA(sh), ST(sh), latitude,
+ AM(sh))
+ }
+ a = AM(sh)
+ if (IS_INDEF (IT(sh)))
+ t = 1.
+ else
+ t = IT(sh)
+
+ # Apply extinction correction if needed
+ if (EC(sh) == ECNO) {
+ if (ecv != NULL) {
+ xmin = cvstatr (ecv, CVXMIN)
+ xmax = cvstatr (ecv, CVXMAX)
+ }
+ do i = 1, SN(sh) {
+ w = Memr[SX(sh)+i-1]
+ call intrp (1, wextn, extn, nextn, w, e, err)
+ if (ecv != NULL)
+ e = e + cveval (ecv, min (xmax, max (w, xmin)))
+ Memr[SY(sh)+i-1] = Memr[SY(sh)+i-1] * 10. ** (0.4 * a * e)
+ }
+ } else {
+ call printf ("-%s already extinction corrected-")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ }
+
+ # Apply flux calibration if needed
+ if (FC(sh) == FCNO) {
+ do i = 1, SN(sh) {
+ w = Memr[SX(sh)+i-1]
+ dw = abs (shdr_lw (sh, double (i+0.5)) -
+ shdr_lw (sh, double (i-0.5)))
+ sens = cveval (cv, w)
+ Memr[SY(sh)+i-1] = Memr[SY(sh)+i-1] / t / dw / 10.**(0.4*sens)
+ }
+ } else {
+ call printf ("-%s already flux calibrated-")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ }
+
+ # Set flux scaling
+ call alimr (Memr[SY(sh)], SN(sh), smin, smax)
+ if (smax < 0.)
+ scale[wc] = 0.
+ else if (GP_LOG(gp) == NO) {
+ scale[wc] = -log10 (smax) + 1
+ w = 10. ** scale[wc]
+ call amulkr (Memr[SY(sh)], w, Memr[SY(sh)], SN(sh))
+ } else {
+ scale[wc] = INDEFI
+ smin = smax / 1000.
+ w = smax
+ y = SY(sh)
+ do i = 1, SN(sh) {
+ if (Memr[y] > smin)
+ w = min (w, Memr[y])
+ y = y + 1
+ }
+ y = SY(sh)
+ do i = 1, SN(sh) {
+ Memr[y] = log10 (max (Memr[y], w))
+ y = y + 1
+ }
+ }
+ log[wc] = GP_LOG(gp)
+
+ # Save the spectrum for future redraw.
+ call smw_close (MW(sh))
+ call imunmap (im)
+ GP_SHDR(gp,wc) = sh
+
+plot_
+ # Plot scaled graph.
+ smin = GP_FMIN(gp)
+ smax = GP_FMAX(gp)
+ if (IS_INDEFI(scale[wc])) {
+ call sprintf (Memc[str], SZ_LINE, "%s: Log Flux")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ if (!IS_INDEF(smin)) {
+ if (smin > 0.)
+ smin = log10 (smin)
+ else
+ smin = INDEF
+ }
+ if (!IS_INDEF(smax)) {
+ if (smax > 0.)
+ smax = log10 (smax)
+ else
+ smax = INDEF
+ }
+ } else if (scale[wc] != 0) {
+ call sprintf (Memc[str], SZ_LINE, "%s: Flux x 1E%d")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ call pargi (scale[wc])
+ w = 10. ** scale[wc]
+ if (!IS_INDEF(smin))
+ smin = w * smin
+ if (!IS_INDEF(smax))
+ smax = w * smax
+ } else {
+ call sprintf (Memc[str], SZ_LINE, "%s: Flux")
+ call pargstr (Memc[GP_IMAGES(gp,wc)])
+ w = 1.
+ }
+
+ gio = GP_GIO(gp)
+ call gascale (gio, Memr[SX(sh)], SN(sh), 1)
+ call gascale (gio, Memr[SY(sh)], SN(sh), 2)
+ call gswind (gio, INDEF, INDEF, smin, smax)
+ call glabax (gio, Memc[str], "", "")
+ call gseti (gio, G_PLCOLOR, GP_PLCOLOR(gp))
+ call gpline (gio, Memr[SX(sh)], Memr[SY(sh)], SN(sh))
+
+ call sfree (sp)
+
+ # Check if image is one of the standard stars and plot flux points.
+ do i = 1, nstds {
+ if (strne (Memc[GP_IMAGES(gp,wc)], STD_IMAGE(stds[i])))
+ next
+ n = STD_NWAVES(stds[i])
+ x = STD_WAVES(stds[i])
+ y = STD_FLUXES(stds[i])
+ z = STD_DWAVES(stds[i])
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, GP_CMARK(gp))
+ if (IS_INDEFI(scale[wc])) {
+ do j = 0, n-1
+ call gmark (gio, Memr[x+j], log10 (Memr[y+j]), GM_HEBAR,
+ -Memr[z+j], 1.)
+ } else {
+ do j = 0, n-1
+ call gmark (gio, Memr[x+j], w * Memr[y+j], GM_HEBAR,
+ -Memr[z+j], 1.)
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfmarks.x b/noao/onedspec/sensfunc/sfmarks.x
new file mode 100644
index 00000000..39d85af6
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfmarks.x
@@ -0,0 +1,46 @@
+include <gset.h>
+include "sensfunc.h"
+
+define GMTYPES "|point|box|plus|cross|diamond|hline|vline|hebar|vebar|circle|"
+
+
+# SF_MARKS -- Decode user mark types into GIO mark types. The input string
+# consists of two whitespace separated mark types.
+
+procedure sf_marks (gp, marks)
+
+pointer gp
+char marks[ARB]
+
+int i, nscan(), strdic()
+pointer sp, str
+
+int gmtypes[10]
+data gmtypes /GM_POINT,GM_BOX,GM_PLUS,GM_CROSS,GM_DIAMOND,GM_HLINE,GM_VLINE,
+ GM_HEBAR,GM_VEBAR,GM_CIRCLE/
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call sscan (marks)
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == 1) {
+ i = strdic (Memc[str], Memc[str], SZ_LINE, GMTYPES)
+ if (i != 0)
+ GP_MARK(gp) = gmtypes[i]
+ }
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == 2) {
+ i = strdic (Memc[str], Memc[str], SZ_LINE, GMTYPES)
+ if (i != 0)
+ GP_MDEL(gp) = gmtypes[i]
+ }
+ call gargwrd (Memc[str], SZ_LINE)
+ if (nscan() == 3) {
+ i = strdic (Memc[str], Memc[str], SZ_LINE, GMTYPES)
+ if (i != 0)
+ GP_MADD(gp) = gmtypes[i]
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfmove.x b/noao/onedspec/sensfunc/sfmove.x
new file mode 100644
index 00000000..4451938e
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfmove.x
@@ -0,0 +1,166 @@
+include <gset.h>
+include "sensfunc.h"
+
+
+# SF_MOVE -- Move point, star, or wavelength.
+
+procedure sf_move (gp, stds, nstds, key, istd, ipt, shift)
+
+pointer gp # GIO pointer
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+int key # Delete point, star, or wavelength
+int istd # Index of standard star
+int ipt # Index of point
+real shift
+
+int i, j, n, wcs, mark, mdel, cdel, color, stridx()
+real wave, szmark, szmdel
+pointer x, y, z, w, gio
+
+begin
+ gio = GP_GIO(gp)
+ mdel = GP_MDEL(gp)
+ cdel = GP_CDEL(gp)
+ szmdel = GP_SZMDEL(gp)
+ szmark = GP_SZMARK(gp)
+
+ # Move points in each displayed graph.
+ for (wcs = 1; GP_GRAPHS(gp,wcs) != EOS; wcs = wcs + 1) {
+ if (stridx (GP_GRAPHS(gp,wcs), "ars") == 0)
+ next
+
+ call gseti (gio, G_WCS, wcs)
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wcs))
+ switch (key) {
+ case 'p':
+ if (istd != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ x = STD_X(stds[istd])+ipt-1
+ y = STD_Y(stds[istd],1)+ipt-1
+ w = STD_WTS(stds[istd])+ipt-1
+ if (Memr[w] != 0.) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark, szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y]+shift, mark, szmark,
+ szmark)
+ } else {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y]+shift, mdel, szmdel,
+ szmdel)
+ }
+ case 's':
+ if (istd != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ n = STD_NWAVES(stds[istd])
+ x = STD_X(stds[istd])
+ y = STD_Y(stds[istd])
+ w = STD_WTS(stds[istd])
+ do i = 1, n {
+ if (Memr[w] != 0.) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark, szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y]+shift, mark, szmark,
+ szmark)
+ } else {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y]+shift, mdel, szmdel,
+ szmdel)
+ }
+ x = x + 1
+ y = y + 1
+ w = w + 1
+ }
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ if (i != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i])
+ y = STD_Y(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[z] == wave) {
+ if (Memr[w] != 0.) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark,
+ szmark)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y]+shift, mark,
+ szmark, szmark)
+ } else {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel,
+ szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, cdel)
+ call gmark (gio, Memr[x], Memr[y]+shift, mdel,
+ szmdel, szmdel)
+ }
+ }
+ x = x + 1
+ y = y + 1
+ z = z + 1
+ w = w + 1
+ }
+ }
+ }
+ }
+
+ # Now add the shift to the data.
+ switch (key) {
+ case 'p':
+ y = STD_SENS(stds[istd])+ipt-1
+ Memr[y] = Memr[y] + shift
+ case 's':
+ n = STD_NWAVES(stds[istd])
+ y = STD_SENS(stds[istd])
+ call aaddkr (Memr[y], shift, Memr[y], n)
+ STD_SHIFT(stds[istd]) = STD_SHIFT(stds[istd]) + shift
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ z = STD_WAVES(stds[i])
+ y = STD_SENS(stds[i])
+ do j = 1, n {
+ if (Memr[z] == wave)
+ Memr[y] = Memr[y] + shift
+ w = w + 1
+ y = y + 1
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfnearest.x b/noao/onedspec/sensfunc/sfnearest.x
new file mode 100644
index 00000000..540faad2
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfnearest.x
@@ -0,0 +1,69 @@
+include <gset.h>
+include <mach.h>
+include "sensfunc.h"
+
+# SF_NEAREST -- Find the nearest point to the cursor. Return the standard
+# star index and the wavelength point index. The metric is in NDC.
+# The cursor is moved to the nearest point selected. Return zero for
+# the standard star index if valid point not found.
+
+procedure sf_nearest (gp, stds, nstds, wx, wy, wcs, type, istd, ipt)
+
+pointer gp # Graphics pointer
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+real wx, wy # Cursor position
+int wcs # WCS
+int type # Type of points (0=not del, 1=del, 2=both)
+int istd # Index of standard star (returned)
+int ipt # Index of point (returned)
+
+int i, j, n, stridx()
+real x0, y0, x1, y1, x2, y2, r2, r2min
+pointer x, y, w, gio
+
+begin
+ # Check for valid wc.
+ istd = 0
+ if (stridx (GP_GRAPHS(gp,wcs), "ars") == 0)
+ return
+
+ # Transform world cursor coordinates to NDC.
+ gio = GP_GIO(gp)
+ call gctran (gio, wx, wy, wx, wy, wcs, 0)
+
+ # Search for nearest point.
+ r2min = MAX_REAL
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i]) - 1
+ y = STD_Y(stds[i]) - 1
+ w = STD_WTS(stds[i]) - 1
+ do j = 1, n {
+ if (type == 0) {
+ if (Memr[w+j] == 0.)
+ next
+ } else if (type == 1) {
+ if (Memr[w+j] != 0.)
+ next
+ }
+ x1 = Memr[x+j]
+ y1 = Memr[y+j]
+ call gctran (gio, x1, y1, x0, y0, wcs, 0)
+ r2 = (x0 - wx) ** 2 + (y0 - wy) ** 2
+ if (r2 < r2min) {
+ r2min = r2
+ istd = i
+ ipt = j
+ x2 = x1
+ y2 = y1
+ }
+ }
+ }
+
+ # Move the cursor to the selected point.
+ call gseti (gio, G_WCS, wcs)
+ call gscur (gio, x2, y2)
+end
diff --git a/noao/onedspec/sensfunc/sfoutput.x b/noao/onedspec/sensfunc/sfoutput.x
new file mode 100644
index 00000000..e21df280
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfoutput.x
@@ -0,0 +1,114 @@
+include <mach.h>
+include <imhdr.h>
+include "sensfunc.h"
+
+
+# SF_OUTPUT -- Write the sensitivity function image.
+
+procedure sf_output (stds, nstds, cv, output, ignoreaps)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+char output[SZ_FNAME] # Output root image name (must be SZ_FNAME)
+bool ignoreaps # Ignore apertures?
+
+int i, ap, nw, scan(), nscan()
+real w1, w2, dw, dw1, aplow[2], aphigh[2], cveval()
+pointer sp, fname, std, im, mw, buf, immap(), mw_open(), impl1r()
+errchk imaddi, imaddr
+
+define makeim_ 99
+
+begin
+ # Return if no output root sensitivity imagename is specified.
+ if (output[1] == EOS)
+ return
+
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+
+ # Determine wavelength range and reference standard star.
+ w1 = MAX_REAL
+ w2 = -MAX_REAL
+ dw = MAX_REAL
+ do i = 1, nstds-2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ std = stds[i]
+ dw1 = abs ((STD_WEND(std) - STD_WSTART(std)) / (STD_NPTS(std) - 1))
+ w1 = min (w1, STD_WSTART(std), STD_WEND(std))
+ w2 = max (w2, STD_WSTART(std), STD_WEND(std))
+ dw = min (dw, dw1)
+ }
+ nw = (w2 - w1) / dw + 1.5
+
+ # Make output image name with aperture number appended. If the
+ # image exists allow the user to change root name.
+makeim_
+ if (ignoreaps) {
+ call strcpy (output, Memc[fname], SZ_FNAME)
+ } else {
+ call sprintf (Memc[fname], SZ_FNAME, "%s.%04d")
+ call pargstr (output)
+ call pargi (STD_BEAM(std))
+ }
+
+ iferr (im = immap (Memc[fname], NEW_IMAGE, 0)) {
+ call printf ("Cannot create %s -- Enter new name: ")
+ call pargstr (Memc[fname])
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargwrd (Memc[fname], SZ_FNAME)
+ if (nscan() == 1) {
+ call strcpy (Memc[fname], output, SZ_FNAME)
+ goto makeim_
+ }
+ }
+ call printf ("No sensitivity function created for aperture %2d\n")
+ call pargi (STD_BEAM(std))
+ call flush (STDOUT)
+ return
+ }
+
+ # Define the image header.
+ IM_NDIM(im) = 1
+ IM_LEN(im,1) = nw
+ IM_PIXTYPE(im) = TY_REAL
+ if (ignoreaps) {
+ call sprintf (IM_TITLE(im), SZ_FNAME,
+ "Sensitivity function for all apertures")
+ } else {
+ call sprintf (IM_TITLE(im), SZ_FNAME,
+ "Sensitivity function for aperture %d")
+ call pargi (STD_BEAM(std))
+ }
+
+ mw = mw_open (NULL, 1)
+ call mw_newsystem (mw, "equispec", 1)
+ call mw_swtype (mw, 1, 1, "linear", "label=Wavelength units=Angstroms")
+ call smw_open (mw, NULL, im)
+ ap = STD_BEAM(std)
+ aplow[1] = INDEF
+ aphigh[1] = INDEF
+ aplow[2] = INDEF
+ aphigh[2] = INDEF
+ call smw_swattrs (mw, 1, 1, ap, STD_BEAM(std), 0,
+ double(w1), double(dw), nw, 0D0, aplow, aphigh, "")
+ call smw_saveim (mw, im)
+ call smw_close (mw)
+
+ # Write sensitivity data.
+ buf = impl1r (im)
+ do i = 0, nw-1
+ Memr[buf+i] = cveval (cv, w1 + i * dw)
+
+ # Notify user.
+ call printf ("%s --> %s\n")
+ call pargstr (IM_TITLE(im))
+ call pargstr (Memc[fname])
+ call flush (STDOUT)
+
+ call imunmap (im)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfreset.x b/noao/onedspec/sensfunc/sfreset.x
new file mode 100644
index 00000000..fc4d974e
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfreset.x
@@ -0,0 +1,62 @@
+include "sensfunc.h"
+
+# SF_RESET -- Reset the standard star data to the original input.
+# This is called cancel changes and start over.
+
+procedure sf_reset (stds, nstds, wextn, extn, nextn, ecv, shift)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction values
+pointer ecv # Residual extinction curve
+int shift # Shift flag
+
+int i, j, n, err
+real exptime, airmass, ext
+pointer waves, fluxes, dwaves, counts, sens, iwts, wts
+
+begin
+ # Reset the flags, sensitivity, and weight values.
+ shift = NO
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ STD_FLAG(stds[i]) = SF_INCLUDE
+ STD_SHIFT(stds[i]) = 0.
+ n = STD_NWAVES(stds[i])
+ exptime = STD_EXPTIME(stds[i])
+ airmass = STD_AIRMASS(stds[i])
+ waves = STD_WAVES(stds[i])
+ fluxes = STD_FLUXES(stds[i])
+ dwaves = STD_DWAVES(stds[i])
+ counts = STD_COUNTS(stds[i])
+ sens = STD_SENS(stds[i])
+ iwts = STD_IWTS(stds[i])
+ wts = STD_WTS(stds[i])
+ do j = 1, n {
+ call intrp (1, wextn, extn, nextn, Memr[waves], ext, err)
+ Memr[sens] = Memr[counts] /
+ (Memr[fluxes] * Memr[dwaves] * exptime)
+ Memr[sens] = 2.5 * log10 (Memr[sens]) + airmass * ext
+ Memr[wts] = Memr[iwts]
+
+ waves = waves + 1
+ fluxes = fluxes + 1
+ dwaves = dwaves + 1
+ counts = counts + 1
+ sens = sens + 1
+ iwts = iwts + 1
+ wts = wts + 1
+ }
+ }
+
+ # Reset the added and composite stars.
+ STD_NWAVES(stds[nstds-1]) = 0
+ STD_FLAG(stds[nstds-1]) = SF_DELETE
+ STD_FLAG(stds[nstds]) = SF_EXCLUDE
+
+ # Clear the residual extinction curve.
+ call cvfree (ecv)
+end
diff --git a/noao/onedspec/sensfunc/sfrms.x b/noao/onedspec/sensfunc/sfrms.x
new file mode 100644
index 00000000..72b8ea98
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfrms.x
@@ -0,0 +1,43 @@
+include "sensfunc.h"
+
+
+# SF_RMS -- Compute the RMS of the sensitivity function fit.
+
+procedure sf_rms (stds, nstds, rms, npts)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+real rms # RMS about fit (returned)
+int npts # Number of points in fit (excluding zero wts.)
+
+int i, j, f, n
+pointer x, y, w
+
+begin
+ npts = 0
+ rms = 0.
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ x = STD_WAVES(stds[i])
+ y = STD_SENS(stds[i])
+ f = STD_FIT(stds[i])
+ w = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[w] != 0.) {
+ rms = rms + (Memr[y] - Memr[f]) ** 2
+ npts = npts + 1
+ }
+ x = x + 1
+ y = y + 1
+ f = f + 1
+ w = w + 1
+ }
+ }
+
+ if (npts > 1)
+ rms = sqrt (rms / (npts - 1))
+ else
+ rms = INDEF
+end
diff --git a/noao/onedspec/sensfunc/sfsensfunc.x b/noao/onedspec/sensfunc/sfsensfunc.x
new file mode 100644
index 00000000..ee2f1b2a
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfsensfunc.x
@@ -0,0 +1,255 @@
+include <error.h>
+include <gset.h>
+include <mach.h>
+include "sensfunc.h"
+
+define KEY "noao$onedspec/sensfunc/sensfunc.key"
+define PROMPT "sensfunc options"
+
+
+# SF_SENSFUNC -- Interactive sensitivity function determination.
+
+procedure sf_sensfunc (gp, stds, nstds, wextn, extn, nextn, sensimage, logfile,
+ ecv, function, order, ignoreaps, interactive)
+
+pointer gp # Graphics structure
+pointer stds[nstds] # Pointer to standard observations
+int nstds # Number of standards
+real wextn[nextn] # Extinction table wavelengths
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction table values
+char sensimage[ARB] # Output rootname
+char logfile[ARB] # Statistics filename
+pointer ecv # Residual extinction curve
+char function[ARB] # Fitting function type
+int order # Function order
+bool ignoreaps # Ignore apertures?
+int interactive # Interactive?
+
+char cmd[SZ_FNAME]
+int wc, key, newgraph, newfit
+real wx, wy
+
+int i, j, aperture, shift, npts, fd, open()
+real xmin, xmax, rms, delta
+pointer cv
+
+int clgcur(), scan(), nscan(), clgwrd()
+errchk open
+
+define output_ 99
+
+begin
+ # Initialize data and do the initial fit.
+ call sf_reset (stds, nstds, wextn, extn, nextn, ecv, shift)
+
+ xmin = MAX_REAL
+ xmax = -MAX_REAL
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ aperture = STD_BEAM(stds[i])
+ xmin = min (xmin, STD_WSTART(stds[i]), STD_WEND(stds[i]))
+ xmax = max (xmax, STD_WSTART(stds[i]), STD_WEND(stds[i]))
+ }
+ cv = NULL
+ call sf_fit (stds, nstds, cv, function, order, xmin, xmax)
+ call sf_rms (stds, nstds, rms, npts)
+
+ # If not interactive go to the output.
+ if (interactive == 3)
+ goto output_
+ if (interactive != 4) {
+ call printf ("Fit aperture %d interactively? ")
+ call pargi (aperture)
+ interactive = clgwrd ("answer", cmd, SZ_FNAME, "|no|yes|NO|YES")
+ switch (interactive) {
+ case 1:
+ goto output_
+ case 3:
+ call sf_gfree (gp)
+ goto output_
+ }
+ }
+
+ # Initialize graphics structure parameters: airmass and wavelength
+ # limits and default images to plot.
+
+ if (gp == NULL)
+ call sf_ginit (gp)
+ GP_AIRMASS(gp,1) = MAX_REAL
+ GP_AIRMASS(gp,2) = -MAX_REAL
+ j = 0
+ do i = 1, nstds - 2 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ GP_AIRMASS(gp,1) = min (GP_AIRMASS(gp,1), STD_AIRMASS(stds[i]))
+ GP_AIRMASS(gp,2) = max (GP_AIRMASS(gp,2), STD_AIRMASS(stds[i]))
+ if (j < SF_NGRAPHS) {
+ j = j + 1
+ call strcpy (STD_IMAGE(stds[i]), Memc[GP_IMAGES(gp,j)],
+ SZ_FNAME)
+ call strcpy (STD_SKY(stds[i]), Memc[GP_SKYS(gp,j)], SZ_FNAME)
+ }
+ }
+ delta = GP_AIRMASS(gp,2) - GP_AIRMASS(gp,1)
+ GP_AIRMASS(gp,1) = GP_AIRMASS(gp,1) - 0.05 * delta
+ GP_AIRMASS(gp,2) = GP_AIRMASS(gp,2) + 0.05 * delta
+ GP_WSTART(gp) = xmin
+ GP_WEND(gp) = xmax
+ call sf_title (gp, aperture, function, order, npts, rms)
+
+ # Enter cursor loop by drawing the graphs.
+ key = 'r'
+ repeat {
+ switch (key) {
+ case '?':
+ call gpagefile (GP_GIO(gp), KEY, PROMPT)
+ case ':':
+ call sf_colon (cmd, gp, stds, nstds, cv, wextn, extn, nextn,
+ ecv, function, order, npts, rms, newfit, newgraph)
+ case 'a':
+ call sf_add (gp, stds, nstds, cv, wx, wy, wc)
+ case 'c':
+ call sf_composite (stds, nstds, cv)
+ newfit = YES
+ newgraph = YES
+ case 'd':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wc))
+ call sf_nearest (gp, stds, nstds, wx, wy, wc, 0, i, j)
+ if (i > 0) {
+ call printf (
+ "%s - Delete p(oint), s(tar), or w(avelength):")
+ call pargstr (STD_IMAGE(stds[i]))
+ if (clgcur ("cursor", wx, wy, wc, key, cmd, SZ_FNAME)==EOF)
+ break
+ call printf ("\n")
+ call sf_delete (gp, stds, nstds, key, i, j)
+ }
+ case 'e':
+ call sf_extinct (gp, stds, nstds, cv, ecv, function, order)
+ newfit = YES
+ newgraph = YES
+ case 'f':
+ newfit = YES
+ case 'g':
+ newgraph = YES
+ newfit = YES
+ case 'i':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wc))
+ call sf_nearest (gp, stds, nstds, wx, wy, wc, 2, i, j)
+ if (i > 0) {
+ call printf (
+ "%s: airmass=%6.3f wavelen=%6.3f sens=%6.3f fit=%6.3f weight=%3f")
+ call pargstr (STD_IMAGE(stds[i]))
+ call pargr (STD_AIRMASS(stds[i]))
+ call pargr (Memr[STD_WAVES(stds[i])+j-1])
+ call pargr (Memr[STD_SENS(stds[i])+j-1])
+ call pargr (Memr[STD_FIT(stds[i])+j-1])
+ call pargr (Memr[STD_WTS(stds[i])+j-1])
+ }
+ case 'm':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wc))
+ call sf_nearest (gp, stds, nstds, wx, wy, wc, 2, i, j)
+ if (i > 0) {
+ call printf (
+ "%s - Move p(oint), s(tar), or w(avelength) to cursor:")
+ call pargstr (STD_IMAGE(stds[i]))
+ if (clgcur ("cursor", wx, wy, wc, key, cmd, SZ_FNAME)==EOF)
+ break
+ call printf ("\n")
+ delta = wy - Memr[STD_Y(stds[i])+j-1]
+ call sf_move (gp, stds, nstds, key, i, j, delta)
+ }
+ case 'o':
+ call sf_reset (stds, nstds, wextn, extn, nextn, ecv, shift)
+ newfit = YES
+ newgraph = YES
+ case 'q':
+ break
+ case 'I':
+ call fatal (0, "Interrupt")
+ case 'r':
+ newgraph = YES
+ case 's':
+ call sf_shift (stds, nstds, shift)
+ newfit=YES
+ newgraph=YES
+ case 'u':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wc))
+ call sf_nearest (gp, stds, nstds, wx, wy, wc, 1, i, j)
+ if (i > 0) {
+ call printf (
+ "%s - Undelete p(oint), s(tar), or w(avelength):")
+ call pargstr (STD_IMAGE(stds[i]))
+ if (clgcur ("cursor", wx, wy, wc, key, cmd, SZ_FNAME)==EOF)
+ break
+ call printf ("\n")
+ call sf_undelete (gp, stds, nstds, key, i, j)
+ }
+ case 'w':
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wc))
+ call sf_nearest (gp, stds, nstds, wx, wy, wc, 0, i, j)
+ if (i > 0) {
+ call printf (
+ "%s - Reweight p(oint), s(tar), or w(avelength):")
+ call pargstr (STD_IMAGE(stds[i]))
+ if (clgcur ("cursor", wx, wy, wc, key, cmd, SZ_FNAME)==EOF)
+ break
+ call printf ("New weight (%g):")
+ call pargr (Memr[STD_IWTS(stds[i])+j-1])
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargr (delta)
+ if (nscan() == 1)
+ call sf_weights (stds, nstds, key, i, j, delta)
+ }
+ call printf ("\n")
+ }
+ default:
+ call printf ("\007")
+ }
+
+ # Do a new fit and recompute the RMS, and title string.
+ if (newfit == YES) {
+ call sf_fit (stds, nstds, cv, function, order, xmin, xmax)
+ call sf_rms (stds, nstds, rms, npts)
+ call sf_title (gp, aperture, function, order, npts, rms)
+ do i = 1, SF_NGRAPHS
+ if (GP_SHDR(gp,i) != NULL)
+ call shdr_close (GP_SHDR(gp,i))
+ }
+
+ # Draw new graphs.
+ if (newgraph == YES) {
+ call sf_graph (gp, stds, nstds, cv, wextn, extn, nextn, ecv)
+ newgraph = NO
+ newfit = YES
+ }
+
+ # Overplot new fit.
+ if (newfit == YES) {
+ call sf_fitgraph (gp, cv)
+ newfit = NO
+ }
+ } until (clgcur ("cursor", wx, wy, wc, key, cmd, SZ_FNAME) == EOF)
+
+ # Close any open images.
+ do i = 1, SF_NGRAPHS
+ if (GP_SHDR(gp,i) != NULL)
+ call shdr_close (GP_SHDR(gp,i))
+
+output_
+ # Output the sensitivity function and logfile statistics.
+ call sf_output (stds, nstds, cv, sensimage, ignoreaps)
+ if (logfile[1] != EOS) {
+ iferr {
+ fd = open (logfile, APPEND, TEXT_FILE)
+ call sf_stats (fd, stds, nstds, function, order, npts, rms)
+ call sf_vstats (fd, stds, nstds, cv, wextn, extn, nextn, ecv)
+ call close (fd)
+ } then
+ call erract (EA_WARN)
+ }
+ call cvfree (cv)
+end
diff --git a/noao/onedspec/sensfunc/sfshift.x b/noao/onedspec/sensfunc/sfshift.x
new file mode 100644
index 00000000..07b204f3
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfshift.x
@@ -0,0 +1,81 @@
+include "sensfunc.h"
+
+
+# SF_SHIFT -- Shift or unshift all standard stars to have zero mean residual.
+
+procedure sf_shift (stds, nstds, flag)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+int flag # Shift flag
+
+pointer x, y, w, f
+int i, j, n, nshift
+real shift, shift1, minshift
+
+begin
+ # If flag is YES then unshift the data.
+ if (flag == YES) {
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ if (n == 0)
+ next
+ y = STD_SENS(stds[i])
+ call asubkr (Memr[y], STD_SHIFT(stds[i]), Memr[y], n)
+ STD_SHIFT(stds[i]) = 0.
+ }
+ flag = NO
+ call printf ("Data unshifted")
+ return
+ }
+
+ # Determine the shifts needed to make the mean residual zero.
+ # Also determine the minimum shift.
+
+ minshift = 0.
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ if (n == 0)
+ next
+ x = STD_WAVES(stds[i])
+ y = STD_SENS(stds[i])
+ w = STD_WTS(stds[i])
+ f = STD_FIT(stds[i])
+ nshift = 0
+ shift = 0.
+ shift1 = 0.
+ do j = 1, n {
+ shift1 = shift1 + Memr[f+j-1] - Memr[y+j-1]
+ if (Memr[w+j-1] > 0.) {
+ shift = shift + Memr[f+j-1] - Memr[y+j-1]
+ nshift = nshift + 1
+ }
+ }
+ if (nshift > 0) {
+ shift = STD_SHIFT(stds[i]) + shift / nshift
+ if (shift < minshift)
+ minshift = shift
+ } else
+ shift = STD_SHIFT(stds[i]) + shift1 / n
+ STD_SHIFT(stds[i]) = shift
+ }
+
+ # Adjust the shifts to be upwards.
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ if (n == 0)
+ next
+ y = STD_SENS(stds[i])
+ shift = STD_SHIFT(stds[i]) - minshift
+ call aaddkr (Memr[y], shift, Memr[y], n)
+ STD_SHIFT(stds[i]) = shift
+ }
+ flag = YES
+ call printf ("Data shifted")
+end
diff --git a/noao/onedspec/sensfunc/sfstats.x b/noao/onedspec/sensfunc/sfstats.x
new file mode 100644
index 00000000..a94691a4
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfstats.x
@@ -0,0 +1,152 @@
+include "sensfunc.h"
+
+
+# SF_STATS -- Print basic statistics about the stars and the fit.
+
+procedure sf_stats (fd, stds, nstds, function, order, npts, rms)
+
+int fd # Output file descriptor (may be STDOUT)
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+char function[ARB] # Fitted function
+int order # Order of function
+int npts # Number of points in fit
+real rms # RMS of fit
+
+int i, j, n
+real rms1, dev1, dev2, dev3
+pointer sp, str, wts
+
+begin
+ # Start with system ID.
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call sysid (Memc[str], SZ_LINE)
+
+ # Determine beam from first standard star not excluded.
+ for (i=1; (i<nstds) && (STD_FLAG(stds[i])==SF_EXCLUDE); i=i+1)
+ ;
+ call fprintf (fd, "%s\n")
+ call pargstr (Memc[str])
+ call fprintf (fd, "Sensitivity function for aperture %d:\n")
+ call pargi (STD_BEAM(stds[i]))
+ call fprintf (fd,
+ "Fitting function is %s of order %d with %d points and RMS of %6.4f.\n")
+ call pargstr (function)
+ call pargi (order)
+ call pargi (npts)
+ call pargr (rms)
+
+ call fprintf (fd, "%12s %7s %7s %7s %7s %7s %7s %7s\n")
+ call pargstr ("Image")
+ call pargstr ("Airmass")
+ call pargstr ("Points")
+ call pargstr ("Shift")
+ call pargstr ("RMS Fit")
+ call pargstr ("Dev 1")
+ call pargstr ("Dev 2")
+ call pargstr ("Dev 3")
+
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+
+ n = 0
+ wts = STD_WTS(stds[i]) - 1
+ for (j=1; j<=STD_NWAVES(stds[i]); j=j+1)
+ if (Memr[wts+j] != 0.)
+ n = n + 1
+ if ((i == nstds-1) && (n == 0))
+ next
+
+ call sf_devs (stds[i], rms1, dev1, dev2, dev3)
+
+ call fprintf (fd, "%12s %7.3f %7d %7.4f %7.4f %7.4f %7.4f %7.4f")
+ call pargstr (STD_IMAGE(stds[i]))
+ call pargr (STD_AIRMASS(stds[i]))
+ call pargi (n)
+ call pargr (STD_SHIFT(stds[i]))
+ call pargr (rms1)
+ call pargr (dev1)
+ call pargr (dev2)
+ call pargr (dev3)
+
+ if (n == 0) {
+ call fprintf (fd, "%s")
+ call pargstr (" <-- deleted")
+ }
+ call fprintf (fd, "\n")
+ }
+
+ # Trailing spacer
+ call fprintf (fd, "\n")
+end
+
+
+# SF_DEVS - Compute rms and mean deviations from the fit.
+# The deviations are computed in three segments.
+
+procedure sf_devs (std, rms, dev1, dev2, dev3)
+
+pointer std # Standard star data
+real rms # RMS about fit
+real dev1 # Average deviation in first third of data
+real dev2 # Average deviation in second third of data
+real dev3 # Average deviation in last third of data
+
+int i, ndev1, ndev2, ndev3, nrms, nbin, nwaves
+real dev
+pointer sens, fit, wts
+
+begin
+ # Get elements froms standard star structure.
+ nwaves = STD_NWAVES(std)
+ sens = STD_SENS(std)
+ fit = STD_FIT(std)
+ wts = STD_WTS(std)
+
+ # Divide into thirds.
+ rms = 0.
+ ndev1 = 0
+ dev1 = 0.
+ nbin = nwaves / 3
+ for (i=1; i<= nbin; i=i+1)
+ if (Memr[wts+i-1] != 0.) {
+ dev = Memr[sens+i-1] - Memr[fit+i-1]
+ dev1 = dev1 + dev
+ rms = rms + dev ** 2
+ ndev1 = ndev1 + 1
+ }
+ if (ndev1 > 0)
+ dev1 = dev1 / ndev1
+
+ ndev2 = 0
+ dev2 = 0.
+ nbin = 2 * nwaves / 3
+ for (; i<=nbin; i=i+1)
+ if (Memr[wts+i-1] != 0.) {
+ dev = Memr[sens+i-1] - Memr[fit+i-1]
+ dev2 = dev2 + dev
+ rms = rms + dev ** 2
+ ndev2 = ndev2 + 1
+ }
+ if (ndev2 > 0)
+ dev2 = dev2 / ndev2
+
+ ndev3 = 0
+ dev3 = 0.
+ nbin = nwaves
+ for (; i<=nbin; i=i+1)
+ if (Memr[wts+i-1] != 0.) {
+ dev = Memr[sens+i-1] - Memr[fit+i-1]
+ dev3 = dev3 + dev
+ rms = rms + dev ** 2
+ ndev3 = ndev3 + 1
+ }
+ if (ndev3 > 0)
+ dev3 = dev3 / ndev3
+
+ nrms = ndev1 + ndev2 + ndev3
+ if (nrms > 0)
+ rms = sqrt (rms / nrms)
+end
diff --git a/noao/onedspec/sensfunc/sfstds.x b/noao/onedspec/sensfunc/sfstds.x
new file mode 100644
index 00000000..07219729
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfstds.x
@@ -0,0 +1,266 @@
+include "sensfunc.h"
+
+
+# SF_STDS -- Get the standard observations for the specified apertures.
+# If ignoring aperture set all apertures to 1.
+# This routine knows the output of the task STANDARD.
+
+procedure sf_stds (standards, aps, ignoreaps, stds, nstds)
+
+char standards # Standard star data file
+pointer aps # Aperture list
+bool ignoreaps # Ignore apertures?
+pointer stds # Pointer to standard observations (returned)
+int nstds # Number of standard observations (returned)
+
+int i, j, fd, beam, npts, nwaves, nalloc
+real exptime, airmass, wstart, wend
+real wavelength, flux, dwave, count
+pointer sp, image, title, std
+pointer waves, fluxes, dwaves, counts, sens, fit, wts, iwts, x, y
+
+bool rng_elementi()
+int open(), fscan(), nscan(), stridxs()
+errchk open, malloc, realloc
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_STDIMAGE, TY_CHAR)
+ call salloc (title, SZ_STDTITLE, TY_CHAR)
+
+ # Open the standard observation data file.
+ fd = open (standards, READ_ONLY, TEXT_FILE)
+
+ # Read the standard observations and create a structure for each one.
+ # The beginning of a new star is found by a line whose first word
+ # begins with the character '['. Otherwise the line is interpreted
+ # as a data line. All unrecognized formats are skipped.
+
+ nwaves = 0
+ nstds = 0
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[image], SZ_STDIMAGE)
+ if (Memc[image] == '[') {
+ call gargi (beam)
+ call gargi (npts)
+ call gargr (exptime)
+ call gargr (airmass)
+ call gargr (wstart)
+ call gargr (wend)
+ call gargstr (Memc[title], SZ_STDTITLE)
+ if (nscan() < 7)
+ next
+ if (!rng_elementi (aps, beam))
+ next
+ if (IS_INDEF (exptime) || exptime <= 0.) {
+ call eprintf (
+ "%s: Warning - exposure time missing or zero, using 1 second\n")
+ call pargstr (Memc[image])
+ exptime = 1.
+ }
+
+ # For the first one create the pointer to the array of
+ # structures. For the following stars increase the size
+ # of the pointer array and finish up the previous standard
+ # star.
+
+ if (nstds == 0) {
+ nstds = nstds + 1
+ call calloc (stds, nstds, TY_INT)
+ call calloc (std, LEN_STD, TY_STRUCT)
+ Memi[stds+nstds-1] = std
+ } else {
+ if (nwaves > 0) {
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (fluxes, nwaves, TY_REAL)
+ call realloc (dwaves, nwaves, TY_REAL)
+ call realloc (counts, nwaves, TY_REAL)
+ call realloc (wts, nwaves, TY_REAL)
+ call malloc (sens, nwaves, TY_REAL)
+ call malloc (fit, nwaves, TY_REAL)
+ call malloc (iwts, nwaves, TY_REAL)
+ call malloc (x, nwaves, TY_REAL)
+ call malloc (y, nwaves, TY_REAL)
+ call amovr (Memr[wts], Memr[iwts], nwaves)
+ STD_NWAVES(std) = nwaves
+ STD_WAVES(std) = waves
+ STD_FLUXES(std) = fluxes
+ STD_DWAVES(std) = dwaves
+ STD_COUNTS(std) = counts
+ STD_SENS(std) = sens
+ STD_FIT(std) = fit
+ STD_WTS(std) = wts
+ STD_IWTS(std) = iwts
+ STD_X(std) = x
+ STD_Y(std) = y
+
+ nstds = nstds + 1
+ call realloc (stds, nstds, TY_INT)
+ call calloc (std, LEN_STD, TY_STRUCT)
+ Memi[stds+nstds-1] = std
+ }
+ }
+
+ # Start a new standard star.
+ std = Memi[stds+nstds-1]
+ if (ignoreaps)
+ STD_BEAM(std) = 1
+ else
+ STD_BEAM(std) = beam
+ STD_NPTS(std) = npts
+ STD_EXPTIME(std) = exptime
+ STD_AIRMASS(std) = airmass
+ STD_WSTART(std) = wstart
+ STD_WEND(std) = wend
+ STD_SHIFT(std) = 0.
+ STD_NWAVES(std) = 0
+
+ # Decode the image and sky strings.
+ call strcpy (Memc[title], STD_TITLE(std), SZ_STDTITLE)
+ i = stridxs ("]", Memc[image])
+ if (Memc[image+i] == ']')
+ i = i + 1
+ Memc[image+i-1] = EOS
+ call strcpy (Memc[image+1], STD_IMAGE(std), SZ_STDIMAGE)
+ if (Memc[image+i] == '-') {
+ i = i + 2
+ j = stridxs ("]", Memc[image+i]) + i
+ Memc[image+j-1] = EOS
+ call strcpy (Memc[image+i], STD_SKY(std), SZ_STDIMAGE)
+ } else
+ STD_SKY(std) = EOS
+ nwaves = 0
+
+ # Interprete the line as standard star wavelength point.
+ } else if (nstds > 0) {
+ call reset_scan()
+ call gargr (wavelength)
+ call gargr (flux)
+ call gargr (dwave)
+ call gargr (count)
+ if (nscan() < 3)
+ next
+ if (wavelength < min (wstart, wend) ||
+ wavelength > max (wstart, wend) ||
+ flux<=0. || dwave<=0. || count<=0.)
+ next
+ if (!rng_elementi (aps, beam))
+ next
+ nwaves = nwaves + 1
+
+ # Allocate in blocks to minimize the number of reallocations.
+ if (nwaves == 1) {
+ nalloc = 100
+ call malloc (waves, nalloc, TY_REAL)
+ call malloc (fluxes, nalloc, TY_REAL)
+ call malloc (dwaves, nalloc, TY_REAL)
+ call malloc (counts, nalloc, TY_REAL)
+ call malloc (wts, nalloc, TY_REAL)
+ } else if (nwaves > nalloc) {
+ nalloc = nalloc + 100
+ call realloc (waves, nalloc, TY_REAL)
+ call realloc (fluxes, nalloc, TY_REAL)
+ call realloc (dwaves, nalloc, TY_REAL)
+ call realloc (counts, nalloc, TY_REAL)
+ call realloc (wts, nalloc, TY_REAL)
+ }
+
+ # Record the data and compute the sensitivity.
+ Memr[waves+nwaves-1] = wavelength
+ Memr[fluxes+nwaves-1] = flux
+ Memr[dwaves+nwaves-1] = dwave
+ Memr[counts+nwaves-1] = count
+ Memr[wts+nwaves-1] = 1.
+ }
+ }
+
+ # Finish up the last standard star and close the file.
+ if (nstds > 0) {
+ STD_NWAVES(std) = nwaves
+ if (nwaves > 0) {
+ call realloc (waves, nwaves, TY_REAL)
+ call realloc (fluxes, nwaves, TY_REAL)
+ call realloc (dwaves, nwaves, TY_REAL)
+ call realloc (counts, nwaves, TY_REAL)
+ call realloc (wts, nwaves, TY_REAL)
+ call malloc (sens, nwaves, TY_REAL)
+ call malloc (fit, nwaves, TY_REAL)
+ call malloc (iwts, nwaves, TY_REAL)
+ call malloc (x, nwaves, TY_REAL)
+ call malloc (y, nwaves, TY_REAL)
+ call amovr (Memr[wts], Memr[iwts], nwaves)
+ STD_WAVES(std) = waves
+ STD_FLUXES(std) = fluxes
+ STD_DWAVES(std) = dwaves
+ STD_COUNTS(std) = counts
+ STD_SENS(std) = sens
+ STD_FIT(std) = fit
+ STD_WTS(std) = wts
+ STD_IWTS(std) = iwts
+ STD_X(std) = x
+ STD_Y(std) = y
+ }
+ }
+ call close (fd)
+ call sfree (sp)
+
+ # Add standard stars for any added points and composite points.
+ nstds = nstds + 2
+ call realloc (stds, nstds, TY_INT)
+ call calloc (std, LEN_STD, TY_STRUCT)
+ Memi[stds+nstds-2] = std
+ call strcpy ("Added", STD_IMAGE(std), SZ_STDIMAGE)
+ STD_BEAM(std) = STD_BEAM(Memi[stds])
+ STD_NPTS(std) = STD_NPTS(Memi[stds])
+ STD_EXPTIME(std) = 1.
+ STD_AIRMASS(std) = 1.
+ STD_WSTART(std) = STD_WSTART(Memi[stds])
+ STD_WEND(std) = STD_WEND(Memi[stds])
+ STD_SHIFT(std) = 0.
+ STD_NWAVES(std) = 0
+ call calloc (std, LEN_STD, TY_STRUCT)
+ Memi[stds+nstds-1] = std
+ call strcpy ("Composite", STD_IMAGE(std), SZ_STDIMAGE)
+ STD_BEAM(std) = STD_BEAM(Memi[stds])
+ STD_NPTS(std) = STD_NPTS(Memi[stds])
+ STD_EXPTIME(std) = 1.
+ STD_AIRMASS(std) = 1.
+ STD_WSTART(std) = STD_WSTART(Memi[stds])
+ STD_WEND(std) = STD_WEND(Memi[stds])
+ STD_SHIFT(std) = 0.
+ STD_NWAVES(std) = 0
+end
+
+
+# SF_FREE -- Free the standard observations and aperture array.
+
+procedure sf_free (stds, nstds, apertures, napertures)
+
+pointer stds # Pointer to standard observations
+int nstds # Number of standard observations
+pointer apertures # Pointer to apertures array
+int napertures # Number of apertures
+
+int i
+pointer std
+
+begin
+ do i = 1, nstds {
+ std = Memi[stds+i-1]
+ if (STD_NWAVES(std) > 0) {
+ call mfree (STD_WAVES(std), TY_REAL)
+ call mfree (STD_FLUXES(std), TY_REAL)
+ call mfree (STD_DWAVES(std), TY_REAL)
+ call mfree (STD_COUNTS(std), TY_REAL)
+ call mfree (STD_SENS(std), TY_REAL)
+ call mfree (STD_FIT(std), TY_REAL)
+ call mfree (STD_WTS(std), TY_REAL)
+ call mfree (STD_IWTS(std), TY_REAL)
+ call mfree (STD_X(std), TY_REAL)
+ call mfree (STD_Y(std), TY_REAL)
+ }
+ call mfree (std, TY_STRUCT)
+ }
+ call mfree (stds, TY_INT)
+ call mfree (apertures, TY_INT)
+end
diff --git a/noao/onedspec/sensfunc/sftitle.x b/noao/onedspec/sensfunc/sftitle.x
new file mode 100644
index 00000000..50cece9a
--- /dev/null
+++ b/noao/onedspec/sensfunc/sftitle.x
@@ -0,0 +1,23 @@
+include "sensfunc.h"
+
+
+# SF_TITLE -- Make title string for graphs.
+
+procedure sf_title (gp, aperture, function, order, npts, rms)
+
+pointer gp
+int aperture
+char function[ARB]
+int order
+int npts
+real rms
+
+begin
+ call sprintf (GP_TITLE(gp), GP_SZTITLE,
+ "Aperture=%d Function=%s Order=%d Points=%d RMS=%6.4f")
+ call pargi (aperture)
+ call pargstr (function)
+ call pargi (order)
+ call pargi (npts)
+ call pargr (rms)
+end
diff --git a/noao/onedspec/sensfunc/sfundelete.x b/noao/onedspec/sensfunc/sfundelete.x
new file mode 100644
index 00000000..25161cc3
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfundelete.x
@@ -0,0 +1,141 @@
+include <gset.h>
+include "sensfunc.h"
+
+
+# SF_UNDELETE -- Unelete point, star, or wavelength.
+
+procedure sf_undelete (gp, stds, nstds, key, istd, ipt)
+
+pointer gp # GIO pointer
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+int key # Delete point, star, or wavelength
+int istd # Index of standard star
+int ipt # Index of point
+
+int i, j, n, wcs, mark, mdel, color, stridx()
+real wave, szmark, szmdel
+pointer x, y, z, w, w1, gio
+
+begin
+ gio = GP_GIO(gp)
+ mdel = GP_MDEL(gp)
+ szmdel = GP_SZMDEL(gp)
+ szmark = GP_SZMARK(gp)
+
+ # Undelete points from each displayed graph.
+ for (wcs = 1; GP_GRAPHS(gp,wcs) != EOS; wcs = wcs + 1) {
+ if (stridx (GP_GRAPHS(gp,wcs), "ars") == 0)
+ next
+
+ call gseti (gio, G_WCS, wcs)
+ call gseti (gio, G_PMLTYPE, 0)
+ call sf_data (stds, nstds, GP_GRAPHS(gp,wcs))
+ switch (key) {
+ case 'p':
+ if (istd != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ x = STD_X(stds[istd])+ipt-1
+ y = STD_Y(stds[istd])+ipt-1
+ w = STD_WTS(stds[istd])+ipt-1
+ w1 = STD_IWTS(stds[istd])+ipt-1
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark , szmark)
+ case 's':
+ if (istd != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ n = STD_NWAVES(stds[istd])
+ x = STD_X(stds[istd])
+ y = STD_Y(stds[istd])
+ w = STD_WTS(stds[istd])
+ do j = 1, n {
+ if (Memr[w] == 0.) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel, szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark, szmark)
+ }
+ x = x + 1
+ y = y + 1
+ w = w + 1
+ }
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ if (i != nstds-1) {
+ mark = GP_MARK(gp)
+ color = GP_CMARK(gp)
+ } else {
+ mark = GP_MADD(gp)
+ color = GP_CADD(gp)
+ }
+ n = STD_NWAVES(stds[i])
+ x = STD_X(stds[i])
+ y = STD_Y(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ do j = 1, n {
+ if ((Memr[z] == wave) && (Memr[w] == 0.)) {
+ call gseti (gio, G_PMLTYPE, 0)
+ call gmark (gio, Memr[x], Memr[y], mdel, szmdel,
+ szmdel)
+ call gseti (gio, G_PMLTYPE, 1)
+ call gseti (gio, G_PLCOLOR, color)
+ call gmark (gio, Memr[x], Memr[y], mark, szmark,
+ szmark)
+ }
+ x = x + 1
+ y = y + 1
+ z = z + 1
+ w = w + 1
+ }
+ }
+ }
+ }
+
+ # Now actually undelete the points by resetting the weights.
+ switch (key) {
+ case 'p':
+ w = STD_WTS(stds[istd])+ipt-1
+ w1 = STD_IWTS(stds[istd])+ipt-1
+ Memr[w] = Memr[w1]
+ case 's':
+ n = STD_NWAVES(stds[istd])
+ w = STD_WTS(stds[istd])
+ w1 = STD_IWTS(stds[istd])
+ call amovr (Memr[w1], Memr[w], n)
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ w1 = STD_IWTS(stds[i])
+ do j = 1, n {
+ if (Memr[z] == wave)
+ Memr[w] = Memr[w1]
+ z = z + 1
+ w = w + 1
+ w1 = w1 + 1
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/sfvstats.x b/noao/onedspec/sensfunc/sfvstats.x
new file mode 100644
index 00000000..add49da7
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfvstats.x
@@ -0,0 +1,104 @@
+include "sensfunc.h"
+
+# SF_VSTATS -- Verbose statistics output.
+
+procedure sf_vstats (fd, stds, nstds, cv, wextn, extn, nextn, ecv)
+
+int fd # Output file descriptor (may be STDOUT)
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+pointer cv # Sensitivity function curve
+real wextn[nextn] # Extinction table wavelength
+real extn[nextn] # Extinction table values
+int nextn # Number of extinction table values
+pointer ecv # Residual extinction curve
+
+int i, j, n, nwaves
+real w, fit, ext, dext, cveval()
+double sum, sum2, s
+pointer sp, waves, sens, xp, yp, zp
+
+begin
+ nwaves = 0
+ do i = 1, nstds-1
+ if (STD_FLAG(stds[i]) != SF_EXCLUDE)
+ nwaves = nwaves + STD_NWAVES(stds[i])
+
+ call smark (sp)
+ call salloc (waves, nwaves, TY_REAL)
+ call salloc (sens, nwaves, TY_REAL)
+
+ nwaves = 0
+ do i = 1, nstds-1 {
+ if (STD_FLAG(stds[i]) == SF_EXCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ xp = STD_WAVES(stds[i])
+ yp = STD_SENS(stds[i])
+ zp = STD_WTS(stds[i])
+ do j = 1, n {
+ if (Memr[zp] != 0.) {
+ Memr[waves+nwaves] = Memr[xp]
+ Memr[sens+nwaves] = Memr[yp]
+ nwaves = nwaves + 1
+ }
+ xp = xp + 1
+ yp = yp + 1
+ zp = zp + 1
+ }
+ }
+ call xt_sort2 (Memr[waves], Memr[sens], nwaves)
+
+ call fprintf (fd, "%8s %7s %7s %7s %7s %5s %7s %7s\n")
+ call pargstr ("Lambda")
+ call pargstr ("Fit")
+ call pargstr ("Avg")
+ call pargstr ("Resid")
+ call pargstr ("SD Avg")
+ call pargstr ("N")
+ call pargstr ("Ext")
+ call pargstr ("Dext")
+
+ dext = 0.
+ n = 0
+ sum = 0.
+ sum2 = 0.
+ do i = 0, nwaves-1 {
+ w = Memr[waves+i]
+ s = Memr[sens+i]
+ n = n + 1
+ sum = sum + s
+ sum2 = sum2 + s * s
+
+ if ((i < nwaves-1) && (w == Memr[waves+i+1]))
+ next
+
+ sum = sum / n
+ sum2 = sum2 / n - sum * sum
+ if (sum2 > 0)
+ sum2 = sqrt (sum2 / n)
+ else
+ sum2 = 0.
+ fit = cveval (cv, w)
+ call intrp (1, wextn, extn, nextn, w, ext, j)
+ if (ecv != NULL)
+ dext = cveval (ecv, w)
+ call fprintf (fd, "%8.2f %7.3f %7.3f %7.4f %7.4f %5d %7.4f %7.4f\n")
+ call pargr (w)
+ call pargr (fit)
+ call pargd (sum)
+ call pargd (sum - fit)
+ call pargd (sum2)
+ call pargi (n)
+ call pargr (ext)
+ call pargr (dext)
+ n = 0
+ sum = 0.
+ sum2 = 0.
+ }
+
+ # Trailing spacer
+ call fprintf (fd, "\n")
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/sensfunc/sfweights.x b/noao/onedspec/sensfunc/sfweights.x
new file mode 100644
index 00000000..2ce24b1a
--- /dev/null
+++ b/noao/onedspec/sensfunc/sfweights.x
@@ -0,0 +1,51 @@
+include "sensfunc.h"
+
+
+# SF_WEIGHTS -- Change weights for point, star, or wavelength.
+# The original input weight is permanently lost.
+
+procedure sf_weights (stds, nstds, key, istd, ipt, weight)
+
+pointer stds[nstds] # Standard star data
+int nstds # Number of standard stars
+int key # Delete point, star, or wavelength
+int istd # Index of standard star
+int ipt # Index of point
+real weight # New weight
+
+int i, j, n
+real wave
+pointer z, w, iw
+
+begin
+ switch (key) {
+ case 'p':
+ Memr[STD_WTS(stds[istd])+ipt-1] = weight
+ Memr[STD_IWTS(stds[istd])+ipt-1] = weight
+ case 's':
+ n = STD_NWAVES(stds[istd])
+ w = STD_WTS(stds[istd])
+ iw = STD_IWTS(stds[istd])
+ call amovkr (weight, Memr[w], n)
+ call amovkr (weight, Memr[iw], n)
+ case 'w':
+ wave = Memr[STD_WAVES(stds[istd])+ipt-1]
+ do i = 1, nstds {
+ if (STD_FLAG(stds[i]) != SF_INCLUDE)
+ next
+ n = STD_NWAVES(stds[i])
+ z = STD_WAVES(stds[i])
+ w = STD_WTS(stds[i])
+ iw = STD_IWTS(stds[i])
+ do j = 1, n {
+ if (Memr[z] == wave) {
+ Memr[w] = weight
+ Memr[iw] = weight
+ }
+ w = w + 1
+ iw = iw + 1
+ z = z + 1
+ }
+ }
+ }
+end
diff --git a/noao/onedspec/sensfunc/t_sensfunc.x b/noao/onedspec/sensfunc/t_sensfunc.x
new file mode 100644
index 00000000..82f18678
--- /dev/null
+++ b/noao/onedspec/sensfunc/t_sensfunc.x
@@ -0,0 +1,99 @@
+include "sensfunc.h"
+
+
+# T_SENSFUNC -- Determine sensitivities and residual extinctions.
+# The input is a file of standard star produced by the task STANDARD.
+# The input data is read into an array of structures, one per standard
+# star. The stars common to the aperture to be fit are flagged
+# and then the data is passed to the main routine SF_SENSFUNC.
+# This routine determines a sensitivity curve for the aperture which
+# is output by the procedure as well as some optional statistical
+# information. It returns an optional residual extinction curve
+# for each aperture. The residual extinctions curves are finally combined
+# and output as a revised extinction table.
+
+procedure t_sensfunc ()
+
+pointer standards # Input standard star data filename
+pointer sensitivity # Output root sensitivity function imagename
+pointer aps # Aperture list
+bool ignoreaps # Ignore apertures?
+pointer logfile # Output log for statistics
+pointer function # Sensitivity function type
+int order # Order of sensitivity function
+int interactive # Interactive?
+
+int i, j, aperture, nstds, napertures, nextn, clgeti()
+pointer sp, str, stds, apertures, wextn, extn, ecvs, gp
+bool clgetb()
+pointer rng_open()
+errchk sf_sensfunc
+
+begin
+ call smark (sp)
+ call salloc (standards, SZ_FNAME, TY_CHAR)
+ call salloc (sensitivity, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (logfile, SZ_FNAME, TY_CHAR)
+ call salloc (function, SZ_FNAME, TY_CHAR)
+
+ # CL parameter input.
+ call clgstr ("standards", Memc[standards], SZ_FNAME)
+ call clgstr ("sensitivity", Memc[sensitivity], SZ_FNAME)
+ call clgstr ("apertures", Memc[str], SZ_LINE)
+ ignoreaps = clgetb ("ignoreaps")
+ call clgstr ("logfile", Memc[logfile], SZ_FNAME)
+ call clgstr ("function", Memc[function], SZ_FNAME)
+ order = clgeti ("order")
+ if (clgetb ("interactive"))
+ interactive = 2
+ else
+ interactive = 3
+
+ # Decode aperture list.
+ iferr (aps = rng_open (Memc[str], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+
+ # Get the standard star data, the aperture array, and the
+ # extinction table, and allocate and initialize an array of
+ # residual extinction curves for each aperture.
+
+ call sf_stds (Memc[standards], aps, ignoreaps, stds, nstds)
+ if (nstds == 0) {
+ call sfree (sp)
+ return
+ }
+ call sf_apertures (Memi[stds], nstds, apertures, napertures)
+ call ext_load (wextn, extn, nextn)
+ call salloc (ecvs, napertures, TY_INT)
+ call amovki (NULL, Memi[ecvs], napertures)
+
+ # For each aperture flag standard stars to be used and call sf_sensfunc.
+ gp = NULL
+ do j = 1, napertures {
+ aperture = Memi[apertures+j-1]
+ do i = 1, nstds - 2
+ if (STD_BEAM(Memi[stds+i-1]) == aperture)
+ STD_FLAG(Memi[stds+i-1]) = SF_INCLUDE
+ else
+ STD_FLAG(Memi[stds+i-1]) = SF_EXCLUDE
+ call sf_sensfunc (gp, Memi[stds], nstds, Memr[wextn], Memr[extn],
+ nextn, Memc[sensitivity], Memc[logfile], Memi[ecvs+j-1],
+ Memc[function], order, ignoreaps, interactive)
+ }
+ call sf_gfree (gp)
+
+ # Output a revised extinction table by combining the residual
+ # extinction curves for the apertures. The table name is obtained
+ # by this proceudre.
+
+ call sf_eout (Memr[wextn], Memr[extn], nextn, Memi[ecvs], napertures)
+
+ # Finish up.
+ call sf_free (stds, nstds, apertures, napertures)
+ call ext_free (wextn, extn)
+ do j = 1, napertures
+ call cvfree (Memi[ecvs+j-1])
+ call rng_close (aps)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/setdisp.par b/noao/onedspec/setdisp.par
new file mode 100644
index 00000000..80777884
--- /dev/null
+++ b/noao/onedspec/setdisp.par
@@ -0,0 +1,6 @@
+# Parameter file for SETDISP
+
+images,s,a,,,,List of images to be set
+dispaxis,i,h,1,1,7,Dispersion axis
+disptype,s,h,"lambda",,,Dispersion type
+dispunit,s,h,"angstroms",,,Dispersion units
diff --git a/noao/onedspec/sfit.par b/noao/onedspec/sfit.par
new file mode 100644
index 00000000..e168a079
--- /dev/null
+++ b/noao/onedspec/sfit.par
@@ -0,0 +1,25 @@
+input,s,a,,,,Input images
+output,s,a,,,,Output images
+lines,s,h,"*",,,Image lines to be fit
+bands,s,h,"1",,,Image bands to be fit
+type,s,h,"fit","data|fit|difference|ratio",,Type of output
+replace,b,h,no,,,Replace rejected points by fit?
+wavescale,b,h,yes,,,Scale the X axis with wavelength?
+logscale,b,h,no,,,Take the log (base 10) of both axes?
+override,b,h,no,,,Override previously fit lines?
+listonly,b,h,no,,,List fit but don't modify any images?
+logfiles,s,h,"logfile",,,List of log files
+interactive,b,h,yes,,,Set fitting parameters interactively?
+sample,s,h,"*",,,Sample points to use in fit
+naverage,i,h,1,,,Number of points in sample averaging
+function,s,h,"spline3","spline3|legendre|chebyshev|spline1",,Fitting function
+order,i,h,1,1,,Order of fitting function
+low_reject,r,h,3.,0.,,Low rejection in sigma of fit
+high_reject,r,h,3.,0.,,High rejection in sigma of fit
+niterate,i,h,0,0,,Number of rejection iterations
+grow,r,h,1.,0.,,Rejection growing radius
+markrej,b,h,yes,,,Mark rejected points?
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Graphics cursor input
+ask,s,q,"yes","yes|no|skip|YES|NO|SKIP",," "
+mode,s,h,"ql"
diff --git a/noao/onedspec/sflip.par b/noao/onedspec/sflip.par
new file mode 100644
index 00000000..2e9dc8c2
--- /dev/null
+++ b/noao/onedspec/sflip.par
@@ -0,0 +1,6 @@
+# SFLIP parameter file
+
+input,s,a,,,,Input spectra to flip
+output,s,a,,,,Output flipped spectra
+coord_flip,b,h,no,,,Flip coordinate system?
+data_flip,b,h,yes,,,Flip data?
diff --git a/noao/onedspec/sinterp.par b/noao/onedspec/sinterp.par
new file mode 100644
index 00000000..e9935b92
--- /dev/null
+++ b/noao/onedspec/sinterp.par
@@ -0,0 +1,14 @@
+# SINTERP parameter file
+
+tbl_file,f,a,,,,File containing table of x-y pairs
+input,f,a,STDIN,,,input for x-interpolant values
+image,s,a,,,,Image name to create
+order,i,h,5,1,,Order of fit
+x1,r,a,0.0,,,First point in range of generated curve
+x2,r,h,0.0,,,Last point in range of generated curve
+dx,r,h,0.0,,,Interval between generated points
+npts,i,h,0,,,Number of points to generate
+curve_gen,b,h,no,,,Generate a curve between specified limits
+make_image,b,h,no,,,Create IRAF spectral image
+tbl_size,i,h,1024,200,,Maximum space to allocate for table
+interp_mode,s,h,"chebyshev",,,By (line|curve|legen|cheby|spline3|spline1)
diff --git a/noao/onedspec/skytweak.par b/noao/onedspec/skytweak.par
new file mode 100644
index 00000000..9ffd0f0a
--- /dev/null
+++ b/noao/onedspec/skytweak.par
@@ -0,0 +1,19 @@
+# SKYTWEAK
+
+input,s,a,,,,List of input spectra to correct
+output,s,a,,,,List of output corrected spectra
+cal,s,a,,,,List of sky calibration spectra
+ignoreaps,b,h,no,,,Ignore aperture numbers in calibration spectra?
+xcorr,b,h,yes,,,Cross correlate for shift?
+tweakrms,b,h,yes,,,Tweak to minimize RMS?
+interactive,b,h,yes,,,Interactive tweaking?
+sample,s,h,"*",,,Sample ranges
+lag,i,h,10,0,,Cross correlation lag (pixels)
+shift,r,h,0.,,,Initial shift of calibration spectrum (pixels)
+scale,r,h,1.,1e-10,,Initial scale factor
+dshift,r,h,0.1,0.,,Initial shift search step
+dscale,r,h,0.1,0.,0.99,Initial scale factor search step
+offset,r,h,1.,0.,,Initial offset for graphs
+smooth,i,h,1,1,,Smoothing box for graphs
+cursor,*gcur,h,"",,,Cursor input
+answer,s,q,"yes","no|yes|NO|YES",,Search interactively?
diff --git a/noao/onedspec/slist.par b/noao/onedspec/slist.par
new file mode 100644
index 00000000..224d79f0
--- /dev/null
+++ b/noao/onedspec/slist.par
@@ -0,0 +1,3 @@
+images,s,a,,,,List of images
+apertures,s,h,"",,,Apertures to list
+long_header,b,h,no,,,List in long format?
diff --git a/noao/onedspec/smw/README b/noao/onedspec/smw/README
new file mode 100644
index 00000000..2f2c27b0
--- /dev/null
+++ b/noao/onedspec/smw/README
@@ -0,0 +1,6 @@
+This directory contains interface routines for the spectral world
+coordinate systems. The interface has two functions. The first is to
+convert various input formats, including old formats, to one of the WCS
+formats used by the ONEDSPEC package. These are MULTISPEC, EQUISPEC, and
+NDSPEC. The second is to split large numbers of spectra which exceed the
+limits of the MWCS for a single WCS into a number of smaller WCS.
diff --git a/noao/onedspec/smw/funits.x b/noao/onedspec/smw/funits.x
new file mode 100644
index 00000000..e3c8ddf5
--- /dev/null
+++ b/noao/onedspec/smw/funits.x
@@ -0,0 +1,445 @@
+include <ctype.h>
+include <error.h>
+include <funits.h>
+
+
+# FUN_OPEN -- Open funits package
+# It is allowed to open an unknown funit type
+
+pointer procedure fun_open (funits)
+
+char funits[ARB] # Units string
+pointer fun # Units pointer returned
+
+begin
+ call calloc (fun, FUN_LEN, TY_STRUCT)
+ iferr (call fun_decode (fun, funits)) {
+ call fun_close (fun)
+ call erract (EA_ERROR)
+ }
+ return (fun)
+end
+
+
+# FUN_CLOSE -- Close funits package
+
+procedure fun_close (fun)
+
+pointer fun # Units pointer
+
+begin
+ call mfree (fun, TY_STRUCT)
+end
+
+
+# FUN_COPY -- Copy funits pointer
+
+procedure fun_copy (fun1, fun2)
+
+pointer fun1, fun2 # Units pointers
+
+begin
+ if (fun2 == NULL)
+ call malloc (fun2, FUN_LEN, TY_STRUCT)
+ call amovi (Memi[fun1], Memi[fun2], FUN_LEN)
+end
+
+
+# FUN_DECODE -- Decode funits string and set up funits structure.
+# The main work is done in FUN_DECODE1 so that the funits string may
+# be recursive; i.e. the funits string may contain other funits strings.
+
+procedure fun_decode (fun, funits)
+
+pointer fun # Units pointer
+char funits[ARB] # Units string
+
+bool streq()
+pointer sp, funits1, temp
+errchk fun_decode1, fun_ctranr
+
+begin
+ if (streq (funits, FUN_USER(fun)))
+ return
+
+ call smark (sp)
+ call salloc (funits1, SZ_LINE, TY_CHAR)
+ call salloc (temp, FUN_LEN, TY_STRUCT)
+
+ # Save a copy to restore in case of an error.
+ call fun_copy (fun, temp)
+
+ iferr (call fun_decode1 (fun, funits, Memc[funits1], SZ_LINE)) {
+ call fun_copy (temp, fun)
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+
+ call sfree (sp)
+end
+
+
+# FUN_DECODE1 -- Decode funits string and set up funits structure.
+# Unknown funit strings are allowed.
+
+procedure fun_decode1 (fun, funits, funits1, sz_funits1)
+
+pointer fun # Units pointer
+char funits[ARB] # Units string
+char funits1[sz_funits1] # Secondary funits string to return
+int sz_funits1 # Size of secondary funits string
+
+int funmod, funtype
+int i, j, k, nscan(), strdic(), strlen()
+real funscale
+pointer sp, str
+
+int class[FUN_NUNITS]
+real scale[FUN_NUNITS]
+data class /FUN_FREQ,FUN_FREQ,FUN_FREQ,FUN_WAVE/
+data scale /FUN_J,FUN_FU,FUN_CGSH,FUN_CGSA/
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ call strcpy (funits, Memc[str], SZ_FNAME)
+ call strlwr (Memc[str])
+ call sscan (Memc[str])
+ funtype = 0
+ funmod = 0
+ do i = 1, 2 {
+ call gargwrd (Memc[str], SZ_FNAME)
+ if (nscan() != i)
+ break
+
+ j = strdic (Memc[str], Memc[str], SZ_FNAME, FUN_DIC)
+ for (k=strlen(Memc[str]); k>0 &&
+ (IS_WHITE(Memc[str+k-1]) || Memc[str+k-1]=='\n'); k=k-1)
+ Memc[str+k-1] = EOS
+
+ if (j > FUN_NUNITS) {
+ if (funmod != 0)
+ break
+ funmod = j - FUN_NUNITS
+ } else {
+ funtype = j
+ break
+ }
+ }
+ i = nscan()
+ call gargr (funscale)
+ if (nscan() != i+1)
+ funscale = 1
+
+ if (funtype == 0) {
+ FUN_TYPE(fun) = 0
+ FUN_CLASS(fun) = FUN_UNKNOWN
+ FUN_LABEL(fun) = EOS
+ call strcpy (funits, FUN_UNITS(fun), SZ_UNITS)
+ } else {
+ FUN_TYPE(fun) = funtype
+ FUN_CLASS(fun) = class[funtype]
+ FUN_MOD(fun) = funmod
+ FUN_SCALE(fun) = scale[funtype] * funscale
+ FUN_LABEL(fun) = EOS
+ FUN_UNITS(fun) = EOS
+ call strcpy (funits, FUN_USER(fun), SZ_UNITS)
+ switch (funmod) {
+ case FUN_LOG:
+ call strcat ("Log ", FUN_LABEL(fun), SZ_UNITS)
+ case FUN_MAG:
+ call strcat ("Mag ", FUN_LABEL(fun), SZ_UNITS)
+ }
+ call strcat ("Flux", FUN_LABEL(fun), SZ_UNITS)
+ if (funscale != 1) {
+ call sprintf (FUN_UNITS(fun), SZ_UNITS, "%sx%.1g")
+ call pargstr (Memc[str])
+ call pargr (funscale)
+ } else {
+ call sprintf (FUN_UNITS(fun), SZ_UNITS, "%s")
+ call pargstr (Memc[str])
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# FUN_COMPARE -- Compare two funits
+
+bool procedure fun_compare (fun1, fun2)
+
+pointer fun1, fun2 # Units pointers to compare
+bool strne()
+
+begin
+ if (strne (FUN_UNITS(fun1), FUN_UNITS(fun2)))
+ return (false)
+ if (strne (FUN_LABEL(fun1), FUN_LABEL(fun2)))
+ return (false)
+ return (true)
+end
+
+
+# FUN_CTRANR -- Transform funits
+# Error is returned if the transform cannot be made
+
+procedure fun_ctranr (fun1, fun2, dun, dval, fval1, fval2, nvals)
+
+pointer fun1 # Input funits pointer
+pointer fun2 # Output funits pointer
+pointer dun # Input units pointer
+real dval[nvals] # Input dispersion values
+real fval1[nvals] # Input flux values
+real fval2[nvals] # Output flux values
+int nvals # Number of values
+
+int i
+real s, lambda
+pointer ang, un_open()
+bool fun_compare()
+errchk un_open, un_ctranr
+
+begin
+ if (fun_compare (fun1, fun2)) {
+ call amovr (fval1, fval2, nvals)
+ return
+ }
+
+ if (FUN_CLASS(fun1) == FUN_UNKNOWN || FUN_CLASS(fun2) == FUN_UNKNOWN)
+ call error (1, "Cannot convert between selected funits")
+
+ call amovr (fval1, fval2, nvals)
+
+ s = FUN_SCALE(fun1)
+ switch (FUN_MOD(fun1)) {
+ case FUN_LOG:
+ do i = 1, nvals
+ fval2[i] = 10. ** fval2[i]
+ case FUN_MAG:
+ do i = 1, nvals
+ fval2[i] = 10. ** (-0.4 * fval2[i])
+ }
+ switch (FUN_CLASS(fun1)) {
+ case FUN_FREQ:
+ do i = 1, nvals
+ fval2[i] = fval2[i] / s
+ case FUN_WAVE:
+ if (FUN_CLASS(fun2) != FUN_WAVE) {
+ s = s * FUN_VLIGHT
+ ang = un_open ("angstroms")
+ do i = 1, nvals {
+ call un_ctranr (dun, ang, dval[i], lambda, 1)
+ fval2[i] = fval2[i] / s * lambda**2
+ }
+ call un_close (ang)
+ } else {
+ do i = 1, nvals
+ fval2[i] = fval2[i] / s
+ }
+ }
+
+ s = FUN_SCALE(fun2)
+ switch (FUN_CLASS(fun2)) {
+ case FUN_FREQ:
+ do i = 1, nvals
+ fval2[i] = fval2[i] * s
+ case FUN_WAVE:
+ if (FUN_CLASS(fun1) != FUN_WAVE) {
+ s = s * FUN_VLIGHT
+ ang = un_open ("angstroms")
+ do i = 1, nvals {
+ call un_ctranr (dun, ang, dval[i], lambda, 1)
+ fval2[i] = fval2[i] * s / lambda**2
+ }
+ call un_close (ang)
+ } else {
+ do i = 1, nvals
+ fval2[i] = fval2[i] * s
+ }
+ }
+ switch (FUN_MOD(fun2)) {
+ case FUN_LOG:
+ do i = 1, nvals
+ fval2[i] = log10 (fval2[i])
+ case FUN_MAG:
+ do i = 1, nvals
+ fval2[i] = -2.5 * log10 (fval2[i])
+ }
+end
+
+
+# FUN_CHANGER -- Change funits
+# Error is returned if the conversion cannot be made
+
+procedure fun_changer (fun, funits, dun, dvals, fvals, nvals, update)
+
+pointer fun # Units pointer (may be changed)
+char funits[ARB] # Desired funits
+pointer dun # Dispersion units pointer
+real dvals[nvals] # Dispersion values
+real fvals[nvals] # Flux Values
+int nvals # Number of values
+int update # Update funits pointer?
+
+bool streq(), fun_compare()
+pointer fun1, fun_open()
+errchk fun_open, fun_ctranr
+
+begin
+
+ # Check for same funit string
+ if (streq (funits, FUN_USER(fun)))
+ return
+
+ # Check for error in funits string, or the same funits.
+ fun1 = fun_open (funits)
+ if (fun_compare (fun1, fun)) {
+ call strcpy (funits, FUN_USER(fun), SZ_UNITS)
+ call fun_close (fun1)
+ return
+ }
+
+ iferr {
+ call fun_ctranr (fun, fun1, dun, dvals, fvals, fvals, nvals)
+ if (update == YES)
+ call fun_copy (fun1, fun)
+ call fun_close(fun1)
+ } then {
+ call fun_close(fun1)
+ call erract (EA_ERROR)
+ }
+end
+
+
+# FUN_CTRAND -- Transform funits
+# Error is returned if the transform cannot be made
+
+procedure fun_ctrand (fun1, fun2, dun, dval, fval1, fval2, nvals)
+
+pointer fun1 # Input funits pointer
+pointer fun2 # Output funits pointer
+pointer dun # Input dispersion units pointer
+double dval[nvals] # Input dispersion values
+double fval1[nvals] # Input flux values
+double fval2[nvals] # Output flux values
+int nvals # Number of values
+
+int i
+double s, lambda
+pointer ang, un_open()
+bool fun_compare()
+errchk un_open, un_ctrand
+
+begin
+ if (fun_compare (fun1, fun2)) {
+ call amovd (fval1, fval2, nvals)
+ return
+ }
+
+ if (FUN_CLASS(fun1) == FUN_UNKNOWN || FUN_CLASS(fun2) == FUN_UNKNOWN)
+ call error (1, "Cannot convert between selected funits")
+
+ call amovd (fval1, fval2, nvals)
+
+ s = FUN_SCALE(fun1)
+ switch (FUN_MOD(fun1)) {
+ case FUN_LOG:
+ do i = 1, nvals
+ fval2[i] = 10. ** fval2[i]
+ case FUN_MAG:
+ do i = 1, nvals
+ fval2[i] = 10. ** (-0.4 * fval2[i])
+ }
+ switch (FUN_CLASS(fun1)) {
+ case FUN_FREQ:
+ do i = 1, nvals
+ fval2[i] = fval2[i] / s
+ case FUN_WAVE:
+ if (FUN_CLASS(fun2) != FUN_WAVE) {
+ s = s * FUN_VLIGHT
+ ang = un_open ("angstroms")
+ do i = 1, nvals {
+ call un_ctrand (dun, ang, dval[i], lambda, 1)
+ fval2[i] = fval2[i] / s * lambda**2
+ }
+ call un_close (ang)
+ } else {
+ do i = 1, nvals
+ fval2[i] = fval2[i] / s
+ }
+ }
+
+ s = FUN_SCALE(fun2)
+ switch (FUN_CLASS(fun2)) {
+ case FUN_FREQ:
+ do i = 1, nvals
+ fval2[i] = fval2[i] * s
+ case FUN_WAVE:
+ if (FUN_CLASS(fun1) != FUN_WAVE) {
+ s = s * FUN_VLIGHT
+ ang = un_open ("angstroms")
+ do i = 1, nvals {
+ call un_ctrand (dun, ang, dval[i], lambda, 1)
+ fval2[i] = fval2[i] * s / lambda**2
+ }
+ call un_close (ang)
+ } else {
+ do i = 1, nvals
+ fval2[i] = fval2[i] * s
+ }
+ }
+ switch (FUN_MOD(fun2)) {
+ case FUN_LOG:
+ do i = 1, nvals
+ fval2[i] = log10 (fval2[i])
+ case FUN_MAG:
+ do i = 1, nvals
+ fval2[i] = -2.5 * log10 (fval2[i])
+ }
+
+end
+
+
+# FUN_CHANGED -- Change funits
+# Error is returned if the conversion cannot be made
+
+procedure fun_changed (fun, funits, dun, dvals, fvals, nvals, update)
+
+pointer fun # Units pointer (may be changed)
+char funits[ARB] # Desired funits
+pointer dun # Input dispersion pointer
+double dvals[nvals] # Input dispersion values
+double fvals[nvals] # Flux values
+int nvals # Number of values
+int update # Update funits pointer?
+
+bool streq(), fun_compare()
+pointer fun1, fun_open()
+errchk fun_open, fun_ctrand
+
+begin
+
+ # Check for same funit string
+ if (streq (funits, FUN_USER(fun)))
+ return
+
+ # Check for error in funits string, or the same funits.
+ fun1 = fun_open (funits)
+ if (fun_compare (fun1, fun)) {
+ call strcpy (funits, FUN_USER(fun), SZ_UNITS)
+ call fun_close (fun1)
+ return
+ }
+
+ iferr {
+ call fun_ctrand (fun, fun1, dun, dvals, fvals, fvals, nvals)
+ if (update == YES)
+ call fun_copy (fun1, fun)
+ call fun_close(fun1)
+ } then {
+ call fun_close(fun1)
+ call erract (EA_ERROR)
+ }
+end
diff --git a/noao/onedspec/smw/mkpkg b/noao/onedspec/smw/mkpkg
new file mode 100644
index 00000000..64326969
--- /dev/null
+++ b/noao/onedspec/smw/mkpkg
@@ -0,0 +1,48 @@
+# SMW/SHDR Interface
+
+update:
+ $checkout libsmw.a noaolib$
+ $update libsmw.a
+ $checkin libsmw.a noaolib$
+ ;
+
+generic:
+ $set GEN = "$$generic -k"
+
+ $ifolder (smwctran.x, smwctran.gx)
+ $(GEN) smwctran.gx -o smwctran.x $endif
+ ;
+
+libsmw.a:
+ $ifeq (USE_GENERIC, yes) $call generic $endif
+
+ funits.x <ctype.h> <error.h> <funits.h>
+ shdr.x <error.h> <funits.h> <imset.h> <math/iminterp.h>\
+ <smw.h> <units.h> <imhdr.h>
+ smwclose.x <smw.h>
+ smwct.x <smw.h>
+ smwctfree.x <smw.h>
+ smwctran.x <smw.h>
+ smwdaxis.x <smw.h>
+ smwequispec.x <mwset.h> <smw.h> <imhdr.h>
+ smwesms.x <mwset.h> <smw.h>
+ smwgapid.x <smw.h>
+ smwgwattrs.x <error.h> <smw.h>
+ smwmerge.x <mwset.h> <smw.h>
+ smwmultispec.x <smw.h>
+ smwmw.x <smw.h>
+ smwnd.x <imhdr.h> <smw.h>
+ smwndes.x <imhdr.h> <smw.h>
+ smwnewcopy.x <smw.h>
+ smwoldms.x <mwset.h> <smw.h>
+ smwonedspec.x <smw.h> <imhdr.h>
+ smwopen.x <smw.h>
+ smwopenim.x <imio.h> <mwset.h> <imhdr.h>
+ smwsapid.x <smw.h>
+ smwsaveim.x <imio.h> <smw.h> <imhdr.h>
+ smwsaxes.x <imhdr.h> <mwset.h> <smw.h>
+ smwsctran.x <smw.h>
+ smwsmw.x <smw.h>
+ smwswattrs.x <error.h> <smw.h>
+ units.x <ctype.h> <error.h> <units.h>
+ ;
diff --git a/noao/onedspec/smw/shdr.x b/noao/onedspec/smw/shdr.x
new file mode 100644
index 00000000..bdcc4b95
--- /dev/null
+++ b/noao/onedspec/smw/shdr.x
@@ -0,0 +1,1269 @@
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <smw.h>
+include <units.h>
+include <funits.h>
+include <math/iminterp.h>
+
+
+# SHDR_OPEN -- Open the SHDR spectrum header structure.
+# SHDR_TYPE -- Determine spectrum type.
+# SHDR_GTYPE -- Get the selected spectrum type.
+# SHDR_CLOSE -- Close and free the SHDR structure.
+# SHDR_COPY -- Make a copy of an SHDR structure.
+# SHDR_SYSTEM -- Set or change the WCS system.
+# SHDR_UNITS -- Set or change user units.
+# SHDR_LW -- Logical to world coordinate transformation.
+# SHDR_WL -- World to logical coordinate transformation.
+# SHDR_REBIN -- Rebin spectrum to dispersion of reference spectrum.
+# SHDR_LINEAR -- Rebin spectrum to linear dispersion.
+# SHDR_EXTRACT -- Extract a specific wavelength region.
+# SHDR_GI -- Load an integer value from the header.
+# SHDR_GR -- Load a real value from the header.
+
+
+# SHDR_OPEN -- Open SHDR spectrum header structure.
+#
+# This routine sets header information, WCS transformations, and extracts the
+# spectrum from EQUISPEC, MULTISPEC, and NDSPEC format images. The spectrum
+# from a 2D/3D format is specified by a logical line and band number.
+# Optionally an EQUISPEC or MULTISPEC spectrum may be selected by it's
+# aperture number. The access modes are header only or header and data.
+# Special checks are made to avoid repeated setting of the header and WCS
+# information common to all spectra in an image provided the previously set
+# structure is input. Note that the logical to world and world to logical
+# transformations require that the MWCS pointer not be closed.
+
+procedure shdr_open (im, smw, index1, index2, ap, mode, sh)
+
+pointer im # IMIO pointer
+pointer smw # SMW pointer
+int index1 # Image index desired
+int index2 # Image index desired
+int ap # Aperture number desired
+int mode # Access mode
+pointer sh # SHDR pointer
+
+int i, j, k, l, n, np, np1, np2, aplow[2], aphigh[2], strncmp()
+real smw_c1tranr(), asumr()
+double dval, shdr_lw()
+bool newim, streq()
+pointer sp, key, str, coeff, mw, ct, buf
+pointer smw_sctran(), imgs3r(), un_open(), fun_open()
+errchk smw_sctran, imgstr, imgeti, imgetr, smw_gwattrs
+errchk un_open, fun_open, fun_ctranr, imgs3r, shdr_gtype
+
+define data_ 90
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Allocate basic structure or check if the same spectrum is requested.
+ if (sh == NULL) {
+ call calloc (sh, LEN_SHDR, TY_STRUCT)
+ call calloc (SID(sh,1), LEN_SHDRS, TY_CHAR)
+ newim = true
+ } else {
+ call imstats (im, IM_IMAGENAME, Memc[str], SZ_LINE)
+ newim = !streq (Memc[str], IMNAME(sh))
+ if (!newim) {
+ if (LINDEX(sh,1)==index1 && LINDEX(sh,2)==index2) {
+ if (IS_INDEFI(ap) || AP(sh)==ap) {
+ np1 = NP1(sh)
+ np2 = NP2(sh)
+ np = np2 - np1 + 1
+ if (CTLW(sh) == NULL || CTWL(sh) == NULL)
+ goto data_
+ if (mode == SHHDR) {
+ do i = 1, SH_NTYPES
+ call mfree (SPEC(sh,i), TY_REAL)
+ } else {
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ if (mode == SHDATA && SPEC(sh,mode) == NULL)
+ goto data_
+ case SMW_ES, SMW_MS:
+ if (SPEC(sh,mode) == NULL)
+ goto data_
+ }
+ }
+ call sfree (sp)
+ return
+ }
+ }
+ }
+ }
+
+ # Set parameters common to an entire image.
+ if (newim) {
+ call imstats (im, IM_IMAGENAME, IMNAME(sh), LEN_SHDRS)
+ IM(sh) = im
+ MW(sh) = smw
+
+ # Get standard parameters.
+ call shdr_gi (im, "OFLAG", OBJECT, OFLAG(sh))
+ call shdr_gr (im, "EXPOSURE", INDEF, IT(sh))
+ call shdr_gr (im, "ITIME", IT(sh), IT(sh))
+ call shdr_gr (im, "EXPTIME", IT(sh), IT(sh))
+ call shdr_gr (im, "RA", INDEF, RA(sh))
+ call shdr_gr (im, "DEC", INDEF, DEC(sh))
+ call shdr_gr (im, "UT", INDEF, UT(sh))
+ call shdr_gr (im, "ST", INDEF, ST(sh))
+ call shdr_gr (im, "HA", INDEF, HA(sh))
+ call shdr_gr (im, "AIRMASS", INDEF, AM(sh))
+ call shdr_gi (im, "DC-FLAG", DCNO, DC(sh))
+ call shdr_gi (im, "EX-FLAG", ECNO, EC(sh))
+ call shdr_gi (im, "CA-FLAG", FCNO, FC(sh))
+ iferr (call imgstr (im, "DEREDDEN", RC(sh), LEN_SHDRS))
+ RC(sh) = EOS
+
+ # Flag bad airmass value; i.e. 0.
+ if (!IS_INDEF (AM(sh)) && AM(sh) < 1.)
+ AM(sh) = INDEF
+
+ # Set the SMW information.
+ if (SMW_FORMAT(smw) == SMW_MS)
+ i = 3B
+ else
+ i = 2 ** (SMW_PAXIS(smw,1) - 1)
+ CTLW1(sh) = smw_sctran (smw, "logical", "world", i)
+ CTWL1(sh) = smw_sctran (smw, "world", "logical", i)
+
+ # Set the units.
+ mw = SMW_MW(smw,0)
+ i = SMW_PAXIS(smw,1)
+ iferr (call mw_gwattrs (mw, i, "label", LABEL(sh),LEN_SHDRS))
+ call strcpy ("", LABEL(sh), LEN_SHDRS)
+ if (streq (LABEL(sh), "equispec") || streq (LABEL(sh), "multispe"))
+ call strcpy ("", LABEL(sh), LEN_SHDRS)
+ iferr (call mw_gwattrs (mw, i, "units", UNITS(sh),LEN_SHDRS)) {
+ call sprintf (Memc[key], SZ_FNAME, "cunit%d")
+ call pargi (i)
+ iferr (call imgstr (im, Memc[key], UNITS(sh), LEN_SHDRS)) {
+ call strlwr (LABEL(sh))
+ if (LABEL(sh) == EOS)
+ call strcpy ("", UNITS(sh), LEN_SHDRS)
+ else if (streq (LABEL(sh), "lambda"))
+ call strcpy ("angstroms", UNITS(sh), LEN_SHDRS)
+ else if (streq (LABEL(sh), "freq"))
+ call strcpy ("hertz", UNITS(sh), LEN_SHDRS)
+ else if (strncmp (LABEL(sh), "velo", 4) == 0)
+ call strcpy ("m/s", UNITS(sh), LEN_SHDRS)
+ else if (streq (LABEL(sh), "waveleng"))
+ call strcpy ("angstroms", UNITS(sh), LEN_SHDRS)
+ else
+ call strcpy ("", UNITS(sh), LEN_SHDRS)
+ }
+ if (strncmp (LABEL(sh), "velo", 4) == 0)
+ call strcat (" 21 centimeters", UNITS(sh), LEN_SHDRS)
+ }
+ if (UNITS(sh) == EOS && DC(sh) != DCNO)
+ call strcpy ("Angstroms", UNITS(sh), LEN_SHDRS)
+ MWUN(sh) = un_open (UNITS(sh))
+ call un_copy (MWUN(sh), UN(sh))
+
+ iferr (call imgstr (im, "bunit", Memc[str], SZ_LINE))
+ call strcpy ("", Memc[str], SZ_LINE)
+ FUNIM(sh) = fun_open (Memc[str])
+ if (FUN_CLASS(FUNIM(sh)) != FUN_UNKNOWN)
+ FC(sh) = FCYES
+
+ call fun_copy (FUNIM(sh), FUN(sh))
+ call strcpy (FUN_LABEL(FUN(sh)), FLABEL(sh), LEN_SHDRS)
+ call strcpy (FUN_UNITS(FUN(sh)), FUNITS(sh), LEN_SHDRS)
+ }
+
+ # Set WCS parameters for spectrum type.
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ # Set physical and logical indices.
+ if (!IS_INDEFI (ap)) {
+ i = max (1, min (SMW_NSPEC(smw), ap))
+ j = 1
+ } else {
+ i = max (1, index1)
+ j = max (1, index2)
+ }
+ call smw_mw (smw, i, j, mw, k, l)
+
+ LINDEX(sh,1) = max (1, min (SMW_LLEN(smw,2), k))
+ LINDEX(sh,2) = max (1, min (SMW_LLEN(smw,3), l))
+ PINDEX(sh,1) = LINDEX(sh,1)
+ PINDEX(sh,2) = LINDEX(sh,2)
+ APINDEX(sh) = LINDEX(sh,1)
+
+ # Set aperture information. Note the use of the logical index.
+ np1 = 1
+ call smw_gwattrs (smw, i, j, AP(sh), BEAM(sh), DC(sh),
+ dval, dval, np2, dval, APLOW(sh,1), APHIGH(sh,1), coeff)
+
+ call smw_gapid (smw, i, j, TITLE(sh), LEN_SHDRS)
+ Memc[SID(sh,1)] = EOS
+
+ switch (SMW_LDIM(smw)) {
+ case 1:
+ IMSEC(sh) = EOS
+ case 2:
+ if (APLOW(sh,1) == APHIGH(sh,1)) {
+ if (SMW_LAXIS(smw,1) == 1)
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d]")
+ else
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d,*]")
+ call pargi (nint (APLOW(sh,1)))
+ } else {
+ if (SMW_LAXIS(smw,1) == 1)
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d:%d]")
+ else
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d:%d,*]")
+ call pargi (nint (APLOW(sh,1)))
+ call pargi (nint (APHIGH(sh,1)))
+ }
+ case 3:
+ if (APLOW(sh,1)==APHIGH(sh,1) && APLOW(sh,2)==APHIGH(sh,2)) {
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d,%d]")
+ case 2:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d,*,%d]")
+ case 3:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d,%d,*]")
+ }
+ call pargi (nint (APLOW(sh,1)))
+ call pargi (nint (APLOW(sh,2)))
+ } else if (APLOW(sh,1) == APHIGH(sh,1)) {
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d,%d:%d]")
+ case 2:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d,*,%d:%d]")
+ case 3:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d,%d:%d,*]")
+ }
+ call pargi (nint (APLOW(sh,1)))
+ call pargi (nint (APLOW(sh,2)))
+ call pargi (nint (APHIGH(sh,2)))
+ } else if (APLOW(sh,2) == APHIGH(sh,2)) {
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d:%d,%d]")
+ case 2:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d:%d,*,%d]")
+ case 3:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d:%d,%d,*]")
+ }
+ call pargi (nint (APLOW(sh,1)))
+ call pargi (nint (APHIGH(sh,1)))
+ call pargi (nint (APLOW(sh,2)))
+ } else {
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d:%d,%d:%d]")
+ case 2:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d:%d,*,%d:%d]")
+ case 3:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[%d:%d,%d:%d,*]")
+ }
+ call pargi (nint (APLOW(sh,1)))
+ call pargi (nint (APHIGH(sh,1)))
+ call pargi (nint (APLOW(sh,2)))
+ call pargi (nint (APHIGH(sh,2)))
+ }
+ }
+
+ case SMW_ES, SMW_MS:
+ # Set the image and aperture indices.
+ if (SMW_PAXIS(smw,2) != 3) {
+ PINDEX(sh,1) = max (1, min (SMW_LLEN(smw,2), index1))
+ PINDEX(sh,2) = max (1, min (SMW_LLEN(smw,3), index2))
+ LINDEX(sh,1) = PINDEX(sh,1)
+ LINDEX(sh,2) = PINDEX(sh,2)
+ APINDEX(sh) = LINDEX(sh,1)
+ } else {
+ PINDEX(sh,1) = 1
+ PINDEX(sh,2) = max (1, min (SMW_LLEN(smw,2), index2))
+ LINDEX(sh,1) = PINDEX(sh,2)
+ LINDEX(sh,2) = 1
+ APINDEX(sh) = 1
+ }
+
+ # If an aperture is specified first try and find it.
+ # If it is not specified or found then use the physical index.
+
+ coeff = NULL
+ AP(sh) = 0
+ if (!IS_INDEFI(ap)) {
+ do i = 1, SMW_NSPEC(smw) {
+ call smw_gwattrs (smw, i, 1, AP(sh), BEAM(sh), DC(sh),
+ dval, dval, np2, dval, APLOW(sh,1), APHIGH(sh,1), coeff)
+ if (AP(sh) == ap && SMW_PAXIS(smw,2) != 3) {
+ PINDEX(sh,1) = i
+ LINDEX(sh,1) = i
+ APINDEX(sh) = i
+ break
+ }
+ }
+ }
+ if (AP(sh) != ap)
+ call smw_gwattrs (smw, APINDEX(sh), 1, AP(sh), BEAM(sh), DC(sh),
+ dval, dval, np2, dval, APLOW(sh,1), APHIGH(sh,1), coeff)
+ call mfree (coeff, TY_CHAR)
+
+ np1 = 1
+ if (SMW_PDIM(smw) > 1) {
+ ct = smw_sctran (smw, "logical", "physical", 2)
+ PINDEX(sh,1) = nint (smw_c1tranr (ct, real(PINDEX(sh,1))))
+ call smw_ctfree (ct)
+ }
+ if (SMW_PDIM(smw) > 2) {
+ ct = smw_sctran (smw, "logical", "physical", 4)
+ PINDEX(sh,2) = nint (smw_c1tranr (ct, real(PINDEX(sh,2))))
+ call smw_ctfree (ct)
+ }
+
+ call smw_gapid (smw, APINDEX(sh), 1, TITLE(sh), LEN_SHDRS)
+ call shdr_type (sh, 1, PINDEX(sh,2))
+
+ switch (SMW_LDIM(smw)) {
+ case 1:
+ IMSEC(sh) = EOS
+ case 2:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d]")
+ call pargi (APINDEX(sh))
+ case 3:
+ call sprintf (IMSEC(sh), LEN_SHDRS, "[*,%d,%d]")
+ call pargi (APINDEX(sh))
+ call pargi (LINDEX(sh,2))
+ }
+ }
+
+ # Set NP1 and NP2 in logical coordinates.
+ i = 2 ** (SMW_PAXIS(smw,1) - 1)
+ ct = smw_sctran (smw, "physical", "logical", i)
+ i = max (1, min (int (smw_c1tranr (ct, real (np1))), SMW_LLEN(smw,1)))
+ j = max (1, min (int (smw_c1tranr (ct, real (np2))), SMW_LLEN(smw,1)))
+ call smw_ctfree (ct)
+ np1 = min (i, j)
+ np2 = max (i, j)
+ np = np2 - np1 + 1
+
+ NP1(sh) = np1
+ NP2(sh) = np2
+ SN(sh) = np
+
+
+data_ # Set the coordinate and data arrays if desired otherwise free them.
+ CTLW(sh) = CTLW1(sh)
+ CTWL(sh) = CTWL1(sh)
+
+ # Set linear dispersion terms.
+ W0(sh) = shdr_lw (sh, double(1))
+ W1(sh) = shdr_lw (sh, double(np))
+ WP(sh) = (W1(sh) - W0(sh)) / (np - 1)
+ SN(sh) = np
+
+ if (mode == SHHDR) {
+ do i = 1, SH_NTYPES
+ call mfree (SPEC(sh,i), TY_REAL)
+ call sfree (sp)
+ return
+ }
+
+ # Set WCS array
+ if (SX(sh) == NULL)
+ call malloc (SX(sh), np, TY_REAL)
+ else
+ call realloc (SX(sh), np, TY_REAL)
+ do i = 1, np
+ Memr[SX(sh)+i-1] = shdr_lw (sh, double(i))
+
+ # Set spectrum array in most efficient way.
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ if (mode == SHDATA || SY(sh) == NULL) {
+ if (SY(sh) == NULL)
+ call malloc (SY(sh), np, TY_REAL)
+ else
+ call realloc (SY(sh), np, TY_REAL)
+ call aclrr (Memr[SY(sh)], np)
+ if (IS_INDEF(APLOW(sh,1)))
+ aplow[1] = 1
+ else
+ aplow[1] = nint (APLOW(sh,1))
+ if (IS_INDEF(APHIGH(sh,1)))
+ aphigh[1] = 1
+ else
+ aphigh[1] = nint (APHIGH(sh,1))
+ if (IS_INDEF(APLOW(sh,2)))
+ aplow[2] = 1
+ else
+ aplow[2] = nint (APLOW(sh,2))
+ if (IS_INDEF(APHIGH(sh,2)))
+ aphigh[2] = 1
+ else
+ aphigh[2] = nint (APHIGH(sh,2))
+ k = aplow[1]
+ l = aphigh[1]
+ n = aphigh[1] - aplow[1] + 1
+ if (SMW_LAXIS(smw,1) == 1) {
+ do j = aplow[2], aphigh[2] {
+ do i = aplow[1], aphigh[1] {
+ buf = imgs3r (im, np1, np2, i, i, j, j)
+ call aaddr (Memr[buf], Memr[SY(sh)],
+ Memr[SY(sh)], np)
+ }
+ }
+ } else if (SMW_LAXIS(smw,1) == 2) {
+ do j = aplow[2], aphigh[2] {
+ do i = np1, np2 {
+ buf = imgs3r (im, k, l, i, i, j, j)
+ Memr[SY(sh)+i-np1] = Memr[SY(sh)+i-np1] +
+ asumr (Memr[buf], n)
+ }
+ }
+ } else {
+ do i = np1, np2 {
+ do j = aplow[2], aphigh[2] {
+ buf = imgs3r (im, k, l, j, j, i, i)
+ Memr[SY(sh)+i-np1] = Memr[SY(sh)+i-np1] +
+ asumr (Memr[buf], n)
+ }
+ }
+ }
+ }
+ case SMW_ES, SMW_MS:
+ if (mode == SHDATA || SY(sh) == NULL) {
+ if (SY(sh) == NULL)
+ call malloc (SY(sh), np, TY_REAL)
+ else
+ call realloc (SY(sh), np, TY_REAL)
+ i = LINDEX(sh,1)
+ j = LINDEX(sh,2)
+ buf = imgs3r (im, np1, np2, i, i, j, j)
+ call amovr (Memr[buf], Memr[SY(sh)], np)
+ }
+
+ if (mode > SHDATA)
+ call shdr_gtype (sh, mode)
+ }
+
+ # Guess flux scale if necessary.
+ if (FC(sh) == FCYES && FUN_CLASS(FUNIM(sh)) == FUN_UNKNOWN) {
+ if (Memr[SY(sh)+np/2] < 1e-18)
+ call strcpy ("erg/cm2/s/Hz", Memc[str], SZ_LINE)
+ else if (Memr[SY(sh)+np/2] < 1e-5)
+ call strcpy ("erg/cm2/s/A", Memc[str], SZ_LINE)
+ call fun_close (FUNIM(sh))
+ FUNIM(sh) = fun_open (Memc[str])
+ if (FUN_CLASS(FUN(sh)) == FUN_UNKNOWN) {
+ call fun_copy (FUNIM(sh), FUN(sh))
+ call strcpy (FUN_LABEL(FUN(sh)), FLABEL(sh), LEN_SHDRS)
+ call strcpy (FUN_UNITS(FUN(sh)), FUNITS(sh), LEN_SHDRS)
+ }
+ }
+ if (SPEC(sh,mode) != 0)
+ iferr (call fun_ctranr (FUNIM(sh), FUN(sh), UN(sh), Memr[SX(sh)],
+ Memr[SPEC(sh,mode)], Memr[SPEC(sh,mode)], SN(sh)))
+ ;
+
+ call sfree (sp)
+end
+
+
+# SHDR_GTYPE -- Get the selected spectrum type.
+# Currently this only works for multispec data.
+
+procedure shdr_gtype (sh, type)
+
+pointer sh #I SHDR pointer
+int type #I Spectrum type
+
+int i, j, ctowrd(), strdic()
+pointer sp, key, str, im, smw, ct, buf, smw_sctran(), imgs3r()
+real smw_c1tranr()
+
+begin
+ im = IM(sh)
+ smw = MW(sh)
+
+ if (SMW_FORMAT(smw) == SMW_ND)
+ return
+ if (SMW_PDIM(smw) < 3) {
+ if (type != SHDATA && type != SHRAW) {
+ if (SID(sh,type) != NULL)
+ call mfree (SID(sh,type), TY_CHAR)
+ if (SPEC(sh,type) != NULL)
+ call mfree (SPEC(sh,type), TY_REAL)
+ }
+ return
+ }
+
+ # Find the band.
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ do i = 1, 5 {
+ call sprintf (Memc[key], SZ_LINE, "BANDID%d")
+ call pargi (i)
+ ifnoerr (call imgstr (im, Memc[key], Memc[str], SZ_LINE)) {
+ j = 1
+ if (ctowrd (Memc[str], j, Memc[key], SZ_LINE) == 0)
+ next
+ if (strdic (Memc[key], Memc[key], SZ_LINE, STYPES) != type)
+ next
+ if (SID(sh,type) == NULL)
+ call malloc (SID(sh,type), LEN_SHDRS, TY_CHAR)
+ call strcpy (Memc[str], Memc[SID(sh,type)], LEN_SHDRS)
+ STYPE(sh,type) = type
+ break
+ }
+ }
+ call sfree (sp)
+ if (i == 6) {
+ if (SID(sh,type) != NULL)
+ call mfree (SID(sh,type), TY_CHAR)
+ if (SPEC(sh,type) != NULL)
+ call mfree (SPEC(sh,type), TY_REAL)
+ return
+ }
+
+ # Map the physical band to logical vector.
+ ct = smw_sctran (smw, "physical", "logical", 4)
+ i = nint (smw_c1tranr (ct, real(i)))
+ call smw_ctfree (ct)
+ if (SMW_PAXIS(smw,2) != 3) {
+ if (i > SMW_LLEN(smw,3))
+ return
+ j = i
+ i = LINDEX(sh,1)
+ } else {
+ if (i > SMW_LLEN(smw,2))
+ return
+ j = 1
+ }
+
+ # Get the spectrum.
+ if (SPEC(sh,type) == NULL)
+ call malloc (SPEC(sh,type), SN(sh), TY_REAL)
+ else
+ call realloc (SPEC(sh,type), SN(sh), TY_REAL)
+ buf = imgs3r (im, NP1(sh), NP2(sh), i, i, j, j)
+ call amovr (Memr[buf], Memr[SPEC(sh,type)], SN(sh))
+end
+
+
+# SHDR_TYPE -- Determine the spectrum type.
+# Currently this only works for multispec data.
+
+procedure shdr_type (sh, index, band)
+
+pointer sh #I SHDR pointer
+int index #I Index
+int band #I Physical band
+
+int i, ctowrd(), strdic()
+pointer sp, key
+
+begin
+ if (SMW_FORMAT(MW(sh)) == SMW_ND)
+ return
+
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+
+ if (SID(sh,index) == NULL)
+ call malloc (SID(sh,index), LEN_SHDRS, TY_CHAR)
+
+ call sprintf (Memc[key], SZ_FNAME, "BANDID%d")
+ call pargi (band)
+ iferr (call imgstr (IM(sh), Memc[key], Memc[SID(sh,index)], LEN_SHDRS))
+ Memc[SID(sh,index)] = EOS
+
+ i = 1
+ if (ctowrd (Memc[SID(sh,index)], i, Memc[key], SZ_LINE) > 0)
+ STYPE(sh,index) = strdic (Memc[key], Memc[key], SZ_LINE, STYPES)
+ else
+ STYPE(sh,index) = 0
+
+ call sfree (sp)
+end
+
+
+# SHDR_CLOSE -- Close and free the SHDR structure.
+
+procedure shdr_close (sh)
+
+pointer sh # SHDR structure
+int i
+
+begin
+ if (sh == NULL)
+ return
+ do i = 1, SH_NTYPES {
+ call mfree (SPEC(sh,i), TY_REAL)
+ call mfree (SID(sh,i), TY_CHAR)
+ }
+ call un_close (UN(sh))
+ call un_close (MWUN(sh))
+ call fun_close (FUN(sh))
+ call fun_close (FUNIM(sh))
+ if (MW(sh) != NULL) {
+ call smw_ctfree (CTLW1(sh))
+ call smw_ctfree (CTWL1(sh))
+ }
+ call mfree (sh, TY_STRUCT)
+end
+
+
+# SHDR_COPY -- Make a copy of an SHDR structure.
+# The image pointer is not copied and the MWCS pointer and transform pointers
+# may or may not be copied . The uncopied pointers mean that they will be
+# shared by multiple spectrum structures but it also means that when they are
+# closed the structures will have invalid pointers. The advantage of not
+# copying is that many spectra may come from the same image and the overhead
+# of having copies of the IMIO and MWCS pointers can be avoided.
+
+procedure shdr_copy (sh1, sh2, wcs)
+
+pointer sh1 # SHDR structure to copy
+pointer sh2 # SHDR structure copy
+int wcs # Make copy of wcs?
+
+int i
+pointer un, mwun, fun, funim, spec[SH_NTYPES], sid[SH_NTYPES], smw_newcopy()
+errchk shdr_system
+
+begin
+ if (sh2 == NULL) {
+ call calloc (sh2, LEN_SHDR, TY_STRUCT)
+ call calloc (SID(sh2,1), LEN_SHDRS, TY_CHAR)
+ }
+
+ un = UN(sh2)
+ mwun = MWUN(sh2)
+ fun = FUN(sh2)
+ funim = FUNIM(sh2)
+ call amovi (SPEC(sh2,1), spec, SH_NTYPES)
+ call amovi (SID(sh2,1), sid, SH_NTYPES)
+ call amovi (Memi[sh1], Memi[sh2], LEN_SHDR)
+ call amovi (spec, SPEC(sh2,1), SH_NTYPES)
+ call amovi (sid, SID(sh2,1), SH_NTYPES)
+ UN(sh2) = un
+ MWUN(sh2) = mwun
+ FUN(sh2) = fun
+ FUNIM(sh2) = funim
+ call un_copy (UN(sh1), UN(sh2))
+ call un_copy (MWUN(sh1), MWUN(sh2))
+ call fun_copy (FUN(sh1), FUN(sh2))
+ call fun_copy (FUNIM(sh1), FUNIM(sh2))
+ do i = 1, SH_NTYPES {
+ if (SPEC(sh1,i) != NULL) {
+ if (SPEC(sh2,i) == NULL)
+ call malloc (SPEC(sh2,i), SN(sh1), TY_REAL)
+ else
+ call realloc (SPEC(sh2,i), SN(sh1), TY_REAL)
+ call amovr (Memr[SPEC(sh1,i)], Memr[SPEC(sh2,i)], SN(sh1))
+ }
+ }
+
+ if (wcs == YES && MW(sh1) != NULL) {
+ MW(sh2) = smw_newcopy (MW(sh1))
+ CTLW1(sh2) = NULL
+ CTWL1(sh2) = NULL
+ call shdr_system (sh2, "world")
+ }
+end
+
+
+# SHDR_SYSTEM -- Set or change the WCS system.
+
+procedure shdr_system (sh, system)
+
+pointer sh # SHDR pointer
+char system[ARB] # System
+
+int i, sn
+bool streq()
+double shdr_lw()
+pointer smw, mw, smw_sctran(), un_open()
+errchk smw_sctran, un_open
+
+begin
+ smw = MW(sh)
+ if (smw == NULL)
+ call error (1, "shdr_system: MWCS not defined")
+
+ call smw_ctfree (CTLW1(sh))
+ call smw_ctfree (CTWL1(sh))
+
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND, SMW_ES:
+ i = 2 ** (SMW_PAXIS(smw,1) - 1)
+ CTLW1(sh) = smw_sctran (smw, "logical", system, i)
+ CTWL1(sh) = smw_sctran (smw, system, "logical", i)
+ case SMW_MS:
+ CTLW1(sh) = smw_sctran (smw, "logical", system, 3B)
+ CTWL1(sh) = smw_sctran (smw, system, "logical", 3B)
+ }
+ CTLW(sh) = CTLW1(sh)
+ CTWL(sh) = CTWL1(sh)
+
+ # Set labels and units
+ call un_close (MWUN(sh))
+ if (streq (system, "physical")) {
+ call strcpy ("Pixel", LABEL(sh), LEN_SHDRS)
+ call strcpy ("", UNITS(sh), LEN_SHDRS)
+ MWUN(sh) = un_open (UNITS(sh))
+ } else {
+ call smw_mw (smw, 1, 1, mw, i, i)
+ iferr (call mw_gwattrs (mw, SMW_PAXIS(smw,1), "label", LABEL(sh),
+ LEN_SHDRS))
+ call strcpy ("", LABEL(sh), LEN_SHDRS)
+ if (streq (LABEL(sh), "equispec") || streq (LABEL(sh), "multispe"))
+ call strcpy ("", LABEL(sh), LEN_SHDRS)
+ iferr (call mw_gwattrs (mw, SMW_PAXIS(smw,1), "units", UNITS(sh),
+ LEN_SHDRS))
+ call strcpy ("", UNITS(sh), LEN_SHDRS)
+ MWUN(sh) = un_open (UNITS(sh))
+ call strcpy (UN_LABEL(UN(sh)), LABEL(sh), LEN_SHDRS)
+ call strcpy (UN_UNITS(UN(sh)), UNITS(sh), LEN_SHDRS)
+ }
+
+ sn = SN(sh)
+ W0(sh) = shdr_lw (sh, double(1))
+ W1(sh) = shdr_lw (sh, double(sn))
+ WP(sh) = (W1(sh) - W0(sh)) / (sn - 1)
+ if (SX(sh) != NULL)
+ do i = 1, sn
+ Memr[SX(sh)+i-1] = shdr_lw (sh, double(i))
+end
+
+
+# SHDR_UNITS -- Set or change the WCS system.
+# This changes W0, W1, WP, and SX.
+
+procedure shdr_units (sh, units)
+
+pointer sh # SHDR pointer
+char units[ARB] # Units
+
+int i, sn
+bool streq()
+double shdr_lw()
+pointer str, un, un_open()
+errchk un_open
+
+begin
+ # Check for unknown units.
+ if (streq (units, "display")) {
+ call malloc (str, SZ_LINE, TY_CHAR)
+ iferr (call mw_gwattrs (SMW_MW(MW(sh),0), SMW_PAXIS(MW(sh),1),
+ "units_display", Memc[str], SZ_LINE)) {
+ un = NULL
+ call un_copy (MWUN(sh), un)
+ } else
+ un = un_open (Memc[str])
+ call mfree (str, TY_CHAR)
+ } else if (streq (units, "default")) {
+ un = NULL
+ call un_copy (MWUN(sh), un)
+ } else
+ un = un_open (units)
+ if (UN_CLASS(un) == UN_UNKNOWN || UN_CLASS(MWUN(sh)) == UN_UNKNOWN) {
+ call un_close (un)
+ call error (1, "Cannot convert to specified units")
+ }
+
+ # Update the coordinates.
+ call un_close (UN(sh))
+ UN(sh) = un
+
+ call strcpy (UN_LABEL(UN(sh)), LABEL(sh), LEN_SHDRS)
+ call strcpy (UN_UNITS(UN(sh)), UNITS(sh), LEN_SHDRS)
+
+ sn = SN(sh)
+ W0(sh) = shdr_lw (sh, double(1))
+ W1(sh) = shdr_lw (sh, double(sn))
+ WP(sh) = (W1(sh) - W0(sh)) / (sn - 1)
+ if (SX(sh) != NULL)
+ do i = 1, sn
+ Memr[SX(sh)+i-1] = shdr_lw (sh, double(i))
+end
+
+
+# SHDR_LW -- Logical to world coordinate transformation.
+# The transformation pointer is generally NULL only after SHDR_LINEAR
+
+double procedure shdr_lw (sh, l)
+
+pointer sh # SHDR pointer
+double l # Logical coordinate
+double w # World coordinate
+
+double l0, l1, l2, w1, smw_c1trand()
+
+begin
+ l0 = l + NP1(sh) - 1
+ if (CTLW(sh) != NULL) {
+ switch (SMW_FORMAT(MW(sh))) {
+ case SMW_ND, SMW_ES:
+ w = smw_c1trand (CTLW(sh), l0)
+ case SMW_MS:
+ call smw_c2trand (CTLW(sh), l0, double (APINDEX(sh)), w, w1)
+ }
+ } else {
+ switch (DC(sh)) {
+ case DCLOG:
+ w = W0(sh) * 10. ** (log10(W1(sh)/W0(sh)) * (l0-1) / (SN(sh)-1))
+ case DCFUNC:
+ w = W0(sh)
+ call smw_c2trand (CTWL1(sh), w, double (AP(sh)), l1, w1)
+ w = W1(sh)
+ call smw_c2trand (CTWL1(sh), w, double (AP(sh)), l2, w1)
+ if (SN(sh) > 1)
+ l1 = (l2 - l1) / (SN(sh) - 1) * (l0 - 1) + l1
+ else
+ l1 = l0 - 1 + l1
+ call smw_c2trand (CTLW1(sh), l1, double (APINDEX(sh)), w, w1)
+ default:
+ w = W0(sh) + (l0 - 1) * WP(sh)
+ }
+ }
+
+ iferr (call un_ctrand (MWUN(sh), UN(sh), w, w, 1))
+ ;
+ return (w)
+end
+
+
+# SHDR_WL -- World to logical coordinate transformation.
+# The transformation pointer is generally NULL only after SHDR_LINEAR
+
+double procedure shdr_wl (sh, w)
+
+pointer sh # SHDR pointer
+double w # World coordinate
+double l # Logical coordinate
+
+double w1, l1, l2, smw_c1trand()
+
+begin
+ iferr (call un_ctrand (UN(sh), MWUN(sh), w, w1, 1))
+ w1 = w
+
+ if (CTWL(sh) != NULL) {
+ switch (SMW_FORMAT(MW(sh))) {
+ case SMW_ND, SMW_ES:
+ l = smw_c1trand (CTWL(sh), w1)
+ case SMW_MS:
+ call smw_c2trand (CTWL(sh), w1, double (AP(sh)),l,l1)
+ }
+ } else {
+ switch (DC(sh)) {
+ case DCLOG:
+ l = log10(w1/W0(sh)) / log10(W1(sh)/W0(sh)) * (SN(sh)-1) + 1
+ case DCFUNC:
+ call smw_c2trand (CTWL1(sh), w1, double (AP(sh)), l, l1)
+
+ w1 = W0(sh)
+ call smw_c2trand (CTWL1(sh), w1, double (AP(sh)), l1, w1)
+ w1 = W1(sh)
+ call smw_c2trand (CTWL1(sh), w1, double (AP(sh)), l2, w1)
+ if (l1 != l2)
+ l = (SN(sh) - 1) / (l2 - l1) * (l - l1) + 1
+ else
+ l = l - l1 + 1
+ default:
+ l = (w1 - W0(sh)) / WP(sh) + 1
+ }
+ }
+
+ return (l-NP1(sh)+1)
+end
+
+
+# SHDR_REBIN -- Rebin spectrum to dispersion of reference spectrum.
+# The interpolation function is set by ONEDINTERP.
+
+procedure shdr_rebin (sh, shref)
+
+pointer sh # Spectrum to be rebinned
+pointer shref # Reference spectrum
+
+char interp[10]
+int i, j, type, ia, ib, n, clgwrd()
+real a, b, sum, asieval(), asigrl()
+double x, w, xmin, xmax, shdr_lw(), shdr_wl()
+pointer unref, unsave, asi, spec
+bool fp_equalr()
+
+begin
+ # Check if rebinning is needed
+ if (DC(sh) == DC(shref) && DC(sh) != DCFUNC &&
+ fp_equalr (W0(sh), W0(shref)) && fp_equalr(WP(sh), WP(shref)) &&
+ SN(sh) == SN(shref))
+ return
+
+ # Do everything in units of reference WCS.
+ unref = UN(shref)
+ unsave = UN(sh)
+ UN(SH) = unref
+
+ call asiinit (asi, clgwrd ("interp", interp, 10, II_FUNCTIONS))
+ do type = 1, SH_NTYPES {
+ if (SPEC(sh,type) == NULL)
+ next
+
+ # Fit the interpolation function to the spectrum.
+ # Extend the interpolation by one pixel at each end.
+
+ n = SN(sh)
+ call malloc (spec, n+2, TY_REAL)
+ call amovr (Memr[SPEC(sh,type)], Memr[spec+1], n)
+ Memr[spec] = Memr[SPEC(sh,type)]
+ Memr[spec+n+1] = Memr[SPEC(sh,type)+n-1]
+ call asifit (asi, Memr[spec], n+2)
+ call mfree (spec, TY_REAL)
+
+ xmin = 0.5
+ xmax = n + 0.5
+
+ # Reallocate data array
+ if (n != SN(shref)) {
+ n = SN(shref)
+ call realloc (SPEC(sh,type), n, TY_REAL)
+ call aclrr (Memr[SPEC(sh,type)], n)
+ }
+ spec = SPEC(sh,type)
+
+ # Compute the average flux in each output pixel.
+
+ x = 0.5
+ w = shdr_lw (shref, x)
+ x = shdr_wl (sh, w)
+ b = max (xmin, min (xmax, x)) + 1
+ do i = 1, n {
+ x = i + 0.5
+ w = shdr_lw (shref, x)
+ x = shdr_wl (sh, w)
+ a = b
+ b = max (xmin, min (xmax, x)) + 1
+ if (a <= b) {
+ ia = nint (a + 0.5)
+ ib = nint (b - 0.5)
+ if (abs (a+0.5-ia) < .00001 && abs (b-0.5-ib) < .00001) {
+ sum = 0.
+ do j = ia, ib
+ sum = sum + asieval (asi, real(j))
+ if (ib - ia > 0)
+ sum = sum / (ib - ia)
+ } else {
+ sum = asigrl (asi, a, b)
+ if (b - a > 0.)
+ sum = sum / (b - a)
+ }
+ } else {
+ ib = nint (b + 0.5)
+ ia = nint (a - 0.5)
+ if (abs (a-0.5-ia) < .00001 && abs (b+0.5-ib) < .00001) {
+ sum = 0.
+ do j = ib, ia
+ sum = sum + asieval (asi, real(j))
+ if (ia - ib > 0)
+ sum = sum / (ia - ib)
+ } else {
+ sum = asigrl (asi, b, a)
+ if (a - b > 0.)
+ sum = sum / (a - b)
+ }
+ }
+
+ Memr[spec] = sum
+ spec = spec + 1
+ }
+ }
+ call asifree (asi)
+
+ # Set the rest of the header. The coordinate transformations are
+ # canceled to indicate they are not valid for the data. They
+ # are not freed because the same pointer may be used in other
+ # spectra from the same image.
+
+ if (SN(sh) != n)
+ call realloc (SX(sh), n, TY_REAL)
+ call amovr (Memr[SX(shref)], Memr[SX(sh)], n)
+ DC(sh) = DC(shref)
+ W0(sh) = W0(shref)
+ W1(sh) = W1(shref)
+ WP(sh) = WP(shref)
+ SN(sh) = SN(shref)
+
+ CTLW(sh) = NULL
+ CTWL(sh) = NULL
+
+ # Restore original units
+ UN(sh) = unsave
+ iferr (call un_ctranr (unref, UN(sh), Memr[SX(sh)], Memr[SX(sh)],
+ SN(sh)))
+ ;
+end
+
+
+# SHDR_LINEAR -- Rebin spectrum to linear dispersion.
+# The interpolation function is set by ONEDINTERP
+
+procedure shdr_linear (sh, w0, w1, sn, dc)
+
+pointer sh # Spectrum to be rebinned
+real w0 # Wavelength of first logical pixel
+real w1 # Wavelength of last logical pixel
+int sn # Number of pixels
+int dc # Dispersion type (DCLINEAR | DCLOG)
+
+char interp[10]
+int i, j, type, ia, ib, n, clgwrd()
+real w0mw, w1mw, a, b, sum, asieval(), asigrl()
+double x, w, w0l, wp, xmin, xmax, shdr_wl()
+pointer unsave, asi, spec
+bool fp_equalr()
+
+begin
+ # Check if rebinning is needed
+ if (DC(sh) == dc && fp_equalr (W0(sh), w0) &&
+ fp_equalr (W1(sh), w1) && SN(sh) == sn)
+ return
+
+ # Do everything in units of MWCS.
+ call un_ctranr (UN(sh), MWUN(sh), w0, w0mw, 1)
+ call un_ctranr (UN(sh), MWUN(sh), w1, w1mw, 1)
+ unsave = UN(sh)
+ UN(SH) = MWUN(sh)
+
+ call asiinit (asi, clgwrd ("interp", interp, 10, II_FUNCTIONS))
+ do type = 1, SH_NTYPES {
+ if (SPEC(sh,type) == NULL)
+ next
+
+ # Fit the interpolation function to the spectrum.
+ # Extend the interpolation by one pixel at each end.
+
+ n = SN(sh)
+ call malloc (spec, n+2, TY_REAL)
+ call amovr (Memr[SPEC(sh,type)], Memr[spec+1], n)
+ Memr[spec] = Memr[SPEC(sh,type)]
+ Memr[spec+n+1] = Memr[SPEC(sh,type)+n-1]
+ call asifit (asi, Memr[spec], n+2)
+ call mfree (spec, TY_REAL)
+
+ xmin = 0.5
+ xmax = n + 0.5
+
+ # Reallocate spectrum data array
+ if (n != sn) {
+ n = sn
+ call realloc (SPEC(sh,type), n, TY_REAL)
+ }
+ spec = SPEC(sh,type)
+
+ # Integrate across pixels using ASIGRL.
+
+ x = 0.5
+ if (dc == DCLOG) {
+ w0l = log10 (w0mw)
+ wp = (log10 (w1mw) - log10(w0mw)) / (n - 1)
+ w = 10. ** (w0l+(x-1)*wp)
+ } else {
+ wp = (w1mw - w0mw) / (n - 1)
+ w = w0mw + (x - 1) * wp
+ }
+ x = shdr_wl (sh, w)
+ b = max (xmin, min (xmax, x)) + 1
+ do i = 1, n {
+ x = i + 0.5
+ if (dc == DCLOG)
+ w = 10. ** (w0l + (x - 1) * wp)
+ else
+ w = w0mw + (x - 1) * wp
+ x = shdr_wl (sh, w)
+ a = b
+ b = max (xmin, min (xmax, x)) + 1
+ if (a <= b) {
+ ia = nint (a + 0.5)
+ ib = nint (b - 0.5)
+ if (abs (a+0.5-ia) < .00001 && abs (b-0.5-ib) < .00001) {
+ sum = 0.
+ do j = ia, ib
+ sum = sum + asieval (asi, real(j))
+ if (ib - ia > 0)
+ sum = sum / (ib - ia)
+ } else {
+ sum = asigrl (asi, a, b)
+ if (b - a > 0.)
+ sum = sum / (b - a)
+ }
+ } else {
+ ib = nint (b + 0.5)
+ ia = nint (a - 0.5)
+ if (abs (a-0.5-ia) < .00001 && abs (b+0.5-ib) < .00001) {
+ sum = 0.
+ do j = ib, ia
+ sum = sum + asieval (asi, real(j))
+ if (ia - ib > 0)
+ sum = sum / (ia - ib)
+ } else {
+ sum = asigrl (asi, b, a)
+ if (a - b > 0.)
+ sum = sum / (a - b)
+ }
+ }
+ Memr[spec] = sum
+ spec = spec + 1
+ }
+ }
+ call asifree (asi)
+
+ # Set the rest of the header. The coordinate transformations are
+ # canceled to indicate they are not valid for the data. They
+ # are not freed because the same pointer may be used in other
+ # spectra from the same image.
+
+ if (SN(sh) != n)
+ call realloc (SX(sh), n, TY_REAL)
+ do i = 0, n-1 {
+ if (dc == DCLOG)
+ w = 10. ** (w0l + i * wp)
+ else
+ w = w0mw + i * wp
+ Memr[SX(sh)+i] = w
+ }
+ W0(sh) = w0
+ W1(sh) = w1
+ WP(sh) = (w1 - w0) / (sn - 1)
+ SN(sh) = sn
+ NP1(sh) = 1
+ NP2(sh) = sn
+ DC(sh) = dc
+
+ CTLW(sh) = NULL
+ CTWL(sh) = NULL
+
+ # Restore original units
+ UN(sh) = unsave
+ iferr (call un_ctranr (MWUN(sh), UN(sh), Memr[SX(sh)], Memr[SX(sh)],
+ sn))
+ ;
+end
+
+
+# SHDR_EXTRACT -- Extract a specific wavelength region.
+
+procedure shdr_extract (sh, w1, w2, rebin)
+
+pointer sh # SHDR structure
+real w1 # Starting wavelength
+real w2 # Ending wavelength
+bool rebin # Rebin wavelength region?
+
+int i, j, i1, i2, n
+double l1, l2
+pointer buf
+bool fp_equald()
+double shdr_wl(), shdr_lw()
+errchk shdr_linear, shdr_lw, shdr_wl
+
+begin
+ l1 = shdr_wl (sh, double (w1))
+ l2 = shdr_wl (sh, double (w2))
+ if (fp_equald(l1,l2) || max(l1,l2) < 1 || min (l1,l2) > SN(sh))
+ call error (1, "No pixels to extract")
+ l1 = max (1D0, min (double (SN(sh)), l1))
+ l2 = max (1D0, min (double (SN(sh)), l2))
+ i1 = nint (l1)
+ i2 = nint (l2)
+ n = abs (i2 - i1) + 1
+
+ if (rebin) {
+ l1 = shdr_lw (sh, l1)
+ l2 = shdr_lw (sh, l2)
+ if (DC(sh) == DCFUNC)
+ call shdr_linear (sh, real (l1), real (l2), n, DCLINEAR)
+ else
+ call shdr_linear (sh, real (l1), real (l2), n, DC(sh))
+ } else {
+ if (i1 == 1 && i2 == SN(sh))
+ return
+
+ if (i1 <= i2) {
+ do j = 1, SH_NTYPES
+ if (SPEC(sh,j) != NULL)
+ call amovr (Memr[SPEC(sh,j)+i1-1], Memr[SPEC(sh,j)], n)
+ } else {
+ call malloc (buf, n, TY_REAL)
+ do j = 1, SH_NTYPES {
+ if (SPEC(sh,j) != NULL) {
+ do i = i1, i2, -1
+ Memr[buf+i1-i] = Memr[SPEC(sh,j)+i-1]
+ call amovr (Memr[buf], Memr[SPEC(sh,j)], n)
+ }
+ }
+ call mfree (buf, TY_REAL)
+ }
+ W0(sh) = Memr[SX(sh)]
+ W1(sh) = Memr[SX(sh)+n-1]
+ SN(sh) = n
+ NP1(sh) = 1
+ NP2(sh) = n
+ if (n > 1)
+ WP(sh) = (W1(sh) - W0(sh)) / (SN(sh) - 1)
+ CTLW(sh) = NULL
+ CTWL(sh) = NULL
+ }
+end
+
+
+# SHDR_GI -- Load an integer value from the header.
+
+procedure shdr_gi (im, field, default, ival)
+
+pointer im
+char field[ARB]
+int default
+int ival
+
+int dummy, imaccf(), imgeti()
+
+begin
+ ival = default
+ if (imaccf (im, field) == YES) {
+ iferr (dummy = imgeti (im, field))
+ call erract (EA_WARN)
+ else
+ ival = dummy
+ }
+end
+
+
+# SHDR_GR -- Load a real value from the header.
+
+procedure shdr_gr (im, field, default, rval)
+
+pointer im
+char field[ARB]
+real default
+real rval
+
+int imaccf()
+real dummy, imgetr()
+
+begin
+ rval = default
+ if (imaccf (im, field) == YES) {
+ iferr (dummy = imgetr (im, field))
+ call erract (EA_WARN)
+ else
+ rval = dummy
+ }
+end
diff --git a/noao/onedspec/smw/smwclose.x b/noao/onedspec/smw/smwclose.x
new file mode 100644
index 00000000..339ebd98
--- /dev/null
+++ b/noao/onedspec/smw/smwclose.x
@@ -0,0 +1,46 @@
+include <smw.h>
+
+
+# SMW_CLOSE -- Close the SMW data structure.
+# This includes closing the MWCS pointers.
+
+procedure smw_close (smw)
+
+pointer smw # SMW pointer
+
+int i
+pointer apids
+
+begin
+ if (smw == NULL)
+ return
+
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call mfree (SMW_APID(smw), TY_CHAR)
+ call mw_close (SMW_MW(smw,0))
+ case SMW_ES:
+ call mfree (SMW_APS(smw), TY_INT)
+ call mfree (SMW_BEAMS(smw), TY_INT)
+ call mfree (SMW_APLOW(smw), TY_REAL)
+ call mfree (SMW_APHIGH(smw), TY_REAL)
+ call mfree (SMW_APID(smw), TY_CHAR)
+ apids = SMW_APIDS(smw) - 1
+ do i = 1, SMW_NSPEC(smw)
+ call mfree (Memi[apids+i], TY_CHAR)
+ call mfree (SMW_APIDS(smw), TY_POINTER)
+ call mw_close (SMW_MW(smw,0))
+ case SMW_MS:
+ call mfree (SMW_APS(smw), TY_INT)
+ call mfree (SMW_BEAMS(smw), TY_INT)
+ call mfree (SMW_APLOW(smw), TY_REAL)
+ call mfree (SMW_APHIGH(smw), TY_REAL)
+ call mfree (SMW_APID(smw), TY_CHAR)
+ apids = SMW_APIDS(smw) - 1
+ do i = 1, SMW_NSPEC(smw)
+ call mfree (Memi[apids+i], TY_CHAR)
+ do i = 0, SMW_NMW(smw)-1
+ call mw_close (SMW_MW(smw,i))
+ }
+ call mfree (smw, TY_STRUCT)
+end
diff --git a/noao/onedspec/smw/smwct.x b/noao/onedspec/smw/smwct.x
new file mode 100644
index 00000000..b568f759
--- /dev/null
+++ b/noao/onedspec/smw/smwct.x
@@ -0,0 +1,19 @@
+include <smw.h>
+
+
+# SMW_CT -- Get MCWS CT pointer for the specified physical line.
+
+pointer procedure smw_ct (sct, line)
+
+pointer sct #I SMW pointer
+int line #I Physical line
+
+begin
+ if (SMW_NCT(sct) == 1)
+ return (SMW_CT(sct,0))
+
+ if (line < 1 || line > SMW_NSPEC(SMW_SMW(sct)))
+ call error (1, "smw_ct: aperture not found")
+
+ return (SMW_CT(sct,(line-1)/SMW_NSPLIT))
+end
diff --git a/noao/onedspec/smw/smwctfree.x b/noao/onedspec/smw/smwctfree.x
new file mode 100644
index 00000000..90a506d7
--- /dev/null
+++ b/noao/onedspec/smw/smwctfree.x
@@ -0,0 +1,19 @@
+include <smw.h>
+
+
+# SMW_CTFREE -- Free a spectral SMW coordinate transform pointer.
+
+procedure smw_ctfree (ct)
+
+pointer ct # SMW CT pointer
+int i
+
+begin
+ if (ct == NULL)
+ return
+
+ do i = 0, SMW_NCT(ct)-1
+ call mw_ctfree (SMW_CT(ct,i))
+ call mw_ctfree (SMW_CTL(ct))
+ call mfree (ct, TY_STRUCT)
+end
diff --git a/noao/onedspec/smw/smwctran.gx b/noao/onedspec/smw/smwctran.gx
new file mode 100644
index 00000000..4029aaab
--- /dev/null
+++ b/noao/onedspec/smw/smwctran.gx
@@ -0,0 +1,166 @@
+include <smw.h>
+
+
+# Evaluate SMW coordinate transform. These procedures call the
+# MWCS procedures unless the WCS is a split MULTISPEC WCS. In that
+# case the appropriate piece needs to be determined and the physical
+# line numbers manipulated. For log sampled spectra conversions
+# must be made for EQUISPEC/NDSPEC. The convention is that coordinates
+# are always input and output and linear. Note that the MULTISPEC
+# function driver already takes care of this.
+#
+# SMW_CTRANR -- N dimensional real coordinate transformation.
+# SMW_CTRAND -- N dimensional double coordinate transformation.
+# SMW_C1TRANR -- One dimensional real coordinate transformation.
+# SMW_C1TRAND -- One dimensional double coordinate transformation.
+# SMW_C2TRANR -- Two dimensional real coordinate transformation.
+# SMW_C2TRAND -- Two dimensional double coordinate transformation.
+
+
+$for (rd)
+# SMW_CTRAN -- N dimensional coordinate transformation.
+
+procedure smw_ctran$t (ct, p1, p2, ndim)
+
+pointer ct #I SMW CT pointer
+PIXEL p1[ndim] #I Input coordinate
+PIXEL p2[ndim] #O Output coordinate
+int ndim #I Dimensionality
+
+int i, j, format, daxis, aaxis, dtype, naps
+pointer smw, aps
+errchk mw_ctran$t
+
+begin
+ if (SMW_NCT(ct) != 1)
+ call error (1, "SMW_CTRAN: Wrong WCS type")
+
+ call amov$t (p1, p2, ndim)
+
+ smw = SMW_SMW(ct)
+ format = SMW_FORMAT(smw)
+ daxis = SMW_DAXIS(ct)
+ aaxis = SMW_AAXIS(ct)
+ dtype = SMW_DTYPE(smw)
+ naps = SMW_NSPEC(smw)
+ aps = SMW_APS(smw)
+ switch (format) {
+ case SMW_ND, SMW_ES:
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW, SMW_PW:
+ call mw_ctran$t (SMW_CT(ct,0), p2, p2, ndim)
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = 10. ** max (-20$F, min (20$F, p2[daxis]))
+ if (aaxis > 0 && format == SMW_ES) {
+ i = max (1, min (naps, nint (p2[aaxis])))
+ p2[aaxis] = Memi[aps+i-1]
+ }
+ case SMW_WL, SMW_WP:
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = log10 (p2[daxis])
+ if (aaxis > 0 && format == SMW_ES) {
+ j = nint (p2[aaxis])
+ p2[aaxis] = 1
+ do i = 1, naps {
+ if (j == Memi[aps+i-1]) {
+ p2[aaxis] = i
+ break
+ }
+ }
+ }
+ call mw_ctran$t (SMW_CT(ct,0), p2, p2, ndim)
+ default:
+ call mw_ctran$t (SMW_CT(ct,0), p2, p2, ndim)
+ }
+ case SMW_MS:
+ call mw_ctran$t (SMW_CT(ct,0), p1, p2, ndim)
+ }
+end
+
+
+# SMW_C1TRAN -- One dimensional coordinate transformation.
+
+PIXEL procedure smw_c1tran$t (ct, x1)
+
+pointer ct #I SMW CT pointer
+PIXEL x1 #I Input coordinate
+PIXEL x2 #O Output coordinate
+
+errchk mw_ctran$t
+
+begin
+ call smw_ctran$t (ct, x1, x2, 1)
+ return (x2)
+end
+
+
+# SMW_C2TRAN -- Two dimensional coordinate transformation.
+
+procedure smw_c2tran$t (ct, x1, y1, x2, y2)
+
+pointer ct #I SMW CT pointer
+PIXEL x1, y1 #I Input coordinates
+PIXEL x2, y2 #O Output coordinates
+
+PIXEL p1[2], p2[2]
+int i, j, naps
+PIXEL xp, yp
+pointer aps, smw_ct()
+errchk smw_ct, mw_c2tran$t
+
+begin
+ # Unsplit WCS.
+ if (SMW_NCT(ct) == 1) {
+ p1[1] = x1
+ p1[2] = y1
+ call smw_ctran$t (ct, p1, p2, 2)
+ x2 = p2[1]
+ y2 = p2[2]
+ return
+ }
+
+ # If we get here then we are dealing with a split MULTISPEC WCS.
+ # Depending on the systems being transformed there may need to
+ # transformation made on the physical coordinate system.
+
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW:
+ call mw_c2tran$t (SMW_CTL(ct), x1, y1, xp, yp)
+ i = nint (yp)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2tran$t (smw_ct(ct,i), xp, yp, x2, y2)
+ case SMW_PW:
+ i = nint (y1)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2tran$t (smw_ct(ct,i), x1, yp, x2, y2)
+ case SMW_WL:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2tran$t (smw_ct(ct,i), x1, y1, xp, yp)
+ yp = i
+ call mw_c2tran$t (SMW_CTL(ct), xp, yp, x2, y2)
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ case SMW_WP:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2tran$t (smw_ct(ct,i), x1, y1, x2, y2)
+ y2 = i
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ default:
+ x2 = x1
+ y2 = y1
+ }
+end
+$endfor
diff --git a/noao/onedspec/smw/smwctran.x b/noao/onedspec/smw/smwctran.x
new file mode 100644
index 00000000..38967be2
--- /dev/null
+++ b/noao/onedspec/smw/smwctran.x
@@ -0,0 +1,312 @@
+include <smw.h>
+
+
+# Evaluate SMW coordinate transform. These procedures call the
+# MWCS procedures unless the WCS is a split MULTISPEC WCS. In that
+# case the appropriate piece needs to be determined and the physical
+# line numbers manipulated. For log sampled spectra conversions
+# must be made for EQUISPEC/NDSPEC. The convention is that coordinates
+# are always input and output and linear. Note that the MULTISPEC
+# function driver already takes care of this.
+#
+# SMW_CTRANR -- N dimensional real coordinate transformation.
+# SMW_CTRAND -- N dimensional double coordinate transformation.
+# SMW_C1TRANR -- One dimensional real coordinate transformation.
+# SMW_C1TRAND -- One dimensional double coordinate transformation.
+# SMW_C2TRANR -- Two dimensional real coordinate transformation.
+# SMW_C2TRAND -- Two dimensional double coordinate transformation.
+
+
+
+# SMW_CTRAN -- N dimensional coordinate transformation.
+
+procedure smw_ctranr (ct, p1, p2, ndim)
+
+pointer ct #I SMW CT pointer
+real p1[ndim] #I Input coordinate
+real p2[ndim] #O Output coordinate
+int ndim #I Dimensionality
+
+int i, j, format, daxis, aaxis, dtype, naps
+pointer smw, aps
+errchk mw_ctranr
+
+begin
+ if (SMW_NCT(ct) != 1)
+ call error (1, "SMW_CTRAN: Wrong WCS type")
+
+ call amovr (p1, p2, ndim)
+
+ smw = SMW_SMW(ct)
+ format = SMW_FORMAT(smw)
+ daxis = SMW_DAXIS(ct)
+ aaxis = SMW_AAXIS(ct)
+ dtype = SMW_DTYPE(smw)
+ naps = SMW_NSPEC(smw)
+ aps = SMW_APS(smw)
+ switch (format) {
+ case SMW_ND, SMW_ES:
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW, SMW_PW:
+ call mw_ctranr (SMW_CT(ct,0), p2, p2, ndim)
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = 10. ** max (-20.0, min (20.0, p2[daxis]))
+ if (aaxis > 0 && format == SMW_ES) {
+ i = max (1, min (naps, nint (p2[aaxis])))
+ p2[aaxis] = Memi[aps+i-1]
+ }
+ case SMW_WL, SMW_WP:
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = log10 (p2[daxis])
+ if (aaxis > 0 && format == SMW_ES) {
+ j = nint (p2[aaxis])
+ p2[aaxis] = 1
+ do i = 1, naps {
+ if (j == Memi[aps+i-1]) {
+ p2[aaxis] = i
+ break
+ }
+ }
+ }
+ call mw_ctranr (SMW_CT(ct,0), p2, p2, ndim)
+ default:
+ call mw_ctranr (SMW_CT(ct,0), p2, p2, ndim)
+ }
+ case SMW_MS:
+ call mw_ctranr (SMW_CT(ct,0), p1, p2, ndim)
+ }
+end
+
+
+# SMW_C1TRAN -- One dimensional coordinate transformation.
+
+real procedure smw_c1tranr (ct, x1)
+
+pointer ct #I SMW CT pointer
+real x1 #I Input coordinate
+real x2 #O Output coordinate
+
+errchk mw_ctranr
+
+begin
+ call smw_ctranr (ct, x1, x2, 1)
+ return (x2)
+end
+
+
+# SMW_C2TRAN -- Two dimensional coordinate transformation.
+
+procedure smw_c2tranr (ct, x1, y1, x2, y2)
+
+pointer ct #I SMW CT pointer
+real x1, y1 #I Input coordinates
+real x2, y2 #O Output coordinates
+
+real p1[2], p2[2]
+int i, j, naps
+real xp, yp
+pointer aps, smw_ct()
+errchk smw_ct, mw_c2tranr
+
+begin
+ # Unsplit WCS.
+ if (SMW_NCT(ct) == 1) {
+ p1[1] = x1
+ p1[2] = y1
+ call smw_ctranr (ct, p1, p2, 2)
+ x2 = p2[1]
+ y2 = p2[2]
+ return
+ }
+
+ # If we get here then we are dealing with a split MULTISPEC WCS.
+ # Depending on the systems being transformed there may need to
+ # transformation made on the physical coordinate system.
+
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW:
+ call mw_c2tranr (SMW_CTL(ct), x1, y1, xp, yp)
+ i = nint (yp)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2tranr (smw_ct(ct,i), xp, yp, x2, y2)
+ case SMW_PW:
+ i = nint (y1)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2tranr (smw_ct(ct,i), x1, yp, x2, y2)
+ case SMW_WL:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2tranr (smw_ct(ct,i), x1, y1, xp, yp)
+ yp = i
+ call mw_c2tranr (SMW_CTL(ct), xp, yp, x2, y2)
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ case SMW_WP:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2tranr (smw_ct(ct,i), x1, y1, x2, y2)
+ y2 = i
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ default:
+ x2 = x1
+ y2 = y1
+ }
+end
+
+# SMW_CTRAN -- N dimensional coordinate transformation.
+
+procedure smw_ctrand (ct, p1, p2, ndim)
+
+pointer ct #I SMW CT pointer
+double p1[ndim] #I Input coordinate
+double p2[ndim] #O Output coordinate
+int ndim #I Dimensionality
+
+int i, j, format, daxis, aaxis, dtype, naps
+pointer smw, aps
+errchk mw_ctrand
+
+begin
+ if (SMW_NCT(ct) != 1)
+ call error (1, "SMW_CTRAN: Wrong WCS type")
+
+ call amovd (p1, p2, ndim)
+
+ smw = SMW_SMW(ct)
+ format = SMW_FORMAT(smw)
+ daxis = SMW_DAXIS(ct)
+ aaxis = SMW_AAXIS(ct)
+ dtype = SMW_DTYPE(smw)
+ naps = SMW_NSPEC(smw)
+ aps = SMW_APS(smw)
+ switch (format) {
+ case SMW_ND, SMW_ES:
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW, SMW_PW:
+ call mw_ctrand (SMW_CT(ct,0), p2, p2, ndim)
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = 10. ** max (-20.0D0, min (20.0D0, p2[daxis]))
+ if (aaxis > 0 && format == SMW_ES) {
+ i = max (1, min (naps, nint (p2[aaxis])))
+ p2[aaxis] = Memi[aps+i-1]
+ }
+ case SMW_WL, SMW_WP:
+ if (daxis > 0 && dtype == DCLOG)
+ p2[daxis] = log10 (p2[daxis])
+ if (aaxis > 0 && format == SMW_ES) {
+ j = nint (p2[aaxis])
+ p2[aaxis] = 1
+ do i = 1, naps {
+ if (j == Memi[aps+i-1]) {
+ p2[aaxis] = i
+ break
+ }
+ }
+ }
+ call mw_ctrand (SMW_CT(ct,0), p2, p2, ndim)
+ default:
+ call mw_ctrand (SMW_CT(ct,0), p2, p2, ndim)
+ }
+ case SMW_MS:
+ call mw_ctrand (SMW_CT(ct,0), p1, p2, ndim)
+ }
+end
+
+
+# SMW_C1TRAN -- One dimensional coordinate transformation.
+
+double procedure smw_c1trand (ct, x1)
+
+pointer ct #I SMW CT pointer
+double x1 #I Input coordinate
+double x2 #O Output coordinate
+
+errchk mw_ctrand
+
+begin
+ call smw_ctrand (ct, x1, x2, 1)
+ return (x2)
+end
+
+
+# SMW_C2TRAN -- Two dimensional coordinate transformation.
+
+procedure smw_c2trand (ct, x1, y1, x2, y2)
+
+pointer ct #I SMW CT pointer
+double x1, y1 #I Input coordinates
+double x2, y2 #O Output coordinates
+
+double p1[2], p2[2]
+int i, j, naps
+double xp, yp
+pointer aps, smw_ct()
+errchk smw_ct, mw_c2trand
+
+begin
+ # Unsplit WCS.
+ if (SMW_NCT(ct) == 1) {
+ p1[1] = x1
+ p1[2] = y1
+ call smw_ctrand (ct, p1, p2, 2)
+ x2 = p2[1]
+ y2 = p2[2]
+ return
+ }
+
+ # If we get here then we are dealing with a split MULTISPEC WCS.
+ # Depending on the systems being transformed there may need to
+ # transformation made on the physical coordinate system.
+
+ switch (SMW_CTTYPE(ct)) {
+ case SMW_LW:
+ call mw_c2trand (SMW_CTL(ct), x1, y1, xp, yp)
+ i = nint (yp)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2trand (smw_ct(ct,i), xp, yp, x2, y2)
+ case SMW_PW:
+ i = nint (y1)
+ yp = mod (i-1, SMW_NSPLIT) + 1
+ call mw_c2trand (smw_ct(ct,i), x1, yp, x2, y2)
+ case SMW_WL:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2trand (smw_ct(ct,i), x1, y1, xp, yp)
+ yp = i
+ call mw_c2trand (SMW_CTL(ct), xp, yp, x2, y2)
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ case SMW_WP:
+ aps = SMW_APS(SMW_SMW(ct))
+ naps = SMW_NSPEC(SMW_SMW(ct))
+ j = nint (y1)
+ do i = 1, naps {
+ if (Memi[aps+i-1] == j) {
+ call mw_c2trand (smw_ct(ct,i), x1, y1, x2, y2)
+ y2 = i
+ return
+ }
+ }
+ call error (1, "Aperture not found")
+ default:
+ x2 = x1
+ y2 = y1
+ }
+end
+
diff --git a/noao/onedspec/smw/smwdaxis.x b/noao/onedspec/smw/smwdaxis.x
new file mode 100644
index 00000000..0bea9375
--- /dev/null
+++ b/noao/onedspec/smw/smwdaxis.x
@@ -0,0 +1,109 @@
+include <smw.h>
+
+define CTYPES "|LAMBDA|FREQ|WAVELENGTH|VELO|VELO-LSR|VELO-HEL|VELO-OBS|"
+
+# SMW_DAXIS -- Set physical dispersion axis and summing factors.
+# A default value of zero for the dispersion axis will cause the dispersion
+# axis to be sought in the image header and, if not found, from the CL
+# "dispaxis" parameter. A default value of zero for the summing factors will
+# cause them to be queried from the CL "nsum" parameter. A default value of
+# INDEFI in either parameter will leave the current default unchanged.
+#
+# When this procedure is called with an SMW and IMIO pointer the SMW
+# pointer is updated to desired default dispersion axis and summing
+# parameters.
+
+procedure smw_daxis (smw, im, daxisp, nsum1, nsum2)
+
+pointer smw #I SMW pointer
+pointer im #I IMIO pointer
+int daxisp #I Default dispersion axis
+int nsum1, nsum2 #I Default summing factors
+
+int i, da, ns[2]
+int imgeti(), clgeti(), clscan(), nscan(), nowhite(), strdic()
+pointer sp, key, val
+data da/0/, ns/0,0/
+errchk clgeti
+
+begin
+ # Set defaults.
+ # A value of 0 will use the image DISPAXIS or query the CL and
+ # a value of INDEFI will leave the current default unchanged.
+
+ if (!IS_INDEFI (daxisp))
+ da = daxisp
+ if (!IS_INDEFI (nsum1))
+ ns[1] = nsum1
+ if (!IS_INDEFI (nsum2))
+ ns[2] = nsum2
+
+ if (smw == NULL)
+ return
+
+ # This procedure is specific to the NDSPEC format.
+ if (SMW_FORMAT(smw) != SMW_ND)
+ return
+
+ # Set dispersion axis.
+ if (da == 0) {
+ if (im == NULL)
+ SMW_PAXIS(smw,1) = clgeti ("dispaxis")
+ else {
+ iferr (SMW_PAXIS(smw,1) = imgeti (im, "DISPAXIS")) {
+ SMW_PAXIS(smw,1) = clgeti ("dispaxis")
+ call smark (sp)
+ call salloc (key, 8, TY_CHAR)
+ call salloc (val, SZ_FNAME, TY_CHAR)
+ do i = 1, 7 {
+ call sprintf (Memc[key], 8, "CTYPE%d")
+ call pargi (i)
+ iferr (call imgstr (im, Memc[key], Memc[val], SZ_FNAME))
+ break
+ if (nowhite (Memc[val], Memc[val], SZ_FNAME) > 0) {
+ call strupr (Memc[val])
+ if (strdic(Memc[val],Memc[val],SZ_FNAME,CTYPES)>0) {
+ SMW_PAXIS(smw,1) = i
+ break
+ }
+ }
+ }
+ call sfree (sp)
+ }
+ if (SMW_PAXIS(smw,1) < 1 || SMW_PAXIS(smw,1) > 7) {
+ i = SMW_PAXIS(smw,1)
+ SMW_PAXIS(smw,1) = clgeti ("dispaxis")
+ call eprintf (
+ "WARNING: Image header dispersion axis %d invalid. Using axis %d.\n")
+ call pargi (i)
+ call pargi (SMW_PAXIS(smw,1))
+ }
+ }
+ } else
+ SMW_PAXIS(smw,1) = da
+
+ # Set summing parameters.
+ if (ns[1] == 0 || ns[2] == 0) {
+ if (clscan("nsum") == EOF)
+ call error (1, "smw_daxis: Error in 'nsum' parameter")
+ call gargi (i)
+ if (ns[1] == 0) {
+ if (nscan() == 1)
+ SMW_NSUM(smw,1) = max (1, i)
+ else
+ call error (1, "smw_daxis: Error in 'nsum' parameter")
+ } else
+ SMW_NSUM(smw,1) = ns[1]
+ call gargi (i)
+ if (ns[2] == 0) {
+ if (nscan() == 2)
+ SMW_NSUM(smw,2) = max (1, i)
+ else
+ SMW_NSUM(smw,2) = SMW_NSUM(smw,1)
+ } else
+ SMW_NSUM(smw,2) = ns[2]
+ } else {
+ SMW_NSUM(smw,1) = ns[1]
+ SMW_NSUM(smw,2) = ns[2]
+ }
+end
diff --git a/noao/onedspec/smw/smwequispec.x b/noao/onedspec/smw/smwequispec.x
new file mode 100644
index 00000000..5cda57c0
--- /dev/null
+++ b/noao/onedspec/smw/smwequispec.x
@@ -0,0 +1,86 @@
+include <imhdr.h>
+include <mwset.h>
+include <smw.h>
+
+
+# SMW_EQUISPEC -- Setup the EQUISPEC SMW parameters.
+# The aperture information is in the APNUM and APID keywords and the
+# WCS information is in the linear MWCS.
+
+procedure smw_equispec (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U MWCS pointer input SMW pointer output
+
+int i, j, k, nchar, ap, beam, dtype, nw
+double w1, dw, z
+real aplow[2], aphigh[2], mw_c1tranr()
+pointer sp, key, val, wterm, mw, ct, mw_sctran()
+int imgeti(), mw_stati(), ctoi(), ctor()
+errchk imgstr, mw_gwtermd, mw_sctran
+errchk smw_open, smw_saxes, smw_swattrs, smw_sapid
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (val, SZ_LINE, TY_CHAR)
+
+ # Determine the dispersion parameters
+ mw = smw
+ i = mw_stati (mw, MW_NDIM)
+ call salloc (wterm, 2*i+i*i, TY_DOUBLE)
+ call mw_gwtermd (mw, Memd[wterm], Memd[wterm+i], Memd[wterm+2*i], i)
+ w1 = Memd[wterm+i]
+ dw = Memd[wterm+2*i]
+
+ # Determine the number of physical pixels.
+ ct = mw_sctran (mw, "logical", "physical", 1)
+ nw = max (mw_c1tranr (ct, 1.), mw_c1tranr (ct, real(IM_LEN(im,1))))
+ call mw_ctfree (ct)
+
+ # Determine the dispersion type.
+ iferr (dtype = imgeti (im, "DC-FLAG"))
+ dtype = DCNO
+ if (dtype==DCLOG) {
+ if (abs(w1)>20. || abs(w1+(nw-1)*dw)>20.)
+ dtype = DCLINEAR
+ else {
+ w1 = 10D0 ** w1
+ dw = w1 * (10D0 ** ((nw-1)*dw) - 1) / (nw - 1)
+ }
+ }
+
+ # Set the SMW data structure.
+ call smw_open (smw, NULL, im)
+ do i = 1, SMW_NSPEC(smw) {
+ call smw_mw (smw, i, 1, mw, j, k)
+ if (j < 1000)
+ call sprintf (Memc[key], SZ_FNAME, "APNUM%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "AP%d")
+ call pargi (j)
+ call imgstr (im, Memc[key], Memc[val], SZ_LINE)
+ k = 1
+ nchar = ctoi (Memc[val], k, ap)
+ nchar = ctoi (Memc[val], k, beam)
+ if (ctor (Memc[val], k, aplow[1]) == 0)
+ aplow[1] = INDEF
+ if (ctor (Memc[val], k, aphigh[1]) == 0)
+ aphigh[1] = INDEF
+ if (ctor (Memc[val], k, aplow[2]) == 0)
+ aplow[2] = INDEF
+ if (ctor (Memc[val], k, aphigh[2]) == 0)
+ aphigh[2] = INDEF
+ z = 0.
+
+ call smw_swattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, "")
+
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (j)
+ ifnoerr (call imgstr (im, Memc[key], Memc[val], SZ_LINE))
+ call smw_sapid (smw, i, 1, Memc[val])
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwesms.x b/noao/onedspec/smw/smwesms.x
new file mode 100644
index 00000000..d1b8a368
--- /dev/null
+++ b/noao/onedspec/smw/smwesms.x
@@ -0,0 +1,96 @@
+include <mwset.h>
+include <smw.h>
+
+
+# SMW_ESMS -- Convert EQUISPEC WCS into MULTISPEC WCS.
+# This is called by SMW_SWATTRS when the equal linear coordinate system
+# requirement of the EQUISPEC WCS is violated.
+
+procedure smw_esms (smw)
+
+pointer smw #U SMW pointer
+
+int i, j, k, pdim1, pdim2, ap, beam, dtype, nw, axes[2]
+double w1, dw, z
+real aplow, aphigh
+pointer sp, key, str, lterm, mw, mw1, mw2, apid, mw_open()
+data axes/1,2/
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (lterm, 12, TY_DOUBLE)
+
+ # Set the basic MWCS
+ mw1 = SMW_MW(smw,0)
+ pdim1 = SMW_PDIM(smw)
+ pdim2 = max (2, pdim1)
+ mw2 = mw_open (NULL, pdim2)
+ call mw_newsystem (mw2, "multispec", pdim2)
+ call mw_swtype (mw2, axes, 2, "multispec", "")
+ if (pdim2 > 2)
+ call mw_swtype (mw2, 3, 1, "linear", "")
+ call mw_gltermd (mw1, Memd[lterm+pdim2], Memd[lterm], pdim1)
+ if (pdim1 == 1) {
+ Memd[lterm+1] = 0.
+ Memd[lterm+3] = 0.
+ Memd[lterm+4] = 0.
+ Memd[lterm+5] = 1.
+ }
+ call mw_sltermd (mw2, Memd[lterm+pdim2], Memd[lterm], pdim2)
+ ifnoerr (call mw_gwattrs (mw1, 1, "label", Memc[str], SZ_LINE))
+ call mw_swattrs (mw2, 1, "label", Memc[str])
+ ifnoerr (call mw_gwattrs (mw1, 1, "units", Memc[str], SZ_LINE))
+ call mw_swattrs (mw2, 1, "units", Memc[str])
+ ifnoerr (call mw_gwattrs (mw1, 1, "units_display", Memc[str], SZ_LINE))
+ call mw_swattrs (mw2, 1, "units_display", Memc[str])
+
+ # Write the MULTISPEC WCS
+ dtype = SMW_DTYPE(smw)
+ w1 = SMW_W1(smw)
+ dw = SMW_DW(smw)
+ nw = SMW_NW(smw)
+ z = SMW_Z(smw)
+ if (dtype == DCLOG) {
+ dw = log10 ((w1+(nw-1)*dw)/w1)/(nw-1)
+ w1 = log10 (w1)
+ }
+
+ call smw_open (mw2, smw, 0)
+ do i = 1, SMW_NSPEC(smw) {
+ ap = Memi[SMW_APS(smw)+i-1]
+ beam = Memi[SMW_BEAMS(smw)+i-1]
+ aplow = Memr[SMW_APLOW(smw)+2*i-2]
+ aphigh = Memr[SMW_APHIGH(smw)+2*i-2]
+ apid = Memi[SMW_APIDS(smw)+i-1]
+
+ call smw_mw (mw2, i, 1, mw, j, k)
+ call sprintf (Memc[key], SZ_FNAME, "spec%d")
+ call pargi (j)
+ call sprintf (Memc[str], SZ_LINE,
+ "%d %d %d %.14g %.14g %d %.14g %.2f %.2f")
+ call pargi (ap)
+ call pargi (beam)
+ call pargi (dtype)
+ call pargd (w1)
+ call pargd (dw)
+ call pargi (nw)
+ call pargd (z)
+ call pargr (aplow)
+ call pargr (aphigh)
+ call mw_swattrs (mw, 2, Memc[key], Memc[str])
+
+ if (apid != NULL)
+ call smw_sapid (mw2, i, 1, Memc[apid])
+
+ # This is used if there is a split MULTISPEC WCS.
+ if (SMW_APS(mw2) != NULL)
+ Memi[SMW_APS(mw2)+i-1] = ap
+ }
+
+ call smw_close (smw)
+ smw = mw2
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwgapid.x b/noao/onedspec/smw/smwgapid.x
new file mode 100644
index 00000000..214be533
--- /dev/null
+++ b/noao/onedspec/smw/smwgapid.x
@@ -0,0 +1,30 @@
+include <smw.h>
+
+
+# SMW_GAPID -- Get aperture id.
+
+procedure smw_gapid (smw, index1, index2, apid, maxchar)
+
+pointer smw #I SMW pointer
+int index1 #I Spectrum index
+int index2 #I Spectrum index
+char apid[maxchar] #O Aperture id
+int maxchar #I Maximum number of characters
+
+pointer ptr
+
+begin
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call strcpy (Memc[SMW_APID(smw)], apid, maxchar)
+ case SMW_ES, SMW_MS:
+ if (index1 < 0 || index1 > SMW_NSPEC(smw))
+ call error (1, "smw_gapid: index out of bounds")
+
+ ptr = Memi[SMW_APIDS(smw)+index1-1]
+ if (index1 == 0 || ptr == NULL)
+ call strcpy (Memc[SMW_APID(smw)], apid, maxchar)
+ else
+ call strcpy (Memc[ptr], apid, maxchar)
+ }
+end
diff --git a/noao/onedspec/smw/smwgwattrs.x b/noao/onedspec/smw/smwgwattrs.x
new file mode 100644
index 00000000..4084fd4c
--- /dev/null
+++ b/noao/onedspec/smw/smwgwattrs.x
@@ -0,0 +1,134 @@
+include <error.h>
+include <smw.h>
+
+
+# SMW_GWATTRS -- Get spectrum attribute parameters.
+# BE CAREFUL OF OUTPUT VARIABLES BEING THE SAME MEMORY ADDRESS!
+
+procedure smw_gwattrs (smw, index1, index2, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+
+pointer smw # SMW pointer
+int index1 # Spectrum index
+int index2 # Spectrum index
+int ap # Aperture number
+int beam # Beam number
+int dtype # Dispersion type
+double w1 # Starting coordinate
+double dw # Coordinate interval
+int nw # Number of valid pixels
+double z # Redshift factor
+real aplow[2], aphigh[2] # Aperture limits
+pointer coeff # Nonlinear coeff string (input/output)
+
+int i, j, n, ip, sz_coeff, strlen(), ctoi(), ctor(), ctod()
+double a, b
+pointer sp, key, mw
+errchk smw_mw, mw_gwattrs
+
+data sz_coeff /SZ_LINE/
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ if (coeff == NULL)
+ call malloc (coeff, sz_coeff, TY_CHAR)
+ else
+ call realloc (coeff, sz_coeff, TY_CHAR)
+
+ # Determine parameters based on the SMW format.
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call smw_mw (smw, index1, index2, mw, i, j)
+
+ dtype = SMW_DTYPE(smw)
+ nw = SMW_NW(smw)
+ w1 = SMW_W1(smw)
+ dw = SMW_DW(smw)
+ z = SMW_Z(smw)
+
+ ap = index1
+ beam = 0
+ aplow[1] = 1
+ aphigh[1] = 1
+ aplow[2] = 1
+ aphigh[2] = 1
+ if (SMW_LDIM(smw) > 1) {
+ aplow[1] = i - (SMW_NSUM(smw,1)-1) / 2
+ aphigh[1] = nint (aplow[1]) + SMW_NSUM(smw,1) - 1
+ aplow[1] = max (1, nint (aplow[1]))
+ aphigh[1] = min (SMW_LLEN(smw,2), nint (aphigh[1]))
+ }
+ if (SMW_LDIM(smw) > 2) {
+ aplow[2] = j - (SMW_NSUM(smw,2)-1) / 2
+ aphigh[2] = nint (aplow[2]) + SMW_NSUM(smw,2) - 1
+ aplow[2] = max (1, nint (aplow[2]))
+ aphigh[2] = min (SMW_LLEN(smw,3), nint (aphigh[2]))
+ }
+
+ Memc[coeff] = EOS
+ case SMW_ES:
+ call smw_mw (smw, index1, index2, mw, i, j)
+
+ dtype = SMW_DTYPE(smw)
+ nw = SMW_NW(smw)
+ w1 = SMW_W1(smw)
+ dw = SMW_DW(smw)
+ z = SMW_Z(smw)
+
+ ap = Memi[SMW_APS(smw)+index1-1]
+ beam = Memi[SMW_BEAMS(smw)+index1-1]
+ aplow[1] = Memr[SMW_APLOW(smw)+2*index1-2]
+ aphigh[1] = Memr[SMW_APHIGH(smw)+2*index1-2]
+ aplow[2] = Memr[SMW_APLOW(smw)+2*index1-1]
+ aphigh[2] = Memr[SMW_APHIGH(smw)+2*index1-1]
+
+ Memc[coeff] = EOS
+ case SMW_MS:
+ call smw_mw (smw, index1, index2, mw, i, j)
+
+ call sprintf (Memc[key], SZ_FNAME, "spec%d")
+ call pargi (i)
+
+ call mw_gwattrs (mw, 2, Memc[key], Memc[coeff], sz_coeff)
+ while (strlen (Memc[coeff]) == sz_coeff) {
+ sz_coeff = 2 * sz_coeff
+ call realloc (coeff, sz_coeff, TY_CHAR)
+ call mw_gwattrs (mw, 2, Memc[key], Memc[coeff], sz_coeff)
+ }
+
+ ip = 1
+ i = ctoi (Memc[coeff], ip, ap)
+ i = ctoi (Memc[coeff], ip, beam)
+ i = ctoi (Memc[coeff], ip, j)
+ i = ctod (Memc[coeff], ip, a)
+ i = ctod (Memc[coeff], ip, b)
+ i = ctoi (Memc[coeff], ip, n)
+ i = ctod (Memc[coeff], ip, z)
+ i = ctor (Memc[coeff], ip, aplow[1])
+ i = ctor (Memc[coeff], ip, aphigh[1])
+ aplow[2] = INDEF
+ aphigh[2] = INDEF
+ if (Memc[coeff+ip-1] != EOS)
+ call strcpy (Memc[coeff+ip], Memc[coeff], sz_coeff)
+ else
+ Memc[coeff] = EOS
+
+ if (j==DCLOG) {
+ if (abs(a)>20. || abs(a+(n-1)*b)>20.)
+ j = DCLINEAR
+ else {
+ a = 10D0 ** a
+ b = a * (10D0 ** ((n-1)*b) - 1) / (n - 1)
+ }
+ }
+
+ dtype = j
+ w1 = a
+ dw = b
+ nw = n
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwmerge.x b/noao/onedspec/smw/smwmerge.x
new file mode 100644
index 00000000..d3e09bd1
--- /dev/null
+++ b/noao/onedspec/smw/smwmerge.x
@@ -0,0 +1,102 @@
+include <mwset.h>
+include <smw.h>
+
+
+# SMW_MERGE -- Merge split MWCS array to a single MWCS.
+
+procedure smw_merge (smw)
+
+pointer smw #U Input split WCS, output single WCS
+
+int i, pdim, naps, format, beam, dtype, dtype1, nw, nw1
+int ap, axes[3]
+double w1, dw, z, w11, dw1, z1
+real aplow[2], aphigh[2]
+pointer sp, key, val, term, coeff, mw, mw1, mw_open()
+data axes/1,2,3/
+
+begin
+ if (SMW_NMW(smw) == 1)
+ return
+
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (val, SZ_LINE, TY_CHAR)
+ call salloc (term, 15, TY_DOUBLE)
+ coeff = NULL
+
+ pdim = SMW_PDIM(smw)
+ naps = SMW_NSPEC(smw)
+ mw1 = SMW_MW(smw,0)
+
+ # Determine output WCS format.
+ format = SMW_ES
+ do i = 1, naps {
+ call smw_gwattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+ if (i == 1) {
+ dtype1 = dtype
+ w11 = w1
+ dw1 = dw
+ z1 = z
+ nw1 = nw
+ }
+ if (dtype>1||dtype!=dtype1||w1!=w11||dw!=dw1||nw!=nw1||z!=z1) {
+ format = SMW_MS
+ break
+ }
+ }
+
+ # Setup WCS.
+ switch (format) {
+ case SMW_ES:
+ mw = mw_open (NULL, pdim)
+ call mw_newsystem (mw, "equispec", pdim)
+ call mw_swtype (mw, axes, pdim, "linear", "")
+
+ case SMW_MS:
+ mw = mw_open (NULL, pdim)
+ call mw_newsystem (mw, "multispec", pdim)
+ call mw_swtype (mw, axes, pdim, "multispec", "")
+ if (pdim > 2)
+ call mw_swtype (mw, 3, 1, "linear", "")
+ }
+
+ ifnoerr (call mw_gwattrs (mw1, 1, "label", Memc[val], SZ_LINE))
+ call mw_swattrs (mw, 1, "label", Memc[val])
+ ifnoerr (call mw_gwattrs (mw1, 1, "units", Memc[val], SZ_LINE))
+ call mw_swattrs (mw, 1, "units", Memc[val])
+ ifnoerr (call mw_gwattrs (mw1, 1, "units_display", Memc[val], SZ_LINE))
+ call mw_swattrs (mw, 1, "units_display", Memc[val])
+ call mw_gltermd (mw1, Memd[term+pdim], Memd[term], pdim)
+ call mw_sltermd (mw, Memd[term+pdim], Memd[term], pdim)
+ call mw_gwtermd (mw1, Memd[term], Memd[term+pdim],
+ Memd[term+2*pdim], pdim)
+ Memd[term] = 1.
+ Memd[term+pdim] = w1 / (1 + z)
+ Memd[term+2*pdim] = dw / (1 + z)
+ call mw_swtermd (mw, Memd[term], Memd[term+pdim],
+ Memd[term+2*pdim], pdim)
+
+ # Set the SMW structure.
+ call smw_open (mw, smw, NULL)
+ if (format == SMW_MS) {
+ do i = 1, SMW_NMW(mw) - 1
+ call mw_close (SMW_MW(mw,i))
+ SMW_NMW(mw) = 1
+ }
+ do i = 1, naps {
+ call smw_gwattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+ call smw_swattrs (mw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, Memc[coeff])
+ call smw_gapid (smw, i, 1, Memc[val], SZ_LINE)
+ call smw_sapid (mw, i, 1, Memc[val])
+ }
+
+ call smw_close (smw)
+ smw = mw
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwmultispec.x b/noao/onedspec/smw/smwmultispec.x
new file mode 100644
index 00000000..18e2dbd0
--- /dev/null
+++ b/noao/onedspec/smw/smwmultispec.x
@@ -0,0 +1,30 @@
+include <smw.h>
+
+
+# SMW_MULTISPEC -- Setup the MULTISPEC SMW parameters.
+
+procedure smw_multispec (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U MWCS pointer input SMW pointer output
+
+int i, j, k
+pointer sp, key, val, mw
+errchk smw_open, smw_saxes, smw_sapid
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (val, SZ_LINE, TY_CHAR)
+
+ call smw_open (smw, NULL, im)
+ do i = 1, SMW_NSPEC(smw) {
+ call smw_mw (smw, i, 1, mw, j, k)
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (j)
+ ifnoerr (call imgstr (im, Memc[key], Memc[val], SZ_LINE))
+ call smw_sapid (smw, i, 1, Memc[val])
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwmw.x b/noao/onedspec/smw/smwmw.x
new file mode 100644
index 00000000..a79aaf98
--- /dev/null
+++ b/noao/onedspec/smw/smwmw.x
@@ -0,0 +1,38 @@
+include <smw.h>
+
+
+# SMW_MW -- Get MWCS pointer and coordinates from spectrum line and band
+
+procedure smw_mw (smw, line, band, mw, x, y)
+
+pointer smw #I SMW pointer
+int line #I Spectrum line
+int band #I Spectrum band
+pointer mw #O MWCS pointer
+int x, y #O MWCS coordinates
+
+real mw_c1tranr()
+
+begin
+ if (line < 1 || line > SMW_NSPEC(smw))
+ call error (1, "smw_mw: spectrum not found")
+
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ mw = SMW_MW(smw,0)
+ x = mod (line - 1, SMW_LLEN(smw,2)) + 1
+ y = (line - 1) / SMW_LLEN(smw,2) + band
+ default:
+ if (SMW_NMW(smw) == 1) {
+ mw = SMW_MW(smw,0)
+ x = line
+ y = band
+ if (SMW_CTLP(smw) != NULL)
+ x = nint (mw_c1tranr (SMW_CTLP(smw), real(line)))
+ } else {
+ mw = SMW_MW(smw,(line-1)/SMW_NSPLIT)
+ x = mod (line - 1, SMW_NSPLIT) + 1
+ y = band
+ }
+ }
+end
diff --git a/noao/onedspec/smw/smwnd.x b/noao/onedspec/smw/smwnd.x
new file mode 100644
index 00000000..10b48079
--- /dev/null
+++ b/noao/onedspec/smw/smwnd.x
@@ -0,0 +1,19 @@
+include <imhdr.h>
+include <smw.h>
+
+
+# SMW_ND -- Setup the NDSPEC SMW.
+# If there is only one spectrum convert it to EQUISPEC if possible.
+
+procedure smw_nd (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U MWCS pointer input SMW pointer output
+
+errchk smw_open, smw_daxis, smw_ndes
+
+begin
+ call smw_open (smw, NULL, im)
+ if (SMW_NSPEC(smw) == 1)
+ call smw_ndes (im, smw)
+end
diff --git a/noao/onedspec/smw/smwndes.x b/noao/onedspec/smw/smwndes.x
new file mode 100644
index 00000000..9d35ca6d
--- /dev/null
+++ b/noao/onedspec/smw/smwndes.x
@@ -0,0 +1,82 @@
+include <imhdr.h>
+include <smw.h>
+
+
+# SMW_NDES -- Convert NDSPEC WCS into EQUISPEC WCS.
+# This requires that the logical dispersion axis be 1.
+
+procedure smw_ndes (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U Input NDSPEC SMW, output EQUISPEC SMW
+
+int i, pdim1, pdim2, daxis, ap, beam, dtype, nw, axes[2]
+real aplow[2], aphigh[2]
+double w1, dw, z
+pointer sp, key, str, lterm1, lterm2, coeff, mw1, mw2, mw_open()
+errchk mw_open, mw_gltermd, mw_gwtermd, smw_open, smw_saxes, smw_gwattrs
+data axes/1,2/, coeff/NULL/
+
+begin
+ # Require the dispersion to be along the first logical axis.
+ if (SMW_LAXIS(smw,1) != 1)
+ return
+
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (lterm1, 15, TY_DOUBLE)
+ call salloc (lterm2, 15, TY_DOUBLE)
+
+ # Set the MWCS. Only the logical and world transformations along
+ # the dispersion axis are transfered.
+
+ pdim1 = SMW_PDIM(smw)
+ pdim2 = IM_NDIM(im)
+ daxis = SMW_PAXIS(smw,1)
+ mw1 = SMW_MW(smw,0)
+
+ mw2 = mw_open (NULL, pdim2)
+ call mw_newsystem (mw2, "equispec", pdim2)
+ call mw_swtype (mw2, axes, pdim2, "linear", "")
+ ifnoerr (call mw_gwattrs (mw1, daxis, "label", Memc[str], SZ_LINE))
+ call mw_swattrs (mw2, 1, "label", Memc[str])
+ ifnoerr (call mw_gwattrs (mw1, daxis, "units", Memc[str], SZ_LINE))
+ call mw_swattrs (mw2, 1, "units", Memc[str])
+ ifnoerr (call mw_gwattrs (mw1, daxis, "units_display", Memc[str],
+ SZ_LINE))
+ call mw_swattrs (mw2, 1, "units_display", Memc[str])
+
+ call mw_gltermd (mw1, Memd[lterm1+pdim1], Memd[lterm1], pdim1)
+ call mw_gltermd (mw2, Memd[lterm2+pdim2], Memd[lterm2], pdim2)
+ Memd[lterm2] = Memd[lterm1+daxis-1]
+ Memd[lterm2+pdim2] = Memd[lterm1+pdim1+(pdim1+1)*(daxis-1)]
+ call mw_sltermd (mw2, Memd[lterm2+pdim2], Memd[lterm2], pdim2)
+
+ call mw_gwtermd (mw1, Memd[lterm1], Memd[lterm1+pdim1],
+ Memd[lterm1+2*pdim1], pdim1)
+ call mw_gwtermd (mw2, Memd[lterm2], Memd[lterm2+pdim2],
+ Memd[lterm2+2*pdim2], pdim2)
+ Memd[lterm2] = Memd[lterm1+daxis-1]
+ Memd[lterm2+pdim2] = Memd[lterm1+pdim1+daxis-1]
+ Memd[lterm2+2*pdim2] = Memd[lterm1+2*pdim1+(pdim1+1)*(daxis-1)]
+ call mw_swtermd (mw2, Memd[lterm2], Memd[lterm2+pdim2],
+ Memd[lterm2+2*pdim2], pdim2)
+
+ # Set the EQUISPEC SMW.
+ IM_LEN(im,2) = SMW_NSPEC(smw)
+ IM_LEN(im,3) = SMW_NBANDS(smw)
+ call smw_open (mw2, NULL, im)
+ do i = 1, SMW_NSPEC(smw) {
+ call smw_gwattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+ call smw_swattrs (mw2, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, Memc[coeff])
+ }
+ call mfree (coeff, TY_CHAR)
+
+ call smw_close (smw)
+ smw = mw2
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwnewcopy.x b/noao/onedspec/smw/smwnewcopy.x
new file mode 100644
index 00000000..230ed205
--- /dev/null
+++ b/noao/onedspec/smw/smwnewcopy.x
@@ -0,0 +1,58 @@
+include <smw.h>
+
+
+# SMW_NEWCOPY -- Make a new copy of an SMW structure.
+
+pointer procedure smw_newcopy (smw)
+
+pointer smw #I SMW pointer to copy
+pointer new #O SMW copy
+
+int i, nspec
+pointer mw_newcopy(), mw_sctran()
+
+begin
+ call calloc (new, SMW_LEN(SMW_NMW(smw)), TY_STRUCT)
+ call amovi (Memi[smw], Memi[new], SMW_LEN(SMW_NMW(smw)))
+
+ if (SMW_APID(smw) != NULL) {
+ call malloc (SMW_APID(new), SZ_LINE, TY_CHAR)
+ call strcpy (Memc[SMW_APID(smw)], Memc[SMW_APID(new)], SZ_LINE)
+ }
+
+ nspec = SMW_NSPEC(smw)
+ if (SMW_APS(smw) != NULL) {
+ call malloc (SMW_APS(new), nspec, TY_INT)
+ call amovi (Memi[SMW_APS(smw)], Memi[SMW_APS(new)], nspec)
+ }
+ if (SMW_BEAMS(smw) != NULL) {
+ call malloc (SMW_BEAMS(new), nspec, TY_INT)
+ call amovi (Memi[SMW_BEAMS(smw)], Memi[SMW_BEAMS(new)], nspec)
+ }
+ if (SMW_APLOW(smw) != NULL) {
+ call malloc (SMW_APLOW(new), 2*nspec, TY_REAL)
+ call amovr (Memr[SMW_APLOW(smw)], Memr[SMW_APLOW(new)], 2*nspec)
+ }
+ if (SMW_APHIGH(smw) != NULL) {
+ call malloc (SMW_APHIGH(new), 2*nspec, TY_REAL)
+ call amovr (Memr[SMW_APHIGH(smw)], Memr[SMW_APHIGH(new)], 2*nspec)
+ }
+ if (SMW_APIDS(smw) != NULL) {
+ call calloc (SMW_APIDS(new), nspec, TY_POINTER)
+ do i = 0, nspec-1 {
+ if (Memi[SMW_APIDS(smw)+i] != NULL) {
+ call malloc (Memi[SMW_APIDS(new)+i], SZ_LINE, TY_CHAR)
+ call strcpy (Memc[Memi[SMW_APIDS(smw)+i]],
+ Memc[Memi[SMW_APIDS(new)+i]], SZ_LINE)
+ }
+ }
+ }
+
+ do i = 0, SMW_NMW(smw)-1
+ SMW_MW(new,i) = mw_newcopy (SMW_MW(smw,i))
+
+ if (SMW_PDIM(smw) > 1)
+ SMW_CTLP(new) = mw_sctran (SMW_MW(new,0), "logical", "physical", 2)
+
+ return (new)
+end
diff --git a/noao/onedspec/smw/smwoldms.x b/noao/onedspec/smw/smwoldms.x
new file mode 100644
index 00000000..6640641f
--- /dev/null
+++ b/noao/onedspec/smw/smwoldms.x
@@ -0,0 +1,101 @@
+include <mwset.h>
+include <smw.h>
+
+
+# SMW_OLDMS -- Convert old multispec format into MULTISPEC SMW.
+
+procedure smw_oldms (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U Input MWCS pointer, output SMW pointer
+
+int i, j, k, nchar, ap, beam, dtype, nw, axes[2]
+double w1, dw, z
+real aplow[2], aphigh[2]
+pointer sp, key, val, lterm, mw, mw_open()
+int imgeti(), mw_stati(), ctoi(), ctor(), ctod(), imofnlu(), imgnfn()
+errchk imgstr, mw_gltermd, mw_sltermd
+data axes/1,2/
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (val, SZ_LINE, TY_CHAR)
+ call salloc (lterm, 12, TY_DOUBLE)
+
+ # Set the basic multispec MWCS
+ i = mw_stati (smw, MW_NDIM)
+ j = max (2, i)
+ mw = mw_open (NULL, j)
+ call mw_newsystem (mw, "multispec", j)
+ call mw_swtype (mw, axes, 2, "multispec", "")
+ if (j > 2)
+ call mw_swtype (mw, 3, 1, "linear", "")
+ call mw_gltermd (smw, Memd[lterm+j], Memd[lterm], i)
+ if (i == 1) {
+ Memd[lterm+1] = 0.
+ Memd[lterm+3] = 0.
+ Memd[lterm+4] = 0.
+ Memd[lterm+5] = 1.
+ }
+ call mw_sltermd (mw, Memd[lterm+j], Memd[lterm], j)
+
+ iferr (dtype = imgeti (im, "DC-FLAG"))
+ dtype = -1
+ else {
+ call mw_swattrs (mw, 1, "label", "Wavelength")
+ call mw_swattrs (mw, 1, "units", "Angstroms")
+ }
+
+ call mw_close (smw)
+ smw = mw
+
+ # Set the SMW data structure.
+ call smw_open (smw, NULL, im)
+ do i = 1, SMW_NSPEC(smw) {
+ call smw_mw (smw, i, 1, mw, j, k)
+ call sprintf (Memc[key], SZ_FNAME, "APNUM%d")
+ call pargi (j)
+ call imgstr (im, Memc[key], Memc[val], SZ_LINE)
+ call imdelf (im, Memc[key])
+
+ k = 1
+ nchar = ctoi (Memc[val], k, ap)
+ nchar = ctoi (Memc[val], k, beam)
+ nchar = ctod (Memc[val], k, w1)
+ nchar = ctod (Memc[val], k, dw)
+ nchar = ctoi (Memc[val], k, nw)
+ if (ctor (Memc[val], k, aplow[1]) == 0)
+ aplow[1] = INDEF
+ if (ctor (Memc[val], k, aphigh[1]) == 0)
+ aphigh[1] = INDEF
+ z = 0.
+
+ k = dtype
+ if (k==1 && (abs(w1)>20. || abs(w1+(nw-1)*dw)>20.))
+ k = 0
+ call smw_swattrs (smw, i, 1, ap, beam, k, w1, dw, nw, z,
+ aplow, aphigh, "")
+
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (j)
+ ifnoerr (call imgstr (im, Memc[key], Memc[val], SZ_LINE)) {
+ call smw_sapid (smw, i, 1, Memc[val])
+ call imdelf (im, Memc[key])
+ }
+ }
+
+ # Delete old parameters
+ i = imofnlu (im,
+ "DISPAXIS,APFORMAT,BEAM-NUM,DC-FLAG,W0,WPC,NP1,NP2")
+ while (imgnfn (i, Memc[key], SZ_FNAME) != EOF) {
+ iferr (call imdelf (im, Memc[key]))
+ ;
+ }
+ call imcfnl (i)
+
+ # Update MWCS
+ call smw_saveim (smw, im)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwonedspec.x b/noao/onedspec/smw/smwonedspec.x
new file mode 100644
index 00000000..b7d2fa6a
--- /dev/null
+++ b/noao/onedspec/smw/smwonedspec.x
@@ -0,0 +1,109 @@
+include <imhdr.h>
+include <smw.h>
+
+
+# SMW_ONEDSPEC -- Convert old "onedspec" format to EQUISPEC.
+
+procedure smw_onedspec (im, smw)
+
+pointer im #I IMIO pointer
+pointer smw #U MWCS pointer input SMW pointer output
+
+int i, dtype, ap, beam, nw, imgeti(), imofnlu(), imgnfn()
+real aplow[2], aphigh[2], imgetr(), mw_c1tranr()
+double ltm, ltv, r, w, dw, z, imgetd()
+pointer sp, key, mw, ct, mw_openim(), mw_sctran()
+bool fp_equald()
+errchk smw_open, smw_saxes, mw_gwtermd, mw_sctran
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+
+ # Convert old W0/WPC keywords if needed.
+ mw = smw
+ iferr (w = imgetd (im, "CRVAL1")) {
+ ifnoerr (w = imgetd (im, "W0")) {
+ dw = imgetd (im, "WPC")
+ iferr (ltm = imgetd (im, "LTM1_1"))
+ ltm = 1
+ iferr (ltv = imgetd (im, "LTV1"))
+ ltv = 0
+ r = ltm + ltv
+ dw = dw / ltm
+ call imaddd (im, "CRPIX1", r)
+ call imaddd (im, "CRVAL1", w)
+ call imaddd (im, "CD1_1", dw)
+ call imaddd (im, "CDELT1", dw)
+ call mw_close(mw)
+ mw = mw_openim (im)
+ }
+ }
+
+ # Get dispersion and determine number of valid pixels.
+ call mw_gwtermd (mw, r, w, dw, 1)
+ w = w - (r - 1) * dw
+ r = 1
+ call mw_swtermd (mw, r, w, dw, 1)
+ ct = mw_sctran (mw, "logical", "physical", 1)
+ nw = max (mw_c1tranr (ct, 1.), mw_c1tranr (ct, real (IM_LEN(im,1))))
+ call mw_ctfree (ct)
+
+ iferr (dtype = imgeti (im, "DC-FLAG")) {
+ if (fp_equald (1D0, w) || fp_equald (1D0, dw))
+ dtype = DCNO
+ else
+ dtype = DCLINEAR
+ }
+ if (dtype==DCLOG) {
+ if (abs(w)>20. || abs(w+(nw-1)*dw)>20.)
+ dtype = DCLINEAR
+ else {
+ w = 10D0 ** w
+ dw = w * (10D0 ** ((nw-1)*dw) - 1) / (nw - 1)
+ }
+ }
+
+ # Convert to EQUISPEC system.
+ call mw_swattrs (mw, 0, "system", "equispec")
+ if (dtype != DCNO) {
+ iferr (call mw_gwattrs (mw, 1, "label", Memc[key], SZ_FNAME)) {
+ iferr (call mw_gwattrs (mw, 1, "units", Memc[key], SZ_FNAME)) {
+ call mw_swattrs (mw, 1, "units", "angstroms")
+ call mw_swattrs (mw, 1, "label", "Wavelength")
+ }
+ }
+ }
+
+ # Set the SMW data structure.
+ call smw_open (smw, NULL, im)
+
+ # Determine the aperture parameters.
+ iferr (beam = imgeti (im, "BEAM-NUM"))
+ beam = 1
+ iferr (ap = imgeti (im, "APNUM"))
+ ap = beam
+ iferr (aplow[1] = imgetr (im, "APLOW"))
+ aplow[1] = INDEF
+ iferr (aphigh[1] = imgetr (im, "APHIGH"))
+ aphigh[1] = INDEF
+ iferr (z = imgetd (im, "DOPCOR"))
+ z = 0.
+
+ call smw_swattrs (smw, 1, 1, ap, beam, dtype, w, dw, nw, z,
+ aplow, aphigh, "")
+
+ # Delete old parameters
+ i = imofnlu (im,
+ "BEAM-NUM,APNUM,APLOW,APHIGH,DOPCOR,DC-FLAG,W0,WPC,NP1,NP2")
+ while (imgnfn (i, Memc[key], SZ_FNAME) != EOF) {
+ iferr (call imdelf (im, Memc[key]))
+ ;
+ }
+ call imcfnl (i)
+
+ # Update MWCS
+ call smw_saveim (smw, im)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwopen.x b/noao/onedspec/smw/smwopen.x
new file mode 100644
index 00000000..782c8749
--- /dev/null
+++ b/noao/onedspec/smw/smwopen.x
@@ -0,0 +1,70 @@
+include <smw.h>
+
+
+# SMW_OPEN -- Open SMW structure.
+# The basic MWCS pointer and a template SMW pointer or image is input
+# and the SMW pointer is returned in its place.
+
+procedure smw_open (mw, smw1, im)
+
+pointer mw #U MWCS pointer input and SMW pointer output
+pointer smw1 #I Template SMW pointer
+pointer im #I Template IMIO pointer
+
+int i, nspec, nmw, format, strdic()
+pointer sp, sys, smw, mw_sctran(), mw_newcopy()
+errchk smw_daxis, smw_saxes, mw_sctran
+
+begin
+ call smark (sp)
+ call salloc (sys, SZ_FNAME, TY_CHAR)
+
+ call mw_gwattrs (mw, 0, "system", Memc[sys], SZ_FNAME)
+ format = strdic (Memc[sys], Memc[sys], SZ_FNAME, SMW_FORMATS)
+
+ call calloc (smw, SMW_LEN(1), TY_STRUCT)
+ call malloc (SMW_APID(smw), SZ_LINE, TY_CHAR)
+ SMW_FORMAT(smw) = format
+ SMW_DTYPE(smw) = INDEFI
+ SMW_NMW(smw) = 1
+ SMW_MW(smw,0) = mw
+
+ switch (format) {
+ case SMW_ND:
+ call smw_daxis (smw, im, INDEFI, INDEFI, INDEFI)
+ call smw_saxes (smw, smw1, im)
+
+ case SMW_ES:
+ call smw_saxes (smw, smw1, im)
+
+ nspec = SMW_NSPEC(smw)
+ call calloc (SMW_APS(smw), nspec, TY_INT)
+ call calloc (SMW_BEAMS(smw), nspec, TY_INT)
+ call calloc (SMW_APLOW(smw), 2*nspec, TY_REAL)
+ call calloc (SMW_APHIGH(smw), 2*nspec, TY_REAL)
+ call calloc (SMW_APIDS(smw), nspec, TY_POINTER)
+ if (SMW_PDIM(smw) > 1)
+ SMW_CTLP(smw) = mw_sctran (mw, "logical", "physical", 2)
+
+ case SMW_MS:
+ call smw_saxes (smw, smw1, im)
+
+ nspec = SMW_NSPEC(smw)
+ call calloc (SMW_APIDS(smw), nspec, TY_POINTER)
+ if (SMW_PDIM(smw) > 1)
+ SMW_CTLP(smw) = mw_sctran (mw, "logical", "physical", 2)
+
+ nmw = 1 + (nspec - 1) / SMW_NSPLIT
+ if (nmw > 1) {
+ call realloc (smw, SMW_LEN(nmw), TY_STRUCT)
+ call calloc (SMW_APS(smw), nspec, TY_INT)
+ }
+ do i = 1, nmw-1
+ SMW_MW(smw,i) = mw_newcopy (mw)
+ SMW_NMW(smw) = nmw
+ }
+
+ mw = smw
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwopenim.x b/noao/onedspec/smw/smwopenim.x
new file mode 100644
index 00000000..468f09a7
--- /dev/null
+++ b/noao/onedspec/smw/smwopenim.x
@@ -0,0 +1,69 @@
+include <imhdr.h>
+include <imio.h>
+include <mwset.h>
+
+define SYSTEMS "|equispec|multispec|physical|image|world|linear|"
+
+
+# SMW_OPENIM -- Open the spectral MWCS for various input formats.
+
+pointer procedure smw_openim (im)
+
+pointer im #I Image pointer
+pointer mw #O MWCS pointer
+
+pointer sp, system, mw_openim()
+bool streq()
+int i, wcsdim, sys, strdic(), mw_stati()
+errchk mw_openim, smw_oldms, smw_linear
+
+begin
+ call smark (sp)
+ call salloc (system, SZ_FNAME, TY_CHAR)
+
+ # Workaround for truncation of header during image header copy.
+ IM_HDRLEN(im) = IM_LENHDRMEM(im)
+
+ # Force higher dimensions to length 1.
+ do i = IM_NDIM(im) + 1, 3
+ IM_LEN(im,i) = 1
+
+ mw = mw_openim (im)
+ call mw_seti (mw, MW_USEAXMAP, NO)
+ wcsdim = mw_stati (mw, MW_NDIM)
+ call mw_gwattrs (mw, 0, "system", Memc[system], SZ_FNAME)
+ sys = strdic (Memc[system], Memc[system], SZ_FNAME, SYSTEMS)
+
+ # Set various input systems.
+ switch (sys) {
+ case 1:
+ call smw_equispec (im, mw)
+ case 2:
+ call smw_multispec (im, mw)
+ default:
+ if (sys == 0) {
+ call eprintf (
+ "WARNING: Unknown coordinate system `%s' - assuming `linear'.\n")
+ call pargstr (Memc[system])
+ } else if (sys == 3)
+ call mw_newsystem (mw, "image", wcsdim)
+
+ # Old "multispec" format.
+ ifnoerr (call imgstr (im, "APFORMAT", Memc[system], SZ_FNAME)) {
+ if (streq (Memc[system], "onedspec"))
+ call smw_onedspec (im, mw)
+ else
+ call smw_oldms (im, mw)
+
+ # Old "onedspec" format or other 1D image.
+ } else if (wcsdim == 1) {
+ call smw_onedspec (im, mw)
+
+ # N-dimensional image.
+ } else
+ call smw_nd (im, mw)
+ }
+
+ call sfree (sp)
+ return (mw)
+end
diff --git a/noao/onedspec/smw/smwsapid.x b/noao/onedspec/smw/smwsapid.x
new file mode 100644
index 00000000..1bdf30a8
--- /dev/null
+++ b/noao/onedspec/smw/smwsapid.x
@@ -0,0 +1,40 @@
+include <smw.h>
+
+
+# SMW_SAPID -- Set aperture id.
+
+procedure smw_sapid (smw, index1, index2, apid)
+
+pointer smw #I SMW pointer
+int index1 #I Spectrum index
+int index2 #I Spectrum index
+char apid[ARB] #I Aperture id
+
+pointer ptr
+bool streq()
+errchk malloc
+
+begin
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call strcpy (apid, Memc[SMW_APID(smw)], SZ_LINE)
+ case SMW_ES, SMW_MS:
+ if (index1 < 0 || index1 > SMW_NSPEC(smw))
+ call error (1, "smw_sapid: index out of bounds")
+
+ if (index1 == 0)
+ call strcpy (apid, Memc[SMW_APID(smw)], SZ_LINE)
+
+ else {
+ ptr = Memi[SMW_APIDS(smw)+index1-1]
+ if (streq (apid, Memc[SMW_APID(smw)]))
+ call mfree (ptr, TY_CHAR)
+ else {
+ if (ptr == NULL)
+ call malloc (ptr, SZ_LINE, TY_CHAR)
+ call strcpy (apid, Memc[ptr], SZ_LINE)
+ }
+ Memi[SMW_APIDS(smw)+index1-1] = ptr
+ }
+ }
+end
diff --git a/noao/onedspec/smw/smwsaveim.x b/noao/onedspec/smw/smwsaveim.x
new file mode 100644
index 00000000..892a3319
--- /dev/null
+++ b/noao/onedspec/smw/smwsaveim.x
@@ -0,0 +1,251 @@
+include <imhdr.h>
+include <imio.h>
+include <smw.h>
+
+
+# SMW_SAVEIM -- Save spectral WCS in image header.
+# The input and output formats are EQUISPEC and MULTISPEC. A split input
+# MULTISPEC WCS is first merged to a single EQUISPEC or MULTISPEC WCS.
+# An input MULTISPEC WCS is converted to EQUISPEC output if possible.
+
+procedure smw_saveim (smw, im)
+
+pointer smw # SMW pointer
+pointer im # Image pointer
+
+int i, j, format, nl, pdim, pdim1, beam, dtype, dtype1, nw, nw1
+int ap, axes[3]
+real aplow[2], aphigh[2]
+double v, m, w1, dw, z, w11, dw1, z1
+pointer sp, key, str1, str2, axmap, lterm, coeff, mw, mw1
+
+bool strne(), fp_equald()
+int imaccf(), imgeti()
+pointer mw_open()
+errchk smw_merge, imdelf
+data axes/1,2,3/
+
+begin
+ call smark (sp)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (str1, SZ_LINE, TY_CHAR)
+ call salloc (str2, SZ_LINE, TY_CHAR)
+ call salloc (axmap, 6, TY_INT)
+ call salloc (lterm, 15, TY_DOUBLE)
+ coeff = NULL
+
+ # Merge split WCS into a single WCS.
+ call smw_merge (smw)
+
+ mw = SMW_MW(smw,0)
+ pdim = SMW_PDIM(smw)
+ format = SMW_FORMAT(smw)
+ if (IM_NDIM(im) == 1)
+ nl = 1
+ else
+ nl = IM_LEN(im,2)
+
+ # If writing to an existing image we must follow IM_NPHYSDIM
+ # but in a NEW_COPY header we may really want a lower dimension.
+ # Since IM_NPHYSDIM is outside the interface we only violate
+ # it here and use a temporary keyword to communicate from the
+ # routine setting up the WCS.
+
+ pdim1 = max (IM_NDIM(im), IM_NPHYSDIM(im))
+ ifnoerr (i = imgeti (im, "SMW_NDIM")) {
+ pdim1 = i
+ call imdelf (im, "SMW_NDIM")
+ }
+
+ # Check if MULTISPEC WCS can be converted to EQUISPEC.
+ if (format == SMW_MS) {
+ format = SMW_ES
+ do i = 1, nl {
+ call smw_gwattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+ if (i == 1) {
+ dtype1 = dtype
+ w11 = w1
+ dw1 = dw
+ z1 = z
+ nw1 = nw
+ }
+ if (dtype>1||dtype!=dtype1||!fp_equald(w1,w11)||
+ !fp_equald(dw,dw1)||nw!=nw1||!fp_equald(z,z1)) {
+ format = SMW_MS
+ break
+ }
+ }
+ }
+
+ # Save WCS in desired format.
+ switch (format) {
+ case SMW_ND:
+ if (SMW_DTYPE(smw) != -1)
+ call imaddi (im, "DC-FLAG", SMW_DTYPE(smw))
+ else if (imaccf (im, "DC-FLAG") == YES)
+ call imdelf (im, "DC-FLAG")
+ if (imaccf (im, "DISPAXIS") == YES)
+ call imaddi (im, "DISPAXIS", SMW_PAXIS(smw,1))
+
+ call smw_gapid (smw, 1, 1, IM_TITLE(im), SZ_IMTITLE)
+ call mw_saveim (mw, im)
+
+ case SMW_ES:
+ # Save aperture information.
+ do i = 1, nl {
+ call smw_gwattrs (smw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+ if (i < 1000)
+ call sprintf (Memc[key], SZ_FNAME, "APNUM%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "AP%d")
+ call pargi (i)
+ call sprintf (Memc[str1], SZ_LINE, "%d %d")
+ call pargi (ap)
+ call pargi (beam)
+ if (!IS_INDEF(aplow[1]) || !IS_INDEF(aphigh[1])) {
+ call sprintf (Memc[str2], SZ_LINE, " %.2f %.2f")
+ call pargr (aplow[1])
+ call pargr (aphigh[1])
+ call strcat (Memc[str2], Memc[str1], SZ_LINE)
+ if (!IS_INDEF(aplow[2]) || !IS_INDEF(aphigh[2])) {
+ call sprintf (Memc[str2], SZ_LINE, " %.2f %.2f")
+ call pargr (aplow[2])
+ call pargr (aphigh[2])
+ call strcat (Memc[str2], Memc[str1], SZ_LINE)
+ }
+ }
+ call imastr (im, Memc[key], Memc[str1])
+ if (i == 1) {
+ iferr (call imdelf (im, "APID1"))
+ ;
+ }
+ call smw_gapid (smw, i, 1, Memc[str1], SZ_LINE)
+ if (Memc[str1] != EOS) {
+ if (strne (Memc[str1], IM_TITLE(im))) {
+ if (nl == 1) {
+ call imastr (im, "MSTITLE", IM_TITLE(im))
+ call strcpy (Memc[str1], IM_TITLE(im), SZ_IMTITLE)
+ } else {
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (i)
+ call imastr (im, Memc[key], Memc[str1])
+ }
+ }
+ }
+ }
+
+ # Delete unnecessary aperture information.
+ do i = nl+1, ARB {
+ if (i < 1000)
+ call sprintf (Memc[key], SZ_FNAME, "APNUM%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "AP%d")
+ call pargi (i)
+ iferr (call imdelf (im, Memc[key]))
+ break
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (i)
+ iferr (call imdelf (im, Memc[key]))
+ ;
+ }
+
+ # Add dispersion parameters to image.
+ if (dtype != -1)
+ call imaddi (im, "DC-FLAG", dtype)
+ else if (imaccf (im, "DC-FLAG") == YES)
+ call imdelf (im, "DC-FLAG")
+ if (nw < IM_LEN(im,1))
+ call imaddi (im, "NP2", nw)
+ else if (imaccf (im, "NP2") == YES)
+ call imdelf (im, "NP2")
+
+ # Setup EQUISPEC WCS.
+
+ mw1 = mw_open (NULL, pdim1)
+ call mw_newsystem (mw1, "equispec", pdim1)
+ call mw_swtype (mw1, axes, pdim1, "linear", "")
+ ifnoerr (call mw_gwattrs (mw, 1, "label", Memc[str1], SZ_LINE))
+ call mw_swattrs (mw1, 1, "label", Memc[str1])
+ ifnoerr (call mw_gwattrs (mw, 1, "units", Memc[str1], SZ_LINE))
+ call mw_swattrs (mw1, 1, "units", Memc[str1])
+ ifnoerr (call mw_gwattrs (mw, 1, "units_display", Memc[str1],
+ SZ_LINE))
+ call mw_swattrs (mw1, 1, "units_display", Memc[str1])
+ call mw_gltermd (mw, Memd[lterm+pdim], Memd[lterm], pdim)
+ v = Memd[lterm]
+ m = Memd[lterm+pdim]
+ call mw_gltermd (mw1, Memd[lterm+pdim1], Memd[lterm], pdim1)
+ Memd[lterm] = v
+ Memd[lterm+pdim1] = m
+ call mw_sltermd (mw1, Memd[lterm+pdim1], Memd[lterm], pdim1)
+ call mw_gwtermd (mw1, Memd[lterm], Memd[lterm+pdim1],
+ Memd[lterm+2*pdim1], pdim1)
+ Memd[lterm] = 1.
+ w1 = w1 / (1 + z)
+ dw = dw / (1 + z)
+ if (dtype == DCLOG) {
+ dw = log10 ((w1 + (nw - 1) * dw) / w1) / (nw - 1)
+ w1 = log10 (w1)
+ }
+ Memd[lterm+pdim1] = w1
+ Memd[lterm+2*pdim1] = dw
+ call mw_swtermd (mw1, Memd[lterm], Memd[lterm+pdim1],
+ Memd[lterm+2*pdim1], pdim1)
+ call mw_saveim (mw1, im)
+ call mw_close (mw1)
+
+ case SMW_MS:
+ # Delete any APNUM keywords. If there is only one spectrum
+ # define the axis mapping.
+
+ do j = 1, ARB {
+ if (j < 1000)
+ call sprintf (Memc[key], SZ_FNAME, "APNUM%d")
+ else
+ call sprintf (Memc[key], SZ_FNAME, "AP%d")
+ call pargi (j)
+ iferr (call imdelf (im, Memc[key]))
+ break
+ }
+ if (IM_NDIM(im) == 1) {
+ call aclri (Memi[axmap], 2*pdim)
+ Memi[axmap] = 1
+ call mw_saxmap (mw, Memi[axmap], Memi[axmap+pdim], pdim)
+ }
+
+ # Set aperture ids.
+ do i = 1, nl {
+ if (i == 1) {
+ iferr (call imdelf (im, "APID1"))
+ ;
+ }
+ call smw_gapid (smw, i, 1, Memc[str1], SZ_LINE)
+ if (Memc[str1] != EOS) {
+ if (strne (Memc[str1], IM_TITLE(im))) {
+ if (nl == 1) {
+ call imastr (im, "MSTITLE", IM_TITLE(im))
+ call strcpy (Memc[str1], IM_TITLE(im), SZ_IMTITLE)
+ } else {
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (i)
+ call imastr (im, Memc[key], Memc[str1])
+ }
+ }
+ }
+ }
+
+ do i = nl+1, ARB {
+ call sprintf (Memc[key], SZ_FNAME, "APID%d")
+ call pargi (i)
+ iferr (call imdelf (im, Memc[key]))
+ break
+ }
+
+ call mw_saveim (mw, im)
+ }
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwsaxes.x b/noao/onedspec/smw/smwsaxes.x
new file mode 100644
index 00000000..f5e31b63
--- /dev/null
+++ b/noao/onedspec/smw/smwsaxes.x
@@ -0,0 +1,247 @@
+include <imhdr.h>
+include <mwset.h>
+include <smw.h>
+
+
+# SMW_SAXES -- Set axes parameters based on previously set dispersion axis.
+# If the dispersion axis has been excluded for NDSPEC allow another axis to
+# be chosen with a warning. For EQUISPEC and MULTISPEC require the dispersion
+# to be 1 and also to be present.
+
+procedure smw_saxes (smw, smw1, im)
+
+pointer smw #I SMW pointer
+pointer smw1 #I Template SMW pointer
+pointer im #I Template IMIO pointer
+
+int i, pdim, ldim, paxis, laxis, nw, dtype, nspec
+real smw_c1tranr()
+double w1, dw
+pointer sp, str, axno, axval, r, w, cd, mw, ct, smw_sctran()
+int mw_stati(), imgeti()
+bool streq(), fp_equald()
+errchk smw_sctran
+
+begin
+ # If a template SMW pointer is specified just copy the axes parameters.
+ if (smw1 != NULL) {
+ call strcpy (Memc[SMW_APID(smw1)], Memc[SMW_APID(smw)], SZ_LINE)
+ SMW_NSPEC(smw) = SMW_NSPEC(smw1)
+ SMW_NBANDS(smw) = SMW_NBANDS(smw1)
+ SMW_TRANS(smw) = SMW_TRANS(smw1)
+ call amovi (SMW_PAXIS(smw1,1), SMW_PAXIS(smw,1), 3)
+ SMW_LDIM(smw) = SMW_LDIM(smw1)
+ call amovi (SMW_LAXIS(smw1,1), SMW_LAXIS(smw,1), 3)
+ call amovi (SMW_LLEN(smw1,1), SMW_LLEN(smw,1), 3)
+ call amovi (SMW_NSUM(smw1,1), SMW_NSUM(smw,1), 2)
+
+ mw = SMW_MW(smw,0)
+ SMW_PDIM(smw) = mw_stati (mw, MW_NDIM)
+ if (SMW_PDIM(smw) > SMW_PDIM(smw1))
+ do i = SMW_PDIM(smw1)+1, SMW_PDIM(smw)
+ SMW_PAXIS(smw,i) = i
+
+ return
+ }
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (axno, 3, TY_INT)
+ call salloc (axval, 3, TY_INT)
+ call aclri (Memi[axno], 3)
+
+ # Determine the dimensions.
+ mw = SMW_MW(smw,0)
+ pdim = mw_stati (mw, MW_NDIM)
+ ldim = IM_NDIM(im)
+ call mw_gaxmap (mw, Memi[axno], Memi[axval], pdim)
+
+ # Set the physical dispersion axis.
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call salloc (r, pdim, TY_DOUBLE)
+ call salloc (w, pdim, TY_DOUBLE)
+ call salloc (cd, pdim*pdim, TY_DOUBLE)
+
+ # Check for a transposed or rotated 2D image.
+ SMW_TRANS(smw) = NO
+ if (pdim == 2) {
+ call mw_gltermd (mw, Memd[cd], Memd[w], pdim)
+ if (Memd[cd] == 0D0 && Memd[cd+3] == 0D0) {
+ Memd[cd] = Memd[cd+1]
+ Memd[cd+1] = 0.
+ Memd[cd+3] = Memd[cd+2]
+ Memd[cd+2] = 0.
+ call mw_sltermd (mw, Memd[cd], Memd[w], pdim)
+ paxis = SMW_PAXIS(smw,1)
+ if (paxis == 1)
+ SMW_PAXIS(smw,1) = 2
+ else
+ SMW_PAXIS(smw,1) = 1
+ SMW_TRANS(smw) = YES
+ } else if (Memd[cd+1] != 0D0 || Memd[cd+2] != 0D0) {
+ Memd[w] = 0
+ Memd[w+1] = 0
+ Memd[cd] = 1
+ Memd[cd+1] = 0
+ Memd[cd+2] = 0
+ Memd[cd+3] = 1
+ call mw_sltermd (mw, Memd[cd], Memd[w], pdim)
+ }
+ }
+
+ # If the dispersion axis is of length 1 or has been excluded find
+ # the first longer axis and print a warning.
+
+ paxis = SMW_PAXIS(smw,1)
+ i = max (1, min (pdim, paxis))
+ laxis = max (1, Memi[axno+i-1])
+ if (IM_LEN(im,laxis) == 1)
+ do laxis = 1, ldim
+ if (IM_LEN(im,laxis) != 1)
+ break
+
+ # Determine the number of spectra.
+ nspec = 1
+ do i = 1, ldim
+ if (i != laxis)
+ nspec = nspec * IM_LEN(im,i)
+ SMW_NSPEC(smw) = nspec
+ SMW_NBANDS(smw) = 1
+
+ i = paxis
+ do paxis = 1, pdim
+ if (Memi[axno+paxis-1] == laxis)
+ break
+
+ if (i != paxis && nspec > 1) {
+ call eprintf (
+ "WARNING: Dispersion axis %d not found. Using axis %d.\n")
+ call pargi (i)
+ call pargi (paxis)
+ }
+
+ # Set the dispersion system.
+ call mw_gwtermd (mw, Memd[r], Memd[w], Memd[cd], pdim)
+ if (SMW_TRANS(smw) == YES) {
+ Memd[cd] = Memd[cd+1]
+ Memd[cd+1] = 0.
+ Memd[cd+3] = Memd[cd+2]
+ Memd[cd+2] = 0.
+ }
+ if (pdim == 2 && (Memd[cd+1] != 0D0 || Memd[cd+2] != 0D0)) {
+ iferr (dtype = imgeti (im, "DC-FLAG"))
+ dtype = DCNO
+ if (dtype != DCNO) {
+ call sfree (sp)
+ call error (1,
+ "Rotated, dispersion calibrated spectra are not allowed")
+ }
+ Memd[r] = 0
+ Memd[r+1] = 0
+ Memd[w] = 0
+ Memd[w+1] = 0
+ Memd[cd] = 1
+ Memd[cd+1] = 0
+ Memd[cd+2] = 0
+ Memd[cd+3] = 1
+ }
+ do i = 0, pdim-1 {
+ dw = Memd[cd+i*(pdim+1)]
+ if (dw == 0D0)
+ Memd[cd+i*(pdim+1)] = 1D0
+ }
+ call mw_swtermd (mw, Memd[r], Memd[w], Memd[cd], pdim)
+
+ dw = Memd[cd+(paxis-1)*(pdim+1)]
+ w1 = Memd[w+paxis-1] - (Memd[r+paxis-1] - 1) * dw
+ nw = IM_LEN(im,laxis)
+
+ i = 2 ** (paxis - 1)
+ ct = smw_sctran (smw, "logical", "physical", i)
+ nw = max (smw_c1tranr (ct, 0.5), smw_c1tranr (ct, nw+0.5))
+ call smw_ctfree (ct)
+
+ iferr (dtype = imgeti (im, "DC-FLAG")) {
+ iferr (call mw_gwattrs (mw,paxis,"axtype",Memc[str],SZ_LINE))
+ Memc[str] = EOS
+ if (streq (Memc[str], "ra") || streq (Memc[str], "dec"))
+ dtype = DCNO
+ else if (fp_equald (1D0, w1) || fp_equald (1D0, dw))
+ dtype = DCNO
+ else
+ dtype = DCLINEAR
+ }
+ if (dtype==DCLOG) {
+ if (abs(w1)>20. || abs(w1+(nw-1)*dw)>20.)
+ dtype = DCLINEAR
+ else {
+ w1 = 10D0 ** w1
+ dw = w1 * (10D0 ** ((nw-1)*dw) - 1) / (nw - 1)
+ }
+ }
+
+ if (dtype != DCNO) {
+
+
+ iferr (call mw_gwattrs (mw,paxis,"label",Memc[str],SZ_LINE)) {
+ iferr (call mw_gwattrs(mw,paxis,"units",Memc[str],SZ_LINE)) {
+ call mw_swattrs (mw, paxis, "units", "angstroms")
+ call mw_swattrs (mw, paxis, "label", "Wavelength")
+ }
+ }
+ }
+
+ SMW_DTYPE(smw) = INDEFI
+ call smw_swattrs (smw, 1, 1, INDEFI, INDEFI,
+ dtype, w1, dw, nw, 0D0, INDEFR, INDEFR, "")
+ case SMW_ES, SMW_MS:
+ paxis = 1
+ i = Memi[axno+1]
+ if (i == 0)
+ SMW_NSPEC(smw) = 1
+ else
+ SMW_NSPEC(smw) = IM_LEN(im,i)
+ i = Memi[axno+2]
+ if (i == 0)
+ SMW_NBANDS(smw) = 1
+ else
+ SMW_NBANDS(smw) = IM_LEN(im,i)
+ }
+
+ # Check and set the physical and logical dispersion axes.
+ laxis = Memi[axno+paxis-1]
+ if (laxis == 0) {
+ if (Memi[axval+paxis-1] == 0)
+ laxis = paxis
+ else
+ call error (1, "No dispersion axis")
+ }
+
+ SMW_PDIM(smw) = pdim
+ SMW_LDIM(smw) = ldim
+ SMW_PAXIS(smw,1) = paxis
+ SMW_LAXIS(smw,1) = laxis
+ SMW_LLEN(smw,1) = IM_LEN(im,laxis)
+ SMW_LLEN(smw,2) = 1
+ SMW_LLEN(smw,3) = 1
+
+ # Set the spatial axes.
+ i = 2
+ do laxis = 1, ldim {
+ if (laxis != SMW_LAXIS(smw,1)) {
+ do paxis = 1, pdim
+ if (Memi[axno+paxis-1] == laxis)
+ break
+ SMW_PAXIS(smw,i) = paxis
+ SMW_LAXIS(smw,i) = laxis
+ SMW_LLEN(smw,i) = IM_LEN(im,laxis)
+ i = i + 1
+ }
+ }
+
+ # Set the default title.
+ call smw_sapid (smw, 0, 1, IM_TITLE(im))
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/smwsctran.x b/noao/onedspec/smw/smwsctran.x
new file mode 100644
index 00000000..06f240db
--- /dev/null
+++ b/noao/onedspec/smw/smwsctran.x
@@ -0,0 +1,96 @@
+include <error.h>
+include <smw.h>
+
+
+# SMW_SCTRAN -- Set the SMW coordinate system transformation.
+# This routine sets up the SMW_CT structure which may consist of multiple
+# pointers if the MWCS is split. If the MWCS is not split then the MWCS
+# transformation routine is used directly. However if the MWCS is split then
+# there may need to be an intermediate mapping from the input coordinate to
+# the physical coordinate in which the split MWCS is defined as well as a
+# transformation for each WCS piece.
+
+pointer procedure smw_sctran (smw, system1, system2, axbits)
+
+pointer smw #I SMW pointer
+char system1[ARB] #I Input coordinate system
+char system2[ARB] #I Output coordinate system
+int axbits #I Bitmap defining axes to be transformed
+pointer ct #O SMW CT pointer
+
+int i, cttype, nct, axes[3], naxes, strdic()
+pointer mw_sctran()
+errchk mw_sctran
+
+begin
+ # Determine the coordinate transformation type and setup the structure.
+ cttype = 10 * (strdic (system1, system1, ARB, SMW_CTTYPES) + 1) +
+ strdic (system2, system2, ARB, SMW_CTTYPES) + 1
+
+ nct = SMW_NMW(smw)
+ if (cttype == SMW_LP || cttype == SMW_PL)
+ nct = 1
+
+ call calloc (ct, SMW_CTLEN(nct), TY_STRUCT)
+ SMW_SMW(ct) = smw
+ SMW_CTTYPE(ct) = cttype
+ SMW_NCT(ct) = nct
+
+ # Determine dispersion and aperture axes.
+ call mw_gaxlist (SMW_MW(smw,0), axbits, axes, naxes)
+ do i = 1, naxes {
+ if (axes[i] == SMW_PAXIS(smw,1))
+ SMW_DAXIS(ct) = i
+ if (axes[i] == SMW_PAXIS(smw,2))
+ SMW_AAXIS(ct) = i
+ }
+
+ # If the MWCS is not split use the MWCS transformation directly.
+ if (nct == 1) {
+ iferr (SMW_CT(ct,0) = mw_sctran (SMW_MW(smw,0), system1, system2,
+ axbits)) {
+ switch (cttype) {
+ case SMW_WL, SMW_WP:
+ SMW_CT(ct,0) = mw_sctran (SMW_MW(smw,0), "physical",
+ system2, axbits)
+ case SMW_LW, SMW_PW:
+ SMW_CT(ct,0) = mw_sctran (SMW_MW(smw,0), system1,
+ "physical", axbits)
+ default:
+ call erract (EA_ERROR)
+ }
+ }
+ return(ct)
+ }
+
+ # If there is a split MWCS then setup the intermediary transformation.
+ switch (cttype) {
+ case SMW_LW:
+ SMW_CTL(ct) = mw_sctran (SMW_MW(smw,0), system1, "physical",
+ axbits)
+ do i = 0, nct-1 {
+ iferr (SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), "physical",
+ system2, axbits))
+ SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), "physical",
+ "physical", axbits)
+ }
+ case SMW_WL:
+ do i = 0, nct-1 {
+ iferr (SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), system1,
+ "physical", axbits))
+ SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), "physical",
+ "physical", axbits)
+ }
+ SMW_CTL(ct) = mw_sctran (SMW_MW(smw,0), "physical", system2,
+ axbits)
+ case SMW_PW, SMW_WP:
+ do i = 0, nct-1 {
+ iferr (SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), system1,
+ system2, axbits))
+ SMW_CT(ct,i) = mw_sctran (SMW_MW(smw,i), "physical",
+ system2, axbits)
+ }
+ }
+
+ return (ct)
+end
diff --git a/noao/onedspec/smw/smwsmw.x b/noao/onedspec/smw/smwsmw.x
new file mode 100644
index 00000000..c3870c4a
--- /dev/null
+++ b/noao/onedspec/smw/smwsmw.x
@@ -0,0 +1,21 @@
+include <smw.h>
+
+
+# SMW_SMW -- Set MCWS pointer
+
+procedure smw_smw (smw, line, mw)
+
+pointer smw #I SMW pointer
+int line #I Physical line
+pointer mw #I MWCS pointer
+
+begin
+ if (SMW_NMW(smw) == 1)
+ SMW_MW(smw,0) = mw
+
+ else {
+ if (line < 1 || line > SMW_NSPEC(smw))
+ call error (1, "smw_smw: aperture not found")
+ SMW_MW(smw,(line-1)/SMW_NSPLIT) = mw
+ }
+end
diff --git a/noao/onedspec/smw/smwswattrs.x b/noao/onedspec/smw/smwswattrs.x
new file mode 100644
index 00000000..ff859cfc
--- /dev/null
+++ b/noao/onedspec/smw/smwswattrs.x
@@ -0,0 +1,162 @@
+include <error.h>
+include <smw.h>
+
+
+# SMW_SWATTRS -- Set spectrum attribute parameters
+# This routine has the feature that if the coordinate system of a single
+# spectrum in an EQUISPEC WCS is changed then the image WCS is changed
+# to a MULTISPEC WCS.
+
+procedure smw_swattrs (smw, index1, index2, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff)
+
+pointer smw # SMW pointer
+int index1 # Spectrum index
+int index2 # Spectrum index
+int ap # Aperture number
+int beam # Beam number
+int dtype # Dispersion type
+double w1 # Starting coordinate
+double dw # Coordinate interval
+int nw # Number of valid pixels
+double z # Redshift factor
+real aplow[2], aphigh[2] # Aperture limits
+char coeff[ARB] # Nonlinear coeff string
+
+bool fp_equald()
+int i, j, sz_val, strlen()
+double a, b
+pointer sp, str, val, mw
+errchk smw_mw
+
+define start_ 10
+
+begin
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+start_
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ if (!IS_INDEFI(SMW_DTYPE(smw)) && (!fp_equald(w1,SMW_W1(smw)) ||
+ !fp_equald(dw,SMW_DW(smw)) || !fp_equald(z,SMW_Z(smw)))) {
+ call malloc (val, 15, TY_DOUBLE)
+ mw = SMW_MW(smw,0)
+ i = SMW_PDIM(smw)
+ j = SMW_PAXIS(smw,1)
+ call mw_gwtermd (mw, Memd[val], Memd[val+i], Memd[val+2*i], i)
+ Memd[val+j-1] = 1.
+ switch (dtype) {
+ case DCNO, DCLINEAR:
+ a = w1 / (1 + z)
+ b = dw / (1 + z)
+ case DCLOG:
+ a = log10 (w1 / (1 + z))
+ b = log10 ((w1 + (nw - 1) * dw) / w1) / (nw - 1)
+ case DCFUNC:
+ call error (1,
+ "Nonlinear functions not allowed for NSPEC format")
+ }
+ Memd[val+i+j-1] = a
+ Memd[val+2*i+(i+1)*(j-1)] = b
+ call mw_swtermd (mw, Memd[val], Memd[val+i], Memd[val+2*i], i)
+ call mfree (val, TY_DOUBLE)
+ }
+ SMW_DTYPE(smw) = dtype
+ SMW_NW(smw) = nw
+ SMW_W1(smw) = w1
+ SMW_DW(smw) = dw
+ SMW_Z(smw) = z
+
+ case SMW_ES:
+ # Check for any changes to the dispersion system.
+ if (dtype == DCFUNC) {
+ call smw_esms(smw)
+ goto start_
+ }
+ if (!IS_INDEFI(SMW_DTYPE(smw)) && (dtype != SMW_DTYPE(smw) ||
+ nw != SMW_NW(smw) || !fp_equald(w1,SMW_W1(smw)) ||
+ !fp_equald(dw,SMW_DW(smw)) || !fp_equald(z,SMW_Z(smw)))) {
+ if (SMW_NSPEC(smw) > 1 && index1 > 0) {
+ call smw_esms(smw)
+ goto start_
+ }
+ if (!fp_equald(w1,SMW_W1(smw)) || !fp_equald(dw,SMW_DW(smw)) ||
+ !fp_equald(z,SMW_Z(smw))) {
+ call malloc (val, 15, TY_DOUBLE)
+ mw = SMW_MW(smw,0)
+ i = SMW_PDIM(smw)
+ j = SMW_PAXIS(smw,1)
+ call mw_gwtermd (mw, Memd[val], Memd[val+i],
+ Memd[val+2*i], i)
+ Memd[val+j-1] = 1.
+ switch (dtype) {
+ case DCNO, DCLINEAR:
+ a = w1 / (1 + z)
+ b = dw / (1 + z)
+ case DCLOG:
+ a = log10 (w1 / (1 + z))
+ b = log10 ((w1 + (nw - 1) * dw) / w1) / (nw - 1)
+ }
+ Memd[val+i+j-1] = a
+ Memd[val+2*i+(i+1)*(j-1)] = b
+ call mw_swtermd (mw, Memd[val], Memd[val+i],
+ Memd[val+2*i], i)
+ call mfree (val, TY_DOUBLE)
+ }
+ }
+
+ SMW_DTYPE(smw) = dtype
+ SMW_NW(smw) = nw
+ SMW_W1(smw) = w1
+ SMW_DW(smw) = dw
+ SMW_Z(smw) = z
+
+ if (index1 > 0) {
+ Memi[SMW_APS(smw)+index1-1] = ap
+ Memi[SMW_BEAMS(smw)+index1-1] = beam
+ Memr[SMW_APLOW(smw)+2*index1-2] = aplow[1]
+ Memr[SMW_APHIGH(smw)+2*index1-2] = aphigh[1]
+ Memr[SMW_APLOW(smw)+2*index1-1] = aplow[2]
+ Memr[SMW_APHIGH(smw)+2*index1-1] = aphigh[2]
+ }
+
+ case SMW_MS:
+ # We can't use SPRINTF for the whole string because it can only
+ # handle a limited length and trucates long coefficient strings.
+ # Use STRCAT instead.
+
+ call smw_mw (smw, index1, index2, mw, i, j)
+ sz_val = strlen (coeff) + SZ_LINE
+ call salloc (val, sz_val, TY_CHAR)
+ call sprintf (Memc[str], SZ_LINE, "spec%d")
+ call pargi (i)
+ call sprintf (Memc[val], sz_val,
+ "%d %d %d %.14g %.14g %d %.14g %.2f %.2f")
+ call pargi (ap)
+ call pargi (beam)
+ call pargi (dtype)
+ if (dtype == DCLOG) {
+ call pargd (log10 (w1))
+ call pargd (log10 ((w1+(nw-1)*dw)/w1)/(nw-1))
+ } else {
+ call pargd (w1)
+ call pargd (dw)
+ }
+ call pargi (nw)
+ call pargd (z)
+ call pargr (aplow[1])
+ call pargr (aphigh[1])
+ if (coeff[1] != EOS) {
+ call strcat (" ", Memc[val], sz_val)
+ call strcat (coeff, Memc[val], sz_val)
+ }
+ call mw_swattrs (mw, 2, Memc[str], Memc[val])
+
+ if (SMW_APS(smw) != NULL)
+ Memi[SMW_APS(smw)+index1-1] = ap
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/smw/units.x b/noao/onedspec/smw/units.x
new file mode 100644
index 00000000..f44abb57
--- /dev/null
+++ b/noao/onedspec/smw/units.x
@@ -0,0 +1,529 @@
+include <ctype.h>
+include <error.h>
+include <units.h>
+
+
+# UN_OPEN -- Open units package
+# It is allowed to open an unknown unit type
+
+pointer procedure un_open (units)
+
+char units[ARB] # Units string
+pointer un # Units pointer returned
+
+begin
+ call calloc (un, UN_LEN, TY_STRUCT)
+ iferr (call un_decode (un, units)) {
+ call un_close (un)
+ call erract (EA_ERROR)
+ }
+ return (un)
+end
+
+
+# UN_CLOSE -- Close units package
+
+procedure un_close (un)
+
+pointer un # Units pointer
+
+begin
+ call mfree (un, TY_STRUCT)
+end
+
+
+# UN_COPY -- Copy units pointer
+
+procedure un_copy (un1, un2)
+
+pointer un1, un2 # Units pointers
+
+begin
+ if (un2 == NULL)
+ call malloc (un2, UN_LEN, TY_STRUCT)
+ call amovi (Memi[un1], Memi[un2], UN_LEN)
+end
+
+
+# UN_DECODE -- Decode units string and set up units structure.
+# The main work is done in UN_DECODE1 so that the units string may
+# be recursive; i.e. the units string may contain other units strings.
+# In particular, this is required for the velocity units to specify
+# a reference wavelength.
+
+procedure un_decode (un, units)
+
+pointer un # Units pointer
+char units[ARB] # Units string
+
+bool streq()
+pointer sp, units1, temp, un1, un2
+errchk un_decode1, un_ctranr
+
+begin
+ if (streq (units, UN_USER(un)))
+ return
+
+ call smark (sp)
+ call salloc (units1, SZ_LINE, TY_CHAR)
+ call salloc (temp, UN_LEN, TY_STRUCT)
+
+ # Save a copy to restore in case of an error.
+ call un_copy (un, temp)
+
+ iferr {
+ # Decode the primary units
+ call un_decode1 (un, units, Memc[units1], SZ_LINE)
+
+ # Decode velocity reference wavelength if necessary.
+ if (UN_CLASS(un) == UN_VEL || UN_CLASS(un) == UN_DOP) {
+ call salloc (un1, UN_LEN, TY_STRUCT)
+ call un_decode1 (un1, Memc[units1], Memc[units1], SZ_LINE)
+ if (UN_CLASS(un1) == UN_VEL || UN_CLASS(un1) == UN_DOP)
+ call error (1,
+ "Velocity reference units may not be velocity")
+ call salloc (un2, UN_LEN, TY_STRUCT)
+ call un_decode1 (un2, "angstroms", Memc[units1], SZ_LINE)
+ call un_ctranr (un1, un2, UN_VREF(un), UN_VREF(un), 1)
+ }
+ } then {
+ call un_copy (temp, un)
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+
+ call sfree (sp)
+end
+
+
+# UN_DECODE1 -- Decode units string and set up units structure.
+# Return any secondary units string. Unknown unit strings are allowed.
+
+procedure un_decode1 (un, units, units1, sz_units1)
+
+pointer un # Units pointer
+char units[ARB] # Units string
+char units1[sz_units1] # Secondary units string to return
+int sz_units1 # Size of secondary units string
+
+int unlog, uninv, untype
+int i, j, k, nscan(), strdic(), strlen()
+pointer sp, str
+pointer stp, sym, stfind(), strefsbuf()
+
+int class[UN_NUNITS]
+real scale[UN_NUNITS]
+data stp/NULL/
+data class /UN_WAVE,UN_WAVE,UN_WAVE,UN_WAVE,UN_WAVE,UN_WAVE,UN_WAVE,
+ UN_FREQ,UN_FREQ,UN_FREQ,UN_FREQ,UN_VEL,UN_VEL,
+ UN_ENERGY,UN_ENERGY,UN_ENERGY,UN_DOP/
+data scale /UN_ANG,UN_NM,UN_MMIC,UN_MIC,UN_MM,UN_CM,UN_M,UN_HZ,UN_KHZ,
+ UN_MHZ,UN_GHZ,UN_MPS,UN_KPS,UN_EV,UN_KEV,UN_MEV,UN_Z/
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ iferr (call un_abbr (stp))
+ ;
+
+ call strcpy (units, Memc[str], SZ_FNAME)
+ if (stp != NULL) {
+ sym = stfind (stp, Memc[str])
+ if (sym != NULL)
+ call strcpy (Memc[strefsbuf(stp,Memi[sym])],
+ Memc[str], SZ_FNAME)
+ }
+ call strlwr (Memc[str])
+ call sscan (Memc[str])
+ untype = 0
+ unlog = NO
+ uninv = NO
+ do i = 1, 3 {
+ call gargwrd (Memc[str], SZ_FNAME)
+ if (nscan() != i)
+ break
+
+ j = strdic (Memc[str], Memc[str], SZ_FNAME, UN_DIC)
+ for (k=strlen(Memc[str]); k>0 &&
+ (IS_WHITE(Memc[str+k-1]) || Memc[str+k-1]=='\n'); k=k-1)
+ Memc[str+k-1] = EOS
+
+ if (j > UN_NUNITS) {
+ j = j - UN_NUNITS
+ if (j == 1) {
+ if (unlog == YES)
+ break
+ unlog = YES
+ } else if (j == 2) {
+ if (uninv == YES)
+ break
+ uninv = YES
+ }
+ } else {
+ if (class[j] == UN_VEL || class[j] == UN_DOP) {
+ call gargr (UN_VREF(un))
+ call gargstr (units1, sz_units1)
+ if (nscan() != i+2)
+ call error (1, "Error in velocity reference wavelength")
+ } else
+ UN_VREF(un) = 0.
+ untype = j
+ break
+ }
+ }
+
+ if (untype == 0) {
+ UN_TYPE(un) = 0
+ UN_CLASS(un) = UN_UNKNOWN
+ UN_LABEL(un) = EOS
+ call strcpy (units, UN_UNITS(un), SZ_UNITS)
+ } else {
+ UN_TYPE(un) = untype
+ UN_CLASS(un) = class[untype]
+ UN_LOG(un) = unlog
+ UN_INV(un) = uninv
+ UN_SCALE(un) = scale[untype]
+ UN_LABEL(un) = EOS
+ UN_UNITS(un) = EOS
+ call strcpy (units, UN_USER(un), SZ_UNITS)
+
+ if (unlog == YES)
+ call strcat ("Log ", UN_LABEL(un), SZ_UNITS)
+ if (uninv == YES)
+ call strcat ("inverse ", UN_UNITS(un), SZ_UNITS)
+ call strcat (Memc[str], UN_UNITS(un), SZ_UNITS)
+ switch (class[j]) {
+ case UN_WAVE:
+ if (uninv == NO)
+ call strcat ("Wavelength", UN_LABEL(un), SZ_UNITS)
+ else
+ call strcat ("Wavenumber", UN_LABEL(un), SZ_UNITS)
+ case UN_FREQ:
+ call strcat ("Frequency", UN_LABEL(un), SZ_UNITS)
+ case UN_VEL:
+ call strcat ("Velocity", UN_LABEL(un), SZ_UNITS)
+ case UN_ENERGY:
+ call strcat ("Energy", UN_LABEL(un), SZ_UNITS)
+ case UN_DOP:
+ call strcat ("Redshift", UN_LABEL(un), SZ_UNITS)
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# UN_COMPARE -- Compare two units
+
+bool procedure un_compare (un1, un2)
+
+pointer un1, un2 # Units pointers to compare
+bool strne()
+
+begin
+ if (strne (UN_UNITS(un1), UN_UNITS(un2)))
+ return (false)
+ if (strne (UN_LABEL(un1), UN_LABEL(un2)))
+ return (false)
+ if (UN_VREF(un1) != UN_VREF(un2))
+ return (false)
+ return (true)
+end
+
+
+# UN_CTRANR -- Transform units
+# Error is returned if the transform cannot be made
+
+procedure un_ctranr (un1, un2, val1, val2, nvals)
+
+pointer un1 # Input units pointer
+pointer un2 # Output units pointer
+real val1[nvals] # Input values
+real val2[nvals] # Output values
+int nvals # Number of values
+
+int i
+real s, v, z
+bool un_compare()
+
+begin
+ if (un_compare (un1, un2)) {
+ call amovr (val1, val2, nvals)
+ return
+ }
+
+ if (UN_CLASS(un1) == UN_UNKNOWN || UN_CLASS(un2) == UN_UNKNOWN)
+ call error (1, "Cannot convert between selected units")
+
+ call amovr (val1, val2, nvals)
+
+ s = UN_SCALE(un1)
+ if (UN_LOG(un1) == YES)
+ do i = 1, nvals
+ val2[i] = 10. ** val2[i]
+ if (UN_INV(un1) == YES)
+ do i = 1, nvals
+ val2[i] = 1. / val2[i]
+ switch (UN_CLASS(un1)) {
+ case UN_WAVE:
+ do i = 1, nvals
+ val2[i] = val2[i] / s
+ case UN_FREQ:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_VEL:
+ v = UN_VREF(un1)
+ do i = 1, nvals {
+ z = val2[i] / s
+ val2[i] = sqrt ((1 + z) / (1 - z)) * v
+ }
+ case UN_ENERGY:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_DOP:
+ v = UN_VREF(un1)
+ do i = 1, nvals
+ val2[i] = (val2[i] / s + 1) * v
+ }
+
+ s = UN_SCALE(un2)
+ switch (UN_CLASS(un2)) {
+ case UN_WAVE:
+ do i = 1, nvals
+ val2[i] = val2[i] * s
+ case UN_FREQ:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_VEL:
+ v = UN_VREF(un2)
+ do i = 1, nvals {
+ z = (val2[i] / v) ** 2
+ val2[i] = (z - 1) / (z + 1) * s
+ }
+ case UN_ENERGY:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_DOP:
+ v = UN_VREF(un2)
+ do i = 1, nvals
+ val2[i] = (val2[i] / v - 1) * s
+ }
+ if (UN_INV(un2) == YES)
+ do i = 1, nvals
+ val2[i] = 1. / val2[i]
+ if (UN_LOG(un2) == YES)
+ do i = 1, nvals
+ val2[i] = log10 (val2[i])
+end
+
+
+# UN_CHANGER -- Change units
+# Error is returned if the conversion cannot be made
+
+procedure un_changer (un, units, vals, nvals, update)
+
+pointer un # Units pointer (may be changed)
+char units[ARB] # Desired units
+real vals[nvals] # Values
+int nvals # Number of values
+int update # Update units pointer?
+
+bool streq(), un_compare()
+pointer un1, un_open()
+errchk un_open, un_ctranr
+
+begin
+
+ # Check for same unit string
+ if (streq (units, UN_USER(un)))
+ return
+
+ # Check for error in units string, or the same units.
+ un1 = un_open (units)
+ if (un_compare (un1, un)) {
+ call strcpy (units, UN_USER(un), SZ_UNITS)
+ call un_close (un1)
+ return
+ }
+
+ iferr {
+ call un_ctranr (un, un1, vals, vals, nvals)
+ if (update == YES)
+ call un_copy (un1, un)
+ call un_close(un1)
+ } then {
+ call un_close(un1)
+ call erract (EA_ERROR)
+ }
+end
+
+
+# UN_CTRAND -- Transform units
+# Error is returned if the transform cannot be made
+
+procedure un_ctrand (un1, un2, val1, val2, nvals)
+
+pointer un1 # Input units pointer
+pointer un2 # Output units pointer
+double val1[nvals] # Input values
+double val2[nvals] # Output values
+int nvals # Number of values
+
+int i
+double s, v, z
+bool un_compare()
+
+begin
+ if (un_compare (un1, un2)) {
+ call amovd (val1, val2, nvals)
+ return
+ }
+
+ if (UN_CLASS(un1) == UN_UNKNOWN || UN_CLASS(un2) == UN_UNKNOWN)
+ call error (1, "Cannot convert between selected units")
+
+ call amovd (val1, val2, nvals)
+
+ s = UN_SCALE(un1)
+ if (UN_LOG(un1) == YES)
+ do i = 1, nvals
+ val2[i] = 10. ** val2[i]
+ if (UN_INV(un1) == YES)
+ do i = 1, nvals
+ val2[i] = 1. / val2[i]
+ switch (UN_CLASS(un1)) {
+ case UN_WAVE:
+ do i = 1, nvals
+ val2[i] = val2[i] / s
+ case UN_FREQ:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_VEL:
+ v = UN_VREF(un1)
+ do i = 1, nvals {
+ z = val2[i] / s
+ val2[i] = sqrt ((1 + z) / (1 - z)) * v
+ }
+ case UN_ENERGY:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_DOP:
+ v = UN_VREF(un1)
+ do i = 1, nvals
+ val2[i] = (val2[i] / s + 1) * v
+ }
+
+ s = UN_SCALE(un2)
+ switch (UN_CLASS(un2)) {
+ case UN_WAVE:
+ do i = 1, nvals
+ val2[i] = val2[i] * s
+ case UN_FREQ:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_VEL:
+ v = UN_VREF(un2)
+ do i = 1, nvals {
+ z = (val2[i] / v) ** 2
+ val2[i] = (z - 1) / (z + 1) * s
+ }
+ case UN_ENERGY:
+ do i = 1, nvals
+ val2[i] = s / val2[i]
+ case UN_DOP:
+ v = UN_VREF(un2)
+ do i = 1, nvals
+ val2[i] = (val2[i] / v - 1) * s
+ }
+ if (UN_INV(un2) == YES)
+ do i = 1, nvals
+ val2[i] = 1. / val2[i]
+ if (UN_LOG(un2) == YES)
+ do i = 1, nvals
+ val2[i] = log10 (val2[i])
+end
+
+
+# UN_CHANGED -- Change units
+# Error is returned if the conversion cannot be made
+
+procedure un_changed (un, units, vals, nvals, update)
+
+pointer un # Units pointer (may be changed)
+char units[ARB] # Desired units
+double vals[nvals] # Values
+int nvals # Number of values
+int update # Update units pointer?
+
+bool streq(), un_compare()
+pointer un1, un_open()
+errchk un_open, un_ctrand
+
+begin
+
+ # Check for same unit string
+ if (streq (units, UN_USER(un)))
+ return
+
+ # Check for error in units string, or the same units.
+ un1 = un_open (units)
+ if (un_compare (un1, un)) {
+ call strcpy (units, UN_USER(un), SZ_UNITS)
+ call un_close (un1)
+ return
+ }
+
+ iferr {
+ call un_ctrand (un, un1, vals, vals, nvals)
+ if (update == YES)
+ call un_copy (un1, un)
+ call un_close(un1)
+ } then {
+ call un_close(un1)
+ call erract (EA_ERROR)
+ }
+end
+
+
+# UN_ABBR -- Load abbreviations into a symbol table.
+
+procedure un_abbr (stp)
+
+pointer stp #U Symbol table
+
+int fd, open(), fscan(), nscan(), stpstr()
+pointer sp, key, val
+pointer sym, stopen(), stfind(), stenter(), strefsbuf()
+errchk open
+
+begin
+ if (stp != NULL)
+ return
+
+ fd = open (ABBREVIATIONS, READ_ONLY, TEXT_FILE)
+ stp = stopen ("unabbr", 20, 20, 40*SZ_LINE)
+
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (val, SZ_LINE, TY_CHAR)
+
+ while (fscan (fd) != EOF) {
+ call gargwrd (Memc[key], SZ_LINE)
+ call gargwrd (Memc[val], SZ_LINE)
+ if (nscan() != 2)
+ next
+ if (Memc[key] == '#')
+ next
+
+ sym = stfind (stp, Memc[key])
+ if (sym == NULL) {
+ sym = stenter (stp, Memc[key], 1)
+ Memi[sym] = stpstr (stp, Memc[val], SZ_LINE)
+ } else
+ call strcpy (Memc[val], Memc[strefsbuf(stp,Memi[sym])], SZ_LINE)
+ }
+
+ call close (fd)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/specplot.h b/noao/onedspec/specplot.h
new file mode 100644
index 00000000..f4f62ff4
--- /dev/null
+++ b/noao/onedspec/specplot.h
@@ -0,0 +1,49 @@
+# Data structure for each spectrum
+
+define SP_SZNAME 99 # Length of image name
+define SP_SZTITLE 99 # Length of title
+define SP_SZPTYPE 9 # Length of plot type
+define SP_SZULABEL 99 # Length of user label
+define SP_SZLABEL 99 # Length of label
+define SP_LEN 225 # Length of SP structure
+
+define SP_INDEX Memi[$1] # Index
+define SP_SH Memi[$1+1] # Spectrum header
+define SP_NPTS Memi[$1+2] # Number of data points
+define SP_W0 Memr[P2R($1+3)] # Starting wavelength
+define SP_WPC Memr[P2R($1+4)] # Wavelength per pix
+define SP_OMEAN Memr[P2R($1+5)] # Original mean intensity
+define SP_OMIN Memr[P2R($1+6)] # Original minimum intensity
+define SP_OMAX Memr[P2R($1+7)] # Original maximum intensity
+
+define SP_XSCALE Memr[P2R($1+8)] # Wavelength scale
+define SP_XOFFSET Memr[P2R($1+9)] # Wavelength offset
+define SP_SCALE Memr[P2R($1+10)] # Intensity scale
+define SP_OFFSET Memr[P2R($1+11)] # Intensity offset
+define SP_MEAN Memr[P2R($1+12)] # Mean intensity
+define SP_MIN Memr[P2R($1+13)] # Minimum intensity
+define SP_MAX Memr[P2R($1+14)] # Maximum intensity
+define SP_PX Memi[$1+15] # Pointer to wavelengths
+define SP_PY Memi[$1+16] # Pointer to intensities
+define SP_XLPOS Memr[P2R($1+17)] # X label position
+define SP_YLPOS Memr[P2R($1+18)] # Y label position
+define SP_COLOR Memi[$1+19] # Color
+define SP_IMNAME Memc[P2C($1+20)] # Image name
+define SP_IMTITLE Memc[P2C($1+70)] # Title
+define SP_PTYPE Memc[P2C($1+120)] # Plot type
+define SP_ULABEL Memc[P2C($1+125)] # Label
+define SP_LABEL Memc[P2C($1+175)] # Label
+
+define SP_X Memr[SP_PX($1)] # Wavelengths
+define SP_Y Memr[SP_PY($1)] # Intensities
+
+define LABELS "|none|imname|imtitle|index|user|"
+define LABEL_NONE 1 # No labels
+define LABEL_IMNAME 2 # Image name
+define LABEL_IMTITLE 3 # Image title
+define LABEL_INDEX 4 # Index
+define LABEL_USER 5 # No labels
+
+define TRANSFORMS "|none|log|"
+define TRANS_NONE 1 # No transform
+define TRANS_LOG 2 # Log transform
diff --git a/noao/onedspec/specplot.key b/noao/onedspec/specplot.key
new file mode 100644
index 00000000..acb61487
--- /dev/null
+++ b/noao/onedspec/specplot.key
@@ -0,0 +1,134 @@
+ SPECPLOT COMMAND OPTIONS
+
+ SUMMARY
+
+? Help o Reorder v Velocity plot
+a Append spectrum p Position label w Window
+d Delete spectrum q Quit x No scaling
+e Undelete spectrum r Redraw y Offset layout
+f Toggle world/pixel s Shift z Scale layout
+i Insert spectrum t Set X scale
+l Label u Set wavelength
+
+:/title <val> :move[#] <to_index> :ulabel[#|*] <val>
+:/xlabel <val> :offset[#|*] <val> :units[#|*] <val>
+:/xwindow <min max> :ptype[#|*] <val> :velocity[#|*] <val>
+:/ylabel <val> :redshift[#|*] <val> :vshow <file>
+:/ywindow <min max> :scale[#|*] <val> :w0[#|*] <val>
+:color[#|*] <val> :shift[#|*] <val> :wpc[#|*] <val>
+:fraction <val> :show <file> :xlpos[#|*] <val>
+:label <val> :step <val> :ylpos[#|*] <val>
+
+ CURSOR COMMANDS
+
+The indicated spectrum is the one with a point closest to the cursor position.
+
+? - Print help summary
+a - Append a new spectrum following the indicated spectrum
+d - Delete the indicated spectrum
+e - Insert last deleted spectrum before indicated spectrum
+f - Toggle between world coordinates and logical pixel coordinates
+i - Insert a new spectrum before the indicated spectrum
+l - Define the user label at the indicated position
+o - Reorder the spectra to eliminate gaps
+p - Define the label position at the indicated position
+q - Quit
+r - Redraw the plot
+s - Repeatedly shift the indicated spectrum position with the cursor
+ q - Quit shift x - Shift horizontally in velocity
+ s - Shift vertically in scale y - Shift vertically in offset
+ t - Shift horizontally in velocity z - Shift horizontally in velocity
+ and vertically in scale and vertically in offset
+t - Set a wavelength scale using the cursor
+u - Set a wavelength point using the cursor
+v - Set velocity plot with zero point at cursor
+w - Window the plot
+x - Cancel all scales and offsets
+y - Automatically layout the spectra with offsets to common mean
+z - Automatically layout the spectra scaled to common mean
+
+
+ COLON COMMANDS
+
+A command without a value generally shows the current value of the
+parameter while with a value it sets the value of the parameter. The
+show commands print to the terminal unless a file is given. For the
+spectrum parameters the index specification, "[#]", is optional. If
+absent the nearest spectrum to the cursor when the command is given is
+selected except that for the "units" command all spectra are selected.
+The index is either a number or the character *. The latter
+applies the command to all the spectra.
+
+:show <file> Show spectrum parameters (file optional)
+:vshow <file> Show verbose parameters (file optional)
+:step <val> Set or show step
+:fraction <val> Set or show autolayout fraction
+:label <val> Set or show label type
+ (none|imtitle|imname|index|user)
+
+:move[#] <to_index> Move spectrum to new index position
+:shift[#|*] <val> Shift spectra by adding to index
+:w0[#|*] <val> Set or show zero point wavelength
+:wpc[#|*] <val> Set or show wavelength per channel
+:velocity[#|*] <val> Set or show radial velocity (km/s)
+:redshift[#|*] <val> Set or show redshift
+:offset[#|*] <val> Set or show intensity offset
+:scale[#|*] <val> Set or show intensity scale
+:xlpos[#|*] <val> Set or show X label position
+:ylpos[#|*] <val> Set or show Y label position
+:ptype[#|*] <val> Set or show plotting type
+:color[#|*] <val> Set or show color (1-9)
+:ulabel[#|*] <val> Set or show user labels
+:units[#|*] <val> Change coordinate units (see below)
+
+:/title <val> Set the title of the graph
+:/xlabel <val> Set the X label of the graph
+:/ylabel <val> Set the Y label of the graph
+:/xwindow <min max> Set the X graph range
+ (use INDEF for autoscaling)
+:/ywindow <min max> Set the X graph range
+ (use INDEF for autoscaling)
+
+
+Examples:
+ w0 Print value of wavelength zero point
+ w0 4010 Set wavelength zero point of spectrum nearest the cursor
+ w0[3] 4010 Set wavelength zero point of spectrum with index 3
+ w0[*] 4010 Set wavelength zero point of all spectra
+
+
+ UNITS
+
+The units are specified by strings having a unit type from the list
+below along with the possible modifiers, "inverse", to select
+the inverse of the unit and "log" to select logarithmic units.
+The various identifiers may be abbreviated as words but the syntax
+is not sophisticated enough to recognized standard scientific abbreviations
+such as mm for millimeter.
+
+ angstroms - Wavelength in Angstroms
+ nanometers - Wavelength in nanometers
+ millimicrons - Wavelength in millimicrons
+ microns - Wavelength in microns
+ millimeters - Wavelength in millimeters
+ centimeter - Wavelength in centimeters
+ meters - Wavelength in meters
+ hertz - Frequency in hertz (cycles per second)
+ kilohertz - Frequency in kilohertz
+ megahertz - Frequency in megahertz
+ gigahertz - Frequency in gigahertz
+ m/s - Velocity in meters per second
+ km/s - Velocity in kilometers per second
+ ev - Energy in electron volts
+ kev - Energy in kilo electron volts
+ mev - Energy in mega electron volts
+ z - Redshift
+
+The velocity and redshift units require a trailing value and unit defining the
+velocity zero point. For example to plot velocity relative to
+a wavelength of 1 micron the unit string would be:
+
+ km/s 1 micron
+
+The syntax :units[#] km/s <value> <unit> is available to plot different
+(or the same) spectrum with different features at zero velocity.
diff --git a/noao/onedspec/specplot.par b/noao/onedspec/specplot.par
new file mode 100644
index 00000000..c7b7cbce
--- /dev/null
+++ b/noao/onedspec/specplot.par
@@ -0,0 +1,28 @@
+spectra,s,a,,,,List of spectra to plot
+apertures,s,h,"",,,Apertures to plot
+bands,s,h,"1",,,Bands of 3D images to plot
+autolayout,b,h,yes,,,Use automatic layout algorithm?
+autoscale,b,h,yes,,,Scale to common mean for automatic layout?
+fraction,r,h,1.0,,,Fraction of automatic minimum separation step
+units,s,h,"",,,Coordinate units
+transform,s,h,"none","none|log",,Flux transformation
+scale,s,h,1.,,,"Intensity scale (value, @file, keyword)"
+offset,s,h,0.,,,"Intensity offset (value, @file, keyword)"
+step,r,h,0.,,,Default separation step
+ptype,s,h,"1",,,Plotting type
+labels,s,h,"user","none|imname|imtitle|index|user",,Type of labels
+ulabels,s,h,"",,,User labels (file)
+xlpos,r,h,1.02,,,X label position (fraction of range)
+ylpos,r,h,0.0,,,Y label position (fraction of mean)
+sysid,b,h,yes,,,Include system banner and step value?
+yscale,b,h,no,,,Draw Y axis scale?
+title,s,h,"",,,Plot title
+xlabel,s,h,"",,,X axis label
+ylabel,s,h,"",,,Y axis label
+xmin,r,h,INDEF,,,X axis left limit
+xmax,r,h,INDEF,,,X axis right limit
+ymin,r,h,INDEF,,,Y axis bottom limit
+ymax,r,h,INDEF,,,Y axis top limit
+logfile,f,h,"",,,Logfile
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Cursor input
diff --git a/noao/onedspec/specshift.par b/noao/onedspec/specshift.par
new file mode 100644
index 00000000..65cc9e3c
--- /dev/null
+++ b/noao/onedspec/specshift.par
@@ -0,0 +1,4 @@
+spectra,s,a,,,,List of spectra
+shift,r,a,0.,,,Shift to add to dispersion coordinates
+apertures,s,h,"",,,List of apertures to shift
+verbose,b,h,no,,,Print verbose information?
diff --git a/noao/onedspec/splot.par b/noao/onedspec/splot.par
new file mode 100644
index 00000000..a40589f7
--- /dev/null
+++ b/noao/onedspec/splot.par
@@ -0,0 +1,52 @@
+# SPLOT -- Parameter file for spectral plot package
+
+images,s,a,,,,"List of images to plot"
+line,i,q,1,0,,"Image line/aperture to plot"
+band,i,q,1,1,,"Image band to plot"
+
+units,s,h,"",,,"Plotting units"
+options,s,h,"auto wreset",,,"Combination of plotting options:
+auto, zero, xydraw, histogram,
+nosysid, wreset, flip, overplot"
+xmin,r,h,INDEF,,,"Minimum X value of initial graph"
+xmax,r,h,INDEF,,,"Maximum X value of initial graph"
+ymin,r,h,INDEF,,,"Minimum Y value of initial graph"
+ymax,r,h,INDEF,,,"Maximum Y value of initial graph"
+save_file,s,h,"splot.log",,,"File to contain answers"
+graphics,s,h,"stdgraph",,,"Output graphics device"
+cursor,*gcur,h,"",,,"Graphics cursor input
+
+# PARAMETERS FOR ERROR ANALYSIS"
+nerrsample,i,h,0,0,,"Number of error samples (<10 for no errors)"
+sigma0,r,h,INDEF,,,"Constant gaussian noise term (INDEF for no errors)"
+invgain,r,h,INDEF,,,"Inverse gain term (INDEF for no errors)
+
+# PARAMETERS FOR CONTINUUM FITTING"
+function,s,h,"spline3","spline3|legendre|chebyshev|spline1",,"Fitting function"
+order,i,h,1,1,,"Order of fitting function"
+low_reject,r,h,2.,0.,,"Low rejection in sigma of fit"
+high_reject,r,h,4.,0.,,"High rejection in sigma of fit"
+niterate,i,h,10,0,,"Number of rejection iterations"
+grow,r,h,1.,0.,,"Rejection growing radius"
+markrej,b,h,yes,,,"Mark rejected points?
+
+# PARAMETERS FOR OVERPLOTTING STANDARD STAR FLUXES"
+star_name,s,q,,,,"Standard star name"
+mag,r,q,,,,"Magnitude of star"
+magband,s,q,,"U|B|V|R|I|J|H|K|L|Lprime|M",,"Magnitude type"
+teff,s,q,,,,"Effective temperature or spectral type"
+caldir,s,h,)_.caldir,,,"Directory containing calibration data"
+fnuzero,r,h,3.68e-20,,,"Absolute flux zero point
+
+# PARAMETERS USED IN INTERACTIVE QUERIES"
+next_image,s,q,,,,"Next image to plot"
+new_image,s,q,,,,"Image to create"
+overwrite,b,q,,,,"Overwrite image?"
+spec2,s,q,,,,"Spectrum"
+constant,r,q,,,,"Constant to be applied"
+wavelength,r,q,,,,"Dispersion coordinate:"
+linelist,f,q,,,,"File"
+wstart,r,q,,,,"Starting wavelength"
+wend,r,q,,,,"Ending wavelength"
+dw,r,q,,,,"Wavelength per pixel"
+boxsize,i,q,,1,,"Smoothing box size (odd number)"
diff --git a/noao/onedspec/splot/anshdr.x b/noao/onedspec/splot/anshdr.x
new file mode 100644
index 00000000..e314454b
--- /dev/null
+++ b/noao/onedspec/splot/anshdr.x
@@ -0,0 +1,84 @@
+include <time.h>
+include <fset.h>
+include <smw.h>
+
+# ANS_HDR -- Add answer header in answer file
+
+procedure ans_hdr (sh, newimage, key, fname1, fname2, fd1, fd2)
+
+pointer sh
+int newimage
+int key
+char fname1[SZ_FNAME]
+char fname2[SZ_FNAME]
+int fd1, fd2
+
+pointer sp, time
+long clktime()
+int key1, open()
+errchk open
+data key1/0/
+
+begin
+ # Check for valid file name
+ if (fd1 == NULL && fname1[1] != EOS) {
+ fd1 = open (fname1, APPEND, TEXT_FILE)
+ call fseti (fd1, F_FLUSHNL, YES)
+ }
+ if (fd2 == NULL && fname2[1] != EOS) {
+ fd2 = open (fname2, APPEND, TEXT_FILE)
+ call fseti (fd2, F_FLUSHNL, YES)
+ }
+
+ # Print image name.
+ if (newimage == YES) {
+ call smark (sp)
+ call salloc (time, SZ_DATE, TY_CHAR)
+ call cnvdate (clktime(0), Memc[time], SZ_DATE)
+
+ if (fd1 != NULL) {
+ call fprintf (fd1, "\n%s [%s%s]: %s\n")
+ call pargstr (Memc[time])
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (TITLE(sh))
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "\n%s [%s%s]: %s\n")
+ call pargstr (Memc[time])
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (TITLE(sh))
+ }
+ call sfree (sp)
+ }
+
+ # Print key dependent header.
+ if (key != key1) {
+ if (key != 'm') {
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%10s%10s%10s%10s%10s%10s%10s\n")
+ call pargstr ("center")
+ call pargstr ("cont")
+ call pargstr ("flux")
+ call pargstr ("eqw")
+ call pargstr ("core")
+ call pargstr ("gfwhm")
+ call pargstr ("lfwhm")
+ call flush (fd1)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%10s%10s%10s%10s%10s%10s%10s\n")
+ call pargstr ("center")
+ call pargstr ("cont")
+ call pargstr ("flux")
+ call pargstr ("eqw")
+ call pargstr ("core")
+ call pargstr ("gfwhm")
+ call pargstr ("lfwhm")
+ call flush (fd2)
+ }
+ }
+ key1 = key
+ }
+end
diff --git a/noao/onedspec/splot/autoexp.x b/noao/onedspec/splot/autoexp.x
new file mode 100644
index 00000000..c36f6a8b
--- /dev/null
+++ b/noao/onedspec/splot/autoexp.x
@@ -0,0 +1,79 @@
+include <mach.h>
+include <gset.h>
+include <pkg/gtools.h>
+
+# AUTO_EXP -- Auto expand around the marked region
+
+procedure auto_exp (gp, gt, key, wx1, x, y, n)
+
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+int key # Key
+real wx1 # Cursor position
+real x[n] # Pixel coordinates
+real y[n] # Pixel data for Y scaling
+int n # Number of pixels
+
+char cmd[1]
+int i, wcs
+real x1, x2, y1, y2, wx2, wy, dx, xmin, xmax, ymin, ymax
+
+int clgcur()
+
+begin
+ # Get the current window.
+ call ggwind (gp, x1, x2, y1, y2)
+
+ # Compute the new window in x.
+ dx = x2 - x1
+ switch (key) {
+ case 'a': # Expand
+ call printf ("again:\n")
+ i = clgcur ("cursor", wx2, wy, wcs, key, cmd, SZ_LINE)
+ x1 = wx1
+ x2 = wx2
+ case ',': # Shift left
+ x1 = x1 - 0.85 * dx
+ x2 = x2 - 0.85 * dx
+ case '.': # Shift right
+ x1 = x1 + 0.85 * dx
+ x2 = x2 + 0.85 * dx
+ case 'z': # Zoom x axis
+ x1 = x1 + 0.25 * dx
+ x2 = x2 - 0.25 * dx
+ }
+
+ if (x1 == x2) {
+ # Autoscale.
+ x1 = INDEF
+ x2 = INDEF
+ ymin = INDEF
+ ymax = INDEF
+ } else {
+ # Determine the y limits for pixels between x1 and x2.
+ xmin = min (x1, x2)
+ xmax = max (x1, x2)
+ ymin = MAX_REAL
+ ymax = -MAX_REAL
+ do i = 1, n {
+ if (x[i] < xmin || x[i] > xmax)
+ next
+ ymin = min (y[i], ymin)
+ ymax = max (y[i], ymax)
+ }
+ if (ymin > ymax) {
+ ymin = y1
+ ymax = y2
+ } else if (y1 > y2) {
+ y1 = ymin
+ ymin = ymax
+ ymax = y1
+ }
+ }
+
+ call gt_setr (gt, GTXMIN, x1)
+ call gt_setr (gt, GTXMAX, x2)
+ call gt_setr (gt, GTYMIN, ymin)
+ call gt_setr (gt, GTYMAX, ymax)
+ call replot (gp, gt, x, y, n, YES)
+end
diff --git a/noao/onedspec/splot/avgsnr.x b/noao/onedspec/splot/avgsnr.x
new file mode 100644
index 00000000..a4ad9ceb
--- /dev/null
+++ b/noao/onedspec/splot/avgsnr.x
@@ -0,0 +1,72 @@
+# AVGSNR -- Compute average value and signal-to-noise in region
+
+procedure avgsnr (sh, wx1, wy1, y, n, fd1, fd2)
+
+pointer sh
+real wx1, wy1
+real y[n]
+int n
+int fd1, fd2
+
+char command[SZ_FNAME]
+real wx2, wy2
+real avg, snr, rms
+int i, i1, i2, nsum
+int wc, key
+
+int clgcur()
+
+begin
+ # Get second position
+ call printf ("m again:")
+ call flush (STDOUT)
+ i = clgcur ("cursor", wx2, wy2, wc, key, command, SZ_FNAME)
+
+ # Fix pixel indices
+ call fixx (sh, wx1, wx2, wy1, wy2, i1, i2)
+ if (i1 == i2) {
+ call printf ("Cannot determine SNR - move cursor")
+ return
+ }
+
+ # Compute avg, rms, snr
+ nsum = i2 - i1 + 1
+ avg = 0.
+ rms = 0.
+ snr = 0.
+
+ if (nsum > 0) {
+ do i = i1, i2
+ avg = avg + y[i]
+ avg = avg / nsum
+ }
+
+ if (nsum > 1) {
+ call alimr (y[i1], nsum, wy1, wy2)
+ wy1 = wy2 - wy1
+ if (wy1 > 0.) {
+ do i = i1, i2
+ rms = rms + ((y[i] - avg) / wy1) ** 2
+ rms = wy1 * sqrt (rms / (nsum-1))
+ snr = avg / rms
+ }
+ }
+
+ # Print out
+ call printf ("avg: %10.4g rms: %10.4g snr: %8.2f\n")
+ call pargr (avg)
+ call pargr (rms)
+ call pargr (snr)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "avg: %10.4g rms: %10.4g snr: %8.2f\n")
+ call pargr (avg)
+ call pargr (rms)
+ call pargr (snr)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "avg: %10.4g rms: %10.4g snr: %8.2f\n")
+ call pargr (avg)
+ call pargr (rms)
+ call pargr (snr)
+ }
+end
diff --git a/noao/onedspec/splot/conflam.x b/noao/onedspec/splot/conflam.x
new file mode 100644
index 00000000..c322d566
--- /dev/null
+++ b/noao/onedspec/splot/conflam.x
@@ -0,0 +1,28 @@
+include <error.h>
+include <smw.h>
+
+define VLIGHT 2.997925e18
+
+# CONFLAM -- Convert to FLAMBDA from FNU
+
+procedure conflam (sh)
+
+pointer sh # SHDR pointer
+
+int i
+real lambda
+pointer ang, un_open()
+errchk un_open, un_ctranr
+
+begin
+ ang = un_open ("angstroms")
+ iferr {
+ do i = 0, SN(sh)-1 {
+ call un_ctranr (UN(sh), ang, Memr[SX(sh)+i], lambda, 1)
+ Memr[SY(sh)+i] = Memr[SY(sh)+i] * VLIGHT / lambda**2
+ }
+ } then
+ call erract (EA_WARN)
+
+ call un_close (ang)
+end
diff --git a/noao/onedspec/splot/confnu.x b/noao/onedspec/splot/confnu.x
new file mode 100644
index 00000000..228cea6f
--- /dev/null
+++ b/noao/onedspec/splot/confnu.x
@@ -0,0 +1,28 @@
+include <error.h>
+include <smw.h>
+
+define VLIGHT 2.997925e18
+
+# CONFNU -- Convert to FNU from FLAMBDA
+
+procedure confnu (sh)
+
+pointer sh # SHDR pointer
+
+int i
+real lambda
+pointer ang, un_open()
+errchk un_open, un_ctranr
+
+begin
+ ang = un_open ("angstroms")
+ iferr {
+ do i = 0, SN(sh)-1 {
+ call un_ctranr (UN(sh), ang, Memr[SX(sh)+i], lambda, 1)
+ Memr[SY(sh)+i] = Memr[SY(sh)+i] * lambda**2 / VLIGHT
+ }
+ } then
+ call erract (EA_WARN)
+
+ call un_close (ang)
+end
diff --git a/noao/onedspec/splot/deblend.x b/noao/onedspec/splot/deblend.x
new file mode 100644
index 00000000..d43a9d52
--- /dev/null
+++ b/noao/onedspec/splot/deblend.x
@@ -0,0 +1,627 @@
+include <math.h>
+include <mach.h>
+
+# Profile types.
+define GAUSS 1 # Gaussian profile
+define LORENTZ 2 # Lorentzian profile
+define VOIGT 3 # Voigt profile
+
+# Elements of fit array.
+define BKG 1 # Background
+define POS 2 # Position
+define INT 3 # Intensity
+define GAU 4 # Gaussian FWHM
+define LOR 5 # Lorentzian FWHM
+
+# Type of constraints.
+define FIXED 1 # Fixed parameter
+define SINGLE 2 # Fit a single value for all lines
+define INDEP 3 # Fit independent values for all lines
+
+
+# DOFIT -- Fit line profiles. This is an interface to DOFIT1
+# which puts parameters into the required form and vice-versa.
+# It also implements a constrained approach to the solution.
+
+procedure dofit (fit, x, y, s, npts, dx, nsub, y1, dy,
+ xp, yp, gp, lp, tp, np, chisq)
+
+int fit[5] # Fit constraints
+real x[npts] # X data
+real y[npts] # Y data
+real s[npts] # Sigma data
+int npts # Number of points
+real dx # Pixel size
+int nsub # Number of subpixels
+real y1 # Continuum offset
+real dy # Continuum slope
+real xp[np] # Profile positions
+real yp[np] # Profile intensities
+real gp[np] # Profile Gaussian FWHM
+real lp[np] # Profile Lorentzian FWHM
+int tp[np] # Profile type
+int np # Number of profiles
+real chisq # Chi squared
+
+int i, j, fit1[5]
+pointer sp, a, b
+errchk dofit1
+
+begin
+ call smark (sp)
+ call salloc (a, 8 + 5 * np, TY_REAL)
+
+ # Convert positions and widths relative to first component.
+ Memr[a] = dx
+ Memr[a+1] = nsub
+ Memr[a+2] = y1
+ Memr[a+3] = dy
+ Memr[a+4] = yp[1]
+ Memr[a+5] = xp[1]
+ Memr[a+6] = gp[1]
+ Memr[a+7] = lp[1]
+ do i = 1, np {
+ b = a + 5 * i + 3
+ Memr[b] = yp[i] / Memr[a+4]
+ Memr[b+1] = xp[i] - Memr[a+5]
+ switch (tp[i]) {
+ case GAUSS:
+ if (Memr[a+6] == 0.)
+ Memr[a+6] = gp[i]
+ Memr[b+2] = gp[i] / Memr[a+6]
+ case LORENTZ:
+ if (Memr[a+7] == 0.)
+ Memr[a+7] = lp[i]
+ Memr[b+3] = lp[i] / Memr[a+7]
+ case VOIGT:
+ if (Memr[a+6] == 0.)
+ Memr[a+6] = gp[i]
+ Memr[b+2] = gp[i] / Memr[a+6]
+ if (Memr[a+7] == 0.)
+ Memr[a+7] = lp[i]
+ Memr[b+3] = lp[i] / Memr[a+7]
+ }
+ Memr[b+4] = tp[i]
+ }
+
+ # Do fit.
+ fit1[INT] = fit[INT]
+ do i = 1, fit[BKG] {
+ fit1[BKG] = i
+ fit1[GAU] = min (SINGLE, fit[GAU])
+ fit1[LOR] = min (SINGLE, fit[LOR])
+ do j = FIXED, fit[POS] {
+ fit1[POS] = j
+ if (np > 1 || j != INDEP)
+ call dofit1 (fit1, x, y, s, npts, Memr[a], np, chisq)
+ }
+ if (np > 1 && (fit[GAU] == INDEP || fit[LOR] == INDEP)) {
+ fit1[GAU] = fit[GAU]
+ fit1[LOR] = fit[LOR]
+ call dofit1 (fit1, x, y, s, npts, Memr[a], np, chisq)
+ }
+ }
+
+ y1 = Memr[a+2]
+ dy = Memr[a+3]
+ do i = 1, np {
+ b = a + 5 * i + 3
+ yp[i] = Memr[b] * Memr[a+4]
+ xp[i] = Memr[b+1] + Memr[a+5]
+ switch (tp[i]) {
+ case GAUSS:
+ gp[i] = abs (Memr[b+2] * Memr[a+6])
+ case LORENTZ:
+ lp[i] = abs (Memr[b+3] * Memr[a+7])
+ case VOIGT:
+ gp[i] = abs (Memr[b+2] * Memr[a+6])
+ lp[i] = abs (Memr[b+3] * Memr[a+7])
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# DOREFIT -- Refit line profiles. This assumes the input is very close
+# to the final solution and minimizes the number of calls to the
+# fitting routines. This is intended for efficient use in the
+# in computing bootstrap error estimates.
+
+procedure dorefit (fit, x, y, s, npts, dx, nsub, y1, dy,
+ xp, yp, gp, lp, tp, np, chisq)
+
+int fit[5] # Fit constraints
+real x[npts] # X data
+real y[npts] # Y data
+real s[npts] # Sigma data
+int npts # Number of points
+real dx # Pixel size
+int nsub # Number of subpixels
+real y1 # Continuum offset
+real dy # Continuum slope
+real xp[np] # Profile positions
+real yp[np] # Profile intensities
+real gp[np] # Profile Gaussian FWHM
+real lp[np] # Profile Lorentzian FWHM
+int tp[np] # Profile type
+int np # Number of profiles
+real chisq # Chi squared
+
+int i
+pointer sp, a, b
+errchk dofit1
+
+begin
+ call smark (sp)
+ call salloc (a, 8 + 5 * np, TY_REAL)
+
+ # Convert positions and widths relative to first component.
+ Memr[a] = dx
+ Memr[a+1] = nsub
+ Memr[a+2] = y1
+ Memr[a+3] = dy
+ Memr[a+4] = yp[1]
+ Memr[a+5] = xp[1]
+ Memr[a+6] = gp[1]
+ Memr[a+7] = lp[1]
+ do i = 1, np {
+ b = a + 5 * i + 3
+ Memr[b] = yp[i] / Memr[a+4]
+ Memr[b+1] = xp[i] - Memr[a+5]
+ switch (tp[i]) {
+ case GAUSS:
+ if (Memr[a+6] == 0.)
+ Memr[a+6] = gp[i]
+ Memr[b+2] = gp[i] / Memr[a+6]
+ case LORENTZ:
+ if (Memr[a+7] == 0.)
+ Memr[a+7] = lp[i]
+ Memr[b+3] = lp[i] / Memr[a+7]
+ case VOIGT:
+ if (Memr[a+6] == 0.)
+ Memr[a+6] = gp[i]
+ Memr[b+2] = gp[i] / Memr[a+6]
+ if (Memr[a+7] == 0.)
+ Memr[a+7] = lp[i]
+ Memr[b+3] = lp[i] / Memr[a+7]
+ }
+ Memr[b+4] = tp[i]
+ }
+
+ # Do fit.
+ call dofit1 (fit, x, y, s, npts, Memr[a], np, chisq)
+
+ y1 = Memr[a+2]
+ dy = Memr[a+3]
+ do i = 1, np {
+ b = a + 5 * i + 3
+ yp[i] = Memr[b] * Memr[a+4]
+ xp[i] = Memr[b+1] + Memr[a+5]
+ switch (tp[i]) {
+ case GAUSS:
+ gp[i] = abs (Memr[b+2] * Memr[a+6])
+ case LORENTZ:
+ lp[i] = abs (Memr[b+3] * Memr[a+7])
+ case VOIGT:
+ gp[i] = abs (Memr[b+2] * Memr[a+6])
+ lp[i] = abs (Memr[b+3] * Memr[a+7])
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# MODEL -- Compute model.
+
+real procedure model (x, dx, nsub, xp, yp, gp, lp, tp, np)
+
+real x # X value to be evaluated
+real dx # Pixel width
+int nsub # Number of subpixels
+real xp[np] # Profile positions
+real yp[np] # Profile intensities
+real gp[np] # Profile Gaussian FWHM
+real lp[np] # Profile Lorentzian FWHM
+int tp[np] # Profile type
+int np # Number of profiles
+
+int i, j
+real delta, x1, y, arg1, arg2, v, v0, u
+
+begin
+ delta = dx / nsub
+ x1 = x - (dx + delta) / 2
+ y = 0.
+ do j = 1, nsub {
+ x1 = x1 + delta
+ do i = 1, np {
+ switch (tp[i]) {
+ case GAUSS:
+ arg1 = 1.66511 * abs ((x1 - xp[i]) / gp[i])
+ if (arg1 < 5.)
+ y = y + yp[i] * exp (-arg1**2)
+ case LORENTZ:
+ arg2 = abs ((x1 - xp[i]) / (lp[i] / 2))
+ y = y + yp[i] / (1 + arg2**2)
+ case VOIGT:
+ arg1 = 1.66511 * (x1 - xp[i]) / gp[i]
+ arg2 = 0.832555 * lp[i] / gp[i]
+ call voigt (0., arg2, v0, u)
+ call voigt (arg1, arg2, v, u)
+ y = y + yp[i] * v / v0
+ }
+ }
+ }
+ y = y / nsub
+ return (y)
+end
+
+
+# DERIVS -- Compute model and derivatives for MR_SOLVE procedure.
+# This could be optimized more for the Voigt profile by reversing
+# the do loops since v0 need only be computed once per line.
+
+procedure derivs (x, a, y, dyda, na)
+
+real x # X value to be evaluated
+real a[na] # Parameters
+real y # Function value
+real dyda[na] # Derivatives
+int na # Number of parameters
+
+int i, j, nsub
+real dx, dx1, delta, x1, wg, wl, arg1, arg2, I0, dI, c, u, v, v0
+
+begin
+ dx = a[1]
+ nsub = a[2]
+ delta = dx / nsub
+ dx1 = .1 * delta
+ x1 = x - (dx + delta) / 2
+ y = 0.
+ do i = 1, na
+ dyda[i] = 0.
+ do j = 1, nsub {
+ x1 = x1 + delta
+ y = y + a[3] + a[4] * x1
+ dyda[3] = dyda[3] + 1.
+ dyda[4] = dyda[4] + x1
+ do i = 9, na, 5 {
+ switch (a[i+4]) {
+ case GAUSS:
+ I0 = a[5] * a[i]
+ wg = a[7] * a[i+2]
+ arg1 = 1.66511 * (x1 - a[6] - a[i+1]) / wg
+ if (abs (arg1) < 5.) {
+ dI = exp (-arg1**2)
+ c = I0 * dI * arg1
+ y = y + I0 * dI
+ dyda[5] = dyda[5] + a[i] * dI
+ dyda[6] = dyda[6] + c / wg
+ dyda[7] = dyda[7] + c * arg1 / a[7]
+ dyda[i] = dyda[i] + a[5] * dI
+ dyda[i+1] = dyda[i+1] + c / wg
+ dyda[i+2] = dyda[i+2] + c * arg1 / a[i+2]
+ }
+ case LORENTZ:
+ I0 = a[5] * a[i]
+ wl = (a[8] * a[i+3] / 2)
+ arg2 = (x1 - a[6] - a[i+1]) / wl
+ dI = 1 / (1 + arg2**2)
+ c = 2 * I0 * dI * dI * arg2
+ y = y + I0 * dI
+ dyda[5] = dyda[5] + a[i] * dI
+ dyda[6] = dyda[6] + c / wl
+ dyda[8] = dyda[8] + c * arg2 / a[8]
+ dyda[i] = dyda[i] + a[5] * dI
+ dyda[i+1] = dyda[i+1] + c / wl
+ dyda[i+3] = dyda[i+3] + c * arg2 / a[i+3]
+ case VOIGT:
+ a[7] = max (dx1, abs(a[7]))
+ a[8] = max (dx1, abs(a[8]))
+ a[i+2] = max (1E-6, abs(a[i+2]))
+ a[i+3] = max (1E-6, abs(a[i+3]))
+
+ I0 = a[5] * a[i]
+ wg = a[7] * a[i+2]
+ wl = a[8] * a[i+3]
+ arg1 = 1.66511 * (x1 - a[6] - a[i+1]) / wg
+ arg2 = 0.832555 * wl / wg
+ call voigt (0., arg2, v0, u)
+ call voigt (arg1, arg2, v, u)
+ v = v / v0; u = u / v0
+ dI = (1 - v) / (v0 * SQRTOFPI)
+ c = 2 * I0 * arg2
+ y = y + I0 * v
+ dyda[5] = dyda[5] + a[i] * v
+ dyda[6] = dyda[6] + 2 * c * (arg1 * v - arg2 * u) / wl
+ dyda[7] = dyda[7] +
+ c * (dI + arg1 * (arg1 / arg2 * v - 2 * u)) / a[7]
+ dyda[8] = dyda[8] + c * (arg1 * u - dI) / a[8]
+ dyda[i] = dyda[i] + a[5] * v
+ dyda[i+1] = dyda[i+1] + 2 * c * (arg1 * v - arg2 * u) / wl
+ dyda[i+2] = dyda[i+2] +
+ c * (dI + arg1 * (arg1 / arg2 * v - 2 * u)) / a[i+2]
+ dyda[i+3] = dyda[i+3] + c * (arg1 * u - dI) / a[i+3]
+ }
+ }
+ }
+ y = y / nsub
+ do i = 1, na
+ dyda[i] = dyda[i] / nsub
+end
+
+
+# DOFIT1 -- Perform nonlinear iterative fit for the specified parameters.
+# This uses the Levenberg-Marquardt method from NUMERICAL RECIPES.
+
+procedure dofit1 (fit, x, y, s, npts, a, nlines, chisq)
+
+int fit[5] # Fit constraints
+real x[npts] # X data
+real y[npts] # Y data
+real s[npts] # Sigma data
+int npts # Number of points
+real a[ARB] # Fitting parameters
+int nlines # Number of lines
+real chisq # Chi squared
+
+int i, np, nfit
+real mr, chi2
+pointer sp, flags, ptr
+errchk mr_solve
+
+begin
+ # Number of terms is 5 for each line plus common background, center,
+ # intensity and widths. Also the pixel size and number of subpixels.
+
+ np = 5 * nlines + 8
+
+ call smark (sp)
+ call salloc (flags, np, TY_INT)
+ ptr = flags
+
+ # Background.
+ switch (fit[BKG]) {
+ case SINGLE:
+ Memi[ptr] = 3
+ Memi[ptr+1] = 4
+ ptr = ptr + 2
+ }
+
+ # Peaks.
+ switch (fit[INT]) {
+ case SINGLE:
+ Memi[ptr] = 5
+ ptr = ptr + 1
+ case INDEP:
+ do i = 1, nlines {
+ Memi[ptr] = 5 * i + 4
+ ptr = ptr + 1
+ }
+ }
+
+ # Positions.
+ switch (fit[POS]) {
+ case SINGLE:
+ Memi[ptr] = 6
+ ptr = ptr + 1
+ case INDEP:
+ do i = 1, nlines {
+ Memi[ptr] = 5 * i + 5
+ ptr = ptr + 1
+ }
+ }
+
+ # Gaussian FWHM.
+ switch (fit[GAU]) {
+ case SINGLE:
+ Memi[ptr] = 7
+ ptr = ptr + 1
+ case INDEP:
+ do i = 1, nlines {
+ Memi[ptr] = 5 * i + 6
+ ptr = ptr + 1
+ }
+ }
+
+ # Lorentzian FWHM.
+ switch (fit[LOR]) {
+ case SINGLE:
+ Memi[ptr] = 8
+ ptr = ptr + 1
+ case INDEP:
+ do i = 1, nlines {
+ Memi[ptr] = 5 * i + 7
+ ptr = ptr + 1
+ }
+ }
+
+ nfit = ptr - flags
+ mr = -1.
+ i = 0
+ chi2 = MAX_REAL
+ repeat {
+ call mr_solve (x, y, s, npts, a, Memi[flags], np, nfit, mr, chisq)
+ if (chi2 - chisq > 0.0001)
+ i = 0
+ else
+ i = i + 1
+ chi2 = chisq
+ } until (i == 5)
+
+ mr = 0.
+ call mr_solve (x, y, s, npts, a, Memi[flags], np, nfit, mr, chisq)
+
+ call sfree (sp)
+end
+
+
+# MR_SOLVE -- Levenberg-Marquardt nonlinear chi square minimization.
+#
+# Use the Levenberg-Marquardt method to minimize the chi squared of a set
+# of paraemters. The parameters being fit are indexed by the flag array.
+# To initialize the Marquardt parameter, MR, is less than zero. After that
+# the parameter is adjusted as needed. To finish set the parameter to zero
+# to free memory. This procedure requires a subroutine, DERIVS, which
+# takes the derivatives of the function being fit with respect to the
+# parameters. There is no limitation on the number of parameters or
+# data points. For a description of the method see NUMERICAL RECIPES
+# by Press, Flannery, Teukolsky, and Vetterling, p523.
+
+procedure mr_solve (x, y, s, npts, params, flags, np, nfit, mr, chisq)
+
+real x[npts] # X data array
+real y[npts] # Y data array
+real s[npts] # Sigma data array
+int npts # Number of data points
+real params[np] # Parameter array
+int flags[np] # Flag array indexing parameters to fit
+int np # Number of parameters
+int nfit # Number of parameters to fit
+real mr # MR parameter
+real chisq # Chi square of fit
+
+int i
+real chisq1
+pointer new, a1, a2, delta1, delta2
+
+errchk mr_invert
+
+begin
+ # Allocate memory and initialize.
+ if (mr < 0.) {
+ call mfree (new, TY_REAL)
+ call mfree (a1, TY_REAL)
+ call mfree (a2, TY_REAL)
+ call mfree (delta1, TY_REAL)
+ call mfree (delta2, TY_REAL)
+
+ call malloc (new, np, TY_REAL)
+ call malloc (a1, nfit*nfit, TY_REAL)
+ call malloc (a2, nfit*nfit, TY_REAL)
+ call malloc (delta1, nfit, TY_REAL)
+ call malloc (delta2, nfit, TY_REAL)
+
+ call amovr (params, Memr[new], np)
+ call mr_eval (x, y, s, npts, Memr[new], flags, np, Memr[a2],
+ Memr[delta2], nfit, chisq)
+ mr = 0.001
+ }
+
+ # Restore last good fit and apply the Marquardt parameter.
+ call amovr (Memr[a2], Memr[a1], nfit * nfit)
+ call amovr (Memr[delta2], Memr[delta1], nfit)
+ do i = 1, nfit
+ Memr[a1+(i-1)*(nfit+1)] = Memr[a2+(i-1)*(nfit+1)] * (1. + mr)
+
+ # Matrix solution.
+ call mr_invert (Memr[a1], Memr[delta1], nfit)
+
+ # Compute the new values and curvature matrix.
+ do i = 1, nfit
+ Memr[new+flags[i]-1] = params[flags[i]] + Memr[delta1+i-1]
+ call mr_eval (x, y, s, npts, Memr[new], flags, np, Memr[a1],
+ Memr[delta1], nfit, chisq1)
+
+ # Check if chisq has improved.
+ if (chisq1 < chisq) {
+ mr = max (EPSILONR, 0.1 * mr)
+ chisq = chisq1
+ call amovr (Memr[a1], Memr[a2], nfit * nfit)
+ call amovr (Memr[delta1], Memr[delta2], nfit)
+ call amovr (Memr[new], params, np)
+ } else
+ mr = 10. * mr
+
+ if (mr == 0.) {
+ call mfree (new, TY_REAL)
+ call mfree (a1, TY_REAL)
+ call mfree (a2, TY_REAL)
+ call mfree (delta1, TY_REAL)
+ call mfree (delta2, TY_REAL)
+ }
+end
+
+
+# MR_EVAL -- Evaluate curvature matrix. This calls procedure DERIVS.
+
+procedure mr_eval (x, y, s, npts, params, flags, np, a, delta, nfit, chisq)
+
+real x[npts] # X data array
+real y[npts] # Y data array
+real s[npts] # Sigma data array
+int npts # Number of data points
+real params[np] # Parameter array
+int flags[np] # Flag array indexing parameters to fit
+int np # Number of parameters
+real a[nfit,nfit] # Curvature matrix
+real delta[nfit] # Delta array
+int nfit # Number of parameters to fit
+real chisq # Chi square of fit
+
+int i, j, k
+real ymod, dy, dydpj, dydpk, sig2i
+pointer sp, dydp
+
+begin
+ call smark (sp)
+ call salloc (dydp, np, TY_REAL)
+
+ do j = 1, nfit {
+ do k = 1, j
+ a[j,k] = 0.
+ delta[j] = 0.
+ }
+
+ chisq = 0.
+ do i = 1, npts {
+ call derivs (x[i], params, ymod, Memr[dydp], np)
+ if (IS_INDEF(ymod))
+ next
+ sig2i = 1. / (s[i] * s[i])
+ dy = y[i] - ymod
+ do j = 1, nfit {
+ dydpj = Memr[dydp+flags[j]-1] * sig2i
+ delta[j] = delta[j] + dy * dydpj
+ do k = 1, j {
+ dydpk = Memr[dydp+flags[k]-1]
+ a[j,k] = a[j,k] + dydpj * dydpk
+ }
+ }
+ chisq = chisq + dy * dy * sig2i
+ }
+
+ do j = 2, nfit
+ do k = 1, j-1
+ a[k,j] = a[j,k]
+
+ call sfree (sp)
+end
+
+
+# MR_INVERT -- Solve a set of linear equations using Householder transforms.
+
+procedure mr_invert (a, b, n)
+
+real a[n,n] # Input matrix and returned inverse
+real b[n] # Input RHS vector and returned solution
+int n # Dimension of input matrices
+
+int krank
+real rnorm
+pointer sp, h, g, ip
+
+begin
+ call smark (sp)
+ call salloc (h, n, TY_REAL)
+ call salloc (g, n, TY_REAL)
+ call salloc (ip, n, TY_INT)
+
+ call hfti (a, n, n, n, b, n, 1, 1E-10, krank, rnorm,
+ Memr[h], Memr[g], Memi[ip])
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/eqwidth.x b/noao/onedspec/splot/eqwidth.x
new file mode 100644
index 00000000..0594041a
--- /dev/null
+++ b/noao/onedspec/splot/eqwidth.x
@@ -0,0 +1,109 @@
+# EQWIDTH -- Compute equivalent width, flux and center
+
+procedure eqwidth (sh, gfd, wx1, wy1, x, y, n, fd1, fd2)
+
+pointer sh
+int gfd
+real wx1, wy1
+real x[ARB]
+real y[ARB]
+int n
+int fd1, fd2
+
+char command[SZ_FNAME]
+real wx2, wy2, sigma0, invgain
+real flux_diff, rsum[2], esum[2], sum[2], cont, ctr[2]
+int i, wc, key
+pointer sp, s
+
+real clgetr()
+int clgcur()
+double shdr_wl()
+
+begin
+ # Get second position
+ call printf ("e again:")
+ i = clgcur ("cursor", wx2, wy2, wc, key, command, SZ_FNAME)
+
+ if (wx1 == wx2) {
+ call printf ("Cannot get EQW - move cursor")
+ return
+ }
+
+ # Set noise.
+ sigma0 = clgetr ("sigma0")
+ invgain = clgetr ("invgain")
+ if (IS_INDEF(sigma0) || IS_INDEF(invgain) || sigma0<0. || invgain<0.) {
+ sigma0 = INDEF
+ invgain = INDEF
+ }
+ call smark (sp)
+ call salloc (s, n, TY_REAL)
+ if (IS_INDEF(invgain))
+ call amovkr (INDEF, Memr[s], n)
+ else {
+ do i = 1, n {
+ if (y[i] > 0)
+ Memr[s+i-1] = sqrt (sigma0 ** 2 + invgain * y[i])
+ else
+ Memr[s+i-1] = sqrt (sigma0 ** 2)
+ }
+ }
+
+ # Derive the needed values
+ call sumflux (sh, x, y, Memr[s], n, wx1, wx2, wy1, wy2,
+ sum, rsum, esum, ctr)
+
+ # Compute difference in flux between ramp and spectrum
+ flux_diff = sum[1] - rsum[1]
+
+ # Compute eq. width of feature using ramp midpoint as
+ # continuum
+ cont = 0.5 * (wy1 + wy2)
+
+ # Print on status line - save in answer buffer
+ call printf (
+ "center = %9.7g, eqw = %9.4f, continuum = %9.7g flux = %9.6g\n")
+ call pargr (ctr[1])
+ call pargr (esum[1])
+ call pargr (cont)
+ call pargr (flux_diff)
+
+ if (fd1 != NULL) {
+ call fprintf (fd1, " %9.7g %9.7g %9.6g %9.4g\n")
+ call pargr (ctr[1])
+ call pargr (cont)
+ call pargr (flux_diff)
+ call pargr (esum[1])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, " %9.7g %9.7g %9.6g %9.4g\n")
+ call pargr (ctr[1])
+ call pargr (cont)
+ call pargr (flux_diff)
+ call pargr (esum[1])
+ }
+ if (!IS_INDEF(sigma0)) {
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ " (%7.5g) %9w (%7.4g) (%7.2g)\n")
+ call pargr (ctr[2])
+ call pargr (sum[2])
+ call pargr (esum[2])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ " (%7.5g) %9w (%7.4g) (%7.2g)\n")
+ call pargr (ctr[2])
+ call pargr (sum[2])
+ call pargr (esum[2])
+ }
+ }
+
+ # Draw cursor position
+ i = max (1, min (n, nint (shdr_wl (sh, double(ctr[1])))))
+ call gline (gfd, wx1, wy1, wx2, wy2)
+ call gline (gfd, ctr[1], cont, ctr[1], y[i])
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/eqwidthcp.x b/noao/onedspec/splot/eqwidthcp.x
new file mode 100644
index 00000000..4fc4fd3d
--- /dev/null
+++ b/noao/onedspec/splot/eqwidthcp.x
@@ -0,0 +1,240 @@
+include <gset.h>
+
+
+# EQWIDTH_CP -- Equivalent width following algorithm provided by
+# Caty Pilachowski. This assumes a Gaussian line profile
+# and fits to the specified core level, the width at the
+# specified flux level above the core, and the specified
+# continuum. The line position is found by searching
+# near the vertical cursor for the nearest minimum.
+
+define LEFT 1 # Fit to left edge
+define RIGHT 2 # Fit to right edge
+define BOTH 3 # Fit to both edges
+
+procedure eqwidth_cp (sh, gfd, center, cont, ylevel, y, n, key, fd1, fd2,
+ xg, yg, sg, lg, pg, ng)
+
+pointer sh
+int gfd
+real center, cont, ylevel
+real y[n]
+int n
+int key
+int fd1, fd2
+pointer xg, yg, sg, lg, pg # Pointers to fit parameters
+int ng # Number of components
+
+int i, i1, i2, isrch, icore, edge
+double xleft, xright, rcore, rinter, yl, gfwhm, lfwhm, flux, eqw, w, w1, w2
+double xpara[3], ypara[3], coefs[3], xcore, ycore
+double shdr_lw(), shdr_wl()
+
+# Initialize reasonable values
+# isrch -- nr of pixels on either side of cursor to search for min
+
+data isrch /3/
+
+begin
+ # Check continuum.
+ if (cont <= 0.) {
+ call eprintf ("Continuum cannot be less than zero.\n")
+ return
+ }
+
+ # Determine which edges of the line to use.
+ switch (key) {
+ case 'a', 'l':
+ edge = LEFT
+ case 'b', 'r':
+ edge = RIGHT
+ default:
+ edge = BOTH
+ }
+
+ # Search for local minimum or maximum
+ icore = max (1, min (n, nint (shdr_wl (sh, double(center)))))
+ i1 = max (1, icore-isrch)
+ i2 = min (n, icore+isrch)
+
+ # If half lines is selected, restrict the search
+ if (edge == LEFT)
+ i2 = max (i2-2, icore+1)
+ if (edge == RIGHT)
+ i1 = min (i1+2, icore-1)
+
+ # Search for core.
+ # Someday it may be desirable to use parabolic interpolation
+ # to locate an estimated minimum or maximum for the region
+ do i = i1, i2 {
+ if (abs (y[i] - cont) > abs (y[icore] - cont))
+ icore = i
+ }
+
+ # Fit parabola to three points around minimum pixel
+ xpara[1] = icore - 1
+ xpara[2] = icore
+ xpara[3] = icore + 1
+ ypara[1] = y[icore-1]
+ ypara[2] = y[icore]
+ ypara[3] = y[icore+1]
+
+ call para (xpara, ypara, coefs)
+
+ # Compute pixel value at minimum
+ xcore = -coefs[2] / 2.0 / coefs[3]
+ ycore = coefs[1] + coefs[2] * xcore + coefs[3] * xcore**2
+
+ # Locate left and right line edges. If the ylevel is INDEF then use
+ # the half flux point.
+ if (IS_INDEF (ylevel))
+ yl = (cont + ycore) / 2.
+ else
+ yl = ylevel
+
+ rcore = abs (ycore - cont)
+ rinter = abs (yl - cont)
+
+ if (rcore <= rinter) {
+ call eprintf (
+ "Y cursor must be between the continuum and the line core\n")
+ return
+ }
+
+ # Bound flux level of interest
+ if ((edge == LEFT) || (edge == BOTH)) {
+ for (i=icore; i >= 1; i=i-1)
+ if (abs (y[i] - cont) < rinter)
+ break
+
+ if (i < 1) {
+ call eprintf ("Can't find left edge of line\n")
+ return
+ }
+
+ xleft = float (i) + (yl - y[i]) / (y[i+1] - y[i])
+ if (edge == LEFT)
+ xright = xcore + (xcore - xleft)
+ }
+
+ # Now bound the right side
+ if ((edge == RIGHT) || (edge == BOTH)) {
+ for (i=icore; i <= n; i=i+1)
+ if (abs (y[i] - cont) < rinter)
+ break
+
+ if (i > n) {
+ call eprintf ("Can't find right edge of line\n")
+ return
+ }
+
+ xright = float (i) - (yl - y[i]) / (y[i-1] - y[i])
+ if (edge == RIGHT)
+ xleft = xcore - (xright - xcore)
+ }
+
+ # Compute in wavelength
+ w = shdr_lw (sh, double(xcore))
+ w1 = shdr_lw (sh, double(xleft))
+ w2 = shdr_lw (sh, double(xright))
+
+ # Apply Gaussian model
+ gfwhm = 1.665109 * abs (w2 - w1) / 2. / sqrt (log (rcore/rinter))
+ lfwhm = 0.
+ rcore = ycore - cont
+ flux = 1.064467 * rcore * gfwhm
+ eqw = -flux / cont
+
+ call printf (
+ "center = %9.7g, eqw = %9.4g, gfwhm = %9.4g\n")
+ call pargd (w)
+ call pargd (eqw)
+ call pargd (gfwhm)
+
+ if (fd1 != NULL) {
+ call fprintf (fd1, " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargd (w)
+ call pargr (cont)
+ call pargd (flux)
+ call pargd (eqw)
+ call pargd (ycore - cont)
+ call pargd (gfwhm)
+ call pargd (lfwhm)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargd (w)
+ call pargr (cont)
+ call pargd (flux)
+ call pargd (eqw)
+ call pargd (ycore - cont)
+ call pargd (gfwhm)
+ call pargd (lfwhm)
+ }
+
+ # Mark line computed
+ call gline (gfd, real(w), cont, real(w), real(ycore))
+ call gline (gfd, real(w1), real(yl), real(w2), real(yl))
+
+ w1 = w - 2 * gfwhm
+ w2 = cont + rcore * exp (-(1.665109*(w1-w)/gfwhm)**2)
+ call gseti (gfd, G_PLTYPE, 2)
+ call gseti (gfd, G_PLCOLOR, 2)
+ call gamove (gfd, real(w1), real(w2))
+ for (; w1 <= w+2*gfwhm; w1=w1+0.05*gfwhm) {
+ w2 = cont + rcore * exp (-(1.665109*(w1-w)/gfwhm)**2)
+ call gadraw (gfd, real(w1), real(w2))
+ }
+ call gseti (gfd, G_PLTYPE, 1)
+ call gseti (gfd, G_PLCOLOR, 1)
+
+ # Save fit parameters
+ if (ng == 0) {
+ call malloc (xg, 1, TY_REAL)
+ call malloc (yg, 1, TY_REAL)
+ call malloc (sg, 1, TY_REAL)
+ call malloc (lg, 1, TY_REAL)
+ call malloc (pg, 1, TY_INT)
+ } else if (ng != 1) {
+ call realloc (xg, 1, TY_REAL)
+ call realloc (yg, 1, TY_REAL)
+ call realloc (sg, 1, TY_REAL)
+ call realloc (lg, 1, TY_REAL)
+ call realloc (pg, 1, TY_INT)
+ }
+ Memr[xg] = w
+ Memr[yg] = rcore
+ Memr[sg] = gfwhm
+ Memr[lg] = lfwhm
+ Memi[pg] = 1
+ ng = 1
+end
+
+# PARA -- Fit a parabola to three points
+
+procedure para (x, y, c)
+
+double x[3], y[3], c[3]
+double x12, x13, x23, x213, x223, y13, y23
+
+begin
+ x12 = x[1] - x[2]
+ x13 = x[1] - x[3]
+ x23 = x[2] - x[3]
+
+ if (x12 == 0. || x13 == 0. || x23 == 0.)
+ call error (1, "X points are not distinct")
+
+ # Compute relative to an origin at x[3]
+ x213 = x13 * x13
+ x223 = x23 * x23
+ y13 = y[1] - y[3]
+ y23 = y[2] - y[3]
+ c[3] = (y13 - y23 * x13 / x23) / (x213 - x223 * x13 / x23)
+ c[2] = (y23 - c[3] * x223) / x23
+ c[1] = y[3]
+
+ # Compute relative to an origin at 0.
+ c[1] = c[1] - x[3] * (c[2] - c[3] * x[3])
+ c[2] = c[2] - 2 * c[3] * x[3]
+end
diff --git a/noao/onedspec/splot/fixx.x b/noao/onedspec/splot/fixx.x
new file mode 100644
index 00000000..65bd4e38
--- /dev/null
+++ b/noao/onedspec/splot/fixx.x
@@ -0,0 +1,27 @@
+include <smw.h>
+
+# FIXX - Adjust so that pixel indices are increasing.
+
+procedure fixx (sh, x1, x2, y1, y2, i1, i2)
+
+pointer sh
+real x1, x2, y1, y2
+int i1, i2
+
+double z, z1, z2, shdr_wl(), shdr_lw()
+
+begin
+ z1 = x1
+ z2 = x2
+ z1 = max (0.5D0, min (double (SN(sh)+.499), shdr_wl(sh, z1)))
+ z2 = max (0.5D0, min (double (SN(sh)+.499), shdr_wl(sh, z2)))
+ if (z1 > z2) {
+ z = y1; y1 = y2; y2 = z
+ z = z1; z1 = z2; z2 = z
+ }
+
+ x1 = shdr_lw (sh, z1)
+ x2 = shdr_lw (sh, z2)
+ i1 = nint (z1)
+ i2 = nint (z2)
+end
diff --git a/noao/onedspec/splot/flatten.x b/noao/onedspec/splot/flatten.x
new file mode 100644
index 00000000..aa038d27
--- /dev/null
+++ b/noao/onedspec/splot/flatten.x
@@ -0,0 +1,110 @@
+include <pkg/gtools.h>
+
+# FLATTEN -- Flatten a spectrum and normalize to 1.0
+# Use ICFIT for fitting the spectrum
+
+procedure flatten (gp, gt, x, y, n)
+
+pointer gp, gt
+real x[n]
+real y[n]
+int n
+
+bool b
+real wx, z
+int i, key
+pointer sp, str, w, gt2, ic, cv
+
+bool clgetb()
+real clgetr(), ic_getr(), cveval()
+int clgeti(), ic_geti(), btoi(), clgcur()
+errchk icg_fit
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+ call salloc (w, n, TY_REAL)
+
+ key = '?'
+ repeat {
+ switch (key) {
+ case '/', '-', 'f', 'c', 'n':
+ call ic_open (ic)
+ call clgstr ("function", Memc[str], SZ_FNAME)
+ call ic_pstr (ic, "function", Memc[str])
+ call ic_puti (ic, "order", clgeti ("order"))
+ call ic_putr (ic, "low", clgetr ("low_reject"))
+ call ic_putr (ic, "high", clgetr ("high_reject"))
+ call ic_puti (ic, "niterate", clgeti ("niterate"))
+ call ic_putr (ic, "grow", clgetr ("grow"))
+ call ic_puti (ic, "markrej", btoi (clgetb ("markrej")))
+ switch (key) {
+ case '/':
+ call ic_puti (ic, "key", 4)
+ case '-':
+ call ic_puti (ic, "key", 3)
+ case 'f', 'n', 'c':
+ call ic_puti (ic, "key", 1)
+ }
+
+ call ic_putr (ic, "xmin", min (x[1], x[n]))
+ call ic_putr (ic, "xmax", max (x[1], x[n]))
+
+ call gt_copy (gt, gt2)
+ call gt_gets (gt2, GTXLABEL, Memc[str], SZ_FNAME)
+ call ic_pstr (ic, "xlabel", Memc[str])
+ call gt_gets (gt2, GTYLABEL, Memc[str], SZ_FNAME)
+ call ic_pstr (ic, "ylabel", Memc[str])
+ call gt_gets (gt2, GTXUNITS, Memc[str], SZ_FNAME)
+ call ic_pstr (ic, "xunits", Memc[str])
+ call gt_gets (gt2, GTYUNITS, Memc[str], SZ_FNAME)
+ call ic_pstr (ic, "yunits", Memc[str])
+
+ call amovkr (1., Memr[w], n)
+ call icg_fit (ic, gp, "cursor", gt2, cv, x, y, Memr[w], n)
+
+ switch (key) {
+ case '/':
+ do i = 1, n {
+ z = cveval (cv, x[i])
+ if (abs (z) < 1e-30)
+ y[i] = 1.
+ else
+ y[i] = y[i] / z
+ }
+ case '-':
+ do i = 1, n
+ y[i] = y[i] - cveval (cv, x[i])
+ case 'f':
+ do i = 1, n
+ y[i] = cveval (cv, x[i])
+ case 'c':
+ call ic_clean (ic, cv, x, y, Memr[w], n)
+ case 'n':
+ ;
+ }
+
+ call ic_gstr (ic, "function", Memc[str], SZ_FNAME)
+ call clpstr ("function", Memc[str])
+ call clputi ("order", ic_geti (ic, "order"))
+ call clputr ("low_reject", ic_getr (ic, "low"))
+ call clputr ("high_reject", ic_getr (ic, "high"))
+ call clputi ("niterate", ic_geti (ic, "niterate"))
+ call clputr ("grow", ic_getr (ic, "grow"))
+ b = (ic_geti (ic, "markrej") == YES)
+ call clputb ("markrej", b)
+
+ call cv_free (cv)
+ call gt_free (gt2)
+ call ic_closer (ic)
+ break
+ case 'q':
+ break
+ default:
+ call printf (
+ "/=normalize, -=subtract, f=fit, c=clean, n=nop, q=quit")
+ }
+ } until (clgcur ("cursor", wx, z, i, key, Memc[str], SZ_FNAME) == EOF)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/fudgept.x b/noao/onedspec/splot/fudgept.x
new file mode 100644
index 00000000..c2aa3740
--- /dev/null
+++ b/noao/onedspec/splot/fudgept.x
@@ -0,0 +1,38 @@
+# FUDGEPT -- Fudge a point
+
+procedure fudgept (sh, gfd, x, y, n, wx, wy)
+
+pointer sh
+int gfd
+real x[n]
+real y[n]
+int n
+real wx, wy
+
+int i1, nplot, istart
+double shdr_wl()
+
+begin
+ # Get pixel number
+ i1 = max (1, min (n, nint (shdr_wl (sh, double(wx)))))
+
+ # Replace with Y-value
+ if (i1 > 0 && i1 <= n)
+ y[i1] = wy
+ else
+ return
+
+ # Plot region around new point
+ if (i1 > 1 && i1 < n) {
+ nplot = 3
+ istart = i1 - 1
+ } else if (i1 == 1) {
+ nplot = 2
+ istart = i1
+ } else if (i1 == n) {
+ nplot = 2
+ istart = n - 1
+ }
+
+ call gpline (gfd, x[istart], y[istart], nplot)
+end
diff --git a/noao/onedspec/splot/fudgex.x b/noao/onedspec/splot/fudgex.x
new file mode 100644
index 00000000..f1612b31
--- /dev/null
+++ b/noao/onedspec/splot/fudgex.x
@@ -0,0 +1,46 @@
+# FUDGEX -- Fudge an extended region marked by the cursor
+
+procedure fudgex (sh, gfd, x, y, n, wx1, wy1, xydraw)
+
+pointer sh
+int gfd
+real x[n]
+real y[n]
+int n
+real wx1, wy1
+int xydraw
+
+char command[SZ_FNAME]
+int i, i1, i2, wc, key
+real slope
+real wx2, wy2
+
+int clgcur()
+bool fp_equalr()
+
+begin
+ # Get second point
+ call printf ("x again:")
+ call flush (STDOUT)
+ i = clgcur ("cursor", wx2, wy2, wc, key, command, SZ_FNAME)
+
+ # Fix order
+ call fixx (sh, wx1, wx2, wy1, wy2, i1, i2)
+
+ if (xydraw == NO) {
+ wy1 = y[i1]
+ wy2 = y[i2]
+ }
+ if (fp_equalr (wx1, wx2))
+ slope = 0.
+ else
+ slope = (wy2-wy1) / (wx2-wx1)
+
+ # Replace pixels
+ do i = i1, i2
+ y[i] = wy1 + (x[i] - wx1) * slope
+
+ # Plot replaced pixels
+ i = i2 - i1 + 1
+ call gpline (gfd, x[i1], y[i1], i)
+end
diff --git a/noao/onedspec/splot/getimage.x b/noao/onedspec/splot/getimage.x
new file mode 100644
index 00000000..671f81de
--- /dev/null
+++ b/noao/onedspec/splot/getimage.x
@@ -0,0 +1,159 @@
+include <error.h>
+include <imhdr.h>
+include <pkg/gtools.h>
+include <smw.h>
+
+# GETIMAGE -- Read new image pixels.
+
+procedure getimage (image, nline, nband, nap, wave_scl, w0, wpc, units,
+ im, mw, sh, gt)
+
+char image[ARB]
+int nline, nband, nap
+bool wave_scl
+double w0, wpc
+real a, b
+char units[ARB]
+pointer sp, imsect, im, mw, sh, gt
+
+int da, n, sec[3,3], clgeti()
+real gt_getr()
+double shdr_lw()
+pointer immap(), smw_openim()
+errchk immap, shdr_open, shdr_system, un_changer
+
+begin
+ call smark (sp)
+ call salloc (imsect, SZ_FNAME, TY_CHAR)
+
+ # Map the image if necessary. Don't allow image sections but
+ # determine requested spectrum from any explicit specification.
+
+ da = 0
+ if (im == NULL) {
+ call imgsection (image, Memc[imsect], SZ_FNAME)
+ call imgimage (image, image, SZ_FNAME)
+ im = immap (image, READ_ONLY, 0)
+ mw = smw_openim (im)
+ n = IM_NDIM(im)
+ if (Memc[imsect] != EOS) {
+ call amovki (1, sec[1,1], n)
+ call amovi (IM_LEN(im,1), sec[1,2], n)
+ call amovki (1, sec[1,3], n)
+ call id_section (Memc[imsect], sec[1,1], sec[1,2], sec[1,3], n)
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ if (n == 1)
+ da = 1
+ if (n == 2) {
+ if (abs (sec[1,2]-sec[1,1]) == 0) {
+ nline = sec[1,1]
+ da = 2
+ } else if (abs (sec[2,2]-sec[2,1]) == 0) {
+ nline = sec[2,1]
+ da = 1
+ }
+ } else {
+ if (abs (sec[1,2]-sec[1,1]) == 0) {
+ nline = sec[1,1]
+ if (abs (sec[2,2]-sec[2,1]) == 0) {
+ nband = sec[2,1]
+ if (abs (sec[3,2]-sec[3,1]) > 0)
+ da = 3
+ } else if (abs (sec[3,2]-sec[3,1]) == 0) {
+ nband = sec[3,1]
+ da = 2
+ }
+ } else if (abs (sec[2,2]-sec[2,1]) == 0) {
+ nline = sec[2,1]
+ if (abs (sec[3,2]-sec[3,1]) == 0) {
+ nband = sec[3,1]
+ da = 1
+ }
+ }
+ }
+ if (da > 0) {
+ call smw_daxis (mw, im, da, INDEFI, INDEFI)
+ call smw_saxis (mw, NULL, im)
+ }
+ default:
+ da = 1
+ if (n > 1 && abs (sec[2,2]-sec[2,1]) == 0)
+ nline = sec[2,1]
+ if (n > 2 && abs (sec[3,2]-sec[3,1]) == 0)
+ nband = sec[3,1]
+ }
+ }
+ }
+
+ # Get header info.
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ nap = INDEFI
+ n = SMW_LLEN(mw,2)
+ if (n > 1) {
+ if (nline == 0)
+ nline = max (1, min (n, clgeti ("line")))
+ } else
+ nline = 0
+ n = SMW_LLEN(mw,3)
+ if (n > 1) {
+ if (nband == 0)
+ nband = max (1, min (n, clgeti ("band")))
+ } else
+ nband = 0
+ default:
+ n = SMW_NSPEC(mw)
+ if (n > 1) {
+ if (nline == 0) {
+ nline = clgeti ("line")
+ nap = nline
+ }
+ } else {
+ nline = 0
+ nap = INDEFI
+ }
+ n = SMW_NBANDS(mw)
+ if (n > 1) {
+ if (nband == 0)
+ nband = max (1, min (n, clgeti ("band")))
+ } else
+ nband = 0
+ }
+
+ call shdr_open (im, mw, nline, nband, nap, SHDATA, sh)
+ nap = AP(sh)
+ nline = LINDEX(sh,1)
+
+ if (DC(sh) == DCNO && !IS_INDEFD(w0))
+ call usercoord (sh, 'l', 1D0, w0, 2D0, w0+wpc)
+
+ # Cancel wavelength coordinates if not desired or set units.
+ if (!wave_scl)
+ call shdr_system (sh, "physical")
+ else {
+ iferr (call shdr_units (sh, units))
+ ;
+ }
+
+ if (da > 0) {
+ a = gt_getr (gt, GTXMIN)
+ b = gt_getr (gt, GTXMAX)
+ if (IS_INDEF(a) && IS_INDEF(b)) {
+ if (!wave_scl) {
+ call gt_setr (gt, GTXMIN, real(sec[da,1]))
+ call gt_setr (gt, GTXMAX, real(sec[da,2]))
+ } else {
+ a = shdr_lw (sh, double(sec[da,1]))
+ b = shdr_lw (sh, double(sec[da,2]))
+ call gt_setr (gt, GTXMIN, a)
+ call gt_setr (gt, GTXMAX, b)
+ }
+ }
+ }
+
+ # Make a title.
+ call mktitle (sh, gt)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/gfit.x b/noao/onedspec/splot/gfit.x
new file mode 100644
index 00000000..2e60d8c4
--- /dev/null
+++ b/noao/onedspec/splot/gfit.x
@@ -0,0 +1,391 @@
+include <error.h>
+include <mach.h>
+include <gset.h>
+
+define NSUB 3 # Number of pixel subsamples
+define MC_N 50 # Monte-Carlo samples
+define MC_P 10 # Percent done interval (percent)
+define MC_SIG 68 # Sigma sample point (percent)
+
+# GFIT -- Fit Gaussian
+
+procedure gfit (sh, gfd, wx1, wy1, wcs, pix, n, fd1, fd2, xg, yg, sg, lg, pg,ng)
+
+pointer sh # SHDR pointer
+pointer gfd # GIO file descriptor
+real wx1, wy1 # Cursor position
+real wcs[n] # Spectrum data
+real pix[n] # Spectrum data
+int n # Number of points
+int fd1, fd2 # Output file descriptors
+pointer xg, yg, sg, lg, pg # Pointers to fit parameters
+int ng # Number of components
+
+int fit[5], nsub, mc_p, mc_sig, mc_n
+int i, j, i1, npts, nlines, wc, key
+long seed
+real w, dw, wyc, wx, wy, wx2, wy2, v, u
+real slope, peak, flux, cont, gfwhm, lfwhm, eqw, scale, sscale, chisq
+real sigma0, invgain, wyc1, slope1, flux1, cont1, eqw1
+bool fitit
+pointer xg1, yg1, sg1, lg1
+pointer sp, cmd, x, y, s, z, ym, conte, xge, yge, sge, lge, fluxe, eqwe
+
+int clgeti(), clgcur()
+real clgetr(), model(), gasdev(), asumr()
+errchk dofit, dorefit
+
+define done_ 99
+
+begin
+ call smark (sp)
+ call salloc (cmd, SZ_FNAME, TY_CHAR)
+
+ # Input cursor is first continuum point now get second continuum point.
+ call printf ("k again:")
+ if (clgcur ("cursor", wx2, wy2, wc, key, Memc[cmd], SZ_FNAME) == EOF) {
+ call sfree (sp)
+ return
+ }
+
+ # Set pixel indices and determine number of points to fit.
+ call fixx (sh, wx1, wx2, wy1, wy2, i1, j)
+ npts = j - i1 + 1
+ if (npts < 3) {
+ call eprintf ("At least 3 points are required\n")
+ call sfree (sp)
+ return
+ }
+
+ # Allocate space for the points to be fit.
+ call salloc (x, npts, TY_REAL)
+ call salloc (y, npts, TY_REAL)
+ call salloc (s, npts, TY_REAL)
+ call salloc (z, npts, TY_REAL)
+
+ # Scale the data.
+ mc_n = clgeti ("nerrsample")
+ sigma0 = clgetr ("sigma0")
+ invgain = clgetr ("invgain")
+ if (IS_INDEF(sigma0) || IS_INDEF(invgain) || sigma0<0. ||
+ invgain<0. || (sigma0 == 0. && invgain == 0.)) {
+ sigma0 = INDEF
+ invgain = INDEF
+ }
+ scale = 0.
+ do i = 1, npts {
+ Memr[x+i-1] = wcs[i1+i-1]
+ Memr[y+i-1] = pix[i1+i-1]
+ if (Memr[y+i-1] <= 0.)
+ if (!IS_INDEF(invgain) && invgain != 0.) {
+ sigma0 = INDEF
+ invgain = INDEF
+ call eprintf (
+ "WARNING: Cannot compute errors with non-zero gain")
+ call eprintf (
+ " and negative pixel values.\n")
+ }
+ scale = max (scale, abs (Memr[y+i-1]))
+ }
+ if (IS_INDEF(sigma0)) {
+ call amovkr (1., Memr[s], npts)
+ sscale = 1.
+ } else {
+ do i = 1, npts
+ Memr[s+i-1] = sqrt (sigma0 ** 2 + invgain * Memr[y+i-1])
+ sscale = asumr (Memr[s], npts) / npts
+ }
+ call adivkr (Memr[y], scale, Memr[y], npts)
+ call adivkr (Memr[s], sscale, Memr[s], npts)
+
+ # Allocate memory.
+ nlines = 1
+ if (ng == 0) {
+ call malloc (xg, nlines, TY_REAL)
+ call malloc (yg, nlines, TY_REAL)
+ call malloc (sg, nlines, TY_REAL)
+ call malloc (lg, nlines, TY_REAL)
+ call malloc (pg, nlines, TY_INT)
+ } else if (ng != nlines) {
+ call realloc (xg, nlines, TY_REAL)
+ call realloc (yg, nlines, TY_REAL)
+ call realloc (sg, nlines, TY_REAL)
+ call realloc (lg, nlines, TY_REAL)
+ call realloc (pg, nlines, TY_INT)
+ }
+ ng = nlines
+
+ # Do fit.
+ fit[1] = 1
+ fit[2] = 2
+ fit[3] = 2
+ fit[4] = 2
+ fit[5] = 2
+
+ # Setup initial estimates.
+ slope = (wy2-wy1) / (wx2-wx1) / scale
+ wyc = wy1 / scale - slope * wx1
+ wx = 0
+ do i = 0, npts-1 {
+ w = Memr[x+i]
+ wy = Memr[y+i] - wyc - slope * w
+ if (abs (wy) > wx) {
+ wx = abs (wy)
+ j = i
+ Memr[xg] = w
+ Memr[yg] = wy
+ }
+ }
+
+ if (j > 0 && j < npts-1) {
+ w = Memr[x+j-1]
+ wy = min (0.99, max (0.01, abs (Memr[y+j-1] - wyc - slope*w) / wx))
+ gfwhm = 2.355 * sqrt (-0.5 * (w-Memr[xg])**2 / log (wy))
+ w = Memr[x+j+1]
+ wy = min (0.99, max (0.01, abs (Memr[y+j+1] - wyc - slope*w) / wx))
+ gfwhm = (gfwhm + 2.355 * sqrt (-0.5*(w-Memr[xg])**2/log (wy))) / 2
+ } else
+ gfwhm = 0.3 * abs (Memr[x+npts-1] - Memr[x])
+
+ switch (key) {
+ case 'l':
+ Memr[sg] = 0.
+ Memr[lg] = gfwhm
+ Memi[pg] = 2
+ case 'v':
+ Memr[sg] = 0.5 * gfwhm
+ Memr[lg] = 0.5 * gfwhm
+ Memi[pg] = 3
+ default:
+ Memr[sg] = gfwhm
+ Memr[lg] = 0.
+ Memi[pg] = 1
+ }
+
+ nsub = NSUB
+ dw = (wcs[n] - wcs[1]) / (n - 1)
+ iferr (call dofit (fit, Memr[x], Memr[y], Memr[s], npts, dw, nsub,
+ wyc, slope, Memr[xg], Memr[yg], Memr[sg], Memr[lg], Memi[pg],
+ ng, chisq)) {
+ fitit = false
+ goto done_
+ }
+
+ # Compute Monte-Carlo errors.
+ if (mc_n > 9 && !IS_INDEF(sigma0)) {
+ mc_p = nint (mc_n * MC_P / 100.)
+ mc_sig = nint (mc_n * MC_SIG / 100.)
+
+ call salloc (ym, npts, TY_REAL)
+ call salloc (xg1, ng, TY_REAL)
+ call salloc (yg1, ng, TY_REAL)
+ call salloc (sg1, ng, TY_REAL)
+ call salloc (lg1, ng, TY_REAL)
+ call salloc (conte, mc_n*ng, TY_REAL)
+ call salloc (xge, mc_n*ng, TY_REAL)
+ call salloc (yge, mc_n*ng, TY_REAL)
+ call salloc (sge, mc_n*ng, TY_REAL)
+ call salloc (lge, mc_n*ng, TY_REAL)
+ call salloc (fluxe, mc_n*ng, TY_REAL)
+ call salloc (eqwe, mc_n*ng, TY_REAL)
+ do i = 1, npts {
+ w = Memr[x+i-1]
+ Memr[ym+i-1] = model (w, dw, nsub, Memr[xg], Memr[yg],
+ Memr[sg], Memr[lg], Memi[pg], ng) + wyc + slope * w
+ }
+ seed = 1
+ do i = 0, mc_n-1 {
+ if (i > 0 && mod (i, mc_p) == 0) {
+ call printf ("%2d ")
+ call pargi (100 * i / mc_n)
+ call flush (STDOUT)
+ }
+ do j = 1, npts
+ Memr[y+j-1] = Memr[ym+j-1] +
+ sscale / scale * Memr[s+j-1] * gasdev (seed)
+ wyc1 = wyc
+ slope1 = slope
+ call amovr (Memr[xg], Memr[xg1], ng)
+ call amovr (Memr[yg], Memr[yg1], ng)
+ call amovr (Memr[sg], Memr[sg1], ng)
+ call amovr (Memr[lg], Memr[lg1], ng)
+ call dorefit (fit, Memr[x], Memr[y], Memr[s], npts,
+ dw, nsub, wyc1, slope1,
+ Memr[xg1], Memr[yg1], Memr[sg1], Memr[lg1], Memi[pg], ng,
+ chisq)
+
+ do j = 0, ng-1 {
+ cont = wyc + slope * Memr[xg+j]
+ cont1 = wyc1 + slope1 * Memr[xg+j]
+ switch (Memi[pg+j]) {
+ case 1:
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j]
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j]
+ case 2:
+ flux = 1.570795 * Memr[yg+j] * Memr[lg+j]
+ flux1 = 1.570795 * Memr[yg1+j] * Memr[lg1+j]
+ case 3:
+ call voigt (0., 0.832555*Memr[lg+j]/Memr[sg+j], v, u)
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j] / v
+ call voigt (0., 0.832555*Memr[lg1+j]/Memr[sg1+j], v, u)
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j] / v
+ }
+ if (cont > 0. && cont1 > 0.) {
+ eqw = -flux / cont
+ eqw1 = -flux1 / cont1
+ } else {
+ eqw = 0.
+ eqw1 = 0.
+ }
+ Memr[conte+j*mc_n+i] = abs (cont1 - cont)
+ Memr[xge+j*mc_n+i] = abs (Memr[xg1+j] - Memr[xg+j])
+ Memr[yge+j*mc_n+i] = abs (Memr[yg1+j] - Memr[yg+j])
+ Memr[sge+j*mc_n+i] = abs (Memr[sg1+j] - Memr[sg+j])
+ Memr[lge+j*mc_n+i] = abs (Memr[lg1+j] - Memr[lg+j])
+ Memr[fluxe+j*mc_n+i] = abs (flux1 - flux)
+ Memr[eqwe+j*mc_n+i] = abs (eqw1 - eqw)
+ }
+ }
+ do j = 0, ng-1 {
+ call asrtr (Memr[conte+j*mc_n], Memr[conte+j*mc_n], mc_n)
+ call asrtr (Memr[xge+j*mc_n], Memr[xge+j*mc_n], mc_n)
+ call asrtr (Memr[yge+j*mc_n], Memr[yge+j*mc_n], mc_n)
+ call asrtr (Memr[sge+j*mc_n], Memr[sge+j*mc_n], mc_n)
+ call asrtr (Memr[lge+j*mc_n], Memr[lge+j*mc_n], mc_n)
+ call asrtr (Memr[fluxe+j*mc_n], Memr[fluxe+j*mc_n], mc_n)
+ call asrtr (Memr[eqwe+j*mc_n], Memr[eqwe+j*mc_n], mc_n)
+ }
+ call amulkr (Memr[conte], scale, Memr[conte], mc_n*ng)
+ call amulkr (Memr[yge], scale, Memr[yge], mc_n*ng)
+ call amulkr (Memr[fluxe], scale, Memr[fluxe], mc_n*ng)
+ }
+
+ call amulkr (Memr[yg], scale, Memr[yg], ng)
+ wyc = (wyc + slope * wx1) * scale
+ slope = slope * scale
+
+ # Compute model spectrum with continuum and plot.
+ fitit = true
+ do i = 1, npts {
+ w = wcs[i1+i-1]
+ Memr[z+i-1] = model (w, dw, nsub, Memr[xg], Memr[yg],
+ Memr[sg], Memr[lg], Memi[pg], ng) + wyc + slope * (w - wx1)
+ }
+
+ call gseti (gfd, G_PLTYPE, 2)
+ call gseti (gfd, G_PLCOLOR, 2)
+ call gpline (gfd, wcs[i1], Memr[z], npts)
+ call gseti (gfd, G_PLTYPE, 3)
+ call gseti (gfd, G_PLCOLOR, 3)
+ call gline (gfd, wx1, wyc, wx2, wyc + slope * (wx2 - wx1))
+ call gseti (gfd, G_PLTYPE, 1)
+ call gseti (gfd, G_PLCOLOR, 1)
+ call gflush (gfd)
+
+done_
+ # Log computed values
+ if (fitit) {
+ do i = 1, nlines {
+ w = Memr[xg+i-1]
+ cont = wyc + slope * (w - wx1)
+ peak = Memr[yg+i-1]
+ gfwhm = Memr[sg+i-1]
+ lfwhm = Memr[lg+i-1]
+ switch (Memi[pg+i-1]) {
+ case 1:
+ flux = 1.064467 * peak * gfwhm
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, flux = %8.4g, eqw = %6.4g, gfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (gfwhm)
+ case 2:
+ flux = 1.570795 * peak * lfwhm
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, flux = %8.4g, eqw = %6.4g, lfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (lfwhm)
+ case 3:
+ call voigt (0., 0.832555*lfwhm/gfwhm, v, u)
+ flux = 1.064467 * peak * gfwhm / v
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, eqw = %6.4g, gfwhm = %6.4g, lfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (eqw)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargr (w)
+ call pargr (cont)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (peak)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargr (w)
+ call pargr (cont)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (peak)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ if (mc_n > 9 && !IS_INDEF(sigma0)) {
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ " (%7.5g) (%7w) (%7.4g) (%7.4g) (%7.4g) (%7.4g) (%7.4g)\n")
+ call pargr (Memr[xge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[fluxe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[eqwe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[yge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[sge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[lge+(i-1)*mc_n+mc_sig])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ " (%7.5g) (%7w) (%7.4g) (%7.4g) (%7.4g) (%7.4g) (%7.4g)\n")
+ call pargr (Memr[xge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[fluxe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[eqwe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[yge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[sge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[lge+(i-1)*mc_n+mc_sig])
+ }
+ }
+ }
+ } else {
+ call mfree (xg, TY_REAL)
+ call mfree (yg, TY_REAL)
+ call mfree (sg, TY_REAL)
+ call mfree (lg, TY_REAL)
+ call mfree (pg, TY_INT)
+ ng = 0
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/mkpkg b/noao/onedspec/splot/mkpkg
new file mode 100644
index 00000000..43deb993
--- /dev/null
+++ b/noao/onedspec/splot/mkpkg
@@ -0,0 +1,38 @@
+# SPLOT task.
+
+$checkout libpkg.a ../
+$update libpkg.a
+$checkin libpkg.a ../
+$exit
+
+libpkg.a:
+ anshdr.x <smw.h> <time.h>
+ autoexp.x <gset.h> <mach.h> <pkg/gtools.h>
+ avgsnr.x
+ conflam.x <error.h> <smw.h>
+ confnu.x <error.h> <smw.h>
+ deblend.x <mach.h> <math.h>
+ eqwidth.x
+ eqwidthcp.x <gset.h>
+ fixx.x <smw.h>
+ flatten.x <pkg/gtools.h>
+ fudgept.x
+ fudgex.x
+ getimage.x <error.h> <imhdr.h> <pkg/gtools.h> <smw.h>
+ gfit.x <error.h> <gset.h> <mach.h>
+ mktitle.x <pkg/gtools.h> <smw.h> <units.h>
+ plotstd.x <error.h> <gset.h> <smw.h>
+ replot.x <gset.h>
+ smooth.x
+ spdeblend.x <error.h> <gset.h>
+ splabel.x <gset.h> <smw.h>
+ splot.x <error.h> <gset.h> <imhdr.h> <pkg/gtools.h> <units.h>\
+ <smw.h>
+ splotcolon.x <ctype.h> <error.h> <pkg/gtools.h> <smw.h> <units.h>
+ splotfun.x <mach.h> <smw.h> <error.h>
+ stshelp.x <error.h>
+ sumflux.x
+ usercoord.x <error.h> <smw.h> <units.h>
+ voigt.x
+ wrspect.x <error.h> <imhdr.h> <imio.h> <smw.h> <units.h>
+ ;
diff --git a/noao/onedspec/splot/mktitle.x b/noao/onedspec/splot/mktitle.x
new file mode 100644
index 00000000..554599bf
--- /dev/null
+++ b/noao/onedspec/splot/mktitle.x
@@ -0,0 +1,41 @@
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+
+# MKTITLE -- Make a spectrum title (IIDS style)
+
+procedure mktitle (sh, gt)
+
+pointer sh, gt
+
+pointer sp, str
+
+begin
+ # Do nothing if the GTOOLS pointer is undefined.
+ if (gt == NULL)
+ return
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ call sprintf (Memc[str], SZ_LINE,
+ "[%s%s]: %s %.2s ap:%d beam:%d")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (TITLE(sh))
+ call pargr (IT(sh))
+ call pargi (AP(sh))
+ call pargi (BEAM(sh))
+
+ # Set GTOOLS labels.
+ call gt_sets (gt, GTTITLE, Memc[str])
+ if (UN_LABEL(UN(sh)) != EOS) {
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ } else {
+ call gt_sets (gt, GTXLABEL, LABEL(sh))
+ call gt_sets (gt, GTXUNITS, UNITS(sh))
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/plotstd.x b/noao/onedspec/splot/plotstd.x
new file mode 100644
index 00000000..dab1554d
--- /dev/null
+++ b/noao/onedspec/splot/plotstd.x
@@ -0,0 +1,70 @@
+include <error.h>
+include <gset.h>
+include <smw.h>
+
+define VLIGHT 2.997925e18
+
+# PLOT_STD -- Plot the flux values for a standard star on current screen
+
+procedure plot_std (sh, gfd, fnu)
+
+pointer sh
+int gfd
+bool fnu
+
+pointer waves, bands, mags
+int i, nwaves
+real w1, w2
+real fnuzero, clgetr()
+double shdr_lw()
+
+begin
+ # Get calibration data.
+ iferr (call getcalib (waves, bands, mags, nwaves)) {
+ call erract (EA_WARN)
+ return
+ }
+
+ # Convert to fnu or flambda
+ fnuzero = clgetr ("fnuzero")
+ do i = 1, nwaves {
+ Memr[mags+i-1] = fnuzero * 10.0**(-0.4 * Memr[mags+i-1])
+ if (!fnu)
+ Memr[mags+i-1] = Memr[mags+i-1] * VLIGHT / Memr[waves+i-1]**2
+ }
+
+ # Overplot boxes on current plot
+ w1 = shdr_lw (sh, double(1))
+ w2 = shdr_lw (sh, double(SN(sh)))
+
+ do i = 1, nwaves
+ if (Memr[waves+i-1] > w1 && Memr[waves+i-1] < w2)
+ call plbox2 (gfd, Memr[waves+i-1]-Memr[bands+i-1]/2,
+ Memr[mags+i-1], Memr[waves+i-1]+Memr[bands+i-1]/2, .015)
+
+ call freecalib (waves, bands, mags)
+end
+
+# PLBOX2 -- Plot a box of given height and width
+
+procedure plbox2 (gfd, x1, y1, x2, ndcy)
+
+int gfd
+real x1, x2, y1, ndcy
+
+real ya1, ya2
+real wx1, wx2, wy1, wy2
+
+begin
+ # Get current WCS range
+ call ggwind (gfd, wx1, wx2, wy1, wy2)
+
+ # Adjust vertical spacing
+ ya1 = y1 - ndcy * (wy2 - wy1)
+ ya2 = y1 + ndcy * (wy2 - wy1)
+
+ call gline (gfd, x1, ya1, x2, ya1)
+ call gline (gfd, x2, ya1, x2, ya2)
+ call gline (gfd, x2, ya2, x1, ya2)
+ call gline (gfd, x1, ya2, x1, ya1)
+end
diff --git a/noao/onedspec/splot/replot.x b/noao/onedspec/splot/replot.x
new file mode 100644
index 00000000..9157846a
--- /dev/null
+++ b/noao/onedspec/splot/replot.x
@@ -0,0 +1,27 @@
+include <gset.h>
+
+# REPLOT -- Replot the current array
+
+procedure replot (gfd, gt, x, y, npts, clear)
+
+pointer gfd
+pointer gt
+real x[ARB]
+real y[ARB]
+int npts
+int clear
+
+int wc, gstati()
+
+begin
+ if (clear == YES) {
+ wc = gstati (gfd, G_WCS)
+ call gclear (gfd)
+ call gseti (gfd, G_WCS, wc)
+ call gt_ascale (gfd, gt, x, y, npts)
+ call gt_swind (gfd, gt)
+ call gt_labax (gfd, gt)
+ }
+
+ call gt_plot (gfd, gt, x, y, npts)
+end
diff --git a/noao/onedspec/splot/smooth.x b/noao/onedspec/splot/smooth.x
new file mode 100644
index 00000000..1418fc4f
--- /dev/null
+++ b/noao/onedspec/splot/smooth.x
@@ -0,0 +1,54 @@
+# SMOOTH -- Box smooth the array
+
+procedure smooth (y, n)
+
+real y[ARB]
+int n
+
+int i, j, boxsize, halfbox, del
+int nsum
+real sum
+pointer sp, smy
+
+int clgeti()
+
+begin
+ call smark (sp)
+ call salloc (smy, n, TY_REAL)
+
+ # Get boxsize
+ boxsize = clgeti ("boxsize")
+ if (mod (boxsize, 2) == 0) {
+ boxsize = boxsize + 1
+ call eprintf ("WARNING: Using a box size of %d")
+ call pargi (boxsize)
+ }
+
+ halfbox = boxsize/2
+
+ # This is not efficiently coded, but easy to code
+ # A running box mean would be faster
+ do i = 1, n {
+ sum = 0.0
+ nsum = 0
+
+ if (i > halfbox && i < (n-halfbox))
+ del = halfbox
+ else
+ if (i <= halfbox)
+ del = i/2
+ else
+ del = (n - i + 1)/2
+
+ do j = i-del, i+del {
+ nsum = nsum + 1
+ sum = sum + y[j]
+ }
+
+ Memr[smy+i-1] = sum / nsum
+ }
+
+ # Replace pixels back
+ call amovr (Memr[smy], y, n)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/spdeblend.x b/noao/onedspec/splot/spdeblend.x
new file mode 100644
index 00000000..a07cd52d
--- /dev/null
+++ b/noao/onedspec/splot/spdeblend.x
@@ -0,0 +1,819 @@
+include <error.h>
+include <gset.h>
+
+define NSUB 3 # Number of pixel subsamples
+define MC_N 50 # Monte-Carlo samples
+define MC_P 10 # Percent done interval (percent)
+define MC_SIG 68 # Sigma sample point (percent)
+
+# Profile types.
+define PTYPES "|gaussian|lorentzian|voigt|"
+define GAUSS 1 # Gaussian profile
+define LORENTZ 2 # Lorentzian profile
+define VOIGT 3 # Voigt profile
+
+
+# SP_DEBLEND -- Deblend lines in a spectral region.
+
+procedure sp_deblend (sh, gfd, wx1, wy1, wcs, pix, n, fd1, fd2,
+ xg, yg, sg, lg, pg, ng)
+
+pointer sh # SHDR pointer
+pointer gfd # GIO file descriptor
+real wx1, wy1 # Cursor position
+real wcs[n] # Coordinates
+real pix[n] # Spectrum data
+int n # Number of points
+int fd1, fd2 # Output file descriptors
+pointer xg, yg, sg, lg, pg # Pointers to fit parameters
+int ng # Number of components
+
+int fit[5], nsub, mc_p, mc_sig, mc_n
+int i, j, i1, npts, nlines, maxlines, wc, key, type, ifit
+long seed
+real w, dw, wyc, wx, wy, wx2, wy2, u, v
+real slope, peak, flux, cont, gfwhm, lfwhm, eqw, scale, sscale, chisq, rms
+real sigma0, invgain, wyc1, slope1, flux1, cont1, eqw1
+bool fitit, fitg, fitl
+pointer xg1, yg1, sg1, lg1
+pointer sp, cmd, x, y, s, z, waves, types, gfwhms, lfwhms, peaks, ym
+pointer conte, xge, yge, sge, lge, fluxe, eqwe
+
+int clgeti(), clgcur(), open(), fscan(), nscan(), strdic()
+real clgetr(), model(), gasdev(), asumr()
+double shdr_wl()
+errchk dofit, dorefit
+
+define fitp_ 95
+define fitg_ 96
+define fitl_ 97
+define fitb_ 98
+define done_ 99
+
+begin
+ call smark (sp)
+ call salloc (cmd, SZ_FNAME, TY_CHAR)
+
+ # Input cursor is first continuum point now get second continuum point.
+ call printf ("d again:")
+ if (clgcur ("cursor", wx2, wy2, wc, key, Memc[cmd], SZ_FNAME) == EOF) {
+ call sfree (sp)
+ return
+ }
+
+ # Set pixel indices and determine number of points to fit.
+ call fixx (sh, wx1, wx2, wy1, wy2, i1, j)
+
+ npts = j - i1 + 1
+ if (npts < 3) {
+ call eprintf ("At least 3 points are required\n")
+ call sfree (sp)
+ return
+ }
+
+ # Allocate space for the points to be fit.
+ call salloc (x, npts, TY_REAL)
+ call salloc (y, npts, TY_REAL)
+ call salloc (s, npts, TY_REAL)
+ call salloc (z, npts, TY_REAL)
+
+ # Scale the data.
+ sigma0 = clgetr ("sigma0")
+ invgain = clgetr ("invgain")
+ mc_n = clgeti ("nerrsample")
+ if (IS_INDEF(sigma0) || IS_INDEF(invgain) || sigma0<0. ||
+ invgain<0. || (sigma0 == 0. && invgain == 0.)) {
+ sigma0 = INDEF
+ invgain = INDEF
+ }
+ scale = 0.
+ do i = 1, npts {
+ Memr[x+i-1] = wcs[i1+i-1]
+ Memr[y+i-1] = pix[i1+i-1]
+ if (Memr[y+i-1] <= 0.)
+ if (invgain != 0.) {
+ sigma0 = INDEF
+ invgain = INDEF
+ call eprintf (
+ "WARNING: Cannot compute errors with non-zero gain")
+ call eprintf (
+ " and negative pixel values.\n")
+ }
+ scale = max (scale, abs (Memr[y+i-1]))
+ }
+ if (IS_INDEF(sigma0)) {
+ call amovkr (1., Memr[s], npts)
+ sscale = 1.
+ } else {
+ do i = 1, npts
+ Memr[s+i-1] = sqrt (sigma0 ** 2 + invgain * Memr[y+i-1])
+ sscale = asumr (Memr[s], npts) / npts
+ }
+ call adivkr (Memr[y], scale, Memr[y], npts)
+ call adivkr (Memr[s], sscale, Memr[s], npts)
+
+ # Select the lines to be fit. If no lines return.
+ fitit = false
+ fitg = false
+ fitl = false
+ maxlines = 5
+ call malloc (waves, maxlines, TY_REAL)
+ call malloc (peaks, maxlines, TY_REAL)
+ call malloc (gfwhms, maxlines, TY_REAL)
+ call malloc (lfwhms, maxlines, TY_REAL)
+ call malloc (types, maxlines, TY_INT)
+ nlines = 0
+ call printf (
+ "Lines ('f'ile, 'g'aussian, 'l'orentzian, 'v'oigt, 't'ype, 'q'uit:")
+ while (clgcur ("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME) != EOF) {
+ switch (key) {
+ case 'f':
+ call clgstr ("linelist", Memc[cmd], SZ_FNAME)
+ call printf (
+ "Lines ('f'ile, 'g'aussian, 'l'orentzian, 'v'oigt, 't'ype, 'q'uit:")
+ iferr (j = open (Memc[cmd], READ_ONLY, TEXT_FILE)) {
+ call erract (EA_WARN)
+ next
+ }
+ while (fscan (j) != EOF) {
+ call gargr (wx)
+ if (nscan() < 1)
+ next
+ if (wx < min (wcs[1], wcs[n]) || wx > max (wcs[1], wcs[n]))
+ next
+ call gargr (peak)
+ call gargwrd (Memc[cmd], SZ_FNAME)
+ call gargr (gfwhm)
+ call gargr (lfwhm)
+ type = strdic (Memc[cmd], Memc[cmd], SZ_FNAME, PTYPES)
+ if (type == 0)
+ type = GAUSS
+ switch (nscan()) {
+ case 0:
+ next
+ case 1:
+ peak = INDEF
+ type = GAUSS
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 2:
+ type = GAUSS
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 3:
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 4:
+ switch (type) {
+ case GAUSS:
+ lfwhm = INDEF
+ case LORENTZ:
+ lfwhm = gfwhm
+ gfwhm = INDEF
+ case VOIGT:
+ lfwhm = INDEF
+ }
+ }
+ for (i = 0; i < nlines && wx != Memr[waves+i]; i = i + 1)
+ ;
+ if (i == nlines) {
+ if (nlines == maxlines) {
+ maxlines = maxlines + 5
+ call realloc (waves, maxlines, TY_REAL)
+ call realloc (peaks, maxlines, TY_REAL)
+ call realloc (gfwhms, maxlines, TY_REAL)
+ call realloc (lfwhms, maxlines, TY_REAL)
+ call realloc (types, maxlines, TY_INT)
+ }
+ Memr[waves+i] = wx
+ Memr[peaks+i] = peak
+ Memr[gfwhms+i] = gfwhm
+ Memr[lfwhms+i] = lfwhm
+ Memi[types+i] = type
+ switch (type) {
+ case GAUSS:
+ fitg = true
+ case LORENTZ:
+ fitl = true
+ case VOIGT:
+ fitg = true
+ fitl = true
+ }
+ nlines = nlines + 1
+ call gmark (gfd, wx, wy, GM_VLINE, 3., 3.)
+ }
+ }
+ call close (j)
+ next
+ case 'g':
+ type = GAUSS
+ peak = INDEF
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 'l':
+ type = LORENTZ
+ peak = INDEF
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 'v':
+ type = VOIGT
+ peak = INDEF
+ gfwhm = INDEF
+ lfwhm = INDEF
+ case 't':
+ type = GAUSS
+ wx = clgetr ("wavelength")
+ peak = INDEF
+ gfwhm = INDEF
+ lfwhm = INDEF
+ call printf (
+ "Lines ('f'ile, 'g'aussian, 'l'orentzian, 'v'oigt, 't'ype, 'q'uit:")
+ case 'q':
+ call printf ("\n")
+ break
+ case 'I':
+ call fatal (0, "Interrupt")
+ default:
+ call printf (
+ "Lines ('f'ile, 'g'aussian, 'l'orentzian, 'v'oigt, 't'ype, 'q'uit:\007")
+ next
+ }
+ for (i = 0; i < nlines && wx != Memr[waves+i]; i = i + 1)
+ ;
+ if (i == nlines) {
+ if (nlines == maxlines) {
+ maxlines = maxlines + 5
+ call realloc (waves, maxlines, TY_REAL)
+ call realloc (peaks, maxlines, TY_REAL)
+ call realloc (gfwhms, maxlines, TY_REAL)
+ call realloc (lfwhms, maxlines, TY_REAL)
+ call realloc (types, maxlines, TY_INT)
+ }
+ Memr[waves+i] = wx
+ Memr[peaks+i] = peak
+ Memr[gfwhms+i] = gfwhm
+ Memr[lfwhms+i] = lfwhm
+ Memi[types+i] = type
+ switch (type) {
+ case GAUSS:
+ fitg = true
+ case LORENTZ:
+ fitl = true
+ case VOIGT:
+ fitg = true
+ fitl = true
+ }
+ nlines = nlines + 1
+ call gmark (gfd, wx, wy, GM_VLINE, 3., 3.)
+ }
+ }
+ if (nlines == 0)
+ goto done_
+
+ # Allocate memory.
+ if (ng == 0) {
+ call malloc (xg, nlines, TY_REAL)
+ call malloc (yg, nlines, TY_REAL)
+ call malloc (sg, nlines, TY_REAL)
+ call malloc (lg, nlines, TY_REAL)
+ call malloc (pg, nlines, TY_INT)
+ } else if (ng != nlines) {
+ call realloc (xg, nlines, TY_REAL)
+ call realloc (yg, nlines, TY_REAL)
+ call realloc (sg, nlines, TY_REAL)
+ call realloc (lg, nlines, TY_REAL)
+ call realloc (pg, nlines, TY_INT)
+ }
+ ng = nlines
+
+ # Do fits.
+ ifit = 0
+ fit[3] = 3
+ repeat {
+fitp_ call printf ("Fit positions (fixed, single, all, quit) ")
+ if (clgcur ("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME) == EOF)
+ break
+ switch (key) {
+ case 'f':
+ fit[2] = 1
+ case 's':
+ fit[2] = 2
+ case 'a':
+ fit[2] = 3
+ case 'q':
+ break
+ default:
+ goto fitp_
+ }
+ if (fitg) {
+fitg_ call printf ("Fit Gaussian widths (fixed, single, all, quit) ")
+ if (clgcur("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME)==EOF)
+ break
+ switch (key) {
+ case 'f':
+ fit[4] = 1
+ case 's':
+ fit[4] = 2
+ case 'a':
+ fit[4] = 3
+ case 'q':
+ break
+ default:
+ goto fitg_
+ }
+ }
+ if (fitl) {
+fitl_ call printf (
+ "Fit Lorentzian widths (fixed, single, all, quit) ")
+ if (clgcur("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME)==EOF)
+ break
+ switch (key) {
+ case 'f':
+ fit[5] = 1
+ case 's':
+ fit[5] = 2
+ case 'a':
+ fit[5] = 3
+ case 'q':
+ break
+ default:
+ goto fitl_
+ }
+ }
+fitb_ call printf ("Fit background (no, yes, quit) ")
+ if (clgcur ("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME) == EOF)
+ break
+ switch (key) {
+ case 'n':
+ fit[1] = 1
+ case 'y':
+ fit[1] = 2
+ case 'q':
+ break
+ default:
+ goto fitb_
+ }
+ call printf ("Fitting...")
+ call flush (STDOUT)
+
+ # Setup initial estimates.
+ if (ifit == 0) {
+ slope = (wy2-wy1) / (wx2-wx1) / scale
+ wyc = wy1 / scale - slope * wx1
+ eqw = abs (Memr[x+npts-1] - Memr[x]) / nlines
+ do i = 0, nlines-1 {
+ w = Memr[waves+i]
+ peak = Memr[peaks+i]
+ gfwhm = Memr[gfwhms+i]
+ lfwhm = Memr[lfwhms+i]
+ type = Memi[types+i]
+ j = max (1, min (n, nint (shdr_wl (sh, double(w)))))
+ Memr[xg+i] = w
+ if (IS_INDEF(peak))
+ Memr[yg+i] = pix[j] / scale - wyc - slope * w
+ else
+ Memr[yg+i] = peak / scale
+ Memr[sg+i] = 0.
+ Memr[lg+i] = 0.
+ Memi[pg+i] = type
+ switch (type) {
+ case GAUSS:
+ if (IS_INDEF(gfwhm))
+ Memr[sg+i] = 0.3 * eqw
+ else
+ Memr[sg+i] = gfwhm
+ case LORENTZ:
+ if (IS_INDEF(lfwhm))
+ Memr[lg+i] = 0.3 * eqw
+ else
+ Memr[lg+i] = lfwhm
+ case VOIGT:
+ if (IS_INDEF(Memr[gfwhms+i]))
+ Memr[sg+i] = 0.1 * eqw
+ else
+ Memr[sg+i] = gfwhm
+ if (IS_INDEF(Memr[lfwhms+i]))
+ Memr[lg+i] = 0.1 * eqw
+ else
+ Memr[lg+i] = lfwhm
+ }
+ }
+ } else {
+ call adivkr (Memr[yg], scale, Memr[yg], ng)
+ slope = slope / scale
+ wyc = wyc / scale - slope * wx1
+ }
+
+ nsub = NSUB
+ dw = (wcs[n] - wcs[1]) / (n - 1)
+ iferr (call dofit (fit, Memr[x], Memr[y], Memr[s], npts,
+ dw, nsub, wyc, slope,
+ Memr[xg], Memr[yg], Memr[sg], Memr[lg], Memi[pg], ng, chisq)) {
+ call erract (EA_WARN)
+ next
+ }
+ ifit = ifit + 1
+
+ # Compute Monte-Carlo errors.
+ if (mc_n > 9 && !IS_INDEF(sigma0)) {
+ mc_p = nint (mc_n * MC_P / 100.)
+ mc_sig = nint (mc_n * MC_SIG / 100.)
+
+ call salloc (ym, npts, TY_REAL)
+ call salloc (xg1, ng, TY_REAL)
+ call salloc (yg1, ng, TY_REAL)
+ call salloc (sg1, ng, TY_REAL)
+ call salloc (lg1, ng, TY_REAL)
+ call salloc (conte, mc_n*ng, TY_REAL)
+ call salloc (xge, mc_n*ng, TY_REAL)
+ call salloc (yge, mc_n*ng, TY_REAL)
+ call salloc (sge, mc_n*ng, TY_REAL)
+ call salloc (lge, mc_n*ng, TY_REAL)
+ call salloc (fluxe, mc_n*ng, TY_REAL)
+ call salloc (eqwe, mc_n*ng, TY_REAL)
+ do i = 1, npts {
+ w = Memr[x+i-1]
+ Memr[ym+i-1] = model (w, dw, nsub, Memr[xg], Memr[yg],
+ Memr[sg], Memr[lg], Memi[pg], ng) + wyc + slope * w
+ }
+ seed = 1
+ do i = 0, mc_n-1 {
+ if (i > 0 && mod (i, mc_p) == 0) {
+ call printf ("%2d ")
+ call pargi (100 * i / mc_n)
+ call flush (STDOUT)
+ }
+ do j = 1, npts
+ Memr[y+j-1] = Memr[ym+j-1] +
+ sscale / scale * Memr[s+j-1] * gasdev (seed)
+ wyc1 = wyc
+ slope1 = slope
+ call amovr (Memr[xg], Memr[xg1], ng)
+ call amovr (Memr[yg], Memr[yg1], ng)
+ call amovr (Memr[sg], Memr[sg1], ng)
+ call amovr (Memr[lg], Memr[lg1], ng)
+ call dorefit (fit, Memr[x], Memr[y], Memr[s], npts,
+ dw, nsub, wyc1, slope1,
+ Memr[xg1], Memr[yg1], Memr[sg1], Memr[lg1], Memi[pg],
+ ng, chisq)
+
+ do j = 0, ng-1 {
+ cont = wyc + slope * Memr[xg+j]
+ cont1 = wyc1 + slope1 * Memr[xg+j]
+ switch (Memi[pg+j]) {
+ case GAUSS:
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j]
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j]
+ case LORENTZ:
+ flux = 1.570795 * Memr[yg+j] * Memr[lg+j]
+ flux1 = 1.570795 * Memr[yg1+j] * Memr[lg1+j]
+ case VOIGT:
+ call voigt (0., 0.832555*Memr[lg+j]/Memr[sg+j],
+ v, u)
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j] / v
+ call voigt (0., 0.832555*Memr[lg1+j]/Memr[sg1+j],
+ v, u)
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j] / v
+ }
+ if (cont > 0. && cont1 > 0.) {
+ eqw = -flux / cont
+ eqw1 = -flux1 / cont1
+ } else {
+ eqw = 0.
+ eqw1 = 0.
+ }
+ Memr[conte+j*mc_n+i] = abs (cont1 - cont)
+ Memr[xge+j*mc_n+i] = abs (Memr[xg1+j] - Memr[xg+j])
+ Memr[yge+j*mc_n+i] = abs (Memr[yg1+j] - Memr[yg+j])
+ Memr[sge+j*mc_n+i] = abs (Memr[sg1+j] - Memr[sg+j])
+ Memr[lge+j*mc_n+i] = abs (Memr[lg1+j] - Memr[lg+j])
+ Memr[fluxe+j*mc_n+i] = abs (flux1 - flux)
+ Memr[eqwe+j*mc_n+i] = abs (eqw1 - eqw)
+ }
+ }
+ do j = 0, ng-1 {
+ call asrtr (Memr[conte+j*mc_n], Memr[conte+j*mc_n], mc_n)
+ call asrtr (Memr[xge+j*mc_n], Memr[xge+j*mc_n], mc_n)
+ call asrtr (Memr[yge+j*mc_n], Memr[yge+j*mc_n], mc_n)
+ call asrtr (Memr[sge+j*mc_n], Memr[sge+j*mc_n], mc_n)
+ call asrtr (Memr[lge+j*mc_n], Memr[lge+j*mc_n], mc_n)
+ call asrtr (Memr[fluxe+j*mc_n], Memr[fluxe+j*mc_n], mc_n)
+ call asrtr (Memr[eqwe+j*mc_n], Memr[eqwe+j*mc_n], mc_n)
+ }
+ call amulkr (Memr[conte], scale, Memr[conte], mc_n*ng)
+ call amulkr (Memr[yge], scale, Memr[yge], mc_n*ng)
+ call amulkr (Memr[fluxe], scale, Memr[fluxe], mc_n*ng)
+ }
+
+ call amulkr (Memr[yg], scale, Memr[yg], ng)
+ wyc = (wyc + slope * wx1) * scale
+ slope = slope * scale
+
+ fitit = true
+
+ # Compute model spectrum with continuum and plot.
+ call printf ("Overplot (total, components, both, none) ")
+ if (clgcur ("cursor", wx, wy, wc, key, Memc[cmd], SZ_FNAME) == EOF)
+ break
+
+ rms = 0.
+ do i = 1, npts {
+ w = Memr[x+i-1]
+ Memr[z+i-1] = model (w, dw, nsub, Memr[xg], Memr[yg],
+ Memr[sg], Memr[lg], Memi[pg], ng)
+ Memr[z+i-1] = Memr[z+i-1] + wyc + slope * (w - wx1)
+ rms = rms + (Memr[z+i-1] / scale - Memr[y+i-1]) ** 2
+ }
+
+ # Total.
+ if (key == 't' || key == 'b') {
+ call gseti (gfd, G_PLTYPE, 2)
+ call gseti (gfd, G_PLCOLOR, 2)
+ call gpline (gfd, Memr[x], Memr[z], npts)
+ call gseti (gfd, G_PLTYPE, 1)
+ call gflush (gfd)
+ }
+
+ # Components.
+ if (key == 'c' || key == 'b') {
+ call gseti (gfd, G_PLTYPE, 3)
+ call gseti (gfd, G_PLCOLOR, 5)
+ do j = 0, ng-1 {
+ do i = 1, npts {
+ w = Memr[x+i-1]
+ Memr[z+i-1] = model (w, dw, nsub, Memr[xg+j], Memr[yg+j],
+ Memr[sg+j], Memr[lg+j], Memi[pg+j], 1)
+ Memr[z+i-1] = Memr[z+i-1] + wyc + slope * (w - wx1)
+ }
+ call gpline (gfd, Memr[x], Memr[z], npts)
+ }
+ call gseti (gfd, G_PLTYPE, 1)
+ call gflush (gfd)
+ }
+
+ if (key != 'n') {
+ call gseti (gfd, G_PLTYPE, 4)
+ call gseti (gfd, G_PLCOLOR, 3)
+ call gline (gfd, wx1, wyc, wx2, wyc + slope * (wx2 - wx1))
+ call gseti (gfd, G_PLTYPE, 1)
+ call gflush (gfd)
+ }
+
+
+ # Print computed values on status line.
+ i = 1
+ key = ''
+ repeat {
+ switch (key) {
+ case '-':
+ i = i - 1
+ if (i < 1)
+ i = nlines
+ case '+':
+ i = i + 1
+ if (i > nlines)
+ i = 1
+ case 'q':
+ call printf ("\n")
+ break
+ }
+
+ if (key == 'r') {
+ call printf ("\nrms = %8.4g")
+ call pargr (scale * sqrt (chisq / npts))
+ } else {
+ w = Memr[xg+i-1]
+ cont = wyc + slope * (w - wx1)
+ peak = Memr[yg+i-1]
+ gfwhm = Memr[sg+i-1]
+ lfwhm = Memr[lg+i-1]
+ switch (Memi[pg+i-1]) {
+ case GAUSS:
+ flux = 1.064467 * peak * gfwhm
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, flux = %8.4g, eqw = %6.4g, gfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (gfwhm)
+ case LORENTZ:
+ flux = 1.570795 * peak * lfwhm
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, flux = %8.4g, eqw = %6.4g, lfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (lfwhm)
+ case VOIGT:
+ call voigt (0., 0.832555*lfwhm/gfwhm, v, u)
+ flux = 1.064467 * peak * gfwhm / v
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ call printf (
+ "\n%d: center = %8.6g, eqw = %6.4g, gfwhm = %6.4g, lfwhm = %6.4g")
+ call pargi (i)
+ call pargr (w)
+ call pargr (eqw)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ }
+
+ call printf (" (+,-,r,q):")
+ call flush (STDOUT)
+ } until (clgcur ("cursor",
+ wx, wy, wc, key, Memc[cmd], SZ_FNAME) == EOF)
+ }
+
+done_
+ call printf ("Deblending done\n")
+ # Log computed values
+ if (fitit) {
+ do i = 1, nlines {
+ w = Memr[xg+i-1]
+ cont = wyc + slope * (w - wx1)
+ peak = Memr[yg+i-1]
+ gfwhm = Memr[sg+i-1]
+ lfwhm = Memr[lg+i-1]
+ switch (Memi[pg+i-1]) {
+ case GAUSS:
+ flux = 1.064467 * peak * gfwhm
+ case LORENTZ:
+ flux = 1.570795 * peak * lfwhm
+ case VOIGT:
+ call voigt (0., 0.832555*lfwhm/gfwhm, v, u)
+ flux = 1.064467 * peak * gfwhm / v
+ }
+
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargr (w)
+ call pargr (cont)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (peak)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargr (w)
+ call pargr (cont)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (peak)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ }
+ if (mc_n > 9 && !IS_INDEF(sigma0)) {
+ if (fd1 != NULL) {
+ call fprintf (fd1,
+ " (%7.5g) (%7.5g) (%7.4g) (%7.4g) (%7.4g) (%7.4g) (%7.4g)\n")
+ call pargr (Memr[xge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[conte+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[fluxe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[eqwe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[yge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[sge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[lge+(i-1)*mc_n+mc_sig])
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2,
+ " (%7.5g) (%7.5g) (%7.4g) (%7.4g) (%7.4g) (%7.4g) (%7.4g)\n")
+ call pargr (Memr[xge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[conte+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[fluxe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[eqwe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[yge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[sge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[lge+(i-1)*mc_n+mc_sig])
+ }
+ }
+ }
+ } else {
+ call mfree (xg, TY_REAL)
+ call mfree (yg, TY_REAL)
+ call mfree (sg, TY_REAL)
+ call mfree (lg, TY_REAL)
+ call mfree (pg, TY_INT)
+ ng = 0
+ }
+
+ call mfree (waves, TY_REAL)
+ call mfree (peaks, TY_REAL)
+ call mfree (gfwhms, TY_REAL)
+ call mfree (lfwhms, TY_REAL)
+ call mfree (types, TY_INT)
+ call sfree (sp)
+end
+
+
+# SUBBLEND -- Subtract last fit.
+
+procedure subblend (sh, gfd, x, y, n, wx1, wy1, xg, yg, sg, lg, pg, ng)
+
+pointer sh # SHDR pointer
+pointer gfd # GIO file descriptor
+real wx1, wy1 # Cursor position
+real x[n] # Spectrum data
+real y[n] # Spectrum data
+int n # Number of points
+pointer xg, yg, sg, lg, pg # Pointers to fit parameters
+int ng # Number of components
+
+int i, j, i1, npts, wc, key, nsub
+real wx2, wy2, dw
+pointer sp, cmd
+
+int clgcur()
+real model()
+
+begin
+ if (ng == 0)
+ return
+
+ call smark (sp)
+ call salloc (cmd, SZ_FNAME, TY_CHAR)
+
+ # Determine fit range
+ call printf ("- again:")
+ call flush (STDOUT)
+ if (clgcur ("cursor", wx2, wy2, wc, key, Memc[cmd], SZ_FNAME) == EOF) {
+ call sfree (sp)
+ return
+ }
+
+ call fixx (sh, wx1, wx2, wy1, wy2, i1, j)
+ npts = j - i1 + 1
+
+ dw = (x[n] - x[1]) / (n - 1)
+ nsub = NSUB
+ do i = 1, npts {
+ y[i1+i-1] = y[i1+i-1] -
+ model (x[i1+i-1], dw, nsub, Memr[xg], Memr[yg], Memr[sg],
+ Memr[lg], Memi[pg], ng)
+ }
+
+ # Plot subtracted curve
+ call gpline (gfd, x[i1], y[i1], npts)
+ call gflush (gfd)
+
+ call mfree (xg, TY_REAL)
+ call mfree (yg, TY_REAL)
+ call mfree (sg, TY_REAL)
+ call mfree (lg, TY_REAL)
+ call mfree (pg, TY_INT)
+ ng = 0
+ call sfree (sp)
+end
+
+
+# GASDEV -- Return a normally distributed deviate with zero mean and unit
+# variance. The method computes two deviates simultaneously.
+#
+# Based on Numerical Recipes by Press, Flannery, Teukolsky, and Vetterling.
+# Used by permission of the authors.
+# Copyright(c) 1986 Numerical Recipes Software.
+
+real procedure gasdev (seed)
+
+long seed # Seed for random numbers
+
+real v1, v2, r, fac, urand()
+int iset
+data iset/0/
+
+begin
+ if (iset == 0) {
+ repeat {
+ v1 = 2 * urand (seed) - 1.
+ v2 = 2 * urand (seed) - 1.
+ r = v1 ** 2 + v2 ** 2
+ } until ((r > 0) && (r < 1))
+ fac = sqrt (-2. * log (r) / r)
+
+ iset = 1
+ return (v1 * fac)
+ } else {
+ iset = 0
+ return (v2 * fac)
+ }
+end
diff --git a/noao/onedspec/splot/splabel.x b/noao/onedspec/splot/splabel.x
new file mode 100644
index 00000000..fcba2584
--- /dev/null
+++ b/noao/onedspec/splot/splabel.x
@@ -0,0 +1,112 @@
+include <gset.h>
+include <smw.h>
+
+define OPTIONS "|label|mabove|mbelow|"
+define LABEL 1 # Label at cursor position
+define MABOVE 2 # Tick mark plus label above spectrum
+define MBELOW 3 # Tick mark plus label below spectrum
+
+
+# SPLABEL -- Add a label.
+
+procedure splabel (option, sh, gp, x, y, label, format)
+
+char option[ARB] #I Label option
+pointer sh #I Spectrum object
+pointer gp #I Graphics object
+real x, y #I Label position
+char label[ARB] #I Label
+char format[ARB] #I Format
+
+int op, pix, color, markcolor, strdic(), gstati()
+real mx, my, x1, x2, y1, y2
+pointer sp, fmt, lab
+double shdr_wl()
+
+define TICK .03 # Tick size in NDC
+define GAP .02 # Gap size in NDC
+
+begin
+ call smark (sp)
+ call salloc (fmt, SZ_LINE, TY_CHAR)
+ call salloc (lab, SZ_LINE, TY_CHAR)
+
+ op = strdic (option, Memc[lab], SZ_LINE, OPTIONS)
+ if (op == 0) {
+ call sfree (sp)
+ return
+ }
+ call ggwind (gp, x1, x2, y1, y2)
+ if ((x < min (x1, x2)) || (x > max (x1, x2))) {
+ call sfree (sp)
+ return
+ }
+
+ # Set label position and draw tick mark.
+ switch (op) {
+ case LABEL:
+ call gctran (gp, x, y, mx, my, 1, 0)
+ call gctran (gp, mx, my, x1, y2, 0, 1)
+ markcolor = gstati (gp, G_TICKLABELCOLOR)
+ if (format[1] == EOS)
+ call strcpy ("h=c;v=c;s=1.0", Memc[fmt], SZ_LINE)
+ else
+ call strcpy (format, Memc[fmt], SZ_LINE)
+
+ case MABOVE:
+ pix = max (2, min (SN(sh)-3, int (shdr_wl (sh, double (x)))))
+ y1 = max (Memr[SY(sh)+pix-2], Memr[SY(sh)+pix-1],
+ Memr[SY(sh)+pix], Memr[SY(sh)+pix+1])
+ call gctran (gp, x, y1, mx, my, 1, 0)
+ call gctran (gp, mx, my + GAP, x1, y1, 0, 1)
+ call gctran (gp, mx, my + GAP + TICK, x1, y2, 0, 1)
+
+ color = gstati (gp, G_PLCOLOR)
+ markcolor = gstati (gp, G_TICKLABELCOLOR)
+ call gseti (gp, G_PLCOLOR, markcolor)
+ call gline (gp, x1, y1, x1, y2)
+ call gseti (gp, G_PLCOLOR, color)
+
+ call gctran (gp, mx, my + TICK + 2 * GAP, x1, y2, 0, 1)
+ if (format[1] == EOS)
+ call strcpy ("u=180;h=c;v=b;s=0.5", Memc[fmt], SZ_LINE)
+ else
+ call strcpy (format, Memc[fmt], SZ_LINE)
+
+ case MBELOW:
+ pix = max (2, min (SN(sh)-3, int (shdr_wl (sh, double (x)))))
+ y1 = min (Memr[SY(sh)+pix-2], Memr[SY(sh)+pix-1],
+ Memr[SY(sh)+pix], Memr[SY(sh)+pix+1])
+ call gctran (gp, x, y1, mx, my, 1, 0)
+ call gctran (gp, mx, my - GAP, x1, y1, 0, 1)
+ call gctran (gp, mx, my - GAP - TICK, x1, y2, 0, 1)
+
+ color = gstati (gp, G_PLCOLOR)
+ markcolor = gstati (gp, G_TICKLABELCOLOR)
+ call gseti (gp, G_PLCOLOR, markcolor)
+ call gline (gp, x1, y1, x1, y2)
+ call gseti (gp, G_PLCOLOR, color)
+
+ call gctran (gp, mx, my - TICK - 2 * GAP, x1, y2, 0, 1)
+ if (format[1] == EOS)
+ call strcpy ("u=0;h=c;v=t;s=0.5", Memc[fmt], SZ_LINE)
+ else
+ call strcpy (format, Memc[fmt], SZ_LINE)
+ }
+
+ # Draw the label.
+ if (label[1] != EOS) {
+ color = gstati (gp, G_TXCOLOR)
+ call gseti (gp, G_TXCOLOR, markcolor)
+ if (label[1] == '%') {
+ call sprintf (Memc[lab], SZ_LINE, label)
+ call pargr (x)
+ call gtext (gp, x1, y2, Memc[lab], Memc[fmt])
+ } else
+ call gtext (gp, x1, y2, label, Memc[fmt])
+ call gseti (gp, G_TXCOLOR, color)
+ }
+
+ call gflush (gp)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/splot.key b/noao/onedspec/splot/splot.key
new file mode 100644
index 00000000..b78c722d
--- /dev/null
+++ b/noao/onedspec/splot/splot.key
@@ -0,0 +1,116 @@
+? - This display r - Redraw the current window
+/ - Cycle thru short help on stat line s - Smooth (boxcar)
+a - Autoexpand between cursors t - Fit continuum(*)
+b - Toggle base plot level to 0.0 u - Adjust coordinate scale(*)
+c - Clear and redraw full spectrum v - Velocity scale (toggle)
+d - Deblend lines using profile models w - Window the graph
+e - Equiv. width, integ flux, center x - Connects 2 cursor positions
+f - Arithmetic functions: log, sqrt... y - Plot std star flux from calib file
+g - Get new image and plot z - Expand x range by factor of 2
+h - Equivalent widths(*) ) - Go to next spectrum in image
+i - Write current image as new image ( - Go to previous spectrum in image
+j - Fudge a point to Y-cursor value # - Select new line/aperture
+k - Profile fit to single line(*) % - Select new band
+l - Convert to F-lambda $ - Toggle wavelength/pixel scale
+m - Mean, RMS, snr in marked region - - Subtract deblended fit
+n - Convert to F-nu , - Down slide spectrum
+o - Toggle overplot of following plot . - Up slide spectrum
+p - Convert to wavelength scale I - Interrupt task immediately
+q - Quit and exit <space> - Cursor position and flux
+
+(*) For 'h' key: Measure equivalent widths
+ a - Left side for width at 1/2 flux l - Left side for continuum = 1
+ b - Right side for width at 1/2 flux r - Right side for continuum = 1
+ c - Both sides for width at 1/2 flux k - Both sides for continuum = 1
+
+(*) For 'k' key: Second key may be used to select profile type
+ g - Gaussian, l - Lorentzian, v - Voigt, all others - Gaussiank
+
+(*) For 't' key: Fit the continuum with ICFIT and apply to spectrum
+ / = normalize by the continuum fit
+ - = subtract the continuum fit (residuals)
+ f = replace spectrum by the continuum fit
+ c = clean spectrum of rejected points
+ n = do the fitting but leave the spectrum unchanged
+ q = quit without fitting or modifying spectrum
+
+(*) For 'u' key: Adjust the coordinate scale by marking features
+ d = apply doppler correction to bring marked feature to specified coord.
+ l = set linear (in wavelength) coordinates based on two marked features
+ z = apply zero point shift to bring marked feature to
+ specified coordinate
+
+The colon commands do not allow abbreviations.
+
+:# <comment> - Add comment to log file
+:dispaxis <val> - Change summing parameter for 2D images
+:log - Enable logging to save_file
+:nolog - Disable logging to save_file
+:nsum <val> - Change summing parameter for 2D images
+:show - Show full output of deblending and equiv. width measurments
+:units <value> - Change coordinate units (see below)
+
+:label <label> <format> - Add label at cursor position
+:mabove <label> <format> - Add tick mark and label above spectrum
+:mbelow <label> <format> - Add tick mark and label below spectrum
+ The label must be quoted if it contains blanks. A label beginning
+ with % (i.e. %.2f) is treated as a format for the x cursor position.
+ The optional format is a gtext string (see help on "cursors").
+ The labels are not remembered between redraws.
+
+:auto [yes|no] - Enable/disable autodraw option
+:zero [yes|no] - Enable/disable zero baseline option
+:xydraw [yes|no] - Enable/disable xydraw option
+:hist [yes|no] - Enable/disable histogram line type option
+:nosysid [yes|no] - Enable/disable system ID option
+:wreset [yes|no] - Enable/disable window reset for new spectra option
+:flip [yes|no] - Enable/disable dispersion coordinate flip
+:overplot [yes|no]- Enable/disable permanent overplot mode
+
+:/help Get help on GTOOLS options
+:.help Get help on cursor mode options
+
+
+ UNITS
+
+The units are specified by strings having a unit type from the list below
+along with the possible preceding modifiers, "inverse", to select the
+inverse of the unit and "log" to select logarithmic units. For example "log
+angstroms" to plot the logarithm of wavelength in Angstroms and "inv
+microns" to plot inverse microns. The various identifiers may be
+abbreviated as words but the syntax is not sophisticated enough to
+recognized standard scientific abbreviations except as noted below.
+
+ angstroms - Wavelength in Angstroms
+ nanometers - Wavelength in nanometers
+ millimicrons - Wavelength in millimicrons
+ microns - Wavelength in microns
+ millimeters - Wavelength in millimeters
+ centimeter - Wavelength in centimeters
+ meters - Wavelength in meters
+ hertz - Frequency in hertz (cycles per second)
+ kilohertz - Frequency in kilohertz
+ megahertz - Frequency in megahertz
+ gigahertz - Frequency in gigahertz
+ m/s - Velocity in meters per second
+ km/s - Velocity in kilometers per second
+ ev - Energy in electron volts
+ kev - Energy in kilo electron volts
+ mev - Energy in mega electron volts
+ z - Redshift
+
+ nm - Wavelength in nanometers
+ mm - Wavelength in millimeters
+ cm - Wavelength in centimeters
+ m - Wavelength in meters
+ Hz - Frequency in hertz (cycles per second)
+ KHz - Frequency in kilohertz
+ MHz - Frequency in megahertz
+ GHz - Frequency in gigahertz
+ wn - Wave number (inverse centimeters)
+
+The velocity and redshift units require a trailing value and unit defining the
+velocity zero point. For example to plot velocity relative to
+a wavelength of 1 micron the unit string would be:
+
+ km/s 1 micron
diff --git a/noao/onedspec/splot/splot.log b/noao/onedspec/splot/splot.log
new file mode 100644
index 00000000..1efd91c0
--- /dev/null
+++ b/noao/onedspec/splot/splot.log
@@ -0,0 +1,8 @@
+
+Feb 6 15:44 [tofu$s2ndR136002]: NDR136002[1/1]
+ 4103.416 5.470E-14 7.082E-14 -1.296
+ 4060.36 5.637E-14 -6.87E-14 1.218
+
+Feb 6 15:45 [tofu$s2ndR136002]: NDR136002[1/1]
+ 964.983 5.606E-14 1.030E-13 -1.84
+ 907.3833 5.425E-14 -8.26E-14 1.525
diff --git a/noao/onedspec/splot/splot.x b/noao/onedspec/splot/splot.x
new file mode 100644
index 00000000..4b676660
--- /dev/null
+++ b/noao/onedspec/splot/splot.x
@@ -0,0 +1,605 @@
+include <error.h>
+include <imhdr.h>
+include <gset.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+
+define KEY "noao$onedspec/splot/splot.key"
+define HELP "noao$onedspec/splot/stshelp.key"
+define PROMPT "splot options"
+
+define OPTIONS ",auto,zero,xydraw,histogram,nosysid,wreset,flip,overplot,"
+define NOPTIONS 8
+define AUTO 1 # Option number for auto graph
+define ZERO 2 # Option number of zero y minimum
+define XYDRAW 3 # Draw connection X,Y pairs
+define HIST 4 # Draw histogram style lines
+define NOSYSID 5 # Don't include system id
+define WRESET 6 # Reset window for each new spectrum
+define FLIP 7 # Flip spectra
+define OVERPLOT 8 # Overplot toggle
+
+
+# SPLOT -- Plot an image line and play with it - Most appropriate for spectra
+
+procedure splot ()
+
+int list
+int i, j, npts, nline, nband, nap
+int wc, key, keyu
+real wx, wy
+double w1, u1, w2, u2, w0, wpc
+real avg_pix, sigma_pix
+
+int fd1, fd2, ng, hline, hlines
+int newgraph, newimage, overplot, options[NOPTIONS]
+pointer sp, image, units, units1, units2, units3, cmd, save1, save2
+pointer gp, gt, im, mw, x, y, sh, xg, yg, sg, lg, pg, hptr
+bool wave_scl, fnu
+
+pointer gopen(), gt_init()
+int clgcur(), imtopen(), imtgetim(), imaccess(), gt_geti(), nowhite()
+real clgetr(), gt_getr()
+double clgetd(), shdr_wl()
+bool streq(), fp_equald()
+errchk getimage, fun_do, ans_hdr, un_changer
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (units, SZ_FNAME, TY_CHAR)
+ call salloc (units1, SZ_FNAME, TY_CHAR)
+ call salloc (units2, SZ_FNAME, TY_CHAR)
+ call salloc (units3, SZ_FNAME, TY_CHAR)
+ call salloc (cmd, SZ_FNAME, TY_CHAR)
+ call salloc (save1, SZ_FNAME, TY_CHAR)
+ call salloc (save2, SZ_FNAME, TY_CHAR)
+
+ # Get task parameters.
+
+ call clgstr ("images", Memc[image], SZ_FNAME)
+ list = imtopen (Memc[image])
+ call clgstr ("save_file", Memc[save1], SZ_FNAME)
+ call clgstr ("options", Memc[save2], SZ_FNAME)
+ call xt_gids (Memc[save2], OPTIONS, options, NOPTIONS)
+ call clgstr ("units", Memc[units], SZ_FNAME)
+ call mktemp ("tmp$splot", Memc[save2], SZ_FNAME)
+
+ # Allocate space for User area
+ x = NULL
+ y = NULL
+
+ # Initialize
+ im = NULL
+ sh = NULL
+ fd1 = NULL
+ fd2 = NULL
+ hptr = NULL
+ ng = 0
+ hline = 1
+ nline = 0
+ nband = 0
+ nap = 0
+ if (nowhite (Memc[units], Memc[units1], SZ_FNAME) == 0)
+ call strcpy ("display", Memc[units], SZ_FNAME)
+ call strcpy (Memc[units], Memc[units1], SZ_FNAME)
+ call strcpy (Memc[units], Memc[units2], SZ_FNAME)
+ w0 = INDEFD
+ wpc = INDEFD
+
+ call clgstr ("graphics", Memc[cmd], SZ_FNAME)
+ gp = gopen (Memc[cmd], NEW_FILE+AW_DEFER, STDGRAPH)
+ call gseti (gp, G_WCS, 1)
+# call gseti (gp, G_YNMINOR, 0)
+
+ gt = gt_init()
+ call gt_setr (gt, GTXMIN, clgetr ("xmin"))
+ call gt_setr (gt, GTXMAX, clgetr ("xmax"))
+ call gt_setr (gt, GTYMIN, clgetr ("ymin"))
+ call gt_setr (gt, GTYMAX, clgetr ("ymax"))
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ if (options[HIST] == YES)
+ call gt_sets (gt, GTTYPE, "histogram")
+ else
+ call gt_sets (gt, GTTYPE, "line")
+ call gt_seti (gt, GTXFLIP, options[FLIP])
+ if (options[NOSYSID] == YES)
+ call gt_seti (gt, GTSYSID, NO)
+
+ while (imtgetim (list, Memc[image], SZ_FNAME) != EOF) {
+
+ # Initialize to plot a wavelength scale
+ wave_scl = true
+
+ # Open image and get pixels
+ if (imaccess (Memc[image], READ_ONLY) == NO) {
+ call eprintf ("Can't get image %s\n")
+ call pargstr (Memc[image])
+ next
+ }
+ call getimage (Memc[image], nline, nband, nap, wave_scl,
+ w0, wpc, Memc[units], im, mw, sh, gt)
+ x = SX(sh)
+ y = SY(sh)
+ npts = SN(sh)
+ newimage = YES
+ overplot = options[OVERPLOT]
+
+ # Enter cursor loop with 'r' redraw.
+ key = 'r'
+ repeat {
+ switch (key) {
+ case ':':
+ if (Memc[cmd] == '/')
+ call gt_colon (Memc[cmd], gp, gt, newgraph)
+ else {
+ call splot_colon (Memc[cmd], options, gp, gt, sh,
+ wx, wy, Memc[units], Memc[save1], Memc[save2],
+ fd1, fd2, newgraph)
+ overplot = options[OVERPLOT]
+ if (sh == NULL) {
+ call getimage (Memc[image], nline, nband, nap,
+ wave_scl, w0, wpc, Memc[units], im, mw, sh, gt)
+ x = SX(sh)
+ y = SY(sh)
+ npts = SN(sh)
+ newgraph = YES
+ newimage = YES
+ }
+ }
+
+ case 'a': # Autoexpand
+ call auto_exp (gp, gt, key, wx, Memr[x], Memr[y], npts)
+
+ case 'b': # Toggle base to 0.0
+ if (options[ZERO] == NO) {
+ call gt_setr (gt, GTYMIN, 0.)
+ options[ZERO] = YES
+ } else {
+ call gt_setr (gt, GTYMIN, INDEF)
+ options[ZERO] = NO
+ }
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case 'c':
+ call gt_setr (gt, GTXMIN, INDEF)
+ call gt_setr (gt, GTXMAX, INDEF)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = YES
+ overplot = NO
+
+ case 'd': # De-blend a group of lines
+ call ans_hdr (sh, newimage, key, Memc[save1], Memc[save2],
+ fd1, fd2)
+ call sp_deblend (sh, gp, wx, wy, Memr[x], Memr[y], npts,
+ fd1, fd2, xg, yg, sg, lg, pg, ng)
+ newimage = NO
+
+ case 'k': # Fit gaussian
+ call ans_hdr (sh, newimage, key, Memc[save1], Memc[save2],
+ fd1, fd2)
+ call gfit (sh, gp, wx, wy, Memr[x], Memr[y], npts,
+ fd1, fd2, xg, yg, sg, lg, pg, ng)
+ newimage = NO
+
+ case 'e': # Equivalent width
+ call ans_hdr (sh, newimage, key, Memc[save1], Memc[save2],
+ fd1, fd2)
+ call eqwidth (sh, gp, wx, wy, Memr[x], Memr[y], npts,
+ fd1, fd2)
+ newimage = NO
+
+ case 'v':
+ iferr {
+ if (UN_CLASS(UN(sh)) == UN_VEL) {
+ call strcpy (Memc[units1], Memc[units], SZ_FNAME)
+ call strcpy (Memc[units2], Memc[units3], SZ_FNAME)
+ } else {
+ call strcpy (Memc[units], Memc[units1], SZ_FNAME)
+ call strcpy (UNITS(sh), Memc[units2], SZ_FNAME)
+ call un_changer (UN(sh), "angstroms", wx, 1, NO)
+ call sprintf (Memc[units], SZ_FNAME,
+ "km/s %g angstroms")
+ call pargr (wx)
+ call strcpy (Memc[units], Memc[units3], SZ_FNAME)
+ }
+ wx = gt_getr (gt, GTXMIN)
+ if (!IS_INDEF(wx)) {
+ call un_changer (UN(sh), Memc[units3], wx, 1, NO)
+ call gt_setr (gt, GTXMIN, wx)
+ }
+ wx = gt_getr (gt, GTXMAX)
+ if (!IS_INDEF(wx)) {
+ call un_changer (UN(sh), Memc[units3], wx, 1, NO)
+ call gt_setr (gt, GTXMAX, wx)
+ }
+ call un_changer (UN(sh), Memc[units3], Memr[x], npts,
+ YES)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ newgraph = YES
+ overplot = NO
+ } then
+ call erract (EA_WARN)
+
+ case 'h': # Equivalent widths -- C. Pilachowski style
+ call ans_hdr (sh, newimage, key, Memc[save1], Memc[save2],
+ fd1, fd2)
+ repeat {
+ switch (key) {
+ case 'a', 'b', 'c': # Continuum at cursor width at 1/2
+ call eqwidth_cp (sh, gp, wx, wy, INDEF,
+ Memr[y], npts, key, fd1, fd2, xg, yg, sg,
+ lg, pg, ng)
+ break
+ case 'l', 'r', 'k': # Continuum at 1
+ call eqwidth_cp (sh, gp, wx, 1., wy,
+ Memr[y], npts, key, fd1, fd2, xg, yg, sg,
+ lg, pg, ng)
+ break
+ default:
+ call printf (
+ "Set cursor and type a, b, c, l, r, or k:")
+ }
+ } until (clgcur ("cursor", wx, wy, wc, key, Memc[cmd],
+ SZ_FNAME) == EOF)
+ newimage = NO
+
+ case 'o': # Set overplot
+ overplot = YES
+
+ case 'g', '#', '%', '(', ')': # Get new image to plot
+ i = nline
+ j = nband
+
+ switch (key) {
+ case '(':
+ if (IM_LEN(im,2) > 1) {
+ nline = max (1, min (IM_LEN(im,2), nline-1))
+ nap = INDEFI
+ } else if (IM_LEN(im,3) > 1) {
+ nband = max (1, min (IM_LEN(im,3), nband-1))
+ }
+ case ')':
+ if (IM_LEN(im,2) > 1) {
+ nline = max (1, min (IM_LEN(im,2), nline+1))
+ nap = INDEFI
+ } else if (IM_LEN(im,3) > 1) {
+ nband = max (1, min (IM_LEN(im,3), nband+1))
+ }
+ case '#':
+ nline = 0
+ case '%':
+ nband = 0
+ default:
+ call clgstr ("next_image", Memc[cmd], SZ_FNAME)
+ if (streq (Memc[image], Memc[cmd])) {
+ call shdr_close (sh)
+ } else if (imaccess (Memc[cmd], READ_ONLY) == YES) {
+ call shdr_close (sh)
+ call smw_close (mw)
+ call imunmap (im)
+ newimage = YES
+ } else {
+ call eprintf ("Can't get %s\n")
+ call pargstr (Memc[cmd])
+ next
+ }
+ call strcpy (Memc[cmd], Memc[image], SZ_FNAME)
+ nline = 0
+ nband = 0
+ }
+
+ call getimage (Memc[image], nline, nband, nap, wave_scl,
+ w0, wpc, Memc[units], im, mw, sh, gt)
+ x = SX(sh)
+ y = SY(sh)
+ npts = SN(sh)
+
+ if (options[WRESET] == YES && overplot == NO) {
+ call gt_setr (gt, GTXMIN, clgetr ("xmin"))
+ call gt_setr (gt, GTXMAX, clgetr ("xmax"))
+ call gt_setr (gt, GTYMIN, clgetr ("ymin"))
+ call gt_setr (gt, GTYMAX, clgetr ("ymax"))
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ }
+
+ if (nline != i || nband != j)
+ newimage = YES
+ newgraph = YES
+
+ case 'w': # Window the graph
+ call gt_window (gt, gp, "cursor", newgraph)
+ if (newgraph == YES) {
+ newgraph = options[AUTO]
+ overplot = NO
+ }
+
+ case 'l': # Convert to f-lambda - issue warning if not a
+ # calibrated image
+ if (FC(sh) == FCNO)
+ call eprintf (
+ "Warning: (>flam) spectrum not calibrated\n")
+
+ call conflam (sh)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case 'f': # Function operators
+ call fun_help ()
+ while (clgcur ("cursor", wx, wy, wc, key, Memc[cmd],
+ SZ_FNAME) != EOF) {
+ switch (key) {
+ case '?':
+ call fun_help ()
+ case 'q':
+ break
+ case 'I':
+ call fatal (0, "Interrupt")
+ default:
+ iferr {
+ call fun_do (key, sh, Memr[y], npts, w0, wpc)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ if (options[AUTO] == YES)
+ call replot (gp, gt, Memr[x], Memr[y],
+ npts, YES)
+ overplot = NO
+ call fun_help ()
+ } then {
+ call erract (EA_WARN)
+ call tsleep (2)
+ call fun_help ()
+ }
+ }
+ }
+ call printf ("\n")
+
+ case 'm': # Signal-to-noise
+ call ans_hdr (sh, newimage, key, Memc[save1], Memc[save2],
+ fd1, fd2)
+ call avgsnr (sh, wx, wy, Memr[y], npts, fd1, fd2)
+ newimage = NO
+
+ case 'n': # Convert to f-nu
+ if (FC(sh) == FCNO)
+ call eprintf (
+ "Warning: (>fnu) spectrum not calibrated\n")
+
+ call confnu (sh)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case 'q':
+ if (options[WRESET] == YES) {
+ call gt_setr (gt, GTXMIN, clgetr ("xmin"))
+ call gt_setr (gt, GTXMAX, clgetr ("xmax"))
+ call gt_setr (gt, GTYMIN, clgetr ("ymin"))
+ call gt_setr (gt, GTYMAX, clgetr ("ymax"))
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ }
+
+ if (nline != i || nband != j)
+ newimage = YES
+ newgraph = YES
+ break
+
+ case 'r': # Replot
+ newgraph = YES
+ overplot = NO
+
+ case 's': # Smooth
+ call smooth (Memr[y], npts)
+ newgraph = options[AUTO]
+
+ case 't': # FlaTTen spectrum
+ call flatten (gp, gt, Memr[x], Memr[y], npts)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case 'p', 'u': # Set user coordinates
+ if (!wave_scl) {
+ call shdr_system (sh, "world")
+ wave_scl = true
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ }
+ switch (key) {
+ case 'p':
+ keyu = 'l'
+ w1 = Memr[x]
+ u1 = clgetd ("wstart")
+ w2 = Memr[x+npts-1]
+ u2 = clgetd ("wend")
+ if (IS_INDEFD(u1)) {
+ u1 = clgetd ("dw")
+ u1 = u2 - (npts - 1) * u1
+ } else if (IS_INDEFD(u2)) {
+ u2 = clgetd ("dw")
+ u2 = u1 + (npts - 1) * u2
+ }
+ case 'u':
+ call printf (
+ "Set cursor and select correction: d(oppler), z(eropoint), l(inear)\n")
+ call flush (STDOUT)
+ i = clgcur ("cursor", wx, wy, wc, keyu, Memc[cmd],
+ SZ_FNAME)
+ w1 = wx
+ u1 = clgetd ("wavelength")
+ if (keyu == 'l') {
+ repeat {
+ call printf ("Set cursor to second position:")
+ call flush (STDOUT)
+ i = clgcur ("cursor", wx, wy, wc, key,
+ Memc[cmd], SZ_FNAME)
+ w2 = wx
+ if (!fp_equald (w1, w2)) {
+ u2 = clgetd ("wavelength")
+ break
+ }
+ call printf ("Cursor not moved: ")
+ }
+ }
+ }
+ call usercoord (sh, keyu, w1, u1, w2, u2)
+ w0 = Memr[x]
+ wpc = Memr[x+1] - w0
+ call gt_setr (gt, GTXMIN, INDEF)
+ call gt_setr (gt, GTXMAX, INDEF)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case 'i': # Write image spectrum out
+ call sp_wrspect (sh)
+ im = IM(sh)
+ mw = MW(sh)
+
+ case 'j': # Fudge (fix) a data point
+ call fudgept (sh, gp, Memr[x], Memr[y], npts, wx, wy)
+
+ case 'x': # Fudge eXtended over a line
+ call fudgex (sh, gp, Memr[x], Memr[y], npts, wx, wy,
+ options[XYDRAW])
+
+ case 'y': # Over plot standard star data
+ # Estimate data is fnu or flambda: cutoff around dexp[-20]
+ fnu = false
+ call aavgr (Memr[y], npts, avg_pix, sigma_pix)
+ if (log10 (avg_pix) < -19.5)
+ fnu = true
+ call plot_std (sh, gp, fnu)
+ call printf ("\n")
+
+ case 'z': # Zoom x region to larger range
+ call auto_exp (gp, gt, key, wx, Memr[x], Memr[y], npts)
+
+ case '-': # Subtract deblended fit
+ call subblend (sh, gp, Memr[x], Memr[y], npts, wx, wy,
+ xg, yg, sg, lg, pg, ng)
+
+ case '.': # Slide upward
+ call auto_exp (gp, gt, key, wx, Memr[x], Memr[y], npts)
+
+ case ',': # Slide downward
+ call auto_exp (gp, gt, key, wx, Memr[x], Memr[y], npts)
+
+ case '$': # Toggle wavelength scale
+ if (wave_scl) {
+ call shdr_system (sh, "physical")
+ wave_scl = false
+ call gt_sets (gt, GTXLABEL, "Pixel")
+ call gt_sets (gt, GTXUNITS, "")
+ } else {
+ call shdr_system (sh, "world")
+ wave_scl = true
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ }
+ call gt_setr (gt, GTXMIN, INDEF)
+ call gt_setr (gt, GTXMAX, INDEF)
+ call gt_setr (gt, GTYMIN, INDEF)
+ call gt_setr (gt, GTYMAX, INDEF)
+ if (options[ZERO] == YES)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = options[AUTO]
+ overplot = NO
+
+ case '/': # Help on status line
+ call sts_help (hline, hlines, HELP, hptr)
+ hline = mod (hline, hlines) + 1
+
+ case '?': # Help screen
+ call gpagefile (gp, KEY, PROMPT)
+
+ case 'I': # Interrupt
+ call fatal (0, "Interrupt")
+
+ default: # Default - print cursor info
+ i = max (1, min (npts, nint (shdr_wl (sh, double(wx)))))
+ call printf ("x,y,z(x): %10.3f %10.4g %10.4g\n")
+ call pargr (wx)
+ call pargr (wy)
+ call pargr (Memr[y+i-1])
+ }
+
+ if (newgraph == YES) {
+ if (overplot == YES) {
+ call printf ("Overplotting: %s")
+ call pargstr (Memc[image])
+ if (nline > 0) {
+ if (nband > 0) {
+ call printf ("(%d,%d)")
+ call pargi (nline)
+ call pargi (nband)
+ } else {
+ call printf ("(%d)")
+ call pargi (nline)
+ }
+ }
+ call flush (STDOUT)
+ i = gt_geti (gt, GTLINE)
+ j = gt_geti (gt, GTCOLOR)
+ if (options[OVERPLOT] == NO) {
+ call gt_seti (gt, GTLINE, i+1)
+ call gt_seti (gt, GTCOLOR, j+1)
+ }
+ call replot (gp, gt, Memr[x], Memr[y], npts, NO)
+ call gt_seti (gt, GTLINE, i)
+ call gt_seti (gt, GTCOLOR, j)
+ } else
+ call replot (gp, gt, Memr[x], Memr[y], npts, YES)
+ newgraph = NO
+ overplot = options[OVERPLOT]
+ }
+ } until (clgcur ("cursor",wx,wy,wc,key,Memc[cmd],SZ_FNAME) == EOF)
+ if (im != ERR) {
+ call shdr_close (sh)
+ call smw_close (mw)
+ call imunmap (im)
+ }
+ }
+
+ call gclose (gp)
+ if (fd1 != NULL)
+ call close (fd1)
+ if (fd2 != NULL) {
+ call close (fd2)
+ call delete (Memc[save2])
+ }
+ if (hptr != NULL)
+ call mfree (hptr, TY_CHAR)
+ if (ng > 0) {
+ call mfree (xg, TY_REAL)
+ call mfree (yg, TY_REAL)
+ call mfree (sg, TY_REAL)
+ call mfree (lg, TY_REAL)
+ call mfree (pg, TY_INT)
+ }
+ call smw_daxis (NULL, NULL, 0, 0, 0)
+ call gt_free (gt)
+ call imtclose (list)
+end
diff --git a/noao/onedspec/splot/splotcolon.x b/noao/onedspec/splot/splotcolon.x
new file mode 100644
index 00000000..e68bbecc
--- /dev/null
+++ b/noao/onedspec/splot/splotcolon.x
@@ -0,0 +1,263 @@
+include <error.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include <ctype.h>
+
+# List of colon commands.
+define CMDS "|show|nolog|log|dispaxis|nsum|#|units|auto|zero\
+ |xydraw|histogram|nosysid|wreset|flip|overplot\
+ |label|mabove|mbelow|"
+define SHOW 1 # Show logged data
+define NOLOG 2 # Turn off logging
+define LOG 3 # Turn on logging
+define DA 4 # Dispersion axis
+define NS 5 # Summing parameter
+define COMMENT 6 # Comment
+define UNITS 7 # Units
+define AUTO 8 # Option auto graph
+define ZERO 9 # Option for zero y minimum
+define XYDRAW 10 # Draw connection X,Y pairs
+define HIST 11 # Draw histogram style lines
+define NOSYSID 12 # Don't include system id
+define WRESET 13 # Reset window for each new spectrum
+define FLIP 14 # Flip the dispersion coordinates
+define OVERPLOT 15 # Toggle overplot
+define LABEL 16 # Label spectrum
+define MABOVE 17 # Tick mark plus label above spectrum
+define MBELOW 18 # Tick mark plus label below spectrum
+
+define OPOFF 7 # Offset in options array
+
+# SPLOT_COLON -- Respond to colon command.
+
+procedure splot_colon (command, options, gp, gt, sh, x, y, units,
+ fname1, fname2, fd1, fd2, newgraph)
+
+char command[ARB] # Colon command
+int options[ARB] # Options
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+pointer sh # SHIO pointer
+real x, y # Coordinate
+char units[SZ_FNAME] # Units string
+char fname1[SZ_FNAME] # Log file
+char fname2[SZ_FNAME] # Temporary log file
+int fd1, fd2 # Log file descriptors
+int newgraph # New graph?
+
+bool bval
+char cmd[SZ_LINE]
+real xval, gt_getr()
+int ncmd, ival, access(), nscan(), strdic(), btoi(), gt_geti()
+pointer sp, str, smw
+errchk un_changer
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Scan the command string and get the first word.
+ call sscan (command)
+ call gargwrd (cmd, SZ_LINE)
+ ncmd = strdic (cmd, cmd, SZ_LINE, CMDS)
+
+ smw = MW(sh)
+
+ switch (ncmd) {
+ case SHOW:
+ if (fd2 != NULL) {
+ call close (fd2)
+ fd2 = NULL
+ }
+ if (access (fname2, 0, 0) == YES)
+ call gpagefile (gp, fname2, "splot data")
+ else
+ call printf ("No measurements\n")
+ case NOLOG:
+ call printf ("Logging to %s disabled")
+ call pargstr (fname1)
+ fname1[1] = EOS
+ if (fd1 != NULL) {
+ call close (fd1)
+ fd1 = NULL
+ }
+ case LOG:
+ call clgstr ("save_file", fname1, SZ_FNAME)
+ call printf ("Logging to %s enabled")
+ call pargstr (fname1)
+ case DA:
+ if (SMW_FORMAT(smw) == SMW_ND) {
+ call gargi (ival)
+ if (nscan() == 2) {
+ if (ival < 1) {
+ call printf ("Bad value for dispaxis (%d)\n")
+ call pargi (ival)
+ } else if (ival != SMW_PAXIS(smw,1)) {
+ call smw_daxis (smw, IM(sh), ival, INDEFI, INDEFI)
+ call smw_saxes (smw, NULL, IM(sh))
+ call shdr_close (sh)
+ }
+ } else {
+ call printf ("dispaxis %d\n")
+ call pargi (SMW_PAXIS(smw,1))
+ }
+ } else
+ call printf ("Image is not two dimensional\n")
+ case NS:
+ if (SMW_FORMAT(smw) == SMW_ND) {
+ call gargi (ival)
+ call gargi (ncmd)
+ if (nscan() == 1) {
+ call printf ("nsum %d %d\n")
+ call pargi (SMW_NSUM(smw,1))
+ call pargi (SMW_NSUM(smw,2))
+ } else {
+ if (nscan() == 2)
+ ncmd = INDEFI
+ if ((!IS_INDEFI(ival) && ival != SMW_NSUM(smw,1)) ||
+ (!IS_INDEFI(ncmd) && ncmd != SMW_NSUM(smw,2))) {
+ call smw_daxis (smw, IM(sh), INDEFI, ival, ncmd)
+ call smw_saxes (smw, NULL, IM(sh))
+ call shdr_close (sh)
+ }
+ }
+ } else
+ call printf ("Invalid image format\n")
+ case COMMENT:
+ call ans_hdr (sh, NO, 'm', fname1, fname2, fd1, fd2)
+ call gargstr (cmd, SZ_LINE)
+ if (fd1 != NULL) {
+ call fprintf (fd1, "%s\n")
+ call pargstr (command)
+ }
+ if (fd2 != NULL) {
+ call fprintf (fd2, "%s\n")
+ call pargstr (command)
+ }
+ case UNITS:
+ call gargstr (cmd, SZ_LINE)
+ for (ival=1; IS_WHITE(cmd[ival]); ival=ival+1)
+ ;
+ iferr {
+ xval = gt_getr (gt, GTXMIN)
+ if (!IS_INDEF(xval)) {
+ call un_changer (UN(sh), cmd[ival], xval, 1, NO)
+ call gt_setr (gt, GTXMIN, xval)
+ }
+ xval = gt_getr (gt, GTXMAX)
+ if (!IS_INDEF(xval)) {
+ call un_changer (UN(sh), cmd[ival], xval, 1, NO)
+ call gt_setr (gt, GTXMAX, xval)
+ }
+ call un_changer (UN(sh), cmd[ival], Memr[SX(sh)], SN(sh), YES)
+ call strcpy (cmd[ival], units, SZ_FNAME)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ newgraph = YES
+ } then
+ call erract (EA_WARN)
+ case AUTO:
+ call gargb (bval)
+ if (nscan() == 2)
+ options[AUTO-OPOFF] = btoi (bval)
+ else {
+ call printf ("auto %b\n")
+ call pargi (options[AUTO-OPOFF])
+ }
+ case ZERO:
+ call gargb (bval)
+ if (nscan() == 2) {
+ options[ZERO-OPOFF] = btoi (bval)
+ if (bval)
+ call gt_setr (gt, GTYMIN, 0.)
+ newgraph = options[AUTO-OPOFF]
+ } else {
+ call printf ("zero %b\n")
+ call pargi (options[ZERO-OPOFF])
+ }
+ case XYDRAW:
+ call gargb (bval)
+ if (nscan() == 2)
+ options[XYDRAW-OPOFF] = btoi (bval)
+ else {
+ call printf ("xydraw %b\n")
+ call pargi (options[XYDRAW-OPOFF])
+ }
+ case HIST:
+ call gargb (bval)
+ if (nscan() == 2) {
+ options[HIST-OPOFF] = btoi (bval)
+ if (bval)
+ call gt_sets (gt, GTTYPE, "histogram")
+ else
+ call gt_sets (gt, GTTYPE, "line")
+ newgraph = options[AUTO-OPOFF]
+ } else {
+ call printf ("hist %b\n")
+ call pargi (options[HIST-OPOFF])
+ }
+ case NOSYSID:
+ call gargb (bval)
+ if (nscan() == 2) {
+ options[NOSYSID-OPOFF] = btoi (bval)
+ if (bval)
+ call gt_seti (gt, GTSYSID, NO)
+ else
+ call gt_seti (gt, GTSYSID, YES)
+ newgraph = options[AUTO-OPOFF]
+ } else {
+ call printf ("nosysid %b\n")
+ call pargi (options[NOSYSID-OPOFF])
+ }
+ case WRESET:
+ call gargb (bval)
+ if (nscan() == 2)
+ options[WRESET-OPOFF] = btoi (bval)
+ else {
+ call printf ("wreset %b\n")
+ call pargi (options[WRESET-OPOFF])
+ }
+ case FLIP:
+ call gargb (bval)
+ if (nscan() == 2) {
+ options[FLIP-OPOFF] = btoi (bval)
+ call gt_seti (gt, GTXFLIP, options[FLIP-OPOFF])
+ } else {
+ options[FLIP-OPOFF] = gt_geti (gt, GTXFLIP)
+ call printf ("flip %b\n")
+ call pargi (options[FLIP-OPOFF])
+ }
+ case OVERPLOT:
+ call gargb (bval)
+ if (nscan() == 2) {
+ options[OVERPLOT-OPOFF] = btoi (bval)
+ } else {
+ call printf ("overplot %b\n")
+ call pargi (options[OVERPLOT-OPOFF])
+ }
+ case LABEL, MABOVE, MBELOW:
+ call gargwrd (cmd, SZ_LINE)
+ for (ival=1; IS_WHITE(cmd[ival]); ival=ival+1)
+ ;
+ call strcpy (cmd[ival], cmd, SZ_LINE)
+ call gargwrd (Memc[str], SZ_LINE)
+ for (ival=1; IS_WHITE(Memc[str+ival-1]); ival=ival+1)
+ ;
+ call strcpy (Memc[str+ival-1], Memc[str], SZ_LINE)
+
+ switch (ncmd) {
+ case LABEL:
+ call splabel ("label", sh, gp, x, y, cmd, Memc[str])
+ case MABOVE:
+ call splabel ("mabove", sh, gp, x, y, cmd, Memc[str])
+ case MBELOW:
+ call splabel ("mbelow", sh, gp, x, y, cmd, Memc[str])
+ }
+
+ default:
+ call printf ("Unrecognized or ambiguous command\007")
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/splot/splotfun.x b/noao/onedspec/splot/splotfun.x
new file mode 100644
index 00000000..4c94350f
--- /dev/null
+++ b/noao/onedspec/splot/splotfun.x
@@ -0,0 +1,127 @@
+include <error.h>
+include <mach.h>
+include <smw.h>
+
+# Function Mode for STEK
+
+# FUN_DO -- Branch and execute proper function
+
+procedure fun_do (key, sh1, y, n, w0, wpc)
+
+int key
+pointer sh1
+real y[n]
+int n
+double w0, wpc
+
+char spec2[SZ_FNAME]
+int i, nline, nband, nap, strlen()
+real const, clgetr()
+pointer im, mw, sh2
+bool wave_scl
+errchk getimage, shdr_rebin
+
+begin
+ switch (key) {
+ case 'a': # Absolute value
+ do i = 1, n
+ y[i] = abs (y[i])
+ case 'd': # Dexp (base 10)
+ const = log10 (MAX_REAL)
+ do i = 1, n
+ if (abs (y[i]) < const)
+ y[i] = 10.0 ** y[i]
+ else if (y[i] >= const)
+ y[i] = MAX_REAL
+ else
+ y[i] = 0.0
+ case 'e': # Exp base e
+ const = log (MAX_REAL)
+ do i = 1, n
+ if (abs (y[i]) < const)
+ y[i] = exp (y[i])
+ else if (y[i] >= const)
+ y[i] = MAX_REAL
+ else
+ y[i] = 0.0
+ case 'i': # Inverse
+ do i = 1, n
+ if (y[i] != 0.0)
+ y[i] = 1.0/y[i]
+ else
+ y[i] = 0.0
+ case 'l': # Log10
+ do i = 1, n
+ if (y[i] > 0.0)
+ y[i] = log10 (y[i])
+ else
+ y[i] = -0.5
+ case 'm': # Multiply by constant
+ const = clgetr ("constant")
+ call amulkr (y, const, y, n)
+ case 'n': # Log base e
+ do i = 1, n
+ if (y[i] > 0.0)
+ y[i] = log (y[i])
+ else
+ y[i] = -0.5
+ case 'p': # Add constant
+ const = clgetr ("constant")
+ call aaddkr (y, const, y, n)
+ case 's': # Square root
+ do i = 1, n
+ if (y[i] >= 0.0)
+ y[i] = sqrt (y[i])
+ else
+ y[i] = 0.0
+
+ case '+', '-', '*', '/': # Binary operations
+ call printf ("Second spectrum ")
+ call clgstr ("spec2", spec2, SZ_FNAME)
+ if (strlen (spec2) == 0)
+ return
+
+ wave_scl = true
+ nline = 0
+ nband = 0
+ nap = 0
+ im = NULL
+ mw = NULL
+ sh2 = NULL
+ call getimage (spec2, nline, nband, nap, wave_scl, w0, wpc,
+ "angstroms", im, mw, sh2, NULL)
+ call shdr_rebin (sh2, sh1)
+ switch (key) {
+ case '+':
+ call aaddr (y, Memr[SY(sh2)], y, n)
+ case '-':
+ call asubr (y, Memr[SY(sh2)], y, n)
+ case '*':
+ call amulr (y, Memr[SY(sh2)], y, n)
+ case '/':
+ do i = 1, n
+ if (Memr[SY(sh2)+i-1] == 0.0)
+ y[i] = 0.0
+ else
+ y[i] = y[i] / Memr[SY(sh2)+i-1]
+ }
+ call shdr_close (sh2)
+ call smw_close (mw)
+ call imunmap (im)
+
+ # Redraw
+ case 'r':
+ ;
+ default:
+ call error (0, "Unknown function")
+ }
+end
+
+# FUN_HELP
+
+procedure fun_help ()
+
+begin
+ call printf ("q=quit l,n=log10,e d,e=d,exp s=sqrt a=abs i=1/s")
+ call printf (" p=+k m=*k +,-,*,/=2spec ops\n")
+end
diff --git a/noao/onedspec/splot/stshelp.key b/noao/onedspec/splot/stshelp.key
new file mode 100644
index 00000000..fe351182
--- /dev/null
+++ b/noao/onedspec/splot/stshelp.key
@@ -0,0 +1,7 @@
+a=expand b=zero base ,=left .=right w=window z=zoom
+c=redraw full scale o=overplot r=redraw current scale
+d=deblend e=eq. width f=functions h=1 sided eqw i=write sp j=fix pix
+k=gauss fit l=flambda m=mean/snr n=fnu p=>wavelth q=quit s=smooth
+t=curfit u=set wave v=velocity scale x=fix line y=plot std
+/=status help ?=help -=subtr fit $=wavelength/pixel
+g=new spectrum #=aperture %=band (=previous )=next
diff --git a/noao/onedspec/splot/stshelp.x b/noao/onedspec/splot/stshelp.x
new file mode 100644
index 00000000..f34de38a
--- /dev/null
+++ b/noao/onedspec/splot/stshelp.x
@@ -0,0 +1,34 @@
+include <error.h>
+
+
+# STS_HELP -- Issue a help line
+
+procedure sts_help (line, nlines, fname, ptr)
+
+int line # Line to print
+int nlines # Number of lines of help
+char fname[ARB] # Help file
+pointer ptr # Cache help
+
+int fd, open(), getline()
+
+begin
+ if (ptr == NULL) {
+ iferr (fd = open (fname, READ_ONLY, TEXT_FILE)) {
+ call erract (EA_WARN)
+ return
+ }
+ nlines = 0
+ call malloc (ptr, SZ_LINE, TY_CHAR)
+ while (getline (fd, Memc[ptr+nlines*SZ_LINE]) != EOF) {
+ nlines = nlines + 1
+ call realloc (ptr, (nlines+1)*SZ_LINE, TY_CHAR)
+ }
+ call close (fd)
+ }
+
+ if (line >= 1 && line <= nlines) {
+ call putline (STDOUT, Memc[ptr+(line-1)*SZ_LINE])
+ call flush (STDOUT)
+ }
+end
diff --git a/noao/onedspec/splot/sumflux.x b/noao/onedspec/splot/sumflux.x
new file mode 100644
index 00000000..36f6ca3b
--- /dev/null
+++ b/noao/onedspec/splot/sumflux.x
@@ -0,0 +1,165 @@
+# SUMFLUX -- Sum up the flux in a specified bandpass
+
+procedure sumflux (sh, x, y, s, n, eqx1, eqx2, eqy1, eqy2,
+ sum, rsum, esum, ctr)
+
+pointer sh
+real x[n], y[n], s[n]
+int n
+real eqx1, eqx2, eqy1, eqy2
+real sum[2], rsum[2], esum[2], ctr[2]
+
+real slope, csum[2], sum2[2], rampval, scale, delta, wpc
+real w1, w2
+int i, i1, i2
+bool fp_equalr()
+
+begin
+ call fixx (sh, eqx1, eqx2, eqy1, eqy2, i1, i2)
+ slope = (eqy2-eqy1) / (eqx2-eqx1)
+
+ sum[1] = 0.0
+ rsum[1] = 0.0
+ esum[1] = 0.0
+ csum[1] = 0.0
+ sum2[1] = 0.0
+ scale = 0.0
+
+ for (i=i1+1; i <= i2-1; i = i+1)
+ scale = max (scale, y[i])
+ if (scale <= 0.)
+ scale = 1.
+
+ for (i=i1+1; i <= i2-1; i = i+1) {
+ rampval = eqy1 + slope * (x[i] - eqx1)
+ sum[1] = sum[1] + y[i]
+ rsum[1] = rsum[1] + rampval
+ if (!IS_INDEF(esum[1])) {
+ if (fp_equalr (0., rampval/scale))
+ esum[1] = INDEF
+ else
+ esum[1] = esum[1] + (1. - y[i] / rampval)
+ }
+ }
+
+ for (i=i1+1; i <= i2-1; i = i+1) {
+ rampval = eqy1 + slope * (x[i] - eqx1)
+ delta = (y[i] - rampval) / scale
+ csum[1] = csum[1] + abs(delta)**1.5 * x[i]
+ sum2[1] = sum2[1] + abs(delta)**1.5
+ }
+
+ # end points
+ if (eqx1 < x[i1]) {
+ if (i1 > 1)
+ w1 = (x[i1] - eqx1) / (x[i1] - x[i1-1])
+ else
+ w1 = (x[i1] - eqx1) / (x[i1+1] - x[i1])
+ } else {
+ if (i1 < n)
+ w1 = (x[i1] - eqx1) / (x[i1+1] - x[i1])
+ else
+ w1 = (x[i1] - eqx1) / (x[i1] - x[i1-1])
+ }
+ if (eqx2 < x[i2]) {
+ if (i2 > 1)
+ w2 = (x[i2] - eqx2) / (x[i2] - x[i2-1])
+ else
+ w2 = (x[i2] - eqx2) / (x[i2+1] - x[i2])
+ } else {
+ if (i2 < n)
+ w2 = (x[i2] - eqx2) / (x[i2+1] - x[i2])
+ else
+ w2 = (x[i2] - eqx2) / (x[i2] - x[i2-1])
+ }
+ w2 = 1.0 - w2
+
+ sum[1] = sum[1] + w1 * y[i1] + w2 * y[i2]
+ rsum[1] = rsum[1] + w1 * eqy1 + w2 * eqy2
+ if (!IS_INDEF(esum[1])) {
+ if (fp_equalr (0., eqy1/scale)|| fp_equalr (0., eqy2/scale))
+ esum[1] = INDEF
+ else
+ esum[1] = esum[1] + w1 * (1. - y[i1] / eqy1) +
+ w2 * (1. - y[i2] / eqy2)
+ }
+
+ delta = (y[i1] - eqy1) / scale
+ csum[1] = csum[1] + w1 * abs(delta)**1.5 * eqx1
+ sum2[1] = sum2[1] + w1 * abs(delta)**1.5
+
+ delta = (y[i2] - eqy2) / scale
+ csum[1] = csum[1] + w2 * abs(delta)**1.5 * eqx2
+ sum2[1] = sum2[1] + w2 * abs(delta)**1.5
+
+ if (sum2[1] != 0.0)
+ ctr[1] = csum[1] / sum2[1]
+ else
+ ctr[1] = 0.0
+
+ # Correct for angstroms/channel
+ if (i1 != i2)
+ wpc = abs ((x[i2] - x[i1]) / (i2 - i1))
+ else if (i1 < n)
+ wpc = abs (x[i1+1] - x[i1])
+ else
+ wpc = abs (x[i1-1] - x[i1])
+ sum[1] = sum[1] * wpc
+ if (!IS_INDEF(esum[1]))
+ esum[1] = esum[1] * wpc
+ rsum[1] = rsum[1] * wpc
+
+ # Errors (Note there are no errors in the ramp values).
+ if (!IS_INDEF(s[1])) {
+ sum[2] = 0.0
+ rsum[2] = 0.0
+ esum[2] = 0.0
+ csum[2] = 0.0
+ sum2[2] = 0.0
+ for (i=i1+1; i <= i2-1; i = i+1) {
+ rampval = eqy1 + slope * (x[i] - eqx1)
+ sum[2] = sum[2] + s[i]**2
+ if (!IS_INDEF(esum[1])) {
+ if (fp_equalr (0., rampval/scale))
+ esum[2] = INDEF
+ else
+ esum[2] = esum[2] + (s[i] / rampval) ** 2
+ }
+ }
+
+ for (i=i1+1; i <= i2-1; i = i+1) {
+ rampval = eqy1 + slope * (x[i] - eqx1)
+ delta = (y[i] - rampval) / scale
+ csum[2] = csum[2] + abs(delta)*((x[i]-ctr[1])*s[i]) ** 2
+ }
+
+ # endpoints
+ sum[2] = sum[2] + (w1 * s[i1])**2 + (w2 * s[i2])**2
+ if (!IS_INDEF(esum[1])) {
+ if (fp_equalr (0., eqy1/scale)|| fp_equalr (0., eqy2/scale))
+ esum[2] = INDEF
+ else
+ esum[2] = esum[2] + (w1 * s[i1] / eqy1) ** 2 +
+ (w2 * s[i2] / eqy2) ** 2
+ }
+
+ delta = (y[i1] - eqy1) / scale
+ csum[2] = csum[2] + abs(delta)*(w1*(eqx1-ctr[1])*s[i1]) ** 2
+
+ delta = (y[i2] - eqy2) / scale
+ csum[2] = csum[2] + abs(delta)*(w2*(eqx2-ctr[1])*s[i2]) ** 2
+
+ if (sum2[1] != 0.0)
+ ctr[2] = 1.5 / scale * sqrt (csum[2]) / sum2[1]
+ else
+ ctr[2] = 0.0
+
+ sum[2] = sqrt (sum[2])
+ esum[2] = sqrt (esum[2])
+
+ # Correct for angstroms/channel
+ sum[2] = sum[2] * wpc
+ if (!IS_INDEF(esum[1]))
+ esum[2] = esum[2] * wpc
+ }
+end
diff --git a/noao/onedspec/splot/usercoord.x b/noao/onedspec/splot/usercoord.x
new file mode 100644
index 00000000..2a9b3584
--- /dev/null
+++ b/noao/onedspec/splot/usercoord.x
@@ -0,0 +1,94 @@
+include <error.h>
+include <smw.h>
+include <units.h>
+
+# USERCOORD -- Set user coordinates
+
+procedure usercoord (sh, key, w1, u1, w2, u2)
+
+pointer sh
+int key
+double w1, u1, w2, u2
+
+int i, format, ap, beam, dtype, nw
+double shift, wa, wb, ua, ub, w0, dw, z, smw_c1trand()
+real aplow[2], aphigh[2]
+pointer coeff, smw, mw, ct, smw_sctran()
+errchk smw_sctran
+
+begin
+ coeff = NULL
+ smw = MW(sh)
+ mw = SMW_MW(smw,0)
+ format = SMW_FORMAT(smw)
+
+ iferr {
+ call un_ctrand (UN(sh), MWUN(sh), w1, wa, 1)
+ call un_ctrand (UN(sh), MWUN(sh), u1, ua, 1)
+
+ call smw_gwattrs (MW(sh), APINDEX(sh), LINDEX(sh,2),
+ ap, beam, dtype, w0, dw, nw, z, aplow, aphigh, coeff)
+
+ switch (key) {
+ case 'd':
+ wa = wa * (1 + z)
+ switch (UN_CLASS(MWUN(sh))) {
+ case UN_WAVE:
+ z = (wa - ua) / ua
+ case UN_FREQ, UN_ENERGY:
+ z = (ua - wa) / wa
+ default:
+ call error (1, "Inappropriate coordinate units")
+ }
+ case 'z':
+ shift = ua - wa
+ w0 = w0 + shift
+ if (dtype == 2)
+ call sshift1 (shift, coeff)
+ case 'l':
+ call un_ctrand (UN(sh), MWUN(sh), w2, wb, 1)
+ call un_ctrand (UN(sh), MWUN(sh), u2, ub, 1)
+
+ switch (format) {
+ case SMW_ND:
+ i = 2 ** (SMW_PAXIS(smw,1) - 1)
+ ct = smw_sctran (smw, "world", "physical", i)
+ wa = smw_c1trand (ct, wa)
+ wb = smw_c1trand (ct, wb)
+ case SMW_ES, SMW_MS:
+ ct = smw_sctran (smw, "world", "physical", 3)
+ call smw_c2trand (ct, wa, double (ap), wa, shift)
+ call smw_c2trand (ct, wb, double (ap), wb, shift)
+ }
+ call smw_ctfree (ct)
+
+ dw = (ub - ua) / (wb - wa)
+ w0 = ua - (wa - 1) * dw
+ dtype = 0
+ if (UNITS(sh) == EOS) {
+ call mw_swattrs (mw, SMW_PAXIS(smw,1),
+ "label", "Wavelength")
+ call mw_swattrs (mw, SMW_PAXIS(smw,1),
+ "units", "angstroms")
+ }
+ default:
+ call error (1, "Unknown correction")
+ }
+
+ call smw_swattrs (smw, LINDEX(sh,1), 1, ap, beam, dtype, w0,
+ dw, nw, z, aplow, aphigh, Memc[coeff])
+ if (smw != MW(sh)) {
+ CTLW1(sh) = NULL
+ CTWL1(sh) = NULL
+ MW(sh) = smw
+ }
+
+ DC(sh) = dtype
+ call shdr_system (sh, "world")
+ if (UN_CLASS(UN(sh)) == UN_UNKNOWN)
+ call un_copy (MWUN(sh), UN(sh))
+ } then
+ call erract (EA_WARN)
+
+ call mfree (coeff, TY_CHAR)
+end
diff --git a/noao/onedspec/splot/voigt.x b/noao/onedspec/splot/voigt.x
new file mode 100644
index 00000000..08a44c78
--- /dev/null
+++ b/noao/onedspec/splot/voigt.x
@@ -0,0 +1,71 @@
+# VOIGT -- Compute the real (Voigt function) and imaginary parts of the
+# complex function w(z)=exp(-z**2)*erfc(-i*z) in the upper half-plane
+# z=x+iy. The maximum relative error of the real part is 2E-6 and the
+# imaginary part is 5E-6.
+#
+# From: Humlicek, J. Quant. Spectrosc. Radiat. Transfer, V21, p309, 1979.
+
+procedure voigt (xarg, yarg, wr, wi)
+
+real xarg #I Real part of argument
+real yarg #I Imaginary part of argument
+real wr #O Real part of function
+real wi #O Imaginary part of function
+
+int i
+real x, y, y1, y2, y3, d, d1, d2, d3, d4, r, r2
+real t[6], c[6], s[6]
+
+data t/.314240376,.947788391,1.59768264,2.27950708,3.02063703,3.8897249/
+data c/1.01172805,-.75197147,1.2557727e-2,1.00220082e-2,-2.42068135e-4,
+ 5.00848061e-7/
+data s/1.393237,.231152406,-.155351466,6.21836624e-3,9.19082986e-5,
+ -6.27525958e-7/
+
+begin
+ x = xarg
+ y = abs (yarg)
+ wr = 0.
+ wi = 0.
+ y1 = y + 1.5
+ y2 = y1 * y1
+
+ # Region II
+ if (y < 0.85 && abs(x) > 18.1*y+1.65) {
+ if (abs(x) < 12)
+ wr = exp (-x * x)
+ y3 = y + 3
+ do i = 1, 6 {
+ r = x - t[i]
+ r2 = r * r
+ d = 1 / (r2 + y2)
+ d1 = y1 * d
+ d2 = r * d
+ wr = wr + y * (c[i] * (r * d2 - 1.5 * d1) + s[i] * y3 * d2) /
+ (r2 + 2.25)
+ r = x + t[i]
+ r2 = r * r
+ d = 1 / (r2 + y2)
+ d3 = y1 * d
+ d4 = r * d
+ wr = wr + y * (c[i] * (r * d4 - 1.5 * d3) - s[i] * y3 * d4) /
+ (r2 + 2.25)
+ wi = wi + c[i] * (d2 + d4) + s[i] * (d1 - d3)
+ }
+
+ # Region I
+ } else {
+ do i = 1, 6 {
+ r = x - t[i]
+ d = 1 / (r * r + y2)
+ d1 = y1 * d
+ d2 = r * d
+ r = x + t[i]
+ d = 1 / (r * r + y2)
+ d3 = y1 * d
+ d4 = r * d
+ wr = wr + c[i] * (d1 + d3) - s[i] * (d2 - d4)
+ wi = wi + c[i] * (d2 + d4) + s[i] * (d1 - d3)
+ }
+ }
+end
diff --git a/noao/onedspec/splot/wrspect.x b/noao/onedspec/splot/wrspect.x
new file mode 100644
index 00000000..b744a180
--- /dev/null
+++ b/noao/onedspec/splot/wrspect.x
@@ -0,0 +1,397 @@
+include <error.h>
+include <syserr.h>
+include <imhdr.h>
+include <imio.h>
+include <smw.h>
+include <units.h>
+
+# SP_WRSPECT -- Write spectrum to the same image or another image.
+
+procedure sp_wrspect (sh1)
+
+pointer sh1 # Spectrum pointer to be written
+
+bool overwrite
+pointer sp, str
+int nowhite(), errcode()
+bool clgetb(), xt_imnameeq()
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Initially set overwrite to false in order to warn the user.
+ overwrite = false
+
+ # Get new image name.
+ call clgstr ("new_image", Memc[str], SZ_LINE)
+ if (nowhite (Memc[str], Memc[str], SZ_LINE) == 0) {
+ call sfree (sp)
+ return
+ }
+
+ # Check for overwriting the current file.
+ if (xt_imnameeq (IMNAME(sh1), Memc[str])) {
+ overwrite = clgetb ("overwrite")
+ if (!overwrite) {
+ call sfree (sp)
+ return
+ }
+ }
+
+ # Write spectrum.
+ iferr (call wrspect (sh1, Memc[str], overwrite)) {
+ switch (errcode()) {
+ case SYS_IKICLOB:
+ call erract (EA_WARN)
+ # Try again if overwrite is requested.
+ if (!overwrite)
+ overwrite = clgetb ("overwrite")
+ if (overwrite) {
+ iferr (call wrspect (sh1, Memc[str], overwrite))
+ call erract (EA_WARN)
+ }
+ default:
+ call erract (EA_WARN)
+ }
+ }
+ call sfree (sp)
+end
+
+
+# WRSPECT -- Write spectrum to the same image or another image.
+#
+# If overwriting reopen the image READ_WRITE. If this is not possible it is
+# an error which may be trapped by the calling routine if desired.
+#
+# If writing to another image determine if the image exists. If not make a
+# NEW_COPY of the image and copy all spectra and associated data. NDSPEC
+# format spectra, i.e. 2D or 3D images, are copied to a 1D spectrum.
+#
+# If the image exists check the overwrite parameter. If overwriting, open the
+# image READ_WRITE and return an error if this is not possible. If the
+# output image has only one spectrum delete the image and create a NEW_COPY
+# of the current spectrum image. Otherwise we will be replacing only the
+# current spectrum so copy all spectra from the current image.
+#
+# When the input and output images are not the same open the output WCS and
+# select the spectrum of the same aperture to replace. It is an error if the
+# output spectrum does not contain a spectrum of the same aperture. It is
+# also an error if the output spectrum is an NDSPEC image.
+
+procedure wrspect (sh1, output, overwrite)
+
+pointer sh1 # Spectrum pointer to be written
+char output[ARB] # Output spectrum filename
+bool overwrite # Overwrite existing spectrum?
+
+bool delim
+char errstr[SZ_LINE]
+int i, j, np1, np2, dtype[2], nw[2], err
+real r[2]
+double w1[2], dw[2], z[2]
+pointer coeff, im, in, out, mw1, mw2, sh2, outbuf, ptr
+
+int imaccf(), errget()
+bool xt_imnameeq(), fp_equald()
+pointer immap(), smw_openim(), imgl3r(), impl3r(), imps3r()
+errchk immap, imgl3r, impl3r, imps3r, imdelf, shdr_open, wrspect1
+errchk smw_openim, smw_gwattrs, smw_swattrs, smw_saveim
+
+begin
+ in = IM(sh1)
+ mw1 = MW(sh1)
+ out = NULL
+ mw2 = NULL
+ sh2 = NULL
+ ptr = NULL
+ delim = false
+
+ iferr {
+ # Open and initialize the output image.
+ if (xt_imnameeq (IMNAME(sh1), output)) {
+ if (!overwrite) {
+ call sprintf (errstr, SZ_LINE, "No overwrite set (%s)")
+ call pargstr (output)
+ call error (1, errstr)
+ }
+
+ call imunmap (in)
+ iferr (im = immap (IMNAME(sh1), READ_WRITE, 0)) {
+ in = immap (IMNAME(sh1), READ_ONLY, 0)
+ call erract (EA_ERROR)
+ }
+ in = im
+ IM(sh1) = in
+ out = in
+ mw2 = MW(sh1)
+ sh2 = sh1
+
+ } else {
+ iferr (im = immap (output, NEW_COPY, in)) {
+ if (!overwrite)
+ call erract (EA_ERROR)
+ im = immap (output, READ_WRITE, 0); out = im
+
+ if (IM_LEN(out,2) == 1) {
+ call imunmap (out)
+ call imdelete (output)
+ im = immap (output, NEW_COPY, in); out = im
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ do j = 1, IM_LEN(out,3)
+ do i = 1, IM_LEN(out,2)
+ call amovr (Memr[imgl3r(in,i,j)],
+ Memr[impl3r(out,i,j)], IM_LEN(out,1))
+ }
+
+ im = smw_openim (out); mw2 = im
+ switch (SMW_FORMAT(mw1)) {
+ case SMW_ND:
+ if (SMW_FORMAT(mw2) != SMW_ND)
+ call error (1, "Incompatible spectral formats")
+ if (IM_NDIM(in) != IM_NDIM(out))
+ call error (2, "Incompatible dimensions")
+ do i = 1, IM_NDIM(in)
+ if (IM_LEN(in,i) != IM_LEN(out,i))
+ call error (2, "Incompatible dimensions")
+ coeff = NULL
+ call smw_gwattrs (mw1, 1, 1, i, i,
+ dtype[1], w1[1], dw[1], nw[1], z, r, r, coeff)
+ call smw_gwattrs (mw2, 1, 1, i, i,
+ dtype[2], w1[2], dw[2], nw[2], z, r, r, coeff)
+ call mfree (coeff, TY_CHAR)
+ if (dtype[1]!=dtype[2] || !fp_equald (w1[1],w1[2]) ||
+ !fp_equald (dw[1],dw[2]))
+ call error (3,
+ "Incompatible dispersion coordinates")
+ call shdr_open (out, mw2, APINDEX(sh1), LINDEX(sh1,2),
+ AP(sh1), SHHDR, ptr)
+ sh2 = ptr
+ case SMW_ES, SMW_MS:
+ if (SMW_FORMAT(mw2) == SMW_ND)
+ call error (1, "Incompatible spectral formats")
+ call shdr_open (out, mw2, APINDEX(sh1), LINDEX(sh1,2),
+ AP(sh1), SHHDR, ptr)
+ sh2 = ptr
+ }
+
+ } else {
+ delim = true
+ out = im
+ IM_PIXTYPE(out) = TY_REAL
+ im = smw_openim (out); mw2 = im
+ call shdr_open (out, mw2, APINDEX(sh1), LINDEX(sh1,2),
+ AP(sh1), SHHDR, ptr)
+ sh2 = ptr
+
+ do j = 1, IM_LEN(out,3)
+ do i = 1, IM_LEN(out,2)
+ call amovr (Memr[imgl3r(in,i,j)],
+ Memr[impl3r(out,i,j)], IM_LEN(out,1))
+ }
+ }
+
+ # Check, set, and update the WCS information. Note that
+ # wrspect1 may change the smw pointers.
+
+ call wrspect1 (sh1, sh2)
+ mw1 = MW(sh1)
+ mw2 = MW(sh2)
+ call smw_saveim (mw2, out)
+
+ # Update spectrum calibration parameters.
+ if (EC(sh1) == ECYES)
+ call imaddi (out, "EX-FLAG", EC(sh1))
+ else if (imaccf (out, "EX-FLAG") == YES)
+ call imdelf (out, "EX-FLAG")
+ if (FC(sh1) == FCYES)
+ call imaddi (out, "CA-FLAG", FC(sh1))
+ else if (imaccf (out, "CA-FLAG") == YES)
+ call imdelf (out, "CA-FLAG")
+ if (RC(sh1) != EOS)
+ call imastr (out, "DEREDDEN", RC(sh1))
+ else if (imaccf (out, "DEREDDEN") == YES)
+ call imdelf (out, "DEREDDEN")
+
+ # Copy the spectrum.
+ i = max (1, LINDEX(sh2,1))
+ j = max (1, LINDEX(sh2,2))
+ np1 = NP1(sh1)
+ np2 = NP2(sh1)
+ switch (SMW_FORMAT(mw1)) {
+ case SMW_ND:
+ switch (SMW_LAXIS(mw1,1)) {
+ case 1:
+ outbuf = imps3r (out, np1, np2, i, i, j, j)
+ case 2:
+ outbuf = imps3r (out, i, i, np1, np2, j, j)
+ case 3:
+ outbuf = imps3r (out, i, i, j, j, np1, np2)
+ }
+ call amovr (Memr[SY(sh1)], Memr[outbuf], SN(sh1))
+ case SMW_ES, SMW_MS:
+ outbuf = impl3r (out, i, j)
+ call amovr (Memr[SY(sh1)], Memr[outbuf+np1-1], SN(sh1))
+ if (np1 > 1)
+ call amovkr (Memr[outbuf+np1-1], Memr[outbuf], np1-1)
+ if (np2 < IM_LEN(out,1))
+ call amovkr (Memr[outbuf+np2-1], Memr[outbuf+np2],
+ IM_LEN(out,1)-np2)
+ }
+
+ # Close output image if not the same as the input image.
+ if (out != in) {
+ call shdr_close (sh2)
+ call smw_close (mw2)
+ call imunmap (out)
+ }
+ } then {
+ err = errget (errstr, SZ_LINE)
+ if (out != in) {
+ if (sh2 != NULL)
+ call shdr_close (sh2)
+ if (mw2 != NULL)
+ call smw_close (mw2)
+ if (out != NULL) {
+ call imunmap (out)
+ if (delim)
+ iferr (call imdelete (output))
+ ;
+ }
+ }
+ call error (err, errstr)
+ }
+
+end
+
+
+# WRSPECT1 -- Set output WCS attributes.
+# This requires checking compatibility of the WCS with other spectra
+# in the image.
+
+procedure wrspect1 (sh1, sh2)
+
+pointer sh1 # Input
+pointer sh2 # Output
+
+int i, j, beam, dtype, nw
+double w1, wb, dw, z, a, b, p1, p2, p3, shdr_lw()
+real aplow[2], aphigh[2]
+pointer in, out, smw1, smw2, mw, smw_sctran()
+pointer sp, key, str, ltm, ltv, coeff
+bool fp_equald(), strne()
+errchk mw_glterm, smw_gwattrs, smw_swattrs, smw_sctran
+
+begin
+ in = IM(sh1)
+ out = IM(sh2)
+ smw1 = MW(sh1)
+ smw2 = MW(sh2)
+ mw = SMW_MW(smw2,0)
+
+ # The output format must not be NDSPEC and there must be a
+ # matching aperture in the output image.
+
+ if (AP(sh2) != AP(sh1) || LINDEX(sh2,1) != LINDEX(sh1,1))
+ call error (6, "Matching aperture not found in output image")
+
+ call smark (sp)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (ltm, 3*3, TY_DOUBLE)
+ call salloc (ltv, 3, TY_DOUBLE)
+ call malloc (coeff, SZ_LINE, TY_CHAR)
+
+ # Check dispersion function compatibility.
+ # Nonlinear functions can't be copied to a different physical
+ # coordinate system though the linear dispersion can be
+ # adjusted.
+
+ i = SMW_PDIM(smw2)
+ j = SMW_PAXIS(smw2,1)
+ call mw_gltermd (mw, Memd[ltm], Memd[ltv], SMW_PDIM(smw2))
+ a = Memd[ltv+(j-1)]
+ b = Memd[ltm+(i+1)*(j-1)]
+ if (DC(sh1) == DCFUNC) {
+ i = SMW_PDIM(smw1)
+ j = SMW_PAXIS(smw1,1)
+ call mw_gltermd (SMW_MW(smw1,0), Memd[ltm], Memd[ltv], i)
+ Memd[ltv] = Memd[ltv+(j-1)]
+ Memd[ltm] = Memd[ltm+(i+1)*(j-1)]
+ if (!fp_equald (a, Memd[ltv]) || !fp_equald (b ,Memd[ltm])) {
+ call error (7,
+ "Physical basis for nonlinear dispersion functions don't match")
+ }
+ }
+
+ call smw_gwattrs (smw1, LINDEX(sh1,1), LINDEX(sh1,2),
+ AP(sh1), beam, dtype, w1, dw, nw, z, aplow, aphigh, coeff)
+
+ w1 = shdr_lw (sh1, 1D0)
+ wb = shdr_lw (sh1, double(SN(sh1)))
+ iferr {
+ call un_ctrand (UN(sh1), MWUN(sh1), w1, w1, 1)
+ call un_ctrand (UN(sh1), MWUN(sh1), wb, wb, 1)
+ } then
+ ;
+
+ p1 = (NP1(sh1) - a) / b
+ p2 = (NP2(sh1) - a) / b
+ p3 = (IM_LEN(out,1) - a) / b
+ nw = nint (min (max (p1 ,p3), max (p1, p2))) + NP1(sh1) - 1
+ if (dtype == DCLOG) {
+ if (p1 != p2)
+ dw = (log10(wb*(1+z)) - log10(w1*(1+z))) / (p2 - p1)
+ w1 = log10 (w1*(1+z)) - (p1 - 1) * dw
+ w1 = 10. ** w1
+ dw = (w1 * 10D0 ** ((nw-1)*dw) - w1) / (nw - 1)
+ } else {
+ if (p1 != p2)
+ dw = (wb - w1) / (p2 - p1) * (1 + z)
+ w1 = w1 * (1 + z) - (p1 - 1) * dw
+ }
+
+ # Note that this may change the smw pointer.
+ call smw_swattrs (smw2, LINDEX(sh2,1), 1, AP(sh2), beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, Memc[coeff])
+ if (smw2 != MW(sh2)) {
+ switch (SMW_FORMAT(smw2)) {
+ case SMW_ND, SMW_ES:
+ i = 2 ** (SMW_PAXIS(smw2,1) - 1)
+ case SMW_MS:
+ i = 3B
+ }
+ CTLW1(sh2) = smw_sctran (smw2, "logical", "world", i)
+ CTWL1(sh2) = smw_sctran (smw2, "world", "logical", i)
+ CTLW(sh2) = CTLW1(sh2)
+ CTWL(sh2) = CTWL1(sh2)
+ MW(sh2) = smw2
+ mw = SMW_MW(smw2,0)
+ }
+
+ # Copy title
+ call smw_sapid (smw2, LINDEX(sh2,1), 1, TITLE(sh1))
+ if (Memc[SID(sh1,1)] != EOS) {
+ call sprintf (Memc[key], SZ_LINE, "BANDID%d")
+ call pargi (LINDEX(sh1,2))
+ iferr (call imgstr (out, Memc[key], Memc[str], SZ_LINE))
+ call imastr (out, Memc[key], Memc[SID(sh1,1)])
+ else {
+ if (strne (Memc[SID(sh1,1)], Memc[str]))
+ call eprintf (
+ "Warning: Input and output types (BANDID) differ\n")
+ }
+ }
+
+ # Copy label and units
+ if (UN_LABEL(MWUN(sh1)) != EOS)
+ call mw_swattrs (mw, 1, "label", UN_LABEL(MWUN(sh1)))
+ if (UN_UNITS(MWUN(sh1)) != EOS)
+ call mw_swattrs (mw, 1, "units", UN_UNITS(MWUN(sh1)))
+ if (UN_USER(UN(sh1)) != EOF)
+ call mw_swattrs (mw, 1, "units_display", UN_USER(UN(sh1)))
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/standard.key b/noao/onedspec/standard.key
new file mode 100644
index 00000000..01e0165e
--- /dev/null
+++ b/noao/onedspec/standard.key
@@ -0,0 +1,11 @@
+ STANDARD TASK CURSOR KEY OPTIONS
+
+? Display help page
+a Add a new band by marking the endpoints
+d Delete band nearest the cursor in wavelength
+r Redraw current plot
+q Quit with current bandpass definitions
+w Window plot (follow with '?' for help)
+I Interrupt task immediately
+
+:show Show current bandpass data
diff --git a/noao/onedspec/standard.par b/noao/onedspec/standard.par
new file mode 100644
index 00000000..d83a98d1
--- /dev/null
+++ b/noao/onedspec/standard.par
@@ -0,0 +1,21 @@
+input,f,a,,,,Input image file root name
+output,s,a,std,,,Output flux file (used by SENSFUNC)
+samestar,b,h,yes,,,Same star in all apertures?
+beam_switch,b,h,no,,,Beam switch spectra?
+apertures,s,h,"",,,Aperture selection list
+bandwidth,r,h,INDEF,,,Bandpass widths
+bandsep,r,h,INDEF,,,Bandpass separation
+fnuzero,r,h,3.68e-20,,,Absolute flux zero point
+extinction,s,h,,,,Extinction file
+caldir,s,h,,,,Directory containing calibration data
+observatory,s,h,)_.observatory,,,Observatory for data
+interact,b,h,yes,,,Graphic interaction to define new bandpasses
+graphics,s,h,"stdgraph",,,Graphics output device
+cursor,*gcur,h,"",,,Graphics cursor input
+star_name,s,q,,,,Star name in calibration list
+airmass,r,q,,1.,,Airmass
+exptime,r,q,,,,Exposure time (seconds)
+mag,r,q,,,,Magnitude of star
+magband,s,q,,"U|B|V|R|I|J|H|K|L|Lprime|M",,"Magnitude type"
+teff,s,q,,,,Effective temperature or spectral type
+answer,s,q,no,,,"(no|yes|NO|YES|NO!|YES!)"
diff --git a/noao/onedspec/t_calibrate.x b/noao/onedspec/t_calibrate.x
new file mode 100644
index 00000000..5df62b45
--- /dev/null
+++ b/noao/onedspec/t_calibrate.x
@@ -0,0 +1,437 @@
+include <error.h>
+include <imset.h>
+include <imhdr.h>
+include <math/iminterp.h>
+include <smw.h>
+
+define EXTN_LOOKUP 10 # Interp index for de-extinction
+define VLIGHT 2.997925e18 # Speed of light, Angstroms/sec
+
+# T_CALIBRATE -- Apply extinction correction and flux calibration to spectra.
+# The sensitivity function derived from the tasks STANDARD and SENSFUNC
+# are applied to the given spectra. The output may be the same as the
+# input or new spectra may be created.
+#
+# The sensitivity function is contained in an image having its aperture
+# number indicated by the trailing integer of the image filename.
+# An option, "ignoreaps", can be set to override the appending of the
+# aperture number on those cases where no aperture correspondence is
+# appropriate.
+
+procedure t_calibrate ()
+
+pointer inlist # Input list
+pointer outlist # Output list
+pointer sens # Sensitivity image root name
+pointer ob # Observatory
+bool ignoreaps # Ignore aperture numbers?
+bool extinct # Apply extinction correction?
+bool flux # Apply flux calibration?
+bool fnu # Calibration flux in FNU?
+
+bool doextinct, doflux, newobs, obshead
+int i, j, k, l, n, enwaves, nout, ncal
+real a, latitude, time, ext, fcor, ical, w, dw
+pointer sp, input, output, temp
+pointer obs, in, smw, sh, out, ewaves, emags, pcal, cal, asi, x, y, data
+
+int imtgetim(), imtlen()
+bool clgetb(), streq()
+real clgetr(), obsgetr(), asieval()
+double shdr_lw(), shdr_wl()
+pointer imtopenp(), immap(), smw_openim(), imgl3r(), impl3r()
+errchk immap, smw_openim, shdr_open, imgl3r, impl3r
+errchk obsimopen, get_airm, ext_load, cal_getflux, cal_extn, cal_flux
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (sens, SZ_FNAME, TY_CHAR)
+ call salloc (ob, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_LINE, TY_CHAR)
+
+ # Get task parameters.
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ call clgstr ("records", Memc[temp], SZ_LINE)
+ call odr_openp (inlist, Memc[temp])
+ call odr_openp (outlist, Memc[temp])
+ call clgstr ("sensitivity", Memc[sens], SZ_FNAME)
+ call clgstr ("observatory", Memc[ob], SZ_FNAME)
+ extinct = clgetb ("extinct")
+ flux = clgetb ("flux")
+ fnu = clgetb ("fnu")
+ ignoreaps = clgetb ("ignoreaps")
+
+ if (!extinct && !flux)
+ call error (0, "No calibration correction specified")
+
+ # Loop over all input images.
+ sh = NULL
+ obs = NULL
+ enwaves = 0
+ ncal = 0
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+
+ # Set output image. Use a temporary image when output=input.
+ if (imtlen (outlist) > 0) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ break
+ } else
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ # Map the input image.
+ iferr (in = immap (Memc[input], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+ smw = smw_openim (in)
+
+ # Check the input image calibration status.
+ call shdr_open (in, smw, 1, 1, INDEFI, SHHDR, sh)
+ if (DC(sh) == DCNO) {
+ call eprintf ("WARNING: [%s] has no dispersion function\n")
+ call pargstr (Memc[input])
+ call smw_close (MW(sh))
+ call imunmap (in)
+ next
+ }
+ call shdr_units (sh, "angstroms")
+
+ doextinct = extinct && (EC(sh) == ECNO)
+ doflux = flux && (FC(sh) == FCNO)
+ if (!(doextinct || doflux)) {
+ call eprintf ("WARNING: [%s] is already calibrated\n")
+ call pargstr (Memc[input])
+ call smw_close (MW(sh))
+ call imunmap (in)
+ next
+ }
+
+ # Map the output image.
+ if (streq (Memc[input], Memc[output]))
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ else
+ call strcpy (Memc[output], Memc[temp], SZ_LINE)
+ out = immap (Memc[temp], NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+
+ # Log the operation.
+ call printf ("%s: %s\n")
+ call pargstr (Memc[output])
+ call pargstr (IM_TITLE(out))
+ call flush (STDOUT)
+
+ # Initialize the extinction correction.
+ if (doextinct) {
+ EC(sh) = ECYES
+
+ # Load extinction function.
+ if (enwaves == 0) {
+ call ext_load (ewaves, emags, enwaves)
+ call intrp0 (EXTN_LOOKUP)
+ }
+
+ # Determine airmass if needed.
+ if (IS_INDEF(AM(sh))) {
+ call obsimopen (obs, in, Memc[ob], NO, newobs, obshead)
+ if (newobs)
+ call obslog (obs, "CALIBRATE", "latitude", STDOUT)
+ latitude = obsgetr (obs, "latitude")
+ iferr (call get_airm (RA(sh), DEC(sh), HA(sh), ST(sh),
+ latitude, AM(sh))) {
+ call printf ("%s: ")
+ call pargstr (Memc[input])
+ call flush (STDOUT)
+ AM(sh) = clgetr ("airmass")
+ call imunmap (in)
+ ifnoerr (in = immap (Memc[input], READ_WRITE, 0)) {
+ IM(sh) = in
+ call imseti (IM(sh), IM_WHEADER, YES)
+ call imaddr (IM(sh), "airmass", AM(sh))
+ } else {
+ in = immap (Memc[input], READ_ONLY, 0)
+ IM(sh) = in
+ }
+ }
+ }
+ a = AM(sh)
+ } else
+ ext = 1.
+
+ # Initialize the flux correction.
+ nout = 0
+ if (doflux) {
+ FC(sh) = FCYES
+
+ if (IS_INDEF (IT(sh)) || IT(sh) <= 0.) {
+ call printf ("%s: ")
+ call pargstr (Memc[input])
+ call flush (STDOUT)
+ IT(sh) = clgetr ("exptime")
+ call imunmap (in)
+ ifnoerr (in = immap (Memc[input], READ_WRITE, 0)) {
+ IM(sh) = in
+ call imseti (IM(sh), IM_WHEADER, YES)
+ call imaddr (IM(sh), "exptime", IT(sh))
+ call imaddr (out, "exptime", IT(sh))
+ } else {
+ in = immap (Memc[input], READ_ONLY, 0)
+ IM(sh) = in
+ }
+ }
+ time = IT(sh)
+ } else
+ fcor = 1.
+
+ # Calibrate.
+ do j = 1, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ data = impl3r (out, i, j)
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ if (doflux) {
+ call cal_getflux (Memc[sens], INDEFI, fnu,
+ pcal, ncal, cal)
+
+ asi = IM(cal)
+ n = SN(cal)
+ }
+ y = imgl3r (in, i, j)
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ do k = 1, IM_LEN(out,1) {
+ w = shdr_lw (sh, double(k))
+ if (doextinct) {
+ call intrp (EXTN_LOOKUP, Memr[ewaves],
+ Memr[emags], enwaves, w, ext, l)
+ ext = 10.0 ** (0.4 * a * ext)
+ }
+ if (doflux) {
+ ical = shdr_wl (cal, double(w))
+ if (ical < 1. || ical > n) {
+ if (ical < 0.5 || ical > n + 0.5)
+ nout = nout + 1
+ ical = max (1., min (real(n), ical))
+ }
+ dw = abs (shdr_lw (sh, double(k+0.5)) -
+ shdr_lw (sh, double(k-0.5)))
+ fcor = asieval (asi, ical) / dw / time
+ }
+ Memr[data] = Memr[y] * ext * fcor
+ y = y + 1
+ data = data + 1
+ }
+ case 2, 3:
+ if (SMW_LAXIS(smw,1) == 2)
+ k = i
+ else
+ k = j
+ w = shdr_lw (sh, double(k))
+ if (doextinct) {
+ call intrp (EXTN_LOOKUP, Memr[ewaves],
+ Memr[emags], enwaves, w, ext, l)
+ ext = 10.0 ** (0.4 * a * ext)
+ }
+ if (doflux) {
+ ical = shdr_wl (cal, double(w))
+ if (ical < 1. || ical > n) {
+ if (ical < 0.5 || ical > n + 0.5)
+ nout = nout + 1
+ ical = max (1., min (real(n), ical))
+ }
+ dw = abs (shdr_lw (sh, double(k+0.5)) -
+ shdr_lw (sh, double(k-0.5)))
+ fcor = asieval (asi, ical) / dw / time
+ }
+ call amulkr (Memr[y], ext * fcor, Memr[data],
+ IM_LEN(out,1))
+ }
+ case SMW_ES, SMW_MS:
+ call shdr_open (in, smw, i, j, INDEFI, SHDATA, sh)
+ call shdr_units (sh, "angstroms")
+ if (doflux) {
+ if (ignoreaps)
+ call cal_getflux (Memc[sens], INDEFI, fnu,
+ pcal, ncal, cal)
+ else
+ call cal_getflux (Memc[sens], AP(sh), fnu,
+ pcal, ncal, cal)
+
+ asi = IM(cal)
+ n = SN(cal)
+ }
+ x = SX(sh)
+ y = SY(sh)
+ do k = 1, SN(sh) {
+ w = Memr[x]
+ if (doextinct) {
+ call intrp (EXTN_LOOKUP, Memr[ewaves],
+ Memr[emags], enwaves, w, ext, l)
+ ext = 10.0 ** (0.4 * a * ext)
+ }
+ if (doflux) {
+ ical = shdr_wl (cal, double(w))
+ if (ical < 1. || ical > n) {
+ if (ical < 0.5 || ical > n + 0.5)
+ nout = nout + 1
+ ical = max (1., min (real(n), ical))
+ }
+ dw = abs (shdr_lw (sh, double(k+0.5)) -
+ shdr_lw (sh, double(k-0.5)))
+ fcor = asieval (asi, ical) / dw / time
+ }
+ Memr[data] = Memr[y] * ext * fcor
+ x = x + 1
+ y = y + 1
+ data = data + 1
+ }
+ do k = SN(sh)+1, IM_LEN(out,1) {
+ Memr[data] = 0
+ data = data + 1
+ }
+ }
+ }
+ }
+
+ # Log the results.
+ if (doflux && (IS_INDEF (IT(sh)) || IT(sh) <= 0.)) {
+ call printf (
+ " WARNING: No exposure time found. Using a time of %g.\n")
+ call pargr (time)
+ }
+ if (nout > 0) {
+ call printf (
+ " WARNING: %d pixels outside of flux calibration limits\n")
+ call pargi (nout)
+ }
+ if (doextinct)
+ call printf (" Extinction correction applied\n")
+ if (doflux)
+ call printf (" Flux calibration applied\n")
+ call flush (STDOUT)
+
+ call imaddr (out, "AIRMASS", AM(sh))
+ call imaddi (out, "EX-FLAG", EC(sh))
+ call imaddi (out, "CA-FLAG", FC(sh))
+ if (doflux) {
+ if (fnu)
+ call imastr (out, "BUNIT", "erg/cm2/s/Hz")
+ else
+ call imastr (out, "BUNIT", "erg/cm2/s/A")
+ }
+
+ # Close the input and output images.
+ call smw_close (MW(sh))
+ call imunmap (in)
+ call imunmap (out)
+ if (streq (Memc[input], Memc[output])) {
+ call imdelete (Memc[input])
+ call imrename (Memc[temp], Memc[output])
+ }
+ }
+
+ # Finish up.
+ if (enwaves > 0) {
+ call mfree (ewaves, TY_REAL)
+ call mfree (emags, TY_REAL)
+ }
+ if (ncal > 0) {
+ do i = 0, ncal-1 {
+ cal = Memi[pcal+i]
+ call asifree (IM(cal))
+ call smw_close (MW(cal))
+ call shdr_close (cal)
+ }
+ call mfree (pcal, TY_POINTER)
+ }
+ if (obs != NULL)
+ call obsclose (obs)
+ call shdr_close (sh)
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call sfree (sp)
+end
+
+
+# CAL_GETFLUX -- Get flux calibration data
+# The sensitivity spectrum is in peculiar magnitudish units of 2.5 log10
+# [counts/sec/A / ergs/cm2/s/A]. This is converted back to reasonable
+# numbers to be multiplied into the data spectra. An interpolation function
+# is then fit and stored in the image pointer field. For efficiency the
+# calibration data is saved by aperture so that additional calls simply
+# return the data pointer.
+
+procedure cal_getflux (sens, ap, fnu, pcal, ncal, cal)
+
+char sens[ARB] # Sensitivity function image or rootname
+int ap # Aperture
+bool fnu # Fnu units?
+pointer pcal # Pointer to cal data
+int ncal # Number of active cal data structures
+pointer cal # Calibration data structure
+
+int i, j, n, clgwrd()
+pointer sp, fname, im, smw, x, y, immap(), smw_openim()
+errchk immap, smw_openim, shdr_open, asifit
+
+begin
+ # Check for previously saved calibration
+ for (i=0; i<ncal; i=i+1) {
+ cal = Memi[pcal+i]
+ if (AP(cal) == ap)
+ return
+ }
+
+ # Allocate space for a new data pointer, get the calibration data,
+ # and convert to calibration array.
+
+ call smark (sp)
+ call salloc (fname, SZ_FNAME, TY_CHAR)
+
+ if (ncal == 0)
+ call malloc (pcal, 10, TY_POINTER)
+ else if (mod (ncal, 10) == 0)
+ call realloc (pcal, ncal+10, TY_POINTER)
+
+ if (IS_INDEFI(ap))
+ call strcpy (sens, Memc[fname], SZ_FNAME)
+ else {
+ call sprintf (Memc[fname], SZ_FNAME, "%s.%04d")
+ call pargstr (sens)
+ call pargi (ap)
+ }
+
+ im = immap (Memc[fname], READ_ONLY, 0)
+ smw = smw_openim (im)
+ cal = NULL
+ call shdr_open (im, smw, 1, 1, ap, SHDATA, cal)
+ call shdr_units (cal, "angstroms")
+ AP(cal) = ap
+ Memi[pcal+ncal] = cal
+ ncal = ncal + 1
+ call imunmap (im)
+
+ x = SX(cal)
+ y = SY(cal)
+ n = SN(cal)
+ do j = 1, n {
+ Memr[y] = 10.0 ** (-0.4 * Memr[y])
+ if (fnu) {
+ Memr[y] = Memr[y] * Memr[x] ** 2 / VLIGHT
+ x = x + 1
+ }
+ y = y + 1
+ }
+
+ call asiinit (im, clgwrd ("interp", Memc[fname], SZ_FNAME,II_FUNCTIONS))
+ call asifit (im, Memr[SY(cal)], n)
+ IM(cal) = im
+
+ call mfree (SX(cal), TY_REAL)
+ call mfree (SY(cal), TY_REAL)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_deredden.x b/noao/onedspec/t_deredden.x
new file mode 100644
index 00000000..e68eccbd
--- /dev/null
+++ b/noao/onedspec/t_deredden.x
@@ -0,0 +1,361 @@
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+
+define DEREDTYPES "|A(V)|E(B-V)|c|"
+
+
+# T_DEREDDEN -- Apply interstellar extinction correction to spectra.
+# The extinction function is taken from Cardelli, Clayton, and Mathis,
+# ApJ 345:245. The input parameters are A(V)/E(B-V) and one of A(V),
+# E(B-V), or c.
+
+procedure t_deredden ()
+
+pointer inlist # Input list
+pointer outlist # Output list
+real av # Extinction parameter: A(V), E(B-V), c
+real rv # A(V)/E(B-V)
+
+int i, j, n
+real w, avold, rvold
+pointer sp, input, output, temp, log, aps
+pointer in, out, mw, sh, tmp, inbuf, outbuf
+
+long clktime()
+real clgetr()
+double shdr_lw()
+bool clgetb(), streq(), rng_elementi()
+int clgwrd(), imtgetim(), imtlen(), imaccf(), nscan(), strncmp(), ctor()
+pointer imtopenp(), rng_open(), immap(), smw_openim(), imgl3r(), impl3r()
+errchk immap, smw_openim, shdr_open, deredden, deredden1
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_LINE, TY_CHAR)
+ call salloc (log, SZ_LINE, TY_CHAR)
+
+ call cnvdate (clktime(0), Memc[log], SZ_LINE)
+
+ # Get task parameters.
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ call clgstr ("records", Memc[input], SZ_FNAME)
+ call odr_openp (inlist, Memc[input])
+ call odr_openp (outlist, Memc[input])
+
+ av = clgetr ("value")
+ rv = clgetr ("R")
+
+ # Convert input extinction type to A(V)
+ switch (clgwrd ("type", Memc[input], SZ_FNAME, DEREDTYPES)) {
+ case 1:
+ call sprintf (Memc[log], SZ_LINE, "%s A(V)=%g R=%g")
+ call pargstr (Memc[log])
+ call pargr (av)
+ call pargr (rv)
+ case 2:
+ call sprintf (Memc[log], SZ_LINE, "%s E(B-V)=%g A(V)=%g R=%g")
+ call pargstr (Memc[log])
+ call pargr (av)
+ call pargr (rv * av)
+ call pargr (rv)
+ av = rv * av
+ case 3:
+ call sprintf (Memc[log], SZ_LINE, "%s c=%g A(V)=%g R=%g")
+ call pargstr (Memc[log])
+ call pargr (av)
+ call pargr (rv * av * (0.61 + 0.024 * av))
+ call pargr (rv)
+ av = rv * av * (0.61 + 0.024 * av)
+ }
+
+ call clgstr ("apertures", Memc[temp], SZ_LINE)
+ iferr (aps = rng_open (Memc[temp], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+ if (Memc[temp] != EOS) {
+ call sprintf (Memc[log], SZ_LINE, "%s ap=%s")
+ call pargstr (Memc[log])
+ call pargstr (Memc[temp])
+ }
+
+ # Loop over all input images.
+ in = NULL
+ out = NULL
+ mw = NULL
+ sh = NULL
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtlen (outlist) > 0) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ break
+ } else
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ iferr {
+ # Map the image and check its calibration status.
+ tmp = immap (Memc[input], READ_ONLY, 0); in = tmp
+ tmp = smw_openim (in); mw = tmp
+
+ call shdr_open (in, mw, 1, 1, INDEFI, SHHDR, sh)
+ if (DC(sh) == DCNO) {
+ call sprintf (Memc[temp], SZ_LINE,
+ "[%s] has no dispersion function")
+ call pargstr (Memc[input])
+ call error (1, Memc[temp])
+ }
+ call shdr_units (sh, "angstroms")
+
+ rvold = rv
+ avold = 0.
+ if (imaccf (in, "DEREDDEN") == YES) {
+ if (!clgetb ("override")) {
+ call sprintf (Memc[temp], SZ_LINE,
+ "[%s] has already been corrected")
+ call pargstr (Memc[input])
+ call error (1, Memc[temp])
+ } else {
+ if (clgetb ("uncorrect")) {
+ call imgstr (in, "DEREDDEN", Memc[temp], SZ_LINE)
+ call sscan (Memc[temp])
+ for (i=1;; i=i+1) {
+ call gargwrd (Memc[temp], SZ_LINE)
+ if (nscan() < i)
+ break
+ if (strncmp (Memc[temp], "A(V)=", 5) == 0) {
+ j = 6
+ j = ctor (Memc[temp], j, avold)
+ } else if (strncmp (Memc[temp], "R=", 2) == 0) {
+ j = 3
+ j = ctor (Memc[temp], j, rvold)
+ }
+ }
+ }
+ }
+ }
+
+ # Map the output image.
+ if (streq (Memc[input], Memc[output]))
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ else
+ call strcpy (Memc[output], Memc[temp], SZ_LINE)
+ tmp = immap (Memc[temp], NEW_COPY, in); out = tmp
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ call imastr (out, "DEREDDEN", Memc[log])
+
+ # Initialize for NDSPEC data.
+ if (SMW_FORMAT(mw) == SMW_ND) {
+ if (SX(sh) == NULL)
+ call malloc (SX(sh), SN(sh), TY_REAL)
+ else
+ call realloc (SX(sh), SN(sh), TY_REAL)
+ do i = 1, SN(sh)
+ Memr[SX(sh)+i-1] = shdr_lw (sh, double(i))
+ }
+
+ # Log operation.
+ call printf ("[%s]: %s\n %s\n")
+ call pargstr (Memc[output])
+ call pargstr (IM_TITLE(in))
+ call pargstr (Memc[log])
+
+ # Deredden data.
+ n = IM_LEN(in,1)
+ do j = 1, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ outbuf = impl3r (out, i, j)
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ inbuf = imgl3r (in, i, j)
+ switch (SMW_LAXIS(mw,1)) {
+ case 1:
+ call deredden (Memr[SX(sh)], Memr[inbuf],
+ Memr[outbuf], SN(sh), av, rv, avold, rvold)
+ case 2:
+ w = Memr[SX(sh)+i-1]
+ call deredden1 (w, Memr[inbuf], Memr[outbuf],
+ n, av, rv, avold, rvold)
+ case 3:
+ w = Memr[SX(sh)+j-1]
+ call deredden1 (w, Memr[inbuf], Memr[outbuf],
+ n, av, rv, avold, rvold)
+ }
+ case SMW_ES, SMW_MS:
+ call shdr_open (in, mw, i, j, INDEFI, SHDATA, sh)
+ if (rng_elementi (aps, AP(sh))) {
+ if (j==1) {
+ call printf (" Ap %d: %s\n")
+ call pargi (AP(sh))
+ call pargstr (TITLE(sh))
+ }
+ call deredden (Memr[SX(sh)], Memr[SY(sh)],
+ Memr[outbuf], SN(sh), av, rv, avold, rvold)
+ } else
+ call amovr (Memr[SY(sh)], Memr[outbuf], SN(sh))
+ if (IM_LEN(out,1) > SN(sh))
+ call amovkr (Memr[SY(sh)+SN(sh)-1],
+ Memr[outbuf+SN(sh)], IM_LEN(out,1)-SN(sh))
+ }
+ }
+ }
+ } then {
+ call erract (EA_WARN)
+ if (out != NULL) {
+ call imunmap (out)
+ call imdelete (Memc[temp])
+ }
+ }
+
+ if (mw != NULL) {
+ if (MW(sh) == mw)
+ call smw_close (MW(sh))
+ else
+ call smw_close (mw)
+ }
+ if (out != NULL) {
+ call imunmap (out)
+ call imunmap (in)
+ if (streq (Memc[input], Memc[output])) {
+ call imdelete (Memc[input])
+ call imrename (Memc[temp], Memc[output])
+ }
+ } else if (in != NULL)
+ call imunmap (in)
+ }
+
+ call shdr_close (sh)
+ call rng_close (aps)
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call sfree (sp)
+end
+
+
+# DEREDDEN -- Deredden spectrum
+
+procedure deredden (x, y, z, n, av, rv, avold, rvold)
+
+real x[n] # Wavelengths
+real y[n] # Input fluxes
+real z[n] # Output fluxes
+int n # Number of points
+real av, avold # A(V)
+real rv, rvold # A(V)/E(B-V)
+
+int i
+real cor, ccm()
+errchk ccm
+
+begin
+ if (avold != 0.) {
+ if (rv != rvold) {
+ do i = 1, n {
+ cor = 10. ** (0.4 *
+ (av * ccm (x[i], rv) - avold * ccm (x[i], rvold)))
+ z[i] = y[i] * cor
+ }
+ } else {
+ do i = 1, n {
+ cor = 10. ** (0.4 * (av - avold) * ccm (x[i], rv))
+ z[i] = y[i] * cor
+ }
+ }
+ } else {
+ do i = 1, n {
+ cor = 10. ** (0.4 * av * ccm (x[i], rv))
+ z[i] = y[i] * cor
+ }
+ }
+end
+
+
+# DEREDDEN1 -- Deredden fluxes at a single wavelength
+
+procedure deredden1 (x, y, z, n, av, rv, avold, rvold)
+
+real x # Wavelength
+real y[n] # Input fluxes
+real z[n] # Output fluxes
+int n # Number of points
+real av, avold # A(V)
+real rv, rvold # A(V)/E(B-V)
+
+int i
+real cor, ccm()
+errchk ccm
+
+begin
+ if (avold != 0.) {
+ if (rv != rvold)
+ cor = 10. ** (0.4 *
+ (av * ccm (x, rv) - avold * ccm (x, rvold)))
+ else
+ cor = 10. ** (0.4 * (av - avold) * ccm (x, rv))
+ } else
+ cor = 10. ** (0.4 * av * ccm (x, rv))
+ do i = 1, n
+ z[i] = y[i] * cor
+end
+
+
+# CCM -- Compute CCM Extinction Law
+
+real procedure ccm (wavelength, rv)
+
+real wavelength # Wavelength in Angstroms
+real rv # A(V) / E(B-V)
+
+real x, y, a, b
+
+begin
+ # Convert to inverse microns
+ x = 10000. / wavelength
+
+ # Compute a(x) and b(x)
+ if (x < 0.3) {
+ call error (1, "Wavelength out of range of extinction function")
+
+ } else if (x < 1.1) {
+ y = x ** 1.61
+ a = 0.574 * y
+ b = -0.527 * y
+
+ } else if (x < 3.3) {
+ y = x - 1.82
+ a = 1 + y * (0.17699 + y * (-0.50447 + y * (-0.02427 +
+ y * (0.72085 + y * (0.01979 + y * (-0.77530 + y * 0.32999))))))
+ b = y * (1.41338 + y * (2.28305 + y * (1.07233 + y * (-5.38434 +
+ y * (-0.62251 + y * (5.30260 + y * (-2.09002)))))))
+
+ } else if (x < 5.9) {
+ y = (x - 4.67) ** 2
+ a = 1.752 - 0.316 * x - 0.104 / (y + 0.341)
+ y = (x - 4.62) ** 2
+ b = -3.090 + 1.825 * x + 1.206 / (y + 0.263)
+
+ } else if (x < 8.0) {
+ y = (x - 4.67) ** 2
+ a = 1.752 - 0.316 * x - 0.104 / (y + 0.341)
+ y = (x - 4.62) ** 2
+ b = -3.090 + 1.825 * x + 1.206 / (y + 0.263)
+
+ y = x - 5.9
+ a = a - 0.04473 * y**2 - 0.009779 * y**3
+ b = b + 0.2130 * y**2 + 0.1207 * y**3
+
+ } else if (x <= 10.0) {
+ y = x - 8
+ a = -1.072 - 0.628 * y + 0.137 * y**2 - 0.070 * y**3
+ b = 13.670 + 4.257 * y - 0.420 * y**2 + 0.374 * y**3
+
+ } else {
+ call error (1, "Wavelength out of range of extinction function")
+
+ }
+
+ # Compute A(lambda)/A(V)
+ y = a + b / rv
+ return (y)
+end
diff --git a/noao/onedspec/t_dopcor.x b/noao/onedspec/t_dopcor.x
new file mode 100644
index 00000000..a6f2d9a5
--- /dev/null
+++ b/noao/onedspec/t_dopcor.x
@@ -0,0 +1,293 @@
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+
+define EXTN_LOOKUP 10 # Interp index for de-extinction
+define VLIGHT 2.997925e5 # Speed of light, Km/sec
+
+# T_DOPCOR -- Apply doppler correction to spectra.
+
+procedure t_dopcor ()
+
+int inlist # List of input spectra
+int outlist # List of output spectra
+double z # Doppler redshift or velocity
+bool isvel # Is redshift parameter a velocity?
+bool add # Add to existing correction?
+bool dcor # Apply dispersion correction?
+bool fcor # Apply flux correction?
+real ffac # Flux correction factor (power of 1+z)
+pointer aps # Apertures
+bool verbose # Verbose?
+
+real fcval
+bool wc, fc, aplow[2], aphigh[2]
+int i, j, ap, beam, nw, dtype
+double w1, dw, zold, znew, zvel
+pointer ptr, in, out, mw, sh, inbuf, outbuf
+pointer sp, input, output, vkey, apstr, key, log, coeff
+
+real clgetr()
+double imgetd()
+bool clgetb(), streq(), rng_elementi()
+int imtopenp(), imtgetim(), ctod()
+pointer rng_open(), immap(), smw_openim(), imgl3r(), impl3r()
+errchk immap, imgetd, imgstr,imgl3r, impl3r
+errchk smw_openim, shdr_open, smw_gwattrs
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (vkey, SZ_FNAME, TY_CHAR)
+ call salloc (apstr, SZ_FNAME, TY_CHAR)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call salloc (log, SZ_LINE, TY_CHAR)
+ coeff = NULL
+
+ # Parameters
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ call clgstr ("redshift", Memc[vkey], SZ_FNAME)
+ isvel = clgetb ("isvelocity")
+ add = clgetb ("add")
+ dcor = clgetb ("dispersion")
+ fcor = clgetb ("flux")
+ if (fcor)
+ ffac = clgetr ("factor")
+ else
+ ffac = 0.
+ call clgstr ("apertures", Memc[apstr], SZ_FNAME)
+ verbose = clgetb ("verbose")
+
+ # Parameter checks
+ if (!dcor && !fcor)
+ call error (1, "No correction specified")
+ iferr (aps = rng_open (Memc[apstr], INDEF, INDEF, INDEF))
+ call error (1, "Bad aperture list")
+ if (Memc[apstr] == EOS)
+ call strcpy ("all", Memc[apstr], SZ_LINE)
+
+ # Loop over input images.
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ iferr {
+ in = NULL
+ out = NULL
+ mw = NULL
+ sh = NULL
+
+ # Map and check input image.
+ if (streq (Memc[input], Memc[output]))
+ ptr = immap (Memc[input], READ_WRITE, 0)
+ else
+ ptr = immap (Memc[input], READ_ONLY, 0)
+ in = ptr
+
+ ptr = smw_openim (in); mw = ptr
+ call shdr_open (in, mw, 1, 1, INDEFI, SHHDR, sh)
+ if (DC(sh) == DCNO) {
+ call sprintf (Memc[output], SZ_LINE,
+ "[%s] has no dispersion function")
+ call pargstr (Memc[input])
+ call error (1, Memc[output])
+ }
+
+ # Map output image.
+ if (streq (Memc[input], Memc[output]))
+ ptr = in
+ else
+ ptr = immap (Memc[output], NEW_COPY, in)
+ out = ptr
+
+ # Set velocity and flux correction
+ i = 1
+ if (Memc[vkey] == '-' || Memc[vkey] == '+') {
+ if (ctod (Memc[vkey+1], i, z) == 0) {
+ z = imgetd (in, Memc[vkey+1])
+ if (Memc[vkey] == '-') {
+ if (isvel)
+ z = -z
+ else
+ z = 1 / (1 + z) - 1
+ }
+ } else if (Memc[vkey] == '-')
+ z = -z
+ } else {
+ if (ctod (Memc[vkey], i, z) == 0)
+ z = imgetd (in, Memc[vkey])
+ }
+ zvel = z
+ if (isvel) {
+ z = z / VLIGHT
+ if (abs (z) >= 1.)
+ call error (1, "Impossible velocity")
+ z = sqrt ((1 + z) / (1 - z)) - 1
+ }
+ if (z <= -1.)
+ call error (1, "Impossible redshift")
+
+ if (fcor) {
+ fcval = (1 + z) ** ffac
+ if (in != out && IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ }
+
+ # Go through spectrum and apply corrections.
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ if (dcor) {
+ call smw_gwattrs (mw, 1, 1, ap, beam, dtype,
+ w1, dw, nw, zold, aplow, aphigh, coeff)
+ if (add)
+ znew = (1+z) * (1+zold) - 1
+ else
+ znew = z
+ call smw_swattrs (mw, 1, 1, ap, beam, dtype,
+ w1, dw, nw, znew, aplow, aphigh, Memc[coeff])
+ }
+
+ if (fcor || in != out) {
+ do j = 1, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ inbuf = imgl3r (in, i, j)
+ outbuf = impl3r (out, i, j)
+ if (fcor)
+ call amulkr (Memr[inbuf], fcval,
+ Memr[outbuf], IM_LEN(in,1))
+ else
+ call amovr (Memr[inbuf], Memr[outbuf],
+ IM_LEN(in,1))
+ }
+ }
+ }
+ case SMW_ES, SMW_MS:
+ do i = 1, IM_LEN(in,2) {
+ call shdr_open (in, mw, i, 1, INDEFI, SHHDR, sh)
+ if (rng_elementi (aps, AP(sh))) {
+ wc = dcor
+ fc = fcor
+ } else {
+ wc = false
+ fc = false
+ }
+
+ if (wc) {
+ call smw_gwattrs (mw, i, 1, ap, beam, dtype,
+ w1, dw, nw, zold, aplow, aphigh, coeff)
+ if (add)
+ znew = (1+z) * (1+zold) - 1
+ else
+ znew = z
+ call smw_swattrs (mw, i, 1, ap, beam, dtype, w1,
+ dw, nw, znew, aplow, aphigh, Memc[coeff])
+ if (mw != MW(sh)) {
+ MW(sh) = NULL
+ call shdr_close (sh)
+ }
+ }
+
+ # Correct fluxes
+ # Note that if the operation is in-place we can skip
+ # this step if there is no corrections. Otherwise we
+ # still have to copy the data even if there is no
+ # correction.
+
+ if (fc || in != out) {
+ do j = 1, IM_LEN(in,3) {
+ call shdr_open (in, mw, i, j, INDEFI,
+ SHDATA, sh)
+ outbuf = impl3r (out, i, j)
+ if (fc)
+ call amulkr (Memr[SY(sh)], fcval,
+ Memr[outbuf], SN(sh))
+ else
+ call amovr (Memr[SY(sh)], Memr[outbuf],
+ SN(sh))
+ if (IM_LEN(out,1) > SN(sh))
+ call amovkr (Memr[outbuf+SN(sh)-1],
+ Memr[outbuf+SN(sh)],
+ IM_LEN(out,1)-SN(sh))
+ }
+ }
+ }
+ }
+
+ # Document header.
+ do i = 1, 98 {
+ call sprintf (Memc[key], SZ_FNAME, "DOPCOR%02d")
+ call pargi (i)
+ iferr (call imgstr (out, Memc[key], Memc[log], SZ_LINE))
+ break
+ }
+ if (fcor) {
+ call sprintf (Memc[log], SZ_LINE, "%8g %g %s")
+ if (isvel)
+ call pargd (zvel)
+ else
+ call pargd (z)
+ call pargr (ffac)
+ call pargstr (Memc[apstr])
+ } else {
+ call sprintf (Memc[log], SZ_LINE, "%8g %s")
+ if (isvel)
+ call pargd (zvel)
+ else
+ call pargd (z)
+ call pargstr (Memc[apstr])
+ }
+ call imastr (out, Memc[key], Memc[log])
+
+
+ # Verbose output
+ if (verbose) {
+ call printf ("%s: Doppler correction -")
+ call pargstr (Memc[output])
+ if (SMW_FORMAT(mw) != SMW_ND) {
+ call printf (" apertures=%s,")
+ call pargstr (Memc[apstr])
+ }
+ if (isvel) {
+ call printf (" velocity=%8g,")
+ call pargd (zvel)
+ }
+ call printf (" redshift=%8g, flux factor=%g\n")
+ call pargd (z)
+ call pargr (ffac)
+ if (add && zold != 0.) {
+ call printf (" Correction added: %g + %g = %g\n")
+ call pargd (zold)
+ call pargd (z)
+ call pargd (znew)
+ }
+ call flush (STDOUT)
+ }
+
+ } then {
+ call erract (EA_WARN)
+ if (out != NULL && out != in) {
+ call imunmap (out)
+ call imdelete (Memc[output])
+ }
+ }
+
+ if (mw != NULL && out != NULL)
+ call smw_saveim (mw, out)
+ if (sh != NULL)
+ call shdr_close (sh)
+ if (mw != NULL)
+ call smw_close (mw)
+ if (out != NULL && out != in)
+ call imunmap (out)
+ if (in != NULL)
+ call imunmap (in)
+ }
+
+ call rng_close (aps)
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_fitprofs.x b/noao/onedspec/t_fitprofs.x
new file mode 100644
index 00000000..9aa389bc
--- /dev/null
+++ b/noao/onedspec/t_fitprofs.x
@@ -0,0 +1,1151 @@
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+include <gset.h>
+include <ctotok.h>
+
+
+# Profile types.
+define PTYPES "|gaussian|lorentzian|voigt|"
+define GAUSS 1 # Gaussian profile
+define LORENTZ 2 # Lorentzian profile
+define VOIGT 3 # Voigt profile
+
+# Type of constraints.
+define FITTYPES "|fixed|single|all|"
+define FIXED 1 # Fixed parameter
+define SINGLE 2 # Fit a single value for all lines
+define INDEP 3 # Fit independent values for all lines
+
+# Elements of fit array.
+define BKG 1 # Background
+define POS 2 # Position
+define INT 3 # Intensity
+define GAU 4 # Gaussian FWHM
+define LOR 5 # Lorentzian FWHM
+
+# Output image options.
+define OPTIONS "|difference|fit|"
+define DIFF 1
+define FIT 2
+
+# Monte-Carlo errors
+define MC_N 50 # Monte-Carlo samples (overridden by users)
+define MC_P 10 # Percent done interval (percent)
+define MC_SIG 68 # Sigma sample point (percent)
+
+define NSUB 3 # Number of pixel subsamples
+
+
+# T_FITPROFS -- Fit image profiles.
+
+procedure t_fitprofs()
+
+int inlist # List of input spectra
+pointer aps # Aperture list
+pointer bands # Band list
+
+int ptype # Profile type
+pointer pg, xg, yg, sg, lg # Fitting region and initial components
+real gfwhm # Default gfwhm
+real lfwhm # Default lfwhm
+int fit[5] # Fit flags: background, position, gfwhm, lfwhm
+
+int nerrsample # Number of error samples to use
+real sigma0 # Constant noise
+real invgain # Inverse gain
+
+pointer components # List of components
+bool verbose # Verbose?
+int log # Log file
+int plot # Plot file
+int outlist # List of output spectra
+int option # Output image option
+bool clobber # Clobber existing images?
+bool merge # Merge with existing images?
+
+real x, y, g, l
+bool complement
+int i, p, ng, nalloc
+pointer sp, input, output, ptr
+
+real clgetr()
+bool clgetb()
+int clgeti(), clgwrd(), clscan()
+int imtopenp(), imtgetim(), imtlen()
+int open(), fscan(), nscan(), strdic(), nowhite()
+pointer rng_open()
+errchk open
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+
+ # Get parameters.
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ if (imtlen (outlist) > 1 && imtlen (outlist) != imtlen (inlist))
+ call error (1, "Input and output image lists do not make sense")
+
+ verbose = clgetb ("verbose")
+ call clgstr ("logfile", Memc[output], SZ_FNAME)
+ if (nowhite (Memc[output], Memc[output], SZ_FNAME) == 0)
+ log = NULL
+ else
+ log = open (Memc[output], APPEND, TEXT_FILE)
+ call clgstr ("plotfile", Memc[output], SZ_FNAME)
+ if (nowhite (Memc[output], Memc[output], SZ_FNAME) == 0)
+ plot = NULL
+ else
+ plot = open (Memc[output], APPEND, BINARY_FILE)
+
+ ptype = clgwrd ("profile", Memc[output], SZ_FNAME, PTYPES)
+ gfwhm = clgetr ("gfwhm")
+ lfwhm = clgetr ("lfwhm")
+
+ if (clgetb ("fitbackground"))
+ fit[BKG] = SINGLE
+ else
+ fit[BKG] = FIXED
+ fit[POS] = clgwrd ("fitpositions", Memc[output], SZ_FNAME, FITTYPES)
+ fit[INT] = INDEP
+ fit[GAU] = clgwrd ("fitgfwhm", Memc[output], SZ_FNAME, FITTYPES)
+ fit[LOR] = clgwrd ("fitlfwhm", Memc[output], SZ_FNAME, FITTYPES)
+ option = clgwrd ("option", Memc[output], SZ_FNAME, OPTIONS)
+ clobber = clgetb ("clobber")
+ merge = clgetb ("merge")
+ nerrsample = clgeti ("nerrsample")
+ sigma0 = clgetr ("sigma0")
+ invgain = clgetr ("invgain")
+ if (IS_INDEF(sigma0) || IS_INDEF(invgain) || sigma0<0. || invgain<0.) {
+ sigma0 = INDEF
+ invgain = INDEF
+ }
+
+ # Get the initial positions/peak/ptype/gfwhm/lfwhm.
+ call clgstr ("positions", Memc[input], SZ_FNAME)
+ if (nowhite (Memc[input], Memc[input], SZ_FNAME) == 0) {
+ call sfree (sp)
+ call error (1, "A 'positions' file must be specified")
+ }
+ i = open (Memc[input], READ_ONLY, TEXT_FILE)
+ ng = 0
+ while (fscan (i) != EOF) {
+ call gargr (x)
+ call gargr (y)
+ call gargwrd (Memc[output], SZ_FNAME)
+ call gargr (g)
+ call gargr (l)
+ p = strdic (Memc[output], Memc[output], SZ_FNAME, PTYPES)
+ if (p == 0)
+ p = ptype
+ switch (nscan()) {
+ case 0:
+ next
+ case 1:
+ y = INDEF
+ p = ptype
+ g = gfwhm
+ l = lfwhm
+ case 2:
+ p = ptype
+ g = gfwhm
+ l = lfwhm
+ case 3:
+ g = gfwhm
+ l = lfwhm
+ case 4:
+ switch (p) {
+ case GAUSS:
+ l = lfwhm
+ case LORENTZ:
+ l = g
+ g = gfwhm
+ case VOIGT:
+ l = lfwhm
+ }
+ }
+
+ if (ng == 0) {
+ nalloc = 10
+ call malloc (pg, nalloc, TY_INT)
+ call malloc (xg, nalloc, TY_REAL)
+ call malloc (yg, nalloc, TY_REAL)
+ call malloc (sg, nalloc, TY_REAL)
+ call malloc (lg, nalloc, TY_REAL)
+ } else if (ng == nalloc) {
+ nalloc = nalloc + 10
+ call realloc (pg, nalloc, TY_INT)
+ call realloc (xg, nalloc, TY_REAL)
+ call realloc (yg, nalloc, TY_REAL)
+ call realloc (sg, nalloc, TY_REAL)
+ call realloc (lg, nalloc, TY_REAL)
+ }
+ switch (p) {
+ case GAUSS:
+ Memi[pg+ng] = p
+ Memr[xg+ng] = x
+ Memr[yg+ng] = y
+ Memr[sg+ng] = g
+ Memr[lg+ng] = 0.
+ case LORENTZ:
+ Memi[pg+ng] = p
+ Memr[xg+ng] = x
+ Memr[yg+ng] = y
+ Memr[sg+ng] = 0.
+ Memr[lg+ng] = g
+ case VOIGT:
+ Memi[pg+ng] = p
+ Memr[xg+ng] = x
+ Memr[yg+ng] = y
+ Memr[sg+ng] = g
+ Memr[lg+ng] = l
+ }
+ ng = ng + 1
+ }
+ call close (i)
+ if (ng == 0)
+ call error (1, "No profiles defined")
+
+ call realloc (xg, ng+2, TY_REAL)
+ call realloc (yg, ng+2, TY_REAL)
+ call realloc (sg, ng+2, TY_REAL)
+ call realloc (lg, ng+2, TY_REAL)
+
+ # Get fitting region and add to end of xg array.
+ i = clscan ("region")
+ call gargr (Memr[xg+ng])
+ call gargr (Memr[xg+ng+1])
+ if (i == EOF || nscan() < 1)
+
+ # Decode range strings and set complement if needed.
+ complement = false
+ call clgstr ("lines", Memc[input], SZ_FNAME)
+ ptr = input
+ if (Memc[ptr] == '!') {
+ complement = true
+ ptr = ptr + 1
+ }
+ iferr (aps = rng_open (Memc[ptr], INDEF, INDEF, INDEF))
+ call error (1, "Bad lines/column/aperture list")
+
+ call clgstr ("bands", Memc[input], SZ_FNAME)
+ ptr = input
+ if (Memc[ptr] == '!') {
+ complement = true
+ ptr = ptr + 1
+ }
+ iferr (bands = rng_open (Memc[ptr], INDEF, INDEF, INDEF))
+ call error (1, "Bad band list")
+
+ # Decode components.
+ call clgstr ("components", Memc[input], SZ_FNAME)
+ iferr (components = rng_open (Memc[input], INDEF, INDEF, INDEF))
+ call error (1, "Bad component list")
+
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ Memc[output] = EOS
+
+ call fp_ms (Memc[input], aps, bands, complement, Memi[pg], Memr[xg],
+ Memr[yg], Memr[sg], Memr[lg], ng, fit, nerrsample,
+ sigma0, invgain, components, verbose, log, plot, Memc[output],
+ option, clobber, merge)
+ }
+
+ if (log != NULL)
+ call close (log)
+ if (plot != NULL)
+ call close (plot)
+ call rng_close (aps)
+ call rng_close (bands)
+ call rng_close (components)
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call mfree (pg, TY_INT)
+ call mfree (xg, TY_REAL)
+ call mfree (yg, TY_REAL)
+ call mfree (sg, TY_REAL)
+ call mfree (lg, TY_REAL)
+ call sfree (sp)
+end
+
+
+# FP_MS -- Handle I/O and call fitting procedure.
+
+procedure fp_ms (input, aps, bands, complement, pg, xg, yg, sg, lg, ng, fit,
+ nerrsample, sigma0, invgain, components, verbose, log, plot, output,
+ option, clobber, merge)
+
+char input[ARB] # Input image
+pointer aps # Apertures
+pointer bands # Bands
+bool complement # Complement aperture selection
+
+int pg[ng] # Profile type
+real xg[ng] # Positions
+real yg[ng] # Peaks
+real sg[ng] # Gaussian FWHM
+real lg[ng] # Lorentzian FWHM
+int ng # Number of profiles
+int fit[5] # Fit flags
+
+int nerrsample # Number of error samples
+real sigma0 # Constant noise
+real invgain # Inverse gain
+
+pointer components # Output Component list
+bool verbose # Verbose output?
+int log # Log file descriptor
+int plot # Plot file descriptor
+char output[ARB] # Output image
+int option # Output image option
+bool clobber # Clobber existing image?
+bool merge # Merge with existing image?
+
+real aplow[2], aphigh[2]
+double a, b, w1, wb, dw, z, p1, p2, p3
+bool select
+int i, j, k, l, ap, beam, dtype, nw, ninaps, noutaps, nbands, naps, last
+int mwoutdim, axis[3]
+pointer ptr, in, out, tmp, mwin, mwout, sh, shout
+pointer sp, str, key, temp, ltm1, ltv1, ltm2, ltv2, coeff, outaps
+pointer model
+
+double shdr_lw()
+int imaccess(), imgnfn()
+bool streq(), strne(), rng_elementi(), fp_equald()
+pointer smw_openim(), mw_open()
+pointer immap(), imgl3r(), impl3r(), imofnlu()
+errchk immap, smw_openim, mw_open, shdr_open, imunmap, imgstr, imgl3r, impl3r
+errchk imdelete
+data axis/1,2,3/
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (ltm1, 3*3, TY_DOUBLE)
+ call salloc (ltv1, 3, TY_DOUBLE)
+ call salloc (ltm2, 3*3, TY_DOUBLE)
+ call salloc (ltv2, 3, TY_DOUBLE)
+ coeff = NULL
+
+ # Initialize.
+ in = NULL; out = NULL; tmp = NULL
+ mwin = NULL; mwout = NULL
+ sh = NULL; shout = NULL
+ ninaps = 0; noutaps = 0; nbands = 0
+
+ iferr {
+ # Check for existing output image and abort if clobber is not set.
+ if (output[1] != EOS && imaccess (output, READ_ONLY) == YES) {
+ if (!clobber) {
+ call sprintf (Memc[str], SZ_LINE,
+ "Output spectrum %s already exists")
+ call pargstr (output)
+ call error (1, Memc[str])
+ } else if (merge) {
+ # Merging when the input and output are the same is a nop.
+ if (streq (input, output)) {
+ call sfree (sp)
+ return
+ }
+
+ # Open the output and check the type.
+ ptr = immap (output, READ_ONLY, 0); out = ptr
+ ptr = smw_openim (out); mwout = ptr
+ if (SMW_FORMAT(mwout) == SMW_ND) {
+ call sprintf (Memc[str], SZ_LINE, "%s - Wrong format")
+ call pargstr (output)
+ call error (1, Memc[str])
+ }
+
+ # Determine existing apertures.
+ noutaps = SMW_NSPEC(mwout)
+ nbands = SMW_NBANDS(mwout)
+ call salloc (outaps, noutaps, TY_INT)
+ do i = 1, noutaps {
+ call shdr_open (out, mwout, i, 1, INDEFI, SHHDR, sh)
+ Memi[outaps+i-1] = AP(sh)
+ }
+ }
+ call mktemp ("temp", Memc[temp], SZ_FNAME)
+ } else
+ call strcpy (output, Memc[temp], SZ_FNAME)
+
+ # Open the input and determine the number of final output
+ # apertures in order to set the output dimensions.
+
+ ptr = immap (input, READ_ONLY, 0); in = ptr
+ ptr = smw_openim (in); mwin = ptr
+
+ naps = noutaps
+
+ j = 1
+ if (SMW_FORMAT(mwin) != SMW_ND) {
+ j = 0
+ do i = 1, SMW_NBANDS(mwin) {
+ select = rng_elementi (bands, i)
+ if (!select)
+ next
+ j = j + 1
+ }
+ if (j == 0)
+ call error (1, "No bands selected in image")
+ }
+ nbands = max (j, nbands)
+
+ do i = 1, SMW_NSPEC(mwin) {
+ call shdr_open (in, mwin, i, 1, INDEFI, SHHDR, sh)
+ ap = AP(sh)
+ if (SMW_FORMAT(mwin) == SMW_ND) {
+ call smw_mw (mwin, i, 1, ptr, j, k)
+ select = rng_elementi (aps, j) && rng_elementi (bands, k)
+ } else
+ select = rng_elementi (aps, ap)
+
+ if ((complement && select) || (!complement && !select))
+ next
+ for (j=0; j<noutaps && Memi[outaps+j]!=ap; j=j+1)
+ ;
+ if (j == noutaps)
+ naps = naps + 1
+ ninaps = ninaps + 1
+ }
+ if (ninaps == 0) {
+ call sprintf (Memc[str], SZ_LINE, "No apertures selected in %s")
+ call pargstr (input)
+ call error (1, Memc[str])
+ }
+
+ # Set the output spectrum. For merging with an existing output
+ # copy to a temporary spectrum with size set appropriately.
+ # For a new output setup copy the input header, reset the
+ # physical line mapping, and clear all dispersion parameters.
+
+ if (out != NULL) {
+ ptr = immap (Memc[temp], NEW_COPY, out); tmp = ptr
+ if (IM_PIXTYPE(tmp) != TY_DOUBLE)
+ IM_PIXTYPE(tmp) = TY_REAL
+
+ IM_LEN(tmp,1) = max (SMW_LLEN(mwin,1), IM_LEN(out,1))
+ IM_LEN(tmp,2) = naps
+ IM_LEN(tmp,3) = max (nbands, IM_LEN(out,3))
+ if (nbands > 1)
+ IM_NDIM(tmp) = 3
+ else if (naps > 1)
+ IM_NDIM(tmp) = 2
+ else
+ IM_NDIM(tmp) = 1
+
+ do j = 1, IM_LEN(out,3)
+ do i = 1, IM_LEN(out,2) {
+ ptr = impl3r (tmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(tmp,1))
+ call amovr (Memr[imgl3r(out,i,j)], Memr[ptr], IM_LEN(out,1))
+ }
+ do j = 1, IM_LEN(out,3)
+ do i = IM_LEN(out,2)+1, IM_LEN(tmp,2) {
+ ptr = impl3r (tmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(tmp,1))
+ }
+ do j = IM_LEN(out,3)+1, nbands
+ do i = 1, IM_LEN(tmp,2) {
+ ptr = impl3r (tmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(tmp,1))
+ }
+ call imunmap (out)
+ out = tmp
+ tmp = NULL
+ } else if (Memc[temp] != EOS) {
+ ptr = immap (Memc[temp], NEW_COPY, in); out = ptr
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+
+ # Set header
+ IM_LEN(out,1) = SMW_LLEN(mwin,1)
+ IM_LEN(out,2) = naps
+ IM_LEN(out,3) = nbands
+ if (nbands > 1)
+ IM_NDIM(out) = 3
+ else if (naps > 1)
+ IM_NDIM(out) = 2
+ else
+ IM_NDIM(out) = 1
+ mwoutdim = IM_NDIM(out)
+
+ j = imofnlu (out, "DISPAXIS,APID*,BANDID*")
+ while (imgnfn (j, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (j)
+
+ i = SMW_PDIM(mwin)
+ j = SMW_PAXIS(mwin,1)
+
+ ptr = mw_open (NULL, mwoutdim); mwout = ptr
+ call mw_newsystem (mwout, "equispec", mwoutdim)
+ call mw_swtype (mwout, axis, mwoutdim, "linear", "")
+ if (LABEL(sh) != EOS)
+ call mw_swattrs (mwout, 1, "label", LABEL(sh))
+ if (UNITS(sh) != EOS)
+ call mw_swattrs (mwout, 1, "units", UNITS(sh))
+
+ call mw_gltermd (SMW_MW(mwin,0), Memd[ltm1], Memd[ltv1], i)
+ call mw_gltermd (mwout, Memd[ltm2], Memd[ltv2], mwoutdim)
+ Memd[ltv2] = Memd[ltv1+(j-1)]
+ Memd[ltm2] = Memd[ltm1+(i+1)*(j-1)]
+ call mw_sltermd (mwout, Memd[ltm2], Memd[ltv2], mwoutdim)
+ call smw_open (mwout, NULL, out)
+ }
+
+ if (out != NULL) {
+ # Check dispersion function compatibility
+ # Nonlinear functions can be copied to different physical
+ # coordinate system though the linear dispersion can be
+ # modified.
+
+ call mw_gltermd (SMW_MW(mwout,0), Memd[ltm2], Memd[ltv2], mwoutdim)
+ a = Memd[ltv2]
+ b = Memd[ltm2]
+ if (DC(sh) == DCFUNC) {
+ i = SMW_PDIM(mwin)
+ j = SMW_PAXIS(mwin,1)
+
+ call mw_gltermd (SMW_MW(mwin,0), Memd[ltm1], Memd[ltv1], i)
+ Memd[ltv1] = Memd[ltv1+(j-1)]
+ Memd[ltm1] = Memd[ltm1+(i+1)*(j-1)]
+ if (!fp_equald (a,Memd[ltv1]) || !fp_equald (b,Memd[ltm1])) {
+ call error (1,
+ "Physical basis for nonlinear dispersion functions don't match")
+ }
+ }
+ }
+
+ # Now do the actual fitting
+ call salloc (model, SMW_LLEN(mwin,1), TY_REAL)
+ last = noutaps
+ do i = 1, SMW_NSPEC(mwin) {
+ call shdr_open (in, mwin, i, 1, INDEFI, SHHDR, sh)
+
+ # Check apertures.
+ ap = AP(sh)
+ if (SMW_FORMAT(mwin) == SMW_ND) {
+ call smw_mw (mwin, i, 1, ptr, j, k)
+ select = rng_elementi (aps, j) && rng_elementi (bands, k)
+ } else
+ select = rng_elementi (aps, ap)
+
+ if ((complement && select) || (!complement && !select))
+ next
+
+ call fp_title (sh, Memc[str], verbose, log)
+
+ call shdr_open (in, mwin, i, 1, INDEFI, SHDATA, sh)
+ if (SN(sh) < SMW_LLEN(mwin,1))
+ call aclrr (Memr[model], SMW_LLEN(mwin,1))
+ iferr (call fp_fit (sh, Memr[SX(sh)], Memr[SY(sh)], SN(sh), pg,
+ xg, yg, sg, lg, ng, fit, nerrsample, sigma0, invgain,
+ components, verbose, log, plot, Memc[str], Memr[model])) {
+ call erract (EA_WARN)
+ }
+
+ if (out != NULL) {
+ for (j=0; j<noutaps && Memi[outaps+j]!=ap; j=j+1)
+ ;
+
+ # Set output logical and physical lines
+ if (j < noutaps)
+ l = j + 1
+ else {
+ l = last + 1
+ last = l
+ }
+
+ # Copy and adjust dispersion info
+ call smw_gwattrs (mwin, i, 1, AP(sh), beam,
+ dtype, w1, dw, nw, z, aplow, aphigh, coeff)
+
+ w1 = shdr_lw (sh, 1D0)
+ wb = shdr_lw (sh, double (SN(sh)))
+ p1 = (NP1(sh) - a) / b
+ p2 = (NP2(sh) - a) / b
+ p3 = (IM_LEN(out,1) - a) / b
+ nw = nint (min (max (p1 ,p3), max (p1 ,p2))) + NP1(sh) - 1
+ if (p1 != p2)
+ dw = (wb - w1) / (p2 - p1) * (1 + z)
+ w1 = w1 * (1 + z) - (p1 - 1) * dw
+
+ call smw_swattrs (mwout, l, 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, Memc[coeff])
+
+ # Copy titles
+ call smw_sapid (mwout, l, 1, TITLE(sh))
+ if (Memc[SID(sh,1)] != EOS)
+ call imastr (out, "BANDID1", Memc[SID(sh,1)])
+
+ # Copy the data
+ switch (option) {
+ case DIFF:
+ call asubr (Memr[SY(sh)], Memr[model],
+ Memr[impl3r(out,l,1)+NP1(sh)-1], SN(sh))
+ case FIT:
+ call amovr (Memr[model], Memr[impl3r(out,l,1)+NP1(sh)-1],
+ SN(sh))
+ }
+
+ # Verify copy
+ if (verbose) {
+ call shdr_open (out, mwout, l, 1, INDEFI, SHHDR, shout)
+ call printf ("%s%s(%d) --> %s%s(%s)\n")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargi (AP(sh))
+ call pargstr (IMNAME(shout))
+ call pargstr (IMSEC(shout))
+ call pargi (AP(shout))
+ call flush (STDOUT)
+ }
+ }
+ }
+
+ call smw_close (MW(sh))
+ if (out != NULL) {
+ call smw_saveim (mwout, out)
+ if (shout != NULL)
+ call smw_close (MW(shout))
+ call imunmap (out)
+ if (strne (Memc[temp], output)) {
+ call imdelete (output)
+ call imrename (Memc[temp], output)
+ }
+ }
+ call imunmap (in)
+ } then {
+ if (shout != NULL)
+ call smw_close (MW(shout))
+ else if (mwout != NULL)
+ call smw_close (mwout)
+ if (sh != NULL)
+ call smw_close (MW(sh))
+ else if (mwin != NULL)
+ call smw_close (mwin)
+ if (tmp != NULL)
+ call imunmap (tmp)
+ if (out != NULL)
+ call imunmap (out)
+ if (in != NULL)
+ call imunmap (in)
+ call erract (EA_WARN)
+ }
+
+ call shdr_close (shout)
+ call shdr_close (sh)
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+define SQ2PI 2.5066283
+
+# FP_FIT -- Fit profile functions
+
+procedure fp_fit (sh, x, y, n, ptypes, pos, peaks, gfwhms, lfwhms, ng, fit,
+ nerrsample, sigma0, invgain, components, verbose, log, plot, title, mod)
+
+pointer sh # Spectrum data structure
+real x[n] # Coordinates
+real y[n] # Data
+int n # Number of data points
+
+int ptypes[ARB] # Profile types
+real pos[ARB] # Fitting region and initial positions
+real peaks[ARB] # Peak values
+real gfwhms[ARB] # Background levels and initial gfwhm
+real lfwhms[ARB] # Initial lfwhm
+int ng # Number of gaussian components
+
+int fit[5] # Fit flags
+
+int nerrsample # Number of error samples
+real sigma0 # Constant noise
+real invgain # Inverse gain
+
+pointer components # Component list
+bool verbose # Output to STDOUT?
+int log # Log file descriptor
+int plot # Plot file descriptor
+char title[ARB] # Plot title
+real mod[n] # Model
+
+int i, j, k, i1, i2, nfit, nsub, mc_n, mc_p, mc_sig
+long seed
+real xc, x1, x2, dx, y1, dy, z1, dz, w, z, scale, sscale
+real peak, flux, cont, gfwhm, lfwhm, eqw, chisq
+real flux1, cont1, eqw1, wyc1, slope1, v, u
+bool doerr
+pointer sp, str, xd, yd, sd, xg, yg, sg, lg, pg, yd1, xg1, yg1, sg1, lg1
+pointer ym, conte, xge, yge, sge, lge, fluxe, eqwe
+pointer gp, gopen()
+bool rng_elementi()
+real model(), gasdev(), asumr()
+double shdr_lw(), shdr_wl
+errchk fp_background, dofit, dorefit
+
+begin
+ # Determine fitting region.
+ x1 = pos[ng+1]
+ x2 = pos[ng+2]
+ i1 = nint (shdr_wl (sh, double(x1)))
+ i2 = nint (shdr_wl (sh, double(x2)))
+ i = min (n, max (i1, i2))
+ i1 = max (1, min (i1, i2))
+ i2 = i
+ nfit = i2 - i1 + 1
+ if (nfit < 3) {
+ call aclrr (mod, n)
+ call error (1, "Too few data points in fitting region")
+ }
+ x1 = shdr_lw (sh, double(i1))
+ x2 = shdr_lw (sh, double(i2))
+
+ # Allocate memory.
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (xd, nfit, TY_REAL)
+ call salloc (yd, nfit, TY_REAL)
+ call salloc (sd, nfit, TY_REAL)
+ call salloc (xg, ng, TY_REAL)
+ call salloc (yg, ng, TY_REAL)
+ call salloc (sg, ng, TY_REAL)
+ call salloc (lg, ng, TY_REAL)
+ call salloc (pg, ng, TY_INT)
+
+ # Subtract the continuum and scale the data.
+ call fp_background (sh, x, y, n, x1, x2, y1, dy)
+ scale = 0.
+ doerr = !IS_INDEF(sigma0)
+ do i = i1, i2 {
+ Memr[xd+i-i1] = x[i]
+ Memr[yd+i-i1] = y[i] - (y1 + dy * (x[i]-x1))
+ if (y[i] <= 0.)
+ doerr = false
+ scale = max (scale, abs (Memr[yd+i-i1]))
+ }
+ if (doerr) {
+ do i = i1, i2
+ Memr[sd+i-i1] = sqrt (sigma0 ** 2 + invgain * y[i])
+ sscale = asumr (Memr[sd], nfit) / nfit
+ } else {
+ call amovkr (1., Memr[sd], nfit)
+ sscale = 1.
+ }
+ call adivkr (Memr[yd], scale, Memr[yd], nfit)
+ call adivkr (Memr[sd], sscale, Memr[sd], nfit)
+ y1 = y1 / scale
+ dy = dy / scale
+
+ # Setup initial estimates.
+ do i = 1, ng {
+ Memr[xg+i-1] = pos[i]
+ Memr[sg+i-1] = gfwhms[i]
+ Memr[lg+i-1] = lfwhms[i]
+ Memi[pg+i-1] = ptypes[i]
+ if (IS_INDEF(peaks[i])) {
+ j = max (1, min (nfit, nint (shdr_wl(sh,double(pos[i])))-i1+1))
+ Memr[yg+i-1] = Memr[yd+j-1]
+ } else
+ Memr[yg+i-1] = peaks[i] / scale
+ }
+ z1 = 0.
+ dz = 0.
+ dx = (x[n] - x[1]) / (n - 1)
+ nsub = NSUB
+ call dofit (fit, Memr[xd], Memr[yd], Memr[sd],
+ nfit, dx, nsub, z1, dz, Memr[xg], Memr[yg], Memr[sg],
+ Memr[lg], Memi[pg], ng, chisq)
+
+ # Compute Monte-Carlo errors.
+ mc_n = nerrsample
+ mc_p = nint (mc_n * MC_P / 100.)
+ mc_sig = nint (mc_n * MC_SIG / 100.)
+ if (doerr && mc_sig > 9) {
+ call salloc (yd1, nfit, TY_REAL)
+ call salloc (ym, nfit, TY_REAL)
+ call salloc (xg1, ng, TY_REAL)
+ call salloc (yg1, ng, TY_REAL)
+ call salloc (sg1, ng, TY_REAL)
+ call salloc (lg1, ng, TY_REAL)
+ call salloc (conte, mc_n*ng, TY_REAL)
+ call salloc (xge, mc_n*ng, TY_REAL)
+ call salloc (yge, mc_n*ng, TY_REAL)
+ call salloc (sge, mc_n*ng, TY_REAL)
+ call salloc (lge, mc_n*ng, TY_REAL)
+ call salloc (fluxe, mc_n*ng, TY_REAL)
+ call salloc (eqwe, mc_n*ng, TY_REAL)
+ do i = 1, nfit {
+ w = Memr[xd+i-1]
+ Memr[ym+i-1] = model (w, dx, nsub, Memr[xg], Memr[yg],
+ Memr[sg], Memr[lg], Memi[pg], ng)
+ }
+ seed = 1
+ do i = 0, mc_n-1 {
+ do j = 1, nfit
+ Memr[yd1+j-1] = Memr[ym+j-1] +
+ sscale / scale * Memr[sd+j-1] * gasdev (seed)
+ wyc1 = z1
+ slope1 = dz
+ call amovr (Memr[xg], Memr[xg1], ng)
+ call amovr (Memr[yg], Memr[yg1], ng)
+ call amovr (Memr[sg], Memr[sg1], ng)
+ call amovr (Memr[lg], Memr[lg1], ng)
+ call dorefit (fit, Memr[xd], Memr[yd1], Memr[sd],
+ nfit, dx, nsub, wyc1, slope1,
+ Memr[xg1], Memr[yg1], Memr[sg1],
+ Memr[lg1], Memi[pg], ng, chisq)
+
+ do j = 0, ng-1 {
+ cont = y1 + z1 + (dy + dz) * Memr[xg+j] - dy * x1
+ cont1 = y1 + wyc1 + (dy + slope1) * Memr[xg+j] - dy * x1
+ switch (Memi[pg+j]) {
+ case GAUSS:
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j]
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j]
+ case LORENTZ:
+ flux = 1.570795 * Memr[yg+j] * Memr[lg+j]
+ flux1 = 1.570795 * Memr[yg1+j] * Memr[lg1+j]
+ case VOIGT:
+ call voigt (0., 0.832555*Memr[lg+j]/Memr[sg+j], v, u)
+ flux = 1.064467 * Memr[yg+j] * Memr[sg+j] / v
+ call voigt (0., 0.832555*Memr[lg1+j]/Memr[sg1+j], v, u)
+ flux1 = 1.064467 * Memr[yg1+j] * Memr[sg1+j] / v
+ }
+ if (cont > 0. && cont1 > 0.) {
+ eqw = -flux / cont
+ eqw1 = -flux1 / cont1
+ } else {
+ eqw = 0.
+ eqw1 = 0.
+ }
+ Memr[conte+j*mc_n+i] = abs (cont1 - cont)
+ Memr[xge+j*mc_n+i] = abs (Memr[xg1+j] - Memr[xg+j])
+ Memr[yge+j*mc_n+i] = abs (Memr[yg1+j] - Memr[yg+j])
+ Memr[sge+j*mc_n+i] = abs (Memr[sg1+j] - Memr[sg+j])
+ Memr[lge+j*mc_n+i] = abs (Memr[lg1+j] - Memr[lg+j])
+ Memr[fluxe+j*mc_n+i] = abs (flux1 - flux)
+ Memr[eqwe+j*mc_n+i] = abs (eqw1 - eqw)
+ }
+ }
+ do j = 0, ng-1 {
+ call asrtr (Memr[conte+j*mc_n], Memr[conte+j*mc_n], mc_n)
+ call asrtr (Memr[xge+j*mc_n], Memr[xge+j*mc_n], mc_n)
+ call asrtr (Memr[yge+j*mc_n], Memr[yge+j*mc_n], mc_n)
+ call asrtr (Memr[sge+j*mc_n], Memr[sge+j*mc_n], mc_n)
+ call asrtr (Memr[lge+j*mc_n], Memr[lge+j*mc_n], mc_n)
+ call asrtr (Memr[fluxe+j*mc_n], Memr[fluxe+j*mc_n], mc_n)
+ call asrtr (Memr[eqwe+j*mc_n], Memr[eqwe+j*mc_n], mc_n)
+ }
+ call amulkr (Memr[conte], scale, Memr[conte], mc_n*ng)
+ call amulkr (Memr[yge], scale, Memr[yge], mc_n*ng)
+ call amulkr (Memr[fluxe], scale, Memr[fluxe], mc_n*ng)
+ }
+
+ call amulkr (Memr[yg], scale, Memr[yg], ng)
+ y1 = (y1 + z1 + dz * x1) * scale
+ dy = (dy + dz) * scale
+
+ # Log computed values
+ call sprintf (Memc[str], SZ_LINE,
+ "# Nfit=%d, background=%b, positions=%s, gfwhm=%s, lfwhm=%s\n")
+ call pargi (ng)
+ call pargb (fit[BKG] == SINGLE)
+ if (fit[POS] == FIXED)
+ call pargstr ("fixed")
+ else if (fit[POS] == SINGLE)
+ call pargstr ("single")
+ else
+ call pargstr ("all")
+ if (fit[GAU] == FIXED)
+ call pargstr ("fixed")
+ else if (fit[GAU] == SINGLE)
+ call pargstr ("single")
+ else
+ call pargstr ("all")
+ if (fit[LOR] == FIXED)
+ call pargstr ("fixed")
+ else if (fit[LOR] == SINGLE)
+ call pargstr ("single")
+ else
+ call pargstr ("all")
+ if (log != NULL)
+ call fprintf (log, Memc[str])
+ if (verbose)
+ call printf (Memc[str])
+ call sprintf (Memc[str], SZ_LINE, "# %8s%10s%10s%10s%10s%10s%10s\n")
+ call pargstr ("center")
+ call pargstr ("cont")
+ call pargstr ("flux")
+ call pargstr ("eqw")
+ call pargstr ("core")
+ call pargstr ("gfwhm")
+ call pargstr ("lfwhm")
+ if (log != NULL)
+ call fprintf (log, Memc[str])
+ if (verbose)
+ call printf (Memc[str])
+ do i = 1, ng {
+ if (!rng_elementi (components, i))
+ next
+ xc = Memr[xg+i-1]
+ cont = y1 + dy * (xc - x1)
+ peak = Memr[yg+i-1]
+ gfwhm = Memr[sg+i-1]
+ lfwhm = Memr[lg+i-1]
+ switch (Memi[pg+i-1]) {
+ case 1:
+ flux = 1.064467 * peak * gfwhm
+ case 2:
+ flux = 1.570795 * peak * lfwhm
+ case 3:
+ call voigt (0., 0.832555*lfwhm/gfwhm, v, u)
+ flux = 1.064467 * peak * gfwhm / v
+ }
+
+ if (cont > 0.)
+ eqw = -flux / cont
+ else
+ eqw = INDEF
+
+ call sprintf (Memc[str], SZ_LINE,
+ " %9.7g %9.7g %9.6g %9.4g %9.6g %9.4g %9.4g\n")
+ call pargr (xc)
+ call pargr (cont)
+ call pargr (flux)
+ call pargr (eqw)
+ call pargr (peak)
+ call pargr (gfwhm)
+ call pargr (lfwhm)
+ if (log != NULL)
+ call fprintf (log, Memc[str])
+ if (verbose)
+ call printf (Memc[str])
+ if (doerr && mc_sig > 9) {
+ call sprintf (Memc[str], SZ_LINE,
+ " (%7.7g) (%7.7g) (%7.6g) (%7.4g) (%7.6g) (%7.4g) (%7.4g)\n")
+ call pargr (Memr[xge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[conte+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[fluxe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[eqwe+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[yge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[sge+(i-1)*mc_n+mc_sig])
+ call pargr (Memr[lge+(i-1)*mc_n+mc_sig])
+ if (log != NULL)
+ call fprintf (log, Memc[str])
+ if (verbose)
+ call printf (Memc[str])
+ }
+ }
+
+ # Compute model.
+ call aclrr (mod, n)
+ do i = 0, ng-1 {
+ if (!rng_elementi (components, i+1))
+ next
+ do j = 1, n
+ #mod[j] = model (x[j], dx, nsub, Memr[xg+i], Memr[yg+i],
+ # Memr[sg+i], Memr[lg+i], Memi[pg+i], ng)
+ mod[j] = mod[j] + model (x[j], dx, nsub, Memr[xg+i], Memr[yg+i],
+ Memr[sg+i], Memr[lg+i], Memi[pg+i], 1)
+ }
+
+ # Draw graphs
+ if (plot != NULL) {
+ gp = gopen ("stdvdm", NEW_FILE, plot)
+ call gascale (gp, y[i1], nfit, 2)
+ call asubr (y[i1], mod[i1], Memr[yd], nfit)
+ call grscale (gp, Memr[yd], nfit, 2)
+ do i = i1, i2
+ Memr[yd+i-i1] = mod[i] + y1 + dy * (x[i] - x1)
+ call grscale (gp, Memr[yd], nfit, 2)
+ call gswind (gp, x1, x2, INDEF, INDEF)
+ call glabax (gp, title, "", "")
+ call gseti (gp, G_PLTYPE, 1)
+ call gpline (gp, Memr[xd], y[i1], nfit)
+ call gseti (gp, G_PLTYPE, 2)
+ call gpline (gp, Memr[xd], Memr[yd], nfit)
+ call gline (gp, x1, y1, x2, y1+dy*(x2-x1))
+ call gseti (gp, G_PLTYPE, 3)
+ call asubr (y[i1], mod[i1], Memr[yd], nfit)
+ call gpline (gp, Memr[xd], Memr[yd], nfit)
+ call gseti (gp, G_PLTYPE, 4)
+ do i = 0, ng-1 {
+ if (!rng_elementi (components, i+1))
+ next
+ k = 0
+ do j = i1, i2 {
+ w = x[j]
+ z = model (w, dx, nsub, Memr[xg+i], Memr[yg+i],
+ Memr[sg+i], Memr[lg+i], Memi[pg+i], 1)
+ z = z + y1 + dy * (w - x1)
+ if (k == 0) {
+ call gamove (gp, w, z)
+ k = 1
+ } else
+ call gadraw (gp, w, z)
+ }
+ }
+ call gclose (gp)
+ }
+
+ call sfree (sp)
+end
+
+
+# FP_BACKGROUND -- Iniital background.
+
+procedure fp_background (sh, x, y, n, x1, x2, y1, dy)
+
+pointer sh #I Spectrum pointer
+real x[n] #I Coordinate values
+real y[n] #I Data
+int n #I Number of data points
+real x1, x2 #I Fit endpoints
+real y1, dy #O Background
+
+int i, j, k, m, func
+real xval[2], yval[2]
+double z1, z2, z3
+pointer sp, bkg, str
+
+int ctotok(), ctor(), ctod(), strdic(), nscan()
+real asumr(), amedr()
+double shdr_wl(), shdr_lw()
+
+define err_ 10
+
+begin
+ call smark (sp)
+ call salloc (bkg, SZ_LINE, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ xval[1] = x1
+ xval[2] = x2
+
+ call clgstr ("background", Memc[bkg], SZ_LINE)
+ call sscan (Memc[bkg])
+ do j = 1, 2 {
+ call gargwrd (Memc[bkg], SZ_LINE)
+ if (nscan() != j) {
+ i = max (1, min (n, nint (shdr_wl (sh, double(xval[j])))))
+ xval[j] = shdr_lw (sh, double(i))
+ yval[j] = y[i]
+ next
+ }
+
+ k = 1
+ if (ctor (Memc[bkg], k, yval[j]) == 0) {
+ if (ctotok (Memc[bkg], k, Memc[str], SZ_LINE) != TOK_IDENTIFIER)
+ goto err_
+ func = strdic (Memc[str], Memc[str], SZ_LINE, "|avg|med|")
+ if (func == 0)
+ goto err_
+ k = k + 1
+ if (ctod (Memc[bkg], k, z1) == 0)
+ goto err_
+ k = k + 1
+ if (ctod (Memc[bkg], k, z2) == 0)
+ goto err_
+ k = k + 1
+ if (ctod (Memc[bkg], k, z3) == 0)
+ z3 = 1
+
+ z1 = shdr_wl (sh, z1)
+ z2 = shdr_wl (sh, z2)
+ i = max (1, nint(min(z1,z2)))
+ m = min (n, nint(max(z1,z2))) - i + 1
+ if (m < 1)
+ goto err_
+
+ # This is included to eliminate an optimizer bug on solaris.
+ call sprintf (Memc[bkg], SZ_LINE, "%g %g %g %d %d\n")
+ call pargd (z1)
+ call pargd (z2)
+ call pargd (z3)
+ call pargi (i)
+ call pargi (m)
+
+ switch (func) {
+ case 1:
+ xval[j] = z3 * asumr (x[i], m) / m
+ yval[j] = z3 * asumr (y[i], m) / m
+ case 2:
+ xval[j] = z3 * asumr (x[i], m) / m
+ yval[j] = z3 * amedr (y[i], m)
+ }
+ }
+ }
+
+ if (xval[1] == xval[2]) {
+ dy = 0.
+ y1 = (yval[1] + yval[2]) / 2.
+ } else {
+ dy = (yval[2] - yval[1]) / (xval[2] - xval[1])
+ y1 = yval[1] + dy * (x1 - xval[1])
+ }
+ return
+
+err_
+ call sfree (sp)
+ call error (1, "Syntax error in background specification")
+end
+
+
+include <time.h>
+
+# FP_TITLE -- Set title string and print.
+
+procedure fp_title (sh, str, verbose, log)
+
+pointer sh # Spectrum header structure
+char str[SZ_LINE] # Title string
+bool verbose # Verbose?
+int log # Log file descriptor
+
+pointer sp, time, smw
+long clktime()
+
+begin
+ # Select title format.
+ smw = MW(sh)
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ call sprintf (str, SZ_LINE, "%s%s: %s")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (TITLE(sh))
+ case SMW_ES, SMW_MS:
+ call sprintf (str, SZ_LINE, "%s - Ap %d: %s")
+ call pargstr (IMNAME(sh))
+ call pargi (AP(sh))
+ call pargstr (TITLE(sh))
+ }
+
+ # Set time and log header.
+ call smark (sp)
+ call salloc (time, SZ_DATE, TY_CHAR)
+ call cnvdate (clktime(0), Memc[time], SZ_DATE)
+ if (log != NULL) {
+ call fprintf (log, "# %s %s\n")
+ call pargstr (Memc[time])
+ call pargstr (str)
+ }
+ if (verbose) {
+ call printf ("# %s %s\n")
+ call pargstr (Memc[time])
+ call pargstr (str)
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_lcalib.x b/noao/onedspec/t_lcalib.x
new file mode 100644
index 00000000..92f9a531
--- /dev/null
+++ b/noao/onedspec/t_lcalib.x
@@ -0,0 +1,98 @@
+include <ctype.h>
+
+define VLIGHT 2.997925e18 # Speed of light in Angstroms/sec
+
+# Options
+define OPTION "|ext|mags|fnu|flam|bands|stars|"
+define EXT 1 # Extinction
+define MAGS 2 # Standard star magnitudes
+define FNU 3 # Standard star fluxes
+define FLAM 4 # Standard star fluxes
+define BANDS 5 # Standard star band passes
+define STARS 6 # Standard stars
+
+# T_LCALIB -- List information in calibration file:
+# 1) Extinction vs wavelength
+# 2) Magnitude vs wavelength
+# 3) F-nu vs wavelength
+# 4) F-lambda vs wavelength
+# 5) Bandpass vs wavelength
+# 6) Standard stars
+
+procedure t_lcalib ()
+
+int i, nwaves, fd
+real fnu, flam, fnuzero
+pointer sp, str, file, waves, bands, mags, extns
+
+int getline(), open(), clgwrd()
+real clgetr()
+errchk ext_load, getcalib
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (file, SZ_LINE, TY_CHAR)
+
+ #Switch on the option.
+ switch (clgwrd ("option", Memc[str], SZ_LINE, OPTION)) {
+ case EXT:
+ call ext_load (waves, extns, nwaves)
+ do i = 1, nwaves {
+ call printf ("%6f %12.5g\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (Memr[extns+i-1])
+ }
+ call ext_free (waves, extns)
+ case MAGS:
+ call getcalib (waves, bands, mags, nwaves)
+ do i = 1, nwaves {
+ call printf ("%6f %12.5g\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (Memr[mags+i-1])
+ }
+ call freecalib (waves, bands, mags)
+ case FNU:
+ fnuzero = clgetr ("fnuzero")
+ call getcalib (waves, bands, mags, nwaves)
+ do i = 1, nwaves {
+ fnu = fnuzero * 10. ** (-0.4 * Memr[mags+i-1])
+ call printf ("%6f %12.5g\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (fnu)
+ }
+ call freecalib (waves, bands, mags)
+ case FLAM:
+ fnuzero = clgetr ("fnuzero")
+ call getcalib (waves, bands, mags, nwaves)
+ do i = 1, nwaves {
+ fnu = fnuzero * 10. ** (-0.4 * Memr[mags+i-1])
+ flam = fnu * VLIGHT / Memr[waves+i-1] ** 2
+ call printf ("%6f %12.5g\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (flam)
+ }
+ call freecalib (waves, bands, mags)
+ case BANDS:
+ call getcalib (waves, bands, mags, nwaves)
+ do i = 1, nwaves {
+ call printf ("%6f %12.5g\n")
+ call pargr (Memr[waves+i-1])
+ call pargr (Memr[bands+i-1])
+ }
+ call freecalib (waves, bands, mags)
+ case STARS:
+ call clgstr ("caldir", Memc[str], SZ_LINE)
+ call sprintf (Memc[file], SZ_LINE, "%sstandards.men")
+ call pargstr (Memc[str])
+ fd = open (Memc[file], READ_ONLY, TEXT_FILE)
+ while (getline (fd, Memc[file]) != EOF)
+ call putline (STDERR, Memc[file])
+ call close (fd)
+ default:
+ call eprintf ("Unknown option: %s\n")
+ call pargstr (Memc[str])
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_mkspec.x b/noao/onedspec/t_mkspec.x
new file mode 100644
index 00000000..5264dbe2
--- /dev/null
+++ b/noao/onedspec/t_mkspec.x
@@ -0,0 +1,120 @@
+include <imhdr.h>
+
+# T_MKSPEC -- Make a test artificial spectrum - May be 2 dimensional
+# Options for the form of the spectrum currently include
+# 1 - Flat spectrum
+# 2 - Ramp
+# 3 - Black body - f-lambda
+
+procedure t_mkspec()
+
+char spec[SZ_FNAME], sname[SZ_IMTITLE]
+int ncols, nlines, func_type, i
+real const1, const2, dconst, const
+real wstart, wend, dw, temp, x, w, fmax
+real c1, c2
+pointer im, buf, sp, row
+
+pointer immap(), impl1r(), impl2r()
+int clgeti()
+real clgetr()
+
+begin
+ # Initialize Black body constants
+ c1 = 3.7415e-5
+ c2 = 1.4388
+
+ # Get spectrum file name
+ call clgstr ("image_name", spec, SZ_FNAME)
+
+ # And title
+ call clgstr ("image_title", sname, SZ_IMTITLE)
+
+ # Length
+ ncols = clgeti ("ncols")
+
+ # Height
+ nlines = clgeti ("nlines")
+
+ # Pixel type
+
+ # Open image
+ im = immap (spec, NEW_IMAGE, 0)
+
+ # Load parameters
+ IM_LEN(im,1) = ncols
+ IM_LEN(im,2) = nlines
+
+ # 1 or 2 Dimensional image
+ if (nlines > 1)
+ IM_NDIM(im) = 2
+ else
+ IM_NDIM(im) = 1
+
+ IM_PIXTYPE(im) = TY_REAL
+ call strcpy (sname, IM_TITLE(im), SZ_IMTITLE)
+
+
+ func_type = clgeti ("function")
+
+ # Get additional parameters for functin types
+ switch (func_type) {
+
+ # Flat spectrum
+ case 1:
+ const = clgetr ("constant")
+
+ # Ramp spectrum
+ case 2:
+ const1 = clgetr ("start_level")
+ const2 = clgetr ("end_level")
+ dconst = (const2 - const1) / (ncols - 1)
+
+ # Black body
+ case 3:
+ wstart = clgetr ("start_wave") # Start wave Angstroms
+ wend = clgetr ("end_wave") # End wave
+ temp = clgetr ("temperature") # BB temp deg.K
+ dw = (wend - wstart) / (ncols - 1)
+ w = wstart * 1.0e-8 # Convert to cm.
+ fmax = 1.2865e-4 * temp**5 # Peak f-lambda
+
+ default:
+ call error (1, "Unknown Function type")
+ }
+
+ # Allocate space for a row since each row will be duplicated
+ # NLINES times
+ call smark (sp)
+ call salloc (row, ncols, TY_REAL)
+
+ # Fill a row
+ do i = 1, ncols {
+ switch (func_type) {
+ case 1:
+ Memr[row+i-1] = const
+ case 2:
+ Memr[row+i-1] = const1 + (i-1) * dconst
+ case 3:
+ x = exp (c2 /w /temp)
+ Memr[row+i-1] = (c1 / w**5 / (x-1.0)) / fmax
+ w = w + dw * 1.0e-8
+ }
+ }
+
+ # Write all lines out
+ do i = 1, nlines {
+
+ # Access either 1 or 2 dimensional line
+ if (nlines > 1)
+ buf = impl2r (im,i)
+ else
+ buf = impl1r (im)
+
+ # Copy saved row to output image
+ call amovr (Memr[row], Memr[buf], ncols)
+ }
+
+ call sfree (sp)
+ call imunmap (im)
+end
diff --git a/noao/onedspec/t_names.x b/noao/onedspec/t_names.x
new file mode 100644
index 00000000..b6f81fe9
--- /dev/null
+++ b/noao/onedspec/t_names.x
@@ -0,0 +1,45 @@
+# T_NAMES -- Expand record extension format into a list of images.
+
+procedure t_names ()
+
+pointer list # Input record list
+pointer append # String to append to name
+bool check # Check existence of image?
+
+int imtgetim()
+bool clgetb()
+pointer sp, image, im, imtopenp(), immap()
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (append, SZ_LINE, TY_CHAR)
+
+ # Get parameters.
+ list = imtopenp ("input")
+ call clgstr ("records", Memc[append], SZ_LINE)
+ call odr_openp (list, Memc[append])
+ call clgstr ("append", Memc[append], SZ_LINE)
+ check = clgetb ("check")
+
+ # Loop over all input images - print name on STDOUT
+ while (imtgetim (list, Memc[image], SZ_FNAME) != EOF) {
+ # Open image if check for existence required
+ if (check) {
+ ifnoerr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call printf ("%s%s\n")
+ call pargstr (Memc[image])
+ call pargstr (Memc[append])
+ call imunmap (im)
+ }
+ } else {
+ call printf ("%s%s\n")
+ call pargstr (Memc[image])
+ call pargstr (Memc[append])
+ }
+ call flush (STDOUT)
+ }
+
+ call imtclose (list)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_rstext.x b/noao/onedspec/t_rstext.x
new file mode 100644
index 00000000..16efa1dc
--- /dev/null
+++ b/noao/onedspec/t_rstext.x
@@ -0,0 +1,91 @@
+# T_RSTEXT -- This procedure replaces the following CL script code to
+# make the RSPECTEXT script efficient. It also determines whether there
+# is a header rather than requiring the user to specify it.
+#
+# # Separate the header and flux values for RTEXTIMAGE and the
+# # wavelengths for later use.
+#
+# fd = specin
+# if (header) {
+# while (fscan (fd, line) != EOF) {
+# print (line, >> temp1)
+# if (substr (line,1,3) == "END")
+# break
+# }
+# }
+# dim = 0
+# while (fscan (fd, x, y) != EOF) {
+# if (nscan() == 2) {
+# print (y, >> temp1)
+# print (x, >> temp2)
+# dim = dim + 1
+# }
+# }
+
+procedure t_rstext ()
+
+pointer input # Input RSPECTEXT text file
+pointer output1 # Output text file for RTEXTIMAGE
+pointer output2 # Output text file for DISPCOR
+
+int in, out1, out2, dim
+bool header
+real x, y
+pointer sp, line
+int open(), getline(), strncmp(), fscan(), nscan()
+errchk open
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output1, SZ_FNAME, TY_CHAR)
+ call salloc (output2, SZ_FNAME, TY_CHAR)
+ call salloc (line, SZ_LINE, TY_CHAR)
+
+ call clgstr ("input", Memc[input], SZ_FNAME)
+ call clgstr ("output1", Memc[output1], SZ_FNAME)
+ call clgstr ("output2", Memc[output2], SZ_FNAME)
+
+ in = open (Memc[input], READ_ONLY, TEXT_FILE)
+ out1 = open (Memc[output1], NEW_FILE, TEXT_FILE)
+ out2 = open (Memc[output2], NEW_FILE, TEXT_FILE)
+
+ header = false
+ while (getline (in, Memc[line]) != EOF) {
+ if (strncmp (Memc[line], "END", 3) == 0) {
+ header = true
+ break
+ }
+ }
+ call seek (in, BOF)
+
+ if (header) {
+ while (getline (in, Memc[line]) != EOF) {
+ call putline (out1, Memc[line])
+ if (strncmp (Memc[line], "END", 3) == 0)
+ break
+ }
+ }
+
+ dim = 0
+ while (fscan (in) != EOF) {
+ call gargr (x)
+ call gargr (y)
+ if (nscan() != 2)
+ next
+ call fprintf (out1, "%g\n")
+ call pargr (y)
+ call fprintf (out2, "%g\n")
+ call pargr (x)
+ dim = dim + 1
+ }
+
+ call printf ("%b %d\n")
+ call pargb (header)
+ call pargi (dim)
+
+ call close (out2)
+ call close (out1)
+ call close (in)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_sapertures.x b/noao/onedspec/t_sapertures.x
new file mode 100644
index 00000000..4aa0c16f
--- /dev/null
+++ b/noao/onedspec/t_sapertures.x
@@ -0,0 +1,428 @@
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+
+define LEN_SAP 52 # Length of structure
+define LEN_SAPTITLE 79 # Length of title
+
+define AP Memi[$1] # Aperture number
+define BEAM Memi[$1+1] # Beam number
+define DTYPE Memi[$1+2] # Dispersion type
+define W1 Memd[P2D($1+4)] # Starting wavelength
+define DW Memd[P2D($1+6)] # Wavelength per pixel
+define Z Memd[P2D($1+8)] # Doppler factor
+define APLOW Memr[P2R($1+10)] # Low aperture
+define APHIGH Memr[P2R($1+11)] # High aperture
+define TITLE Memc[P2C($1+12)] # Title
+
+
+# T_SAPERTURES -- Set aperture beam numbers and titles.
+
+procedure t_sapertures()
+
+int list # Input list
+bool wcsreset # Reset WCS?
+bool verbose # Verbose?
+pointer saps # Pointer to array of aperture structures
+
+int imtopenp(), imtgetim()
+bool clgetb()
+pointer sp, input, ranges, tmp, im, mw, rng_open(), immap(), smw_openim()
+errchk sap_gids, immap, smw_openim
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+
+ list = imtopenp ("input")
+ wcsreset = clgetb ("wcsreset")
+ verbose = clgetb ("verbose")
+ call clgstr ("apertures", Memc[input], SZ_FNAME)
+ iferr (ranges = rng_open (Memc[input], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+
+ call sap_gids (saps, wcsreset, verbose)
+
+ while (imtgetim (list, Memc[input], SZ_FNAME) != EOF) {
+ im = NULL
+ mw = NULL
+ iferr {
+ tmp = immap (Memc[input], READ_WRITE, 0); im = tmp
+ tmp = smw_openim (im); mw = tmp
+ if (SMW_FORMAT(mw) != SMW_ES && SMW_FORMAT(mw) != SMW_MS)
+ call error (1, "Wrong spectrum format")
+ call sap_ms (im, mw, Memc[input], ranges, saps, verbose)
+ } then
+ call erract (EA_WARN)
+
+ if (mw != NULL) {
+ call smw_saveim (mw, im)
+ call smw_close (mw)
+ }
+ if (im != NULL)
+ call imunmap (im)
+ }
+
+ call rng_close (ranges)
+ call imtclose (list)
+ call sap_fids (saps)
+ call sfree (sp)
+end
+
+
+# SAP_MS -- Set aperture information
+
+procedure sap_ms (im, mw, input, ranges, saps, verbose)
+
+pointer im # IMIO pointer
+pointer mw # SMW pointer
+char input[ARB] # Image name
+pointer ranges # Aperture range list
+pointer saps # Pointer to array of aperture structures
+bool verbose # Verbose?
+
+int i, naps, ap, beam, dtype, nw, obeam, odtype
+double w1, dw, z, ow1, odw, oz
+real aplow[2], aphigh[2], oaplow[2], oaphigh[2]
+bool newtitle, streq(), rng_elementi()
+pointer sp, title, coeff, sap
+
+begin
+ call smark (sp)
+ call salloc (title, SZ_LINE, TY_CHAR)
+ coeff = NULL
+
+ # Go through each spectrum and change the selected apertures.
+ naps = -1
+ do i = 1, SMW_NSPEC(mw) {
+ # Get aperture info
+ iferr (call smw_gwattrs (mw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff))
+ break
+
+ # Check if aperture is to be changed
+ if (!rng_elementi (ranges, ap))
+ next
+
+ # Check for aperture info
+ for (sap = saps; Memi[sap] != NULL; sap = sap + 1)
+ if (ap == AP(Memi[sap]))
+ break
+ if (Memi[sap] == NULL) {
+ for (sap = saps; Memi[sap] != NULL; sap = sap + 1)
+ if (IS_INDEFI (AP(Memi[sap])))
+ break
+ }
+ if (Memi[sap] == NULL)
+ next
+
+ # Get aperture title
+ call smw_gapid (mw, i, 1, Memc[title], SZ_LINE)
+
+ # Set new aperture values
+ sap = Memi[sap]
+ obeam = BEAM(sap)
+ odtype = DTYPE(sap)
+ ow1 = W1(sap)
+ odw = DW(sap)
+ oz = Z(sap)
+ oaplow[1] = APLOW(sap)
+ oaphigh[1] = APHIGH(sap)
+ oaplow[2] = INDEF
+ oaphigh[2] = INDEF
+
+ if (IS_INDEFI (obeam))
+ obeam = beam
+ if (IS_INDEFI (odtype))
+ odtype = dtype
+ else
+ odtype = max (-1, min (1, odtype))
+ if (IS_INDEFD (ow1))
+ ow1 = w1
+ if (IS_INDEFD (odw))
+ odw = dw
+ if (IS_INDEFD (oz))
+ oz = z
+ if (IS_INDEF (oaplow[1]))
+ oaplow[1] = aplow[1]
+ if (IS_INDEF (oaphigh[1]))
+ oaphigh[1] = aphigh[1]
+ if (streq (TITLE(sap), "INDEF") || TITLE(sap) == EOS)
+ newtitle = false
+ else
+ newtitle = !streq (TITLE(sap), Memc[title])
+
+ if (dtype == 2 && odtype != 2)
+ Memc[coeff] = EOS
+
+ # Make change if needed
+ if (obeam!=beam || odtype!=dtype || ow1!=w1 || odw !=dw || oz!=z ||
+ oaplow[1]!=aplow[1] || oaphigh[1]!=aphigh[1] || newtitle) {
+ call smw_swattrs (mw, i, 1, ap, obeam, odtype, ow1, odw, nw,
+ oz, oaplow, oaphigh, Memc[coeff])
+ if (newtitle)
+ call smw_sapid (mw, i, 1, TITLE(sap))
+ naps = naps + 1
+
+ # Make record
+ if (verbose) {
+ if (naps == 0) {
+ call printf ("%s:\n")
+ call pargstr (input)
+ naps = naps + 1
+ }
+ call printf (" Aperture %d:\n")
+ call pargi (ap)
+ if (obeam != beam) {
+ call printf (" beam %d --> %d\n")
+ call pargi (beam)
+ call pargi (obeam)
+ }
+ if (odtype != dtype) {
+ call printf (" dtype %d --> %d\n")
+ call pargi (dtype)
+ call pargi (odtype)
+ }
+ if (ow1 != w1) {
+ call printf (" w1 %g --> %g\n")
+ call pargd (w1)
+ call pargd (ow1)
+ }
+ if (odw != dw) {
+ call printf (" dw %g --> %g\n")
+ call pargd (dw)
+ call pargd (odw)
+ }
+ if (oz != z) {
+ call printf (" z %g --> %g\n")
+ call pargd (z)
+ call pargd (oz)
+ }
+ if (oaplow[1] != aplow[1]) {
+ call printf (" aplow %g --> %g\n")
+ call pargr (aplow[1])
+ call pargr (oaplow[1])
+ }
+ if (oaphigh[1] != aphigh[1]) {
+ call printf (" aphigh %g --> %g\n")
+ call pargr (aphigh[1])
+ call pargr (oaphigh[1])
+ }
+ if (newtitle) {
+ call printf (" apid %s --> %s\n")
+ call pargstr (Memc[title])
+ call pargstr (TITLE(sap))
+ }
+ }
+ }
+ }
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# SA_GIDS -- Get user aperture ID's.
+
+procedure sap_gids (saps, wcsreset, verbose)
+
+pointer saps # Pointer to array of aperture structures
+bool wcsreset # Reset WCS?
+bool verbose # Verbose (negative beam warning)?
+pointer sap
+
+int naps, ap, beam, fd, nalloc
+double ra, dec
+pointer sp, str, key, im, list
+
+real clgetr()
+double clgetd()
+int nowhite(), open(), fscan(), nscan(), clgeti()
+pointer immap(), imofnlu(), imgnfn()
+errchk open
+
+begin
+
+ # If resetting ignore the APIDTABLE and the task parameters.
+ if (wcsreset) {
+ call malloc (saps, 2, TY_POINTER)
+ call malloc (Memi[saps], LEN_SAP, TY_STRUCT)
+ Memi[saps+1] = NULL
+
+ sap = Memi[saps]
+ AP(sap) = INDEFI
+ BEAM(sap) = INDEFI
+ DTYPE(sap) = -1
+ W1(sap) = 1.
+ DW(sap) = 1.
+ Z(sap) = 0.
+ APLOW(sap) = INDEF
+ APHIGH(sap) = INDEF
+ TITLE(sap) = EOS
+ return
+ }
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (key, SZ_FNAME, TY_CHAR)
+ call clgstr ("apidtable", Memc[str], SZ_LINE)
+
+ # Set parameters from an APIDTABLE if given.
+ naps = 0
+ if (nowhite (Memc[str], Memc[str], SZ_LINE) > 0) {
+ iferr {
+ # Read aperture information from an image.
+ ifnoerr (im = immap (Memc[str], READ_ONLY, 0)) {
+ list = imofnlu (im, "SLFIB[0-9]*")
+ while (imgnfn (list, Memc[key], SZ_FNAME) != EOF) {
+ call imgstr (im, Memc[key], Memc[str], SZ_LINE)
+ call sscan (Memc[str])
+ call gargi (ap)
+ call gargi (beam)
+ if (nscan() < 2)
+ next
+ if (!IS_INDEFI(beam) && beam < 0 && verbose) {
+ call eprintf (
+ "Negative beam number for aperture %d ignored.\n")
+ call pargi (ap)
+ beam = INDEFI
+ }
+ if (naps == 0) {
+ nalloc = 50
+ call malloc (saps, nalloc, TY_POINTER)
+ } else if (naps == nalloc) {
+ nalloc = nalloc + 50
+ call realloc (saps, nalloc, TY_POINTER)
+ }
+ call malloc (Memi[saps+naps], LEN_SAP, TY_STRUCT)
+
+ sap = Memi[saps+naps]
+ AP(sap) = ap
+ BEAM(sap) = beam
+ call gargd (ra)
+ call gargd (dec)
+ if (nscan() != 4) {
+ call reset_scan ()
+ call gargi (ap)
+ call gargi (beam)
+ call gargstr (TITLE(sap), LEN_SAPTITLE)
+ } else {
+ Memc[str] = EOS
+ call gargstr (Memc[str], SZ_LINE)
+ call xt_stripwhite (Memc[str])
+ if (Memc[str] == EOS) {
+ call sprintf (TITLE(sap), LEN_SAPTITLE,
+ "(%.2h %.2h)")
+ call pargd (ra)
+ call pargd (dec)
+ } else {
+ call sprintf (TITLE(sap), LEN_SAPTITLE,
+ "%s (%.2h %.2h)")
+ call pargstr (Memc[str])
+ call pargd (ra)
+ call pargd (dec)
+ }
+ }
+ DTYPE(sap) = INDEFI
+ W1(sap) = INDEFD
+ DW(sap) = INDEFD
+ Z(sap) = INDEFD
+ APLOW(sap) = INDEF
+ APHIGH(sap) = INDEF
+ call xt_stripwhite (TITLE(sap))
+ naps = naps + 1
+ }
+ call imcfnl (list)
+ call imunmap (im)
+
+ # Read aperture information from a file.
+ } else {
+ fd = open (Memc[str], READ_ONLY, TEXT_FILE)
+ while (fscan (fd) != EOF) {
+ call gargi (ap)
+ call gargi (beam)
+ if (nscan() < 2)
+ next
+ if (!IS_INDEFI(beam) && beam < 0 && verbose) {
+ call eprintf (
+ "Negative beam number for aperture %d ignored.\n")
+ call pargi (ap)
+ beam = INDEFI
+ }
+ if (naps == 0) {
+ nalloc = 50
+ call malloc (saps, nalloc, TY_POINTER)
+ } else if (naps == nalloc) {
+ nalloc = nalloc + 50
+ call realloc (saps, nalloc, TY_POINTER)
+ }
+ call malloc (Memi[saps+naps], LEN_SAP, TY_STRUCT)
+
+ sap = Memi[saps+naps]
+ AP(sap) = ap
+ BEAM(sap) = beam
+ call gargi (DTYPE(sap))
+ call gargd (W1(sap))
+ call gargd (DW(sap))
+ call gargd (Z(sap))
+ call gargr (APLOW(sap))
+ call gargr (APHIGH(sap))
+ call gargstr (TITLE(sap), LEN_SAPTITLE)
+ if (nscan() < 9) {
+ call reset_scan()
+ call gargi (AP(sap))
+ call gargi (BEAM(sap))
+ if (!IS_INDEFI(BEAM(sap)) && BEAM(sap) < 0)
+ BEAM(sap) = INDEFI
+ call gargstr (TITLE(sap), LEN_SAPTITLE)
+ DTYPE(sap) = INDEFI
+ W1(sap) = INDEFD
+ DW(sap) = INDEFD
+ Z(sap) = INDEFD
+ APLOW(sap) = INDEF
+ APHIGH(sap) = INDEF
+ }
+ call xt_stripwhite (TITLE(sap))
+ naps = naps + 1
+ }
+ call close (fd)
+ }
+ } then
+ call erract (EA_WARN)
+ }
+
+ # Set remaining default parameters and the list terminator.
+ call realloc (saps, naps+2, TY_INT)
+ call malloc (Memi[saps+naps], LEN_SAP, TY_STRUCT)
+ Memi[saps+naps+1] = NULL
+
+ sap = Memi[saps+naps]
+ AP(sap) = INDEFI
+ BEAM(sap) = clgeti ("beam")
+ if (!IS_INDEFI(BEAM(sap)) && BEAM(sap) < 0 && verbose) {
+ call eprintf (
+ "Negative default beam number ignored.\n")
+ BEAM(sap) = INDEFI
+ }
+ DTYPE(sap) = clgeti ("dtype")
+ W1(sap) = clgetd ("w1")
+ DW(sap) = clgetd ("dw")
+ Z(sap) = clgetd ("z")
+ APLOW(sap) = clgetr ("aplow")
+ APHIGH(sap) = clgetr ("aphigh")
+ call clgstr ("title", TITLE(sap), LEN_SAPTITLE)
+
+ call sfree (sp)
+end
+
+
+procedure sap_fids (saps)
+
+pointer saps # Pointer to array of aperture structures
+pointer sap
+
+begin
+ for (sap=saps; Memi[sap] != NULL; sap = sap + 1)
+ call mfree (Memi[sap], TY_STRUCT)
+ call mfree (saps, TY_POINTER)
+end
diff --git a/noao/onedspec/t_sarith.x b/noao/onedspec/t_sarith.x
new file mode 100644
index 00000000..460ebd7a
--- /dev/null
+++ b/noao/onedspec/t_sarith.x
@@ -0,0 +1,1423 @@
+include <error.h>
+include <imhdr.h>
+include <mach.h>
+include <smw.h>
+
+# Output formats.
+define FORMATS "|multispec|onedspec|"
+
+# Operations.
+define OPS "|abs|copy|dex|exp|flam|fnu|inv|ln|log|lum|mag|sqrt\
+ |replace|+|-|*|/|^|"
+define ABS 1
+define COPY 2
+define DEX 3
+define EXP 4
+define FLAM 5
+define FNU 6
+define INV 7
+define LN 8
+define LOG 9
+define LUM 10
+define MAG 11
+define SQRT 12
+
+define REP 13
+define ADD 14
+define SUB 15
+define MUL 16
+define DIV 17
+define POW 18
+
+
+# T_SARITH -- Arithmetic operations (including copying) on spectra.
+
+procedure t_sarith()
+
+int inlist1 # List of input spectra
+int op # Operation
+int inlist2 # List of input spectra or operands
+int outlist # List of output spectra
+double w1 # Starting wavelength
+double w2 # Ending wavelength
+bool rebin # Rebin wavelength region?
+int format # Output format
+pointer aps # Aperture/col/line list
+pointer bands # Band list
+pointer beams # Beam list
+bool complement # Complement aperture/beam selection
+int apmod # Aperture modulus (used with subapertures)
+int offset # Add this offset to apertures on output
+bool reverse # Reverse order of operands
+bool ignoreaps # Ignore apertures?
+bool clobber # Clobber existing images?
+bool merge # Merge with existing images?
+bool renumber # Renumber apertures?
+bool verbose # Verbose?
+real errval # Error value
+
+int list1, list2
+pointer sp, input1, opstr, input2, output, ptr
+
+double clgetd()
+int imtopenp(), imtopen(), imtlen(), imtgetim()
+int clgwrd(), clgeti()
+bool clgetb()
+pointer rng_open()
+common /sarith/ errval
+
+begin
+ call smark (sp)
+ call salloc (input1, SZ_LINE, TY_CHAR)
+ call salloc (opstr, SZ_LINE, TY_CHAR)
+ call salloc (input2, SZ_LINE, TY_CHAR)
+ call salloc (output, SZ_LINE, TY_CHAR)
+
+ # Get parameters.
+ inlist1 = imtopenp ("input1")
+ op = clgwrd ("op", Memc[opstr], SZ_LINE, OPS)
+ if (op > SQRT)
+ inlist2 = imtopenp ("input2")
+ else
+ inlist2 = imtopen ("")
+ outlist = imtopenp ("output")
+
+ w1 = clgetd ("w1")
+ w2 = clgetd ("w2")
+ if (IS_INDEFD(w1) && IS_INDEFD(w2))
+ rebin = false
+ else
+ rebin = clgetb ("rebin")
+
+ format = clgwrd ("format", Memc[input1], SZ_LINE, FORMATS)
+ call clgstr ("apertures", Memc[input1], SZ_LINE)
+ call clgstr ("bands", Memc[input2], SZ_LINE)
+ call clgstr ("beams", Memc[output], SZ_LINE)
+ apmod = clgeti ("apmodulus")
+ offset = clgeti ("offset")
+ reverse = clgetb ("reverse")
+ ignoreaps = clgetb ("ignoreaps")
+ clobber = clgetb ("clobber")
+ merge = clgetb ("merge")
+ renumber = clgetb ("renumber")
+ verbose = clgetb ("verbose")
+ errval = clgetd ("errval")
+
+ if (op == 0)
+ call error (1, "Unknown operation")
+
+ # Decode range strings and set complement if needed
+ ptr = input1
+ complement = false
+ if (Memc[ptr] == '!') {
+ complement = true
+ ptr = ptr + 1
+ }
+ iferr (aps = rng_open (Memc[ptr], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture/column/line list")
+
+ ptr = input2
+ if (Memc[ptr] == '!') {
+ complement = true
+ ptr = ptr + 1
+ }
+ iferr (bands = rng_open (Memc[ptr], INDEF, INDEF, INDEF))
+ call error (0, "Bad band list")
+
+ ptr = output
+ if (Memc[ptr] == '!') {
+ complement = true
+ ptr = ptr + 1
+ }
+ iferr (beams = rng_open (Memc[ptr], INDEF, INDEF, INDEF))
+ call error (0, "Bad beam list")
+
+ # Check lists.
+ if (imtlen (outlist) > 1 && imtlen (outlist) != imtlen (inlist1))
+ call error (1, "Input and output image lists don't make sense")
+ if (op > SQRT &&
+ imtlen (inlist2) > 1 && imtlen (inlist2) != imtlen (inlist1))
+ call error (1, "Input operand lists don't make sense")
+
+ # Do the operations.
+ while (imtgetim (inlist1, Memc[input1], SZ_LINE) != EOF) {
+ if (imtgetim (inlist2, Memc[output], SZ_LINE) == EOF)
+ call strcpy (Memc[input2], Memc[output], SZ_LINE)
+ call strcpy (Memc[output], Memc[input2], SZ_LINE)
+
+ if (imtlen (outlist) > 1) {
+ list1 = imtopen (Memc[input1])
+ list2 = imtopen (Memc[input2])
+ } else {
+ list1 = inlist1
+ list2 = inlist2
+ }
+
+ switch (format) {
+ case 1:
+ if (imtgetim (outlist, Memc[output], SZ_LINE) == EOF)
+ call strcpy (Memc[input1], Memc[output], SZ_LINE)
+ call imgimage (Memc[output], Memc[output], SZ_LINE)
+ call sa_ms (list1, list2, Memc[output], op, Memc[opstr],
+ w1, w2, rebin, aps, bands, beams, complement, apmod,
+ offset, reverse, ignoreaps, clobber, merge, renumber,
+ verbose)
+ case 2:
+ call sa_getim (outlist, Memc[input1], Memc[output], SZ_LINE)
+ call imgimage (Memc[output], Memc[output], SZ_LINE)
+ call sa_1d (list1, list2, Memc[output], op, Memc[opstr],
+ w1, w2, rebin, aps, bands, beams, complement, apmod,
+ offset, reverse, ignoreaps, clobber, renumber, verbose)
+ }
+
+ if (list1 != inlist1) {
+ call imtclose (list1)
+ call imtclose (list2)
+ }
+ }
+
+ call rng_close (aps)
+ call rng_close (bands)
+ call rng_close (beams)
+ call imtclose (inlist1)
+ call imtclose (inlist2)
+ call imtclose (outlist)
+ call sfree (sp)
+end
+
+
+# SA_MS -- Operate on input list to multispec output
+
+procedure sa_ms (list1, list2, output, op, opstr, w1, w2, rebin,
+ aps, bands, beams, complement, apmod, offset, reverse, ignoreaps,
+ clobber, merge, renumber, verbose)
+
+int list1 # Input image list
+int list2 # Input image list
+char output[ARB] # Output image
+int op # Operation
+char opstr[ARB] # Operation string
+double w1 # Starting wavelength
+double w2 # Ending wavelength
+bool rebin # Rebin wavelength region?
+pointer aps # Apertures/columns/lines
+pointer bands # Bands
+pointer beams # Beams
+bool complement # Complement aperture/beam selection
+int apmod # Aperture modulus
+int offset # Offset to add to output aperture numbers
+bool reverse # Reverse order of operands
+bool ignoreaps # Ignore apertures?
+bool clobber # Clobber existing image?
+bool merge # Merge with existing image?
+bool renumber # Renumber apertures?
+bool verbose # Verbose output?
+
+bool select, same
+real aplow[2], aphigh[2]
+double l1, dl, a, b, w, wb, dw, z, p1, p2, p3
+int i, j, k, l, nin
+int ap, beam, dtype, nw, err
+int ninaps, noutaps, naps, npts, nbands, mwoutdim
+int last, op1, axis[3]
+pointer ptr, in1, in2, out, outtmp, mwtmp, mwin1, mwin2, mwout
+pointer sh1, sh2, shout, const, coeff, inaps, outaps
+pointer sp, str, str1, key, input1, input2, temp, ltm1, ltv1, ltm2, ltv2
+
+double shdr_lw()
+int imaccess(), ctod()
+int imtlen(), imtgetim(), imgnfn()
+bool strne(), rng_elementi(), fp_equald()
+pointer immap() , imgl3r(), impl3r(), imofnlu()
+pointer smw_openim(), mw_open()
+errchk immap, smw_openim, mw_open, imunmap, imgstr, imdelete
+errchk imgl3r, impl3r
+errchk shdr_open, sa_sextract
+data axis/1,2,3/
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (str1, SZ_LINE, TY_CHAR)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (input1, SZ_FNAME, TY_CHAR)
+ call salloc (input2, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (ltm1, 3*3, TY_DOUBLE)
+ call salloc (ltv1, 3, TY_DOUBLE)
+ call salloc (ltm2, 3*3, TY_DOUBLE)
+ call salloc (ltv2, 3, TY_DOUBLE)
+ call malloc (coeff, 1, TY_CHAR)
+ const = NULL
+
+ # Initialize.
+ Memc[input2] = EOS
+ in1 = NULL; in2 = NULL; out = NULL; outtmp=NULL; mwtmp = NULL
+ mwin1 = NULL; mwin2 = NULL; mwout = NULL
+ sh1 = NULL; sh2 = NULL; shout = NULL
+ ninaps = 0; noutaps = 0; nbands = 0
+ l1 = 1.; dl = 1.
+ err = NO
+
+ iferr {
+ # Check for existing output image and abort if clobber is not set.
+ if (imaccess (output, READ_ONLY) == YES) {
+ if (!clobber) {
+ call sprintf (Memc[str], SZ_LINE,
+ "Output spectrum %s already exists")
+ call pargstr (output)
+ call error (1, Memc[str])
+ } else if (merge) {
+ # Open the output and check the type.
+ ptr = immap (output, READ_ONLY, 0); out = ptr
+ ptr = smw_openim (out); mwout = ptr
+ if (SMW_FORMAT(mwout) == SMW_ND) {
+ call sprintf (Memc[str], SZ_LINE, "%s - Wrong format")
+ call pargstr (output)
+ call error (1, Memc[str])
+ }
+
+ # Determine existing apertures and renumber them if needed
+ noutaps = SMW_NSPEC(mwout)
+ nbands = SMW_NBANDS(mwout)
+ call salloc (outaps, noutaps, TY_INT)
+ do i = 1, noutaps {
+ call shdr_open (out, mwout, i, 1, INDEFI, SHHDR, sh2)
+ if (renumber)
+ Memi[outaps+i-1] = i + offset
+ else
+ Memi[outaps+i-1] = AP(sh2)
+ }
+ }
+ call mktemp ("temp", Memc[temp], SZ_FNAME)
+ } else
+ call strcpy (output, Memc[temp], SZ_FNAME)
+
+ # Open input list. Determine the number of final output apertures
+ # and maximum length in order to set the output dimensions. Check
+ # also that there is data to copy.
+
+ call imtrew (list1)
+ nin = imtlen (list1)
+ npts = 0
+ naps = noutaps
+ while (imtgetim (list1, Memc[input1], SZ_FNAME) != EOF) {
+ iferr {
+ in1 = NULL
+ mwin1 = NULL
+
+ ptr = immap (Memc[input1], READ_ONLY, 0); in1 = ptr
+ ptr = smw_openim (in1); mwin1 = ptr
+
+ j = 1
+ if (SMW_FORMAT(mwin1) != SMW_ND) {
+ j = 0
+ do i = 1, SMW_NBANDS(mwin1) {
+ select = rng_elementi (bands, i)
+ if (!select)
+ next
+ j = j + 1
+ }
+ if (j == 0)
+ call error (1, "No bands selected in image")
+ }
+ nbands = max (j, nbands)
+
+ do i = 1, SMW_NSPEC(mwin1) {
+ call shdr_open (in1, mwin1, i, 1, INDEFI, SHHDR, sh1)
+ ap = AP(sh1)
+ if (SMW_FORMAT(mwin1) == SMW_ND) {
+ call smw_mw (mwin1, i, 1, ptr, j, k)
+ select = rng_elementi (aps, j) && rng_elementi (bands, k)
+ } else {
+ j = ap
+ if (apmod > 1)
+ j = mod (j, apmod)
+ select = rng_elementi (aps, j)
+ }
+
+ select = select && rng_elementi (beams, BEAM(sh1))
+ if ((complement && select) || (!complement && !select))
+ next
+ if (renumber)
+ ap = naps + 1
+ ap = ap + offset
+ for (j=0; j<noutaps && Memi[outaps+j]!=ap; j=j+1)
+ ;
+ if (j == noutaps)
+ naps = naps + 1
+ if (ninaps == 0)
+ call malloc (inaps, 10, TY_INT)
+ else if (mod (ninaps, 10) == 0)
+ call realloc (inaps, ninaps+10, TY_INT)
+ Memi[inaps+ninaps] = ap
+
+ call sa_sextract (sh1, w1, w2, rebin, dtype, w, dw, nw)
+ if (ninaps == 0) {
+ l1 = w
+ dl = dw
+ same = true
+ }
+ if (same && !(fp_equald (w, l1) && fp_equald (dw, dl))) {
+ l1 = 1.
+ dl = 1.
+ same = false
+ }
+
+ npts = max (npts, nw+NP1(sh1)-1)
+ ninaps = ninaps + 1
+ if (Memc[input2] == EOS)
+ call strcpy (Memc[input1], Memc[input2], SZ_FNAME)
+ }
+ } then
+ call erract (EA_WARN)
+
+ if (nin > 1) {
+ call shdr_close (sh1)
+ call smw_close (mwin1)
+ if (in1 != NULL)
+ call imunmap (in1)
+ }
+ }
+
+ # Check the selected apertures.
+ if (ninaps == 0)
+ call error (1, "No spectra selected")
+ for (i=0; i<ninaps-1; i=i+1) {
+ for (j=i+1; j<ninaps; j=j+1) {
+ if (Memi[inaps+i] == Memi[inaps+j]) {
+ call error (1,
+ "Output spectra cannot have the same aperture number.\n\tUse renumber parameter.")
+ }
+ }
+ }
+
+ # Set output image dimensions and WCS. The WCS preserves the
+ # dispersion axis physical coordinates but resets the aperture
+ # axis physical coordinates.
+
+ if (out != NULL) {
+ ptr = immap (Memc[temp], NEW_COPY, out); outtmp = ptr
+ if (IM_PIXTYPE(outtmp) != TY_DOUBLE)
+ IM_PIXTYPE(outtmp) = TY_REAL
+
+ IM_LEN(outtmp,1) = max (npts, IM_LEN(out,1))
+ IM_LEN(outtmp,2) = naps
+ IM_LEN(outtmp,3) = max (nbands, IM_LEN(out,3))
+ if (nbands > 1)
+ IM_NDIM(outtmp) = 3
+ else if (naps > 1)
+ IM_NDIM(outtmp) = 2
+ else
+ IM_NDIM(outtmp) = 1
+
+ l1 = 1.
+ dl = 1.
+ i = SMW_PDIM(MW(sh2))
+ j = SMW_PAXIS(MW(sh2),1)
+ mwoutdim = IM_NDIM(outtmp)
+
+ mwtmp = mw_open (NULL, mwoutdim)
+ call mw_newsystem (mwtmp, "equispec", mwoutdim)
+ call mw_swattrs (SMW_MW(mwout,0), 0, "sformat", "equispec")
+ call mw_swtype (mwtmp, axis, mwoutdim, "linear", "")
+ if (LABEL(sh2) != EOS)
+ call mw_swattrs (mwtmp, 1, "label", LABEL(sh2))
+ if (UNITS(sh2) != EOS)
+ call mw_swattrs (mwtmp, 1, "units", UNITS(sh2))
+ ifnoerr (call mw_gwattrs (SMW_MW(MW(sh2),0), SMW_PAXIS(MW(sh2),1),
+ "units_display", Memc[str], SZ_LINE))
+ call mw_swattrs (mwtmp, 1, "units_display", Memc[str])
+
+ call mw_gltermd (SMW_MW(mwout,0), Memd[ltm1], Memd[ltv1], i)
+ call mw_gltermd (mwtmp, Memd[ltm2], Memd[ltv2], mwoutdim)
+ Memd[ltm2] = dl * Memd[ltm1+(i+1)*(j-1)]
+ Memd[ltv2] = (Memd[ltv1+(j-1)] - l1) / dl + 1
+ call mw_sltermd (mwtmp, Memd[ltm2], Memd[ltv2], mwoutdim)
+ call smw_open (mwtmp, NULL, outtmp)
+
+ do i = 1, noutaps {
+ call smw_gwattrs (mwout, i, 1, ap, beam, dtype,
+ w, dw, nw, z, aplow, aphigh, coeff)
+ call smw_swattrs (mwtmp, i, 1, Memi[outaps+i-1], beam, dtype,
+ w, dw, nw, z, aplow, aphigh, Memc[coeff])
+ }
+
+ do j = 1, IM_LEN(out,3) {
+ do i = 1, IM_LEN(out,2) {
+ ptr = impl3r (outtmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(outtmp,1))
+ call amovr (Memr[imgl3r(out,i,j)], Memr[ptr], IM_LEN(out,1))
+ if (verbose) {
+ call shdr_open (out, mwout, i, j, INDEFI, SHHDR, sh2)
+ call shdr_open (outtmp, mwtmp, i, j, INDEFI,
+ SHHDR, shout)
+ if (AP(sh2) != AP(shout))
+ call sa_verbose (sh2, NULL, shout, output,
+ COPY, "copy", const, reverse)
+ }
+ }
+ }
+ do j = 1, IM_LEN(out,3)
+ do i = IM_LEN(out,2)+1, IM_LEN(outtmp,2) {
+ ptr = impl3r (outtmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(outtmp,1))
+ }
+ do j = IM_LEN(out,3)+1, nbands
+ do i = 1, IM_LEN(outtmp,2) {
+ ptr = impl3r (outtmp, i, j)
+ call aclrr (Memr[ptr], IM_LEN(outtmp,1))
+ }
+
+ call shdr_close (shout)
+ call shdr_close (sh2)
+ call smw_close (mwout)
+ mwout = mwtmp
+ mwtmp = NULL
+ call imunmap (out)
+ out = outtmp
+ outtmp = NULL
+
+ } else {
+ if (nin > 1) {
+ ptr = immap (Memc[input2], READ_ONLY, 0); in1 = ptr
+ ptr = smw_openim (in1); mwin1 = ptr
+ call shdr_open (in1, mwin1, i, 1, INDEFI, SHDATA, sh1)
+ }
+ ptr = immap (Memc[temp], NEW_COPY, in1); out = ptr
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ ifnoerr (call imgstr (out, "MSTITLE", Memc[str], SZ_LINE)) {
+ call strcpy (Memc[str], IM_TITLE(out), SZ_IMTITLE)
+ call imdelf (out, "MSTITLE")
+ }
+
+ # Set header
+ IM_LEN(out,1) = npts
+ IM_LEN(out,2) = naps
+ IM_LEN(out,3) = nbands
+ if (nbands > 1)
+ IM_NDIM(out) = 3
+ else if (naps > 1)
+ IM_NDIM(out) = 2
+ else
+ IM_NDIM(out) = 1
+ mwoutdim = IM_NDIM(out)
+
+ j = imofnlu (out, "DISPAXIS,APID*,BANDID*")
+ while (imgnfn (j, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (j)
+
+ i = SMW_PDIM(MW(sh1))
+ j = SMW_PAXIS(MW(sh1),1)
+
+ ptr = mw_open (NULL, mwoutdim); mwout = ptr
+ call mw_newsystem (mwout, "equispec", mwoutdim)
+ call mw_swattrs (mwout, 0, "sformat", "equispec")
+ call mw_swtype (mwout, axis, mwoutdim, "linear", "")
+ if (LABEL(sh1) != EOS)
+ call mw_swattrs (mwout, 1, "label", LABEL(sh1))
+ if (UNITS(sh1) != EOS)
+ call mw_swattrs (mwout, 1, "units", UNITS(sh1))
+ ifnoerr (call mw_gwattrs (SMW_MW(MW(sh1),0), SMW_PAXIS(MW(sh1),1),
+ "units_display", Memc[str], SZ_LINE))
+ call mw_swattrs (mwout, 1, "units_display", Memc[str])
+
+ call mw_gltermd (SMW_MW(mwin1,0), Memd[ltm1], Memd[ltv1], i)
+ call mw_gltermd (mwout, Memd[ltm2], Memd[ltv2], mwoutdim)
+ Memd[ltv2] = (Memd[ltv1+(j-1)] - l1) / dl + 1
+ Memd[ltm2] = dl * Memd[ltm1+(i+1)*(j-1)]
+ call mw_sltermd (mwout, Memd[ltm2], Memd[ltv2], mwoutdim)
+ call smw_open (mwout, NULL, out)
+
+ if (nin > 1) {
+ call shdr_close (sh1)
+ call smw_close (mwin1)
+ call imunmap (in1)
+ }
+ }
+
+ # Now do the actual copy
+ last = noutaps
+ call imtrew (list1)
+ call imtrew (list2)
+ while (imtgetim (list1, Memc[input1], SZ_FNAME) != EOF) {
+ i = imtgetim (list2, Memc[input2], SZ_FNAME)
+ iferr {
+ if (nin > 1) {
+ in1 = NULL
+ mwin1 = NULL
+
+ ptr = immap (Memc[input1], READ_ONLY, 0); in1 = ptr
+ ptr = smw_openim (in1); mwin1 = ptr
+ call shdr_open (in1, mwin1, 1, 1, INDEFI, SHHDR, sh1)
+ }
+
+ # Check dispersion function compatibility
+ # Nonlinear functions can't be copied to different physical
+ # coordinate system though the linear dispersion can be
+ # adjusted.
+
+ call mw_gltermd (SMW_MW(mwout,0), Memd[ltm2], Memd[ltv2],
+ mwoutdim)
+ a = Memd[ltv2]
+ b = Memd[ltm2]
+ if (DC(sh1) == DCFUNC && !rebin) {
+ i = SMW_PDIM(mwin1)
+ j = SMW_PAXIS(mwin1,1)
+
+ call mw_gltermd (SMW_MW(mwin1,0), Memd[ltm1], Memd[ltv1], i)
+ Memd[ltv1] = (Memd[ltv1+(j-1)] - l1) / dl + 1
+ Memd[ltm1] = dl * Memd[ltm1+(i+1)*(j-1)]
+ if (!fp_equald (a, Memd[ltv1]) || !fp_equald (b, Memd[ltm1])) {
+ call error (1,
+ "Physical basis for nonlinear dispersion functions don't match")
+ }
+ }
+
+ # Check for second operand
+ if (op > SQRT) {
+ ifnoerr (ptr = immap (Memc[input2], READ_ONLY, 0)) {
+ in2 = ptr
+ sh2 = NULL
+ mwin2 = NULL
+ ptr = smw_openim (in2); mwin2 = ptr
+ call shdr_open (in2, mwin2, 1, 1, INDEFI, SHHDR, sh2)
+ } else {
+ const = NULL
+ i = 1
+ if (ctod (Memc[input2], i, w) <= 0)
+ call error (1, "Error in second operand")
+ call malloc (const, IM_LEN(out,1), TY_REAL)
+ call amovkr (real (w), Memr[const], IM_LEN(out,1))
+ }
+ }
+
+ do i = 1, SMW_NSPEC(mwin1) {
+ call shdr_open (in1, mwin1, i, 1, INDEFI, SHHDR, sh1)
+ ap = AP(sh1)
+ if (SMW_FORMAT(mwin1) == SMW_ND) {
+ call smw_mw (mwin1, i, 1, ptr, j, k)
+ select = rng_elementi (aps, j) && rng_elementi (bands, k)
+ } else {
+ j = ap
+ if (apmod > 1)
+ j = mod (j, apmod)
+ select = rng_elementi (aps, j)
+ }
+
+ select = select && rng_elementi (beams, BEAM(sh1))
+ if ((complement && select) || (!complement && !select))
+ next
+ if (renumber)
+ ap = last + 1
+ ap = ap + offset
+ for (j=0; j<noutaps && Memi[outaps+j]!=ap; j=j+1)
+ ;
+ if (j < noutaps)
+ l = j + 1
+ else {
+ l = last + 1
+ last = l
+ }
+
+ call shdr_open (in1, mwin1, i, 1, INDEFI, SHDATA, sh1)
+ call sa_sextract (sh1, w1, w2, rebin, dtype, w, dw, nw)
+
+ # Copy and adjust dispersion info
+ call smw_gwattrs (mwin1, i, 1, AP(sh1), beam,
+ j, w, dw, nw, z, aplow, aphigh, coeff)
+
+ w = shdr_lw (sh1, 1D0)
+ wb = shdr_lw (sh1, double (SN(sh1)))
+ if (rebin)
+ Memc[coeff] = EOS
+
+ p1 = (NP1(sh1) - a) / b
+ p2 = (NP2(sh1) - a) / b
+ p3 = (IM_LEN(out,1) - a) / b
+ nw = nint (min (max (p1, p3), max (p1, p2))) + NP1(sh1) - 1
+
+ w = w * (1 + z)
+ wb = wb * (1 + z)
+ if (dtype == DCLOG) {
+ w = log10 (w)
+ wb = log10 (wb)
+ if (p1 != p2)
+ dw = (wb - w) / (p2 - p1)
+ w = w - (p1 - 1) * dw
+ wb = w + (nw - 1) * dw
+ w = 10.**w
+ wb = 10.**wb
+ dw = (wb - w) / (nw - 1)
+ } else {
+ if (p1 != p2)
+ dw = (wb - w) / (p2 - p1)
+ w = w - (p1 - 1) * dw
+ wb = w + (nw - 1) * dw
+ }
+
+ call smw_swattrs (mwout, l, 1, ap, beam, dtype,
+ w, dw, nw, z, aplow, aphigh, Memc[coeff])
+
+ # Copy title
+ call smw_sapid (mwout, l, 1, TITLE(sh1))
+
+ # Copy the data
+ op1 = op
+ k = 0
+ do j = 1, SMW_NBANDS(mwin1) {
+ if (SMW_FORMAT(mwin1) != SMW_ND) {
+ select = rng_elementi (bands, j)
+ if (!select)
+ next
+ }
+ k = k + 1
+ if (j != 1) {
+ call shdr_open (in1, mwin1, i, j, INDEFI, SHDATA, sh1)
+ call sa_sextract (sh1, w1, w2, rebin, dtype, w, dw,nw)
+ }
+
+ if (Memc[SID(sh1,1)] != EOS) {
+ call sprintf (Memc[key], SZ_LINE, "BANDID%d")
+ call pargi (k)
+ iferr (call imgstr (out, Memc[key], Memc[str], SZ_LINE))
+ call imastr (out, Memc[key], Memc[SID(sh1,1)])
+ else {
+ if (strne (Memc[SID(sh1,1)], Memc[str]))
+ call eprintf (
+ "Warning: Input and output types (BANDID) differ\n")
+ }
+ }
+
+ if (sh2 != NULL) {
+ if (ignoreaps)
+ call shdr_open (in2, mwin2, i, j, INDEFI,
+ SHDATA, sh2)
+ else {
+ call shdr_open (in2, mwin2, i, j, AP(sh1),
+ SHDATA, sh2)
+ if (AP(sh2) != AP(sh1))
+ op1 = COPY
+ }
+ }
+
+ # For now just copy noise band.
+ if (STYPE(sh1,1) == SHSIG)
+ op1 = COPY
+
+ call sa_arith (op1, sh1, sh2, const, reverse,
+ Memr[SY(sh1)], Memr[impl3r(out,l,k)+NP1(sh1)-1],SN(sh1))
+
+ if (verbose) {
+ call shdr_open (out, mwout, l, k, INDEFI, SHHDR, shout)
+ call sa_verbose (sh1, sh2, shout, output,
+ op1, opstr, const, reverse)
+ call shdr_close (shout)
+ }
+ }
+ do j = k+1, IM_LEN(out,3)
+ call aclrr (Memr[impl3r(out,l,j)], IM_LEN(out,1))
+ }
+ } then
+ call erract (EA_WARN)
+
+ call shdr_close (shout)
+ call shdr_close (sh1)
+ call shdr_close (sh2)
+ call mfree (const, TY_REAL)
+ call smw_close (mwin2)
+ call smw_close (mwin1)
+ if (in2 != NULL)
+ call imunmap (in2)
+ if (in1 != NULL)
+ call imunmap (in1)
+ }
+ } then {
+ err = YES
+ call erract (EA_WARN)
+ }
+
+ # Finish up the output image.
+ if (mwout != NULL) {
+ call smw_saveim (mwout, out)
+ call smw_close (mwout)
+ }
+ if (outtmp != NULL)
+ call imunmap (outtmp)
+ call smw_close (mwtmp)
+ if (out != NULL) {
+ call imunmap (out)
+ if (strne (Memc[temp], output)) {
+ if (err == NO) {
+ call imdelete (output)
+ call imrename (Memc[temp], output)
+ } else {
+ iferr (call imdelete (Memc[temp]))
+ ;
+ }
+ }
+ }
+
+ call mfree (inaps, TY_INT)
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# SA_1D -- Operate on input list to onedspec output.
+
+procedure sa_1d (list1, list2, output, op, opstr, w1, w2, rebin,
+ aps, bands, beams, complement, apmod, offset, reverse, ignoreaps,
+ clobber, renumber, verbose)
+
+int list1 # Input image list
+int list2 # Input image list
+char output[ARB] # Output image
+int op # Operation
+char opstr[ARB] # Operation string
+double w1 # Starting wavelength
+double w2 # Ending wavelength
+bool rebin # Rebin wavelength region?
+pointer aps # Apertures/columns/lines
+pointer bands # Bands
+pointer beams # Beams
+bool complement # Complement aperture/beam selection
+int apmod # Aperture modulus
+int offset # Offset to add to output aperture numbers
+bool reverse # Reverse order of operands
+bool ignoreaps # Ignore apertures?
+bool clobber # Clobber existing image?
+bool renumber # Renumber apertures?
+bool verbose # Verbose output?
+
+bool select
+int i, j, k
+int ap, band, beam, dtype, nw, naps, op1, err
+double w, wb, dw, z, p1, p2, p3
+real aplow[2], aphigh[2]
+pointer ptr, in1, in2, out, mwin1, mwin2, mwout, sh1, sh2, shout
+pointer sp, str, key, input1, input2, output1, temp
+pointer ltm1, ltv1, ltm2, ltv2, coeff, const
+
+double shdr_lw()
+int imaccess(), ctod(), patmake(), patmatch()
+int imtgetim(), imgnfn()
+bool rng_elementi(), streq()
+pointer immap(), impl1r(), imofnlu(), smw_openim(), mw_open()
+errchk immap, smw_openim, mw_open, imunmap, impl1r, imdelete
+errchk shdr_open, sa_sextract
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (key, SZ_LINE, TY_CHAR)
+ call salloc (input1, SZ_FNAME, TY_CHAR)
+ call salloc (input2, SZ_FNAME, TY_CHAR)
+ call salloc (output1, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (ltm1, 3*3, TY_DOUBLE)
+ call salloc (ltv1, 3, TY_DOUBLE)
+ call salloc (ltm2, 3*3, TY_DOUBLE)
+ call salloc (ltv2, 3, TY_DOUBLE)
+ call malloc (coeff, 1, TY_CHAR)
+
+ # Loop through each spectrum in each input image.
+ call imtrew (list1)
+ call imtrew (list2)
+ sh1 = NULL; sh2 = NULL; shout = NULL
+ naps = 0
+ while (imtgetim (list1, Memc[input1], SZ_FNAME) != EOF) {
+ i = imtgetim (list2, Memc[input2], SZ_FNAME)
+
+ iferr {
+ in1 = NULL; in2 = NULL; mwin1 = NULL; mwin2 = NULL
+ ptr = immap (Memc[input1], READ_ONLY, 0); in1 = ptr
+ ptr = smw_openim (in1); mwin1 = ptr
+
+ # Check for second operand
+ if (op > SQRT) {
+ ifnoerr (ptr = immap (Memc[input2], READ_ONLY, 0)) {
+ in2 = ptr
+ sh2 = NULL
+ mwin2 = NULL
+ ptr = smw_openim (in2); mwin2 = ptr
+ call shdr_open (in2, mwin2,1,1,INDEFI,SHHDR,sh2)
+ } else {
+ const = NULL
+ i = 1
+ if (ctod (Memc[input2], i, w) <= 0)
+ call error (1, "Error in second operand")
+ call malloc (const, IM_LEN(in1,1), TY_REAL)
+ call amovkr (real (w), Memr[const], IM_LEN(in1,1))
+ }
+ }
+
+ do band = 1, SMW_NBANDS(mwin1) {
+ if (SMW_FORMAT(mwin1) != SMW_ND) {
+ select = rng_elementi (bands, band)
+ if (!select)
+ next
+ }
+ do i = 1, SMW_NSPEC(mwin1) {
+ call shdr_open (in1, mwin1, i, band, INDEFI, SHHDR, sh1)
+
+ # Check aperture and beam numbers.
+ ap = AP(sh1)
+ if (SMW_FORMAT(mwin1) == SMW_ND) {
+ call smw_mw (mwin1, i, band, ptr, j, k)
+ select = rng_elementi (aps,j) && rng_elementi (bands,k)
+ } else {
+ j = ap
+ if (apmod > 1)
+ j = mod (j, apmod)
+ select = rng_elementi (aps, j)
+ }
+
+ select = select && rng_elementi (beams, BEAM(sh1))
+ if ((complement && select) || (!complement && !select))
+ next
+ if (renumber) {
+ naps = naps + 1
+ ap = naps
+ }
+ ap = ap + offset
+
+ iferr {
+ out = NULL
+ mwout = NULL
+ err = NO
+
+ # Open output spectrum
+ call strcpy (output, Memc[str], SZ_LINE)
+ j = patmake (".[0-9][0-9][0-9][0-9]$", Memc[key],
+ SZ_LINE)
+ j = patmatch (Memc[str], Memc[key])
+ if (j > 0)
+ Memc[str+j-6] = EOS
+ if (SMW_FORMAT(mwin1) != SMW_ND) {
+ call sprintf (Memc[output1], SZ_FNAME,
+ "%s.%d%03d")
+ call pargstr (Memc[str])
+ call pargi (PINDEX(sh1,2)-1)
+ call pargi (ap)
+ } else {
+ call sprintf (Memc[output1], SZ_FNAME,
+ "%s.%04d")
+ call pargstr (Memc[str])
+ call pargi (ap)
+ }
+ if (imaccess (Memc[output1], READ_ONLY) == YES) {
+ if (clobber)
+ call mktemp ("temp", Memc[temp], SZ_FNAME)
+ else {
+ call sprintf (Memc[str], SZ_LINE,
+ "Output spectrum %s already exists")
+ call pargstr (output)
+ call error (1, Memc[str])
+ }
+ } else
+ call strcpy (Memc[output1], Memc[temp],
+ SZ_FNAME)
+
+ # Get data
+ call shdr_open (in1, mwin1, i, band, INDEFI, SHDATA,
+ sh1)
+
+ # Set header
+ ptr = immap (Memc[temp], NEW_COPY, in1); out = ptr
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+ IM_NDIM(out) = 1
+ if (!streq (TITLE(sh1), IM_TITLE(out))) {
+ call imastr (out, "MSTITLE", IM_TITLE(out))
+ call strcpy (TITLE(sh1), IM_TITLE(out),
+ SZ_IMTITLE)
+ }
+ j = imofnlu (out, "DISPAXIS,APID*,BANDID*")
+ while (imgnfn (j, Memc[key], SZ_LINE) != EOF)
+ call imdelf (out, Memc[key])
+ call imcfnl (j)
+
+ if (Memc[SID(sh1,1)] != EOS)
+ call imastr (out, "BANDID1", Memc[SID(sh1,1)])
+
+ # Set WCS
+ j = SMW_PDIM(MW(sh1))
+ k = SMW_PAXIS(MW(sh1),1)
+ ptr = mw_open (NULL, 1); mwout = ptr
+ call mw_newsystem (mwout, "equispec", 1)
+ call mw_swattrs (mwout, 0, "sformat", "equispec")
+ call mw_swtype (mwout, 1, 1, "linear", "")
+ if (LABEL(sh1) != EOS)
+ call mw_swattrs (mwout, 1, "label", LABEL(sh1))
+ if (UNITS(sh1) != EOS)
+ call mw_swattrs (mwout, 1, "units", UNITS(sh1))
+ ifnoerr (call mw_gwattrs (SMW_MW(MW(sh1),0),
+ SMW_PAXIS(MW(sh1),1), "units_display",
+ Memc[str], SZ_LINE))
+ call mw_swattrs (mwout, 1, "units_display", Memc[str])
+
+ call mw_gltermd (SMW_MW(mwin1,0), Memd[ltm1],
+ Memd[ltv1], j)
+ call mw_gltermd (mwout, Memd[ltm2], Memd[ltv2], 1)
+ Memd[ltv2] = Memd[ltv1+(k-1)]
+ Memd[ltm2] = Memd[ltm1+(j+1)*(k-1)]
+ call sa_sextract (sh1, w1, w2, rebin, dtype, w, dw, nw)
+ IM_LEN(out,1) = nw + NP1(sh1) - 1
+ Memd[ltv2] = (Memd[ltv1] - w) / dw + 1
+ Memd[ltm2] = dw * Memd[ltm1]
+ call mw_sltermd (mwout, Memd[ltm2], Memd[ltv2], 1)
+ call smw_open (mwout, NULL, out)
+
+ # Copy and adjust dispersion info
+ call smw_gwattrs (mwin1, i, band, AP(sh1),
+ beam, j, w, dw, nw, z, aplow, aphigh, coeff)
+ w = shdr_lw (sh1, 1D0)
+ wb = shdr_lw (sh1, double(SN(sh1)))
+ if (rebin)
+ Memc[coeff] = EOS
+
+ p1 = (NP1(sh1) - Memd[ltv2]) / Memd[ltm2]
+ p2 = (NP2(sh1) - Memd[ltv2]) / Memd[ltm2]
+ p3 = (IM_LEN(out,1) - Memd[ltv2]) / Memd[ltm2]
+ nw = nint (min (max (p1, p3), max (p1, p2))) + NP1(sh1) - 1
+
+ w = w * (1 + z)
+ wb = wb * (1 + z)
+ if (dtype == DCLOG) {
+ w = log10 (w)
+ wb = log10 (wb)
+ if (p1 != p2)
+ dw = (wb - w) / (p2 - p1)
+ w = w - (p1 - 1) * dw
+ wb = w + (nw - 1) * dw
+ w = 10.**w
+ wb = 10.**wb
+ dw = (wb - w) / (nw - 1)
+ } else {
+ if (p1 != p2)
+ dw = (wb - w) / (p2 - p1)
+ w = w - (p1 - 1) * dw
+ wb = w + (nw - 1) * dw
+ }
+
+ call smw_swattrs (mwout, 1, 1, ap, beam, dtype,
+ w, dw, nw, z, aplow, aphigh, Memc[coeff])
+
+ # Copy data
+ op1 = op
+ if (sh2 != NULL) {
+ if (ignoreaps)
+ call shdr_open (in2, mwin2, i, band, INDEFI,
+ SHDATA, sh2)
+ else {
+ call shdr_open (in2, mwin2, i, band, AP(sh1),
+ SHDATA, sh2)
+ if (AP(sh2) != AP(sh1))
+ op1 = COPY
+ }
+ }
+
+ # For now just copy noise band.
+ if (STYPE(sh1,1) == SHSIG)
+ op1 = COPY
+
+ call sa_arith (op1, sh1, sh2, const, reverse,
+ Memr[SY(sh1)], Memr[impl1r(out)+NP1(sh1)-1], SN(sh1))
+
+
+ if (verbose) {
+ call shdr_open (out, mwout, 1, 1, INDEFI, SHHDR, shout)
+ call sa_verbose (sh1, sh2, shout, Memc[output1],
+ op1, opstr, const, reverse)
+ }
+ } then {
+ err = YES
+ call erract (EA_WARN)
+ }
+
+ call shdr_close (shout)
+ if (mwout != NULL) {
+ if (err == NO)
+ call smw_saveim (mwout, out)
+ call smw_close (mwout)
+ }
+ if (out != NULL) {
+ call imunmap (out)
+ if (!streq (Memc[output1], Memc[temp])) {
+ if (err == NO) {
+ call imgimage (Memc[input1], Memc[str], SZ_LINE)
+ if (streq (Memc[output1], Memc[str]))
+ call imunmap (in1)
+ call imgimage (Memc[input2], Memc[str], SZ_LINE)
+ if (streq (Memc[output1], Memc[str]))
+ call imunmap (in2)
+ call imdelete (Memc[output1])
+ call imrename (Memc[temp], Memc[output1])
+ } else
+ call imdelete (Memc[temp])
+ } else if (err == YES)
+ call imdelete (Memc[output1])
+ }
+ }
+ }
+ } then
+ call erract (EA_WARN)
+
+ call shdr_close (sh2)
+ call shdr_close (sh1)
+ call smw_close (mwin1)
+ call smw_close (mwin2)
+ if (in2 != NULL)
+ call imunmap (in2)
+ if (in1 != NULL)
+ call imunmap (in1)
+ }
+
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# SA_ARITH -- Do arithmetic operation
+
+procedure sa_arith (op, sh, sh2, const, reverse, in, out, n)
+
+int op # Operation
+pointer sh # Input SHDR pointer
+pointer sh2 # Second operand spectrum (NULL if none)
+pointer const # Second operand constant (NULL if none)
+bool reverse # Reverse order of operands
+real in[n] # Input data
+real out[n] # Output data
+int n # Number of data points
+
+int i
+pointer buf
+real sa_errfcn()
+extern sa_errfcn()
+
+begin
+ if (op > SQRT) {
+ if (sh2 != NULL) {
+ call shdr_rebin (sh2, sh)
+ buf = SY(sh2)
+ } else
+ buf = const
+ }
+
+ switch (op) {
+ case ABS:
+ call aabsr (in, out, n)
+ case COPY:
+ call amovr (in, out, n)
+ case DEX:
+ do i = 1, n
+ out[i] = 10. ** in[i]
+ case EXP:
+ do i = 1, n
+ out[i] = exp (in[i])
+ case FLAM:
+ buf = SX(sh)
+ do i = 1, n {
+ out[i] = in[i] / (Memr[buf] ** 2 / 2.997925e18)
+ buf = buf + 1
+ }
+ case FNU:
+ buf = SX(sh)
+ do i = 1, n {
+ out[i] = in[i] * (Memr[buf] ** 2 / 2.997925e18)
+ buf = buf + 1
+ }
+ case INV:
+ call arczr (1., in, out, n, sa_errfcn)
+ case LN:
+ call allnr (in, out, n, sa_errfcn)
+ case LOG:
+ call alogr (in, out, n, sa_errfcn)
+ case LUM:
+ do i = 1, n
+ out[i] = 10. ** (-0.4 * in[i])
+ case MAG:
+ do i = 1, n {
+ if (in[i] <= 0.)
+ out[i] = sa_errfcn (0.)
+ else
+ out[i] = -2.5 * log10 (in[i])
+ }
+ case SQRT:
+ call asqrr (in, out, n, sa_errfcn)
+
+ case REP:
+ call amovr (Memr[buf], out, n)
+ case ADD:
+ call aaddr (in, Memr[buf], out, n)
+ case SUB:
+ if (reverse)
+ call asubr (Memr[buf], in, out, n)
+ else
+ call asubr (in, Memr[buf], out, n)
+ case MUL:
+ call amulr (in, Memr[buf], out, n)
+ case DIV:
+ if (reverse)
+ call advzr (Memr[buf], in, out, n, sa_errfcn)
+ else
+ call advzr (in, Memr[buf], out, n, sa_errfcn)
+ case POW:
+ if (reverse) {
+ do i = 1, n
+ out[i] = Memr[buf+i-1] ** in[i]
+ } else {
+ do i = 1, n
+ out[i] = in[i] ** Memr[buf+i-1]
+ }
+ }
+end
+
+
+# SA_ERRFCN -- SARITH Error Function
+
+real procedure sa_errfcn (x)
+
+real x, errval
+common /sarith/ errval
+
+begin
+ return (errval)
+end
+
+
+# SA_VERBOSE -- Print verbose output.
+
+procedure sa_verbose1 (input1, input2, output, ap1, ap2, apout, op, opstr,
+ const, reverse)
+
+char input1[ARB], input2[ARB] # Input spectra
+char output[ARB] # Output spectrum
+int ap1, ap2 # Input apertures
+int apout # Output apertures
+int op # Opcode
+char opstr[ARB] # Operation string
+pointer const # Pointer to constant if used
+bool reverse # Reverse operands?
+
+begin
+ if (op <= SQRT) {
+ if (op == COPY) {
+ call printf ("%s[%d] --> %s")
+ call pargstr (input1)
+ call pargi (ap1)
+ call pargstr (output)
+ call pargi (apout)
+ } else {
+ call printf ("%s[%d] -- %s --> %s")
+ call pargstr (input1)
+ call pargi (ap1)
+ call pargstr (opstr)
+ call pargstr (output)
+ call pargi (apout)
+ }
+ } else if (const == NULL) {
+ call printf ("%s[%d] %s %s[%d] --> %s")
+ if (reverse) {
+ call pargstr (input2)
+ call pargi (ap2)
+ call pargstr (opstr)
+ call pargstr (input1)
+ call pargi (ap1)
+ } else {
+ call pargstr (input1)
+ call pargi (ap1)
+ call pargstr (opstr)
+ call pargstr (input2)
+ call pargi (ap2)
+ }
+ call pargstr (output)
+ call pargi (apout)
+ } else {
+ if (reverse) {
+ call printf ("%g %s %s[%d] --> %s")
+ call pargr (Memr[const])
+ call pargstr (opstr)
+ call pargstr (input1)
+ call pargi (ap1)
+ } else {
+ call printf ("%s[%d] %s %g --> %s")
+ call pargstr (input1)
+ call pargi (ap1)
+ call pargstr (opstr)
+ call pargr (Memr[const])
+ }
+ }
+ call pargstr (output)
+ if (!IS_INDEFI(apout)) {
+ call printf ("[%d]")
+ call pargi (apout)
+ }
+ call printf ("\n")
+ call flush (STDOUT)
+end
+
+
+# SA_VERBOSE -- Print verbose output.
+
+procedure sa_verbose (sh1, sh2, shout, output, op, opstr, const, reverse)
+
+pointer sh1, sh2 # Input spectra
+pointer shout # Output spectrum
+char output[ARB] # Output image name
+int op # Opcode
+char opstr[ARB] # Operation string
+pointer const # Pointer to constant if used
+bool reverse # Reverse operands?
+
+begin
+ if (op <= SQRT) {
+ if (op == COPY) {
+ call printf ("%s%s(%d) --> %s%s(%d)")
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ } else {
+ call printf ("%s%s(%d) -- %s --> %s%s(%d)")
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ call pargstr (opstr)
+ }
+ } else if (const == NULL) {
+ call printf ("%s%s(%d) %s %s%s(%d) --> %s%s(%d)")
+ if (reverse) {
+ call pargstr (IMNAME(sh2))
+ call pargstr (IMSEC(sh2))
+ call pargi (AP(sh2))
+ call pargstr (opstr)
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ } else {
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ call pargstr (opstr)
+ call pargstr (IMNAME(sh2))
+ call pargstr (IMSEC(sh2))
+ call pargi (AP(sh2))
+ }
+ } else {
+ if (reverse) {
+ call printf ("%g %s %s%s(%d) --> %s%s(%d)")
+ call pargr (Memr[const])
+ call pargstr (opstr)
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ } else {
+ call printf ("%s%s(%d) %s %g --> %s%s(%d)")
+ call pargstr (IMNAME(sh1))
+ call pargstr (IMSEC(sh1))
+ call pargi (AP(sh1))
+ call pargstr (opstr)
+ call pargr (Memr[const])
+ }
+ }
+ call pargstr (output)
+ call pargstr (IMSEC(shout))
+ call pargi (AP(shout))
+ call printf ("\n")
+ call flush (STDOUT)
+end
+
+
+# SA_GETIM -- Get image from a list with the image kernal extension removed.
+
+procedure sa_getim (list, defname, image, maxchar)
+
+int list # Image list
+char defname[ARB] # Default image name
+char image[maxchar] # Image name
+int maxchar # Image name maximum character length
+
+int i, stat, imtgetim(), strmatch()
+pointer sp, str, section
+
+begin
+ call smark (sp)
+ call salloc (str, maxchar, TY_CHAR)
+ call salloc (section, SZ_FNAME, TY_CHAR)
+
+ stat = imtgetim (list, Memc[str], maxchar)
+ if (stat == EOF)
+ call strcpy (defname, Memc[str], maxchar)
+
+ call imgsection (Memc[str], Memc[section], SZ_FNAME)
+ call imgimage (Memc[str], image, maxchar)
+ i = strmatch (image, ".??h$")
+ if (i > 0)
+ image[i-4] = EOS
+ call strcat (Memc[section], image, maxchar)
+
+ call sfree (sp)
+end
+
+
+# SA_SEXTRACT -- Extract a specific wavelength region
+
+procedure sa_sextract (sh, w1, w2, rebin, dtype, l1, dl, n)
+
+pointer sh #U SHDR structure
+double w1 #I Starting wavelength
+double w2 #I Ending wavelength
+bool rebin #I Rebin wavelength region?
+int dtype #O Dispersion type
+double l1 #O Starting logical pixel
+double dl #O Logical pixel increment
+int n #O Number of logical pixels
+
+int i1, i2
+double a, b
+bool fp_equald()
+double shdr_lw(), shdr_wl()
+errchk shdr_wl, shdr_linear, shdr_extract
+
+begin
+ if (IS_INDEFD(w1) && IS_INDEFD(w2)) {
+ l1 = 1.
+ dl = 1.
+ n = SN(sh)
+ dtype = DC(sh)
+ return
+ }
+
+ a = w1
+ b = w2
+ if (IS_INDEFD(a))
+ a = shdr_lw (sh, 1.0D0)
+ if (IS_INDEFD(b))
+ b = shdr_lw (sh, double (SN(sh)))
+
+ l1 = shdr_wl (sh, a)
+ dl = shdr_wl (sh, b)
+ if (fp_equald(l1,dl) || max(l1,dl) < 1. || min (l1,dl) > SN(sh))
+ call error (1, "No pixels to extract")
+ l1 = max (1D0, min (double (SN(sh)), l1))
+ dl = max (1D0, min (double (SN(sh)), dl))
+ i1 = nint (l1)
+ i2 = nint (dl)
+ n = abs (i2 - i1) + 1
+ if (!rebin) {
+ l1 = i1
+ dl = i2
+ }
+ if (n == 1)
+ dl = 1
+ else
+ dl = (dl - l1) / (n - 1)
+
+ if (SY(sh) != NULL)
+ call shdr_extract (sh, real(a), real(b), rebin)
+ dtype = DC(sh)
+end
diff --git a/noao/onedspec/t_sbands.x b/noao/onedspec/t_sbands.x
new file mode 100644
index 00000000..d3c3d1e2
--- /dev/null
+++ b/noao/onedspec/t_sbands.x
@@ -0,0 +1,585 @@
+include <error.h>
+include <smw.h>
+
+# Band structure
+define LEN_BAND 9 # length of structure
+define BAND_ID Memi[$1] # ptr to band id string
+define BAND_FILTER Memi[$1+1] # ptr to filter string
+define BAND_WC Memd[P2D($1+2)] # center wavelength
+define BAND_DW Memd[P2D($1+4)] # wavelength width
+define BAND_FN Memi[$1+6] # no. of filter points
+define BAND_FW Memi[$1+7] # ptr to filter wavelengths
+define BAND_FR Memi[$1+8] # ptr to filter responses
+
+# Multiple bands for indices and equivalent widths.
+define NBANDS 3 # maximum number of bands
+define BAND1 1
+define BAND2 2
+define BAND3 3
+define BAND Memi[$1+($2-1)*NBANDS+($3-1)]
+
+
+# T_SBANDS -- Compute band fluxes, indices, and equivalent widths.
+# A list of bandpasses is supplied in a text file, and all of them are applied
+# to each spectrum in the list. The output is written to an output file
+# in multicolumn format.
+
+procedure t_sbands ()
+
+pointer inlist # Input list of spectra
+pointer output # Output file name
+pointer fbands # Band file name
+pointer apertures # Aperture list string
+bool norm # Normalize bands by response?
+bool mag # Output magnitudes instead of fluxes?
+double magzero # Magnitude zeropoint for magnitude output
+bool verbose # Verbose header?
+
+int i, nbands, nsubbands, nimages, fd
+pointer bands, aps, im, smw, sh
+pointer sp, input
+
+int open(), imtgetim()
+bool clgetb(), rng_elementi()
+double clgetd()
+pointer imtopenp(), immap(), smw_openim(), rng_open()
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (fbands, SZ_FNAME, TY_CHAR)
+ call salloc (apertures, SZ_LINE, TY_CHAR)
+
+ # Get task parameters.
+ inlist = imtopenp ("input")
+ call clgstr ("output", Memc[output], SZ_FNAME)
+ call clgstr ("bands", Memc[fbands], SZ_FNAME)
+ call clgstr ("apertures", Memc[apertures], SZ_LINE)
+ norm = clgetb ("normalize")
+ mag = clgetb ("mag")
+ magzero = clgetd ("magzero")
+ verbose = clgetb ("verbose")
+
+ # Read bands from the band file.
+ fd = open (Memc[fbands], READ_ONLY, TEXT_FILE)
+ call sb_bands (fd, bands, nbands, nsubbands)
+ call close (fd)
+
+ # Open the aperture list.
+ iferr (aps = rng_open (Memc[apertures], INDEF, INDEF, INDEF))
+ call error (1, "Bad aperture list")
+
+ # Loop over the input spectra.
+ fd = 0
+ nimages = 0
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ nimages = nimages + 1
+
+ # Open the input image and get the WCS.
+ iferr (im = immap (Memc[input], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+ iferr (smw = smw_openim (im)) {
+ call imunmap (im)
+ call erract (EA_WARN)
+ next
+ }
+
+ # Open output file and write a verbose header if desired.
+ # It is delayed until now to avoid output if an error occurs
+ # such as image not found.
+
+ if (nimages == 1) {
+ fd = open (Memc[output], APPEND, TEXT_FILE)
+ if (verbose)
+ call sb_header (fd, norm, mag, magzero,
+ Memc[fbands], bands, nbands, nsubbands)
+ }
+
+ # Measure selected apertures.
+ do i = 1, SMW_NSPEC(smw) {
+ call shdr_open (im, smw, i, 1, INDEFI, SHHDR, sh)
+ if (!rng_elementi (aps, AP(sh)))
+ next
+ call shdr_open (im, smw, i, 1, INDEFI, SHDATA, sh)
+
+ call sb_proc (fd, sh, bands, nbands, norm, mag, magzero)
+ }
+
+ # Finish with image.
+ call shdr_close (sh)
+ call smw_close (smw)
+ call imunmap (im)
+ }
+
+ # Finish up.
+ call sb_free (bands, nbands)
+ if (fd != 0)
+ call close (fd)
+ call imtclose (inlist)
+ call sfree (sp)
+end
+
+
+# SB_BANDS - Read bands from the band file and put them into an array
+# of band pointers.
+
+procedure sb_bands (fd, bands, nbands, nsubbands)
+
+int fd #I Bandpass file descriptor
+pointer bands #O Bandpass table descriptor
+int nbands #O Number of bandpasses
+int nsubbands #O Number of individual bands
+
+bool bandok
+int ip
+double center, width
+pointer sp, line, id, filter
+
+int getline(), ctowrd(), ctod()
+
+begin
+ call smark (sp)
+ call salloc (line, SZ_LINE, TY_CHAR)
+ call salloc (id, SZ_FNAME, TY_CHAR)
+ call salloc (filter, SZ_FNAME, TY_CHAR)
+
+ # Read the bands. If the first band is not seen
+ # skip the line. Check for 1, 2, or 3 bandpasses.
+ # Can't use fscan() because fscan() will be called later to
+ # read any filter file.
+
+ bands = NULL
+ nbands = 0
+ nsubbands = 0
+ while (getline (fd, Memc[line]) != EOF) {
+ ip = 1
+ bandok = (ctowrd (Memc[line], ip, Memc[id], SZ_FNAME) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, center) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, width) > 0)
+ bandok = (bandok && ctowrd (Memc[line],ip,Memc[filter],SZ_FNAME)>0)
+ if (!bandok || Memc[id] == '#')
+ next
+
+ # Allocate and reallocate the array of band pointers.
+ if (nbands == 0)
+ call malloc (bands, 10 * NBANDS, TY_POINTER)
+ else if (mod (nbands, 10) == 0)
+ call realloc (bands, (nbands + 10) * NBANDS, TY_POINTER)
+ nbands = nbands + 1
+
+ call sb_alloc (BAND(bands,nbands,BAND1),
+ Memc[id], Memc[filter], center, width)
+ nsubbands = nsubbands + 1
+
+ bandok = (ctowrd (Memc[line], ip, Memc[id], SZ_FNAME) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, center) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, width) > 0)
+ bandok = (bandok && ctowrd (Memc[line],ip,Memc[filter],SZ_FNAME)>0)
+ if (bandok) {
+ call sb_alloc (BAND(bands,nbands,BAND2),
+ Memc[id], Memc[filter], center, width)
+ nsubbands = nsubbands + 1
+ } else
+ BAND(bands,nbands,BAND2) = NULL
+
+ bandok = (ctowrd (Memc[line], ip, Memc[id], SZ_FNAME) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, center) > 0)
+ bandok = (bandok && ctod (Memc[line], ip, width) > 0)
+ bandok = (bandok && ctowrd (Memc[line],ip,Memc[filter],SZ_FNAME)>0)
+ if (bandok) {
+ call sb_alloc (BAND(bands,nbands,BAND3),
+ Memc[id], Memc[filter], center, width)
+ nsubbands = nsubbands + 1
+ } else
+ BAND(bands,nbands,BAND3) = NULL
+ }
+
+ call sfree (sp)
+end
+
+
+# SB_ALLOC -- Allocate a band structure.
+
+procedure sb_alloc (band, id, filter, center, width)
+
+pointer band #O Band pointer
+char id[ARB] #I Band id
+char filter[ARB] #I Band filter
+double center #I Band wavelength
+double width #I Band width
+
+int fn, fd, strlen(), open(), fscan(), nscan()
+double w, r
+pointer fw, fr
+bool streq()
+errchk open()
+
+begin
+ call calloc (band, LEN_BAND, TY_STRUCT)
+ call malloc (BAND_ID(band), strlen(id), TY_CHAR)
+ call malloc (BAND_FILTER(band), strlen(filter), TY_CHAR)
+ call strcpy (id, Memc[BAND_ID(band)], ARB)
+ call strcpy (filter, Memc[BAND_FILTER(band)], ARB)
+ BAND_WC(band) = center
+ BAND_DW(band) = width
+ BAND_FN(band) = 0
+ BAND_FW(band) = NULL
+ BAND_FR(band) = NULL
+
+ if (streq (filter, "none"))
+ return
+
+ # Read the filter file.
+ fd = open (filter, READ_ONLY, TEXT_FILE)
+ fn = 0
+ while (fscan (fd) != EOF) {
+ call gargd (w)
+ call gargd (r)
+ if (nscan() != 2)
+ next
+ if (fn == 0) {
+ call malloc (fw, 100, TY_DOUBLE)
+ call malloc (fr, 100, TY_DOUBLE)
+ } else if (mod (fn, 100) == 0) {
+ call realloc (fw, fn+100, TY_DOUBLE)
+ call realloc (fr, fn+100, TY_DOUBLE)
+ }
+ Memd[fw+fn] = w
+ Memd[fr+fn] = r
+ fn = fn + 1
+ }
+ call close (fd)
+
+ BAND_FN(band) = fn
+ BAND_FW(band) = fw
+ BAND_FR(band) = fr
+end
+
+
+# SB_FREE -- Free band structures.
+
+procedure sb_free (bands, nbands)
+
+pointer bands #I bands descriptor
+int nbands #I number of bands
+
+int i, j
+pointer band
+
+begin
+ do i = 1, nbands {
+ do j = 1, NBANDS {
+ band = BAND(bands,i,j)
+ if (band != NULL) {
+ call mfree (BAND_ID(band), TY_CHAR)
+ call mfree (BAND_FILTER(band), TY_CHAR)
+ call mfree (BAND_FW(band), TY_DOUBLE)
+ call mfree (BAND_FR(band), TY_DOUBLE)
+ call mfree (band, TY_STRUCT)
+ }
+ }
+ }
+ call mfree (bands, TY_POINTER)
+end
+
+
+# SB_HEADER -- Print output header.
+
+procedure sb_header (fd, norm, mag, magzero, fbands, bands, nbands, nsubbands)
+
+pointer fd #I Output file descriptor
+bool norm #I Normalization flag
+bool mag #I Magnitude flag
+double magzero #I Magnitude zeropoint
+char fbands[ARB] #I Band file
+pointer bands #I Pointer to array of bands
+int nbands #I Number of bands
+int nsubbands #I Number of subbands
+
+int i, j
+pointer sp, str, band
+
+begin
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Output a banner and task parameters.
+ call sysid (Memc[str], SZ_LINE)
+ call fprintf (fd, "\n# SBANDS: %s\n# ")
+ call pargstr (Memc[str])
+ if (fbands[1] != EOS) {
+ call fprintf (fd, " bands = %s,")
+ call pargstr (fbands)
+ }
+ call fprintf (fd, " norm = %b, mag = %b")
+ call pargb (norm)
+ call pargb (mag)
+ if (mag) {
+ call fprintf (fd, ", magzero = %.2f")
+ call pargd (magzero)
+ call strcpy ("mag", Memc[str], SZ_LINE)
+ } else
+ call strcpy ("flux", Memc[str], SZ_LINE)
+
+ # Output the bands.
+ call fprintf (fd, "\n# %10s %10s %10s %10s\n")
+ call pargstr ("band")
+ call pargstr ("filter")
+ call pargstr ("wavelength")
+ call pargstr ("width")
+ do i = 1, nbands {
+ do j = 1, NBANDS {
+ band = BAND(bands,i,j)
+ if (band == NULL)
+ next
+ call fprintf (fd, "# %10s %10s %10g %10g\n")
+ call pargstr (Memc[BAND_ID(band)])
+ call pargstr (Memc[BAND_FILTER(band)])
+ call pargd (BAND_WC(band))
+ call pargd (BAND_DW(band))
+ }
+ }
+
+ # Output column headings.
+ call fprintf (fd,
+ "#\n# %24s %7.7s %11.11s")
+ call pargstr ("spectrum")
+ call pargstr ("band")
+ call pargstr (Memc[str])
+ if (nsubbands > nbands) {
+ call fprintf (fd, " %7.7s %11.11s %9.9s %9.9s")
+ call pargstr ("band")
+ call pargstr (Memc[str])
+ call pargstr ("index")
+ call pargstr ("eqwidth")
+ }
+ call fprintf (fd, "\n")
+
+ call sfree (sp)
+end
+
+
+# SB_PROC -- Measure the band fluxes and possibly a band index and eq. width.
+
+procedure sb_proc (fd, sh, bands, nbands, norm, mag, magzero)
+
+int fd #I Output file descriptor
+pointer sh #I Spectrum descriptor
+pointer bands #I Bandpass table pointer
+int nbands #I Number of bandpasses
+bool norm #I Normalize?
+bool mag #I Magnitude output?
+double magzero #I Magnitude zero point
+
+int i
+double flux, contval, index, eqwidth
+double flux1, norm1, flux2, norm2, flux3, norm3, a, b
+pointer sp, imname, band1, band2, band3
+
+begin
+ call smark (sp)
+ call salloc (imname, SZ_FNAME, TY_CHAR)
+
+ call sprintf (Memc[imname], SZ_FNAME, "%s%s(%d)")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargi (AP(sh))
+
+ # Loop over all bandpasses
+ do i = 1, nbands {
+ # Measure primary band flux, normalize, and print result.
+ band1 = BAND(bands,i,BAND1)
+ call sb_flux (sh, band1, flux1, norm1)
+ if (IS_INDEFD(flux1))
+ next
+
+ if (norm) {
+ flux1 = flux1 / norm1
+ norm1 = 1
+ }
+ if (mag && flux1 > 0.)
+ flux = magzero - 2.5 * log10 (flux1)
+ else
+ flux = flux1
+
+ call fprintf (fd, "%26s %7.7s %11.6g")
+ call pargstr (Memc[imname])
+ call pargstr (Memc[BAND_ID(band1)])
+ call pargd (flux)
+
+ # Measure the alternate band fluxes and compute and output
+ # the band index and equivalent width.
+
+ band2 = BAND(bands,i,BAND2)
+ band3 = BAND(bands,i,BAND3)
+ call sb_flux (sh, band2, flux2, norm2)
+ call sb_flux (sh, band3, flux3, norm3)
+ if (IS_INDEFD(flux2) && IS_INDEFD(flux3)) {
+ call fprintf (fd, "\n")
+ next
+ }
+
+ if (norm) {
+ if (!IS_INDEFD(flux2)) {
+ flux2 = flux2 / norm2
+ norm2 = 1
+ }
+ if (!IS_INDEFD(flux3)) {
+ flux3 = flux3 / norm3
+ norm3 = 1
+ }
+ }
+
+ contval = INDEFD
+ index = INDEFD
+ eqwidth = INDEFD
+ if (!IS_INDEFD(flux2) && !IS_INDEFD(flux3)) {
+ # Interpolate to the center of the primary band.
+ a = (flux2 / norm2 - flux3 / norm3) /
+ (BAND_WC(band2) - BAND_WC(band3))
+ b = flux2 / norm2 - a * BAND_WC(band2)
+ contval = (a * BAND_WC(band1) + b) * norm1
+ call fprintf (fd, " %7.7s")
+ call pargstr ("cont")
+ } else if (!IS_INDEFD(flux2)) {
+ contval = flux2
+ call fprintf (fd, " %7.7s")
+ call pargstr (Memc[BAND_ID(band2)])
+ } else if (!IS_INDEFD(flux3)) {
+ contval = flux3
+ call fprintf (fd, " %7.7s")
+ call pargstr (Memc[BAND_ID(band3)])
+ }
+
+ if (mag && contval > 0.)
+ flux = magzero - 2.5 * log10 (contval)
+ else
+ flux = contval
+ call fprintf (fd, " %11.6g")
+ call pargd (flux)
+
+ if (flux1 > 0. && contval > 0.) {
+ index = flux1 / contval
+ eqwidth = (1 - index) * BAND_DW(band1)
+ }
+ if (mag) {
+ if (!IS_INDEFD(contval) && contval > 0.)
+ contval = magzero - 2.5 * log10 (contval)
+ if (!IS_INDEFD(index))
+ index = -2.5 * log10 (index)
+ }
+
+ call fprintf (fd, " %9.6g %9.6g\n")
+ call pargd (index)
+ call pargd (eqwidth)
+ }
+
+ # Flush output and finish up.
+ call flush (fd)
+ call sfree (sp)
+end
+
+
+# SB_FLUX - Compute the flux and total response in a given band.
+# Return INDEF if the band is outside of the spectrum.
+
+procedure sb_flux (sh, band, flux, norm)
+
+pointer sh #I spectrum descriptor
+pointer band #I band descriptor
+double flux #O flux
+double norm #O normalization
+
+int i, i1, i2
+double a, b, w1, w2, x1, x2, wt
+pointer x, y
+double sb_filter(), shdr_wl()
+
+begin
+ # Return if no band is defined.
+ flux = INDEFD
+ norm = 1
+ if (band == NULL)
+ return
+
+ # Check end points.
+ a = BAND_WC(band) - BAND_DW(band) / 2.
+ b = BAND_WC(band) + BAND_DW(band) / 2.
+ w1 = min (a, b)
+ w2 = max (a, b)
+ a = shdr_wl (sh, w1)
+ b = shdr_wl (sh, w2)
+ x1 = min (a, b)
+ x2 = max (a, b)
+ i1 = nint (x1)
+ i2 = nint (x2)
+ if (x1 == x2 || i1 < 1 || i2 > SN(sh))
+ return
+
+ x = SX(sh) + i1 - 1
+ y = SY(sh) + i1 - 1
+
+ if (i1 == i2) {
+ wt = sb_filter (double(Memr[x]), band) * (x2 - x1)
+ flux = wt * Memr[y]
+ norm = wt
+ } else {
+ wt = sb_filter (double(Memr[x]), band) * (i1 + 0.5 - x1)
+ flux = wt * Memr[y]
+ norm = wt
+ x = x + 1
+ y = y + 1
+ for (i = i1 + 1; i <= i2 - 1; i = i + 1) {
+ wt = sb_filter (double(Memr[x]), band)
+ flux = flux + wt * Memr[y]
+ norm = norm + wt
+ x = x + 1
+ y = y + 1
+ }
+ wt = sb_filter (double(Memr[x]), band) * (x2 - i2 + 0.5)
+ flux = flux + wt * Memr[y]
+ norm = norm + wt
+ }
+end
+
+
+# SB_FILTER -- Given a filter array interpolate to the specified wavelength.
+
+double procedure sb_filter (w, band)
+
+double w # Wavelength desired
+pointer band # Band pointer
+
+int i, n
+double x1, x2
+pointer x, y
+
+begin
+ n = BAND_FN(band)
+ if (n == 0)
+ return (1.)
+
+ x = BAND_FW(band)
+ y = BAND_FR(band)
+ x1 = Memd[x]
+ x2 = Memd[x+n-1]
+
+ if (w <= x1)
+ return (Memd[y])
+ else if (w >= x2)
+ return (Memd[y+n-1])
+
+ if ((w - x1) < (x2 - w))
+ for (i = 1; w > Memd[x+i]; i=i+1)
+ ;
+ else
+ for (i = n - 1; w < Memd[x+i-1]; i=i-1)
+ ;
+
+ x1 = Memd[x+i-1]
+ x2 = Memd[x+i]
+ return ((w - x1) / (x2 - x1) * (Memd[y+i] - Memd[y+i-1]) + Memd[y+i-1])
+end
diff --git a/noao/onedspec/t_scoords.x b/noao/onedspec/t_scoords.x
new file mode 100644
index 00000000..fe2dd067
--- /dev/null
+++ b/noao/onedspec/t_scoords.x
@@ -0,0 +1,179 @@
+include <error.h>
+include <imhdr.h>
+
+# T_SCOORDS -- Set sampled coordinates in spectra.
+# This task is currently limited to 1D spectra.
+# It reads a text file of spectral coordinates and sets the WCS.
+
+procedure t_scoords ()
+
+pointer speclist # List of spectrum image names
+pointer coordlist # List of coordinate file names
+pointer label # Coordinate axis label
+pointer units # Coordinate axis units
+
+int n, fd, open(), fscan(), nscan()
+int imtopenp, imtlen(), imtgetim(), clpopnu(), fntlenb(), fntgfnb()
+bool verbose, clgetb()
+pointer sp, spec, coords, values, im, tmp, immap()
+
+errchk immap, open, scoords
+
+begin
+ call smark (sp)
+ call salloc (spec, SZ_FNAME, TY_CHAR)
+ call salloc (coords, SZ_FNAME, TY_CHAR)
+ call salloc (label, SZ_FNAME, TY_CHAR)
+ call salloc (units, SZ_FNAME, TY_CHAR)
+
+ # Get task parameters.
+ speclist = imtopenp ("images")
+ coordlist = clpopnu ("coords")
+ call clgstr ("label", Memc[label], SZ_FNAME)
+ call clgstr ("units", Memc[units], SZ_FNAME)
+ verbose = clgetb ("verbose")
+
+ # Check for match between image and coordinate lists.
+ if (fntlenb (coordlist) > 1 && fntlenb (coordlist) != imtlen (speclist))
+ call error (1, "Image and coordinate lists do not match")
+
+ # Loop through spectrum list.
+ while (imtgetim (speclist, Memc[spec], SZ_FNAME) != EOF) {
+ if (fntgfnb (coordlist, Memc[coords], SZ_FNAME) == EOF)
+ ;
+
+ iferr {
+ im = NULL
+ fd = NULL
+
+ # Open the image.
+ tmp = immap (Memc[spec], READ_WRITE, 0); im = tmp
+
+ # Get the coordinate values.
+ tmp = open (Memc[coords], READ_ONLY, TEXT_FILE); fd = tmp
+ call salloc (values, IM_LEN(im,1)+1, TY_DOUBLE)
+ n = 0
+ while (fscan(fd) != EOF) {
+ call gargd (Memd[values+n])
+ if (nscan() == 1)
+ n = n + 1
+ if (n > IM_LEN(im,1))
+ break
+ }
+ if (n != IM_LEN(im,1))
+ call error (1, "Wrong number of coordinate values in file")
+
+ # Create the WCS
+ if (verbose) {
+ call printf ("SCOORDS: ")
+ call printf (
+ "Setting coordinates for %s from coordinate file %s.\n")
+ call pargstr (Memc[spec])
+ call pargstr (Memc[coords])
+ }
+ call scoords (im, Memc[label], Memc[units], Memd[values])
+ } then
+ call erract (EA_WARN)
+
+ # Close files.
+ if (im != NULL)
+ call imunmap (im)
+ if (fd != NULL)
+ call close (fd)
+ }
+
+ call imtclose (speclist)
+ call fntclsb (coordlist)
+ call sfree (sp)
+end
+
+
+
+# SCOORDS -- Make a multispec pixel array coordinate system.
+# This is currently limited to 1D spectra.
+
+procedure scoords (im, label, units, waves)
+
+pointer im #I Imageio I/O pointer (must be 1D image)
+char label[ARB] #I Axis label (e.g. "Wavelength")
+char units[ARB] #I Axis units (e.g. "Angstroms")
+double waves[ARB] #I Array of dispersion coordinates
+
+int i, n, fd, stropen()
+double dw
+pointer sp, coeffs, mw, mw_open()
+
+int axes[6]
+data axes/1, 2, 1, 0, 0, 0/
+
+errchk mw_open, mw_saveim, stropen
+
+begin
+ call smark (sp)
+
+ if (IM_NDIM(im) != 1)
+ call error (1, "scoords: image must be one dimensional")
+
+ # Initialize the MWCS.
+ mw = mw_open (NULL, 2)
+ call mw_newsystem (mw, "multispec", 2)
+ call mw_swtype (mw, axes, 2, "multispec", "")
+ if (label[1] != EOS)
+ call mw_swattrs (mw, 1, "label", label)
+ if (units[1] != EOS)
+ call mw_swattrs (mw, 1, "units", label)
+ call mw_saxmap (mw, axes[3], axes[4], 2)
+
+ # Setup multispec coefficient string.
+ n = IM_LEN(im,1)
+ i = 20 * (n + 6)
+ call salloc (coeffs, i, TY_CHAR)
+ call aclrc (Memc[coeffs], i)
+ fd = stropen (Memc[coeffs], i, NEW_FILE)
+
+ # Set the common attribute parameters.
+ dw = (waves[n] - waves[1]) / (n - 1)
+ call fprintf (fd, "%d %d %d %g %g %d %g %g %g ")
+ call pargi (1) # Aperture number
+ call pargi (1) # Beam number
+ call pargi (2) # Dispersion type (2=non-linear)
+ call pargd (waves[1]) # Starting coordinate
+ call pargd (dw) # Average dispersion
+ call pargi (n) # Number of pixels
+ call pargd (0D0) # Redshift
+ call pargd (INDEFD) # Aperture limit
+ call pargd (INDEFD) # Aperture limit
+
+ # Set the general non-linear function parameters.
+ call fprintf (fd, "%g %g %d ")
+ call pargd (1D0) # Function weight
+ call pargd (0D0) # Zero point shift
+ call pargi (5) # Function type (5=pixel array)
+
+ # Set the pixel array function values.
+ call fprintf (fd, "%d")
+ call pargi (n) # Number of pixels
+ do i = 1, n {
+ if (i > 2) {
+ if ((waves[i]-waves[i-1]) * dw <= 0) {
+ call strclose (fd)
+ call mw_close (mw)
+ call sfree (sp)
+ call error (1, "Coordinates are not monotonic")
+ }
+ }
+
+ call fprintf (fd, " %g")
+ call pargd (waves[i]) # Coordinates
+ }
+
+ # Write the attribute.
+ call strclose (fd)
+ call mw_swattrs (mw, 2, "spec1", Memc[coeffs])
+
+ # Store the WCS in the image header.
+ call mw_saveim (mw, im)
+ call mw_close (mw)
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_sfit.x b/noao/onedspec/t_sfit.x
new file mode 100644
index 00000000..54e896bb
--- /dev/null
+++ b/noao/onedspec/t_sfit.x
@@ -0,0 +1,986 @@
+include <imhdr.h>
+include <pkg/gtools.h>
+include <pkg/rg.h>
+include <math/curfit.h>
+include <error.h>
+include <smw.h>
+
+# SFIT -- Fit a function to spectra and output the fit, difference or
+# ratio; or print the power series coefficients of the fit. The fitting
+# parameters may be set interactively using the icfit package.
+
+# Image header keywords for saving the previous fit
+
+define SFT_KW "SFIT"
+define SFT_KWB "SFITB"
+
+# Choices for the type of output
+
+define OUT_TYPES "|data|fit|difference|ratio|"
+
+define DATA 1
+define FIT 2
+define DIFFERENCE 3
+define RATIO 4
+
+# Choices for the interactive prompts
+# (the 1st define is for clgwrd (strdic), the 2nd for CL enumeration)
+# Note that the CL assumes that the separator is `|'.
+
+define SFT_ANS1 "|yes|no|skip|YES|NO|SKIP|"
+define SFT_ANS1X "yes|no|skip|YES|NO|SKIP"
+
+define SFT_ANS2 "|spectrum|image|all|cancel|"
+define SFT_ANS2X "spectrum|image|all|cancel"
+
+define LEN_ANS 7
+
+define YES_ONCE 1
+define NO_ONCE 2
+define SKIP_ONCE 3
+define YES_ALWAYS 4
+define NO_ALWAYS 5
+define SKIP_ALWAYS 6
+
+define SKIP_SPEC 1
+define SKIP_IMAGE 2
+define SKIP_ALL 3
+define SKIP_CANCEL 4
+
+# Switches and pointers
+
+define SFT_OFF 22
+
+define INTERACTIVE Memi[$1] # all spectra are noninteractive
+define REPLACE Memi[$1+1] # replace rejected points?
+define WAVESCALE Memi[$1+2] # X is wavelength if possible
+define LOGSCALE Memi[$1+3] # axes are logarithmic
+define OVERRIDE Memi[$1+4] # allow lines to be redone
+define LISTONLY Memi[$1+5] # don't modify images
+define OUTTYPE Memi[$1+6] # output type code
+
+define GRAPH_OPEN Memi[$1+7] # keep track of gopen
+define LOG_TO_STDOUT Memi[$1+8] # STDOUT/ERR is used
+define PROMPT Memi[$1+9] # prompt flag
+define QUIET Memi[$1+10] # quiet flag
+
+define RGIN Memi[$1+11] # lines specified
+define RGFIT Memi[$1+12] # all lines to fit
+define RGREFIT Memi[$1+13] # those to fit again
+define RGINB Memi[$1+14] # bands specified
+define RGFITB Memi[$1+15] # all bands to fit
+define RGREFITB Memi[$1+16] # those to fit again
+
+define NLOGFD Memi[$1+17] # number of logfiles
+define LOGFD Memi[$1+18] # array of logfiles
+
+define IC Memi[$1+19] # current ic descriptor
+define YMAX Memi[$1+20] # max number of lines
+define BMAX Memi[$1+21] # max number of lines
+define IC_DESC Memi[$1+SFT_OFF+($3-1)*YMAX($1)+$2-1] # ic descriptors
+
+
+# T_SFIT -- Entry point for the task. Read parameters,
+# initialize structures and loop over the image templates.
+
+procedure t_sfit ()
+
+pointer listin, listout, input, output, graphics
+pointer sf, gp, gt, in, out, mw, sh, sp
+int stat
+
+int sft_icfit(), imtgetim(), gt_init(), imtlen()
+bool clgetb()
+pointer imtopenp()
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (graphics, SZ_FNAME, TY_CHAR)
+
+ # Open the image templates
+ listin = imtopenp ("input")
+
+ if (clgetb ("listonly"))
+ listout = NULL
+ else {
+ listout = imtopenp ("output")
+ if (imtlen (listin) != imtlen (listout)) {
+ call imtclose (listin)
+ call imtclose (listout)
+ call sfree (sp)
+ call error (1, "Input and output image lists do not match")
+ }
+ }
+
+ # Initialize the various descriptors
+ iferr (call sft_init (listin, listout, sf)) {
+ call imtclose (listin)
+ if (listout != NULL)
+ call imtclose (listout)
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+
+ # The graphics pointers are passed explicitly
+ if (INTERACTIVE(sf) == YES) {
+ call clgstr ("graphics", Memc[graphics], SZ_FNAME)
+ gt = gt_init()
+ call gt_sets (gt, GTTYPE, "line")
+ }
+
+ # Fit the lines in each input image.
+
+ while (imtgetim (listin, Memc[input], SZ_FNAME) != EOF) {
+
+ if (listout != NULL)
+ stat = imtgetim (listout, Memc[output], SZ_FNAME)
+ else
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ iferr {
+ call sft_immap (Memc[input], Memc[output],
+ in, out, mw, sh, sf, gp)
+ } then {
+ call erract (EA_WARN)
+ next
+ }
+
+ stat = sft_icfit (in, out, mw, sh, sf, gp, gt, Memc[graphics])
+
+ call sft_unmap (in, out, mw, sh, sf)
+
+ if (stat == EOF)
+ break
+ }
+
+ if (INTERACTIVE(sf) == YES)
+ call gt_free (gt)
+ if (GRAPH_OPEN(sf) == YES)
+ call gclose (gp)
+
+ call sft_close (sf)
+ call imtclose (listin)
+ if (listout != NULL)
+ call imtclose (listout)
+ call sfree (sp)
+end
+
+
+# SFT_INIT -- Initialize templates, ranges, logfiles, type, icfit descriptors.
+
+procedure sft_init (listin, listout, sf)
+
+pointer listin, listout #I Image template descriptors
+pointer sf #I Pointer to task switches
+
+pointer input, output, im, sp, mw
+int ymax, bmax, i, j
+
+real clgetr()
+bool clgetb()
+int clgwrd(), clgeti(), btoi(), strlen()
+int rg_next(), imtgetim(), imaccess(), xt_logopen()
+pointer rg_ranges(), immap(), smw_openim()
+errchk immap, smw_openim
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_LINE, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+
+ sf = NULL
+ iferr {
+
+ # find the maximum number of lines and bands (spectra)
+ ymax = 0
+ bmax = 0
+
+ while (imtgetim (listin, Memc[input], SZ_LINE) != EOF)
+ if (imaccess (Memc[input], READ_ONLY) == YES) {
+ im = immap (Memc[input], READ_ONLY, 0)
+ mw = smw_openim (im)
+ ymax = max (SMW_LLEN(mw,2), ymax)
+ bmax = max (SMW_LLEN(mw,3), bmax)
+ call smw_close (mw)
+ call imunmap (im)
+ }
+ call imtrew (listin)
+
+ if (listout != NULL) {
+ while (imtgetim (listout, Memc[output], SZ_FNAME) != EOF)
+ if (imaccess (Memc[output], READ_ONLY) == YES) {
+ im = immap (Memc[output], READ_ONLY, 0)
+ mw = smw_openim (im)
+ ymax = max (SMW_LLEN(mw,2), ymax)
+ bmax = max (SMW_LLEN(mw,3), bmax)
+ call smw_close (mw)
+ call imunmap (im)
+ }
+ call imtrew (listout)
+ }
+
+ # allocate space for the task switch structure
+ call malloc (sf, SFT_OFF + ymax * bmax, TY_STRUCT)
+
+ YMAX(sf) = ymax
+ BMAX(sf) = bmax
+
+ # NULL the pointers for error handling
+ RGIN(sf) = NULL
+ RGINB(sf) = NULL
+ NLOGFD(sf) = 0
+ do j = 1, BMAX(sf)
+ do i = 1, YMAX(sf)
+ IC_DESC(sf,i,j) = NULL
+
+ # Set the switches
+ INTERACTIVE(sf) = btoi (clgetb ("interactive"))
+ REPLACE(sf) = btoi (clgetb ("replace"))
+ WAVESCALE(sf) = btoi (clgetb ("wavescale"))
+ LOGSCALE(sf) = btoi (clgetb ("logscale"))
+ OVERRIDE(sf) = btoi (clgetb ("override"))
+ LISTONLY(sf) = btoi (clgetb ("listonly"))
+ GRAPH_OPEN(sf) = NO
+ PROMPT(sf) = INTERACTIVE(sf)
+ QUIET(sf) = btoi (INTERACTIVE(sf) == NO)
+
+ # Expand the range specification, allow either hyphens or colons
+
+ call clgstr ("lines", Memc[input], SZ_LINE)
+ do i = 1, strlen (Memc[input])
+ if (Memc[input+i-1] == '-')
+ Memc[input+i-1] = ':'
+ else if (Memc[input+i-1] == 'x' || Memc[input+i-1] == 'X')
+ call error (1, "Range step (`x' notation) not implemented")
+
+ RGIN(sf) = rg_ranges (Memc[input], 1, YMAX(sf))
+ call rg_order (RGIN(sf))
+ call rg_merge (RGIN(sf))
+
+ call clgstr ("bands", Memc[input], SZ_LINE)
+ do i = 1, strlen (Memc[input])
+ if (Memc[input+i-1] == '-')
+ Memc[input+i-1] = ':'
+ else if (Memc[input+i-1] == 'x' || Memc[input+i-1] == 'X')
+ call error (1, "Range step (`x' notation) not implemented")
+
+ RGINB(sf) = rg_ranges (Memc[input], 1, BMAX(sf))
+ call rg_order (RGINB(sf))
+ call rg_merge (RGINB(sf))
+
+ i = 0
+ j = 0
+ if (rg_next (RGIN(sf), i) == EOF || rg_next (RGINB(sf), j) == EOF)
+ call error (1, "With range specification for `lines or bands'")
+ else {
+ # Open the initial icfit descriptor
+ call ic_open (IC(sf))
+
+ call clgstr ("sample", Memc[input], SZ_LINE)
+ call ic_pstr (IC(sf), "sample", Memc[input])
+ call clgstr ("function", Memc[input], SZ_LINE)
+ call ic_pstr (IC(sf), "function", Memc[input])
+
+ call ic_puti (IC(sf), "naverage", clgeti ("naverage"))
+ call ic_puti (IC(sf), "order", clgeti ("order"))
+ call ic_putr (IC(sf), "low", clgetr ("low_reject"))
+ call ic_putr (IC(sf), "high", clgetr ("high_reject"))
+ call ic_puti (IC(sf), "niterate", clgeti ("niterate"))
+ call ic_putr (IC(sf), "grow", clgetr ("grow"))
+ call ic_puti (IC(sf), "markrej", btoi (clgetb ("markrej")))
+
+ IC_DESC(sf,i,j) = IC(sf)
+ }
+
+ # Get the desired output type
+ OUTTYPE(sf) = clgwrd ("type", Memc[input], SZ_LINE, OUT_TYPES)
+
+ # Open the logfiles
+ NLOGFD(sf) = xt_logopen ("logfiles", "SFIT:", LOGFD(sf),
+ LOG_TO_STDOUT(sf))
+
+ } then {
+ call sfree (sp)
+ call sft_close (sf)
+ call erract (EA_ERROR)
+ }
+
+ call sfree (sp)
+ return
+end
+
+
+# SFT_CLOSE -- Close the various descriptors.
+
+procedure sft_close (sf)
+
+pointer sf #I Pointer to task switches
+
+int i, j
+
+begin
+ if (sf != NULL) {
+ if (RGIN(sf) != NULL)
+ call rg_free (RGIN(sf))
+ if (RGINB(sf) != NULL)
+ call rg_free (RGINB(sf))
+ if (NLOGFD(sf) != 0)
+ call xt_logclose (LOGFD(sf), NLOGFD(sf), "END:")
+ do j = 1, BMAX(sf)
+ do i = 1, YMAX(sf)
+ if (IC_DESC(sf,i,j) != NULL)
+ call ic_closer (IC_DESC(sf,i,j))
+ call mfree (sf, TY_STRUCT)
+ }
+end
+
+
+# SFT_IMMAP -- Map images for sfit.
+
+procedure sft_immap (input, output, in, out, mw, sh, sf, gp)
+
+char input[ARB] #I Input image name
+char output[ARB] #I Output image name
+pointer in, out #O IMIO pointers
+pointer mw #O MWCS pointer
+pointer sh #O SHDR pointer
+pointer sf #I Pointer for task switches
+pointer gp #I GIO pointer
+
+int i, ax1, ax2, ax3
+pointer inroot, insect, outroot, outsect, b1, b2
+pointer sp, inranges, outranges
+pointer rgin, rgout, rgtmp, rgtmpb
+long v1[IM_MAXDIM], v2[IM_MAXDIM]
+char emsg[SZ_LINE]
+
+int imaccess(), imaccf(), imgnlr(), impnlr(), strcmp()
+pointer immap(), smw_openim()
+pointer rg_ranges(), rg_window(), rg_union(), rg_intersect()
+errchk immap, smw_openim
+
+define err_ 13
+
+begin
+ call smark (sp)
+ call salloc (inroot, SZ_FNAME, TY_CHAR)
+ call salloc (insect, SZ_FNAME, TY_CHAR)
+ call salloc (outroot, SZ_FNAME, TY_CHAR)
+ call salloc (outsect, SZ_FNAME, TY_CHAR)
+ call salloc (inranges, SZ_LINE, TY_CHAR)
+ call salloc (outranges, SZ_LINE, TY_CHAR)
+
+ in = NULL
+ out = NULL
+ mw = NULL
+ sh = NULL
+ RGFIT(sf) = NULL
+ RGREFIT(sf) = NULL
+ RGFITB(sf) = NULL
+ RGREFITB(sf) = NULL
+
+ call imgimage (input, Memc[inroot], SZ_FNAME)
+ call imgsection (input, Memc[insect], SZ_FNAME)
+
+ call imgimage (output, Memc[outroot], SZ_FNAME)
+ call imgsection (output, Memc[outsect], SZ_FNAME)
+
+ if (Memc[insect] != EOS || Memc[outsect] != EOS) {
+
+ call sprintf (emsg, SZ_LINE, "Sections not allowed (%s --> %s)")
+ call pargstr (input)
+ call pargstr (output)
+ goto err_
+
+ } else if (imaccess (Memc[inroot], READ_ONLY) == NO) {
+
+ call sprintf (emsg, SZ_LINE, "Cannot access %s")
+ call pargstr (input)
+ goto err_
+
+ } else if (LISTONLY(sf) == YES) {
+
+ # The `out = in' allows the ranges code at the end of this
+ # procedure to cover all cases (with a little inefficiency).
+ # No check on the sizes of the input and output images.
+
+ in = immap (Memc[inroot], READ_ONLY, 0)
+ out = in
+
+ } else if (strcmp (Memc[inroot], Memc[outroot]) == 0) {
+
+ # Overwrite the input image.
+ in = immap (Memc[inroot], READ_WRITE, 0)
+ out = in
+
+ } else if (imaccess (Memc[outroot], READ_WRITE) == NO) {
+
+ in = immap (Memc[inroot], READ_ONLY, 0)
+ out = immap (Memc[outroot], NEW_COPY, in)
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+
+ # Do this since imcopy is unimplemented
+
+ call amovkl (long(1), v1, IM_MAXDIM)
+ call amovkl (long(1), v2, IM_MAXDIM)
+
+ while (imgnlr (in, b1, v1) != EOF && impnlr (out, b2, v2) != EOF)
+ call amovr (Memr[b1], Memr[b2], IM_LEN(in, 1))
+
+ } else {
+
+ in = immap (Memc[inroot], READ_ONLY, 0)
+ out = immap (Memc[outroot], READ_WRITE, 0)
+
+ # This relies on the axes beyond IM_NDIM(im) being unity
+
+ do i = 1, max (IM_NDIM(in), IM_NDIM(out))
+ if (IM_LEN(in, i) != IM_LEN(out, i)) {
+ call sprintf (emsg, SZ_LINE, "%s & %s aren't the same size")
+ call pargstr (Memc[inroot])
+ call pargstr (Memc[outroot])
+ goto err_
+ }
+
+ }
+
+ do i = 4, IM_NDIM(in)
+ if (IM_LEN(in, i) != 1) {
+ call sprintf (emsg, SZ_LINE, "Too many dimensions for %s")
+ call pargstr (Memc[inroot])
+ goto err_
+ }
+
+ if (imaccf (in, SFT_KW) == YES)
+ call imgstr (in, SFT_KW, Memc[inranges], SZ_LINE)
+ else
+ call strcpy ("", Memc[inranges], SZ_LINE)
+
+ if (imaccf (out, SFT_KW) == YES)
+ call imgstr (out, SFT_KW, Memc[outranges], SZ_LINE)
+ else {
+ call strcpy ("", Memc[outranges], SZ_LINE)
+ call imastr (out, SFT_KW, Memc[outranges])
+ }
+
+ mw = smw_openim (in)
+ ax1 = SMW_LLEN(mw,1)
+ ax2 = SMW_LLEN(mw,2)
+ ax3 = SMW_LLEN(mw,3)
+
+ rgin = rg_ranges (Memc[inranges], 1, ax2)
+ rgout = rg_ranges (Memc[outranges], 1, ax2)
+ rgtmp = rg_union (rgin, rgout)
+ call rg_free (rgin)
+ call rg_free (rgout)
+
+ if (imaccf (in, SFT_KWB) == YES)
+ call imgstr (in, SFT_KWB, Memc[inranges], SZ_LINE)
+ else
+ call strcpy ("", Memc[inranges], SZ_LINE)
+
+ if (imaccf (out, SFT_KWB) == YES)
+ call imgstr (out, SFT_KWB, Memc[outranges], SZ_LINE)
+ else {
+ call strcpy ("", Memc[outranges], SZ_LINE)
+ call imastr (out, SFT_KWB, Memc[outranges])
+ }
+
+ rgin = rg_ranges (Memc[inranges], 1, ax3)
+ rgout = rg_ranges (Memc[outranges], 1, ax3)
+ rgtmpb = rg_union (rgin, rgout)
+ call rg_free (rgin)
+ call rg_free (rgout)
+
+ if (OVERRIDE(sf) == YES) {
+ RGFIT(sf) = rg_window (RGIN(sf), 1, ax2)
+ RGREFIT(sf) = rgtmp
+ RGFITB(sf) = rg_window (RGINB(sf), 1, ax3)
+ RGREFITB(sf) = rgtmpb
+ } else {
+ call rg_inverse (rgtmp, 1, ax2)
+ RGFIT(sf) = rg_intersect (RGIN(sf), rgtmp)
+ RGREFIT(sf) = rg_ranges ("0", 1, 2)
+ call rg_free (rgtmp)
+ #call rg_inverse (rgtmpb, 1, ax3)
+ #RGFITB(sf) = rg_intersect (RGINB(sf), rgtmpb)
+ #RGREFITB(sf) = rg_ranges ("0", 1, 2)
+ #call rg_free (rgtmpb)
+ RGFITB(sf) = rg_window (RGINB(sf), 1, ax3)
+ RGREFITB(sf) = rgtmpb
+ }
+
+ if (RG_NPTS(RGFIT(sf)) <= 0) {
+ call sprintf (emsg, SZ_LINE, "No lines left to fit for %s")
+ call pargstr (Memc[inroot])
+ goto err_
+ }
+
+ call sfree (sp)
+ return
+
+err_ call sfree (sp)
+ call sft_unmap (in, out, mw, sh, sf)
+ if (GRAPH_OPEN(sf) == YES) {
+ call gclose (gp)
+ GRAPH_OPEN(sf) = NO
+ }
+ # STDERR should get flushed AFTER closing graphics
+ call error (1, emsg)
+end
+
+
+# SFT_UNMAP -- Unmap images for sfit.
+
+procedure sft_unmap (in, out, mw, sh, sf)
+
+pointer in, out #I IMIO pointers
+pointer mw #I MWCS pointer
+pointer sh #I SHDR pointer
+pointer sf #I Task structure pointer
+
+begin
+ call shdr_close (sh)
+ if (mw != NULL)
+ call smw_close (mw)
+ if (out != NULL && out != in)
+ call imunmap (out)
+ if (in != NULL)
+ call imunmap (in)
+ if (RGFIT(sf) != NULL)
+ call rg_free (RGFIT(sf))
+ if (RGREFIT(sf) != NULL)
+ call rg_free (RGREFIT(sf))
+ if (RGFITB(sf) != NULL)
+ call rg_free (RGFITB(sf))
+ if (RGREFITB(sf) != NULL)
+ call rg_free (RGREFITB(sf))
+end
+
+
+# SFT_ICFIT -- Given the image descriptors determine the fitting function
+# for each line and output the fit, difference, ratio or coefficients.
+
+int procedure sft_icfit (in, out, mw, sh, sf, gp, gt, graphics)
+
+pointer in, out #I IMIO pointers
+pointer mw #I MWCS pointer
+pointer sh #I SHDR pointer
+pointer sf #I Pointer for task switches
+pointer gp #I GIO pointer
+pointer gt #I GTOOLS pointer
+char graphics[ARB] #I Graphics device
+
+pointer sp, wts, cv, data
+int line, band, i, j, n
+
+int sft_getline()
+pointer gopen(), imps3r()
+real sft_efncr()
+extern sft_efncr
+
+begin
+ call smark (sp)
+ call salloc (wts, SMW_LLEN(mw,1), TY_REAL)
+
+ line = 0
+ band = 0
+ while (sft_getline (in, mw, sh, sf, gt, line, band) != EOF) {
+
+ call amovkr (1., Memr[wts], SN(sh))
+
+ if (QUIET(sf) == NO) {
+ if (GRAPH_OPEN(sf) == NO) {
+ gp = gopen (graphics, NEW_FILE, STDGRAPH)
+ GRAPH_OPEN(sf) = YES
+ }
+ call icg_fit (IC(sf), gp, "cursor", gt, cv, Memr[SX(sh)],
+ Memr[SY(sh)], Memr[wts], SN(sh))
+ } else
+ call ic_fit (IC(sf), cv, Memr[SX(sh)], Memr[SY(sh)],
+ Memr[wts], SN(sh), YES, YES, YES, YES)
+
+ if (LISTONLY(sf) == NO) {
+ i = LINDEX(sh,1)
+ j = LINDEX(sh,2)
+ n = SMW_LLEN(mw,1)
+ switch (SMW_LAXIS(mw,1)) {
+ case 1:
+ data = imps3r (out, 1, n, i, i, j, j)
+ case 2:
+ data = imps3r (out, i, i, 1, n, j, j)
+ case 3:
+ data = imps3r (out, i, i, j, j, 1, n)
+ }
+ if (SN(sh) < n)
+ call aclrr (Memr[data], n)
+
+ switch (OUTTYPE(sf)) {
+ case DATA:
+ if (REPLACE(sf) == YES)
+ call ic_clean (IC(sf), cv, Memr[SX(sh)], Memr[SY(sh)],
+ Memr[wts], SN(sh))
+ call amovr (Memr[SY(sh)], Memr[data], SN(sh))
+ call sft_update (out, mw, line, band)
+ case FIT:
+ call cvvector (cv, Memr[SX(sh)], Memr[data], SN(sh))
+ call sft_update (out, mw, line, band)
+ case DIFFERENCE:
+ call cvvector (cv, Memr[SX(sh)], Memr[data], SN(sh))
+ if (REPLACE(sf) == YES)
+ call ic_clean (IC(sf), cv, Memr[SX(sh)], Memr[SY(sh)],
+ Memr[wts], SN(sh))
+ call asubr (Memr[SY(sh)], Memr[data], Memr[data], SN(sh))
+ call sft_update (out, mw, line, band)
+ case RATIO:
+ call cvvector (cv, Memr[SX(sh)], Memr[data], SN(sh))
+ if (REPLACE(sf) == YES)
+ call ic_clean (IC(sf), cv, Memr[SX(sh)], Memr[SY(sh)],
+ Memr[wts], SN(sh))
+ call advzr (Memr[SY(sh)], Memr[data], Memr[data], SN(sh),
+ sft_efncr)
+ call sft_update (out, mw, line, band)
+ default:
+ call error (1, "bad switch in sft_icfit")
+ }
+ }
+
+ call sft_power (in, line, cv, gp, sf)
+ call cvfree (cv)
+
+ }
+
+ # This terminates the cursor (GIN) mode echoplex suppression in
+ # case the next sft_immap generates a password prompt from ZFIOKS.
+ # Note that any such password prompt (from the kernel!) will
+ # now show up on the status line, not the graphics plane.
+
+ if (GRAPH_OPEN(sf) == YES) {
+ call printf ("\r")
+ call flush (STDOUT)
+ }
+
+ call sfree (sp)
+ return (line)
+end
+
+
+# SFT_GETLINE -- Get image data to be fit. Returns the line and band numbers.
+# Returns EOF when done.
+
+int procedure sft_getline (in, mw, sh, sf, gt, line, band)
+
+pointer in #I IMIO pointer
+pointer mw #I MWCS pointer
+pointer sh #I SHDR pointer
+pointer sf #I Pointer for task switches
+pointer gt #I GTOOLS pointer
+int line #U Line number
+int band #U Band number
+
+int i
+bool waveok
+char ask[LEN_ANS]
+pointer linebuf, rg1, rg2, sp
+
+int clgwrd(), rg_next(), rg_inrange()
+pointer rg_ranges(), rg_intersect()
+real sft_efncr()
+extern sft_efncr
+errchk shdr_open
+
+define again_ 99
+
+begin
+ call smark (sp)
+ call salloc (linebuf, SZ_LINE, TY_CHAR)
+
+ if (band == 0)
+ if (rg_next (RGFITB(sf), band) == EOF)
+ return (EOF)
+
+again_ if (rg_next (RGFIT(sf), line) == EOF) {
+ line = 0
+ if (rg_next (RGFITB(sf), band) == EOF)
+ return (EOF)
+ goto again_
+ }
+
+ if (PROMPT(sf) == YES) {
+ call clprintf ("ask.p_min", "%s")
+ call pargstr (SFT_ANS1X)
+
+ if (rg_inrange (RGREFIT(sf), line) == YES &&
+ rg_inrange (RGREFITB(sf), band) == YES) {
+ call clprintf ("ask.p_prompt",
+ "Refit [%d,%d] of %s w/ graph? ")
+ } else {
+ call clprintf ("ask.p_prompt",
+ "Fit [%d,%d] of %s w/ graph? ")
+ }
+ call pargi (line)
+ call pargi (band)
+ call pargstr (IM_HDRFILE(in))
+
+ switch (clgwrd ("ask", ask, LEN_ANS, SFT_ANS1)) {
+
+ case YES_ONCE:
+ QUIET(sf) = NO
+
+ case NO_ONCE:
+ QUIET(sf) = YES
+
+ case SKIP_ONCE:
+ goto again_
+
+ case YES_ALWAYS:
+ QUIET(sf) = NO
+ PROMPT(sf) = NO
+
+ case NO_ALWAYS:
+ QUIET(sf) = YES
+ PROMPT(sf) = NO
+
+ case SKIP_ALWAYS:
+ call clprintf ("ask", "cancel")
+ call clprintf ("ask.p_min", "%s")
+ call pargstr (SFT_ANS2X)
+ call clprintf ("ask.p_prompt",
+ "Skip what? (`all' to exit task) ")
+
+ switch (clgwrd ("ask", ask, LEN_ANS, SFT_ANS2)) {
+
+ case SKIP_SPEC:
+ call clprintf ("ask", "yes")
+ # delete the spectrum from the list
+ call sprintf (Memc[linebuf], SZ_LINE, "%d")
+ call pargi (line)
+
+ rg1 = rg_ranges (Memc[linebuf], 1, SMW_LLEN(mw,2))
+ call rg_inverse (rg1, 1, SMW_LLEN(mw,2))
+ rg2 = rg_intersect (RGIN(sf), rg1)
+ call rg_free (rg1)
+ call rg_free (RGIN(sf))
+
+ RGIN(sf) = rg2
+ goto again_
+
+ case SKIP_IMAGE:
+ call clprintf ("ask", "yes")
+ return (EOF)
+
+ case SKIP_ALL:
+ call clprintf ("ask", "yes")
+ return (EOF)
+
+ case SKIP_CANCEL:
+ call clprintf ("ask", "yes")
+ line = line - 1
+ goto again_
+
+ default:
+ call error (1, "bad switch (2) in sft_getline")
+
+ }
+
+ default:
+ call error (1, "bad switch (1) in sft_getline")
+
+ }
+
+ }
+
+ call shdr_open (in, mw, line, band, INDEFI, SHDATA, sh)
+
+ if (LOGSCALE(sf) == YES)
+ call alogr (Memr[SY(sh)], Memr[SY(sh)], SN(sh), sft_efncr)
+
+ if (WAVESCALE(sf) == YES) {
+ waveok = true
+ } else
+ waveok = false
+
+ if (!waveok)
+ do i = 1, SN(sh)
+ Memr[SX(sh)+i-1] = i
+
+ if (LOGSCALE(sf) == YES)
+ call alogr (Memr[SX(sh)], Memr[SX(sh)], SN(sh), sft_efncr)
+
+ # Initialize and/or update the icfit descriptor
+
+ if (IC_DESC(sf,line,band) == NULL) {
+ call ic_open (IC_DESC(sf,line,band))
+ call ic_copy (IC(sf), IC_DESC(sf,line,band))
+ #call ic_pstr (IC_DESC(sf,line,band), "sample", "*")
+ }
+
+ IC(sf) = IC_DESC(sf,line,band)
+
+ call ic_putr (IC(sf), "xmin", min (Memr[SX(sh)], Memr[SX(sh)+SN(sh)-1]))
+ call ic_putr (IC(sf), "xmax", max (Memr[SX(sh)], Memr[SX(sh)+SN(sh)-1]))
+
+ if (QUIET(sf) == NO) {
+ if (waveok && LOGSCALE(sf) == YES) {
+ call ic_pstr (IC(sf), "xlabel", "log wavelength")
+ call ic_pstr (IC(sf), "ylabel", "log data")
+ } else if (LOGSCALE(sf) == YES) {
+ call ic_pstr (IC(sf), "xlabel", "log column")
+ call ic_pstr (IC(sf), "ylabel", "log data")
+ } else if (waveok) {
+ call ic_pstr (IC(sf), "xlabel", "wavelength")
+ call ic_pstr (IC(sf), "ylabel", "")
+ } else {
+ call ic_pstr (IC(sf), "xlabel", "column")
+ call ic_pstr (IC(sf), "ylabel", "")
+ }
+
+ call sprintf (Memc[linebuf], SZ_LINE, "%s, [%d,%d]\n%s")
+ call pargstr (IM_HDRFILE(in))
+ call pargi (line)
+ call pargi (band)
+ call pargstr (TITLE(sh))
+
+ call gt_sets (gt, GTTITLE, Memc[linebuf])
+ }
+
+ call sfree (sp)
+ return (OK)
+end
+
+
+# SFT_EFNCR -- Called by advzr on division by zero or by alogr for a
+# zero or negative argument.
+
+real procedure sft_efncr (x)
+
+real x
+
+begin
+ return (0.)
+end
+
+
+# SFT_POWER -- Transform the curfit output into a power series and
+# print the coefficients to the logfiles. This should be modified to
+# print the errors as well. That requires modifying the curfit routine
+# cvpower to deal with errors; and adding an icfit routine (or include
+# file define) that allows access to the dynamic arrays of sample points
+# that are initialized if the sample is less than the whole set of points.
+
+procedure sft_power (im, line, cv, gp, sf)
+
+pointer im #I IMIO descriptor for labeling
+int line #I Image line number for labeling
+pointer cv #I CURFIT pointer
+pointer gp #I GIO pointer for tidy output
+pointer sf #I Pointer for task switches
+
+pointer ps_coeff, linebuf, sp
+int ncoeffs, i, j, fd
+
+int cvstati(), strcmp()
+
+begin
+ if (NLOGFD(sf) <= 0)
+ return
+
+ call smark (sp)
+ call salloc (linebuf, SZ_LINE, TY_CHAR)
+
+ # cvpower only works with legendre or chebyshev functions
+
+ call ic_gstr (IC(sf), "function", Memc[linebuf], SZ_LINE)
+ if (strcmp (Memc[linebuf], "legendre") != 0 &&
+ strcmp (Memc[linebuf], "chebyshev") != 0) {
+ call sfree (sp)
+ return
+ }
+
+ if (GRAPH_OPEN(sf) == YES && LOG_TO_STDOUT(sf) == YES) {
+ call gclose (gp)
+ GRAPH_OPEN(sf) = NO
+ }
+
+ ncoeffs = cvstati (cv, CVNCOEFF)
+ call salloc (ps_coeff, ncoeffs, TY_REAL)
+ call cvpower (cv, Memr[ps_coeff], ncoeffs)
+
+ do i = 1, NLOGFD(sf) {
+ fd = Memi[LOGFD(sf)+i-1]
+
+ call fprintf (fd, "Line %d of %s:\n\n")
+ call pargi (line)
+ call pargstr (IM_HDRFILE(im))
+
+ call fprintf (fd, " coeff value\n")
+
+ do j = 1, ncoeffs {
+ call fprintf (fd, "\t%d\t%12.5e\n")
+ call pargi (j)
+ call pargr (Memr[ps_coeff+j-1])
+ }
+
+ call fprintf (fd, "\n")
+ call flush (fd)
+ }
+
+ call sfree (sp)
+end
+
+
+# SFT_UPDATE -- Update the keyword with completed spectrum. Flush the pixels.
+
+procedure sft_update (im, mw, line, band)
+
+pointer im #I IMIO pointer
+pointer mw #I MWCS pointer
+int line #I Line just completed
+int band #I Band just completed
+
+pointer linebuf, rg1, rg2, rgold, sp
+
+pointer rg_ranges(), rg_union()
+
+begin
+ call smark (sp)
+ call salloc (linebuf, SZ_LINE, TY_CHAR)
+
+ # this could be recoded to use "rg_add"
+
+ call sprintf (Memc[linebuf], SZ_LINE, "%d")
+ call pargi (line)
+ rg1 = rg_ranges (Memc[linebuf], 1, SMW_LLEN(mw,2))
+
+ call imgstr (im, SFT_KW, Memc[linebuf], SZ_LINE)
+ rg2 = rg_ranges (Memc[linebuf], 1, SMW_LLEN(mw,2))
+
+ rgold = rg_union (rg1, rg2)
+ call rg_encode (rgold, Memc[linebuf], SZ_LINE)
+ call impstr (im, SFT_KW, Memc[linebuf])
+
+ call rg_free (rg1)
+ call rg_free (rg2)
+ call rg_free (rgold)
+
+ call sprintf (Memc[linebuf], SZ_LINE, "%d")
+ call pargi (band)
+ rg1 = rg_ranges (Memc[linebuf], 1, SMW_LLEN(mw,3))
+
+ call imgstr (im, SFT_KWB, Memc[linebuf], SZ_LINE)
+ rg2 = rg_ranges (Memc[linebuf], 1, SMW_LLEN(mw,3))
+
+ rgold = rg_union (rg1, rg2)
+ call rg_encode (rgold, Memc[linebuf], SZ_LINE)
+ call impstr (im, SFT_KWB, Memc[linebuf])
+
+ call rg_free (rg1)
+ call rg_free (rg2)
+ call rg_free (rgold)
+
+ call imflush (im)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_sflip.x b/noao/onedspec/t_sflip.x
new file mode 100644
index 00000000..b14e2ae0
--- /dev/null
+++ b/noao/onedspec/t_sflip.x
@@ -0,0 +1,145 @@
+include <error.h>
+include <imhdr.h>
+include <smw.h>
+
+
+# SFLIP -- Flip data and/or coordinate system in spectra.
+
+procedure t_sflip ()
+
+pointer inlist # Input list
+pointer outlist # Output list
+bool coord_flip # Flip coordinates?
+bool data_flip # Flip data?
+
+bool in_place
+int i, j, k, n, axis
+pointer sp, input, output, temp, a, b, c
+pointer in, out, smw, mw, tmp, inbuf, outbuf
+
+bool clgetb(), streq()
+int imtgetim(), imtlen()
+pointer imtopenp(), immap(), smw_openim(), imgl3r(), impl3r()
+errchk immap, smw_openim
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_FNAME, TY_CHAR)
+ call salloc (a, 3*3, TY_DOUBLE)
+ call salloc (b, 3, TY_DOUBLE)
+ call salloc (c, 3, TY_DOUBLE)
+
+ # Get task parameters.
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ coord_flip = clgetb ("coord_flip")
+ data_flip = clgetb ("data_flip")
+
+ # Loop over all input images.
+ in = NULL
+ out = NULL
+ smw = NULL
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtlen (outlist) > 0) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ break
+ } else
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+ if (streq (Memc[input], Memc[output])) {
+ if (data_flip) {
+ in_place = false
+ call mktemp ("temp", Memc[temp], SZ_FNAME)
+ } else
+ in_place = true
+ } else {
+ in_place = false
+ call strcpy (Memc[output], Memc[temp], SZ_FNAME)
+ }
+
+ iferr {
+ # Map the images and WCS.
+ if (in_place) {
+ tmp = immap (Memc[input], READ_WRITE, 0); in = tmp
+ out = in
+ } else {
+ tmp = immap (Memc[input], READ_ONLY, 0); in = tmp
+ tmp = immap (Memc[temp], NEW_COPY, in); out = tmp
+ }
+ tmp = smw_openim (in); smw = tmp
+
+ # Flip coordinates.
+ if (coord_flip) {
+ mw = SMW_MW(smw,0)
+ n = SMW_PDIM(smw)
+ axis = SMW_PAXIS(smw,1) - 1
+ call mw_gltermd (mw, Memd[a], Memd[b], n)
+ Memd[a+axis*(n+1)] = -Memd[a+axis*(n+1)]
+ Memd[b+axis] = SMW_LLEN(smw,1) - Memd[b+axis] + 1
+ call mw_sltermd (mw, Memd[a], Memd[b], n)
+ call smw_saveim (smw, out)
+ }
+
+ # Flip data.
+ if (data_flip) {
+ n = IM_LEN(in,1)
+ do j = 1, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ inbuf = imgl3r (in, i, j)
+ switch (SMW_FORMAT(smw)) {
+ case SMW_ND:
+ switch (SMW_LAXIS(smw,1)) {
+ case 1:
+ outbuf = impl3r (out, i, j) + n - 1
+ do k = 0, n-1
+ Memr[outbuf-k] = Memr[inbuf+k]
+ case 2:
+ outbuf = impl3r (out, IM_LEN(in,2)-i+1, j)
+ call amovr (Memr[inbuf], Memr[outbuf], n)
+ case 3:
+ outbuf = impl3r (out, i, IM_LEN(in,3)-j+1)
+ call amovr (Memr[inbuf], Memr[outbuf], n)
+ }
+ case SMW_ES, SMW_MS:
+ outbuf = impl3r (out, i, j) + n - 1
+ do k = 0, n-1
+ Memr[outbuf-k] = Memr[inbuf+k]
+ }
+ }
+ }
+ } else if (!in_place) {
+ n = IM_LEN(in,1)
+ do j = 1, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ inbuf = imgl3r (in, i, j)
+ outbuf = impl3r (out, i, j)
+ call amovr (Memr[inbuf], Memr[outbuf], n)
+ }
+ }
+ }
+ } then {
+ if (!in_place && out != NULL) {
+ call imunmap (out)
+ call imdelete (Memc[temp])
+ }
+ call erract (EA_WARN)
+ }
+
+ if (smw != NULL)
+ call smw_close (smw)
+ if (!in_place && out != NULL) {
+ call imunmap (out)
+ call imunmap (in)
+ if (streq (Memc[input], Memc[output])) {
+ call imdelete (Memc[input])
+ call imrename (Memc[temp], Memc[output])
+ }
+ } else if (in != NULL)
+ call imunmap (in)
+ }
+
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_sinterp.x b/noao/onedspec/t_sinterp.x
new file mode 100644
index 00000000..2275a42b
--- /dev/null
+++ b/noao/onedspec/t_sinterp.x
@@ -0,0 +1,232 @@
+include <imhdr.h>
+include <math/curfit.h>
+
+# Interpolation mode
+define SI_LINEAR 1
+define SI_CURVES 2
+define SI_LEGENDRE 3
+define SI_CHEBYSHEV 4
+define SI_SPLINE3 5
+define SI_SPLINE1 6
+
+# T_SINTERP -- Interpolate for values in a table and optionally generate
+# a spectral image
+#
+# A table of x,y pairs contained in a file is used to
+# find interpolated values, y, for any other given independent
+# variable, x. Extrapolation is performed if necessary.
+#
+# A series of values may be generated to generate a fine grid
+# through a coarse sampling for purposes of plotting. This is
+# done by setting the hidden parameter curve_gen to yes.
+# The starting point, ending point, and sampling interval
+# are also needed in this case (x1, x2, dx).
+#
+# If only a small number of values are needed to be interpolated
+# from the table, the user may enter a number of x's from either
+# a file or STDIN.
+
+procedure t_sinterp()
+
+real x, y, x1, x2, dx
+int npts, i
+int filelist, tbl, in
+int user_mode, imlen, order, maxpts
+char fname[SZ_FNAME], tbl_file[SZ_FNAME]
+char image[SZ_FNAME]
+char interp[SZ_LINE]
+bool gen, make_image
+pointer im, pix, sp, xtab, ytab, cv
+
+int clpopni(), clgfil(), open(), fscan(), nscan()
+int clgeti(), clgwrd()
+real clgetr()
+bool clgetb()
+pointer immap(), impl1r()
+
+begin
+ # Initialize interpolator
+ call intrp0 (1)
+ cv = NULL
+
+ # File containing x,y pairs in a table
+ call clgstr ("tbl_file", tbl_file, SZ_FNAME)
+
+ # Open table file and read as many points as possible
+ tbl = open (tbl_file, READ_ONLY, TEXT_FILE)
+
+ npts = 0
+ maxpts = clgeti ("tbl_size")
+
+ call smark (sp)
+ call salloc (xtab, maxpts, TY_REAL)
+ call salloc (ytab, maxpts, TY_REAL)
+
+ while (fscan(tbl) != EOF) {
+ npts = npts + 1
+ if (npts > maxpts)
+ call error (1, "Maximum table size exceeded.")
+ call gargr (Memr[xtab+npts-1])
+ call gargr (Memr[ytab+npts-1])
+ if (nscan() < 2) {
+# call eprintf ("Error reading x,y pairs\n")
+ npts = npts - 1
+ }
+ }
+
+ call close (tbl)
+
+ if (npts < 1)
+ call error (1, "Table has no entries.")
+
+ # Linear, spline, or CURFIT option interpolator?
+ user_mode = clgwrd ("interp_mode", interp, SZ_LINE,
+ ",linear,curves,legendre,chebyshev,spline3,spline1")
+
+ if (user_mode > 2 && user_mode <= 6)
+ order = clgeti ("order")
+
+ # Generate a curve?
+ gen = clgetb ("curve_gen")
+
+ # Or an image?
+ make_image = clgetb ("make_image")
+
+ if (gen || make_image) {
+ x1 = clgetr ("x1")
+ x2 = clgetr ("x2")
+ dx = clgetr ("dx")
+ imlen = clgeti ("npts")
+
+ # The above four variables overdefine the function
+ # One (other than x1) must be 0.0 --> solve for it
+ if (x2 == 0.0)
+ x2 = x1 + (imlen-1) * dx
+ else if (dx == 0.0)
+ dx = (x2 - x1) / (imlen - 1)
+
+ imlen = nint ((x2 - x1) / dx + 1)
+
+ # Verify that dx will not cause an infinite loop
+ if (dx == 0.0 || dx * (x2-x1) < 0.0)
+ call error (1, "Interval paramater dx implies infinite loop.")
+
+ if (make_image) {
+ call clgstr ("image", image, SZ_FNAME)
+ im = immap (image, NEW_IMAGE, 0)
+
+ IM_NDIM (im) = 1
+ IM_LEN (im, 1) = imlen
+ IM_PIXTYPE (im) = TY_REAL
+
+ pix = impl1r (im)
+
+ do i = 1, imlen {
+ x = x1 + (i - 1) * dx
+ call gen_pixel (Memr[xtab], Memr[ytab], npts,
+ user_mode, order, x, y, cv)
+ Memr[pix+i-1] = y
+ }
+
+ call imaddr (im, "CRVAL1", x1)
+ call imaddr (im, "CDELT1", dx)
+ call imaddr (im, "CD1_1", dx)
+ call imaddr (im, "CRPIX1", 1.)
+ call imaddi (im, "DC-FLAG", 0)
+
+ call imunmap (im)
+ } else {
+ do i = 1, imlen {
+ x = x1 + (i - 1) * dx
+ call gen_pixel (Memr[xtab], Memr[ytab], npts,
+ user_mode, order, x, y, cv)
+ call printf ("%12.5g %12.5g\n")
+ call pargr (x)
+ call pargr (y)
+ }
+ call flush (STDOUT)
+ }
+
+ # No, just one point at a time
+ } else {
+
+ # Open input list
+ filelist = clpopni ("input")
+
+ while (clgfil (filelist, fname, SZ_FNAME) != EOF) {
+ in = open (fname, READ_ONLY, TEXT_FILE)
+
+ # Process input requests
+ while (fscan(in) != EOF) {
+ call gargr (x)
+
+ call gen_pixel (Memr[xtab], Memr[ytab], npts,
+ user_mode, order, x, y, cv)
+ call printf ("%12.5g %12.5g\n")
+ call pargr (x)
+ call pargr (y)
+ call flush (STDOUT)
+ }
+
+ call close (in)
+ }
+
+ call clpcls (filelist)
+ }
+
+ call cvfree (cv)
+ call sfree (sp)
+end
+
+# GEN_PIXEL -- Generate a pixel value using specified interpolator
+
+procedure gen_pixel (xtab, ytab, npts, mode, order, x, y, cv)
+
+real xtab[ARB], ytab[ARB]
+int npts
+real x
+int mode, order
+real y
+pointer cv
+
+int fit, ier
+pointer wt, sp
+
+real cveval()
+
+begin
+ # Interpolate after selecting option
+ switch (mode) {
+ case SI_LINEAR:
+ call lintrp (1, xtab, ytab, npts, x, y, ier)
+
+ case SI_CURVES:
+ call intrp (1, xtab, ytab, npts, x, y, ier)
+
+ default:
+ if (cv == NULL) {
+ call smark (sp)
+ call salloc (wt, npts, TY_REAL)
+ call amovkr (1.0, Memr[wt], npts)
+
+ switch (mode) {
+ case SI_LEGENDRE:
+ fit = LEGENDRE
+ case SI_CHEBYSHEV:
+ fit = CHEBYSHEV
+ case SI_SPLINE3:
+ fit = SPLINE3
+ case SI_SPLINE1:
+ fit = SPLINE1
+ default:
+ fit = SPLINE1
+ }
+
+ call cvinit (cv, fit, order, xtab[1], xtab[npts])
+ call cvfit (cv, xtab, ytab, Memr[wt], npts, WTS_UNIFORM,
+ ier)
+ call sfree (sp)
+ }
+ y = cveval (cv, x)
+ }
+end
diff --git a/noao/onedspec/t_slist.x b/noao/onedspec/t_slist.x
new file mode 100644
index 00000000..aa31fa08
--- /dev/null
+++ b/noao/onedspec/t_slist.x
@@ -0,0 +1,105 @@
+include <error.h>
+include <imhdr.h>
+include <fset.h>
+include <smw.h>
+
+
+# T_SLIST -- Lists header information from MULTISPEC format header
+
+procedure t_slist ()
+
+int list # Input list
+pointer aps # Aperture range list
+int long_header # Long header?
+
+int i
+pointer sp, image, im, mw, sh, ptr
+
+bool clgetb(), rng_elementi()
+int imtopenp(), imtgetim(), btoi()
+pointer rng_open(), immap(), smw_openim()
+errchk immap, smw_openim, shdr_open
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+
+ # Parameters
+ list = imtopenp ("images")
+ call clgstr ("apertures", Memc[image], SZ_LINE)
+ long_header = btoi (clgetb ("long_header"))
+
+ # Initialize
+ call fseti (STDOUT, F_FLUSHNL, YES)
+ iferr (aps = rng_open (Memc[image], INDEF, INDEF, INDEF))
+ call error (0, "Bad range specification")
+
+ # Loop over all input images.
+ while (imtgetim (list, Memc[image], SZ_LINE) != EOF) {
+ iferr {
+ im = NULL
+ mw = NULL
+ ptr = immap (Memc[image], READ_ONLY, 0); im = ptr
+ ptr = smw_openim (im); mw = ptr
+ #if (SMW_FORMAT(mw) != SMW_ES && SMW_FORMAT(mw) != SMW_MS)
+ # call error (1, "Invalid spectrum format")
+ call shdr_open (im, mw, 1, 1, INDEFI, SHHDR, sh)
+ } then {
+ if (mw != NULL) {
+ call smw_close (mw)
+ if (sh != NULL)
+ MW(sh) = NULL
+ }
+ if (im != NULL)
+ call imunmap (im)
+ call erract (EA_WARN)
+ next
+ }
+
+ if (long_header == YES) {
+ call printf ("%s: %s\n")
+ call pargstr (IMNAME(sh))
+ call pargstr (IM_TITLE(im))
+ call printf (
+ " EXPTIME = %.2f%24tUT = %0.1h%44tST = %0.1h\n")
+ call pargr (IT(sh))
+ call pargr (UT(sh))
+ call pargr (ST(sh))
+ call printf (
+ " RA = %0.2h%24tDEC = %0.1h%44tHA = %0.2h%64tAIRMASS = %5.3f\n")
+ call pargr (RA(sh))
+ call pargr (DEC(sh))
+ call pargr (HA(sh))
+ call pargr (AM(sh))
+ }
+ do i = 1, IM_LEN(im, SMW_LAXIS(MW(sh),2)) {
+ call shdr_open (im, mw, i, 1, INDEFI, SHHDR, sh)
+ if (!rng_elementi (aps, AP(sh)))
+ next
+ if (long_header == NO)
+ call printf (IMNAME(sh))
+ else
+ call printf (" ")
+ call printf (" %d %d %d %d %g %g %d %s\n")
+ call pargi (i)
+ call pargi (AP(sh))
+ call pargi (BEAM(sh))
+ call pargi (DC(sh))
+ call pargr (W0(sh))
+ call pargr (WP(sh))
+ call pargi (SN(sh))
+ call pargstr (TITLE(sh))
+ }
+
+ call smw_close (MW(sh))
+ if (sh != NULL)
+ MW(sh) = NULL
+ call imunmap (im)
+ }
+
+ # Free space
+ call shdr_close (sh)
+ call rng_close (aps)
+ call imtclose (list)
+ call sfree (sp)
+end
diff --git a/noao/onedspec/t_specplot.x b/noao/onedspec/t_specplot.x
new file mode 100644
index 00000000..414b275c
--- /dev/null
+++ b/noao/onedspec/t_specplot.x
@@ -0,0 +1,2030 @@
+include <ctype.h>
+include <imhdr.h>
+include <error.h>
+include <gset.h>
+include <mach.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include "specplot.h"
+
+# Define the help information.
+define HELP "noao$onedspec/specplot.key"
+define PROMPT "specplot options"
+
+
+# T_SPECPLOT -- Plot multiple spectra in a variety of formats and layouts.
+# The spectra may be individually scaled and offset in intensity, shifted
+# and scaled in wavelength, and plotted in uniform steps. The plotting
+# type may be symbols or lines. The spectra may be labeled. Each spectrum
+# is read into memory in a structre defined in "specplot.h". An array
+# of structures is then manipulated. Each line of two dimensional images
+# are treated as separate spectra.
+
+procedure t_specplot ()
+
+pointer list # List of input spectra
+real step # Initial separation step
+int labels # Labeling mode
+real fraction # Fraction of minimum step
+bool yscale # Draw y scale?
+
+bool wscale
+int i, j, n, fd, nspec, wcs, key, redraw
+real wx, wy, wx1, wy1, wx2, wy2
+pointer stack, units, cmd, sp, sh, spsave, sps, gp, gt
+
+bool clgetb()
+int clgwrd(), clgcur()
+int open(), imtgetim(), getline(), scan(), nscan()
+int stridxs(), nowhite(), btoi(), gt_geti()
+real clgetr()
+pointer sp_nearest(), imtopenp(), gopen(), gt_init()
+errchk sp_gdata, un_changer
+
+define nospec_ 99
+
+begin
+ call smark (stack)
+ call salloc (units, SZ_LINE, TY_CHAR)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+ call calloc (sps, 100, TY_POINTER)
+ spsave = NULL
+
+ # Read the input spectrum list into an array of structures.
+ i = 0
+ nspec = 0
+ list = imtopenp ("spectra")
+ call clgstr ("units", Memc[units], SZ_LINE)
+ if (nowhite (Memc[units], Memc[cmd], SZ_LINE) == 0)
+ call strcpy ("display", Memc[units], SZ_LINE)
+ while (imtgetim (list, Memc[cmd], SZ_FNAME) != EOF) {
+ iferr (call sp_gdata (Memc[cmd], Memc[units], i, sps, nspec))
+ call erract (EA_WARN)
+ }
+ call imtclose (list)
+
+
+ # Set the layout of the spectra.
+ step = clgetr ("step")
+ fraction = clgetr ("fraction")
+ if (clgetb ("autolayout")) {
+ if (clgetb ("autoscale"))
+ call sp_autolayout (Memi[sps], nspec, true, fraction, step)
+ else
+ call sp_autolayout (Memi[sps], nspec, false, fraction, step)
+ }
+ call sp_scale (Memi[sps], nspec, step)
+
+ # Get optional user labels from a file and set the label type.
+ call clgstr ("ulabels", Memc[cmd], SZ_FNAME)
+ ifnoerr (fd = open (Memc[cmd], READ_ONLY, TEXT_FILE)) {
+ do i = 1, nspec {
+ sp = Memi[sps+i-1]
+ if (getline (fd, Memc[cmd]) != EOF)
+ call strcpy (Memc[cmd], SP_ULABEL(sp), SP_SZULABEL)
+ else
+ SP_ULABEL(sp) = EOS
+ j = stridxs ("\n", SP_ULABEL(sp))
+ if (j > 0)
+ call strcpy (SP_ULABEL(sp), SP_ULABEL(sp), j-1)
+ }
+ call close (fd)
+ }
+ labels = clgwrd ("labels", Memc[cmd], SZ_FNAME, LABELS)
+ call sp_labels (Memi[sps], nspec, labels)
+
+ # Initialize the graphics
+ call clgstr ("graphics", Memc[cmd], SZ_FNAME)
+ gp = gopen (Memc[cmd], NEW_FILE, STDGRAPH)
+
+ gt = gt_init ()
+ call gt_seti (gt, GTSYSID, btoi (clgetb ("sysid")))
+ call clgstr ("title", Memc[cmd], SZ_LINE)
+ call gt_sets (gt, GTTITLE, Memc[cmd])
+ call clgstr ("xlabel", Memc[cmd], SZ_LINE)
+ if (Memc[cmd] != EOS) {
+ call gt_sets (gt, GTXLABEL, Memc[cmd])
+ call gt_sets (gt, GTXUNITS, "")
+ } else if (nspec > 0) {
+ if (UN_LABEL(UN(SP_SH(Memi[sps]))) != EOS) {
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(SP_SH(Memi[sps]))))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(SP_SH(Memi[sps]))))
+ } else {
+ call gt_sets (gt, GTXLABEL, LABEL(SP_SH(Memi[sps])))
+ call gt_sets (gt, GTXUNITS, UNITS(SP_SH(Memi[sps])))
+ }
+ }
+ call clgstr ("ylabel", Memc[cmd], SZ_LINE)
+ call gt_sets (gt, GTYLABEL, Memc[cmd])
+ wx = clgetr ("xmin")
+ call gt_setr (gt, GTXMIN, wx)
+ wx = clgetr ("xmax")
+ call gt_setr (gt, GTXMAX, wx)
+ wx = clgetr ("ymin")
+ call gt_setr (gt, GTYMIN, wx)
+ wx = clgetr ("ymax")
+ call gt_setr (gt, GTYMAX, wx)
+ wscale = true
+ yscale = clgetb ("yscale")
+ #if (!scale)
+ # call gseti (gp, G_YDRAWTICKS, NO)
+
+ # Draw the graph on the first pass and then read the cursor.
+ key = 'r'
+ repeat {
+ switch (key) {
+ case '?': # Page help summary
+ call gpagefile (gp, HELP, PROMPT)
+ case ':': # Process colon commands
+ if (Memc[cmd] == '/')
+ call gt_colon (Memc[cmd], gp, gt, redraw)
+ else {
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps],
+ nspec)
+ call sp_colon (Memc[cmd], gp, gt, Memi[sps], nspec,
+ Memc[units], labels, i, step, fraction, redraw)
+ if (nspec == 0) {
+ redraw = NO
+ goto nospec_
+ }
+ }
+ case 'a', 'i': # Append or insert a new spectrum
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ if (key == 'i')
+ i = max (0, i - 1)
+ call printf ("Spectrum: ")
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (nscan() == 1) {
+ iferr {
+ call sp_gdata (Memc[cmd], Memc[units],
+ i, sps, nspec)
+ call sp_labels (Memi[sps], nspec, labels)
+ call sp_scale (Memi[sps], nspec, step)
+ redraw = YES
+ } then
+ call erract (EA_WARN)
+ }
+ }
+ case 'd': # Delete a spectrum
+ if (nspec == 0)
+ goto nospec_
+
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ sp = Memi[sps+i-1]
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp), YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp), SP_Y(SP))
+ call sp_delete (i, sps, nspec)
+ call sp_labels (Memi[sps], nspec, labels)
+ call sp_scale (Memi[sps], nspec, step)
+ if (spsave != NULL)
+ call sp_free (spsave)
+ spsave = sp
+# redraw = YES
+ case 'e': # Undelete a spectrum
+ if (spsave != NULL) {
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps],
+ nspec)
+ i = max (0, i - 1)
+ call sp_add (spsave, i, sps, nspec)
+ call sp_labels (Memi[sps], nspec, labels)
+ call sp_scale (Memi[sps], nspec, step)
+ spsave = NULL
+ redraw = YES
+ }
+ case 'f': # Toggle wavelength scale
+ if (wscale) {
+ call gt_sets (gt, GTXLABEL, "Pixels")
+ call gt_sets (gt, GTXUNITS, "")
+ wscale = false
+ } else {
+ if (nspec > 0) {
+ sp = Memi[sps]
+ sh = SP_SH(sp)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ }
+ wscale = true
+ }
+ redraw = YES
+ case 'l', 'p': # Mark label position and enter label.
+ if (nspec == 0)
+ goto nospec_
+
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ sp = Memi[sps+i-1]
+ call printf (
+ "Spectrum %d: Mark position for label ('q' to cancel)")
+ call pargi (SP_INDEX(sp))
+ i = clgcur ("cursor", wx, wy, wcs, j, Memc[cmd], SZ_LINE)
+ if (j != 'q') {
+ call ggwind (gp, wx1, wx2, wy1, wy2)
+ wx2 = wx2 - wx1
+ wy2 = wy2 - wy1
+ SP_XLPOS(sp) = (wx - wx1) / wx2
+ SP_YLPOS(sp) = (wy - SP_MEAN(sp)) / wy2
+
+ if (key == 'l') {
+ call printf ("Spectrum %d: Label = ")
+ call pargi (SP_INDEX(sp))
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargstr (SP_ULABEL(sp), SP_SZULABEL)
+ j = stridxs ("\n", SP_ULABEL(sp))
+ if (j > 0)
+ call strcpy (SP_ULABEL(sp), SP_ULABEL(sp), j-1)
+ call strcpy (SP_ULABEL(sp), SP_LABEL(sp),
+ SP_SZLABEL)
+ }
+ }
+ call gtext (gp, wx, wy, SP_LABEL(sp), "")
+ }
+ call printf ("\n")
+ case 'o': # Reorder the spectra to eliminate gaps.
+ if (nspec == 0)
+ goto nospec_
+
+ do i = 1, nspec {
+ sp = Memi[sps+i-1]
+ if (SP_INDEX(sp) != i) {
+ SP_INDEX (sp) = i
+ redraw = YES
+ }
+ }
+ if (redraw == YES) {
+ call sp_labels (Memi[sps], nspec, labels)
+ call sp_scale (Memi[sps], nspec, step)
+ }
+ case 'q', 'I': # Quit or interrupt
+ break
+ case 'r': # Redraw the current graph
+ redraw = YES
+ case 's': # Shift the spectrum nearest the cursor
+ if (nspec == 0)
+ goto nospec_
+
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ sp = Memi[sps+i-1]
+ call printf ( "Shift spectrum %d: (q, r, s, t, x, y, z)")
+ call pargi (SP_INDEX(sp))
+ while (clgcur ("cursor", wx1, wy1, wcs, key, Memc[cmd],
+ SZ_LINE) != EOF) {
+ switch (key) {
+ case 's':
+ if (wy != SP_OFFSET(sp)) {
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp),
+ SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp),
+ SP_Y(sp))
+ SP_SCALE(sp) = SP_SCALE(sp) *
+ (wy1 - SP_OFFSET(sp)) / (wy - SP_OFFSET(sp))
+ call sp_scale (sp, 1, step)
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ NO, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp),
+ SP_NPTS(sp))
+ wy = wy1
+ }
+ case 't':
+ if (wy != SP_OFFSET(sp)) {
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp),
+ SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp),
+ SP_Y(sp))
+ if (UN_CLASS(UN(SP_SH(sp))) == UN_VEL)
+ SP_XOFFSET(sp) = SP_XOFFSET(sp) + wx1 - wx
+ else
+ SP_XSCALE(sp) = SP_XSCALE(sp) * wx1 / wx
+ SP_SCALE(sp) = SP_SCALE(sp) *
+ (wy1 - SP_OFFSET(sp)) / (wy - SP_OFFSET(sp))
+ call sp_scale (sp, 1, step)
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ NO, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp),
+ SP_NPTS(sp))
+ wx = wx1
+ wy = wy1
+ }
+ case 'x':
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp), SP_Y(SP))
+ if (UN_CLASS(UN(SP_SH(sp))) == UN_VEL)
+ SP_XOFFSET(sp) = SP_XOFFSET(sp) + wx1 - wx
+ else
+ SP_XSCALE(sp) = SP_XSCALE(sp) * wx1 / wx
+ call sp_scale (sp, 1, step)
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ NO, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ wx = wx1
+ case 'y':
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp), SP_Y(SP))
+ SP_OFFSET(sp) = SP_OFFSET(sp) + wy1 - wy
+ call sp_scale (sp, 1, step)
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ NO, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ wy = wy1
+ case 'z':
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ YES, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ call gline (gp, SP_X(sp), SP_Y(sp), SP_X(sp), SP_Y(SP))
+ if (UN_CLASS(UN(SP_SH(sp))) == UN_VEL)
+ SP_XOFFSET(sp) = SP_XOFFSET(sp) + wx1 - wx
+ else
+ SP_XSCALE(sp) = SP_XSCALE(sp) * wx1 / wx
+ SP_OFFSET(sp) = SP_OFFSET(sp) + wy1 - wy
+ call sp_scale (sp, 1, step)
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp),
+ NO, gp, gt)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ wx = wx1
+ wy = wy1
+ case 'r':
+ if (gt_geti (gt, GTSYSID) == YES) {
+ call sprintf (Memc[cmd], SZ_LINE,
+ "Separation step = %g")
+ call pargr (step)
+ call gt_sets (gt, GTPARAMS, Memc[cmd])
+ } else
+ call gt_sets (gt, GTPARAMS, "")
+ call sp_plot (gp, gt, Memi[sps], nspec, wscale, yscale)
+ case 'q':
+ break
+ }
+ call printf ( "Shift spectrum %d: (q, r, s, t, x, y, z)")
+ call pargi (SP_INDEX(sp))
+ }
+ call printf ("\n")
+ case 't': # Set a wavelength scale using the cursor.
+ if (nspec == 0)
+ goto nospec_
+
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ sp = Memi[sps+i-1]
+ call printf ("X coordinate (%g): ")
+ call pargr (wx)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargr (wy)
+ if (nscan() == 0)
+ wy = wx
+ } else
+ wy = wx
+ call printf ("Mark another position")
+ i = clgcur ("cursor", wx1, wy1, wcs, key, Memc[cmd], SZ_LINE)
+ call printf ("X coordinate (%g): ")
+ call pargr (wx1)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargr (wy1)
+ if (nscan() == 0)
+ wy1 = wx1
+ } else
+ wy1 = wx1
+ if (wx != wx1) {
+ n = SP_NPTS(sp) - 1
+ sh = SP_PX(sp) - 1
+ if (SP_WPC(sp) > 0.) {
+ for (i=1; i<n && wx<Memr[sh+i]; i=i+1)
+ ;
+ for (j=1; j<n && wx1<Memr[sh+j]; j=j+1)
+ ;
+ } else {
+ for (i=1; i>n && wx>Memr[sh+i]; i=i+1)
+ ;
+ for (j=1; j>n && wx1>Memr[sh+j]; j=j+1)
+ ;
+ }
+ wx = i + (wx - Memr[sh+i]) / (Memr[sh+i+1] - Memr[sh+i])
+ wx1 = j + (wx1 - Memr[sh+j]) / (Memr[sh+j+1] - Memr[sh+j])
+ SP_WPC(sp) = (wy - wy1) / (wx - wx1)
+ SP_W0(sp) = wy - SP_WPC(sp) * (wx - 1)
+ call sp_linear (sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ case 'u': # Set a wavelength point using the cursor.
+ if (nspec == 0)
+ goto nospec_
+
+ i = sp_nearest (gp, wx, wy, key, Memc[cmd], Memi[sps], nspec)
+ sp = Memi[sps+i-1]
+ call printf ("X coordinate (%g): ")
+ call pargr (wx)
+ call flush (STDOUT)
+ if (scan() != EOF) {
+ call gargr (wx1)
+ if (nscan() == 1) {
+ SP_XOFFSET(sp) = SP_XOFFSET(sp) + wx1 - wx
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ }
+ case 'v': # Change to velocity scale
+ if (nspec == 0)
+ goto nospec_
+
+ iferr {
+ do i = 1, nspec {
+ sp = Memi[sps+i-1]
+ sh = SP_SH(sp)
+ if (i == 1) {
+ call un_changer (UN(sh), "angstroms", wx, 1, NO)
+ call sprintf (Memc[units], SZ_LINE,
+ "km/s %g angstroms")
+ call pargr (wx)
+ call un_changer (UN(sh), Memc[units], Memr[SX(sh)],
+ SN(sh), YES)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ redraw = YES
+ } else
+ call un_changer (UN(sh), Memc[units], Memr[SX(sh)],
+ SN(sh), YES)
+ SP_W0(sp) = Memr[SX(sh)]
+ SP_WPC(sp) = (Memr[SX(sh)+SN(sh)-1] - Memr[SX(sh)]) /
+ (SN(sh) - 1)
+ SP_XSCALE(sp) = 1.
+ SP_XOFFSET(sp) = 0.
+ call sp_scale (sp, 1, step)
+ }
+ } then
+ call erract (EA_WARN)
+ case 'w': # Window the graph
+ call gt_window (gt, gp, "cursor", redraw)
+ case 'x': # No layout
+ if (nspec == 0)
+ goto nospec_
+
+ do i = 1, nspec {
+ sp = Memi[sps+i-1]
+ SP_SCALE(sp) = 1.
+ SP_OFFSET(sp) = 0.
+ }
+ call sp_scale (Memi[sps], nspec, step)
+ redraw = YES
+ case 'y': # Layout the spectra offsets to common mean
+ if (nspec == 0)
+ goto nospec_
+
+ call sp_autolayout (Memi[sps], nspec, false, fraction, step)
+ call sp_scale (Memi[sps], nspec, step)
+ redraw = YES
+ case 'z': # Layout the spectra scaled to common mean
+ if (nspec == 0)
+ goto nospec_
+
+ call sp_autolayout (Memi[sps], nspec, true, fraction, step)
+ call sp_scale (Memi[sps], nspec, step)
+ redraw = YES
+ default:
+ call printf ("\007")
+ }
+
+ # Redraw the graph as needed.
+ if (redraw == YES) {
+ if (gt_geti (gt, GTSYSID) == YES) {
+ call sprintf (Memc[cmd], SZ_LINE, "Separation step = %g")
+ call pargr (step)
+ call gt_sets (gt, GTPARAMS, Memc[cmd])
+ } else
+ call gt_sets (gt, GTPARAMS, "")
+ call sp_plot (gp, gt, Memi[sps], nspec, wscale, yscale)
+ redraw = NO
+ }
+nospec_
+ if (nspec == 0)
+ call printf ("No spectra defined\007")
+
+ } until (clgcur ("cursor", wx, wy, wcs, key, Memc[cmd], SZ_LINE) == EOF)
+
+
+ call clgstr ("logfile", Memc[cmd], SZ_LINE)
+ if (nowhite (Memc[cmd], Memc[cmd], SZ_LINE) > 0)
+ iferr (call sp_vshow (Memc[cmd], NULL, Memi[sps], nspec, step))
+ call erract (EA_WARN)
+
+ # Close the graphics device and free memory.
+ call gclose (gp)
+ call gt_free (gt)
+
+ if (nspec > 0) {
+ do i = 1, nspec
+ call sp_free (Memi[sps+i-1])
+ }
+ if (spsave != NULL)
+ call sp_free (spsave)
+ call mfree (sps, TY_POINTER)
+ call sfree (stack)
+end
+
+
+# SP_SCALE -- Scale the spectra. This uses the wavelength scale and intensity
+# scale parameters defined for each spectrum and adds the intensity offset.
+
+procedure sp_scale (sps, nspec, step)
+
+pointer sps[ARB] # Spectrum structures
+int nspec # Number of spectra
+real step # Final step
+
+int i, npts
+real scale, offset
+pointer sp, sh
+
+begin
+ do i = 1, nspec {
+ sp = sps[i]
+ sh = SP_SH(sp)
+ npts = SP_NPTS(sp)
+
+ scale = SP_XSCALE(sp)
+ offset = SP_XOFFSET(sp)
+ call altmr (Memr[SX(sh)], SP_X(sp), npts, scale, offset)
+
+ scale = SP_SCALE(sp)
+ offset = SP_OFFSET(sp) + (SP_INDEX(sp) - 1) * step
+ call altmr (Memr[SY(sh)], SP_Y(sp), npts, scale, offset)
+
+ SP_MEAN(sp) = SP_OMEAN(sp) * scale + offset
+ SP_MIN(sp) = SP_OMIN(sp) * scale + offset
+ SP_MAX(sp) = SP_OMAX(sp) * scale + offset
+ }
+end
+
+
+# SP_AUTOLAYOUT -- Apply an automatic layout algorithm in which the spectra
+# are scaled or offset to a common mean and a separation step is computed
+# to provide a specified degree of overlap between the nearest spectra.
+
+procedure sp_autolayout (sps, nspec, autoscale, fraction, step)
+
+pointer sps[ARB] # Spectrum structures
+int nspec # Number of spectra
+bool autoscale # Scale spectra to common mean?
+real fraction # Fraction to adjust step
+real step # Final step
+
+int i
+real a, b, scale, offset
+pointer sp
+
+begin
+ if (nspec < 2)
+ return
+
+ # Scale to the lowest indexed spectrum (usually 1).
+ sp = sps[1]
+ scale = SP_SCALE(sp)
+ offset = SP_OFFSET(sp)
+ a = SP_OMEAN(sp)
+
+ # If desired use a multiplicative scaling to a common mean.
+ # If the mean is <= 0 then use offset to common mean.
+
+ if (autoscale) {
+ do i = 2, nspec {
+ sp = sps[i]
+ if (a * SP_OMEAN(sp) > 0.) {
+ SP_SCALE(sp) = a / SP_OMEAN(sp) * scale
+ SP_OFFSET(sp) = offset
+ } else {
+ SP_SCALE(sp) = scale
+ SP_OFFSET(sp) = (a - SP_OMEAN(sp)) * scale + offset
+ }
+ }
+
+ # Otherwise use an offset scaling to a common mean.
+ } else {
+ do i = 2, nspec {
+ sp = sps[i]
+ SP_SCALE(sp) = scale
+ SP_OFFSET(sp) = (a - SP_OMEAN(sp)) * scale + offset
+ }
+ }
+
+ # Compute the minimum step which just separates the maximum of
+ # one spectrum from the minimum of the next spectrum. A degree
+ # of overlap can be set using the fraction parameter.
+
+ step = -MAX_REAL
+ do i = 2, nspec {
+ sp = sps[i-1]
+ a = SP_OMAX(sp) * SP_SCALE(sp) + SP_OFFSET(sp)
+ sp = sps[i]
+ b = SP_OMIN(sp) * SP_SCALE(sp) + SP_OFFSET(sp)
+ step = max (step, a - b)
+ }
+ step = fraction * step
+end
+
+
+# SP_PLOT -- Determine the range of all the data and then make a plot with
+# specified labels. The GTOOLS procedures are used to allow user adjustment.
+
+procedure sp_plot (gp, gt, sps, nspec, wscale, yscale)
+
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+pointer sps[ARB] # Spectrum structures
+int nspec # Number of spectra
+bool wscale # Draw in world coordinates?
+bool yscale # Draw Y scale?
+
+int i, n
+real x, y, xmin, xmax, ymin, ymax
+pointer sp, pix
+
+begin
+ # Set the default limits from the data.
+ xmin = MAX_REAL
+ xmax = -MAX_REAL
+ ymin = MAX_REAL
+ ymax = -MAX_REAL
+ n = 0
+ do i = 1, nspec {
+ sp = sps[i]
+ if (wscale) {
+ xmin = min (xmin, SP_X(sp), Memr[SP_PX(sp)+SP_NPTS(sp)-1])
+ xmax = max (xmax, SP_X(sp), Memr[SP_PX(sp)+SP_NPTS(sp)-1])
+ } else {
+ n = max (n, SP_NPTS(sp))
+ xmin = 1
+ xmax = n
+ }
+ ymin = min (ymin, SP_MIN(sp))
+ ymax = max (ymax, SP_MAX(sp))
+ }
+
+ if (xmin > xmax) {
+ xmin = 0.
+ xmax = 1.
+ }
+ if (ymin > ymax) {
+ ymin = 0.
+ ymax = 1.
+ }
+
+ # Draw the axes with GTOOLS limits override.
+ #call gframe (gp)
+ call gclear (gp)
+ if (!yscale)
+ call gseti (gp, G_YDRAWTICKS, NO)
+ call gswind (gp, xmin, xmax, ymin, ymax)
+ call gt_swind (gp, gt)
+ call gt_labax (gp, gt)
+
+ # The label positions are based on the limits of the graph.
+ call ggwind (gp, xmin, xmax, ymin, ymax)
+ xmax = xmax - xmin
+ ymax = ymax - ymin
+
+ if (!wscale) {
+ call malloc (pix, n, TY_REAL)
+ do i = 1, n
+ Memr[pix+i-1] = i
+ }
+
+ do i = 1, nspec {
+ sp = sps[i]
+ call sp_ptype (SP_PTYPE(sp), SP_COLOR(sp), NO, gp, gt)
+ if (wscale)
+ call gt_plot (gp, gt, SP_X(sp), SP_Y(sp), SP_NPTS(sp))
+ else
+ call gt_plot (gp, gt, Memr[pix], SP_Y(sp), SP_NPTS(sp))
+ x = SP_XLPOS(sp) * xmax + xmin
+ y = SP_YLPOS(sp) * ymax + SP_MEAN(sp)
+ call gtext (gp, x, y, SP_LABEL(sp), "")
+ }
+
+ if (!wscale)
+ call mfree (pix, TY_REAL)
+end
+
+
+# SP_PTYPE -- Decode the plotting type and set the GTOOLS structure.
+
+procedure sp_ptype (ptype, color, erase, gp, gt)
+
+char ptype[ARB] # Plotting type string
+int color # Color
+int erase # Erase plot?
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+
+int i, j, ctoi()
+pointer sp, gttype
+
+begin
+ call smark (sp)
+ call salloc (gttype, SZ_LINE, TY_CHAR)
+ call gt_gets (gt, GTTYPE, Memc[gttype], SZ_LINE)
+
+ i = 1
+ if (ctoi (ptype, i, j) > 0) {
+ if (j < 0)
+ call gt_sets (gt, GTTYPE, "histogram")
+ else
+ call gt_sets (gt, GTTYPE, "line")
+ if (erase == YES)
+ call gt_seti (gt, GTLINE, 0)
+ else
+ call gt_seti (gt, GTLINE, abs(j))
+ } else {
+ call gt_sets (gt, GTTYPE, "mark")
+ call gt_sets (gt, GTMARK, ptype)
+ if (erase == YES)
+ call gseti (gp, G_PMLTYPE, 0)
+ else
+ call gseti (gp, G_PMLTYPE, 1)
+ }
+ call gt_seti (gt, GTCOLOR, color)
+
+ call sfree (sp)
+end
+
+
+# List of colon commands.
+define CMDS "|show|vshow|step|fraction|move|shift|w0|wpc|velocity|redshift\
+ |offset|scale|xlpos|ylpos|label|ulabel|ptype|units|color|"
+
+define SHOW 1 # Show
+define VSHOW 2 # Verbose show
+define STEP 3 # Separation step
+define FRACTION 4 # Fraction for autolayout
+define MOVE 5 # Move spectrum index
+define SHIFT 6 # Shift spectrum indices
+define WZP 7 # Wavelength zero point
+define WPC 8 # Wavelength per channel
+define VELOCITY 9 # Radial velocity
+define REDSHIFT 10 # Redshift
+define OFFSET 11 # Intensity offset
+define SCALE 12 # Intensity scale
+define XLPOS 13 # X label position
+define YLPOS 14 # Y label position
+define LABEL 15 # Type of labels
+define ULABEL 16 # User label
+define PTYPE 17 # Plot type
+define UNITS 18 # Plot units
+define COLOR 19 # Color
+
+# SP_COLON -- Interpret colon commands.
+
+procedure sp_colon (cmdstr, gp, gt, sps, nspec, units, labels, current, step,
+ fraction, redraw)
+
+char cmdstr[ARB] # Colon command
+pointer gp # GIO pointer (used for paging screen)
+pointer gt # GTOOLS pointer
+pointer sps[ARB] # Array of spectra structures
+int nspec # Number of spectra
+char units[SZ_LINE] # Units string
+int labels # Label type
+int current # Current spectrum element (0 if not defined)
+real step # Separation step
+real fraction # Fraction for autolayout
+int redraw # Redraw graph
+
+int i, j, index, ncmd
+real rval
+pointer stack, cmd, sp, sh, un1, un2
+
+int nscan(), strdic(), ctoi(), stridxs()
+pointer un_open()
+
+define done_ 10
+
+begin
+ call smark (stack)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+
+ # Scan the command string and get the first word.
+ call sscan (cmdstr)
+ call gargwrd (Memc[cmd], SZ_LINE)
+
+ # Parse the optional spectrum index. Moving the the end of string.
+ # Set the spectrum element to 0 if a non-numeric index is specified.
+ # If an index number is given find the appropriate element and print
+ # an error if the spectrum index is not defined.
+ i = stridxs ("[", Memc[cmd])
+ j = 0
+ if (i > 0) {
+ Memc[cmd+i-1] = EOS
+ current = 0
+
+ i = i + 1
+ if (ctoi (Memc[cmd], i, index) > 0) {
+ for (i=1; (i<=nspec)&&(SP_INDEX(sps[i])!=index); i=i+1)
+ ;
+
+ current = i
+ if (current > nspec) {
+ call printf ("Spectrum %d not defined")
+ call pargi (index)
+ call sfree (stack)
+ return
+ }
+ }
+ j = current
+ }
+
+ # Parse the command. Print the command if unknown.
+ ncmd = strdic (Memc[cmd], Memc[cmd], SZ_LINE, CMDS)
+
+ switch (ncmd) {
+ case SHOW: # show spectrum parameters
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (nscan() == 1)
+ call sp_show ("STDOUT", gp, sps, nspec, step)
+ else
+ iferr (call sp_show (Memc[cmd], NULL, sps, nspec, step))
+ call erract (EA_WARN)
+ case VSHOW: # show spectrum parameters
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (nscan() == 1)
+ call sp_vshow ("STDOUT", gp, sps, nspec, step)
+ else
+ iferr (call sp_vshow (Memc[cmd], NULL, sps, nspec, step))
+ call erract (EA_WARN)
+ case STEP: # set or show step
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("step %g")
+ call pargr (step)
+ } else {
+ step = rval
+ call sp_scale (sps, nspec, step)
+ redraw = YES
+ }
+ case FRACTION: # set or show autolayout fraction
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("fraction %g")
+ call pargr (fraction)
+ } else
+ fraction = rval
+ case MOVE: # Move spectrum by index
+ call gargi (index)
+ if (nscan() > 1) {
+ if (current > 0) {
+ sp = sps[current]
+ if (index != SP_INDEX(sp)) {
+ SP_INDEX(sp) = index
+
+ for (i=current; i<nspec; i=i+1) {
+ sps[i] = sps[i+1]
+ SP_INDEX(sps[i]) = SP_INDEX(sps[i]) - 1
+ }
+ for (i=1; (i<nspec)&&(index>SP_INDEX(sps[i])); i=i+1)
+ ;
+ for (j=nspec; j>i; j=j-1)
+ sps[j] = sps[j-1]
+ sps[i] = sp
+ current = i
+
+ for (j=i; j<nspec; j=j+1) {
+ sp = sps[j+1]
+ if (SP_INDEX(sps[j]) == SP_INDEX(sp))
+ SP_INDEX(sp) = SP_INDEX(sp) + 1
+ }
+
+ if (labels == LABEL_INDEX)
+ for (i=1; i<=nspec; i=i+1) {
+ sp = sps[i]
+ call sprintf (SP_LABEL(sp), SP_SZLABEL, "%-4d")
+ call pargi (SP_INDEX(sp))
+ }
+ call sp_scale (sps, nspec, step)
+ redraw = YES
+ }
+ } else
+ call printf ("\007")
+ }
+ case SHIFT: # Shift spectra by index
+ call gargi (j)
+ if (nscan() > 1) {
+ if (current > 0) {
+ if (j > 0) {
+ for (i=current; i<=nspec; i=i+1) {
+ sp = sps[i]
+ SP_INDEX(sp) = SP_INDEX(sp) + j
+ call sp_scale (sp, 1, step)
+ call sp_labels (sp, 1, labels)
+ redraw = YES
+ }
+ } else if (j < 0) {
+ for (i=current; i>0; i=i-1) {
+ sp = sps[i]
+ SP_INDEX(sp) = SP_INDEX(sp) + j
+ call sp_scale (sp, 1, step)
+ call sp_labels (sp, 1, labels)
+ redraw = YES
+ }
+ }
+ } else {
+ for (i=1; i<=nspec; i=i+1) {
+ sp = sps[i]
+ SP_INDEX(sp) = SP_INDEX(sp) + j
+ call sp_scale (sp, 1, step)
+ call sp_labels (sp, 1, labels)
+ redraw = YES
+ }
+ }
+ }
+ case WZP: # set or show zero point wavelength
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("w0[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_W0(sp)*SP_XSCALE(sp)+SP_XOFFSET(sp))
+ } else {
+ SP_XOFFSET(sp) = rval - SP_W0(sp) * SP_XSCALE(sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("w0:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_W0(sp)*SP_XSCALE(sp)+SP_XOFFSET(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ sp = sps[i]
+ SP_XOFFSET(sp) = rval - SP_W0(sp) * SP_XSCALE(sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ }
+ }
+ case WPC: # set or show wavelength per channel
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("wpc[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_WPC(sp)*SP_XSCALE(sp))
+ } else {
+ SP_WPC(sp) = rval
+ call sp_linear (sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("wpc:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_WPC(sp)*SP_XSCALE(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ sp = sps[i]
+ SP_WPC(sp) = rval
+ call sp_linear (sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ }
+ }
+ case VELOCITY: # set or show radial velocity
+ if (nspec < 0)
+ goto done_
+ call gargr (rval)
+ un1 = UN(SP_SH(sps[1]))
+ if (UN_CLASS(un1) == UN_VEL) {
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("velocity[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XOFFSET(sp))
+ } else {
+ SP_XOFFSET(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("velocity:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XOFFSET(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ sp = sps[i]
+ SP_XOFFSET(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ }
+ }
+ } else if (UN_CLASS(un1) != UN_UNKNOWN) {
+ if (current > 0) {
+ sp = sps[current]
+ call sprintf (Memc[cmd], SZ_LINE, "km/s %g %s")
+ call pargr (SP_W0(sp))
+ call pargstr (UN_UNITS(un1))
+ if (nscan() == 1) {
+ if (SP_XSCALE(sp) != 1.) {
+ rval = SP_W0(sp) * SP_XSCALE(sp)
+ call un_changer (un1, Memc[cmd], rval, 1, NO)
+ } else
+ rval = 0.
+ call printf ("velocity[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (rval)
+ } else {
+ un2 = un_open (Memc[cmd])
+ call un_ctranr (un2, un1, rval, rval, 1)
+ call un_close (un2)
+ SP_XSCALE(sp) = rval / SP_W0(sp)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("velocity:")
+ do i = 1, nspec {
+ sp = sps[i]
+ if (SP_XSCALE(sp) != 1.) {
+ call sprintf (Memc[cmd], SZ_LINE, "km/s %g %s")
+ call pargr (SP_W0(sp))
+ call pargstr (UN_UNITS(un1))
+ rval = SP_W0(sp) * SP_XSCALE(sp)
+ call un_changer (un1, Memc[cmd], rval, 1, NO)
+ } else
+ rval = 0.
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (rval)
+ }
+ } else {
+ do i = 1, nspec {
+ sp = sps[i]
+ call sprintf (Memc[cmd], SZ_LINE, "km/s %g %s")
+ call pargr (SP_W0(sp))
+ call pargstr (UN_UNITS(un1))
+ un2 = un_open (Memc[cmd])
+ call un_ctranr (un2, un1, rval, rval, 1)
+ call un_close (un1)
+ SP_XSCALE(sp) = rval / SP_W0(sp)
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ }
+ case REDSHIFT: # set or show redshift
+ if (nspec < 0)
+ goto done_
+ call gargr (rval)
+ un1 = UN(SP_SH(sps[1]))
+ if (UN_CLASS(un1) == UN_VEL) {
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("redshift[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XOFFSET(sp)/UN_SCALE(un1))
+ } else {
+ SP_XOFFSET(sp) = rval * UN_SCALE(un1)
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("redshift:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XOFFSET(sp)/UN_SCALE(un1))
+ }
+ } else {
+ do i = 1, nspec {
+ SP_XOFFSET(sp) = rval * UN_SCALE(un1)
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ } else if (UN_CLASS(un1) == UN_WAVE) {
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("redshift[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XSCALE(sp)-1)
+ } else {
+ rval = 1. + rval
+ SP_XSCALE(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("redshift:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XSCALE(sp)-1)
+ }
+ } else {
+ rval = 1. + rval
+ do i = 1, nspec {
+ SP_XSCALE(sps[i]) = rval
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ } else if (UN_CLASS(un1) == UN_FREQ || UN_CLASS(un1) == UN_ENERGY) {
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("redshift[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (1./SP_XSCALE(sp)-1)
+ } else {
+ rval = 1. / (1. + rval)
+ SP_XSCALE(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("redshift:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (1./SP_XSCALE(sp)-1)
+ }
+ } else {
+ rval = 1./ (1. + rval)
+ do i = 1, nspec {
+ SP_XSCALE(sps[i]) = rval
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ }
+ case OFFSET: # set or show intensity offset
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("offset[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_OFFSET(sp))
+ } else {
+ SP_OFFSET(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("offset:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_OFFSET(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ SP_OFFSET(sps[i]) = rval
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ case SCALE: # set or show intensity scale
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("scale[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_SCALE(sp))
+ } else {
+ SP_SCALE(sp) = rval
+ call sp_scale (sp, 1, step)
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("scale:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_SCALE(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ SP_SCALE(sps[i]) = rval
+ call sp_scale (sps[i], 1, step)
+ redraw = YES
+ }
+ }
+ }
+ case XLPOS: # set or show X label position
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("xlpos[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XLPOS(sp))
+ } else {
+ SP_XLPOS(sp) = rval
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("xlpos:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_XLPOS(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ SP_XLPOS(sps[i]) = rval
+ redraw = YES
+ }
+ }
+ }
+ case YLPOS: # set or show Y label position
+ call gargr (rval)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("ylpos[%d] %g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_YLPOS(sp))
+ } else {
+ SP_YLPOS(sp) = rval
+ redraw = YES
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("ylpos:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%g")
+ call pargi (SP_INDEX(sp))
+ call pargr (SP_YLPOS(sp))
+ }
+ } else {
+ do i = 1, nspec {
+ SP_YLPOS(sps[i]) = rval
+ redraw = YES
+ }
+ }
+ }
+ case LABEL: # Set or show label type
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (nscan() == 1) {
+ switch (labels) {
+ case LABEL_NONE:
+ call printf ("labels none")
+ case LABEL_IMNAME:
+ call printf ("labels imname")
+ case LABEL_IMTITLE:
+ call printf ("labels imtitle")
+ case LABEL_INDEX:
+ call printf ("labels index")
+ case LABEL_USER:
+ call printf ("labels user")
+ }
+ } else {
+ ncmd = strdic (Memc[cmd], Memc[cmd], SZ_LINE, LABELS)
+ if (ncmd == 0) {
+ call printf ("Unknown label type: %s")
+ call pargstr (Memc[cmd])
+ } else {
+ labels = ncmd
+ call sp_labels (sps, nspec, labels)
+ }
+ }
+ case ULABEL: # Set or show user labels
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("ulabel[%d] %s")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_ULABEL(sp))
+ } else {
+ call reset_scan ()
+ call gargwrd (Memc[cmd], SZ_LINE)
+ call gargstr (Memc[cmd], SZ_LINE)
+ call strcpy (Memc[cmd], SP_ULABEL(sp), SP_SZULABEL)
+ if (labels == LABEL_USER)
+ call strcpy (SP_ULABEL(sp), SP_LABEL(sp), SP_SZLABEL)
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("ulabel:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%s")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_ULABEL(sp))
+ }
+ } else {
+ call reset_scan ()
+ call gargwrd (Memc[cmd], SZ_LINE)
+ call gargstr (Memc[cmd], SZ_LINE)
+ do i = 1, nspec {
+ sp = sps[i]
+ call strcpy (Memc[cmd], SP_ULABEL(sp), SP_SZULABEL)
+ if (labels == LABEL_USER)
+ call strcpy (SP_ULABEL(sp), SP_LABEL(sp),SP_SZLABEL)
+ }
+ }
+ }
+ case PTYPE: # Set or show plotting type
+ call gargwrd (Memc[cmd], SZ_LINE)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("ptype[%d] %s")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_PTYPE(sp))
+ } else {
+ call strcpy (Memc[cmd], SP_PTYPE(sp), SP_SZPTYPE)
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("ptype:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%s")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_PTYPE(sp))
+ }
+ } else {
+ do i = 1, nspec
+ call strcpy (Memc[cmd], SP_PTYPE(sps[i]),
+ SP_SZPTYPE)
+ }
+ }
+ case UNITS: # Change plotting units
+ # Any change of units resets the offset and scale parametes.
+ call gargstr (Memc[cmd], SZ_LINE)
+ iferr {
+ do i = 1, nspec {
+ if (j > 0 && i != j)
+ next
+ sp = sps[i]
+ sh = SP_SH(sp)
+ call un_changer (UN(sh), Memc[cmd], Memr[SX(sh)],
+ SN(sh), YES)
+ SP_W0(sp) = Memr[SX(sh)]
+ SP_WPC(sp) = (Memr[SX(sh)+SN(sh)-1] - Memr[SX(sh)]) /
+ (SN(sh) - 1)
+ SP_XSCALE(sp) = 1.
+ SP_XOFFSET(sp) = 0.
+ call sp_scale (sp, 1, step)
+ if (i == 1) {
+ call strcpy (Memc[cmd], units, SZ_FNAME)
+ call gt_sets (gt, GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt, GTXUNITS, UN_UNITS(UN(sh)))
+ }
+ redraw = YES
+ }
+ } then
+ call erract (EA_WARN)
+ case COLOR: # Set or show color
+ call gargi (j)
+ if (current > 0) {
+ sp = sps[current]
+ if (nscan() == 1) {
+ call printf ("color[%d] %d")
+ call pargi (SP_INDEX(sp))
+ call pargi (SP_COLOR(sp))
+ } else {
+ SP_COLOR(sp) = j
+ }
+ } else {
+ if (nscan() == 1) {
+ call printf ("color:")
+ do i = 1, nspec {
+ sp = sps[i]
+ call printf (" %d=%d")
+ call pargi (SP_INDEX(sp))
+ call pargi (SP_COLOR(sp))
+ }
+ } else {
+ do i = 1, nspec
+ SP_COLOR(sps[i]) = j
+ }
+ }
+ default: # Print unknown command
+ call printf ("Unknown command: %s\007")
+ call pargstr (cmdstr)
+ }
+
+done_ call sfree (stack)
+end
+
+
+# SP_GDATA -- Get spectrum and add it to the array of spectrum structures.
+# Return an error if the image is not found. If a two or three dimensional
+# image enter each line. The spectrum data kept in memory and the image is
+# closed.
+
+procedure sp_gdata (image, units, current, sps, nspec)
+
+char image[ARB] # Image name
+char units[ARB] # Coordinate units
+int current # Element to append
+pointer sps # Pointer to array of spectra structures
+int nspec # Number of spectra
+
+real scale # Default intensity scale
+real offset # Default intensity offset
+real xlpos, ylpos # Default position of labels
+char ptype[SP_SZPTYPE] # Default plot type
+
+int i, j, k, l, m, trans
+pointer sp, im, mw, sh, stack, aps, bands, str, ptr
+
+int ctor(), open(), fscan(), nowhite(), clgwrd()
+bool rng_elementi(), fp_equalr()
+real clgetr(), asumr(), imgetr(), sp_logerr()
+pointer immap(), smw_openim(), rng_open()
+
+errchk immap, smw_openim, open
+
+extern sp_logerr
+
+begin
+ call smark (stack)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Map the image and return an error if this fails.
+ im = immap (image, READ_ONLY, 0)
+ mw = smw_openim (im)
+
+ # Get parameters.
+ if (nspec == 0) {
+ #scale = clgetr ("scale")
+ #offset = clgetr ("offset")
+ xlpos = clgetr ("xlpos")
+ ylpos = clgetr ("ylpos")
+ call clgstr ("ptype", ptype, SP_SZPTYPE)
+ trans = clgwrd ("transform", Memc[str], SZ_LINE, TRANSFORMS)
+ }
+
+ call clgstr ("scale", Memc[str], SZ_LINE)
+ if (nowhite (Memc[str], Memc[str], SZ_LINE) == 0)
+ call error (1, "Error in scale parameter")
+ if (Memc[str] == '@') {
+ j = open (Memc[str+1], READ_ONLY, TEXT_FILE)
+ do i = 1, nspec+1
+ if (fscan(j) == EOF)
+ call error (1, "Error reading scale file")
+ call gargr (scale)
+ call close (j)
+ } else if (IS_ALPHA(Memc[str])) {
+ scale = imgetr (im, Memc[str])
+ } else {
+ i = 1
+ if (ctor (Memc[str], i, scale) == 0)
+ call error (1, "Error in scale parameter")
+ }
+
+ call clgstr ("offset", Memc[str], SZ_LINE)
+ if (nowhite (Memc[str], Memc[str], SZ_LINE) == 0)
+ call error (1, "Error in offset parameter")
+ if (Memc[str] == '@') {
+ j = open (Memc[str+1], READ_ONLY, TEXT_FILE)
+ do i = 1, nspec+1
+ if (fscan(j) == EOF)
+ call error (1, "Error reading offset file")
+ call gargr (offset)
+ call close (j)
+ } else if (IS_ALPHA(Memc[str]))
+ offset = imgetr (im, Memc[str])
+ else {
+ i = 1
+ if (ctor (Memc[str], i, offset) == 0)
+ call error (1, "Error in offset parameter")
+ }
+
+ call clgstr ("apertures", Memc[str], SZ_LINE)
+ iferr (aps = rng_open (Memc[str], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture/record list")
+ call clgstr ("bands", Memc[str], SZ_LINE)
+ iferr (bands = rng_open (Memc[str], INDEF, INDEF, INDEF))
+ call error (0, "Bad band list")
+
+ # For each line in the image, allocate memory for the spectrum
+ # structure, get the pixel data, compute the mean and limits,
+ # set the structure parameters, and add the structure to the
+ # array of structures.
+
+ do j = 1, SMW_NBANDS(mw) {
+ if (SMW_FORMAT(mw) != SMW_ND)
+ if (!rng_elementi (bands, j))
+ next
+ do i = 1, SMW_NSPEC(mw) {
+ if (SMW_FORMAT(mw) == SMW_ND) {
+ call smw_mw (mw, i, j, ptr, k, l)
+ if (!rng_elementi (aps, k) || !rng_elementi (bands, l))
+ next
+ } else {
+ call shdr_open (im, mw, i, j, INDEFI, SHHDR, sh)
+ if (!rng_elementi (aps, AP(sh)))
+ next
+ }
+ call shdr_open (im, mw, i, j, INDEFI, SHDATA, sh)
+ iferr (call shdr_units (sh, units))
+ ;
+
+ call sp_alloc (sp, sh)
+ SP_NPTS(sp) = SN(sh)
+ SP_W0(sp) = Memr[SX(sh)]
+ SP_WPC(sp) = (Memr[SX(sh)+SN(sh)-1] - Memr[SX(sh)]) /
+ (SN(sh) - 1)
+ switch (trans) {
+ case TRANS_LOG:
+ SP_OMIN(sp) = MAX_REAL; SP_OMAX(sp) = -MAX_REAL
+ ptr = SY(sh);
+ do m = 1, SP_NPTS(sp) {
+ if (Memr[ptr] > 0.) {
+ SP_OMIN(sp) = min (SP_OMIN(sp), Memr[ptr])
+ SP_OMAX(sp) = max (SP_OMAX(sp), Memr[ptr])
+ }
+ ptr = ptr + 1
+ }
+ if (SP_OMAX(sp) > 0.) {
+ call amaxkr (Memr[SY(sh)], SP_OMIN(sp), Memr[SY(sh)],
+ SN(sh))
+ call alogr (Memr[SY(sh)], Memr[SY(sh)], SN(sh),
+ sp_logerr)
+ call amovr (Memr[SY(sh)], Memr[SY(SP_SH(sp))], SN(sh))
+ }
+ }
+ SP_OMEAN(sp) = asumr (Memr[SY(sh)], SN(sh)) / SN(sh)
+ call alimr (Memr[SY(sh)], SN(sh), SP_OMIN(sp), SP_OMAX(sp))
+
+ SP_XSCALE(sp) = 1.
+ SP_XOFFSET(sp) = 0.
+ SP_SCALE(sp) = scale
+ SP_OFFSET(sp) = offset
+ SP_XLPOS(sp) = xlpos
+ SP_YLPOS(sp) = ylpos
+ SP_COLOR(sp) = 1
+
+ call sprintf (SP_IMNAME(sp), SP_SZNAME, "%s%s(%d)")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargi (AP(sh))
+ call strcpy (TITLE(sh), SP_IMTITLE(sp), SP_SZTITLE)
+ call strcpy (ptype, SP_PTYPE(sp), SP_SZPTYPE)
+ SP_ULABEL(sp) = EOS
+
+ call sp_add (sp, current, sps, nspec)
+ }
+ }
+
+ # Close the image.
+ call shdr_close (sh)
+ call rng_close (bands)
+ call rng_close (aps)
+ call smw_close (mw)
+ call imunmap (im)
+
+ call sfree (stack)
+end
+
+
+# SP_LINEAR -- Reset linear coordinates
+
+procedure sp_linear (sp)
+
+pointer sp # SPECPLOT pointer
+
+int i
+pointer x
+
+begin
+ x = SX(SP_SH(sp))
+ do i = 0, SP_NPTS(sp)-1
+ Memr[x+i] = SP_W0(sp) + i * SP_WPC(sp)
+ SP_XSCALE(sp) = 1.
+ SP_XOFFSET(sp) = 0.
+end
+
+
+# SP_DELETE -- Delete a spectrum from memory. The index numbers are
+# decreased to fill the hole.
+
+procedure sp_delete (current, sps, nspec)
+
+int current # Element to be deleted
+pointer sps # Pointer to array of spectrum structures
+int nspec # Number of spectra
+
+int i
+
+begin
+ if (nspec == 0)
+ return
+
+ for (i = current; i < nspec; i = i + 1) {
+ Memi[sps+i-1] = Memi[sps+i]
+ SP_INDEX(Memi[sps+i-1]) = SP_INDEX(Memi[sps+i-1]) - 1
+ }
+ nspec = nspec - 1
+end
+
+
+# SP_ADD -- Add a spectrum structure to the array of structures
+# following the specified element. The spectrum index is defined to be
+# one higher than the spectrum to be followed and all higher indexed
+# spectra are increased by 1. Special cases are when there are no
+# spectra in which case the index is set to 1 and when the current
+# element to be followed is zero. The current element is set to the
+# added spectrum. The array of pointers is expanded in blocks of 100.
+
+procedure sp_add (sp, current, sps, nspec)
+
+pointer sp # Spectrum structure to be appended
+int current # Element followed (in), added element (out)
+pointer sps # Pointer to array of spectrum structures
+int nspec # Number of spectra
+
+int i
+
+begin
+ # Reallocate memory for the array of structure pointers in steps of 100.
+ if (mod (nspec, 100) == 0)
+ call realloc (sps, nspec + 100, TY_POINTER)
+
+ # Shift higher spectra in the array and increase the index numbers by 1
+ # and then add the new spectrum pointer.
+ for (i = nspec; i > current; i = i - 1) {
+ Memi[sps+i] = Memi[sps+i-1]
+ SP_INDEX(Memi[sps+i]) = SP_INDEX(Memi[sps+i]) + 1
+ }
+ Memi[sps+current] = sp
+
+ # Set the new spectrum index.
+ if (nspec == 0)
+ SP_INDEX(sp) = 1
+ else if (current == 0)
+ SP_INDEX(sp) = SP_INDEX(Memi[sps+current+1]) - 1
+ else
+ SP_INDEX(sp) = SP_INDEX(Memi[sps+current-1]) + 1
+
+ # Adjust the current element and number of spectra.
+ current = current + 1
+ nspec = nspec + 1
+end
+
+
+# SP_LABELS -- Set the spectrum labels to the specified type.
+
+procedure sp_labels (sps, nspec, labels)
+
+pointer sps[ARB] # Spectrum pointers
+int nspec # Number of spectra
+int labels # Type of labels
+
+int i
+
+begin
+ for (i = 1; i <= nspec; i = i + 1) {
+ switch (labels) {
+ case LABEL_NONE:
+ SP_LABEL(sps[i]) = EOS
+ case LABEL_IMNAME:
+ call strcpy (SP_IMNAME(sps[i]), SP_LABEL(sps[i]), SP_SZLABEL)
+ case LABEL_IMTITLE:
+ call strcpy (SP_IMTITLE(sps[i]), SP_LABEL(sps[i]), SP_SZLABEL)
+ case LABEL_INDEX:
+ call sprintf (SP_LABEL(sps[i]), SP_SZLABEL, "%-4d")
+ call pargi (SP_INDEX(sps[i]))
+ case LABEL_USER:
+ call strcpy (SP_ULABEL(sps[i]), SP_LABEL(sps[i]), SP_SZULABEL)
+ }
+ }
+end
+
+
+# SP_ALLOC -- Allocate memory for a spectrum structure with given number of
+# data points. The MWCS is not used.
+
+procedure sp_alloc (sp, sh)
+
+pointer sp # Spectrum structure pointer to be allocated
+pointer sh # Spectrum header pointer
+
+begin
+ call calloc (sp, SP_LEN, TY_STRUCT)
+ call calloc (SP_PX(sp), SN(sh), TY_REAL)
+ call calloc (SP_PY(sp), SN(sh), TY_REAL)
+
+ call shdr_copy (sh, SP_SH(sp), NO)
+ MW(SP_SH(sp)) = NULL
+end
+
+
+# SP_FREE -- Free a spectrum structure.
+
+procedure sp_free (sp)
+
+pointer sp, sh # Spectrum structure pointers
+
+begin
+ sh = SP_SH(sp)
+ call shdr_close (sh)
+
+ call mfree (SP_PX(sp), TY_REAL)
+ call mfree (SP_PY(sp), TY_REAL)
+ call mfree (sp, TY_STRUCT)
+end
+
+
+# SP_NEAREST -- Find the nearest spectrum to the cursor and return the element.
+# Return zero if no spectra are defined. The distance is in NDC.
+
+int procedure sp_nearest (gp, wx1, wy1, key, cmd, sps, nspec)
+
+pointer gp # GIO pointer
+real wx1, wy1 # Cursor position
+int key # Key
+char cmd[ARB] # Cursor command
+pointer sps[ARB] # Array of structure pointers
+int nspec # Number of spectra
+
+int i, j, k, stridxs()
+real wx0, wy0, x0, y0, x1, y1, r2, r2min
+pointer sp, px, py
+
+begin
+ # Check for explicit specification.
+ if (key == ':') {
+ if (stridxs ("[", cmd) > 0)
+ return (1)
+ }
+
+ if (IS_INDEFR(wx1))
+ wx1 = 0.
+ if (IS_INDEFR(wy1))
+ wy1 = 0.
+
+ # Transform world cursor coordinates to NDC.
+ call gctran (gp, wx1, wy1, wx0, wy0, 1, 0)
+
+ # Search for nearest point.
+ k = 0
+ r2min = MAX_REAL
+ do i = 1, nspec {
+ sp = sps[i]
+ px = SP_PX(sp) - 1
+ py = SP_PY(sp) - 1
+ do j = 1, SP_NPTS(sp) {
+ x1 = Memr[px + j]
+ y1 = Memr[py + j]
+ call gctran (gp, x1, y1, x0, y0, 1, 0)
+ r2 = (x0 - wx0) ** 2 + (y0 - wy0) ** 2
+ if (r2 < r2min) {
+ r2min = r2
+ k = i
+ }
+ }
+ }
+
+ return (k)
+end
+
+
+# SP_SHOW -- Show parameter information. Clear the screen if the output is
+# to the graphics device otherwise append to the specified file.
+
+procedure sp_show (file, gp, sps, nspec, step)
+
+char file[ARB] # Optional file
+pointer gp # Graphics pointer
+pointer sps[ARB] # Spectra data
+int nspec # Number of spectra
+real step # Separation step
+
+int i, fd
+pointer stack, line, sp
+
+int open()
+errchk open()
+
+begin
+ fd = open (file, APPEND, TEXT_FILE)
+ if (gp != NULL)
+ call gdeactivate (gp, AW_CLEAR)
+
+ call smark (stack)
+ call salloc (line, SZ_LINE, TY_CHAR)
+ call sysid (Memc[line], SZ_LINE)
+ call fprintf (fd, "%s\n\n")
+ call pargstr (Memc[line])
+
+ call fprintf (fd, "Step = %g\n\n")
+ call pargr (step)
+ call fprintf (fd, " %16s %7s %7s %7s %7s %s\n")
+ call pargstr ("Image Name")
+ call pargstr ("W0")
+ call pargstr ("WPC")
+ call pargstr ("Offset")
+ call pargstr ("Scale")
+ call pargstr ("Title")
+
+ do i = 1, nspec {
+ sp = sps[i]
+ call fprintf (fd, "%2d %16s %7g %7g %7g %7g %s\n")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_IMNAME(sp))
+ call pargr (SP_W0(sp)*SP_XSCALE(sp)+SP_XOFFSET(sp))
+ call pargr (SP_WPC(sp)*SP_XSCALE(sp))
+ call pargr (SP_OFFSET(sp))
+ call pargr (SP_SCALE(sp))
+ call pargstr (SP_IMTITLE(sp))
+ }
+
+ call sfree (stack)
+
+ call close (fd)
+ if (gp != NULL)
+ call greactivate (gp, AW_PAUSE)
+end
+
+
+# SP_VSHOW -- Show verbose parameter information. Clear the screen if the
+# output is to the graphics device otherwise append to the specified file.
+
+procedure sp_vshow (file, gp, sps, nspec, step)
+
+char file[ARB] # Optional file
+pointer gp # Graphics pointer
+pointer sps[ARB] # Spectra data
+int nspec # Numbeer of spectra
+real step # Separation step
+
+int i, fd
+real z, v
+pointer stack, line, sp, un
+
+int open()
+errchk open()
+
+begin
+ fd = open (file, APPEND, TEXT_FILE)
+ if (gp != NULL)
+ call gdeactivate (gp, AW_CLEAR)
+
+ call smark (stack)
+ call salloc (line, SZ_LINE, TY_CHAR)
+ call sysid (Memc[line], SZ_LINE)
+ call fprintf (fd, "%s\n\n")
+ call pargstr (Memc[line])
+
+ call fprintf (fd, "Step = %g\n")
+ call pargr (step)
+ call fprintf (fd, "\n %16s %7s %7s %7s %7s %s\n")
+ call pargstr ("Image Name")
+ call pargstr ("W0")
+ call pargstr ("WPC")
+ call pargstr ("Offset")
+ call pargstr ("Scale")
+ call pargstr ("Title")
+
+ do i = 1, nspec {
+ sp = sps[i]
+ call fprintf (fd, "%2d %16s %7g %7g %7g %7g %s\n")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_IMNAME(sp))
+ call pargr (SP_W0(sp)*SP_XSCALE(sp)+SP_XOFFSET(sp))
+ call pargr (SP_WPC(sp)*SP_XSCALE(sp))
+ call pargr (SP_OFFSET(sp))
+ call pargr (SP_SCALE(sp))
+ call pargstr (SP_IMTITLE(sp))
+ }
+
+ call fprintf (fd, "\n %16s %9s %9s %9s %9s\n")
+ call pargstr ("Image Name")
+ call pargstr ("Mean")
+ call pargstr ("DW0")
+ call pargstr ("Z")
+ call pargstr ("V(km/s)")
+
+ un = UN(SP_SH(sps[1]))
+ if (UN_CLASS(un) == UN_VEL) {
+ do i = 1, nspec {
+ sp = sps[i]
+ z = SP_XOFFSET(sp) / UN_SCALE(un)
+ v = SP_XOFFSET(sp)
+ call fprintf (fd, "%2d %16s %9g %9g %9g %9g\n")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_IMNAME(sp))
+ call pargr (SP_OMEAN(sp))
+ call pargr (SP_XOFFSET(sp))
+ call pargr (z)
+ call pargr (v)
+ }
+ } else if (UN_CLASS(un) == UN_WAVE) {
+ do i = 1, nspec {
+ sp = sps[i]
+ if (SP_XSCALE(sp) != 1.) {
+ call sprintf (Memc[line], SZ_LINE, "km/s %g %s")
+ call pargr (SP_W0(sp))
+ call pargstr (UN_UNITS(un))
+ z = SP_XSCALE(sp) - 1
+ v = SP_W0(sp) * SP_XSCALE(sp)
+ call un_changer (un, Memc[line], v, 1, NO)
+ } else {
+ z = 0.
+ v = 0.
+ }
+ call fprintf (fd, "%2d %16s %9g %9g %9g %9g\n")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_IMNAME(sp))
+ call pargr (SP_OMEAN(sp))
+ call pargr (SP_XOFFSET(sp))
+ call pargr (z)
+ call pargr (v)
+ }
+ } else if (UN_CLASS(un) == UN_FREQ || UN_CLASS(un) == UN_ENERGY) {
+ do i = 1, nspec {
+ sp = sps[i]
+ if (SP_XSCALE(sp) != 1.) {
+ call sprintf (Memc[line], SZ_LINE, "km/s %g %s")
+ call pargr (SP_W0(sp))
+ call pargstr (UN_UNITS(un))
+ z = 1. / SP_XSCALE(sp) - 1
+ v = SP_W0(sp) * SP_XSCALE(sp)
+ call un_changer (un, Memc[line], v, 1, NO)
+ } else {
+ z = 0.
+ v = 0.
+ }
+ call fprintf (fd, "%2d %16s %9g %9g %9g %9g\n")
+ call pargi (SP_INDEX(sp))
+ call pargstr (SP_IMNAME(sp))
+ call pargr (SP_OMEAN(sp))
+ call pargr (SP_XOFFSET(sp))
+ call pargr (z)
+ call pargr (v)
+ }
+ }
+
+ call sfree (stack)
+
+ call close (fd)
+ if (gp != NULL)
+ call greactivate (gp, AW_PAUSE)
+end
+
+
+# SP_LOGERR -- Value for non-positive values in log function.
+
+real procedure sp_logerr (x)
+
+real x
+
+begin
+ return (0.)
+end
diff --git a/noao/onedspec/t_specshift.x b/noao/onedspec/t_specshift.x
new file mode 100644
index 00000000..e5b8ea0e
--- /dev/null
+++ b/noao/onedspec/t_specshift.x
@@ -0,0 +1,222 @@
+include <error.h>
+include <smw.h>
+
+# Function types.
+define CHEBYSHEV 1 # CURFIT Chebyshev polynomial
+define LEGENDRE 2 # CURFIT Legendre polynomial
+define SPLINE3 3 # CURFIT cubic spline
+define SPLINE1 4 # CURFIT linear spline
+define PIXEL 5 # pixel coordinate array
+define SAMPLE 6 # sampled coordinates
+
+
+# T_SSHIFT -- Shift the spectral coordinates
+
+procedure t_sshift ()
+
+int list # Input list of spectra
+double shift # Shift to apply
+pointer aps # Aperture list
+bool verbose # Verbose?
+
+int ap, beam, dtype, nw
+double w1, dw, z
+real aplow[2], aphigh[2]
+pointer sp, image, coeff, tmp, im, mw
+
+bool clgetb()
+double clgetd()
+int imtopenp(), imtgetim()
+pointer rng_open(), immap(), smw_openim()
+errchk immap, smw_openim, smw_gwattrs, smw_swattrs, sshift
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ coeff = NULL
+
+ list = imtopenp ("spectra")
+ shift = clgetd ("shift")
+ call clgstr ("apertures", Memc[image], SZ_FNAME)
+ verbose = clgetb ("verbose")
+
+ iferr (aps = rng_open (Memc[image], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+
+ while (imtgetim (list, Memc[image], SZ_FNAME) != EOF) {
+ im = NULL
+ mw = NULL
+ iferr {
+ tmp = immap (Memc[image], READ_WRITE, 0); im = tmp
+ tmp = smw_openim (im); mw = tmp
+
+ switch (SMW_FORMAT(mw)) {
+ case SMW_ND:
+ call smw_gwattrs (mw, 1, 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, coeff)
+ w1 = w1 + shift
+ call smw_swattrs (mw, 1, 1, ap, beam, dtype,
+ w1, dw, nw, z, aplow, aphigh, Memc[coeff])
+ if (verbose) {
+ call printf ("%s: shift = %g, %g --> %g\n")
+ call pargstr (Memc[image])
+ call pargd (shift)
+ call pargd (w1 - shift)
+ call pargd (w1)
+ }
+ case SMW_ES, SMW_MS:
+ call sshift (im, mw, Memc[image], aps, shift,
+ verbose)
+ }
+ } then
+ call erract (EA_WARN)
+
+ if (mw != NULL) {
+ call smw_saveim (mw, im)
+ call smw_close (mw)
+ }
+ if (im != NULL)
+ call imunmap (im)
+ }
+
+ call rng_close (aps)
+ call imtclose (list)
+ call mfree (coeff, TY_CHAR)
+ call sfree (sp)
+end
+
+
+# SSHIFT -- Shift coordinate zero point of selected aperture in
+# MULTISPEC and EQUISPEC images.
+
+procedure sshift (im, mw, image, aps, shift, verbose)
+
+pointer im # IMIO pointer
+pointer mw # MWCS pointer
+char image[ARB] # Image name
+pointer aps # Aperture range list
+double shift # Shift to add
+bool verbose # Verbose?
+
+int i, ap, beam, dtype, nw, naps
+double w1, dw, z
+real aplow[2], aphigh[2]
+pointer coeff, coeffs
+bool rng_elementi()
+errchk sshift1
+
+begin
+ coeff = NULL
+ coeffs = NULL
+
+ # Go through each spectrum and change the selected apertures.
+ naps = 0
+ do i = 1, SMW_NSPEC(mw) {
+ # Get aperture info
+ iferr (call smw_gwattrs (mw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, coeff))
+ break
+
+ # Check if aperture is to be changed
+ if (!rng_elementi (aps, ap))
+ next
+
+ # Apply shift
+ w1 = w1 + shift
+ if (dtype == 2)
+ call sshift1 (shift, coeff)
+
+ call smw_swattrs (mw, i, 1, ap, beam, dtype, w1, dw, nw, z,
+ aplow, aphigh, Memc[coeff])
+
+ # Make record
+ if (verbose) {
+ if (naps == 1) {
+ call printf ("%s: shift = %g\n")
+ call pargstr (image)
+ call pargd (shift)
+ }
+ call printf (" Aperture %d: %g --> %g\n")
+ call pargi (ap)
+ call pargd (w1 - shift)
+ call pargd (w1)
+ }
+ }
+
+ call mfree (coeff, TY_CHAR)
+ call mfree (coeffs, TY_DOUBLE)
+end
+
+
+# SSHIFT1 -- Shift coordinate zero point of nonlinear functions.
+
+procedure sshift1 (shift, coeff)
+
+double shift # Shift to add
+pointer coeff # Attribute function coefficients
+
+int i, j, ip, nalloc, ncoeff, type, order, fd
+double dval
+pointer coeffs
+int ctod(), stropen()
+errchk stropen
+
+begin
+ if (coeff == NULL)
+ return
+ if (Memc[coeff] == EOS)
+ return
+
+ coeffs = NULL
+ ncoeff = 0
+ ip = 1
+ while (ctod (Memc[coeff], ip, dval) > 0) {
+ if (coeffs == NULL) {
+ nalloc = 10
+ call malloc (coeffs, nalloc, TY_DOUBLE)
+ } else if (ncoeff == nalloc) {
+ nalloc = nalloc + 10
+ call realloc (coeffs, nalloc, TY_DOUBLE)
+ }
+ Memd[coeffs+ncoeff] = dval
+ ncoeff = ncoeff + 1
+ }
+ ip = ip + SZ_LINE
+ call realloc (coeff, ip, TY_CHAR)
+ call aclrc (Memc[coeff], ip)
+ fd = stropen (Memc[coeff], ip, NEW_FILE)
+
+ ip = 0
+ while (ip < ncoeff) {
+ if (ip > 0)
+ call fprintf (fd, " ")
+ Memd[coeffs+ip+1] = Memd[coeffs+ip+1] + shift
+ type = nint (Memd[coeffs+ip+2])
+ order = nint (Memd[coeffs+ip+3])
+ call fprintf (fd, "%.3g %g %d %d")
+ call pargd (Memd[coeffs+ip])
+ call pargd (Memd[coeffs+ip+1])
+ call pargi (type)
+ call pargi (order)
+ switch (type) {
+ case CHEBYSHEV, LEGENDRE:
+ j = 6 + order
+ case SPLINE3:
+ j = 9 + order
+ case SPLINE1:
+ j = 7 + order
+ case PIXEL:
+ j = 4 + order
+ case SAMPLE:
+ j = 5 + order
+ }
+ do i = 4, j-1 {
+ call fprintf (fd, " %g")
+ call pargd (Memd[coeffs+ip+i])
+ }
+ ip = ip + j
+ }
+ call strclose (fd)
+
+ call mfree (coeffs, TY_DOUBLE)
+end
diff --git a/noao/onedspec/t_standard.x b/noao/onedspec/t_standard.x
new file mode 100644
index 00000000..9a596150
--- /dev/null
+++ b/noao/onedspec/t_standard.x
@@ -0,0 +1,835 @@
+include <error.h>
+include <imhdr.h>
+include <imset.h>
+include <gset.h>
+include <mach.h>
+include <pkg/gtools.h>
+include <smw.h>
+
+define KEY "noao$onedspec/standard.key"
+define PROMPT "STANDARD options"
+
+define VLIGHT 2.997925e18 # Velocity of light in Angstroms/sec
+define EXT_LOOKUP 1 # Interp entry ID for extinction table
+define MAG_LOOKUP 2 # Interp entry ID for magnitude table
+
+define STD_LEN 13 # Length of standard structure
+define STD_AP Memi[$1] # Aperture number
+define STD_TYPE Memi[$1+1] # Spectrum type
+define STD_SH Memi[$1+2] # Pointer to spectrum parameters
+define STD_IFLAG Memi[$1+3] # Interactive flag
+define STD_NWAVES Memi[$1+4] # Number of calibration points
+define STD_WAVES Memi[$1+5] # Pointer to standard star wavelengths
+define STD_DWAVES Memi[$1+6] # Pointer to standard star bandpasses
+define STD_MAGS Memi[$1+7] # Pointer to standard star magnitudes
+define STD_FLUXES Memi[$1+8] # Pointer to standard star fluxes
+define CAL_NWAVES Memi[$1+9] # Number of calibration points
+define CAL_WAVES Memi[$1+10] # Pointer to calibration wavelengths
+define CAL_DWAVES Memi[$1+11] # Pointer to calibration bandpasses
+define CAL_MAGS Memi[$1+12] # Pointer to calibration magnitudes
+
+# Object flags
+define NONE -1 # No object flag
+define SKY 0 # Sky
+define OBJ 1 # Object
+
+# Interactive flags
+define ANSWERS "|no|yes|N|Y|NO|YES|NO!|YES!|"
+define NO1 1 # No for a single spectrum
+define YES1 2 # Yes for a single spectrum
+define N2 3 # No for all spectra of the same aperture
+define Y2 4 # Yes for all spectra of the same aperture
+define NO2 5 # No for all spectra of the same aperture
+define YES2 6 # Yes for all spectra of the same aperture
+define NO3 7 # No for all spectra
+define YES3 8 # Yes for all spectra
+
+# T_STANDARD -- Read standard star spectrum and compare with tabulated
+# fluxes for given star to ascertain the system sensitivity
+# across the spectrum. The user may optionally define
+# new and arbitrary bandpasses
+#
+# The sensitivity function is stored in tabular form in a file
+# containing the wavelength, sensitivity factor, and counts per
+# bandpass at each required position along the spectrum.
+# The file is appended to for each new measurement from either
+# same or different standard stars.
+
+procedure t_standard()
+
+int list # List of input spectra
+pointer output # Output standard file
+pointer observatory # Observatory
+pointer aps # Aperture list
+real bandwidth # Width of bandpass
+real bandsep # Separation of bandpass
+bool bswitch # Beam switch?
+bool samestar # Same star in all apertures?
+int interactive # Interactive bandpass definition
+
+bool newobs, obshead
+int i, j, line, enwaves, nstds
+real wave, dwave, latitude
+pointer sp, units, errstr, str, image, ewaves, emags
+pointer im, mw, un, unang, sh, obj, sky, std, stds, obs, gp
+
+int imtgetim(), errget()
+real clgetr(), obsgetr()
+bool clgetb(), rng_elementi(), streq()
+pointer imtopenp(), rng_open(), immap(), smw_openim(), un_open()
+errchk immap, smw_openim, shdr_open, std_calib, get_airm, ext_load, obsimopen
+errchk un_open, std_gcalib
+
+begin
+ call smark (sp)
+ call salloc (image, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (observatory, SZ_FNAME, TY_CHAR)
+ call salloc (units, SZ_FNAME, TY_CHAR)
+ call salloc (errstr, SZ_FNAME, TY_CHAR)
+ call salloc (str, SZ_LINE, TY_CHAR)
+
+ # Get task parameters.
+ list = imtopenp ("input")
+ call clgstr ("records", Memc[image], SZ_FNAME)
+ call odr_openp (list, Memc[image])
+ call clgstr ("output", Memc[output], SZ_FNAME)
+ call clgstr ("apertures", Memc[image], SZ_FNAME)
+ bandwidth = clgetr ("bandwidth")
+ bandsep = clgetr ("bandsep")
+ bswitch = clgetb ("beam_switch")
+ if (bswitch)
+ samestar = true
+ else
+ samestar = clgetb ("samestar")
+ if (clgetb ("interact"))
+ interactive = YES1
+ else
+ interactive = NO3
+
+ # Expand the aperture list.
+ iferr (aps = rng_open (Memc[image], INDEF, INDEF, INDEF))
+ call error (0, "Bad aperture list")
+
+ call ext_load (ewaves, emags, enwaves)
+
+ un = NULL
+ sh = NULL
+ obj = NULL
+ sky = NULL
+ obs = NULL
+ gp = NULL
+ nstds = 0
+ while (imtgetim (list, Memc[image], SZ_FNAME) != EOF) {
+ iferr (im = immap (Memc[image], READ_ONLY, 0)) {
+ call erract (EA_WARN)
+ next
+ }
+ mw = smw_openim (im)
+ call shdr_open (im, mw, 1, 1, INDEFI, SHHDR, sh)
+
+ if (DC(sh) == DCNO) {
+ call eprintf ("%s: No dispersion function\n")
+ call pargstr (Memc[image])
+ call smw_close (MW(sh))
+ call imunmap (IM(sh))
+ next
+ }
+
+ # Work in units of first spectrum.
+ if (un == NULL) {
+ call strcpy (UNITS(sh), Memc[units], SZ_FNAME)
+ un = un_open (Memc[units])
+ unang = un_open ("Angstroms")
+ call un_ctranr (unang, un, Memr[ewaves], Memr[ewaves], enwaves)
+ }
+ if (IS_INDEF (IT(sh))) {
+ call printf ("%s: ")
+ call pargstr (Memc[image])
+ call flush (STDOUT)
+ IT(sh) = clgetr ("exptime")
+ call imunmap (IM(sh))
+ ifnoerr (im = immap (Memc[image], READ_WRITE, 0)) {
+ IM(sh) = im
+ call imseti (IM(sh), IM_WHEADER, YES)
+ call imaddr (IM(sh), "exptime", IT(sh))
+ } else {
+ im = immap (Memc[image], READ_ONLY, 0)
+ IM(sh) = im
+ }
+ }
+
+ do line = 1, SMW_NSPEC(mw) {
+ call shdr_open (im, mw, line, 1, INDEFI, SHDATA, sh)
+ if (!rng_elementi (aps, AP(sh)))
+ next
+ call shdr_units (sh, Memc[units])
+
+ if (!bswitch || OFLAG(sh) == OBJ) {
+ call printf ("%s%s(%d): %s\n")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargi (AP(sh))
+ call pargstr (TITLE(sh))
+ call flush (STDOUT)
+ }
+
+ if (IS_INDEF (AM(sh))) {
+ call clgstr ("observatory", Memc[observatory], SZ_FNAME)
+ call obsimopen (obs, im, Memc[observatory], NO, newobs,
+ obshead)
+ if (newobs)
+ call obslog (obs, "STANDARD", "latitude", STDOUT)
+ latitude = obsgetr (obs, "latitude")
+ iferr (call get_airm (RA(sh), DEC(sh), HA(sh), ST(sh),
+ latitude, AM(sh))) {
+ call printf ("%s: ")
+ call pargstr (Memc[image])
+ call flush (STDOUT)
+ AM(sh) = clgetr ("airmass")
+ call imunmap (IM(sh))
+ ifnoerr (im = immap (Memc[image], READ_WRITE, 0)) {
+ IM(sh) = im
+ call imseti (IM(sh), IM_WHEADER, YES)
+ call imaddr (IM(sh), "airmass", AM(sh))
+ } else {
+ im = immap (Memc[image], READ_ONLY, 0)
+ IM(sh) = im
+ }
+ }
+ }
+
+ for (i=0; i<nstds; i=i+1) {
+ std = Memi[stds+i]
+ if (STD_AP(std) == AP(sh))
+ break
+ }
+
+ # Allocate space for this aperture if not already done.
+ if (i >= nstds) {
+ if (nstds == 0)
+ call malloc (stds, 10, TY_INT)
+ else if (mod (nstds, 10) == 0)
+ call realloc (stds, nstds+10, TY_INT)
+ call salloc (std, STD_LEN, TY_STRUCT)
+ Memi[stds+i] = std
+ nstds = nstds + 1
+
+ STD_AP(std) = AP(sh)
+ STD_TYPE(std) = NONE
+ STD_SH(std) = NULL
+ STD_IFLAG(std) = interactive
+ STD_NWAVES(std) = 0
+
+ if (!samestar || i == 0) {
+ # Read calibration data
+ Memc[str] = EOS
+ repeat {
+ iferr (call std_gcalib (std, un)) {
+ j = errget (Memc[errstr], SZ_FNAME)
+ if (streq (Memc[errstr], Memc[str]))
+ call erract (EA_ERROR)
+ call strcpy (Memc[errstr], Memc[str], SZ_LINE)
+ call erract (EA_WARN)
+ next
+ }
+ break
+ }
+ } else {
+ CAL_NWAVES(std) = CAL_NWAVES(Memi[stds])
+ CAL_WAVES(std) = CAL_WAVES(Memi[stds])
+ CAL_DWAVES(std) = CAL_DWAVES(Memi[stds])
+ CAL_MAGS(std) = CAL_MAGS(Memi[stds])
+ }
+
+ if (IS_INDEF (bandwidth)) {
+ do j = 1, CAL_NWAVES(std) {
+ wave = Memr[CAL_WAVES(std)+j-1]
+ dwave = Memr[CAL_DWAVES(std)+j-1]
+ call std_addband (std, wave, dwave, 0.)
+ }
+ } else {
+ wave = W0(sh) + bandwidth / 2
+ dwave = W0(sh) + (SN(sh)-1) * WP(sh) - bandwidth / 2
+ while (wave <= dwave) {
+ call std_addband (std, wave, bandwidth, 0.)
+ wave = wave + bandsep
+ }
+ }
+ }
+
+ # The copying of SHDR structures and associated MWCS only
+ # occurs with beam switched data.
+
+ if (bswitch) {
+ switch (STD_TYPE(std)) {
+ case NONE:
+ STD_TYPE(std) = OFLAG(sh)
+ call shdr_copy (sh, STD_SH(std), YES)
+ next
+ case SKY:
+ obj = sh
+ sky = STD_SH(std)
+ if (OFLAG(sh) == SKY) {
+ call eprintf ("%s[%d]: Object spectrum not found\n")
+ call pargstr (IMNAME(sky))
+ call pargi (AP(sky))
+
+ call smw_close (MW(sky))
+ call shdr_copy (sh, STD_SH(std), YES)
+ next
+ }
+ case OBJ:
+ obj = STD_SH(std)
+ sky = sh
+ if (OFLAG(sh) == OBJ) {
+ obj = STD_SH(std)
+ call eprintf ("%s[%d]: Sky spectrum not found\n")
+ call pargstr (IMNAME(obj))
+ call pargi (AP(obj))
+
+ call smw_close (MW(obj))
+ call shdr_copy (sh, STD_SH(std), YES)
+ next
+ }
+ }
+ } else {
+ obj = sh
+ sky = NULL
+ }
+
+ # Generate a calibration table
+ call std_calib (obj, sky, std, gp, Memr[ewaves], Memr[emags],
+ enwaves)
+ call std_output (obj, sky, std, Memc[output])
+
+ if (interactive == YES1) {
+ if (STD_IFLAG(std) == NO3 || STD_IFLAG(std) == YES3) {
+ interactive = STD_IFLAG(std)
+ do i = 0, nstds-1
+ STD_IFLAG(Memi[stds+i]) = interactive
+ }
+ if (interactive == NO3 && gp != NULL)
+ call gclose (gp)
+ }
+
+ if (bswitch) {
+ call smw_close (MW(STD_SH(std)))
+ STD_TYPE(std) = NONE
+ }
+ }
+
+ call smw_close (MW(sh))
+ call imunmap (IM(sh))
+ }
+
+ if (un != NULL) {
+ call un_close (un)
+ call un_close (unang)
+ }
+ if (obs != NULL)
+ call obsclose (obs)
+ if (gp != NULL)
+ call gclose (gp)
+ do i = 0, nstds-1 {
+ std = Memi[stds+i]
+ obj = STD_SH(std)
+ switch (STD_TYPE(std)) {
+ case SKY:
+ call eprintf ("%s[%d]: Object spectrum not found\n")
+ call pargstr (IMNAME(obj))
+ call pargi (AP(obj))
+ case OBJ:
+ call eprintf ("%s[%d]: Sky spectrum not found\n")
+ call pargstr (IMNAME(obj))
+ call pargi (AP(obj))
+ }
+ if (obj != NULL)
+ call shdr_close (obj)
+ if (!samestar || i == 0) {
+ call mfree (CAL_WAVES(std), TY_REAL)
+ call mfree (CAL_DWAVES(std), TY_REAL)
+ call mfree (CAL_MAGS(std), TY_REAL)
+ }
+ call mfree (STD_WAVES(std), TY_REAL)
+ call mfree (STD_DWAVES(std), TY_REAL)
+ call mfree (STD_MAGS(std), TY_REAL)
+ call mfree (STD_FLUXES(std), TY_REAL)
+ }
+ call mfree (stds, TY_INT)
+ call mfree (ewaves, TY_REAL)
+ call mfree (emags, TY_REAL)
+ call shdr_close (sh)
+ call rng_close (aps)
+ call imtclose (list)
+ call sfree (sp)
+end
+
+
+# STD_CALIB -- Compute standard star calibrations
+
+procedure std_calib (obj, sky, std, gp, ewaves, emags, enwaves)
+
+pointer obj # Object pointer
+pointer sky # Sky pointer
+pointer std # Standard pointer
+pointer gp # Graphics pointer
+real ewaves[enwaves] # Extinction wavelengths
+real emags[enwaves] # Extinction magnitudes
+int enwaves # Extinction points
+
+int i, j, n, nwaves, wcs, key, newgraph
+real wave, dwave, flux, wx1, wx2, wy
+pointer sp, cmd, gt, waves, dwaves, fluxes, x, y
+
+real std_flux()
+double shdr_wl()
+int clgcur(), strdic(), clgwrd()
+pointer gopen(), gt_init()
+errchk gopen, std_output
+
+define beep_ 99
+
+begin
+ call smark (sp)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+
+ # Sky subtract
+ if (sky != NULL) {
+ call shdr_rebin (sky, obj)
+ call asubr (Memr[SY(obj)], Memr[SY(sky)], Memr[SY(obj)], SN(obj))
+ }
+
+ # Remove extinction correction
+ if (EC(obj) == ECYES) {
+ x = SX(obj)
+ y = SY(obj)
+ n = SN(obj)
+ do i = 1, n {
+ call intrp (EXT_LOOKUP, ewaves, emags, enwaves,
+ Memr[x], flux, j)
+ Memr[y] = Memr[y] * 10.0 ** (-0.4 * flux * AM(obj))
+ x = x + 1
+ y = y + 1
+ }
+ }
+
+ nwaves = STD_NWAVES(std)
+ waves = STD_WAVES(std)
+ dwaves = STD_DWAVES(std)
+ fluxes = STD_FLUXES(std)
+ do i = 0, nwaves-1 {
+ wave = Memr[waves+i]
+ dwave = Memr[dwaves+i]
+ Memr[fluxes+i] = std_flux (obj, wave, dwave, ewaves, emags, enwaves)
+ }
+
+ # Plot spectrum if user wants to see whats happening
+ if (STD_IFLAG(std) == NO1 || STD_IFLAG(std) == YES1) {
+ call printf ("%s[%d]: Edit bandpasses? ")
+ call pargstr (IMNAME(obj))
+ call pargi (AP(obj))
+ STD_IFLAG(std) = clgwrd ("answer", Memc[cmd], SZ_FNAME, ANSWERS)
+ }
+
+ i = STD_IFLAG(std)
+ if (i==YES1||i==Y2||i==YES2||i==YES3) {
+ if (gp == NULL) {
+ call clgstr ("graphics", Memc[cmd], SZ_FNAME)
+ gp = gopen (Memc[cmd], NEW_FILE, STDGRAPH)
+ }
+ gt = gt_init()
+ call gt_sets (gt, GTTITLE, TITLE(obj))
+ call gt_sets (gt, GTPARAMS, IMNAME(obj))
+ call gt_sets (gt, GTXLABEL, LABEL(obj))
+ call gt_sets (gt, GTXUNITS, UNITS(obj))
+ call gt_sets (gt, GTYLABEL, "instrumental flux")
+ call gt_sets (gt, GTTYPE, "line")
+
+ key = 'r'
+ repeat {
+ switch (key) {
+ case '?':
+ call gpagefile (gp, KEY, PROMPT)
+ case ':':
+ if (Memc[cmd] == '/')
+ call gt_colon (Memc[cmd], gp, gt, newgraph)
+ else {
+ switch (strdic (Memc[cmd],Memc[cmd],SZ_LINE,"|show|")) {
+ case 1:
+ call mktemp ("std", Memc[cmd], SZ_LINE)
+ call std_output (obj, sky, std, Memc[cmd])
+ call gpagefile (gp, Memc[cmd], "standard star data")
+ call delete (Memc[cmd])
+ default:
+ goto beep_
+ }
+ }
+ case 'a':
+ call printf ("a again:\n")
+ i = clgcur ("cursor", wx2, wy, wcs, key, Memc[cmd], SZ_LINE)
+ call printf ("\n")
+ if (wx1 == wx2) {
+ call printf ("\07Two cursor positions required")
+ goto beep_
+ }
+
+ # Create artificial standard wavelength and bandpass
+ wave = (wx1 + wx2) / 2.0
+ dwave = wx2 - wx1
+ flux = std_flux (obj, wave, dwave, ewaves, emags, enwaves)
+ call std_addband (std, wave, dwave, flux)
+ flux = flux / abs (shdr_wl (obj, double(wx1)) -
+ shdr_wl (obj, double (wx2)))
+ call gmark (gp, wave, flux, GM_BOX, -dwave, 3.)
+
+ nwaves = STD_NWAVES(std)
+ waves = STD_WAVES(std)
+ dwaves = STD_DWAVES(std)
+ fluxes = STD_FLUXES(std)
+ case 'd':
+ dwave = MAX_REAL
+ do i = 0, nwaves-1 {
+ wave = Memr[waves+i]
+ if (abs (wx1 - wave) < dwave) {
+ dwave = abs (wx1 - wave)
+ j = i
+ }
+ }
+ wave = Memr[waves+j]
+ dwave = Memr[dwaves+j]
+ flux = Memr[fluxes+j]
+ flux = flux / abs (shdr_wl (obj, double(wave-dwave/2)) -
+ shdr_wl (obj, double (wave+dwave/2)))
+ call gseti (gp, G_PMLTYPE, 0)
+ call gmark (gp, wave, flux, GM_BOX, -dwave, 3.)
+ call gseti (gp, G_PMLTYPE, 1)
+ call gscur (gp, wave, flux)
+ call std_delband (std, j)
+ case 'q':
+ break
+ case 'I':
+ call fatal (0, "Interrupt")
+ case 'r':
+ newgraph = YES
+ case 'w':
+ call gt_window (gt, gp, "cursor", newgraph)
+ default: # Invalid keystroke
+beep_ call printf ("\007")
+ }
+
+ if (newgraph == YES) {
+ call std_graph (obj, std, gp, gt, YES)
+ newgraph = NO
+ }
+ } until (clgcur ("cursor",wx1,wy,wcs,key,Memc[cmd],SZ_LINE) == EOF)
+ call gt_free (gt)
+ }
+
+ call sfree (sp)
+end
+
+
+# STD_OUTPUT -- Output standard star data.
+# For now we do this in Angstroms.
+
+procedure std_output (obj, sky, std, output)
+
+pointer obj # Object pointer
+pointer sky # Sky pointer
+pointer std # Standard pointer
+char output[ARB] # Output file name
+
+int i, fd, open()
+real wave, dwave, mag, flux, fnuzero, flambda, clgetr()
+pointer unang, un_open()
+errchk open, un_open, un_ctranr, std_units
+
+begin
+ fd = open (output, APPEND, TEXT_FILE)
+ call fprintf (fd, "[%s]")
+ call pargstr (IMNAME(obj))
+ if (sky != NULL) {
+ call fprintf (fd, "-[%s]")
+ call pargstr (IMNAME(sky))
+ }
+
+ unang = un_open ("Angstroms")
+ call un_ctranr (UN(obj), unang, W0(obj), wave, 1)
+ call un_ctranr (UN(obj), unang, W0(obj)+(SN(obj)-1)*WP(obj), dwave, 1)
+ call fprintf (fd, " %d %d %.2f %5.3f %9.3f %9.3f %s\n")
+ call pargi (AP(obj))
+ call pargi (SN(obj))
+ call pargr (IT(obj))
+ call pargr (AM(obj))
+ #call pargr (W0(obj))
+ #call pargr (W0(obj) + (SN(obj)-1) * WP(obj))
+ call pargr (wave)
+ call pargr (dwave)
+ call pargstr (TITLE(obj))
+
+ fnuzero = clgetr ("fnuzero")
+ do i = 0, STD_NWAVES(std)-1 {
+ wave = Memr[STD_WAVES(std)+i]
+ dwave = Memr[STD_DWAVES(std)+i]
+ mag = Memr[STD_MAGS(std)+i]
+ flux = Memr[STD_FLUXES(std)+i]
+ if (flux == 0.)
+ next
+
+ call std_units (UN(obj), unang, wave, dwave, 1)
+ flambda = fnuzero * 10. ** (-0.4 * mag) * VLIGHT / wave**2
+ call fprintf (fd, "%8.2f %12.5g %8.3f %12.5g\n")
+ call pargr (wave)
+ call pargr (flambda)
+ call pargr (dwave)
+ call pargr (flux)
+ }
+ call close (fd)
+
+ call un_close (unang)
+end
+
+
+# STD_FLUX -- Add up the flux in a given bandpass centered on a given
+# wavelength. The bandpass must be entirely within the data.
+# A correction for differential extinction across the bandpass is made
+# by applying the extinction correction and then removing the correction
+# at the bandpass center
+
+real procedure std_flux (sh, wave, dwave, ewaves, emags, enwaves)
+
+pointer sh # Spectrum
+real wave # Bandpass wavelength
+real dwave # Bandpass width
+real ewaves[enwaves] # Extinction wavelengths
+real emags[enwaves] # Extinction magnitudes
+int enwaves # Extinction points
+
+real flux # Bandpass flux
+
+int i, i1, i2, ierr
+real a, e, ec, x1, x2
+double w1, w2, w3, w4, shdr_lw(), shdr_wl()
+pointer x, y
+
+begin
+ # Determine bandpass limits in pixel and return if out of bounds.
+ w1 = wave - dwave / 2.
+ w2 = wave + dwave / 2.
+ w3 = shdr_lw (sh, 0.5D0)
+ w4 = shdr_lw (sh, double (SN(sh)+0.5))
+ if (w1 < min (w3, w4) || w2 > max (w3, w4))
+ return (0.)
+
+ a = shdr_wl (sh, w1)
+ x2 = shdr_wl (sh, w2)
+ x1 = min (a, x2)
+ x2 = max (a, x2)
+ i1 = nint (x1)
+ i2 = nint (x2 - 0.00001)
+ if (x1 == x2 || i1 < 1 || i2 > SN(sh))
+ return (0.)
+
+ a = AM(sh)
+ x = SX(sh) + i1 - 1
+ y = SY(sh) + i1 - 1
+
+ call intrp (EXT_LOOKUP, ewaves, emags, enwaves, wave, ec, ierr)
+ call intrp (EXT_LOOKUP, ewaves, emags, enwaves, Memr[x], e, ierr)
+
+ if (i1 == i2) {
+ flux = (x2-x1) * Memr[y] * 10.0 ** (0.4 * a * (e - ec))
+ return (flux)
+ }
+
+ flux = (i1+0.5-x1) * Memr[y] * 10.0 ** (0.4 * a * (e - ec))
+ x = x + 1
+ y = y + 1
+
+ for (i=i1+1; i<=i2-1; i=i+1) {
+ call intrp (EXT_LOOKUP, ewaves, emags, enwaves, Memr[x], e, ierr)
+ flux = flux + Memr[y] * 10.0 ** (0.4 * a * (e - ec))
+ x = x + 1
+ y = y + 1
+ }
+
+ call intrp (EXT_LOOKUP, ewaves, emags, enwaves, Memr[x], e, ierr)
+ flux = flux + (x2-i2+0.5) * Memr[y] * 10.0 ** (0.4 * a * (e - ec))
+
+ return (flux)
+end
+
+
+# STD_ADDBAND -- Add a standard bandpass
+
+procedure std_addband (std, wave, dwave, flux)
+
+pointer std # Pointer to standard star data
+real wave # Wavelength to be added
+real dwave # Bandpass to be added
+real flux # Flux to be added
+
+int i, nwaves
+real mag
+pointer waves, dwaves, mags, fluxes
+
+begin
+ nwaves = STD_NWAVES(std)
+ if (nwaves == 0) {
+ call malloc (STD_WAVES(std), 10, TY_REAL)
+ call malloc (STD_DWAVES(std), 10, TY_REAL)
+ call malloc (STD_MAGS(std), 10, TY_REAL)
+ call malloc (STD_FLUXES(std), 10, TY_REAL)
+ } else if (mod (nwaves, 10) == 0) {
+ call realloc (STD_WAVES(std), nwaves+10, TY_REAL)
+ call realloc (STD_DWAVES(std), nwaves+10, TY_REAL)
+ call realloc (STD_MAGS(std), nwaves+10, TY_REAL)
+ call realloc (STD_FLUXES(std), nwaves+10, TY_REAL)
+ }
+
+ call intrp (MAG_LOOKUP, Memr[CAL_WAVES(std)], Memr[CAL_MAGS(std)],
+ CAL_NWAVES(std), wave, mag, i)
+
+ waves = STD_WAVES(std)
+ dwaves = STD_DWAVES(std)
+ mags = STD_MAGS(std)
+ fluxes = STD_FLUXES(std)
+ for (i=nwaves; (i>0)&&(Memr[waves+i-1]>wave); i=i-1) {
+ Memr[waves+i] = Memr[waves+i-1]
+ Memr[dwaves+i] = Memr[dwaves+i-1]
+ Memr[mags+i] = Memr[mags+i-1]
+ Memr[fluxes+i] = Memr[fluxes+i-1]
+ }
+ Memr[waves+i] = wave
+ Memr[dwaves+i] = dwave
+ Memr[mags+i] = mag
+ Memr[fluxes+i] = flux
+ STD_NWAVES(std) = nwaves + 1
+end
+
+
+# STD_DELBAND -- Delete a bandpass
+
+procedure std_delband (std, band)
+
+pointer std # Pointer to standard star data
+int band # Band to be deleted
+
+int i, nwaves
+pointer waves, dwaves, mags, fluxes
+
+begin
+ nwaves = STD_NWAVES(std)
+ waves = STD_WAVES(std)
+ dwaves = STD_DWAVES(std)
+ mags = STD_MAGS(std)
+ fluxes = STD_FLUXES(std)
+ for (i=band+1; i<nwaves; i=i+1) {
+ Memr[waves+i-1] = Memr[waves+i]
+ Memr[dwaves+i-1] = Memr[dwaves+i]
+ Memr[mags+i-1] = Memr[mags+i]
+ Memr[fluxes+i-1] = Memr[fluxes+i]
+ }
+ nwaves = nwaves - 1
+
+ STD_NWAVES(std) = nwaves
+ if (nwaves == 0) {
+ call mfree (STD_WAVES(std), TY_REAL)
+ call mfree (STD_DWAVES(std), TY_REAL)
+ call mfree (STD_MAGS(std), TY_REAL)
+ call mfree (STD_FLUXES(std), TY_REAL)
+ }
+end
+
+
+# STD_GRAPH -- Graph the spectrum and standard star calibration points.
+
+procedure std_graph (sh, std, gp, gt, clear)
+
+pointer sh # Spectrum pointer
+pointer std # Standard star data
+pointer gp # GIO pointer
+pointer gt # GTOOLS pointer
+int clear # Clear flag
+
+int i
+real dw, wave, dwave, flux
+double shdr_wl()
+
+begin
+ if (clear == YES) {
+ call gclear (gp)
+ call gascale (gp, Memr[SX(sh)], SN(sh), 1)
+ call gascale (gp, Memr[SY(sh)], SN(sh), 2)
+ call gt_swind (gp, gt)
+ call gt_labax (gp, gt)
+ }
+
+ call gt_plot (gp, gt, Memr[SX(sh)], Memr[SY(sh)], SN(sh))
+
+ do i = 0, STD_NWAVES(std)-1 {
+ wave = Memr[STD_WAVES(std)+i]
+ dwave = Memr[STD_DWAVES(std)+i]
+ flux = Memr[STD_FLUXES(std)+i]
+ if (flux == 0.)
+ next
+ dw = abs (shdr_wl (sh, double(wave-dwave/2)) -
+ shdr_wl (sh, double (wave+dwave/2)))
+ flux = flux / dw
+ call gmark (gp, wave, flux, GM_BOX, -dwave, 3.)
+ }
+end
+
+
+# STD_GCALIB -- Get calibration data in desired units.
+
+procedure std_gcalib (std, un)
+
+pointer std #I Standard pointer
+pointer un #I Desired units pointer
+
+pointer unang, un_open()
+errchk getcalib, std_units
+
+begin
+ call getcalib (CAL_WAVES(std), CAL_DWAVES(std), CAL_MAGS(std),
+ CAL_NWAVES(std))
+
+ # Cnvert to desired units.
+ unang = un_open ("Angstroms")
+ call std_units (unang, un,
+ Memr[CAL_WAVES(std)], Memr[CAL_DWAVES(std)], CAL_NWAVES(std))
+ call un_close (unang)
+end
+
+
+# STD_UNITS -- Convert bandpass information to different units.
+
+procedure std_units (unin, unout, center, width, n)
+
+pointer unin #I Input units
+pointer unout #I Output units
+real center[ARB] #U Bandpass centers
+real width[ARB] #U Bandpass widths
+int n #I Number of bandpasses
+
+int i
+real x1, x2
+bool un_compare()
+errchk un_ctranr
+
+
+begin
+ if (un_compare (unin, unout))
+ return
+
+ do i = 1, n {
+ x1 = center[i] - width[i] / 2
+ x2 = center[i] + width[i] / 2
+ call un_ctranr (unin, unout, x1, x1, 1)
+ call un_ctranr (unin, unout, x2, x2, 1)
+ center[i] = (x1 + x2) / 2.
+ width[i] = abs (x1 - x2)
+ }
+end
diff --git a/noao/onedspec/t_tweak.x b/noao/onedspec/t_tweak.x
new file mode 100644
index 00000000..e50dccea
--- /dev/null
+++ b/noao/onedspec/t_tweak.x
@@ -0,0 +1,1352 @@
+include <error.h>
+include <gset.h>
+include <imset.h>
+include <imhdr.h>
+include <math.h>
+include <math/iminterp.h>
+include <pkg/gtools.h>
+include <smw.h>
+include <units.h>
+include <pkg/xtanswer.h>
+
+# Tweak data object definitions.
+define TWK_SLEN 999 # Length of sample region string
+define TWK_LEN 580 # Length of data object
+
+define TWK_TYPE Memc[P2C($1)] # Tweak type (maxchars=19)
+define TWK_SH Memi[$1+11] # Spectrum pointer
+define TWK_CAL Memi[$1+12] # Calibration pointer
+define TWK_WAVE Memi[$1+13] # Pointer to wavelengths
+define TWK_SPEC Memi[$1+14] # Pointer to calibrated spectrum
+define TWK_SHIFT Memr[P2R($1+15)] # Shift
+define TWK_DSHIFT Memr[P2R($1+16)] # Shift step
+define TWK_SCALE Memr[P2R($1+17)] # Scaling factor
+define TWK_DSCALE Memr[P2R($1+18)] # Scaling factor step
+define TWK_RG Memi[$1+19] # Range pointer
+define TWK_RMS Memr[P2R($1+20)] # RMS in sample regions
+define TWK_OFFSET Memr[P2R($1+21)] # Offset in graphs
+define TWK_BOX Memi[$1+22] # Boxcar smoothing size
+define TWK_THRESH Memr[P2R($1+23)] # Calibration threshold
+define TWK_SAMPLE Memc[P2C($1+30)] # Sample regions (maxchars=999)
+define TWK_HELP Memc[P2C($1+530)] # Help file (maxchars=99)
+
+# Tweak types.
+define SKYTWEAK 1 # Sky subtraction
+define TELLURIC 2 # Telluric division
+
+# Secondary graph types.
+define GNONE 0 # No graph
+define GCAL 1 # Graph calibration spectrum
+define GDATA 2 # Graph data spectrum
+
+
+# T_SKYTWEAK -- Sky subtract spectra with shift and scale tweaking.
+# The sky calibration spectra are scaled and shifted to best subtract
+# sky features. Automatic and interactive methods are provided.
+
+procedure t_skytweak ()
+
+pointer twk # TWK data object
+
+begin
+ call malloc (twk, TWK_LEN, TY_STRUCT)
+ call strcpy ("SKYTWEAK", TWK_TYPE(twk), 19)
+ call strcpy ("onedspec$doc/skytweak.key", TWK_HELP(twk), 99)
+ call tweak (twk)
+ call mfree (twk, TY_STRUCT)
+end
+
+
+# T_TELLURIC -- Correct spectra for telluric features.
+# The telluric calibration spectra are scaled by raising to a power (Beers law)
+# and shifted to best remove telluric features. Automatic and interactive
+# methods are provided.
+
+procedure t_telluric ()
+
+pointer twk # TWK data object
+
+begin
+ call malloc (twk, TWK_LEN, TY_STRUCT)
+ call strcpy ("TELLURIC", TWK_TYPE(twk), 19)
+ call strcpy ("onedspec$doc/telluric.key", TWK_HELP(twk), 99)
+ call tweak (twk)
+ call mfree (twk, TY_STRUCT)
+end
+
+
+# TWEAK -- Tweak spectra for shift and scale before applying a correction.
+# This procedure implements both sky subtraction and telluric division.
+
+procedure tweak (twk)
+
+pointer twk #I TWK data object
+
+pointer inlist # Input list
+pointer outlist # Output list
+pointer callist # Calibration list
+bool xcorr # Cross correlate for initial shift
+bool tweakrms # Tweak to minimize RMS?
+bool ignoreaps # Ignore aperture numbers?
+int lag # Cross correlation lag
+bool interactive # Interactive?
+
+int i, j, k, n, nout, ncal, answer
+real shift, scale, fcor, ical, mean
+pointer sp, input, output, calname, temp
+pointer in, smw, sh, out, pcal, cal, x, y, data, tmp
+
+int clgeti(), imtgetim(), imtlen()
+bool clgetb(), streq()
+real clgetr(), asieval()
+double shdr_wl(), shdr_lw()
+pointer imtopenp(), immap(), smw_openim(), impl3r(), imgl3r()
+errchk immap, smw_openim, shdr_open, twk_gcal, twk_tweak, impl3r, imgl3r
+
+begin
+ call smark (sp)
+ call salloc (input, SZ_FNAME, TY_CHAR)
+ call salloc (output, SZ_FNAME, TY_CHAR)
+ call salloc (calname, SZ_FNAME, TY_CHAR)
+ call salloc (temp, SZ_LINE, TY_CHAR)
+ call malloc (TWK_WAVE(twk), 1000, TY_DOUBLE)
+ call malloc (TWK_SPEC(twk), 1000, TY_REAL)
+
+ # Get task parameters.
+ inlist = imtopenp ("input")
+ outlist = imtopenp ("output")
+ callist = imtopenp ("cal")
+ ignoreaps = clgetb ("ignoreaps")
+ if (TWK_TYPE(twk) == 'T')
+ TWK_THRESH(twk) = clgetr ("threshold")
+ TWK_SHIFT(twk) = clgetr ("shift")
+ TWK_SCALE(twk) = clgetr ("scale")
+ xcorr = clgetb ("xcorr")
+ tweakrms = clgetb ("tweakrms")
+ interactive = clgetb ("interactive")
+ if (interactive)
+ answer = YES
+ else
+ answer = ALWAYSNO
+ call clgstr ("sample", TWK_SAMPLE(twk), TWK_SLEN)
+ lag = clgeti ("lag")
+ TWK_DSHIFT(twk) = max (0., clgetr ("dshift"))
+ TWK_DSCALE(twk) = max (0., min (0.99, clgetr ("dscale")))
+ TWK_OFFSET(twk) = clgetr ("offset")
+ TWK_BOX(twk) = max (1, clgeti ("smooth"))
+
+ if (imtlen (inlist) != imtlen (callist) && imtlen (callist) != 1) {
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call imtclose (callist)
+ call sfree (sp)
+ call error (1, "Image lists do not match")
+ }
+
+ # Loop over all input images.
+ sh = NULL
+ ncal = 0
+ while (imtgetim (inlist, Memc[input], SZ_FNAME) != EOF) {
+ if (imtgetim (callist, Memc[calname], SZ_FNAME) != EOF) {
+ if (ncal > 0) {
+ do i = 0, ncal-1 {
+ cal = Memi[pcal+i]
+ call asifree (IM(cal))
+ call smw_close (MW(cal))
+ call shdr_close (cal)
+ }
+ call mfree (pcal, TY_POINTER)
+ ncal = 0
+ }
+ }
+
+ in = NULL; smw = NULL; sh = NULL; out = NULL
+ iferr {
+ # Set output image. Use a temporary image when output=input.
+ if (imtlen (outlist) > 0) {
+ if (imtgetim (outlist, Memc[output], SZ_FNAME) == EOF)
+ break
+ } else
+ call strcpy (Memc[input], Memc[output], SZ_FNAME)
+
+ # Map the input image.
+ tmp = immap (Memc[input], READ_ONLY, 0); in = tmp
+ tmp = smw_openim (in); smw = tmp
+ if (smw == SMW_ND)
+ call error (1, "NDSPEC data not supported")
+ call shdr_open (in, smw, 1, 1, INDEFI, SHHDR, sh)
+
+ # Map the output image.
+ if (streq (Memc[input], Memc[output]))
+ call mktemp ("temp", Memc[temp], SZ_LINE)
+ else
+ call strcpy (Memc[output], Memc[temp], SZ_LINE)
+ tmp = immap (Memc[temp], NEW_COPY, in); out = tmp
+ if (IM_PIXTYPE(out) != TY_DOUBLE)
+ IM_PIXTYPE(out) = TY_REAL
+
+ # Determine airmass if needed.
+ if (TWK_TYPE(twk) == 'T') {
+ if (IS_INDEF(AM(sh))) {
+ call printf ("%s: ")
+ call pargstr (Memc[input])
+ call flush (STDOUT)
+ AM(sh) = clgetr ("airmass")
+ }
+ }
+
+ # Calibrate all spectra in the image.
+ # Only the first band is done.
+ do i = 1, IM_LEN(in,2) {
+
+ # Get the spectra.
+ call shdr_open (in, smw, i, 1, INDEFI, SHDATA, sh)
+ call realloc (TWK_WAVE(twk), SN(sh), TY_DOUBLE)
+ x = TWK_WAVE(twk)
+ do k = 1, SN(sh) {
+ Memd[x] = shdr_lw (sh, double(k))
+ x = x + 1
+ }
+ if (ignoreaps)
+ call twk_gcal (twk, Memc[calname], INDEFI,
+ pcal, ncal, cal)
+ else
+ call twk_gcal (twk, Memc[calname], AP(sh),
+ pcal, ncal, cal)
+
+ # Determine the shift and scale.
+ TWK_SH(twk) = sh
+ TWK_CAL(twk) = cal
+ call realloc (SY(cal), SN(sh), TY_REAL)
+ call realloc (TWK_SPEC(twk), SN(sh), TY_REAL)
+ if (answer == NO || answer == YES) {
+ call printf ("%s%s: ")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call flush (STDOUT)
+ call xt_clanswer ("answer", answer)
+ }
+ if (answer == YES || answer == ALWAYSYES)
+ interactive = true
+ else
+ interactive = false
+ call twk_tweak (twk, xcorr, tweakrms, interactive, lag)
+ shift = TWK_SHIFT(twk)
+ if (TWK_TYPE(twk) == 'T')
+ scale = TWK_SCALE(twk) * AM(sh) / AM(cal)
+ else
+ scale = TWK_SCALE(twk)
+
+ # Calibrate the output spectrum.
+ nout = 0
+ mean = 0.
+ x = TWK_WAVE(twk)
+ y = SY(sh)
+ n = SN(sh)
+ data = impl3r (out, i, 1)
+ do k = 1, n {
+ ical = shdr_wl (cal, Memd[x]) + shift
+ if (ical < 1. || ical > SN(cal)) {
+ if (ical < 0.5 || ical > SN(cal) + 0.5)
+ nout = nout + 1
+ ical = max (1., min (real(SN(cal)), ical))
+ }
+ if (TWK_TYPE(twk) == 'T') {
+ fcor = max (TWK_THRESH(twk),
+ asieval (IM(cal),ical)) ** scale
+ Memr[data] = Memr[y] / fcor
+ mean = mean + fcor
+ } else {
+ fcor = asieval (IM(cal),ical) * scale
+ Memr[data] = Memr[y] - fcor
+ }
+ x = x + 1
+ y = y + 1
+ data = data + 1
+ }
+ mean = mean / n
+ if (TWK_TYPE(twk) == 'T')
+ call amulkr (Memr[data-n], mean, Memr[data-n], n)
+ do k = n+1, IM_LEN(out,1) {
+ Memr[data] = 0
+ data = data + 1
+ }
+
+ # Log the results.
+ if (i == 1) {
+ call printf ("%s:\n Output: %s - %s\n")
+ call pargstr (TWK_TYPE(twk))
+ call pargstr (Memc[output])
+ call pargstr (IM_TITLE(out))
+ }
+ call printf (" Input: %s%s - %s\n")
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (TITLE(sh))
+ call printf (" Calibration: %s%s - %s\n")
+ call pargstr (IMNAME(cal))
+ call pargstr (IMSEC(cal))
+ call pargstr (TITLE(cal))
+ call printf (" Tweak: shift = %.2f, scale = %.3f")
+ call pargr (shift)
+ call pargr (TWK_SCALE(twk))
+ if (TWK_TYPE(twk) == 'T') {
+ call printf (", normalization = %.4g\n")
+ call pargr (mean)
+ } else
+ call printf ("\n")
+ if (nout > 0) {
+ call printf (
+ " WARNING: %d pixels outside of calibration limits\n")
+ call pargi (nout)
+ }
+ call flush (STDOUT)
+ }
+ do j = 2, IM_LEN(in,3) {
+ do i = 1, IM_LEN(in,2) {
+ y = imgl3r (in, i, j)
+ data = impl3r (out, i, j)
+ call amovr (Memr[y], Memr[data], IM_LEN(out,1))
+ }
+ }
+ } then {
+ call erract (EA_WARN)
+ if (out != NULL) {
+ call imunmap (out)
+ if (!streq (Memc[input], Memc[output]))
+ call imdelete (Memc[output])
+ }
+ }
+
+ # Finish up this image.
+ if (in != NULL)
+ call imunmap (in)
+ if (smw != NULL) {
+ call smw_close (smw)
+ if (sh != NULL)
+ MW(sh) = NULL
+ }
+ if (out != NULL) {
+ call imunmap (out)
+ if (streq (Memc[input], Memc[output])) {
+ call imdelete (Memc[input])
+ call imrename (Memc[temp], Memc[output])
+ }
+ }
+ }
+
+ # Finish up.
+ if (ncal > 0) {
+ do i = 0, ncal-1 {
+ cal = Memi[pcal+i]
+ call asifree (IM(cal))
+ call smw_close (MW(cal))
+ call shdr_close (cal)
+ }
+ call mfree (pcal, TY_POINTER)
+ }
+ if (sh != NULL)
+ call shdr_close (sh)
+ call imtclose (inlist)
+ call imtclose (outlist)
+ call imtclose (callist)
+ call mfree (TWK_SPEC(twk), TY_REAL)
+ call sfree (sp)
+end
+
+
+# TWK_GCAL -- Get calibration data
+# An interpolation function is fit and stored in the image pointer field.
+# For efficiency the calibration data is saved by aperture so that additional
+# calls simply return the data pointer.
+
+procedure twk_gcal (twk, calname, ap, pcal, ncal, cal)
+
+pointer twk # TWK data object
+char calname[ARB] # Calibration image name
+int ap # Aperture
+pointer pcal # Pointer to cal data
+int ncal # Number of active cal data structures
+pointer cal # Calibration data structure
+
+int i, clgwrd()
+pointer sp, str, im, smw, immap(), smw_openim()
+real clgetr()
+errchk immap, smw_openim, shdr_open, asifit
+
+begin
+ # Check for previously saved calibration
+ for (i=0; i<ncal; i=i+1) {
+ cal = Memi[pcal+i]
+ if (AP(cal) == ap)
+ return
+ }
+
+ call smark (sp)
+ call salloc (str, SZ_FNAME, TY_CHAR)
+
+ # Allocate space for a new data pointer and get the calibration data.
+
+ if (ncal == 0)
+ call malloc (pcal, 10, TY_POINTER)
+ else if (mod (ncal, 10) == 0)
+ call realloc (pcal, ncal+10, TY_POINTER)
+
+ im = immap (calname, READ_ONLY, 0)
+ smw = smw_openim (im)
+ cal = NULL
+ call shdr_open (im, smw, 1, 1, ap, SHDATA, cal)
+ AP(cal) = ap
+ Memi[pcal+ncal] = cal
+ ncal = ncal + 1
+ call imunmap (im)
+
+ call asiinit (im, clgwrd ("interp", Memc[str], SZ_FNAME,II_FUNCTIONS))
+ call asifit (im, Memr[SY(cal)], SN(cal))
+ IM(cal) = im
+
+ # Determine airmass if needed.
+ if (TWK_TYPE(twk) == 'T') {
+ if (IS_INDEF(AM(cal))) {
+ call printf ("%s: ")
+ call pargstr (calname)
+ call flush (STDOUT)
+ AM(cal) = clgetr ("airmass")
+ }
+ }
+
+ call sfree (sp)
+end
+
+
+# TWK_TWEAK -- Determine the shift and scale using automatic and interactive
+# methods.
+
+procedure twk_tweak (twk, xcorr, tweakrms, interactive, lag)
+
+pointer twk #I TWK data object
+bool xcorr #I Cross correlate for shift
+bool tweakrms #I Tweak by minimizing RMS?
+bool interactive #I Interactive fitting?
+int lag #I Cross correlation lag
+
+int i, n, nlag
+real ical, asieval()
+double shdr_wl()
+pointer sh, cal, rg, asi, x, y, rg_xrangesd()
+errchk twk_rmsmin, twk_fit
+
+begin
+ sh = TWK_SH(twk)
+ cal = TWK_CAL(twk)
+
+ # Set ranges.
+ rg = rg_xrangesd (TWK_SAMPLE(twk), Memd[TWK_WAVE(twk)], SN(sh))
+ call rg_order (rg)
+ call rg_merge (rg)
+ TWK_RG(twk) = rg
+
+ # Cross correlate for shift.
+ if (xcorr && lag > 0) {
+ n = SN(sh)
+ nlag = n + 2 * lag
+ call malloc (x, nlag, TY_REAL)
+ call malloc (y, nlag, TY_REAL)
+
+ do i = 0, n-1 {
+ ical = max (1D0, min (double(SN(cal)),
+ shdr_wl (cal, Memd[TWK_WAVE(twk)+i])))
+ Memr[y+i] = asieval (IM(cal), ical)
+ }
+
+ call twk_prep (Memr[y], n, Memr[x], nlag)
+ call twk_prep (Memr[SY(sh)], n, Memr[y], nlag)
+ call twk_xcorr (Memr[x], Memr[y], i, rg, lag, asi, TWK_SHIFT(twk),
+ ical, 0.5)
+ call asifree (asi)
+ call mfree (x, TY_REAL)
+ call mfree (y, TY_REAL)
+ }
+
+ # Tweak by minimizing RMS.
+ if (tweakrms)
+ call twk_rmsmin (twk)
+
+ # Do interactive step.
+ if (interactive)
+ call twk_fit (twk)
+
+ call rg_free (TWK_RG(twk))
+end
+
+
+# TWK_PREP -- Prepare spectra for correlation: fit continuum, subtract, taper
+
+procedure twk_prep (in, nin, out, nout)
+
+real in[nin] # Input spectrum
+int nin # Number of pixels in input spectrum
+real out[nout] # Output spectrum
+int nout # Number of pixels output spectrum (nin+2*lag)
+
+int i, lag
+real cveval()
+pointer sp, x, w, ic, cv
+
+begin
+ call smark (sp)
+ call salloc (x, nin, TY_REAL)
+ call salloc (w, nin, TY_REAL)
+
+ call ic_open (ic)
+ call ic_pstr (ic, "function", "chebyshev")
+ call ic_puti (ic, "order", 3)
+ call ic_putr (ic, "low", 3.)
+ call ic_putr (ic, "high", 1.)
+ call ic_puti (ic, "niterate", 5)
+ call ic_putr (ic, "grow", 1.)
+ call ic_putr (ic, "xmin", 1.)
+ call ic_putr (ic, "xmax", real(nin))
+
+ do i = 1, nin {
+ Memr[x+i-1] = i
+ Memr[w+i-1] = 1
+ }
+ call ic_fit (ic, cv, Memr[x], in, Memr[w], nin, YES, YES, YES, YES)
+
+ lag = (nout - nin) / 2
+ do i = 1-lag, 0
+ out[i+lag] = 0.
+ do i = 1, lag-1
+ out[i+lag] = (1-cos (PI*i/lag))/2 * (in[i] - cveval (cv, real(i)))
+ do i = lag, nin-lag+1
+ out[i+lag] = (in[i] - cveval (cv, real(i)))
+ do i = nin-lag+2, nin
+ out[i+lag] = (1-cos (PI*(nin+1-i)/lag))/2 *
+ (in[i] - cveval (cv, real(i)))
+ do i = nin+1, nin+lag
+ out[i+lag] = 0.
+
+ call cvfree (cv)
+ call ic_closer (ic)
+ call sfree (sp)
+end
+
+
+# TWK_XCORR -- Correlate spectra, fit profile, and measure center/width
+
+procedure twk_xcorr (spec1, spec2, npix, rg, lag, asi, center, width, level)
+
+real spec1[npix] # First spectrum
+real spec2[npix] # Second spectrum
+int npix # Number of pixels in spectra
+pointer rg # Ranges
+int lag # Maximum correlation lag
+pointer asi # Pointer to correlation profile interpolator
+real center # Center of profile
+real width # Width of profile
+real level # Level at which width is determined
+
+int i, j, k, n, ishift, nprof, rg_inrange()
+real x, p, pmin, pmax, asieval()
+pointer sp, prof
+
+begin
+ nprof = 2 * lag + 1
+
+ call smark (sp)
+ call salloc (prof, nprof, TY_REAL)
+
+ ishift = nint (center)
+ n = 0
+ do j = -lag, lag {
+ p = 0.
+ do i = 1+lag, npix-lag {
+ if (rg_inrange (rg, i-lag) == NO)
+ next
+ k = i - j - ishift
+ if (k < 1 || k > npix)
+ next
+ p = p + spec1[i] * spec2[k]
+ n = n + 1
+ }
+ Memr[prof+j+lag] = p
+ }
+ if (n < 10 * nprof) {
+ call sfree (sp)
+ return
+ }
+
+ # Fit interpolator
+ call asiinit (asi, II_SPLINE3)
+ call asifit (asi, Memr[prof], nprof)
+
+ # Find the minimum and maximum
+ center = 1.
+ pmin = asieval (asi, 1.)
+ pmax = pmin
+ for (x=1; x<=nprof; x=x+.01) {
+ p = asieval (asi, x)
+ if (p < pmin)
+ pmin = p
+ if (p > pmax) {
+ pmax = p
+ center = x
+ }
+ }
+
+ # Normalize
+ pmax = pmax - pmin
+ do i = 0, nprof-1
+ Memr[prof+i] = (Memr[prof+i] - pmin) / pmax
+
+ call asifit (asi, Memr[prof], nprof)
+
+ # Find the equal flux points
+ for (x=center; x>=1 && asieval (asi,x)>level; x=x-0.01)
+ ;
+ width = x
+ for (x=center; x<=nprof && asieval (asi,x)>level; x=x+0.01)
+ ;
+ width = (x - width - 0.01) / sqrt (2.)
+ center = center - lag - 1 + ishift
+
+ call sfree (sp)
+end
+
+
+# TWK_RMSMIN -- Tweak shift and scale to minimize the RMS.
+# This changes the shift and scale parameters but not the step.
+
+procedure twk_rmsmin (twk)
+
+pointer twk #I TWK data object
+
+int i
+real lastshift, lastscale
+errchk twk_ashift, twk_ascale
+
+begin
+ lastshift = INDEFR
+ lastscale = INDEFR
+ do i = 1, 2 {
+ if (TWK_SHIFT(twk) == lastshift && TWK_SCALE(twk) == lastscale)
+ break
+ lastshift = TWK_SHIFT(twk)
+ call twk_ashift (twk)
+
+ if (TWK_SHIFT(twk) == lastshift && TWK_SCALE(twk) == lastscale)
+ break
+ lastscale = TWK_SCALE(twk)
+ call twk_ascale (twk)
+ }
+end
+
+
+# TWK_ASCALE -- Automatically determine scale by minimizing RMS.
+
+procedure twk_ascale (twk)
+
+pointer twk #I TWK data object
+
+int i
+real shift, oscale, dscale, lastscale, scale[3], rms[3]
+errchk twk_spec
+
+begin
+ dscale = TWK_DSCALE(twk)
+ if (dscale == 0.)
+ return
+ oscale = TWK_SCALE(twk)
+ shift = TWK_SHIFT(twk)
+ do i = 1, 3 {
+ scale[i] = (1 - (i - 2) * dscale) * oscale
+ call twk_spec (twk, shift, scale[i])
+ rms[i] = TWK_RMS(twk)
+ lastscale = TWK_SCALE(twk)
+ }
+ while (dscale > 0.01) {
+ if (scale[1] / oscale < 0.5 || scale[3] / oscale > 2.) {
+ TWK_SCALE(twk) = oscale
+ break
+ }
+ if (rms[1] < rms[2]) {
+ scale[3] = scale[2]
+ scale[2] = scale[1]
+ scale[1] = (1 - dscale) * scale[2]
+ rms[3] = rms[2]
+ rms[2] = rms[1]
+ call twk_spec (twk, shift, scale[1])
+ rms[1] = TWK_RMS(twk)
+ lastscale = TWK_SCALE(twk)
+ } else if (rms[3] < rms[2]) {
+ scale[1] = scale[2]
+ scale[2] = scale[3]
+ scale[3] = (1+dscale) * scale[2]
+ rms[1] = rms[2]
+ rms[2] = rms[3]
+ call twk_spec (twk, shift, scale[3])
+ rms[3] = TWK_RMS(twk)
+ lastscale = TWK_SCALE(twk)
+ } else {
+ dscale = dscale / 2
+ scale[1] = (1-dscale) * scale[2]
+ scale[3] = (1+dscale) * scale[2]
+ call twk_spec (twk, shift, scale[1])
+ rms[1] = TWK_RMS(twk)
+ call twk_spec (twk, shift, scale[3])
+ rms[3] = TWK_RMS(twk)
+ lastscale = TWK_SCALE(twk)
+ }
+ if (rms[1] < rms[2])
+ TWK_SCALE(twk) = scale[1]
+ else if (rms[3] < rms[2])
+ TWK_SCALE(twk) = scale[3]
+ else
+ TWK_SCALE(twk) = scale[2]
+ }
+
+ if (TWK_SCALE(twk) != lastscale)
+ call twk_spec (twk, shift, TWK_SCALE(twk))
+end
+
+
+# TWK_ASHIFT -- Automatically determine shift by minimizing RMS.
+
+procedure twk_ashift (twk)
+
+pointer twk #I TWK data object
+
+int i
+real scale, oshift, dshift, lastshift, shift[3], rms[3]
+errchk twk_spec
+
+begin
+ dshift = TWK_DSHIFT(twk)
+ if (dshift == 0.)
+ return
+ oshift = TWK_SHIFT(twk)
+ scale = TWK_SCALE(twk)
+ do i = 1, 3 {
+ shift[i] = oshift + dshift * (i - 2)
+ call twk_spec (twk, shift[i], scale)
+ rms[i] = TWK_RMS(twk)
+ lastshift = TWK_SHIFT(twk)
+ }
+ while (dshift > 0.01) {
+ if (abs (oshift - shift[2]) > 2.) {
+ TWK_SHIFT(twk) = oshift
+ break
+ }
+ if (rms[1] < rms[2]) {
+ shift[3] = shift[2]
+ shift[2] = shift[1]
+ shift[1] = shift[2] - dshift
+ rms[3] = rms[2]
+ rms[2] = rms[1]
+ call twk_spec (twk, shift[1], scale)
+ rms[1] = TWK_RMS(twk)
+ lastshift = TWK_SHIFT(twk)
+ } else if (rms[3] < rms[2]) {
+ shift[1] = shift[2]
+ shift[2] = shift[3]
+ shift[3] = shift[2] + dshift
+ rms[1] = rms[2]
+ rms[2] = rms[3]
+ call twk_spec (twk, shift[3], scale)
+ rms[3] = TWK_RMS(twk)
+ lastshift = TWK_SHIFT(twk)
+ } else {
+ dshift = dshift / 2
+ shift[1] = shift[2] - dshift
+ call twk_spec (twk, shift[1], scale)
+ rms[1] = TWK_RMS(twk)
+ shift[3] = shift[2] + dshift
+ call twk_spec (twk, shift[3], scale)
+ rms[3] = TWK_RMS(twk)
+ lastshift = TWK_SHIFT(twk)
+ }
+ if (rms[1] < rms[2])
+ TWK_SHIFT(twk) = shift[1]
+ else if (rms[3] < rms[2])
+ TWK_SHIFT(twk) = shift[3]
+ else
+ TWK_SHIFT(twk) = shift[2]
+ }
+
+ if (TWK_SHIFT(twk) != lastshift)
+ call twk_spec (twk, TWK_SHIFT(twk), scale)
+end
+
+
+# TWK_SPEC -- Evaluate the calibrated spectrum with the specified shift
+# and scale. Compute the RMS within the sample regions. Apply a
+# smoothing if necessary. The output spectrum and shift and scale
+# used are returned in the TWK data structure.
+
+procedure twk_spec (twk, shift, scale)
+
+pointer twk #I TWK data object
+real shift #I Shift
+real scale #I Scale
+
+char type
+pointer sh, cal, asi, x, y, ycal, z, rg, temp
+int i, j, k, n, ncal, nstat, box, rg_inrange()
+real thresh, amratio, norm, sum1, sum2, xcal, xcal1, zval, asieval()
+double shdr_wl()
+
+begin
+ # Dereference the data structures.
+ type = TWK_TYPE(twk)
+ sh = TWK_SH(twk)
+ cal = TWK_CAL(twk)
+ asi = IM(cal)
+ x = TWK_WAVE(twk)
+ y = SY(sh)
+ ycal = SY(cal)
+ z = TWK_SPEC(twk)
+ n = SN(sh)
+ ncal = SN(cal)
+ rg = TWK_RG(twk)
+ thresh = TWK_THRESH(twk)
+ amratio = AM(sh) / AM(cal)
+
+ # Evaluate the calibrated spectrum and the statistics.
+ norm = 0.
+ sum1 = 0.
+ sum2 = 0.
+ nstat = 0
+ do i = 0, n-1 {
+ # Spectra
+ xcal = shdr_wl (cal, Memd[x+i]) + shift
+ xcal1 = max (1., min (real(ncal), xcal))
+ #Memr[ycal+i] = asieval (asi, xcal1) ** (amratio * scale)
+ #Memr[z+i] = Memr[y+i] / (Memr[ycal+i]
+ Memr[ycal+i] = asieval (asi, xcal1)
+ if (type == 'T') {
+ Memr[ycal+i] = max (thresh, Memr[ycal+i])
+ if (Memr[ycal+i] <= 0.)
+ call error (1,
+ "Calibration spectrum negative or zero (set threshold parameter)")
+ Memr[z+i] = Memr[y+i] / (Memr[ycal+i] ** (amratio * scale))
+ } else
+ Memr[z+i] = Memr[y+i] - (Memr[ycal+i] * scale)
+ norm = norm + Memr[z+i]
+ }
+
+ do i = 3, n-4 {
+ # Statistics
+ if (rg_inrange (rg, i+1) == NO)
+ next
+# if (xcal < 1 || xcal > ncal)
+# next
+# zval = Memr[z+i]
+ zval = Memr[z+i] - (Memr[z+i-3] + Memr[z+i+3]) / 2
+ sum1 = sum1 + zval
+ sum2 = sum2 + zval * zval
+ nstat = nstat + 1
+ }
+
+ # Normalize
+ if (TWK_TYPE(twk) == 'T') {
+ norm = norm / n
+ if (norm > 0.) {
+ call adivkr (Memr[z], norm, Memr[z], n)
+ sum1 = sum1 / norm
+ sum2 = sum2 / norm / norm
+ }
+ }
+
+ # RMS
+ if (nstat == 0)
+ TWK_RMS(twk) = INDEF
+ else
+ TWK_RMS(twk) = sqrt (nstat * sum2 - sum1**2) / nstat
+
+ TWK_SHIFT(twk) = shift
+ TWK_SCALE(twk) = scale
+
+ # Smooth
+ if (TWK_BOX(twk) > 1) {
+ call malloc (temp, n, TY_REAL)
+ box = TWK_BOX(twk)
+ box = min (n, box)
+ i = (1-box) / 2
+ sum1 = 0.
+ for (j=i; j<i+box; j=j+1)
+ sum1 = sum1 + Memr[z+max(0,j)]
+ for (k=0; k<n; k=k+1) {
+ Memr[temp+k] = sum1
+ sum1 = sum1 - Memr[z+max(0,i)] + Memr[z+min(n-1,j)]
+ i = i + 1
+ j = j + 1
+ }
+ call adivkr (Memr[temp], real(box), Memr[z], n)
+ call mfree (temp, TY_REAL)
+ }
+end
+
+
+# TWK_FIT -- Interactive fitting procedure.
+
+procedure twk_fit (twk)
+
+pointer twk #I TWK data object
+
+int i, j, n, newgraph, newdata, key, wcs, pix, clgcur(), gt_geti()
+int graph1, graph2
+real wx, wy, shift[3], scale[3], dy, gt_getr()
+double shdr_wl()
+pointer sp, str, cmd, z[3]
+pointer sh, gp, gt[2], gopen(), gt_init(), rg_xrangesd()
+errchk twk_spec, twk_rmsmin
+
+begin
+ sh = TWK_SH(twk)
+ n = SN(sh)
+
+ call smark (sp)
+ call salloc (str, SZ_LINE, TY_CHAR)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+ call salloc (z[1], n, TY_REAL)
+ call salloc (z[3], n, TY_REAL)
+ z[2] = TWK_SPEC(twk)
+
+ # Initialize the graphics.
+ gp = gopen ("stdgraph", NEW_FILE+AW_DEFER, STDGRAPH)
+ gt[1] = gt_init ()
+ call sprintf (Memc[str], SZ_LINE,
+ "%s: spectrum = %s%s, calibration = %s%s")
+ call pargstr (TWK_TYPE(twk))
+ call pargstr (IMNAME(sh))
+ call pargstr (IMSEC(sh))
+ call pargstr (IMNAME(TWK_CAL(twk)))
+ call pargstr (IMSEC(TWK_CAL(twk)))
+ call gt_sets (gt[1], GTTITLE, Memc[str])
+ if (UN_LABEL(UN(sh)) != EOS) {
+ call gt_sets (gt[1], GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt[1], GTXUNITS, UN_UNITS(UN(sh)))
+ } else
+ call gt_sets (gt[1], GTXLABEL, "Pixels")
+ call gt_sets (gt[1], GTTYPE, "line")
+
+ gt[2] = gt_init ()
+ if (UN_LABEL(UN(sh)) != EOS) {
+ call gt_sets (gt[2], GTXLABEL, UN_LABEL(UN(sh)))
+ call gt_sets (gt[2], GTXUNITS, UN_UNITS(UN(sh)))
+ } else
+ call gt_sets (gt[2], GTXLABEL, "Pixels")
+ call gt_sets (gt[2], GTTYPE, "line")
+
+ # Cursor loop.
+ if (TWK_DSCALE(twk) > 0.)
+ graph1 = 'y'
+ else
+ graph1 = 'x'
+ graph2 = GCAL
+ newdata = YES
+ key = 'r'
+ repeat {
+ switch (key) {
+ case ':':
+ call twk_colon (Memc[cmd], twk, gp, gt, wcs, newdata, newgraph)
+ case '?':
+ call twk_colon ("help", twk, gp, gt, wcs, newdata, newgraph)
+ case 'a':
+ call twk_rmsmin (twk)
+ newdata = YES
+ case 'c':
+ if (graph2 == GCAL)
+ graph2 = GNONE
+ else
+ graph2 = GCAL
+ call gt_setr (gt[2], GTYMIN, INDEF)
+ call gt_setr (gt[2], GTYMAX, INDEF)
+ newgraph = YES
+ case 'd':
+ if (graph2 == GDATA)
+ graph2 = GNONE
+ else
+ graph2 = GDATA
+ call gt_setr (gt[2], GTYMIN, INDEF)
+ call gt_setr (gt[2], GTYMAX, INDEF)
+ newgraph = YES
+ case 'e':
+ switch (graph1) {
+ case 'x':
+ if (TWK_DSHIFT(twk) == 0.)
+ TWK_DSHIFT(twk) = 0.1
+ else
+ TWK_DSHIFT(twk) = 2 * TWK_DSHIFT(twk)
+ case 'y':
+ if (TWK_DSCALE(twk) == 0.)
+ TWK_DSCALE(twk) = 0.1
+ else
+ TWK_DSCALE(twk) = min (0.99, 2 * TWK_DSCALE(twk))
+ }
+ newdata = YES
+ case 'q':
+ break
+ case 'r':
+ newgraph = YES
+ case 's':
+ dy = wx
+ call printf ("s to add sample region or n for new regions:\n")
+ if (clgcur ("cursor",wx,wy,wcs,key,Memc[cmd],SZ_LINE) == EOF)
+ break
+ switch (key) {
+ case 'n':
+ call rg_free (TWK_RG(twk))
+ call sprintf (TWK_SAMPLE(twk), TWK_SLEN, "%g:%g")
+ call pargr (dy)
+ call pargr (wx)
+ TWK_RG(twk) = rg_xrangesd (TWK_SAMPLE(twk),
+ Memd[TWK_WAVE(twk)], SN(sh))
+ newdata = YES
+ case 's':
+ call rg_free (TWK_RG(twk))
+ if (TWK_SAMPLE(twk) == '*') {
+ call sprintf (TWK_SAMPLE(twk), TWK_SLEN, "%g:%g")
+ call pargr (dy)
+ call pargr (wx)
+ } else {
+ call sprintf (Memc[cmd], SZ_LINE, ",%g:%g")
+ call pargr (dy)
+ call pargr (wx)
+ call strcat (Memc[cmd], TWK_SAMPLE(twk), TWK_SLEN)
+ }
+ TWK_RG(twk) = rg_xrangesd (TWK_SAMPLE(twk),
+ Memd[TWK_WAVE(twk)], SN(sh))
+ newdata = YES
+ }
+ case 'w':
+ call gt_window (gt[wcs], gp, "cursor", newgraph)
+ if (wcs == 1) {
+ call gt_setr (gt[2], GTXMIN, gt_getr (gt[1], GTXMIN))
+ call gt_setr (gt[2], GTXMAX, gt_getr (gt[1], GTXMAX))
+ call gt_seti (gt[2], GTXFLIP, gt_geti (gt[1], GTXFLIP))
+ } else {
+ call gt_setr (gt[1], GTXMIN, gt_getr (gt[2], GTXMIN))
+ call gt_setr (gt[1], GTXMAX, gt_getr (gt[2], GTXMAX))
+ call gt_seti (gt[1], GTXFLIP, gt_geti (gt[2], GTXFLIP))
+ }
+ case 'x', 'y':
+ pix = max (1, min (n, nint (shdr_wl (sh, double (wx))))) - 1
+ j = 1
+ dy = abs (wy - Memr[z[j]+pix])
+ do i = 2, 3
+ if (abs (wy - Memr[z[i]+pix]) < dy) {
+ j = i
+ dy = abs (wy - Memr[z[j]+pix])
+ }
+ TWK_SHIFT(twk) = shift[j]
+ TWK_SCALE(twk) = scale[j]
+ if (j == 2 && graph1 == key) {
+ if (key == 'x')
+ TWK_DSHIFT(twk) = TWK_DSHIFT(twk) / 2.
+ else if (key == 'y')
+ TWK_DSCALE(twk) = TWK_DSCALE(twk) / 2.
+ }
+ if (TWK_DSHIFT(twk) == 0.)
+ graph1 = 'y'
+ else if (TWK_DSHIFT(twk) == 0.)
+ graph1 = 'x'
+ else
+ graph1 = key
+ newdata = YES
+ default:
+ call printf ("\007\n")
+ }
+
+ if (newdata == YES) {
+ if (graph1 == 'x') {
+ shift[1] = TWK_SHIFT(twk) - TWK_DSHIFT(twk)
+ shift[2] = TWK_SHIFT(twk)
+ shift[3] = TWK_SHIFT(twk) + TWK_DSHIFT(twk)
+ scale[1] = TWK_SCALE(twk)
+ scale[2] = TWK_SCALE(twk)
+ scale[3] = TWK_SCALE(twk)
+ } else if (graph1 == 'y') {
+ shift[1] = TWK_SHIFT(twk)
+ shift[2] = TWK_SHIFT(twk)
+ shift[3] = TWK_SHIFT(twk)
+ scale[1] = TWK_SCALE(twk) * (1 - TWK_DSCALE(twk))
+ scale[2] = TWK_SCALE(twk)
+ scale[3] = TWK_SCALE(twk) * (1 + TWK_DSCALE(twk))
+ }
+ iferr {
+ TWK_SPEC(twk) = z[1]
+ call twk_spec (twk, shift[1], scale[1])
+ call asubkr (Memr[z[1]], TWK_OFFSET(twk), Memr[z[1]], n)
+ TWK_SPEC(twk) = z[3]
+ call twk_spec (twk, shift[3], scale[3])
+ call aaddkr (Memr[z[3]], TWK_OFFSET(twk), Memr[z[3]], n)
+ TWK_SPEC(twk) = z[2]
+ call twk_spec (twk, shift[2], scale[2])
+ newdata = NO
+ } then {
+ TWK_SPEC(twk) = z[2]
+ call gt_free (gt[1])
+ call gt_free (gt[2])
+ call gclose (gp)
+ call sfree (sp)
+ call erract (EA_ERROR)
+ }
+
+ call sprintf (Memc[str], SZ_LINE, "scale = %5g")
+ call pargr (TWK_SCALE(twk))
+ if (graph1 == 'y') {
+ call sprintf (Memc[cmd], SZ_LINE, " +/- %6g")
+ call pargr (TWK_DSCALE(twk))
+ call strcat (Memc[cmd], Memc[str], SZ_LINE)
+ }
+ call sprintf (Memc[cmd], SZ_LINE, ", shift = %.2f")
+ call pargr (TWK_SHIFT(twk))
+ call strcat (Memc[cmd], Memc[str], SZ_LINE)
+ if (graph1 == 'x') {
+ call sprintf (Memc[cmd], SZ_LINE, " +/- %.2f")
+ call pargr (TWK_DSHIFT(twk))
+ call strcat (Memc[cmd], Memc[str], SZ_LINE)
+ }
+ call sprintf (Memc[cmd], SZ_LINE, ", offset = %3g")
+ call pargr (TWK_OFFSET(twk))
+ call strcat (Memc[cmd], Memc[str], SZ_LINE)
+ call sprintf (Memc[cmd], SZ_LINE, ", rms = %.3g")
+ call pargr (TWK_RMS(twk))
+ call strcat (Memc[cmd], Memc[str], SZ_LINE)
+ call gt_sets (gt[1], GTCOMMENTS, Memc[str])
+
+ newgraph = YES
+ }
+
+ if (newgraph == YES) {
+ call twk_graph (twk, gp, gt, graph1, graph2, Memr[SX(sh)],
+ Memr[z[1]], Memr[z[2]], Memr[z[3]], SN(sh))
+ newgraph = NO
+ }
+ } until (clgcur ("cursor", wx, wy, wcs, key, Memc[cmd], SZ_LINE) == EOF)
+
+ call gt_free (gt[1])
+ call gt_free (gt[2])
+ call gclose (gp)
+ call sfree (sp)
+end
+
+
+# TWK_GRAPH -- Make the interactive graph.
+
+procedure twk_graph (twk, gp, gt, graph1, graph2, x, y1, y2, y3, npts)
+
+pointer twk #I TWK data object
+pointer gp #I GIO pointer
+pointer gt[2] #I GTOOLS pointer
+int graph1 #I Type for graph 1
+int graph2 #I Type for graph 2
+real x[npts] #I X values
+real y1[npts] #I Y values
+real y2[npts] #I Y values
+real y3[npts] #I Y values
+int npts #I Number of values
+
+real xmin, xmax, ymin, ymax, xmin1, xmax1, ymin1, ymax1
+
+begin
+ call gclear (gp)
+ call gseti (gp, G_WCS, 1)
+ if (graph2 != GNONE) {
+ call gsview (gp, 0.1, 0.9, 0.4, 0.9)
+ call gseti (gp, G_XLABELTICKS, NO)
+ call gt_seti (gt[1], GTDRAWXLABELS, NO)
+ }
+ call gt_ascale (gp, gt[1], x, y1, npts)
+ call ggwind (gp, xmin, xmax, ymin, ymax)
+ call gt_ascale (gp, gt[1], x, y2, npts)
+ call ggwind (gp, xmin1, xmax1, ymin1, ymax1)
+ xmin = min (xmin, xmin1)
+ xmax = max (xmax, xmax1)
+ ymin = min (ymin, ymin1)
+ ymax = max (ymax, ymax1)
+ call gt_ascale (gp, gt[1], x, y3, npts)
+ call ggwind (gp, xmin1, xmax1, ymin1, ymax1)
+ xmin = min (xmin, xmin1)
+ xmax = max (xmax, xmax1)
+ ymin = min (ymin, ymin1)
+ ymax = max (ymax, ymax1)
+ call gswind (gp, xmin, xmax, ymin, ymax)
+ call gt_swind (gp, gt[1])
+ call gt_labax (gp, gt[1])
+
+ call gt_plot (gp, gt[1], x, y1, npts)
+ call gt_plot (gp, gt[1], x, y2, npts)
+ call gt_plot (gp, gt[1], x, y3, npts)
+ call rg_gxmarkr (gp, TWK_SAMPLE(twk), x, npts, 1)
+
+ switch (graph2) {
+ case GCAL:
+ call gseti (gp, G_WCS, 2)
+ call gseti (gp, G_YNMAJOR, 3)
+ call gseti (gp, G_XLABELTICKS, YES)
+ call gt_seti (gt[2], GTDRAWXLABELS, YES)
+ call gt_seti (gt[2], GTDRAWTITLE, NO)
+ call gt_ascale (gp, gt[2], x, Memr[SY(TWK_CAL(twk))], npts)
+ call gsview (gp, 0.1, 0.9, 0.1, 0.4)
+ call gswind (gp, xmin, xmax, INDEF, INDEF)
+ call gt_swind (gp, gt[2])
+ call gt_labax (gp, gt[2])
+ call gt_plot (gp, gt[2], x, Memr[SY(TWK_CAL(twk))], npts)
+ case GDATA:
+ call gseti (gp, G_WCS, 2)
+ call gseti (gp, G_YNMAJOR, 3)
+ call gseti (gp, G_XLABELTICKS, YES)
+ call gt_seti (gt[2], GTDRAWXLABELS, YES)
+ call gt_seti (gt[2], GTDRAWTITLE, NO)
+ call gt_ascale (gp, gt[2], x, Memr[SY(TWK_SH(twk))], npts)
+ call gsview (gp, 0.1, 0.9, 0.1, 0.4)
+ call gswind (gp, xmin, xmax, INDEF, INDEF)
+ call gt_swind (gp, gt[2])
+ call gt_labax (gp, gt[2])
+ call gt_plot (gp, gt[2], x, Memr[SY(TWK_SH(twk))], npts)
+ }
+end
+
+
+# List of colon commands.
+define CMDS "|help|shift|scale|dshift|dscale|offset|smooth|sample|"
+define HELP 1 # Print help
+define SHIFT 2 # Shift
+define SCALE 3 # Scale factor
+define DSHIFT 4 # Shift intervale
+define DSCALE 5 # Scale factor interval
+define OFFSET 6 # Offset
+define SMOOTH 7 # Boxcar smoothing
+define SAMPLE 8 # Sample
+
+# TWK_COLON -- Act on colon commands.
+
+procedure twk_colon (command, twk, gp, gt, wcs, newdata, newgraph)
+
+char command[ARB] #I Colon command
+pointer twk #I TWK data object
+pointer gp #I GIO
+pointer gt[2] #I GTOOLS
+int wcs #I WCS
+int newdata #O New data flag
+int newgraph #O New graph flag
+
+int ncmd, ival, gt_geti(), strdic(), nscan()
+real rval, gt_getr()
+pointer sp, cmd, rg, rg_xrangesd()
+
+begin
+ # Check for GTOOLS command.
+ if (command[1] == '/') {
+ call gt_colon (command, gp, gt[wcs], newgraph)
+ if (wcs == 1) {
+ call gt_setr (gt[2], GTXMIN, gt_getr (gt[1], GTXMIN))
+ call gt_setr (gt[2], GTXMAX, gt_getr (gt[1], GTXMAX))
+ call gt_seti (gt[2], GTXFLIP, gt_geti (gt[1], GTXFLIP))
+ } else {
+ call gt_setr (gt[1], GTXMIN, gt_getr (gt[2], GTXMIN))
+ call gt_setr (gt[1], GTXMAX, gt_getr (gt[2], GTXMAX))
+ call gt_seti (gt[1], GTXFLIP, gt_geti (gt[2], GTXFLIP))
+ }
+ return
+ }
+
+ call smark (sp)
+ call salloc (cmd, SZ_LINE, TY_CHAR)
+
+ # Scan the command string.
+ call sscan (command)
+ call gargwrd (Memc[cmd], SZ_LINE)
+ ncmd = strdic (Memc[cmd], Memc[cmd], SZ_LINE, CMDS)
+
+ # Execute command.
+ switch (ncmd) {
+ case HELP:
+ call gpagefile (gp, TWK_HELP(twk), TWK_TYPE(twk))
+ case SHIFT:
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("shift %g\n")
+ call pargr (TWK_SHIFT(twk))
+ } else {
+ TWK_SHIFT(twk) = rval
+ newdata = YES
+ }
+ case SCALE:
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("scale %g\n")
+ call pargr (TWK_SCALE(twk))
+ } else {
+ TWK_SCALE(twk) = rval
+ newdata = YES
+ }
+ case DSHIFT:
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("dshift %g\n")
+ call pargr (TWK_DSHIFT(twk))
+ } else {
+ TWK_DSHIFT(twk) = rval
+ newdata = YES
+ }
+ case DSCALE:
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("dscale %g\n")
+ call pargr (TWK_DSCALE(twk))
+ } else {
+ if (rval < 0. || rval >= 1.)
+ call printf ("dscale must be between zero and one\007\n")
+ else {
+ TWK_DSCALE(twk) = rval
+ newdata = YES
+ }
+ }
+ case OFFSET:
+ call gargr (rval)
+ if (nscan() == 1) {
+ call printf ("offset %g\n")
+ call pargr (TWK_OFFSET(twk))
+ } else if (rval != TWK_OFFSET(twk)) {
+ TWK_OFFSET(twk) = rval
+ call gt_setr (gt[1], GTYMIN, INDEF)
+ call gt_setr (gt[1], GTYMAX, INDEF)
+ newdata = YES
+ }
+ case SMOOTH:
+ call gargi (ival)
+ if (nscan() == 1) {
+ call printf ("smooth %d\n")
+ call pargi (TWK_BOX(twk))
+ } else {
+ ival = max (1, ival)
+ if (ival != TWK_BOX(twk)) {
+ TWK_BOX(twk) = max (1, ival)
+ newdata = YES
+ }
+ }
+ case SAMPLE:
+ call gargstr (Memc[cmd], SZ_LINE)
+ if (Memc[cmd] == EOS) {
+ call printf ("sample %s\n")
+ call pargstr (TWK_SAMPLE(twk))
+ } else {
+ ifnoerr (rg = rg_xrangesd (Memc[cmd+1], Memd[TWK_WAVE(twk)],
+ SN(TWK_SH(twk)))) {
+ call rg_free (TWK_RG(twk))
+ call strcpy (Memc[cmd+1], TWK_SAMPLE(twk), TWK_SLEN)
+ TWK_RG(twk) = rg
+ newdata = YES
+ } else
+ call erract (EA_WARN)
+ }
+ default:
+ call printf ("\007\n")
+ }
+
+ call sfree (sp)
+end
diff --git a/noao/onedspec/telluric.par b/noao/onedspec/telluric.par
new file mode 100644
index 00000000..80e5c55a
--- /dev/null
+++ b/noao/onedspec/telluric.par
@@ -0,0 +1,21 @@
+# TELLURIC
+
+input,s,a,,,,List of input spectra to correct
+output,s,a,,,,List of output corrected spectra
+cal,s,a,,,,List of telluric calibration spectra
+ignoreaps,b,h,no,,,Ignore aperture numbers in calibration spectra?
+xcorr,b,h,yes,,,Cross correlate for shift?
+tweakrms,b,h,yes,,,Tweak to minimize RMS?
+interactive,b,h,yes,,,Interactive tweaking?
+sample,s,h,"*",,,Sample ranges
+threshold,r,h,0.,,,Threshold for calibration
+lag,i,h,10,0,,Cross correlation lag (pixels)
+shift,r,h,0.,,,Initial shift of calibration spectrum (pixels)
+scale,r,h,1.,1e-10,,Initial scale factor multiplying airmass ratio
+dshift,r,h,1.,0.,,Initial shift search step
+dscale,r,h,0.2,0.,0.99,Initial scale factor search step
+offset,r,h,1.,0.,,Initial offset for graphs
+smooth,i,h,1,1,,Smoothing box for graphs
+cursor,*gcur,h,"",,,Cursor input
+airmass,r,q,,1.,,Airmass
+answer,s,q,"yes","no|yes|NO|YES",,Search interactively?
diff --git a/noao/onedspec/wspectext.cl b/noao/onedspec/wspectext.cl
new file mode 100644
index 00000000..9a7e1571
--- /dev/null
+++ b/noao/onedspec/wspectext.cl
@@ -0,0 +1,47 @@
+# WSPECTEXT -- Write a 1D image spectrum as an ascii text file.
+# This simply uses WTEXTIMAGE to write the header is selected and
+# formats the wavelength/flux data using LISTPIX.
+
+procedure wspectext (input, output)
+
+string input {prompt="Input list of image spectra"}
+string output {prompt="Output list of text spectra"}
+bool header = yes {prompt="Include header?"}
+string wformat = "" {prompt="Wavelength format"}
+
+begin
+ int ndim
+ string specin, specout, spec
+
+ specin = mktemp ("tmp$iraf")
+ specout = mktemp ("tmp$iraf")
+ spec = mktemp ("tmp$iraf")
+
+ # Expand the input and output image templates and include naxis.
+ hselect (input, "$I,naxis", yes, > specin)
+ sections (output, option="fullname", > specout)
+ join (specin, specout, output=spec, delim=" ", shortest=yes, verbose=yes)
+ delete (specin, verify=no)
+ delete (specout, verify=no)
+
+ # For each input spectrum check the dimensionality. Extract the header
+ # with WTEXTIMAGE if desired and then use LISTPIX to extract the
+ # wavelengths and fluxes.
+
+ list = spec
+ while (fscan (list, specin, ndim, specout) != EOF) {
+ if (ndim != 1) {
+ print ("WARNING: "//specin//" is not one dimensional")
+ next
+ }
+ if (header) {
+ wtextimage (specin, specout, header=yes, pixels=no, format="",
+ maxlinelen=80)
+ listpixels (specin, wcs="world", formats=wformat, verbose=no,
+ >> specout)
+ } else
+ listpixels (specin, wcs="world", formats=wformat, verbose=no,
+ > specout)
+ }
+ list=""; delete (spec, verify=no)
+end
diff --git a/noao/onedspec/x_onedspec.x b/noao/onedspec/x_onedspec.x
new file mode 100644
index 00000000..4db13865
--- /dev/null
+++ b/noao/onedspec/x_onedspec.x
@@ -0,0 +1,43 @@
+task addsets = t_addsets,
+ autoidentify = t_autoidentify,
+ bswitch = t_bswitch,
+ calibrate = t_calibrate,
+ coefs = t_coefs,
+ coincor = t_coincor,
+ continuum = t_sfit,
+ deredden = t_deredden,
+ dispcor = t_dispcor,
+ disptrans = t_disptrans,
+ dopcor = t_dopcor,
+ ecidentify = t_ecidentify,
+ ecreidentify = t_ecreidentify,
+ fitprofs = t_fitprofs,
+ flatfit = t_flatfit,
+ flatdiv = t_flatdiv,
+ identify = t_identify,
+ lcalib = t_lcalib,
+ mkspec = t_mkspec,
+ names = t_names,
+ odcombine = t_odcombine,
+ refspectra = t_refspectra,
+ reidentify = t_reidentify,
+ rstext = t_rstext,
+ sapertures = t_sapertures,
+ sbands = t_sbands,
+ scoords = t_scoords,
+ sensfunc = t_sensfunc,
+ sinterp = t_sinterp,
+ sfit = t_sfit,
+ sflip = t_sflip,
+ slist = t_slist,
+ slist1d = t_slist1d,
+ specplot = t_specplot,
+ specshift = t_sshift,
+ splot = splot,
+ sarith = t_sarith,
+ skytweak = t_skytweak,
+ standard = t_standard,
+ subsets = t_subsets,
+ sums = t_sums,
+ telluric = t_telluric,
+ widstape = t_widstape