Skip to content

Commit e8ec9d0

Browse files
authored
Merge pull request #45 from elmbeech/master
pcdl release v4.1.6
2 parents e95d774 + e7343af commit e8ec9d0

17 files changed

Lines changed: 678 additions & 202 deletions

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,19 @@ Developers, please make pull requests to the https://github.com/elmbeech/physice
130130

131131

132132
## Release Notes:
133-
+ version 4.1.5 (2026-04-05)
133+
134+
+ version 4.1.6 (2026-08-20): elmbeech/physicelldataloader
135+
+ pcdl installs now by default lightweight with only the core dependencies; users are prompted to install optional libraries if necessary. special thanks to Heber Rocha!
136+
137+
+ version 4.1.5 (2026-04-05): elmbeech/physicelldataloader
134138
+ bugfix library dependencies and library versions.
135139

136-
+ version 4.1.4 (2026-04-04)
140+
+ version 4.1.4 (2026-04-04): elmbeech/physicelldataloader
137141
+ pyMCDSts **mcdsts.make_contour** extrema parameter is replaced by vmin and vmax to be compatible with mcds.make\_contour and plt.contour.
138142
+ **make_cell_vtk** and **make_conc_vtk** now offer an ext parameter, allowing to manually specify the exact file extension. special thanks to Danyon Gedris!
139143
+ new **make_muspan** TimeStep class and TimeSeris class function and **pcdl_get_muspan** command line command. special thanks to Joshua Moore and Joshua Bull!
140144

141-
+ version 4.1.3 (2026-03-21):
145+
+ version 4.1.3 (2026-03-21): elmbeech/physicelldataloader
142146
+ new **pcdl.pccmap** color map. this is an adaptation of the physicell pathology paint by number color map.
143147
+ new TimeStep **get_pcdl_version** function.
144148

man/HOWTO.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ Below you will find information about how to install, load, update, uninstall, a
88
```bash
99
pip3 install pcdl
1010
```
11+
This installs pcdl light weight: only the libraries needed to load PhysiCell output into pandas dataframes and to render basic contour and scatter plots (matplotlib, numpy, pandas, scipy).
12+
A handful of pcdl functions (e.g. get\_anndata) rely on other libraryes (anndata).
13+
If you call such a function and a necessary library is missing, the function will prompt you to install it.
14+
15+
To install pcdl with all optional dependencies at once, run:
16+
```bash
17+
pip3 install pcdl[full]
18+
```
1119

1220

1321
## How to load the pcdl library?

man/TUTORIAL_blender.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ This is why the ext parameter explicitly has to be set.
1919
pcdl_make_conc_vtk output
2020
```
2121
```bash
22-
pcdl_make_cell_vtk output --ext .vtp # blender bvtk nodes compatible filename and extension.
22+
pcdl_make_cell_vtk output --ext .vtp # generate blender bvtk nodes compatible filename and extension!
2323
```
2424

2525
### Generate vtk files from within python
@@ -29,7 +29,7 @@ import pcdl
2929

3030
mcdsts = pcdl.TimeSeries('output/')
3131
mcdsts.make_conc_vtk()
32-
mcdsts.make_cell_vtk(ext='.vtp') # blender bvtk nodes compatible filename and extension.
32+
mcdsts.make_cell_vtk(ext='.vtp') # generate blender bvtk nodes compatible filename and extension!
3333
```
3434

3535
### Blender vtk nodes plugin installation
@@ -49,6 +49,21 @@ including workspace setup.
4949
5. tkXMLPolyDataReader FileName: path/to/output00000000.vtp
5050
6. VTKtoBlenderMesh: click Update Node
5151

