Package gavo :: Package protocols :: Module sdm
[frames] | no frames]

Source Code for Module gavo.protocols.sdm

  1  """ 
  2  Code dealing with spectra (the actual data), in particular in the spectral 
  3  data model (sdm). 
  4   
  5  This assumes Spectral Data Model Version 1, but doesn't use very much of it. 
  6  There's an sdm2 module for version 2 support.  Generic, version-independent  
  7  code should go here. 
  8  """ 
  9   
 10  #c Copyright 2008-2019, the GAVO project 
 11  #c 
 12  #c This program is free software, covered by the GNU GPL.  See the 
 13  #c COPYING file in the source distribution. 
 14   
 15   
 16  import datetime 
 17  import os 
 18  from cStringIO import StringIO 
 19   
 20  from gavo import base 
 21  from gavo import formats 
 22  from gavo import rsc 
 23  from gavo import svcs 
 24  from gavo import utils 
 25  from gavo.formats import fitstable 
 26  from gavo.protocols import products 
 27   
 28   
 29  # MIME types we can generate *from* SDM-compliant data; the values are 
 30  # either keys for formats.formatData, or None if we have special 
 31  # handling below. 
 32  GETDATA_FORMATS = { 
 33          base.votableType: "votable", 
 34          "application/x-votable+xml;serialization=tabledata": "votabletd", 
 35          "text/plain": "tsv", 
 36          "text/csv": "csv", 
 37          "application/fits": None, 
 38          "application/x-votable+xml;content=spec2": None, 
 39          "application/x-votable+xml;version=1.4": "vodml"} 
 40   
 41   
 42  _SDM1_IRREGULARS = { 
 43          "Dataset.Type": "Spectrum.Type", 
 44          "Dataset.Length ": "Spectrum.Length", 
 45          "Dataset.TimeSI": "Spectrum.TimeSI", 
 46          "Dataset.SpectralSI": "Spectrum.SpectralSI", 
 47          "Dataset.FluxSI": "Spectrum.FluxSI", 
 48          "Access.Format": None, 
 49          "Access.Reference": None, 
 50          "Access.Size": None, 
 51          "AstroCoords.Position2D.Value2":  
 52                  "Spectrum.Char.SpatialAxis.Coverage.Location.Value", 
 53          "Target.pos.spoint": "Spectrum.Target.pos", 
 54  } 
 55   
