Skip to content

Commit c850520

Browse files
authored
Merge pull request #28 from biosimulations/show-saved-plots
show saved plots from simulators in notebook
2 parents 04824e9 + b392395 commit c850520

File tree

6 files changed

+212
-76
lines changed

6 files changed

+212
-76
lines changed

biosim_client/verify/dataset_comparison.py

Whitespace-only changes.

biosim_client/verify/models.py

+82
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
from io import BytesIO
12
from typing import TypeAlias
3+
from zipfile import ZIP_DEFLATED, ZipFile
24

5+
import fitz # type: ignore
36
import matplotlib.pyplot as plt
47
import numpy as np
8+
import PIL
9+
import urllib3
510
from IPython.display import Markdown, display
11+
from PIL.Image import Image
12+
from PIL.PngImagePlugin import PngImageFile
613

714
from biosim_client.api.biosim.models.biosim_simulation_run import BiosimSimulationRun
815
from biosim_client.api.biosim.models.biosimulator_version import BiosimulatorVersion
@@ -229,6 +236,81 @@ def _create_markdown_report(self) -> str:
229236
simulator_markdown += f"- no match for {run_link_map[run_group[0].id]} (closest score {min_score})\n"
230237
return simulator_markdown
231238

239+
def show_saved_plots(self, max_columns: int = 3, width_in: float = 10) -> None:
240+
images: list[Image] = []
241+
image_run_ids = []
242+
image_not_found_run_ids = []
243+
sim_version_names = self.simulator_version_names
244+
http = urllib3.PoolManager()
245+
for run_id in self.run_ids:
246+
pdf_images: list[PngImageFile] = self._retrieve_pdf_as_image(run_id=run_id, http=http)
247+
if len(pdf_images) == 0:
248+
image_not_found_run_ids.append(run_id)
249+
elif len(pdf_images) == 1:
250+
images.append(pdf_images[0])
251+
image_run_ids.append(run_id)
252+
else:
253+
# concatenate all images in pdf_images into a single image
254+
width = max([image.width for image in pdf_images])
255+
height = sum([image.height for image in pdf_images])
256+
new_image = PIL.Image.new("RGB", (width, height))
257+
y_offset = 0
258+
for image in pdf_images:
259+
new_image.paste(image, (0, y_offset))
260+
y_offset += image.height
261+
images.append(new_image)
262+
# images.append(ImageFile(new_image.tobytes("png")))
263+
image_run_ids.append(run_id)
264+
265+
if len(images) == 0:
266+
display(f"no saved plots for runs {image_not_found_run_ids}") # type: ignore
267+
return
268+
269+
# determine number of columns and rows, start with max_columns and reduce to two columns
270+
# until the number of images is divisible (or almost divisible) by number of columns
271+
ncols = min(len(images), max_columns)
272+
while len(images) % ncols != 0 and len(images) % ncols != (ncols - 1) and ncols > 2:
273+
ncols -= 1
274+
nrows = len(images) // ncols + 1
275+
276+
width_pixels = max([image.width for image in images]) * ncols
277+
height_pixels = max([image.height for image in images]) * nrows
278+
height_in = width_in * height_pixels / width_pixels
279+
figsize = (width_in, height_in)
280+
fig, ax = plt.subplots(ncols=ncols, nrows=nrows, figsize=figsize)
281+
ax = ax.flatten()
282+
for i in range(nrows * ncols):
283+
if i >= len(images):
284+
ax[i].axis("off")
285+
ax[i].set_title("")
286+
ax[i].set_visible(False)
287+
else:
288+
ax[i].axis("off")
289+
ax[i].set_title(f"{sim_version_names[i]}")
290+
ax[i].imshow(images[i])
291+
292+
plt.tight_layout()
293+
plt.show()
294+
295+
def _retrieve_pdf_as_image(self, run_id: str, http: urllib3.PoolManager) -> list[PngImageFile]:
296+
pdf_url = f"https://api.biosimulations.org/results/{run_id}/download"
297+
images: list[PngImageFile] = []
298+
head_response = http.request("HEAD", pdf_url)
299+
if head_response.status == 200:
300+
response = http.request("GET", pdf_url)
301+
with BytesIO(response.data) as zip_buffer, ZipFile(zip_buffer, "a", ZIP_DEFLATED, False) as zip_file:
302+
file_names = zip_file.namelist()
303+
for file_name in file_names:
304+
if file_name.endswith(".pdf") and file_name.lower().index("plot") != -1:
305+
with zip_file.open(file_name) as pdf_file:
306+
content = pdf_file.read()
307+
with fitz.open(stream=content, filetype="pdf") as doc:
308+
for page in doc:
309+
pix: fitz.Pixmap = page.get_pixmap()
310+
png_content = pix.tobytes("png")
311+
images.append(PIL.Image.open(BytesIO(png_content))) # type: ignore
312+
return images
313+
232314

233315
def _extract_dataset_attr(
234316
dataset_name: str, attr_key: str, hdf5_file: BiosimHDF5File | SimDataHDF5File

notebooks/verify-demo.ipynb

+12-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@
2727
{
2828
"cell_type": "code",
2929
"execution_count": null,
30-
"id": "75fa30ae7e01483c",
30+
"id": "1cec8049-5ca4-4b45-b68c-773cc7fc8a11",
31+
"metadata": {},
32+
"outputs": [],
33+
"source": [
34+
"run.show_saved_plots(max_columns=4, width_in=10)"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": null,
40+
"id": "82c8576f-030b-4e31-ba95-7f93cb4a5637",
3141
"metadata": {},
3242
"outputs": [],
3343
"source": [
@@ -45,6 +55,7 @@
4555
"omex_file = \"/Users/jimschaff/Documents/workspace/biosim-client/tests/fixtures/data/BIOMD0000000010_tellurium_Negative_feedback_and_ultrasen.omex\"\n",
4656
"run_2: VerifyResults = BiosimClient().compare_omex(omex_path=omex_file, simulators=simulators)\n",
4757
"run_2.show_report()\n",
58+
"run_2.show_saved_plots()\n",
4859
"run_2.show_dataset_heatmaps()"
4960
]
5061
},

0 commit comments

Comments
 (0)