ROPO
Loading...
Searching...
No Matches
ropo_realtime.py
Go to the documentation of this file.
1#!/usr/bin/env python
2'''
3Copyright (C) 2011- Swedish Meteorological and Hydrological Institute (SMHI)
4
5This file is part of the bRopo extension to RAVE.
6
7RAVE is free software: you can redistribute it and/or modify
8it under the terms of the GNU Lesser General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12RAVE is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15See the GNU Lesser General Public License for more details.
16
17You should have received a copy of the GNU Lesser General Public License
18along with RAVE. If not, see <http://www.gnu.org/licenses/>.
19'''
20
22
23
26
27from Proj import rd
28from rave_defines import UTF8
29import _fmiimage
30import _polarscan
31import _polarvolume
32import _raveio
33import _ropogenerator
34import odim_source
35import os
36import time
37import copy
38import xml.etree.ElementTree as ET
39from rave_quality_plugin import QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY
40from rave_quality_plugin import QUALITY_CONTROL_MODE_ANALYZE
41
42
43CONFIG_FILE = os.path.join(os.path.join(os.path.split(os.path.split(_ropogenerator.__file__)[0])[0],
44 'config'), 'ropo_options.xml')
45
46DEFAULT_PADWIDTH = 3 # Default value for the number of rays to pad on either side of the 360-0 degree boundary. This is used in the PadNrays function,
47 # which addresses a design flaw in ropo's original C code that leads to data being removed in this sector. The padwidth value is based on the width of the emitter2 filter,
48 # unless explicitly specified by the padwidth value or if the emitter2 filter is not specified in which case the default value is used. The emitter2 filter is chosen as the basis for determining the padwidth
49 # because it is typically the widest filter applied by ropo, which is the most aggressive filter applied by ropo and thus determines how much padding is needed. The value can be adjusted as needed,
50 # but it should be large enough to ensure that all affected rays are included in the processing.
51# Guideline command-line arguments when creating this functionality
52# --parameters=DBZH --threshold=<see below> --restore-fill=True --restore-thresh=108
53# --softcut=5,170,180 --speckNormOld=-20,24,8 --emitter2=-10,3,2 --ship=20,8 --speck=-30,12
54
55
58THRESHOLDS = {"COLD" : (-6, -4, -2, 0, 2, 4, 6, 4, 2, 0, -2, -4),
59 "VERY_COLD" : (-12, -10, -6, -4, 0, 4, 6, 4, -4, -8, -10, -12),
60 "TEMPERATE" : (0, 2, 4, 6, 8, 10, 10, 8, 6, 4, 2, 0),
61 "FLAT-10" : (-10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10),
62 "FLAT-24" : (-24, -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, -24),
63 "FLAT-30" : (-30, -30, -30, -30, -30, -30, -30, -30, -30, -30, -30, -30),
64 "NONE" : None
65 }
66THRESHOLDS["DEFAULT"] = THRESHOLDS["FLAT-24"]
67
68initialized = 0
69
70ARGS = {} # Empty dictionary to be filled with site-specific options/arguments
71
72
73def init():
74 global initialized
75 if initialized: return
76
77 C = ET.parse(CONFIG_FILE)
78 OPTIONS = C.getroot()
79
80 for site in list(OPTIONS):
81 opts = options()
82
83 for k in site.attrib.keys():
84 if k == "parameters": opts.params = site.attrib[k]
85 elif k == "threshold": opts.threshold = site.attrib[k]
86 elif k == "highest-elev": opts.elev = float(site.attrib[k])
87 elif k == "restore": opts.restore = eval(site.attrib[k])
88 elif k == "restore-fill": opts.restore2 = eval(site.attrib[k])
89 elif k == "restore-thresh": opts.restore_thresh = eval(site.attrib[k])
90 elif k == "softcut": opts.softcut = eval(site.attrib[k])
91 elif k == "speckNormOld": opts.speckNormOld = eval(site.attrib[k])
92 elif k == "emitter2": opts.emitter2 = eval(site.attrib[k])
93 elif k == "ship": opts.ship = eval(site.attrib[k])
94 elif k == "speck": opts.speck = eval(site.attrib[k])
95 elif k == "padwidth": opts.padwidth = eval(site.attrib[k])
96
97 ARGS[site.tag] = opts
98 initialized = 1
99
100
101
106 def __init__(self):
107 self.params = None
108 self.threshold = None
109 self.elev = None
110 self.restore = None
111 self.restore2 = None
112 self.restore_thresh = None
113 self.softcut = None
114 self.speckNormOld = None
115 self.emitter2 = None
116 self.ship = None
117 self.speck = None
118 self.padwidth = None
119
120
121
124def get_options(inobj):
125 odim_source.CheckSource(inobj)
126 S = odim_source.ODIM_Source(inobj.source)
127 try:
128 return copy.deepcopy(ARGS[S.nod])
129 except KeyError:
130 return copy.deepcopy(ARGS["default"])
131
132
133
137def copy_topwhat(ino, outo):
138 outo.beamwidth = ino.beamwidth
139 outo.date = ino.date
140 outo.time = ino.time
141 outo.height = ino.height
142 outo.latitude = ino.latitude
143 outo.longitude = ino.longitude
144 outo.source = ino.source
145
146
147
152def process_scan(scan, options, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY):
153 newscan, gates = PadNrays(scan, options)
154
155 image = _fmiimage.fromRave(newscan, options.params)
156 param = newscan.getParameter(options.params)
157 rg = _ropogenerator.new(image)
158 if options.threshold:
159 raw_thresh = int((options.threshold - image.offset) / image.gain)
160 rg.threshold(raw_thresh)
161 if options.speck:
162 a, b = options.speck
163 rg.speck(a, b)
164 if scan.elangle * rd < options.elev:
165 if options.speckNormOld:
166 a, b, c = options.speckNormOld
167 rg.speckNormOld(a, b, c)
168 if options.softcut:
169 a, b, c = options.softcut
170 rg.softcut(a, b, c)
171 if options.ship:
172 a, b = options.ship
173 rg.ship(a, b)
174 if options.emitter2:
175 a, b, c = options.emitter2
176 rg.emitter2(a, b, c)
177
178 classification = rg.classify().classification.toRaveField()
179 if options.restore:
180 restored = rg.restore(int(options.restore_thresh)).toPolarScan()
181 elif options.restore2:
182 restored = rg.restore2(int(options.restore_thresh)).toPolarScan()
183
184 restored, classification = UnpadNrays(restored, classification, gates)
185 dbzh = scan.getParameter("DBZH")
186 if quality_control_mode != QUALITY_CONTROL_MODE_ANALYZE:
187 dbzh.setData(restored.getParameter("DBZH").getData())
188 scan.addParameter(dbzh)
189 scan.addOrReplaceQualityField(classification)
190
191 return scan
192
193
194
198def process_pvol(pvol, options, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY):
199 import _polarvolume
200
201 out = _polarvolume.new()
202 copy_topwhat(pvol, out)
203
204 # Get month and use it to determine dBZ threshold, recalling that
205 # options.threshold contains the name of the look-up. This is
206 # over-written with the looked-up threshold itself.
207 month = int(pvol.date[4:6]) - 1
208 options.threshold = THRESHOLDS[options.threshold][month]
209
210 for a in pvol.getAttributeNames(): # Copy also 'how' attributes at top level of volume, if any
211 out.addAttribute(a, pvol.getAttribute(a))
212
213 for s in range(pvol.getNumberOfScans()):
214 scan = pvol.getScan(s)
215 scan = process_scan(scan, options, quality_control_mode)
216 out.addScan(scan)
217
218 return out
219
220
221
225def generate(inobj, reprocess_quality_flag=True, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY):
226 if _polarscan.isPolarScan(inobj) == False and _polarvolume.isPolarVolume(inobj) == False:
227 raise IOError("Input file must be either polar scan or volume.")
228
229 if reprocess_quality_flag == False:
230 if _polarscan.isPolarScan(inobj) and inobj.findQualityFieldByHowTask("fi.fmi.ropo.detector.classification"):
231 return inobj
232 elif _polarvolume.isPolarVolume(inobj):
233 allprocessed = True
234 for i in range(inobj.getNumberOfScans()):
235 scan = inobj.getScan(i)
236 if not scan.findQualityFieldByHowTask("fi.fmi.ropo.detector.classification"):
237 allprocessed = False
238 break
239 if allprocessed:
240 return inobj
241
242 options = get_options(inobj) # Gets options/arguments for this radar. Fixes /what/source if required.
243 if _polarvolume.isPolarVolume(inobj):
244 ret = process_pvol(inobj, options, quality_control_mode)
245 elif _polarscan.isPolarScan(inobj):
246 month = int(inobj.date[4:6]) - 1
247 options.threshold = THRESHOLDS[options.threshold][month]
248 ret = process_scan(inobj, options, quality_control_mode)
249 copy_topwhat(inobj, ret)
250
251 return ret
252
253
254
261def PadNrays(scan, options):
262 from numpy import vstack
263
264 if options.padwidth is not None:
265 width = float(options.padwidth)
266 elif options.emitter2 is not None and len(options.emitter2) > 2:
267 width = float(options.emitter2[2])
268 else:
269 width = DEFAULT_PADWIDTH
270
271 gatew = 360.0 / scan.nrays
272 gates = (width / gatew) # / 2 # May as well pad with good margin
273 if (gates - 1) > 0.0:
274 gates = int(gates + 1)
275 else:
276 gates = int(gates)
277
278 newscan = _polarscan.new()
279
280 dbzh = scan.getParameter("DBZH").clone()
281 data = dbzh.getData()
282 toprays = data[0:gates, ]
283 botrays = data[scan.nrays - gates:, ]
284 data = vstack((botrays, data, toprays))
285 dbzh.setData(data)
286 dbzh.quantity = "DBZH"
287 newscan.addParameter(dbzh)
288 newscan.elangle = scan.elangle
289 return newscan, gates
290
291
296def UnpadNrays(scan, classification, gates):
297 dbzh = scan.getParameter("DBZH")
298 data = dbzh.getData()
299 data = data[gates:scan.nrays - gates, ]
300 dbzh.setData(data)
301
302 qdata = classification.getData()
303 qdata = qdata[gates:scan.nrays - gates, ]
304 classification.setData(qdata)
305
306 scan.removeParameter("DBZH")
307 scan.addParameter(dbzh)
308 return scan, classification
309
310
311
312init()
313
314
315if __name__ == "__main__":
316 pass
Class used to organize options and argument values to ropo.
process_scan(scan, options, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY)
TODO: activate parameters list.
UnpadNrays(scan, classification, gates)
Internal function to unwrap a scan from overlapping rays.
get_options(inobj)
Based on the /what/source attribute, find site-specific options/arguments.
init()
Initializes the ARGS dictionary by reading content from XML file.
process_pvol(pvol, options, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY)
Loops through a volume and processes scans using process_scan.
generate(inobj, reprocess_quality_flag=True, quality_control_mode=QUALITY_CONTROL_MODE_ANALYZE_AND_APPLY)
Generate - does the real work.
PadNrays(scan, options)
Internal function to wrap rays near 360-0 degrees.
copy_topwhat(ino, outo)
Copies the top-level 'what' attributes from one object to another.