siliconcompiler 0.28.2__py3-none-any.whl → 0.28.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. siliconcompiler/_common.py +12 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc_dashboard.py +6 -2
  4. siliconcompiler/apps/sc_install.py +61 -13
  5. siliconcompiler/apps/sc_remote.py +1 -1
  6. siliconcompiler/core.py +132 -68
  7. siliconcompiler/fpgas/vpr_example.py +8 -0
  8. siliconcompiler/package.py +3 -2
  9. siliconcompiler/remote/client.py +41 -10
  10. siliconcompiler/report/__init__.py +1 -1
  11. siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +56 -10
  12. siliconcompiler/report/dashboard/components/__init__.py +546 -0
  13. siliconcompiler/report/dashboard/components/flowgraph.py +114 -0
  14. siliconcompiler/report/dashboard/components/graph.py +208 -0
  15. siliconcompiler/report/dashboard/layouts/__init__.py +20 -0
  16. siliconcompiler/report/dashboard/layouts/_common.py +43 -0
  17. siliconcompiler/report/dashboard/layouts/vertical_flowgraph.py +96 -0
  18. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +117 -0
  19. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +110 -0
  20. siliconcompiler/report/dashboard/state.py +217 -0
  21. siliconcompiler/report/dashboard/utils/__init__.py +73 -0
  22. siliconcompiler/report/dashboard/utils/file_utils.py +120 -0
  23. siliconcompiler/report/dashboard/viewer.py +36 -0
  24. siliconcompiler/report/report.py +22 -4
  25. siliconcompiler/report/summary_table.py +1 -2
  26. siliconcompiler/report/utils.py +1 -2
  27. siliconcompiler/scheduler/__init__.py +45 -6
  28. siliconcompiler/schema/schema_obj.py +4 -2
  29. siliconcompiler/sphinx_ext/dynamicgen.py +6 -0
  30. siliconcompiler/tools/_common/__init__.py +44 -6
  31. siliconcompiler/tools/_common/asic.py +79 -23
  32. siliconcompiler/tools/genfasm/genfasm.py +7 -0
  33. siliconcompiler/tools/ghdl/convert.py +7 -0
  34. siliconcompiler/tools/klayout/convert_drc_db.py +60 -0
  35. siliconcompiler/tools/klayout/drc.py +156 -0
  36. siliconcompiler/tools/klayout/export.py +2 -0
  37. siliconcompiler/tools/klayout/klayout.py +0 -1
  38. siliconcompiler/tools/klayout/klayout_convert_drc_db.py +182 -0
  39. siliconcompiler/tools/klayout/operations.py +2 -0
  40. siliconcompiler/tools/klayout/screenshot.py +2 -0
  41. siliconcompiler/tools/klayout/show.py +4 -4
  42. siliconcompiler/tools/magic/drc.py +21 -0
  43. siliconcompiler/tools/magic/extspice.py +21 -0
  44. siliconcompiler/tools/magic/magic.py +29 -0
  45. siliconcompiler/tools/magic/sc_drc.tcl +2 -12
  46. siliconcompiler/tools/magic/sc_extspice.tcl +3 -15
  47. siliconcompiler/tools/openroad/floorplan.py +5 -0
  48. siliconcompiler/tools/openroad/openroad.py +56 -5
  49. siliconcompiler/tools/openroad/scripts/sc_apr.tcl +15 -0
  50. siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
  51. siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +61 -10
  52. siliconcompiler/tools/openroad/scripts/sc_metrics.tcl +10 -0
  53. siliconcompiler/tools/openroad/scripts/sc_procs.tcl +31 -1
  54. siliconcompiler/tools/openroad/scripts/sc_route.tcl +8 -2
  55. siliconcompiler/tools/openroad/scripts/sc_screenshot.tcl +0 -5
  56. siliconcompiler/tools/openroad/scripts/sc_write_images.tcl +36 -6
  57. siliconcompiler/tools/surelog/__init__.py +12 -0
  58. siliconcompiler/tools/verilator/compile.py +27 -0
  59. siliconcompiler/tools/verilator/verilator.py +9 -0
  60. siliconcompiler/tools/vpr/vpr.py +18 -0
  61. siliconcompiler/tools/yosys/{syn_asic_fpga_shared.tcl → procs.tcl} +23 -0
  62. siliconcompiler/tools/yosys/sc_screenshot.tcl +104 -0
  63. siliconcompiler/tools/yosys/sc_syn.tcl +7 -9
  64. siliconcompiler/tools/yosys/screenshot.py +153 -0
  65. siliconcompiler/tools/yosys/syn_asic.py +3 -0
  66. siliconcompiler/tools/yosys/syn_asic.tcl +1 -3
  67. siliconcompiler/tools/yosys/syn_fpga.tcl +3 -2
  68. siliconcompiler/toolscripts/_tools.json +10 -5
  69. siliconcompiler/toolscripts/rhel8/install-chisel.sh +26 -0
  70. siliconcompiler/toolscripts/rhel8/install-ghdl.sh +25 -0
  71. siliconcompiler/toolscripts/rhel8/install-icarus.sh +40 -0
  72. siliconcompiler/toolscripts/rhel8/install-klayout.sh +17 -0
  73. siliconcompiler/toolscripts/rhel8/install-magic.sh +26 -0
  74. siliconcompiler/toolscripts/rhel8/install-montage.sh +5 -0
  75. siliconcompiler/toolscripts/rhel8/install-netgen.sh +25 -0
  76. siliconcompiler/toolscripts/rhel8/install-openroad.sh +31 -0
  77. siliconcompiler/toolscripts/rhel8/install-slang.sh +31 -0
  78. siliconcompiler/toolscripts/rhel8/install-surelog.sh +32 -0
  79. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +27 -0
  80. siliconcompiler/toolscripts/rhel8/install-verible.sh +24 -0
  81. siliconcompiler/toolscripts/rhel8/install-verilator.sh +40 -0
  82. siliconcompiler/toolscripts/rhel8/install-xyce.sh +64 -0
  83. siliconcompiler/toolscripts/rhel8/install-yosys.sh +23 -0
  84. siliconcompiler/toolscripts/rhel9/install-chisel.sh +26 -0
  85. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +25 -0
  86. siliconcompiler/toolscripts/rhel9/install-icarus.sh +40 -0
  87. siliconcompiler/toolscripts/rhel9/install-klayout.sh +17 -0
  88. siliconcompiler/toolscripts/rhel9/install-magic.sh +26 -0
  89. siliconcompiler/toolscripts/rhel9/install-montage.sh +5 -0
  90. siliconcompiler/toolscripts/rhel9/install-netgen.sh +25 -0
  91. siliconcompiler/toolscripts/rhel9/install-slang.sh +31 -0
  92. siliconcompiler/toolscripts/rhel9/install-surelog.sh +32 -0
  93. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +27 -0
  94. siliconcompiler/toolscripts/rhel9/install-verible.sh +24 -0
  95. siliconcompiler/toolscripts/rhel9/install-verilator.sh +40 -0
  96. siliconcompiler/toolscripts/rhel9/install-xdm.sh +43 -0
  97. siliconcompiler/toolscripts/rhel9/install-xyce.sh +64 -0
  98. siliconcompiler/toolscripts/rhel9/install-yosys.sh +23 -0
  99. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +1 -1
  100. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +40 -0
  101. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -2
  102. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +1 -1
  103. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +40 -0
  104. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -2
  105. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +1 -1
  106. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
  107. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +40 -0
  108. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -2
  109. siliconcompiler/utils/__init__.py +30 -1
  110. siliconcompiler/utils/showtools.py +4 -0
  111. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/METADATA +22 -8
  112. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/RECORD +116 -67
  113. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/WHEEL +1 -1
  114. siliconcompiler/report/streamlit_viewer.py +0 -944
  115. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/LICENSE +0 -0
  116. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/entry_points.txt +0 -0
  117. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/top_level.txt +0 -0
