server.py 9.8 KB
Newer Older
marius's avatar
marius committed
1
2
3
import tornado.ioloop
import grond
import os.path as op
Marius Isken's avatar
Marius Isken committed
4
import logging
5
import numpy as num
Marius Isken's avatar
wip    
Marius Isken committed
6
import socket
marius's avatar
marius committed
7
8
9

from collections import OrderedDict

Marius Isken's avatar
wip    
Marius Isken committed
10
from pyrocko.guts import Object, Bool, String, Int, List
11

Marius Isken's avatar
Marius Isken committed
12
from tornado.web import RequestHandler, StaticFileHandler
marius's avatar
marius committed
13
14
15
from tornado import gen

from bokeh.embed import autoload_server
Marius Isken's avatar
Marius Isken committed
16
17
18
from bokeh.application import Application
from bokeh.server.server import Server as BokehServer
from bokeh.application.handlers import Handler as BokehHandler
marius's avatar
marius committed
19

Marius Isken's avatar
Marius Isken committed
20
21
from bokeh.models import ColumnDataSource
from bokeh import layouts
marius's avatar
marius committed
22
23
from bokeh.plotting import figure

Marius Isken's avatar
wip    
Marius Isken committed
24
25
26
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('grond.baraddur')

marius's avatar
marius committed
27

Marius Isken's avatar
Marius Isken committed
28
29
30
31
32
33
34
35
36
def makeColorGradient(misfits, fr=1., fg=.5, fb=1.,
                      pr=0, pg=2.5, pb=4):
    misfits /= misfits.max()
    r = num.sin(fr * misfits + pr) * 127 + 128
    g = num.sin(fg * misfits + pg) * 127 + 128
    b = num.sin(fb * misfits + pb) * 127 + 128
    return ['#%02x%02x%02x' % (r[i], g[i], b[i]) for i in xrange(misfits.size)]


37
38
39
40
class BaraddurRequestHandler(RequestHandler):
    def initialize(self, config):
        self.config = config

Marius Isken's avatar
Marius Isken committed
41

42
43
44
45
46
47
48
49
50
class BaraddurBokehHandler(BokehHandler):
    def __init__(self, config, *args, **kwargs):
        BokehHandler.__init__(self, *args, **kwargs)
        self.config = config


