chipsalliance / silicon-notebooks

Apache License 2.0
156 stars 28 forks source link

gdstk not found in colab environment #30

Closed proppy closed 1 year ago

proppy commented 1 year ago

all notebooks currently fails with the following error:

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
[<ipython-input-5-f3322b103f89>](https://localhost:8080/#) in <module>
      1 import pathlib
----> 2 import gdstk
      3 import IPython.display
      4 
      5 gdss = sorted(pathlib.Path('runs').glob('*/results/final/gds/*.gds'))

ModuleNotFoundError: No module named 'gdstk'

This is likely due to a recently update from colab to python 3.8. https://medium.com/google-colab/colab-updated-to-python-3-8-4922f9970a72

paul-k-young commented 1 year ago

+1 - I'm having this issue as well. Will look to see if I can fix it myself and if so I'll post the results back here (and maybe submit a PR)

paul-k-young commented 1 year ago

tl;dr - I wasn't able to get a quick fix. Full steps to repro as well as the error message are below.

I also included a list of installed conda packages. It looks like gdstk is getting installed (version 0.7.0) but for some reason python isn't recognizing it.

Based on a suggestion by @proppy I tried updating these lines in the first code block to 3.8. I got a dependency error after doing that.

site_package_path = conda_prefix_path / 'lib/python3.8/site-packages' !echo 'python ==3.8*' >> {CONDA_PREFIX}/conda-meta/pinned

Minimum steps to reproduce the initial bug:

  1. Load the "Digital inverter with OpenLane" colab in a browser: https://colab.research.google.com/github/chipsalliance/silicon-notebooks/blob/main/digital-inverter-openlane.ipynb#scrollTo=NC__X6Jph4CU
  2. Select Runtime > Run All
  3. Click "Run Anyway" in the warning dialog (warning that the colab is not authored by Google)
  4. The "Display layout" code block will have the following error in the output:

ModuleNotFoundError Traceback (most recent call last) in 1 import pathlib ----> 2 import gdstk 3 import IPython.display 4 5 gdss = sorted(pathlib.Path('runs').glob('/results/final/gds/.gds'))

ModuleNotFoundError: No module named 'gdstk'


NOTE: If your import is failing due to a missing package, you can manually install dependencies using either !pip or !apt.

To view examples of installing some common dependencies, click the "Open Examples" button below.

Here is the output of '!CI=0 bin/micromamba list' when run from the notebook.

List of packages in environment: "/content/conda-env"

Name Version Build Channel
──────────────────────────────────────────────────────────────────────────────── _libgcc_mutex 0.1 main main
_openmp_mutex 5.1 1_gnu main
bzip2 1.0.8 h7b6447c_0 main
ca-certificates 2022.10.11 h06a4308_0 main
cairo 1.16.0 h19f5f5c_2 main
certifi 2022.9.24 py37h06a4308_0 main
click 8.0.4 py37h06a4308_0 main
flit-core 3.6.0 pyhd3eb1b0_0 main
fontconfig 2.14.1 hef1e5e3_0 main
freetype 2.12.1 h4a9f257_0 main
gdstk 0.7.0 py37h60116be_0 conda-forge glib 2.69.1 h4ff587b_1 main
icu 58.2 he6710b0_3 main
importlib-metadata 4.11.3 py37h06a4308_0 main
ld_impl_linux-64 2.38 h1181459_1 main
libblas 3.9.0 15_linux64_openblas conda-forge libboost 1.73.0 h28710b8_12 main
libcblas 3.9.0 15_linux64_openblas conda-forge libffi 3.3 he6710b0_2 main
libgcc-ng 11.2.0 h1234567_1 main
libgfortran-ng 12.2.0 h69a702a_19 conda-forge libgfortran5 12.2.0 h337968e_19 conda-forge libgomp 11.2.0 h1234567_1 main
liblapack 3.9.0 15_linux64_openblas conda-forge libopenblas 0.3.20 pthreads_h78a6416_0 conda-forge libpng 1.6.37 hbc83047_0 main
libstdcxx-ng 11.2.0 h1234567_1 main
libxcb 1.15 h7f8727e_0 main
libxml2 2.9.14 h74e7548_0 main
lz4-c 1.9.3 h295c915_1 main
magic 8.3.349_0_g02bbb10 20221206_125647 litex-hub
ncurses 6.3 h5eee18b_3 main
netgen 1.5.242_0_g4edaf08 20221206_125647 litex-hub
numpy 1.21.6 py37h976b520_0 conda-forge open_pdks.sky130a 1.0.369_2_gffb8b61 20221206_125647 litex-hub
openlane 2022.11.12_3_g1298859 20221104_084554 litex-hub
openroad 2.0_5614_g71fa4b775 20221104_084554 litex-hub
openssl 1.1.1s h7f8727e_0 main
pcre 8.45 h295c915_0 main
pip 22.2.2 py37h06a4308_0 main
pixman 0.40.0 h7f8727e_1 main
python 3.7.15 haa1d7c7_0 main
python_abi 3.7 2_cp37m conda-forge pyyaml 6.0 py37h7f8727e_1 main
readline 8.2 h5eee18b_0 main
setuptools 65.5.0 py37h06a4308_0 main
sqlite 3.40.0 h5082296_0 main
tcllib 1.20 0 litex-hub
tk 8.6.12 h1ccaba5_0 main
typing_extensions 4.4.0 py37h06a4308_0 main
wheel 0.37.1 pyhd3eb1b0_0 main
xz 5.2.8 h5eee18b_0 main
yaml 0.2.5 h7b6447c_0 main
yosys 0.23_6_g853f4bb3c 20221104_084554_py37 litex-hub
zipp 3.8.0 py37h06a4308_0 main
zlib 1.2.13 h5eee18b_0 main
zstd 1.5.2 ha4553b6_0 main

proppy commented 1 year ago

Installing from pypi https://pypi.org/project/gdstk/ triggers the following issue:

RuntimeError: module compiled against API version 0x10 but this version of numpy is 0xe
proppy commented 1 year ago

Rebuilding the wheel against the version of numpy pre-installed in colab like this:

!python -m pip install --no-binary gdstk gdstk

Seems to workaround the issue: image

proppy commented 1 year ago

Alternatively we could rely on the klayout 0.28 wheel to display the gds (see https://github.com/KLayout/klayout/issues/1082#issuecomment-1345709883), that would have the advantage to scale to bigger design (gdstk generated svg will start crashing chrome once it crosses a few hundreds megabyte).

tvt173 commented 1 year ago

would be great to make it interactive as well, as was done here: https://github.com/klayoutmatthias/canvas2canvas

since jupyter notebooks inherently already have a python server running, i don't think it should be too hard to adapt to run within a notebook

proppy commented 1 year ago

@tvt173 I think there might be ways to leverage https://holoviews.org/ to call the appropriate method to refresh the klayout LayoutView on holoview navigation event (zoom, pan, click) without the need of running a server?

tvt173 commented 1 year ago

interesting idea... at that point, you might as well just be using bokeh directly though, right? https://docs.bokeh.org/en/latest/docs/gallery/image.html

to my understanding, holoviews is just a layer on top of bokeh used for efficient rendering of large datasets. but in this case, we would be using klayout to render the data into images anyways (usually the job of holoviews).

have you seen my schematic capture notebook demo in gdsfactory yet? https://github.com/gdsfactory/gdsfactory/blob/main/docs/notebooks/20_schematic_driven_layout.ipynb

it's still a bit rough around the edges, but in principle, you could do what i'm doing there to create a layout viewer in bokeh as well, if you're interested in going in that direction. if you make the datasources based on cells, you can leverage the same advantages with hierarchy... only have one datasource per cell, rather than a flat one for the entire layout. not sure what the rendering time would be like, but in that case, it could be completely browser based without need for python callbacks

this may actually be an interesting avenue to explore also...

tvt173 commented 1 year ago

though I think the above will start to get dicey when you have multiple levels of hierarchy, if you try to maintain the polygons as polygon data. it may work well to have each cell under the top level as its own image though (and display each instance as an image, with its own hit-testing capabilities etc.)

proppy commented 1 year ago

@tvt173 There seems to be some colab support for being in the work for holoview: https://github.com/bokeh/bokeh/issues/9302 did you already give it a try?

tvt173 commented 1 year ago

hi @proppy , sorry what's the issue? you can't get the notebook to work in colab?

proppy commented 1 year ago

@tvt173 yep, I have trouble running most of the push_notebook based bokeh demos in colab, so I'd suspect that the schematic capture notebook wouldn't work either.

tvt173 commented 1 year ago

this doesn't actually use push_notebook since it is a live server application and not a static bokeh visualization. give it a try maybe?

proppy commented 1 year ago

@tvt173 got an error because of the version of numpy linked against gdsfactory is different from the colab environment:

RuntimeError: module compiled against API version 0x10 but this version of numpy is 0xe

My understanding is that service running within the colab environment aren't directly accessible from the JS context (and that colab comms mecanism have to used, hence: https://github.com/bokeh/bokeh/issues/9302).

I managed to get something interactive working with ipywidgets see: https://colab.research.google.com/gist/proppy/b71d4678bd47bf6f6e94d86bf32d83f2/klayout-widgets-playground.ipynb which lead me to believe something similar using bokeh is possible.

tvt173 commented 1 year ago

by the way @proppy , you had initially recommended holoviews... are you able to get that to run in colab? if so, are you using the bokeh backend? if so, that's a bit peculiar, right?

mithro commented 1 year ago

We tried !python -m pip install --force-reinstall --no-binary gdstk gdstk but it doesn't seem to be working?

proppy commented 1 year ago

@mithro can you save your "not working" notebook as a gist (File > Save a copy as a GitHub Gist) and link it here?

proppy commented 1 year ago

are you able to get that to run in colab?

It does run in colab but as you said, since it depends on bokeh for its backend, we would likely run into the same issue with regard to colab comms.

tvt173 commented 1 year ago

yeah, I was able to take the widget concept a bit further, and have it listen to mouse and wheel events, but it's still not working very well. it listens to mouse and wheel events, and I can respond with layout_view.zoom_in() and layout_view.zoom_out() just fine, but when I try layout_view.send_wheel_event(delta, False, db.Point(x, y), buttons) as matthias did in his demo, I am not able to get the desired reaction. not yet sure what i am doing wrong. also it seems that drag actions get intercepted by other events inherent to Image, so we may need to make a canvas from scratch, pop the existing callbacks off, or something like that. i'll push what I have for now to a branch on gdsfactory later

tvt173 commented 1 year ago

it also just struck me that matplotlib may be another viable option: https://matplotlib.org/stable/users/explain/event_handling.html

proppy commented 1 year ago

Maybe with https://matplotlib.org/ipympl/ (since the event handling wouldn't work with the inline backend).

proppy commented 1 year ago

It also look there is some support for the notebook backend with https://github.com/googlecolab/colabtools/issues/706#issuecomment-921266837

tvt173 commented 1 year ago

for reference, this is what I had so far


from ipywidgets import Label, HTML, HBox, Image, VBox, Box, HBox
from ipyevents import Event
from typing import Optional
import klayout.db as db
import klayout.lay as lay
from loguru import logger

class LayoutViewer:
    def __init__(self, filepath: str, layer_properties: Optional[str]):
        self.filepath = filepath
        self.layer_properties = layer_properties
        self.layout_view = lay.LayoutView()
        self.load_layout(filepath, layer_properties)
        pixel_buffer = self.layout_view.get_pixels_with_options(800, 600)
        png_data = pixel_buffer.to_png_data()
        self.image = Image(value=png_data, format='png')
        scroll_event = Event(source=self.image, watched_events=['wheel'])
        scroll_event.on_dom_event(self.on_scroll)
        self.wheel_info = HTML("Waiting for a scroll...")
        self.mouse_info = HTML("Waiting for a mouse event...")
        # self.layout_view.on_image_updated_event = lambda: self.refresh
        mouse_event = Event(source=self.image, watched_events=['mousedown', 'mouseup', 'mousemove'])
        mouse_event.on_dom_event(self.on_mouse_down)

    def load_layout(self, filepath: str, layer_properties: Optional[str]):
        self.layout_view.load_layout(filepath)
        self.layout_view.max_hier()
        if layer_properties:
            self.layout_view.load_layer_props(layer_properties)

    def refresh(self):
        pixel_buffer = self.layout_view.get_pixels_with_options(800, 600)
        png_data = pixel_buffer.to_png_data()
        self.image.value = png_data

    def _get_modifier_buttons(self, event):
        shift = event['shiftKey']
        alt = event['altKey']
        ctrl = event['ctrlKey']
        meta = event['metaKey']

        mouse_buttons = event['buttons']

        buttons = 0
        if shift:
            buttons |= lay.ButtonState.ShiftKey
        if alt:
            buttons |= lay.ButtonState.AltKey
        if ctrl:
            buttons |= lay.ButtonState.ControlKey

        if mouse_buttons == 1:
            buttons |= lay.ButtonState.LeftButton
        if mouse_buttons == 2:
            buttons |= lay.ButtonState.RightButton
        if mouse_buttons == 4:
            buttons |= lay.ButtonState.MidButton

        return buttons

    def on_scroll(self, event):
        delta = event['deltaY']
        x = event['offsetX']
        y = event['offsetY']
        self.wheel_info.value = f'scroll event: {event}'
        buttons = self._get_modifier_buttons(event)
        # TODO: this is what I *want* to respond with, but it doesn't work, so I am using zoom_in/zoom_out instead
        # self.layout_view.send_wheel_event(delta, False, db.Point(x, y), buttons)
        if delta < 0:
            self.layout_view.zoom_in()
        else:
            self.layout_view.zoom_out()
        self.refresh()

    def on_mouse_down(self, event):
        x = event['offsetX']
        y = event['offsetY']
        moved_x = event['movementX']
        moved_y = event['movementY']
        buttons = self._get_modifier_buttons(event)
        # TODO: this part is also not working. why?
        if event == 'mousedown':
            self.layout_view.send_mouse_press_event(db.Point(x, y), buttons)
        elif event == 'mouseup':
            self.layout_view.send_mouse_release_event(db.Point(x, y), buttons)
        elif event == 'mousemove':
            self.layout_view.send_mouse_move_event(db.Point(moved_x, moved_y), buttons)
        self.refresh()
        self.mouse_info.value = f'mouse event: {event}'

in the notebook you can instantiate like

layout_viewer = LayoutViewer(gds_filepath, lyp_filepath)
layout_viewer.image

additionally you can return layout_viewer.mouse_info or layout_viewer.wheel_info to see that indeed the events are properly registering, but you will see the issues i describe both on the klayout side and with Image reacting in unpleasant ways to drag events

proppy commented 1 year ago

@mithro can you try #44?