holoviz-topics / EarthSim

Tools for working with and visualizing environmental simulations.
https://earthsim.holoviz.org
BSD 3-Clause "New" or "Revised" License
65 stars 21 forks source link

Panel Pipeline inheritance #268

Closed kcpevey closed 5 years ago

kcpevey commented 5 years ago

@philippjfr I'm adding instantiated widgets to the second page/class similar to what we did with the first:

class viewResults(param.Parameterized):
    # objects inherited from the previous page
    load_sim_widget = param.ClassSelector(class_=model.load_adh_simulation)
    att_widget = param.ClassSelector(class_=model.attributes)
    proj_widget = param.ClassSelector(class_=projections.projections)

    # objects passed in from the previous page output
    adh_viz = param.ClassSelector(class_=model.adhViz)
    adh_model = param.ClassSelector(class_=adhModel)

    # objects carried in
    wmts_widget = param.ClassSelector(class_=display_opts.wmts)

    def __init__(self, wmts_widget, **params):
        super(viewResults, self).__init__(wmts_widget=wmts_widget, **params)

    # what to pass out of this page
    @param.output()
    def output(self):
        pass

    # how to build this page
    def panel(self):
        return pn.Pane(self.adh_viz.view_animation('dep'))

But this causes the error:

/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py <lambda> L321
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py _depends L234
/Users/rdchlkcp/ers/gitHub/panel/master/panel/panel/pipeline.py _next L127
/Users/rdchlkcp/ers/gitHub/panel/master/panel/panel/pipeline.py _next L121
/Users/rdchlkcp/ers/gitHub/panel/master/panel/panel/pipeline.py _init_stage L95
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py inner L939
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py set_param L2224
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py set_param L1078
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py __set__ L742
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py __set__ L658
    TypeError: Constant parameter 'name' cannot be modified

However, I can't figure out where this error is coming from since nothing I can touch has any effect. My instantiation: wmts_widget = display_opts.wmts() isn't the source of the error (and would happily accept name='').