class Status(BaraddurRequestHandler):

    class MisfitsPlot(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
51
52

        def modify_document(self, doc):
53
            self.nmodels = 0
Marius Isken's avatar
Marius Isken committed
54
55
            self.source = ColumnDataSource(
                data={'n': [],
56
                      'gm': []})
Marius Isken's avatar
Marius Isken committed
57
58
59
60
61
62
63
64
65
66
67
68
69
            self.update_misfits()

            plot = figure(webgl=True,
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')
            plot.scatter('n', 'gm',
                         source=self.source, alpha=.4)

            doc.add_root(plot)
            doc.add_periodic_callback(self.update_misfits, 1e3)

        @gen.coroutine
        def update_misfits(self):
70
            mx, misfits = grond.core.load_problem_data(
71
72
                self.config.rundir, self.config.problem,
                skip_models=self.nmodels)
73
74
            new_nmodels = mx.shape[0]

Marius Isken's avatar
Marius Isken committed
75
            fits = num.mean(misfits, axis=1)
76
77
78
79
80
            self.source.stream(dict(gm=fits[:, 0],
                                    n=num.arange(new_nmodels,
                                                 dtype=num.int) +
                                    self.nmodels + 1))
            self.nmodels += new_nmodels
Marius Isken's avatar
Marius Isken committed
81
82
83
84
85
86
87
88

    bokeh_handlers = {'misfit_plot': MisfitsPlot}

    @gen.coroutine
    def get(self):
        self.render('status.html',
                    pages=pages,
                    misfit_plot=autoload_server(None, url='/misfit_plot'),
89
                    problem=self.config.problem)
Marius Isken's avatar
Marius Isken committed
90
91


92
class Parameters(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
93

94
    class ParameterPlots(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
95
96
97
98

        ncols = 4

        def modify_document(self, doc):
99
            self.nmodels = 0
100
101
            problem = self.config.problem

102
103
104
            self.source = ColumnDataSource()
            for p in ['n'] + [p.name for p in problem.parameters]:
                self.source.add([], p)
Marius Isken's avatar
Marius Isken committed
105
106
107
108
109
110
111
112
            self.update_parameters()

            plots = []
            for par in problem.parameters:
                fig = figure(webgl=True,
                             x_axis_label='Iteration #',
                             y_axis_label='%s [%s]' % (par.label, par.unit))
                fig.scatter('n', par.name,
113
                            source=self.source, alpha=.4)
Marius Isken's avatar
Marius Isken committed
114
                plots.append(fig)
Marius Isken's avatar
wip    
Marius Isken committed
115
116
            plots += [None] * (self.ncols - (len(plots) % self.ncols))
            print plots
Marius Isken's avatar
Marius Isken committed
117
118
119
120
121
122
123

            grid = layouts.gridplot(
                plots,
                responsive=True,
                ncols=self.ncols)

            doc.add_root(grid)
124
            doc.add_periodic_callback(self.update_parameters, 2.5*1e3)
Marius Isken's avatar
Marius Isken committed
125
126
127

        @gen.coroutine
        def update_parameters(self):
128
129
130
131
132
133
134
135
            problem = self.config.problem

            try:
                mx, misfits = grond.core.load_problem_data(
                    self.config.rundir, problem, skip_models=self.nmodels)
            except IOError:
                return

136
            new_nmodels = mx.shape[0]
Marius Isken's avatar
Marius Isken committed
137

138
139
140
141
142
            new_data = {}
            for ip, par in enumerate(problem.parameters):
                new_data[par.name] = mx[:, ip]
            new_data['n'] = num.arange(new_nmodels, dtype=num.int) +\
                self.nmodels + 1
Marius Isken's avatar
Marius Isken committed
143

144
145
            self.source.stream(new_data)
            self.nmodels += new_nmodels
Marius Isken's avatar
Marius Isken committed
146
147

    bokeh_handlers = {'parameter_plot': ParameterPlots}
marius's avatar
marius committed
148
149
150
151

    @gen.coroutine
    def get(self):

Marius Isken's avatar
Marius Isken committed
152
153
154
155
156
        self.render('parameter_plots.html',
                    pages=pages,
                    parameter_plot=autoload_server(
                        None,
                        url='/parameter_plot'),
157
                    problem=self.config.problem)
Marius Isken's avatar
Marius Isken committed
158
159


160
class Summary(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
161
162
163

    @gen.coroutine
    def get(self):
marius's avatar
marius committed
164
165
        self.render('summary.html',
                    pages=pages,
166
                    problem=self.config.problem)
marius's avatar
marius committed
167
168


Marius Isken's avatar
wip    
Marius Isken committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
class Targets(BaraddurRequestHandler):

    class TargetContributionPlot(BaraddurBokehHandler):

        def modify_document(self, doc):
            self.nmodels = 0
            self.source = ColumnDataSource()
            self.update_contributions()

            plot = figure(webgl=True,
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')

            doc.add_root(plot)
            doc.add_periodic_callback(self.update_contributions, 1e3)

        @gen.coroutine
        def update_contributions(self):
            mx, misfits = grond.core.load_problem_data(
                self.config.rundir, self.config.problem,
                skip_models=self.nmodels)

            print misfits
            new_nmodels = mx.shape[0]

            # self.source.stream(dict(m=misfits[:, :, 0],
            #                         n=num.arange(new_nmodels,
            #                                      dtype=num.int) +
            #                         self.nmodels + 1))
            self.nmodels += new_nmodels

    bokeh_handlers = {'contribution_plot': TargetContributionPlot}
Marius Isken's avatar
Marius Isken committed
201

marius's avatar
marius committed
202
203
    @gen.coroutine
    def get(self):
Marius Isken's avatar
wip    
Marius Isken committed
204
205
206
207
208
209
        self.render('targets.html',
                    contribution_plot=autoload_server(
                        None,
                        url='/contribution_plot'),
                    pages=pages,
                    problem=self.config.problem)
marius's avatar
marius committed
210
211
212


pages = OrderedDict([
Marius Isken's avatar
wip    
Marius Isken committed
213
    ('Summary', Summary),
Marius Isken's avatar
Marius Isken committed
214
215
    ('Status', Status),
    ('Parameters', Parameters),
Marius Isken's avatar
wip    
Marius Isken committed
216
    ('Targets', Targets),
marius's avatar
marius committed
217
218
219
])


220
class BaraddurConfig(Object):
Marius Isken's avatar
wip    
Marius Isken committed
221
222
    rundir = String.T(
        help='Grond rundir.')
223
    template_path = String.T(
Marius Isken's avatar
wip    
Marius Isken committed
224
225
226
        default='templates',
        optional=True,
        help='Baraddur templates.')
227
228
229
    debug = Bool.T(
        default=True,
        optional=True)
Marius Isken's avatar
wip    
Marius Isken committed
230
231
232
233
234
    hosts = List.T(
        String.T(),
        default=['*'],
        optional=True,
        help='List of allowed hosts.')
marius's avatar
wip    
marius committed
235
236
    port = Int.T(
        default=8080,
Marius Isken's avatar
wip    
Marius Isken committed
237
238
        optional=True,
        help='Port to listen on.')
239
240
241
242
243
244
245
246
247

    @property
    def problem(self):
        return grond.core.load_problem_info(self.rundir)


class Baraddur(BokehServer):
    def __init__(self, rundir=None, *args, **kwargs):
        self.config = BaraddurConfig(rundir=rundir)
Marius Isken's avatar
wip    
Marius Isken committed
248
        port_offset = 0
249

Marius Isken's avatar
wip    
Marius Isken committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
        while True:
            try:
                BokehServer.__init__(
                    self,
                    self.get_bokeh_apps(),
                    io_loop=tornado.ioloop.IOLoop.current(),
                    extra_patterns=self.get_tornado_handlers(),
                    port=self.config.port + port_offset,
                    host=self.config.hosts)
                break
            except socket.error as se:
                if se.errno == 98 and port_offset < 50:  # Port in use
                    port_offset += 1
                    logger.info('Port %d in use, bailing to %d'
                                % (self.config.port + port_offset - 1,
                                   self.config.port + port_offset))
                else:
                    raise se
        logger.info('Created Baraddur server on http://localhost:%d'
                    % (self.port))
270
271
272
273
274
275
276
277
278
279
280
281
282

        tornado_app = self._tornado
        tornado_app.settings['template_path'] = self.config.template_path

        if self.config.debug:
            tornado_app.settings.setdefault('autoreload', True)
            tornado_app.settings.setdefault('compiled_template_cache', False)
            tornado_app.settings.setdefault('static_hash_cache', False)
            tornado_app.settings.setdefault('serve_traceback', True)
            # Automatically reload modified modules
            from tornado import autoreload
            autoreload.start()

Marius Isken's avatar
wip    
Marius Isken committed
283
            logging.getLogger('').setLevel(logging.DEBUG)
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

    def get_bokeh_apps(self):
        bokeh_apps = {}
        for tornado_handler in pages.itervalues():
            handler_docs = getattr(tornado_handler, 'bokeh_handlers',
                                   None)
            if handler_docs is None:
                continue

            for url, bokeh_handler in handler_docs.iteritems():
                bokeh_apps['/%s' % url] = Application(bokeh_handler(
                    self.config))
        return bokeh_apps

    def get_tornado_handlers(self):
Marius Isken's avatar
Marius Isken committed
299
300
        return [(r'/', pages.values()[0],
                 {'config': self.config})] +\
301
302
               [(r'/%s' % title, handler,
                 {'config': self.config})
Marius Isken's avatar
Marius Isken committed
303
304
305
306
                for title, handler in pages.iteritems()] +\
               [(r'/css/(.*)', StaticFileHandler,
                {'path': op.join(op.dirname(__file__), 'css')})]

307
308
309
310
311
312
313
314
315
    def start(self):
        BokehServer.start(self)
        tornado.ioloop.IOLoop.current().start()


if __name__ == '__main__':
    baraddur = Baraddur(
        rundir='/home/marius/Development/testing/grond/rundir')
    baraddur.start()