52+
### Load vtk polynomial data vtk files as a time course
53+
54+
Special thank to Danyon Gedris from the Stein-O'Brien Lab, who figuring all of this out!
55+
56+
1. In the BVTK Node Tree Workspace from the previous section, right click Add / Custom / TimeSelector.
57+
2. Connect vtkXMLPolyDataReader output with input TimeSelector.
58+
3. Connect TimeSelector output with input VTKtoBlenderMesh.
59+
4. In the BVTK Node Editor, press N to open the sidebar.
60+
5. Select the Inspect tab and change: Update Mode → Update All Automatically.
61+
6. Click update node on each element in the BVTK Node Editor and force update upstream on VTKtoBlenderMesh.
62+
7. Set your scene frame range to:
63+
+ Start = 1 (this selects output00000000.vtp).
64+
+ End = # of .vtp files in the sequence (the last output file number plus 1).
65+
8. Press play on the animation.
66+
5267
### Load rectilinear grid vtk files
5368

5469
I was not able to bridge that data yet.

man/TUTORIAL_matlab_octave.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pcdl_get_cell_df output
3333
df_conc = readtable("output/timeseries_conc.csv")
3434
```
3535
```matlab
36-
df_cell = readtable("output/timeseries_conc.csv")
36+
df_cell = readtable("output/timeseries_cell.csv")
3737
```
3838

3939

pcdl/VERSION.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '4.1.5'
1+
__version__ = '4.1.6'

pcdl/dependency.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
######
2+
# title: dependency.py
3+
#
4+
# language: python3
5+
# date: 2026-08-14
6+
# license: BSD-3-Clause
7+
# author: Elmar Bucher, Heber Rocha, Claude Anthropic
8+
#
9+
# description:
10+
# pcdl, by default, only installs and imports lightweight core dependencies.
11+
# the code below deals with installation and import of the more specialized
12+
# heavyweight libraries.
13+
######
14+
15+
16+
# library
17+
import importlib
18+
19+
20+
# function
21+
def optional_import(s_module, s_attr=None, s_pip=None, s_caller=None):
22+
"""
23+
input:
24+
s_module: string
25+
dotted module path to import, e.g. 'anndata' or 'bioio.writers'.
26+
27+
s_attr: string; default None
28+
if given, equivalent to from s_module import s_attr.
29+
30+
s_pip: string; default None
31+
pip install name for the module, if this differs from s_module
32+
(e.g. s_module='skimage' but s_pip='scikit-image').
33+
if None, the first dot-separated part of s_module is used.
34+
35+
s_caller: string; default None
36+
name of the calling pcdl function, to mention in the error message.
37+
38+
output:
39+
the imported module, or, if s_attr is given, the requested attribute
40+
of the imported module.
41+
42+
description:
43+
function lazily load an optional pcdl dependency.
44+
if the library is not installed, an error message is raised,
45+
pointing the user to install the missing library.
46+
"""
47+
s_pip = s_module.split('.')[0] if (s_pip is None) else s_pip
48+
try:
49+
o_module = importlib.import_module(s_module)
50+
except ImportError as e:
51+
s_fct = s_caller if not (s_caller is None) else ' '
52+
raise ModuleNotFoundError(
53+
f"Error{s_fct}: this functionality requires the optional dependency '{s_pip}', which is not installed.\n" +
54+
f"pcdl was installed light weight (default), without this and other heavyweight, specialized libraries.\n" +
55+
f"to fix this, either:\n" +
56+
f"+ install the missing library manually: pip install {s_pip}\n" +
57+
f"+ or install pcdl with all optional dependencies: pip install pcdl[full]\n"
58+
) from e
59+
return o_module if (s_attr is None) else getattr(o_module, s_attr)

pcdl/neuromancer.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,18 @@
3939

4040
# library
4141
import argparse
42-
from bioio import BioImage
4342
import matplotlib as mpl
44-
import neuroglancer
45-
import neuroglancer.cli
4643
import numpy as np
47-
from skimage import exposure, util
4844
import sys
4945

46+
# lazy loading heavyweight: bioio, neuroglancer, and scikit-image
47+
from pcdl.dependency import optional_import
48+
BioImage = optional_import('bioio', s_attr='BioImage', s_pip='bioio', s_caller='pcdl.render_neuroglancer')
49+
neuroglancer = optional_import('neuroglancer', s_caller='pcdl.render_neuroglancer')
50+
optional_import('neuroglancer.cli', s_caller='pcdl.render_neuroglancer') # binds neuroglancer.cli as attribute
51+
exposure = optional_import('skimage.exposure', s_pip='scikit-image', s_caller='pcdl.render_neuroglancer')
52+
util = optional_import('skimage.util', s_pip='scikit-image', s_caller='pcdl.render_neuroglancer')
53+
5054

5155
# functions
5256
def ometiff2neuro(

pcdl/output_data.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import os
2424
import pathlib
2525
import pcdl
26-
import requests
26+
from pcdl.dependency import optional_import
2727
import shutil
2828
import tarfile
2929

@@ -40,6 +40,9 @@ class install_data:
4040
function to install a 2D and 3D PhysiCell output test dataset.
4141
"""
4242
def __init__(self):
43+
# load optional dependency
44+
requests = optional_import('requests', s_caller='pcdl.install_data')
45+
4346
# get pcdl library installation path
4447
s_path = str(pathlib.Path(pcdl.__file__).parent).replace('\\','/') + '/'
4548

pcdl/timeseries.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616

1717

1818
# load libraries
19-
import anndata as ad
20-
import bioio_base
21-
from bioio.writers import OmeTiffWriter
2219
import glob
2320
import matplotlib.pyplot as plt
2421
import numpy as np
2522
import os
2623
import pandas as pd
2724
from pcdl import render_neuroglancer
25+
from pcdl.dependency import optional_import
2826
from pcdl.timestep import TimeStep, es_coor_cell, es_coor_conc, _anndextract
2927
from pcdl.VERSION import __version__
3028
import platform
@@ -1176,6 +1174,11 @@ def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=Tr
11761174

11771175
# output 11 ometiff file
11781176
elif (file and collapse): # 11
1177+
# load optional dependency
1178+
OmeTiffWriter = optional_import('bioio.writers', s_attr='OmeTiffWriter', s_pip='bioio', s_caller='TimeSeries.make_ome_tiff')
1179+
bioio_base = optional_import('bioio_base', s_pip='bioio', s_caller='TimeSeries.make_ome_tiff')
1180+
1181+
# numpy array
11791182
a_tczyx_img = np.array(l_tczyx_img)
11801183
if self.verbose:
11811184
print('a_tczyx_img shape:', a_tczyx_img.shape)
@@ -1661,6 +1664,9 @@ def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs', collapse
16611664
function to transform mcds time steps into one or many
16621665
anndata objects for downstream analysis.
16631666
"""
1667+
# load optional dependency
1668+
ad = optional_import('anndata', s_caller='TimeSeries.get_anndata')
1669+
16641670
# initialize vaiable
16651671
l_annmcds = []
16661672
df_anncount = None

pcdl/timestep.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,22 @@
1515

1616

1717
# load library
18-
import anndata as ad
19-
import bioio_base
20-
from bioio.writers import OmeTiffWriter
21-
import geopandas as gpd
2218
import matplotlib.pyplot as plt
2319
from matplotlib import cm
2420
from matplotlib import colors
2521
try:
2622
import muspan as ms
2723
except ModuleNotFoundError:
2824
ms = None
29-
import networkx as nx
30-
import neuroglancer
3125
import numpy as np
3226
import os
3327
import pandas as pd
3428
from pcdl import imagine
3529
from pcdl import pdplt
36-
from pcdl import neuromancer
30+
from pcdl.dependency import optional_import
3731
from scipy import io
3832
from scipy import sparse
39-
import shapely
40-
import spatialdata as sd
4133
import sys
42-
import vtk
4334
import warnings
4435
import xml.etree.ElementTree as etree
4536
from pcdl.VERSION import __version__
@@ -223,6 +214,10 @@ def render_neuroglancer(tiffpathfile, timestep=0, intensity_cmap='gray'):
223214
function to load a time step from an ome tiff files, generated
224215
with make_ome_tiff, into neuroglancer.
225216
"""
217+
# load optional dependencies (neuromancer itself lazy loads bioio and scikit-image)
218+
neuroglancer = optional_import('neuroglancer', s_caller='pcdl.render_neuroglancer')
219+
from pcdl import neuromancer
220+
226221
# start neuroglancer
227222
viewer = neuroglancer.Viewer()
228223
with viewer.txn() as state:
@@ -1404,6 +1399,9 @@ def make_conc_vtk(self, ext='_conc.vtr'):
14041399
14051400
https://www.paraview.org/
14061401
"""
1402+
# load optional dependency
1403+
vtk = optional_import('vtk', s_caller='TimeStep.make_conc_vtk')
1404+
14071405
# off we go.
14081406
s_vtkfile = self.xmlfile.replace('.xml', ext)
14091407
if self.verbose:
@@ -1896,6 +1894,9 @@ def make_cell_vtk(self, attribute=['cell_type'], ext='_cell.vtp'):
18961894
18971895
https://www.paraview.org/
18981896
"""
1897+
# load optional dependency
1898+
vtk = optional_import('vtk', s_caller='TimeStep.make_cell_vtk')
1899+
18991900
# off we go.
19001901
s_vtkfile = self.xmlfile.replace('.xml', ext)
19011902
if self.verbose:
@@ -2031,6 +2032,11 @@ def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=Tr
20312032
https://napari.org/stable/
20322033
https://fiji.sc/
20332034
"""
2035+
# load optional dependencies
2036+
if file:
2037+
OmeTiffWriter = optional_import('bioio.writers', s_attr='OmeTiffWriter', s_pip='bioio', s_caller='TimeStep.make_ome_tiff')
2038+
bioio_base = optional_import('bioio_base', s_pip='bioio', s_caller='TimeStep.make_ome_tiff')
2039+
20342040
# handle channels
20352041
ls_substrate = self.get_substrate_list()
20362042
ls_celltype = self.get_celltype_list()
@@ -2404,6 +2410,9 @@ def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs'):
24042410
function to transform a mcds time step into an anndata object
24052411
for downstream analysis.
24062412
"""
2413+
# load optional dependency
2414+
ad = optional_import('anndata', s_caller='TimeStep.get_anndata')
2415+
24072416
# processing
24082417
if self.verbose:
24092418
print(f'processing: 1/1 {round(self.get_time(),9)}[min] mcds into anndata obj.')
@@ -2479,6 +2488,12 @@ def get_spatialdata(self, images={'subs'}, labels={}, points={'subs'}, shapes={'
24792488
function to transform a mcds time step into
24802489
a spatialdata object for downstream analysis.
24812490
"""
2491+
# load optional dependencies
2492+
ad = optional_import('anndata', s_caller='TimeStep.get_spatialdata')
2493+
sd = optional_import('spatialdata', s_caller='TimeStep.get_spatialdata')
2494+
shapely = optional_import('shapely', s_caller='TimeStep.get_spatialdata')
2495+
gpd = optional_import('geopandas', s_caller='TimeStep.get_spatialdata')
2496+
24822497
# set table spatial element links
24832498
s_region_subs = None
24842499
s_region_cell = None
@@ -2720,9 +2735,12 @@ def get_muspan(self, z_slice=None, values=1, drop=set(), keep=set()):
27202735
+ https://docs.muspan.co.uk/latest/Documentation.html
27212736
"""
27222737
# check if muspan library is installed
2723-
if ms is None:
2738+
if (ms is None) or (ms.__file__ is None):
27242739
sys.exit(f'Error @ TimeStep.get_muspa : the muspan Multi Spatial Analysis python3 library is not installed!\nfor instructions check out : https://www.muspan.co.uk/')
27252740

2741+
# load optional dependency
2742+
nx = optional_import('networkx', s_caller='TimeStep.get_muspan')
2743+
27262744
# get conc and cell dataframe
27272745
df_conc = self.get_conc_df(values=values, drop=drop, keep=keep)
27282746
df_cell = self.get_cell_df(values=values, drop=drop, keep=keep)

0 commit comments

Comments
 (0)