|
| 1 | +from io import BytesIO |
1 | 2 | from typing import TypeAlias
|
| 3 | +from zipfile import ZIP_DEFLATED, ZipFile |
2 | 4 |
|
| 5 | +import fitz # type: ignore |
3 | 6 | import matplotlib.pyplot as plt
|
4 | 7 | import numpy as np
|
| 8 | +import PIL |
| 9 | +import urllib3 |
5 | 10 | from IPython.display import Markdown, display
|
| 11 | +from PIL.Image import Image |
| 12 | +from PIL.PngImagePlugin import PngImageFile |
6 | 13 |
|
7 | 14 | from biosim_client.api.biosim.models.biosim_simulation_run import BiosimSimulationRun
|
8 | 15 | from biosim_client.api.biosim.models.biosimulator_version import BiosimulatorVersion
|
@@ -229,6 +236,81 @@ def _create_markdown_report(self) -> str:
|
229 | 236 | simulator_markdown += f"- no match for {run_link_map[run_group[0].id]} (closest score {min_score})\n"
|
230 | 237 | return simulator_markdown
|
231 | 238 |
|
| 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 | + |
232 | 314 |
|
233 | 315 | def _extract_dataset_attr(
|
234 | 316 | dataset_name: str, attr_key: str, hdf5_file: BiosimHDF5File | SimDataHDF5File
|
|
0 commit comments