56 -def getSDM1UtypeForSSA(utype):
57 """returns a utype from the spectrum data model for a utype of the ssa 58 data model. 59 60 For most utypes, this just removes a prefix and adds spec:Spectrum. Heaven 61 knows why these are two different data models anyway. There are some 62 (apparently random) differences, though. 63 64 For convenience, utype=None is allowed and returned as such. 65 """ 66 if utype is None: 67 return None 68 localName = utype.split(":")[-1] 69 specLocal = _SDM1_IRREGULARS.get(localName, "Spectrum."+localName) 70 if specLocal: 71 return "spec:"+specLocal 72 else: 73 return None
74 75 76 _SDM2_IRREGULARS = { 77 # a dict mapping utypes from SSA trunks to SDM2 trunks where 78 # they are different 79 "dataid.instrument": "ObsConfig.Instrument.Name", 80 "dataid.bandpass": "ObsConfig.Bandpass.Name", 81 "dataid.datasource": "ObsConfig.DataSource.Name", 82 83 "char.spatialaxis.samplingprecision.fillfactor": 84 "Char.SpatialAxis.SamplingPrecision.SamplingPrecisionRefval.fillFactor", 85 "char.spatialaxis.calibration": "Char.SpatialAxis.CalibrationStatus", 86 "char.spatialaxis.resolution": "Char.SpatialAxis.Resolution.refVal", 87 88 "char.spectralaxis.samplingprecision.fillfactor": 89 "Char.SpectralAxis.SamplingPrecision.SamplingPrecisionRefval.fillFactor", 90 "char.spectralaxis.calibration": "Char.SpectralAxis.CalibrationStatus", 91 "char.spectralaxis.resolution": "Char.SpectralAxis.Resolution.refVal", 92 93 "char.timeaxis.samplingprecision.fillfactor": 94 "Char.TimeAxis.SamplingPrecision.SamplingPrecisionRefval.fillFactor", 95 "char.timeaxis.calibration": "Char.TimeAxis.CalibrationStatus", 96 "char.timeaxis.resolution": "Char.TimeAxis.Resolution.refVal", 97 98 "char.fluxaxis.calibration": "Char.FluxAxis.CalibrationStatus", 99 100 "curation.publisherdid": "DataID.DatasetID", 101 } 102 103
104 -def getSDM2UtypeForSSA(utype):
105 """returns a utype from the spectral data model 2 for a utype of the ssa 106 data model. 107 108 For convenience, utype=None is allowed and returned as such. 109 """ 110 # This isn't used yet. This would be used in ssap#sdm-instance 111 # if we made sdm2 our default (and then we'd have to translate back 112 # the sdm2 utypes to sdm1 ones if we still wanted to spit out sdm1) 113 if utype is None: 114 return None 115 localName = utype.split(":")[-1] 116 specLocal = _SDM2_IRREGULARS.get(localName.lower(), localName) 117 return "spec2:"+specLocal
118 119 120 _SDM_TO_SED_UTYPES = { 121 "spec:Spectrum.Data.SpectralAxis.Value": 122 "sed:Segment.Points.SpectralCoord.Value", 123 "spec2:Data.SpectralAxis.Value": 124 "sed:Segment.Points.SpectralCoord.Value", 125 "spec:Spectrum.Data.FluxAxis.Value": "sed:Segment.Points.Flux.Value", 126 "spec2:Data.FluxAxis.Value": "sed:Segment.Points.Flux.Value", 127 } 128 129 130 SDM1_TO_SDM2_IRREGULARS = { 131 "Char.FluxAxis.Calibration": "Char.FluxAxis.CalibrationStatus", 132 "Char.SpatialAxis.Calibration": "Char.SpatialAxis.CalibrationStatus", 133 "Char.SpatialAxis.Resolution": "Char.SpatialAxis.Resolution.refVal", 134 "Char.SpectralAxis.Calibration": "Char.SpectralAxis.CalibrationStatus", 135 "Char.SpectralAxis.Resolution": "Char.SpectralAxis.Resolution.refVal", 136 "Char.SpectralAxis.ResPower": 137 "Char.SpectralAxis.Resolution.ResolPower.refVal", 138 "Char.TimeAxis.Calibration": "Char.TimeAxis.CalibrationStatus", 139 "Char.TimeAxis.Resolution": "Char.TimeAxis.Resolution.refVal", 140 "Data.FluxAxis.Quality": "Data.FluxAxis.Accuracy.QualityStatus", 141 "Data.SpectralAxis.Resolution": "Data.SpectralAxis.Resolution.refVal", 142 "Type": "Dataset.Type", 143 "DataModel": "Dataset.DataModel.Name", 144 "Length": "Dataset.Length", 145 "TimeSI": "Dataset.TimeSI", 146 "SpectralSI": "Dataset.SpectralSI", 147 "FluxSI": "Dataset.FluxSI", 148 "DataID.Instrument": "ObsConfig.Instrument", 149 "DataID.Bandpass": "ObsConfig.Bandpass", 150 "DataID.DataSource": "ObsConfig.DataSource", 151 "Curation.PublisherDID": "DataID.DatasetID", 152 } 153
154 -def getSDM2utypeForSDM1(utype):
155 """returns a utype for the spectral data model 2 from a version 1 one. 156 """ 157 if utype is None: 158 return None 159 160 if not utype.startswith("spec:Spectrum."): 161 return utype 162 localPart = SDM1_TO_SDM2_IRREGULARS.get(utype[14:], utype[14:]) 163 return "spec2:%s"%localPart
164 165
166 -def hackSDM1ToSDM2(data):
167 """changes some utypes to make an SDM2 compliant spectrum from an 168 SDM1 compliant one. 169 170 All this is too horrible to even contemplate, but it's really pending 171 sound DM support in both DaCHS and the VO. If we have that, I hope 172 sick mess like this is going to disappear. 173 """ 174 table = data.getPrimaryTable() 175 table.tableDef = table.tableDef.copy(table.tableDef.parent) 176 for group in table.tableDef.groups: 177 group.utype = getSDM2utypeForSDM1(group.utype) 178 for param in group.params: 179 param.utype = getSDM2utypeForSDM1(param.utype) 180 for paramRef in group.paramRefs: 181 paramRef.utype = getSDM2utypeForSDM1(paramRef.utype) 182 for columnRef in group.columnRefs: 183 columnRef.utype = getSDM2utypeForSDM1(columnRef.utype) 184 185 for param in table.iterParams(): 186 param.utype = getSDM2utypeForSDM1(param.utype) 187 for column in table.tableDef.columns: 188 column.utype = getSDM2utypeForSDM1(column.utype) 189 190 param = table.getParamByUtype("spec2:Dataset.DataModel") 191 param.utype = "spec2:Dataset.DataModel.Name" 192 table.setParam(param.name, "Spectrum-2.0") 193 194 table.setMeta("utype", "spec2:Spectrum") 195 data.DACHS_SDM_VERSION = "2"
196 197 198 ################### Making SDM compliant tables (from SSA rows and 199 ################### data descriptors making spectral data) 200
201 -def makeSDMDataForSSARow(ssaRow, spectrumData, 202 sdmVersion=base.getConfig("ivoa", "sdmVersion")):
203 """returns a rsc.Data instance containing an SDM compliant spectrum 204 for the spectrum described by ssaRow. 205 206 spectrumData is a data element making a primary table containing 207 the spectrum data from an SSA row (typically, this is going to be 208 the tablesource property of an SSA service). 209 210 You'll usually use this via //soda#sdm_genData 211 """ 212 with base.getTableConn() as conn: 213 resData = rsc.makeData(spectrumData, forceSource=ssaRow, 214 connection=conn) 215 resData.setMeta("utype", "spec:Spectrum") 216 217 resTable = resData.getPrimaryTable() 218 resTable.setMeta("description", 219 "Spectrum from %s"%products.makeProductLink(ssaRow["accref"])) 220 221 # fudge accref into a full URL 222 resTable.setParam("accref", 223 products.makeProductLink(resTable.getParam("accref"))) 224 resData.DACHS_SDM_VERSION = sdmVersion 225 226 # fudge spoint params into 2-arrays 227 for param in resTable.iterParams(): 228 # Bad, bad: In-place changes; we should think how such things 229 # can be done better in a rewrite 230 if param.type=="spoint": 231 val = param.value 232 param.type = "double precision(2)" 233 param.xtype = None 234 param.unit = "deg" 235 if val: 236 param.set([val.x/utils.DEG, val.y/utils.DEG]) 237 238 if sdmVersion=="2": 239 hackSDM1ToSDM2(resData) 240 return resData
241 242
243 -def makeSDMDataForPUBDID(pubDID, ssaTD, spectrumData, 244 sdmVersion=base.getConfig("ivoa", "sdmVersion")):
245 """returns a rsc.Data instance containing an SDM compliant spectrum 246 for pubDID from ssaTable. 247 248 ssaTD is the definition of a table containg the SSA metadata, 249 spectrumData is a data element making a primary table containing 250 the spectrum data from an SSA row (typically, this is going to be 251 the tablesource property of an SSA service). 252 """ 253 matchingRows = ssaTD.doSimpleQuery(fragments="ssa_pubdid=%(pubDID)s", 254 params={"pubDID": pubDID}) 255 if not matchingRows: 256 raise svcs.UnknownURI("No spectrum with pubdid %s known here"% 257 pubDID) 258 return makeSDMDataForSSARow( 259 matchingRows[0], 260 spectrumData, 261 sdmVersion=sdmVersion)
262 263 264 ################## Special FITS hacks for SDM serialization 265
266 -def _add_target_pos_cards(header, par):
267 """_SDM_HEADER_MAPPING for target.pos. 268 """ 269 header.set("RA_TARG", par.value.x/utils.DEG) 270 header.set("DEC_TARG", par.value.y/utils.DEG)
271 272
273 -def _add_location_cards(header, par):
274 """_SDM_HEADER_MAPPING for target.pos. 275 """ 276 header.set("RA", par.value[0]) 277 header.set("DEC", par.value[1])
278 279
280 -def getColIndForUtype(header, colUtype):
281 """returns the fits field index that has colUtype as its utype within 282 header. 283 284 If no such field exists, this raises a KeyError 285 """ 286 nCols = header.get("TFIELDS", 0) 287 for colInd in range(1, nCols+1): 288 key = "TUTYP%d"%colInd 289 if header.get(key, None)==colUtype: 290 return colInd 291 else: 292 raise KeyError(colUtype)
293 294
295 -def _make_limits_func(colUtype, keyBase):
296 """returns a _SDM_HEADER_MAPPING for declaring TDMIN/MAXn and friends. 297 298 colUtype determines the n here. 299 """ 300 def func(header, par): 301 try: 302 key = keyBase+str(getColIndForUtype(header, colUtype)) 303 header.set(key, par.value, comment="[%s]"%par.unit) 304 except KeyError: 305 # no field for utype 306 pass
307 308 return func 309 310 311 # A mapping from utypes to the corresponding FITS keywords 312 # There are some more complex cases, for which a function is a value 313 # here; the funciton is called with the FITS header and the parameter 314 # in question. 315 _SDM_HEADER_MAPPING = { 316 "datamodel": "VOCLASS", 317 "length": "DATALEN", 318 "type": "VOSEGT", 319 "coordsys.id": "VOCSID", 320 "coordsys.spaceframe.name": "RADECSYS", 321 "coordsys.spaceframe.equinox": "EQUINOX", 322 "coordsys.spaceframe.ucd": "SKY_UCD", 323 "coordsys.spaceframe.refpos": "SKY_REF", 324 "coordsys.timeframe.name": "TIMESYS", 325 "coordsys.timeframe.ucd": None, 326 "coordsys.timeframe.zero": "MJDREF", 327 "coordsys.timeframe.refpos": None, 328 "coordsys.spectralframe.refpos": "SPECSYS", 329 "coordsys.spectralframe.redshift": "REST_Z", 330 "coordsys.spectralframe.name": "SPECNAME", 331 "coordsys.redshiftframe.name": "ZNAME", 332 "coordsys.redshiftframe.refpos": "SPECSYSZ", 333 "curation.publisher": "VOPUB", 334 "curation.reference": "VOREF", 335 "curation.publisherid": "VOPUBID", 336 "curation.version": "VOVER", 337 "curation.contactname": "CONTACT", 338 "curation.contactemail": "EMAIL", 339 "curation.rights": "VORIGHTS", 340 "curation.date": "VODATE", 341 "curation.publisherdid": "DS_IDPUB", 342 "target.name": "OBJECT", 343 "target.description": "OBJDESC", 344 "target.class": "SRCCLASS", 345 "target.spectralclass": "SPECTYPE", 346 "target.redshift": "REDSHIFT", 347 "target.varampl": "TARGVAR", 348 "dataid.title": "TITLE", 349 "dataid.creator": "AUTHOR", 350 "dataid.datasetid": "DS_IDENT", 351 "dataid.creatordid": "CR_IDENT", 352 "dataid.date": "DATE", 353 "dataid.version": "VERSION", 354 "dataid.instrument": "INSTRUME", 355 "dataid.creationtype": "CRETYPE", 356 "dataid.logo": "VOLOGO", 357 # collection will need work when we properly implement it 358 "dataid.collection": "COLLECT1", 359 "dataid.contributor": "CONTRIB1", 360 "dataid.datasource": "DSSOURCE", 361 "dataid.bandpass": "SPECBAND", 362 "derived.snr": "DER_SNR", 363 "derived.redshift.value": "DER_Z", 364 "derived.redshift.staterror": "DER_ZERR", 365 "derived.redshift.confidence": "DER_ZCNF", 366 "derived.varampl": "DER_VAR", 367 "timesi": "TIMESDIM", 368 "spectralsi": "SPECSDIM", 369 "fluxsi": "FLUXSDIM", 370 "char.fluxaxis.name": None, 371 "char.fluxaxis.unit": None, 372 "char.fluxaxis.ucd": None, 373 "char.spectralaxis.name": None, 374 "char.spectralaxis.unit": None, 375 "char.spectralaxis.ucd": None, 376 "char.timeaxis.name": None, 377 "char.timeaxis.ucd": None, 378 "char.spatialaxis.name": None, 379 "char.spatialaxis.unit": None, 380 "char.fluxaxis.accuracy.staterror": "STAT_ERR", 381 "char.fluxaxis.accuracy.syserror": "SYS_ERR", 382 "char.timeaxis.accuracy.staterror": "TIME_ERR", 383 "char.timeaxis.accuracy.syserror": "TIME_SYE", 384 "char.timeaxis.resolution": "TIME_RES", 385 "char.fluxaxis.calibration": "FLUX_CAL", 386 "char.spectralaxis.calibration": "SPEC_CAL", 387 "char.spectralaxis.coverage.location.value": "SPEC_VAL", 388 "char.spectralaxis.coverage.bounds.extent": "SPEC_BW", 389 "char.spectralaxis.coverage.bounds.start": 390 _make_limits_func("spec:spectrum.data.spectralaxis.value", "TDMIN"), 391 "char.spectralaxis.coverage.bounds.stop": 392 _make_limits_func("spec:spectrum.data.spectralaxis.value", "TDMAX"), 393 "char.spectralaxis.samplingprecision.": None, 394 "samplingprecisionrefval.fillfactor": "SPEC_FIL", 395 "char.spectralaxis.samplingprecision.SampleExtent": "SPEC BIN", 396 "char.spectralaxis.accuracy.binsize": "SPEC_BIN", 397 "char.spectralaxis.accuracy.staterror": "SPEC_ERR", 398 "char.spectralaxis.accuracy.syserror": "SPEC_SYE", 399 "char.spectralaxis.resolution": "SPEC_RES", 400 "char.spectralaxis.respower": "SPEC_RP", 401 "char.spectralaxis.coverage.support.extent": "SPECWID", 402 "char.timeaxis.unit": "TIMEUNIT", 403 "char.timeaxis.accuracy.binsize": "TIMEDEL", 404 "char.timeaxis.calibration": "TIME_CAL", 405 "char.timeaxis.coverage.location.value": "TMID", 406 "char.timeaxis.coverage.bounds.extent": "TELAPSE", 407 "char.timeaxis.coverage.bounds.start": "TSTART", 408 "char.timeaxis.coverage.bounds.stop": "TSTOP", 409 "char.timeaxis.coverage.support.extent": "EXPOSURE", 410 "char.timeaxis.samplingprecision.samplingprecisionrefval.fillfactor": "DTCOR", 411 "char.timeaxis.samplingprecision.sampleextent": "TIMEDEL", 412 "char.spatialaxis.ucd": "SKY_UCD", 413 "char.spatialaxis.accuracy.staterr": "SKY_ERR", 414 "char.spatialaxis.accuracy.syserror": "SKY_SYE", 415 "char.spatialaxis.calibration": "SKY_CAL", 416 "char.spatialaxis.resolution": "SKY_RES", 417 "char.spatialaxis.coverage.bounds.extent": "APERTURE", 418 "char.spatialaxis.coverage.support.area": "REGION", 419 "char.spatialaxis.coverage.support.extent": "AREA", 420 "char.spatialaxis.samplingprecision.samplingprecisionrefval.fillfactor": 421 "SKY_FILL", 422 423 # special handling through functions 424 "target.pos": _add_target_pos_cards, 425 "char.spatialaxis.coverage.location.value": _add_location_cards, 426 } 427
428 -def makeBasicSDMHeader(sdmData, header):
429 """updates the fits header header with the params from sdmData. 430 """ 431 for par in sdmData.getPrimaryTable().iterParams(): 432 if par.value is None or par.utype is None: 433 continue 434 435 mapKey = par.utype.lower().split(":")[-1] 436 if mapKey.startswith("spectrum."): # WTF? 437 mapKey = mapKey[9:] 438 439 destKey = _SDM_HEADER_MAPPING.get(mapKey, None) 440 if destKey is None: 441 pass 442 elif callable(destKey): 443 destKey(header, par) 444 else: 445 comment = "" 446 if par.unit: 447 comment = str("[%s]"%par.unit) 448 449 # TODO: use our serialising infrastructure here 450 value = par.value 451 if isinstance(value, unicode): 452 value = value.encode("ascii", "ignore") 453 elif isinstance(value, datetime.datetime): 454 value = value.isoformat() 455 456 header.set(destKey, value, comment)
457 458
459 -def makeSDMFITS(sdmData):
460 """returns sdmData in an SDM-compliant FITS. 461 462 This only works for SDM version 1. Behaviour with version 2 SDM 463 data is undefined. 464 """ 465 sdmData.getPrimaryTable().IgnoreTableParams = None 466 hdus = fitstable.makeFITSTable(sdmData) 467 sdmHdr = hdus[1].header 468 makeBasicSDMHeader(sdmData, sdmHdr) 469 srcName = fitstable.writeFITSTableFile(hdus) 470 with open(srcName) as f: 471 data = f.read() 472 os.unlink(srcName) 473 return data
474 475 476 ################## Serializing SDM compliant tables 477
478 -def formatSDMData(sdmData, format, queryMeta=svcs.emptyQueryMeta):
479 """returns a pair of mime-type and payload for a rendering of the SDM 480 Data instance sdmData in format. 481 482 (you'll usually use this via //soda#sdm_format) 483 """ 484 485 destMime = str(format or base.votableType) 486 if queryMeta["tdEnc"] and destMime==base.votableType: 487 destMime = "application/x-votable+xml;serialization=tabledata" 488 formatId = GETDATA_FORMATS.get(destMime, None) 489 490 if formatId is None: 491 # special or unknown format 492 if destMime=="application/fits": 493 return destMime, makeSDMFITS(sdmData) 494 elif destMime =="application/x-votable+xml;content=spec2": 495 # At the last moment, hack data so it becomes 496 # SDM2. We'll have to finally decide when we actually want 497 # to apply the hacks. And do the whole thing completely 498 # differently. 499 hackSDM1ToSDM2(sdmData) 500 formatId = "votabletd" 501 else: 502 raise base.ValidationError("Cannot format table to %s"%destMime, 503 "FORMAT") 504 505 # target version is usually set by makeSDMDataForSSARow; but if 506 # people made sdmData in some other way, fall back to default. 507 sdmVersion = getattr(sdmData, "DACHS_SDM_VERSION", 508 base.getConfig("ivoa", "sdmVersion")) 509 if sdmVersion=="1": 510 sdmData.addMeta("_votableRootAttributes", 511 'xmlns:spec="http://www.ivoa.net/xml/SpectrumModel/v1.01"') 512 513 resF = StringIO() 514 formats.formatData(formatId, sdmData, resF, acquireSamples=False) 515 return destMime, resF.getvalue()
516 517 518 ################## Manipulation of SDM compliant tables 519 # The idea here is that you can push in a table, the function does some 520 # magic, and it returns that table. The getData implementation (see ssap.py) 521 # and some SODA data functions (//soda) 522 # use these functions to provide some spectrum transformations. We 523 # may want to provide some plugin system so people can add their own 524 # transformations, but let's first see someone request that. 525
526 -def getFluxColumn(sdmTable):
527 """returns the column containing the flux in sdmTable. 528 529 sdmTable can be in SDM1 or SDM2. 530 """ 531 return sdmTable.tableDef.getByUtypes( 532 "spec:Spectrum.Data.FluxAxis.Value", 533 "spec2:Data.FluxAxis.Value")
534 535
536 -def getSpectralColumn(sdmTable):
537 """returns the column containing the spectral coordindate in sdmTable. 538 539 sdmTable can be in SDM1 or SDM2. 540 """ 541 return sdmTable.tableDef.getByUtypes( 542 "spec:Spectrum.Data.SpectralAxis.Value", 543 "spec2:Data.SpectralAxis.Value")
544 545
546 -def mangle_cutout(sdmTable, low, high):
547 """returns only those rows from sdmTable for which the spectral coordinate 548 is between low and high. 549 550 Both low and high must be given. If you actually want half-open intervals, 551 do it in interface code (low=-1 and high=1e308 should do fine). 552 """ 553 spectralColumn = getSpectralColumn(sdmTable) 554 555 spectralUnit = spectralColumn.unit 556 # convert low and high from meters to the unit on the 557 # spectrum's spectral axis 558 factor = base.computeConversionFactor("m", spectralUnit) 559 low = low*factor 560 high = high*factor 561 562 # Whoa! we should have an API that allows replacing table rows safely 563 # (this stuff will blow up when we have indices): 564 spectralName = spectralColumn.name 565 sdmTable.rows=[ 566 row for row in sdmTable.rows if low<=row[spectralName]<=high] 567 568 specVals = [r[spectralName] for r in sdmTable.rows] 569 if specVals: 570 specstart, specend = min(specVals)/factor, max(specVals)/factor 571 sdmTable.setParam("ssa_specext", specend-specstart) 572 sdmTable.setParam("ssa_specstart", specstart) 573 sdmTable.setParam("ssa_specend", specend) 574 sdmTable.setParam("ssa_specmid", (specstart+specend)/2) 575 576 return sdmTable
577 578
579 -def mangle_fluxcalib(sdmTable, newCalib):
580 """returns sdmTable with a new calibration. 581 582 Currently, we can only normalize the spectrum to the maximum value. 583 """ 584 newCalib = newCalib.lower() 585 if newCalib==sdmTable.getParam("ssa_fluxcalib").lower(): 586 return sdmTable 587 fluxName = getFluxColumn(sdmTable).name 588 589 try: 590 # TODO: parameterize this 591 errorName = sdmTable.tableDef.getColumnByUCD( 592 "stat.error;phot.flux;em.opt").name 593 except ValueError: 594 # no (known) error column 595 errorName = None 596 597 if newCalib=="relative": 598 # whoa! we're changing this in place; I guess that should be 599 # legalized for tables in general. 600 normalizer = float(max(row[fluxName] for row in sdmTable.rows)) 601 for row in sdmTable.rows: 602 row[fluxName] = row[fluxName]/normalizer 603 604 if errorName: 605 for row in sdmTable.rows: 606 row[errorName] = row[errorName]/normalizer 607 608 sdmTable.setParam("ssa_fluxcalib", "RELATIVE") 609 sdmTable.tableDef = sdmTable.tableDef.copy(sdmTable.tableDef.parent) 610 sdmTable.tableDef.getColumnByName("flux").unit = "" 611 return sdmTable 612 613 raise base.ValidationError("Do not know how to turn a %s spectrum" 614 " into a %s one."%(sdmTable.getParam("ssa_fluxcalib"), newCalib), 615 "FLUXCALIB")
616