I can instantiate and visualize pn.Pane(wmts_widget). And I get the same error if I try to make a second instance of one of the widgets from the first page (thereby proving it has nothing to do with the wmts_widget class.

Also, I'm creating the stages using the same approach from the first page:

stages = [
    ('Input', inputData(load_sim_widget, att_widget, proj_widget)),
    ('view', viewResults(wmts_widget)),
    ('Stage 1', Stage1),
    ('Stage 2', Stage2)
]

If I just remove all the wmts_widget references, this code works without errors.

kcpevey commented 5 years ago

This problem is bigger than I had originally thought. It has nothing to do with wmts_widget. I pulled the non-pipeline code out of viewResults without changing anything significant and when its pulled out, it will run and produce the panel it's supposed to.

This is the full viewResults:

class viewResults(param.Parameterized):
    # objects inherited from the previous page
    load_sim_widget = param.ClassSelector(class_=model.load_adh_simulation)
    att_widget = param.ClassSelector(class_=model.attributes)
    proj_widget = param.ClassSelector(class_=projections.projections)

    # objects passed in from the previous page output
    adh_viz = param.ClassSelector(class_=model.adhViz)
    adh_model = param.ClassSelector(class_=adhModel)

    # objects carried in
    wmts_widget = param.ClassSelector(class_=display_opts.wmts)

    def __init__(self, wmts_widget, **params):
        super(viewResults, self).__init__(wmts_widget=wmts_widget, **params)

    # what to pass out of this page
    @param.output()
    def output(self):
        pass

    # how to build this page
    def panel(self):

        # return pn.Pane(self.adh_viz.view_animation('dep'))
#         return pn.Pane(self.run())
#         return pn.Pane(wmts_widget)
        return self.run()

    # what to do with the stuff in this page
    def run(self):
#         pass
        # get the coorinate system 
        coord_sys = self.proj_widget.get_crs()

        if self.adh_viz is None:
            # instantiate adhviz object
            adh_viz = model.adhViz(projection=coord_sys)

            # instantiate adhModel object
            adh_model = adhModel(d_version=5.0)

            # load the data from the sim widget
            self.adh_viz, self.adh_model = self.load_sim_widget.run(viz_obj=adh_viz, mod=adh_model, 
                                                                    att_widget=self.att_widget, proj=coord_sys)

        # hard coded !!!!!!  
        fpath = os.path.join(self.load_sim_widget.adh_directory, self.load_sim_widget.adh_root_filename + '_dep.dat')
        dfs = read_mesh2d(fpath)
        internal_names = list(dfs[0])
        print('internal names {}'.format(internal_names))
        label='Depth'

        # Create a parameterized class to select between ERROR and Depth
        results = parameters(display_result=param.ObjectSelector(default=internal_names[0], objects=internal_names))

        # Define callback which returns TriMesh for specific time and ERROR/Depth
        def time_mesh(time, display_result):
            color_ramp_threshold = display_range.color_range[0]
            df = dfs[time].copy()
            df[df[display_result] < color_ramp_threshold] = np.nan
            depth_points = self.adh_viz.mesh_points.add_dimension(display_result, 0, df.values, vdim=True)
            return gv.TriMesh((self.adh_viz.tris, depth_points), label=display_result, crs=ccrs.GOOGLE_MERCATOR)

        # Declare DynamicMap which varies by time and is linked to results class
        meshes = hv.DynamicMap(time_mesh, kdims='Time', streams=[Params(results)]).redim.values(Time=sorted(dfs.keys()))

        # Define function which applies colormap and color_range
        def apply_opts(obj, colormap, color_range):
            return obj.options(cmap=colormap).redim.range(**{obj.vdims[0].name: color_range})

        viz = PolyAndPointAnnotator(path_type=gv.Path, 
                    crs=ccrs.GOOGLE_MERCATOR,
                    point_columns=['depth_elevation'],
                    poly_columns=['name']
                    )

        # Apply the colormap and color range dynamically
        dynamic = hv.util.Dynamic(rasterize(meshes), operation=apply_opts, streams=[Params(cmap_opts), Params(display_range)]
                                 ).options(colorbar=True) * self.wmts_widget.view() * viz.polys * viz.points

        # Display everything as a Panel
        hv_panel = pn.panel(dynamic, widget_type='scrubber')
        map_pane = pn.Column(hv_panel[0], hv_panel[1])

        disp_tab = pn.Column(self.wmts_widget, 
                      mesh_opts(name=''), 
                      cmap_opts,
                      display_range,
                      results,
                      name='Display')

        output_tab = pn.Pane(self.proj_widget, name='Output')

        tool_pane = pn.Tabs(("unused", disp_tab),
                         ("unused", output_tab))

        return pn.Row(map_pane, tool_pane)

When I try to run it inside of the pipeline, I'm getting the error above in the java console, plus I was able to get a full traceback:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/miniconda3/envs/earthsim/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

~/ers/gitHub/panel/master/panel/panel/viewable.py in _repr_mimebundle_(self, include, exclude)
    107         doc = Document()
    108         comm = self._comm_manager.get_server_comm()
--> 109         model = self._get_root(doc, comm)
    110         Viewable._views[model.ref['id']] = (self, model)
    111         return render_mimebundle(model, doc, comm)

~/ers/gitHub/panel/master/panel/panel/viewable.py in _get_root(self, doc, comm)
     84           Optional pyviz_comms when working in notebook
     85         """
---> 86         root = self._get_model(doc, comm=comm)
     87         self._preprocess(root)
     88         return root

~/ers/gitHub/panel/master/panel/panel/layout.py in _get_model(self, doc, root, parent, comm)
    130         model = self._bokeh_model()
    131         root = model if root is None else root
--> 132         objects = self._get_objects(model, [], doc, root, comm)
    133 
    134         # HACK ALERT: Insert Spacer if last item in Column has no height

~/ers/gitHub/panel/master/panel/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    123                 child = pane._models[root.ref['id']]
    124             else:
--> 125                 child = pane._get_model(doc, root, model, comm)
    126             new_models.append(child)
    127         return new_models

~/ers/gitHub/panel/master/panel/panel/layout.py in _get_model(self, doc, root, parent, comm)
    130         model = self._bokeh_model()
    131         root = model if root is None else root
--> 132         objects = self._get_objects(model, [], doc, root, comm)
    133 
    134         # HACK ALERT: Insert Spacer if last item in Column has no height

~/ers/gitHub/panel/master/panel/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    123                 child = pane._models[root.ref['id']]
    124             else:
--> 125                 child = pane._get_model(doc, root, model, comm)
    126             new_models.append(child)
    127         return new_models

~/ers/gitHub/panel/master/panel/panel/holoviews.py in _get_model(self, doc, root, parent, comm)
    112         """
    113         ref = root.ref['id']
--> 114         plot = self._render(doc, comm, root)
    115         child_pane = Pane(plot.state, _temporary=True)
    116         model = child_pane._get_model(doc, root, parent, comm)

~/ers/gitHub/panel/master/panel/panel/holoviews.py in _render(self, doc, comm, root)
     98         if comm:
     99             kwargs['comm'] = comm
--> 100         plot = renderer.get_plot(self.object, **kwargs)
    101         ref = root.ref['id']
    102         if ref in self._plots:

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/bokeh/renderer.py in get_plot(self_or_cls, obj, doc, renderer, **kwargs)
    160             curdoc().theme = self_or_cls.theme
    161         doc.theme = self_or_cls.theme
--> 162         plot = super(BokehRenderer, self_or_cls).get_plot(obj, renderer, **kwargs)
    163         plot.document = doc
    164         return plot

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/renderer.py in get_plot(self_or_cls, obj, renderer, **kwargs)
    201             init_key = tuple(v if d is None else d for v, d in
    202                              zip(plot.keys[0], defaults))
--> 203             plot.update(init_key)
    204         else:
    205             plot = obj

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/plot.py in update(self, key)
    532     def update(self, key):
    533         if len(self) == 1 and ((key == 0) or (key == self.keys[0])) and not self.drawn:
--> 534             return self.initialize_plot()
    535         item = self.__getitem__(key)
    536         self.traverse(lambda x: setattr(x, '_updated', True))

~/ers/gitHub/geoviews/master/geoviews/geoviews/plotting/bokeh/plot.py in initialize_plot(self, ranges, plot, plots, source)
     86     def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
     87         opts = {} if isinstance(self, HvOverlayPlot) else {'source': source}
---> 88         fig = super(GeoPlot, self).initialize_plot(ranges, plot, plots, **opts)
     89         if self.geographic and self.show_bounds and not self.overlaid:
     90             from . import GeoShapePlot

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/bokeh/element.py in initialize_plot(self, ranges, plot, plots)
   1597 
   1598         if self.top_level:
-> 1599             self.init_links()
   1600 
   1601         self._execute_hooks(element)

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/bokeh/plot.py in init_links(self)
    402         for link, src_plot, tgt_plot in links:
    403             cb = Link._callbacks['bokeh'][type(link)]
--> 404             callbacks.append(cb(self.root, link, src_plot, tgt_plot))
    405         return callbacks
    406 

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/bokeh/callbacks.py in __init__(self, root_model, link, source_plot, target_plot)
   1226     def __init__(self, root_model, link, source_plot, target_plot):
   1227         src_cds = source_plot.handles['source']
-> 1228         tgt_cds = target_plot.handles['source']
   1229         src_len = [len(v) for v in src_cds.data.values()]
   1230         tgt_len = [len(v) for v in tgt_cds.data.values()]

AttributeError: 'NoneType' object has no attribute 'handles'

And I've traced the source of that error back to the visualization of dynamic (not the instantiation of it).

I'm fairly certain its a bug in panel or pipeline, but this is as far as I can go. Hope this helps!

kcpevey commented 5 years ago

More info: The panel from self.run() generates successfully (in memory) without issue. The error above is coming from whatever happens to the panel AFTER it gets returned from the method def panel(self): return self.run()

philippjfr commented 5 years ago

Looks like an issue in setting up the links, will investigate.

kcpevey commented 5 years ago

@philippjfr Did you find anything?

philippjfr commented 5 years ago

I haven't been able to reproduce anything yet no. Is there a simpler example where you see the issue?

kcpevey commented 5 years ago

I don't have one, but I guess I could adapt #254 as a simpler example?

kcpevey commented 5 years ago

I'm also happy to demo my full code during another call if that helps

philippjfr commented 5 years ago

Let's both try to see if we can reproduce it in a simpler example and fall back to that if this doesn't work.

kcpevey commented 5 years ago

Got it.

philippjfr commented 5 years ago

The easiest way to make progress is probably for you to go to line 1226 in this file:

~/ers/gitHub/holoviews/master/holoviews/holoviews/plotting/bokeh/callbacks.py

and add print(link) so we can see which link is actually breaking.

philippjfr commented 5 years ago

Nevermind, I see it's the DataLink, so I'll investigate that.

philippjfr commented 5 years ago

Having no luck reproducing the issue unfortunately.

kcpevey commented 5 years ago

Ok. I'll try and come up with a simplified example.

kcpevey commented 5 years ago

And also add print(link) like you suggested

philippjfr commented 5 years ago

Thank you, if you can't reproduce it should we schedule a 1:1 meeting tomorrow?

kcpevey commented 5 years ago

That would be helpful. I'll be available after our usual meeting.

kcpevey commented 5 years ago

I stripped my code down and moved a few things into the notebook to make a self-contained example. This is giving the Constant parameter 'name' cannot be modified error, but I have to head out now and I haven't had time to finish digging though it. If its helpful for you, great. If not, I'll keep working on this in the morning.

pipeline_simple_example.ipynb.zip

kcpevey commented 5 years ago

This has been resolved. I believe it had to do with passing the instantiated widgets into the class instantiation and/or properly setting up the init to handle the incoming classes.