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
14
15
16
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"
37
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):
61
62 @classmethod
68 setattr(cls, errCode, classmethod(meth))
69
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
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
97
100 """can be raised by data functions to abort all further processing
101 and return the current descriptor.data to the client.
102 """
103
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
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
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
154
155
156
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
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
187 continue
188
189 try:
190 ax = fitstools.WCSAxis.fromHeader(descriptor.hdr, fitsAxis)
191 except ValueError:
192
193
194
195 continue
196
197 descriptor.axisNames[ax.name] = fitsAxis
198 minPhys, maxPhys = ax.getLimits()
199
200
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
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
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
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
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
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:
277 raise EmptyData()
278 descriptor.data[descriptor.imageExtind] = fitstools.cutoutFITS(
279 descriptor.data[descriptor.imageExtind],
280 *slices)
281 descriptor.dataIsPristine = False
282