@@ -10,12 +10,24 @@ import multiprocessing
10
10
  import subprocess
11
11
  import atexit
12
12
  import shutil
13
+ import fasteners
14
+ import signal
15
+ import socketserver
16
+
17
+ from siliconcompiler.report.dashboard import utils
13
18
 
14
19
 
15
20
  class Dashboard():
16
21
  __port = 8501
17
22
 
23
+ @staticmethod
24
+ def __signal_handler(signal, frame):
25
+ # used to avoid issues during shutdown
26
+ pass
27
+
18
28
  def __init__(self, chip, port=None, graph_chips=None):
29
+ if not port:
30
+ port = Dashboard.get_next_port()
19
31
  if not port:
20
32
  port = Dashboard.__port
21
33
 
@@ -24,16 +36,18 @@ class Dashboard():
24
36
  self.__directory = tempfile.mkdtemp(prefix='sc_dashboard_',
25
37
  suffix=f'_{self.__chip.design}')
26
38
  self.__manifest = os.path.join(self.__directory, 'manifest.json')
39
+ self.__manifest_lock = os.path.join(self.__directory, 'manifest.lock')
27
40
  self.__port = port
28
41
  dirname = os.path.dirname(__file__)
29
- self.__streamlit_file = os.path.join(dirname, 'streamlit_viewer.py')
42
+ self.__streamlit_file = os.path.join(dirname, 'viewer.py')
30
43
 
