bigraph-viz
Advanced tools
| import os | ||
| from pdf2image import convert_from_path | ||
| from PIL import Image | ||
| # Print current working directory | ||
| cwd = os.getcwd() | ||
| print("Current working directory:", cwd) | ||
| # Go up one level in the directory | ||
| cwd = os.path.dirname(cwd) | ||
| # Input PDF file (single page) | ||
| pdf_path = os.path.join(cwd, "notebooks/out/ecoli.pdf") | ||
| # Output directory and files | ||
| output_dir = os.path.join(cwd, "notebooks/out/") | ||
| output_full_res = os.path.join(output_dir, "ecoli_full_res.png") | ||
| output_scaled = os.path.join(output_dir, "ecoli_google_slides.png") | ||
| # Ensure the output directory exists | ||
| os.makedirs(output_dir, exist_ok=True) | ||
| # Convert PDF to an image at high DPI | ||
| images = convert_from_path(pdf_path, dpi=1200) # Use high DPI for sharpness | ||
| if images: | ||
| # Save the **full resolution** image first | ||
| images[0].save(output_full_res, "PNG") | ||
| print(f"Full resolution image saved at: {output_full_res}") | ||
| # Resize image to match Google Slides width (3000 px) | ||
| target_width = 3000 | ||
| aspect_ratio = images[0].width / images[0].height | ||
| target_height = int(target_width / aspect_ratio) | ||
| resized_image = images[0].resize((target_width, target_height), Image.LANCZOS) | ||
| resized_image.save(output_scaled, "PNG") | ||
| print(f"Resized Google Slides image saved at: {output_scaled}") | ||
| else: | ||
| print("Error: No images found in PDF!") |
| [build-system] | ||
| requires = ["setuptools>=61.0", "wheel"] | ||
| build-backend = "setuptools.build_meta" | ||
| [project] | ||
| name = "bigraph-viz" | ||
| version = "0.1.6" | ||
| description = "A visualization method for displaying the structure of process bigraphs" | ||
| readme = "README.md" | ||
| requires-python = "==3.12.9" | ||
| license = { file = "LICENSE" } | ||
| authors = [ | ||
| { name = "Eran Agmon", email = "agmon.eran@gmail.com" } | ||
| ] | ||
| maintainers = [ | ||
| { name = "Eran Agmon", email = "agmon.eran@gmail.com" } | ||
| ] | ||
| keywords = ["visualization", "systems biology", "bigraph", "graphviz", "vivarium"] | ||
| classifiers = [ | ||
| "Development Status :: 3 - Alpha", | ||
| "Intended Audience :: Science/Research", | ||
| "License :: OSI Approved :: MIT License", | ||
| "Operating System :: OS Independent", | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Topic :: Scientific/Engineering :: Visualization", | ||
| "Topic :: Software Development :: Libraries" | ||
| ] | ||
| dependencies = [ | ||
| "bigraph-schema", | ||
| "graphviz" | ||
| ] | ||
| [tool.setuptools] | ||
| packages = ["bigraph_viz"] | ||
| [tool.uv.sources] | ||
| bigraph-schema = { path = "../bigraph-schema", editable = true } |
| Metadata-Version: 2.1 | ||
| Name: bigraph-viz | ||
| Version: 0.1.5 | ||
| Version: 0.1.6 | ||
| Summary: A graphviz-based plotting tool for compositional bigraph schema | ||
@@ -8,2 +8,4 @@ Home-page: https://github.com/vivarium-collective/bigraph-viz | ||
| Author-email: agmon.eran@gmail.com | ||
| License: UNKNOWN | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 3 - Alpha | ||
@@ -93,1 +95,3 @@ Classifier: Intended Audience :: Developers | ||
| Bigraph-viz is open-source software released under the [Apache 2 License](https://github.com/vivarium-collective/bigraph-viz/blob/main/LICENSE). | ||
| AUTHORS.md | ||
| LICENSE | ||
| README.md | ||
| pyproject.toml | ||
| setup.py | ||
| bigraph_viz/__init__.py | ||
| bigraph_viz/convert.py | ||
| bigraph_viz/convert_vivarium_v1.py | ||
@@ -7,0 +9,0 @@ bigraph_viz/dict_utils.py |
| import os | ||
| import inspect | ||
| import graphviz | ||
| from itertools import islice | ||
| import numpy as np | ||
@@ -9,3 +10,2 @@ | ||
| PROCESS_SCHEMA_KEYS = [ | ||
@@ -22,2 +22,8 @@ 'config', | ||
| def chunked(iterable, size): | ||
| """Yield successive chunks from iterable.""" | ||
| it = iter(iterable) | ||
| return iter(lambda: tuple(islice(it, size)), ()) | ||
| def make_label(label): | ||
@@ -32,6 +38,6 @@ # Insert line breaks after every max_length characters | ||
| def get_graph_wires( | ||
| ports_schema, # the ports schema | ||
| wires, # the wires, from port to path | ||
| graph_dict, # the current graph dict that is being built | ||
| schema_key, # inputs or outputs | ||
| ports_schema, # the ports schema | ||
| wires, # the wires, from port to path | ||
| graph_dict, # the current graph dict that is being built | ||
| schema_key, # inputs or outputs | ||
| edge_path, # the path up to this process | ||
@@ -79,3 +85,3 @@ bridge_wires=None, | ||
| graph_dict['input_edges'].append({ | ||
| 'edge_path': edge_path , | ||
| 'edge_path': edge_path, | ||
| 'target_path': target_path, | ||
@@ -190,3 +196,3 @@ 'port': f'bridge_{port}', | ||
| rankdir='TB', | ||
| aspect_ratio='auto', # 'compress', 'expand', 'auto', 'fill' | ||
| aspect_ratio='auto', # 'compress', 'expand', 'auto', 'fill' | ||
| dpi='70', | ||
@@ -204,2 +210,3 @@ significant_digits=2, | ||
| node_groups=False, | ||
| max_nodes_per_row=None, | ||
| ): | ||
@@ -233,3 +240,3 @@ """make a graphviz figure from a graph_dict""" | ||
| ratio=aspect_ratio, # "fill", | ||
| splines = 'true', | ||
| splines='true', | ||
| ) | ||
@@ -239,18 +246,57 @@ | ||
| graph.attr('node', **state_node_spec) | ||
| for node in graph_dict['state_nodes']: | ||
| node_path = node['path'] | ||
| node_name = add_node_to_graph(graph, node, state_node_spec, show_values, show_types, significant_digits) | ||
| node_names.append(node_name) | ||
| state_nodes = graph_dict['state_nodes'] | ||
| if max_nodes_per_row: | ||
| previous_node = None | ||
| for i, chunk in enumerate(chunked(state_nodes, max_nodes_per_row)): | ||
| with graph.subgraph(name=f'state_row_{i}') as row: | ||
| row.attr(rank='same') | ||
| chunk_node_names = [] | ||
| for node in chunk: | ||
| node_name = add_node_to_graph(graph, node, state_node_spec, show_values, show_types, | ||
| significant_digits) | ||
| node_names.append(node_name) | ||
| chunk_node_names.append(node_name) | ||
| # Add invisible edge to stack rows | ||
| if previous_node and chunk_node_names: | ||
| graph.edge(previous_node, chunk_node_names[0], style='invis', weight='10') | ||
| if chunk_node_names: | ||
| previous_node = chunk_node_names[-1] | ||
| else: | ||
| for node in state_nodes: | ||
| node_name = add_node_to_graph(graph, node, state_node_spec, show_values, show_types, significant_digits) | ||
| node_names.append(node_name) | ||
| # process nodes | ||
| process_paths = [] | ||
| graph.attr('node', **process_node_spec) | ||
| for node in graph_dict['process_nodes']: | ||
| node_path = node['path'] | ||
| process_paths.append(node_path) | ||
| node_name = str(node_path) | ||
| node_names.append(node_name) | ||
| label = make_label(node_path[-1]) | ||
| graph.node(node_name, label=label) | ||
| process_nodes = graph_dict['process_nodes'] | ||
| if max_nodes_per_row: | ||
| previous_node = None | ||
| for i, chunk in enumerate(chunked(process_nodes, max_nodes_per_row)): | ||
| with graph.subgraph(name=f'process_row_{i}') as row: | ||
| row.attr(rank='same') | ||
| chunk_node_names = [] | ||
| for node in chunk: | ||
| node_path = node['path'] | ||
| process_paths.append(node_path) | ||
| node_name = str(node_path) | ||
| node_names.append(node_name) | ||
| chunk_node_names.append(node_name) | ||
| label = make_label(node_path[-1]) | ||
| row.node(node_name, label=label) | ||
| if previous_node and chunk_node_names: | ||
| graph.edge(previous_node, chunk_node_names[0], style='invis', weight='10') | ||
| if chunk_node_names: | ||
| previous_node = chunk_node_names[-1] | ||
| else: | ||
| for node in process_nodes: | ||
| node_path = node['path'] | ||
| process_paths.append(node_path) | ||
| node_name = str(node_path) | ||
| node_names.append(node_name) | ||
| label = make_label(node_path[-1]) | ||
| graph.node(node_name, label=label) | ||
| # place edges | ||
@@ -278,3 +324,3 @@ graph.attr('edge', arrowhead='none', penwidth='2') | ||
| if edge['type'] == 'bridge_inputs': | ||
| graph.attr('edge', **output_edge_spec) # reverse arrow direction to go from composite to store | ||
| graph.attr('edge', **output_edge_spec) # reverse arrow direction to go from composite to store | ||
| plot_edges(graph, edge, port_labels, port_label_size, state_node_spec, constraint='false') | ||
@@ -287,3 +333,3 @@ else: | ||
| if edge['type'] == 'bridge_outputs': | ||
| graph.attr('edge', **input_edge_spec) # reverse arrow direction to go from store to composite | ||
| graph.attr('edge', **input_edge_spec) # reverse arrow direction to go from store to composite | ||
| plot_edges(graph, edge, port_labels, port_label_size, state_node_spec, constraint='false') | ||
@@ -300,6 +346,6 @@ else: | ||
| if 'bridge_outputs' in edge['type']: | ||
| graph.attr('edge', **input_edge_spec) # reverse arrow direction to go from store to composite | ||
| graph.attr('edge', **input_edge_spec) # reverse arrow direction to go from store to composite | ||
| plot_edges(graph, edge, port_labels, port_label_size, state_node_spec, constraint='false') | ||
| if 'bridge_inputs' in edge['type']: | ||
| graph.attr('edge', **output_edge_spec) # reverse arrow direction to go from composite to store | ||
| graph.attr('edge', **output_edge_spec) # reverse arrow direction to go from composite to store | ||
| plot_edges(graph, edge, port_labels, port_label_size, state_node_spec, constraint='false') | ||
@@ -396,3 +442,3 @@ | ||
| (), | ||
| options=viztype_kwargs # TODO | ||
| options=viztype_kwargs # TODO | ||
| ) | ||
@@ -431,3 +477,3 @@ | ||
| if not is_schema_key(key): | ||
| subpath = path + (key,) | ||
| subpath = path + (key,) | ||
| graph = core.get_graph_dict( | ||
@@ -441,4 +487,4 @@ schema.get(key, {}), | ||
| return graph | ||
| def graphviz_edge(core, schema, state, path, options, graph): | ||
@@ -490,3 +536,2 @@ # add process node to graph | ||
| (input_edge['edge_path'] == output_edge['edge_path']): | ||
| graph['bidirectional_edges'].append({ | ||
@@ -523,5 +568,7 @@ 'edge_path': input_edge['edge_path'], | ||
| def graphviz_none(core, schema, state, path, options, graph): | ||
| return graph | ||
| def graphviz_composite(core, schema, state, path, options, graph): | ||
@@ -591,3 +638,2 @@ # add the composite edge | ||
| def get_graph_dict(self, schema, state, path, options, graph=None): | ||
@@ -621,3 +667,2 @@ path = path or () | ||
| def generate_graph_dict(self, schema, state, path, options): | ||
@@ -627,3 +672,2 @@ full_schema, full_state = self.generate(schema, state) | ||
| def plot_graph(self, | ||
@@ -657,3 +701,2 @@ graph_dict, | ||
| # Begin Tests | ||
@@ -667,2 +710,3 @@ ############### | ||
| def test_simple_store(): | ||
@@ -677,2 +721,3 @@ simple_store_state = { | ||
| def test_forest(): | ||
@@ -692,2 +737,3 @@ forest = { | ||
| def test_nested_composite(): | ||
@@ -708,27 +754,27 @@ state = { | ||
| 'config': {'_type': 'quote', | ||
| 'state': {'grow': {'_type': 'process', | ||
| 'address': 'local:grow', | ||
| 'config': {'rate': 0.03}, | ||
| 'inputs': {'mass': ['mass']}, | ||
| 'outputs': {'mass': ['mass']}}, | ||
| 'divide': {'_type': 'process', | ||
| 'address': 'local:divide', | ||
| 'config': {'agent_id': '0', | ||
| 'agent_schema': {'mass': 'float'}, | ||
| 'threshold': 2.0, | ||
| 'divisions': 2}, | ||
| 'inputs': {'trigger': ['mass']}, | ||
| 'outputs': {'environment': ['environment']}}, | ||
| 'global_time': 0.0}, | ||
| 'bridge': {'inputs': {'mass': ['mass']}, | ||
| 'outputs': {'mass': ['mass'], | ||
| 'environment': ['environment']}}, | ||
| 'composition': {'global_time': 'float'}, | ||
| 'interface': {'inputs': {}, 'outputs': {}}, | ||
| 'emitter': {'path': ['emitter'], | ||
| 'address': 'local:ram-emitter', | ||
| 'config': {}, | ||
| 'mode': 'none', | ||
| 'emit': {}}, | ||
| 'global_time_precision': None} | ||
| 'state': {'grow': {'_type': 'process', | ||
| 'address': 'local:grow', | ||
| 'config': {'rate': 0.03}, | ||
| 'inputs': {'mass': ['mass']}, | ||
| 'outputs': {'mass': ['mass']}}, | ||
| 'divide': {'_type': 'process', | ||
| 'address': 'local:divide', | ||
| 'config': {'agent_id': '0', | ||
| 'agent_schema': {'mass': 'float'}, | ||
| 'threshold': 2.0, | ||
| 'divisions': 2}, | ||
| 'inputs': {'trigger': ['mass']}, | ||
| 'outputs': {'environment': ['environment']}}, | ||
| 'global_time': 0.0}, | ||
| 'bridge': {'inputs': {'mass': ['mass']}, | ||
| 'outputs': {'mass': ['mass'], | ||
| 'environment': ['environment']}}, | ||
| 'composition': {'global_time': 'float'}, | ||
| 'interface': {'inputs': {}, 'outputs': {}}, | ||
| 'emitter': {'path': ['emitter'], | ||
| 'address': 'local:ram-emitter', | ||
| 'config': {}, | ||
| 'mode': 'none', | ||
| 'emit': {}}, | ||
| 'global_time_precision': None} | ||
| }}}} | ||
@@ -739,2 +785,3 @@ plot_bigraph(state, | ||
| def test_graphviz(): | ||
@@ -744,3 +791,3 @@ cell = { | ||
| '_type': 'map[float]', | ||
| 'a': 11.0, #{'_type': 'float', '_value': 11.0}, | ||
| 'a': 11.0, # {'_type': 'float', '_value': 11.0}, | ||
| 'b': 3333.33}, | ||
@@ -782,2 +829,3 @@ 'cell': { | ||
| def test_bigraph_cell(): | ||
@@ -787,3 +835,3 @@ cell = { | ||
| '_type': 'map[float]', | ||
| 'a': 11.0, #{'_type': 'float', '_value': 11.0}, | ||
| 'a': 11.0, # {'_type': 'float', '_value': 11.0}, | ||
| 'b': 3333.33}, | ||
@@ -793,3 +841,3 @@ 'cell': { | ||
| 'config': {}, | ||
| 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from | ||
| 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from | ||
| 'internal': 1.0, | ||
@@ -820,2 +868,3 @@ '_inputs': { | ||
| def test_bio_schema(): | ||
@@ -852,3 +901,3 @@ core = VisualizeTypes() | ||
| 'inputs': { | ||
| 'fields': ['fields',] | ||
| 'fields': ['fields', ] | ||
| }, | ||
@@ -864,2 +913,3 @@ 'outputs': { | ||
| def test_flat_composite(): | ||
@@ -893,2 +943,3 @@ flat_composite_spec = { | ||
| def test_multi_processes(): | ||
@@ -915,2 +966,3 @@ process_schema = { | ||
| def test_nested_processes(): | ||
@@ -947,2 +999,3 @@ nested_process_spec = { | ||
| def test_cell_hierarchy(): | ||
@@ -983,5 +1036,5 @@ core = VisualizeTypes() | ||
| core.register('cell', { | ||
| 'membrane': 'membrane', | ||
| 'cytoplasm': 'cytoplasm', | ||
| 'nucleoid': 'nucleoid'}) | ||
| 'membrane': 'membrane', | ||
| 'cytoplasm': 'cytoplasm', | ||
| 'nucleoid': 'nucleoid'}) | ||
@@ -1009,2 +1062,3 @@ # state | ||
| def test_multiple_disconnected_ports(): | ||
@@ -1034,2 +1088,3 @@ core = VisualizeTypes() | ||
| def test_composite_process(): | ||
@@ -1052,3 +1107,3 @@ core = VisualizeTypes() | ||
| '_inputs': {'port3': 'any'}, | ||
| '_outputs': {'port4': 'any',}, | ||
| '_outputs': {'port4': 'any', }, | ||
| 'inputs': {'port3': ['store1']}, | ||
@@ -1063,2 +1118,3 @@ 'outputs': {'port4': ['store2']}}}} | ||
| def test_bidirectional_edges(): | ||
@@ -1089,2 +1145,3 @@ core = VisualizeTypes() | ||
| def test_array_paths(): | ||
@@ -1150,4 +1207,4 @@ core = VisualizeTypes() | ||
| 'acetate': np.array([[1.0], [2.0]]), | ||
| 'biomass': np.array([[3.0],[4.0]]), | ||
| 'glucose': np.array([[5.0],[6.0]]) | ||
| 'biomass': np.array([[3.0], [4.0]]), | ||
| 'glucose': np.array([[5.0], [6.0]]) | ||
| } | ||
@@ -1154,0 +1211,0 @@ } |
+5
-1
| Metadata-Version: 2.1 | ||
| Name: bigraph-viz | ||
| Version: 0.1.5 | ||
| Version: 0.1.6 | ||
| Summary: A graphviz-based plotting tool for compositional bigraph schema | ||
@@ -8,2 +8,4 @@ Home-page: https://github.com/vivarium-collective/bigraph-viz | ||
| Author-email: agmon.eran@gmail.com | ||
| License: UNKNOWN | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 3 - Alpha | ||
@@ -93,1 +95,3 @@ Classifier: Intended Audience :: Developers | ||
| Bigraph-viz is open-source software released under the [Apache 2 License](https://github.com/vivarium-collective/bigraph-viz/blob/main/LICENSE). | ||
+1
-1
@@ -5,3 +5,3 @@ import re | ||
| VERSION = '0.1.5' | ||
| VERSION = '0.1.6' | ||
@@ -8,0 +8,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
82820
5.96%18
12.5%1522
5.11%