| Home | Trees | Indices | Help |
|
|---|
|
|
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
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
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
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
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
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
274 """_SDM_HEADER_MAPPING for target.pos.
275 """
276 header.set("RA", par.value[0])
277 header.set("DEC", par.value[1])
278
279
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
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
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
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
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
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
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
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
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 2 07:29:09 2019 | http://epydoc.sourceforge.net |