31
44
  self.__streamlit_args = [
32
45
  ("browser.gatherUsageStats", False),
33
46
  ("browser.serverPort", self.__port),
34
47
  ("logger.level", 'error'),
35
48
  ("runner.fastReruns", True),
36
- ("server.port", self.__port)
49
+ ("server.port", self.__port),
50
+ ("client.toolbarMode", "viewer")
37
51
  ]
38
52
 
39
53
  # pass in a json object called __graph_chips
@@ -42,20 +56,33 @@ class Dashboard():
42
56
 
43
57
  # use of list is to preserve order
44
58
  self.__graph_chips = []
45
- self.__graph_chips_names = []
59
+ graph_chips_config = []
46
60
  if graph_chips:
47
61
  for chip_object_and_name in graph_chips:
48
62
  chip_file_path = \
49
63
  os.path.join(self.__directory,
50
64
  f"{chip_object_and_name['name']}.json")
51
- self.__graph_chips.append({'chip': chip_object_and_name['chip'],
52
- 'name': chip_file_path})
53
- self.__graph_chips_names.append(chip_file_path)
54
-
55
- self.__config = {"manifest": self.__manifest,
56
- "graph_chips": self.__graph_chips_names}
65
+ self.__graph_chips.append({
66
+ 'chip': chip_object_and_name['chip'],
67
+ 'name': chip_file_path
68
+ })
69
+ graph_chips_config.append({
70
+ "path": chip_file_path,
71
+ "cwd": utils.get_chip_cwd(
72
+ chip_object_and_name['chip'],
73
+ chip_object_and_name['cfg_path'])
74
+ })
75
+
76
+ self.__config = {
77
+ "manifest": self.__manifest,
78
+ "lock": self.__manifest_lock,
79
+ "graph_chips": graph_chips_config
80
+ }
57
81
 
58
82
  self.__sleep_time = 0.5
83
+ self.__signal_handler = None
84
+
85
+ self.__lock = fasteners.InterProcessLock(self.__manifest_lock)
59
86
 
60
87
  atexit.register(self.__cleanup)
61
88
 
@@ -70,10 +97,19 @@ class Dashboard():
70
97
  self.__dashboard = multiprocessing.Process(
71
98
  target=self._run_streamlit_bootstrap)
72
99
 
100
+ self.__signal_handler = signal.signal(signal.SIGINT, Dashboard.__signal_handler)
101
+
73
102
  self.__dashboard.start()
74
103
 
75
104
  def update_manifest(self):
76
- self.__chip.write_manifest(self.__manifest)
105
+ if not self.__manifest:
106
+ return
107
+
108
+ new_file = f"{self.__manifest}.new.json"
109
+ self.__chip.write_manifest(new_file)
110
+
111
+ with self.__lock:
112
+ shutil.move(new_file, self.__manifest)
77
113
 
78
114
  def update_graph_manifests(self):
79
115
  for chip_object_and_name in self.__graph_chips:
@@ -103,8 +139,12 @@ class Dashboard():
103
139
  self.__dashboard.terminate()
104
140
  self._sleep()
105
141
 
142
+ if self.__signal_handler:
143
+ signal.signal(signal.SIGINT, self.__signal_handler)
144
+
106
145
  self.__dashboard = None
107
146
  self.__manifest = None
