In [1]:
# -*- coding: utf-8 -*-
"""
Radar (Radolan) Viewer 0.1
First implementation of radar viewer based on openGL (vispy-package)
# Author: Kai Muehlbauer
"""
import glob
import datetime as dt
import warnings
from PyQt4.QtCore import Qt, QSize, QTimer
from PyQt4.QtGui import QApplication, QWidget, QSlider, QLabel, QBoxLayout, QGridLayout, QFileDialog, QPushButton, \
QPlainTextEdit, QFont, QHBoxLayout, QVBoxLayout, QToolButton, QStyle
import numpy as np
from vispy import app
from vispy import gloo
from vispy import visuals
from vispy.visuals.transforms import STTransform, TransformSystem
import wradlib as wrl
In [2]:
# function taken from wradlib.io module to load raw data
def read_RADOLAN_composite(fname, missing=-9999, loaddata=True):
"""Read quantitative radar composite format of the German Weather Service
The quantitative composite format of the DWD (German Weather Service) was
established in the course of the `RADOLAN project <http://www.dwd.de/RADOLAN>`
and includes several file types, e.g. RX, RO, RK, RZ, RP, RT, RC, RI, RG, PC,
PG and many, many more.
(see format description on the RADOLAN project homepage :cite:`DWD2009`).
At the moment, the national RADOLAN composite is a 900 x 900 grid with 1 km
resolution and in polar-stereographic projection. There are other grid resolutions
for different composites (eg. PC, PG)
**Beware**: This function already evaluates and applies the so-called PR factor which is
specified in the header section of the RADOLAN files. The raw values in an RY file
are in the unit 0.01 mm/5min, while read_RADOLAN_composite returns values
in mm/5min (i. e. factor 100 higher). The factor is also returned as part of
attrs dictionary under keyword "precision".
Parameters
----------
fname : path to the composite file
missing : value assigned to no-data cells
Returns
-------
output : tuple of two items (data, attrs)
- data : numpy array of shape (number of rows, number of columns)
- attrs : dictionary of metadata information from the file header
"""
NODATA = missing
mask = 0xFFF # max value integer
f = wrl.io.get_radolan_filehandle(fname)
header = wrl.io.read_radolan_header(f)
attrs = wrl.io.parse_DWD_quant_composite_header(header)
if not loaddata:
f.close()
return None, attrs
attrs["nodataflag"] = NODATA
if not attrs["radarid"] == "10000":
warnings.warn("WARNING: You are using function e" +
"wradlib.io.read_RADOLAN_composit for a non " +
"composite file.\n " +
"This might work...but please check the validity " +
"of the results")
# read the actual data
indat = wrl.io.read_radolan_binary_array(f, attrs['datasize'])
if attrs["producttype"] in ["RX", "EX"]:
#convert to 8bit integer
arr = np.frombuffer(indat, np.uint8).astype(np.uint8)
arr = np.where(arr == 250, 255, arr)
attrs['cluttermask'] = np.where(arr == 249)[0]
elif attrs['producttype'] in ["PG", "PC"]:
arr = wrl.io.decode_radolan_runlength_array(indat, attrs)
else:
# convert to 16-bit integers
arr = np.frombuffer(indat, np.uint16).astype(np.uint16)
# evaluate bits 13, 14, 15 and 16
attrs['secondary'] = np.where(arr & 0x1000)[0]
nodata = np.where(arr & 0x2000)[0]
negative = np.where(arr & 0x4000)[0]
attrs['cluttermask'] = np.where(arr & 0x8000)[0]
# mask out the last 4 bits
arr = arr & mask
# consider negative flag if product is RD (differences from adjustment)
if attrs["producttype"] == "RD":
# NOT TESTED, YET
arr[negative] = -arr[negative]
# apply precision factor
# this promotes arr to float if precision is float
#arr = arr * attrs["precision"]
# set nodata value
#arr[nodata] = NODATA
arr[attrs['secondary']] = 4096
arr[nodata] = 4096
# anyway, bring it into right shape
arr = arr.reshape((attrs["nrow"], attrs["ncol"]))
return arr, attrs
In [3]:
# Colorbar Canvas
class CBarCanvas(app.Canvas):
def __init__(self):
app.Canvas.__init__(self, keys='interactive', size=(25, 900))
# create colorbar data
self.data = np.flipud(np.repeat(np.arange(0, 900, 1, dtype=np.int16)[:, np.newaxis], 25, 1))
# create image visual
self.image = visuals.ImageVisual(self.data, method='auto', cmap='cubehelix', clim=(0, 900))
# Create a TransformSystem that will tell the visual how to draw
self.tr_sys = TransformSystem(self)
def on_draw(self, ev):
gloo.clear(color='black', depth=True)
gloo.set_viewport(0, 0, *self.physical_size)
self.image.draw(self.tr_sys)
In [4]:
# Radar View Canvas
class Canvas(app.Canvas):
def __init__(self):
app.Canvas.__init__(self, keys='interactive', size=(900, 900))
# dummy data (Radolan 900x900)
self.data = np.zeros((900, 900))
# create image visual
self.image = visuals.ImageVisual(self.data, method='auto', cmap='cubehelix', clim='auto')
# Create a TransformSystem that will tell the visual how to draw
self.tr_sys = TransformSystem(self)
self.flist = None
self.actualFrame = 0
self.frames = 0
def on_draw(self, ev):
gloo.clear(color='black', depth=True)
gloo.set_viewport(0, 0, *self.physical_size)
if self.flist:
self.image.set_data(np.flipud(self.data))
self.image.draw(self.tr_sys)
In [5]:
# create Widget which holds some printed information
class TextWidget(QWidget):
def __init__(self):
QWidget.__init__(self, None)
self.productTypeLabel = QLabel("Product Type", self)
self.dateTimeLabel = QLabel("dateTime", self)
self.radolanVersionLabel = QLabel("Radolan Version", self)
self.productType = QLabel("Product Type", self)
self.dateTime = QLabel("Date", self)
self.radolanVersion = QLabel("Radolan Version", self)
self.hbox1 = QHBoxLayout()
self.hbox1.addWidget(self.productTypeLabel)
self.hbox1.addWidget(self.dateTimeLabel)
self.hbox1.addWidget(self.radolanVersionLabel)
self.hbox2 = QHBoxLayout()
self.hbox2.addWidget(self.productType)
self.hbox2.addWidget(self.dateTime)
self.hbox2.addWidget(self.radolanVersion)
self.attrBox = QVBoxLayout()
self.attrBox.addLayout(self.hbox1)
self.attrBox.addLayout(self.hbox2)
self.setLayout(self.attrBox)
self.show()
In [8]:
# main Window Widget
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
# initiate timer
self.timer = QTimer()
self.timer.timeout.connect(self.reload)
# layout id gridbox-like
self.box = QGridLayout()
self.resize(800, 800)
self.setLayout(self.box)
# Create two labels and a button
self.vertLabel = QLabel("Radolan Data Window", self)
self.timeLabel = QLabel("Time", self)
self.sliderLabel = QLabel("00:00", self)
# File Dialog
self.dlg = QFileDialog()
self.dlg.setFileMode(QFileDialog.Directory)
self.dlg.setOption(QFileDialog.ShowDirsOnly, True)
# Canvas
self.canvas = None
self.cbar = CBarCanvas()
# Sliders
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(100)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
self.slider.valueChanged.connect(self.slider_moved)
self.cHighSlider = QSlider(Qt.Horizontal)
self.cHighSlider.setMinimum(0)
self.cHighSlider.setMaximum(4096)
self.cHighSlider.setTickInterval(1)
self.cHighSlider.setSingleStep(1)
self.cHighSlider.setValue(4096)
self.cHighSlider.valueChanged.connect(self.cHighSlider_moved)
self.cHighLabel = QLabel("Upper Limit:")
self.cHighValue = QLabel("4096")
# Load Button
self.loadButton = QPushButton("Open Directory")
self.loadButton.clicked.connect(self.button_clicked)
# Text Output Widget
self.attrWidget = TextWidget()
self.attrWidget.setVisible(False)
self.createButtons()
# grid parameters
self.c1 = 0
self.c2 = 10
# add Widgets to Layout
self.box.addWidget(self.loadButton, 0, self.c1, 1, 11)
self.box.addWidget(self.dlg, 1, self.c1, 3, 11)
self.box.addWidget(self.attrWidget, 1, self.c1, 3, -1)
self.box.addWidget(self.vertLabel, 6, self.c1, 1, 10)
self.box.addWidget(self.cbar.native, 5, self.c2, 10, 1)
self.box.addWidget(self.timeLabel, 4, self.c1, 1, 10)
self.box.addWidget(self.playPauseButton, 4, self.c1 + 1, 1, 1)
self.box.addWidget(self.cHighLabel, 4, self.c1 + 4, 1, 1)
self.box.addWidget(self.cHighValue, 4, self.c1 + 5, 1, 1)
self.box.addWidget(self.cHighSlider, 4, self.c1 + 6, 1, 4)
self.box.addWidget(self.sliderLabel, 5, self.c1, 1, 1)
self.box.addWidget(self.slider, 5, self.c1 + 1, 1, 9)
self.show()
# connect Filedialog
self.dlg.fileSelected.connect(self.folder_selected)
def folder_selected(self, folder):
if not self.canvas:
self.canvas = Canvas()
self.box.addWidget(self.canvas.native, 7, self.c1, -1, 10)
inname = "/raa*"
self.canvas.flist = sorted(glob.glob(str(folder) + inname))
self.canvas.frames = len(self.canvas.flist)
self.slider.setMaximum(self.canvas.frames)
self.attrWidget.setVisible(True)
self.slider.setValue(1)
self.slider_moved(1)
def button_clicked(self):
self.attrWidget.setVisible(False)
self.dlg.setVisible(True)
# loop continuously through data
def reload(self):
if self.slider.value() == self.slider.maximum():
self.slider.setValue(1)
else:
self.slider.setValue(self.slider.value() + 1)
# changing upper limit
def cHighSlider_moved(self, position):
clow, chigh = self.canvas.image.clim
self.canvas.image.clim = (clow, position)
self.cHighValue.setText(str(position))
# slide through data
def slider_moved(self, position):
self.canvas.actualFrame = position - 1
self.canvas.data, self.canvas.attrs = read_RADOLAN_composite(self.canvas.flist[self.canvas.actualFrame],
missing=0)
# adapt color limits
if self.canvas.data.dtype == 'uint8':
self.cHighSlider.setMaximum(255)
else:
self.cHighSlider.setMaximum(4096)
# change and update
self.canvas.update()
self.sliderLabel.setText(self.canvas.attrs['datetime'].strftime("%H:%M"))
self.attrWidget.radolanVersion.setText(self.canvas.attrs['radolanversion'])
self.attrWidget.dateTime.setText(self.canvas.attrs['datetime'].strftime("%Y-%m-%d"))
self.attrWidget.productType.setText(self.canvas.attrs['producttype'].upper())
# start/stop capability
def playpause(self):
if self.playPauseButton.toolTip() == 'Play':
self.playPauseButton.setToolTip("Pause")
self.timer.start()
self.playPauseButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playPauseButton.setToolTip("Play")
self.timer.stop()
self.playPauseButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
# create play/pause Button
def createButtons(self):
iconSize = QSize(18, 18)
self.playPauseButton = QToolButton()
self.playPauseButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playPauseButton.setIconSize(iconSize)
self.playPauseButton.setToolTip("Play")
self.playPauseButton.clicked.connect(self.playpause)
if __name__ == '__main__':
app.create()
m = MainWindow()
app.run()
In [ ]:
In [ ]: