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

Source Code for Module gavo.protocols.soda

  1  """ 
  2  Helper functions for SODA manipulators. 
  3   
  4  This primarily comprises various WCS helpers.  It is built on base.coords, 
  5  which is where you'll get the wcsFields. 
  6   
  7  Note that this must not use things from protocols.datalink, as it 
  8  is imported from there.  Essentially, use this space for helpers 
  9  for SODA manipulations that are generic enough to be kept outside of 
 10  the RD but not generic enough to go do base.coords. 
 11  """ 
 12   
 13  #c Copyright 2008-2019, the GAVO project 
 14  #c 
 15  #c This program is free software, covered by the GNU GPL.  See the 
 16  #c COPYING file in the source distribution. 
 17   
 18   
 19  from gavo import base 
 20  from gavo import rscdef 
 21  from gavo import svcs 
 22  from gavo.base import coords 
 23  from gavo.utils import fitstools 
 24   
 25  from gavo.base import makeStruct as MS 
 26   
 27   
 28  DEFAULT_SEMANTICS = "http://dc.g-vo.org/datalink#other" 
29 30 31 -class EmptyData(base.ExecutiveAction):
32 """raise this when you notice you won't have any data to return. 33 """ 34 responseCode = 204 35 # SODA and HTTP say no bytes are allowed in empty responses. 36 responsePayload = ""
37
38 39 -class DatalinkFault(object):
40 """A datalink error ("fault", as it's called in the spec). 41 42 These are usually constructed using one of the classmethods 43 44 * AuthenticationFault -- Not authenticated (and authentication required) 45 * AuthorizationFault -- Not authorized (to access the resource) 46 * NotFoundFault -- Unknown ID value 47 * UsageFault -- Invalid input (e.g. no ID values) 48 * TransientFault -- Service is not currently able to function 49 * FatalFault -- Service cannot perform requested action 50 * Fault -- General error (not covered above) 51 52 all of which take the pubDID that caused the failure and a human-oriented 53 error message. 54 """
55 - def __init__(self, code, pubDID, message, exceptionClass, semantics, 56 description=None):
57 self.code, self.pubDID, self.message = code, pubDID, message 58 self.semantics = semantics 59 self.exceptionClass = exceptionClass 60 self.description = description
61 62 @classmethod
63 - def _addErrorMaker(cls, errCode, exceptionClass):
64 def meth(inner, pubDID, message, semantics=DEFAULT_SEMANTICS, 65 description=None): 66 return inner(errCode, pubDID, message, exceptionClass, semantics, 67 description)
68 setattr(cls, errCode, classmethod(meth))
69
70 - def asDict(self):
71 """returns an error row for the datalink response. 72 """ 73 return {"ID": self.pubDID, "error_message": 74 "%s: %s"%(self.code, self.message), 75 "semantics": self.semantics, 76 "description": self.description}
77
78 - def raiseException(self):
79 raise self.exceptionClass(self.message+" (pubDID: %s)"%self.pubDID)
80 81 for errName, exClass in [ 82 ("AuthenticationFault", svcs.ForbiddenURI), 83 ("AuthorizationFault", svcs.ForbiddenURI), 84 ("NotFoundFault", svcs.UnknownURI), 85 ("UsageFault", svcs.BadMethod), 86 ("TransientFault", svcs.BadMethod), 87 ("FatalFault", svcs.Error), 88 ("Fault", svcs.Error)]: 89 DatalinkFault._addErrorMaker(errName, exClass) 90 del errName, exClass
91 92 93 -class FormatNow(base.ExecutiveAction):
94 """can be raised by data functions to abort all further processing 95 and format the current descriptor.data. 96 """
97
98 99 -class DeliverNow(base.ExecutiveAction):
100 """can be raised by data functions to abort all further processing 101 and return the current descriptor.data to the client. 102 """
103
104 105 -def ensureSkyWCS(descriptor):
106 """furnishes a soda fits descriptor with skyWCS and spatialAxes attributes 107 if necessary. 108 109 This is usually called by the functions needing this automatically, and 110 it's a no-op if it has already run. 111 112 skyWCS will be None after this function as run if no usable WCS is found; 113 otherwise, it will be a astropy.wcs.WCS instance. Additionally, spatialAxes 114 will be a sequence of 1-based axis indices, and an empty dictionary 115 axisNames is available to be filled by metaMakers. It will then map 116 the SODA parameter name to either a FITS axis index or to the special 117 WCSLONG, WCSLAT values. 118 """ 119 if hasattr(descriptor, "skyWCS"): 120 return 121 122 descriptor.skyWCS, descriptor.spatialAxes = coords.getSkyWCS(descriptor.hdr) 123 descriptor.axisNames = {}
124
125 126 -def iterSpatialAxisKeys(descriptor, axisMetaOverrides):
127 """yields SODA inputKeys for spatial cutouts along the spatial 128 coordinate axes. 129 130 This can be nothing if descriptor doesn't have a skyWCS attribute 131 or if it's None. 132 """ 133 ensureSkyWCS(descriptor) 134 if descriptor.skyWCS is None: 135 return 136 137 footprint = descriptor.skyWCS.calcFootprint(descriptor.hdr) 138 wcsprm = descriptor.skyWCS.wcs 139 140 # FIXME: UCD inference! 141 for name, colInd, description, baseUCD, cutoutName in [ 142 (wcsprm.lattyp.strip(), wcsprm.lat, "The latitude coordinate", 143 "pos.eq.dec", "WCSLAT"), 144 (wcsprm.lngtyp.strip(), wcsprm.lng, "The longitude coordinate", 145 "pos.eq.ra", "WCSLONG")]: 146 if name: 147 vertexCoos = footprint[:,colInd] 148 paramArgs = {"name": name, "unit": "deg", 149 "description": description, 150 "ucd": baseUCD} 151 152 minCoo, maxCoo = min(vertexCoos), max(vertexCoos) 153 # for RA, we need to move the stitching line out 154 # of the way (and go to negative longitudes) if 155 # 0 is on the image; we're doing a little heuristic 156 # there assuming that images are smaller than 180 deg. 157 if cutoutName=="WCSLONG": 158 if coords.straddlesStitchingLine(minCoo, maxCoo): 159 minCoo, maxCoo = maxCoo-360, minCoo 160 161 if name in axisMetaOverrides: 162 paramArgs.update(axisMetaOverrides[name]) 163 164 yield MS(svcs.InputKey, multiplicity="single", 165 type="double precision[2]", xtype="interval", 166 values=MS(rscdef.Values, min=minCoo, max=maxCoo), 167 **paramArgs) 168 descriptor.axisNames[name] = cutoutName
169
170 171 -def iterOtherAxisKeys(descriptor, axisMetaOverrides):
172 """yields inputKeys for all non-spatial WCS axes. 173 174 descriptor must be a FITSDescriptor. 175 """ 176 ensureSkyWCS(descriptor) 177 if descriptor.skyWCS is None: 178 return 179 180 axesLengths = fitstools.getAxisLengths(descriptor.hdr) 181 for axIndex, length in enumerate(axesLengths): 182 fitsAxis = axIndex+1 183 if fitsAxis in descriptor.spatialAxes: 184 continue 185 if length==1: 186 # no cutouts along degenerate axes 187 continue 188 189 try: 190 ax = fitstools.WCSAxis.fromHeader(descriptor.hdr, fitsAxis) 191 except ValueError: 192 # probably botched WCS, or an inseparable axis. 193 # Just ignore this axis, operators can add it manually 194 # using forceSeparable 195 continue 196 197 descriptor.axisNames[ax.name] = fitsAxis 198 minPhys, maxPhys = ax.getLimits() 199 200 # FIXME: ucd inference 201 paramArgs = {"name": ax.name, "unit": ax.cunit, 202 "description": "Coordinate along axis number %s"%fitsAxis, 203 "ucd": None} 204 if fitsAxis in axisMetaOverrides: 205 paramArgs.update(axisMetaOverrides[fitsAxis]) 206 207 yield MS(svcs.InputKey, multiplicity="single", 208 type="double precision[2]", xtype="interval", 209 values=MS(rscdef.Values, min=minPhys, max=maxPhys), 210 **paramArgs)
211
212 213 -def addPolygonSlices(descriptor, poly, srcPar="Unknown"):
214 """adds slicings in descriptor.slices for a pgsphere.SPoly poly. 215 216 srcPar is the name of the parameter that generated the polygon 217 (for making error messages) 218 """ 219 for axisInd, lower, upper in coords.getPixelLimits( 220 poly.asCooPairs(), descriptor.skyWCS): 221 descriptor.changingAxis(axisInd, srcPar) 222 descriptor.slices.append((axisInd, lower, upper))
223
224 225 -def doAxisCutout(descriptor, args):
226 """updates descriptor.data on a FITS descriptor, interpreting the 227 parameters defined by iter*AxisKeys, passed in in args. 228 229 This is the main implementation of //soda#fits_doWCSCutout 230 """ 231 ensureSkyWCS(descriptor) 232 slices = descriptor.slices 233 234 # limits: [minRA, maxRA], [minDec, maxDec]] 235 footprint = descriptor.skyWCS.calcFootprint(descriptor.hdr) 236 limits = [[min(footprint[:,0]), max(footprint[:,0])], 237 [min(footprint[:,1]), max(footprint[:,1])]] 238 if coords.straddlesStitchingLine(limits[0][0], limits[0][1]): 239 limits[0] = [limits[0][1]-360, limits[0][0]] 240 limitsChangedName = None 241 242 for parName, fitsAxis in descriptor.axisNames.iteritems(): 243 if args[parName] is None: 244 continue 245 limitsChangedName = parName 246 247 if not isinstance(fitsAxis, int): 248 # some sort of spherical axis 249 if fitsAxis=="WCSLAT": 250 cooLimits = limits[1] 251 elif fitsAxis=="WCSLONG": 252 cooLimits = limits[0] 253 else: 254 assert False 255 256 cooLimits[0] = max(cooLimits[0], args[parName][0]) 257 cooLimits[1] = min(cooLimits[1], args[parName][1]) 258 259 else: 260 # 1-d axis 261 transform = fitstools.WCSAxis.fromHeader(descriptor.hdr, fitsAxis) 262 axMin, axMax = args[parName] 263 descriptor.changingAxis(fitsAxis, parName) 264 slices.append((fitsAxis, 265 transform.physToPix(axMin), transform.physToPix(axMax))) 266 267 if limitsChangedName: 268 for axisInd, lower, upper in coords.getPixelLimits([ 269 (limits[0][0], limits[1][0]), 270 (limits[0][1], limits[1][1])], descriptor.skyWCS): 271 descriptor.changingAxis(axisInd, limitsChangedName) 272 slices.append((axisInd, lower, upper)) 273 274 if slices: 275 for axis, lower, upper in slices: 276 if lower==upper: # Sentinel for emtpy data 277 raise EmptyData() 278 descriptor.data[descriptor.imageExtind] = fitstools.cutoutFITS( 279 descriptor.data[descriptor.imageExtind], 280 *slices) 281 descriptor.dataIsPristine = False
282