147
+ self.__signal_handler = None
108
148
 
109
149
  def wait(self):
110
150
  self.__dashboard.join()
@@ -135,3 +175,9 @@ class Dashboard():
135
175
 
136
176
  if os.path.exists(self.__directory):
137
177
  shutil.rmtree(self.__directory)
178
+
179
+ @staticmethod
180
+ def get_next_port():
181
+ with socketserver.TCPServer(("localhost", 0), None) as s:
182
+ return s.server_address[1]
183
+ return None
@@ -0,0 +1,546 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ import pandas
5
+
6
+ import streamlit
7
+ from streamlit_agraph import agraph
8
+ import streamlit_antd_components as sac
9
+
10
+ from PIL import Image
11
+
12
+ from siliconcompiler import __version__ as sc_version
13
+ from siliconcompiler import utils
14
+ from siliconcompiler.report import report
15
+
16
+ from siliconcompiler.report.dashboard import state
17
+ from siliconcompiler.report.dashboard import layouts
18
+ from siliconcompiler.report.dashboard.utils import file_utils
19
+ from siliconcompiler.report.dashboard.components import flowgraph
20
+
21
+
22
+ SC_ABOUT = [
23
+ f"SiliconCompiler {sc_version}",
24
+ '''A compiler framework that automates translation from source code to
25
+ silicon.''',
26
+ "https://www.siliconcompiler.com/",
27
+ "https://github.com/siliconcompiler/siliconcompiler/"
28
+ ]
29
+ SC_MENU = {
30
+ "Get help": "https://docs.siliconcompiler.com/",
31
+ "Report a Bug":
32
+ '''https://github.com/siliconcompiler/siliconcompiler/issues''',
33
+ "About": "\n\n".join(SC_ABOUT)}
34
+ SC_DATA_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'data'))
35
+ SC_LOGO_PATH = os.path.join(SC_DATA_ROOT, 'logo.png')
36
+ SC_FONT_PATH = os.path.join(SC_DATA_ROOT, 'RobotoMono', 'RobotoMono-Regular.ttf')
37
+
38
+
39
+ def page_header(title_col_width=0.7):
40
+ """
41
+ Displays the title and a selectbox that allows you to select a given run
42
+ to inspect.
43
+
44
+ Args:
45
+ title_col_width (float) : A number between 0 and 1 which is the percentage of the
46
+ width of the screen given to the title and logo. The rest is given to selectbox.
47
+ """
48
+
49
+ if state.DEVELOPER:
50
+ col_width = (1 - title_col_width) / 2
51
+ title_col, job_select_col, settings_col = \
52
+ streamlit.columns([title_col_width, col_width, col_width], gap="large")
53
+ else:
54
+ title_col, job_select_col = \
55
+ streamlit.columns([title_col_width, 1 - title_col_width], gap="large")
56
+
57
+ with title_col:
58
+ design_title(design=state.get_chip().design)
59
+ with job_select_col:
60
+ job_selector()
61
+
62
+ if state.DEVELOPER:
63
+ with settings_col:
64
+ with streamlit.popover("Settings", use_container_width=True):
65
+ all_layouts = layouts.get_all_layouts()
66
+ layout_index = all_layouts.index(state.get_key(state.APP_LAYOUT))
67
+ if state.set_key(
68
+ state.APP_LAYOUT,
69
+ streamlit.selectbox("Layout", all_layouts, index=layout_index)):
70
+ state.set_key(state.APP_RERUN, "Layout")
71
+
72
+ state._DEBUG = streamlit.checkbox("Debug", state._DEBUG)
73
+
74
+ state.set_key(
75
+ state.APP_RUNNING_REFRESH,
76
+ streamlit.slider(
77
+ "Running refresh rate (ms)",
78
+ min_value=1000,
79
+ max_value=10000,
80
+ step=500,
81
+ value=state.get_key(state.APP_RUNNING_REFRESH)))
82
+
83
+ state.set_key(
84
+ state.APP_STOPPED_REFRESH,
85
+ streamlit.slider(
86
+ "Stopped refresh rate (ms)",
87
+ min_value=1000,
88
+ max_value=100000,
89
+ step=1000,
90
+ value=state.get_key(state.APP_STOPPED_REFRESH)))
91
+
92
+ state.set_key(
93
+ state.MAX_DICT_ITEMS_TO_SHOW,
94
+ streamlit.number_input(
95
+ "Maximum dict item to show",
96
+ min_value=1,
97
+ max_value=10000,
98
+ value=state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)))
99
+
100
+ state.set_key(
101
+ state.MAX_FILE_LINES_TO_SHOW,
102
+ streamlit.number_input(
103
+ "Maximum file lines to show",
104
+ min_value=100,
105
+ max_value=1000,
106
+ step=100,
107
+ value=state.get_key(state.MAX_FILE_LINES_TO_SHOW)))
108
+
109
+
110
+ def design_title(design=""):
111
+ font = base64.b64encode(open(SC_FONT_PATH, "rb").read()).decode()
112
+ streamlit.html(
113
+ f'''
114
+ <head>
115
+ <style>
116
+ /* Define the @font-face rule */
117
+ @font-face {{
118
+ font-family: 'Roboto Mono';
119
+ src: url(data:font/truetype;charset=utf-8;base64,{font}) format('truetype');
120
+ font-weight: normal;
121
+ font-style: normal;
122
+ }}
123
+
124
+ /* Styles for the logo and text */
125
+ .logo-container {{
126
+ display: flex;
127
+ align-items: flex-start;
128
+ }}
129
+
130
+ .logo-image {{
131
+ margin-right: 10px;
132
+ margin-top: -10px;
133
+ }}
134
+
135
+ .logo-text {{
136
+ display: flex;
137
+ flex-direction: column;
138
+ margin-top: -20px;
139
+ }}
140
+
141
+ .design-text {{
142
+ color: #F1C437; /* Yellow color */
143
+ font-family: 'Roboto Mono', sans-serif;
144
+ font-weight: 700 !important;
145
+ font-size: 30px !important;
146
+ margin-bottom: -16px;
147
+ }}
148
+
149
+ .dashboard-text {{
150
+ color: #1D4482; /* Blue color */
151
+ font-family: 'Roboto Mono', sans-serif;
152
+ font-weight: 700 !important;
153
+ font-size: 30px !important;
154
+ }}
155
+
156
+ </style>
157
+ </head>
158
+ '''
159
+ )
160
+
161
+ logo = base64.b64encode(open(SC_LOGO_PATH, "rb").read()).decode()
162
+ streamlit.html(
163
+ f'''
164
+ <body>
165
+ <div class="logo-container">
166
+ <img src="data:image/png;base64,{logo}" alt="SiliconCompiler logo"
167
+ class="logo-image" height="61">
168
+ <div class="logo-text">
169
+ <p class="design-text">{design}</p>
170
+ <p class="dashboard-text">dashboard</p>
171
+ </div>
172
+ </div>
173
+ </body>
174
+ '''
175
+ )
176
+
177
+
178
+ def job_selector():
179
+ job = streamlit.selectbox(
180
+ 'pick a job',
181
+ state.get_chips(),
182
+ label_visibility='collapsed')
183
+
184
+ if state.set_key(state.SELECTED_JOB, job):
185
+ # Job changed, so need to run
186
+ state.set_key(state.APP_RERUN, "Job")
187
+
188
+
189
+ def setup_page():
190
+ streamlit.set_page_config(
191
+ page_title=f'{state.get_chip().design} dashboard',
192
+ page_icon=Image.open(SC_LOGO_PATH),
193
+ layout="wide",
194
+ menu_items=SC_MENU)
195
+
196
+
197
+ def file_viewer(chip, path, page_key=None, header_col_width=0.89):
198
+ if not path:
199
+ streamlit.error('Select a file')
200
+ return
201
+
202
+ if not os.path.isfile(path):
203
+ streamlit.error(f'{path} is not a file')
204
+ return
205
+
206
+ # Detect file type
207
+ relative_path = os.path.relpath(path, chip.getworkdir())
208
+ filename = os.path.basename(path)
209
+ file_extension = utils.get_file_ext(path)
210
+
211
+ # Build streamlit module
212
+ header_col, download_col = \
213
+ streamlit.columns([header_col_width, 1 - header_col_width], gap='small')
214
+
215
+ with header_col:
216
+ streamlit.header(relative_path)
217
+
218
+ with download_col:
219
+ streamlit.markdown(' ') # aligns download button with title
220
+ streamlit.download_button(
221
+ label="Download",
222
+ data=path,
223
+ file_name=filename,
224
+ use_container_width=True)
225
+
226
+ try:
227
+ if file_extension in ('jpg', 'jpeg', 'png'):
228
+ # Data is an image
229
+ streamlit.image(path)
230
+ elif file_extension == 'json':
231
+ # Data is a json file
232
+ data = json.loads(file_utils.read_file(path, None))
233
+ expand_keys = report.get_total_manifest_key_count(data) < \
234
+ state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
235
+ if not expand_keys:
236
+ # Open two levels
237
+ expand_keys = 2
238
+ streamlit.json(data, expanded=expand_keys)
239
+ else:
240
+ file_data = file_utils.read_file(path, None).splitlines()
241
+ max_pages = len(file_data)
242
+ page_size = state.get_key(state.MAX_FILE_LINES_TO_SHOW)
243
+
244
+ file_section = streamlit.container()
245
+
246
+ if page_key:
247
+ if state.get_key(page_key) is None:
248
+ state.set_key(page_key, 1)
249
+ index = state.get_key(page_key)
250
+ else:
251
+ index = 1
252
+
253
+ page = sac.pagination(
254
+ align='center',
255
+ index=index,
256
+ jump=True,
257
+ show_total=True,
258
+ page_size=page_size,
259
+ total=max_pages,
260
+ disabled=max_pages < state.get_key(state.MAX_FILE_LINES_TO_SHOW))
261
+
262
+ if page_key:
263
+ state.set_key(page_key, page)
264
+
265
+ start_idx = (page - 1) * state.get_key(state.MAX_FILE_LINES_TO_SHOW)
266
+ end_idx = start_idx + state.get_key(state.MAX_FILE_LINES_TO_SHOW)
267
+ file_show = file_data[start_idx:end_idx]
268
+ with file_section:
269
+ # Assume file is text
270
+ streamlit.code(
271
+ "\n".join(file_show),
272
+ language=file_utils.get_file_type(file_extension),
273
+ line_numbers=True)
274
+ except Exception as e:
275
+ streamlit.markdown(f'Error occurred reading file: {e}')
276
+
277
+
278
+ def manifest_viewer(
279
+ chip,
280
+ header_col_width=0.70):
281
+ """
282
+ Displays the manifest and a way to search through the manifest.
283
+
284
+ Args:
285
+ chip (Chip) : Chip object
286
+ header_col_width (float) : A number between 0 and 1 which is the maximum
287
+ percentage of the width of the screen given to the header. The rest
288
+ is given to the settings and download buttons.
289
+ """
290
+ end_column_widths = (1 - header_col_width) / 2
291
+ header_col, settings_col, download_col = \
292
+ streamlit.columns(
293
+ [header_col_width, end_column_widths, end_column_widths],
294
+ gap='small')
295
+ with header_col:
296
+ streamlit.header('Manifest')
297
+
298
+ with settings_col:
299
+ streamlit.markdown(' ') # aligns with title
300
+ with streamlit.popover("Settings", use_container_width=True):
301
+ if streamlit.checkbox(
302
+ 'Raw manifest',
303
+ help='Click here to see the manifest before it was made more readable'):
304
+ manifest_to_show = chip.schema.cfg
305
+ else:
306
+ manifest_to_show = report.make_manifest(chip)
307
+
308
+ if streamlit.checkbox(
309
+ 'Hide empty values',
310
+ help='Hide empty keypaths',
311
+ value=True):
312
+ manifest_to_show = report.search_manifest(
313
+ manifest_to_show,
314
+ value_search='*')
315
+
316
+ search_key = streamlit.text_input('Search Keys', '', placeholder="Keys")
317
+ search_value = streamlit.text_input('Search Values', '', placeholder="Values")
318
+
319
+ manifest_to_show = report.search_manifest(
320
+ manifest_to_show,
321
+ key_search=search_key,
322
+ value_search=search_value)
323
+
324
+ with download_col:
325
+ streamlit.markdown(' ') # aligns with title
326
+ streamlit.download_button(
327
+ label='Download',
328
+ file_name='manifest.json',
329
+ data=json.dumps(chip.schema.cfg, indent=2),
330
+ mime="application/json",
331
+ use_container_width=True)
332
+
333
+ expand_keys = report.get_total_manifest_key_count(manifest_to_show) < \
334
+ state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
335
+ if not expand_keys:
336
+ # Open two levels
337
+ expand_keys = 2
338
+ streamlit.json(manifest_to_show, expanded=expand_keys)
339
+
340
+
341
+ def metrics_viewer(metric_dataframe, metric_to_metric_unit_map, header_col_width=0.7, height=None):
342
+ """
343
+ Displays multi-select check box to the users which allows them to select
344
+ which nodes and metrics to view in the dataframe.
345
+
346
+ Args:
347
+ metric_dataframe (Pandas.DataFrame) : Contains the metrics of all nodes.
348
+ metric_to_metric_unit_map (dict) : Maps the metric to the associated metric unit.
349
+ """
350
+
351
+ all_nodes = metric_dataframe.columns.tolist()
352
+ all_metrics = list(metric_to_metric_unit_map.values())
353
+
354
+ header_col, settings_col = streamlit.columns(
355
+ [header_col_width, 1 - header_col_width],
356
+ gap="large")
357
+ with header_col:
358
+ streamlit.header('Metrics')
359
+ with settings_col:
360
+ # Align to header
361
+ streamlit.markdown('')
362
+
363
+ with streamlit.popover("Settings", use_container_width=True):
364
+ transpose = streamlit.checkbox(
365
+ 'Transpose',
366
+ help='Transpose the metrics table')
367
+
368
+ display_nodes = streamlit.multiselect('Pick nodes to include', all_nodes, [])
369
+ display_metrics = streamlit.multiselect('Pick metrics to include?', all_metrics, [])
370
+
371
+ # Filter data
372
+ if not display_nodes:
373
+ display_nodes = all_nodes
374
+
375
+ if not display_metrics:
376
+ display_metrics = all_metrics
377
+
378
+ dataframe_nodes = list(display_nodes)
379
+ dataframe_metrics = []
380
+ for metric in metric_dataframe.index.tolist():
381
+ if metric_to_metric_unit_map[metric] in display_metrics:
382
+ dataframe_metrics.append(metric)
383
+
384
+ metric_dataframe = metric_dataframe.loc[dataframe_metrics, dataframe_nodes]
385
+ if transpose:
386
+ metric_dataframe = metric_dataframe.transpose()
387
+
388
+ # TODO By July 2024, Streamlit will let catch click events on the dataframe
389
+ streamlit.dataframe(metric_dataframe, use_container_width=True, height=height)
390
+
391
+
392
+ def node_image_viewer(chip, step, index):
393
+ exts = ('png', 'jpg', 'jpeg')
394
+ images = []
395
+ for path, _, files in report.get_files(chip, step, index):
396
+ images.extend([os.path.join(path, f) for f in files if utils.get_file_ext(f) in exts])
397
+
398
+ if not images:
399
+ streamlit.markdown("No images to show")
400
+
401
+ work_dir = chip.getworkdir(step=step, index=index)
402
+
403
+ columns = streamlit.slider(
404
+ "Image columns",
405
+ min_value=1,
406
+ max_value=min(len(images), 10),
407
+ value=min(len(images), 4),
408
+ disabled=len(images) < 2)
409
+
410
+ column = 0
411
+ for image in sorted(images):
412
+ if column == 0:
413
+ cols = streamlit.columns(columns)
414
+
415
+ with cols[column]:
416
+ streamlit.image(
417
+ image,
418
+ caption=os.path.relpath(image, work_dir),
419
+ use_column_width=True)
420
+
421
+ column += 1
422
+ if column == columns:
423
+ column = 0
424
+
425
+
426
+ def node_file_tree_viewer(chip, step, index):
427
+ logs_and_reports = file_utils.convert_filepaths_to_select_tree(
428
+ report.get_files(chip, step, index))
429
+
430
+ if not logs_and_reports:
431
+ streamlit.markdown("No files to show")
432
+
433
+ lookup = {}
434
+ tree_items = []
435
+
436
+ file_metrics = report.get_metrics_source(chip, step, index)
437
+ work_dir = chip.getworkdir(step=step, index=index)
438
+
439
+ def make_item(file):
440
+ lookup[file['value']] = file['label']
441
+ item = sac.TreeItem(
442
+ file['value'],
443
+ icon=file_utils.get_file_icon(file['value']),
444
+ tag=[],
445
+ children=[])
446
+
447
+ check_file = os.path.relpath(file['value'], work_dir)
448
+ if check_file in file_metrics:
449
+ for metric in file_metrics[check_file]:
450
+ if len(item.tag) < 5:
451
+ item.tag.append(sac.Tag(metric, color='green'))
452
+ else:
453
+ item.tag.append(sac.Tag('metrics...', color='geekblue'))
454
+ break
455
+ item.tooltip = "metrics: " + ", ".join(file_metrics[check_file])
456
+
457
+ if 'children' in file:
458
+ item.icon = 'folder'
459
+ for child in file['children']:
460
+ item.children.append(make_item(child))
461
+
462
+ return item
463
+
464
+ for file in logs_and_reports:
465
+ tree_items.append(make_item(file))
466
+
467
+ def format_label(value):
468
+ return lookup[value]
469
+
470
+ selected = sac.tree(
471
+ items=tree_items,
472
+ format_func=format_label,
473
+ size='md',
474
+ icon='table',
475
+ open_all=True)
476
+
477
+ if selected and os.path.isfile(selected):
478
+ state.set_key(state.SELECTED_FILE, selected)
479
+ state.set_key(state.SELECTED_FILE_PAGE, None)
480
+
481
+
482
+ def node_viewer(chip, step, index, metric_dataframe, height=None):
483
+ metrics_col, records_col, logs_and_reports_col = streamlit.columns(3, gap='small')
484
+
485
+ node_name = f'{step}{index}'
486
+
487
+ with metrics_col:
488
+ streamlit.subheader(f'{node_name} metrics')
489
+ if node_name in metric_dataframe:
490
+ streamlit.dataframe(
491
+ metric_dataframe[node_name].dropna(),
492
+ use_container_width=True,
493
+ height=height)
494
+ with records_col:
495
+ streamlit.subheader(f'{step}{index} details')
496
+ nodes = {}
497
+ nodes[step + index] = report.get_flowgraph_nodes(chip, step, index)
498
+ streamlit.dataframe(
499
+ pandas.DataFrame.from_dict(nodes),
500
+ use_container_width=True,
501
+ height=height)
502
+ with logs_and_reports_col:
503
+ streamlit.subheader(f'{step}{index} files')
504
+ node_file_tree_viewer(chip, step, index)
505
+
506
+
507
+ def flowgraph_viewer(chip):
508
+ '''
509
+ This function creates, displays, and returns the selected node of the flowgraph.
510
+
511
+ Args:
512
+ chip (Chip) : The chip object that contains the schema read from.
513
+ '''
514
+
515
+ nodes, edges = flowgraph.get_nodes_and_edges(chip)
516
+ if state.set_key(state.SELECTED_FLOWGRAPH_NODE, agraph(
517
+ nodes=nodes,
518
+ edges=edges,
519
+ config=flowgraph.get_graph_config())):
520
+ if state.get_key(state.SELECTED_FLOWGRAPH_NODE):
521
+ state.set_key(state.NODE_SOURCE, "flowgraph")
522
+
523
+
524
+ def node_selector(nodes):
525
+ """
526
+ Displays selectbox for nodes to show in the node information panel. Since
527
+ both the flowgraph and selectbox show which node's information is
528
+ displayed, the one clicked more recently will be displayed.
529
+
530
+ Args:
531
+ nodes (list) : Contains the metrics of all nodes.
532
+ """
533
+ prev_node = state.get_selected_node()
534
+
535
+ with streamlit.popover("Select Node", use_container_width=True):
536
+ # Preselect node
537
+ idx = 0
538
+ if prev_node:
539
+ idx = nodes.index(prev_node)
540
+ if state.set_key(
541
+ state.SELECTED_SELECTOR_NODE,
542
+ streamlit.selectbox(
543
+ 'Pick a node to inspect',
544
+ nodes,
545
+ index=idx)):
546
+ state.set_key(state.NODE_SOURCE, "selector")