From 95c239488fa1edeba44c959fcad8b7d9b6d7fa54 Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 13 Mar 2025 06:19:51 -0400 Subject: [PATCH 01/41] @ pcdl : towards release v3.3.5. --- .github/workflows/apple.yml | 6 +- .github/workflows/linux.yml | 6 +- .github/workflows/windows.yml | 6 +- README.md | 17 +- man/REFERENCE.md | 13 +- man/TUTORIAL_blender.md | 28 - man/TUTORIAL_commandline.md | 31 - man/TUTORIAL_fijiimagej.md | 51 -- man/TUTORIAL_julia.md | 18 +- man/TUTORIAL_paraview.md | 8 +- man/TUTORIAL_python3_napari.md | 54 -- man/TUTORIAL_python3_ometiff.md | 43 +- man/TUTORIAL_python3_timeseries.md | 3 - man/TUTORIAL_python3_timestep.md | 28 - man/docstring/mcds.make_ome_tiff.md | 50 -- man/docstring/mcdsts.make_ome_tiff.md | 55 -- man/docstring/pcdl_get_anndata.md | 83 --- man/docstring/pcdl_get_cell_attribute.md | 63 -- man/docstring/pcdl_get_cell_df.md | 49 -- man/docstring/pcdl_get_celltype_list.md | 23 - man/docstring/pcdl_get_conc_attribute.md | 39 -- man/docstring/pcdl_get_conc_df.md | 35 -- man/docstring/pcdl_get_substrate_list.md | 16 - man/docstring/pcdl_get_unit_dict.md | 27 - man/docstring/pcdl_get_version.md | 16 - man/docstring/pcdl_make_cell_vtk.md | 43 -- man/docstring/pcdl_make_conc_vtk.md | 18 - man/docstring/pcdl_make_gif.md | 17 - man/docstring/pcdl_make_graph_gml.md | 58 -- man/docstring/pcdl_make_movie.md | 23 - man/docstring/pcdl_make_ome_tiff.md | 56 -- man/docstring/pcdl_plot_contour.md | 61 -- man/docstring/pcdl_plot_scatter.md | 96 --- man/docstring/pcdl_plot_timeseries.md | 112 ---- man/scarab.py | 11 - pcdl/imagine.py | 745 ----------------------- pcdl/ometiff2neuro.py | 266 -------- pcdl/pdplt.py | 132 ---- pcdl/pyCLI.py | 148 ----- pcdl/pyMCDS.py | 305 +++------- pcdl/pyMCDSts.py | 137 ----- pyproject.toml | 2 - test/test_cli_2d.py | 189 ------ test/test_timeseries_2d.py | 46 -- test/test_timeseries_3d.py | 25 - test/test_timestep_2d.py | 78 --- test/test_timestep_3d.py | 22 - 47 files changed, 97 insertions(+), 3261 deletions(-) delete mode 100644 man/TUTORIAL_fijiimagej.md delete mode 100644 man/TUTORIAL_python3_napari.md delete mode 100644 man/docstring/mcds.make_ome_tiff.md delete mode 100644 man/docstring/mcdsts.make_ome_tiff.md delete mode 100644 man/docstring/pcdl_make_ome_tiff.md delete mode 100644 pcdl/imagine.py delete mode 100644 pcdl/ometiff2neuro.py delete mode 100644 pcdl/pdplt.py diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index bb81a02..946a1cc 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on mac os x; the latest pytho on: push: - branches: ["master", "v3", "v4"] - #pull_request: - # branches: ["master", "v3", "v4"] + branches: ["v3"] jobs: build-macosx: @@ -33,7 +31,7 @@ jobs: run: | brew install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install /Users/runner/work/physicelldataloader/physicelldataloader -v #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: lint with flake8 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 00914ed..9ae17bc 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on linux os; all python3 vers on: push: - branches: ["master", "v3", "v4"] - #pull_request: - # branches: ["master", "v3", "v4"] + branches: ["v3"] jobs: build-linux: @@ -33,7 +31,7 @@ jobs: run: | sudo apt install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install /home/runner/work/physicelldataloader/physicelldataloader -v #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: lint with flake8 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a763ab1..088247d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on windows os; the latest pyt on: push: - branches: ["master", "v3", "v4"] - #pull_request: - # branches: ["master", "v3", "v4"] + branches: ["v3"] jobs: build-windows: @@ -33,7 +31,7 @@ jobs: run: | choco install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install D:\a\physicelldataloader\physicelldataloader -v #echo 'set PYTHONPATH=D:\a\physicelldataloader\physicelldataloader' >> $GITHUB_ENV #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi diff --git a/README.md b/README.md index e884ad2..e83450d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The pcdl python3 library maintains three branches: ## Header: + Language: python [>= 3.9](https://devguide.python.org/versions/) -+ Library dependencies: anndata, bioio, matplotlib, numpy, pandas, (requests), scipy, vtk ++ Library dependencies: anndata, matplotlib, numpy, pandas, (requests), scipy, vtk + Date of origin original PhysiCell-Tools python-loader: 2019-09-02 + Date of origin pcdl fork: 2022-08-30 + Doi: https://doi.org/10.5281/ZENODO.8176399 @@ -49,7 +49,7 @@ Extras tutorials python3 language: + [pcdl and python3 and graphs](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_graph.md) + [pcdl and python3 and matplotlib](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_matplotlib.md) + [pcdl and python3 and vtk](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_vtk.md) -+ [pcdl and python3 and ome.tiff, tiff, png, and jpeg](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_ometiff.md) ++ [pcdl and python3 and tiff, png, and jpeg](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_ometiff.md) + [pcdl and python3 and napari](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_napari.md) Extras tutorials for other languages than python3: @@ -62,8 +62,6 @@ Extras tutorials for GUI software: + [pcdl and paraview](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_paraview.md) + [pcdl and blender](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_blender.md) -+ [pcdl and napari](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_napari.md) -+ [pcdl and fiji imagej](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_fijiimagej.md) @@ -120,14 +118,13 @@ Developers, please make pull requests to the https://github.com/elmbeech/physice ## Road Map: -+ evt generate lineage tree graph output files. -+ evt add neuroglancer ome.tiff support. -+ evt add DataDiVR support. - ## Release Notes: -+ version 3.3.4 (2025-03-07): elmbeech/physicelldataloader ++ version 3.3.5 (2025-03-xx): elmbeech/physicelldataloader + + remove pyMCDS and pyMCDSts **make_ome_tiff** and pyCLI **pcdl_make_ome_tiff** removed to make pyMCS.py stand alone again. + ++ version 3.3.4 (2025-03-07): elmbeech/physicelldataloader + replace the **aicsimageio** library dependency with its successor **bioio**. special thanks to Joel Eliason! - + **make_ome_tiff** can handle automatically generated file names with > 255 characters. special thank to Genevieve Stein-O'Brien and DanielBergman! + + **make_ome_tiff** can now handel generated file names with > 255 characters. special thank to Genevieve Stein-O'Brien and DanielBergman! + **get_mesh_spacing** handels now an edge case correctly that would have resulted in a division by zero. special thanks to Randy Heiland! + version 3.3.3 (2025-01-10): elmbeech/physicelldataloader diff --git a/man/REFERENCE.md b/man/REFERENCE.md index 32c7e48..a4af6c7 100644 --- a/man/REFERENCE.md +++ b/man/REFERENCE.md @@ -34,7 +34,7 @@ Familiarize yourself well with their parameters! Basically, there are four types of functions: + set_ : set a python3 variable. + get_ : recall a python3 variable. -+ make_ : make functions generate file output (gml, ome.tiff, vtk). ++ make_ : make functions generate file output (gml, vtk). + plot_ : plot functions generate a matplotlib figure, or axis object, or file output (jpeg, png, tiff), depending on your parameter settings. ### TimeStep initialize @@ -100,9 +100,6 @@ Basically, there are four types of functions: + [help(mcds.get_neighbor_graph_dict)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_neighbor_graph_dict.md) + [help(mcds.make_graph_gml)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_graph_gml.md) #! workhose function -### TimeStep microenvironment and cells -+ [help(mcds.make_ome_tiff)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_ome_tiff.md) #! workhose function - ### TimeStep internal functions + [help(pcdl.scaler)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl.scaler.md) # anndata + [help(pcdl.graphfile_parser)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl.graphfile_parser.md) # mcds @@ -117,7 +114,7 @@ help(pcdl.pyAnnData._anndextract) Basically, there are four types of functions: + set_ : set a python3 variable. + get_ : recall a python3 variable. -+ make_ : make functions generate file output (gif, gml, mp4, ome.tiff, vtk). ++ make_ : make functions generate file output (gif, gml, mp4, vtk). + plot_ : plot functions generate a matplotlib figure, or axis object, or file output (jpeg, png, tiff), depending on your parameter settings. ### TimeSeries initialization @@ -145,9 +142,6 @@ Basically, there are four types of functions: ### TimeSeries cell graph + [help(mcdsts.get_graph_gml)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.make_graph_gml.md) #! workhose function -### TimeSteries microenvironment and cells -+ [help(mcdsts.make_ome_tiff)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.make_ome_tiff.md) #! workhose function - ### Timeseries timeseries + [help(mcdsts.plot_timeseries)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.plot_timeseries.md) #! workhorse function @@ -194,9 +188,6 @@ The command line interface functions mimic the name and parameter arguments as c ### Command line cell graph + [pcdl_make_graph_gml --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_make_graph_gml.md) #! workhorse function -### Command line cells and microenvironment -+ [pcdl_make_ome_tiff --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_make_ome_tiff.md) #! workhorse function - ### Command line timeseries + [pcdl_plot_timeseries --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_plot_timeseries.md) #! workhorse function diff --git a/man/TUTORIAL_blender.md b/man/TUTORIAL_blender.md index d5e05ca..f9fd8eb 100644 --- a/man/TUTORIAL_blender.md +++ b/man/TUTORIAL_blender.md @@ -3,7 +3,6 @@ [Blender](https://www.blender.org/) is a modeling, rigging, animation, simulation, rendering, compositing, motion tracking, video editing, and game creation software. Blender is free and open source. There exists a vtk nodes plugin, that lets us load vtk polynomial data files. -And there exists a bioxel nodes plugin, that lets us load ome tiff files. ## ✨ Handle vtk files @@ -64,31 +63,4 @@ To learn more about Blender and BVTK Node plugin, please study the official docu + https://docs.blender.org/manual/en/latest/ -## ✨ Handle ome tiff files - -The blender bioxel nodes plugin allows us load single time step ome tiff files into blender. - -### Generate ome tiff files from the command line - -```bash -pcdl_make_ome_tiff output --collapse false -``` - -### Generate ome tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff(collapse=False) -``` - -### The blender bioxel nodes plugin - -Please follow the official bioxel nodes instructions for installation -and to learn how to use the plugin. - -+ https://omoolab.github.io/BioxelNodes/latest/ - - That's it! The rest is analysis within blender! diff --git a/man/TUTORIAL_commandline.md b/man/TUTORIAL_commandline.md index 3023a39..61b0429 100644 --- a/man/TUTORIAL_commandline.md +++ b/man/TUTORIAL_commandline.md @@ -387,37 +387,6 @@ pcdl_plot_timeseries -h ``` -### ✨ pcdl\_make\_ome\_tiff - -Generate an [ome.tiff](https://ome-model.readthedocs.io/en/stable/index.html) file, -to analyze a single time step or the whole time series, -the same way as usually fluorescent microscopy data is analyzed. - -By default, the cell\_attribute outputted is the cell ID + 1. -However, any numerical (bool, int, float) cell\_attribute can be outputted. -For example: dead, cells\_per\_voxel, or pressure. - -These ome.tiff files can be further analyzed, -for example with the [napari](https://napari.org/stable/) or [fiji imagej](https://fiji.sc/) or [blender](https://www.blender.org/) software, -as described in the extra tutorials. - -```bash -pcdl_make_ome_tiff output/output00000000.xml pressure -``` -```bash -pcdl_make_ome_tiff output -``` -```bash -pcdl_make_ome_tiff -h -``` - -Further readings: -+ [TUTORIAL_python3_napari.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_napari.md) -+ [TUTORIAL_fiji_imagej.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_fijiimagej.md) -+ [TUTORIAL_blender.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_blender.md) - - - ## [Making movies](https://en.wikipedia.org/wiki/Making_Movies) diff --git a/man/TUTORIAL_fijiimagej.md b/man/TUTORIAL_fijiimagej.md deleted file mode 100644 index 2a215a1..0000000 --- a/man/TUTORIAL_fijiimagej.md +++ /dev/null @@ -1,51 +0,0 @@ -# PhysiCell Data Loader Tutorial: pcdl and Fiji Imagej - -[Fiji](https://fiji.sc/) is a free and open source image processing library, -a "batteries-included" distribution of [ImageJ](https://en.wikipedia.org/wiki/ImageJ), -able to read [ome.tiff](https://www.openmicroscopy.org/ome-files/) files. - -Fiji Imagej is used by wetlab scientists and bioinformatician to analyze fluorescent microscopy data. - -## Install Fiji Imagej ~ the 64[bit] version! - -Please follow the installation instructions on the official homepage. -+ https://imagej.net/software/fiji/ - - -## Generate ome.tiff files from the command line - -```bash -pcdl_make_ome_tiff('output/') -``` - - -## Generate ome.tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff() -``` - -## Linux: open ome.tiff files in Fiji Imagej from the command line - -```bash -./ImageJ-linux64 path/to/output/timeseries_ID.ome.tiff -``` - - -## Windows: - - - -## MacOS X: - - - -## Running Fiji Imagej - -Please work through the official documentation to learn how to run the software. -+ https://imagej.net/learn/ - -That's it. The rest is analysis within fiij imagej! diff --git a/man/TUTORIAL_julia.md b/man/TUTORIAL_julia.md index b766b79..189d733 100644 --- a/man/TUTORIAL_julia.md +++ b/man/TUTORIAL_julia.md @@ -166,9 +166,9 @@ Please study the Muon and AnnData documentation to learn how to analyze this dat + https://github.com/scverse/anndata -## ✨ Handle ome.tiff, tiff, png, and jpeg file format +## ✨ Handle tiff, png, and jpeg file format -### Save pcdl data structures as jpeg, png, tiff, and ome.tiff files from the command line +### Save pcdl data structures as jpeg, png, and tiff files from the command line ```bash pcdl_plot_contour output/output00000021.xml oxygen --ext tiff @@ -179,16 +179,10 @@ pcdl_plot_contour output/output00000021.xml oxygen ```bash pcdl_plot_scatter output/output00000021.xml ``` -```bash -pcdl_make_ome_tiff('output/') -``` - -### Load jpeg, png, tiff, and ometiff files into a julia data structures -⚠ **bue 2024-09-04:** ome.tiff files currently cannot be loaded ( github issue: https://github.com/tlnagy/OMETIFF.jl/issues/112 ). +### Load jpeg, png, and tiff files into a julia data structures -We will use the [Images](https://github.com/JuliaImages/Images.jl) library, and it's [OMETIFF](https://github.com/tlnagy/OMETIFF.jl) extension, -to load jpeg, png, tiff, and ome.tiff files +We will use the [Images](https://github.com/JuliaImages/Images.jl) library to load jpeg, png, and tiff files Package installation. @@ -196,7 +190,6 @@ Package installation. using Pkg Pkg.add("FileIO") Pkg.add("Images") -Pkg.add("OMETIFF") ``` Load image file. @@ -206,9 +199,6 @@ using FileIO using Images ``` ```julia -omeimg = load("output/timeseries_ID.ome.tiff") -``` -```julia img = load("output/cell_cell_type_z0.0/output00000021_cell_type.jpeg") ``` diff --git a/man/TUTORIAL_paraview.md b/man/TUTORIAL_paraview.md index 8d94ced..fda8707 100644 --- a/man/TUTORIAL_paraview.md +++ b/man/TUTORIAL_paraview.md @@ -1,13 +1,7 @@ # PhysiCell Data Loader Tutorial: pcdl and Paraview - - [Paraview](https://www.paraview.org/) is a free and open source scientific visualization software, -that lets us load and analyze vtk rectilinear grid data and vtk polynomial data. +that lets us load and analyze vtk rectilinear grid data and vtk polynomial data. ## Install paraview diff --git a/man/TUTORIAL_python3_napari.md b/man/TUTORIAL_python3_napari.md deleted file mode 100644 index e6e6908..0000000 --- a/man/TUTORIAL_python3_napari.md +++ /dev/null @@ -1,54 +0,0 @@ -# PhysiCell Data Loader Tutorial: pcdl and Python and Napari - -[Napari](https://napari.org/stable/) is both a python library and a GUI software. -Napari is used by wetlab scientists and bioinformatician to analyze fluorescent microscopy data. -Napari can read [ome.tiff](https://www.openmicroscopy.org/ome-files/) files. -https://github.com/AllenCellModeling/napari-aicsimageio - - -## Install napari - -```bash -pip3 install napari[all] -``` - - -### Generate ome.tiff files from the command line - -```bash -pcdl_make_ome_tiff('output/') -``` - - -### Generate ome.tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff() -``` - - -### Open ome.tiff files in napari from within python - -```python -import napari - -viewer = napari.Viewer() -viewer.open('output/timeseries_ID.ome.tiff') -``` - - -### Open ome.tiff files in napari from the command line - -```bash -napari output/timeseries_ID.ome.tiff -``` - -## Running napari - -Please work through the official documentation to learn how to run the software. -+ https://napari.org/stable/tutorials/start_index.html - -That's it. The rest is analysis within napari! diff --git a/man/TUTORIAL_python3_ometiff.md b/man/TUTORIAL_python3_ometiff.md index 1274219..58b91e2 100644 --- a/man/TUTORIAL_python3_ometiff.md +++ b/man/TUTORIAL_python3_ometiff.md @@ -1,27 +1,14 @@ # PhysiCell Data Loader Tutorial: pcdl and Python and the Ome.tiff, Tiff, Png, and Jpeg File Format -In pcdl output from [TimeSteps](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timestep.md) and [TimeSeries](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timeseries.md) can be stotred as [ome.tiff](https://www.openmicroscopy.org/ome-files/), [tiff](https://www.loc.gov/preservation/digital/formats/fdd/fdd000022.shtml), [png](http://libpng.org/pub/png/), and [jpeg](https://jpeg.org/jpeg/) files. +In pcdl output from [TimeSteps](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timestep.md) and [TimeSeries](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timeseries.md) can be stotred as [tiff](https://www.loc.gov/preservation/digital/formats/fdd/fdd000022.shtml), [png](http://libpng.org/pub/png/), and [jpeg](https://jpeg.org/jpeg/) files. Tiff, png, and jpeg are raster graphic file formats. -Ome.tiff is the open microscopy image standard. -This means, being able to export PhysiCell output in ome.tiff files format -enables us to study PhysiCell output the same way -as commonly fluorescent microscopy data is analyzed by wetlab scientists. -Please have a look at [TUTORIAL_python3_napari.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_napari.md) -and [TUTORIAL_fiji_imagej.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_fijiimagej.md) to learn more. +Additionally tiff, png, and jpeg files can as well be loaded back in to python as [numpy](https://numpy.org/) array, for example with the [sci-kit image](https://scikit-image.org/) library (image data only). -Additionally ome.tiff, tiff, png, and jpeg files can as well be loaded back in to python as [numpy](https://numpy.org/) array, for example with the [sci-kit image](https://scikit-image.org/) library (image data only). +### Save pcdl data constructs from the command line into tiff files -Besides that, ome.tiff files can be loaded with the [bioio](https://github.com/bioio-devs/bioio) library (image and metadata). - - -### Save pcdl data constructs from the command line into tiff and ome.tiff files - -```bash -pcdl_make_ome_tiff('output/') -``` ```bash pcdl_plot_contour output/output00000012.xml oxygen ``` @@ -30,7 +17,7 @@ pcdl_plot_scatter output/output00000012.xml ``` -### Save pcdl data constructs from within python into tiff and ome.tiff files +### Save pcdl data constructs from within python into tiff files ```python import pcdl @@ -38,7 +25,6 @@ import pcdl mcdsts = pcdl.TimeSeries('output/') mcdsts.get_mcds_list()[12].plot_contour(focus='oxygen', ext='tiff') mcdsts.get_mcds_list()[12].plot_scatter(ext='tiff') -mcdsts.make_ome_tiff() ``` @@ -56,27 +42,6 @@ from skimage import io a_cell = io.imread('output/cell_cell_type_z0.0/output00000012_cell_type.tiff') a_cell.shape # (480, 640, 4) ``` -```python -from skimage import io - -a_ome = io.imread('output/timeseries_ID.ome.tiff') -a_ome.shape # (25, 2, 200, 300) -``` -### Load ome.tiff files as BioImage object into python - -```python -from bioio import BioImage - -img = BioImage('output/timeseries_ID.ome.tiff') -img.shape # (25, 2, 1, 200, 300) -``` -```python -img.dims # -``` -```python -img.channel_names # [np.str_('oxygen'), np.str_('cancer_cell')] -``` - That's it. The rest is analysis! diff --git a/man/TUTORIAL_python3_timeseries.md b/man/TUTORIAL_python3_timeseries.md index 5950eaf..89eadbb 100644 --- a/man/TUTORIAL_python3_timeseries.md +++ b/man/TUTORIAL_python3_timeseries.md @@ -24,9 +24,6 @@ For cell data, these are the functions: + mcdsts.plot_scatter() + mcdsts.make_cell_vtk() -For microenvironment and cell data, this is the function: -+ mcdsts,make_ome_tiff() - Yet, there are additional functions, that only make sense for TimeSeries, and those functions will be discussed in this chapter. diff --git a/man/TUTORIAL_python3_timestep.md b/man/TUTORIAL_python3_timestep.md index 0f37503..3bce765 100644 --- a/man/TUTORIAL_python3_timestep.md +++ b/man/TUTORIAL_python3_timestep.md @@ -447,34 +447,6 @@ help(mcds.make_cell_vtk) Please have a look at [TUTORIAL_paraview.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_paraview.md) to learn more. - -## Microenvironment and Cell Data Related Functions - -### ✨ PhysiCell Data Analysis with [Napari](https://napari.org/stable/) and [Fiji Imagej](https://fiji.sc/) - -For substrate and cell agent visualization, data can be saved in open microscopy's [ome.tiff](https://www.openmicroscopy.org/ome-files/) file format. - -For cell agents, the default cell\_attribute outputted is the cell ID + 1, results in segmentation masks, -although, any numerical (bool, int, float) cell\_attribute can be outputted. - -```python -mcds.make_ome_tiff() # mark by cell ID + 1. -``` -```python -mcds.make_ome_tiff('dead') # mark dead and alive cells. -``` - -The tiff and ome.tiff files can be loaded back in to python as [numpy](https://numpy.org/) arrays. -Besides that, ome.tiff files enables us to study PhysiCell output the same way -as commonly fluorescent microscopy data is analyzed by wetlab scientists. -Please have a look at -[TUTORIAL_python3_ometiff.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_ometiff.md), -[TUTORIAL_python3_napari.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_napari.md) -and [TUTORIAL_fiji_imagej.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_fijiimagej.md) -to learn more. - - - ## Mesh Data Related Functions For data analysis, the functions related to the mesh are most probably the least one you have to deal with. diff --git a/man/docstring/mcds.make_ome_tiff.md b/man/docstring/mcds.make_ome_tiff.md deleted file mode 100644 index 8e278af..0000000 --- a/man/docstring/mcds.make_ome_tiff.md +++ /dev/null @@ -1,50 +0,0 @@ -# mcds.make_ome_tiff() - - -## input: -``` - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be - specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape czyx is the output. - -``` - -## output: -``` - a_czyx_img: numpy array or ome tiff file. - -``` - -## description: -``` - function to transform chosen mcds output into an 1[um] spaced - czyx (channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - an ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - -``` \ No newline at end of file diff --git a/man/docstring/mcdsts.make_ome_tiff.md b/man/docstring/mcdsts.make_ome_tiff.md deleted file mode 100644 index f9b9ca9..0000000 --- a/man/docstring/mcdsts.make_ome_tiff.md +++ /dev/null @@ -1,55 +0,0 @@ -# mcdsts.make_ome_tiff() - - -## input: -``` - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape tczyx is the output. - - collapse: boole; default True - should all mcds time steps from the time series be collapsed - into one ome tiff file (numpy array), - or an ome tiff file (numpy array) for each time step? - -``` - -## output: -``` - a_tczyx_img: numpy array or ome tiff file. - - -``` - -## description: -``` - function to transform chosen mcdsts output into an 1[um] spaced - tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - a ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - -``` \ No newline at end of file diff --git a/man/docstring/pcdl_get_anndata.md b/man/docstring/pcdl_get_anndata.md index 54e4763..47d4a37 100644 --- a/man/docstring/pcdl_get_anndata.md +++ b/man/docstring/pcdl_get_anndata.md @@ -1,85 +1,2 @@ ``` -usage: pcdl_get_anndata [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--graph GRAPH] - [--physiboss PHYSIBOSS] [--settingxml SETTINGXML] - [-v VERBOSE] [--drop [DROP ...]] [--keep [KEEP ...]] - [--scale SCALE] [--collapse COLLAPSE] - [path] [values] - -function to transform mcds time steps into one or many anndata objects for -downstream analysis. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - values minimal number of values a variable has to have in any - of the mcds time steps to be outputted. variables that - have only 1 state carry no information. None is a - state too. default is 1. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment be extracted and loaded - into the anndata object? setting microenv to False - will use less memory and speed up processing, similar - to the original pyMCDS_cells.py script. default is - True. - --graph GRAPH should neighbor graph, attach graph, and attached - spring graph be extracted and loaded into the anndata - object? default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the anndata object? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --drop [DROP ...] set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. Attention: when the - keep parameter is given, then the drop parameter has - to be an empty string! default is an empty string. - --keep [KEEP ...] set of column labels to be kept in the dataframe. set - values=1 to be sure that all variables are kept. don't - worry: essential columns like ID, coordinates and time - will always be kept. default is an empty string. - --scale SCALE specify how the data should be scaled. possible values - are None, maxabs, minmax, std. None: no scaling. set - scale to None if you would like to have raw data or - entirely scale, transform, and normalize the data - later. maxabs: maximum absolute value distance scaler - will linearly map all values into a [-1, 1] interval. - if the original data has no negative values, the - result will be the same as with the minmax scaler - (except with attributes with only one value). if the - attribute has only zeros, the value will be set to 0. - minmax: minimum maximum distance scaler will map all - values linearly into a [0, 1] interval. if the - attribute has only one value, the value will be set to - 0. std: standard deviation scaler will result in - sigmas. each attribute will be mean centered around 0. - ddof delta degree of freedom is set to 1 because it is - assumed that the values are samples out of the - population and not the entire population. it is - incomprehensible to me that the equivalent sklearn - method has ddof set to 0. if the attribute has only - one value, the value will be set to 0. default is - maxabs - --collapse COLLAPSE should all mcds time steps from the time series be - collapsed into one big anndata h5ad file, or a many - h5ad, one h5ad for each time step?, default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_cell_attribute.md b/man/docstring/pcdl_get_cell_attribute.md index 63628ab..47d4a37 100644 --- a/man/docstring/pcdl_get_cell_attribute.md +++ b/man/docstring/pcdl_get_cell_attribute.md @@ -1,65 +1,2 @@ ``` -usage: pcdl_get_cell_attribute [-h] - [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--drop [DROP ...]] [--keep [KEEP ...]] - [--allvalues ALLVALUES] - [path] [values] - -function to detect informative variables in a time series. this function -detects even variables which have less than the minimal state count in each -time step, but different values from time step to time step. the output is a -json file with an entry of all non-coordinate column names that at least in -one of the time steps or in between time steps, reach the given minimal value -count. key is the column name, mapped is a list of all values (bool, str, and, -if allvalues is True, int and float) or a list with minimum and maximum values -(int, float). - -positional arguments: - path path to the PhysiCell output directory. default is . . - values minimal number of values a variable has to have in any - of the mcds time steps to be outputted. variables that - have only 1 state carry no information. None is a - state too. default is 1. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --drop [DROP ...] set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. Attention: when the - keep parameter is given, then the drop parameter has - to be an empty string! default is an empty string. - --keep [KEEP ...] set of column labels to be kept in the dataframe. set - values=1 to be sure that all variables are kept. don't - worry: essential columns like ID, coordinates and time - will always be kept. default is an empty string. - --allvalues ALLVALUES - for numeric data, should only the min and max values - or all values be returned? default is false. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_cell_df.md b/man/docstring/pcdl_get_cell_df.md index f810166..47d4a37 100644 --- a/man/docstring/pcdl_get_cell_df.md +++ b/man/docstring/pcdl_get_cell_df.md @@ -1,51 +1,2 @@ ``` -usage: pcdl_get_cell_df [-h] [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--drop [DROP ...]] [--keep [KEEP ...]] - [--collapse COLLAPSE] - [path] [values] - -this function extracts dataframes with a cell centric view of the simulation -and saves them as csv files. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - values minimal number of values a variable has to have in any - of the mcds time steps to be outputted. variables that - have only 1 state carry no information. None is a - state too. default is 1. - -options: - -h, --help show this help message and exit - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --drop [DROP ...] set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. Attention: when the - keep parameter is given, then the drop parameter has - to be an empty string! default is an empty string. - --keep [KEEP ...] set of column labels to be kept in the dataframe. set - values=1 to be sure that all variables are kept. don't - worry: essential columns like ID, coordinates and time - will always be kept. default is an empty string. - --collapse COLLAPSE should all mcds time steps from the time series be - collapsed into one big csv, or a many csv, one csv for - each time step?, default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_celltype_list.md b/man/docstring/pcdl_get_celltype_list.md index 683c59b..47d4a37 100644 --- a/man/docstring/pcdl_get_celltype_list.md +++ b/man/docstring/pcdl_get_celltype_list.md @@ -1,25 +1,2 @@ ``` -usage: pcdl_get_celltype_list [-h] [--settingxml SETTINGXML] [-v VERBOSE] - [path] - -this function is returns a list with all celltype labels ordered by celltype -ID. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - -options: - -h, --help show this help message and exit - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to True for more text output, while - processing. default is False. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_conc_attribute.md b/man/docstring/pcdl_get_conc_attribute.md index 686cadc..47d4a37 100644 --- a/man/docstring/pcdl_get_conc_attribute.md +++ b/man/docstring/pcdl_get_conc_attribute.md @@ -1,41 +1,2 @@ ``` -usage: pcdl_get_conc_attribute [-h] [-v VERBOSE] [--drop [DROP ...]] - [--keep [KEEP ...]] [--allvalues ALLVALUES] - [path] [values] - -function to detect informative substrate concentration variables in a time -series. this function detects even variables which have less than the minimal -state count in each time step, but different values from time step to time -step. the output is a json file with an entry of all non-coordinate column -names that, at least in one of the time steps or in between time steps, reach -the given minimal value count. key is the column name, mapped is a list of all -values (bool, str, and, if allvalues is True, int and float) or a list with -minimum and maximum values (int, float). - -positional arguments: - path path to the PhysiCell output directory. default is . . - values minimal number of values a variable has to have in any - of the mcds time steps to be outputted. variables that - have only 1 state carry no information. None is a - state too. default is 1. - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --drop [DROP ...] set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. Attention: when the - keep parameter is given, then the drop parameter has - to be an empty string! default is an empty string. - --keep [KEEP ...] set of column labels to be kept in the dataframe. set - values=1 to be sure that all variables are kept. don't - worry: essential columns like ID, coordinates and time - will always be kept. default is an empty string. - --allvalues ALLVALUES - for numeric data, should only the min and max values - or all values be returned? default is false. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_conc_df.md b/man/docstring/pcdl_get_conc_df.md index 9fd93b4..47d4a37 100644 --- a/man/docstring/pcdl_get_conc_df.md +++ b/man/docstring/pcdl_get_conc_df.md @@ -1,37 +1,2 @@ ``` -usage: pcdl_get_conc_df [-h] [-v VERBOSE] [--drop [DROP ...]] - [--keep [KEEP ...]] [--collapse COLLAPSE] - [path] [values] - -this function extracts dataframes with concentration values for all chemical -species in all voxels and saves them as csv files. additionally, this -dataframe lists voxel and mesh center coordinates. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - values minimal number of values a variable has to have in any - of the mcds time steps to be outputted. variables that - have only 1 state carry no information. None is a - state too. default is 1. - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --drop [DROP ...] set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. Attention: when the - keep parameter is given, then the drop parameter has - to be an empty string! default is an empty string. - --keep [KEEP ...] set of column labels to be kept in the dataframe. set - values=1 to be sure that all variables are kept. don't - worry: essential columns like ID, coordinates and time - will always be kept. default is an empty string. - --collapse COLLAPSE should all mcds time steps from the time series be - collapsed into one big csv, or a many csv, one for - each time step? default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_substrate_list.md b/man/docstring/pcdl_get_substrate_list.md index 55cd668..47d4a37 100644 --- a/man/docstring/pcdl_get_substrate_list.md +++ b/man/docstring/pcdl_get_substrate_list.md @@ -1,18 +1,2 @@ ``` -usage: pcdl_get_substrate_list [-h] [-v VERBOSE] [path] - -this function is returns all chemical species names, modeled in the -microenvironment, ordered by subsrate ID. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to True for more text output, while - processing. default is False. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_unit_dict.md b/man/docstring/pcdl_get_unit_dict.md index 3f52826..47d4a37 100644 --- a/man/docstring/pcdl_get_unit_dict.md +++ b/man/docstring/pcdl_get_unit_dict.md @@ -1,29 +1,2 @@ ``` -usage: pcdl_get_unit_dict [-h] [--microenv MICROENV] [--settingxml SETTINGXML] - [-v VERBOSE] - [path] - -function returns a csv that lists all tracked variables from metadata, cell, -and microenvironment and maps them to their unit. - -positional arguments: - path path to the PhysiCell output directory. default is . . - -options: - -h, --help show this help message and exit - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_version.md b/man/docstring/pcdl_get_version.md index af37f6d..47d4a37 100644 --- a/man/docstring/pcdl_get_version.md +++ b/man/docstring/pcdl_get_version.md @@ -1,18 +1,2 @@ ``` -usage: pcdl_get_version [-h] [-v VERBOSE] [path] - -this function is extracting PhysiCell and MultiCellDS version from the dataset -and the installed pcdl module version. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to True for more text output, while - processing. default is False. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_cell_vtk.md b/man/docstring/pcdl_make_cell_vtk.md index a067d57..47d4a37 100644 --- a/man/docstring/pcdl_make_cell_vtk.md +++ b/man/docstring/pcdl_make_cell_vtk.md @@ -1,45 +1,2 @@ ``` -usage: pcdl_make_cell_vtk [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [path] [attribute ...] - -function that generates 3D glyph vtk file for cells. cells can have specified -attributes like cell_type, pressure, dead, etc. you can post-process this file -in other software like paraview (https://www.paraview.org/). - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - attribute listing of mcds.get_cell_df dataframe column names, - used for cell attributes. default is a single term: - cell_type. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_conc_vtk.md b/man/docstring/pcdl_make_conc_vtk.md index 3c80aaa..47d4a37 100644 --- a/man/docstring/pcdl_make_conc_vtk.md +++ b/man/docstring/pcdl_make_conc_vtk.md @@ -1,20 +1,2 @@ ``` -usage: pcdl_make_conc_vtk [-h] [-v VERBOSE] [path] - -function generates rectilinear grid vtk files, one per mcds time step, -contains distribution of substrates over microenvironment. you can post- -process this files in other software like paraview -(https://www.paraview.org/). - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_gif.md b/man/docstring/pcdl_make_gif.md index 85615cf..47d4a37 100644 --- a/man/docstring/pcdl_make_gif.md +++ b/man/docstring/pcdl_make_gif.md @@ -1,19 +1,2 @@ ``` -usage: pcdl_make_gif [-h] [path] [interface] - -this function generates a gif movie from all image files found in the path -directory in the specified interface file format. - -positional arguments: - path relative or absolute path to where the images are from which the - gif will be generated. default is . . - interface specify the image format from which the gif will be generated. - these images have to exist under the given path. they can be - generated with the plot_scatter or plot_contour function. - default is jpeg. - -options: - -h, --help show this help message and exit - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_graph_gml.md b/man/docstring/pcdl_make_graph_gml.md index 20dfbd0..47d4a37 100644 --- a/man/docstring/pcdl_make_graph_gml.md +++ b/man/docstring/pcdl_make_graph_gml.md @@ -1,60 +1,2 @@ ``` -usage: pcdl_make_graph_gml [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--edge_attribute EDGE_ATTRIBUTE] - [--node_attribute [NODE_ATTRIBUTE ...]] - [path] [graph_type] - -function to generate graph files in the gml graph modelling language standard -format. gml was the outcome of an initiative that started at the international -symposium on graph drawing 1995 in Passau and ended at Graph Drawing 1996 in -Berkeley. the networkx python library (https://networkx.org/) and igraph C and -python libraries (https://igraph.org/) for graph analysis are gml compatible -and can as such read and write this file format. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - graph_type to specify which physicell output data should be - processed. attached: processes - mcds.get_attached_graph_dict dictionary. neighbor: - processes mcds.get_neighbor_graph_dict dictionary - spring: processes mcds.get_spring_graph_dict - dictionary. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --edge_attribute EDGE_ATTRIBUTE - specifies if the spatial Euclidean distance is used - for edge attribute, to generate a weighted graph. - default is True. - --node_attribute [NODE_ATTRIBUTE ...] - listing of mcds.get_cell_df dataframe columns, used - for node attributes. default is and empty list. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_movie.md b/man/docstring/pcdl_make_movie.md index 1dbf260..47d4a37 100644 --- a/man/docstring/pcdl_make_movie.md +++ b/man/docstring/pcdl_make_movie.md @@ -1,25 +1,2 @@ ``` -usage: pcdl_make_movie [-h] [--framerate FRAMERATE] [path] [interface] - -this function generates an mp4 movie file from all image files found in the -path directory in the specified interface file format. - -positional arguments: - path relative or absolute path to where the images are from - which the mp4 movie will be generated. default is . . - interface specify the image format from which the mp4 movie will - be generated. these images have to exist under the - given path. they can be generated with the - plot_scatter or plot_contour function. default is - jpeg. - -options: - -h, --help show this help message and exit - --framerate FRAMERATE - specifies how many images per second will be used. - humans are capable of processing 12 images per second - and seeing them individually. higher rates are seen as - motion. default is 12. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_ome_tiff.md b/man/docstring/pcdl_make_ome_tiff.md deleted file mode 100644 index edf2f68..0000000 --- a/man/docstring/pcdl_make_ome_tiff.md +++ /dev/null @@ -1,56 +0,0 @@ -``` -usage: pcdl_make_ome_tiff [-h] [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--conc_cutoff [CONC_CUTOFF ...]] - [--focus FOCUS [FOCUS ...]] [--collapse COLLAPSE] - [path] [cell_attribute] - -function to transform chosen mcdsts output into an 1[um] spaced tczyx (time, -channel, z-axis, y-axis, x-axis) ome tiff file, one substrate or cell_type per -channel. the ome tiff file format can for example be read by the napari -(https://napari.org/stable/) or fiji imagej (https://fiji.sc/) software. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - cell_attribute mcds.get_cell_df dataframe column, used for - cell_attribute. the column data type has to be numeric - (bool, int, float) and cannot be string. the result - will be stored as 32 bit float. default is ID, with - will result in a segmentation mask. - -options: - -h, --help show this help message and exit - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --conc_cutoff [CONC_CUTOFF ...] - if a contour from a substrate not should be cut by - greater than zero (shifted to integer 1), another - cutoff value can be specified here like this: - substarte:value substrate:value substarte:value . - default is and empty string. - --focus FOCUS [FOCUS ...] - set of substrate and cell_type names to specify what - will be translated into ome tiff format. if None, all - substrates and cell types will be processed. default - is a None. - --collapse COLLAPSE should all mcds time steps from the time series be - collapsed into one big ome.tiff, or a many ome.tiff, - one ome.tiff for each time step?, default is True. - -homepage: https://github.com/elmbeech/physicelldataloader -``` diff --git a/man/docstring/pcdl_plot_contour.md b/man/docstring/pcdl_plot_contour.md index c368910..47d4a37 100644 --- a/man/docstring/pcdl_plot_contour.md +++ b/man/docstring/pcdl_plot_contour.md @@ -1,63 +1,2 @@ ``` -usage: pcdl_plot_contour [-h] [-v VERBOSE] [--z_slice Z_SLICE] - [--extrema EXTREMA [EXTREMA ...]] [--alpha ALPHA] - [--fill FILL] [--cmap CMAP] [--title TITLE] - [--grid GRID] [--xlim XLIM [XLIM ...]] - [--ylim YLIM [YLIM ...]] [--xyequal XYEQUAL] - [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] [--ext EXT] - [--figbgcolor FIGBGCOLOR] - [path] [focus] - -function generates matplotlib contour (or contourf) plots, inclusive color -bar, for the substrate specified, under the returned path. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - focus column name within conc dataframe. - -options: - -h, --help show this help message and exit - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D - mesh. if z_slice position numeric but not an exact - mesh center coordinate, then z_slice will be adjusted - to the nearest mesh center value, the smaller one, if - the coordinate lies on a saddle point. default is 0.0. - --extrema EXTREMA [EXTREMA ...] - listing of two floats. None takes min and max from - data. default is None. - --alpha ALPHA alpha channel transparency value between 1 (not - transparent at all) and 0 (totally transparent). - default is 1.0. - --fill FILL True generates a matplotlib contourf plot. False - generates a matplotlib contour plot. default is True. - --cmap CMAP matplotlib colormap string from https://matplotlib.org - /stable/tutorials/colors/colormaps.html . default is - viridis. - --title TITLE title prefix. default is an empty string. - --grid GRID plot axis grid lines. default is True. - --xlim XLIM [XLIM ...] - two floats. x axis min and max value. None takes min - and max from mesh x axis range. default is None. - --ylim YLIM [YLIM ...] - two floats. y axis min and max value. None takes min - and max from mesh y axis range. default is None. - --xyequal XYEQUAL to specify equal axis spacing for x and y axis. - default is true. - --figsizepx FIGSIZEPX [FIGSIZEPX ...] - size of the figure in pixels (integer), x y. the given - x and y will be rounded to the nearest even number, to - be able to generate movies from the images. None tries - to take the values from the initial.svg file. fall - back setting is 640 480. default is None. - --ext EXT output image format. possible formats are jpeg, png, - and tiff. default is jpeg. - --figbgcolor FIGBGCOLOR - figure background color. None is transparent (png) or - white (jpeg, tiff). default is None. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_plot_scatter.md b/man/docstring/pcdl_plot_scatter.md index 551804e..47d4a37 100644 --- a/man/docstring/pcdl_plot_scatter.md +++ b/man/docstring/pcdl_plot_scatter.md @@ -1,98 +1,2 @@ ``` -usage: pcdl_plot_scatter [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--z_slice Z_SLICE] [--z_axis Z_AXIS [Z_AXIS ...]] - [--alpha ALPHA] [--cmap CMAP] [--title TITLE] - [--grid GRID] [--legend_loc LEGEND_LOC] - [--xlim XLIM [XLIM ...]] [--ylim YLIM [YLIM ...]] - [--xyequal XYEQUAL] [--s S] - [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] [--ext EXT] - [--figbgcolor FIGBGCOLOR] - [path] [focus] - -function generates pandas scatter plots, under the returned path. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - focus column name within conc dataframe. default is - cell_type. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D - mesh. if z_slice position numeric but not an exact - mesh center coordinate, then z_slice will be adjusted - to the nearest mesh center value, the smaller one, if - the coordinate lies on a saddle point. default is 0.0. - --z_axis Z_AXIS [Z_AXIS ...] - for a categorical focus: list of labels; for a numeric - focus: list of two floats; None, depending on the - focus column variable dtype, extracts labels or min - and max values from data. default is None - --alpha ALPHA alpha channel transparency value between 1 (not - transparent at all) and 0 (totally transparent). - default is 1.0. - --cmap CMAP matplotlib colormap string from https://matplotlib.org - /stable/tutorials/colors/colormaps.html . default is - viridis. - --title TITLE title prefix. default is an empty string. - --grid GRID plot axis grid lines. default is True. - --legend_loc LEGEND_LOC - the location of the categorical legend, if applicable. - possible strings are: best, 'upper right', 'upper - center', 'upper left', 'center left', 'lower left', - 'lower center', 'lower right', 'center right', center. - default is 'lower left' - --xlim XLIM [XLIM ...] - two floats. x axis min and max value. None takes min - and max from mesh x axis range. default is None. - --ylim YLIM [YLIM ...] - two floats. y axis min and max value. None takes min - and max from mesh y axis range. default is None. - --xyequal XYEQUAL to specify equal axis spacing for x and y axis. - default is True. - --s S scatter plot dot size in pixel. typographic points are - 1/72 inch. the marker size s is specified in - points**2. plt.rcParams['lines.markersize']**2 is in - my case 36. None tries to take the value from the - initial.svg file. fall back setting is 36. default is - None. - --figsizepx FIGSIZEPX [FIGSIZEPX ...] - size of the figure in pixels (integer), x y. the given - x and y will be rounded to the nearest even number, to - be able to generate movies from the images. None tries - to take the values from the initial.svg file. fall - back setting is 640 480. default is None. - --ext EXT output image format. possible formats are jpeg, png, - and tiff. default is jpeg. - --figbgcolor FIGBGCOLOR - figure background color. None is transparent (png) or - white (jpeg, tiff). default is None. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_plot_timeseries.md b/man/docstring/pcdl_plot_timeseries.md index c529514..47d4a37 100644 --- a/man/docstring/pcdl_plot_timeseries.md +++ b/man/docstring/pcdl_plot_timeseries.md @@ -1,114 +1,2 @@ ``` -usage: pcdl_plot_timeseries [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] - [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--frame FRAME] [--z_slice Z_SLICE] [--logy LOGY] - [--ylim YLIM [YLIM ...]] - [--secondary_y SECONDARY_Y [SECONDARY_Y ...]] - [--subplots SUBPLOTS] [--sharex SHAREX] - [--sharey SHAREY] [--linestyle LINESTYLE] - [--linewidth LINEWIDTH] [--cmap CMAP] - [--color COLOR [COLOR ...]] [--grid GRID] - [--legend LEGEND] [--yunit YUNIT] [--title TITLE] - [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] - [--ext EXT] [--figbgcolor FIGBGCOLOR] - [path] [focus_cat] [focus_num] [aggregate_num] - -this function to generate a timeseries plot and either returns a matplotlib -figure or an image file (jpeg, png, tiff). - -positional arguments: - path path to the PhysiCell output directory. default is . . - focus_cat categorical or boolean data column within dataframe - specified under frame. default is None, which is - total, which is all agents or voxels, no categories. - default is None. - focus_num numerical data column within dataframe specified under - frame. default is None, which is count, agent or voxel - count. default is None. - aggregate_num aggregation function {max, mean, median, min, std, - var} for focus_num data. default is mean. - -options: - -h, --help show this help message and exit - --custom_data_type [CUSTOM_DATA_TYPE ...] - parameter to specify custom_data variable types other - than float (namely: int, bool, str) like this - var:dtype myint:int mybool:bool mystr:str . downstream - float and int will be handled as numeric, bool as - Boolean, and str as categorical data. default is an - empty string. - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing, similar to the original pyMCDS_cells.py - script. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --frame FRAME to specifies the data dataframe. cell: dataframe will - be retrieved through the mcds.get_cell_df function. - conc: dataframe will be retrieved through the - mcds.get_conc_df function. default is cell. - --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D - mesh. if z_slice position numeric but not an exact - mesh center coordinate, then z_slice will be adjusted - to the nearest mesh center value, the smaller one, if - the coordinate lies on a saddle point. if set to None, - the whole domain is taken. default is None. - --logy LOGY if True, then y axis is natural log scaled. default is - False. - --ylim YLIM [YLIM ...] - two floats. y axis min and max value. default is None, - which automatically detects min and max value. default - is None. - --secondary_y SECONDARY_Y [SECONDARY_Y ...] - whether to plot on the secondary y-axis. if a listing - of string, which columns to plot on the secondary - y-axis. default is False. - --subplots SUBPLOTS whether to split the plot into subplots, one per - column. default is False. - --sharex SHAREX in case subplots is True, share x-axis by setting some - x-axis labels to invisible. default is False. - --sharey SHAREY in case subplots is True, share y-axis range and - possibly setting some y-axis labels to invisible. - default is False. - --linestyle LINESTYLE - matplotlib line style {-, --, .-, :} string. default - is - . - --linewidth LINEWIDTH - line width in points, integer. default is None. - --cmap CMAP matplotlib colormap string from https://matplotlib.org - /stable/tutorials/colors/colormaps.html . default is - None. - --color COLOR [COLOR ...] - listing of color strings referred to by name, RGB or - RGBA code. default is None. - --grid GRID plot axis grid lines. default is True. - --legend LEGEND if True or reverse, place legend on axis subplots. - default is True. - --yunit YUNIT string to specify y-axis unit. None will not print a - unit on the y-axis. default is None. - --title TITLE title to use for the plot. None will print no title. - default is None. - --figsizepx FIGSIZEPX [FIGSIZEPX ...] - size of the figure in pixels (integer), x y. the given - x and y will be rounded to the nearest even number, to - be able to generate movies from the images. default is - 640 480. - --ext EXT output image format. possible formats are jpeg, png, - and tiff. default is jpeg. - --figbgcolor FIGBGCOLOR - figure background color. None is transparent (png) or - white (jpeg, tiff). default is None. - -homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/scarab.py b/man/scarab.py index 6d38dc3..a138386 100644 --- a/man/scarab.py +++ b/man/scarab.py @@ -302,12 +302,6 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.make_graph_gml.__doc__.split('\n'), ) -# write pyMCDS microenvironment and cells function markdown files -docstring_md( - s_function = 'mcds.make_ome_tiff', - ls_doc = pcdl.TimeStep.make_ome_tiff.__doc__.split('\n'), -) - # write pyMCDS internal function makdown files docstring_md( s_function = 'pcdl.scaler', @@ -398,10 +392,6 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ) # write pyMCDSts microenvironment and cells function makdown files -docstring_md( - s_function = 'mcdsts.make_ome_tiff', - ls_doc = pcdl.TimeSeries.make_ome_tiff.__doc__.split('\n'), -) docstring_md( s_function = 'mcdsts.plot_timeseries', ls_doc = pcdl.TimeSeries.plot_timeseries.__doc__.split('\n'), @@ -440,7 +430,6 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): help_md(s_command='pcdl_make_cell_vtk') # substrate and cell agent help_md(s_command='pcdl_plot_timeseries') -help_md(s_command='pcdl_make_ome_tiff') # making movies help_md(s_command='pcdl_make_gif') help_md(s_command='pcdl_make_movie') diff --git a/pcdl/imagine.py b/pcdl/imagine.py deleted file mode 100644 index 232475f..0000000 --- a/pcdl/imagine.py +++ /dev/null @@ -1,745 +0,0 @@ -### -# title: biotransistor.imagine.py -# -# language Python3 -# license: GPLv3 -# author: bue, jenny eng -# date: 2019-01-31 -# -# run: -# form pcdl import imagine -# -# description: -# my image analysis library -# -# note: -# load basins tiff into numpy array -# -# import matplotlib.pyplot as plt -# from skimage import io -# -# a_tiff = ski.io.imread('segmentation_label_file.tiff') -# io.imshow(a_tiff) -# plt.imshow(a_tiff) -#### - -# library -import numpy as np -import pandas as pd -import sys - -# function -def slide_up(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up. - top row get deleted, - bottom row of zeros is inserted. - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, a.shape[0], 0, axis=0), 0, axis=0) - return(a) - - -def slide_down(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down. - top row of zeros is inserted. - bottom row get deleted, - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, 0, 0, axis=0), -1, axis=0) - return(a) - - -def slide_left(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one column left. - left most column gets deleted, - right most a column of zeros is inserted. - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, a.shape[1], 0, axis=1), 0, axis=1) - return(a) - - -def slide_right(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one column right. - left most a column of zeros is inserted. - right most column gets deleted, - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, 0, 0, axis=1), -1, axis=1) - return(a) - - -def slide_upleft(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up and one column left. - - description: - inspired by np.roll function. - """ - a = slide_left(slide_up(a)) - return(a) - - -def slide_upright(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up and one column right. - - description: - inspired by np.roll function. - """ - a = slide_right(slide_up(a)) - return(a) - - -def slide_downleft(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down and one column left. - - description: - inspired by np.roll function. - """ - a = slide_left(slide_down(a)) - return(a) - - -def slide_downright(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down and one column right. - - description: - inspired by np.roll function. - """ - a = slide_right(slide_down(a)) - return(a) - - -def border(ai_segment): - """ - input: - ai_segment: numpy array representing a cells or nuclei basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - output: - ai_border: numpy array containing only the cell or nuclei basin border. - border value will be 1, non border value will be 0. - - description: - algorithm to extract the basin borders form basin numpy arrays. - """ - ab_border_up = (ai_segment - slide_up(ai_segment)) != 0 - ab_border_down = (ai_segment - slide_down(ai_segment)) != 0 - ab_border_left = (ai_segment - slide_left(ai_segment)) != 0 - ab_border_right = (ai_segment - slide_right(ai_segment)) != 0 - ab_border_upleft = (ai_segment - slide_upleft(ai_segment)) != 0 - ab_border_upright = (ai_segment - slide_upright(ai_segment)) != 0 - ab_border_downleft = (ai_segment - slide_downleft(ai_segment)) != 0 - ab_border_downright = (ai_segment - slide_downright(ai_segment)) != 0 - ab_border = ab_border_up | ab_border_down | ab_border_left | ab_border_right | ab_border_upleft | ab_border_upright | ab_border_downleft | ab_border_downright - ai_border = ab_border * 1 - return(ai_border) - - -def grow(ai_segment, i_step=1, b_verbose=True): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step: integer which specifies how many pixels the basin - to each direction should grow. - function can handle shrinking. enter negative steps like -1. - - b_verbose: boolean which specifies if, while processing, - text should be outputted. - - output: - ai_grown: numpy array with the grown basins - - description: - algorithm to grow the basins in a given basin numpy array. - growing happens counterclockwise, starting at noon. - """ - ai_tree = ai_segment.copy() # initialize output - if (i_step > -1): - # growing - for i in range(i_step): - # next grow cycle - if b_verbose: - print(f'grow {i+1}[px] ring ...') - ai_treering = ai_tree.copy() - for o_slide in [slide_up, slide_upleft, slide_left, slide_downleft, slide_down, slide_downright, slide_right, slide_upright]: - ai_evolve = o_slide(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - #print(ai_treering) - # update output - ai_tree = ai_treering - else: - # shrinking - ai_border = border(ai_segment) - ai_membrane = grow(ai_border, i_step=abs(i_step) - 1) - ai_tree[ai_membrane != 0] = 0 - - # output - return(ai_tree) - - -def grow_seed(ai_segment, i_step=1, b_verbose=True): - """ - input: - ai_segment: numpy array representing a cells center basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step: integer which specifies how many pixels the seed pixel - should grow. - - b_verbose: boolean which specifies if, while processing, - text should be outputted. - - output: - ai_grown: numpy array with the grown basins - - description: - algorithm to grow the basins in a given cell center seed numpy array. - growing happens counterclockwise, starting at noon. - """ - # initialize output - ai_tree = ai_segment.copy() - - # growing - for i in range(i_step): - # next grow cycle - if b_verbose: - print(f'grow {i+1}[px] ring ...') - ai_treering = ai_tree.copy() - - # calculate pythagoras - b_circle = ((i + 1) * 2**(1/2)) < (i_step + 1) - - # up - ai_evolve = slide_up(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # upleft - if b_circle: - ai_evolve = slide_upleft(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # left - ai_evolve = slide_left(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # downleft - if b_circle: - ai_evolve = slide_downleft(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # down - ai_evolve = slide_down(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # downright - if b_circle: - ai_evolve = slide_downright(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # right - ai_evolve = slide_right(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # upright - if b_circle: - ai_evolve = slide_upright(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - - # update output - #print(ai_treering) - ai_tree = ai_treering - # output - return(ai_tree) - - -def collision(ai_segment, i_step_size=1): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step_size: integer that specifies the distance from a basin - where collisions with other basins are detected. - increasing the step size behind > 1 will result in faster processing - but less certain results. step size < 1 make no sense. - default step size is 1. - - output: - eti_collision: a set of tuples representing colliding basins. - - description: - algorithm to detect which basin collide a given step size away. - """ - eti_collision = set() - for o_slide in [slide_up, slide_down, slide_left, slide_right, slide_upleft, slide_upright, slide_downleft, slide_downright]: - ai_walk = ai_segment.copy() - for _ in range(i_step_size): - ai_walk = o_slide(ai_walk) - ai_alice = ai_walk[(ai_segment != 0) & (ai_walk != 0)] - ai_bob = ai_segment[(ai_segment != 0) & (ai_walk != 0)] - eti_collision = eti_collision.union(set( - zip( - ai_alice[(ai_alice != ai_bob)], - ai_bob[(ai_bob != ai_alice)] - ) - )) - # return - return(eti_collision) - - -def touching_cells(ai_segment, i_border_width=0, i_step_size=1): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_border_width: maximal acceptable border with in pixels. - this is half of the range how far two the adjacent cell maximal - can be apart and still are regarded as touching each other. - - i_step_size: step size by which the border width is sampled for - touching cells. - increase the step size behind > 1 will result in faster processing - but less certain results. step size < 1 make no sense. - default step size is 1. - - output: - dei_touch: a dictionary that for each basin states - which other basins are touching. - - description: - algorithm to extract the touching basins from a cell basin numpy array. - algorithm inspired by C=64 computer games with sprit collision. - """ - - # detect neighbors - eti_collision = set() - ai_evolve = ai_segment.copy() - for _ in range(-1, i_border_width, i_step_size): - # detect cell border collision - eti_collision = eti_collision.union( - collision(ai_segment=ai_evolve, i_step_size=i_step_size) - ) - # grow basin - ai_evolve = grow(ai_segment=ai_evolve, i_step=i_step_size) - - # transform set of tuple of alice and bob collision to dictionary of sets - dei_touch = {} - ei_alice = set(np.ndarray.flatten(ai_segment)) - ei_alice.remove(0) - for i_alice in ei_alice: - dei_touch.update({i_alice : set()}) - for i_alice, i_bob in eti_collision: - ei_bob = dei_touch[i_alice] - ei_bob.add(i_bob) - dei_touch.update({i_alice : ei_bob}) - - # output - return(dei_touch) - - -def detouch_to_df(deo_touch, ls_column=["cell_center","cell_touch"]): - """ - input: - deo_touch: touching_cells generated dictionary - ls_column: future dictionary_key dictionary_value column name - - output: - df_touch: dataframe which contains the same information - as the input deo_touch dictionary. - - description: - transforms dei_touch dictionary into a two column dataframe. - """ - lo_key_total= [] - lo_value_total = [] - for o_key, eo_value in deo_touch.items(): - try: - lo_value = sorted(eo_value, key=int) - except ValueError: - lo_value = sorted(eo_value) - # extract form dictionary - if (len(lo_value) == 0): - lo_key_total.append(o_key) - lo_value_total.append(0) - else: - lo_key_total.extend([o_key] * len(lo_value)) - lo_value_total.extend(lo_value) - # generate datafarme - df_touch = pd.DataFrame([lo_key_total,lo_value_total], index=ls_column).T - return(df_touch) - - -# bue: 202021016: maybe refracture this into segment_px and membrane_px function -# the segement px function can then be used too on simple cell and nucles data -def membrane_px(ai_segment, dai_value, i_step=1, b_approximation=False): - ''' - input: - ai_segment: numpy array representing a cells basin tiff file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segement = skimage.io.imread("cells_basins.tif") - - dai_value: dictionary of numpy array representing a - protein expression value tiff file. - the dictionary key should be the protein label. - - i_step: number of pixel the cell border, which is 2 pixel, - in both direction is grown, to cover the membrane segment. - default is 1. - - b_approximation: if set True, calculation works with - non-overlapping membranes and will as such be much faster. - - output: - df_membrane_px: dictionary of pandas datafarame whit cell_id, - image absolute xy coordinate, extended cell relative coordinate, - and membrane pixel related expression values for all proteins. - - description: - function extracts protein membrane expression values, - for each protein submitted to the function. - ''' - # get a fix sensor order - ls_sensor = sorted(dai_value.keys()) - - # get cell border - ab_border = border(ai_segment).astype(bool) - ai_segment_border = ai_segment.copy() - ai_segment_border[~ab_border] = 0 - - # aproximative algorithm - if (b_approximation) or (i_step <= 0): - - # membrane segment - ai_membrane = grow(ai_segment_border, i_step=i_step) - ab_membrane = ai_membrane.astype(bool) - ai_cell = ai_membrane[ab_membrane] - - # coordinates - tai_coor_absolute = np.where(ab_membrane) - ai_ycoor_absolute = tai_coor_absolute[0] - ai_xcoor_absolute = tai_coor_absolute[1] - - ai_membrane_coor = np.stack([ - ai_cell, - ai_ycoor_absolute, - ai_xcoor_absolute, - ], axis=1) - - # values - lai_membrane_value = [] - for s_sensor in ls_sensor: - ai_value = dai_value[s_sensor] - lai_membrane_value.append( - ai_value[ab_membrane] - ) - ai_membrane_value = np.stack( - lai_membrane_value, - axis=1, - ) - - # concatenate coor and value - ai_membrane_px = np.concatenate([ - ai_membrane_coor, - ai_membrane_value, - ], axis=1) - - # pack output - df_membrane_px = pd.DataFrame( - ai_membrane_px, - columns = ['cell', 'y_absolute', 'x_absolute'] + ls_sensor, - )#.astype({}) - df_membrane_px.index.name = f'approximation_membrane_{2*i_step + 1}px' - print(df_membrane_px.info()) - - # exact algorithm - else: - # empty result object - ai_membrane_px = None - - # for each cell - ei_cell = set(ai_segment.flatten()) - ei_cell.discard(0) # kick cell0 which is background - i_total = len(ei_cell) - #for i, i_cell in enumerate(sorted(ei_cell)[0:16]): - for i, i_cell in enumerate(sorted(ei_cell)): - #print(f'processing cell{i_cell}: {i} / {i_total}') - - # membrane segment - tai_coor = np.where(ai_segment_border == i_cell) - ai_ycoor = tai_coor[0] - ai_xcoor = tai_coor[1] - i_ymin = np.array([ai_ycoor.min() - i_step]).clip(min=0)[0] - i_xmin = np.array([ai_xcoor.min() - i_step]).clip(min=0)[0] - i_ymax = np.array([ai_ycoor.max() + i_step + 1]).clip(max=ai_segment_border.shape[0])[0] - i_xmax = np.array([ai_xcoor.max() + i_step + 1]).clip(max=ai_segment_border.shape[1])[0] - ai_border = (ai_segment_border == i_cell)[i_ymin:i_ymax,i_xmin:i_xmax].astype(int) - ab_membrane = grow(ai_border, i_step=i_step).astype(bool) - - # coordiantes - tai_coor_relative = np.where(ab_membrane) - ai_ycoor_relative = tai_coor_relative[0] - ai_xcoor_relative = tai_coor_relative[1] - ai_ycoor_absolute = ai_ycoor_relative + i_ymin - ai_xcoor_absolute = ai_xcoor_relative + i_xmin - ai_cell = np.array([i_cell] * len(ai_ycoor_relative)) - ai_membrane_coor = np.stack([ - ai_cell, - ai_ycoor_relative, - ai_xcoor_relative, - ai_ycoor_absolute, - ai_xcoor_absolute, - ], axis=1) - - # values - lai_membrane_value = [] - for s_sensor in ls_sensor: - ai_value = dai_value[s_sensor] - lai_membrane_value.append( - ai_value[i_ymin:i_ymax,i_xmin:i_xmax][ab_membrane] - ) - ai_membrane_value = np.stack( - lai_membrane_value, - axis=1, - ) - - # concatenate coor and value - ai_membrane_coorvalue = np.concatenate([ - ai_membrane_coor, - ai_membrane_value, - ], axis=1) - - # update ouput - if (ai_membrane_px is None): - ai_membrane_px = ai_membrane_coorvalue - else: - ai_membrane_px = np.concatenate([ - ai_membrane_px, - ai_membrane_coorvalue, - ], axis=0) - - # sanity check - print(f'cell {i_cell} / {i_total}: min {ai_ycoor_relative.min()},{ai_xcoor_relative.min()} x max {ai_ycoor_relative.max()},{ai_xcoor_relative.max()}') - - # pack output - df_membrane_px = pd.DataFrame( - ai_membrane_px, - columns = ['cell', 'y_relative', 'x_relative', 'y_absolute', 'x_absolute'] + ls_sensor, - )#.astype({}) - df_membrane_px.index.name = f'membrane_{2*i_step + 2}px' - - # output - return(df_membrane_px) - - -# bue 20201016: i should add all statistical moments -# bue 20201024: this should become dfpx_stats and be applicable to cell, nucleus, cytoplasm, and membrane -def dfmembranepx_stats(df_membrane_px, di_threshold_raw=None): - ''' - input: - df_membrane_px: membrane_px output which is a - pandas datafarame whit cell_id, - image absolute and cell relative xy coordinate and - membrane pixel related expression values for all proteins. - - di_threshold_raw: dictionary of raw intensity threshold values - for each protein. if set to none, cell membrane positive fraction - will not be calculated and be missing from the output. - default is None. - - output: - ddf_out: dictionary of pandas datafarames. one dataframe per protein. - each dataframe stores mean, min, max and whole range of quantile values - measured for each cell membrane. - - description: - function calculates statistical key numbers for membrane protein - expression values, for each protein submitted to the function. - ''' - # handle input - es_sensor = set(df_membrane_px.columns) - es_sensor = es_sensor.difference( - {'cell', 'y_relative', 'x_relative', 'y_absolute', 'x_absolute'} - ) - - # empty result object - ddlr_membrane = {} - for s_sensor in es_sensor: - ddlr_membrane.update({s_sensor: {}}) - - # for each cell - ei_cell = set(df_membrane_px.cell.unique()) - i_total = len(ei_cell) - 1 - - #for i, i_cell in enumerate(sorted(ei_cell)[0:16]): - for i, i_cell in enumerate(ei_cell): - print(f'processing cell{i_cell}: {i} / {i_total}') - - # for each sensor - df_membrane_cell_px = df_membrane_px.loc[df_membrane_px.cell == i_cell,:] - for s_sensor in es_sensor: - ai_membrane = df_membrane_cell_px.loc[:,s_sensor].values - - # quantile calculation - lr_membrane = [ - np.mean(ai_membrane), - np.std(ai_membrane, ddof=0), - np.min(ai_membrane), - np.quantile(ai_membrane, 0.01), - np.quantile(ai_membrane, 0.05), - np.quantile(ai_membrane, 0.1), - np.quantile(ai_membrane, 0.2), - np.quantile(ai_membrane, 0.3), - np.quantile(ai_membrane, 0.4), - np.quantile(ai_membrane, 0.5), - np.quantile(ai_membrane, 0.6), - np.quantile(ai_membrane, 0.7), - np.quantile(ai_membrane, 0.8), - np.quantile(ai_membrane, 0.9), - np.quantile(ai_membrane, 0.95), - np.quantile(ai_membrane, 0.99), - np.max(ai_membrane), - ai_membrane.shape[0], - ] - - # membrane fraction positive calculation - if not (di_threshold_raw is None): - ab_membrane = ai_membrane > di_threshold_raw[s_sensor] - i_sumpos = ab_membrane.sum() - r_fractpos = i_sumpos / ab_membrane.shape[0] - lr_membrane.extend([i_sumpos, r_fractpos]) - - # update result object - ddlr_membrane[s_sensor].update({i_cell: lr_membrane}) - - # pack output - ls_index = [ - 'mean','std', - 'min','q0_01','q0_05', - 'q0_1','q0_2','q0_3','q0_4','q0_5', - 'q0_6','q0_7','q0_8','q0_9','q0_95', - 'q0_99','max', - 'membrane_size_px' - ] - if not (di_threshold_raw is None): - ls_index.extend(['px_pos','fract_pos']) - ddf_out = {} - for s_sensor, dlr_membrane in ddlr_membrane.items(): - df_membrane = pd.DataFrame( - dlr_membrane, - index=ls_index, - ).T - ddf_out.update({s_sensor: df_membrane}) - df_membrane.index.name = f'cell' - - # output - return(ddf_out) - - -def imgfuse(laaai_in): - """ - input: - laaai_in: list of 3 channel (RGB) images - - output: - aaai_out: fused 3 channel image - - description: - code to fuse many RGB images into one. - """ - # check shape - ti_shape = None - for aaai_in in laaai_in: - if (ti_shape is None): - ti_shape = aaai_in.shape - else: - if (aaai_in.shape != ti_shape): - sys.exit(f"Error: input images have not the same shape. {aaai_in.shape} != {aaai_in}.") - - # fuse images - llli_channel = [] - for i_channel in range(ti_shape[0]): - lli_matrix = [] - for i_y in range(ti_shape[1]): - li_row = [] - for i_x in range(ti_shape[2]): - #print(f"{i_channel} {i_y} {i_x}") - li_px = [] - for aaai_in in laaai_in: - i_in = aaai_in[i_channel,i_y,i_x] - if (i_in != 0): - li_px.append(i_in) - if (len(li_px) != 0): - i_out = np.mean(li_px) - else: - i_out = 0 - li_row.append(int(i_out)) - lli_matrix.append(li_row) - llli_channel.append(lli_matrix) - - # output - aaai_out = np.array(llli_channel) - return(aaai_out) - - -# main code -#if __name__ == "__main__": - diff --git a/pcdl/ometiff2neuro.py b/pcdl/ometiff2neuro.py deleted file mode 100644 index 5cb67aa..0000000 --- a/pcdl/ometiff2neuro.py +++ /dev/null @@ -1,266 +0,0 @@ -######### -# title: ometiff2neuro.py -# -# language: python3 -# date: 2022-02-16 -# license: MIT -# author: jason lu, viviana kwong, elmar bucher -# -# installation: -# conda create -n neuro python=3 # generate an own python environment for to run this code. -# conda activate neuro # activate the generated python environment. -# pip install neuroglancer # the basics -# pip install ipython # for coding -# pip install matplotlib # for expression value color maps -# pip install scikit-image # for loading images as numpy array and signal thresh -# #pip install bioio # for extracting ometiff metadata -# -# test dataset: -# conda activate neuro -# pip install synapseclient # for downloading the test dataset from synapse -# synapse get -r syn26848775 # download test dataset into the current work directory folder. -# -# run: -# python3 -i ometiff2neuro.py -# -# description: -# script to render multi channel multi slice (ome)tiff files into the -# neuroglancer software. -# -# the script here makes use of the neuroglancer python library and is based -# on the neuroglancer/python/examples/example.py code. -# with this script, it is possible to load three-dimensional single -# time step ome-tiff files straight into the neuroglancer software. -# for each channel, mesh generation, rendering, and expression intensity -# coloring is done on the fly. -# channels can be viewed together or alone by toggling them on or off in -# the neuroglancer user interface. -# -# references: -# + https://www.openmicroscopy.org/ome-files/ -# + https://github.com/google/neuroglancer -# + https://github.com/google/neuroglancer/tree/master/python -######### - - -# library -import argparse -from matplotlib import cm -import neuroglancer -import neuroglancer.cli -import numpy as np -from skimage import exposure, filters, io, util -import sys - - -# functions -def ometiff2neuro( - o_state, - s_pathfile_tiff, - ls_channel_label = None, # ['DNA1','PD1','TLR3','SOX10', 'DNA2','CD163','CD3D','PDL1','DNA3','CD4','ICOS','HLADPB1','DNA4','CD8A','CD68','GZMB','DNA5','CD40L','LAG3','HLAA','DNA6','SQSTM','VIN','TIM3', 'DNA7','LAMP1/CD107A','PDL1_2','PD1_2', 'nuc_segement'], # dataset dependent information. - o_intensity_cm = cm.gray, # None - b_intensity_norm = True, # False - o_thresh = filters.threshold_li, # None - di_coor = {'c':1, 'z':0, 'y':2, 'x':3}, # dataset dependent information. - di_nm = {'z':200, 'y':108, 'x':108}, # microscope dependent information. - e_render = None, # {0, 'CD4', 'CD8A', 'GZMB', 28}, - ): - ''' - input: - o_state: neuroglancer viewer state object. - - s_pathfile_tiff: file name and path to input tiff file. - - ls_channel_label: list of channel labels. - default is None. - if None, hexadecimal channel labels will be generated. - this is dataset dependent information. - in future, if ometiff metadata is provided, this information will be extracted from the ometiff metadata. - - o_intensity_cm: matlab color map object, used to display expression intensity values. - default is cm.gray. - if None, no intensity layers will be generated. - - b_intensity_norm: boolean. - default is True. - if True, expression intensity values will be cut by the 2.5 and 97.5 percentiles, to remove outliers, - and intensity will be stretched over the whole range. - - o_thresh: skimage.filter thresh method object to threshold non-threshed data. - default is filters.threshold_li. - this is dataset dependent information. - set None if the tiff contains no raw but already threshed or segmentation mask data! - - di_coor: dictionary of integers, to link c,z,y, and x channel to the corresponding tiff (numpy array) columns. - this is dataset dependent information. - in future, if ometiff metadata is provided, this information will be extracted from the ometiff metadata. - - di_nm: dictionary of integers, to specify z slice distance, and y x pixel size in nanometer. - this is microscope dependent information. - in future, if ometiff metadata is provided, this information will be extracted from the ometiff metadata. - - e_render: set of channel marker labels and/or integers to specify which markers should be rendered into neurogalncer. - default is None. - if None, all channels will be rendered into neuroglancer (if enough RAM is available). - - output: - local url where the rendered data can be viewed. - - description: - function to render multi channel multi slice (ome)tiff files into the neuroglancer software. - ''' - print(f'\nprocessing: {s_pathfile_tiff}') - - # load tiff image as numpy array - a_img = io.imread(s_pathfile_tiff) - print(f'image shape ({[m[0] for m in sorted(di_coor.items(), key=lambda n: n[1])]}): {a_img.shape}') - - # channel count - i_channel = a_img.shape[di_coor['c']] - - # handle channel labels - if ls_channel_label is None: - ls_channel_label = [hex(n) for n in range(i_channel)] - elif len(ls_channel_label) != i_channel: - sys.exit(f'Error @ ometiff2neuro : ls_channel_label shape (len(ls_channel_label)) does not match channel shape {i_channel}.') - print(f'ls_channel_label: {ls_channel_label}') - - # handle set with labels from channels that will be rendered - if e_render is None: - e_render = set(range(i_channel)) - - # generate neuroglancer layers # - i_c = di_coor['c'] - for i_n in range(i_channel): - s_label = ls_channel_label[i_n] - print(f'check channel {i_n}/{i_channel}: {s_label}') - if (i_n in e_render) or (s_label in e_render): - print(f'rendering channel {i_n}/{i_channel}: {s_label}') - - # extract data and xyz coordinate columns - i_x = di_coor['x'] - i_y = di_coor['y'] - i_z = di_coor['z'] - if i_c == 0: - a_channel = a_img[i_n,:,:,:] - elif i_c == 1: - a_channel = a_img[:,i_n,:,:] - if di_coor['x'] > i_c: - i_x -= 1 - if di_coor['y'] > i_c: - i_y -= 1 - if di_coor['z'] > i_c: - i_z -= 1 - elif i_c == 2: - a_channel = a_img[:,:,i_n,:] - if di_coor['x'] > i_c: - i_x -= 1 - if di_coor['y'] > i_c: - i_y -= 1 - if di_coor['z'] > i_c: - i_z -= 1 - elif i_c == 3: - a_channel = a_img[:,:,:,i_n] - else: - sys.exit(f'Error @ ometiff2neuro : the source code is broken. please, fix the script.') - - - # 3D rendering # - # thresh data - a_thresh = a_channel.copy() - if not (o_thresh is None): - r_thresh = o_thresh(a_thresh) - a_thresh[a_thresh < r_thresh] = 0 - ab_thresh = a_thresh > 0 - - # shape - a_shape = np.zeros(ab_thresh.shape, dtype=np.uint32) - a_shape[ab_thresh] = i_n + 1 - - # generate neuroglancer object - ls_name = [None, None, None] - ls_name[i_x] = 'x' - ls_name[i_y] = 'y' - ls_name[i_z] = 'z' - li_scale = [None, None, None] - li_scale[i_x] = di_nm['x'] - li_scale[i_y] = di_nm['y'] - li_scale[i_z] = di_nm['z'] - state.layers.append( - name = s_label, - layer = neuroglancer.LocalVolume( - data = a_shape, - dimensions = neuroglancer.CoordinateSpace( - # rgb, x, y, z - names = ls_name, - scales = li_scale, - units = ['nm', 'nm', 'nm'], - ), - ), - ) - - - # expression intensity rendering # - if not (o_intensity_cm is None): - - # normalize expression values by clip by two sigma and scale over the whole uint range - if (b_intensity_norm): - i_min_clip = int(np.percentile(a_channel, 2.5)) - i_max_clip = int(np.percentile(a_channel, 97.5)) - a_clipped = np.clip(a_channel, a_min=i_min_clip, a_max=i_max_clip) - a_channel = exposure.rescale_intensity(a_clipped, in_range='image') # 16 or 8[bit] normalized - - # translate intensity by color map - a_8bit = util.img_as_ubyte(a_channel) - a_intensity = o_intensity_cm(a_8bit, alpha=None, bytes=True)[:,:,:,0:3] - - # generate neuroglancer object - ls_name = [None, None, None,'c^'] - ls_name[i_x] = 'x' - ls_name[i_y] = 'y' - ls_name[i_z] = 'z' - li_scale = [None, None, None, 3] - li_scale[i_x] = di_nm['x'] - li_scale[i_y] = di_nm['y'] - li_scale[i_z] = di_nm['z'] - state.layers.append( - name = s_label + '_intensity', - layer = neuroglancer.LocalVolume( - data = a_intensity, - dimensions = neuroglancer.CoordinateSpace( - # rgb, x, y, z - names = ls_name, - scales = li_scale, - units = ['nm', 'nm', 'nm', ''], - ), - ), - shader= -""" -void main() { - emitRGB( - vec3(toNormalized(getDataValue(0)), - toNormalized(getDataValue(1)), - toNormalized(getDataValue(2))) - ); -} -""", - ) - - -# run the code from the command line -if __name__ == '__main__': - o_parser = argparse.ArgumentParser(description='Script to render ome.tiff files into the neuroglancer software.') - # request path to ometiff and file name as command line argument - o_parser.add_argument('ometiff', type=str, nargs=1, help='ome.tiff path/filename') - # start neuroglancer - neuroglancer.cli.add_server_arguments(o_parser) - neuroglancer.cli.handle_server_arguments(o_parser.parse_args()) - viewer = neuroglancer.Viewer() - with viewer.txn() as state: - # render ometiff - ometiff2neuro( - o_state = state, - s_pathfile_tiff = o_parser.parse_args().ometiff[0], - ) - # print neuroglancer viewer url - print(viewer) diff --git a/pcdl/pdplt.py b/pcdl/pdplt.py deleted file mode 100644 index c45ed4f..0000000 --- a/pcdl/pdplt.py +++ /dev/null @@ -1,132 +0,0 @@ -#### -# title: biotransistor.pdplt.py -# -# language: python3 -# date: 2019-06-29 -# license: GPL>=v3 -# author: Elmar Bucher -# -# description: -# library with the missing pandas plot features. -# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html -#### - - -# library -import matplotlib.pyplot as plt -from matplotlib import cm -from matplotlib import colors -import matplotlib.patches as mpatches -import numpy as np -import random - - -# pandas to matplotlib -#fig, ax = plt.subplots() -#ax = ax.ravel() -#ax.axis('equal') -#df.plot(ax=ax) -#plt.tight_layout() -#fig.savefig(s_filename, facecolor='white') -#plt.close() - - -# plot stuff -def df_label_to_color(df_abc, s_focus, es_label=None, s_nolabel='gray', s_cmap='viridis', b_shuffle=False): - ''' - input: - df_abc: dataframe to which the color column will be added. - s_focus: column name with sample labels for which a color column will be generated. - es_label: set of labels to color. if None, es_label will be extracted for the s_focus column. - s_nolabel: color for labels not defined in es_label. - s_cmap: matplotlib color map label. - https://matplotlib.org/stable/tutorials/colors/colormaps.html - b_shuffle: should colors be given by alphabetical order, - or should the label color mapping order be random. - - output: - df_abc: dataframe updated with color column. - ds_color: lable to hex color string mapping dictionary - - description: - function adds for the selected label column - a color column to the df_abc dataframe. - ''' - if (es_label is None): - es_label = set(df_abc.loc[:,s_focus]) - if b_shuffle: - ls_label = list(es_label) - random.shuffle(ls_label) - else: - ls_label = sorted(es_label) - a_color = plt.get_cmap(s_cmap)(np.linspace(0, 1, len(ls_label))) - do_color = dict(zip(ls_label, a_color)) - df_abc[f'{s_focus}_color'] = s_nolabel - ds_color = {} - for s_category, o_color in do_color.items(): - s_color = colors.to_hex(o_color) - ds_color.update({s_category : s_color}) - df_abc.loc[(df_abc.loc[:,s_focus] == s_category), f'{s_focus}_color'] = s_color - # output - return(ds_color) - -def ax_colorlegend(ax, ds_color, s_loc='lower left', s_fontsize='small'): - ''' - input: - ax: matplotlib axis object to which a color legend will be added. - ds_color: lables to color strings mapping dictionary - s_loc: the location of the legend. - possible strings are: best, - upper right, upper center, upper left, center left, - lower left, lower center, lower right, center right, - center. - s_fontsize: font size used for the legend. known are: - xx-small, x-small, small, medium, large, x-large, xx-large. - - output: - ax: matplotlib axis object updated with color legend. - - description: - function to add color legend to a figure. - ''' - lo_patch = [] - for s_label, s_color in sorted(ds_color.items()): - o_patch = mpatches.Patch(color=s_color, label=s_label) - lo_patch.append(o_patch) - ax.legend( - handles = lo_patch, - loc = s_loc, - fontsize = s_fontsize - ) - -def ax_colorbar(ax, r_vmin, r_vmax, s_cmap='viridis', s_text=None, o_fontsize='medium', b_axis_erase=False): - ''' - input: - ax: matplotlib axis object to which a colorbar will be added. - r_vmin: colorbar min value. - r_vmax: colorbar max value. - s_cmap: matplotlib color map label. - https://matplotlib.org/stable/tutorials/colors/colormaps.html - s_text: to label the colorbar axis. - o_fontsize: font size used for the legend. known are: - xx-small, x-small, small, medium, large, x-large, xx-large. - b_axis_erase: should the axis ruler be erased? - - output: - ax: matplotlib axis object updated with colorbar. - - description" - function to add colorbar to a figure. - ''' - if b_axis_erase: - ax.axis('off') - if not (s_text is None): - ax.text(0.5,0.5, s_text, fontsize=o_fontsize) - plt.colorbar( - cm.ScalarMappable( - norm=colors.Normalize(vmin=r_vmin, vmax=r_vmax, clip=False), - cmap=s_cmap, - ), - ax=ax, - ) - diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index 5a16608..31b86c0 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -2101,154 +2101,6 @@ def plot_timeseries(): return s_pathfile -def make_ome_tiff(): - # argv - parser = argparse.ArgumentParser( - prog = 'pcdl_make_ome_tiff', - description = 'function to transform chosen mcdsts output into an 1[um] spaced tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file, one substrate or cell_type per channel. the ome tiff file format can for example be read by the napari (https://napari.org/stable/) or fiji imagej (https://fiji.sc/) software.', - epilog = 'homepage: https://github.com/elmbeech/physicelldataloader', - ) - - # TimeSeries path - parser.add_argument( - 'path', - nargs = '?', - default = '.', - help = 'path to the PhysiCell output directory or a outputnnnnnnnn.xml file. default is . .', - ) - # TimeSeries output_path '.' - # TimeSeries custom_data_type {} - # TimeSeries microenv - parser.add_argument( - '--microenv', - default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.' - ) - # TimeSeries graph False - # TimeSeries physiboss - parser.add_argument( - '--physiboss', - default = 'true', - help = 'if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True.' - ) - # TimeSeries settingxml - parser.add_argument( - '--settingxml', - default = 'PhysiCell_settings.xml', - help = 'the settings.xml that is loaded, from which the cell type ID label mapping, is extracted, if this information is not found in the output xml file. set to None or False if the xml file is missing! default is PhysiCell_settings.xml.', - ) - # TimeSeries verbose - parser.add_argument( - '-v', '--verbose', - default = 'true', - help = 'setting verbose to False for less text output, while processing. default is True.', - ) - # make_ome_tiff cell_attribute - parser.add_argument( - 'cell_attribute', - nargs = '?', - default = 'ID', - help = 'mcds.get_cell_df dataframe column, used for cell_attribute. the column data type has to be numeric (bool, int, float) and cannot be string. the result will be stored as 32 bit float. default is ID, with will result in a segmentation mask.', - ) - # make_ome_tiff conc_cutoff - parser.add_argument( - '--conc_cutoff', - nargs = '*', - default = [], - help = 'if a contour from a substrate not should be cut by greater than zero (shifted to integer 1), another cutoff value can be specified here like this: substarte:value substrate:value substarte:value . default is and empty string.', - ) - # make_ome_tiff focus - parser.add_argument( - '--focus', - nargs = '+', - default = ['none'], - help = 'set of substrate and cell_type names to specify what will be translated into ome tiff format. if None, all substrates and cell types will be processed. default is a None.', - ) - # make_ome_tiff file True - # make_ome_tiff collapse - parser.add_argument( - '--collapse', - default = 'true', - help = 'should all mcds time steps from the time series be collapsed into one big ome.tiff, or a many ome.tiff, one ome.tiff for each time step?, default is True.' - ) - - # parse arguments - args = parser.parse_args() - print(args) - - # path - s_path = args.path - while (s_path.find('//') > -1): - s_path = s_path.replace('//','/') - if (s_path.endswith('/')) and (len(s_path) > 1): - s_path = s_path[:-1] - s_pathfile = s_path - if not s_pathfile.endswith('.xml'): - s_pathfile = s_pathfile + '/initial.xml' - else: - s_path = '/'.join(s_pathfile.split('/')[:-1]) - if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pyCLI.make_ome_tiff : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') - - # conc_cutoff - d_conccutoff = {} - for s_conccutoff in args.conc_cutoff: - s_substrate, s_value = s_conccutoff.split(':') - if (s_value.find('.') > -1): - o_value = float(s_value) - else: - o_value = int(s_value) - d_conccutoff.update({s_substrate : o_value}) - - # focus - if (args.focus[0].lower() == 'none'): - es_focus = None - else: - es_focus = set( args.focus) - - # run - if os.path.isfile(args.path): - mcds = pcdl.pyMCDS( - xmlfile = s_pathfile, - output_path = '.', - custom_data_type = {}, - microenv = False if args.microenv.lower().startswith('f') else True, - graph = False, - physiboss = False if args.physiboss.lower().startswith('f') else True, - settingxml = None if ((args.settingxml.lower() == 'none') or (args.settingxml.lower() == 'false')) else args.settingxml, - verbose = False if args.verbose.lower().startswith('f') else True - ) - s_opathfile = mcds.make_ome_tiff( - cell_attribute = args.cell_attribute, - conc_cutoff = d_conccutoff, - focus = es_focus, - file = True, - ) - # going home - return s_opathfile - - else: - mcdsts = pcdl.pyMCDSts( - output_path = s_path, - custom_data_type = {}, - load = True, - microenv = False if args.microenv.lower().startswith('f') else True, - graph = False, - physiboss = False if args.physiboss.lower().startswith('f') else True, - settingxml = None if ((args.settingxml.lower() == 'none') or (args.settingxml.lower() == 'false')) else args.settingxml, - verbose = False if args.verbose.lower().startswith('f') else True, - ) - o_opathfile = mcdsts.make_ome_tiff( - cell_attribute = args.cell_attribute, - conc_cutoff = d_conccutoff, - focus = es_focus, - file = True, - collapse = False if args.collapse.lower().startswith('f') else True, - ) - # going home - return o_opathfile - - ################# # making movies # ################# diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index 5a1fc2d..4b37f00 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -15,16 +15,14 @@ # load library -import bioio_base -from bioio.writers import OmeTiffWriter import matplotlib.pyplot as plt from matplotlib import cm from matplotlib import colors +import matplotlib.patches as mpatches import numpy as np import os import pandas as pd -from pcdl import imagine -from pcdl import pdplt +import random from scipy import io import sys import vtk @@ -171,6 +169,75 @@ # functions +def df_label_to_color(df_abc, s_focus, es_label=None, s_nolabel='gray', s_cmap='viridis', b_shuffle=False): + ''' + input: + df_abc: dataframe to which the color column will be added. + s_focus: column name with sample labels for which a color column will be generated. + es_label: set of labels to color. if None, es_label will be extracted for the s_focus column. + s_nolabel: color for labels not defined in es_label. + s_cmap: matplotlib color map label. + https://matplotlib.org/stable/tutorials/colors/colormaps.html + b_shuffle: should colors be given by alphabetical order, + or should the label color mapping order be random. + + output: + df_abc: dataframe updated with color column. + ds_color: lable to hex color string mapping dictionary + + description: + function adds for the selected label column + a color column to the df_abc dataframe. + ''' + if (es_label is None): + es_label = set(df_abc.loc[:,s_focus]) + if b_shuffle: + ls_label = list(es_label) + random.shuffle(ls_label) + else: + ls_label = sorted(es_label) + a_color = plt.get_cmap(s_cmap)(np.linspace(0, 1, len(ls_label))) + do_color = dict(zip(ls_label, a_color)) + df_abc[f'{s_focus}_color'] = s_nolabel + ds_color = {} + for s_category, o_color in do_color.items(): + s_color = colors.to_hex(o_color) + ds_color.update({s_category : s_color}) + df_abc.loc[(df_abc.loc[:,s_focus] == s_category), f'{s_focus}_color'] = s_color + # output + return(ds_color) + + +def ax_colorlegend(ax, ds_color, s_loc='lower left', s_fontsize='small'): + ''' + input: + ax: matplotlib axis object to which a color legend will be added. + ds_color: lables to color strings mapping dictionary + s_loc: the location of the legend. + possible strings are: best, + upper right, upper center, upper left, center left, + lower left, lower center, lower right, center right, + center. + s_fontsize: font size used for the legend. known are: + xx-small, x-small, small, medium, large, x-large, xx-large. + + output: + ax: matplotlib axis object updated with color legend. + + description: + function to add color legend to a figure. + ''' + lo_patch = [] + for s_label, s_color in sorted(ds_color.items()): + o_patch = mpatches.Patch(color=s_color, label=s_label) + lo_patch.append(o_patch) + ax.legend( + handles = lo_patch, + loc = s_loc, + fontsize = s_fontsize + ) + + def graphfile_parser(s_pathfile): """ input: @@ -1923,7 +1990,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma df_cell.loc[(df_cell.loc[:,focus] == s_category), s_focus_color] = s_color # generate category color dictionary else: - ds_color = pdplt.df_label_to_color( + ds_color = df_label_to_color( df_abc = df_cell, s_focus = focus, es_label = es_category, @@ -1960,7 +2027,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma # plot categorical data legen if not (es_category is None): - pdplt.ax_colorlegend( + ax_colorlegend( ax = ax, ds_color = ds_color, s_loc = legend_loc, @@ -2133,232 +2200,6 @@ def make_cell_vtk(self, attribute=['cell_type'], visualize=True): return s_vtkpathfile - ## MICROENVIRONMENT AND CELL AGENT RELATED FUNCTIONS ## - - def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=True): - """ - input: - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be - specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape czyx is the output. - - output: - a_czyx_img: numpy array or ome tiff file. - - description: - function to transform chosen mcds output into an 1[um] spaced - czyx (channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - an ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - """ - # handle channels - ls_substrate = self.get_substrate_list() - ls_celltype = self.get_celltype_list() - - if not (focus is None): - ls_substrate = [s_substrate for s_substrate in ls_substrate if s_substrate in set(focus)] - ls_celltype = [s_celltype for s_celltype in ls_celltype if s_celltype in set(focus)] - if (set(focus) != set(ls_substrate).union(set(ls_celltype))): - sys.exit(f'Error : {focus} not found in {ls_substrate} {ls_celltype}') - - # const - ls_coor_mnp = ['mesh_center_m', 'mesh_center_n', 'mesh_center_p'] # xyz - ls_coor_xyz = ['position_x', 'position_y', 'position_z'] # xyz - ls_coor = ['voxel_x', 'voxel_y', 'voxel_z'] - - # time step tensor - i_time = int(self.get_time()) - - # get xy coordinate dataframe - lr_axis_z = list(self.get_mesh_mnp_axis()[2] - self.get_voxel_spacing()[2] / 2) - lr_axis_z.append(self.get_mesh_mnp_axis()[2][-1] + self.get_voxel_spacing()[2] / 2) - lll_coor = [] - for i_x in range(int(round(self.get_voxel_ijk_range()[0][1] * self.get_voxel_spacing()[0]))): - for i_y in range(int(round(self.get_voxel_ijk_range()[1][1] * self.get_voxel_spacing()[1]))): - lll_coor.append([i_x, i_y]) - df_coor = pd.DataFrame(lll_coor, columns=ls_coor[:2]) - lr_axis_z[-1] += 1 - - # extract voxel radius - di_grow = {} - for s_substarte in ls_substrate: - di_grow.update({ - s_substarte : int(np.round(np.mean(self.get_voxel_spacing()[:2])) - 1) - }) - - # get and shift substrate xy data - df_conc = self.get_conc_df() - df_conc = df_conc.loc[:, ls_coor_mnp + ls_substrate] - df_conc.loc[:, 'mesh_center_m'] = (df_conc.loc[:, 'mesh_center_m'] - self.get_xyz_range()[0][0]).round() - df_conc.loc[:, 'mesh_center_n'] = (df_conc.loc[:, 'mesh_center_n'] - self.get_xyz_range()[1][0]).round() - df_conc.rename({'mesh_center_m':'voxel_x', 'mesh_center_n':'voxel_y', 'mesh_center_p':'voxel_z'}, axis=1, inplace=True) - df_conc = df_conc.astype({'voxel_x': int, 'voxel_y': int, 'voxel_z': float}) - # level the cake - for s_channel in conc_cutoff.keys(): - try: - df_conc.loc[:, s_channel] = df_conc.loc[:, s_channel] - conc_cutoff[s_channel] + 1 # positive values starting at > 0 - df_conc.loc[(df_conc.loc[:, s_channel] <= conc_cutoff[s_channel]), s_channel] = 0 - except KeyError: - pass - - - # get cell data - df_cell = self.get_cell_df().reset_index() - - # extract cell radius - for s_celltype in ls_celltype: - try: - i_cell_grow = int(round(df_cell.loc[(df_cell.cell_type == s_celltype), 'radius'].mean()) - 1) - except: - i_cell_grow = 0 - di_grow.update({s_celltype : i_cell_grow}) - - # filter and shift - df_cell = df_cell.loc[:, ls_coor_xyz + ['cell_type', cell_attribute]] - if (cell_attribute == 'cell_type'): - sys.exit(f'Error @ pyMCDS.make_ome_tiff : cell_attribute cannot be cell_type.') - elif (df_cell.loc[:, cell_attribute].dtype == str) or (df_cell.loc[:, cell_attribute].dtype == np.object_): # in {str, np.str_, np.object_}): - sys.exit(f'Error @ pyMCDS.make_ome_tiff : {cell_attribute} {df_cell.loc[:, cell_attribute].dtype} cell_attribute cannot be string or object. cell_attribute has to be boolean, integer, or float.') - elif (df_cell.loc[:, cell_attribute].dtype == bool): # in {bool, np.bool_, np.bool}): - df_cell = df_cell.astype({cell_attribute: int}) - df_cell.loc[:, 'position_x'] = (df_cell.loc[:, 'position_x'] - self.get_xyz_range()[0][0]).round() - df_cell.loc[:, 'position_y'] = (df_cell.loc[:, 'position_y'] - self.get_xyz_range()[1][0]).round() - df_cell.rename({'position_x':'voxel_x', 'position_y':'voxel_y', 'position_z':'voxel_z'}, axis=1, inplace=True) - df_cell = df_cell.astype({'voxel_x': int, 'voxel_y': int, 'voxel_z': float}) - # level the cake - df_cell.loc[:, cell_attribute] = df_cell.loc[:, cell_attribute] - df_cell.loc[:, cell_attribute].min() + 1 # positive values starting at > 0 - - # check for duplicates: two cell at exactelly the same xyz position. - #if self.verbose and df_cell.loc[:,['voxel_x', 'voxel_y', 'voxel_z']].duplicated().any(): - # df_duplicate = df_cell.loc[(df_cell.loc[:, ['voxel_x', 'voxel_y', 'voxel_z']].duplicated()), :] - # sys.exit(f"Error @ pyMCDS.make_ome_tiff : {df_duplicate} cells at exactely the same xyz voxel position detected. cannot pivot!") - - # pivot cell_type - df_cell = df_cell.pivot_table(index=ls_coor, columns='cell_type', values=cell_attribute, aggfunc='sum').reset_index() # fill_value is na - for s_celltype in ls_celltype: - if not s_celltype in set(df_cell.columns): - df_cell[s_celltype] = 0 - - # each C channel - time step tensors - la_czyx_img = [] - ls_channel = ls_substrate + ls_celltype - for s_channel in ls_channel: - - # get channel dataframe - if s_channel in set(ls_substrate): - df_channel = df_conc.loc[:, ls_coor + [s_channel]] - elif s_channel in set(ls_celltype): - df_channel = df_cell.loc[:, ls_coor + [s_channel]] - else: - sys.exit(f'Error @ pyMCDS.make_ome_tiff : {s_channel} unknown channel detected. not in substrate and cell type list {ls_substrate} {ls_celltype}!') - - # each z axis - la_zyx_img = [] - for i_zaxis in range(len(lr_axis_z)): - if (i_zaxis < (len(lr_axis_z) - 1)): - print(f'processing: {i_time} [min] {s_channel} [channel] {i_zaxis} [z_axis] ...') - # extract z layer - df_yxchannel = df_channel.loc[ - ((df_channel.loc[:, ls_coor[2]] >= lr_axis_z[i_zaxis]) & (df_channel.loc[:, ls_coor[2]] < lr_axis_z[i_zaxis + 1])), - ls_coor[:2] + [s_channel] - ] - - # drop row with na and duplicate entries - df_yxchannel = df_yxchannel.dropna(axis=0) - df_yxchannel = df_yxchannel.drop_duplicates() - - # merge with coooridnates and get image - # bue 20240811: df_coor left side merge will cut off reset cell that are out of the xyz domain range, which is what we want. - df_yxchannel = pd.merge(df_coor, df_yxchannel, on=ls_coor[:2], how='left').replace({np.nan: 0}) - try: - df_yxchannel = df_yxchannel.pivot(columns=ls_coor[0], index=ls_coor[1], values=s_channel) - except ValueError: # two cells from the same cell type very close to each other detetced. - if self.verbose: - df_duplicate = df_cell.loc[(df_yxchannel.loc[:, ['voxel_x', 'voxel_y']].duplicated()), :] - print(f'Warning: {s_channel} {df_duplicate} cells within 1[um] distance form each detected. cannot pivot. erase cell type from this timestep.') - df_yxchannel.loc[:,s_channel] = 0 # erase cells - df_yxchannel = df_yxchannel.drop_duplicates() - df_yxchannel = df_yxchannel.pivot(columns=ls_coor[0], index=ls_coor[1], values=s_channel) - a_yx_img = df_yxchannel.values - - # grow - a_yx_img = imagine.grow_seed(a_yx_img, i_step=di_grow[s_channel], b_verbose=False) - - # update output - la_zyx_img.append(a_yx_img) - a_zyx_img = np.array(la_zyx_img, np.float32) - la_czyx_img.append(np.array(a_zyx_img, np.float32)) - - # output - a_czyx_img = np.array(la_czyx_img, dtype=np.float32) - - # numpy array - if not file: - return a_czyx_img - - # write to file - else: - if self.verbose: - print('a_czyx_img shape:', a_czyx_img.shape) - # generate filename - s_channel = '' - for s_substrate in ls_substrate: - try: - r_value = conc_cutoff[s_substrate] - s_channel += f'_{s_substrate}{r_value}' - except KeyError: - s_channel += f'_{s_substrate}' - for s_celltype in ls_celltype: - s_channel += f'_{s_celltype}' - if len(ls_celltype) > 0: - s_channel += f'_{cell_attribute}' - s_tifffile = self.xmlfile.replace('.xml', f'{s_channel}.ome.tiff') - if (len(s_tifffile) > 255): - print(f"Warning: filename {len(s_tifffile)} > 255 character.") - s_tifffile = self.xmlfile.replace('.xml', f'_channels.ome.tiff') - print(f"file name adjusted to {s_tifffile}.") - s_tiffpathfile = self.path + '/' + s_tifffile - - # save to file - OmeTiffWriter.save( - a_czyx_img, - s_tiffpathfile, - dim_order = 'CZYX', - #ome_xml=x_img, - channel_names = ls_channel, - image_names = [s_tifffile.replace('.ome.tiff','')], - physical_pixel_sizes = bioio_base.types.PhysicalPixelSizes(self.get_voxel_spacing()[2], 1.0, 1.0), # z,y,x [um] - #channel_colors=, - #fs_kwargs={}, - ) - return s_tiffpathfile - - ## GRAPH RELATED FUNCTIONS ## def get_attached_graph_dict(self): diff --git a/pcdl/pyMCDSts.py b/pcdl/pyMCDSts.py index 2378a38..49528e7 100644 --- a/pcdl/pyMCDSts.py +++ b/pcdl/pyMCDSts.py @@ -20,8 +20,6 @@ # load libraries -import bioio_base -from bioio.writers import OmeTiffWriter import glob import matplotlib.pyplot as plt import numpy as np @@ -986,141 +984,6 @@ def make_cell_vtk(self, attribute=['cell_type'], visualize=False): return ls_vtkpathfile - ## OME TIFF RELATED FUNCTIONS ## - - def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=True): - """ - input: - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape tczyx is the output. - - collapse: boole; default True - should all mcds time steps from the time series be collapsed - into one ome tiff file (numpy array), - or an ome tiff file (numpy array) for each time step? - - output: - a_tczyx_img: numpy array or ome tiff file. - - - description: - function to transform chosen mcdsts output into an 1[um] spaced - tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - a ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - """ - # for each T time step - l_tczyx_img = [] - for i, mcds in enumerate(self.get_mcds_list()): - # processing - b_file = True # 10 - if (not file and not collapse) or (not file and collapse) or (file and collapse): # 00, 01, 11 - b_file = False - o_tczyx_img = mcds.make_ome_tiff( - cell_attribute = cell_attribute, - conc_cutoff = conc_cutoff, - focus = focus, - file = b_file - ) - l_tczyx_img.append(o_tczyx_img) - - # handle channels - ls_substrate = mcds.get_substrate_list() - ls_celltype = mcds.get_celltype_list() - - if not (focus is None): - ls_substrate = [s_substrate for s_substrate in ls_substrate if s_substrate in set(focus)] - ls_celltype = [s_celltype for s_celltype in ls_celltype if s_celltype in set(focus)] - if (set(focus) != set(ls_substrate).union(set(ls_celltype))): - sys.exit(f'Error : {focus} not found in {ls_substrate} {ls_celltype}') - - # output 00 list of numpy arrays - if (not file and not collapse): # 00 - if self.verbose: - print(f'la_tczyx_img shape: {len(l_tczyx_img)} * {l_tczyx_img[0].shape}') - return l_tczyx_img - - # output 01 numpy array - elif (not file and collapse): # 01 - # numpy array - a_tczyx_img = np.array(l_tczyx_img) - if self.verbose: - print('a_tczyx_img shape:', a_tczyx_img.shape) - return a_tczyx_img - - # output 10 list of pathfile strings - elif (file and not collapse): # 10 - return l_tczyx_img - - # output 11 ometiff file - elif (file and collapse): # 11 - a_tczyx_img = np.array(l_tczyx_img) - if self.verbose: - print('a_tczyx_img shape:', a_tczyx_img.shape) - - # generate filename - s_channel = '' - for s_substrate in ls_substrate: - try: - r_value = conc_cutoff[s_substrate] - s_channel += f'_{s_substrate}{r_value}' - except KeyError: - s_channel += f'_{s_substrate}' - for s_celltype in ls_celltype: - s_channel += f'_{s_celltype}' - if len(ls_celltype) > 0: - s_channel += f'_{cell_attribute}' - s_tifffile = f'timeseries{s_channel}.ome.tiff' - if (len(s_tifffile) > 255): - print(f"Warning: filename {len(s_tifffile)} > 255 character.") - s_tifffile = 'timeseries_channels.ome.tiff' - print(f"file name adjusted to {s_tifffile}.") - s_tiffpathfile = self.path + '/' + s_tifffile - - # save to file - OmeTiffWriter.save( - a_tczyx_img, - s_tiffpathfile, - dim_order = 'TCZYX', - #ome_xml=x_img, - channel_names = ls_substrate + ls_celltype, - image_names = [f'timeseries_{cell_attribute}'], - physical_pixel_sizes = bioio_base.types.PhysicalPixelSizes(mcds.get_voxel_spacing()[2], 1.0, 1.0), # z,y,x [um] - #channel_colors=, - #fs_kwargs={}, - ) - return s_tiffpathfile - - # error case - else: - sys.exit(f'Error @ make_ome_tiff : {file} {collapse} strange file collapse combination.') - - ## TIME SERIES RELATED FUNCTIONS ## def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanmean, frame='cell', z_slice=None, logy=False, ylim=None, secondary_y=None, subplots=False, sharex=False, sharey=False, linestyle='-', linewidth=None, cmap=None, color=None, grid=True, legend=True, yunit=None, title=None, ax=None, figsizepx=[640, 480], ext=None, figbgcolor=None): diff --git a/pyproject.toml b/pyproject.toml index 1d21b09..b52f21b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,6 @@ classifiers = [ # bue 2024-12-06: enforcing some versions dependencies = [ "anndata>=0.10.8", - "bioio>=1.2.1", "matplotlib", "numpy<2.0.0", "pandas>=2.2.2", @@ -102,7 +101,6 @@ pcdl_plot_scatter = "pcdl.pyCLI:plot_scatter" pcdl_make_cell_vtk = "pcdl.pyCLI:make_cell_vtk" # substrate and cell agent pcdl_plot_timeseries = "pcdl.pyCLI:plot_timeseries" -pcdl_make_ome_tiff = "pcdl.pyCLI:make_ome_tiff" # making movies pcdl_make_gif = "pcdl.pyCLI:make_gif" pcdl_make_movie = "pcdl.pyCLI:make_movie" diff --git a/test/test_cli_2d.py b/test/test_cli_2d.py index 71b6aed..2a0124e 100644 --- a/test/test_cli_2d.py +++ b/test/test_cli_2d.py @@ -1826,195 +1826,6 @@ def test_pcdl_plot_timeseries_set(self): os.remove(s_opathfile) -class TestPyCliOmeTiff(object): - ''' tests for one pcdl.pyCli function. ''' - - # timestep and timeseries: - # + path nop - # + customtype nop (because bool int float might in the end be treated the same and str is recogniced) - # + microenv (true, false) ok - # + physiboss (true, _false_) ok - # + settingxml (string, _none_, _false_) ok - # + verbose (true, _false_) nop - # + cell_attribute (ID, _dead_, _cell_count_voxel_, _pressure_) ok - # + collapse (true, _false_) ok - - def test_pcdl_make_ome_tiff_timeseries_default(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_settingxmlnone(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_dead(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'dead'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_dead.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_cellcountvoxel(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'cell_count_voxel'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_cell_count_voxel.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_pressure(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'pressure'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen_water_default_blood_cells_pressure.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_conccutoff_oxygenminusone(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--conc_cutoff', 'oxygen:-1'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen-1_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_focus_(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--focus', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_oxygen.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timeseries_collapse_false(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_default(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_microenv(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_physiboss(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_settingxmlnone(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_cellattribute_dead(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'dead'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_dead.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_cellattribute_cellcountvoxel(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'cell_count_voxel'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_cell_count_voxel.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - def test_pcdl_make_ome_tiff_timestep_cellattribute_pressure(self): - s_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'pressure'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_oxygen_water_default_blood_cells_pressure.ome.tiff')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) - - ########################### # making movies test code # ########################### diff --git a/test/test_timeseries_2d.py b/test/test_timeseries_2d.py index 5dff0da..1d355f4 100644 --- a/test/test_timeseries_2d.py +++ b/test/test_timeseries_2d.py @@ -466,52 +466,6 @@ def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): for s_pathfile in ls_pathfile: os.remove(s_pathfile) -## ome tiff related functions ## -class TestPyMcdsOmeTiff(object): - ''' tests for pcdl.pyMCDS ome tiff related functions. ''' - mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=False) - - ## ome tiff related functions ## - def test_mcdsts_make_ome_tiff_defaultattr_00(self, mcdsts=mcdsts): - la_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (type(la_ometiff) is list) and \ - (type(la_ometiff[0]) is np.ndarray) and \ - (type(la_ometiff[-1]) is np.ndarray) and \ - (la_ometiff[0].dtype == np.float32) and \ - (la_ometiff[-1].dtype == np.float32) and \ - (la_ometiff[0].shape == (4, 1, 200, 300)) and \ - (la_ometiff[-1].shape == (4, 1, 200, 300)) and \ - (len(la_ometiff) == 25) - - def test_mcdsts_make_ome_tiff_defaultattr_01(self, mcdsts=mcdsts): - a_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (type(a_ometiff) is np.ndarray) and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (25, 4, 1, 200, 300)) - - def test_mcdsts_make_ome_tiff_defaultattr_10(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (ls_pathfile[0].endswith('pcdl/output_2d/output00000000_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (ls_pathfile[-1].endswith('pcdl/output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(ls_pathfile[0])) and \ - (os.path.exists(ls_pathfile[-1])) and \ - (os.path.getsize(ls_pathfile[0]) > 2**10) and\ - (os.path.getsize(ls_pathfile[-1]) > 2**10) and\ - (len(ls_pathfile) == 25) - for s_pathfile in ls_pathfile: - os.remove(s_pathfile) - - def test_mcdsts_make_ome_tiff_defaultattr_11(self, mcdsts=mcdsts): - s_pathfile = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (s_pathfile.endswith('pcdl/output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10 ) - os.remove(s_pathfile) - ## timeseries related functions ## diff --git a/test/test_timeseries_3d.py b/test/test_timeseries_3d.py index aad5a2a..a718dc5 100644 --- a/test/test_timeseries_3d.py +++ b/test/test_timeseries_3d.py @@ -393,31 +393,6 @@ def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): for s_pathfile in ls_pathfile: os.remove(s_pathfile) -## graph related functions ## -class TestPyMcds3DGraph(object): - ''' tests for pcdl.pyMCDS graph related functions. ''' - mcdsts = pcdl.pyMCDSts(s_path_3d, verbose=False) - - ## graph related functions ## - def test_mcdsts_make_ome_tiff_defaultattr_00(self, mcdsts=mcdsts): - la_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (type(la_ometiff) is list) and \ - (type(la_ometiff[0]) is np.ndarray) and \ - (type(la_ometiff[-1]) is np.ndarray) and \ - (la_ometiff[0].dtype == np.float32) and \ - (la_ometiff[-1].dtype == np.float32) and \ - (la_ometiff[0].shape == (4, 11, 200, 300)) and \ - (la_ometiff[-1].shape == (4, 11, 200, 300)) and \ - (len(la_ometiff) == 25) - - def test_mcdsts_make_ome_tiff_defaultattr_01(self, mcdsts=mcdsts): - a_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (type(a_ometiff) is np.ndarray) and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (25, 4, 11, 200, 300)) - ## timeseries related functions ## diff --git a/test/test_timestep_2d.py b/test/test_timestep_2d.py index 92182bf..f83278b 100644 --- a/test/test_timestep_2d.py +++ b/test/test_timestep_2d.py @@ -928,81 +928,3 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): (s_file.find('edge [\n source') > -1) and \ (s_file.find('distance_microns') > -1) os.remove(s_pathfile) - - -## ome tiff related functions ## - -class TestPyMcdsOmeTiff(object): - ''' tests for pcdl.pyMCDS graph related functions. ''' - mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) - - ## ome tiff related functions ## - def test_mcds_make_ome_tiff_default(self, mcds=mcds): - s_pathfile = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True) - assert(str(type(mcds)) == "") and \ - (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10) - os.remove(s_pathfile) - - def test_mcds_make_ome_tiff_bool(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='dead', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_int(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='cell_count_voxel', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_float(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='pressure', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and\ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_conccutoff(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={'oxygen': -1}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_focus(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus={'default'}, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (1, 1, 200, 300)) and \ - (a_ometiff[0].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) - diff --git a/test/test_timestep_3d.py b/test/test_timestep_3d.py index 7de347b..4af50c5 100644 --- a/test/test_timestep_3d.py +++ b/test/test_timestep_3d.py @@ -618,25 +618,3 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): (s_file.find('edge [\n source') > -1) and \ (s_file.find('distance_microns') > -1) os.remove(s_pathfile) - - -class TestPyMcds3dOmeTiffWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.pyMCDS ome tiff related workhorse functions. ''' - mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True - - ## ome tiff related functions ## - def test_mcds_make_ome_tiff_default(self, mcds=mcds): - s_pathfile = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True) - assert(str(type(mcds)) == "") and \ - (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10) - os.remove(s_pathfile) - - def test_mcds_make_ome_tiff_nofile(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 11, 200, 300)) - From ecbde94b586aeb2062da2a8021488517635ea454 Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 13 Mar 2025 20:35:27 -0400 Subject: [PATCH 02/41] @ make_cell_vtk : fix first attribute coloring for on the fly visualization. --- pcdl/pyMCDS.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index 4b37f00..acbac51 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -1419,8 +1419,8 @@ def make_conc_vtk(self, visualize=True): # build VTKLooktupTable (color scheme) vlt_color = vtk.vtkLookupTable() - vlt_color.SetNumberOfTableValues(256) - vlt_color.SetHueRange(0.667, 0.0) # blue-to-red rainbow + vlt_color.SetNumberOfTableValues(256) # number of color shades + vlt_color.SetHueRange(9/12, 0/12) # rainbow heat map vlt_color.Build() # generate xy cutting plane actor @@ -2162,11 +2162,24 @@ def make_cell_vtk(self, attribute=['cell_type'], visualize=True): # visualize if (visualize): + # select first attribute + s_attribute = attribute[0] + + # build VTKLooktupTable (color scheme) + vlt_color = vtk.vtkLookupTable() + i_element = df_cell.loc[:, s_attribute].unique().shape[0] + if (i_element > 256): + i_element = 256 + vlt_color.SetNumberOfTableValues(i_element) + vlt_color.SetHueRange(9/12, 0/12) # rainbow heat map + vlt_color.Build() + # set up the mapper vpdm_data = vtk.vtkPolyDataMapper() vpdm_data.SetInputConnection(vg_data.GetOutputPort()) vpdm_data.ScalarVisibilityOn() - #vpdm_data.ColorByArrayComponent(s_attribute, 0) # bue 20250110: not working and legacy better to do this in the lookup table. + vpdm_data.SetLookupTable(vlt_color) + vpdm_data.ColorByArrayComponent(s_attribute, 1) # set up the actor actor = vtk.vtkActor() From dbcc08b0dfabdb11cd8dc6115dc1ab7569e60eae Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 13 Mar 2025 20:59:18 -0400 Subject: [PATCH 03/41] @ pcdl : towards release v3.3.5. --- pcdl/pyMCDS.py | 1 - pcdl/pyMCDSts.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index acbac51..c4d7e4c 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -26,7 +26,6 @@ from scipy import io import sys import vtk -from vtkmodules.vtkCommonCore import vtkPoints import xml.etree.ElementTree as etree from pcdl.VERSION import __version__ diff --git a/pcdl/pyMCDSts.py b/pcdl/pyMCDSts.py index 49528e7..1003159 100644 --- a/pcdl/pyMCDSts.py +++ b/pcdl/pyMCDSts.py @@ -28,7 +28,6 @@ from pcdl.pyMCDS import pyMCDS, es_coor_cell, es_coor_conc import platform import sys -import xml.etree.ElementTree as etree ############ From a481477d87bae07a3cbe7ebe3c16c616ae4e12d6 Mon Sep 17 00:00:00 2001 From: bue Date: Mon, 17 Mar 2025 21:14:17 -0400 Subject: [PATCH 04/41] @ plot_scatter : set marker dot size based on cell volume. --- pcdl/pyCLI.py | 9 +++++---- pcdl/pyMCDS.py | 47 +++++++++++++++-------------------------------- pcdl/pyMCDSts.py | 13 +++++-------- 3 files changed, 25 insertions(+), 44 deletions(-) diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index 31b86c0..41590ba 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -1571,8 +1571,9 @@ def plot_scatter(): # plot_scatter s parser.add_argument( '--s', - default = 'none', - help = "scatter plot dot size in pixel. typographic points are 1/72 inch. the marker size s is specified in points**2. plt.rcParams['lines.markersize']**2 is in my case 36. None tries to take the value from the initial.svg file. fall back setting is 36. default is None.", + default = 1.0, + type = float, + help = 'scatter plot dot size scale factor. with figsizepx extracted from initial.svg, scale factor 1.0 should be ok. adjust if necessary. default 1.0.', ) # plot_scatter figsizepx parser.add_argument( @@ -1649,7 +1650,7 @@ def plot_scatter(): xlim = None if (args.xlim[0].lower() == 'none') else args.xlim, ylim = None if (args.ylim[0].lower() == 'none') else args.ylim, xyequal = False if args.xyequal.lower().startswith('f') else True, - s = None if (args.s.lower() == 'none') else int(args.s), + s = args.s, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], ext = args.ext, @@ -1682,7 +1683,7 @@ def plot_scatter(): xlim = None if (args.xlim[0].lower() == 'none') else args.xlim, ylim = None if (args.ylim[0].lower() == 'none') else args.ylim, xyequal = False if args.xyequal.lower().startswith('f') else True, - s = None if (args.s.lower() == 'none') else int(args.s), + s = args.s, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index c4d7e4c..4f4e6f5 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -1781,7 +1781,7 @@ def get_cell_df_at(self, x, y, z=0, values=1, drop=set(), keep=set()): return df_voxel - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=None, ax=None, figsizepx=None, ext=None, figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, ext=None, figbgcolor=None): """ input: focus: string; default is 'cell_type' @@ -1832,13 +1832,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma xyequal: boolean; default True to specify equal axis spacing for x and y axis. - s: integer; default is None - scatter plot dot size in pixel. - typographic points are 1/72 inch. - the marker size s is specified in points**2. - plt.rcParams['lines.markersize']**2 is in my case 36. - None tries to take the value from the initial.svg file. - fall back setting is 36. + s: floating point number; default is 1.0 + scatter plot dot size scale factor. + with figsizepx extracted from initial.svg, scale factor 1.0 + should be ok. adjust if necessary. ax: matplotlib axis object; default setting is None the ax object, which will be used as a canvas for plotting. @@ -1876,36 +1873,19 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma https://en.wikipedia.org/wiki/Portable_Network_Graphics https://en.wikipedia.org/wiki/TIFF """ - # handle initial.svg for s and figsizepx - if (s is None) or (figsizepx is None): + # handle initial.svg for figsizepx + if (figsizepx is None): s_pathfile = self.path + '/initial.svg' try: x_tree = etree.parse(s_pathfile) x_root = x_tree.getroot() - if s is None: - circle_element = x_root.find('.//{*}circle') - if not (circle_element is None): - r_radius = float(circle_element.get('r')) # px - s = int(round((r_radius)**2)) - else: - if self.verbose: - print(f'Warning @ pyMCDSts.plot_scatter : these agents are not circles.') - s = plt.rcParams['lines.markersize']**2 - if self.verbose: - print(f's set to {s}.') - if figsizepx is None: - i_width = int(np.ceil(float(x_root.get('width')))) # px - i_height = int(np.ceil(float(x_root.get('height')))) # px - figsizepx = [i_width, i_height] + i_width = int(np.ceil(float(x_root.get('width')) * 2)) # px + i_height = int(np.ceil(float(x_root.get('height')) * 2)) # px + figsizepx = [i_width, i_height] except FileNotFoundError: if self.verbose: print(f'Warning @ pyMCDSts.plot_scatter : could not load {s_pathfile}.') - if s is None: - s = plt.rcParams['lines.markersize']**2 - if self.verbose: - print(f's set to {s}.') - if figsizepx is None: - figsizepx = [640, 480] + figsizepx = [640, 480] # handle figure size figsizepx[0] = figsizepx[0] - (figsizepx[0] % 2) # enforce even pixel number @@ -1928,6 +1908,9 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma df_cell = self.get_cell_df(values=1, drop=set(), keep=set()) df_cell = df_cell.loc[(df_cell.mesh_center_p == z_slice),:] + # calculate marker size + df_cell.loc[:,'s'] = ((6 * df_cell.total_volume) / np.pi)**(2/3) # diamter of a sphere and plt.rcParams['lines.markersize']**2. + # handle z_axis categorical cases if (str(df_cell.loc[:,focus].dtype) in {'bool', 'object'}): lr_extrema = [None, None] @@ -2019,7 +2002,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma title = title, xlim = xlim, ylim = ylim, - s = s, + s = 's', grid = grid, ax = ax, ) diff --git a/pcdl/pyMCDSts.py b/pcdl/pyMCDSts.py index 1003159..7364969 100644 --- a/pcdl/pyMCDSts.py +++ b/pcdl/pyMCDSts.py @@ -828,7 +828,7 @@ def get_cell_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dl_variable_range - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=None, figsizepx=None, ext='jpeg', figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, ext='jpeg', figbgcolor=None): """ input: self: pyMCDSts class instance @@ -880,13 +880,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma xyequal: boolean; default True to specify equal axis spacing for x and y axis. - s: integer; default is None - scatter plot dot size in pixel. - typographic points are 1/72 inch. - the marker size s is specified in points**2. - plt.rcParams['lines.markersize']**2 is in my case 36. - None tries to take the value from the initial.svg file. - fall back setting is 36. + s: floating point number; default is 1.0 + scatter plot dot size scale factor. + with figsizepx extracted from initial.svg, scale factor 1.0 + should be ok. adjust if necessary. figsizepx: list of two integers; default is None size of the figure in pixels, (x, y). From 8744c494afd4039b6dae64613abda029be7506ca Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 13 May 2025 13:07:51 -0400 Subject: [PATCH 05/41] @ physicelldataloader : release v3.3.5. --- pcdl/VERSION.py | 2 +- pcdl/pyCLI.py | 77 ++++++++++++++++++++++++++++++++++++++++ pcdl/pyMCDS.py | 17 +++++++++ pyproject.toml | 1 + test/test_cli_2d.py | 58 ++++++++++++++++++++++++++---- test/test_timestep_2d.py | 7 ++++ 6 files changed, 155 insertions(+), 7 deletions(-) diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index 6c61f23..ee7075b 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '3.3.4' +__version__ = '3.3.5' diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index 41590ba..f619535 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -839,6 +839,83 @@ def get_celltype_list(): return mcds.get_celltype_list() +def get_cell_attribute_list(): + # argv + parser = argparse.ArgumentParser( + prog = 'pcdl_get_cell_attribute_list', + description = 'this function is returns a list with all cell attribute labels, alphabetically ordered.', + epilog = 'homepage: https://github.com/elmbeech/physicelldataloader', + ) + + # TimeSeries path + parser.add_argument( + 'path', + nargs = '?', + default = '.', + help = 'path to the PhysiCell output directory or a outputnnnnnnnn.xml file. default is . .', + ) + # TimeSeries output_path '.' + # TimeSeries custom_data_type nop + # TimeSeries microenv + parser.add_argument( + '--microenv', + default = 'true', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + ) + # TimeSeries graph False + # TimeSeries physiboss + parser.add_argument( + '--physiboss', + default = 'true', + help = 'if found, should physiboss state data be extracted and loaded into df_cell dataframe? default is True.' + ) + # TimeSeries settingxml + parser.add_argument( + '--settingxml', + default = 'PhysiCell_settings.xml', + help = 'the settings.xml that is loaded, from which the cell type ID label mapping, is extracted, if this information is not found in the output xml file. set to None or False if the xml file is missing! default is PhysiCell_settings.xml.', + ) + # TimeSeries verbose + parser.add_argument( + '-v', '--verbose', + default = 'false', + help = 'setting verbose to True for more text output, while processing. default is False.', + ) + + # parse arguments + args = parser.parse_args() + print(args) + + # process arguments + s_path = args.path.replace('\\','/') + while (s_path.find('//') > -1): + s_path = s_path.replace('//','/') + if (s_path.endswith('/')) and (len(s_path) > 1): + s_path = s_path[:-1] + s_pathfile = s_path + if not s_pathfile.endswith('.xml'): + s_pathfile = s_pathfile + '/initial.xml' + else: + s_path = '/'.join(s_path.split('/')[:-1]) + if not os.path.exists(s_pathfile): + sys.exit(f'Error @ pcdl_get_cell_attribute_list : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + + # run + mcds = pcdl.TimeStep( + xmlfile = s_pathfile, + output_path = '.', + #custom_data_type, + microenv = False if args.microenv.lower().startswith('f') else True, + graph = False, + physiboss = False if args.physiboss.lower().startswith('f') else True, + settingxml = None if ((args.settingxml.lower() == 'none') or (args.settingxml.lower() == 'false')) else args.settingxml, + verbose = True if args.verbose.lower().startswith('t') else False + ) + + # going home + return mcds.get_cell_attribute_list() + + def get_cell_attribute(): # argv parser = argparse.ArgumentParser( diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index 4f4e6f5..8a5fa0b 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -1781,6 +1781,23 @@ def get_cell_df_at(self, x, y, z=0, values=1, drop=set(), keep=set()): return df_voxel + def get_cell_attribute_list(self): + """ + input: + + output: + ls_cellattr: list of strings + alphabetically ordered list of all tracked cell attributes. + + description: + function returns a list with all cell attribute labels, + alphabetically ordered. + """ + df_cell = self.get_cell_df() + ls_cellattr = sorted(set(df_cell.columns).difference(es_coor_cell)) + return ls_cellattr + + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, ext=None, figbgcolor=None): """ input: diff --git a/pyproject.toml b/pyproject.toml index b52f21b..bb5d8bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ pcdl_plot_contour = "pcdl.pyCLI:plot_contour" pcdl_make_conc_vtk = "pcdl.pyCLI:make_conc_vtk" # cell agent pcdl_get_celltype_list = "pcdl.pyCLI:get_celltype_list" +pcdl_get_cell_attribute_list = "pcdl.pyCLI:get_cell_attribute_list" pcdl_get_cell_attribute = "pcdl.pyCLI:get_cell_attribute" pcdl_get_cell_df = "pcdl.pyCLI:get_cell_df" pcdl_get_anndata = "pcdl.pyCLI:get_anndata" diff --git a/test/test_cli_2d.py b/test/test_cli_2d.py index 2a0124e..f63240b 100644 --- a/test/test_cli_2d.py +++ b/test/test_cli_2d.py @@ -594,6 +594,52 @@ def test_pcdl_get_cell_attribute_timeseries_allvalues(self): os.remove(s_opathfile) +class TestPyCliCellAttributeList(object): + ''' tests for one pcdl command line interface function. ''' + + # timeseries collapsed: + # + path (str) nop + # + microenv (true, _false_) ok + # + physiboss (true, _false_) + # + settingxml (string, _none_, _false_) ok + # + verbose (true, _false_) nop + + def test_pcdl_get_cell_attribute_list_timeseries(self): + s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d], check=False, capture_output=True) + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') + s_listing = s_result.stderr.decode('UTF8').replace('\r','') + assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + + def test_pcdl_get_cell_attribute_list_timeseries_microenv(self): + s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') + s_listing = s_result.stderr.decode('UTF8').replace('\r','') + assert (s_listing.endswith("'uptake_rates_1', 'velocity_vectorlength', 'velocity_x', 'velocity_y', 'velocity_z']\n")) + + def test_pcdl_get_cell_attribute_list_timeseries_physiboss(self): + s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') + s_listing = s_result.stderr.decode('UTF8').replace('\r','') + assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + + def test_pcdl_get_cell_attribute_list_timeseries_settingxmlfalse(self): + s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') + s_listing = s_result.stderr.decode('UTF8').replace('\r','') + assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + + def test_pcdl_get_cell_attribute_list_timeseries_settingxmlnone(self): + s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') + s_listing = s_result.stderr.decode('UTF8').replace('\r','') + assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + + class TestPyCliCellDf(object): ''' tests for one pcdl.pyCli function. ''' @@ -1444,7 +1490,7 @@ def test_pcdl_plot_scatter_default(self): #print(f'\ns_result.stdout: {s_result.stdout}\n') #print(f'\ns_result.stderr: {s_result.stderr}\n') s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] + s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] s_opath = '/'.join(s_opathfile.split('/')[:-1]) assert (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/output00000024_cell_type.jpeg')) and \ @@ -1463,7 +1509,7 @@ def test_pcdl_plot_scatter_default(self): (s_stdout.find("xlim=['none']") > -1) and \ (s_stdout.find("ylim=['none']") > -1) and \ (s_stdout.find("xyequal='true'") > -1) and \ - (s_stdout.find("s='none'") > -1) and \ + (s_stdout.find("s=1.0") > -1) and \ (s_stdout.find("figsizepx=['none']") > -1) and \ (s_stdout.find("ext='jpeg'") > -1) and \ (s_stdout.find("figbgcolor='none'") > -1) @@ -1491,9 +1537,9 @@ def test_pcdl_plot_scatter_set(self): '--ext', 'tiff', '--figbgcolor', 'yellow', ], check=False, capture_output=True) - print(f'\ns_result: {s_result}\n') - print(f'\ns_result.stdout: {s_result.stdout}\n') - print(f'\ns_result.stderr: {s_result.stderr}\n') + #print(f'\ns_result: {s_result}\n') + #print(f'\ns_result.stdout: {s_result.stdout}\n') + #print(f'\ns_result.stderr: {s_result.stderr}\n') s_stdout = s_result.stdout.decode('UTF8').replace('\r','') s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') s_opath = '/'.join(s_opathfile.split('/')[:-1]) @@ -1514,7 +1560,7 @@ def test_pcdl_plot_scatter_set(self): (s_stdout.find("xlim=['-40', '400']") > -1) and \ (s_stdout.find("ylim=['-30', '300']") > -1) and \ (s_stdout.find("xyequal='false'") > -1) and \ - (s_stdout.find("s='74'") > -1) and \ + (s_stdout.find("s=74.0") > -1) and \ (s_stdout.find("figsizepx=['842', '531']") > -1) and \ (s_stdout.find("ext='tiff'") > -1) and \ (s_stdout.find("figbgcolor='yellow'") > -1) diff --git a/test/test_timestep_2d.py b/test/test_timestep_2d.py index f83278b..c114c58 100644 --- a/test/test_timestep_2d.py +++ b/test/test_timestep_2d.py @@ -598,6 +598,13 @@ class TestPyMcdsCell(object): ''' tests for pcdl.pyMCDS cell related functions. ''' mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) + def test_mcds_get_cell_attribute_list(self, mcds=mcds): + ls_cellattr = mcds.get_cell_attribute_list() + assert(str(type(mcds)) == "") and \ + (str(type(ls_cellattr)) == "") and \ + (str(type(ls_cellattr[0])) == "") and \ + (len(ls_cellattr) == 110) + def test_mcds_get_celltype_list(self, mcds=mcds): ls_celltype = mcds.get_celltype_list() assert(str(type(mcds)) == "") and \ From 9aec3c4d246d67e3b86b80e484021b960d093d3a Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 13 May 2025 14:07:18 -0400 Subject: [PATCH 06/41] @ physicelldataloader : next release v3.3.6. --- pcdl/VERSION.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index ee7075b..fe210a6 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '3.3.5' +__version__ = '3.3.6' diff --git a/pyproject.toml b/pyproject.toml index bb5d8bc..fc02989 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ classifiers = [ dependencies = [ "anndata>=0.10.8", "matplotlib", - "numpy<2.0.0", + "numpy>=2.0.0", "pandas>=2.2.2", "requests", "scipy>=1.13.0", From 92913838c1a3e3ed545ecdcd6032afc64325570e Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 15 May 2025 06:35:37 -0400 Subject: [PATCH 07/41] @ pcdl v3 : in the middle of galaxy wrapper implementation. --- pcdl/galaxy.sh | 222 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100755 pcdl/galaxy.sh diff --git a/pcdl/galaxy.sh b/pcdl/galaxy.sh new file mode 100755 index 0000000..c398883 --- /dev/null +++ b/pcdl/galaxy.sh @@ -0,0 +1,222 @@ +# pcdl_get_anndata +planemo tool_init --force \ +--id pcdl_get_anndata \ +--name pcdl_get_anndata \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_anndata output_3d 1 --custom_data_type --microenv true --graph true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --scale maxabs --collapse true' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_cell_maxabs.h5ad \ +--help_from_command 'pcdl_get_anndata --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_cell_attribute +planemo tool_init --force \ +--id pcdl_get_cell_attribute \ +--name pcdl_get_cell_attribute \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_cell_attribute output_3d 1 --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --allvalues false' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_cell_attribute_minmax.json \ +--help_from_command 'pcdl_get_cell_attribute --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_cell_attribute_list +planemo tool_init --force \ +--id pcdl_get_cell_attribute_list \ +--name pcdl_get_cell_attribute_list \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_cell_attribute_list output_3d --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false 2> attribute.txt' \ +--example_input output_3d/ \ +--example_output version.txt \ +--help_from_command 'pcdl_get_cell_attribute_list --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_cell_df +planemo tool_init --force \ +--id pcdl_get_cell_df \ +--name pcdl_get_cell_df \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_cell_df output_3d 1 --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --collapse true' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_cell.csv \ +--help_from_command 'pcdl_get_cell_df --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_celltype_list +planemo tool_init --force \ +--id pcdl_get_celltype_list \ +--name pcdl_get_celltype_list \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_celltype_list output_3d --settingxml PhysiCell_settings.xml --verbose false 2> celltype.txt' \ +--example_input output_3d/ \ +--example_output celltype.txt \ +--help_from_command 'pcdl_get_celltype_list --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_conc_attribute +planemo tool_init --force \ +--id pcdl_get_conc_attribute \ +--name pcdl_get_conc_attribute \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_conc_attribute output_3d 1 --verbose false --drop --keep --allvalues false' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_conc_attribute_minmax.json \ +--help_from_command 'pcdl_get_conc_attribute --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_conc_df +planemo tool_init --force \ +--id pcdl_get_conc_df \ +--name pcdl_get_conc_df \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_conc_df output_3d 1 --verbose false --drop --keep --collapse true' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_conc.csv \ +--help_from_command 'pcdl_get_conc_df --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_substrate_list +planemo tool_init --force \ +--id pcdl_get_substrate_list \ +--name pcdl_get_substrate_list \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_substrate_list output_3d --verbose false 2> substrate.txt' \ +--example_input output_3d/ \ +--example_output substrate.txt \ +--help_from_command 'pcdl_get_substrate_list --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_unit_dict +planemo tool_init --force \ +--id pcdl_get_unit_dict \ +--name pcdl_get_unit_dict \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_unit_dict output_3d --microenv true --settingxml PhysiCell_settings.xml --verbose false' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_unit.csv \ +--help_from_command 'pcdl_get_unit_dict --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_get_version +planemo tool_init --force \ +--id pcdl_get_version \ +--name pcdl_get_version \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_get_version output_3d --verbose false 2> version.txt' \ +--example_input output_3d/ \ +--example_output version.txt \ +--help_from_command 'pcdl_get_version --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_make_cell_vtk +planemo tool_init --force \ +--id pcdl_make_cell_vtk \ +--name pcdl_make_cell_vtk \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_make_cell_vtk output_3d/output00000000.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false' \ +--example_input output_3d/output00000000.xml \ +--example_output output_3d/output00000000_cell.vtp \ +--help_from_command 'pcdl_make_cell_vtk --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader +--test_case + +# pcdl_make_conc_vtk +planemo tool_init --force \ +--id pcdl_make_conc_vtk \ +--name pcdl_make_conc_vtk \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_make_conc_vtk output_3d/output00000000.xml --verbose false' \ +--example_input output_3d/output00000000.xml \ +--example_output output_3d/output00000000_conc.vtr \ +--help_from_command 'pcdl_make_conc_vtk --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader +--test_case + +# pcdl_make_graph_gml +planemo tool_init --force \ +--id pcdl_make_graph_gml \ +--name pcdl_make_graph_gml \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_make_graph_gml output_3d/output00000012.xml neighbor --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --edge_attribute true --node_attribute' \ +--example_input output_3d/output00000012.xml \ +--example_output output_3d/output00000012_neighbor.gml \ +--help_from_command 'pcdl_get_anndata --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader +--test_case + +# pcdl_plot_contour +planemo tool_init --force \ +--id pcdl_plot_contour \ +--name pcdl_plot_contour \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_plot_contour output_3d/output00000012.xml oxygen --verbose false --z_slice 0.0 --extrema none --alpha 1.0 --fill true --cmap viridis --title "" --grid true --xlim none --ylim none --xyequal true --figsizepx none --ext jpeg --figbgcolor none' \ +--example_input output_3d/output00000012.xml \ +--example_output output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg \ +--help_from_command 'pcdl_plot_contour --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader +--test_case + + +# pcdl_plot_scatter +planemo tool_init --force \ +--id pcdl_plot_scatter \ +--name pcdl_plot_scatter \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_plot_scatter output_3d/output00000012.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --z_slice 0.0 --z_axis none --alpha 1.0 --cmap viridis --title "" --grid true --legend_loc "lower left" --xlim none --ylim none --xyequal true --s 1.0 --figsizepx none --ext jpeg --figbgcolor none' \ +--example_input output_3d/output00000012.xml \ +--example_output output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg \ +--help_from_command 'pcdl_plot_scatter --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader +--test_case + +# pcdl_plot_timeseries +planemo tool_init --force \ +--id pcdl_plot_timeseries \ +--name pcdl_plot_timeseries \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_plot_timeseries output_3d none none mean --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --frame cell --z_slice 0.0 --logy false --ylim none --secondary_y false --subplots false --sharex false --sharey false --linestyle - --linewidth none --cmap none --color none --grid true --legend true --yunit none --title none --figsizepx 640 480 --ext jpeg --figbgcolor none' \ +--example_input output_3d/ \ +--example_output output_3d/timeseries_cell_total_count.jpeg \ +--help_from_command 'pcdl_plot_timeseries --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_make_gif +planemo tool_init --force \ +--id pcdl_make_gif \ +--name pcdl_make_gif \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_make_gif output_3d/cell_cell_type_z-5.0 jpeg' \ +--example_input output_3d/cell_cell_type_z-5.0 \ +--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg.gif \ +--help_from_command 'pcdl_make_gif --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + +# pcdl_make_movie +planemo tool_init --force \ +--id pcdl_make_movie \ +--name pcdl_make_movie \ +--version 3.0.0 \ +--requirement pcdl@3.3.6 \ +--example_command 'pcdl_make_movie output_3d/cell_cell_type_z-5.0 jpeg' \ +--example_input output_3d/cell_cell_type_z-5.0 \ +--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg12.mp4 \ +--help_from_command 'pcdl_make_movie --help' \ +--cite_url https://github.com/elmbeech/physicelldataloader + From 456f4d935cbac7f452b6b57a6ff16edb9be2df87 Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 15 May 2025 06:44:17 -0400 Subject: [PATCH 08/41] @ pcdl v3 : in the middle of galaxy wrapper implementation. --- pcdl/galaxy.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pcdl/galaxy.sh b/pcdl/galaxy.sh index c398883..e8696b9 100755 --- a/pcdl/galaxy.sh +++ b/pcdl/galaxy.sh @@ -127,9 +127,9 @@ planemo tool_init --force \ --example_command 'pcdl_make_cell_vtk output_3d/output00000000.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false' \ --example_input output_3d/output00000000.xml \ --example_output output_3d/output00000000_cell.vtp \ +--test_case \ --help_from_command 'pcdl_make_cell_vtk --help' \ --cite_url https://github.com/elmbeech/physicelldataloader ---test_case # pcdl_make_conc_vtk planemo tool_init --force \ @@ -140,9 +140,9 @@ planemo tool_init --force \ --example_command 'pcdl_make_conc_vtk output_3d/output00000000.xml --verbose false' \ --example_input output_3d/output00000000.xml \ --example_output output_3d/output00000000_conc.vtr \ +--test_case \ --help_from_command 'pcdl_make_conc_vtk --help' \ --cite_url https://github.com/elmbeech/physicelldataloader ---test_case # pcdl_make_graph_gml planemo tool_init --force \ @@ -153,9 +153,9 @@ planemo tool_init --force \ --example_command 'pcdl_make_graph_gml output_3d/output00000012.xml neighbor --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --edge_attribute true --node_attribute' \ --example_input output_3d/output00000012.xml \ --example_output output_3d/output00000012_neighbor.gml \ +--test_case \ --help_from_command 'pcdl_get_anndata --help' \ --cite_url https://github.com/elmbeech/physicelldataloader ---test_case # pcdl_plot_contour planemo tool_init --force \ @@ -166,9 +166,9 @@ planemo tool_init --force \ --example_command 'pcdl_plot_contour output_3d/output00000012.xml oxygen --verbose false --z_slice 0.0 --extrema none --alpha 1.0 --fill true --cmap viridis --title "" --grid true --xlim none --ylim none --xyequal true --figsizepx none --ext jpeg --figbgcolor none' \ --example_input output_3d/output00000012.xml \ --example_output output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg \ +--test_case \ --help_from_command 'pcdl_plot_contour --help' \ --cite_url https://github.com/elmbeech/physicelldataloader ---test_case # pcdl_plot_scatter @@ -180,9 +180,9 @@ planemo tool_init --force \ --example_command 'pcdl_plot_scatter output_3d/output00000012.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --z_slice 0.0 --z_axis none --alpha 1.0 --cmap viridis --title "" --grid true --legend_loc "lower left" --xlim none --ylim none --xyequal true --s 1.0 --figsizepx none --ext jpeg --figbgcolor none' \ --example_input output_3d/output00000012.xml \ --example_output output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg \ +--test_case \ --help_from_command 'pcdl_plot_scatter --help' \ --cite_url https://github.com/elmbeech/physicelldataloader ---test_case # pcdl_plot_timeseries planemo tool_init --force \ From d86aac3be6f66a61982a860bf75982dddef45cb9 Mon Sep 17 00:00:00 2001 From: bue Date: Thu, 15 May 2025 20:32:09 -0400 Subject: [PATCH 09/41] @ pcdl : first version of galaxy wrappers. --- pcdl/galaxy.sh | 455 +++++++++++++++++++------------------ pcdl/pcdl_get_version.xml | 75 ++++++ pcdl/pcdl_plot_contour.xml | 156 +++++++++++++ 3 files changed, 467 insertions(+), 219 deletions(-) create mode 100644 pcdl/pcdl_get_version.xml create mode 100644 pcdl/pcdl_plot_contour.xml diff --git a/pcdl/galaxy.sh b/pcdl/galaxy.sh index e8696b9..f84572a 100755 --- a/pcdl/galaxy.sh +++ b/pcdl/galaxy.sh @@ -1,222 +1,239 @@ -# pcdl_get_anndata -planemo tool_init --force \ ---id pcdl_get_anndata \ ---name pcdl_get_anndata \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_anndata output_3d 1 --custom_data_type --microenv true --graph true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --scale maxabs --collapse true' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_cell_maxabs.h5ad \ ---help_from_command 'pcdl_get_anndata --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_cell_attribute -planemo tool_init --force \ ---id pcdl_get_cell_attribute \ ---name pcdl_get_cell_attribute \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_cell_attribute output_3d 1 --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --allvalues false' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_cell_attribute_minmax.json \ ---help_from_command 'pcdl_get_cell_attribute --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_cell_attribute_list -planemo tool_init --force \ ---id pcdl_get_cell_attribute_list \ ---name pcdl_get_cell_attribute_list \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_cell_attribute_list output_3d --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false 2> attribute.txt' \ ---example_input output_3d/ \ ---example_output version.txt \ ---help_from_command 'pcdl_get_cell_attribute_list --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_cell_df -planemo tool_init --force \ ---id pcdl_get_cell_df \ ---name pcdl_get_cell_df \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_cell_df output_3d 1 --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --collapse true' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_cell.csv \ ---help_from_command 'pcdl_get_cell_df --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_celltype_list -planemo tool_init --force \ ---id pcdl_get_celltype_list \ ---name pcdl_get_celltype_list \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_celltype_list output_3d --settingxml PhysiCell_settings.xml --verbose false 2> celltype.txt' \ ---example_input output_3d/ \ ---example_output celltype.txt \ ---help_from_command 'pcdl_get_celltype_list --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_conc_attribute -planemo tool_init --force \ ---id pcdl_get_conc_attribute \ ---name pcdl_get_conc_attribute \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_conc_attribute output_3d 1 --verbose false --drop --keep --allvalues false' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_conc_attribute_minmax.json \ ---help_from_command 'pcdl_get_conc_attribute --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_conc_df -planemo tool_init --force \ ---id pcdl_get_conc_df \ ---name pcdl_get_conc_df \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_conc_df output_3d 1 --verbose false --drop --keep --collapse true' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_conc.csv \ ---help_from_command 'pcdl_get_conc_df --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_substrate_list -planemo tool_init --force \ ---id pcdl_get_substrate_list \ ---name pcdl_get_substrate_list \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_substrate_list output_3d --verbose false 2> substrate.txt' \ ---example_input output_3d/ \ ---example_output substrate.txt \ ---help_from_command 'pcdl_get_substrate_list --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_unit_dict -planemo tool_init --force \ ---id pcdl_get_unit_dict \ ---name pcdl_get_unit_dict \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_unit_dict output_3d --microenv true --settingxml PhysiCell_settings.xml --verbose false' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_unit.csv \ ---help_from_command 'pcdl_get_unit_dict --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader +## pcdl_get_anndata +#planemo tool_init --force \ +#--id pcdl_get_anndata \ +#--name pcdl_get_anndata \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_anndata output_3d 1 --custom_data_type --microenv true --graph true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --scale maxabs --collapse true' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_cell_maxabs.h5ad \ +#--test_case \ +#--help_from_command 'pcdl_get_anndata --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_cell_attribute +#planemo tool_init --force \ +#--id pcdl_get_cell_attribute \ +#--name pcdl_get_cell_attribute \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_cell_attribute output_3d 1 --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --allvalues false' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_cell_attribute_minmax.json \ +#--test_case \ +#--help_from_command 'pcdl_get_cell_attribute --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_cell_attribute_list +#planemo tool_init --force \ +#--id pcdl_get_cell_attribute_list \ +#--name pcdl_get_cell_attribute_list \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_cell_attribute_list output_3d --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false 2> attribute.txt' \ +#--example_input output_3d/ \ +#--example_output version.txt \ +#--test_case \ +#--help_from_command 'pcdl_get_cell_attribute_list --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_cell_df +#planemo tool_init --force \ +#--id pcdl_get_cell_df \ +#--name pcdl_get_cell_df \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_cell_df output_3d 1 --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --collapse true' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_cell.csv \ +#--test_case \ +#--help_from_command 'pcdl_get_cell_df --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_celltype_list +#planemo tool_init --force \ +#--id pcdl_get_celltype_list \ +#--name pcdl_get_celltype_list \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_celltype_list output_3d --settingxml PhysiCell_settings.xml --verbose false 2> celltype.txt' \ +#--example_input output_3d/ \ +#--example_output celltype.txt \ +#--test_case \ +#--help_from_command 'pcdl_get_celltype_list --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_conc_attribute +#planemo tool_init --force \ +#--id pcdl_get_conc_attribute \ +#--name pcdl_get_conc_attribute \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_conc_attribute output_3d 1 --verbose false --drop --keep --allvalues false' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_conc_attribute_minmax.json \ +#--test_case \ +#--help_from_command 'pcdl_get_conc_attribute --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_conc_df +#planemo tool_init --force \ +#--id pcdl_get_conc_df \ +#--name pcdl_get_conc_df \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_conc_df output_3d 1 --verbose false --drop --keep --collapse true' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_conc.csv \ +#--test_case \ +#--help_from_command 'pcdl_get_conc_df --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_substrate_list +#planemo tool_init --force \ +#--id pcdl_get_substrate_list \ +#--name pcdl_get_substrate_list \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_substrate_list output_3d --verbose false 2> substrate.txt' \ +#--example_input output_3d/ \ +#--example_output substrate.txt \ +#--test_case \ +#--help_from_command 'pcdl_get_substrate_list --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_get_unit_dict +#planemo tool_init --force \ +#--id pcdl_get_unit_dict \ +#--name pcdl_get_unit_dict \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_unit_dict output_3d --microenv true --settingxml PhysiCell_settings.xml --verbose false' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_unit.csv \ +#--test_case \ +#--help_from_command 'pcdl_get_unit_dict --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader # pcdl_get_version -planemo tool_init --force \ ---id pcdl_get_version \ ---name pcdl_get_version \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_get_version output_3d --verbose false 2> version.txt' \ ---example_input output_3d/ \ ---example_output version.txt \ ---help_from_command 'pcdl_get_version --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_make_cell_vtk -planemo tool_init --force \ ---id pcdl_make_cell_vtk \ ---name pcdl_make_cell_vtk \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_make_cell_vtk output_3d/output00000000.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false' \ ---example_input output_3d/output00000000.xml \ ---example_output output_3d/output00000000_cell.vtp \ ---test_case \ ---help_from_command 'pcdl_make_cell_vtk --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_make_conc_vtk -planemo tool_init --force \ ---id pcdl_make_conc_vtk \ ---name pcdl_make_conc_vtk \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_make_conc_vtk output_3d/output00000000.xml --verbose false' \ ---example_input output_3d/output00000000.xml \ ---example_output output_3d/output00000000_conc.vtr \ ---test_case \ ---help_from_command 'pcdl_make_conc_vtk --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_make_graph_gml -planemo tool_init --force \ ---id pcdl_make_graph_gml \ ---name pcdl_make_graph_gml \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_make_graph_gml output_3d/output00000012.xml neighbor --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --edge_attribute true --node_attribute' \ ---example_input output_3d/output00000012.xml \ ---example_output output_3d/output00000012_neighbor.gml \ ---test_case \ ---help_from_command 'pcdl_get_anndata --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_plot_contour -planemo tool_init --force \ ---id pcdl_plot_contour \ ---name pcdl_plot_contour \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_plot_contour output_3d/output00000012.xml oxygen --verbose false --z_slice 0.0 --extrema none --alpha 1.0 --fill true --cmap viridis --title "" --grid true --xlim none --ylim none --xyequal true --figsizepx none --ext jpeg --figbgcolor none' \ ---example_input output_3d/output00000012.xml \ ---example_output output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg \ ---test_case \ ---help_from_command 'pcdl_plot_contour --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - - -# pcdl_plot_scatter -planemo tool_init --force \ ---id pcdl_plot_scatter \ ---name pcdl_plot_scatter \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_plot_scatter output_3d/output00000012.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --z_slice 0.0 --z_axis none --alpha 1.0 --cmap viridis --title "" --grid true --legend_loc "lower left" --xlim none --ylim none --xyequal true --s 1.0 --figsizepx none --ext jpeg --figbgcolor none' \ ---example_input output_3d/output00000012.xml \ ---example_output output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg \ ---test_case \ ---help_from_command 'pcdl_plot_scatter --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_plot_timeseries -planemo tool_init --force \ ---id pcdl_plot_timeseries \ ---name pcdl_plot_timeseries \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_plot_timeseries output_3d none none mean --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --frame cell --z_slice 0.0 --logy false --ylim none --secondary_y false --subplots false --sharex false --sharey false --linestyle - --linewidth none --cmap none --color none --grid true --legend true --yunit none --title none --figsizepx 640 480 --ext jpeg --figbgcolor none' \ ---example_input output_3d/ \ ---example_output output_3d/timeseries_cell_total_count.jpeg \ ---help_from_command 'pcdl_plot_timeseries --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_make_gif -planemo tool_init --force \ ---id pcdl_make_gif \ ---name pcdl_make_gif \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_make_gif output_3d/cell_cell_type_z-5.0 jpeg' \ ---example_input output_3d/cell_cell_type_z-5.0 \ ---example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg.gif \ ---help_from_command 'pcdl_make_gif --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_make_movie -planemo tool_init --force \ ---id pcdl_make_movie \ ---name pcdl_make_movie \ ---version 3.0.0 \ ---requirement pcdl@3.3.6 \ ---example_command 'pcdl_make_movie output_3d/cell_cell_type_z-5.0 jpeg' \ ---example_input output_3d/cell_cell_type_z-5.0 \ ---example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg12.mp4 \ ---help_from_command 'pcdl_make_movie --help' \ ---cite_url https://github.com/elmbeech/physicelldataloader +#planemo tool_init --force \ +#--id pcdl_get_version \ +#--name pcdl_get_version \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_get_version output_3d --verbose false 2> version.txt' \ +#--example_input output_3d/ \ +#--example_output version.txt \ +#--test_case \ +#--help_from_command 'pcdl_get_version --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_make_cell_vtk +#rm output_3d/output00000000_cell.vtp +#planemo tool_init --force \ +#--id pcdl_make_cell_vtk \ +#--name pcdl_make_cell_vtk \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_make_cell_vtk output_3d/output00000000.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false' \ +#--example_input output_3d/output00000000.xml \ +#--example_output output_3d/output00000000_cell.vtp \ +#--test_case \ +#--help_from_command 'pcdl_make_cell_vtk --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_make_conc_vtk +#rm output_3d/output00000000_conc.vtr +#planemo tool_init --force \ +#--id pcdl_make_conc_vtk \ +#--name pcdl_make_conc_vtk \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_make_conc_vtk output_3d/output00000000.xml --verbose false' \ +#--example_input output_3d/output00000000.xml \ +#--example_output output_3d/output00000000_conc.vtr \ +#--test_case \ +#--help_from_command 'pcdl_make_conc_vtk --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_make_graph_gml +#rm output_3d/output00000012_neighbor.gml +#planemo tool_init --force \ +#--id pcdl_make_graph_gml \ +#--name pcdl_make_graph_gml \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_make_graph_gml output_3d/output00000012.xml neighbor --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --edge_attribute true --node_attribute' \ +#--example_input output_3d/output00000012.xml \ +#--example_output output_3d/output00000012_neighbor.gml \ +#--test_case \ +#--help_from_command 'pcdl_get_anndata --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_plot_contour +#rm output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg +#planemo tool_init --force \ +#--id pcdl_plot_contour \ +#--name pcdl_plot_contour \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_plot_contour output_3d/output00000012.xml oxygen --verbose false --z_slice 0.0 --extrema none --alpha 1.0 --fill true --cmap viridis --title "" --grid true --xlim none --ylim none --xyequal true --figsizepx none --ext jpeg --figbgcolor none' \ +#--example_input output_3d/output00000012.xml \ +#--example_output output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg \ +#--test_case \ +#--help_from_command 'pcdl_plot_contour --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_plot_scatter +#rm output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg +#planemo tool_init --force \ +#--id pcdl_plot_scatter \ +#--name pcdl_plot_scatter \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_plot_scatter output_3d/output00000012.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --z_slice 0.0 --z_axis none --alpha 1.0 --cmap viridis --title "" --grid true --legend_loc "lower left" --xlim none --ylim none --xyequal true --s 1.0 --figsizepx none --ext jpeg --figbgcolor none' \ +#--example_input output_3d/output00000012.xml \ +#--example_output output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg \ +#--test_case \ +#--help_from_command 'pcdl_plot_scatter --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_plot_timeseries +#planemo tool_init --force \ +#--id pcdl_plot_timeseries \ +#--name pcdl_plot_timeseries \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_plot_timeseries output_3d none none mean --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --frame cell --z_slice 0.0 --logy false --ylim none --secondary_y false --subplots false --sharex false --sharey false --linestyle - --linewidth none --cmap none --color none --grid true --legend true --yunit none --title none --figsizepx 640 480 --ext jpeg --figbgcolor none' \ +#--example_input output_3d/ \ +#--example_output output_3d/timeseries_cell_total_count.jpeg \ +#--test_case \ +#--help_from_command 'pcdl_plot_timeseries --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_make_gif +#planemo tool_init --force \ +#--id pcdl_make_gif \ +#--name pcdl_make_gif \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_make_gif output_3d/cell_cell_type_z-5.0 jpeg' \ +#--example_input output_3d/cell_cell_type_z-5.0 \ +#--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg.gif \ +#--test_case \ +#--help_from_command 'pcdl_make_gif --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader + +## pcdl_make_movie +#planemo tool_init --force \ +#--id pcdl_make_movie \ +#--name pcdl_make_movie \ +#--version 3.0.0 \ +#--requirement pcdl@3.3.6 \ +#--example_command 'pcdl_make_movie output_3d/cell_cell_type_z-5.0 jpeg' \ +#--example_input output_3d/cell_cell_type_z-5.0 \ +#--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg12.mp4 \ +#--test_case \ +#--help_from_command 'pcdl_make_movie --help' \ +#--cite_url https://github.com/elmbeech/physicelldataloader diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml new file mode 100644 index 0000000..8137e92 --- /dev/null +++ b/pcdl/pcdl_get_version.xml @@ -0,0 +1,75 @@ + + + pcdl + + + + + + $version + ]]> + + +
+ +
+ +
+ +
+
+ + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_plot_contour.xml b/pcdl/pcdl_plot_contour.xml new file mode 100644 index 0000000..a29e6bf --- /dev/null +++ b/pcdl/pcdl_plot_contour.xml @@ -0,0 +1,156 @@ + + + pcdl + + + $sterr + + ]]> + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
From ac5c64724201ac95071e6e20aa87e8aa8b46465c Mon Sep 17 00:00:00 2001 From: bue Date: Fri, 16 May 2025 13:03:31 -0400 Subject: [PATCH 10/41] @ pcdl : in the middle of first version of galaxy wrappers implementation. --- pcdl/pcdl_get_anndata.xml | 114 ++++++++++++++++++++++++++ pcdl/pcdl_get_cell_attribute.xml | 97 ++++++++++++++++++++++ pcdl/pcdl_get_cell_attribute_list.xml | 72 ++++++++++++++++ pcdl/pcdl_get_version.xml | 26 ++---- pcdl/pcdl_make_gif.xml | 61 ++++++++++++++ pcdl/pcdl_plot_contour.xml | 72 +++++++--------- 6 files changed, 380 insertions(+), 62 deletions(-) create mode 100644 pcdl/pcdl_get_anndata.xml create mode 100644 pcdl/pcdl_get_cell_attribute.xml create mode 100644 pcdl/pcdl_get_cell_attribute_list.xml create mode 100644 pcdl/pcdl_make_gif.xml diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml new file mode 100644 index 0000000..09937db --- /dev/null +++ b/pcdl/pcdl_get_anndata.xml @@ -0,0 +1,114 @@ +microenv + + pcdl + + + + + + +
+ + +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml new file mode 100644 index 0000000..1513384 --- /dev/null +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -0,0 +1,97 @@ + + + pcdl + + + + + + +
+ + +
+ +
+ + + + + + + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_cell_attribute_list.xml b/pcdl/pcdl_get_cell_attribute_list.xml new file mode 100644 index 0000000..143c59b --- /dev/null +++ b/pcdl/pcdl_get_cell_attribute_list.xml @@ -0,0 +1,72 @@ + + + + pcdl + + + stdio> + $cell_attribute_txt + ]]> + + +
+ +
+ +
+ + + + +
+
+ + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index 8137e92..7b47bca 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -3,14 +3,8 @@ pcdl - - - - + $version + pcdl_get_version output_pc --verbose $verbose 2> $version_txt ]]> -
+
-
+
- + @@ -55,12 +46,11 @@ + @misc{githubphysicelldataloader, diff --git a/pcdl/pcdl_make_gif.xml b/pcdl/pcdl_make_gif.xml new file mode 100644 index 0000000..43a4eb4 --- /dev/null +++ b/pcdl/pcdl_make_gif.xml @@ -0,0 +1,61 @@ + + + pcdl + + + + + + +
+ + + + + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_plot_contour.xml b/pcdl/pcdl_plot_contour.xml index a29e6bf..1854a78 100644 --- a/pcdl/pcdl_plot_contour.xml +++ b/pcdl/pcdl_plot_contour.xml @@ -3,7 +3,8 @@ pcdl - + $sterr - + pcdl_plot_contour output_pc $focus --verbose $verbose --z_slice $z_slice --extrema $extrema --alpha $alpha --fill $fill --cmap $cmap --title "$title" --grid $grid --xlim $xlim --ylim $ylim --xyequal $xyequal --figsizepx $figsizepx --ext $ext --figbgcolor $figbgcolor ]]> -
+
-
+
- + - +
- + + + - + From 9f086c0f48db5b97924e84ae024d03e24a66a454 Mon Sep 17 00:00:00 2001 From: bue Date: Sat, 17 May 2025 07:56:59 -0400 Subject: [PATCH 11/41] @ pcdl : in the middle of first version of galaxy wrappers implementation. --- pcdl/pcdl_get_anndata.xml | 2 +- pcdl/pcdl_get_cell_attribute.xml | 6 +- pcdl/pcdl_get_cell_attribute_list.xml | 6 +- pcdl/pcdl_get_cell_df.xml | 93 +++++++++++++++++++++++++++ pcdl/pcdl_get_celltype_list.xml | 62 ++++++++++++++++++ pcdl/pcdl_get_substrate_list.xml | 57 ++++++++++++++++ pcdl/pcdl_get_unit_dict.xml | 67 +++++++++++++++++++ pcdl/pcdl_get_version.xml | 2 +- pcdl/pcdl_make_gif.xml | 5 +- pcdl/pcdl_make_movie.xml | 66 +++++++++++++++++++ 10 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 pcdl/pcdl_get_cell_df.xml create mode 100644 pcdl/pcdl_get_celltype_list.xml create mode 100644 pcdl/pcdl_get_substrate_list.xml create mode 100644 pcdl/pcdl_get_unit_dict.xml create mode 100644 pcdl/pcdl_make_movie.xml diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml index 09937db..a44b552 100644 --- a/pcdl/pcdl_get_anndata.xml +++ b/pcdl/pcdl_get_anndata.xml @@ -1,4 +1,4 @@ -microenv + pcdl diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml index 1513384..9b04863 100644 --- a/pcdl/pcdl_get_cell_attribute.xml +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -38,12 +38,12 @@ - + pcdl - stdio> + + + pcdl + + + + + + +
+ + +
+ +
+ + + + + + + +
+
+ + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_celltype_list.xml b/pcdl/pcdl_get_celltype_list.xml new file mode 100644 index 0000000..a6d20a4 --- /dev/null +++ b/pcdl/pcdl_get_celltype_list.xml @@ -0,0 +1,62 @@ + + + pcdl + + + + $celltype_txt + ]]> + + +
+ +
+ +
+ + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_substrate_list.xml b/pcdl/pcdl_get_substrate_list.xml new file mode 100644 index 0000000..f205595 --- /dev/null +++ b/pcdl/pcdl_get_substrate_list.xml @@ -0,0 +1,57 @@ + + + pcdl + + + + $substrate_txt + ]]> + + +
+ +
+ +
+ +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_unit_dict.xml b/pcdl/pcdl_get_unit_dict.xml new file mode 100644 index 0000000..fae60e1 --- /dev/null +++ b/pcdl/pcdl_get_unit_dict.xml @@ -0,0 +1,67 @@ + + + pcdl + + + + + + +
+ +
+ +
+ + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index 7b47bca..d4dc244 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -41,7 +41,7 @@ - + diff --git a/pcdl/pcdl_make_gif.xml b/pcdl/pcdl_make_gif.xml index 43a4eb4..d24ce08 100644 --- a/pcdl/pcdl_make_gif.xml +++ b/pcdl/pcdl_make_gif.xml @@ -25,10 +25,7 @@ diff --git a/pcdl/pcdl_make_movie.xml b/pcdl/pcdl_make_movie.xml new file mode 100644 index 0000000..dd612d8 --- /dev/null +++ b/pcdl/pcdl_make_movie.xml @@ -0,0 +1,66 @@ + + + pcdl + + + + + + +
+ + + + + + +
+ +
+ +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
From af07e02b4253068d667d31d131d8961367ed0e95 Mon Sep 17 00:00:00 2001 From: bue Date: Sat, 17 May 2025 15:06:28 -0400 Subject: [PATCH 12/41] @ pcdl : in the middle of first version of galaxy wrappers implementation. --- pcdl/galaxy.sh | 2 +- pcdl/pcdl_get_cell_attribute.xml | 2 +- pcdl/pcdl_get_cell_df.xml | 2 +- pcdl/pcdl_get_conc_attribute.xml | 79 +++++++++++ pcdl/pcdl_get_conc_df.xml | 77 +++++++++++ pcdl/pcdl_make_cell_vtk.xml | 89 +++++++++++++ pcdl/pcdl_make_conc_vtk.xml | 56 ++++++++ pcdl/pcdl_make_graph_gml.xml | 102 +++++++++++++++ pcdl/pcdl_plot_scatter.xml | 174 +++++++++++++++++++++++++ pcdl/pcdl_plot_timeseries.xml | 217 +++++++++++++++++++++++++++++++ 10 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 pcdl/pcdl_get_conc_attribute.xml create mode 100644 pcdl/pcdl_get_conc_df.xml create mode 100644 pcdl/pcdl_make_cell_vtk.xml create mode 100644 pcdl/pcdl_make_conc_vtk.xml create mode 100644 pcdl/pcdl_make_graph_gml.xml create mode 100644 pcdl/pcdl_plot_scatter.xml create mode 100644 pcdl/pcdl_plot_timeseries.xml diff --git a/pcdl/galaxy.sh b/pcdl/galaxy.sh index f84572a..679a6d0 100755 --- a/pcdl/galaxy.sh +++ b/pcdl/galaxy.sh @@ -167,7 +167,7 @@ #--example_input output_3d/output00000012.xml \ #--example_output output_3d/output00000012_neighbor.gml \ #--test_case \ -#--help_from_command 'pcdl_get_anndata --help' \ +#--help_from_command 'pcdl_make_graph_gml --help' \ #--cite_url https://github.com/elmbeech/physicelldataloader ## pcdl_plot_contour diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml index 9b04863..14643b3 100644 --- a/pcdl/pcdl_get_cell_attribute.xml +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -68,7 +68,7 @@
diff --git a/pcdl/pcdl_get_cell_df.xml b/pcdl/pcdl_get_cell_df.xml index 6c289b5..fa2275a 100644 --- a/pcdl/pcdl_get_cell_df.xml +++ b/pcdl/pcdl_get_cell_df.xml @@ -38,7 +38,7 @@ + + pcdl + + + + + + + +
+ + +
+ +
+ + + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + + diff --git a/pcdl/pcdl_get_conc_df.xml b/pcdl/pcdl_get_conc_df.xml new file mode 100644 index 0000000..0bf874a --- /dev/null +++ b/pcdl/pcdl_get_conc_df.xml @@ -0,0 +1,77 @@ + + + pcdl + + + + + + +
+ + +
+ +
+ + + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_make_cell_vtk.xml b/pcdl/pcdl_make_cell_vtk.xml new file mode 100644 index 0000000..18484b6 --- /dev/null +++ b/pcdl/pcdl_make_cell_vtk.xml @@ -0,0 +1,89 @@ + + + pcdl + + + + + + +
+ + +
+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_make_conc_vtk.xml b/pcdl/pcdl_make_conc_vtk.xml new file mode 100644 index 0000000..8210282 --- /dev/null +++ b/pcdl/pcdl_make_conc_vtk.xml @@ -0,0 +1,56 @@ + + + pcdl + + + + + +
+ +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_make_graph_gml.xml b/pcdl/pcdl_make_graph_gml.xml new file mode 100644 index 0000000..24b6ee2 --- /dev/null +++ b/pcdl/pcdl_make_graph_gml.xml @@ -0,0 +1,102 @@ + + + pcdl + + + + + + +
+ + + + + + +
+ +
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_plot_scatter.xml b/pcdl/pcdl_plot_scatter.xml new file mode 100644 index 0000000..ecf0894 --- /dev/null +++ b/pcdl/pcdl_plot_scatter.xml @@ -0,0 +1,174 @@ + + + pcdl + + + + + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml new file mode 100644 index 0000000..0cb97ba --- /dev/null +++ b/pcdl/pcdl_plot_timeseries.xml @@ -0,0 +1,217 @@ + + + pcdl + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + @misc{githubphysicelldataloader, + author = {Bucher, Elmar}, + year = {2025}, + title = {physicelldataloader}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/elmbeech/physicelldataloader}, + } + +
From 1425f7e625ea334d063111b981e0423577b7467e Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 18 May 2025 14:29:12 -0400 Subject: [PATCH 13/41] @ pcdl : in the middle of first version of galaxy wrappers implementation. --- pcdl/pcdl_get_anndata.xml | 8 ++++---- pcdl/pcdl_get_conc_attribute.xml | 1 - pcdl/pcdl_make_gif.xml | 4 ++-- pcdl/pcdl_make_graph_gml.xml | 4 ++-- pcdl/pcdl_make_movie.xml | 4 ++-- pcdl/pcdl_plot_contour.xml | 4 ++-- pcdl/pcdl_plot_scatter.xml | 12 ++++++------ pcdl/pcdl_plot_timeseries.xml | 22 +++++++++++----------- pcdl/test-data | 1 + 9 files changed, 30 insertions(+), 30 deletions(-) create mode 120000 pcdl/test-data diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml index a44b552..ee934a5 100644 --- a/pcdl/pcdl_get_anndata.xml +++ b/pcdl/pcdl_get_anndata.xml @@ -12,7 +12,7 @@ ln -s $file output_pc/$filename && #end for - pcdl_get_anndata output_pc $focus --custom_data_type "$custom_data_type" --microenv $microenv --graph $graph --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --drop "$drop" --keep "$keep" --scale maxabs --collapse $collapse + pcdl_get_anndata output_pc $focus --custom_data_type $custom_data_type --microenv $microenv --graph $graph --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --drop $drop --keep $keep --scale maxabs --collapse $collapse ]]> @@ -72,7 +72,7 @@ /> @@ -89,8 +89,8 @@ - - + + diff --git a/pcdl/pcdl_get_conc_attribute.xml b/pcdl/pcdl_get_conc_attribute.xml index 0d1f6df..8749d01 100644 --- a/pcdl/pcdl_get_conc_attribute.xml +++ b/pcdl/pcdl_get_conc_attribute.xml @@ -16,7 +16,6 @@ ]]> -
- + diff --git a/pcdl/pcdl_make_graph_gml.xml b/pcdl/pcdl_make_graph_gml.xml index 24b6ee2..82e0958 100644 --- a/pcdl/pcdl_make_graph_gml.xml +++ b/pcdl/pcdl_make_graph_gml.xml @@ -24,10 +24,10 @@ /> - + diff --git a/pcdl/pcdl_make_movie.xml b/pcdl/pcdl_make_movie.xml index dd612d8..d0d3cd8 100644 --- a/pcdl/pcdl_make_movie.xml +++ b/pcdl/pcdl_make_movie.xml @@ -24,10 +24,10 @@ /> - + diff --git a/pcdl/pcdl_plot_contour.xml b/pcdl/pcdl_plot_contour.xml index 1854a78..c440cf1 100644 --- a/pcdl/pcdl_plot_contour.xml +++ b/pcdl/pcdl_plot_contour.xml @@ -92,10 +92,10 @@ /> - + diff --git a/pcdl/pcdl_plot_scatter.xml b/pcdl/pcdl_plot_scatter.xml index ecf0894..a779560 100644 --- a/pcdl/pcdl_plot_scatter.xml +++ b/pcdl/pcdl_plot_scatter.xml @@ -87,7 +87,7 @@ /> @@ -95,7 +95,7 @@ - + @@ -128,10 +128,10 @@ /> - + @@ -144,13 +144,13 @@ - + - + diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml index 0cb97ba..30df67c 100644 --- a/pcdl/pcdl_plot_timeseries.xml +++ b/pcdl/pcdl_plot_timeseries.xml @@ -35,11 +35,11 @@ /> - + @@ -75,10 +75,10 @@ /> - + - + @@ -154,12 +154,12 @@ /> - + - + @@ -194,7 +194,7 @@ - + Date: Sun, 18 May 2025 15:11:36 -0400 Subject: [PATCH 14/41] @ pcdl : in the middle of first version of galaxy wrappers implementation. --- pcdl/pcdl_get_cell_attribute.xml | 2 +- pcdl/pcdl_get_conc_attribute.xml | 2 +- pcdl/pcdl_get_unit_dict.xml | 2 +- pcdl/pcdl_make_cell_vtk.xml | 7 ------- pcdl/pcdl_make_conc_vtk.xml | 7 ------- pcdl/pcdl_make_graph_gml.xml | 7 ------- pcdl/pcdl_plot_contour.xml | 11 ++--------- pcdl/pcdl_plot_scatter.xml | 11 +++-------- pcdl/pcdl_plot_timeseries.xml | 2 +- 9 files changed, 9 insertions(+), 42 deletions(-) diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml index 14643b3..63f95a0 100644 --- a/pcdl/pcdl_get_cell_attribute.xml +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -74,7 +74,7 @@ - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + - - - - - - - - + Date: Tue, 20 May 2025 11:23:42 -0400 Subject: [PATCH 15/41] @ galaxy dev : possible output folder listings. --- pcdl/output_h5ad.txt | 279 +++++++++++++++++++++++++++++++++++++++++++ pcdl/output_raw.txt | 175 +++++++++++++++++++++++++++ pcdl/pyCLI.py | 8 +- 3 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 pcdl/output_h5ad.txt create mode 100644 pcdl/output_raw.txt diff --git a/pcdl/output_h5ad.txt b/pcdl/output_h5ad.txt new file mode 100644 index 0000000..4547eab --- /dev/null +++ b/pcdl/output_h5ad.txt @@ -0,0 +1,279 @@ +cell_rules_parsed.csv +detailed_rules.html +detailed_rules.txt +dictionaries.txt +final_attached_cells_graph.txt +final_cell_neighbor_graph.txt +final_cells.mat +final_microenvironment0.mat +final_spring_attached_cells_graph.txt +final.svg +final.xml +initial_attached_cells_graph.txt +initial_cell_neighbor_graph.txt +initial_cells.mat +initial_mesh0.mat +initial_microenvironment0.mat +initial_spring_attached_cells_graph.txt +initial.svg +initial.xml +legend.svg +output00000000_attached_cells_graph.txt +output00000000_cell_maxabs.h5ad +output00000000_cell_minmax.h5ad +output00000000_cell_neighbor_graph.txt +output00000000_cell_none.h5ad +output00000000_cells.mat +output00000000_cell_std.h5ad +output00000000_microenvironment0.mat +output00000000_spring_attached_cells_graph.txt +output00000000.xml +output00000001_attached_cells_graph.txt +output00000001_cell_maxabs.h5ad +output00000001_cell_minmax.h5ad +output00000001_cell_neighbor_graph.txt +output00000001_cell_none.h5ad +output00000001_cells.mat +output00000001_cell_std.h5ad +output00000001_microenvironment0.mat +output00000001_spring_attached_cells_graph.txt +output00000001.xml +output00000002_attached_cells_graph.txt +output00000002_cell_maxabs.h5ad +output00000002_cell_minmax.h5ad +output00000002_cell_neighbor_graph.txt +output00000002_cell_none.h5ad +output00000002_cells.mat +output00000002_cell_std.h5ad +output00000002_microenvironment0.mat +output00000002_spring_attached_cells_graph.txt +output00000002.xml +output00000003_attached_cells_graph.txt +output00000003_cell_maxabs.h5ad +output00000003_cell_minmax.h5ad +output00000003_cell_neighbor_graph.txt +output00000003_cell_none.h5ad +output00000003_cells.mat +output00000003_cell_std.h5ad +output00000003_microenvironment0.mat +output00000003_spring_attached_cells_graph.txt +output00000003.xml +output00000004_attached_cells_graph.txt +output00000004_cell_maxabs.h5ad +output00000004_cell_minmax.h5ad +output00000004_cell_neighbor_graph.txt +output00000004_cell_none.h5ad +output00000004_cells.mat +output00000004_cell_std.h5ad +output00000004_microenvironment0.mat +output00000004_spring_attached_cells_graph.txt +output00000004.xml +output00000005_attached_cells_graph.txt +output00000005_cell_maxabs.h5ad +output00000005_cell_minmax.h5ad +output00000005_cell_neighbor_graph.txt +output00000005_cell_none.h5ad +output00000005_cells.mat +output00000005_cell_std.h5ad +output00000005_microenvironment0.mat +output00000005_spring_attached_cells_graph.txt +output00000005.xml +output00000006_attached_cells_graph.txt +output00000006_cell_maxabs.h5ad +output00000006_cell_minmax.h5ad +output00000006_cell_neighbor_graph.txt +output00000006_cell_none.h5ad +output00000006_cells.mat +output00000006_cell_std.h5ad +output00000006_microenvironment0.mat +output00000006_spring_attached_cells_graph.txt +output00000006.xml +output00000007_attached_cells_graph.txt +output00000007_cell_maxabs.h5ad +output00000007_cell_minmax.h5ad +output00000007_cell_neighbor_graph.txt +output00000007_cell_none.h5ad +output00000007_cells.mat +output00000007_cell_std.h5ad +output00000007_microenvironment0.mat +output00000007_spring_attached_cells_graph.txt +output00000007.xml +output00000008_attached_cells_graph.txt +output00000008_cell_maxabs.h5ad +output00000008_cell_minmax.h5ad +output00000008_cell_neighbor_graph.txt +output00000008_cell_none.h5ad +output00000008_cells.mat +output00000008_cell_std.h5ad +output00000008_microenvironment0.mat +output00000008_spring_attached_cells_graph.txt +output00000008.xml +output00000009_attached_cells_graph.txt +output00000009_cell_maxabs.h5ad +output00000009_cell_minmax.h5ad +output00000009_cell_neighbor_graph.txt +output00000009_cell_none.h5ad +output00000009_cells.mat +output00000009_cell_std.h5ad +output00000009_microenvironment0.mat +output00000009_spring_attached_cells_graph.txt +output00000009.xml +output00000010_attached_cells_graph.txt +output00000010_cell_maxabs.h5ad +output00000010_cell_minmax.h5ad +output00000010_cell_neighbor_graph.txt +output00000010_cell_none.h5ad +output00000010_cells.mat +output00000010_cell_std.h5ad +output00000010_microenvironment0.mat +output00000010_spring_attached_cells_graph.txt +output00000010.xml +output00000011_attached_cells_graph.txt +output00000011_cell_maxabs.h5ad +output00000011_cell_minmax.h5ad +output00000011_cell_neighbor_graph.txt +output00000011_cell_none.h5ad +output00000011_cells.mat +output00000011_cell_std.h5ad +output00000011_microenvironment0.mat +output00000011_spring_attached_cells_graph.txt +output00000011.xml +output00000012_attached_cells_graph.txt +output00000012_cell_maxabs.h5ad +output00000012_cell_minmax.h5ad +output00000012_cell_neighbor_graph.txt +output00000012_cell_none.h5ad +output00000012_cells.mat +output00000012_cell_std.h5ad +output00000012_microenvironment0.mat +output00000012_spring_attached_cells_graph.txt +output00000012.xml +output00000013_attached_cells_graph.txt +output00000013_cell_maxabs.h5ad +output00000013_cell_minmax.h5ad +output00000013_cell_neighbor_graph.txt +output00000013_cell_none.h5ad +output00000013_cells.mat +output00000013_cell_std.h5ad +output00000013_microenvironment0.mat +output00000013_spring_attached_cells_graph.txt +output00000013.xml +output00000014_attached_cells_graph.txt +output00000014_cell_maxabs.h5ad +output00000014_cell_minmax.h5ad +output00000014_cell_neighbor_graph.txt +output00000014_cell_none.h5ad +output00000014_cells.mat +output00000014_cell_std.h5ad +output00000014_microenvironment0.mat +output00000014_spring_attached_cells_graph.txt +output00000014.xml +output00000015_attached_cells_graph.txt +output00000015_cell_maxabs.h5ad +output00000015_cell_minmax.h5ad +output00000015_cell_neighbor_graph.txt +output00000015_cell_none.h5ad +output00000015_cells.mat +output00000015_cell_std.h5ad +output00000015_microenvironment0.mat +output00000015_spring_attached_cells_graph.txt +output00000015.xml +output00000016_attached_cells_graph.txt +output00000016_cell_maxabs.h5ad +output00000016_cell_minmax.h5ad +output00000016_cell_neighbor_graph.txt +output00000016_cell_none.h5ad +output00000016_cells.mat +output00000016_cell_std.h5ad +output00000016_microenvironment0.mat +output00000016_spring_attached_cells_graph.txt +output00000016.xml +output00000017_attached_cells_graph.txt +output00000017_cell_maxabs.h5ad +output00000017_cell_minmax.h5ad +output00000017_cell_neighbor_graph.txt +output00000017_cell_none.h5ad +output00000017_cells.mat +output00000017_cell_std.h5ad +output00000017_microenvironment0.mat +output00000017_spring_attached_cells_graph.txt +output00000017.xml +output00000018_attached_cells_graph.txt +output00000018_cell_maxabs.h5ad +output00000018_cell_minmax.h5ad +output00000018_cell_neighbor_graph.txt +output00000018_cell_none.h5ad +output00000018_cells.mat +output00000018_cell_std.h5ad +output00000018_microenvironment0.mat +output00000018_spring_attached_cells_graph.txt +output00000018.xml +output00000019_attached_cells_graph.txt +output00000019_cell_maxabs.h5ad +output00000019_cell_minmax.h5ad +output00000019_cell_neighbor_graph.txt +output00000019_cell_none.h5ad +output00000019_cells.mat +output00000019_cell_std.h5ad +output00000019_microenvironment0.mat +output00000019_spring_attached_cells_graph.txt +output00000019.xml +output00000020_attached_cells_graph.txt +output00000020_cell_maxabs.h5ad +output00000020_cell_minmax.h5ad +output00000020_cell_neighbor_graph.txt +output00000020_cell_none.h5ad +output00000020_cells.mat +output00000020_cell_std.h5ad +output00000020_microenvironment0.mat +output00000020_spring_attached_cells_graph.txt +output00000020.xml +output00000021_attached_cells_graph.txt +output00000021_cell_maxabs.h5ad +output00000021_cell_minmax.h5ad +output00000021_cell_neighbor_graph.txt +output00000021_cell_none.h5ad +output00000021_cells.mat +output00000021_cell_std.h5ad +output00000021_microenvironment0.mat +output00000021_spring_attached_cells_graph.txt +output00000021.xml +output00000022_attached_cells_graph.txt +output00000022_cell_maxabs.h5ad +output00000022_cell_minmax.h5ad +output00000022_cell_neighbor_graph.txt +output00000022_cell_none.h5ad +output00000022_cells.mat +output00000022_cell_std.h5ad +output00000022_microenvironment0.mat +output00000022_spring_attached_cells_graph.txt +output00000022.xml +output00000023_attached_cells_graph.txt +output00000023_cell_maxabs.h5ad +output00000023_cell_minmax.h5ad +output00000023_cell_neighbor_graph.txt +output00000023_cell_none.h5ad +output00000023_cells.mat +output00000023_cell_std.h5ad +output00000023_microenvironment0.mat +output00000023_spring_attached_cells_graph.txt +output00000023.xml +output00000024_attached_cells_graph.txt +output00000024_cell_maxabs.h5ad +output00000024_cell_minmax.h5ad +output00000024_cell_neighbor_graph.txt +output00000024_cell_none.h5ad +output00000024_cells.mat +output00000024_cell_std.h5ad +output00000024_microenvironment0.mat +output00000024_spring_attached_cells_graph.txt +output00000024.xml +output_h5ad.txt +PhysiCell_settings.xml +random_seed.txt +rules.html +rules.txt +timeseries_cell_maxabs.h5ad +timeseries_cell_minmax.h5ad +timeseries_cell_none.h5ad +timeseries_cell_std.h5ad diff --git a/pcdl/output_raw.txt b/pcdl/output_raw.txt new file mode 100644 index 0000000..c4ab3ae --- /dev/null +++ b/pcdl/output_raw.txt @@ -0,0 +1,175 @@ +cell_rules_parsed.csv +detailed_rules.html +detailed_rules.txt +dictionaries.txt +final_attached_cells_graph.txt +final_cell_neighbor_graph.txt +final_cells.mat +final_microenvironment0.mat +final_spring_attached_cells_graph.txt +final.svg +final.xml +initial_attached_cells_graph.txt +initial_cell_neighbor_graph.txt +initial_cells.mat +initial_mesh0.mat +initial_microenvironment0.mat +initial_spring_attached_cells_graph.txt +initial.svg +initial.xml +legend.svg +output00000000_attached_cells_graph.txt +output00000000_cell_neighbor_graph.txt +output00000000_cells.mat +output00000000_microenvironment0.mat +output00000000_spring_attached_cells_graph.txt +output00000000.xml +output00000001_attached_cells_graph.txt +output00000001_cell_neighbor_graph.txt +output00000001_cells.mat +output00000001_microenvironment0.mat +output00000001_spring_attached_cells_graph.txt +output00000001.xml +output00000002_attached_cells_graph.txt +output00000002_cell_neighbor_graph.txt +output00000002_cells.mat +output00000002_microenvironment0.mat +output00000002_spring_attached_cells_graph.txt +output00000002.xml +output00000003_attached_cells_graph.txt +output00000003_cell_neighbor_graph.txt +output00000003_cells.mat +output00000003_microenvironment0.mat +output00000003_spring_attached_cells_graph.txt +output00000003.xml +output00000004_attached_cells_graph.txt +output00000004_cell_neighbor_graph.txt +output00000004_cells.mat +output00000004_microenvironment0.mat +output00000004_spring_attached_cells_graph.txt +output00000004.xml +output00000005_attached_cells_graph.txt +output00000005_cell_neighbor_graph.txt +output00000005_cells.mat +output00000005_microenvironment0.mat +output00000005_spring_attached_cells_graph.txt +output00000005.xml +output00000006_attached_cells_graph.txt +output00000006_cell_neighbor_graph.txt +output00000006_cells.mat +output00000006_microenvironment0.mat +output00000006_spring_attached_cells_graph.txt +output00000006.xml +output00000007_attached_cells_graph.txt +output00000007_cell_neighbor_graph.txt +output00000007_cells.mat +output00000007_microenvironment0.mat +output00000007_spring_attached_cells_graph.txt +output00000007.xml +output00000008_attached_cells_graph.txt +output00000008_cell_neighbor_graph.txt +output00000008_cells.mat +output00000008_microenvironment0.mat +output00000008_spring_attached_cells_graph.txt +output00000008.xml +output00000009_attached_cells_graph.txt +output00000009_cell_neighbor_graph.txt +output00000009_cells.mat +output00000009_microenvironment0.mat +output00000009_spring_attached_cells_graph.txt +output00000009.xml +output00000010_attached_cells_graph.txt +output00000010_cell_neighbor_graph.txt +output00000010_cells.mat +output00000010_microenvironment0.mat +output00000010_spring_attached_cells_graph.txt +output00000010.xml +output00000011_attached_cells_graph.txt +output00000011_cell_neighbor_graph.txt +output00000011_cells.mat +output00000011_microenvironment0.mat +output00000011_spring_attached_cells_graph.txt +output00000011.xml +output00000012_attached_cells_graph.txt +output00000012_cell_neighbor_graph.txt +output00000012_cells.mat +output00000012_microenvironment0.mat +output00000012_spring_attached_cells_graph.txt +output00000012.xml +output00000013_attached_cells_graph.txt +output00000013_cell_neighbor_graph.txt +output00000013_cells.mat +output00000013_microenvironment0.mat +output00000013_spring_attached_cells_graph.txt +output00000013.xml +output00000014_attached_cells_graph.txt +output00000014_cell_neighbor_graph.txt +output00000014_cells.mat +output00000014_microenvironment0.mat +output00000014_spring_attached_cells_graph.txt +output00000014.xml +output00000015_attached_cells_graph.txt +output00000015_cell_neighbor_graph.txt +output00000015_cells.mat +output00000015_microenvironment0.mat +output00000015_spring_attached_cells_graph.txt +output00000015.xml +output00000016_attached_cells_graph.txt +output00000016_cell_neighbor_graph.txt +output00000016_cells.mat +output00000016_microenvironment0.mat +output00000016_spring_attached_cells_graph.txt +output00000016.xml +output00000017_attached_cells_graph.txt +output00000017_cell_neighbor_graph.txt +output00000017_cells.mat +output00000017_microenvironment0.mat +output00000017_spring_attached_cells_graph.txt +output00000017.xml +output00000018_attached_cells_graph.txt +output00000018_cell_neighbor_graph.txt +output00000018_cells.mat +output00000018_microenvironment0.mat +output00000018_spring_attached_cells_graph.txt +output00000018.xml +output00000019_attached_cells_graph.txt +output00000019_cell_neighbor_graph.txt +output00000019_cells.mat +output00000019_microenvironment0.mat +output00000019_spring_attached_cells_graph.txt +output00000019.xml +output00000020_attached_cells_graph.txt +output00000020_cell_neighbor_graph.txt +output00000020_cells.mat +output00000020_microenvironment0.mat +output00000020_spring_attached_cells_graph.txt +output00000020.xml +output00000021_attached_cells_graph.txt +output00000021_cell_neighbor_graph.txt +output00000021_cells.mat +output00000021_microenvironment0.mat +output00000021_spring_attached_cells_graph.txt +output00000021.xml +output00000022_attached_cells_graph.txt +output00000022_cell_neighbor_graph.txt +output00000022_cells.mat +output00000022_microenvironment0.mat +output00000022_spring_attached_cells_graph.txt +output00000022.xml +output00000023_attached_cells_graph.txt +output00000023_cell_neighbor_graph.txt +output00000023_cells.mat +output00000023_microenvironment0.mat +output00000023_spring_attached_cells_graph.txt +output00000023.xml +output00000024_attached_cells_graph.txt +output00000024_cell_neighbor_graph.txt +output00000024_cells.mat +output00000024_microenvironment0.mat +output00000024_spring_attached_cells_graph.txt +output00000024.xml +output_raw.txt +PhysiCell_settings.xml +random_seed.txt +rules.html +rules.txt diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index f619535..aa377a0 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -1347,7 +1347,7 @@ def get_anndata(): values = args.values, drop = set(args.drop), keep = set(args.keep), - scale = args.scale, + scale = None if (args.scale.lower() == 'none') else args.scale, ) # going home s_opathfile = s_pathfile.replace('.xml', f'_cell_{args.scale}.h5ad') @@ -1371,16 +1371,16 @@ def get_anndata(): values = args.values, drop = set(args.drop), keep = set(args.keep), - scale = args.scale, # ERROR + scale = None if (args.scale.lower() == 'none') else args.scale, collapse = b_collapse, ) # going home if b_collapse : - s_opathfile = f'{s_path}/timeseries_cell_{args.scale}.h5ad' + s_opathfile = f'{s_path}/timeseries_cell_{args.scale.lower()}.h5ad' ann_mcdsts.write_h5ad(s_opathfile) return s_opathfile else: - ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale))}" for s_xmlfile in mcdsts.get_xmlfile_list()] + ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale.lower()))}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, ann_mcds in enumerate(ann_mcdsts): ann_mcds.write_h5ad(ls_opathfile[i]) return ls_opathfile From fe509e4d248f9188b446f9e725889e3d8bb63d8d Mon Sep 17 00:00:00 2001 From: bue Date: Fri, 23 May 2025 01:47:13 -0400 Subject: [PATCH 16/41] @ pcdl : in the middle of first version of galaxy wrappers implementation ~ fetching the output. --- pcdl/pcdl_get_anndata.xml | 2 +- pcdl/pcdl_get_cell_attribute.xml | 4 +++- pcdl/pcdl_get_cell_attribute_list.xml | 6 ++++++ pcdl/pcdl_get_cell_df.xml | 4 ++-- pcdl/pcdl_get_conc_attribute.xml | 6 ++++-- pcdl/pcdl_get_conc_df.xml | 7 +++++-- pcdl/pcdl_get_substrate_list.xml | 4 ++-- pcdl/pcdl_get_unit_dict.xml | 11 +++++++---- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml index ee934a5..946569d 100644 --- a/pcdl/pcdl_get_anndata.xml +++ b/pcdl/pcdl_get_anndata.xml @@ -90,7 +90,7 @@ - + diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml index 63f95a0..e8770c6 100644 --- a/pcdl/pcdl_get_cell_attribute.xml +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -74,7 +74,9 @@ - + + + + + + + + + - - + + diff --git a/pcdl/pcdl_get_conc_attribute.xml b/pcdl/pcdl_get_conc_attribute.xml index 10ae1f0..52d4318 100644 --- a/pcdl/pcdl_get_conc_attribute.xml +++ b/pcdl/pcdl_get_conc_attribute.xml @@ -4,7 +4,7 @@ - - + + + @@ -54,9 +54,12 @@ - + + + + $substrate_txt diff --git a/pcdl/pcdl_get_unit_dict.xml b/pcdl/pcdl_get_unit_dict.xml index 3cd8452..5a5a37b 100644 --- a/pcdl/pcdl_get_unit_dict.xml +++ b/pcdl/pcdl_get_unit_dict.xml @@ -4,7 +4,7 @@ - - - - + + + + + + Date: Fri, 23 May 2025 11:51:53 -0400 Subject: [PATCH 17/41] @ pcdl : in the middle of first version of galaxy wrappers implementation ~ fetching the output. --- pcdl/pcdl_make_cell_vtk.xml | 4 +++- pcdl/pcdl_make_conc_vtk.xml | 17 ++++++++++++++--- pcdl/pcdl_make_graph_gml.xml | 6 ++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pcdl/pcdl_make_cell_vtk.xml b/pcdl/pcdl_make_cell_vtk.xml index 2d6846b..d3b5901 100644 --- a/pcdl/pcdl_make_cell_vtk.xml +++ b/pcdl/pcdl_make_cell_vtk.xml @@ -59,7 +59,9 @@ - + + + pcdl - + @@ -26,9 +34,12 @@ - + + + + @@ -73,7 +73,9 @@ - + + + Date: Fri, 23 May 2025 12:20:33 -0400 Subject: [PATCH 18/41] @ pcdl : in the middle of first version of galaxy wrappers implementation ~ fetching the output. --- pcdl/pcdl_plot_timeseries.xml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml index 012f90a..b1892a4 100644 --- a/pcdl/pcdl_plot_timeseries.xml +++ b/pcdl/pcdl_plot_timeseries.xml @@ -12,11 +12,10 @@ ln -s $file output_pc/$filename && #end for - pcdl_plot_timeseries output_pc focus_cat focus_num aggregate_num --custom_data_type "$custom_data_type" --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --frame $frame --z_slice "$z_slice" --logy $logy --ylim "$ylim" --secondary_y $secondary_y --subplots $subplots --sharex $sharex --sharey $sharey --linestyle $linestyle --linewidth "$linewidth" --cmap "$cmap" --color "$color" --grid $grid --legend $legend --yunit "$yunit" --title "$title" --figsizepx "$figsizepx" --ext $ext --figbgcolor "$figbgcolor" + pcdl_plot_timeseries output_pc $focus_cat $focus_num $aggregate_num --custom_data_type $custom_data_type --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --frame $frame --z_slice "$z_slice" --logy $logy --ylim "$ylim" --secondary_y $secondary_y --subplots $subplots --sharex $sharex --sharey $sharey --linestyle $linestyle --linewidth "$linewidth" --cmap "$cmap" --color "$color" --grid $grid --legend $legend --yunit "$yunit" --title "$title" --figsizepx $figsizepx --ext $ext --figbgcolor "$figbgcolor" ]]> -
- - + + + Date: Sat, 24 May 2025 00:47:37 -0400 Subject: [PATCH 19/41] @ pcdl : in the middle of first version of galaxy wrappers implementation ~ unittest writing. --- pcdl/pcdl_get_version.xml | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index d4dc244..a0f1177 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -40,8 +40,41 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cdcea2bc3df74124d0daef2271f83bd5d9ac17d8 Mon Sep 17 00:00:00 2001 From: bue Date: Sat, 24 May 2025 17:12:05 -0400 Subject: [PATCH 20/41] @ pcdl : in the middle of first version of galaxy wrappers implementation ~ unittest writing. --- pcdl/pcdl_get_cell_df.xml | 43 ++ pcdl/pcdl_get_version.xml | 20 +- pcdl/test-data | 1 - pcdl/test-data/PhysiCell_settings.xml | 405 ++++++++++++++++++ pcdl/test-data/final.svg | 153 +++++++ pcdl/test-data/final.xml | 168 ++++++++ pcdl/test-data/final_attached_cells_graph.txt | 153 +++++++ pcdl/test-data/final_cell_neighbor_graph.txt | 153 +++++++ pcdl/test-data/final_cells.mat | Bin 0 -> 124873 bytes pcdl/test-data/final_microenvironment0.mat | Bin 0 -> 63935 bytes .../final_spring_attached_cells_graph.txt | 153 +++++++ pcdl/test-data/initial.svg | 81 ++++ pcdl/test-data/initial.xml | 168 ++++++++ .../initial_attached_cells_graph.txt | 128 ++++++ .../test-data/initial_cell_neighbor_graph.txt | 128 ++++++ pcdl/test-data/initial_cells.mat | Bin 0 -> 104473 bytes pcdl/test-data/initial_mesh0.mat | Bin 0 -> 42616 bytes pcdl/test-data/initial_microenvironment0.mat | Bin 0 -> 63935 bytes .../initial_spring_attached_cells_graph.txt | 128 ++++++ pcdl/test-data/legend.svg | 25 ++ pcdl/test-data/output00000000.xml | 168 ++++++++ .../output00000000_attached_cells_graph.txt | 128 ++++++ .../output00000000_cell_neighbor_graph.txt | 128 ++++++ pcdl/test-data/output00000000_cells.mat | Bin 0 -> 104473 bytes .../output00000000_microenvironment0.mat | Bin 0 -> 63935 bytes ...ut00000000_spring_attached_cells_graph.txt | 128 ++++++ pcdl/test-data/output00000001.xml | 168 ++++++++ .../output00000001_attached_cells_graph.txt | 125 ++++++ .../output00000001_cell_neighbor_graph.txt | 125 ++++++ pcdl/test-data/output00000001_cells.mat | Bin 0 -> 102025 bytes .../output00000001_microenvironment0.mat | Bin 0 -> 63935 bytes ...ut00000001_spring_attached_cells_graph.txt | 125 ++++++ 32 files changed, 2993 insertions(+), 9 deletions(-) delete mode 120000 pcdl/test-data create mode 100644 pcdl/test-data/PhysiCell_settings.xml create mode 100644 pcdl/test-data/final.svg create mode 100644 pcdl/test-data/final.xml create mode 100644 pcdl/test-data/final_attached_cells_graph.txt create mode 100644 pcdl/test-data/final_cell_neighbor_graph.txt create mode 100644 pcdl/test-data/final_cells.mat create mode 100644 pcdl/test-data/final_microenvironment0.mat create mode 100644 pcdl/test-data/final_spring_attached_cells_graph.txt create mode 100644 pcdl/test-data/initial.svg create mode 100644 pcdl/test-data/initial.xml create mode 100644 pcdl/test-data/initial_attached_cells_graph.txt create mode 100644 pcdl/test-data/initial_cell_neighbor_graph.txt create mode 100644 pcdl/test-data/initial_cells.mat create mode 100644 pcdl/test-data/initial_mesh0.mat create mode 100644 pcdl/test-data/initial_microenvironment0.mat create mode 100644 pcdl/test-data/initial_spring_attached_cells_graph.txt create mode 100644 pcdl/test-data/legend.svg create mode 100644 pcdl/test-data/output00000000.xml create mode 100644 pcdl/test-data/output00000000_attached_cells_graph.txt create mode 100644 pcdl/test-data/output00000000_cell_neighbor_graph.txt create mode 100644 pcdl/test-data/output00000000_cells.mat create mode 100644 pcdl/test-data/output00000000_microenvironment0.mat create mode 100644 pcdl/test-data/output00000000_spring_attached_cells_graph.txt create mode 100644 pcdl/test-data/output00000001.xml create mode 100644 pcdl/test-data/output00000001_attached_cells_graph.txt create mode 100644 pcdl/test-data/output00000001_cell_neighbor_graph.txt create mode 100644 pcdl/test-data/output00000001_cells.mat create mode 100644 pcdl/test-data/output00000001_microenvironment0.mat create mode 100644 pcdl/test-data/output00000001_spring_attached_cells_graph.txt diff --git a/pcdl/pcdl_get_cell_df.xml b/pcdl/pcdl_get_cell_df.xml index 3516ab8..ab06043 100644 --- a/pcdl/pcdl_get_cell_df.xml +++ b/pcdl/pcdl_get_cell_df.xml @@ -74,11 +74,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @misc{githubphysicelldataloader, diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index a0f1177..c051b34 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -39,7 +39,7 @@ - + @@ -57,12 +57,12 @@ - - - - - - + + + + + + @@ -74,7 +74,11 @@ - + + + + + diff --git a/pcdl/test-data b/pcdl/test-data deleted file mode 120000 index 780a797..0000000 --- a/pcdl/test-data +++ /dev/null @@ -1 +0,0 @@ -output_3d \ No newline at end of file diff --git a/pcdl/test-data/PhysiCell_settings.xml b/pcdl/test-data/PhysiCell_settings.xml new file mode 100644 index 0000000..9bba555 --- /dev/null +++ b/pcdl/test-data/PhysiCell_settings.xml @@ -0,0 +1,405 @@ + + + + -30 + 300 + -20 + 200 + -10 + 100 + 30 + 20 + 10 + false + + + + 1440.0 + min + micron + 0.01 + 0.1 + 6 + + + + 1 + + + + output + + 60 + true + + + 60 + false + + water + YlOrRd + 0 + 1 + + + + false + + + + + false + true + false + 0 + + + + + + 1e3 + 1 + + 1e3 + 0.0 + + + + + + + + + + + + 1e6 + 0.001 + + 1e3 + 1e3 + + 1e3 + 1e3 + 1e3 + 1e3 + 1e3 + 1e3 + + + + true + true + + ./config/initial.mat + + + ./config/dirichlet.mat + + + + + + + + + + 0.003333 + 0.002083 + 0.004167 + 0.016667 + + + + + 5.31667e-05 + + 516 + + + 0.05 + 0 + 1.66667e-02 + 5.83333e-03 + 0 + 2.0 + + + + 0.0 + + 0 + 86400 + + + 1.11667e-2 + 8.33333e-4 + 5.33333e-5 + 2.16667e-3 + 0 + 2.0 + + + + + 2494 + 0.75 + 540 + 0.05 + 0.0045 + 0.0055 + 0 + 0 + 2.0 + + + 0.4 + 10.0 + 1.25 + + 1.0 + 1 + + + 1.8 + 15.12 + + 0.01 + 0.05 + 0.03 + 12 + + + 1 + 1 + .2 + + true + true + + true + oxygen + 1 + + + false + false + + 0.0 + 0.0 + + + + + + + 0 + 0 + 10 + 0.0 + + + 5 + 15 + 0 + 0 + + + + 0.0 + 0.0 + 0.0 + + 0.001 + 0.0 + + + 0.0025 + 0.0 + + 1.0 + 0.1 + + 0.0 + 0.005 + + + + + 0.0 + 0.0 + + + + 0.0 + 0.0 + + + + 1.0 + + + + + + + + + 1388.888889 + + + + + 5.31667e-05 + + 0.001938 + + + 0.05 + 0 + 1.66667e-02 + 5.83333e-03 + 0 + 2.0 + + + + 0.0 + + 9000000000.0 + 1.15741e-05 + + + 1.11667e-02 + 8.33333e-4 + 5.33333e-05 + 2.16667e-4 + 7e-05 + 2.0 + + + + + 2494 + 0.75 + 540 + 0.05 + 0.0045 + 0.0055 + 0.0 + 0.0 + 2 + + + 0.4 + 10.0 + 1.25 + + 1.0 + 1.0 + + + 1.8 + 15.12 + + 0.01 + 0.0 + 0.0 + 12 + + + 2.5 + 1.0 + .5 + + true + true + + true + water + 1 + + + false + false + + 0.0 + 0.0 + + + + + + + 1000 + 50000 + 0 + 0.0 + + + 0 + 1.0 + 10 + 0.0 + + + + 0.0 + 0.0 + 0.0 + + 0.0 + 0.0005 + + + 0 + 0.006 + + 1.0 + 0.1 + + 0.0 + 0.0 + + + + + 0.0 + 0.0 + + + + 0.0 + 0.0 + + + + 0.0 + + + + + + + + + ./config + cells.csv + + + + + + + ./config + cell_rules.csv + + + + + + + 0 + 64 + + diff --git a/pcdl/test-data/final.svg b/pcdl/test-data/final.svg new file mode 100644 index 0000000..bcfdbf3 --- /dev/null +++ b/pcdl/test-data/final.svg @@ -0,0 +1,153 @@ + + + + + + Current time: 1 days, 0 hours, and 0.01 minutes, z = 0.00 μm + + + 153 agents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 μm + + + 0 days, 0 hours, 0 minutes, and 10.5490 seconds + + + + diff --git a/pcdl/test-data/final.xml b/pcdl/test-data/final.xml new file mode 100644 index 0000000..1c9cb2a --- /dev/null +++ b/pcdl/test-data/final.xml @@ -0,0 +1,168 @@ + + + + + PhysiCell + 1.14.1 + http://physicell.org + + + 0000-0002-9925-0151 + Paul + Macklin + macklinp@iu.edu + http://MathCancer.org + Indiana University & PhysiCell Project + Intelligent Systems Engineering + + + + A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 + 10.1371/journal.pcbi.1005991 + https://dx.doi.org/PMC5841829 + 29474446 + PMC5841829 + + + + + 1440.010000 + 10.548248 + 2025-01-05T08:14:42Z + 2025-01-05T08:14:42Z + + + + + -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 + -15 15 45 75 105 135 165 195 225 255 285 + -10 10 30 50 70 90 110 130 150 170 190 + -5 5 15 25 35 45 55 65 75 85 95 + + initial_mesh0.mat + + + + + + + 1000.000000 + 1.000000 + + + + + + 1000000.000000 + 0.001000 + + + + + final_microenvironment0.mat + + + + + + + + + + default + blood_cells + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + final_cells.mat + + + final_cell_neighbor_graph.txt + + + final_attached_cells_graph.txt + + + final_spring_attached_cells_graph.txt + + + + + + diff --git a/pcdl/test-data/final_attached_cells_graph.txt b/pcdl/test-data/final_attached_cells_graph.txt new file mode 100644 index 0000000..daae2c2 --- /dev/null +++ b/pcdl/test-data/final_attached_cells_graph.txt @@ -0,0 +1,153 @@ +210: +1: +221: +225: +211: +206: +108: +230: +164: +152: +213: +137: +143: +185: +159: +229: +242: +17: +102: +161: +217: +115: +135: +160: +239: +244: +106: +181: +120: +112: +188: +116: +125: +158: +136: +186: +139: +190: +124: +226: +132: +238: +142: +228: +148: +153: +145: +123: +48: +154: +131: +51: +215: +127: +216: +144: +130: +57: +214: +121: +180: +61: +163: +227: +64: +65: +138: +67: +134: +69: +70: +71: +72: +73: +114: +75: +76: +241: +78: +79: +150: +81: +82: +83: +84: +151: +119: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +149: +146: +147: +156: +157: +165: +166: +167: +168: +169: +170: +171: +172: +173: +174: +175: +177: +182: +183: +184: +187: +218: +245: +194: +195: +197: +243: +199: +200: +201: +202: +212: +204: +205: +207: +208: +209: +219: +224: +231: +232: +233: +234: +235: +236: +237: +240: +246: +247: +248: +249: +250: +251: +252: \ No newline at end of file diff --git a/pcdl/test-data/final_cell_neighbor_graph.txt b/pcdl/test-data/final_cell_neighbor_graph.txt new file mode 100644 index 0000000..fb79f28 --- /dev/null +++ b/pcdl/test-data/final_cell_neighbor_graph.txt @@ -0,0 +1,153 @@ +210: +1: +221: +225: 195 +211: +206: 158,217 +108: 17 +230: 180,96,188 +164: 48 +152: +213: +137: 218,51 +143: 102 +185: +159: +229: 250,139 +242: 204 +17: 243,108 +102: 143 +161: 214,94 +217: 206 +115: 195,226 +135: +160: 97 +239: 124 +244: 170 +106: +181: 57 +120: 168,240 +112: +188: 230 +116: +125: 158,182 +158: 206,125 +136: 87 +186: 134 +139: 151,229 +190: +124: 239 +226: 115,90 +132: +238: +142: +228: 72 +148: +153: 95 +145: +123: 169 +48: 164 +154: +131: +51: 137,236,247 +215: +127: 207,157 +216: +144: 173 +130: +57: 181,98 +214: 161,94,202,165 +121: 209 +180: 230 +61: +163: +227: 150 +64: 252,194 +65: +138: 204 +67: +134: 186 +69: 209 +70: +71: +72: 228 +73: 183,96 +114: +75: +76: +241: +78: 249,194 +79: +150: 227 +81: 209 +82: +83: +84: 243 +151: 139 +119: +87: 136 +88: +89: +90: 226 +91: 165 +92: +93: +94: 161,235,214 +95: 153 +96: 230,73 +97: 160 +98: 183,57 +149: +146: +147: 170,171 +156: 197 +157: 248,127 +165: 91,214 +166: 251,209 +167: 184 +168: 120 +169: 123 +170: 147,244 +171: 147 +172: +173: 177,144 +174: +175: +177: 173 +182: 125 +183: 98,73 +184: 167 +187: +218: 137 +245: +194: 78,249,64,224 +195: 115,225 +197: 156 +243: 17,84 +199: +200: +201: +202: 214 +212: +204: 138,242 +205: +207: 127 +208: +209: 166,251,81,69,121 +219: +224: 249,194 +231: +232: +233: +234: +235: 94 +236: 51,247 +237: +240: 120 +246: +247: 236,51 +248: 157 +249: 78,194,224 +250: 229 +251: 166,209 +252: 64 \ No newline at end of file diff --git a/pcdl/test-data/final_cells.mat b/pcdl/test-data/final_cells.mat new file mode 100644 index 0000000000000000000000000000000000000000..b22b8de3d0ac9b2f6ceba1ac889a32cd98cbf6a8 GIT binary patch literal 124873 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

T+0UQh7-@5>)0DQhq}iL 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 literal 0 HcmV?d00001 diff --git a/pcdl/test-data/final_microenvironment0.mat b/pcdl/test-data/final_microenvironment0.mat new file mode 100644 index 0000000000000000000000000000000000000000..e20980cb1b6acea567ca58b24188821d2ef617cc GIT binary patch literal 63935 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 + + + + + Current time: 0 days, 0 hours, and 0.00 minutes, z = 0.00 μm + + + 128 agents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 200 μm + + + 0 days, 0 hours, 0 minutes, and 0.0095 seconds + + + + diff --git a/pcdl/test-data/initial.xml b/pcdl/test-data/initial.xml new file mode 100644 index 0000000..54aca77 --- /dev/null +++ b/pcdl/test-data/initial.xml @@ -0,0 +1,168 @@ + + + + + PhysiCell + 1.14.1 + http://physicell.org + + + 0000-0002-9925-0151 + Paul + Macklin + macklinp@iu.edu + http://MathCancer.org + Indiana University & PhysiCell Project + Intelligent Systems Engineering + + + + A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 + 10.1371/journal.pcbi.1005991 + https://dx.doi.org/PMC5841829 + 29474446 + PMC5841829 + + + + + 0.000000 + 0.008479 + 2025-01-05T08:14:32Z + 2025-01-05T08:14:32Z + + + + + -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 + -15 15 45 75 105 135 165 195 225 255 285 + -10 10 30 50 70 90 110 130 150 170 190 + -5 5 15 25 35 45 55 65 75 85 95 + + initial_mesh0.mat + + + + + + + 1000.000000 + 1.000000 + + + + + + 1000000.000000 + 0.001000 + + + + + initial_microenvironment0.mat + + + + + + + + + + default + blood_cells + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + initial_cells.mat + + + initial_cell_neighbor_graph.txt + + + initial_attached_cells_graph.txt + + + initial_spring_attached_cells_graph.txt + + + + + + diff --git a/pcdl/test-data/initial_attached_cells_graph.txt b/pcdl/test-data/initial_attached_cells_graph.txt new file mode 100644 index 0000000..3a92aa5 --- /dev/null +++ b/pcdl/test-data/initial_attached_cells_graph.txt @@ -0,0 +1,128 @@ +0: +1: +2: +3: +4: +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +15: +16: +17: +18: +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: +29: +30: +31: +32: +33: +34: +35: +36: +37: +38: +39: +40: +41: +42: +43: +44: +45: +46: +47: +48: +49: +50: +51: +52: +53: +54: +55: +56: +57: +58: +59: +60: +61: +62: +63: +64: +65: +66: +67: +68: +69: +70: +71: +72: +73: +74: +75: +76: +77: +78: +79: +80: +81: +82: +83: +84: +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +99: +100: +101: +102: +103: +104: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: +117: +118: +119: +120: +121: +122: +123: +124: +125: +126: +127: \ No newline at end of file diff --git a/pcdl/test-data/initial_cell_neighbor_graph.txt b/pcdl/test-data/initial_cell_neighbor_graph.txt new file mode 100644 index 0000000..3a92aa5 --- /dev/null +++ b/pcdl/test-data/initial_cell_neighbor_graph.txt @@ -0,0 +1,128 @@ +0: +1: +2: +3: +4: +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +15: +16: +17: +18: +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: +29: +30: +31: +32: +33: +34: +35: +36: +37: +38: +39: +40: +41: +42: +43: +44: +45: +46: +47: +48: +49: +50: +51: +52: +53: +54: +55: +56: +57: +58: +59: +60: +61: +62: +63: +64: +65: +66: +67: +68: +69: +70: +71: +72: +73: +74: +75: +76: +77: +78: +79: +80: +81: +82: +83: +84: +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +99: +100: +101: +102: +103: +104: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: +117: +118: +119: +120: +121: +122: +123: +124: +125: +126: +127: \ No newline at end of file diff --git a/pcdl/test-data/initial_cells.mat b/pcdl/test-data/initial_cells.mat new file mode 100644 index 0000000000000000000000000000000000000000..bbacc7592ecc0337f92d7002c8af50070e493cd7 GIT binary patch literal 104473 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 ZWgR+f`E_d7B^)0H_m2(0WytrB{U4_Y9j^cY literal 0 HcmV?d00001 diff --git a/pcdl/test-data/initial_mesh0.mat b/pcdl/test-data/initial_mesh0.mat new file mode 100644 index 0000000000000000000000000000000000000000..c50e164262fc47fa86316cb18244e43532803e7b GIT binary patch literal 42616 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 D`}FbyqeseguFyLP}9*adDafhELJ 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 + + + + + + default + + + + + blood_cells + + diff --git a/pcdl/test-data/output00000000.xml b/pcdl/test-data/output00000000.xml new file mode 100644 index 0000000..48a0631 --- /dev/null +++ b/pcdl/test-data/output00000000.xml @@ -0,0 +1,168 @@ + + + + + PhysiCell + 1.14.1 + http://physicell.org + + + 0000-0002-9925-0151 + Paul + Macklin + macklinp@iu.edu + http://MathCancer.org + Indiana University & PhysiCell Project + Intelligent Systems Engineering + + + + A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 + 10.1371/journal.pcbi.1005991 + https://dx.doi.org/PMC5841829 + 29474446 + PMC5841829 + + + + + 0.000000 + 0.000030 + 2025-01-05T08:14:32Z + 2025-01-05T08:14:32Z + + + + + -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 + -15 15 45 75 105 135 165 195 225 255 285 + -10 10 30 50 70 90 110 130 150 170 190 + -5 5 15 25 35 45 55 65 75 85 95 + + initial_mesh0.mat + + + + + + + 1000.000000 + 1.000000 + + + + + + 1000000.000000 + 0.001000 + + + + + output00000000_microenvironment0.mat + + + + + + + + + + default + blood_cells + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + output00000000_cells.mat + + + output00000000_cell_neighbor_graph.txt + + + output00000000_attached_cells_graph.txt + + + output00000000_spring_attached_cells_graph.txt + + + + + + diff --git a/pcdl/test-data/output00000000_attached_cells_graph.txt b/pcdl/test-data/output00000000_attached_cells_graph.txt new file mode 100644 index 0000000..3a92aa5 --- /dev/null +++ b/pcdl/test-data/output00000000_attached_cells_graph.txt @@ -0,0 +1,128 @@ +0: +1: +2: +3: +4: +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +15: +16: +17: +18: +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: +29: +30: +31: +32: +33: +34: +35: +36: +37: +38: +39: +40: +41: +42: +43: +44: +45: +46: +47: +48: +49: +50: +51: +52: +53: +54: +55: +56: +57: +58: +59: +60: +61: +62: +63: +64: +65: +66: +67: +68: +69: +70: +71: +72: +73: +74: +75: +76: +77: +78: +79: +80: +81: +82: +83: +84: +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +99: +100: +101: +102: +103: +104: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: +117: +118: +119: +120: +121: +122: +123: +124: +125: +126: +127: \ No newline at end of file diff --git a/pcdl/test-data/output00000000_cell_neighbor_graph.txt b/pcdl/test-data/output00000000_cell_neighbor_graph.txt new file mode 100644 index 0000000..3a92aa5 --- /dev/null +++ b/pcdl/test-data/output00000000_cell_neighbor_graph.txt @@ -0,0 +1,128 @@ +0: +1: +2: +3: +4: +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +15: +16: +17: +18: +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: +29: +30: +31: +32: +33: +34: +35: +36: +37: +38: +39: +40: +41: +42: +43: +44: +45: +46: +47: +48: +49: +50: +51: +52: +53: +54: +55: +56: +57: +58: +59: +60: +61: +62: +63: +64: +65: +66: +67: +68: +69: +70: +71: +72: +73: +74: +75: +76: +77: +78: +79: +80: +81: +82: +83: +84: +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +99: +100: +101: +102: +103: +104: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: +117: +118: +119: +120: +121: +122: +123: +124: +125: +126: +127: \ No newline at end of file diff --git a/pcdl/test-data/output00000000_cells.mat b/pcdl/test-data/output00000000_cells.mat new file mode 100644 index 0000000000000000000000000000000000000000..bbacc7592ecc0337f92d7002c8af50070e493cd7 GIT binary patch literal 104473 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 ZWgR+f`E_d7B^)0H_m2(0WytrB{U4_Y9j^cY literal 0 HcmV?d00001 diff --git a/pcdl/test-data/output00000000_microenvironment0.mat b/pcdl/test-data/output00000000_microenvironment0.mat new file mode 100644 index 0000000000000000000000000000000000000000..fbf00a2eec2bfdd4e91a5bc2681b58abc9b9ca0e GIT binary patch literal 63935 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 + + + + PhysiCell + 1.14.1 + http://physicell.org + + + 0000-0002-9925-0151 + Paul + Macklin + macklinp@iu.edu + http://MathCancer.org + Indiana University & PhysiCell Project + Intelligent Systems Engineering + + + + A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 + 10.1371/journal.pcbi.1005991 + https://dx.doi.org/PMC5841829 + 29474446 + PMC5841829 + + + + + 60.000000 + 0.510965 + 2025-01-05T08:14:32Z + 2025-01-05T08:14:32Z + + + + + -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 + -15 15 45 75 105 135 165 195 225 255 285 + -10 10 30 50 70 90 110 130 150 170 190 + -5 5 15 25 35 45 55 65 75 85 95 + + initial_mesh0.mat + + + + + + + 1000.000000 + 1.000000 + + + + + + 1000000.000000 + 0.001000 + + + + + output00000001_microenvironment0.mat + + + + + + + + + + default + blood_cells + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + output00000001_cells.mat + + + output00000001_cell_neighbor_graph.txt + + + output00000001_attached_cells_graph.txt + + + output00000001_spring_attached_cells_graph.txt + + + + + + diff --git a/pcdl/test-data/output00000001_attached_cells_graph.txt b/pcdl/test-data/output00000001_attached_cells_graph.txt new file mode 100644 index 0000000..4115d5d --- /dev/null +++ b/pcdl/test-data/output00000001_attached_cells_graph.txt @@ -0,0 +1,125 @@ +0: +1: +2: +3: +4: +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +126: +16: +17: +18: +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: +29: +30: +31: +32: +33: +34: +35: +36: +37: +124: +39: +40: +41: +42: +43: +44: +45: +46: +47: +48: +49: +50: +51: +52: +127: +54: +55: +130: +57: +58: +59: +60: +61: +62: +63: +64: +65: +66: +67: +68: +69: +70: +71: +72: +73: +74: +75: +76: +77: +78: +79: +80: +81: +82: +83: +84: +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: +96: +97: +98: +99: +100: +101: +102: +103: +125: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: +117: +118: +119: +120: +121: +122: +123: +131: \ No newline at end of file diff --git a/pcdl/test-data/output00000001_cell_neighbor_graph.txt b/pcdl/test-data/output00000001_cell_neighbor_graph.txt new file mode 100644 index 0000000..8323137 --- /dev/null +++ b/pcdl/test-data/output00000001_cell_neighbor_graph.txt @@ -0,0 +1,125 @@ +0: +1: 68 +2: +3: +4: 59 +5: +6: +7: 69,58 +8: +9: +10: +11: +12: +13: +14: +126: 22 +16: 109 +17: +18: 98 +19: 76,94 +20: 106 +21: +22: 126 +23: +24: 94 +25: +26: +27: +28: 48 +29: +30: +31: 35 +32: 95 +33: +34: 47 +35: 31 +36: 77 +37: 65,123 +124: +39: 75 +40: 74,86 +41: 87 +42: +43: +44: 84 +45: +46: 54 +47: 34 +48: 28 +49: +50: 60,116 +51: +52: +127: 75 +54: 46 +55: +130: +57: 97 +58: 7 +59: 66,4 +60: 64,50 +61: +62: +63: +64: 60 +65: 37 +66: 59 +67: +68: 1 +69: 7 +70: +71: +72: +73: +74: 40 +75: 127,39 +76: 19,81 +77: 36 +78: +79: 116 +80: 73 +81: 76 +82: +83: +84: 44 +85: +86: 40 +87: 41 +88: +89: +90: +91: +92: +93: +94: 19,24 +95: 32 +96: +97: 57 +98: 18 +99: +100: +101: +102: +103: +125: 121 +105: +106: 20 +107: +108: +109: 16 +110: +111: +112: +113: +114: +115: +116: 50,79 +117: +118: 122 +119: +120: +121: 125 +122: 118 +123: 37 +131: \ No newline at end of file diff --git a/pcdl/test-data/output00000001_cells.mat b/pcdl/test-data/output00000001_cells.mat new file mode 100644 index 0000000000000000000000000000000000000000..0ba0409a92ba857868d5eec5fc6278a7e042220c GIT binary patch literal 102025 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 GV*dx-$8&iA literal 0 HcmV?d00001 diff --git a/pcdl/test-data/output00000001_microenvironment0.mat b/pcdl/test-data/output00000001_microenvironment0.mat new file mode 100644 index 0000000000000000000000000000000000000000..8a76cf8502fda4aa60cf69f241e4ecae1c52af96 GIT binary patch literal 63935 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 F{|Cj~%isV2 literal 0 HcmV?d00001 diff --git a/pcdl/test-data/output00000001_spring_attached_cells_graph.txt b/pcdl/test-data/output00000001_spring_attached_cells_graph.txt new file mode 100644 index 0000000..7277b48 --- /dev/null +++ b/pcdl/test-data/output00000001_spring_attached_cells_graph.txt @@ -0,0 +1,125 @@ +0: +1: +2: +3: +4: 59 +5: +6: +7: +8: +9: +10: +11: +12: +13: +14: +126: +16: +17: +18: 98 +19: +20: +21: +22: +23: +24: +25: +26: +27: +28: 48 +29: +30: +31: 35 +32: 95 +33: +34: +35: 31 +36: 77 +37: 123,65 +124: +39: 75 +40: +41: +42: +43: +44: 84 +45: +46: 54 +47: +48: 28 +49: 117 +50: 116,60 +51: +52: +127: +54: 46 +55: +130: +57: +58: +59: 66,4 +60: 64,50 +61: +62: +63: +64: 60 +65: 37 +66: 59 +67: +68: +69: +70: +71: +72: +73: +74: +75: 39 +76: +77: 36 +78: +79: +80: +81: +82: +83: +84: 44 +85: +86: +87: +88: +89: +90: +91: +92: +93: +94: +95: 32 +96: +97: +98: 18 +99: +100: +101: +102: +103: +125: +105: +106: +107: +108: +109: +110: +111: +112: +113: +114: +115: +116: 50 +117: 49 +118: +119: +120: +121: +122: +123: 37 +131: \ No newline at end of file From a63648a4229ce8be9625489b1eb0898638e823b4 Mon Sep 17 00:00:00 2001 From: bue Date: Sat, 24 May 2025 17:19:15 -0400 Subject: [PATCH 21/41] @ get_anndata : fix scale none bug. --- pcdl/pyCLI.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index f619535..aa377a0 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -1347,7 +1347,7 @@ def get_anndata(): values = args.values, drop = set(args.drop), keep = set(args.keep), - scale = args.scale, + scale = None if (args.scale.lower() == 'none') else args.scale, ) # going home s_opathfile = s_pathfile.replace('.xml', f'_cell_{args.scale}.h5ad') @@ -1371,16 +1371,16 @@ def get_anndata(): values = args.values, drop = set(args.drop), keep = set(args.keep), - scale = args.scale, # ERROR + scale = None if (args.scale.lower() == 'none') else args.scale, collapse = b_collapse, ) # going home if b_collapse : - s_opathfile = f'{s_path}/timeseries_cell_{args.scale}.h5ad' + s_opathfile = f'{s_path}/timeseries_cell_{args.scale.lower()}.h5ad' ann_mcdsts.write_h5ad(s_opathfile) return s_opathfile else: - ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale))}" for s_xmlfile in mcdsts.get_xmlfile_list()] + ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale.lower()))}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, ann_mcds in enumerate(ann_mcdsts): ann_mcds.write_h5ad(ls_opathfile[i]) return ls_opathfile From ef5712c8814f715029ea0a5a1de4dd14926ecc31 Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 27 May 2025 10:09:02 -0400 Subject: [PATCH 22/41] @ pcdl_get_version.xml : planemo linting ok (the missing section structure in the test part was causing the error.) --- pcdl/pcdl_get_version.xml | 72 +++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index c051b34..3face29 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -40,40 +40,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
From e278abdb725aa93564fe9b32ef722d9b95b8d19d Mon Sep 17 00:00:00 2001 From: bue Date: Wed, 28 May 2025 20:58:20 -0400 Subject: [PATCH 23/41] @ pcdl_*.xml : planemo linting and testing ok. --- pcdl/pcdl_get_anndata.xml | 92 +++++++++++++++++ pcdl/pcdl_get_cell_attribute.xml | 46 +++++++++ pcdl/pcdl_get_cell_attribute_list.xml | 52 +++++++++- pcdl/pcdl_get_cell_df.xml | 124 +++++++++++++++------- pcdl/pcdl_get_celltype_list.xml | 48 +++++++++ pcdl/pcdl_get_conc_attribute.xml | 46 +++++++++ pcdl/pcdl_get_conc_df.xml | 91 +++++++++++++++++ pcdl/pcdl_get_substrate_list.xml | 49 +++++++++ pcdl/pcdl_get_unit_dict.xml | 53 +++++++++- pcdl/pcdl_make_cell_vtk.xml | 48 +++++++++ pcdl/pcdl_make_conc_vtk.xml | 46 +++++++++ pcdl/pcdl_make_graph_gml.xml | 142 +++++++++++++++++++++++++- 12 files changed, 790 insertions(+), 47 deletions(-) diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml index 946569d..9917561 100644 --- a/pcdl/pcdl_get_anndata.xml +++ b/pcdl/pcdl_get_anndata.xml @@ -94,6 +94,98 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ - - - - - + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + +
diff --git a/pcdl/pcdl_get_celltype_list.xml b/pcdl/pcdl_get_celltype_list.xml index a6d20a4..331dc45 100644 --- a/pcdl/pcdl_get_celltype_list.xml +++ b/pcdl/pcdl_get_celltype_list.xml @@ -42,6 +42,54 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+
+ + - - + + - - + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
- + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+ Date: Sat, 31 May 2025 14:40:31 -0400 Subject: [PATCH 24/41] @ pcdl : pcdl_plot_timeseries.xml worx ~ inclusive unit test. --- pcdl/pcdl_plot_timeseries.xml | 62 +++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml index b1892a4..8e54e3d 100644 --- a/pcdl/pcdl_plot_timeseries.xml +++ b/pcdl/pcdl_plot_timeseries.xml @@ -11,8 +11,7 @@ #set $filename = re.sub('[^\w\-\.\s]', '_', str($file.element_identifier)) ln -s $file output_pc/$filename && #end for - - pcdl_plot_timeseries output_pc $focus_cat $focus_num $aggregate_num --custom_data_type $custom_data_type --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --frame $frame --z_slice "$z_slice" --logy $logy --ylim "$ylim" --secondary_y $secondary_y --subplots $subplots --sharex $sharex --sharey $sharey --linestyle $linestyle --linewidth "$linewidth" --cmap "$cmap" --color "$color" --grid $grid --legend $legend --yunit "$yunit" --title "$title" --figsizepx $figsizepx --ext $ext --figbgcolor "$figbgcolor" + pcdl_plot_timeseries output_pc "$focus_cat" "$focus_num" "$aggregate_num" --custom_data_type $custom_data_type --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --frame $frame --z_slice "$z_slice" --logy $logy --ylim "$ylim" --secondary_y $secondary_y --subplots $subplots --sharex $sharex --sharey $sharey --linestyle $linestyle --linewidth "$linewidth" --cmap "$cmap" --color "$color" --grid $grid --legend $legend --yunit "$yunit" --title "$title" --figsizepx $figsizepx --ext $ext --figbgcolor "$figbgcolor" ]]> @@ -173,26 +172,75 @@ - + +
- + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + Date: Sun, 1 Jun 2025 00:33:53 -0400 Subject: [PATCH 25/41] @ pcdl : the force awakens. --- pcdl/VERSION.py | 2 +- pcdl/output_h5ad.txt | 279 ------------------------------------- pcdl/output_raw.txt | 175 ----------------------- pcdl/pcdl_plot_scatter.xml | 17 +-- pcdl/pyCLI.py | 16 +++ pcdl/pyMCDS.py | 24 +++- pcdl/pyMCDSts.py | 16 ++- pyproject.toml | 2 +- 8 files changed, 61 insertions(+), 470 deletions(-) delete mode 100644 pcdl/output_h5ad.txt delete mode 100644 pcdl/output_raw.txt diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index fe210a6..830a399 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '3.3.6' +__version__ = '3.3.7' diff --git a/pcdl/output_h5ad.txt b/pcdl/output_h5ad.txt deleted file mode 100644 index 4547eab..0000000 --- a/pcdl/output_h5ad.txt +++ /dev/null @@ -1,279 +0,0 @@ -cell_rules_parsed.csv -detailed_rules.html -detailed_rules.txt -dictionaries.txt -final_attached_cells_graph.txt -final_cell_neighbor_graph.txt -final_cells.mat -final_microenvironment0.mat -final_spring_attached_cells_graph.txt -final.svg -final.xml -initial_attached_cells_graph.txt -initial_cell_neighbor_graph.txt -initial_cells.mat -initial_mesh0.mat -initial_microenvironment0.mat -initial_spring_attached_cells_graph.txt -initial.svg -initial.xml -legend.svg -output00000000_attached_cells_graph.txt -output00000000_cell_maxabs.h5ad -output00000000_cell_minmax.h5ad -output00000000_cell_neighbor_graph.txt -output00000000_cell_none.h5ad -output00000000_cells.mat -output00000000_cell_std.h5ad -output00000000_microenvironment0.mat -output00000000_spring_attached_cells_graph.txt -output00000000.xml -output00000001_attached_cells_graph.txt -output00000001_cell_maxabs.h5ad -output00000001_cell_minmax.h5ad -output00000001_cell_neighbor_graph.txt -output00000001_cell_none.h5ad -output00000001_cells.mat -output00000001_cell_std.h5ad -output00000001_microenvironment0.mat -output00000001_spring_attached_cells_graph.txt -output00000001.xml -output00000002_attached_cells_graph.txt -output00000002_cell_maxabs.h5ad -output00000002_cell_minmax.h5ad -output00000002_cell_neighbor_graph.txt -output00000002_cell_none.h5ad -output00000002_cells.mat -output00000002_cell_std.h5ad -output00000002_microenvironment0.mat -output00000002_spring_attached_cells_graph.txt -output00000002.xml -output00000003_attached_cells_graph.txt -output00000003_cell_maxabs.h5ad -output00000003_cell_minmax.h5ad -output00000003_cell_neighbor_graph.txt -output00000003_cell_none.h5ad -output00000003_cells.mat -output00000003_cell_std.h5ad -output00000003_microenvironment0.mat -output00000003_spring_attached_cells_graph.txt -output00000003.xml -output00000004_attached_cells_graph.txt -output00000004_cell_maxabs.h5ad -output00000004_cell_minmax.h5ad -output00000004_cell_neighbor_graph.txt -output00000004_cell_none.h5ad -output00000004_cells.mat -output00000004_cell_std.h5ad -output00000004_microenvironment0.mat -output00000004_spring_attached_cells_graph.txt -output00000004.xml -output00000005_attached_cells_graph.txt -output00000005_cell_maxabs.h5ad -output00000005_cell_minmax.h5ad -output00000005_cell_neighbor_graph.txt -output00000005_cell_none.h5ad -output00000005_cells.mat -output00000005_cell_std.h5ad -output00000005_microenvironment0.mat -output00000005_spring_attached_cells_graph.txt -output00000005.xml -output00000006_attached_cells_graph.txt -output00000006_cell_maxabs.h5ad -output00000006_cell_minmax.h5ad -output00000006_cell_neighbor_graph.txt -output00000006_cell_none.h5ad -output00000006_cells.mat -output00000006_cell_std.h5ad -output00000006_microenvironment0.mat -output00000006_spring_attached_cells_graph.txt -output00000006.xml -output00000007_attached_cells_graph.txt -output00000007_cell_maxabs.h5ad -output00000007_cell_minmax.h5ad -output00000007_cell_neighbor_graph.txt -output00000007_cell_none.h5ad -output00000007_cells.mat -output00000007_cell_std.h5ad -output00000007_microenvironment0.mat -output00000007_spring_attached_cells_graph.txt -output00000007.xml -output00000008_attached_cells_graph.txt -output00000008_cell_maxabs.h5ad -output00000008_cell_minmax.h5ad -output00000008_cell_neighbor_graph.txt -output00000008_cell_none.h5ad -output00000008_cells.mat -output00000008_cell_std.h5ad -output00000008_microenvironment0.mat -output00000008_spring_attached_cells_graph.txt -output00000008.xml -output00000009_attached_cells_graph.txt -output00000009_cell_maxabs.h5ad -output00000009_cell_minmax.h5ad -output00000009_cell_neighbor_graph.txt -output00000009_cell_none.h5ad -output00000009_cells.mat -output00000009_cell_std.h5ad -output00000009_microenvironment0.mat -output00000009_spring_attached_cells_graph.txt -output00000009.xml -output00000010_attached_cells_graph.txt -output00000010_cell_maxabs.h5ad -output00000010_cell_minmax.h5ad -output00000010_cell_neighbor_graph.txt -output00000010_cell_none.h5ad -output00000010_cells.mat -output00000010_cell_std.h5ad -output00000010_microenvironment0.mat -output00000010_spring_attached_cells_graph.txt -output00000010.xml -output00000011_attached_cells_graph.txt -output00000011_cell_maxabs.h5ad -output00000011_cell_minmax.h5ad -output00000011_cell_neighbor_graph.txt -output00000011_cell_none.h5ad -output00000011_cells.mat -output00000011_cell_std.h5ad -output00000011_microenvironment0.mat -output00000011_spring_attached_cells_graph.txt -output00000011.xml -output00000012_attached_cells_graph.txt -output00000012_cell_maxabs.h5ad -output00000012_cell_minmax.h5ad -output00000012_cell_neighbor_graph.txt -output00000012_cell_none.h5ad -output00000012_cells.mat -output00000012_cell_std.h5ad -output00000012_microenvironment0.mat -output00000012_spring_attached_cells_graph.txt -output00000012.xml -output00000013_attached_cells_graph.txt -output00000013_cell_maxabs.h5ad -output00000013_cell_minmax.h5ad -output00000013_cell_neighbor_graph.txt -output00000013_cell_none.h5ad -output00000013_cells.mat -output00000013_cell_std.h5ad -output00000013_microenvironment0.mat -output00000013_spring_attached_cells_graph.txt -output00000013.xml -output00000014_attached_cells_graph.txt -output00000014_cell_maxabs.h5ad -output00000014_cell_minmax.h5ad -output00000014_cell_neighbor_graph.txt -output00000014_cell_none.h5ad -output00000014_cells.mat -output00000014_cell_std.h5ad -output00000014_microenvironment0.mat -output00000014_spring_attached_cells_graph.txt -output00000014.xml -output00000015_attached_cells_graph.txt -output00000015_cell_maxabs.h5ad -output00000015_cell_minmax.h5ad -output00000015_cell_neighbor_graph.txt -output00000015_cell_none.h5ad -output00000015_cells.mat -output00000015_cell_std.h5ad -output00000015_microenvironment0.mat -output00000015_spring_attached_cells_graph.txt -output00000015.xml -output00000016_attached_cells_graph.txt -output00000016_cell_maxabs.h5ad -output00000016_cell_minmax.h5ad -output00000016_cell_neighbor_graph.txt -output00000016_cell_none.h5ad -output00000016_cells.mat -output00000016_cell_std.h5ad -output00000016_microenvironment0.mat -output00000016_spring_attached_cells_graph.txt -output00000016.xml -output00000017_attached_cells_graph.txt -output00000017_cell_maxabs.h5ad -output00000017_cell_minmax.h5ad -output00000017_cell_neighbor_graph.txt -output00000017_cell_none.h5ad -output00000017_cells.mat -output00000017_cell_std.h5ad -output00000017_microenvironment0.mat -output00000017_spring_attached_cells_graph.txt -output00000017.xml -output00000018_attached_cells_graph.txt -output00000018_cell_maxabs.h5ad -output00000018_cell_minmax.h5ad -output00000018_cell_neighbor_graph.txt -output00000018_cell_none.h5ad -output00000018_cells.mat -output00000018_cell_std.h5ad -output00000018_microenvironment0.mat -output00000018_spring_attached_cells_graph.txt -output00000018.xml -output00000019_attached_cells_graph.txt -output00000019_cell_maxabs.h5ad -output00000019_cell_minmax.h5ad -output00000019_cell_neighbor_graph.txt -output00000019_cell_none.h5ad -output00000019_cells.mat -output00000019_cell_std.h5ad -output00000019_microenvironment0.mat -output00000019_spring_attached_cells_graph.txt -output00000019.xml -output00000020_attached_cells_graph.txt -output00000020_cell_maxabs.h5ad -output00000020_cell_minmax.h5ad -output00000020_cell_neighbor_graph.txt -output00000020_cell_none.h5ad -output00000020_cells.mat -output00000020_cell_std.h5ad -output00000020_microenvironment0.mat -output00000020_spring_attached_cells_graph.txt -output00000020.xml -output00000021_attached_cells_graph.txt -output00000021_cell_maxabs.h5ad -output00000021_cell_minmax.h5ad -output00000021_cell_neighbor_graph.txt -output00000021_cell_none.h5ad -output00000021_cells.mat -output00000021_cell_std.h5ad -output00000021_microenvironment0.mat -output00000021_spring_attached_cells_graph.txt -output00000021.xml -output00000022_attached_cells_graph.txt -output00000022_cell_maxabs.h5ad -output00000022_cell_minmax.h5ad -output00000022_cell_neighbor_graph.txt -output00000022_cell_none.h5ad -output00000022_cells.mat -output00000022_cell_std.h5ad -output00000022_microenvironment0.mat -output00000022_spring_attached_cells_graph.txt -output00000022.xml -output00000023_attached_cells_graph.txt -output00000023_cell_maxabs.h5ad -output00000023_cell_minmax.h5ad -output00000023_cell_neighbor_graph.txt -output00000023_cell_none.h5ad -output00000023_cells.mat -output00000023_cell_std.h5ad -output00000023_microenvironment0.mat -output00000023_spring_attached_cells_graph.txt -output00000023.xml -output00000024_attached_cells_graph.txt -output00000024_cell_maxabs.h5ad -output00000024_cell_minmax.h5ad -output00000024_cell_neighbor_graph.txt -output00000024_cell_none.h5ad -output00000024_cells.mat -output00000024_cell_std.h5ad -output00000024_microenvironment0.mat -output00000024_spring_attached_cells_graph.txt -output00000024.xml -output_h5ad.txt -PhysiCell_settings.xml -random_seed.txt -rules.html -rules.txt -timeseries_cell_maxabs.h5ad -timeseries_cell_minmax.h5ad -timeseries_cell_none.h5ad -timeseries_cell_std.h5ad diff --git a/pcdl/output_raw.txt b/pcdl/output_raw.txt deleted file mode 100644 index c4ab3ae..0000000 --- a/pcdl/output_raw.txt +++ /dev/null @@ -1,175 +0,0 @@ -cell_rules_parsed.csv -detailed_rules.html -detailed_rules.txt -dictionaries.txt -final_attached_cells_graph.txt -final_cell_neighbor_graph.txt -final_cells.mat -final_microenvironment0.mat -final_spring_attached_cells_graph.txt -final.svg -final.xml -initial_attached_cells_graph.txt -initial_cell_neighbor_graph.txt -initial_cells.mat -initial_mesh0.mat -initial_microenvironment0.mat -initial_spring_attached_cells_graph.txt -initial.svg -initial.xml -legend.svg -output00000000_attached_cells_graph.txt -output00000000_cell_neighbor_graph.txt -output00000000_cells.mat -output00000000_microenvironment0.mat -output00000000_spring_attached_cells_graph.txt -output00000000.xml -output00000001_attached_cells_graph.txt -output00000001_cell_neighbor_graph.txt -output00000001_cells.mat -output00000001_microenvironment0.mat -output00000001_spring_attached_cells_graph.txt -output00000001.xml -output00000002_attached_cells_graph.txt -output00000002_cell_neighbor_graph.txt -output00000002_cells.mat -output00000002_microenvironment0.mat -output00000002_spring_attached_cells_graph.txt -output00000002.xml -output00000003_attached_cells_graph.txt -output00000003_cell_neighbor_graph.txt -output00000003_cells.mat -output00000003_microenvironment0.mat -output00000003_spring_attached_cells_graph.txt -output00000003.xml -output00000004_attached_cells_graph.txt -output00000004_cell_neighbor_graph.txt -output00000004_cells.mat -output00000004_microenvironment0.mat -output00000004_spring_attached_cells_graph.txt -output00000004.xml -output00000005_attached_cells_graph.txt -output00000005_cell_neighbor_graph.txt -output00000005_cells.mat -output00000005_microenvironment0.mat -output00000005_spring_attached_cells_graph.txt -output00000005.xml -output00000006_attached_cells_graph.txt -output00000006_cell_neighbor_graph.txt -output00000006_cells.mat -output00000006_microenvironment0.mat -output00000006_spring_attached_cells_graph.txt -output00000006.xml -output00000007_attached_cells_graph.txt -output00000007_cell_neighbor_graph.txt -output00000007_cells.mat -output00000007_microenvironment0.mat -output00000007_spring_attached_cells_graph.txt -output00000007.xml -output00000008_attached_cells_graph.txt -output00000008_cell_neighbor_graph.txt -output00000008_cells.mat -output00000008_microenvironment0.mat -output00000008_spring_attached_cells_graph.txt -output00000008.xml -output00000009_attached_cells_graph.txt -output00000009_cell_neighbor_graph.txt -output00000009_cells.mat -output00000009_microenvironment0.mat -output00000009_spring_attached_cells_graph.txt -output00000009.xml -output00000010_attached_cells_graph.txt -output00000010_cell_neighbor_graph.txt -output00000010_cells.mat -output00000010_microenvironment0.mat -output00000010_spring_attached_cells_graph.txt -output00000010.xml -output00000011_attached_cells_graph.txt -output00000011_cell_neighbor_graph.txt -output00000011_cells.mat -output00000011_microenvironment0.mat -output00000011_spring_attached_cells_graph.txt -output00000011.xml -output00000012_attached_cells_graph.txt -output00000012_cell_neighbor_graph.txt -output00000012_cells.mat -output00000012_microenvironment0.mat -output00000012_spring_attached_cells_graph.txt -output00000012.xml -output00000013_attached_cells_graph.txt -output00000013_cell_neighbor_graph.txt -output00000013_cells.mat -output00000013_microenvironment0.mat -output00000013_spring_attached_cells_graph.txt -output00000013.xml -output00000014_attached_cells_graph.txt -output00000014_cell_neighbor_graph.txt -output00000014_cells.mat -output00000014_microenvironment0.mat -output00000014_spring_attached_cells_graph.txt -output00000014.xml -output00000015_attached_cells_graph.txt -output00000015_cell_neighbor_graph.txt -output00000015_cells.mat -output00000015_microenvironment0.mat -output00000015_spring_attached_cells_graph.txt -output00000015.xml -output00000016_attached_cells_graph.txt -output00000016_cell_neighbor_graph.txt -output00000016_cells.mat -output00000016_microenvironment0.mat -output00000016_spring_attached_cells_graph.txt -output00000016.xml -output00000017_attached_cells_graph.txt -output00000017_cell_neighbor_graph.txt -output00000017_cells.mat -output00000017_microenvironment0.mat -output00000017_spring_attached_cells_graph.txt -output00000017.xml -output00000018_attached_cells_graph.txt -output00000018_cell_neighbor_graph.txt -output00000018_cells.mat -output00000018_microenvironment0.mat -output00000018_spring_attached_cells_graph.txt -output00000018.xml -output00000019_attached_cells_graph.txt -output00000019_cell_neighbor_graph.txt -output00000019_cells.mat -output00000019_microenvironment0.mat -output00000019_spring_attached_cells_graph.txt -output00000019.xml -output00000020_attached_cells_graph.txt -output00000020_cell_neighbor_graph.txt -output00000020_cells.mat -output00000020_microenvironment0.mat -output00000020_spring_attached_cells_graph.txt -output00000020.xml -output00000021_attached_cells_graph.txt -output00000021_cell_neighbor_graph.txt -output00000021_cells.mat -output00000021_microenvironment0.mat -output00000021_spring_attached_cells_graph.txt -output00000021.xml -output00000022_attached_cells_graph.txt -output00000022_cell_neighbor_graph.txt -output00000022_cells.mat -output00000022_microenvironment0.mat -output00000022_spring_attached_cells_graph.txt -output00000022.xml -output00000023_attached_cells_graph.txt -output00000023_cell_neighbor_graph.txt -output00000023_cells.mat -output00000023_microenvironment0.mat -output00000023_spring_attached_cells_graph.txt -output00000023.xml -output00000024_attached_cells_graph.txt -output00000024_cell_neighbor_graph.txt -output00000024_cells.mat -output00000024_microenvironment0.mat -output00000024_spring_attached_cells_graph.txt -output00000024.xml -output_raw.txt -PhysiCell_settings.xml -random_seed.txt -rules.html -rules.txt diff --git a/pcdl/pcdl_plot_scatter.xml b/pcdl/pcdl_plot_scatter.xml index 6badc15..43c0c25 100644 --- a/pcdl/pcdl_plot_scatter.xml +++ b/pcdl/pcdl_plot_scatter.xml @@ -1,6 +1,6 @@ - pcdl + pcdl @@ -12,7 +12,7 @@ ln -s $file output_pc/$filename && #end for - pcdl_plot_scatter output_pc $focus --custom_data_type "$custom_data_type" --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --z_slice $z_slice --z_axis "$z_axis" --alpha $alpha --cmap $"cmap" --title "$title" --grid $grid --legend_loc "$legend_loc" --xlim "$xlim" --ylim "$ylim" --xyequal $xyequal --s $s --figsizepx "$figsizepx" --ext "$ext" --figbgcolor "$figbgcolor" + pcdl_plot_scatter output_pc "$focus" --custom_data_type $custom_data_type --microenv $microenv --physiboss $physiboss --settingxml "$settingxml" --verbose $verbose --z_slice $z_slice --z_axis "$z_axis" --alpha $alpha --cmap $"cmap" --title "$title" --grid $grid --legend_loc "$legend_loc" --xlim "$xlim" --ylim "$ylim" --xyequal $xyequal --s $s --figsizepx "$figsizepx" --jakku true --ext "$ext" --figbgcolor "$figbgcolor" ]]>
@@ -129,26 +129,27 @@ - + - +
- - + + + =0.10.8", "matplotlib", - "numpy>=2.0.0", + "numpy", "pandas>=2.2.2", "requests", "scipy>=1.13.0", From 289ceeaf089fd6e5760da685ecf32d5a51fb0712 Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 1 Jun 2025 16:27:59 -0400 Subject: [PATCH 26/41] @ pcdl : add directory parameter to plot_scatter and plot_contour to make then work on galaxy. --- pcdl/pyCLI.py | 24 ++++++++++++------------ pcdl/pyMCDS.py | 32 ++++++++++++++++---------------- pcdl/pyMCDSts.py | 24 ++++++++++++------------ 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index c63443b..77d3f0a 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -575,11 +575,11 @@ def plot_contour(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) - # plot_scatter jakku + # plot_contour directory parser.add_argument( - '--jakku', - default = 'false', - help = 'if true, then the resulting plots will be moved to a folder named jakku. only set true when this function is run on galaxy. default is false.', + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', ) # plot_contour ext parser.add_argument( @@ -654,7 +654,7 @@ def plot_contour(): xyequal = False if args.xyequal.lower().startswith('f') else True, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], - jakku = True if args.jakku.lower().startswith('t') else False, + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -686,7 +686,7 @@ def plot_contour(): ylim = None if (args.ylim[0].lower() == 'none') else args.ylim, xyequal = False if args.xyequal.lower().startswith('f') else True, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], - jakku = True if args.jakku.lower().startswith('t') else False, + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -1667,11 +1667,11 @@ def plot_scatter(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) - # plot_scatter jakku + # plot_scatter directory parser.add_argument( - '--jakku', - default = 'false', - help = 'if true, then the resulting plots will be moved to a folder named jakku. only set true when this function is run on galaxy. default is false.', + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', ) # plot_scatter ext parser.add_argument( @@ -1744,7 +1744,7 @@ def plot_scatter(): s = args.s, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], - jakku = True if args.jakku.lower().startswith('t') else False, + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -1777,7 +1777,7 @@ def plot_scatter(): xyequal = False if args.xyequal.lower().startswith('f') else True, s = args.s, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], - jakku = True if args.jakku.lower().startswith('t') else False, + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index 1f720d2..2f6a1c3 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -1106,7 +1106,7 @@ def get_conc_df(self, z_slice=None, halt=False, values=1, drop=set(), keep=set() return df_conc - def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, jakku=False, ext=None, figbgcolor=None): + def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): """ input: focus: string @@ -1168,10 +1168,10 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T None tries to take the values from the initial.svg file. fall back setting is [640, 480]. - jakku: boolean; default False - if True, then the resulting plots will be moved - to a folder named jakku. - only set true when this function is run on galaxy. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. ext: string; default is None output image format. possible formats are jpeg, png, and tiff. @@ -1293,10 +1293,10 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T else: # handle output path and filename - if (jakku): - s_path = self.path + '/jakku/' - else: + if (directory is None): s_path = self.path + f'/conc_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' @@ -1806,7 +1806,7 @@ def get_cell_attribute_list(self): return ls_cellattr - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, jakku=False, ext=None, figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): """ input: focus: string; default is 'cell_type' @@ -1873,10 +1873,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. - jakku: boolean; default False - if True, then the resulting plots will be moved - to a folder named jakku. - only set true when this function is run on galaxy. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. ext: string; default is None output image format. possible formats are jpeg, png, and tiff. @@ -2053,10 +2053,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma else: # handle output path and filename - if (jakku): - s_path = self.path + '/jakku/' - else: + if (directory is None): s_path = self.path + f'/cell_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' diff --git a/pcdl/pyMCDSts.py b/pcdl/pyMCDSts.py index 9f624ec..ec7f70a 100644 --- a/pcdl/pyMCDSts.py +++ b/pcdl/pyMCDSts.py @@ -502,7 +502,7 @@ def get_conc_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dlr_variable_range - def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, jakku=False, ext='jpeg', figbgcolor=None): + def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: self: pyMCDSts class instance @@ -556,10 +556,10 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. - jakku: boolean; default False - if True, then the resulting plots will be moved - to a folder named jakku. - only set true when this function is run on galaxy. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. @@ -637,7 +637,7 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma xyequal = xyequal, ax = None, figsizepx = figsizepx, - jakku = jakku, + directory = directory, ext = ext, figbgcolor = figbgcolor, ) @@ -834,7 +834,7 @@ def get_cell_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dl_variable_range - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, jakku=False, ext='jpeg', figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: self: pyMCDSts class instance @@ -898,10 +898,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. - jakku: boolean; default False - if True, then the resulting plots will be moved - to a folder named jakku. - only set true when this function is run on galaxy. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. @@ -948,7 +948,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma s = s, ax = None, figsizepx = figsizepx, - jakku = jakku, + directory = directory, ext = ext, figbgcolor = figbgcolor, ) From 8a109e5d1f9793debc7140097438f99c41207edb Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 1 Jun 2025 16:54:34 -0400 Subject: [PATCH 27/41] @ physicelldataloader : next release v3.3.7. --- pcdl/VERSION.py | 2 +- pcdl/pyCLI.py | 16 ++++++++++++++++ pcdl/pyMCDS.py | 24 ++++++++++++++++++++---- pcdl/pyMCDSts.py | 16 ++++++++++++++-- pyproject.toml | 2 +- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index fe210a6..830a399 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '3.3.6' +__version__ = '3.3.7' diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index aa377a0..77d3f0a 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -575,6 +575,12 @@ def plot_contour(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) + # plot_contour directory + parser.add_argument( + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', + ) # plot_contour ext parser.add_argument( '--ext', @@ -648,6 +654,7 @@ def plot_contour(): xyequal = False if args.xyequal.lower().startswith('f') else True, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -679,6 +686,7 @@ def plot_contour(): ylim = None if (args.ylim[0].lower() == 'none') else args.ylim, xyequal = False if args.xyequal.lower().startswith('f') else True, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -1659,6 +1667,12 @@ def plot_scatter(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) + # plot_scatter directory + parser.add_argument( + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', + ) # plot_scatter ext parser.add_argument( '--ext', @@ -1730,6 +1744,7 @@ def plot_scatter(): s = args.s, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -1762,6 +1777,7 @@ def plot_scatter(): xyequal = False if args.xyequal.lower().startswith('f') else True, s = args.s, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) diff --git a/pcdl/pyMCDS.py b/pcdl/pyMCDS.py index 8a5fa0b..4f14a0c 100644 --- a/pcdl/pyMCDS.py +++ b/pcdl/pyMCDS.py @@ -1106,7 +1106,7 @@ def get_conc_df(self, z_slice=None, halt=False, values=1, drop=set(), keep=set() return df_conc - def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, ext=None, figbgcolor=None): + def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): """ input: focus: string @@ -1168,6 +1168,11 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -1288,7 +1293,10 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T else: # handle output path and filename - s_path = self.path + f'/conc_{focus}_z{round(z_slice,9)}/' + if (directory is None): + s_path = self.path + f'/conc_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' @@ -1798,7 +1806,7 @@ def get_cell_attribute_list(self): return ls_cellattr - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, ext=None, figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): """ input: focus: string; default is 'cell_type' @@ -1865,6 +1873,11 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -2040,7 +2053,10 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma else: # handle output path and filename - s_path = self.path + f'/cell_{focus}_z{round(z_slice,9)}/' + if (directory is None): + s_path = self.path + f'/cell_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' diff --git a/pcdl/pyMCDSts.py b/pcdl/pyMCDSts.py index 7364969..95ab9c8 100644 --- a/pcdl/pyMCDSts.py +++ b/pcdl/pyMCDSts.py @@ -502,7 +502,7 @@ def get_conc_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dlr_variable_range - def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, ext='jpeg', figbgcolor=None): + def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: self: pyMCDSts class instance @@ -556,6 +556,11 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -632,6 +637,7 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma xyequal = xyequal, ax = None, figsizepx = figsizepx, + directory = directory, ext = ext, figbgcolor = figbgcolor, ) @@ -828,7 +834,7 @@ def get_cell_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dl_variable_range - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, ext='jpeg', figbgcolor=None): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: self: pyMCDSts class instance @@ -892,6 +898,11 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -937,6 +948,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma s = s, ax = None, figsizepx = figsizepx, + directory = directory, ext = ext, figbgcolor = figbgcolor, ) diff --git a/pyproject.toml b/pyproject.toml index fc02989..a057e59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ classifiers = [ dependencies = [ "anndata>=0.10.8", "matplotlib", - "numpy>=2.0.0", + "numpy", "pandas>=2.2.2", "requests", "scipy>=1.13.0", From 2bce2b6fa14c404f7a62f0b6808ebd451fc46340 Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 1 Jun 2025 17:00:35 -0400 Subject: [PATCH 28/41] @ pcdl v3 : update man pages. --- man/docstring/mcds.plot_contour.md | 5 + man/docstring/mcds.plot_scatter.md | 16 ++-- man/docstring/mcdsts.plot_contour.md | 5 + man/docstring/mcdsts.plot_scatter.md | 16 ++-- man/docstring/pcdl_get_anndata.md | 83 +++++++++++++++++ man/docstring/pcdl_get_cell_attribute.md | 63 +++++++++++++ man/docstring/pcdl_get_cell_df.md | 49 ++++++++++ man/docstring/pcdl_get_celltype_list.md | 23 +++++ man/docstring/pcdl_get_conc_attribute.md | 39 ++++++++ man/docstring/pcdl_get_conc_df.md | 35 +++++++ man/docstring/pcdl_get_substrate_list.md | 16 ++++ man/docstring/pcdl_get_unit_dict.md | 27 ++++++ man/docstring/pcdl_get_version.md | 16 ++++ man/docstring/pcdl_make_cell_vtk.md | 43 +++++++++ man/docstring/pcdl_make_conc_vtk.md | 18 ++++ man/docstring/pcdl_make_gif.md | 17 ++++ man/docstring/pcdl_make_graph_gml.md | 58 ++++++++++++ man/docstring/pcdl_make_movie.md | 23 +++++ man/docstring/pcdl_plot_contour.md | 67 ++++++++++++++ man/docstring/pcdl_plot_scatter.md | 99 ++++++++++++++++++++ man/docstring/pcdl_plot_timeseries.md | 112 +++++++++++++++++++++++ 21 files changed, 816 insertions(+), 14 deletions(-) diff --git a/man/docstring/mcds.plot_contour.md b/man/docstring/mcds.plot_contour.md index cba83f6..1b94656 100644 --- a/man/docstring/mcds.plot_contour.md +++ b/man/docstring/mcds.plot_contour.md @@ -62,6 +62,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. diff --git a/man/docstring/mcds.plot_scatter.md b/man/docstring/mcds.plot_scatter.md index 4a39f70..1c81c19 100644 --- a/man/docstring/mcds.plot_scatter.md +++ b/man/docstring/mcds.plot_scatter.md @@ -51,13 +51,10 @@ xyequal: boolean; default True to specify equal axis spacing for x and y axis. - s: integer; default is None - scatter plot dot size in pixel. - typographic points are 1/72 inch. - the marker size s is specified in points**2. - plt.rcParams['lines.markersize']**2 is in my case 36. - None tries to take the value from the initial.svg file. - fall back setting is 36. + s: floating point number; default is 1.0 + scatter plot dot size scale factor. + with figsizepx extracted from initial.svg, scale factor 1.0 + should be ok. adjust if necessary. ax: matplotlib axis object; default setting is None the ax object, which will be used as a canvas for plotting. @@ -70,6 +67,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. diff --git a/man/docstring/mcdsts.plot_contour.md b/man/docstring/mcdsts.plot_contour.md index 7317cf6..9078146 100644 --- a/man/docstring/mcdsts.plot_contour.md +++ b/man/docstring/mcdsts.plot_contour.md @@ -54,6 +54,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. diff --git a/man/docstring/mcdsts.plot_scatter.md b/man/docstring/mcdsts.plot_scatter.md index 3c88c66..03f0049 100644 --- a/man/docstring/mcdsts.plot_scatter.md +++ b/man/docstring/mcdsts.plot_scatter.md @@ -52,13 +52,10 @@ xyequal: boolean; default True to specify equal axis spacing for x and y axis. - s: integer; default is None - scatter plot dot size in pixel. - typographic points are 1/72 inch. - the marker size s is specified in points**2. - plt.rcParams['lines.markersize']**2 is in my case 36. - None tries to take the value from the initial.svg file. - fall back setting is 36. + s: floating point number; default is 1.0 + scatter plot dot size scale factor. + with figsizepx extracted from initial.svg, scale factor 1.0 + should be ok. adjust if necessary. figsizepx: list of two integers; default is None size of the figure in pixels, (x, y). @@ -67,6 +64,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. diff --git a/man/docstring/pcdl_get_anndata.md b/man/docstring/pcdl_get_anndata.md index 47d4a37..54e4763 100644 --- a/man/docstring/pcdl_get_anndata.md +++ b/man/docstring/pcdl_get_anndata.md @@ -1,2 +1,85 @@ ``` +usage: pcdl_get_anndata [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--graph GRAPH] + [--physiboss PHYSIBOSS] [--settingxml SETTINGXML] + [-v VERBOSE] [--drop [DROP ...]] [--keep [KEEP ...]] + [--scale SCALE] [--collapse COLLAPSE] + [path] [values] + +function to transform mcds time steps into one or many anndata objects for +downstream analysis. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + values minimal number of values a variable has to have in any + of the mcds time steps to be outputted. variables that + have only 1 state carry no information. None is a + state too. default is 1. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment be extracted and loaded + into the anndata object? setting microenv to False + will use less memory and speed up processing, similar + to the original pyMCDS_cells.py script. default is + True. + --graph GRAPH should neighbor graph, attach graph, and attached + spring graph be extracted and loaded into the anndata + object? default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the anndata object? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --drop [DROP ...] set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. Attention: when the + keep parameter is given, then the drop parameter has + to be an empty string! default is an empty string. + --keep [KEEP ...] set of column labels to be kept in the dataframe. set + values=1 to be sure that all variables are kept. don't + worry: essential columns like ID, coordinates and time + will always be kept. default is an empty string. + --scale SCALE specify how the data should be scaled. possible values + are None, maxabs, minmax, std. None: no scaling. set + scale to None if you would like to have raw data or + entirely scale, transform, and normalize the data + later. maxabs: maximum absolute value distance scaler + will linearly map all values into a [-1, 1] interval. + if the original data has no negative values, the + result will be the same as with the minmax scaler + (except with attributes with only one value). if the + attribute has only zeros, the value will be set to 0. + minmax: minimum maximum distance scaler will map all + values linearly into a [0, 1] interval. if the + attribute has only one value, the value will be set to + 0. std: standard deviation scaler will result in + sigmas. each attribute will be mean centered around 0. + ddof delta degree of freedom is set to 1 because it is + assumed that the values are samples out of the + population and not the entire population. it is + incomprehensible to me that the equivalent sklearn + method has ddof set to 0. if the attribute has only + one value, the value will be set to 0. default is + maxabs + --collapse COLLAPSE should all mcds time steps from the time series be + collapsed into one big anndata h5ad file, or a many + h5ad, one h5ad for each time step?, default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_cell_attribute.md b/man/docstring/pcdl_get_cell_attribute.md index 47d4a37..63628ab 100644 --- a/man/docstring/pcdl_get_cell_attribute.md +++ b/man/docstring/pcdl_get_cell_attribute.md @@ -1,2 +1,65 @@ ``` +usage: pcdl_get_cell_attribute [-h] + [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [--drop [DROP ...]] [--keep [KEEP ...]] + [--allvalues ALLVALUES] + [path] [values] + +function to detect informative variables in a time series. this function +detects even variables which have less than the minimal state count in each +time step, but different values from time step to time step. the output is a +json file with an entry of all non-coordinate column names that at least in +one of the time steps or in between time steps, reach the given minimal value +count. key is the column name, mapped is a list of all values (bool, str, and, +if allvalues is True, int and float) or a list with minimum and maximum values +(int, float). + +positional arguments: + path path to the PhysiCell output directory. default is . . + values minimal number of values a variable has to have in any + of the mcds time steps to be outputted. variables that + have only 1 state carry no information. None is a + state too. default is 1. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --drop [DROP ...] set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. Attention: when the + keep parameter is given, then the drop parameter has + to be an empty string! default is an empty string. + --keep [KEEP ...] set of column labels to be kept in the dataframe. set + values=1 to be sure that all variables are kept. don't + worry: essential columns like ID, coordinates and time + will always be kept. default is an empty string. + --allvalues ALLVALUES + for numeric data, should only the min and max values + or all values be returned? default is false. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_cell_df.md b/man/docstring/pcdl_get_cell_df.md index 47d4a37..f810166 100644 --- a/man/docstring/pcdl_get_cell_df.md +++ b/man/docstring/pcdl_get_cell_df.md @@ -1,2 +1,51 @@ ``` +usage: pcdl_get_cell_df [-h] [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [--drop [DROP ...]] [--keep [KEEP ...]] + [--collapse COLLAPSE] + [path] [values] + +this function extracts dataframes with a cell centric view of the simulation +and saves them as csv files. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + values minimal number of values a variable has to have in any + of the mcds time steps to be outputted. variables that + have only 1 state carry no information. None is a + state too. default is 1. + +options: + -h, --help show this help message and exit + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --drop [DROP ...] set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. Attention: when the + keep parameter is given, then the drop parameter has + to be an empty string! default is an empty string. + --keep [KEEP ...] set of column labels to be kept in the dataframe. set + values=1 to be sure that all variables are kept. don't + worry: essential columns like ID, coordinates and time + will always be kept. default is an empty string. + --collapse COLLAPSE should all mcds time steps from the time series be + collapsed into one big csv, or a many csv, one csv for + each time step?, default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_celltype_list.md b/man/docstring/pcdl_get_celltype_list.md index 47d4a37..683c59b 100644 --- a/man/docstring/pcdl_get_celltype_list.md +++ b/man/docstring/pcdl_get_celltype_list.md @@ -1,2 +1,25 @@ ``` +usage: pcdl_get_celltype_list [-h] [--settingxml SETTINGXML] [-v VERBOSE] + [path] + +this function is returns a list with all celltype labels ordered by celltype +ID. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + +options: + -h, --help show this help message and exit + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to True for more text output, while + processing. default is False. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_conc_attribute.md b/man/docstring/pcdl_get_conc_attribute.md index 47d4a37..686cadc 100644 --- a/man/docstring/pcdl_get_conc_attribute.md +++ b/man/docstring/pcdl_get_conc_attribute.md @@ -1,2 +1,41 @@ ``` +usage: pcdl_get_conc_attribute [-h] [-v VERBOSE] [--drop [DROP ...]] + [--keep [KEEP ...]] [--allvalues ALLVALUES] + [path] [values] + +function to detect informative substrate concentration variables in a time +series. this function detects even variables which have less than the minimal +state count in each time step, but different values from time step to time +step. the output is a json file with an entry of all non-coordinate column +names that, at least in one of the time steps or in between time steps, reach +the given minimal value count. key is the column name, mapped is a list of all +values (bool, str, and, if allvalues is True, int and float) or a list with +minimum and maximum values (int, float). + +positional arguments: + path path to the PhysiCell output directory. default is . . + values minimal number of values a variable has to have in any + of the mcds time steps to be outputted. variables that + have only 1 state carry no information. None is a + state too. default is 1. + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --drop [DROP ...] set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. Attention: when the + keep parameter is given, then the drop parameter has + to be an empty string! default is an empty string. + --keep [KEEP ...] set of column labels to be kept in the dataframe. set + values=1 to be sure that all variables are kept. don't + worry: essential columns like ID, coordinates and time + will always be kept. default is an empty string. + --allvalues ALLVALUES + for numeric data, should only the min and max values + or all values be returned? default is false. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_conc_df.md b/man/docstring/pcdl_get_conc_df.md index 47d4a37..9fd93b4 100644 --- a/man/docstring/pcdl_get_conc_df.md +++ b/man/docstring/pcdl_get_conc_df.md @@ -1,2 +1,37 @@ ``` +usage: pcdl_get_conc_df [-h] [-v VERBOSE] [--drop [DROP ...]] + [--keep [KEEP ...]] [--collapse COLLAPSE] + [path] [values] + +this function extracts dataframes with concentration values for all chemical +species in all voxels and saves them as csv files. additionally, this +dataframe lists voxel and mesh center coordinates. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + values minimal number of values a variable has to have in any + of the mcds time steps to be outputted. variables that + have only 1 state carry no information. None is a + state too. default is 1. + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --drop [DROP ...] set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. Attention: when the + keep parameter is given, then the drop parameter has + to be an empty string! default is an empty string. + --keep [KEEP ...] set of column labels to be kept in the dataframe. set + values=1 to be sure that all variables are kept. don't + worry: essential columns like ID, coordinates and time + will always be kept. default is an empty string. + --collapse COLLAPSE should all mcds time steps from the time series be + collapsed into one big csv, or a many csv, one for + each time step? default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_substrate_list.md b/man/docstring/pcdl_get_substrate_list.md index 47d4a37..55cd668 100644 --- a/man/docstring/pcdl_get_substrate_list.md +++ b/man/docstring/pcdl_get_substrate_list.md @@ -1,2 +1,18 @@ ``` +usage: pcdl_get_substrate_list [-h] [-v VERBOSE] [path] + +this function is returns all chemical species names, modeled in the +microenvironment, ordered by subsrate ID. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to True for more text output, while + processing. default is False. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_unit_dict.md b/man/docstring/pcdl_get_unit_dict.md index 47d4a37..3f52826 100644 --- a/man/docstring/pcdl_get_unit_dict.md +++ b/man/docstring/pcdl_get_unit_dict.md @@ -1,2 +1,29 @@ ``` +usage: pcdl_get_unit_dict [-h] [--microenv MICROENV] [--settingxml SETTINGXML] + [-v VERBOSE] + [path] + +function returns a csv that lists all tracked variables from metadata, cell, +and microenvironment and maps them to their unit. + +positional arguments: + path path to the PhysiCell output directory. default is . . + +options: + -h, --help show this help message and exit + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_get_version.md b/man/docstring/pcdl_get_version.md index 47d4a37..af37f6d 100644 --- a/man/docstring/pcdl_get_version.md +++ b/man/docstring/pcdl_get_version.md @@ -1,2 +1,18 @@ ``` +usage: pcdl_get_version [-h] [-v VERBOSE] [path] + +this function is extracting PhysiCell and MultiCellDS version from the dataset +and the installed pcdl module version. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to True for more text output, while + processing. default is False. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_cell_vtk.md b/man/docstring/pcdl_make_cell_vtk.md index 47d4a37..a067d57 100644 --- a/man/docstring/pcdl_make_cell_vtk.md +++ b/man/docstring/pcdl_make_cell_vtk.md @@ -1,2 +1,45 @@ ``` +usage: pcdl_make_cell_vtk [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [path] [attribute ...] + +function that generates 3D glyph vtk file for cells. cells can have specified +attributes like cell_type, pressure, dead, etc. you can post-process this file +in other software like paraview (https://www.paraview.org/). + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + attribute listing of mcds.get_cell_df dataframe column names, + used for cell attributes. default is a single term: + cell_type. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_conc_vtk.md b/man/docstring/pcdl_make_conc_vtk.md index 47d4a37..3c80aaa 100644 --- a/man/docstring/pcdl_make_conc_vtk.md +++ b/man/docstring/pcdl_make_conc_vtk.md @@ -1,2 +1,20 @@ ``` +usage: pcdl_make_conc_vtk [-h] [-v VERBOSE] [path] + +function generates rectilinear grid vtk files, one per mcds time step, +contains distribution of substrates over microenvironment. you can post- +process this files in other software like paraview +(https://www.paraview.org/). + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_gif.md b/man/docstring/pcdl_make_gif.md index 47d4a37..85615cf 100644 --- a/man/docstring/pcdl_make_gif.md +++ b/man/docstring/pcdl_make_gif.md @@ -1,2 +1,19 @@ ``` +usage: pcdl_make_gif [-h] [path] [interface] + +this function generates a gif movie from all image files found in the path +directory in the specified interface file format. + +positional arguments: + path relative or absolute path to where the images are from which the + gif will be generated. default is . . + interface specify the image format from which the gif will be generated. + these images have to exist under the given path. they can be + generated with the plot_scatter or plot_contour function. + default is jpeg. + +options: + -h, --help show this help message and exit + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_graph_gml.md b/man/docstring/pcdl_make_graph_gml.md index 47d4a37..20dfbd0 100644 --- a/man/docstring/pcdl_make_graph_gml.md +++ b/man/docstring/pcdl_make_graph_gml.md @@ -1,2 +1,60 @@ ``` +usage: pcdl_make_graph_gml [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [--edge_attribute EDGE_ATTRIBUTE] + [--node_attribute [NODE_ATTRIBUTE ...]] + [path] [graph_type] + +function to generate graph files in the gml graph modelling language standard +format. gml was the outcome of an initiative that started at the international +symposium on graph drawing 1995 in Passau and ended at Graph Drawing 1996 in +Berkeley. the networkx python library (https://networkx.org/) and igraph C and +python libraries (https://igraph.org/) for graph analysis are gml compatible +and can as such read and write this file format. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + graph_type to specify which physicell output data should be + processed. attached: processes + mcds.get_attached_graph_dict dictionary. neighbor: + processes mcds.get_neighbor_graph_dict dictionary + spring: processes mcds.get_spring_graph_dict + dictionary. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --edge_attribute EDGE_ATTRIBUTE + specifies if the spatial Euclidean distance is used + for edge attribute, to generate a weighted graph. + default is True. + --node_attribute [NODE_ATTRIBUTE ...] + listing of mcds.get_cell_df dataframe columns, used + for node attributes. default is and empty list. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_make_movie.md b/man/docstring/pcdl_make_movie.md index 47d4a37..1dbf260 100644 --- a/man/docstring/pcdl_make_movie.md +++ b/man/docstring/pcdl_make_movie.md @@ -1,2 +1,25 @@ ``` +usage: pcdl_make_movie [-h] [--framerate FRAMERATE] [path] [interface] + +this function generates an mp4 movie file from all image files found in the +path directory in the specified interface file format. + +positional arguments: + path relative or absolute path to where the images are from + which the mp4 movie will be generated. default is . . + interface specify the image format from which the mp4 movie will + be generated. these images have to exist under the + given path. they can be generated with the + plot_scatter or plot_contour function. default is + jpeg. + +options: + -h, --help show this help message and exit + --framerate FRAMERATE + specifies how many images per second will be used. + humans are capable of processing 12 images per second + and seeing them individually. higher rates are seen as + motion. default is 12. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_plot_contour.md b/man/docstring/pcdl_plot_contour.md index 47d4a37..b5cb596 100644 --- a/man/docstring/pcdl_plot_contour.md +++ b/man/docstring/pcdl_plot_contour.md @@ -1,2 +1,69 @@ ``` +usage: pcdl_plot_contour [-h] [-v VERBOSE] [--z_slice Z_SLICE] + [--extrema EXTREMA [EXTREMA ...]] [--alpha ALPHA] + [--fill FILL] [--cmap CMAP] [--title TITLE] + [--grid GRID] [--xlim XLIM [XLIM ...]] + [--ylim YLIM [YLIM ...]] [--xyequal XYEQUAL] + [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] + [--directory DIRECTORY] [--ext EXT] + [--figbgcolor FIGBGCOLOR] + [path] [focus] + +function generates matplotlib contour (or contourf) plots, inclusive color +bar, for the substrate specified, under the returned path. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + focus column name within conc dataframe. + +options: + -h, --help show this help message and exit + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D + mesh. if z_slice position numeric but not an exact + mesh center coordinate, then z_slice will be adjusted + to the nearest mesh center value, the smaller one, if + the coordinate lies on a saddle point. default is 0.0. + --extrema EXTREMA [EXTREMA ...] + listing of two floats. None takes min and max from + data. default is None. + --alpha ALPHA alpha channel transparency value between 1 (not + transparent at all) and 0 (totally transparent). + default is 1.0. + --fill FILL True generates a matplotlib contourf plot. False + generates a matplotlib contour plot. default is True. + --cmap CMAP matplotlib colormap string from https://matplotlib.org + /stable/tutorials/colors/colormaps.html . default is + viridis. + --title TITLE title prefix. default is an empty string. + --grid GRID plot axis grid lines. default is True. + --xlim XLIM [XLIM ...] + two floats. x axis min and max value. None takes min + and max from mesh x axis range. default is None. + --ylim YLIM [YLIM ...] + two floats. y axis min and max value. None takes min + and max from mesh y axis range. default is None. + --xyequal XYEQUAL to specify equal axis spacing for x and y axis. + default is true. + --figsizepx FIGSIZEPX [FIGSIZEPX ...] + size of the figure in pixels (integer), x y. the given + x and y will be rounded to the nearest even number, to + be able to generate movies from the images. None tries + to take the values from the initial.svg file. fall + back setting is 640 480. default is None. + --directory DIRECTORY + if none, a meaningful output directory name will be + generated, based on focus and z_slice parameters, else + the resulting plots will be moved to the explicit name + directory. + --ext EXT output image format. possible formats are jpeg, png, + and tiff. default is jpeg. + --figbgcolor FIGBGCOLOR + figure background color. None is transparent (png) or + white (jpeg, tiff). default is None. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_plot_scatter.md b/man/docstring/pcdl_plot_scatter.md index 47d4a37..11b3f12 100644 --- a/man/docstring/pcdl_plot_scatter.md +++ b/man/docstring/pcdl_plot_scatter.md @@ -1,2 +1,101 @@ ``` +usage: pcdl_plot_scatter [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [--z_slice Z_SLICE] [--z_axis Z_AXIS [Z_AXIS ...]] + [--alpha ALPHA] [--cmap CMAP] [--title TITLE] + [--grid GRID] [--legend_loc LEGEND_LOC] + [--xlim XLIM [XLIM ...]] [--ylim YLIM [YLIM ...]] + [--xyequal XYEQUAL] [--s S] + [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] + [--directory DIRECTORY] [--ext EXT] + [--figbgcolor FIGBGCOLOR] + [path] [focus] + +function generates pandas scatter plots, under the returned path. + +positional arguments: + path path to the PhysiCell output directory or a + outputnnnnnnnn.xml file. default is . . + focus column name within conc dataframe. default is + cell_type. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D + mesh. if z_slice position numeric but not an exact + mesh center coordinate, then z_slice will be adjusted + to the nearest mesh center value, the smaller one, if + the coordinate lies on a saddle point. default is 0.0. + --z_axis Z_AXIS [Z_AXIS ...] + for a categorical focus: list of labels; for a numeric + focus: list of two floats; None, depending on the + focus column variable dtype, extracts labels or min + and max values from data. default is None + --alpha ALPHA alpha channel transparency value between 1 (not + transparent at all) and 0 (totally transparent). + default is 1.0. + --cmap CMAP matplotlib colormap string from https://matplotlib.org + /stable/tutorials/colors/colormaps.html . default is + viridis. + --title TITLE title prefix. default is an empty string. + --grid GRID plot axis grid lines. default is True. + --legend_loc LEGEND_LOC + the location of the categorical legend, if applicable. + possible strings are: best, 'upper right', 'upper + center', 'upper left', 'center left', 'lower left', + 'lower center', 'lower right', 'center right', center. + default is 'lower left' + --xlim XLIM [XLIM ...] + two floats. x axis min and max value. None takes min + and max from mesh x axis range. default is None. + --ylim YLIM [YLIM ...] + two floats. y axis min and max value. None takes min + and max from mesh y axis range. default is None. + --xyequal XYEQUAL to specify equal axis spacing for x and y axis. + default is True. + --s S scatter plot dot size scale factor. with figsizepx + extracted from initial.svg, scale factor 1.0 should be + ok. adjust if necessary. default 1.0. + --figsizepx FIGSIZEPX [FIGSIZEPX ...] + size of the figure in pixels (integer), x y. the given + x and y will be rounded to the nearest even number, to + be able to generate movies from the images. None tries + to take the values from the initial.svg file. fall + back setting is 640 480. default is None. + --directory DIRECTORY + if none, a meaningful output directory name will be + generated, based on focus and z_slice parameters, else + the resulting plots will be moved to the explicit name + directory. + --ext EXT output image format. possible formats are jpeg, png, + and tiff. default is jpeg. + --figbgcolor FIGBGCOLOR + figure background color. None is transparent (png) or + white (jpeg, tiff). default is None. + +homepage: https://github.com/elmbeech/physicelldataloader ``` diff --git a/man/docstring/pcdl_plot_timeseries.md b/man/docstring/pcdl_plot_timeseries.md index 47d4a37..c529514 100644 --- a/man/docstring/pcdl_plot_timeseries.md +++ b/man/docstring/pcdl_plot_timeseries.md @@ -1,2 +1,114 @@ ``` +usage: pcdl_plot_timeseries [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] + [--microenv MICROENV] [--physiboss PHYSIBOSS] + [--settingxml SETTINGXML] [-v VERBOSE] + [--frame FRAME] [--z_slice Z_SLICE] [--logy LOGY] + [--ylim YLIM [YLIM ...]] + [--secondary_y SECONDARY_Y [SECONDARY_Y ...]] + [--subplots SUBPLOTS] [--sharex SHAREX] + [--sharey SHAREY] [--linestyle LINESTYLE] + [--linewidth LINEWIDTH] [--cmap CMAP] + [--color COLOR [COLOR ...]] [--grid GRID] + [--legend LEGEND] [--yunit YUNIT] [--title TITLE] + [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] + [--ext EXT] [--figbgcolor FIGBGCOLOR] + [path] [focus_cat] [focus_num] [aggregate_num] + +this function to generate a timeseries plot and either returns a matplotlib +figure or an image file (jpeg, png, tiff). + +positional arguments: + path path to the PhysiCell output directory. default is . . + focus_cat categorical or boolean data column within dataframe + specified under frame. default is None, which is + total, which is all agents or voxels, no categories. + default is None. + focus_num numerical data column within dataframe specified under + frame. default is None, which is count, agent or voxel + count. default is None. + aggregate_num aggregation function {max, mean, median, min, std, + var} for focus_num data. default is mean. + +options: + -h, --help show this help message and exit + --custom_data_type [CUSTOM_DATA_TYPE ...] + parameter to specify custom_data variable types other + than float (namely: int, bool, str) like this + var:dtype myint:int mybool:bool mystr:str . downstream + float and int will be handled as numeric, bool as + Boolean, and str as categorical data. default is an + empty string. + --microenv MICROENV should the microenvironment data be loaded? setting + microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py + script. default is True. + --physiboss PHYSIBOSS + if found, should physiboss state data be extracted and + loaded into the df_cell dataframe? default is True. + --settingxml SETTINGXML + the settings.xml that is loaded, from which the cell + type ID label mapping, is extracted, if this + information is not found in the output xml file. set + to None or False if the xml file is missing! default + is PhysiCell_settings.xml. + -v VERBOSE, --verbose VERBOSE + setting verbose to False for less text output, while + processing. default is True. + --frame FRAME to specifies the data dataframe. cell: dataframe will + be retrieved through the mcds.get_cell_df function. + conc: dataframe will be retrieved through the + mcds.get_conc_df function. default is cell. + --z_slice Z_SLICE z-axis position to slice a 2D xy-plain out of the 3D + mesh. if z_slice position numeric but not an exact + mesh center coordinate, then z_slice will be adjusted + to the nearest mesh center value, the smaller one, if + the coordinate lies on a saddle point. if set to None, + the whole domain is taken. default is None. + --logy LOGY if True, then y axis is natural log scaled. default is + False. + --ylim YLIM [YLIM ...] + two floats. y axis min and max value. default is None, + which automatically detects min and max value. default + is None. + --secondary_y SECONDARY_Y [SECONDARY_Y ...] + whether to plot on the secondary y-axis. if a listing + of string, which columns to plot on the secondary + y-axis. default is False. + --subplots SUBPLOTS whether to split the plot into subplots, one per + column. default is False. + --sharex SHAREX in case subplots is True, share x-axis by setting some + x-axis labels to invisible. default is False. + --sharey SHAREY in case subplots is True, share y-axis range and + possibly setting some y-axis labels to invisible. + default is False. + --linestyle LINESTYLE + matplotlib line style {-, --, .-, :} string. default + is - . + --linewidth LINEWIDTH + line width in points, integer. default is None. + --cmap CMAP matplotlib colormap string from https://matplotlib.org + /stable/tutorials/colors/colormaps.html . default is + None. + --color COLOR [COLOR ...] + listing of color strings referred to by name, RGB or + RGBA code. default is None. + --grid GRID plot axis grid lines. default is True. + --legend LEGEND if True or reverse, place legend on axis subplots. + default is True. + --yunit YUNIT string to specify y-axis unit. None will not print a + unit on the y-axis. default is None. + --title TITLE title to use for the plot. None will print no title. + default is None. + --figsizepx FIGSIZEPX [FIGSIZEPX ...] + size of the figure in pixels (integer), x y. the given + x and y will be rounded to the nearest even number, to + be able to generate movies from the images. default is + 640 480. + --ext EXT output image format. possible formats are jpeg, png, + and tiff. default is jpeg. + --figbgcolor FIGBGCOLOR + figure background color. None is transparent (png) or + white (jpeg, tiff). default is None. + +homepage: https://github.com/elmbeech/physicelldataloader ``` From d176b4906836766395fe4828ddb17cb4e9f02100 Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 1 Jun 2025 22:49:28 -0400 Subject: [PATCH 29/41] @ pcdlv3 : galaxy implementation being continued. --- pcdl/pcdl_get_anndata.xml | 2 +- pcdl/pcdl_get_cell_attribute.xml | 2 +- pcdl/pcdl_get_cell_attribute_list.xml | 2 +- pcdl/pcdl_get_cell_df.xml | 2 +- pcdl/pcdl_get_celltype_list.xml | 2 +- pcdl/pcdl_get_conc_attribute.xml | 2 +- pcdl/pcdl_get_conc_df.xml | 2 +- pcdl/pcdl_get_substrate_list.xml | 2 +- pcdl/pcdl_get_unit_dict.xml | 2 +- pcdl/pcdl_get_version.xml | 2 +- pcdl/pcdl_make_cell_vtk.xml | 2 +- pcdl/pcdl_make_conc_vtk.xml | 2 +- pcdl/pcdl_make_gif.xml | 39 +++++++++++++++++----- pcdl/pcdl_make_graph_gml.xml | 2 +- pcdl/pcdl_make_movie.xml | 37 ++++++++++++++++---- pcdl/pcdl_plot_contour.xml | 9 ++--- pcdl/pcdl_plot_scatter.xml | 4 +-- pcdl/pcdl_plot_timeseries.xml | 2 +- pcdl/test-data/output00000000_oxygen.jpeg | Bin 0 -> 12046 bytes pcdl/test-data/output00000001_oxygen.jpeg | Bin 0 -> 13395 bytes 20 files changed, 82 insertions(+), 35 deletions(-) create mode 100644 pcdl/test-data/output00000000_oxygen.jpeg create mode 100644 pcdl/test-data/output00000001_oxygen.jpeg diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml index 9917561..bc7a88b 100644 --- a/pcdl/pcdl_get_anndata.xml +++ b/pcdl/pcdl_get_anndata.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml index 88992ae..fb99a5d 100644 --- a/pcdl/pcdl_get_cell_attribute.xml +++ b/pcdl/pcdl_get_cell_attribute.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_cell_attribute_list.xml b/pcdl/pcdl_get_cell_attribute_list.xml index ca25cc8..c1a626c 100644 --- a/pcdl/pcdl_get_cell_attribute_list.xml +++ b/pcdl/pcdl_get_cell_attribute_list.xml @@ -1,7 +1,7 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_cell_df.xml b/pcdl/pcdl_get_cell_df.xml index 1b5f472..caea90b 100644 --- a/pcdl/pcdl_get_cell_df.xml +++ b/pcdl/pcdl_get_cell_df.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_celltype_list.xml b/pcdl/pcdl_get_celltype_list.xml index 331dc45..9bbeca5 100644 --- a/pcdl/pcdl_get_celltype_list.xml +++ b/pcdl/pcdl_get_celltype_list.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_conc_attribute.xml b/pcdl/pcdl_get_conc_attribute.xml index a5221fe..40e31ea 100644 --- a/pcdl/pcdl_get_conc_attribute.xml +++ b/pcdl/pcdl_get_conc_attribute.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_conc_df.xml b/pcdl/pcdl_get_conc_df.xml index d0080b6..def0823 100644 --- a/pcdl/pcdl_get_conc_df.xml +++ b/pcdl/pcdl_get_conc_df.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_substrate_list.xml b/pcdl/pcdl_get_substrate_list.xml index 0040b17..2637722 100644 --- a/pcdl/pcdl_get_substrate_list.xml +++ b/pcdl/pcdl_get_substrate_list.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_unit_dict.xml b/pcdl/pcdl_get_unit_dict.xml index a5b819c..64ec5da 100644 --- a/pcdl/pcdl_get_unit_dict.xml +++ b/pcdl/pcdl_get_unit_dict.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml index 3face29..30c9ba8 100644 --- a/pcdl/pcdl_get_version.xml +++ b/pcdl/pcdl_get_version.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_make_cell_vtk.xml b/pcdl/pcdl_make_cell_vtk.xml index 694cdbb..95aa2f9 100644 --- a/pcdl/pcdl_make_cell_vtk.xml +++ b/pcdl/pcdl_make_cell_vtk.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_make_conc_vtk.xml b/pcdl/pcdl_make_conc_vtk.xml index cff4be9..2ced8c0 100644 --- a/pcdl/pcdl_make_conc_vtk.xml +++ b/pcdl/pcdl_make_conc_vtk.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/pcdl_make_gif.xml b/pcdl/pcdl_make_gif.xml index aa9142c..d88e8fd 100644 --- a/pcdl/pcdl_make_gif.xml +++ b/pcdl/pcdl_make_gif.xml @@ -1,18 +1,18 @@ - pcdl + pcdl @@ -25,19 +25,42 @@ - + +
- + + + + + +
+ + + + + + +
+
+ +
+ + + +
+
+ - pcdl + pcdl diff --git a/pcdl/pcdl_make_movie.xml b/pcdl/pcdl_make_movie.xml index d0d3cd8..234344c 100644 --- a/pcdl/pcdl_make_movie.xml +++ b/pcdl/pcdl_make_movie.xml @@ -1,18 +1,18 @@ - pcdl + pcdl @@ -27,9 +27,11 @@ type="select" display="radio" help="specify the image format from which the mp4 movie will be generated. these images can be generated with the pcdl_plot_scatter or pcdl_plot_contour function. default is jpeg." > - + +
@@ -43,9 +45,30 @@ - + + + + + +
+ + + + + + +
+
+ +
+ + + +
+
+ - pcdl + pcdl @@ -12,7 +12,7 @@ ln -s $file output_pc/$filename && #end for - pcdl_plot_contour output_pc $focus --verbose $verbose --z_slice $z_slice --extrema $extrema --alpha $alpha --fill $fill --cmap $cmap --title "$title" --grid $grid --xlim $xlim --ylim $ylim --xyequal $xyequal --figsizepx $figsizepx --ext $ext --figbgcolor $figbgcolor + pcdl_plot_contour output_pc $focus --verbose $verbose --z_slice $z_slice --extrema $extrema --alpha $alpha --fill $fill --cmap $cmap --title "$title" --grid $grid --xlim $xlim --ylim $ylim --xyequal $xyequal --figsizepx $figsizepx --directory jakku --ext $ext --figbgcolor $figbgcolor ]]> @@ -108,11 +108,12 @@ - - + + + @@ -145,7 +145,7 @@ - + diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml index 8e54e3d..dd0668e 100644 --- a/pcdl/pcdl_plot_timeseries.xml +++ b/pcdl/pcdl_plot_timeseries.xml @@ -1,6 +1,6 @@ - pcdl + pcdl diff --git a/pcdl/test-data/output00000000_oxygen.jpeg b/pcdl/test-data/output00000000_oxygen.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..194c7c6cee50018a69a3b1409df636de495f0207 GIT binary patch literal 12046 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 literal 0 HcmV?d00001 diff --git a/pcdl/test-data/output00000001_oxygen.jpeg b/pcdl/test-data/output00000001_oxygen.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cc4cdd63ea819943b315b3d86e9ba804e6ec03dc GIT binary patch literal 13395 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 literal 0 HcmV?d00001 From cfbb020b5fe7d8a599199a2dfcf41c8545d1a189 Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 3 Jun 2025 00:33:41 -0400 Subject: [PATCH 30/41] @ pcdl : plot_scatter and plot_contour galaxy wrapper work. --- pcdl/pcdl_plot_contour.xml | 59 ++++++++++++++++++++++++++++++++++---- pcdl/pcdl_plot_scatter.xml | 50 +++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/pcdl/pcdl_plot_contour.xml b/pcdl/pcdl_plot_contour.xml index e9ea02f..0d535e4 100644 --- a/pcdl/pcdl_plot_contour.xml +++ b/pcdl/pcdl_plot_contour.xml @@ -12,7 +12,7 @@ ln -s $file output_pc/$filename && #end for - pcdl_plot_contour output_pc $focus --verbose $verbose --z_slice $z_slice --extrema $extrema --alpha $alpha --fill $fill --cmap $cmap --title "$title" --grid $grid --xlim $xlim --ylim $ylim --xyequal $xyequal --figsizepx $figsizepx --directory jakku --ext $ext --figbgcolor $figbgcolor + pcdl_plot_contour output_pc $focus --verbose $verbose --z_slice $z_slice --extrema "$extrema" --alpha $alpha --fill $fill --cmap "$cmap" --title "$title" --grid $grid --xlim "$xlim" --ylim "$ylim" --xyequal $xyequal --figsizepx "$figsizepx" --directory "jakku" --ext $ext --figbgcolor "$figbgcolor" ]]> @@ -93,16 +93,18 @@ - + +
@@ -113,6 +115,53 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
@@ -132,8 +132,10 @@ help="output image format. possible formats are jpeg, png, and tiff. default is jpeg." > + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
Date: Sat, 14 Jun 2025 14:24:33 -0400 Subject: [PATCH 31/41] @ pcdl : galactic empire realted code moved to https://github.com/elmbeech/tools-iuc/tree/main/tools/pcdl. --- pcdl/galaxy.sh | 239 ----------------------- pcdl/pcdl_get_anndata.xml | 206 -------------------- pcdl/pcdl_get_cell_attribute.xml | 145 -------------- pcdl/pcdl_get_cell_attribute_list.xml | 120 ------------ pcdl/pcdl_get_cell_df.xml | 186 ------------------ pcdl/pcdl_get_celltype_list.xml | 110 ----------- pcdl/pcdl_get_conc_attribute.xml | 126 ------------- pcdl/pcdl_get_conc_df.xml | 171 ----------------- pcdl/pcdl_get_substrate_list.xml | 106 ----------- pcdl/pcdl_get_unit_dict.xml | 115 ------------ pcdl/pcdl_get_version.xml | 106 ----------- pcdl/pcdl_make_cell_vtk.xml | 132 ------------- pcdl/pcdl_make_conc_vtk.xml | 106 ----------- pcdl/pcdl_make_gif.xml | 81 -------- pcdl/pcdl_make_graph_gml.xml | 237 ----------------------- pcdl/pcdl_make_movie.xml | 89 --------- pcdl/pcdl_plot_contour.xml | 183 ------------------ pcdl/pcdl_plot_scatter.xml | 218 --------------------- pcdl/pcdl_plot_timeseries.xml | 261 -------------------------- 19 files changed, 2937 deletions(-) delete mode 100755 pcdl/galaxy.sh delete mode 100644 pcdl/pcdl_get_anndata.xml delete mode 100644 pcdl/pcdl_get_cell_attribute.xml delete mode 100644 pcdl/pcdl_get_cell_attribute_list.xml delete mode 100644 pcdl/pcdl_get_cell_df.xml delete mode 100644 pcdl/pcdl_get_celltype_list.xml delete mode 100644 pcdl/pcdl_get_conc_attribute.xml delete mode 100644 pcdl/pcdl_get_conc_df.xml delete mode 100644 pcdl/pcdl_get_substrate_list.xml delete mode 100644 pcdl/pcdl_get_unit_dict.xml delete mode 100644 pcdl/pcdl_get_version.xml delete mode 100644 pcdl/pcdl_make_cell_vtk.xml delete mode 100644 pcdl/pcdl_make_conc_vtk.xml delete mode 100644 pcdl/pcdl_make_gif.xml delete mode 100644 pcdl/pcdl_make_graph_gml.xml delete mode 100644 pcdl/pcdl_make_movie.xml delete mode 100644 pcdl/pcdl_plot_contour.xml delete mode 100644 pcdl/pcdl_plot_scatter.xml delete mode 100644 pcdl/pcdl_plot_timeseries.xml diff --git a/pcdl/galaxy.sh b/pcdl/galaxy.sh deleted file mode 100755 index 679a6d0..0000000 --- a/pcdl/galaxy.sh +++ /dev/null @@ -1,239 +0,0 @@ -## pcdl_get_anndata -#planemo tool_init --force \ -#--id pcdl_get_anndata \ -#--name pcdl_get_anndata \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_anndata output_3d 1 --custom_data_type --microenv true --graph true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --scale maxabs --collapse true' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_cell_maxabs.h5ad \ -#--test_case \ -#--help_from_command 'pcdl_get_anndata --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_cell_attribute -#planemo tool_init --force \ -#--id pcdl_get_cell_attribute \ -#--name pcdl_get_cell_attribute \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_cell_attribute output_3d 1 --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --allvalues false' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_cell_attribute_minmax.json \ -#--test_case \ -#--help_from_command 'pcdl_get_cell_attribute --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_cell_attribute_list -#planemo tool_init --force \ -#--id pcdl_get_cell_attribute_list \ -#--name pcdl_get_cell_attribute_list \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_cell_attribute_list output_3d --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false 2> attribute.txt' \ -#--example_input output_3d/ \ -#--example_output version.txt \ -#--test_case \ -#--help_from_command 'pcdl_get_cell_attribute_list --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_cell_df -#planemo tool_init --force \ -#--id pcdl_get_cell_df \ -#--name pcdl_get_cell_df \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_cell_df output_3d 1 --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --drop --keep --collapse true' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_cell.csv \ -#--test_case \ -#--help_from_command 'pcdl_get_cell_df --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_celltype_list -#planemo tool_init --force \ -#--id pcdl_get_celltype_list \ -#--name pcdl_get_celltype_list \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_celltype_list output_3d --settingxml PhysiCell_settings.xml --verbose false 2> celltype.txt' \ -#--example_input output_3d/ \ -#--example_output celltype.txt \ -#--test_case \ -#--help_from_command 'pcdl_get_celltype_list --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_conc_attribute -#planemo tool_init --force \ -#--id pcdl_get_conc_attribute \ -#--name pcdl_get_conc_attribute \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_conc_attribute output_3d 1 --verbose false --drop --keep --allvalues false' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_conc_attribute_minmax.json \ -#--test_case \ -#--help_from_command 'pcdl_get_conc_attribute --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_conc_df -#planemo tool_init --force \ -#--id pcdl_get_conc_df \ -#--name pcdl_get_conc_df \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_conc_df output_3d 1 --verbose false --drop --keep --collapse true' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_conc.csv \ -#--test_case \ -#--help_from_command 'pcdl_get_conc_df --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_substrate_list -#planemo tool_init --force \ -#--id pcdl_get_substrate_list \ -#--name pcdl_get_substrate_list \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_substrate_list output_3d --verbose false 2> substrate.txt' \ -#--example_input output_3d/ \ -#--example_output substrate.txt \ -#--test_case \ -#--help_from_command 'pcdl_get_substrate_list --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_get_unit_dict -#planemo tool_init --force \ -#--id pcdl_get_unit_dict \ -#--name pcdl_get_unit_dict \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_unit_dict output_3d --microenv true --settingxml PhysiCell_settings.xml --verbose false' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_unit.csv \ -#--test_case \ -#--help_from_command 'pcdl_get_unit_dict --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -# pcdl_get_version -#planemo tool_init --force \ -#--id pcdl_get_version \ -#--name pcdl_get_version \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_get_version output_3d --verbose false 2> version.txt' \ -#--example_input output_3d/ \ -#--example_output version.txt \ -#--test_case \ -#--help_from_command 'pcdl_get_version --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_make_cell_vtk -#rm output_3d/output00000000_cell.vtp -#planemo tool_init --force \ -#--id pcdl_make_cell_vtk \ -#--name pcdl_make_cell_vtk \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_make_cell_vtk output_3d/output00000000.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false' \ -#--example_input output_3d/output00000000.xml \ -#--example_output output_3d/output00000000_cell.vtp \ -#--test_case \ -#--help_from_command 'pcdl_make_cell_vtk --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_make_conc_vtk -#rm output_3d/output00000000_conc.vtr -#planemo tool_init --force \ -#--id pcdl_make_conc_vtk \ -#--name pcdl_make_conc_vtk \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_make_conc_vtk output_3d/output00000000.xml --verbose false' \ -#--example_input output_3d/output00000000.xml \ -#--example_output output_3d/output00000000_conc.vtr \ -#--test_case \ -#--help_from_command 'pcdl_make_conc_vtk --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_make_graph_gml -#rm output_3d/output00000012_neighbor.gml -#planemo tool_init --force \ -#--id pcdl_make_graph_gml \ -#--name pcdl_make_graph_gml \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_make_graph_gml output_3d/output00000012.xml neighbor --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --edge_attribute true --node_attribute' \ -#--example_input output_3d/output00000012.xml \ -#--example_output output_3d/output00000012_neighbor.gml \ -#--test_case \ -#--help_from_command 'pcdl_make_graph_gml --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_plot_contour -#rm output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg -#planemo tool_init --force \ -#--id pcdl_plot_contour \ -#--name pcdl_plot_contour \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_plot_contour output_3d/output00000012.xml oxygen --verbose false --z_slice 0.0 --extrema none --alpha 1.0 --fill true --cmap viridis --title "" --grid true --xlim none --ylim none --xyequal true --figsizepx none --ext jpeg --figbgcolor none' \ -#--example_input output_3d/output00000012.xml \ -#--example_output output_3d/conc_oxygen_z-5.0/output00000012_oxygen.jpeg \ -#--test_case \ -#--help_from_command 'pcdl_plot_contour --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_plot_scatter -#rm output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg -#planemo tool_init --force \ -#--id pcdl_plot_scatter \ -#--name pcdl_plot_scatter \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_plot_scatter output_3d/output00000012.xml cell_type --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --z_slice 0.0 --z_axis none --alpha 1.0 --cmap viridis --title "" --grid true --legend_loc "lower left" --xlim none --ylim none --xyequal true --s 1.0 --figsizepx none --ext jpeg --figbgcolor none' \ -#--example_input output_3d/output00000012.xml \ -#--example_output output_3d/cell_cell_type_z-5.0/output00000012_cell_type.jpeg \ -#--test_case \ -#--help_from_command 'pcdl_plot_scatter --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_plot_timeseries -#planemo tool_init --force \ -#--id pcdl_plot_timeseries \ -#--name pcdl_plot_timeseries \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_plot_timeseries output_3d none none mean --custom_data_type --microenv true --physiboss true --settingxml PhysiCell_settings.xml --verbose false --frame cell --z_slice 0.0 --logy false --ylim none --secondary_y false --subplots false --sharex false --sharey false --linestyle - --linewidth none --cmap none --color none --grid true --legend true --yunit none --title none --figsizepx 640 480 --ext jpeg --figbgcolor none' \ -#--example_input output_3d/ \ -#--example_output output_3d/timeseries_cell_total_count.jpeg \ -#--test_case \ -#--help_from_command 'pcdl_plot_timeseries --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_make_gif -#planemo tool_init --force \ -#--id pcdl_make_gif \ -#--name pcdl_make_gif \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_make_gif output_3d/cell_cell_type_z-5.0 jpeg' \ -#--example_input output_3d/cell_cell_type_z-5.0 \ -#--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg.gif \ -#--test_case \ -#--help_from_command 'pcdl_make_gif --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - -## pcdl_make_movie -#planemo tool_init --force \ -#--id pcdl_make_movie \ -#--name pcdl_make_movie \ -#--version 3.0.0 \ -#--requirement pcdl@3.3.6 \ -#--example_command 'pcdl_make_movie output_3d/cell_cell_type_z-5.0 jpeg' \ -#--example_input output_3d/cell_cell_type_z-5.0 \ -#--example_output output_3d/cell_cell_type_z-5.0/cell_cell_type_z-5.0_jpeg12.mp4 \ -#--test_case \ -#--help_from_command 'pcdl_make_movie --help' \ -#--cite_url https://github.com/elmbeech/physicelldataloader - diff --git a/pcdl/pcdl_get_anndata.xml b/pcdl/pcdl_get_anndata.xml deleted file mode 100644 index bc7a88b..0000000 --- a/pcdl/pcdl_get_anndata.xml +++ /dev/null @@ -1,206 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - - - - - - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_cell_attribute.xml b/pcdl/pcdl_get_cell_attribute.xml deleted file mode 100644 index fb99a5d..0000000 --- a/pcdl/pcdl_get_cell_attribute.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_cell_attribute_list.xml b/pcdl/pcdl_get_cell_attribute_list.xml deleted file mode 100644 index c1a626c..0000000 --- a/pcdl/pcdl_get_cell_attribute_list.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - pcdl - - - - $cell_attribute_txt - ]]> - - -
- -
- -
- - - - -
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - -
-
- - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_cell_df.xml b/pcdl/pcdl_get_cell_df.xml deleted file mode 100644 index caea90b..0000000 --- a/pcdl/pcdl_get_cell_df.xml +++ /dev/null @@ -1,186 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_celltype_list.xml b/pcdl/pcdl_get_celltype_list.xml deleted file mode 100644 index 9bbeca5..0000000 --- a/pcdl/pcdl_get_celltype_list.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - pcdl - - - - $celltype_txt - ]]> - - -
- -
- -
- - -
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_conc_attribute.xml b/pcdl/pcdl_get_conc_attribute.xml deleted file mode 100644 index 40e31ea..0000000 --- a/pcdl/pcdl_get_conc_attribute.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_conc_df.xml b/pcdl/pcdl_get_conc_df.xml deleted file mode 100644 index def0823..0000000 --- a/pcdl/pcdl_get_conc_df.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_substrate_list.xml b/pcdl/pcdl_get_substrate_list.xml deleted file mode 100644 index 2637722..0000000 --- a/pcdl/pcdl_get_substrate_list.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - pcdl - - - - $substrate_txt - ]]> - - -
- -
- -
- -
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - -
-
- - - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_unit_dict.xml b/pcdl/pcdl_get_unit_dict.xml deleted file mode 100644 index 64ec5da..0000000 --- a/pcdl/pcdl_get_unit_dict.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - pcdl - - - - - - -
- -
- -
- - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_get_version.xml b/pcdl/pcdl_get_version.xml deleted file mode 100644 index 30c9ba8..0000000 --- a/pcdl/pcdl_get_version.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - pcdl - - - - $version_txt - ]]> - - -
- -
- -
- -
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_make_cell_vtk.xml b/pcdl/pcdl_make_cell_vtk.xml deleted file mode 100644 index 95aa2f9..0000000 --- a/pcdl/pcdl_make_cell_vtk.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
-
- - - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_make_conc_vtk.xml b/pcdl/pcdl_make_conc_vtk.xml deleted file mode 100644 index 2ced8c0..0000000 --- a/pcdl/pcdl_make_conc_vtk.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - pcdl - - - - - - -
- -
- -
- -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_make_gif.xml b/pcdl/pcdl_make_gif.xml deleted file mode 100644 index d88e8fd..0000000 --- a/pcdl/pcdl_make_gif.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - pcdl - - - - - - -
- - - - - -
-
- - - - - - - - - -
- - - - - - -
-
- -
- - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_make_graph_gml.xml b/pcdl/pcdl_make_graph_gml.xml deleted file mode 100644 index 263be16..0000000 --- a/pcdl/pcdl_make_graph_gml.xml +++ /dev/null @@ -1,237 +0,0 @@ - - - pcdl - - - - - - -
- - - - - - -
- -
- - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
-
- - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_make_movie.xml b/pcdl/pcdl_make_movie.xml deleted file mode 100644 index 234344c..0000000 --- a/pcdl/pcdl_make_movie.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - pcdl - - - - - - -
- - - - - -
- -
- -
-
- - - - - - - - - -
- - - - - - -
-
- -
- - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_plot_contour.xml b/pcdl/pcdl_plot_contour.xml deleted file mode 100644 index 0d535e4..0000000 --- a/pcdl/pcdl_plot_contour.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - - - - - - - - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_plot_scatter.xml b/pcdl/pcdl_plot_scatter.xml deleted file mode 100644 index 8a7b4d0..0000000 --- a/pcdl/pcdl_plot_scatter.xml +++ /dev/null @@ -1,218 +0,0 @@ - - - pcdl - - - - - - -
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - -
-
- - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
diff --git a/pcdl/pcdl_plot_timeseries.xml b/pcdl/pcdl_plot_timeseries.xml deleted file mode 100644 index dd0668e..0000000 --- a/pcdl/pcdl_plot_timeseries.xml +++ /dev/null @@ -1,261 +0,0 @@ - - - pcdl - - - - - - -
- - - - - - - - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - -
-
- - - - - - - @misc{githubphysicelldataloader, - author = {Bucher, Elmar}, - year = {2025}, - title = {physicelldataloader}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/elmbeech/physicelldataloader}, - } - -
From 393507a6a7d6eb5845eae6e250cd272a5c288521 Mon Sep 17 00:00:00 2001 From: bue Date: Sat, 14 Jun 2025 14:35:46 -0400 Subject: [PATCH 32/41] @ pcdl : galactic empire realted code moved to https://github.com/elmbeech/tools-iuc/tree/main/tools/pcdl. --- pcdl/test-data/PhysiCell_settings.xml | 405 ------------------ pcdl/test-data/final.svg | 153 ------- pcdl/test-data/final.xml | 168 -------- pcdl/test-data/final_attached_cells_graph.txt | 153 ------- pcdl/test-data/final_cell_neighbor_graph.txt | 153 ------- pcdl/test-data/final_cells.mat | Bin 124873 -> 0 bytes pcdl/test-data/final_microenvironment0.mat | Bin 63935 -> 0 bytes .../final_spring_attached_cells_graph.txt | 153 ------- pcdl/test-data/initial.svg | 81 ---- pcdl/test-data/initial.xml | 168 -------- .../initial_attached_cells_graph.txt | 128 ------ .../test-data/initial_cell_neighbor_graph.txt | 128 ------ pcdl/test-data/initial_cells.mat | Bin 104473 -> 0 bytes pcdl/test-data/initial_mesh0.mat | Bin 42616 -> 0 bytes pcdl/test-data/initial_microenvironment0.mat | Bin 63935 -> 0 bytes .../initial_spring_attached_cells_graph.txt | 128 ------ pcdl/test-data/legend.svg | 25 -- pcdl/test-data/output00000000.xml | 168 -------- .../output00000000_attached_cells_graph.txt | 128 ------ .../output00000000_cell_neighbor_graph.txt | 128 ------ pcdl/test-data/output00000000_cells.mat | Bin 104473 -> 0 bytes .../output00000000_microenvironment0.mat | Bin 63935 -> 0 bytes pcdl/test-data/output00000000_oxygen.jpeg | Bin 12046 -> 0 bytes ...ut00000000_spring_attached_cells_graph.txt | 128 ------ pcdl/test-data/output00000001.xml | 168 -------- .../output00000001_attached_cells_graph.txt | 125 ------ .../output00000001_cell_neighbor_graph.txt | 125 ------ pcdl/test-data/output00000001_cells.mat | Bin 102025 -> 0 bytes .../output00000001_microenvironment0.mat | Bin 63935 -> 0 bytes pcdl/test-data/output00000001_oxygen.jpeg | Bin 13395 -> 0 bytes ...ut00000001_spring_attached_cells_graph.txt | 125 ------ 31 files changed, 2938 deletions(-) delete mode 100644 pcdl/test-data/PhysiCell_settings.xml delete mode 100644 pcdl/test-data/final.svg delete mode 100644 pcdl/test-data/final.xml delete mode 100644 pcdl/test-data/final_attached_cells_graph.txt delete mode 100644 pcdl/test-data/final_cell_neighbor_graph.txt delete mode 100644 pcdl/test-data/final_cells.mat delete mode 100644 pcdl/test-data/final_microenvironment0.mat delete mode 100644 pcdl/test-data/final_spring_attached_cells_graph.txt delete mode 100644 pcdl/test-data/initial.svg delete mode 100644 pcdl/test-data/initial.xml delete mode 100644 pcdl/test-data/initial_attached_cells_graph.txt delete mode 100644 pcdl/test-data/initial_cell_neighbor_graph.txt delete mode 100644 pcdl/test-data/initial_cells.mat delete mode 100644 pcdl/test-data/initial_mesh0.mat delete mode 100644 pcdl/test-data/initial_microenvironment0.mat delete mode 100644 pcdl/test-data/initial_spring_attached_cells_graph.txt delete mode 100644 pcdl/test-data/legend.svg delete mode 100644 pcdl/test-data/output00000000.xml delete mode 100644 pcdl/test-data/output00000000_attached_cells_graph.txt delete mode 100644 pcdl/test-data/output00000000_cell_neighbor_graph.txt delete mode 100644 pcdl/test-data/output00000000_cells.mat delete mode 100644 pcdl/test-data/output00000000_microenvironment0.mat delete mode 100644 pcdl/test-data/output00000000_oxygen.jpeg delete mode 100644 pcdl/test-data/output00000000_spring_attached_cells_graph.txt delete mode 100644 pcdl/test-data/output00000001.xml delete mode 100644 pcdl/test-data/output00000001_attached_cells_graph.txt delete mode 100644 pcdl/test-data/output00000001_cell_neighbor_graph.txt delete mode 100644 pcdl/test-data/output00000001_cells.mat delete mode 100644 pcdl/test-data/output00000001_microenvironment0.mat delete mode 100644 pcdl/test-data/output00000001_oxygen.jpeg delete mode 100644 pcdl/test-data/output00000001_spring_attached_cells_graph.txt diff --git a/pcdl/test-data/PhysiCell_settings.xml b/pcdl/test-data/PhysiCell_settings.xml deleted file mode 100644 index 9bba555..0000000 --- a/pcdl/test-data/PhysiCell_settings.xml +++ /dev/null @@ -1,405 +0,0 @@ - - - - -30 - 300 - -20 - 200 - -10 - 100 - 30 - 20 - 10 - false - - - - 1440.0 - min - micron - 0.01 - 0.1 - 6 - - - - 1 - - - - output - - 60 - true - - - 60 - false - - water - YlOrRd - 0 - 1 - - - - false - - - - - false - true - false - 0 - - - - - - 1e3 - 1 - - 1e3 - 0.0 - - - - - - - - - - - - 1e6 - 0.001 - - 1e3 - 1e3 - - 1e3 - 1e3 - 1e3 - 1e3 - 1e3 - 1e3 - - - - true - true - - ./config/initial.mat - - - ./config/dirichlet.mat - - - - - - - - - - 0.003333 - 0.002083 - 0.004167 - 0.016667 - - - - - 5.31667e-05 - - 516 - - - 0.05 - 0 - 1.66667e-02 - 5.83333e-03 - 0 - 2.0 - - - - 0.0 - - 0 - 86400 - - - 1.11667e-2 - 8.33333e-4 - 5.33333e-5 - 2.16667e-3 - 0 - 2.0 - - - - - 2494 - 0.75 - 540 - 0.05 - 0.0045 - 0.0055 - 0 - 0 - 2.0 - - - 0.4 - 10.0 - 1.25 - - 1.0 - 1 - - - 1.8 - 15.12 - - 0.01 - 0.05 - 0.03 - 12 - - - 1 - 1 - .2 - - true - true - - true - oxygen - 1 - - - false - false - - 0.0 - 0.0 - - - - - - - 0 - 0 - 10 - 0.0 - - - 5 - 15 - 0 - 0 - - - - 0.0 - 0.0 - 0.0 - - 0.001 - 0.0 - - - 0.0025 - 0.0 - - 1.0 - 0.1 - - 0.0 - 0.005 - - - - - 0.0 - 0.0 - - - - 0.0 - 0.0 - - - - 1.0 - - - - - - - - - 1388.888889 - - - - - 5.31667e-05 - - 0.001938 - - - 0.05 - 0 - 1.66667e-02 - 5.83333e-03 - 0 - 2.0 - - - - 0.0 - - 9000000000.0 - 1.15741e-05 - - - 1.11667e-02 - 8.33333e-4 - 5.33333e-05 - 2.16667e-4 - 7e-05 - 2.0 - - - - - 2494 - 0.75 - 540 - 0.05 - 0.0045 - 0.0055 - 0.0 - 0.0 - 2 - - - 0.4 - 10.0 - 1.25 - - 1.0 - 1.0 - - - 1.8 - 15.12 - - 0.01 - 0.0 - 0.0 - 12 - - - 2.5 - 1.0 - .5 - - true - true - - true - water - 1 - - - false - false - - 0.0 - 0.0 - - - - - - - 1000 - 50000 - 0 - 0.0 - - - 0 - 1.0 - 10 - 0.0 - - - - 0.0 - 0.0 - 0.0 - - 0.0 - 0.0005 - - - 0 - 0.006 - - 1.0 - 0.1 - - 0.0 - 0.0 - - - - - 0.0 - 0.0 - - - - 0.0 - 0.0 - - - - 0.0 - - - - - - - - - ./config - cells.csv - - - - - - - ./config - cell_rules.csv - - - - - - - 0 - 64 - - diff --git a/pcdl/test-data/final.svg b/pcdl/test-data/final.svg deleted file mode 100644 index bcfdbf3..0000000 --- a/pcdl/test-data/final.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - Current time: 1 days, 0 hours, and 0.01 minutes, z = 0.00 μm - - - 153 agents - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 200 μm - - - 0 days, 0 hours, 0 minutes, and 10.5490 seconds - - - - diff --git a/pcdl/test-data/final.xml b/pcdl/test-data/final.xml deleted file mode 100644 index 1c9cb2a..0000000 --- a/pcdl/test-data/final.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - PhysiCell - 1.14.1 - http://physicell.org - - - 0000-0002-9925-0151 - Paul - Macklin - macklinp@iu.edu - http://MathCancer.org - Indiana University & PhysiCell Project - Intelligent Systems Engineering - - - - A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 - 10.1371/journal.pcbi.1005991 - https://dx.doi.org/PMC5841829 - 29474446 - PMC5841829 - - - - - 1440.010000 - 10.548248 - 2025-01-05T08:14:42Z - 2025-01-05T08:14:42Z - - - - - -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 - -15 15 45 75 105 135 165 195 225 255 285 - -10 10 30 50 70 90 110 130 150 170 190 - -5 5 15 25 35 45 55 65 75 85 95 - - initial_mesh0.mat - - - - - - - 1000.000000 - 1.000000 - - - - - - 1000000.000000 - 0.001000 - - - - - final_microenvironment0.mat - - - - - - - - - - default - blood_cells - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final_cells.mat - - - final_cell_neighbor_graph.txt - - - final_attached_cells_graph.txt - - - final_spring_attached_cells_graph.txt - - - - - - diff --git a/pcdl/test-data/final_attached_cells_graph.txt b/pcdl/test-data/final_attached_cells_graph.txt deleted file mode 100644 index daae2c2..0000000 --- a/pcdl/test-data/final_attached_cells_graph.txt +++ /dev/null @@ -1,153 +0,0 @@ -210: -1: -221: -225: -211: -206: -108: -230: -164: -152: -213: -137: -143: -185: -159: -229: -242: -17: -102: -161: -217: -115: -135: -160: -239: -244: -106: -181: -120: -112: -188: -116: -125: -158: -136: -186: -139: -190: -124: -226: -132: -238: -142: -228: -148: -153: -145: -123: -48: -154: -131: -51: -215: -127: -216: -144: -130: -57: -214: -121: -180: -61: -163: -227: -64: -65: -138: -67: -134: -69: -70: -71: -72: -73: -114: -75: -76: -241: -78: -79: -150: -81: -82: -83: -84: -151: -119: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -149: -146: -147: -156: -157: -165: -166: -167: -168: -169: -170: -171: -172: -173: -174: -175: -177: -182: -183: -184: -187: -218: -245: -194: -195: -197: -243: -199: -200: -201: -202: -212: -204: -205: -207: -208: -209: -219: -224: -231: -232: -233: -234: -235: -236: -237: -240: -246: -247: -248: -249: -250: -251: -252: \ No newline at end of file diff --git a/pcdl/test-data/final_cell_neighbor_graph.txt b/pcdl/test-data/final_cell_neighbor_graph.txt deleted file mode 100644 index fb79f28..0000000 --- a/pcdl/test-data/final_cell_neighbor_graph.txt +++ /dev/null @@ -1,153 +0,0 @@ -210: -1: -221: -225: 195 -211: -206: 158,217 -108: 17 -230: 180,96,188 -164: 48 -152: -213: -137: 218,51 -143: 102 -185: -159: -229: 250,139 -242: 204 -17: 243,108 -102: 143 -161: 214,94 -217: 206 -115: 195,226 -135: -160: 97 -239: 124 -244: 170 -106: -181: 57 -120: 168,240 -112: -188: 230 -116: -125: 158,182 -158: 206,125 -136: 87 -186: 134 -139: 151,229 -190: -124: 239 -226: 115,90 -132: -238: -142: -228: 72 -148: -153: 95 -145: -123: 169 -48: 164 -154: -131: -51: 137,236,247 -215: -127: 207,157 -216: -144: 173 -130: -57: 181,98 -214: 161,94,202,165 -121: 209 -180: 230 -61: -163: -227: 150 -64: 252,194 -65: -138: 204 -67: -134: 186 -69: 209 -70: -71: -72: 228 -73: 183,96 -114: -75: -76: -241: -78: 249,194 -79: -150: 227 -81: 209 -82: -83: -84: 243 -151: 139 -119: -87: 136 -88: -89: -90: 226 -91: 165 -92: -93: -94: 161,235,214 -95: 153 -96: 230,73 -97: 160 -98: 183,57 -149: -146: -147: 170,171 -156: 197 -157: 248,127 -165: 91,214 -166: 251,209 -167: 184 -168: 120 -169: 123 -170: 147,244 -171: 147 -172: -173: 177,144 -174: -175: -177: 173 -182: 125 -183: 98,73 -184: 167 -187: -218: 137 -245: -194: 78,249,64,224 -195: 115,225 -197: 156 -243: 17,84 -199: -200: -201: -202: 214 -212: -204: 138,242 -205: -207: 127 -208: -209: 166,251,81,69,121 -219: -224: 249,194 -231: -232: -233: -234: -235: 94 -236: 51,247 -237: -240: 120 -246: -247: 236,51 -248: 157 -249: 78,194,224 -250: 229 -251: 166,209 -252: 64 \ No newline at end of file diff --git a/pcdl/test-data/final_cells.mat b/pcdl/test-data/final_cells.mat deleted file mode 100644 index b22b8de3d0ac9b2f6ceba1ac889a32cd98cbf6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124873 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

T+0UQh7-@5>)0DQhq}iL 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 diff --git a/pcdl/test-data/final_microenvironment0.mat b/pcdl/test-data/final_microenvironment0.mat deleted file mode 100644 index e20980cb1b6acea567ca58b24188821d2ef617cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63935 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 - - - - - Current time: 0 days, 0 hours, and 0.00 minutes, z = 0.00 μm - - - 128 agents - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 200 μm - - - 0 days, 0 hours, 0 minutes, and 0.0095 seconds - - - - diff --git a/pcdl/test-data/initial.xml b/pcdl/test-data/initial.xml deleted file mode 100644 index 54aca77..0000000 --- a/pcdl/test-data/initial.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - PhysiCell - 1.14.1 - http://physicell.org - - - 0000-0002-9925-0151 - Paul - Macklin - macklinp@iu.edu - http://MathCancer.org - Indiana University & PhysiCell Project - Intelligent Systems Engineering - - - - A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 - 10.1371/journal.pcbi.1005991 - https://dx.doi.org/PMC5841829 - 29474446 - PMC5841829 - - - - - 0.000000 - 0.008479 - 2025-01-05T08:14:32Z - 2025-01-05T08:14:32Z - - - - - -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 - -15 15 45 75 105 135 165 195 225 255 285 - -10 10 30 50 70 90 110 130 150 170 190 - -5 5 15 25 35 45 55 65 75 85 95 - - initial_mesh0.mat - - - - - - - 1000.000000 - 1.000000 - - - - - - 1000000.000000 - 0.001000 - - - - - initial_microenvironment0.mat - - - - - - - - - - default - blood_cells - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - initial_cells.mat - - - initial_cell_neighbor_graph.txt - - - initial_attached_cells_graph.txt - - - initial_spring_attached_cells_graph.txt - - - - - - diff --git a/pcdl/test-data/initial_attached_cells_graph.txt b/pcdl/test-data/initial_attached_cells_graph.txt deleted file mode 100644 index 3a92aa5..0000000 --- a/pcdl/test-data/initial_attached_cells_graph.txt +++ /dev/null @@ -1,128 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -15: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -38: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -53: -54: -55: -56: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -104: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -124: -125: -126: -127: \ No newline at end of file diff --git a/pcdl/test-data/initial_cell_neighbor_graph.txt b/pcdl/test-data/initial_cell_neighbor_graph.txt deleted file mode 100644 index 3a92aa5..0000000 --- a/pcdl/test-data/initial_cell_neighbor_graph.txt +++ /dev/null @@ -1,128 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -15: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -38: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -53: -54: -55: -56: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -104: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -124: -125: -126: -127: \ No newline at end of file diff --git a/pcdl/test-data/initial_cells.mat b/pcdl/test-data/initial_cells.mat deleted file mode 100644 index bbacc7592ecc0337f92d7002c8af50070e493cd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104473 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 ZWgR+f`E_d7B^)0H_m2(0WytrB{U4_Y9j^cY diff --git a/pcdl/test-data/initial_mesh0.mat b/pcdl/test-data/initial_mesh0.mat deleted file mode 100644 index c50e164262fc47fa86316cb18244e43532803e7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42616 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 D`}FbyqeseguFyLP}9*adDafhELJ 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 - - - - - - default - - - - - blood_cells - - diff --git a/pcdl/test-data/output00000000.xml b/pcdl/test-data/output00000000.xml deleted file mode 100644 index 48a0631..0000000 --- a/pcdl/test-data/output00000000.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - PhysiCell - 1.14.1 - http://physicell.org - - - 0000-0002-9925-0151 - Paul - Macklin - macklinp@iu.edu - http://MathCancer.org - Indiana University & PhysiCell Project - Intelligent Systems Engineering - - - - A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 - 10.1371/journal.pcbi.1005991 - https://dx.doi.org/PMC5841829 - 29474446 - PMC5841829 - - - - - 0.000000 - 0.000030 - 2025-01-05T08:14:32Z - 2025-01-05T08:14:32Z - - - - - -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 - -15 15 45 75 105 135 165 195 225 255 285 - -10 10 30 50 70 90 110 130 150 170 190 - -5 5 15 25 35 45 55 65 75 85 95 - - initial_mesh0.mat - - - - - - - 1000.000000 - 1.000000 - - - - - - 1000000.000000 - 0.001000 - - - - - output00000000_microenvironment0.mat - - - - - - - - - - default - blood_cells - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - output00000000_cells.mat - - - output00000000_cell_neighbor_graph.txt - - - output00000000_attached_cells_graph.txt - - - output00000000_spring_attached_cells_graph.txt - - - - - - diff --git a/pcdl/test-data/output00000000_attached_cells_graph.txt b/pcdl/test-data/output00000000_attached_cells_graph.txt deleted file mode 100644 index 3a92aa5..0000000 --- a/pcdl/test-data/output00000000_attached_cells_graph.txt +++ /dev/null @@ -1,128 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -15: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -38: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -53: -54: -55: -56: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -104: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -124: -125: -126: -127: \ No newline at end of file diff --git a/pcdl/test-data/output00000000_cell_neighbor_graph.txt b/pcdl/test-data/output00000000_cell_neighbor_graph.txt deleted file mode 100644 index 3a92aa5..0000000 --- a/pcdl/test-data/output00000000_cell_neighbor_graph.txt +++ /dev/null @@ -1,128 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -15: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -38: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -53: -54: -55: -56: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -104: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -124: -125: -126: -127: \ No newline at end of file diff --git a/pcdl/test-data/output00000000_cells.mat b/pcdl/test-data/output00000000_cells.mat deleted file mode 100644 index bbacc7592ecc0337f92d7002c8af50070e493cd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104473 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 ZWgR+f`E_d7B^)0H_m2(0WytrB{U4_Y9j^cY diff --git a/pcdl/test-data/output00000000_microenvironment0.mat b/pcdl/test-data/output00000000_microenvironment0.mat deleted file mode 100644 index fbf00a2eec2bfdd4e91a5bc2681b58abc9b9ca0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63935 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 diff --git a/pcdl/test-data/output00000000_spring_attached_cells_graph.txt b/pcdl/test-data/output00000000_spring_attached_cells_graph.txt deleted file mode 100644 index 3a92aa5..0000000 --- a/pcdl/test-data/output00000000_spring_attached_cells_graph.txt +++ /dev/null @@ -1,128 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -15: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -38: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -53: -54: -55: -56: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -104: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -124: -125: -126: -127: \ No newline at end of file diff --git a/pcdl/test-data/output00000001.xml b/pcdl/test-data/output00000001.xml deleted file mode 100644 index dc7270c..0000000 --- a/pcdl/test-data/output00000001.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - PhysiCell - 1.14.1 - http://physicell.org - - - 0000-0002-9925-0151 - Paul - Macklin - macklinp@iu.edu - http://MathCancer.org - Indiana University & PhysiCell Project - Intelligent Systems Engineering - - - - A Ghaffarizadeh, R Heiland, SH Friedman, SM Mumenthaler, and P Macklin. PhysiCell: an Open Source Physics-Based Cell Simulator for Multicellular Systems, PLoS Comput. Biol. 14(2): e1005991, 2018. DOI: 10.1371/journal.pcbi.1005991 - 10.1371/journal.pcbi.1005991 - https://dx.doi.org/PMC5841829 - 29474446 - PMC5841829 - - - - - 60.000000 - 0.510965 - 2025-01-05T08:14:32Z - 2025-01-05T08:14:32Z - - - - - -30.000000 -20.000000 -10.000000 300.000000 200.000000 100.000000 - -15 15 45 75 105 135 165 195 225 255 285 - -10 10 30 50 70 90 110 130 150 170 190 - -5 5 15 25 35 45 55 65 75 85 95 - - initial_mesh0.mat - - - - - - - 1000.000000 - 1.000000 - - - - - - 1000000.000000 - 0.001000 - - - - - output00000001_microenvironment0.mat - - - - - - - - - - default - blood_cells - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - output00000001_cells.mat - - - output00000001_cell_neighbor_graph.txt - - - output00000001_attached_cells_graph.txt - - - output00000001_spring_attached_cells_graph.txt - - - - - - diff --git a/pcdl/test-data/output00000001_attached_cells_graph.txt b/pcdl/test-data/output00000001_attached_cells_graph.txt deleted file mode 100644 index 4115d5d..0000000 --- a/pcdl/test-data/output00000001_attached_cells_graph.txt +++ /dev/null @@ -1,125 +0,0 @@ -0: -1: -2: -3: -4: -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -126: -16: -17: -18: -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: -29: -30: -31: -32: -33: -34: -35: -36: -37: -124: -39: -40: -41: -42: -43: -44: -45: -46: -47: -48: -49: -50: -51: -52: -127: -54: -55: -130: -57: -58: -59: -60: -61: -62: -63: -64: -65: -66: -67: -68: -69: -70: -71: -72: -73: -74: -75: -76: -77: -78: -79: -80: -81: -82: -83: -84: -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: -96: -97: -98: -99: -100: -101: -102: -103: -125: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: -117: -118: -119: -120: -121: -122: -123: -131: \ No newline at end of file diff --git a/pcdl/test-data/output00000001_cell_neighbor_graph.txt b/pcdl/test-data/output00000001_cell_neighbor_graph.txt deleted file mode 100644 index 8323137..0000000 --- a/pcdl/test-data/output00000001_cell_neighbor_graph.txt +++ /dev/null @@ -1,125 +0,0 @@ -0: -1: 68 -2: -3: -4: 59 -5: -6: -7: 69,58 -8: -9: -10: -11: -12: -13: -14: -126: 22 -16: 109 -17: -18: 98 -19: 76,94 -20: 106 -21: -22: 126 -23: -24: 94 -25: -26: -27: -28: 48 -29: -30: -31: 35 -32: 95 -33: -34: 47 -35: 31 -36: 77 -37: 65,123 -124: -39: 75 -40: 74,86 -41: 87 -42: -43: -44: 84 -45: -46: 54 -47: 34 -48: 28 -49: -50: 60,116 -51: -52: -127: 75 -54: 46 -55: -130: -57: 97 -58: 7 -59: 66,4 -60: 64,50 -61: -62: -63: -64: 60 -65: 37 -66: 59 -67: -68: 1 -69: 7 -70: -71: -72: -73: -74: 40 -75: 127,39 -76: 19,81 -77: 36 -78: -79: 116 -80: 73 -81: 76 -82: -83: -84: 44 -85: -86: 40 -87: 41 -88: -89: -90: -91: -92: -93: -94: 19,24 -95: 32 -96: -97: 57 -98: 18 -99: -100: -101: -102: -103: -125: 121 -105: -106: 20 -107: -108: -109: 16 -110: -111: -112: -113: -114: -115: -116: 50,79 -117: -118: 122 -119: -120: -121: 125 -122: 118 -123: 37 -131: \ No newline at end of file diff --git a/pcdl/test-data/output00000001_cells.mat b/pcdl/test-data/output00000001_cells.mat deleted file mode 100644 index 0ba0409a92ba857868d5eec5fc6278a7e042220c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102025 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 GV*dx-$8&iA diff --git a/pcdl/test-data/output00000001_microenvironment0.mat b/pcdl/test-data/output00000001_microenvironment0.mat deleted file mode 100644 index 8a76cf8502fda4aa60cf69f241e4ecae1c52af96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63935 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 F{|Cj~%isV2 diff --git a/pcdl/test-data/output00000001_oxygen.jpeg b/pcdl/test-data/output00000001_oxygen.jpeg deleted file mode 100644 index cc4cdd63ea819943b315b3d86e9ba804e6ec03dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13395 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 diff --git a/pcdl/test-data/output00000001_spring_attached_cells_graph.txt b/pcdl/test-data/output00000001_spring_attached_cells_graph.txt deleted file mode 100644 index 7277b48..0000000 --- a/pcdl/test-data/output00000001_spring_attached_cells_graph.txt +++ /dev/null @@ -1,125 +0,0 @@ -0: -1: -2: -3: -4: 59 -5: -6: -7: -8: -9: -10: -11: -12: -13: -14: -126: -16: -17: -18: 98 -19: -20: -21: -22: -23: -24: -25: -26: -27: -28: 48 -29: -30: -31: 35 -32: 95 -33: -34: -35: 31 -36: 77 -37: 123,65 -124: -39: 75 -40: -41: -42: -43: -44: 84 -45: -46: 54 -47: -48: 28 -49: 117 -50: 116,60 -51: -52: -127: -54: 46 -55: -130: -57: -58: -59: 66,4 -60: 64,50 -61: -62: -63: -64: 60 -65: 37 -66: 59 -67: -68: -69: -70: -71: -72: -73: -74: -75: 39 -76: -77: 36 -78: -79: -80: -81: -82: -83: -84: 44 -85: -86: -87: -88: -89: -90: -91: -92: -93: -94: -95: 32 -96: -97: -98: 18 -99: -100: -101: -102: -103: -125: -105: -106: -107: -108: -109: -110: -111: -112: -113: -114: -115: -116: 50 -117: 49 -118: -119: -120: -121: -122: -123: 37 -131: \ No newline at end of file From cf5f41658a9ef5db643607d31e02548443ae94a0 Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 24 Jun 2025 08:21:10 -0400 Subject: [PATCH 33/41] @ v3 : update github workflows. --- .github/workflows/apple.yml | 6 +++--- .github/workflows/linux.yml | 6 +++--- .github/workflows/windows.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index 946a1cc..7be2fbf 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on mac os x; the latest pytho on: push: - branches: ["v3"] + branches: ["utest"] # ["master", "v3", "v4"] jobs: build-macosx: @@ -15,8 +15,8 @@ jobs: strategy: fail-fast: false matrix: - #python-version: ["3.13"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + #python-version: ["3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13"] env: PYTHONPATH: /Users/runner/work/physicelldataloader/physicelldataloader diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9ae17bc..c89dc1f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on linux os; all python3 vers on: push: - branches: ["v3"] + branches: ["utest"] # ["master", "v3", "v4"] jobs: build-linux: @@ -15,8 +15,8 @@ jobs: strategy: fail-fast: false matrix: - #python-version: ["3.13"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + #python-version: ["3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13"] env: PYTHONPATH: /home/runner/work/physicelldataloader/physicelldataloader diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 088247d..90438df 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on windows os; the latest pyt on: push: - branches: ["v3"] + branches: ["utest"] # ["master", "v3", "v4"] jobs: build-windows: @@ -15,8 +15,8 @@ jobs: strategy: fail-fast: false matrix: - #python-version: ["3.13"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + #python-version: ["3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13"] env: PYTHONPATH: D:\a\physicelldataloader\physicelldataloader From 81cf0b12b6014339c9dc63d153a5545579aa982b Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 20 Jul 2025 23:01:46 -0400 Subject: [PATCH 34/41] @ pyCLI : commandline commands return now 0, if successfull. --- pcdl/pyCLI.py | 85 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/pcdl/pyCLI.py b/pcdl/pyCLI.py index 77d3f0a..2dd4f0c 100644 --- a/pcdl/pyCLI.py +++ b/pcdl/pyCLI.py @@ -96,7 +96,8 @@ def get_version(): ) s_version = f'version:\n{mcds.get_physicell_version()}\n{mcds.get_multicellds_version()}\npcdl_{pcdl.__version__}' # going home - return s_version + print(s_version) + return 0 def get_unit_dict(): @@ -173,7 +174,8 @@ def get_unit_dict(): se_unit.sort_index(inplace=True) se_unit.to_csv(s_opathfile) # going home - return s_opathfile + print(s_opathfile) + return 0 ########################################### @@ -238,7 +240,8 @@ def get_substrate_list(): verbose = True if args.verbose.lower().startswith('t') else False ) # going home - return mcds.get_substrate_list() + print(mcds.get_substrate_list()) + return 0 def get_conc_attribute(): @@ -339,7 +342,8 @@ def get_conc_attribute(): s_opathfile = f'{s_path}/timeseries_conc_attribute_{s_values}.json' json.dump(dl_variable, open(s_opathfile, 'w'), sort_keys=True) # going home - return s_opathfile + print(s_opathfile) + return 0 def get_conc_df(): @@ -436,7 +440,7 @@ def get_conc_df(): # going home s_opathfile = s_pathfile.replace('.xml','_conc.csv') df_conc.to_csv(s_opathfile) - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -461,12 +465,14 @@ def get_conc_df(): if b_collapse: s_opathfile = f'{s_path}/timeseries_conc.csv' ldf_conc.to_csv(s_opathfile) - return s_opathfile + print(s_opathfile) else: ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml','_conc.csv')}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, df_conc in enumerate(ldf_conc): df_conc.to_csv(ls_opathfile[i]) - return ls_opathfile + print(ls_opathfile) + # going home + return 0 def plot_contour(): @@ -659,7 +665,7 @@ def plot_contour(): figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) # going home - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -692,7 +698,9 @@ def plot_contour(): ) # going home s_opath = '/'.join(ls_opathfile[0].split('/')[:-1]) - return s_opath + print(s_opath) + # going home + return 0 def make_conc_vtk(): @@ -757,7 +765,7 @@ def make_conc_vtk(): visualize = False, ) # going home - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -774,7 +782,9 @@ def make_conc_vtk(): visualize = False, ) # going home - return ls_opathfile + print(ls_opathfile) + # going home + return 0 ############################################ @@ -844,7 +854,8 @@ def get_celltype_list(): verbose = True if args.verbose.lower().startswith('t') else False ) # going home - return mcds.get_celltype_list() + print(mcds.get_celltype_list()) + return 0 def get_cell_attribute_list(): @@ -921,7 +932,8 @@ def get_cell_attribute_list(): ) # going home - return mcds.get_cell_attribute_list() + print(mcds.get_cell_attribute_list()) + return 0 def get_cell_attribute(): @@ -1076,7 +1088,8 @@ def get_cell_attribute(): allvalues = b_allvalues, ) json.dump(dl_variable, open(s_opathfile, 'w'), sort_keys=True) - return s_opathfile + print(s_opathfile) + return 0 def get_cell_df(): @@ -1188,7 +1201,7 @@ def get_cell_df(): # going home s_opathfile = s_pathfile.replace('.xml','_cell.csv') df_cell.to_csv(s_opathfile) - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -1213,12 +1226,14 @@ def get_cell_df(): if b_collapse: s_opathfile = f'{s_path}/timeseries_cell.csv' ldf_cell.to_csv(s_opathfile) - return s_opathfile + print(s_opathfile) else: ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml','_cell.csv')}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, df_cell in enumerate(ldf_cell): df_cell.to_csv(ls_opathfile[i]) - return ls_opathfile + print(ls_opathfile) + # going home + return 0 def get_anndata(): @@ -1360,7 +1375,7 @@ def get_anndata(): # going home s_opathfile = s_pathfile.replace('.xml', f'_cell_{args.scale}.h5ad') ann_mcds.write_h5ad(s_opathfile) - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.TimeSeries( @@ -1386,12 +1401,14 @@ def get_anndata(): if b_collapse : s_opathfile = f'{s_path}/timeseries_cell_{args.scale.lower()}.h5ad' ann_mcdsts.write_h5ad(s_opathfile) - return s_opathfile + print(s_opathfile) else: ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale.lower()))}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, ann_mcds in enumerate(ann_mcdsts): ann_mcds.write_h5ad(ls_opathfile[i]) - return ls_opathfile + print(ls_opathfile) + # going home + return 0 def make_graph_gml(): @@ -1510,7 +1527,7 @@ def make_graph_gml(): node_attribute = args.node_attribute, ) # going home - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -1529,8 +1546,9 @@ def make_graph_gml(): node_attribute = args.node_attribute, ) # going home - return ls_opathfile - + print(ls_opathfile) + # going home + return 0 def plot_scatter(): # argv @@ -1749,7 +1767,7 @@ def plot_scatter(): figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) # going home - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -1783,7 +1801,9 @@ def plot_scatter(): ) # going home s_opathfile = '/'.join(ls_opathfile[0].split('/')[:-1]) - return s_opathfile + print(s_opathfile) + # going home + return 0 def make_cell_vtk(): @@ -1889,7 +1909,7 @@ def make_cell_vtk(): visualize = False, ) # going home - return s_opathfile + print(s_opathfile) else: mcdsts = pcdl.pyMCDSts( @@ -1907,7 +1927,9 @@ def make_cell_vtk(): visualize = False, ) # going home - return ls_opathfile + print(ls_opathfile) + # going home + return 0 ################################################### @@ -2192,7 +2214,8 @@ def plot_timeseries(): figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) # going home - return s_pathfile + print(s_pathfile) + return 0 ################# @@ -2240,7 +2263,8 @@ def make_gif(): interface = args.interface, ) # going home - return s_opathfile + print(s_opathfile) + return 0 def make_movie(): @@ -2292,4 +2316,5 @@ def make_movie(): framerate = args.framerate, ) # going home - return s_opathfile + print(s_opathfile) + return 0 From 175268bc6c1116f621eb27aa4f5ce5eceec77029 Mon Sep 17 00:00:00 2001 From: bue Date: Sun, 20 Jul 2025 23:22:47 -0400 Subject: [PATCH 35/41] @ workflows : v3 branch specific update. --- .github/workflows/apple.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index 7be2fbf..18fefeb 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on mac os x; the latest pytho on: push: - branches: ["utest"] # ["master", "v3", "v4"] + branches: ["v3"] # ["master", "utest", "v4"] jobs: build-macosx: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c89dc1f..fda3e2a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on linux os; all python3 vers on: push: - branches: ["utest"] # ["master", "v3", "v4"] + branches: ["v3"] # ["master", "utest", "v4"] jobs: build-linux: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 90438df..d7b8065 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on windows os; the latest pyt on: push: - branches: ["utest"] # ["master", "v3", "v4"] + branches: ["v3"] # ["master", "utest", "v4"] jobs: build-windows: From c5159a0ec30a112c40e1d647da66a5c4cb2fe080 Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 22 Jul 2025 14:05:37 -0400 Subject: [PATCH 36/41] @ test : update test_cli_2d to new stdout, sterr, and error code handdling. --- test/test_cli_2d.py | 2212 ++++++++++++++++++------------------------- 1 file changed, 905 insertions(+), 1307 deletions(-) diff --git a/test/test_cli_2d.py b/test/test_cli_2d.py index f63240b..0177dd3 100644 --- a/test/test_cli_2d.py +++ b/test/test_cli_2d.py @@ -54,18 +54,20 @@ class TestPyCliVersion(object): # + verbose (true, _false_) nop def test_pcdl_get_version_timeseries(self): - s_result = subprocess.run(['pcdl_get_version', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_version = s_result.stderr.decode('UTF8').replace('\r','') - assert s_version.startswith('version:\nPhysiCell_') + o_result = subprocess.run(['pcdl_get_version', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_version_timestep(self): - s_result = subprocess.run(['pcdl_get_version', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_version = s_result.stderr.decode('UTF8').replace('\r','') - assert s_version.startswith('version:\nPhysiCell_') + o_result = subprocess.run(['pcdl_get_version', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 class TestPyCliUnitDict(object): @@ -78,52 +80,49 @@ class TestPyCliUnitDict(object): # + verbose (true, _false_) nop def test_pcdl_get_unit_dict_timeseries(self): - s_result = subprocess.run(['pcdl_get_unit_dict', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_unit.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_unit_dict', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_unit.csv') def test_pcdl_get_unit_dict_timestep(self): - s_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_unit.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_unit.csv') def test_pcdl_get_unit_dict_timestep_microenv(self): - s_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_unit.csv')) and \ - (not set(df_cell.index).issuperset({'oxygen'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_unit.csv') def test_pcdl_get_unit_dict_timestep_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_unit.csv')) and \ - (set(df_cell.index).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_unit.csv') def test_pcdl_get_unit_dict_timestep_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_unit.csv')) and \ - (set(df_cell.index).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_unit.csv') ############################## @@ -138,18 +137,20 @@ class TestPyCliSubstrateList(object): # + verbose (true, _false_) nop def test_pcdl_get_substrate_list_timeseries(self): - s_result = subprocess.run(['pcdl_get_substrate_list', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['oxygen', 'water']")) + o_result = subprocess.run(['pcdl_get_substrate_list', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_substrate_list_timestep(self): - s_result = subprocess.run(['pcdl_get_substrate_list', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['oxygen', 'water']")) + o_result = subprocess.run(['pcdl_get_substrate_list', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 class TestPyCliConcDfAttribute(object): @@ -164,54 +165,49 @@ class TestPyCliConcDfAttribute(object): # + allvalues (false _true_) ok def test_pcdl_get_conc_attribute_timeseries(self): - s_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_conc_attribute_minmax.json')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') def test_pcdl_get_conc_attribute_timeseries_value(self): - s_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_conc_attribute_minmax.json')) and \ - (len(d_attribute) == 2) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') def test_pcdl_get_conc_attribute_timeseries_drop(self): - s_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--drop', 'conc_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_conc_attribute_minmax.json')) and \ - (not set(d_attribute.keys()).issuperset({'oxygen'})) and \ - (len(d_attribute) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--drop', 'conc_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') def test_pcdl_get_conc_attribute_timeseries_keep(self): - s_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--keep', 'conc_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_conc_attribute_minmax.json')) and \ - (set(d_attribute.keys()).issuperset({'oxygen'})) and \ - (len(d_attribute) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--keep', 'conc_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') def test_pcdl_get_conc_attribute_timeseries_allvalues(self): - s_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_conc_attribute_all.json')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_attribute_all.json') class TestPyCliConcDf(object): @@ -227,105 +223,86 @@ class TestPyCliConcDf(object): # + keep (oxygen) ok def test_pcdl_get_conc_df_timeseries(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_conc.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc.csv') def test_pcdl_get_conc_df_timeseries_collapsed(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print(ls_opathfile, len(ls_opathfile)) - assert len(ls_opathfile) == 25 and \ - ls_opathfile[0].endswith('output_2d/output00000000_conc.csv') and \ - ls_opathfile[-1].endswith('output_2d/output00000024_conc.csv') and \ - os.path.exists(ls_opathfile[12]) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_conc.csv') def test_pcdl_get_conc_df_timeseries_value(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_conc.csv')) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 11) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc.csv') def test_pcdl_get_conc_df_timeseries_drop(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--drop', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_conc.csv')) and \ - (not set(df_conc.columns).issuperset({'oxygen'})) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 10) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--drop', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc.csv') def test_pcdl_get_conc_df_timeseries_keep(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--keep', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_conc.csv')) and \ - (set(df_conc.columns).issuperset({'oxygen'})) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 10) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--keep', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc.csv') def test_pcdl_get_conc_df_timestep(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_conc.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_conc.csv') def test_pcdl_get_conc_df_timestep_value(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_conc.csv')) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 11) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_conc.csv') def test_pcdl_get_conc_df_timestep_drop(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--drop', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_conc.csv')) and \ - (not set(df_conc.columns).issuperset({'oxygen'})) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 10) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--drop', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_conc.csv') def test_pcdl_get_conc_df_timestep_keep(self): - s_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--keep', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_conc = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_conc.csv')) and \ - (set(df_conc.columns).issuperset({'oxygen'})) and \ - (df_conc.shape[0] > 9) and \ - (df_conc.shape[1] == 10) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--keep', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_conc.csv') class TestPyCliPlotContour(object): @@ -350,35 +327,18 @@ class TestPyCliPlotContour(object): # + figbgcolor (none, _yellow_) def test_pcdl_plot_contour_default(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_contour', s_pathfile_2d, 'oxygen' ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - s_opath = '/'.join(s_opathfile.split('/')[:-1]) - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/conc_oxygen_z0.0/output00000024_oxygen.jpeg')) and \ - (s_stdout.find("focus='oxygen'") > -1) and \ - (s_stdout.find("z_slice=0.0") > -1) and \ - (s_stdout.find("extrema=['none']") > -1) and \ - (s_stdout.find("alpha=1.0") > -1) and \ - (s_stdout.find("fill='true'") > -1) and \ - (s_stdout.find("cmap='viridis'") > -1) and \ - (s_stdout.find("title=''") > -1) and \ - (s_stdout.find("grid='true'") > -1) and \ - (s_stdout.find("xlim=['none']") > -1) and \ - (s_stdout.find("ylim=['none']") > -1) and \ - (s_stdout.find("xyequal='true'") > -1) and \ - (s_stdout.find("figsizepx=['none']") > -1) and \ - (s_stdout.find("ext='jpeg'") > -1) and \ - (s_stdout.find("figbgcolor='none'") > -1) - shutil.rmtree(s_opath) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + shutil.rmtree(f'{s_path_2d}/conc_oxygen_z0.0/') def test_pcdl_plot_contour_set(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_contour', s_pathfile_2d, 'oxygen', '--z_slice', '1.1', '--extrema', '0.0', '40.0', @@ -394,32 +354,14 @@ def test_pcdl_plot_contour_set(self): '--ext', 'tiff', '--figbgcolor', 'yellow', ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - s_opath = '/'.join(s_opathfile.split('/')[:-1]) - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/conc_oxygen_z0.0/output00000024_oxygen.tiff')) and \ - (s_stdout.find("focus='oxygen'") > -1) and \ - (s_stdout.find("z_slice=1.1") > -1) and \ - (s_stdout.find("extrema=['0.0', '40.0']") > -1) and \ - (s_stdout.find("alpha=0.5") > -1) and \ - (s_stdout.find("fill='false'") > -1) and \ - (s_stdout.find("cmap='magma'") > -1) and \ - (s_stdout.find("title='abc'") > -1) and \ - (s_stdout.find("grid='false'") > -1) and \ - (s_stdout.find("xlim=['-40', '400']") > -1) and \ - (s_stdout.find("ylim=['-30', '300']") > -1) and \ - (s_stdout.find("xyequal='false'") > -1) and \ - (s_stdout.find("figsizepx=['842', '531']") > -1) and \ - (s_stdout.find("ext='tiff'") > -1) and \ - (s_stdout.find("figbgcolor='yellow'") > -1) - shutil.rmtree(s_opath) - - -# bue 20250107: broken! FutureWarning causes troubles. + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + shutil.rmtree(f'{s_path_2d}/conc_oxygen_z0.0/') + + class TestPyCliConcVtk(object): ''' tests for one pcdl.pyCli function. ''' @@ -428,26 +370,23 @@ class TestPyCliConcVtk(object): # + verbose (true, _false_) nop def test_pcdl_make_conc_vtk_timeseries_default(self): - s_result = subprocess.run(['pcdl_make_conc_vtk', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead.\n")[-1].split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_conc.vtr')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_conc.vtr')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_conc_vtk', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_conc.vtr') def test_pcdl_make_conc_vtk_timestep_default(self): - s_result = subprocess.run(['pcdl_make_conc_vtk', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').split("FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead.")[-1] - assert (s_opathfile.endswith('output_2d/output00000024_conc.vtr')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_conc_vtk', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_conc.vtr') ################################ @@ -462,18 +401,20 @@ class TestPyCliCelltypeList(object): # + verbose (true, _false_) nop def test_pcdl_get_celltype_list_timeseries(self): - s_result = subprocess.run(['pcdl_get_celltype_list', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['default', 'blood_cells']")) + o_result = subprocess.run(['pcdl_get_celltype_list', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_celltype_list_timestep(self): - s_result = subprocess.run(['pcdl_get_celltype_list', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['default', 'blood_cells']")) + o_result = subprocess.run(['pcdl_get_celltype_list', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 class TestPyCliCellDfAttribute(object): @@ -492,106 +433,94 @@ class TestPyCliCellDfAttribute(object): # + allvalues (false _true_) ok def test_pcdl_get_cell_attribute_timeseries(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_customtype(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_cell = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (d_cell['sample'] == [False, True]) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (not set(d_attribute.keys()).issuperset({'oxygen'})) and \ - (len(d_attribute) == 104) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (set(d_attribute.keys()).issuperset({'default_fusion_rates'})) and \ - (len(d_attribute) == 110) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (set(d_attribute.keys()).issuperset({'default_fusion_rates'})) and \ - (len(d_attribute) == 110) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_value(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (len(d_attribute) == 54) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_drop(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (not set(d_attribute.keys()).issuperset({'cell_type', 'oxygen'})) and \ - (len(d_attribute) == 108) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_keep(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - d_attribute = json.load(open(s_opathfile)) - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_minmax.json')) and \ - (set(d_attribute.keys()).issuperset({'cell_type', 'oxygen'})) and \ - (len(d_attribute) == 2) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') def test_pcdl_get_cell_attribute_timeseries_allvalues(self): - s_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_cell_attribute_all.json')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_attribute_all.json') class TestPyCliCellAttributeList(object): @@ -605,39 +534,44 @@ class TestPyCliCellAttributeList(object): # + verbose (true, _false_) nop def test_pcdl_get_cell_attribute_list_timeseries(self): - s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_list_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.endswith("'uptake_rates_1', 'velocity_vectorlength', 'velocity_x', 'velocity_y', 'velocity_z']\n")) + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_list_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_list_timeseries_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_list_timeseries_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_listing = s_result.stderr.decode('UTF8').replace('\r','') - assert (s_listing.startswith("['apoptotic_phagocytosis_rate', 'asymmetric")) + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 class TestPyCliCellDf(object): @@ -658,181 +592,156 @@ class TestPyCliCellDf(object): # + keep (cell_type oxygen) ok def test_pcdl_get_cell_df_timeseries(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_collapsed(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print(ls_opathfile, len(ls_opathfile)) - assert len(ls_opathfile) == 25 and \ - ls_opathfile[0].endswith('output_2d/output00000000_cell.csv') and \ - ls_opathfile[-1].endswith('output_2d/output00000024_cell.csv') and \ - os.path.exists(ls_opathfile[12]) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.csv') def test_pcdl_get_cell_df_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (not set(df_cell.columns).issuperset({'oxygen'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (set(df_cell.columns).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (set(df_cell.columns).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_value(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 67) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_drop(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (not set(df_cell.columns).issuperset({'cell_type', 'oxygen'})) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 121) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell.csv') def test_pcdl_get_cell_df_timeseries_keep(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/timeseries_cell.csv')) and \ - (set(df_cell.columns).issuperset({'cell_type', 'oxygen'})) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 15) - os.remove(s_opathfile) - + o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_microenv(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (not set(df_cell.columns).issuperset({'oxygen'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_physiboss(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (set(df_cell.columns).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (set(df_cell.columns).issuperset({'default_fusion_rates'})) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_value(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 66) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_drop(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (not set(df_cell.columns).issuperset({'cell_type', 'oxygen'})) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 120) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') def test_pcdl_get_cell_df_timestep_keep(self): - s_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - df_cell = pd.read_csv(s_opathfile, index_col=0) - assert (s_opathfile.endswith('output_2d/output00000024_cell.csv')) and \ - (set(df_cell.columns).issuperset({'cell_type', 'oxygen'})) and \ - (df_cell.shape[0] > 9 ) and \ - (df_cell.shape[1] == 14) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.csv') class TestPyCliAnndata(object): @@ -856,368 +765,204 @@ class TestPyCliAnndata(object): # timeseries def test_pcdl_get_anndata_timeseries(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_collapsed(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.\n','').replace("['","").replace("']\n","").split("', '") # bue 20250307: >= python3.11 - assert len(ls_opathfile) == 25 and \ - ls_opathfile[0].endswith('output_2d/output00000000_cell_maxabs.h5ad') and \ - ls_opathfile[-1].endswith('output_2d/output00000024_cell_maxabs.h5ad') and \ - os.path.exists(ls_opathfile[12]) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_customtype(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'sample'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (not set(ann.var_names).issuperset({'oxygen'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 99) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (99, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_graph(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--graph', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--graph', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'default_fusion_rates'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'default_fusion_rates'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_value(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 50) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (50, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_drop(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (not set(ann.var_names).issuperset({'cell_type'})) and \ - (not set(ann.obs_keys()).issuperset({'oxygen'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 104) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (104, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_keep(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'oxygen'})) and \ - (set(ann.obs_keys()).issuperset({'cell_type'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 1) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 4) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (1, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') def test_pcdl_get_anndata_timeseries_scale(self): - s_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--scale', 'std'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/timeseries_cell_std.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--scale', 'std'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_std.h5ad') # timestep def test_pcdl_get_anndata_timestep(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_microenv(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (not set(ann.var_names).issuperset({'oxygen'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 99) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (99, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_graph(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--graph', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--graph', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_physiboss(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_settingxmlfalse(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'default_fusion_rates'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_settingxmlnone(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'default_fusion_rates'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_value(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '2'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 50) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 6) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (50, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '2'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_drop(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (not set(ann.var_names).issuperset({'cell_type'})) and \ - (not set(ann.obs_keys()).issuperset({'oxygen'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 104) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 6) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (104, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_keep(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_maxabs.h5ad')) and \ - (set(ann.var_names).issuperset({'oxygen'})) and \ - (set(ann.obs_keys()).issuperset({'cell_type'})) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 1) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 3) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (1, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') def test_pcdl_get_anndata_timestep_scale(self): - s_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--scale', 'std'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','').replace(':0:','sys:1:').replace('sys:1: DeprecationWarning: Call to deprecated function (or staticmethod) _destroy.','') # bue 20250307: >= python3.11 - ann = ad.read_h5ad(s_opathfile) - assert (s_opathfile.endswith('output_2d/output00000024_cell_std.h5ad')) and \ - (ann.shape[0] > 9) and \ - (ann.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--scale', 'std'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell_std.h5ad') class TestPyCliGraphGml(object): @@ -1235,224 +980,195 @@ class TestPyCliGraphGml(object): # + node_attribute (cell_type oxygen) ok def test_pcdl_make_graph_gml_timeseries_default(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + def test_pcdl_make_graph_gml_timeseries_customtype_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_settingxmlfalse_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_settingxmlnone_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_graph_type(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'attached'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_attached.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_attached.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'attached'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_attached.gml') def test_pcdl_make_graph_gml_timeseries_edge_attribute(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timeseries_nodeattribute_many(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_neighbor.gml')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') def test_pcdl_make_graph_gml_timestep_default(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_customtype_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_microenv(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_physiboss(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_settingxmlfalse_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_settingxmlnone_nodeattribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_graph_type(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'attached'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_attached.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'attached'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_attached.gml') def test_pcdl_make_graph_gml_timestep_edge_attribute(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_node_attribute_one(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') def test_pcdl_make_graph_gml_timestep_node_attribute_many(self): - s_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_neighbor.gml')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_neighbor.gml') class TestPyCliPlotScatter(object): @@ -1481,42 +1197,19 @@ class TestPyCliPlotScatter(object): # + ext (jpeg, _tiff_) # + figbgcolor (none, _yellow_) - # bue 20250107: broken by Ignoring def test_pcdl_plot_scatter_default(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_scatter', s_pathfile_2d, ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - s_opath = '/'.join(s_opathfile.split('/')[:-1]) - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/output00000024_cell_type.jpeg')) and \ - (s_stdout.find("custom_data_type=[]") > -1) and \ - (s_stdout.find("microenv='true'") > -1) and \ - (s_stdout.find("physiboss='true'") > -1) and \ - (s_stdout.find("settingxml='PhysiCell_settings.xml'") > -1) and \ - (s_stdout.find("focus='cell_type'") > -1) and \ - (s_stdout.find("z_slice=0.0") > -1) and \ - (s_stdout.find("z_axis=['none']") > -1) and \ - (s_stdout.find("alpha=1.0") > -1) and \ - (s_stdout.find("cmap='viridis'") > -1) and \ - (s_stdout.find("title=''") > -1) and \ - (s_stdout.find("grid='true'") > -1) and \ - (s_stdout.find("legend_loc='lower left'") > -1) and \ - (s_stdout.find("xlim=['none']") > -1) and \ - (s_stdout.find("ylim=['none']") > -1) and \ - (s_stdout.find("xyequal='true'") > -1) and \ - (s_stdout.find("s=1.0") > -1) and \ - (s_stdout.find("figsizepx=['none']") > -1) and \ - (s_stdout.find("ext='jpeg'") > -1) and \ - (s_stdout.find("figbgcolor='none'") > -1) - shutil.rmtree(s_opath) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + shutil.rmtree(f'{s_path_2d}/cell_cell_type_z0.0/') def test_pcdl_plot_scatter_set(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_scatter', s_pathfile_2d, 'oxygen', '--custom_data_type', 'sample:bool', '--microenv', 'True', @@ -1537,34 +1230,12 @@ def test_pcdl_plot_scatter_set(self): '--ext', 'tiff', '--figbgcolor', 'yellow', ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - s_opath = '/'.join(s_opathfile.split('/')[:-1]) - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/cell_oxygen_z0.0/output00000024_oxygen.tiff')) and \ - (s_stdout.find("custom_data_type=['sample:bool']") > -1) and \ - (s_stdout.find("microenv='True'") > -1) and \ - (s_stdout.find("physiboss='false'") > -1) and \ - (s_stdout.find("settingxml='false'") > -1) and \ - (s_stdout.find("focus='oxygen'") > -1) and \ - (s_stdout.find("z_slice=1.1") > -1) and \ - (s_stdout.find("z_axis=['0.0', '40.0']") > -1) and \ - (s_stdout.find("alpha=0.5") > -1) and \ - (s_stdout.find("cmap='magma'") > -1) and \ - (s_stdout.find("title='abc'") > -1) and \ - (s_stdout.find("grid='false'") > -1) and \ - (s_stdout.find("legend_loc='upper right'") > -1) and \ - (s_stdout.find("xlim=['-40', '400']") > -1) and \ - (s_stdout.find("ylim=['-30', '300']") > -1) and \ - (s_stdout.find("xyequal='false'") > -1) and \ - (s_stdout.find("s=74.0") > -1) and \ - (s_stdout.find("figsizepx=['842', '531']") > -1) and \ - (s_stdout.find("ext='tiff'") > -1) and \ - (s_stdout.find("figbgcolor='yellow'") > -1) - shutil.rmtree(s_opath) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + shutil.rmtree(f'{s_path_2d}/cell_oxygen_z0.0/') class TestPyCliCellVtk(object): @@ -1580,157 +1251,137 @@ class TestPyCliCellVtk(object): # + attribute (cell_type oxygen, _empty_) ok def test_pcdl_make_cell_vtk_timeseries_default(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_customtype_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_microenv(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_physiboss(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_settingxmlfalse_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_settingxmlnone_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - #print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timeseries_attribute_many(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - ls_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace("['","").replace("']\n","").split("', '") - print('ls_opathfile:', ls_opathfile) - assert (len(ls_opathfile) == 25) and \ - (ls_opathfile[0].endswith('output_2d/output00000000_cell.vtp')) and \ - (ls_opathfile[-1].endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(ls_opathfile[12])) - for s_opathfile in ls_opathfile: - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + for i_step in range(25): + os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') def test_pcdl_make_cell_vtk_timestep_default(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_customtype_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_microenv(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_physiboss(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_settingxmlfalse_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_settingxmlnone_attribute_one(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') def test_pcdl_make_cell_vtk_timestep_attribute_many(self): - s_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (s_opathfile.endswith('output_2d/output00000024_cell.vtp')) and \ - (os.path.exists(s_opathfile)) - os.remove(s_opathfile) + o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/output00000024_cell.vtp') ####################################### @@ -1771,46 +1422,18 @@ class TestPyCliPlotTimeSeries(object): # + figbgcolor (none, _yellow_) def test_pcdl_plot_timeseries_default(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_timeseries', s_path_2d, '-v', 'false' ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/timeseries_cell_total_count.jpeg')) and \ - (s_stdout.find("custom_data_type=[]") > -1) and \ - (s_stdout.find("microenv='true'") > -1) and \ - (s_stdout.find("physiboss='true'") > -1) and \ - (s_stdout.find("settingxml='PhysiCell_settings.xml'") > -1) and \ - (s_stdout.find("focus_cat='none'") > -1) and \ - (s_stdout.find("focus_num='none'") > -1) and \ - (s_stdout.find("aggregate_num='mean'") > -1) and \ - (s_stdout.find("frame='cell'") > -1) and \ - (s_stdout.find("z_slice='none'") > -1) and \ - (s_stdout.find("logy='false'") > -1) and \ - (s_stdout.find("ylim=['none']") > -1) and \ - (s_stdout.find("secondary_y=['false']") > -1) and \ - (s_stdout.find("subplots='false'") > -1) and \ - (s_stdout.find("sharex='false'") > -1) and \ - (s_stdout.find("sharey='false'") > -1) and \ - (s_stdout.find("linestyle='-'") > -1) and \ - (s_stdout.find("linewidth='none'") > -1) and \ - (s_stdout.find("cmap='none'") > -1) and \ - (s_stdout.find("color=['none']") > -1) and \ - (s_stdout.find("grid='true'") > -1) and \ - (s_stdout.find("legend='true'") > -1) and \ - (s_stdout.find("yunit='none'") > -1) and \ - (s_stdout.find("title='none'") > -1) and \ - (s_stdout.find("figsizepx=['640', '480']") > -1) and \ - (s_stdout.find("ext='jpeg'") > -1) and \ - (s_stdout.find("figbgcolor='none'") > -1) - os.remove(s_opathfile) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_cell_total_count.jpeg') def test_pcdl_plot_timeseries_set(self): - s_result = subprocess.run([ + o_result = subprocess.run([ 'pcdl_plot_timeseries', s_path_2d, 'None', 'oxygen', 'entropy', '-v', 'false', '--custom_data_type', 'sample:bool', '--microenv', 'True', @@ -1836,47 +1459,18 @@ def test_pcdl_plot_timeseries_set(self): '--ext', 'tiff', '--figbgcolor', 'cyan', ], check=False, capture_output=True) - #print(f'\ns_result: {s_result}\n') - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_stdout = s_result.stdout.decode('UTF8').replace('\r','') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').replace('\n','') - assert (os.path.exists(s_opathfile)) and \ - (s_opathfile.endswith('pcdl/output_2d/timeseries_conc_total_oxygen_entropy.tiff')) and \ - (s_stdout.find("custom_data_type=['sample:bool']") > -1) and \ - (s_stdout.find("microenv='True'") > -1) and \ - (s_stdout.find("physiboss='false'") > -1) and \ - (s_stdout.find("settingxml='false'") > -1) and \ - (s_stdout.find("focus_cat='None'") > -1) and \ - (s_stdout.find("focus_num='oxygen'") > -1) and \ - (s_stdout.find("aggregate_num='entropy'") > -1) and \ - (s_stdout.find("frame='conc'") > -1) and \ - (s_stdout.find("z_slice='1.1'") > -1) and \ - (s_stdout.find("logy='true'") > -1) and \ - (s_stdout.find("ylim=['6.0', '7.0']") > -1) and \ - (s_stdout.find("secondary_y=['true', 'abc', 'def']") > -1) and \ - (s_stdout.find("subplots='true'") > -1) and \ - (s_stdout.find("sharex='true'") > -1) and \ - (s_stdout.find("sharey='true'") > -1) and \ - (s_stdout.find("linestyle=':'") > -1) and \ - (s_stdout.find("linewidth='9'") > -1) and \ - (s_stdout.find("cmap='magma'") > -1) and \ - (s_stdout.find("color=['maroon', 'orange', 'yellow']") > -1) and \ - (s_stdout.find("grid='false'") > -1) and \ - (s_stdout.find("legend='reverse'") > -1) and \ - (s_stdout.find("yunit='myunit'") > -1) and \ - (s_stdout.find("title='my title'") > -1) and \ - (s_stdout.find("figsizepx=['641', '481']") > -1) and \ - (s_stdout.find("ext='tiff'") > -1) and \ - (s_stdout.find("figbgcolor='cyan'") > -1) - os.remove(s_opathfile) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + os.remove(f'{s_path_2d}/timeseries_conc_total_oxygen_entropy.tiff') ########################### # making movies test code # ########################### -# bue 20250107: broken by Ignoring class TestPyCliMakeGif(object): ''' tests for one pcdl.pyCli function. ''' @@ -1885,30 +1479,31 @@ class TestPyCliMakeGif(object): # + interface (default, 'tiff') def test_pcdl_make_gif_timeseries_default(self): - s_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) - s_path = s_path.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] - s_result = subprocess.run(['pcdl_make_gif', s_path], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - assert (s_opathfile.endswith('output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_jpeg.gif')) and \ - (os.path.exists(s_opathfile)) + o_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) + print(f'o_path: {o_path}\n') + s_path = f'{s_path_2d}/cell_cell_type_z0.0/' + o_result = subprocess.run(['pcdl_make_gif', s_path], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 shutil.rmtree(s_path) def test_pcdl_make_gif_timeseries_interface(self): - s_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) - s_path = s_path.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] - s_result = subprocess.run(['pcdl_make_gif', s_path, 'tiff'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - assert (s_opathfile.endswith('output_2d/conc_oxygen_z0.0/conc_oxygen_z0.0_tiff.gif')) and \ - (os.path.exists(s_opathfile)) + o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') + s_path = f'{s_path_2d}/conc_oxygen_z0.0/' + o_result = subprocess.run(['pcdl_make_gif', s_path, 'tiff'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 shutil.rmtree(s_path) - # bue 20250107: broken by Ignoring -class TestPyCliMakeMove(object): +class TestPyCliMakeMovie(object): ''' tests for one pcdl.pyCli function. ''' # time series @@ -1917,34 +1512,37 @@ class TestPyCliMakeMove(object): # + farme (default, 'tiff') def test_pcdl_make_movie_timeseries_default(self): - s_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) - s_path = s_path.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] - s_result = subprocess.run(['pcdl_make_movie', s_path], check=False, capture_output=True) - print(f'\ns_result.stdout: {s_result.stdout}\n') - print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - assert (s_opathfile.endswith('output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_jpeg12.mp4')) and \ - (os.path.exists(s_opathfile)) + o_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) + print(f'o_path: {o_path}\n') + s_path = f'{s_path_2d}/cell_cell_type_z0.0/' + o_result = subprocess.run(['pcdl_make_movie', s_path], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 shutil.rmtree(s_path) def test_pcdl_make_movie_timeseries_interface(self): - s_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) - s_path = s_path.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] - s_result = subprocess.run(['pcdl_make_movie', s_path, 'tiff'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - assert (s_opathfile.endswith('output_2d/conc_oxygen_z0.0/conc_oxygen_z0.0_tiff12.mp4')) and \ - (os.path.exists(s_opathfile)) + o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') + s_path = f'{s_path_2d}/conc_oxygen_z0.0/' + o_result = subprocess.run(['pcdl_make_movie', s_path, 'tiff'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 shutil.rmtree(s_path) def test_pcdl_make_movie_timeseries_farme(self): - s_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'jpeg'], check=False, capture_output=True) - s_path = s_path.stderr.decode('UTF8').replace('\r','').replace('\n','').split('Ignoring fixed x limits to fulfill fixed data aspect with adjustable data limits.')[-1] - s_result = subprocess.run(['pcdl_make_movie', s_path, '--framerate', '9'], check=False, capture_output=True) - #print(f'\ns_result.stdout: {s_result.stdout}\n') - #print(f'\ns_result.stderr: {s_result.stderr}\n') - s_opathfile = s_result.stderr.decode('UTF8').replace('\r','').split('\n')[-2] - assert (s_opathfile.endswith('output_2d/conc_oxygen_z0.0/conc_oxygen_z0.0_jpeg9.mp4')) and \ - (os.path.exists(s_opathfile)) + o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'jpeg'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') + s_path = f'{s_path_2d}/conc_oxygen_z0.0/' + o_result = subprocess.run(['pcdl_make_movie', s_path, '--framerate', '9'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 shutil.rmtree(s_path) From d2c458cb6269aac4a9332715c7eee3c9299cd067 Mon Sep 17 00:00:00 2001 From: bue Date: Tue, 22 Jul 2025 14:18:51 -0400 Subject: [PATCH 37/41] @ physicelldataloader : next release v3.3.8. --- README.md | 78 +++++++++++++++++++++++++++++++++++++++---------- pcdl/VERSION.py | 2 +- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e83450d..403189e 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,19 @@ into [python3](https://en.wikipedia.org/wiki/Python_(programming_language)). pcdl was forked from the original [PhysiCell-Tools](https://github.com/PhysiCell-Tools) [python-loader](https://github.com/PhysiCell-Tools/python-loader) implementation. -The pcdl python3 library maintains three branches: +The pcdl python3 library maintains four branches: + **Branch version 1** is the original PhysiCell-Tools/python-loader code. + **Branch version 2** will be strictly compatible with the original PhysiCell-Tools/python-loader code, although pip installable. + **Branch version 3** might break with old habits, although tries to be as much downward compatible as possible. The aim of the v3 branch is to get a very lean and agile python3 physicell output interface for the ones coming from the python3 world. ++ Finally, **Branch version 4** reimplemented the backend in a more python3, less C++ like manner. ## Header: -+ Language: python [>= 3.9](https://devguide.python.org/versions/) -+ Library dependencies: anndata, matplotlib, numpy, pandas, (requests), scipy, vtk ++ Language: python [>= 3.10](https://devguide.python.org/versions/) ++ Library dependencies: anndata, bioio, matplotlib, numpy, pandas, (requests), scipy, vtk + Date of origin original PhysiCell-Tools python-loader: 2019-09-02 + Date of origin pcdl fork: 2022-08-30 + Doi: https://doi.org/10.5281/ZENODO.8176399 @@ -28,12 +29,12 @@ The pcdl python3 library maintains three branches: + Source code: [https://github.com/elmbeech/physicelldataloader](https://github.com/elmbeech/physicelldataloader) -## HowTo Guide: +## ✨ HowTo Guide: + [installation and troubleshooting](https://github.com/elmbeech/physicelldataloader/tree/master/man/HOWTO.md) -## Tutorial: +## ✨ Tutorial: Basics Tutorials: @@ -49,7 +50,7 @@ Extras tutorials python3 language: + [pcdl and python3 and graphs](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_graph.md) + [pcdl and python3 and matplotlib](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_matplotlib.md) + [pcdl and python3 and vtk](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_vtk.md) -+ [pcdl and python3 and tiff, png, and jpeg](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_ometiff.md) ++ [pcdl and python3 and ome.tiff, tiff, png, and jpeg](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_ometiff.md) + [pcdl and python3 and napari](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_napari.md) Extras tutorials for other languages than python3: @@ -62,16 +63,16 @@ Extras tutorials for GUI software: + [pcdl and paraview](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_paraview.md) + [pcdl and blender](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_blender.md) - ++ [pcdl and napari](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_python3_napari.md) ++ [pcdl and fiji imagej, icy, qupath](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_fijiimagej.md) ++ [pcdl and neuroglancer](https://github.com/elmbeech/physicelldataloader/tree/master/man/TUTORIAL_neuroglancer.md) Slides: + [presentations given](https://github.com/elmbeech/physicelldataloader/tree/master/man/lecture) -## Reference Manual: +## ✨ Reference Manual: + [API application interface](https://github.com/elmbeech/physicelldataloader/tree/master/man/REFERENCE.md) @@ -93,11 +94,14 @@ Within the pcdl library, we tried to stick to the documentation policy laid out + fork pcdl co-programmer: Furkan Kurtoglu, Jennifer Eng, Heber Rocha + fork pcdl continuous testing and feedbacks: Aneequa Sundus, John Metzcar + student prj on pcdl: - Benjamin Jacobs (make\_gml), + Benjamin Jacobs (make\_graph\_gml), + Jason Lu (render\_neuroglancer), Katie Pletz (beta testing), + Leena Sohail (beta testing), Marshal Gress (plot\_scatter), Nick Oldfather (unit test model), - Thierry-Pascal Fleurant (plot\_timeseries) + Thierry-Pascal Fleurant (plot\_timeseries), + Viviana Kwong (render\_neuroglancer) Developers, please make pull requests to the https://github.com/elmbeech/physicelldataloader/tree/development branch. Thanks! @@ -118,11 +122,53 @@ Developers, please make pull requests to the https://github.com/elmbeech/physice ## Road Map: ++ evt generate lineage tree graph output files. + ## Release Notes: -+ version 3.3.5 (2025-03-xx): elmbeech/physicelldataloader - + remove pyMCDS and pyMCDSts **make_ome_tiff** and pyCLI **pcdl_make_ome_tiff** removed to make pyMCS.py stand alone again. - -+ version 3.3.4 (2025-03-07): elmbeech/physicelldataloader ++ version 4.0.3 (2025-07-20): elmbeech/physicelldataloader + + timestep and timeseries **plot_contour**, **plot_scatter**, and **plot_timeseries** handle now **kwargs** arguments. + + minor bugfixes. + ++ version 4.0.2 (2025-06-29): elmbeech/physicelldataloader + + minor bugfixes. + ++ version 4.0.1 (2025-06-24): elmbeech/physicelldataloader + + man updated. + + minor bugfixes. + ++ version 4.0.0 (2025-05-13): elmbeech/physicelldataloader + + v4 was forked from v3.3.4! + + **mcds.data** struct was rewritten in more python less c++ way. + + pyMCDS.py and part of pyAnnData.py was fused to **timestep.py**. + + pyMCDSts.py and part of pyAnnData.py was fused to **timeseries.py**. + + pyCLI.py was renames to **commandline.py**. + + data\_timeseries.py was renamed to **output_data.py**. + + TimeStep function **get_concentration** was deprecated because pandas already has this functionlity. + + TimeStep function **get_concentration_at** was deprecated because pandas already has this functionlity. + + TimeStep function **get_cell_df_at** was deprecated because pandas already has this functionlity. + + **make_conc_vtk** and **make_cell_vtk** on the fly visualization was removed because paraview is good enough. + + new TimeStep **get_cell_attribute_list** function, to retrieve a list of all tracked cell attribute labels. + + new **pcdl_get_cell_attribute_list** function, to retrieve a list of all tracked cell attribute labels. + + new **render_neuroglancer** function, to render ome tiff image into neuroglancer. + + new **pcdl_render_neuroglancer** function, to render ome tiff images into neuroglancer. + ++ version 3.3.8 (2025-07-23): elmbeech/physicelldataloader + + command line commands return error code 0 if the command runs successfully. + ++ version 3.3.7 (2025-06-01): elmbeech/physicelldataloader + + compatible with current (non end-of-life cycle) python versions. + + minor bugfixes. + ++ version 3.3.6 (2025-05-13): elmbeech/physicelldataloader + + compatible with numpy >= 2.0.0 and current (non end-of-life cycle) python versions. + ++ version 3.3.5 (2025-05-13): elmbeech/physicelldataloader + + compatible with numpy < 2.0.0 and current (non end-of-life cycle) python versions. + + remove pyMCDS and pyMCDSts **make_ome_tiff** and pyCLI **pcdl_make_ome_tiff** to make pyMCS.py stand alone again. + + new TimeStep **get_cell_attribute_list** function, to retrieve a list of all tracked cell attribute labels. + + new **pcdl_get_cell_attribute_list** function, to retrieve a list of all tracked cell attribute labels. + ++ version 3.3.4 (2025-03-07): elmbeech/physicelldataloader + replace the **aicsimageio** library dependency with its successor **bioio**. special thanks to Joel Eliason! + **make_ome_tiff** can now handel generated file names with > 255 characters. special thank to Genevieve Stein-O'Brien and DanielBergman! + **get_mesh_spacing** handels now an edge case correctly that would have resulted in a division by zero. special thanks to Randy Heiland! diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index 830a399..a8cd31d 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '3.3.7' +__version__ = '3.3.8' From 9fa559e6a61ffa6eb66bcc047dad4256b05e2b1c Mon Sep 17 00:00:00 2001 From: bue Date: Wed, 23 Jul 2025 11:54:46 -0400 Subject: [PATCH 38/41] @ test : minor update commandline command unit tests. --- README.md | 7 +- test/test_cli_2d.py | 230 ++++++++++++++++++++++---------------------- 2 files changed, 121 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 403189e..1277b27 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Within the pcdl library, we tried to stick to the documentation policy laid out + original PhysiCell-Tools python-loader implementation: Patrick Wall, Randy Heiland, Paul Macklin + fork pcdl implementation: Elmar Bucher -+ fork pcdl co-programmer: Furkan Kurtoglu, Jennifer Eng, Heber Rocha ++ fork pcdl co-programmer: Furkan Kurtoglu, Heber Rocha, Jennifer Eng + fork pcdl continuous testing and feedbacks: Aneequa Sundus, John Metzcar + student prj on pcdl: Benjamin Jacobs (make\_graph\_gml), @@ -125,6 +125,9 @@ Developers, please make pull requests to the https://github.com/elmbeech/physice + evt generate lineage tree graph output files. ## Release Notes: ++ version 4.0.4 (2025-07-23): elmbeech/physicelldataloader + + command line commands now return **error code 0** if the command runs successfully. + + version 4.0.3 (2025-07-20): elmbeech/physicelldataloader + timestep and timeseries **plot_contour**, **plot_scatter**, and **plot_timeseries** handle now **kwargs** arguments. + minor bugfixes. @@ -153,7 +156,7 @@ Developers, please make pull requests to the https://github.com/elmbeech/physice + new **pcdl_render_neuroglancer** function, to render ome tiff images into neuroglancer. + version 3.3.8 (2025-07-23): elmbeech/physicelldataloader - + command line commands return error code 0 if the command runs successfully. + + command line commands now return **error code 0** if the command runs successfully. + version 3.3.7 (2025-06-01): elmbeech/physicelldataloader + compatible with current (non end-of-life cycle) python versions. diff --git a/test/test_cli_2d.py b/test/test_cli_2d.py index 0177dd3..e8df23a 100644 --- a/test/test_cli_2d.py +++ b/test/test_cli_2d.py @@ -85,8 +85,8 @@ def test_pcdl_get_unit_dict_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_unit.csv') + assert o_result.returncode == 0 def test_pcdl_get_unit_dict_timestep(self): o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d], check=False, capture_output=True) @@ -94,8 +94,8 @@ def test_pcdl_get_unit_dict_timestep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_unit.csv') + assert o_result.returncode == 0 def test_pcdl_get_unit_dict_timestep_microenv(self): o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -103,8 +103,8 @@ def test_pcdl_get_unit_dict_timestep_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_unit.csv') + assert o_result.returncode == 0 def test_pcdl_get_unit_dict_timestep_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -112,8 +112,8 @@ def test_pcdl_get_unit_dict_timestep_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_unit.csv') + assert o_result.returncode == 0 def test_pcdl_get_unit_dict_timestep_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_unit_dict', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -121,8 +121,8 @@ def test_pcdl_get_unit_dict_timestep_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_unit.csv') + assert o_result.returncode == 0 ############################## @@ -170,8 +170,8 @@ def test_pcdl_get_conc_attribute_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_conc_attribute_timeseries_value(self): o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '2'], check=False, capture_output=True) @@ -179,8 +179,8 @@ def test_pcdl_get_conc_attribute_timeseries_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_conc_attribute_timeseries_drop(self): o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--drop', 'conc_type', 'oxygen'], check=False, capture_output=True) @@ -188,8 +188,8 @@ def test_pcdl_get_conc_attribute_timeseries_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_conc_attribute_timeseries_keep(self): o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--keep', 'conc_type', 'oxygen'], check=False, capture_output=True) @@ -197,8 +197,8 @@ def test_pcdl_get_conc_attribute_timeseries_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_conc_attribute_timeseries_allvalues(self): o_result = subprocess.run(['pcdl_get_conc_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) @@ -206,8 +206,8 @@ def test_pcdl_get_conc_attribute_timeseries_allvalues(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_attribute_all.json') + assert o_result.returncode == 0 class TestPyCliConcDf(object): @@ -228,8 +228,8 @@ def test_pcdl_get_conc_df_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timeseries_collapsed(self): o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) @@ -237,9 +237,9 @@ def test_pcdl_get_conc_df_timeseries_collapsed(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timeseries_value(self): o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '2'], check=False, capture_output=True) @@ -247,8 +247,8 @@ def test_pcdl_get_conc_df_timeseries_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timeseries_drop(self): o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--drop', 'oxygen'], check=False, capture_output=True) @@ -256,8 +256,8 @@ def test_pcdl_get_conc_df_timeseries_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timeseries_keep(self): o_result = subprocess.run(['pcdl_get_conc_df', s_path_2d, '--keep', 'oxygen'], check=False, capture_output=True) @@ -265,8 +265,8 @@ def test_pcdl_get_conc_df_timeseries_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timestep(self): o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d], check=False, capture_output=True) @@ -274,8 +274,8 @@ def test_pcdl_get_conc_df_timestep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timestep_value(self): o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '2'], check=False, capture_output=True) @@ -283,8 +283,8 @@ def test_pcdl_get_conc_df_timestep_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timestep_drop(self): o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--drop', 'oxygen'], check=False, capture_output=True) @@ -292,8 +292,8 @@ def test_pcdl_get_conc_df_timestep_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_conc.csv') + assert o_result.returncode == 0 def test_pcdl_get_conc_df_timestep_keep(self): o_result = subprocess.run(['pcdl_get_conc_df', s_pathfile_2d, '--keep', 'oxygen'], check=False, capture_output=True) @@ -301,8 +301,8 @@ def test_pcdl_get_conc_df_timestep_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_conc.csv') + assert o_result.returncode == 0 class TestPyCliPlotContour(object): @@ -334,8 +334,8 @@ def test_pcdl_plot_contour_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(f'{s_path_2d}/conc_oxygen_z0.0/') + assert o_result.returncode == 0 def test_pcdl_plot_contour_set(self): o_result = subprocess.run([ @@ -358,8 +358,8 @@ def test_pcdl_plot_contour_set(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(f'{s_path_2d}/conc_oxygen_z0.0/') + assert o_result.returncode == 0 class TestPyCliConcVtk(object): @@ -375,9 +375,9 @@ def test_pcdl_make_conc_vtk_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_conc.vtr') + assert o_result.returncode == 0 def test_pcdl_make_conc_vtk_timestep_default(self): o_result = subprocess.run(['pcdl_make_conc_vtk', s_pathfile_2d], check=False, capture_output=True) @@ -385,8 +385,8 @@ def test_pcdl_make_conc_vtk_timestep_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_conc.vtr') + assert o_result.returncode == 0 ################################ @@ -438,8 +438,8 @@ def test_pcdl_get_cell_attribute_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_customtype(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) @@ -447,8 +447,8 @@ def test_pcdl_get_cell_attribute_timeseries_customtype(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_microenv(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -456,8 +456,8 @@ def test_pcdl_get_cell_attribute_timeseries_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_physiboss(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -465,8 +465,8 @@ def test_pcdl_get_cell_attribute_timeseries_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -474,8 +474,8 @@ def test_pcdl_get_cell_attribute_timeseries_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -483,8 +483,8 @@ def test_pcdl_get_cell_attribute_timeseries_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_value(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '2'], check=False, capture_output=True) @@ -492,8 +492,8 @@ def test_pcdl_get_cell_attribute_timeseries_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_drop(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -501,8 +501,8 @@ def test_pcdl_get_cell_attribute_timeseries_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_keep(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -510,8 +510,8 @@ def test_pcdl_get_cell_attribute_timeseries_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_minmax.json') + assert o_result.returncode == 0 def test_pcdl_get_cell_attribute_timeseries_allvalues(self): o_result = subprocess.run(['pcdl_get_cell_attribute', s_path_2d, '--allvalues', 'true'], check=False, capture_output=True) @@ -519,8 +519,8 @@ def test_pcdl_get_cell_attribute_timeseries_allvalues(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_attribute_all.json') + assert o_result.returncode == 0 class TestPyCliCellAttributeList(object): @@ -597,8 +597,8 @@ def test_pcdl_get_cell_df_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_collapsed(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) @@ -606,9 +606,9 @@ def test_pcdl_get_cell_df_timeseries_collapsed(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_microenv(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -616,8 +616,8 @@ def test_pcdl_get_cell_df_timeseries_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_physiboss(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -625,8 +625,8 @@ def test_pcdl_get_cell_df_timeseries_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -634,8 +634,8 @@ def test_pcdl_get_cell_df_timeseries_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -643,8 +643,8 @@ def test_pcdl_get_cell_df_timeseries_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_value(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '2'], check=False, capture_output=True) @@ -652,8 +652,8 @@ def test_pcdl_get_cell_df_timeseries_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_drop(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -661,8 +661,8 @@ def test_pcdl_get_cell_df_timeseries_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timeseries_keep(self): o_result = subprocess.run(['pcdl_get_cell_df', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -670,15 +670,17 @@ def test_pcdl_get_cell_df_timeseries_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') + os.remove(f'{s_path_2d}/timeseries_cell.csv') assert o_result.returncode == 0 + def test_pcdl_get_cell_df_timestep(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d], check=False, capture_output=True) print(f'o_result: {o_result}\n') print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_microenv(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -686,8 +688,8 @@ def test_pcdl_get_cell_df_timestep_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_physiboss(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -695,8 +697,8 @@ def test_pcdl_get_cell_df_timestep_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -704,8 +706,8 @@ def test_pcdl_get_cell_df_timestep_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -713,8 +715,8 @@ def test_pcdl_get_cell_df_timestep_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_value(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '2'], check=False, capture_output=True) @@ -722,8 +724,8 @@ def test_pcdl_get_cell_df_timestep_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_drop(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -731,8 +733,8 @@ def test_pcdl_get_cell_df_timestep_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 def test_pcdl_get_cell_df_timestep_keep(self): o_result = subprocess.run(['pcdl_get_cell_df', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -740,8 +742,8 @@ def test_pcdl_get_cell_df_timestep_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.csv') + assert o_result.returncode == 0 class TestPyCliAnndata(object): @@ -770,8 +772,8 @@ def test_pcdl_get_anndata_timeseries(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_collapsed(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) @@ -779,9 +781,9 @@ def test_pcdl_get_anndata_timeseries_collapsed(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_customtype(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--custom_data_type', 'sample:bool'], check=False, capture_output=True) @@ -789,8 +791,8 @@ def test_pcdl_get_anndata_timeseries_customtype(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_microenv(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -798,8 +800,8 @@ def test_pcdl_get_anndata_timeseries_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_graph(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--graph', 'false'], check=False, capture_output=True) @@ -807,8 +809,8 @@ def test_pcdl_get_anndata_timeseries_graph(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_physiboss(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -816,8 +818,8 @@ def test_pcdl_get_anndata_timeseries_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -825,8 +827,8 @@ def test_pcdl_get_anndata_timeseries_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -834,8 +836,8 @@ def test_pcdl_get_anndata_timeseries_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_value(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '2'], check=False, capture_output=True) @@ -843,8 +845,8 @@ def test_pcdl_get_anndata_timeseries_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_drop(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -852,8 +854,8 @@ def test_pcdl_get_anndata_timeseries_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_keep(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -861,8 +863,8 @@ def test_pcdl_get_anndata_timeseries_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timeseries_scale(self): o_result = subprocess.run(['pcdl_get_anndata', s_path_2d, '--scale', 'std'], check=False, capture_output=True) @@ -870,8 +872,8 @@ def test_pcdl_get_anndata_timeseries_scale(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_std.h5ad') + assert o_result.returncode == 0 # timestep def test_pcdl_get_anndata_timestep(self): @@ -880,8 +882,8 @@ def test_pcdl_get_anndata_timestep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_microenv(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -889,8 +891,8 @@ def test_pcdl_get_anndata_timestep_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_graph(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--graph', 'false'], check=False, capture_output=True) @@ -898,8 +900,8 @@ def test_pcdl_get_anndata_timestep_graph(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_physiboss(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -907,8 +909,8 @@ def test_pcdl_get_anndata_timestep_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_settingxmlfalse(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) @@ -916,8 +918,8 @@ def test_pcdl_get_anndata_timestep_settingxmlfalse(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) @@ -925,8 +927,8 @@ def test_pcdl_get_anndata_timestep_settingxmlnone(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_value(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '2'], check=False, capture_output=True) @@ -934,8 +936,8 @@ def test_pcdl_get_anndata_timestep_value(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_drop(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--drop', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -943,8 +945,8 @@ def test_pcdl_get_anndata_timestep_drop(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_keep(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--keep', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -952,8 +954,8 @@ def test_pcdl_get_anndata_timestep_keep(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') + assert o_result.returncode == 0 def test_pcdl_get_anndata_timestep_scale(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--scale', 'std'], check=False, capture_output=True) @@ -961,8 +963,8 @@ def test_pcdl_get_anndata_timestep_scale(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell_std.h5ad') + assert o_result.returncode == 0 class TestPyCliGraphGml(object): @@ -985,9 +987,9 @@ def test_pcdl_make_graph_gml_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_customtype_nodeattribute_one(self): @@ -996,9 +998,9 @@ def test_pcdl_make_graph_gml_timeseries_customtype_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_microenv(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) @@ -1006,9 +1008,9 @@ def test_pcdl_make_graph_gml_timeseries_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_physiboss(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) @@ -1016,9 +1018,9 @@ def test_pcdl_make_graph_gml_timeseries_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_settingxmlfalse_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) @@ -1026,9 +1028,9 @@ def test_pcdl_make_graph_gml_timeseries_settingxmlfalse_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_settingxmlnone_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) @@ -1036,9 +1038,9 @@ def test_pcdl_make_graph_gml_timeseries_settingxmlnone_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_graph_type(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'attached'], check=False, capture_output=True) @@ -1046,9 +1048,9 @@ def test_pcdl_make_graph_gml_timeseries_graph_type(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_attached.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_edge_attribute(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) @@ -1056,9 +1058,9 @@ def test_pcdl_make_graph_gml_timeseries_edge_attribute(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) @@ -1066,9 +1068,9 @@ def test_pcdl_make_graph_gml_timeseries_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timeseries_nodeattribute_many(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -1076,9 +1078,9 @@ def test_pcdl_make_graph_gml_timeseries_nodeattribute_many(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_default(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor'], check=False, capture_output=True) @@ -1086,8 +1088,8 @@ def test_pcdl_make_graph_gml_timestep_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_customtype_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) @@ -1095,8 +1097,8 @@ def test_pcdl_make_graph_gml_timestep_customtype_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_microenv(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--microenv', 'false'], check=False, capture_output=True) @@ -1104,8 +1106,8 @@ def test_pcdl_make_graph_gml_timestep_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_physiboss(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--physiboss', 'false'], check=False, capture_output=True) @@ -1113,8 +1115,8 @@ def test_pcdl_make_graph_gml_timestep_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_settingxmlfalse_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'false', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) @@ -1122,8 +1124,8 @@ def test_pcdl_make_graph_gml_timestep_settingxmlfalse_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_settingxmlnone_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--settingxml', 'none', '--node_attribute', 'default_fusion_rates'], check=False, capture_output=True) @@ -1131,8 +1133,8 @@ def test_pcdl_make_graph_gml_timestep_settingxmlnone_nodeattribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_graph_type(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'attached'], check=False, capture_output=True) @@ -1140,8 +1142,8 @@ def test_pcdl_make_graph_gml_timestep_graph_type(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_attached.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_edge_attribute(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--edge_attribute', 'false'], check=False, capture_output=True) @@ -1149,8 +1151,8 @@ def test_pcdl_make_graph_gml_timestep_edge_attribute(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_node_attribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type'], check=False, capture_output=True) @@ -1158,8 +1160,8 @@ def test_pcdl_make_graph_gml_timestep_node_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 def test_pcdl_make_graph_gml_timestep_node_attribute_many(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_pathfile_2d, 'neighbor', '--node_attribute', 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -1167,8 +1169,8 @@ def test_pcdl_make_graph_gml_timestep_node_attribute_many(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_neighbor.gml') + assert o_result.returncode == 0 class TestPyCliPlotScatter(object): @@ -1205,8 +1207,8 @@ def test_pcdl_plot_scatter_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(f'{s_path_2d}/cell_cell_type_z0.0/') + assert o_result.returncode == 0 def test_pcdl_plot_scatter_set(self): o_result = subprocess.run([ @@ -1234,8 +1236,8 @@ def test_pcdl_plot_scatter_set(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(f'{s_path_2d}/cell_oxygen_z0.0/') + assert o_result.returncode == 0 class TestPyCliCellVtk(object): @@ -1256,9 +1258,9 @@ def test_pcdl_make_cell_vtk_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_customtype_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) @@ -1266,9 +1268,9 @@ def test_pcdl_make_cell_vtk_timeseries_customtype_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_microenv(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -1276,9 +1278,9 @@ def test_pcdl_make_cell_vtk_timeseries_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_physiboss(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -1286,9 +1288,9 @@ def test_pcdl_make_cell_vtk_timeseries_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_settingxmlfalse_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) @@ -1296,9 +1298,9 @@ def test_pcdl_make_cell_vtk_timeseries_settingxmlfalse_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_settingxmlnone_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) @@ -1306,9 +1308,9 @@ def test_pcdl_make_cell_vtk_timeseries_settingxmlnone_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timeseries_attribute_many(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_path_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -1316,9 +1318,9 @@ def test_pcdl_make_cell_vtk_timeseries_attribute_many(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 for i_step in range(25): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_default(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d], check=False, capture_output=True) @@ -1326,8 +1328,8 @@ def test_pcdl_make_cell_vtk_timestep_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_customtype_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'sample', '--custom_data_type', 'sample:bool'], check=False, capture_output=True) @@ -1335,8 +1337,8 @@ def test_pcdl_make_cell_vtk_timestep_customtype_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_microenv(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) @@ -1344,8 +1346,8 @@ def test_pcdl_make_cell_vtk_timestep_microenv(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_physiboss(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) @@ -1353,8 +1355,8 @@ def test_pcdl_make_cell_vtk_timestep_physiboss(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_settingxmlfalse_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'false'], check=False, capture_output=True) @@ -1362,8 +1364,8 @@ def test_pcdl_make_cell_vtk_timestep_settingxmlfalse_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_settingxmlnone_attribute_one(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'default_fusion_rates', '--settingxml', 'none'], check=False, capture_output=True) @@ -1371,8 +1373,8 @@ def test_pcdl_make_cell_vtk_timestep_settingxmlnone_attribute_one(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 def test_pcdl_make_cell_vtk_timestep_attribute_many(self): o_result = subprocess.run(['pcdl_make_cell_vtk', s_pathfile_2d, 'cell_type', 'oxygen'], check=False, capture_output=True) @@ -1380,8 +1382,8 @@ def test_pcdl_make_cell_vtk_timestep_attribute_many(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/output00000024_cell.vtp') + assert o_result.returncode == 0 ####################################### @@ -1429,8 +1431,8 @@ def test_pcdl_plot_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_cell_total_count.jpeg') + assert o_result.returncode == 0 def test_pcdl_plot_timeseries_set(self): o_result = subprocess.run([ @@ -1463,8 +1465,8 @@ def test_pcdl_plot_timeseries_set(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 os.remove(f'{s_path_2d}/timeseries_conc_total_oxygen_entropy.tiff') + assert o_result.returncode == 0 ########################### @@ -1487,8 +1489,8 @@ def test_pcdl_make_gif_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(s_path) + assert o_result.returncode == 0 def test_pcdl_make_gif_timeseries_interface(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) @@ -1499,8 +1501,8 @@ def test_pcdl_make_gif_timeseries_interface(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(s_path) + assert o_result.returncode == 0 class TestPyCliMakeMovie(object): @@ -1520,8 +1522,8 @@ def test_pcdl_make_movie_timeseries_default(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(s_path) + assert o_result.returncode == 0 def test_pcdl_make_movie_timeseries_interface(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) @@ -1532,8 +1534,8 @@ def test_pcdl_make_movie_timeseries_interface(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(s_path) + assert o_result.returncode == 0 def test_pcdl_make_movie_timeseries_farme(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'jpeg'], check=False, capture_output=True) @@ -1544,5 +1546,5 @@ def test_pcdl_make_movie_timeseries_farme(self): print(f'o_result.returncode: {o_result.returncode}\n') print(f'o_result.stdout: {o_result.stdout}\n') print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 shutil.rmtree(s_path) + assert o_result.returncode == 0 From e69f30d9cefefa639701a8e60cffe39ab8ad5ead Mon Sep 17 00:00:00 2001 From: bue Date: Wed, 23 Jul 2025 14:49:21 -0400 Subject: [PATCH 39/41] @ update workflows : branch v3 specific. --- .github/workflows/apple.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index 18fefeb..9e19b7e 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on mac os x; the latest pytho on: push: - branches: ["v3"] # ["master", "utest", "v4"] + branches: ["v3"] # only! jobs: build-macosx: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fda3e2a..d8445b7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on linux os; all python3 vers on: push: - branches: ["v3"] # ["master", "utest", "v4"] + branches: ["v3"] # only! jobs: build-linux: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d7b8065..e355849 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,7 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on windows os; the latest pyt on: push: - branches: ["v3"] # ["master", "utest", "v4"] + branches: ["v3"] # only! jobs: build-windows: From 98c192e09918ed40bedbfb2bc6d87809e962ce6f Mon Sep 17 00:00:00 2001 From: bue Date: Wed, 23 Jul 2025 16:16:19 -0400 Subject: [PATCH 40/41] @ python-loader : introducing branch v3. --- .github/workflows/apple.yml | 6 +- .github/workflows/linux.yml | 6 +- .github/workflows/windows.yml | 6 +- man/HOWTO.md | 5 +- man/REFERENCE.md | 27 +- man/TUTORIAL_blender.md | 28 - man/TUTORIAL_commandline.md | 78 +- man/TUTORIAL_fijiimagej.md | 50 - man/TUTORIAL_introduction.md | 14 +- man/TUTORIAL_julia.md | 18 +- man/TUTORIAL_neuroglancer.md | 99 - man/TUTORIAL_paraview.md | 21 +- man/TUTORIAL_python3_json.md | 4 +- man/TUTORIAL_python3_matplotlib.md | 5 +- man/TUTORIAL_python3_napari.md | 54 - man/TUTORIAL_python3_ometiff.md | 44 +- man/TUTORIAL_python3_scverse.md | 32 +- man/TUTORIAL_python3_timeseries.md | 69 +- man/TUTORIAL_python3_timestep.md | 153 +- man/TUTORIAL_python3_vtk.md | 393 +--- man/TUTORIAL_r.md | 2 +- man/docstring/mcds.__init__.md | 31 +- man/docstring/mcds.get_cell_attribute_list.md | 21 - man/docstring/mcds.get_cell_df_at.md | 47 + man/docstring/mcds.get_concentration.md | 36 + man/docstring/mcds.get_concentration_at.md | 31 + man/docstring/mcds.get_spring_graph_dict.md | 20 - man/docstring/mcds.make_cell_vtk.md | 3 + man/docstring/mcds.make_conc_vtk.md | 2 + man/docstring/mcds.make_ome_tiff.md | 50 - man/docstring/mcds.plot_contour.md | 10 +- man/docstring/mcds.plot_scatter.md | 11 +- man/docstring/mcdsts.__init__.md | 19 +- man/docstring/mcdsts.get_cell_attribute.md | 2 +- man/docstring/mcdsts.get_cell_df.md | 2 +- man/docstring/mcdsts.get_conc_attribute.md | 2 +- man/docstring/mcdsts.get_conc_df.md | 2 +- man/docstring/mcdsts.get_mcds_list.md | 2 +- man/docstring/mcdsts.get_xmlfile_list.md | 2 +- man/docstring/mcdsts.make_cell_vtk.md | 3 + man/docstring/mcdsts.make_conc_vtk.md | 2 + man/docstring/mcdsts.make_graph_gml.md | 2 +- man/docstring/mcdsts.make_ome_tiff.md | 55 - man/docstring/mcdsts.plot_contour.md | 12 +- man/docstring/mcdsts.plot_scatter.md | 11 +- man/docstring/mcdsts.plot_timeseries.md | 6 +- man/docstring/mcdsts.read_mcds.md | 4 +- man/docstring/pcdl.render_neuroglancer.md | 33 - man/docstring/pcdl_get_anndata.md | 5 +- man/docstring/pcdl_get_cell_attribute.md | 3 +- man/docstring/pcdl_get_cell_attribute_list.md | 33 - man/docstring/pcdl_get_cell_df.md | 3 +- man/docstring/pcdl_get_unit_dict.md | 3 +- man/docstring/pcdl_make_cell_vtk.md | 3 +- man/docstring/pcdl_make_graph_gml.md | 3 +- man/docstring/pcdl_make_ome_tiff.md | 55 - man/docstring/pcdl_plot_contour.md | 8 +- man/docstring/pcdl_plot_scatter.md | 11 +- man/docstring/pcdl_plot_timeseries.md | 3 +- man/docstring/pcdl_render_neuroglancer.md | 23 - man/img/physicelldataloafder_github_qr.png | Bin 132163 -> 0 bytes man/scarab.py | 70 +- pcdl/VERSION.py | 2 +- pcdl/__init__.py | 7 +- pcdl/{output_data.py => data_timeseries.py} | 2 +- pcdl/imagine.py | 745 ------- pcdl/neuromancer.py | 206 -- pcdl/pdplt.py | 132 -- pcdl/pyAnnData.py | 616 ++++++ pcdl/{commandline.py => pyCLI.py} | 362 +--- pcdl/{timestep.py => pyMCDS.py} | 1861 ++++++++--------- pcdl/{timeseries.py => pyMCDSts.py} | 457 +--- pyproject.toml | 44 +- test/pcmodel/Makefile | 216 +- .../config/PhysiCell_settings-backup.xml | 0 test/pcmodel/custom_modules/custom.cpp | 248 +-- test/pcmodel/custom_modules/custom.h | 18 +- test/pcmodel/custom_modules/empty.txt | 0 test/pcmodel/main.cpp | 340 +-- test/test_anndata_2d.py | 181 ++ test/test_anndata_3d.py | 157 ++ ...{test_commandline_2d.py => test_cli_2d.py} | 365 +--- test/test_timeseries_2d.py | 342 +-- test/test_timeseries_3d.py | 274 +-- test/test_timestep_2d.py | 566 ++--- test/test_timestep_3d.py | 272 +-- 86 files changed, 3374 insertions(+), 5797 deletions(-) delete mode 100644 man/TUTORIAL_fijiimagej.md delete mode 100644 man/TUTORIAL_neuroglancer.md delete mode 100644 man/TUTORIAL_python3_napari.md delete mode 100644 man/docstring/mcds.get_cell_attribute_list.md create mode 100644 man/docstring/mcds.get_cell_df_at.md create mode 100644 man/docstring/mcds.get_concentration.md create mode 100644 man/docstring/mcds.get_concentration_at.md delete mode 100644 man/docstring/mcds.get_spring_graph_dict.md delete mode 100644 man/docstring/mcds.make_ome_tiff.md delete mode 100644 man/docstring/mcdsts.make_ome_tiff.md delete mode 100644 man/docstring/pcdl.render_neuroglancer.md delete mode 100644 man/docstring/pcdl_get_cell_attribute_list.md delete mode 100644 man/docstring/pcdl_make_ome_tiff.md delete mode 100644 man/docstring/pcdl_render_neuroglancer.md delete mode 100644 man/img/physicelldataloafder_github_qr.png rename pcdl/{output_data.py => data_timeseries.py} (97%) delete mode 100644 pcdl/imagine.py delete mode 100644 pcdl/neuromancer.py delete mode 100644 pcdl/pdplt.py create mode 100644 pcdl/pyAnnData.py rename pcdl/{commandline.py => pyCLI.py} (87%) rename pcdl/{timestep.py => pyMCDS.py} (68%) rename pcdl/{timeseries.py => pyMCDSts.py} (75%) create mode 100644 test/pcmodel/config/PhysiCell_settings-backup.xml create mode 100644 test/pcmodel/custom_modules/empty.txt create mode 100644 test/test_anndata_2d.py create mode 100644 test/test_anndata_3d.py rename test/{test_commandline_2d.py => test_cli_2d.py} (85%) diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index 8ca2f4e..9e19b7e 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on mac os x; the latest pytho on: push: - branches: ["utest", "master"] # ["v3", "v4"] - pull_request: - branches: ["development", "master"] + branches: ["v3"] # only! jobs: build-macosx: @@ -33,7 +31,7 @@ jobs: run: | brew install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install /Users/runner/work/physicelldataloader/physicelldataloader -v #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: lint with flake8 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1a01ad7..d8445b7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on linux os; all python3 vers on: push: - branches: ["utest", "master"] # ["v3","v4"] - pull_request: - branches: ["development", "master"] + branches: ["v3"] # only! jobs: build-linux: @@ -33,7 +31,7 @@ jobs: run: | sudo apt install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install /home/runner/work/physicelldataloader/physicelldataloader -v #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: lint with flake8 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 235e012..e355849 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,9 +7,7 @@ run-name: ${{ github.actor }}::pytest pcdl library on windows os; the latest pyt on: push: - branches: ["utest", "master"] # ["v3", "v4"] - pull_request: - branches: ["development", "master"] + branches: ["v3"] # only! jobs: build-windows: @@ -33,7 +31,7 @@ jobs: run: | choco install ffmpeg imagemagick python -m pip install --upgrade pip - python -m pip install flake8 pytest anndata bioio matplotlib numpy pandas requests scipy vtk + python -m pip install flake8 pytest anndata matplotlib numpy pandas requests scipy vtk python -m pip install D:\a\physicelldataloader\physicelldataloader -v #echo 'set PYTHONPATH=D:\a\physicelldataloader\physicelldataloader' >> $GITHUB_ENV #if [ -f requirements.txt ]; then pip install -r requirements.txt; fi diff --git a/man/HOWTO.md b/man/HOWTO.md index 1e72517..3cfe5d1 100644 --- a/man/HOWTO.md +++ b/man/HOWTO.md @@ -146,6 +146,5 @@ pyMCDS.py and the pyMCDS class is very lightweight. Besides the python3 core library, this code has only matplotlib, numpy, pandas, scipy, and vtk library dependencies.\ The pyMCDS class evolved into the pcdl.TimeStep class, which has additionally anndata dependency, which makes the library slightly heavier but much more powerful for downstream data analysis. Apart from that, pcdl offers the pcdl.TimeSeries class to handle the mcds snapshots from an entire PhysiCell run, and a set of functions that can be run straight from the command line, without even having to fire up a python3 shell. -Finally, branch version 4 broke with this ancient library structure because it is just out of time to run the code like this.\ -Stay assured, if you like pyMCDS.py, it is there to last. -We will keep on maintaining pyMCDS.py from branch version 3. +Future branch version 4 will abandon this ancient library structure to become more concise. +Don't fear. pyMCDS.py is there to last. We will keep on maintaining pyMCDS.py from branch version 3. diff --git a/man/REFERENCE.md b/man/REFERENCE.md index d583780..a4af6c7 100644 --- a/man/REFERENCE.md +++ b/man/REFERENCE.md @@ -34,7 +34,7 @@ Familiarize yourself well with their parameters! Basically, there are four types of functions: + set_ : set a python3 variable. + get_ : recall a python3 variable. -+ make_ : make functions generate file output (gml, ome.tiff, vtk). ++ make_ : make functions generate file output (gml, vtk). + plot_ : plot functions generate a matplotlib figure, or axis object, or file output (jpeg, png, tiff), depending on your parameter settings. ### TimeStep initialize @@ -80,6 +80,8 @@ Basically, there are four types of functions: + [help(mcds.get_substrate_list)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_substrate_list.md) #! workhorse function + [help(mcds.get_substrate_dict)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_substrate_dict.md) + [help(mcds.get_substrate_df)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_substrate_df.md) ++ [help(mcds.get_concentration)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_concentration.md) ++ [help(mcds.get_concentration_at)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_concentration_at.md) + [help(mcds.get_conc_df)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_conc_df.md) #! workhorse function + [help(mcds.plot_contour)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.plot_contour.md) #! workhorse function + [help(mcds.make_conc_vtk)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_conc_vtk.md) #! workhorse function @@ -88,6 +90,7 @@ Basically, there are four types of functions: + [help(mcds.get_celltype_list)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_celltype_list.md) #! workhorse function + [help(mcds.get_celltype_dict)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_celltype_dict.md) + [help(mcds.get_cell_df)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_cell_df.md) #! workhorse function ++ [help(mcds.get_cell_df_at)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_cell_df_at.md) + [help(mcds.plot_scatter)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.plot_scatter.md) #! workhorse function + [help(mcds.make_cell_vtk)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_cell_vtk.md) #! workhorse function + [help(mcds.get_anndata)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_anndata.md) #! workhorse function @@ -97,16 +100,12 @@ Basically, there are four types of functions: + [help(mcds.get_neighbor_graph_dict)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.get_neighbor_graph_dict.md) + [help(mcds.make_graph_gml)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_graph_gml.md) #! workhose function -### TimeStep microenvironment and cells -+ [help(mcds.make_ome_tiff)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_ome_tiff.md) #! workhose function -+ [help(mcds.make_neuroglancer)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcds.make_neuroglancer.md) #! workhose function - ### TimeStep internal functions + [help(pcdl.scaler)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl.scaler.md) # anndata + [help(pcdl.graphfile_parser)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl.graphfile_parser.md) # mcds ```python3 -help(pcdl.TimeStep._read_xml) -help(pcdl.TimeStep._anndextract) +help(pcdl.pyMCDS._read_xml) +help(pcdl.pyAnnData._anndextract) ``` @@ -115,7 +114,7 @@ help(pcdl.TimeStep._anndextract) Basically, there are four types of functions: + set_ : set a python3 variable. + get_ : recall a python3 variable. -+ make_ : make functions generate file output (gif, gml, mp4, ome.tiff, vtk). ++ make_ : make functions generate file output (gif, gml, mp4, vtk). + plot_ : plot functions generate a matplotlib figure, or axis object, or file output (jpeg, png, tiff), depending on your parameter settings. ### TimeSeries initialization @@ -143,10 +142,6 @@ Basically, there are four types of functions: ### TimeSeries cell graph + [help(mcdsts.get_graph_gml)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.make_graph_gml.md) #! workhose function -### TimeSteries microenvironment and cells -+ [help(mcdsts.make_ome_tiff)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.make_ome_tiff.md) #! workhose function -+ [help(mcdsts.make_neuroglancer)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.make_neuroglancer.md) #! workhose function - ### Timeseries timeseries + [help(mcdsts.plot_timeseries)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/mcdsts.plot_timeseries.md) #! workhorse function @@ -159,8 +154,8 @@ Basically, there are four types of functions: ### TimeSeries internal functions + [help(pcdl.scaler)](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl.scaler.md) # anndata ```python3 -help(pcdl.TimeSeries._handle_magick) -help(pcdl.TimeSeries._anndextract) +help(pcdl.pyMCDSts._handle_magick) +help(pcdl.pyAnnData._anndextract) ``` @@ -193,10 +188,6 @@ The command line interface functions mimic the name and parameter arguments as c ### Command line cell graph + [pcdl_make_graph_gml --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_make_graph_gml.md) #! workhorse function -### Command line cells and microenvironment -+ [pcdl_make_ome_tiff --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_make_ome_tiff.md) #! workhorse function -+ [pcdl_make_neuroglancer --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_make_neuroglancer.md) #! workhorse function - ### Command line timeseries + [pcdl_plot_timeseries --help](https://github.com/elmbeech/physicelldataloader/tree/master/man/docstring/pcdl_plot_timeseries.md) #! workhorse function diff --git a/man/TUTORIAL_blender.md b/man/TUTORIAL_blender.md index d5e05ca..f9fd8eb 100644 --- a/man/TUTORIAL_blender.md +++ b/man/TUTORIAL_blender.md @@ -3,7 +3,6 @@ [Blender](https://www.blender.org/) is a modeling, rigging, animation, simulation, rendering, compositing, motion tracking, video editing, and game creation software. Blender is free and open source. There exists a vtk nodes plugin, that lets us load vtk polynomial data files. -And there exists a bioxel nodes plugin, that lets us load ome tiff files. ## ✨ Handle vtk files @@ -64,31 +63,4 @@ To learn more about Blender and BVTK Node plugin, please study the official docu + https://docs.blender.org/manual/en/latest/ -## ✨ Handle ome tiff files - -The blender bioxel nodes plugin allows us load single time step ome tiff files into blender. - -### Generate ome tiff files from the command line - -```bash -pcdl_make_ome_tiff output --collapse false -``` - -### Generate ome tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff(collapse=False) -``` - -### The blender bioxel nodes plugin - -Please follow the official bioxel nodes instructions for installation -and to learn how to use the plugin. - -+ https://omoolab.github.io/BioxelNodes/latest/ - - That's it! The rest is analysis within blender! diff --git a/man/TUTORIAL_commandline.md b/man/TUTORIAL_commandline.md index ae15560..61b0429 100644 --- a/man/TUTORIAL_commandline.md +++ b/man/TUTORIAL_commandline.md @@ -1,5 +1,7 @@ # PhysiCell Data Loader Tutorial: pcdl from the Commandline + + The most important commands for down stream data analysis, available in the pcdl TimeStep and TimeSeries class, can be run straight from a command line shell, like [bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)), [csh](https://en.wikipedia.org/wiki/C_shell), [dos](https://en.wikipedia.org/wiki/DOS), [fish](https://en.wikipedia.org/wiki/Fish_(Unix_shell)), [ksh](https://en.wikipedia.org/wiki/KornShell), [powershell](https://en.wikipedia.org/wiki/PowerShell), [sh](https://en.wikipedia.org/wiki/Bourne_shell), [tsh](https://en.wikipedia.org/wiki/Tcsh), or [zsh](https://en.wikipedia.org/wiki/Z_shell), to name a view. @@ -9,7 +11,7 @@ The command parameter mimics the related python3 function arguments as closely a You can always call the [help](https://en.wikipedia.org/wiki/Help!) parameter ( pcdl\_command -h), to access the man page for a pcdl command! -Please spend some time to learn about each of the about 20 commands, by studying its man page. +Please spend some time to learn about each of the 18 commands, by studying its man page. This will truly make you a power user! @@ -194,19 +196,6 @@ pcdl_get_celltype_list output/output00000000.xml pcdl_get_celltype_list -h ``` -### ✨ pcdl\_get\_cell\_attrribute\_list - -Output all recorded cell attributes. - -```bash -pcdl_get_cell_attribute_list output -``` -```bash -pcdl_get_cell_attribute_list output/output00000000.xml -``` -```bash -pcdl_get_cell_attribute_list -h -``` ### ✨ pcdl\_get\_cell\_attribute @@ -398,60 +387,21 @@ pcdl_plot_timeseries -h ``` -### ✨ pcdl\_make\_ome\_tiff - -Generate an [ome.tiff](https://ome-model.readthedocs.io/en/stable/index.html) file, -to analyze a single time step or the whole time series, -the same way as usually fluorescent microscopy data is analyzed. - -By default, the cell\_attribute outputted is the cell ID + 1. -However, any numerical (bool, int, float) cell\_attribute can be outputted. -For example: dead, cells\_per\_voxel, or pressure. - -These ome.tiff files can be further analyzed, -for example with the [Napari](https://napari.org/stable/) or [Fiji Imagej](https://fiji.sc/) or [Neuroglancer](https://research.google/blog/an-interactive-automated-3d-reconstruction-of-a-fly-brain/) or [Blender](https://www.blender.org/) or similar software, -as described in the extra tutorials. - -```bash -pcdl_make_ome_tiff output/output00000000.xml pressure -``` -```bash -pcdl_make_ome_tiff output -``` -```bash -pcdl_make_ome_tiff -h -``` - -Further readings: -+ [TUTORIAL_python3_napari.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_napari.md) -+ [TUTORIAL_fiji_imagej.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_fijiimagej.md) -+ [TUTORIAL_neuroglancer.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_neuroglancer.md) -+ [TUTORIAL_blender.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_blender.md) +## [Making movies](https://en.wikipedia.org/wiki/Making_Movies) -### ✨ pcdl\_render\_neuroglancer +### ✨ pcdl\_make\_movie -With this command, you can render a time step ome.tiff file or a time step from a whole time series ome.tiff file straight into [Neuroglancer](https://research.google/blog/an-interactive-automated-3d-reconstruction-of-a-fly-brain/), which is a [WebGL](https://en.wikipedia.org/wiki/WebGL)-based viewer that will render the ome.tiff straight in your browser. +Make a [mp4](https://en.wikipedia.org/wiki/MP4_file_format) movie from the jpeg plots from a time series. -Below, we render a time step into Neuroglancer, first utilizing the time step ome.tiff, then using the whole time series ome.tiff. -You can only render one time step at a time and not a entire time series, like, for example, in napari. ```bash -pcdl_make_ome_tiff output/output00000000.xml -pcdl_render_neuroglancer output_2d/output00000000_oxygen_water_default_blood_cells_ID.ome.tiff -``` -```bash -pcdl_make_ome_tiff output -pcdl_render_neuroglancer output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff 3 # render time step 3 from the time series +pcdl_plot_scatter output +pcdl_make_movie output/cell_cell_type_z0.0/ ``` ```bash -pcdl_make_ome_tiff -h +pcdl_make_movie -h ``` -Further readings: -+ [TUTORIAL_neuroglancer.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_neuroglancer.md) - - -## [Making movies](https://en.wikipedia.org/wiki/Making_Movies) ### ✨ pcdl\_make\_gif @@ -465,17 +415,7 @@ pcdl_make_gif output/cell_cell_type_z0.0/ pcdl_make_gif -h ``` -### ✨ pcdl\_make\_movie - -Make a [mp4](https://en.wikipedia.org/wiki/MP4_file_format) movie from the jpeg plots from a time series. -```bash -pcdl_plot_scatter output -pcdl_make_movie output/cell_cell_type_z0.0/ -``` -```bash -pcdl_make_movie -h -``` ## Data Clean Up diff --git a/man/TUTORIAL_fijiimagej.md b/man/TUTORIAL_fijiimagej.md deleted file mode 100644 index 1914369..0000000 --- a/man/TUTORIAL_fijiimagej.md +++ /dev/null @@ -1,50 +0,0 @@ -# PhysiCell Data Loader Tutorial: pcdl and Fiji, Imagej, Icy, QuPath. - -[Fiji](https://fiji.sc/) is a free and open source image processing library, -a "batteries-included" distribution of [ImageJ](https://en.wikipedia.org/wiki/ImageJ), -able to read [ome.tiff](https://www.openmicroscopy.org/ome-files/) files. - -[Icy](https://icy.bioimageanalysis.org/) and [QuPath](https://github.com/qupath/qupath/wiki/What-is-QuPath%3F) are similar bioimage analysis software applications. - -Fiji Imagej, Icy, and QuPath are used by wet lab scientists and bioinformatician to analyze fluorescent and bright field microscopy data. -All these software applications can open the ome.tiff file format. - - -## Install Fiji Imagej (JDK), Icy, or QuPath ~ the 64[bit] version! - -Please follow the installation instructions on the official homepages. -+ https://imagej.net/software/fiji/ -+ https://icy.bioimageanalysis.org/download/ -+ https://qupath.github.io/ - - -## Generate ome.tiff files from the command line - -```bash -pcdl_make_ome_tiff('output/') -``` - - -## Generate ome.tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff() -``` - - -## Open ome.tiff files - -Use the graphical user interface to open the ome.tiff files. - - -## Running Fiji Imagej, Icy, or QuPath - -Please work through the official documentation to learn how to run the software. -+ https://imagej.net/learn/ -+ https://icy.bioimageanalysis.org/ -+ https://qupath.readthedocs.io/en/stable/ - -That's it. The rest is analysis within fiij imagej! diff --git a/man/TUTORIAL_introduction.md b/man/TUTORIAL_introduction.md index ee8ea4a..85dd531 100644 --- a/man/TUTORIAL_introduction.md +++ b/man/TUTORIAL_introduction.md @@ -2,10 +2,8 @@ If you have not already done so, please install the latest version of physicelldataloader (pcdl), as described in the [HowTo](https://github.com/elmbeech/physicelldataloader/blob/master/man/HOWTO.md) section.\ -The current development happens in branch v4. -Branch v3 and v4 are maintained and [pip](https://pypi.org/project/pcdl/) installable. -Branch v3 is also installable through [bioconada](https://bioconda.github.io/). -Branch v1, v2 exists, if ever needed, for reproducibility of old results. +The current development happens in branch v3 and v4. +Branch v1 and v2 exists, if ever needed, for reproducibility of old results. ## Tutorial - branch v1 and v2 @@ -22,9 +20,9 @@ In the very early days, [PhysiCell](https://github.com/MathCancer/PhysiCell) out In 2019, a similar loader script was written for python3. The name of this script filed was pyMCDS.py and basically defined one class named pyMCDS. -In autumn 2022, an endeavor was undertaken to pack the original pyMCDS.py script into a pip installable python3 library and develop it further, but always in such a way that, if necessary, the code could still be run like in the early days.\ +In autumn 2022 an endeavor was undertaken to pack the original pyMCDS.py script into a pip installable python3 library and develop it further, but always in such a way that, if necessary, the code could still be run like in the early days.\ The result is the pcdl physicelldataloader library branch v2, v3. -In spring 2025, the code was stripped of some relics (mainly in the back end) from the early days to make the code more python3 than C++ like, which resulted in branch v4. +In autumn 2024 the code was stripped of some relics from the early days, to make the code more python3 than C++ like, which resulted in branch v4. The result from all of this is the pcdl physicelldataloader library here.\ In the big picture, the pyMCDS class evolved into the TimeStep class, which is slightly heavier but much more powerful for downstream data analysis than the original pyMCDS class. @@ -38,7 +36,7 @@ Naturally, the full-fledged pcdl library with the TimeSteps and TimeSeries class ### Concept PhysiCell data loader is not yet another analysis software, it is just an interface to analysis software! -Please work through the tutorials, and you will in in-depth grasp the meaning behind this sentence. +If you work through the tutorials, and you will in in-depth grasp the meaning behind this sentence. ### Understanding PhysiCell's Time Step Output: the MultiCellular Data Standard (MCDS) Format @@ -63,6 +61,6 @@ The files we care about most from this set consists of: + **output00000012_cells.mat**: This is a MATLAB matrix file that contains tracked information about the individual cells in the model. It tells us things like the cells' position, volume, secretion, cell cycle status, and user-defined cell parameters. + **output00000012_microenvironment0.mat**: This is a MATLAB matrix file that contains data about the microenvironment at this time step. -+ **output00000012_cell_neighbor_graph.txt**, **output00000012_attached_cells_graph.txt**, and **output00000012_spring_attached_cells_graph.txt**: These are files describing the cell neighborhood graph. ++ **output00000012_attached_cells_graph.txt** and **output00000036_cell_neighbor_graph.txt**: These are files describing the cell neighborhood graph. With pcdl we can load a **MCDS time step** or a whole **MCDS time series** for data analysis. diff --git a/man/TUTORIAL_julia.md b/man/TUTORIAL_julia.md index b766b79..189d733 100644 --- a/man/TUTORIAL_julia.md +++ b/man/TUTORIAL_julia.md @@ -166,9 +166,9 @@ Please study the Muon and AnnData documentation to learn how to analyze this dat + https://github.com/scverse/anndata -## ✨ Handle ome.tiff, tiff, png, and jpeg file format +## ✨ Handle tiff, png, and jpeg file format -### Save pcdl data structures as jpeg, png, tiff, and ome.tiff files from the command line +### Save pcdl data structures as jpeg, png, and tiff files from the command line ```bash pcdl_plot_contour output/output00000021.xml oxygen --ext tiff @@ -179,16 +179,10 @@ pcdl_plot_contour output/output00000021.xml oxygen ```bash pcdl_plot_scatter output/output00000021.xml ``` -```bash -pcdl_make_ome_tiff('output/') -``` - -### Load jpeg, png, tiff, and ometiff files into a julia data structures -⚠ **bue 2024-09-04:** ome.tiff files currently cannot be loaded ( github issue: https://github.com/tlnagy/OMETIFF.jl/issues/112 ). +### Load jpeg, png, and tiff files into a julia data structures -We will use the [Images](https://github.com/JuliaImages/Images.jl) library, and it's [OMETIFF](https://github.com/tlnagy/OMETIFF.jl) extension, -to load jpeg, png, tiff, and ome.tiff files +We will use the [Images](https://github.com/JuliaImages/Images.jl) library to load jpeg, png, and tiff files Package installation. @@ -196,7 +190,6 @@ Package installation. using Pkg Pkg.add("FileIO") Pkg.add("Images") -Pkg.add("OMETIFF") ``` Load image file. @@ -206,9 +199,6 @@ using FileIO using Images ``` ```julia -omeimg = load("output/timeseries_ID.ome.tiff") -``` -```julia img = load("output/cell_cell_type_z0.0/output00000021_cell_type.jpeg") ``` diff --git a/man/TUTORIAL_neuroglancer.md b/man/TUTORIAL_neuroglancer.md deleted file mode 100644 index 75a3563..0000000 --- a/man/TUTORIAL_neuroglancer.md +++ /dev/null @@ -1,99 +0,0 @@ -# PhysiCell Data Loader Tutorial: pcdl and Neuroglancer. - -[Neuroglancer](https://research.google/blog/an-interactive-automated-3d-reconstruction-of-a-fly-brain/) is a [WebGL](https://en.wikipedia.org/wiki/WebGL)-based viewer for volumetric data. -It is capable of displaying arbitrary (non axis-aligned) cross-sectional views of volumetric data, as well as 3-D meshes and line-segment based models (skeletons). - -[Ome.tiff](https://www.openmicroscopy.org/ome-files/) is the open microscopy image standard format used by wet lab scientists to store (fluorescent) microscopy data. - -In 2022, in the middle of the pandemic, a small group of programmers undertook at the Image Analysis Working Group of the Cancer Systems Biology Consortium & Physical Sciences-Oncology Network [hackathon](https://github.com/IAWG-CSBC-PSON/hack2022-10-neuroglancer) the endeavor to write a python3 script that directly can render ome.tiff files into Neurogalncer. -This script was adapted for the pcdl project. - - -### ✨ command line - -With pcdl it is very easy to generate time step and time series ome.tiff files from regular PhysiCell output straight from the command line and render them into Neuroglancer. - -#### command line time step -Generate time step 6 ome.tiff - -```bash -pcdl_make_ome_tiff pcdl_make_ome_tiff output_2d/output00000006.xml -``` - -Render time step 6 ome.tiff - -```bash -pcdl_render_neuroglancer output_2d/output00000006_oxygen_water_default_blood_cells_ID.ome.tiff -``` - -#### command line time series - -Generate time series ome.tiff - -```bash -pcdl_make_ome_tiff output/ -``` - -Render timer series time step 0. - -```bash -pcdl_render_neuroglancer output/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff -``` - -Render time series time step 12. - -```bash -pcdl_render_neuroglancer output/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff 12 -``` - -#### command line man pages - -```bash -pcdl_make_ome_tiff -h -``` -```bash -pcdl_render_neuroglancer -h -``` - -### ✨ python -With pcdl it is very easy to generate within python3 time steps and time series ome.tiff files from regular PhysiCell output and render them into Neuroglancer. - -#### python time step - -Generate and directly render time step 6. -```python -import pcdl - -mcds = pcdl.TimeStep('output_2d/output00000006.xml') -mcds.render_neuroglancer(mcds.make_ome_tiff()) -``` - -#### python time series - -Generate time series, then render. -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output_2d/') -s_pathfile = mcdsts.make_ome_tiff() -mcdsts.render_neuroglancer(s_pathfile) # time step 0 -``` -```python -mcdsts.render_neuroglancer(s_pathfile, 12) # time step 12 -``` - -#### python docstrings - -```python -import pcdl -help(pcdl.render_neuroglancer) -``` - -### ✨ Further readings - -Please work through the official documentation to learn how to run the Neuroglancer software. -+ https://github.com/google/neuroglancer -+ https://neuroglancer-docs.web.app/index.html -+ https://research.google/blog/an-interactive-automated-3d-reconstruction-of-a-fly-brain/ - -That's it. The rest is analysis within neuroglancer! diff --git a/man/TUTORIAL_paraview.md b/man/TUTORIAL_paraview.md index d9d9b2b..fda8707 100644 --- a/man/TUTORIAL_paraview.md +++ b/man/TUTORIAL_paraview.md @@ -1,13 +1,7 @@ # PhysiCell Data Loader Tutorial: pcdl and Paraview - - [Paraview](https://www.paraview.org/) is a free and open source scientific visualization software, -that lets us load and analyze vtk rectilinear grid data and vtk polynomial data, and even ome.tiff data files. +that lets us load and analyze vtk rectilinear grid data and vtk polynomial data. ## Install paraview @@ -37,13 +31,12 @@ mcdsts.make_cell_vtk() ## Load vtk files with paraview -1. **View** / **Pipeline Browser** has to be checked! -2. **View** / **Properties** has to be checked! -3. **File** / **Open...** path/to/PhysiCell/output/output..\_conc.vtr [OK] -4. **File** / **Open...** path/to/PhysiCell/output/output..\_cell.vtp [OK] -5. In the **Pipeline Browser**, click the closed **eyes**, so that they open. -6. In the **Pipeline Browser**, click the output00000000\_cell.vtp\* and under **Properties Coloring** select cell\_type in the dropdown menu. -7. In the **Pipeline Browser**, click the output00000000\_conc.vtr\* and under **Properties Coloring** select the oxygen in the dropdown menu. + +1. **File** / **Open...** path/to/PhysiCell/output/output..\_conc.vtr [OK] +2. **File** / **Open...** path/to/PhysiCell/output/output..\_cell.vtp [OK] +3. In the **Pipeline Browser**, click the closed **eyes**, so that they open. +4. In the **Pipeline Browser**, click the output00000000\_cell.vtp\* and under **Coloring** select cell\_type in the dropdown menu. +5. In the **Pipeline Browser**, click the output00000000\_conc.vtr\* and under **Coloring** select the oxygen in the dropdown menu. For learning more about how to run the software, please work through the official documentation. diff --git a/man/TUTORIAL_python3_json.md b/man/TUTORIAL_python3_json.md index 0dcebad..eae2a53 100644 --- a/man/TUTORIAL_python3_json.md +++ b/man/TUTORIAL_python3_json.md @@ -7,10 +7,10 @@ In pcdl output from the [TimeSeries](https://github.com/elmbeech/physicelldatalo The [json library](https://docs.python.org/3/library/json.html) is a part of core python. Json is the ideal data format for unstructured data constructs that not can be stored in csv file format, like a dictionary of lists. -Please note, python objects are not per se json comaptible. +Please note, python objecta are not per se json comaptible. For example: -Python dictionary keys can be of almost any data type, but json object keys have to be strings. Complex numbers are a standard data type in python, but complex numbers cannot be stored in json. +Python dictionary keys can be of almost any data type, but json object keys have to be strings. ### Dump pcdl data construct from the command line into a json file diff --git a/man/TUTORIAL_python3_matplotlib.md b/man/TUTORIAL_python3_matplotlib.md index 8cd5772..3c8be7e 100644 --- a/man/TUTORIAL_python3_matplotlib.md +++ b/man/TUTORIAL_python3_matplotlib.md @@ -126,10 +126,11 @@ df_cell = mcds.get_cell_df() Generate a dataframe with some categorical data + ```pandas df_cat = df_cell.loc[:, ['cell_type', 'cycle_model', 'current_phase', 'dead']] df_cat['count'] = 1 -df_catplot = df_cat.loc[:, ['cell_type','count']].groupby('cell_type').count() +df_catplot = df_cat.loc[:, ['dead','count']].groupby('dead').count() df_catplot.info() ``` @@ -151,7 +152,7 @@ df_catplot.plot( y = 'count', ylabel = '', legend = False, - title = 'cell_type' + title = 'dead' ) ``` diff --git a/man/TUTORIAL_python3_napari.md b/man/TUTORIAL_python3_napari.md deleted file mode 100644 index e6e6908..0000000 --- a/man/TUTORIAL_python3_napari.md +++ /dev/null @@ -1,54 +0,0 @@ -# PhysiCell Data Loader Tutorial: pcdl and Python and Napari - -[Napari](https://napari.org/stable/) is both a python library and a GUI software. -Napari is used by wetlab scientists and bioinformatician to analyze fluorescent microscopy data. -Napari can read [ome.tiff](https://www.openmicroscopy.org/ome-files/) files. -https://github.com/AllenCellModeling/napari-aicsimageio - - -## Install napari - -```bash -pip3 install napari[all] -``` - - -### Generate ome.tiff files from the command line - -```bash -pcdl_make_ome_tiff('output/') -``` - - -### Generate ome.tiff files from within python - -```python -import pcdl - -mcdsts = pcdl.TimeSeries('output/') -mcdsts.make_ome_tiff() -``` - - -### Open ome.tiff files in napari from within python - -```python -import napari - -viewer = napari.Viewer() -viewer.open('output/timeseries_ID.ome.tiff') -``` - - -### Open ome.tiff files in napari from the command line - -```bash -napari output/timeseries_ID.ome.tiff -``` - -## Running napari - -Please work through the official documentation to learn how to run the software. -+ https://napari.org/stable/tutorials/start_index.html - -That's it. The rest is analysis within napari! diff --git a/man/TUTORIAL_python3_ometiff.md b/man/TUTORIAL_python3_ometiff.md index 1dc2d61..58b91e2 100644 --- a/man/TUTORIAL_python3_ometiff.md +++ b/man/TUTORIAL_python3_ometiff.md @@ -1,28 +1,14 @@ # PhysiCell Data Loader Tutorial: pcdl and Python and the Ome.tiff, Tiff, Png, and Jpeg File Format -In pcdl output from [TimeSteps](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timestep.md) and [TimeSeries](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timeseries.md) can be stotred as [ome.tiff](https://www.openmicroscopy.org/ome-files/), [tiff](https://www.loc.gov/preservation/digital/formats/fdd/fdd000022.shtml), [png](http://libpng.org/pub/png/), and [jpeg](https://jpeg.org/jpeg/) files. +In pcdl output from [TimeSteps](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timestep.md) and [TimeSeries](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_timeseries.md) can be stotred as [tiff](https://www.loc.gov/preservation/digital/formats/fdd/fdd000022.shtml), [png](http://libpng.org/pub/png/), and [jpeg](https://jpeg.org/jpeg/) files. Tiff, png, and jpeg are raster graphic file formats. -Ome.tiff is the open microscopy image standard. -This means, being able to export PhysiCell output in ome.tiff files format -enables us to study PhysiCell output the same way -as commonly fluorescent microscopy data is analyzed by wetlab scientists. -Please have a look at [TUTORIAL_python3_napari.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_napari.md), -[TUTORIAL_fiji_imagej.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_fijiimagej.md), and -[TUTORIAL_neuroglancer.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_neuroglancer.md) to learn more. +Additionally tiff, png, and jpeg files can as well be loaded back in to python as [numpy](https://numpy.org/) array, for example with the [sci-kit image](https://scikit-image.org/) library (image data only). -Additionally ome.tiff, tiff, png, and jpeg files can as well be loaded back in to python as [numpy](https://numpy.org/) array, for example with the [sci-kit image](https://scikit-image.org/) library (image data only). +### Save pcdl data constructs from the command line into tiff files -Besides that, ome.tiff files can be loaded with the [bioio](https://github.com/bioio-devs/bioio) library (image and metadata). - - -### Save pcdl data constructs from the command line into tiff and ome.tiff files - -```bash -pcdl_make_ome_tiff('output/') -``` ```bash pcdl_plot_contour output/output00000012.xml oxygen ``` @@ -31,7 +17,7 @@ pcdl_plot_scatter output/output00000012.xml ``` -### Save pcdl data constructs from within python into tiff and ome.tiff files +### Save pcdl data constructs from within python into tiff files ```python import pcdl @@ -39,7 +25,6 @@ import pcdl mcdsts = pcdl.TimeSeries('output/') mcdsts.get_mcds_list()[12].plot_contour(focus='oxygen', ext='tiff') mcdsts.get_mcds_list()[12].plot_scatter(ext='tiff') -mcdsts.make_ome_tiff() ``` @@ -57,27 +42,6 @@ from skimage import io a_cell = io.imread('output/cell_cell_type_z0.0/output00000012_cell_type.tiff') a_cell.shape # (480, 640, 4) ``` -```python -from skimage import io - -a_ome = io.imread('output/timeseries_ID.ome.tiff') -a_ome.shape # (25, 2, 200, 300) -``` -### Load ome.tiff files as BioImage object into python - -```python -from bioio import BioImage - -img = BioImage('output/timeseries_ID.ome.tiff') -img.shape # (25, 2, 1, 200, 300) -``` -```python -img.dims # -``` -```python -img.channel_names # [np.str_('oxygen'), np.str_('cancer_cell')] -``` - That's it. The rest is analysis! diff --git a/man/TUTORIAL_python3_scverse.md b/man/TUTORIAL_python3_scverse.md index ef55b99..a73dd43 100644 --- a/man/TUTORIAL_python3_scverse.md +++ b/man/TUTORIAL_python3_scverse.md @@ -62,8 +62,8 @@ Load the data. ```python mcdsts = pcdl.TimeSeries('output/') -adata = mcdsts.get_anndata(values=2, scale='maxabs', collapse=True) -print(adata) +annts = mcdsts.get_anndata(values=2, scale='maxabs', collapse=True) +print(annts) ``` Let's do an interactive data analysis.\ @@ -78,22 +78,22 @@ Please note, sub-library abbreviations used in the scanpy and squidpy library ar Principal component analysis: ```python -sc.tl.pca(adata) # process anndata object with the pca tool. -sc.pl.pca(adata) # plot pca result. +sc.tl.pca(annts) # process anndata object with the pca tool. +sc.pl.pca(annts) # plot pca result. ``` ```python -sc.pl.pca(adata, color=['current_phase','oxygen']) # plot the pca results colored by some attributes. +sc.pl.pca(annts, color=['current_phase','oxygen']) # plot the pca results colored by some attributes. ``` ```python -sc.pl.pca_variance_ratio(adata) # plot how much of the variation each principal component captures. +sc.pl.pca_variance_ratio(annts) # plot how much of the variation each principal component captures. ``` Neighborhood graph clustering: ```python -sc.pp.neighbors(adata, n_neighbors=15) # compute the neighborhood graph with the neighbors preprocess step. -sc.tl.leiden(adata, resolution=0.01) # cluster the neighborhood graph with the leiden tool. -sc.pl.pca(adata, color='leiden') # plot the pca results colored by leiden clusters. +sc.pp.neighbors(annts, n_neighbors=15) # compute the neighborhood graph with the neighbors preprocess step. +sc.tl.leiden(annts, resolution=0.01) # cluster the neighborhood graph with the leiden tool. +sc.pl.pca(annts, color='leiden') # plot the pca results colored by leiden clusters. ``` -```python -mcdsts.get_conc_df() -``` -```python -mcdsts.plot_contour('substrate') -``` -```python -mcdsts.make_conc_vtk() -``` ++ mcdsts.get_conc_df() ++ mcdsts.plot_contour('substrate') ++ mcdsts.make_conc_vtk() For cell data, these are the functions: - -```python -mcdsts.get_cell_df() -``` -```python -mcdsts.get_anndata() -``` -```python -mcdsts.make_graph_gml() -``` -```python -mcdsts.plot_scatter() -``` -```python -mcdsts.make_cell_vtk() -``` - -For microenvironment and cell data, these are the functions: -```python -mcdsts.make_ome_tiff() -``` -```python -mcdsts.render_neuroglancer(mcdsts.make_ome_tiff(), timestep=0) -``` ++ mcdsts.get_cell_df() ++ mcdsts.get_anndata() ++ mcdsts.make_graph_gml() ++ mcdsts.plot_scatter() ++ mcdsts.make_cell_vtk() Yet, there are additional functions, that only make sense for TimeSeries, and those functions will be discussed in this chapter. For handling TimeSeries, these are the functions: -+ `mcdsts.get_xmlfile_list()` -+ `mcdsts.read_mcds()` -+ `mcdsts.get_mcds_list()` -+ `mcdsts.get_annmcds_list()` ++ mcdsts.get_xmlfile_list() ++ mcdsts.read_mcds() ++ mcdsts.get_mcds_list() ++ mcdsts.get_annmcds_list() For microenvironment data, this is the function: -+ `mcdsts.get_conc_attribute()` ++ mcdsts.get_conc_attribute() For cell data, this is the function: -+ `mcdsts.get_cell_attribute()` ++ mcdsts.get_cell_attribute() For microenvironment and cell data, this is the function: -+ `mcdsts.plot_timeseries()` ++ mcdsts.plot_timeseries() Besides, there are functions to render a set of jpeg, png, or tiff images into a movie. -+ `mcdsts.make_movie()` and `pcdl.make_movie()` -+ `mcdsts.make_gif()` and `pcdl.make_gif()` ++ mcdsts.make_movie() and pcdl.make_movie() ++ mcdsts.make_gif() and pcdl.make_gif() @@ -101,7 +70,7 @@ python3 -c"import pathlib, pcdl, shutil; pcdl.install_data(); s_ipath=str(pathli ## Loading an MCDS Time Series -Like in the TimeStep class, for memory consumption and processing speed control, +Like in the pyMCDs class, for memory consumption and processing speed control, we can specify if we want to load microenvironment data and graph data from the snapshots we later on analyze. Additionally, we can specify, if for first even want to load data at all, or if we only would like to load the output xml file list, which we will see, can be manipulated before actual data is loaded. diff --git a/man/TUTORIAL_python3_timestep.md b/man/TUTORIAL_python3_timestep.md index 0f58a4f..3bce765 100644 --- a/man/TUTORIAL_python3_timestep.md +++ b/man/TUTORIAL_python3_timestep.md @@ -1,6 +1,6 @@ # PhysiCell Data Loader Tutorial: pcdl and python and MCDS TimeSteps -In this chapter, we will load the pcdl library and use its TimeStep class to load the data snapshot output/00000012, from the 2D time series test dataset (https://github.com/elmbeech/physicelldataloader/blob/master/output_2d.tar.gz). +In this chapter, we will load the pcdl library and use its TimeStep class to load the data snapshot 00000012, from [data\_timeseries\_ 2d](https://github.com/elmbeech/physicelldataloader/tree/master/pcdl/output_2d) from the 2D time series test dataset. First, please install the latest version of physicelldataloader (pcdl), as described in the [HowTo](https://github.com/elmbeech/physicelldataloader/blob/master/man/HOWTO.md) chapter. @@ -12,7 +12,7 @@ And, if not already done so, have a quick read through the pcdl [background](htt ## Preparation To runs this tutorial, -you can either work with the data that is currently in your output folder, +you can either work the data that is currently in your output folder, or you can install the 2D unit test dataset into your PhysiCell output folder, by executing the following command sequence. @@ -53,7 +53,7 @@ print('pcdl version:', pcdl.__version__) # it is easy to figure out which pcdl mcds = pcdl.TimeStep(s_pathfile, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) ``` -The verbosity for pcdl shell output you can tune, even after loading the data. +The verbosity for pcdl output you can tune, even after loading the data. ```python3 mcds.set_verbose_false() @@ -74,34 +74,31 @@ Regarding the original python-loader, the structure has slightly changed.\ Anyhow, let's take a look at what we actually have in here. +![mcds.data dictionary blueprint](img/physicelldataloader_data_dictionary_v3.2.2.png) + ```python # main data branches -sorted(mcds.data.keys()) # metadata, mesh, substrate (microenvironment), cell (agent), and raw_substrate and raw_cell data. +sorted(mcds.data.keys()) # metadata, mesh, substrate microenvironment (continuum_variables), and cell agent (discrete_cells) # metadata -sorted(mcds.data['metadata'].keys()) # multicellds_version, physicell_version, simulation time (current_time), runtime (current_runtime), time stamp (created), time unit ('time_units', 'runtime_units'), spatial unit ('spatial_unit'), and all units specified (ds_unit). +sorted(mcds.data['metadata'].keys()) # multicellds version, physicell version, simulation time, runtime, time stamp, time unit, spatial unit, and substrate and cell type ID label mappings # mesh -sorted(mcds.data['mesh'].keys()) # voxel (ijk), mesh (mnp), and position (xyz) range, axis, spacing, voxel volume, coordinate, and grid. +sorted(mcds.data['mesh'].keys()) # voxel (ijk), mesh (nmp), and position (xyz) range, axis, coordinate, grid objects, and voxel volume # microenvironment -sorted(mcds.data['substrate'].keys()) # df_conc, df_substarte, ds_substrate, ls_substarte -print(mcds.data['substrate']['ls_substarte'] # list of all processed substrates -print(mcds.data['substrate']['ds_substrate'] # dictionary with substrate ID label mapping -print(mcds.data['substrate']['df_substarte'] # pandas data frame with substrate decay_rate and diffusion_coefficient. -print(mcds.data['substrate']['df_conc'] # pandas data frame with the actual substrate concentrations in each voxel. +sorted(mcds.data['continuum_variables'].keys()) # list of all processed substrates, e.g. oxygen +sorted(mcds.data['continuum_variables']['oxygen'].keys()) # substrate related data values, unit, diffusion coefficient, and decay rate # cell -sorted(mcds.data['cell'].keys()) # dei_graph, df_cell, ds_celltype, ls_cellattr, ls_celltype -print(mcds.data['cell']['ls_celltype']) # list of all processed cell types -print(mcds.data['cell']['ls_cellattr']) # list of all outputted (df_cell) cell type related attributes -print(mcds.data['cell']['ds_celltype']) # dictionary with cell type ID label mapping -print(mcds.data['cell']['df_cell']) # pandas data frame with each cell's attribute values -sorted(mcds.data['cell']['dei_graph'].keys()) # dictionary with neighbor_cells, attached_cells, and spring_attached_cells graph information. +sorted(mcds.data['discrete_cells'].keys()) # data, units, and graph dictionaries +sorted(mcds.data['discrete_cells']['data'].keys()) # all cell related, tracked data +sorted(mcds.data['discrete_cells']['units'].keys()) # all units from the cell related, tracked data +sorted(mcds.data['discrete_cells']['graph'].keys()) # neighbor_cells and attached_cells graph dictionaries ``` **Once again, loud, for the ones in the back, in pcdl >= version 3, all data is accessible by functions. -There should be no need to fetch data directly from the `mcds.data` dictionaries!** +There should be no need to fetch data directly from the `mcds.data` dictionaries.** We will explore these functions in the upcoming sections. @@ -200,6 +197,39 @@ df_conc.loc[(df_conc.voxel_i == 2) & (df_conc.voxel_j == 1) & (df_conc.voxel_k = Please have a look at [TUTORIAL_python3_pandas.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_pandas.md) to learn more. +Additionally, there is a less often used function to retrieve substrate specific 3D or 2D meshgrid [numpy](https://numpy.org/) arrays. +To get a 2D meshgrids you can slice though any z stack value, the function will always pick the closest mesh center coordinate, the smaller coordinate, if you hit the saddle point between two voxels. +(This function might become deprecated in a future pcdl version.) + +```python +# concentration meshgrid for a particular substrate +oxygen_2d = mcds.get_concentration('oxygen', z_slice=0) +oxygen_2d.shape # (11, 11) +``` +```python +# concentration meshgrid for a particular substrate +oxygen_3d = mcds.get_concentration('oxygen') +oxygen_3d.shape # (11, 11, 1) +``` + + +Additionally, there is a less often used functions to retrieve a [numpy](https://numpy.org/) array of all substrate concentrations at a particular xyz coordinate, ordered alphabetically by substrate name, like the list retrieved by the get\_substrate\_names function. +(This function might become deprecated in a future pcdl version.) + +```python +# all concentration values at a particular coordinate +mcds.get_concentration_at(x=0, y=0, z=0) # array([34.4166271]) +``` +```python +# all concentration values at a particular coordinate +mcds.get_concentration_at(x=111, y=22, z=-5) # array([18.80652216]) +``` +```python +# all concentration values at a particular coordinate +mcds.get_concentration_at(x=111, y=22, z=-5.1) # None and Warning @ pyMCDS.is_in_mesh : z = -5.1 out of bounds: z-range is (-5.0, 5.0) +``` + + ### ✨ Microenvironment Data Analysis with [Matplotlib](https://matplotlib.org/) For substrate concentration visualization, **matplotlib contour and contourf plots**, @@ -250,12 +280,6 @@ We can retrieve a dictionary that maps cell type IDs to labels. mcds.get_celltype_dict() # {'0': 'cancer_cell'} ``` -And we can retrieve an alphabetically ordered list with all cell agent attributes outputted by PhysiCell, loadable with mcds.get\_cell\_df(). - -```python -mcds.get_cell_attribute_list() # ['apoptotic_phagocytosis_rate', ...] -``` - ### ✨ Cell Data Analysis with [Pandas](https://pandas.pydata.org/) @@ -291,6 +315,15 @@ df_cell.loc[(df_cell.voxel_i == 2) & (df_cell.voxel_j == 1) & (df_cell.voxel_k = Please have a look at [TUTORIAL_python3_pandas.md](https://github.com/elmbeech/physicelldataloader/blob/master/man/TUTORIAL_python3_pandas.md) to learn more. +There exist an additional, less often used function, +to filter for cells in xyz position plus minus (voxel spacing / 2). +(This function might become deprecated in a future pcdl version.) + +```python +mcds.get_cell_df_at(x=45, y=10, z=0) # cells: 5, 7, 39 +``` + + ### ✨ Cell Data Analysis within the [Scverse](https://scverse.org/) To be able to analyze cell agent data the same way as single cell RNA seq data is analyzed, @@ -300,8 +333,8 @@ Anndata is the backbone of the scverse (single cell universe) project. ```python ann = mcds.get_anndata(values=2) -print(ann) # AnnData object with n_obs × n_vars = 81 × 50 - # obs: 'z_layer', 'time', 'cell_type', 'chemotaxis_index', 'current_phase', 'cycle_model' +print(ann) # AnnData object with n_obs × n_vars = 992 × 26 + # obs: 'z_layer', 'time', 'current_phase', 'cycle_model' # uns: 'neighbor' # obsm: 'spatial' # obsp: 'physicell_neighbor_conectivities', 'physicell_neighbor_distances' @@ -310,16 +343,16 @@ print(ann) # AnnData object with n_obs × n_vars = 81 × 50 variables: ```python -ann.var_names # numerical cell attributes: Index(['attachment_rate', ...], dtype='object') +ann.var_names # numerical cell attributes: Index(['cell_BM_repulsion_strength', ... , 'total_volume'], dtype='object') ``` observation: ```python -ann.obs_names # cell IDs: Index(['10', ...], dtype='object', name='ID', length=992) +ann.obs_names # cell IDs: Index(['0', ..., '994'], dtype='object', name='ID', length=992) ``` ```python -ann.obs_keys() # categorical cell attributes: ['z_layer', 'time', 'cell_type', 'chemotaxis_index', 'current_phase', 'cycle_model'] +ann.obs_keys() # categorical cell attributes: ['z_layer', 'time', 'current_phase', 'cycle_model'] ``` ```python ann.obsm_keys() # cell coordinates: ['spatial'] @@ -340,8 +373,8 @@ ann.uns_keys() # ['neighbor'] ann.uns['neighbor'] # metadata about the neighborhood graph. ``` -The output tells us that we have loaded a time step with 81 cell agents and 50 numerical attributes (vars). -Further, we have 6 categorical cell agent attributes (obs). +The output tells us that we have loaded a time step with 992 cell agents and 26 numerical attributes (vars). +Further, we have 4 categorical cell agent attributes (obs). We have each cell agent's xy spatial coordinate information (obsm). And we have cell neighbor graph infromation (obsp, uns). @@ -359,19 +392,13 @@ This file format can be read by graph libraries, like network and igraph, for do Cell neighbor touching graph ```python -mcds.make_graph_gml('neighbor') +mcds.make_graph_gml('touch') ``` Cell neighbor attached graph ```python mcds.make_graph_gml('attached') ``` - -Cell neighbor spring attached graph -```python -mcds.make_graph_gml('spring') -``` - +```python3 import vtk # load file -reader = vtk.vtkXMLRectilinearGridReader() -reader.SetFileName('output/output00000012_conc.vtr') -reader.Update() -grid = reader.GetOutput() - -# use scalar field for coloring the grid -scalar_name = 'oxygen' # 'water' -scalar_array = grid.GetPointData().GetArray(scalar_name) -if scalar_array is None: - raise RuntimeError(f'Scalar array "{scalar_name}" not found.') -grid.GetPointData().SetActiveScalars(scalar_name) -scalar_range = scalar_array.GetRange() - -# generate color lookup table -lut = vtk.vtkLookupTable() -lut.SetNumberOfTableValues(256) -lut.SetTableRange(scalar_range) -lut.SetHueRange(0.667, 0.0) # blue to red rainbow -lut.Build() - -# convert grid to z slice to data -# generate xy plane at z center -bounds = grid.GetBounds() -z_center = 0.5 * (bounds[4] + bounds[5]) -plane = vtk.vtkPlane() -plane.SetOrigin(0.5 * (bounds[0] + bounds[1]), 0.5 * (bounds[2] + bounds[3]), center_z) -plane.SetNormal(0, 0, 1) # slice in z -# cut xy plain at z center -cutter = vtk.vtkCutter() -cutter.SetCutFunction(plane) -cutter.SetInputData(grid) -cutter.Update() -# get xy plain data at z center -polydata_zslice = cutter.GetOutput() - -# mapper: map data to geometry -mapper = vtk.vtkPolyDataMapper() -mapper.SetInputData(polydata_zslice) -mapper.SetLookupTable(lut) -mapper.SetScalarRange(scalar_range) -mapper.SetColorModeToMapScalars() -mapper.SetScalarModeToUsePointData() -mapper.ScalarVisibilityOn() - -# actor: map actor to data mapped geometry -actor = vtk.vtkActor() -actor.SetMapper(mapper) - -# actor: color bar -scalar_bar = vtk.vtkScalarBarActor() -scalar_bar.SetLookupTable(lut) -scalar_bar.SetTitle(scalar_name) - -# renderer: director -renderer = vtk.vtkRenderer() -renderer.SetBackground(1/3, 1/3, 1/3) # gray background -renderer.AddActor(actor) -renderer.AddActor2D(scalar_bar) +vtr_reader = vtk.vtkXMLRectilinearGridReader() +vtr_reader.SetFileName('output/output00000012_conc.vtr') +vtr_reader.Update() -# render window: stage -render_window = vtk.vtkRenderWindow() -render_window.AddRenderer(renderer) -render_window.SetSize(800, 600) - -# interactor: 4th wall -interactor = vtk.vtkRenderWindowInteractor() -interactor.SetRenderWindow(render_window) - -# initialize and start the rendering loop -renderer.ResetCamera() -render_window.Render() -interactor.Start() -``` - -Load and display cell.vtp **polydata** files ~ **no coloring**. - -```python -import vtk - -# load file -vtp_reader = vtk.vtkXMLPolyDataReader() -vtp_reader.SetFileName('output/output00000012_cell.vtp') -vtp_reader.Update() -polydatapipe = vtp_reader.GetOutputPort() - -# mapper: map data to geometry -mapper = vtk.vtkPolyDataMapper() -mapper.SetInputConnection(polydatapipe) - -# actor: map actor to data mapped geometry -actor = vtk.vtkActor() -actor.SetMapper(mapper) - -# renderer: director -renderer = vtk.vtkRenderer() -renderer.SetBackground(1/3, 1/3, 1/3) # gray background -renderer.AddActor(actor) - -# render window: stage -render_window = vtk.vtkRenderWindow() -render_window.AddRenderer(renderer) -render_window.SetSize(800, 600) - -# interactor: 4th wall -interactor = vtk.vtkRenderWindowInteractor() -interactor.SetRenderWindow(render_window) - -# initialize and start the rendering loop -render_window.Render() -interactor.Start() -``` - -Load and display cell.vtp **polydata** files ~ **numerical data coloring**. - -```python -import vtk - -# load file -vtp_reader = vtk.vtkXMLPolyDataReader() -vtp_reader.SetFileName('output/output00000012_cell.vtp') -vtp_reader.Update() -polydata = vtp_reader.GetOutput() - -# get color data -scalar_name = 'pressure' # 'positions_and_radii' -scalar_array = polydata.GetPointData().GetArray(scalar_name) -if scalar_array is None: - raise RuntimeError(f'Scalar array "{scalar_name}" not found.') -scalar_range = scalar_array.GetRange() - -# set color data -polydata.GetPointData().SetActiveScalars(scalar_name) - -# mapper: map data to geometry -mapper = vtk.vtkPolyDataMapper() -mapper.SetInputData(polydata) -mapper.SetScalarRange(scalar_range) # min and max value -mapper.SetScalarModeToUsePointFieldData() -mapper.SelectColorArray(scalar_name) -mapper.SetColorModeToMapScalars() -mapper.ScalarVisibilityOn() - -# actor: map actor to data mapped geometry -actor = vtk.vtkActor() -actor.SetMapper(mapper) - -# actor: map actor to color bar -scalar_bar = vtk.vtkScalarBarActor() -lut = mapper.GetLookupTable() -scalar_bar.SetLookupTable(lut) -scalar_bar.SetTitle(scalar_name) - -# renderer: director -renderer = vtk.vtkRenderer() -renderer.SetBackground(1/3, 1/3, 1/3) # gray background -renderer.AddActor(actor) -renderer.AddActor2D(scalar_bar) # color bar ~ if color mapping - -# render window: stage -render_window = vtk.vtkRenderWindow() -render_window.AddRenderer(renderer) -render_window.SetSize(800, 600) - -# interactor: 4th wall -interactor = vtk.vtkRenderWindowInteractor() -interactor.SetRenderWindow(render_window) - -# initialize and start the rendering loop -render_window.Render() -interactor.Start() +# actor +# scene +# show ``` -Load and display cell.vtp **polydata** files ~ **categorical data coloring**. +Load and display cell.vtp ploydata files. -```python -import matplotlib.pyplot as plt + +```python3 import vtk # load file vtp_reader = vtk.vtkXMLPolyDataReader() vtp_reader.SetFileName('output/output00000012_cell.vtp') vtp_reader.Update() -polydata = vtp_reader.GetOutput() - -# get color data -scalar_name_cat = 'cell_type' -scalar_array_str = polydata.GetPointData().GetAbstractArray(scalar_name_cat) -scalar_name_num = scalar_name_cat + '_numeric' -scalar_array = vtk.vtkIntArray() -scalar_array.SetName(scalar_name_num) - -# unique categoris -es_cat = set() -for i_cat in range(scalar_array_str.GetNumberOfValues()): - s_cat = scalar_array_str.GetValue(i_cat) - es_cat.add(s_cat) - -# index unique categories -dsi_cat = {} -for i_cat, s_cat in enumerate(sorted(es_cat)): - dsi_cat.update({s_cat: i_cat}) - -# map each element of the array to the category index -for i in range(scalar_array_str.GetNumberOfValues()): - s_cat = scalar_array_str.GetValue(i) - scalar_array.InsertNextValue(dsi_cat[s_cat]) -scalar_range = scalar_array.GetRange() - -# set color data -polydata.GetPointData().AddArray(scalar_array) -polydata.GetPointData().SetActiveScalars(scalar_name_num) - -# look up table -lut = vtk.vtkLookupTable() -lut.SetNumberOfTableValues(len(es_cat)) -lut.Build() -cmap = plt.get_cmap('turbo', len(es_cat)) -for s_cat, i_cat in dsi_cat.items(): - r, g, b, _ = cmap(i_cat) - lut.SetTableValue(i_cat, r, g, b, 1.0) - lut.SetAnnotation(i_cat, s_cat) - -# mapper: map data to geometry -mapper = vtk.vtkPolyDataMapper() -mapper.SetInputData(polydata) -mapper.SetLookupTable(lut) -mapper.SetScalarRange(scalar_range) # min and max value -mapper.SetScalarModeToUsePointFieldData() -mapper.SelectColorArray(scalar_name_num) -mapper.SetColorModeToMapScalars() -mapper.ScalarVisibilityOn() - -# actor: map actor to data mapped geometry -actor = vtk.vtkActor() -actor.SetMapper(mapper) - -# actor: color bar -scalar_bar = vtk.vtkScalarBarActor() -scalar_bar.SetLookupTable(lut) -scalar_bar.SetTitle(scalar_name_cat) -scalar_bar.DrawTickLabelsOff() - -# renderer: director -renderer = vtk.vtkRenderer() -renderer.SetBackground(1/3, 1/3, 1/3) # gray background -renderer.AddActor(actor) -renderer.AddActor2D(scalar_bar) # color bar ~ if color mapping -# render window: stage -render_window = vtk.vtkRenderWindow() -render_window.AddRenderer(renderer) -render_window.SetSize(800, 600) - -# interactor: 4th wall -interactor = vtk.vtkRenderWindowInteractor() -interactor.SetRenderWindow(render_window) - -# initialize and start the rendering loop -render_window.Render() -interactor.Start() +# actor +# scene +# show ``` Official documentation: @@ -373,38 +56,6 @@ Official documentation: + https://examples.vtk.org/site/PythonicAPIComments/ -### How to load and visualize a vtk file with the pyvista python library - -Installation. - -```bash -pip3 install pyvista -``` - -Load, color, and display **cell.vtr rectilinear grid** files. - -```python -import pyvista as pv - -grid = pv.read('output/output00000012_conc.vtr') -grid.plot(scalars='oxygen', cmap='turbo', opacity=1/3) # 'water' -``` - -Load, color, and display **cell.vtp polydata** files. - -```python -import pyvista as pv - -poly = pv.read('output/output00000012_cell.vtp') -poly.plot(scalars='cell_type', cmap='turbo', opacity=2/3) # 'pressure', 'positions_and_radii' -``` - -Official documentation: -+ https://docs.pyvista.org/ - -Hasta la vista, baby! - - ### How to load and visualize a vtk file with the fury python library Installation. @@ -413,12 +64,10 @@ Installation. pip3 install fury ``` -Unfortunately, **conc.vtr rectilinear grid** files are not yet supported (v0.12.0 and 2.0.0a1); conc.vtr files cannot be loaded. -Although this issue might be resolved any time now. -+ https://github.com/fury-gl/fury/issues/971 - + +Unfortunately, rectiliniar grid files are not supported, conc.vtr files cannot be loaded. -Load and display **cell.vtp polydata** files ~ **no coloring**. +Load and display cell.vtp ploydata files. ```python import fury @@ -433,7 +82,7 @@ actor = fury.get_actor_from_polydata(v_cell) scene = fury.window.Scene() scene.add(actor) -# initialize and start the rendering loop +# show showm = fury.window.ShowManager(scene) showm.initialize() showm.start() diff --git a/man/TUTORIAL_r.md b/man/TUTORIAL_r.md index 33d1d9a..5dac49f 100644 --- a/man/TUTORIAL_r.md +++ b/man/TUTORIAL_r.md @@ -136,7 +136,7 @@ pcdl_get_anndata output/ We will use the [schard](https://github.com/cellgeni/schard) R package to translate the h5ad file into R data structures that can be analyzed by [singlecellexperiment](https://bioconductor.org/packages/release/bioc/html/SingleCellExperiment.html) -and [seurat](https://satijalab.org/seurat/). +and [seurat](https://satijalab.org/seurat/). Special thanks to Marcello Hurtado from the Pancald Lab, who told me that such translation software exist! diff --git a/man/docstring/mcds.__init__.md b/man/docstring/mcds.__init__.md index f760d95..7fdb6ec 100644 --- a/man/docstring/mcds.__init__.md +++ b/man/docstring/mcds.__init__.md @@ -11,19 +11,21 @@ relative or absolute path to the directory where the PhysiCell output files are stored. - custom_dtype: dictionary; default is {} - variable to specify custom_data variable types other than - floats (namely: int, bool, str) like this: {var: dtype, ...}. + custom_data_type: dictionary; default is {} + variable to specify custom_data variable types + besides float (int, bool, str) like this: {var: dtype, ...}. downstream float and int will be handled as numeric, bool as Boolean, and str as categorical data. microenv: boole; default True should the microenvironment data be loaded? - setting microenv to False will use less memory and speed up processing. + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. graph: boole; default True - should the graphs, like cell_neighbor_graph.txt, be loaded? - setting graph to False will use less memory and speed up processing. + should neighbor garph, attached graph, and spring attached graph + be loaded? setting graph to False will use less memory and + speed up processing. physiboss: boole; default True should physiboss state data be loaded, if found? @@ -36,7 +38,7 @@ set to None or False if the xml file is missing! verbose: boole; default True - setting verbose to False for less text output, while processing. + setting verbose to False for less text output while processing. ``` @@ -49,13 +51,12 @@ ## description: ``` - TimeStep.__init__ will generate a class instance with a - dictionary of dictionaries data structure that contains all - output from a single PhysiCell model time step. furthermore, - this class, and as such it's instances, offers functions - to access the stored data. - the code assumes that all related output files are stored in - the same directory. data is loaded by reading the xml file - for a particular time step and the therein referenced files. + TimeStep.__init__ will call pyMCDS.__init__ that generates a mcds + class instance, a dictionary of dictionaries data structure that + contains all output from a single PhysiCell model time step. + furthermore, the mcds object offers functions to access the stored data. + the code assumes that all related output files are stored + in the same directory. data is loaded by reading the xml file for + a particular time step and the therein referenced files. ``` \ No newline at end of file diff --git a/man/docstring/mcds.get_cell_attribute_list.md b/man/docstring/mcds.get_cell_attribute_list.md deleted file mode 100644 index cf4cdc1..0000000 --- a/man/docstring/mcds.get_cell_attribute_list.md +++ /dev/null @@ -1,21 +0,0 @@ -# mcds.get_cell_attribute_list() - - -## input: -``` - -``` - -## output: -``` - ls_cellattr: list of strings - alphabetically ordered list of all tracked cell attributes. - -``` - -## description: -``` - function returns a list with all cell attribute labels, - alphabetically ordered. - -``` \ No newline at end of file diff --git a/man/docstring/mcds.get_cell_df_at.md b/man/docstring/mcds.get_cell_df_at.md new file mode 100644 index 0000000..03dd1ed --- /dev/null +++ b/man/docstring/mcds.get_cell_df_at.md @@ -0,0 +1,47 @@ +# mcds.get_cell_df_at() + + +## input: +``` + x: floating point number + position x-coordinate. + + y: floating point number + position y-coordinate. + + z: floating point number; default is 0 + position z-coordinate. + + values: integer; default is 1 + minimal number of values a variable has to have to be outputted. + variables that have only 1 state carry no information. + None is a state too. + + drop: set of strings; default is an empty set + set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. + Attention: when the keep parameter is given, then + the drop parameter has to be an empty set! + + keep: set of strings; default is an empty set + set of column labels to be kept in the dataframe. + set values=1 to be sure that all variables are kept. + don't worry: essential columns like ID, coordinates + and time will always be kept. + +``` + +## output: +``` + df_voxel: pandas dataframe + x, y, z voxel filtered cell dataframe. + +``` + +## description: +``` + function returns the cell dataframe for the voxel + specified with the x, y, z position coordinate. + +``` \ No newline at end of file diff --git a/man/docstring/mcds.get_concentration.md b/man/docstring/mcds.get_concentration.md new file mode 100644 index 0000000..03e6cef --- /dev/null +++ b/man/docstring/mcds.get_concentration.md @@ -0,0 +1,36 @@ +# mcds.get_concentration() + + +## input: +``` + substrate: string + substrate name. + + z_slice: floating point number; default is None + z-axis position to slice a 2D xy-plain out of the + 3D substrate concentration mesh. if None the + whole 3D mesh will be returned. + + halt: boolean; default is False + should program execution break or just spit out a warning, + if z_slice position is not an exact mesh center coordinate? + if False, z_slice will be adjusted to the nearest + mesh center value, the smaller one, if the coordinate + lies on a saddle point. + +``` + +## output: +``` + ar_conc: numpy array of floating point numbers + substrate concentration meshgrid or xy-plain slice + through the meshgrid. + +``` + +## description: +``` + function returns the concentration meshgrid, or a xy-plain slice + out of the whole meshgrid, for the specified chemical species. + +``` \ No newline at end of file diff --git a/man/docstring/mcds.get_concentration_at.md b/man/docstring/mcds.get_concentration_at.md new file mode 100644 index 0000000..88d306a --- /dev/null +++ b/man/docstring/mcds.get_concentration_at.md @@ -0,0 +1,31 @@ +# mcds.get_concentration_at() + + +## input: +``` + x: floating point number + position x-coordinate. + + y: floating point number + position y-coordinate. + + z: floating point number; default is 0 + position z-coordinate. + +``` + +## output: +``` + ar_concs: numpy array of floating point numbers + array of substrate concentrations in the order + given by get_substrate_list(). + +``` + +## description: +``` + function return concentrations of each chemical species + inside a particular voxel that contains the point specified + in the arguments. + +``` \ No newline at end of file diff --git a/man/docstring/mcds.get_spring_graph_dict.md b/man/docstring/mcds.get_spring_graph_dict.md deleted file mode 100644 index 1227ae6..0000000 --- a/man/docstring/mcds.get_spring_graph_dict.md +++ /dev/null @@ -1,20 +0,0 @@ -# mcds.get_spring_graph_dict() - - -## input: -``` - -``` - -## output: -``` - dei_graph: dictionary of sets of integers - maps each cell ID to the attached connected cell IDs. - -``` - -## description: -``` - function returns the attached spring cell graph as a dictionary object. - -``` \ No newline at end of file diff --git a/man/docstring/mcds.make_cell_vtk.md b/man/docstring/mcds.make_cell_vtk.md index 53e2da0..3784fe3 100644 --- a/man/docstring/mcds.make_cell_vtk.md +++ b/man/docstring/mcds.make_cell_vtk.md @@ -6,6 +6,9 @@ attribute: list of strings; default is ['cell_type'] column name within cell dataframe. + visualize: boolean; default is True + additionally, visualize cells using vtk renderer. + ``` ## output: diff --git a/man/docstring/mcds.make_conc_vtk.md b/man/docstring/mcds.make_conc_vtk.md index dc40043..660500b 100644 --- a/man/docstring/mcds.make_conc_vtk.md +++ b/man/docstring/mcds.make_conc_vtk.md @@ -3,6 +3,8 @@ ## input: ``` + visualize: boolean; default is True + additionally, visualize cells using vtk renderer. ``` diff --git a/man/docstring/mcds.make_ome_tiff.md b/man/docstring/mcds.make_ome_tiff.md deleted file mode 100644 index 9d78c3a..0000000 --- a/man/docstring/mcds.make_ome_tiff.md +++ /dev/null @@ -1,50 +0,0 @@ -# mcds.make_ome_tiff() - - -## input: -``` - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be - specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape czyx is the output. - -``` - -## output: -``` - a_tczyx_img: numpy array or ome tiff file. - -``` - -## description: -``` - function to transform chosen mcds output into an 1[um] spaced - czyx (channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - an ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - -``` \ No newline at end of file diff --git a/man/docstring/mcds.plot_contour.md b/man/docstring/mcds.plot_contour.md index 8b2dd3a..1b94656 100644 --- a/man/docstring/mcds.plot_contour.md +++ b/man/docstring/mcds.plot_contour.md @@ -62,6 +62,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -70,11 +75,6 @@ or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the matplotlib contour and contourf function. - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html - ``` ## output: diff --git a/man/docstring/mcds.plot_scatter.md b/man/docstring/mcds.plot_scatter.md index 332fbbd..1c81c19 100644 --- a/man/docstring/mcds.plot_scatter.md +++ b/man/docstring/mcds.plot_scatter.md @@ -18,7 +18,7 @@ depending on the focus column variable dtype, default extracts labels or min and max values from data. - alpha: floating point number; default is 1.0 + alpha: floating point number; default is 1 alpha channel transparency value between 1 (not transparent at all) and 0 (totally transparent). @@ -67,6 +67,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -75,10 +80,6 @@ or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the pandas dataframe plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html - ``` ## output: diff --git a/man/docstring/mcdsts.__init__.md b/man/docstring/mcdsts.__init__.md index fade05a..1581c00 100644 --- a/man/docstring/mcdsts.__init__.md +++ b/man/docstring/mcdsts.__init__.md @@ -9,7 +9,7 @@ custom_data_type: dictionary; default is {} variable to specify custom_data variable types - other than float (int, bool, str) like this: {var: dtype, ...}. + besides float (int, bool, str) like this: {var: dtype, ...}. downstream float and int will be handled as numeric, bool as Boolean, and str as categorical data. @@ -19,11 +19,13 @@ microenv: boole; default True should the microenvironment data be loaded? - setting microenv to False will use less memory and speed up processing. + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. graph: boole; default True - should the graphs, like cell_neighbor_graph.txt, be loaded? - setting graph to False will use less memory and speed up processing. + should neighbor garph, attached graph, and spring attached graph + be loaded? setting graph to False will use less memory and + speed up processing. physiboss: boole; default True should physiboss state data be loaded, if found? @@ -36,13 +38,13 @@ set to None or False if the xml file is missing! verbose: boole; default True - setting verbose to False for less text output, while processing. + setting verbose to False for less text output while processing. ``` ## output: ``` - mcdsts: TimeSeries class instance + mcdsts: pyMCDSts class instance this instance offers functions to process all stored time steps from a simulation. @@ -50,7 +52,8 @@ ## description: ``` - TimeSeries.__init__ generates a class instance the instance offers - functions to process all time steps in the output_path directory. + TimeSeries.__init__ will call pyMCDSts.__init__ that generates a mcdsts + class instance. this instance offers functions to process all time steps + in the output_path directory. ``` \ No newline at end of file diff --git a/man/docstring/mcdsts.get_cell_attribute.md b/man/docstring/mcdsts.get_cell_attribute.md index fb7ce0d..1db519d 100644 --- a/man/docstring/mcdsts.get_cell_attribute.md +++ b/man/docstring/mcdsts.get_cell_attribute.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have diff --git a/man/docstring/mcdsts.get_cell_df.md b/man/docstring/mcdsts.get_cell_df.md index b9313ec..b15b4ab 100644 --- a/man/docstring/mcdsts.get_cell_df.md +++ b/man/docstring/mcdsts.get_cell_df.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have to be outputted. diff --git a/man/docstring/mcdsts.get_conc_attribute.md b/man/docstring/mcdsts.get_conc_attribute.md index fabb81e..1642975 100644 --- a/man/docstring/mcdsts.get_conc_attribute.md +++ b/man/docstring/mcdsts.get_conc_attribute.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have diff --git a/man/docstring/mcdsts.get_conc_df.md b/man/docstring/mcdsts.get_conc_df.md index fcc0013..74e5d3e 100644 --- a/man/docstring/mcdsts.get_conc_df.md +++ b/man/docstring/mcdsts.get_conc_df.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have to be outputted. diff --git a/man/docstring/mcdsts.get_mcds_list.md b/man/docstring/mcdsts.get_mcds_list.md index 10db3d9..b018caf 100644 --- a/man/docstring/mcdsts.get_mcds_list.md +++ b/man/docstring/mcdsts.get_mcds_list.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. ``` diff --git a/man/docstring/mcdsts.get_xmlfile_list.md b/man/docstring/mcdsts.get_xmlfile_list.md index 50ea8c8..4602b9b 100644 --- a/man/docstring/mcdsts.get_xmlfile_list.md +++ b/man/docstring/mcdsts.get_xmlfile_list.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. ``` diff --git a/man/docstring/mcdsts.make_cell_vtk.md b/man/docstring/mcdsts.make_cell_vtk.md index 1de2295..9c3adcc 100644 --- a/man/docstring/mcdsts.make_cell_vtk.md +++ b/man/docstring/mcdsts.make_cell_vtk.md @@ -6,6 +6,9 @@ attribute: list of strings; default is ['cell_type'] column name within cell dataframe. + visualize: boolean; default is False + additionally, visualize cells using vtk renderer. + ``` ## output: diff --git a/man/docstring/mcdsts.make_conc_vtk.md b/man/docstring/mcdsts.make_conc_vtk.md index 23a854c..753e1e7 100644 --- a/man/docstring/mcdsts.make_conc_vtk.md +++ b/man/docstring/mcdsts.make_conc_vtk.md @@ -3,6 +3,8 @@ ## input: ``` + visualize: boolean; default is False + additionally, visualize cells using vtk renderer. ``` diff --git a/man/docstring/mcdsts.make_graph_gml.md b/man/docstring/mcdsts.make_graph_gml.md index c4e4bb3..c28b163 100644 --- a/man/docstring/mcdsts.make_graph_gml.md +++ b/man/docstring/mcdsts.make_graph_gml.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDS class instance. graph_type: string to specify which physicell output data should be processed. diff --git a/man/docstring/mcdsts.make_ome_tiff.md b/man/docstring/mcdsts.make_ome_tiff.md deleted file mode 100644 index f9b9ca9..0000000 --- a/man/docstring/mcdsts.make_ome_tiff.md +++ /dev/null @@ -1,55 +0,0 @@ -# mcdsts.make_ome_tiff() - - -## input: -``` - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape tczyx is the output. - - collapse: boole; default True - should all mcds time steps from the time series be collapsed - into one ome tiff file (numpy array), - or an ome tiff file (numpy array) for each time step? - -``` - -## output: -``` - a_tczyx_img: numpy array or ome tiff file. - - -``` - -## description: -``` - function to transform chosen mcdsts output into an 1[um] spaced - tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - a ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - -``` \ No newline at end of file diff --git a/man/docstring/mcdsts.plot_contour.md b/man/docstring/mcdsts.plot_contour.md index 659e205..9078146 100644 --- a/man/docstring/mcdsts.plot_contour.md +++ b/man/docstring/mcdsts.plot_contour.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance + self: pyMCDSts class instance focus: string column name within conc dataframe, for example. @@ -54,6 +54,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -62,11 +67,6 @@ or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the matplotlib contour and contourf function. - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html - ``` ## output: diff --git a/man/docstring/mcdsts.plot_scatter.md b/man/docstring/mcdsts.plot_scatter.md index f284c0d..03f0049 100644 --- a/man/docstring/mcdsts.plot_scatter.md +++ b/man/docstring/mcdsts.plot_scatter.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance + self: pyMCDSts class instance focus: string; default is 'cell_type' column name within cell dataframe. @@ -64,6 +64,11 @@ None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -72,10 +77,6 @@ or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the pandas dataframe plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame - ``` ## output: diff --git a/man/docstring/mcdsts.plot_timeseries.md b/man/docstring/mcdsts.plot_timeseries.md index 09385d5..67906fe 100644 --- a/man/docstring/mcdsts.plot_timeseries.md +++ b/man/docstring/mcdsts.plot_timeseries.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance + self: pyMCDSts class instance focus_cat: string; default is None categorical or boolean data column within dataframe specified under frame. @@ -101,10 +101,6 @@ figure background color. only relevant if ext not is None. - **kwargs: possible additional keyword arguments input, - handled by the pandas series plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.plot.html - ``` ## output: diff --git a/man/docstring/mcdsts.read_mcds.md b/man/docstring/mcdsts.read_mcds.md index 8167f20..61fe678 100644 --- a/man/docstring/mcdsts.read_mcds.md +++ b/man/docstring/mcdsts.read_mcds.md @@ -3,7 +3,7 @@ ## input: ``` - self: TimeSeries class instance. + self: pyMCDSts class instance. xmlfile_list: list of strings; default None list of physicell output*.xml strings. @@ -19,6 +19,6 @@ ## description: ``` the function returns a list of mcds objects loaded by - TimeStep calls. + pyMCDS calls. ``` \ No newline at end of file diff --git a/man/docstring/pcdl.render_neuroglancer.md b/man/docstring/pcdl.render_neuroglancer.md deleted file mode 100644 index 5bf8132..0000000 --- a/man/docstring/pcdl.render_neuroglancer.md +++ /dev/null @@ -1,33 +0,0 @@ -# mcdsts.render_neuroglancer('path/to/ome.tiff') - - -## input: -``` - tiffpathfile: string. - path to ome tiff file. - - timestep: integer, default is 0. - variable to specify the specific time step to render. - useful for time series ome.tiff files. - the default is compatible with single time step ome.tiff files. - - intensity_cmap: string; default is 'gray'. - matlab color map label, used to display expression intensity values. - if None, no intensity layers will be generated. - + https://matplotlib.org/stable/users/explain/colors/colormaps.html - -``` - -## output: -``` - viewer: local url where the loaded, neuroglancer rendered ome tiff file - can be viewed. - -``` - -## description: -``` - function to load a time step from an ome tiff files, generated - with make_ome_tiff, into neuroglancer. - -``` \ No newline at end of file diff --git a/man/docstring/pcdl_get_anndata.md b/man/docstring/pcdl_get_anndata.md index cb59d2f..54e4763 100644 --- a/man/docstring/pcdl_get_anndata.md +++ b/man/docstring/pcdl_get_anndata.md @@ -28,8 +28,9 @@ options: empty string. --microenv MICROENV should the microenvironment be extracted and loaded into the anndata object? setting microenv to False - will use less memory and speed up processing. default - is True. + will use less memory and speed up processing, similar + to the original pyMCDS_cells.py script. default is + True. --graph GRAPH should neighbor graph, attach graph, and attached spring graph be extracted and loaded into the anndata object? default is True. diff --git a/man/docstring/pcdl_get_cell_attribute.md b/man/docstring/pcdl_get_cell_attribute.md index 2b1eb98..63628ab 100644 --- a/man/docstring/pcdl_get_cell_attribute.md +++ b/man/docstring/pcdl_get_cell_attribute.md @@ -34,7 +34,8 @@ options: empty string. --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into df_cell dataframe? default is True. diff --git a/man/docstring/pcdl_get_cell_attribute_list.md b/man/docstring/pcdl_get_cell_attribute_list.md deleted file mode 100644 index 8acf2f2..0000000 --- a/man/docstring/pcdl_get_cell_attribute_list.md +++ /dev/null @@ -1,33 +0,0 @@ -``` -usage: pcdl_get_cell_attribute_list [-h] [--microenv MICROENV] - [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [path] - -this function is returns a list with all cell attribute labels, alphabetically -ordered. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - -options: - -h, --help show this help message and exit - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to True for more text output, while - processing. default is False. - -homepage: https://github.com/elmbeech/physicelldataloader -``` diff --git a/man/docstring/pcdl_get_cell_df.md b/man/docstring/pcdl_get_cell_df.md index 2b31c79..f810166 100644 --- a/man/docstring/pcdl_get_cell_df.md +++ b/man/docstring/pcdl_get_cell_df.md @@ -20,7 +20,8 @@ options: -h, --help show this help message and exit --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True. diff --git a/man/docstring/pcdl_get_unit_dict.md b/man/docstring/pcdl_get_unit_dict.md index 710b081..3f52826 100644 --- a/man/docstring/pcdl_get_unit_dict.md +++ b/man/docstring/pcdl_get_unit_dict.md @@ -13,7 +13,8 @@ options: -h, --help show this help message and exit --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --settingxml SETTINGXML the settings.xml that is loaded, from which the cell type ID label mapping, is extracted, if this diff --git a/man/docstring/pcdl_make_cell_vtk.md b/man/docstring/pcdl_make_cell_vtk.md index 0043ac5..a067d57 100644 --- a/man/docstring/pcdl_make_cell_vtk.md +++ b/man/docstring/pcdl_make_cell_vtk.md @@ -26,7 +26,8 @@ options: empty string. --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True. diff --git a/man/docstring/pcdl_make_graph_gml.md b/man/docstring/pcdl_make_graph_gml.md index c47ebf9..20dfbd0 100644 --- a/man/docstring/pcdl_make_graph_gml.md +++ b/man/docstring/pcdl_make_graph_gml.md @@ -34,7 +34,8 @@ options: empty string. --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True. diff --git a/man/docstring/pcdl_make_ome_tiff.md b/man/docstring/pcdl_make_ome_tiff.md deleted file mode 100644 index 50e7d6c..0000000 --- a/man/docstring/pcdl_make_ome_tiff.md +++ /dev/null @@ -1,55 +0,0 @@ -``` -usage: pcdl_make_ome_tiff [-h] [--microenv MICROENV] [--physiboss PHYSIBOSS] - [--settingxml SETTINGXML] [-v VERBOSE] - [--conc_cutoff [CONC_CUTOFF ...]] - [--focus FOCUS [FOCUS ...]] [--collapse COLLAPSE] - [path] [cell_attribute] - -function to transform chosen mcdsts output into an 1[um] spaced tczyx (time, -channel, z-axis, y-axis, x-axis) ome tiff file, one substrate or cell_type per -channel. the ome tiff file format can for example be read by the napari -(https://napari.org/stable/) or fiji imagej (https://fiji.sc/) software. - -positional arguments: - path path to the PhysiCell output directory or a - outputnnnnnnnn.xml file. default is . . - cell_attribute mcds.get_cell_df dataframe column, used for - cell_attribute. the column data type has to be numeric - (bool, int, float) and cannot be string. the result - will be stored as 32 bit float. default is ID, with - will result in a segmentation mask. - -options: - -h, --help show this help message and exit - --microenv MICROENV should the microenvironment data be loaded? setting - microenv to False will use less memory and speed up - processing. default is True. - --physiboss PHYSIBOSS - if found, should physiboss state data be extracted and - loaded into the df_cell dataframe? default is True. - --settingxml SETTINGXML - the settings.xml that is loaded, from which the cell - type ID label mapping, is extracted, if this - information is not found in the output xml file. set - to None or False if the xml file is missing! default - is PhysiCell_settings.xml. - -v VERBOSE, --verbose VERBOSE - setting verbose to False for less text output, while - processing. default is True. - --conc_cutoff [CONC_CUTOFF ...] - if a contour from a substrate not should be cut by - greater than zero (shifted to integer 1), another - cutoff value can be specified here like this: - substarte:value substrate:value substarte:value . - default is and empty string. - --focus FOCUS [FOCUS ...] - set of substrate and cell_type names to specify what - will be translated into ome tiff format. if None, all - substrates and cell types will be processed. default - is a None. - --collapse COLLAPSE should all mcds time steps from the time series be - collapsed into one big ome.tiff, or a many ome.tiff, - one ome.tiff for each time step?, default is True. - -homepage: https://github.com/elmbeech/physicelldataloader -``` diff --git a/man/docstring/pcdl_plot_contour.md b/man/docstring/pcdl_plot_contour.md index c368910..b5cb596 100644 --- a/man/docstring/pcdl_plot_contour.md +++ b/man/docstring/pcdl_plot_contour.md @@ -4,7 +4,8 @@ usage: pcdl_plot_contour [-h] [-v VERBOSE] [--z_slice Z_SLICE] [--fill FILL] [--cmap CMAP] [--title TITLE] [--grid GRID] [--xlim XLIM [XLIM ...]] [--ylim YLIM [YLIM ...]] [--xyequal XYEQUAL] - [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] [--ext EXT] + [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] + [--directory DIRECTORY] [--ext EXT] [--figbgcolor FIGBGCOLOR] [path] [focus] @@ -53,6 +54,11 @@ options: be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None. + --directory DIRECTORY + if none, a meaningful output directory name will be + generated, based on focus and z_slice parameters, else + the resulting plots will be moved to the explicit name + directory. --ext EXT output image format. possible formats are jpeg, png, and tiff. default is jpeg. --figbgcolor FIGBGCOLOR diff --git a/man/docstring/pcdl_plot_scatter.md b/man/docstring/pcdl_plot_scatter.md index b97563b..11b3f12 100644 --- a/man/docstring/pcdl_plot_scatter.md +++ b/man/docstring/pcdl_plot_scatter.md @@ -7,7 +7,8 @@ usage: pcdl_plot_scatter [-h] [--custom_data_type [CUSTOM_DATA_TYPE ...]] [--grid GRID] [--legend_loc LEGEND_LOC] [--xlim XLIM [XLIM ...]] [--ylim YLIM [YLIM ...]] [--xyequal XYEQUAL] [--s S] - [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] [--ext EXT] + [--figsizepx FIGSIZEPX [FIGSIZEPX ...]] + [--directory DIRECTORY] [--ext EXT] [--figbgcolor FIGBGCOLOR] [path] [focus] @@ -30,7 +31,8 @@ options: empty string. --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True. @@ -84,6 +86,11 @@ options: be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None. + --directory DIRECTORY + if none, a meaningful output directory name will be + generated, based on focus and z_slice parameters, else + the resulting plots will be moved to the explicit name + directory. --ext EXT output image format. possible formats are jpeg, png, and tiff. default is jpeg. --figbgcolor FIGBGCOLOR diff --git a/man/docstring/pcdl_plot_timeseries.md b/man/docstring/pcdl_plot_timeseries.md index e93a2ca..c529514 100644 --- a/man/docstring/pcdl_plot_timeseries.md +++ b/man/docstring/pcdl_plot_timeseries.md @@ -40,7 +40,8 @@ options: empty string. --microenv MICROENV should the microenvironment data be loaded? setting microenv to False will use less memory and speed up - processing. default is True. + processing, similar to the original pyMCDS_cells.py + script. default is True. --physiboss PHYSIBOSS if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True. diff --git a/man/docstring/pcdl_render_neuroglancer.md b/man/docstring/pcdl_render_neuroglancer.md deleted file mode 100644 index 939d206..0000000 --- a/man/docstring/pcdl_render_neuroglancer.md +++ /dev/null @@ -1,23 +0,0 @@ -``` -usage: pcdl_render_neuroglancer [-h] [--intensity_cmap INTENSITY_CMAP] - [tiffpathfile] [timestep] - -function to load a time step from an ome tiff files, generated with -make_ome_tiff, into neuroglancer. - -positional arguments: - tiffpathfile path to ome tiff file. - timestep time step, within a possibly collapsed ome tiff file, - to render. the default will work with single time step - ome tiff files. - -options: - -h, --help show this help message and exit - --intensity_cmap INTENSITY_CMAP - matlab color map label, used to display expression - intensity values. if None, no intensity layers will be - generated. https://matplotlib.org/stable/users/explain - /colors/colormaps.html - -homepage: https://github.com/elmbeech/physicelldataloader -``` diff --git a/man/img/physicelldataloafder_github_qr.png b/man/img/physicelldataloafder_github_qr.png deleted file mode 100644 index 86d3d920953f7ab16d2590332a1e339ae8045f55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132163 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

(=I{kQ0g&XU_J1e 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 B9U4t4wyBuEIdKf=Yk0Ii40I@u=6N@$+#$`= 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 B->Lur diff --git a/man/scarab.py b/man/scarab.py index 5ed20a8..a138386 100644 --- a/man/scarab.py +++ b/man/scarab.py @@ -99,13 +99,13 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): # load data mcds = pcdl.TimeStep( 'pcdl/output_2d/output00000000.xml', - #custom_data_type={'oncoprotein': str}, + custom_data_type={'oncoprotein': str}, verbose=False, ) mcdsts = pcdl.TimeSeries( 'pcdl/output_2d/', - #custom_data_type={'oncoprotein': str}, + custom_data_type={'oncoprotein': str}, verbose=False, ) @@ -121,7 +121,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ) -# write TimeStep initialize function makdown files +# write pyMCDS initialize function makdown files docstring_md( s_function = 'mcds.__init__', ls_doc = pcdl.TimeStep.__init__.__doc__.split('\n'), @@ -136,7 +136,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.set_verbose_true.__doc__.split('\n'), ) -# write TimeStep metadata function makdown files +# write pyMCDS metadata function makdown files docstring_md( s_function = 'mcds.get_multicellds_version', ls_doc = pcdl.TimeStep.get_multicellds_version.__doc__.split('\n'), @@ -163,7 +163,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.get_unit_dict.__doc__.split('\n'), ) -# write TimeStep mesh function markdown files +# write pyMCDS mesh function markdown files # range and axis docstring_md( s_function = 'mcds.get_voxel_ijk_range', @@ -224,7 +224,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.get_voxel_ijk.__doc__.split('\n'), ) -# write TimeStep microenv function markdown files +# write pyMCDS microenv function markdown files docstring_md( s_function = 'mcds.get_substrate_list', ls_doc = pcdl.TimeStep.get_substrate_list.__doc__.split('\n'), @@ -237,6 +237,14 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): s_function = 'mcds.get_substrate_df', ls_doc = pcdl.TimeStep.get_substrate_df.__doc__.split('\n'), ) +docstring_md( + s_function = 'mcds.get_concentration', + ls_doc = pcdl.TimeStep.get_concentration.__doc__.split('\n'), +) +docstring_md( + s_function = 'mcds.get_concentration_at', + ls_doc = pcdl.TimeStep.get_concentration_at.__doc__.split('\n'), +) docstring_md( s_function = 'mcds.get_conc_df', ls_doc = pcdl.TimeStep.get_conc_df.__doc__.split('\n'), @@ -250,7 +258,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.make_conc_vtk.__doc__.split('\n'), ) -# write TimeStep cell agent function markdown files +# write pyMCDS cell agent function markdown files docstring_md( s_function = 'mcds.get_celltype_list', ls_doc = pcdl.TimeStep.get_celltype_list.__doc__.split('\n'), @@ -264,8 +272,8 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.get_cell_df.__doc__.split('\n'), ) docstring_md( - s_function = 'mcds.get_cell_attribute_list', - ls_doc = pcdl.TimeStep.get_cell_attribute_list.__doc__.split('\n'), + s_function = 'mcds.get_cell_df_at', + ls_doc = pcdl.TimeStep.get_cell_df_at.__doc__.split('\n'), ) docstring_md( s_function = 'mcds.plot_scatter', @@ -280,7 +288,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeStep.get_anndata.__doc__.split('\n'), ) -# write TimeStep graph function markdown files +# write pyMCDS graph function markdown files docstring_md( s_function = 'mcds.get_attached_graph_dict', ls_doc = pcdl.TimeStep.get_attached_graph_dict.__doc__.split('\n'), @@ -289,28 +297,12 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): s_function = 'mcds.get_neighbor_graph_dict', ls_doc = pcdl.TimeStep.get_neighbor_graph_dict.__doc__.split('\n'), ) -docstring_md( - s_function = 'mcds.get_spring_graph_dict', - ls_doc = pcdl.TimeStep.get_spring_graph_dict.__doc__.split('\n'), -) docstring_md( s_function = 'mcds.make_graph_gml', ls_doc = pcdl.TimeStep.make_graph_gml.__doc__.split('\n'), ) -# write TimeStep microenvironment and cells function markdown files -docstring_md( - s_function = 'mcds.make_ome_tiff', - ls_doc = pcdl.TimeStep.make_ome_tiff.__doc__.split('\n'), -) - -docstring_md( - s_function = 'pcdl.render_neuroglancer', - ls_doc = pcdl.render_neuroglancer.__doc__.split('\n'), - s_header = "mcds.render_neuroglancer('path/to/ome.tiff')" -) - -# write TimeStep internal function makdown files +# write pyMCDS internal function makdown files docstring_md( s_function = 'pcdl.scaler', ls_doc = pcdl.scaler.__doc__.split('\n'), @@ -321,7 +313,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ) -# write TimeSeries initialize function makdown files +# write pyMCDSts initialize function makdown files docstring_md( s_function = 'mcdsts.__init__', ls_doc = pcdl.TimeSeries.__init__.__doc__.split('\n'), @@ -353,7 +345,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeSeries.set_verbose_true.__doc__.split('\n'), ) -# write TimeSeries microenv function makdown files +# write pyMCDSts microenv function makdown files docstring_md( s_function = 'mcdsts.get_conc_df', ls_doc = pcdl.TimeSeries.get_conc_df.__doc__.split('\n'), @@ -371,7 +363,7 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeSeries.make_conc_vtk.__doc__.split('\n'), ) -# write TimeSeries cell agent function makdown files +# write pyMCDSts cell agent function makdown files docstring_md( s_function = 'mcdsts.get_cell_df', ls_doc = pcdl.TimeSeries.get_cell_df.__doc__.split('\n'), @@ -393,28 +385,19 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): ls_doc = pcdl.TimeSeries.get_anndata.__doc__.split('\n'), ) -# write TimeSeries graph function makdown files +# write pyMCDSts graph function makdown files docstring_md( s_function = 'mcdsts.make_graph_gml', ls_doc = pcdl.TimeSeries.make_graph_gml.__doc__.split('\n'), ) -# write TimeSeries microenvironment and cells function makdown files -docstring_md( - s_function = 'mcdsts.make_ome_tiff', - ls_doc = pcdl.TimeSeries.make_ome_tiff.__doc__.split('\n'), -) -docstring_md( - s_function = 'pcdl.render_neuroglancer', - ls_doc = pcdl.render_neuroglancer.__doc__.split('\n'), - s_header = "mcdsts.render_neuroglancer('path/to/ome.tiff')" -) +# write pyMCDSts microenvironment and cells function makdown files docstring_md( s_function = 'mcdsts.plot_timeseries', ls_doc = pcdl.TimeSeries.plot_timeseries.__doc__.split('\n'), ) -# write TimeSeries making movies function markdown files +# write pyMCDSts making movies function markdown files docstring_md( s_function = 'pcdl.make_gif', ls_doc = pcdl.make_gif.__doc__.split('\n'), @@ -439,7 +422,6 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): help_md(s_command='pcdl_make_conc_vtk') # cell agent help_md(s_command='pcdl_get_celltype_list') -help_md(s_command='pcdl_get_cell_attribute_list') help_md(s_command='pcdl_get_cell_attribute') help_md(s_command='pcdl_get_cell_df') help_md(s_command='pcdl_get_anndata') @@ -448,8 +430,6 @@ def docstring_md(s_function, ls_doc, s_header=None, s_opath='man/docstring/'): help_md(s_command='pcdl_make_cell_vtk') # substrate and cell agent help_md(s_command='pcdl_plot_timeseries') -help_md(s_command='pcdl_make_ome_tiff') -help_md(s_command='pcdl_render_neuroglancer') # making movies help_md(s_command='pcdl_make_gif') help_md(s_command='pcdl_make_movie') diff --git a/pcdl/VERSION.py b/pcdl/VERSION.py index d5f9f2a..a8cd31d 100644 --- a/pcdl/VERSION.py +++ b/pcdl/VERSION.py @@ -1 +1 @@ -__version__ = '4.0.4' +__version__ = '3.3.8' diff --git a/pcdl/__init__.py b/pcdl/__init__.py index d0d0da2..a0dc1d8 100644 --- a/pcdl/__init__.py +++ b/pcdl/__init__.py @@ -1,4 +1,5 @@ -from pcdl.timestep import TimeStep, graphfile_parser, render_neuroglancer, scaler -from pcdl.timeseries import TimeSeries, make_gif, make_movie +from pcdl.pyAnnData import TimeStep, TimeSeries, scaler +from pcdl.pyMCDS import pyMCDS, graphfile_parser +from pcdl.pyMCDSts import pyMCDSts, make_gif, make_movie from pcdl.VERSION import __version__ -from pcdl.output_data import install_data, uninstall_data +from pcdl.data_timeseries import install_data, uninstall_data diff --git a/pcdl/output_data.py b/pcdl/data_timeseries.py similarity index 97% rename from pcdl/output_data.py rename to pcdl/data_timeseries.py index 21ed8cd..9358cd6 100644 --- a/pcdl/output_data.py +++ b/pcdl/data_timeseries.py @@ -86,7 +86,7 @@ def __init__(self): # for each obsolet timeserie for s_file in sorted(os.listdir(s_path)): - if s_file.startswith('output_2d') or s_file.startswith('output_3d'): + if s_file.startswith('output_'): # get path and filename s_pathfile = s_path + s_file diff --git a/pcdl/imagine.py b/pcdl/imagine.py deleted file mode 100644 index 232475f..0000000 --- a/pcdl/imagine.py +++ /dev/null @@ -1,745 +0,0 @@ -### -# title: biotransistor.imagine.py -# -# language Python3 -# license: GPLv3 -# author: bue, jenny eng -# date: 2019-01-31 -# -# run: -# form pcdl import imagine -# -# description: -# my image analysis library -# -# note: -# load basins tiff into numpy array -# -# import matplotlib.pyplot as plt -# from skimage import io -# -# a_tiff = ski.io.imread('segmentation_label_file.tiff') -# io.imshow(a_tiff) -# plt.imshow(a_tiff) -#### - -# library -import numpy as np -import pandas as pd -import sys - -# function -def slide_up(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up. - top row get deleted, - bottom row of zeros is inserted. - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, a.shape[0], 0, axis=0), 0, axis=0) - return(a) - - -def slide_down(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down. - top row of zeros is inserted. - bottom row get deleted, - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, 0, 0, axis=0), -1, axis=0) - return(a) - - -def slide_left(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one column left. - left most column gets deleted, - right most a column of zeros is inserted. - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, a.shape[1], 0, axis=1), 0, axis=1) - return(a) - - -def slide_right(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one column right. - left most a column of zeros is inserted. - right most column gets deleted, - - description: - inspired by np.roll function, though elements that roll - beyond the last position are not re-introduced at the first. - """ - a = np.delete(np.insert(a, 0, 0, axis=1), -1, axis=1) - return(a) - - -def slide_upleft(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up and one column left. - - description: - inspired by np.roll function. - """ - a = slide_left(slide_up(a)) - return(a) - - -def slide_upright(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row up and one column right. - - description: - inspired by np.roll function. - """ - a = slide_right(slide_up(a)) - return(a) - - -def slide_downleft(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down and one column left. - - description: - inspired by np.roll function. - """ - a = slide_left(slide_down(a)) - return(a) - - -def slide_downright(a): - """ - input: - a: numpy array - - output: - a: input numpy array shifted one row down and one column right. - - description: - inspired by np.roll function. - """ - a = slide_right(slide_down(a)) - return(a) - - -def border(ai_segment): - """ - input: - ai_segment: numpy array representing a cells or nuclei basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - output: - ai_border: numpy array containing only the cell or nuclei basin border. - border value will be 1, non border value will be 0. - - description: - algorithm to extract the basin borders form basin numpy arrays. - """ - ab_border_up = (ai_segment - slide_up(ai_segment)) != 0 - ab_border_down = (ai_segment - slide_down(ai_segment)) != 0 - ab_border_left = (ai_segment - slide_left(ai_segment)) != 0 - ab_border_right = (ai_segment - slide_right(ai_segment)) != 0 - ab_border_upleft = (ai_segment - slide_upleft(ai_segment)) != 0 - ab_border_upright = (ai_segment - slide_upright(ai_segment)) != 0 - ab_border_downleft = (ai_segment - slide_downleft(ai_segment)) != 0 - ab_border_downright = (ai_segment - slide_downright(ai_segment)) != 0 - ab_border = ab_border_up | ab_border_down | ab_border_left | ab_border_right | ab_border_upleft | ab_border_upright | ab_border_downleft | ab_border_downright - ai_border = ab_border * 1 - return(ai_border) - - -def grow(ai_segment, i_step=1, b_verbose=True): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step: integer which specifies how many pixels the basin - to each direction should grow. - function can handle shrinking. enter negative steps like -1. - - b_verbose: boolean which specifies if, while processing, - text should be outputted. - - output: - ai_grown: numpy array with the grown basins - - description: - algorithm to grow the basins in a given basin numpy array. - growing happens counterclockwise, starting at noon. - """ - ai_tree = ai_segment.copy() # initialize output - if (i_step > -1): - # growing - for i in range(i_step): - # next grow cycle - if b_verbose: - print(f'grow {i+1}[px] ring ...') - ai_treering = ai_tree.copy() - for o_slide in [slide_up, slide_upleft, slide_left, slide_downleft, slide_down, slide_downright, slide_right, slide_upright]: - ai_evolve = o_slide(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - #print(ai_treering) - # update output - ai_tree = ai_treering - else: - # shrinking - ai_border = border(ai_segment) - ai_membrane = grow(ai_border, i_step=abs(i_step) - 1) - ai_tree[ai_membrane != 0] = 0 - - # output - return(ai_tree) - - -def grow_seed(ai_segment, i_step=1, b_verbose=True): - """ - input: - ai_segment: numpy array representing a cells center basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step: integer which specifies how many pixels the seed pixel - should grow. - - b_verbose: boolean which specifies if, while processing, - text should be outputted. - - output: - ai_grown: numpy array with the grown basins - - description: - algorithm to grow the basins in a given cell center seed numpy array. - growing happens counterclockwise, starting at noon. - """ - # initialize output - ai_tree = ai_segment.copy() - - # growing - for i in range(i_step): - # next grow cycle - if b_verbose: - print(f'grow {i+1}[px] ring ...') - ai_treering = ai_tree.copy() - - # calculate pythagoras - b_circle = ((i + 1) * 2**(1/2)) < (i_step + 1) - - # up - ai_evolve = slide_up(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # upleft - if b_circle: - ai_evolve = slide_upleft(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # left - ai_evolve = slide_left(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # downleft - if b_circle: - ai_evolve = slide_downleft(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # down - ai_evolve = slide_down(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # downright - if b_circle: - ai_evolve = slide_downright(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # right - ai_evolve = slide_right(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - # upright - if b_circle: - ai_evolve = slide_upright(ai_tree) - ai_treering[(ai_evolve != ai_tree) & (ai_treering == 0)] = ai_evolve[(ai_evolve != ai_tree) & (ai_treering == 0)] - - # update output - #print(ai_treering) - ai_tree = ai_treering - # output - return(ai_tree) - - -def collision(ai_segment, i_step_size=1): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_step_size: integer that specifies the distance from a basin - where collisions with other basins are detected. - increasing the step size behind > 1 will result in faster processing - but less certain results. step size < 1 make no sense. - default step size is 1. - - output: - eti_collision: a set of tuples representing colliding basins. - - description: - algorithm to detect which basin collide a given step size away. - """ - eti_collision = set() - for o_slide in [slide_up, slide_down, slide_left, slide_right, slide_upleft, slide_upright, slide_downleft, slide_downright]: - ai_walk = ai_segment.copy() - for _ in range(i_step_size): - ai_walk = o_slide(ai_walk) - ai_alice = ai_walk[(ai_segment != 0) & (ai_walk != 0)] - ai_bob = ai_segment[(ai_segment != 0) & (ai_walk != 0)] - eti_collision = eti_collision.union(set( - zip( - ai_alice[(ai_alice != ai_bob)], - ai_bob[(ai_bob != ai_alice)] - ) - )) - # return - return(eti_collision) - - -def touching_cells(ai_segment, i_border_width=0, i_step_size=1): - """ - input: - ai_segment: numpy array representing a cells basin file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segment = skimage.io.imread("cells_basins.tif") - - i_border_width: maximal acceptable border with in pixels. - this is half of the range how far two the adjacent cell maximal - can be apart and still are regarded as touching each other. - - i_step_size: step size by which the border width is sampled for - touching cells. - increase the step size behind > 1 will result in faster processing - but less certain results. step size < 1 make no sense. - default step size is 1. - - output: - dei_touch: a dictionary that for each basin states - which other basins are touching. - - description: - algorithm to extract the touching basins from a cell basin numpy array. - algorithm inspired by C=64 computer games with sprit collision. - """ - - # detect neighbors - eti_collision = set() - ai_evolve = ai_segment.copy() - for _ in range(-1, i_border_width, i_step_size): - # detect cell border collision - eti_collision = eti_collision.union( - collision(ai_segment=ai_evolve, i_step_size=i_step_size) - ) - # grow basin - ai_evolve = grow(ai_segment=ai_evolve, i_step=i_step_size) - - # transform set of tuple of alice and bob collision to dictionary of sets - dei_touch = {} - ei_alice = set(np.ndarray.flatten(ai_segment)) - ei_alice.remove(0) - for i_alice in ei_alice: - dei_touch.update({i_alice : set()}) - for i_alice, i_bob in eti_collision: - ei_bob = dei_touch[i_alice] - ei_bob.add(i_bob) - dei_touch.update({i_alice : ei_bob}) - - # output - return(dei_touch) - - -def detouch_to_df(deo_touch, ls_column=["cell_center","cell_touch"]): - """ - input: - deo_touch: touching_cells generated dictionary - ls_column: future dictionary_key dictionary_value column name - - output: - df_touch: dataframe which contains the same information - as the input deo_touch dictionary. - - description: - transforms dei_touch dictionary into a two column dataframe. - """ - lo_key_total= [] - lo_value_total = [] - for o_key, eo_value in deo_touch.items(): - try: - lo_value = sorted(eo_value, key=int) - except ValueError: - lo_value = sorted(eo_value) - # extract form dictionary - if (len(lo_value) == 0): - lo_key_total.append(o_key) - lo_value_total.append(0) - else: - lo_key_total.extend([o_key] * len(lo_value)) - lo_value_total.extend(lo_value) - # generate datafarme - df_touch = pd.DataFrame([lo_key_total,lo_value_total], index=ls_column).T - return(df_touch) - - -# bue: 202021016: maybe refracture this into segment_px and membrane_px function -# the segement px function can then be used too on simple cell and nucles data -def membrane_px(ai_segment, dai_value, i_step=1, b_approximation=False): - ''' - input: - ai_segment: numpy array representing a cells basin tiff file. - it is assumed that basin borders are represented by 0 values, - and basins are represented with any values different from 0. - ai_segement = skimage.io.imread("cells_basins.tif") - - dai_value: dictionary of numpy array representing a - protein expression value tiff file. - the dictionary key should be the protein label. - - i_step: number of pixel the cell border, which is 2 pixel, - in both direction is grown, to cover the membrane segment. - default is 1. - - b_approximation: if set True, calculation works with - non-overlapping membranes and will as such be much faster. - - output: - df_membrane_px: dictionary of pandas datafarame whit cell_id, - image absolute xy coordinate, extended cell relative coordinate, - and membrane pixel related expression values for all proteins. - - description: - function extracts protein membrane expression values, - for each protein submitted to the function. - ''' - # get a fix sensor order - ls_sensor = sorted(dai_value.keys()) - - # get cell border - ab_border = border(ai_segment).astype(bool) - ai_segment_border = ai_segment.copy() - ai_segment_border[~ab_border] = 0 - - # aproximative algorithm - if (b_approximation) or (i_step <= 0): - - # membrane segment - ai_membrane = grow(ai_segment_border, i_step=i_step) - ab_membrane = ai_membrane.astype(bool) - ai_cell = ai_membrane[ab_membrane] - - # coordinates - tai_coor_absolute = np.where(ab_membrane) - ai_ycoor_absolute = tai_coor_absolute[0] - ai_xcoor_absolute = tai_coor_absolute[1] - - ai_membrane_coor = np.stack([ - ai_cell, - ai_ycoor_absolute, - ai_xcoor_absolute, - ], axis=1) - - # values - lai_membrane_value = [] - for s_sensor in ls_sensor: - ai_value = dai_value[s_sensor] - lai_membrane_value.append( - ai_value[ab_membrane] - ) - ai_membrane_value = np.stack( - lai_membrane_value, - axis=1, - ) - - # concatenate coor and value - ai_membrane_px = np.concatenate([ - ai_membrane_coor, - ai_membrane_value, - ], axis=1) - - # pack output - df_membrane_px = pd.DataFrame( - ai_membrane_px, - columns = ['cell', 'y_absolute', 'x_absolute'] + ls_sensor, - )#.astype({}) - df_membrane_px.index.name = f'approximation_membrane_{2*i_step + 1}px' - print(df_membrane_px.info()) - - # exact algorithm - else: - # empty result object - ai_membrane_px = None - - # for each cell - ei_cell = set(ai_segment.flatten()) - ei_cell.discard(0) # kick cell0 which is background - i_total = len(ei_cell) - #for i, i_cell in enumerate(sorted(ei_cell)[0:16]): - for i, i_cell in enumerate(sorted(ei_cell)): - #print(f'processing cell{i_cell}: {i} / {i_total}') - - # membrane segment - tai_coor = np.where(ai_segment_border == i_cell) - ai_ycoor = tai_coor[0] - ai_xcoor = tai_coor[1] - i_ymin = np.array([ai_ycoor.min() - i_step]).clip(min=0)[0] - i_xmin = np.array([ai_xcoor.min() - i_step]).clip(min=0)[0] - i_ymax = np.array([ai_ycoor.max() + i_step + 1]).clip(max=ai_segment_border.shape[0])[0] - i_xmax = np.array([ai_xcoor.max() + i_step + 1]).clip(max=ai_segment_border.shape[1])[0] - ai_border = (ai_segment_border == i_cell)[i_ymin:i_ymax,i_xmin:i_xmax].astype(int) - ab_membrane = grow(ai_border, i_step=i_step).astype(bool) - - # coordiantes - tai_coor_relative = np.where(ab_membrane) - ai_ycoor_relative = tai_coor_relative[0] - ai_xcoor_relative = tai_coor_relative[1] - ai_ycoor_absolute = ai_ycoor_relative + i_ymin - ai_xcoor_absolute = ai_xcoor_relative + i_xmin - ai_cell = np.array([i_cell] * len(ai_ycoor_relative)) - ai_membrane_coor = np.stack([ - ai_cell, - ai_ycoor_relative, - ai_xcoor_relative, - ai_ycoor_absolute, - ai_xcoor_absolute, - ], axis=1) - - # values - lai_membrane_value = [] - for s_sensor in ls_sensor: - ai_value = dai_value[s_sensor] - lai_membrane_value.append( - ai_value[i_ymin:i_ymax,i_xmin:i_xmax][ab_membrane] - ) - ai_membrane_value = np.stack( - lai_membrane_value, - axis=1, - ) - - # concatenate coor and value - ai_membrane_coorvalue = np.concatenate([ - ai_membrane_coor, - ai_membrane_value, - ], axis=1) - - # update ouput - if (ai_membrane_px is None): - ai_membrane_px = ai_membrane_coorvalue - else: - ai_membrane_px = np.concatenate([ - ai_membrane_px, - ai_membrane_coorvalue, - ], axis=0) - - # sanity check - print(f'cell {i_cell} / {i_total}: min {ai_ycoor_relative.min()},{ai_xcoor_relative.min()} x max {ai_ycoor_relative.max()},{ai_xcoor_relative.max()}') - - # pack output - df_membrane_px = pd.DataFrame( - ai_membrane_px, - columns = ['cell', 'y_relative', 'x_relative', 'y_absolute', 'x_absolute'] + ls_sensor, - )#.astype({}) - df_membrane_px.index.name = f'membrane_{2*i_step + 2}px' - - # output - return(df_membrane_px) - - -# bue 20201016: i should add all statistical moments -# bue 20201024: this should become dfpx_stats and be applicable to cell, nucleus, cytoplasm, and membrane -def dfmembranepx_stats(df_membrane_px, di_threshold_raw=None): - ''' - input: - df_membrane_px: membrane_px output which is a - pandas datafarame whit cell_id, - image absolute and cell relative xy coordinate and - membrane pixel related expression values for all proteins. - - di_threshold_raw: dictionary of raw intensity threshold values - for each protein. if set to none, cell membrane positive fraction - will not be calculated and be missing from the output. - default is None. - - output: - ddf_out: dictionary of pandas datafarames. one dataframe per protein. - each dataframe stores mean, min, max and whole range of quantile values - measured for each cell membrane. - - description: - function calculates statistical key numbers for membrane protein - expression values, for each protein submitted to the function. - ''' - # handle input - es_sensor = set(df_membrane_px.columns) - es_sensor = es_sensor.difference( - {'cell', 'y_relative', 'x_relative', 'y_absolute', 'x_absolute'} - ) - - # empty result object - ddlr_membrane = {} - for s_sensor in es_sensor: - ddlr_membrane.update({s_sensor: {}}) - - # for each cell - ei_cell = set(df_membrane_px.cell.unique()) - i_total = len(ei_cell) - 1 - - #for i, i_cell in enumerate(sorted(ei_cell)[0:16]): - for i, i_cell in enumerate(ei_cell): - print(f'processing cell{i_cell}: {i} / {i_total}') - - # for each sensor - df_membrane_cell_px = df_membrane_px.loc[df_membrane_px.cell == i_cell,:] - for s_sensor in es_sensor: - ai_membrane = df_membrane_cell_px.loc[:,s_sensor].values - - # quantile calculation - lr_membrane = [ - np.mean(ai_membrane), - np.std(ai_membrane, ddof=0), - np.min(ai_membrane), - np.quantile(ai_membrane, 0.01), - np.quantile(ai_membrane, 0.05), - np.quantile(ai_membrane, 0.1), - np.quantile(ai_membrane, 0.2), - np.quantile(ai_membrane, 0.3), - np.quantile(ai_membrane, 0.4), - np.quantile(ai_membrane, 0.5), - np.quantile(ai_membrane, 0.6), - np.quantile(ai_membrane, 0.7), - np.quantile(ai_membrane, 0.8), - np.quantile(ai_membrane, 0.9), - np.quantile(ai_membrane, 0.95), - np.quantile(ai_membrane, 0.99), - np.max(ai_membrane), - ai_membrane.shape[0], - ] - - # membrane fraction positive calculation - if not (di_threshold_raw is None): - ab_membrane = ai_membrane > di_threshold_raw[s_sensor] - i_sumpos = ab_membrane.sum() - r_fractpos = i_sumpos / ab_membrane.shape[0] - lr_membrane.extend([i_sumpos, r_fractpos]) - - # update result object - ddlr_membrane[s_sensor].update({i_cell: lr_membrane}) - - # pack output - ls_index = [ - 'mean','std', - 'min','q0_01','q0_05', - 'q0_1','q0_2','q0_3','q0_4','q0_5', - 'q0_6','q0_7','q0_8','q0_9','q0_95', - 'q0_99','max', - 'membrane_size_px' - ] - if not (di_threshold_raw is None): - ls_index.extend(['px_pos','fract_pos']) - ddf_out = {} - for s_sensor, dlr_membrane in ddlr_membrane.items(): - df_membrane = pd.DataFrame( - dlr_membrane, - index=ls_index, - ).T - ddf_out.update({s_sensor: df_membrane}) - df_membrane.index.name = f'cell' - - # output - return(ddf_out) - - -def imgfuse(laaai_in): - """ - input: - laaai_in: list of 3 channel (RGB) images - - output: - aaai_out: fused 3 channel image - - description: - code to fuse many RGB images into one. - """ - # check shape - ti_shape = None - for aaai_in in laaai_in: - if (ti_shape is None): - ti_shape = aaai_in.shape - else: - if (aaai_in.shape != ti_shape): - sys.exit(f"Error: input images have not the same shape. {aaai_in.shape} != {aaai_in}.") - - # fuse images - llli_channel = [] - for i_channel in range(ti_shape[0]): - lli_matrix = [] - for i_y in range(ti_shape[1]): - li_row = [] - for i_x in range(ti_shape[2]): - #print(f"{i_channel} {i_y} {i_x}") - li_px = [] - for aaai_in in laaai_in: - i_in = aaai_in[i_channel,i_y,i_x] - if (i_in != 0): - li_px.append(i_in) - if (len(li_px) != 0): - i_out = np.mean(li_px) - else: - i_out = 0 - li_row.append(int(i_out)) - lli_matrix.append(li_row) - llli_channel.append(lli_matrix) - - # output - aaai_out = np.array(llli_channel) - return(aaai_out) - - -# main code -#if __name__ == "__main__": - diff --git a/pcdl/neuromancer.py b/pcdl/neuromancer.py deleted file mode 100644 index 7110c64..0000000 --- a/pcdl/neuromancer.py +++ /dev/null @@ -1,206 +0,0 @@ -######### -# title: ometiff2neuro.py -# -# language: python3 -# date: 2022-02-16 -# license: MIT -# author: jason lu, viviana kwong, elmar bucher -# -# installation: -# pip install neuroglancer # the basics -# pip install ipython # for coding -# pip install matplotlib # for expression value color maps -# pip install numpy # for basic code manipulation -# pip install scikit-image # signal thresh and image manipulation. -# pip install bioio bioio-ome-tiff # for loading the image and extracting ometiff metadata -# -# run: -# python3 -i neuromancer.py -# -# description: -# script to render multi channel multi slice ome.tiff files into the -# neuroglancer software. -# -# the script here makes use of the neuroglancer python library and is based -# on the neuroglancer/python/examples/example.py code. -# with this script, it is possible to load three-dimensional single -# time step ome-tiff files straight into the neuroglancer software. -# for each channel, mesh generation, rendering, and expression intensity -# coloring is done on the fly. -# channels can be viewed together or alone by toggling them on or off in -# the neuroglancer user interface. -# -# references: -# + https://www.openmicroscopy.org/ome-files/ -# + https://github.com/google/neuroglancer -# + https://github.com/google/neuroglancer/tree/master/python -######### - - -# library -import argparse -from bioio import BioImage -import matplotlib as mpl -import neuroglancer -import neuroglancer.cli -import numpy as np -from skimage import exposure, util -import sys - - -# functions -def ometiff2neuro( - o_state, - s_pathfile_tiff, - i_timestep = 0, - s_intensity_cmap = 'gray', # None - ): - ''' - input: - o_state: neuroglancer viewer state object. - - s_pathfile_tiff: file name and path to input tiff file. - - s_intensity_cmap: matlab color map object, used to display expression intensity values. - default is cm.gray. - if None, no intensity layers will be generated. - - output: - local url where the rendered data can be viewed. - - description: - function to render multi channel multi slice (ome)tiff files into the neuroglancer software. - ''' - # off we go # - print(f'\nprocessing: {s_pathfile_tiff}') - - # load tiff image as numpy array - o_img = BioImage(s_pathfile_tiff) - - # check dimensionality - if (o_img.dims.order.lower() != 'tczyx'): - sys.exit(f'Error @ ometiff2neuro : cannot handle ome tiff file with dimension {o_img.dims.order.lower()}') - - # generate neuroglancer layers - for i_channel in range(o_img.shape[1]): - - # extract channel label - s_label = o_img.channel_names[i_channel] - print(f'check channel {i_channel}/{o_img.shape[1]}: {s_label}') - - # extract data - a_channel = o_img.data[i_timestep, i_channel, :, :, :] - - # 3D rendering # - # thresh data - # shape - a_shape = np.zeros(a_channel.shape, dtype=np.uint32) - a_shape[a_channel > 0] = i_channel + 1 - - # generate neuroglancer object - ls_name = ['z', 'y', 'x'] - li_scale = [ - o_img.physical_pixel_sizes.Z, - o_img.physical_pixel_sizes.Y, - o_img.physical_pixel_sizes.X, - ] - - o_state.layers.append( - name = s_label, - layer = neuroglancer.LocalVolume( - data = a_shape, - dimensions = neuroglancer.CoordinateSpace( - # rgb, x, y, z - names = ls_name, - scales = li_scale, - units = ['nm', 'nm', 'nm'], - ), - ), - visible = False, # special thanks to Jeremy Maitin-Shepard. - ) - - # expression intensity rendering # - if not (s_intensity_cmap is None): - a_channel = exposure.rescale_intensity(a_channel, in_range='image') # 16 or 8[bit] normalized - - # translate intensity by color map - a_8bit = util.img_as_ubyte(a_channel) - a_intensity = mpl.colormaps[s_intensity_cmap](a_8bit, alpha=None, bytes=True)[:,:,:,0:3] - - # generate neuroglancer object - ls_name = ['z', 'y', 'x','c^'] - li_scale = [ - o_img.physical_pixel_sizes.Z, - o_img.physical_pixel_sizes.Y, - o_img.physical_pixel_sizes.X, - 3 - ] - - o_state.layers.append( - name = s_label + '_intensity', - layer = neuroglancer.LocalVolume( - data = a_intensity, - dimensions = neuroglancer.CoordinateSpace( - # rgb, x, y, z - names = ls_name, - scales = li_scale, - units = ['nm', 'nm', 'nm', ''], - ), - ), - visible = False, # special thanks to Jeremy Maitin-Shepard. - shader= -""" -void main() { - emitRGB( - vec3(toNormalized(getDataValue(0)), - toNormalized(getDataValue(1)), - toNormalized(getDataValue(2))) - ); -} -""", - ) - - -# run the code from the command line -if __name__ == '__main__': - import neuroglancer - o_parser = argparse.ArgumentParser( - description='script to render an ome.tiff file into the neuroglancer software.', - epilog = 'homepage: https://github.com/elmbeech/physicelldataloader', - ) - - # request path to ometiff and file name as command line argument - o_parser.add_argument( - 'ometiff', - type=str, - nargs=1, - help='ome.tiff path/filename' - ) - # time step - o_parser.add_argument( - '--timestep', - default = 0, - type = int, - help = 'time step, within a possibly collapsed ome tiff file, to render. the default will work with single time step ome tiff files.', - ) - # intensity colormap - o_parser.add_argument( - '--intensity_cmap', - default = 'gray', - help = 'matlab color map label, used to display expression intensity values. if None, no intensity layers will be generated. https://matplotlib.org/stable/users/explain/colors/colormaps.html', - ) - - # start neuroglancer - neuroglancer.cli.add_server_arguments(o_parser) - neuroglancer.cli.handle_server_arguments(o_parser.parse_args()) - viewer = neuroglancer.Viewer() - with viewer.txn() as state: - # render ometiff - ometiff2neuro( - o_state = state, - s_pathfile_tiff = o_parser.parse_args().ometiff[0], - i_timestep = o_parser.parse_args().timestep, - s_intensity_cmap = None if (o_parser.parse_args().intensity_cmap.lower() == 'none') else o_parser.parse_args().intensity_cmap, - ) - # print neuroglancer viewer url - print(viewer) diff --git a/pcdl/pdplt.py b/pcdl/pdplt.py deleted file mode 100644 index c45ed4f..0000000 --- a/pcdl/pdplt.py +++ /dev/null @@ -1,132 +0,0 @@ -#### -# title: biotransistor.pdplt.py -# -# language: python3 -# date: 2019-06-29 -# license: GPL>=v3 -# author: Elmar Bucher -# -# description: -# library with the missing pandas plot features. -# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html -#### - - -# library -import matplotlib.pyplot as plt -from matplotlib import cm -from matplotlib import colors -import matplotlib.patches as mpatches -import numpy as np -import random - - -# pandas to matplotlib -#fig, ax = plt.subplots() -#ax = ax.ravel() -#ax.axis('equal') -#df.plot(ax=ax) -#plt.tight_layout() -#fig.savefig(s_filename, facecolor='white') -#plt.close() - - -# plot stuff -def df_label_to_color(df_abc, s_focus, es_label=None, s_nolabel='gray', s_cmap='viridis', b_shuffle=False): - ''' - input: - df_abc: dataframe to which the color column will be added. - s_focus: column name with sample labels for which a color column will be generated. - es_label: set of labels to color. if None, es_label will be extracted for the s_focus column. - s_nolabel: color for labels not defined in es_label. - s_cmap: matplotlib color map label. - https://matplotlib.org/stable/tutorials/colors/colormaps.html - b_shuffle: should colors be given by alphabetical order, - or should the label color mapping order be random. - - output: - df_abc: dataframe updated with color column. - ds_color: lable to hex color string mapping dictionary - - description: - function adds for the selected label column - a color column to the df_abc dataframe. - ''' - if (es_label is None): - es_label = set(df_abc.loc[:,s_focus]) - if b_shuffle: - ls_label = list(es_label) - random.shuffle(ls_label) - else: - ls_label = sorted(es_label) - a_color = plt.get_cmap(s_cmap)(np.linspace(0, 1, len(ls_label))) - do_color = dict(zip(ls_label, a_color)) - df_abc[f'{s_focus}_color'] = s_nolabel - ds_color = {} - for s_category, o_color in do_color.items(): - s_color = colors.to_hex(o_color) - ds_color.update({s_category : s_color}) - df_abc.loc[(df_abc.loc[:,s_focus] == s_category), f'{s_focus}_color'] = s_color - # output - return(ds_color) - -def ax_colorlegend(ax, ds_color, s_loc='lower left', s_fontsize='small'): - ''' - input: - ax: matplotlib axis object to which a color legend will be added. - ds_color: lables to color strings mapping dictionary - s_loc: the location of the legend. - possible strings are: best, - upper right, upper center, upper left, center left, - lower left, lower center, lower right, center right, - center. - s_fontsize: font size used for the legend. known are: - xx-small, x-small, small, medium, large, x-large, xx-large. - - output: - ax: matplotlib axis object updated with color legend. - - description: - function to add color legend to a figure. - ''' - lo_patch = [] - for s_label, s_color in sorted(ds_color.items()): - o_patch = mpatches.Patch(color=s_color, label=s_label) - lo_patch.append(o_patch) - ax.legend( - handles = lo_patch, - loc = s_loc, - fontsize = s_fontsize - ) - -def ax_colorbar(ax, r_vmin, r_vmax, s_cmap='viridis', s_text=None, o_fontsize='medium', b_axis_erase=False): - ''' - input: - ax: matplotlib axis object to which a colorbar will be added. - r_vmin: colorbar min value. - r_vmax: colorbar max value. - s_cmap: matplotlib color map label. - https://matplotlib.org/stable/tutorials/colors/colormaps.html - s_text: to label the colorbar axis. - o_fontsize: font size used for the legend. known are: - xx-small, x-small, small, medium, large, x-large, xx-large. - b_axis_erase: should the axis ruler be erased? - - output: - ax: matplotlib axis object updated with colorbar. - - description" - function to add colorbar to a figure. - ''' - if b_axis_erase: - ax.axis('off') - if not (s_text is None): - ax.text(0.5,0.5, s_text, fontsize=o_fontsize) - plt.colorbar( - cm.ScalarMappable( - norm=colors.Normalize(vmin=r_vmin, vmax=r_vmax, clip=False), - cmap=s_cmap, - ), - ax=ax, - ) - diff --git a/pcdl/pyAnnData.py b/pcdl/pyAnnData.py new file mode 100644 index 0000000..51757ea --- /dev/null +++ b/pcdl/pyAnnData.py @@ -0,0 +1,616 @@ +### +# title: pyAnnData.py +# +# language: python3 +# date: 2023-06-24 +# license: BSD-3-Clause +# author: Elmar Bucher +# +# description: +# pyAnnData.py spices up the pyMCDS (TimeStep) and pyMCDSts (TimeSeries) +# classes with a function to transform mcds time steps and time series +# into anndata objects. +# anndata is the de facto python3 single cell data standard. +# In other words, these functions enable us to analyze PhysiCell output +# the same way that bioinformatician analyze their data retrieved from +# single cell wet lab experiments. +# +# + https://www.biorxiv.org/content/10.1101/2021.12.16.473007v1 +# + https://anndata.readthedocs.io/en/latest/ +# + https://scverse.org/ +#### + + +import anndata as ad +import numpy as np +import pandas as pd +from pcdl.pyMCDS import pyMCDS, es_coor_cell +from pcdl.pyMCDSts import pyMCDSts +from scipy import sparse +import warnings + + +def scaler(df_x, scale='maxabs'): + """ + input: + df_x: pandas dataframe + one attribute per column, one sample per row. + + scale: string; default 'maxabs' + None: no scaling. set scale to None if you would like to have + raw data or scale, transform, and normalize the data later. + + maxabs: maximum absolute value distance scaler will linearly map + all values into a [-1, 1] interval. if the original data + has no negative values, the result will be the same as with + the minmax scaler (except with attributes with only one value). + if the attribute has only zeros, the value will be set to 0. + + minmax: minimum maximum distance scaler will map all values + linearly into a [0, 1] interval. + if the attribute has only one value, the value will be set to 0. + + std: standard deviation scaler will result in sigmas. + each attribute will be mean centered around 0. + ddof delta degree of freedom is set to 1 because it is assumed + that the values are samples out of the population + and not the entire population. it is incomprehensible to me + that the equivalent sklearn method has ddof set to 0. + if the attribute has only one value, the value will be set to 0. + + output: + df_x: pandas dataframe + scaled df_x dataframe. + + description: + inspired by scikit-learn's preprocessing scaling method, this function + offers a re-implementation of the linear re-scaling methods maxabs, + minmax, and scale. + + the robust scaler methods (quantile based) found in scikit-learn are + missing. since we deal with simulated data, we don't expect heavy + outliers, and if they exist, then they are of interest. + the power and quantile based transformation methods and unit circle + based normalizer methods found there are missing too. + if you need to apply any such methods, you can do so to an anndata object + like this: + + from sklearn import preprocessing + adata.obsm["X_scaled"] = preprocessing.scale(adata.X) + + + https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html + + https://scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing + + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.maxabs_scale.html + + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.minmax_scale.html + + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html + """ + if scale is None: + pass + # -1,1 + elif scale == 'maxabs': + a_x = df_x.values + warnings.filterwarnings('ignore', category=RuntimeWarning) + a_maxabs = a_x / abs(a_x).max(axis=0) + warnings.simplefilter('default') + a_maxabs[np.isnan(a_maxabs)] = 0 # fix if entier column is 0 + df_x = pd.DataFrame(a_maxabs, columns=df_x.columns, index=df_x.index) + # 0,1 + elif scale == 'minmax': + a_x = df_x.values + warnings.simplefilter("ignore") + warnings.filterwarnings('ignore', category=RuntimeWarning) + a_minmax = (a_x - a_x.min(axis=0)) / (a_x.max(axis=0) - a_x.min(axis=0)) + warnings.simplefilter('default') + a_minmax[np.isnan(a_minmax)] = 0 # fix if entier column has same value + df_x = pd.DataFrame(a_minmax, columns=df_x.columns, index=df_x.index) + # sigma + elif scale == 'std': + a_x = df_x.values + warnings.filterwarnings('ignore', category=RuntimeWarning) + a_std = (a_x - a_x.mean(axis=0)) / a_x.std(axis=0, ddof=1) + warnings.simplefilter('default') + a_std[np.isnan(a_std)] = 0 # fix if entier column has same value + df_x = pd.DataFrame(a_std, columns=df_x.columns, index=df_x.index) + else: + raise ValueError(f"Error @ scaler : unknown scale algorithm {scale} detected. known are [None, 'maxabs', 'minmax', 'std'].") + + return df_x + + +def _anndextract(df_cell, scale='maxabs', graph_attached={}, graph_neighbor={}, graph_spring={}, graph_method='PhysiCell'): + """ + input: + df_cell: pandas dataframe + data frame retrieved with the mcds.get_cell_df function. + + scale: string; default maxabs + specify how the data should be scaled. + possible values are None, maxabs, minmax, std. + for more input, check out: help(pcdl.scaler). + + graph_attached: dict; default {} + attached graph dictionary, retrieved with + with the mcds.get_attched_graph() function. + + graph_neighbor: dict; default {} + neighbor graph dictionary, retrieved + with the mcds.get_neighbor_graph() function. + + graph_spring: dict; default {} + spring_attached graph dictionary, retrieved + with the mcds.get_spring_graph_dict() function. + + graph_method: string; default PhysiCell + method how the graphs were generated. + + output: + df_count, df_obs, d_obsm, d_obsp, d_uns dataframes and dictionaries, + ready to be backed into an anndata object. + + description: + this function takes a pcdl df_cell pandas dataframe and re-formats + it into a set of two dataframes (df_count, df_obs), + two dictionary of numpy array (d_obsm, d_obsp), + and one dictionary of string (d_uns), + which downstream might be transformed into an anndata object. + """ + # transform index to string + df_coor = df_cell.loc[:,['position_x','position_y','position_z']].copy() + df_cell.index = df_cell.index.astype(str) + + # build obs anndata object (annotation of observations) + df_obs = df_cell.loc[:,['mesh_center_p','time']].copy() + df_obs.columns = ['z_layer', 'time'] + + # buil obsm anndata object spatial (multi-dimensional annotation of observations) + if (len(set(df_cell.position_z)) == 1): + df_obsm = df_cell.loc[:,['position_x','position_y']].copy() + else: + df_obsm = df_cell.loc[:,['position_x','position_y','position_z']].copy() + d_obsm = {"spatial": df_obsm.values} + + # build obsp and uns anndata object graph (pairwise annotation of obeservation) and (unstructured data) + #### + # acknowledgement: + # this code is inspired from the tysserand add_to_AnnData impelmentation + # from Alexis Coullomb form the Pancaldi Lab. + # https://github.com/VeraPancaldiLab/tysserand/blob/main/tysserand/tysserand.py#L1546 + #### + # extract cell_id to index mapping (i always loved perl) + di_ididx = df_cell.reset_index().loc[:,'ID'].reset_index().astype(int).set_index('ID').squeeze().to_dict() + # transform cell id graph dict to index matrix and pack for anndata + d_obsp = {} # pairwise annotation of obeservation + d_uns = {} # unstructured data + for s_graph, dei_graph in [('neighbor', graph_neighbor), ('attached', graph_attached), ('spring', graph_spring)]: + lli_edge = [] + lr_distance = [] + for i_src, ei_dst in dei_graph.items(): + for i_dst in ei_dst: + # extract edge + lli_edge.append([di_ididx[i_src], di_ididx[i_dst]]) + r_distance = ((df_coor.loc[i_src,:].values - df_coor.loc[i_dst,:].values)**2).sum()**(1/2) + lr_distance.append(r_distance) + # if there is a graph + if (len(lli_edge) > 0): + # handle edge data + ai_edge = np.array(lli_edge, dtype=np.uint) + # handle connection data + ai_conectivity = np.ones(ai_edge.shape[0], dtype=np.uint16) + ai_conectivity_sparse = sparse.csr_matrix( + (ai_conectivity, (ai_edge[:,0], ai_edge[:,1])), + shape = (df_cell.shape[0], df_cell.shape[0]), + dtype = np.uint + ) + # handle distance data + ar_distance = np.array(lr_distance, dtype=np.float64) + ar_distance_sparse = sparse.csr_matrix( + (ar_distance, (ai_edge[:,0], ai_edge[:,1])), + shape = (df_cell.shape[0], df_cell.shape[0]), + dtype = np.float64 + ) + # pack obsp + d_obsp.update({ + f'physicell_{s_graph}_conectivities': ai_conectivity_sparse, + f'physicell_{s_graph}_distances': ar_distance_sparse, + }) + # pack uns + d_uns.update({ + s_graph : { + 'connectivities_key': f'physicell_{s_graph}_conectivities', + 'distances_key': f'physicell_{s_graph}_distances', + 'params': { + 'metric': 'euclidean', + 'method': graph_method, + } + } + }) + + # extract discrete cell data + es_drop = set(df_cell.columns).intersection({ + 'voxel_i', 'voxel_j', 'voxel_k', + 'mesh_center_m', 'mesh_center_n', 'mesh_center_p', + 'position_x', 'position_y','position_z', + 'time', 'runtime', 'xmlfile', + }) + df_cell.drop(es_drop, axis=1, inplace=True) # maybe obs? + + # dectect variable types + des_type = {'float': set(), 'int': set(), 'bool': set(), 'str': set()} + for _, se_cell in df_cell.items(): + if str(se_cell.dtype).startswith('float'): + des_type['float'].add(se_cell.name) + elif str(se_cell.dtype).startswith('int'): + des_type['int'].add(se_cell.name) + elif str(se_cell.dtype).startswith('bool'): + des_type['bool'].add(se_cell.name) + elif str(se_cell.dtype).startswith('object'): + des_type['str'].add(se_cell.name) + else: + print(f'Error @ TimeSeries.get_anndata : column {se_cell.name} detected with unknown dtype {str(se_cell.dtype)}.') + + # build on obs and X anndata object + df_cat = df_cell.loc[:,sorted(des_type['str'])].copy() + df_obs = pd.merge(df_obs, df_cat, left_index=True, right_index=True) + es_num = des_type['float'].union(des_type['int'].union(des_type['bool'])) + df_count = df_cell.loc[:,sorted(es_num)].copy() + for s_col in des_type['bool']: + df_count[s_col] = df_count[s_col].astype(int) + df_count = scaler(df_count, scale=scale) + + # return + return(df_count, df_obs, d_obsm, d_obsp, d_uns) + + +# class definition +class TimeStep(pyMCDS): + def __init__(self, xmlfile, output_path='.', custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True): + """ + input: + xmlfile: string + name of the xml file with or without path. + in the with path case, output_path has to be set to the default! + + output_path: string; default '.' + relative or absolute path to the directory where + the PhysiCell output files are stored. + + custom_data_type: dictionary; default is {} + variable to specify custom_data variable types + besides float (int, bool, str) like this: {var: dtype, ...}. + downstream float and int will be handled as numeric, + bool as Boolean, and str as categorical data. + + microenv: boole; default True + should the microenvironment data be loaded? + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. + + graph: boole; default True + should neighbor garph, attached graph, and spring attached graph + be loaded? setting graph to False will use less memory and + speed up processing. + + physiboss: boole; default True + should physiboss state data be loaded, if found? + setting physiboss to False will use less memory and speed up processing. + + settingxml: string; default PhysiCell_settings.xml + the settings.xml that is loaded, from which the cell type ID + label mapping, is extracted, if this information is not found + in the output xml file. + set to None or False if the xml file is missing! + + verbose: boole; default True + setting verbose to False for less text output while processing. + + output: + mcds: TimeStep class instance + all fetched content is stored at mcds.data. + + description: + TimeStep.__init__ will call pyMCDS.__init__ that generates a mcds + class instance, a dictionary of dictionaries data structure that + contains all output from a single PhysiCell model time step. + furthermore, the mcds object offers functions to access the stored data. + the code assumes that all related output files are stored + in the same directory. data is loaded by reading the xml file for + a particular time step and the therein referenced files. + """ + pyMCDS.__init__( + self, + xmlfile = xmlfile, + output_path = output_path, + custom_data_type = custom_data_type, + microenv = microenv, + graph = graph, + physiboss = physiboss, + settingxml = settingxml, + verbose = verbose + ) + + + def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs'): + """ + input: + values: integer; default is 1 + minimal number of values a variable has to have to be outputted. + variables that have only 1 state carry no information. + None is a state too. + + drop: set of strings; default is an empty set + set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. + Attention: when the keep parameter is given, then + the drop parameter has to be an empty set! + + keep: set of strings; default is an empty set + set of column labels to be kept in the dataframe. + set values=1 to be sure that all variables are kept. + don't worry: essential columns like ID, coordinates + and time will always be kept. + + scale: string; default 'maxabs' + specify how the data should be scaled. + possible values are None, maxabs, minmax, std. + for more input, check out: help(pcdl.scaler) + + output: + annmcds: anndata object + for this one time step. + + description: + function to transform a mcds time step into an anndata object + for downstream analysis. + """ + # processing + if self.verbose: + print(f'processing: 1/1 {round(self.get_time(),9)}[min] mcds into anndata obj.') + df_cell = self.get_cell_df(values=values, drop=drop, keep=keep) + df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( + df_cell = df_cell, + scale = scale, + graph_attached = self.get_attached_graph_dict(), + graph_neighbor = self.get_neighbor_graph_dict(), + graph_method = self.get_physicell_version(), + ) + annmcds = ad.AnnData( + X = df_count, + obs = df_obs, + obsm = d_obsm, + obsp = d_obsp, + uns = d_uns + ) + # output + return annmcds + + +class TimeSeries(pyMCDSts): + def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True): + """ + input: + output_path: string, default '.' + relative or absolute path to the directory where + the PhysiCell output files are stored. + + custom_data_type: dictionary; default is {} + variable to specify custom_data variable types + besides float (int, bool, str) like this: {var: dtype, ...}. + downstream float and int will be handled as numeric, + bool as Boolean, and str as categorical data. + + load: boole; default True + should the whole time series data, all time steps, straight at + object initialization be read and stored to mcdsts.l_mcds? + + microenv: boole; default True + should the microenvironment data be loaded? + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. + + graph: boole; default True + should neighbor garph, attached graph, and spring attached graph + be loaded? setting graph to False will use less memory and + speed up processing. + + physiboss: boole; default True + should physiboss state data be loaded, if found? + setting physiboss to False will use less memory and speed up processing. + + settingxml: string; default PhysiCell_settings.xml + the settings.xml that is loaded, from which the cell type ID + label mapping, is extracted, if this information is not found + in the output xml file. + set to None or False if the xml file is missing! + + verbose: boole; default True + setting verbose to False for less text output while processing. + + output: + mcdsts: pyMCDSts class instance + this instance offers functions to process all stored time steps + from a simulation. + + description: + TimeSeries.__init__ will call pyMCDSts.__init__ that generates a mcdsts + class instance. this instance offers functions to process all time steps + in the output_path directory. + """ + pyMCDSts.__init__( + self, + output_path = output_path, + custom_data_type = custom_data_type, + load = load, + microenv = microenv, + graph = graph, + physiboss = physiboss, + settingxml = settingxml, + verbose = verbose + ) + self.l_annmcds = None + + + def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True): + """ + input: + values: integer; default is 1 + minimal number of values a variable has to have to be outputted. + variables that have only 1 state carry no information. + None is a state too. + + drop: set of strings; default is an empty set + set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. + Attention: when the keep parameter is given, then + the drop parameter has to be an empty set! + + keep: set of strings; default is an empty set + set of column labels to be kept in the dataframe. + don't worry: essential columns like ID, coordinates + and time will always be kept. + + scale: string; default 'maxabs' + specify how the data should be scaled. + possible values are None, maxabs, minmax, std. + for more input, check out: help(pcdl.scaler) + + collapse: boole; default True + should all mcds time steps from the time series be collapsed + into one single anndata object, or a list of anndata objects + for each time step? + + keep_mcds: boole; default True + should the loaded original mcds be kept in memory + after transformation? + + output: + annmcds or self.l_annmcds: anndata object or list of anndata objects. + what is returned depends on the collapse setting. + + description: + function to transform mcds time steps into one or many + anndata objects for downstream analysis. + """ + # initialize vaiable + l_annmcds = [] + df_anncount = None + df_annobs = None + ar_annobsm = None + + # variable triage + if (values < 2): + ls_column = list(self.l_mcds[0].get_cell_df(drop=drop, keep=keep).columns) + else: + ls_column = sorted(es_coor_cell.difference({'ID'})) + ls_column.extend(sorted(self.get_cell_attribute(values=values, drop=drop, keep=keep, allvalues=False).keys())) + + # collapse warning + if collapse and self.verbose: + print('Warning @ mcdsts.get_anndata : only df_cell data, but not graph data, can be collapsed.') + + # processing + lann_mcds = [] + i_mcds = len(self.l_mcds) + for i in range(i_mcds): + # fetch mcds + if keep_mcds: + mcds = self.l_mcds[i] + else: + mcds = self.l_mcds.pop(0) + # extract physicell version + s_physicellv = mcds.get_physicell_version(), + # extract time and dataframes + r_time = round(mcds.get_time(),9) + if self.verbose: + print(f'processing: {i+1}/{i_mcds} {r_time}[min] mcds into anndata obj.') + df_cell = mcds.get_cell_df() + df_cell = df_cell.loc[:,ls_column] + + # pack collapsed + if collapse: + # extract + df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( + df_cell=df_cell, + scale = scale, + #graph_attached = {}, + #graph_neighbor = {}, + #graph_spring = {}, + #graph_method = s_physicellv, + ) + # count + df_count.reset_index(inplace=True) + df_count.index = df_count.ID + f'id_{r_time}min' + df_count.index.name = 'id_time' + df_count.drop('ID', axis=1, inplace=True) + if df_anncount is None: + df_anncount = df_count + else: + df_anncount = pd.concat([df_anncount, df_count], axis=0) + # obs + df_obs.reset_index(inplace=True) + df_obs.index = df_obs.ID + f'id_{r_time}min' + df_obs.index.name = 'id_time' + if df_annobs is None: + df_annobs = df_obs + else: + df_annobs = pd.concat([df_annobs, df_obs], axis=0) + # obsm (spatial) + if ar_annobsm is None: + ar_annobsm = d_obsm['spatial'] + else: + ar_annobsm = np.vstack([ar_annobsm, d_obsm['spatial']]) + # obsp: nop (graph) + # uns: nop (graph) + + # pack not collapsed + else: + # extract + df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( + df_cell=df_cell, + scale = scale, + graph_attached = mcds.get_attached_graph_dict(), + graph_neighbor = mcds.get_neighbor_graph_dict(), + graph_spring = mcds.get_spring_graph_dict(), + graph_method = s_physicellv, + ) + # annmcds + ann_mcds = ad.AnnData( + X = df_count, + obs = df_obs, + obsm = d_obsm, + obsp = d_obsp, + uns = d_uns, + ) + lann_mcds.append(ann_mcds) + + # output + if collapse: + ann_mcdsts = ad.AnnData( + X = df_anncount, + obs = df_annobs, + obsm = {'spatial': ar_annobsm}, + #obsp = d_obsp, + #uns = d_uns + ) + return ann_mcdsts + else: + self.l_annmcds = lann_mcds + return self.l_annmcds + + + def get_annmcds_list(self): + """ + input: + self: TimeSeries class instance. + + output: + self.l_annmcds: list of chronologically ordered anndata mcds objects. + watch out, this is a pointer to the + self.l_annmcds list of anndata mcds objects, not a copy of self.l_annmcds! + + description: + function returns a binding to the self.l_annmcds list of anndata mcds objects. + """ + return self.l_annmcds + diff --git a/pcdl/commandline.py b/pcdl/pyCLI.py similarity index 87% rename from pcdl/commandline.py rename to pcdl/pyCLI.py index f77ec5e..2dd4f0c 100644 --- a/pcdl/commandline.py +++ b/pcdl/pyCLI.py @@ -1,5 +1,5 @@ ### -# title: commandline.py +# title: pyCLI.py # # language: python3 # date: 2024-02-21 @@ -7,7 +7,7 @@ # author: Elmar Bucher # # description: -# commandline.py provides command line interface commands for appropriate pcdl functions. +# pyCLI.py provides command line interface commands for appropriate pcdl functions. # all clis mirror the related python function interface as close as possible. # i like to thank Miguel Ponce-de-Leon for making me aware of the # entry point implementation technic which makes all of this possible. @@ -23,10 +23,8 @@ import numpy as np import os import pandas as pd -import pathlib import pcdl from scipy import stats -import subprocess import sys @@ -83,10 +81,10 @@ def get_version(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_version : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_version : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -123,7 +121,7 @@ def get_unit_dict(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.', ) # TimeSeries graph False # TimeSeries physiboss False @@ -156,10 +154,10 @@ def get_unit_dict(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_unit_dict : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_unit_dict : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -228,10 +226,10 @@ def get_substrate_list(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_substrate_list : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_substrate_list : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -318,10 +316,10 @@ def get_conc_attribute(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_conc_attribute : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.pyCLI.get_conc_attribute : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, #custom_data_type, load = True, @@ -341,8 +339,7 @@ def get_conc_attribute(): keep = set(args.keep), allvalues = b_allvalues, ) - s_ofile = f"timeseries_conc_attribute_{s_values.replace(' ','_')}.json" - s_opathfile = s_path + '/' + s_ofile + s_opathfile = f'{s_path}/timeseries_conc_attribute_{s_values}.json' json.dump(dl_variable, open(s_opathfile, 'w'), sort_keys=True) # going home print(s_opathfile) @@ -421,11 +418,11 @@ def get_conc_df(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_conc_df : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_conc_df : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -446,7 +443,7 @@ def get_conc_df(): print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, #custom_data_type, load = True, @@ -474,7 +471,6 @@ def get_conc_df(): for i, df_conc in enumerate(ldf_conc): df_conc.to_csv(ls_opathfile[i]) print(ls_opathfile) - # going home return 0 @@ -585,6 +581,12 @@ def plot_contour(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) + # plot_contour directory + parser.add_argument( + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', + ) # plot_contour ext parser.add_argument( '--ext', @@ -614,15 +616,15 @@ def plot_contour(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_plot_contour : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.plot_contour : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # focus if (args.focus == None): - sys.exit(f'Error @ pcdl_plot_contour : input for positional argument focus is missung! this has to be a column name from the conc dataframe.') + sys.exit(f'Error @ pyCLI.plot_contour : input for positional argument focus is missung! this has to be a column name from the conc dataframe.') # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -658,6 +660,7 @@ def plot_contour(): xyequal = False if args.xyequal.lower().startswith('f') else True, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -665,7 +668,7 @@ def plot_contour(): print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, #custom_data_type, load = True, @@ -689,13 +692,13 @@ def plot_contour(): ylim = None if (args.ylim[0].lower() == 'none') else args.ylim, xyequal = False if args.xyequal.lower().startswith('f') else True, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(n) for n in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) # going home s_opath = '/'.join(ls_opathfile[0].split('/')[:-1]) print(s_opath) - # going home return 0 @@ -744,11 +747,11 @@ def make_conc_vtk(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_make_conc_vtk : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.make_conc_vtk : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', custom_data_type = {}, @@ -758,12 +761,14 @@ def make_conc_vtk(): settingxml = None, verbose = False if args.verbose.lower().startswith('f') else True ) - s_opathfile = mcds.make_conc_vtk() + s_opathfile = mcds.make_conc_vtk( + visualize = False, + ) # going home print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, custom_data_type = {}, load = True, @@ -773,13 +778,15 @@ def make_conc_vtk(): settingxml = None, verbose = False if args.verbose.lower().startswith('f') else True, ) - ls_opathfile = mcdsts.make_conc_vtk() + ls_opathfile = mcdsts.make_conc_vtk( + visualize = False, + ) # going home print(ls_opathfile) - # going home return 0 + ############################################ # cell agent relatd command line functions # ############################################ @@ -833,10 +840,10 @@ def get_celltype_list(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_celltype_list : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_celltype_list : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -926,6 +933,7 @@ def get_cell_attribute_list(): # going home print(mcds.get_cell_attribute_list()) + return 0 def get_cell_attribute(): @@ -955,7 +963,7 @@ def get_cell_attribute(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.', ) # TimeSeries graph False # TimeSeries physiboss @@ -1021,7 +1029,7 @@ def get_cell_attribute(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_cell_attribute : {s_pathfile} path does not look like a physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_cell_attribute : {s_pathfile} path does not look like a physicell output directory ({s_path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -1032,12 +1040,12 @@ def get_cell_attribute(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_get_cell_attribute : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.get_cell_attribute : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', custom_data_type = d_vartype, @@ -1052,10 +1060,10 @@ def get_cell_attribute(): b_allvalues = True if args.allvalues.lower().startswith('t') else False if b_allvalues: s_values = 'all' - s_opathfile = f"{s_pathfile.replace('.xml','')}_{s_values.replace(' ','_')}.json" + s_opathfile = f"{s_pathfile.replace('.xml','')}_{s_values}.json" else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, custom_data_type = d_vartype, load = True, @@ -1070,8 +1078,7 @@ def get_cell_attribute(): b_allvalues = True if args.allvalues.lower().startswith('t') else False if b_allvalues: s_values = 'all' - s_ofile = f"timeseries_cell_attribute_{s_values.replace(' ','_')}.json" - s_opathfile = s_path + '/' + s_ofile + s_opathfile = f'{s_path}/timeseries_cell_attribute_{s_values}.json' # going home dl_variable = mcdsts.get_cell_attribute( @@ -1106,7 +1113,7 @@ def get_cell_df(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.' + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.' ) # TimeSeries graph False # TimeSeries physiboss @@ -1172,11 +1179,11 @@ def get_cell_df(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_cell_df : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_cell_df : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', #custom_data_type, @@ -1197,7 +1204,7 @@ def get_cell_df(): print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, #custom_data_type, load = True, @@ -1225,7 +1232,6 @@ def get_cell_df(): for i, df_cell in enumerate(ldf_cell): df_cell.to_csv(ls_opathfile[i]) print(ls_opathfile) - # going home return 0 @@ -1257,7 +1263,7 @@ def get_anndata(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment be extracted and loaded into the anndata object? setting microenv to False will use less memory and speed up processing. default is True.' + help = 'should the microenvironment be extracted and loaded into the anndata object? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.' ) # TimeSeries graph parser.add_argument( @@ -1334,7 +1340,7 @@ def get_anndata(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_get_anndata : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.get_anndata : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -1345,7 +1351,7 @@ def get_anndata(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_get_anndata : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.get_anndata : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # run @@ -1393,15 +1399,14 @@ def get_anndata(): ) # going home if b_collapse : - s_opathfile = f'{s_path}/timeseries_cell_{args.scale}.h5ad' + s_opathfile = f'{s_path}/timeseries_cell_{args.scale.lower()}.h5ad' ann_mcdsts.write_h5ad(s_opathfile) print(s_opathfile) else: - ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale))}" for s_xmlfile in mcdsts.get_xmlfile_list()] + ls_opathfile = [f"{s_path}/{s_xmlfile.replace('.xml', '_cell_{}.h5ad'.format(args.scale.lower()))}" for s_xmlfile in mcdsts.get_xmlfile_list()] for i, ann_mcds in enumerate(ann_mcdsts): ann_mcds.write_h5ad(ls_opathfile[i]) print(ls_opathfile) - # going home return 0 @@ -1433,7 +1438,7 @@ def make_graph_gml(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.' + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.' ) # TimeSeries graph True # TimeSeries physiboss @@ -1490,7 +1495,7 @@ def make_graph_gml(): else: s_path = '/'.join(s_path.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_make_graph_gml : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.make_graph_gml : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -1501,12 +1506,12 @@ def make_graph_gml(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_make_graph_gml : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.make_graph_gml : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', custom_data_type = d_vartype, @@ -1525,7 +1530,7 @@ def make_graph_gml(): print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, custom_data_type = d_vartype, load = True, @@ -1542,11 +1547,9 @@ def make_graph_gml(): ) # going home print(ls_opathfile) - # going home return 0 - def plot_scatter(): # argv parser = argparse.ArgumentParser( @@ -1574,7 +1577,7 @@ def plot_scatter(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.', ) # TimeSeries graph False # TimeSeries physiboss @@ -1682,6 +1685,12 @@ def plot_scatter(): default = ['none'], help = 'size of the figure in pixels (integer), x y. the given x and y will be rounded to the nearest even number, to be able to generate movies from the images. None tries to take the values from the initial.svg file. fall back setting is 640 480. default is None.', ) + # plot_scatter directory + parser.add_argument( + '--directory', + default = 'none', + help = 'if none, a meaningful output directory name will be generated, based on focus and z_slice parameters, else the resulting plots will be moved to the explicit name directory.', + ) # plot_scatter ext parser.add_argument( '--ext', @@ -1711,7 +1720,7 @@ def plot_scatter(): else: s_path = '/'.join(s_pathfile.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_plot_scatter : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.plot_scatter : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -1722,12 +1731,12 @@ def plot_scatter(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_plot_scatter : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.plot_scatter : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', custom_data_type = d_vartype, @@ -1753,6 +1762,7 @@ def plot_scatter(): s = args.s, ax = None, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) @@ -1760,7 +1770,7 @@ def plot_scatter(): print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, custom_data_type = d_vartype, load = True, @@ -1785,13 +1795,13 @@ def plot_scatter(): xyequal = False if args.xyequal.lower().startswith('f') else True, s = args.s, figsizepx = None if (args.figsizepx[0].lower() == 'none') else [int(i) for i in args.figsizepx], + directory = None if (args.directory.lower() == 'none') else args.directory, ext = args.ext, figbgcolor = None if (args.figbgcolor.lower() == 'none') else args.figbgcolor, ) # going home - s_opath = '/'.join(ls_opathfile[0].split('/')[:-1]) - print(s_opath) - + s_opathfile = '/'.join(ls_opathfile[0].split('/')[:-1]) + print(s_opathfile) # going home return 0 @@ -1823,7 +1833,7 @@ def make_cell_vtk(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.', ) # TimeSeries graph False # TimeSeries physiboss @@ -1868,7 +1878,7 @@ def make_cell_vtk(): else: s_path = '/'.join(s_pathfile.split('/')[:-1]) if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_make_cell_vtk : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.make_cell_vtk : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -1879,12 +1889,12 @@ def make_cell_vtk(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_make_cell_vtk : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.make_cell_vtk : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # run if os.path.isfile(args.path): - mcds = pcdl.TimeStep( + mcds = pcdl.pyMCDS( xmlfile = s_pathfile, output_path = '.', custom_data_type = d_vartype, @@ -1896,12 +1906,13 @@ def make_cell_vtk(): ) s_opathfile = mcds.make_cell_vtk( attribute = args.attribute, + visualize = False, ) # going home print(s_opathfile) else: - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = s_path, custom_data_type = d_vartype, load = True, @@ -1913,10 +1924,10 @@ def make_cell_vtk(): ) ls_opathfile = mcdsts.make_cell_vtk( attribute = args.attribute, + visualize = False, ) # going home print(ls_opathfile) - # going home return 0 @@ -1951,7 +1962,7 @@ def plot_timeseries(): parser.add_argument( '--microenv', default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.', + help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing, similar to the original pyMCDS_cells.py script. default is True.', ) # TimeSeries graph # nop @@ -2132,7 +2143,7 @@ def plot_timeseries(): # path if not os.path.exists(args.path + '/initial.xml'): - sys.exit(f'Error @ pcdl_plot_timeseries : path does not look like a physicell output directory ({args.path}/initial.xml is missing).') + sys.exit(f'Error @ pyCLI.plot_timeseries : path does not look like a physicell output directory ({args.path}/initial.xml is missing).') # custom_data_type d_vartype = {} @@ -2143,7 +2154,7 @@ def plot_timeseries(): elif s_type in {'float'}: o_type = float elif s_type in {'str'}: o_type = str else: - sys.exit(f'Error @ pcdl_plot_timeseries : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') + sys.exit(f'Error @ pyCLI.plot_timeseries : {s_var} {s_type} has an unknowen data type. knowen are bool, int, float, str.') d_vartype.update({s_var : o_type}) # aggregate_num @@ -2154,7 +2165,7 @@ def plot_timeseries(): elif (args.aggregate_num == 'min'): o_aggregate_num = np.nanmin elif (args.aggregate_num == 'std'): o_aggregate_num = np.nanstd elif (args.aggregate_num == 'var'): o_aggregate_num = np.nanvar - else: sys.exit(f'Error @ pcdl_plot_timeseries : unknowen aggregate_num {args.aggregate_num}. knowen are entropy, max, mean, median, min, std, var.') + else: sys.exit(f'Error @ pyCLI.plot_timeseries : unknowen aggregate_num {args.aggregate_num}. knowen are entropy, max, mean, median, min, std, var.') # secondary_y if (args.secondary_y[0].lower() == 'false'): ls_secondary_y = False @@ -2167,7 +2178,7 @@ def plot_timeseries(): else: b_legend = True # run - mcdsts = pcdl.TimeSeries( + mcdsts = pcdl.pyMCDSts( output_path = args.path, custom_data_type = d_vartype, load = True, @@ -2207,203 +2218,6 @@ def plot_timeseries(): return 0 -def make_ome_tiff(): - # argv - parser = argparse.ArgumentParser( - prog = 'pcdl_make_ome_tiff', - description = 'function to transform chosen mcdsts output into an 1[um] spaced tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file, one substrate or cell_type per channel. the ome tiff file format can for example be read by the napari (https://napari.org/stable/) or fiji imagej (https://fiji.sc/) software.', - epilog = 'homepage: https://github.com/elmbeech/physicelldataloader', - ) - - # TimeSeries path - parser.add_argument( - 'path', - nargs = '?', - default = '.', - help = 'path to the PhysiCell output directory or a outputnnnnnnnn.xml file. default is . .', - ) - # TimeSeries output_path '.' - # TimeSeries custom_data_type {} - # TimeSeries microenv - parser.add_argument( - '--microenv', - default = 'true', - help = 'should the microenvironment data be loaded? setting microenv to False will use less memory and speed up processing. default is True.' - ) - # TimeSeries graph False - # TimeSeries physiboss - parser.add_argument( - '--physiboss', - default = 'true', - help = 'if found, should physiboss state data be extracted and loaded into the df_cell dataframe? default is True.' - ) - # TimeSeries settingxml - parser.add_argument( - '--settingxml', - default = 'PhysiCell_settings.xml', - help = 'the settings.xml that is loaded, from which the cell type ID label mapping, is extracted, if this information is not found in the output xml file. set to None or False if the xml file is missing! default is PhysiCell_settings.xml.', - ) - # TimeSeries verbose - parser.add_argument( - '-v', '--verbose', - default = 'true', - help = 'setting verbose to False for less text output, while processing. default is True.', - ) - # make_ome_tiff cell_attribute - parser.add_argument( - 'cell_attribute', - nargs = '?', - default = 'ID', - help = 'mcds.get_cell_df dataframe column, used for cell_attribute. the column data type has to be numeric (bool, int, float) and cannot be string. the result will be stored as 32 bit float. default is ID, with will result in a segmentation mask.', - ) - # make_ome_tiff conc_cutoff - parser.add_argument( - '--conc_cutoff', - nargs = '*', - default = [], - help = 'if a contour from a substrate not should be cut by greater than zero (shifted to integer 1), another cutoff value can be specified here like this: substarte:value substrate:value substarte:value . default is and empty string.', - ) - # make_ome_tiff focus - parser.add_argument( - '--focus', - nargs = '+', - default = ['none'], - help = 'set of substrate and cell_type names to specify what will be translated into ome tiff format. if None, all substrates and cell types will be processed. default is a None.', - ) - # make_ome_tiff file True - # make_ome_tiff collapse - parser.add_argument( - '--collapse', - default = 'true', - help = 'should all mcds time steps from the time series be collapsed into one big ome.tiff, or a many ome.tiff, one ome.tiff for each time step?, default is True.' - ) - - # parse arguments - args = parser.parse_args() - print(args) - - # path - s_path = args.path - while (s_path.find('//') > -1): - s_path = s_path.replace('//','/') - if (s_path.endswith('/')) and (len(s_path) > 1): - s_path = s_path[:-1] - s_pathfile = s_path - if not s_pathfile.endswith('.xml'): - s_pathfile = s_pathfile + '/initial.xml' - else: - s_path = '/'.join(s_pathfile.split('/')[:-1]) - if not os.path.exists(s_pathfile): - sys.exit(f'Error @ pcdl_make_ome_tiff : {s_pathfile} path does not look like a outputnnnnnnnn.xml file or physicell output directory ({s_path}/initial.xml is missing).') - - # conc_cutoff - d_conccutoff = {} - for s_conccutoff in args.conc_cutoff: - s_substrate, s_value = s_conccutoff.split(':') - if (s_value.find('.') > -1): - o_value = float(s_value) - else: - o_value = int(s_value) - d_conccutoff.update({s_substrate : o_value}) - - # focus - if (args.focus[0].lower() == 'none'): - es_focus = None - else: - es_focus = set( args.focus) - - # run - if os.path.isfile(args.path): - mcds = pcdl.TimeStep( - xmlfile = s_pathfile, - output_path = '.', - custom_data_type = {}, - microenv = False if args.microenv.lower().startswith('f') else True, - graph = False, - physiboss = False if args.physiboss.lower().startswith('f') else True, - settingxml = None if ((args.settingxml.lower() == 'none') or (args.settingxml.lower() == 'false')) else args.settingxml, - verbose = False if args.verbose.lower().startswith('f') else True - ) - s_opathfile = mcds.make_ome_tiff( - cell_attribute = args.cell_attribute, - conc_cutoff = d_conccutoff, - focus = es_focus, - file = True, - ) - # going home - print(s_opathfile) - - else: - mcdsts = pcdl.TimeSeries( - output_path = s_path, - custom_data_type = {}, - load = True, - microenv = False if args.microenv.lower().startswith('f') else True, - graph = False, - physiboss = False if args.physiboss.lower().startswith('f') else True, - settingxml = None if ((args.settingxml.lower() == 'none') or (args.settingxml.lower() == 'false')) else args.settingxml, - verbose = False if args.verbose.lower().startswith('f') else True, - ) - o_opathfile = mcdsts.make_ome_tiff( - cell_attribute = args.cell_attribute, - conc_cutoff = d_conccutoff, - focus = es_focus, - file = True, - collapse = False if args.collapse.lower().startswith('f') else True, - ) - # going home - print(o_opathfile) - - # going home - return 0 - - -####################### -# render neuroglancer # -####################### - -def render_neuroglancer(): - # argv - parser = argparse.ArgumentParser( - prog = 'pcdl_render_neuroglancer', - description = 'function to load a time step from an ome tiff files, generated with make_ome_tiff, into neuroglancer.', - epilog = 'homepage: https://github.com/elmbeech/physicelldataloader', - ) - # ome tiff path file - parser.add_argument( - 'tiffpathfile', - nargs = '?', - default = '.', - help = 'path to ome tiff file.', - ) - # time step - parser.add_argument( - 'timestep', - nargs = '?', - default = 0, - type = int, - help = 'time step, within a possibly collapsed ome tiff file, to render. the default will work with single time step ome tiff files.', - ) - # intensity colormap - parser.add_argument( - '--intensity_cmap', - default = 'gray', - help = 'matlab color map label, used to display expression intensity values. if None, no intensity layers will be generated. https://matplotlib.org/stable/users/explain/colors/colormaps.html', - ) - - # parse arguments - args = parser.parse_args() - print(args) - - # process arguments - s_neuromancerpath = str(pathlib.Path(pcdl.__file__).parent).replace('\\','/') + '/' - s_tiffpathfile = args.tiffpathfile.replace('\\','/') - - # run - # bue 20250623: use subprocess to run python3 in interactive mode to run the neuromancer script, which is needed to keep the neuroglancer web gl server running. - subprocess.run(['python3', '-i', f'{s_neuromancerpath}neuromancer.py', s_tiffpathfile, '--timestep', str(args.timestep), '--intensity_cmap', args.intensity_cmap]) - - ################# # making movies # ################# diff --git a/pcdl/timestep.py b/pcdl/pyMCDS.py similarity index 68% rename from pcdl/timestep.py rename to pcdl/pyMCDS.py index c727261..4f14a0c 100644 --- a/pcdl/timestep.py +++ b/pcdl/pyMCDS.py @@ -1,5 +1,5 @@ ######### -# title: timestep.py +# title: pyMCDS.py # # language: python3 # date: 2022-08-22 @@ -7,32 +7,25 @@ # authors: Patrick Wall, Randy Heiland, Furkan Kurtoglu, Paul Macklin, Elmar Bucher # # description: -# timestep.py definds an object class, able to load and access +# pyMCDS.py definds an object class, able to load and access # within python a single time step from the PhysiCell model output folder. -# timestep.py was forked from the original PhysiCell-Tools python-loader +# pyMCDS.py was forked from the original PhysiCell-Tools python-loader # implementation and further developed. ######### # load library -import anndata as ad -import bioio_base -from bioio.writers import OmeTiffWriter import matplotlib.pyplot as plt from matplotlib import cm from matplotlib import colors -import neuroglancer +import matplotlib.patches as mpatches import numpy as np import os import pandas as pd -from pcdl import imagine -from pcdl import pdplt -from pcdl import neuromancer +import random from scipy import io -from scipy import sparse import sys import vtk -import warnings import xml.etree.ElementTree as etree from pcdl.VERSION import __version__ @@ -175,43 +168,73 @@ # functions -def render_neuroglancer(tiffpathfile, timestep=0, intensity_cmap='gray'): - """ +def df_label_to_color(df_abc, s_focus, es_label=None, s_nolabel='gray', s_cmap='viridis', b_shuffle=False): + ''' input: - tiffpathfile: string. - path to ome tiff file. + df_abc: dataframe to which the color column will be added. + s_focus: column name with sample labels for which a color column will be generated. + es_label: set of labels to color. if None, es_label will be extracted for the s_focus column. + s_nolabel: color for labels not defined in es_label. + s_cmap: matplotlib color map label. + https://matplotlib.org/stable/tutorials/colors/colormaps.html + b_shuffle: should colors be given by alphabetical order, + or should the label color mapping order be random. + + output: + df_abc: dataframe updated with color column. + ds_color: lable to hex color string mapping dictionary + + description: + function adds for the selected label column + a color column to the df_abc dataframe. + ''' + if (es_label is None): + es_label = set(df_abc.loc[:,s_focus]) + if b_shuffle: + ls_label = list(es_label) + random.shuffle(ls_label) + else: + ls_label = sorted(es_label) + a_color = plt.get_cmap(s_cmap)(np.linspace(0, 1, len(ls_label))) + do_color = dict(zip(ls_label, a_color)) + df_abc[f'{s_focus}_color'] = s_nolabel + ds_color = {} + for s_category, o_color in do_color.items(): + s_color = colors.to_hex(o_color) + ds_color.update({s_category : s_color}) + df_abc.loc[(df_abc.loc[:,s_focus] == s_category), f'{s_focus}_color'] = s_color + # output + return(ds_color) - timestep: integer, default is 0. - variable to specify the specific time step to render. - useful for time series ome.tiff files. - the default is compatible with single time step ome.tiff files. - intensity_cmap: string; default is 'gray'. - matlab color map label, used to display expression intensity values. - if None, no intensity layers will be generated. - + https://matplotlib.org/stable/users/explain/colors/colormaps.html +def ax_colorlegend(ax, ds_color, s_loc='lower left', s_fontsize='small'): + ''' + input: + ax: matplotlib axis object to which a color legend will be added. + ds_color: lables to color strings mapping dictionary + s_loc: the location of the legend. + possible strings are: best, + upper right, upper center, upper left, center left, + lower left, lower center, lower right, center right, + center. + s_fontsize: font size used for the legend. known are: + xx-small, x-small, small, medium, large, x-large, xx-large. output: - viewer: local url where the loaded, neuroglancer rendered ome tiff file - can be viewed. + ax: matplotlib axis object updated with color legend. description: - function to load a time step from an ome tiff files, generated - with make_ome_tiff, into neuroglancer. - """ - # start neuroglancer - viewer = neuroglancer.Viewer() - with viewer.txn() as state: - # render ometiff into neuroglancer - neuromancer.ometiff2neuro( - o_state = state, - s_pathfile_tiff = tiffpathfile, - i_timestep = timestep, - s_intensity_cmap = intensity_cmap, - ) - - # print neuroglancer viewer url - return viewer + function to add color legend to a figure. + ''' + lo_patch = [] + for s_label, s_color in sorted(ds_color.items()): + o_patch = mpatches.Patch(color=s_color, label=s_label) + lo_patch.append(o_patch) + ax.legend( + handles = lo_patch, + loc = s_loc, + fontsize = s_fontsize + ) def graphfile_parser(s_pathfile): @@ -244,239 +267,8 @@ def graphfile_parser(s_pathfile): return dei_graph -def scaler(df_x, scale='maxabs'): - """ - input: - df_x: pandas dataframe - one attribute per column, one sample per row. - - scale: string; default 'maxabs' - None: no scaling. set scale to None if you would like to have - raw data or scale, transform, and normalize the data later. - - maxabs: maximum absolute value distance scaler will linearly map - all values into a [-1, 1] interval. if the original data - has no negative values, the result will be the same as with - the minmax scaler (except with attributes with only one value). - if the attribute has only zeros, the value will be set to 0. - - minmax: minimum maximum distance scaler will map all values - linearly into a [0, 1] interval. - if the attribute has only one value, the value will be set to 0. - - std: standard deviation scaler will result in sigmas. - each attribute will be mean centered around 0. - ddof delta degree of freedom is set to 1 because it is assumed - that the values are samples out of the population - and not the entire population. it is incomprehensible to me - that the equivalent sklearn method has ddof set to 0. - if the attribute has only one value, the value will be set to 0. - - output: - df_x: pandas dataframe - scaled df_x dataframe. - - description: - inspired by scikit-learn's preprocessing scaling method, this function - offers a re-implementation of the linear re-scaling methods maxabs, - minmax, and scale. - - the robust scaler methods (quantile based) found in scikit-learn are - missing. since we deal with simulated data, we don't expect heavy - outliers, and if they exist, then they are of interest. - the power and quantile based transformation methods and unit circle - based normalizer methods found there are missing too. - if you need to apply any such methods, you can do so to an anndata object - like this: - - from sklearn import preprocessing - adata.obsm["X_scaled"] = preprocessing.scale(adata.X) - - + https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html - + https://scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing - + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.maxabs_scale.html - + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.minmax_scale.html - + https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html - """ - if scale is None: - pass - # -1,1 - elif scale == 'maxabs': - a_x = df_x.values - warnings.filterwarnings('ignore', category=RuntimeWarning) - a_maxabs = a_x / abs(a_x).max(axis=0) - warnings.simplefilter('default') - a_maxabs[np.isnan(a_maxabs)] = 0 # fix if entier column is 0 - df_x = pd.DataFrame(a_maxabs, columns=df_x.columns, index=df_x.index) - # 0,1 - elif scale == 'minmax': - a_x = df_x.values - warnings.simplefilter("ignore") - warnings.filterwarnings('ignore', category=RuntimeWarning) - a_minmax = (a_x - a_x.min(axis=0)) / (a_x.max(axis=0) - a_x.min(axis=0)) - warnings.simplefilter('default') - a_minmax[np.isnan(a_minmax)] = 0 # fix if entier column has same value - df_x = pd.DataFrame(a_minmax, columns=df_x.columns, index=df_x.index) - # sigma - elif scale == 'std': - a_x = df_x.values - warnings.filterwarnings('ignore', category=RuntimeWarning) - a_std = (a_x - a_x.mean(axis=0)) / a_x.std(axis=0, ddof=1) - warnings.simplefilter('default') - a_std[np.isnan(a_std)] = 0 # fix if entier column has same value - df_x = pd.DataFrame(a_std, columns=df_x.columns, index=df_x.index) - else: - raise ValueError(f"Error @ scaler : unknown scale algorithm {scale} detected. known are [None, 'maxabs', 'minmax', 'std'].") - - return df_x - - -def _anndextract(df_cell, scale='maxabs', graph_attached={}, graph_neighbor={}, graph_spring={}, graph_method='PhysiCell'): - """ - input: - df_cell: pandas dataframe - data frame retrieved with the mcds.get_cell_df function. - - scale: string; default maxabs - specify how the data should be scaled. - possible values are None, maxabs, minmax, std. - for more input, check out: help(pcdl.scaler). - - graph_attached: dict; default {} - attached graph dictionary, retrieved with - with the mcds.get_attched_graph() function. - - graph_neighbor: dict; default {} - neighbor graph dictionary, retrieved - with the mcds.get_neighbor_graph() function. - - graph_spring: dict; default {} - spring_attached graph dictionary, retrieved - with the mcds.get_spring_graph_dict() function. - - graph_method: string; default PhysiCell - method how the graphs were generated. - - output: - df_count, df_obs, d_obsm, d_obsp, d_uns dataframes and dictionaries, - ready to be backed into an anndata object. - - description: - this function takes a pcdl df_cell pandas dataframe and re-formats - it into a set of two dataframes (df_count, df_obs), - two dictionary of numpy array (d_obsm, d_obsp), - and one dictionary of string (d_uns), - which downstream might be transformed into an anndata object. - """ - # transform index to string - df_coor = df_cell.loc[:,['position_x','position_y','position_z']].copy() - df_cell.index = df_cell.index.astype(str) - - # build obs anndata object (annotation of observations) - df_obs = df_cell.loc[:,['mesh_center_p','time']].copy() - df_obs.columns = ['z_layer', 'time'] - - # buil obsm anndata object spatial (multi-dimensional annotation of observations) - if (len(set(df_cell.position_z)) == 1): - df_obsm = df_cell.loc[:,['position_x','position_y']].copy() - else: - df_obsm = df_cell.loc[:,['position_x','position_y','position_z']].copy() - d_obsm = {"spatial": df_obsm.values} - - # build obsp and uns anndata object graph (pairwise annotation of obeservation) and (unstructured data) - #### - # acknowledgement: - # this code is inspired from the tysserand add_to_AnnData impelmentation - # from Alexis Coullomb form the Pancaldi Lab. - # https://github.com/VeraPancaldiLab/tysserand/blob/main/tysserand/tysserand.py#L1546 - #### - # extract cell_id to index mapping (i always loved perl) - di_ididx = df_cell.reset_index().loc[:,'ID'].reset_index().astype(int).set_index('ID').squeeze().to_dict() - # transform cell id graph dict to index matrix and pack for anndata - d_obsp = {} # pairwise annotation of obeservation - d_uns = {} # unstructured data - for s_graph, dei_graph in [('neighbor', graph_neighbor), ('attached', graph_attached), ('spring', graph_spring)]: - lli_edge = [] - lr_distance = [] - for i_src, ei_dst in dei_graph.items(): - for i_dst in ei_dst: - # extract edge - lli_edge.append([di_ididx[i_src], di_ididx[i_dst]]) - r_distance = ((df_coor.loc[i_src,:].values - df_coor.loc[i_dst,:].values)**2).sum()**(1/2) - lr_distance.append(r_distance) - # if there is a graph - if (len(lli_edge) > 0): - # handle edge data - ai_edge = np.array(lli_edge, dtype=np.uint) - # handle connection data - ai_conectivity = np.ones(ai_edge.shape[0], dtype=np.uint16) - ai_conectivity_sparse = sparse.csr_matrix( - (ai_conectivity, (ai_edge[:,0], ai_edge[:,1])), - shape = (df_cell.shape[0], df_cell.shape[0]), - dtype = np.uint - ) - # handle distance data - ar_distance = np.array(lr_distance, dtype=np.float64) - ar_distance_sparse = sparse.csr_matrix( - (ar_distance, (ai_edge[:,0], ai_edge[:,1])), - shape = (df_cell.shape[0], df_cell.shape[0]), - dtype = np.float64 - ) - # pack obsp - d_obsp.update({ - f'physicell_{s_graph}_conectivities': ai_conectivity_sparse, - f'physicell_{s_graph}_distances': ar_distance_sparse, - }) - # pack uns - d_uns.update({ - s_graph : { - 'connectivities_key': f'physicell_{s_graph}_conectivities', - 'distances_key': f'physicell_{s_graph}_distances', - 'params': { - 'metric': 'euclidean', - 'method': graph_method, - } - } - }) - - # extract discrete cell data - es_drop = set(df_cell.columns).intersection({ - 'voxel_i', 'voxel_j', 'voxel_k', - 'mesh_center_m', 'mesh_center_n', 'mesh_center_p', - 'position_x', 'position_y','position_z', - 'time', 'runtime', 'xmlfile', - }) - df_cell.drop(es_drop, axis=1, inplace=True) # maybe obs? - - # dectect variable types - des_type = {'float': set(), 'int': set(), 'bool': set(), 'str': set()} - for _, se_cell in df_cell.items(): - if str(se_cell.dtype).startswith('float'): - des_type['float'].add(se_cell.name) - elif str(se_cell.dtype).startswith('int'): - des_type['int'].add(se_cell.name) - elif str(se_cell.dtype).startswith('bool'): - des_type['bool'].add(se_cell.name) - elif str(se_cell.dtype).startswith('object'): - des_type['str'].add(se_cell.name) - else: - print(f'Error @ TimeSeries.get_anndata : column {se_cell.name} detected with unknown dtype {str(se_cell.dtype)}.') - - # build on obs and X anndata object - df_cat = df_cell.loc[:,sorted(des_type['str'])].copy() - df_obs = pd.merge(df_obs, df_cat, left_index=True, right_index=True) - es_num = des_type['float'].union(des_type['int'].union(des_type['bool'])) - df_count = df_cell.loc[:,sorted(es_num)].copy() - for s_col in des_type['bool']: - df_count[s_col] = df_count[s_col].astype(int) - df_count = scaler(df_count, scale=scale) - - # return - return(df_count, df_obs, d_obsm, d_obsp, d_uns) - - # object classes -class TimeStep: +class pyMCDS: def __init__(self, xmlfile, output_path='.', custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True): """ input: @@ -496,10 +288,11 @@ def __init__(self, xmlfile, output_path='.', custom_data_type={}, microenv=True, microenv: boole; default True should the microenvironment data be loaded? - setting microenv to False will use less memory and speed up processing. + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. graph: boole; default True - should the graphs, like cell_neighbor_graph.txt, be loaded? + should the graphs be loaded? setting graph to False will use less memory and speed up processing. physiboss: boole; default True @@ -516,11 +309,11 @@ def __init__(self, xmlfile, output_path='.', custom_data_type={}, microenv=True, setting verbose to False for less text output, while processing. output: - mcds: TimeStep class instance + mcds: pyMCDS class instance all fetched content is stored at mcds.data. description: - TimeStep.__init__ will generate a class instance with a + pyMCDS.__init__ will generate a class instance with a dictionary of dictionaries data structure that contains all output from a single PhysiCell model time step. furthermore, this class, and as such it's instances, offers functions @@ -541,7 +334,6 @@ def __init__(self, xmlfile, output_path='.', custom_data_type={}, microenv=True, self.verbose = verbose self.data = self._read_xml(xmlfile, output_path) - def set_verbose_false(self): """ input: @@ -555,7 +347,6 @@ def set_verbose_false(self): self.verbose = False #print(f'pcdl: set mcds.verbose = False.') - def set_verbose_true(self): """ input: @@ -662,7 +453,35 @@ def get_unit_dict(self): function returns a dictionary that stores all tracked variables and their units. """ - return self.data['metadata']['ds_unit'].copy() + # extract data + ds_unit = {} + # units for metadata parameters + ds_unit.update({'time': self.data['metadata']['time_units']}) + ds_unit.update({'runtime': self.data['metadata']['runtime_units']}) + ds_unit.update({'spatial_unit': self.data['metadata']['spatial_units']}) + + # microenvironment + if self.microenv: + for s_substrate in self.get_substrate_list(): + # unit from substrate parameters + s_unit = self.data['continuum_variables'][s_substrate]['units'] + ds_unit.update({s_substrate: s_unit}) + + # units from microenvironment parameters + s_diffusion_key = f'{s_substrate}_diffusion_coefficient' + s_diffusion_unit = self.data['continuum_variables'][s_substrate]['diffusion_coefficient']['units'] + ds_unit.update({s_diffusion_key: s_diffusion_unit}) + + s_decay_key = f'{s_substrate}_decay_rate' + s_decay_unit = self.data['continuum_variables'][s_substrate]['decay_rate']['units'] + ds_unit.update({s_decay_key: s_decay_unit}) + + # units from cell parameters + ds_unit.update(self.data['discrete_cells']['units']) + + # output + del ds_unit['ID'] + return ds_unit ## MESH RELATED FUNCTIONS ## @@ -813,7 +632,26 @@ def get_mesh_spacing(self): function returns the distance in between mesh centers, in the spacial unit defined in the PhysiCell_settings.xml file. """ - return self.data['mesh']['mnp_spacing'].copy() + tr_m_range, tr_n_range, tr_p_range = self.get_mesh_mnp_range() + ar_m_axis, ar_n_axis, ar_p_axis = self.get_mesh_mnp_axis() + + # m axis + if (len(set(tr_m_range)) == 1): + dm = np.float64(1.0) + else: + dm = (tr_m_range[1] - tr_m_range[0]) / (ar_m_axis.shape[0] - 1) + # n axis + if (len(set(tr_n_range)) == 1): + dn = np.float64(1.0) + else: + dn = (tr_n_range[1] - tr_n_range[0]) / (ar_n_axis.shape[0] - 1) + # p axis + if (len(set(tr_p_range)) == 1): + dp = np.float64(1.0) + else: + dp = (tr_p_range[1] - tr_p_range[0]) / (ar_p_axis.shape[0] - 1) + + return [dm, dn, dp] def is_in_mesh(self, x, y, z, halt=False): @@ -849,15 +687,15 @@ def is_in_mesh(self, x, y, z, halt=False): if (x < tr_x[0]) or (x > tr_x[1]): if self.verbose: - print(f'Warning @ TimeStep.is_in_mesh : x = {x} out of bounds: x-range is {tr_x}.') + print(f'Warning @ pyMCDS.is_in_mesh : x = {x} out of bounds: x-range is {tr_x}.') b_isinmesh = False elif (y < tr_y[0]) or (y > tr_y[1]): if self.verbose: - print(f'Warning @ TimeStep.is_in_mesh : y = {y} out of bounds: y-range is {tr_y}.') + print(f'Warning @ pyMCDS.is_in_mesh : y = {y} out of bounds: y-range is {tr_y}.') b_isinmesh = False elif (z < tr_z[0]) or (z > tr_z[1]): if self.verbose: - print(f'Warning @ TimeStep.is_in_mesh : z = {z} out of bounds: z-range is {tr_z}.') + print(f'Warning @ pyMCDS.is_in_mesh : z = {z} out of bounds: z-range is {tr_z}.') b_isinmesh = False # output @@ -919,7 +757,10 @@ def get_voxel_spacing(self): function returns the voxel width, height, depth measurement, in the spacial unit defined in the PhysiCell_settings.xml file. """ - return self.data['mesh']['mnp_spacing'].copy() + r_volume = self.get_voxel_volume() + dm, dn, _ = self.get_mesh_spacing() + dp = r_volume / (dm * dn) + return [dm, dn, dp] def get_voxel_volume(self): @@ -935,7 +776,11 @@ def get_voxel_volume(self): function returns the volume value for a single voxel, related to the spacial unit defined in the PhysiCell_settings.xml file. """ - return self.data['mesh']['volume'] + ar_volume = np.unique(self.data['mesh']['volumes']) + if ar_volume.shape != (1,): + sys.exit(f'Error @ pyMCDS.get_voxel_volume : mesh is not built out of a unique voxel volume {ar_volume}.') + r_volume = ar_volume[0] + return r_volume def get_voxel_ijk(self, x, y, z, is_in_mesh=True): @@ -996,7 +841,10 @@ def get_substrate_list(self): function returns all chemical species names, modeled in the microenvironment, ordered by chemical species ID. """ - return self.data['substrate']['ls_substarte'].copy() + # get substrate listing + ds_substrate = self.get_substrate_dict() + ls_substrate = [ds_substrate[s_key] for s_key in sorted(ds_substrate, key=int)] + return ls_substrate def get_substrate_dict(self): @@ -1012,7 +860,7 @@ def get_substrate_dict(self): microenvironment_setup variables, specified in the PhysiCell_settings.xml file. """ - return self.data['substrate']['ds_substrate'].copy() + return self.data['metadata']['substrate'] def get_substrate_df(self): @@ -1028,7 +876,114 @@ def get_substrate_df(self): function returns a dataframe with each substrate's decay_rate and difusion_coefficient. """ - return self.data['substrate']['df_substarte'].copy() + # extract data + ls_column = ['substrate','decay_rate','diffusion_coefficient'] + ll_sub = [] + for s_substrate in self.get_substrate_list(): + s_decay_value = self.data['continuum_variables'][s_substrate]['decay_rate']['value'] + s_diffusion_value = self.data['continuum_variables'][s_substrate]['diffusion_coefficient']['value'] + ll_sub.append([s_substrate, s_decay_value, s_diffusion_value]) + + # generate dataframe + df_substrate = pd.DataFrame(ll_sub, columns=ls_column) + df_substrate.set_index('substrate', inplace=True) + df_substrate.columns.name = 'attribute' + + # output + return df_substrate + + + def get_concentration(self, substrate, z_slice=None, halt=False): + """ + input: + substrate: string + substrate name. + + z_slice: floating point number; default is None + z-axis position to slice a 2D xy-plain out of the + 3D substrate concentration mesh. if None the + whole 3D mesh will be returned. + + halt: boolean; default is False + should program execution break or just spit out a warning, + if z_slice position is not an exact mesh center coordinate? + if False, z_slice will be adjusted to the nearest + mesh center value, the smaller one, if the coordinate + lies on a saddle point. + + output: + ar_conc: numpy array of floating point numbers + substrate concentration meshgrid or xy-plain slice + through the meshgrid. + + description: + function returns the concentration meshgrid, or a xy-plain slice + out of the whole meshgrid, for the specified chemical species. + """ + ar_conc = self.data['continuum_variables'][substrate]['data'].copy() + + # check if z_slice is a mesh center or None + if not (z_slice is None): + _, _, ar_p_axis = self.get_mesh_mnp_axis() + if not (z_slice in ar_p_axis): + if self.verbose: + print(f'Warning @ pyMCDS.get_concentration : specified z_slice {z_slice} is not an element of the z-axis mesh centers set {ar_p_axis}.') + if halt: + sys.exit('Processing stopped!') + else: + z_slice = ar_p_axis[abs(ar_p_axis - z_slice).argmin()] + print(f'z_slice set to {z_slice}.') + + # filter by z_slice + _, _, ar_p_grid = self.get_mesh() + mask = ar_p_grid == z_slice + ar_conc = ar_conc[mask].reshape((ar_p_grid.shape[0], ar_p_grid.shape[1])) + + # output + return ar_conc + + + def get_concentration_at(self, x, y, z=0): + """ + input: + x: floating point number + position x-coordinate. + + y: floating point number + position y-coordinate. + + z: floating point number; default is 0 + position z-coordinate. + + output: + ar_concs: numpy array of floating point numbers + array of substrate concentrations in the order + given by get_substrate_list(). + + description: + function return concentrations of each chemical species + inside a particular voxel that contains the point specified + in the arguments. + """ + ar_concs = None + + # is coordinate inside the domain? + b_calc = self.is_in_mesh(x=x, y=y, z=z, halt=False) + if b_calc: + + # get voxel coordinate and substrate names + i, j, k = self.get_voxel_ijk(x, y, z, is_in_mesh=False) + ls_substrate = self.get_substrate_list() + ar_concs = np.zeros(len(ls_substrate)) + + # get substrate concentrations + for n, s_substrate in enumerate(ls_substrate): + ar_concs[n] = self.get_concentration(s_substrate)[j, i, k] + if self.verbose: + print(f'pyMCD.get_concentration_at(x={x},y={y},z={z}) | jkl: [{i},{j},{k}] | substrate: {s_substrate} {ar_concs[n]}') + + # output + return ar_concs def get_conc_df(self, z_slice=None, halt=False, values=1, drop=set(), keep=set()): @@ -1075,22 +1030,55 @@ def get_conc_df(self, z_slice=None, halt=False, values=1, drop=set(), keep=set() """ # check keep and drop if (len(keep) > 0) and (len(drop) > 0): - sys.exit(f"Error @ TimeStep.get_conc_df : when keep is given {keep}, then drop has to be an empty set {drop}!") + sys.exit(f"Error @ pyMCDS.get_conc_df : when keep is given {keep}, then drop has to be an empty set {drop}!") # check if z_slice is a mesh center or None if not (z_slice is None): _, _, ar_p_axis = self.get_mesh_mnp_axis() if not (z_slice in ar_p_axis): if self.verbose: - print(f'Warning @ TimeStep.get_conc_df : specified z_slice {z_slice} is not an element of the z-axis mesh centers set {ar_p_axis}.') + print(f'Warning @ pyMCDS.get_conc_df : specified z_slice {z_slice} is not an element of the z-axis mesh centers set {ar_p_axis}.') if halt: sys.exit('Processing stopped!') else: z_slice = ar_p_axis[abs(ar_p_axis - z_slice).argmin()] print(f'z_slice set to {z_slice}.') - # fetch dataframe - df_conc = self.data['substrate']['df_conc'].copy() + # flatten mesh coordnates + ar_m, ar_n, ar_p = self.get_mesh() + ar_m = ar_m.flatten(order='C') + ar_n = ar_n.flatten(order='C') + ar_p = ar_p.flatten(order='C') + + # get mesh spacing + dm, dn, dp = self.get_voxel_spacing() + + # get voxel coordinates + ai_i = ((ar_m - ar_m.min()) / dm) + ai_j = ((ar_n - ar_n.min()) / dn) + ai_k = ((ar_p - ar_p.min()) / dp) + + # handle coordinates + ls_column = [ + 'voxel_i','voxel_j','voxel_k', + 'mesh_center_m','mesh_center_n','mesh_center_p' + ] + la_data = [ai_i, ai_j, ai_k, ar_m, ar_n, ar_p] + + # handle concentrations + for s_substrate in self.get_substrate_list(): + ls_column.append(s_substrate) + ar_conc = self.get_concentration(substrate=s_substrate, z_slice=None) + la_data.append(ar_conc.flatten(order='C')) + + # generate dataframe + aa_data = np.array(la_data) + df_conc = pd.DataFrame(aa_data.T, columns=ls_column) + df_conc['time'] = self.get_time() + df_conc['runtime'] = self.get_runtime() / 60 # in min + df_conc['xmlfile'] = self.xmlfile + d_dtype = {'voxel_i': int, 'voxel_j': int, 'voxel_k': int} + df_conc = df_conc.astype(d_dtype) # filter z_slice if not (z_slice is None): @@ -1118,7 +1106,7 @@ def get_conc_df(self, z_slice=None, halt=False, values=1, drop=set(), keep=set() return df_conc - def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, ext=None, figbgcolor=None, **kwargs): + def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=True, cmap='viridis', title=None, grid=True, xlim=None, ylim=None, xyequal=True, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): """ input: focus: string @@ -1180,6 +1168,11 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -1188,11 +1181,6 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the matplotlib contour and contourf function. - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html - output: fig: matplotlib figure, depending on ext, either as object or as file. the figure containing the contour plot and color bar. @@ -1213,7 +1201,7 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T figsizepx = [i_width, i_height] except FileNotFoundError: if self.verbose: - print(f'Warning @ TimeStep.plot_contour : could not load {s_pathfile} to auto detect figsizepx. take default.') + print(f'Warning @ pyMCDS.plot_contour : could not load {s_pathfile} to auto detect figsizepx. take default.') figsizepx = [640, 480] # handle figure size @@ -1274,9 +1262,9 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T # get contour plot if fill: - ax.contourf(df_mesh.columns, df_mesh.index, df_mesh.values, vmin=vmin, vmax=vmax, alpha=alpha, cmap=cmap, **kwargs) + ax.contourf(df_mesh.columns, df_mesh.index, df_mesh.values, vmin=vmin, vmax=vmax, alpha=alpha, cmap=cmap) else: - ax.contour(df_mesh.columns, df_mesh.index, df_mesh.values, vmin=vmin, vmax=vmax, alpha=alpha, cmap=cmap, **kwargs) + ax.contour(df_mesh.columns, df_mesh.index, df_mesh.values, vmin=vmin, vmax=vmax, alpha=alpha, cmap=cmap) # set title if not (title is None): @@ -1305,9 +1293,12 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T else: # handle output path and filename - s_path = self.path + f"/conc_{focus.replace(' ','_')}_z{round(z_slice,9)}/" + if (directory is None): + s_path = self.path + f'/conc_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) - s_file = self.xmlfile.replace('.xml', f"_{focus.replace(' ','_')}.{ext}") + s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' # handle figure background color if figbgcolor is None: @@ -1320,9 +1311,11 @@ def plot_contour(self, focus, z_slice=0.0, vmin=None, vmax=None, alpha=1, fill=T return s_pathfile - def make_conc_vtk(self): + def make_conc_vtk(self, visualize=True): """ input: + visualize: boolean; default is True + additionally, visualize cells using vtk renderer. output: s_vtkpathfile: vtk rectilinear grid file that contains @@ -1393,13 +1386,118 @@ def make_conc_vtk(self): (df_conc.loc[:,'voxel_k'] == k) & (df_conc.loc[:,'voxel_j'] == j) & (df_conc.loc[:,'voxel_i'] == i), s_substrate ].values[0] + #vfa_value.InsertNextValue(r_conc) vfa_value.SetValue(i_index, r_conc) if b_first: + #vrg_data.GetCellData().SetScalars(vfa_value) vrg_data.GetPointData().SetScalars(vfa_value) b_first = False else: + #vrg_data.GetCellData().AddArray(vfa_value) vrg_data.GetPointData().AddArray(vfa_value) + # visualize on the fly + if (visualize): + # get scalar range + r_vmin = np.floor(df_conc.loc[:, s_substrate].min()) + r_vmax = np.ceil(df_conc.loc[:, s_substrate].max()) + + # generate the structured grid. + vsp_data = vtk.vtkStructuredPoints() + vsp_data.SetDimensions(ti_dim[0]+1, ti_dim[1]+1, ti_dim[2]+1) + vsp_data.SetSpacing( + self.get_voxel_spacing()[0], + self.get_voxel_spacing()[1], + self.get_voxel_spacing()[2], + ) + vsp_data.SetOrigin( + self.get_mesh_mnp_range()[0][0], + self.get_mesh_mnp_range()[1][0], + self.get_mesh_mnp_range()[2][0], + ) # lower-left-front point of domain bounding box + + # mapp grid and values + vdsm_data = vtk.vtkDataSetMapper() + vsp_data.GetCellData().SetScalars(vfa_value) + vdsm_data.SetInputData(vsp_data) + vdsm_data.Update() + vdsm_data.SetScalarRange(r_vmin, r_vmax) + vdsm_data.SetScalarModeToUseCellData() + + # build VTKLooktupTable (color scheme) + vlt_color = vtk.vtkLookupTable() + vlt_color.SetNumberOfTableValues(256) # number of color shades + vlt_color.SetHueRange(9/12, 0/12) # rainbow heat map + vlt_color.Build() + + # generate xy cutting plane actor + vp_canvas = vtk.vtkPlane() + vp_canvas.SetOrigin(0, 0, 0) # xyz + vp_canvas.SetNormal(0, 0, 1) + + vc_canvas = vtk.vtkCutter() + vc_canvas.SetInputData(vsp_data) + vc_canvas.SetCutFunction(vp_canvas) + vc_canvas.GeneratePolygons = 1 + + vpdm_canvas = vtk.vtkPolyDataMapper() + vpdm_canvas.SetInputConnection(vc_canvas.GetOutputPort()) + vpdm_canvas.ScalarVisibilityOn() + vpdm_canvas.SetScalarRange(r_vmin, r_vmax) + vpdm_canvas.SetLookupTable(vlt_color) + vpdm_canvas.SetScalarModeToUseCellData() + + va_canvas = vtk.vtkActor() + va_canvas.SetMapper(vpdm_canvas) + va_canvas.GetProperty().EdgeVisibilityOn() + + # generate outline actor + vof_frame = vtk.vtkOutlineFilter() + vof_frame.SetInputData(vsp_data) + + vpdm_frame = vtk.vtkPolyDataMapper() + vpdm_frame.SetInputConnection(vof_frame.GetOutputPort()) + + va_frame = vtk.vtkActor() + va_frame.SetMapper(vpdm_frame) + va_frame.GetProperty().SetColor(1, 1, 1) + + # generate scalar bar actor + vsba_spectrum = vtk.vtkScalarBarActor() + vsba_spectrum.SetTitle(s_substrate) + vsba_spectrum.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() + vsba_spectrum.GetPositionCoordinate().SetValue(0.1, 0.01) + vsba_spectrum.SetOrientationToHorizontal() + vsba_spectrum.SetWidth(0.8) + vsba_spectrum.SetHeight(0.1) + vsba_spectrum.GetProperty().SetColor(0, 0, 0) + vsba_spectrum.GetTitleTextProperty().SetColor(0, 0, 0) + vsba_spectrum.GetTitleTextProperty().SetFontSize(22) + vsba_spectrum.SetLookupTable(vpdm_canvas.GetLookupTable()) + + # do render setup + ren = vtk.vtkRenderer() + renWin = vtk.vtkRenderWindow() + renWin.AddRenderer(ren) + renWin.SetSize(800, 600) + iren = vtk.vtkRenderWindowInteractor() + iren.SetRenderWindow(renWin) + + # add the actor to the renderer + #ren.ResetCamera() + ren.SetBackground(1/3, 1/3, 1/3) # gray + ren.AddActor(va_canvas) + ren.AddActor(va_frame) + ren.AddActor2D(vsba_spectrum) + + # render + iren.Initialize() + renWin.Render() + iren.Start() + + # free memory + #del vfa_value + # save vtk file s_vtkpathfile = self.path + '/' + s_vtkfile vw_writer = vtk.vtkXMLRectilinearGridWriter() @@ -1424,7 +1522,9 @@ def get_celltype_list(self): function returns a list with all celltype labels, ordered by cell_type ID. """ - return self.data['cell']['ls_celltype'].copy() + ds_celltype = self.get_celltype_dict() + ls_celltype = [ds_celltype[s_key] for s_key in sorted(ds_celltype, key=int)] + return ls_celltype def get_celltype_dict(self): @@ -1439,7 +1539,7 @@ def get_celltype_dict(self): function returns a dictionary that maps ID and name from all cell_definitions, specified in the PhysiCell_settings.xml file. """ - return self.data['cell']['ds_celltype'].copy() + return self.data['metadata']['cell_type'] def get_cell_df(self, values=1, drop=set(), keep=set()): @@ -1477,66 +1577,254 @@ def get_cell_df(self, values=1, drop=set(), keep=set()): """ # check keep and drop if (len(keep) > 0) and (len(drop) > 0): - sys.exit(f"Error @ TimeStep.get_cell_df : when keep is given {keep}, then drop has to be an empty set {drop}!") - - # fetch data frame - df_cell = self.data['cell']['df_cell'].copy() - - # filter - es_attribute = set(df_cell.columns).difference(es_coor_cell) - if (len(keep) > 0): - es_delete = es_attribute.difference(keep) - else: - es_delete = es_attribute.intersection(drop) - - if (values > 1): # by minimal number of states - for s_column in set(df_cell.columns).difference(es_coor_cell): - if len(set(df_cell.loc[:,s_column])) < values: - es_delete.add(s_column) - if self.verbose and (len(es_delete) > 0): - print('es_delete:', es_delete) - df_cell.drop(es_delete, axis=1, inplace=True) - - # output - df_cell = df_cell.loc[:,sorted(df_cell.columns)] - df_cell.sort_values('ID', axis=0, inplace=True) - return df_cell + sys.exit(f"Error @ pyMCDS.get_cell_df : when keep is given {keep}, then drop has to be an empty set {drop}!") + # get cell position and more + df_cell = pd.DataFrame(self.data['discrete_cells']['data']) + df_cell['time'] = self.get_time() + df_cell['runtime'] = self.get_runtime() / 60 # in min + df_cell['xmlfile'] = self.xmlfile + df_voxel = df_cell.loc[:,['position_x','position_y','position_z']].copy() - def get_cell_attribute_list(self): - """ - input: + # get mesh spacing + dm, dn, dp = self.get_voxel_spacing() - output: - ls_cellattr: list of strings - alphabetically ordered list of all tracked cell attributes. + # get mesh and voxel min max values + tr_m_range, tr_n_range, tr_p_range = self.get_mesh_mnp_range() + tr_i_range, tr_j_range, tr_k_range = self.get_voxel_ijk_range() - description: - function returns a list with all cell attribute labels, - alphabetically ordered. - """ - return self.data['cell']['ls_cellattr'].copy() + # get voxel for each cell + df_voxel.loc[:,'voxel_i'] = np.round((df_voxel.loc[:,'position_x'] - tr_m_range[0]) / dm).astype(int) + df_voxel.loc[:,'voxel_j'] = np.round((df_voxel.loc[:,'position_y'] - tr_n_range[0]) / dn).astype(int) + df_voxel.loc[:,'voxel_k'] = np.round((df_voxel.loc[:,'position_z'] - tr_p_range[0]) / dp).astype(int) + df_voxel.loc[(df_voxel.voxel_i > tr_i_range[1]), 'voxel_i'] = tr_i_range[1] # i_max + df_voxel.loc[(df_voxel.voxel_i < tr_i_range[0]), 'voxel_i'] = tr_i_range[0] # i_min + df_voxel.loc[(df_voxel.voxel_j > tr_j_range[1]), 'voxel_j'] = tr_j_range[1] # j_max + df_voxel.loc[(df_voxel.voxel_j < tr_j_range[0]), 'voxel_j'] = tr_j_range[0] # j_min + df_voxel.loc[(df_voxel.voxel_k > tr_k_range[1]), 'voxel_k'] = tr_k_range[1] # k_max + df_voxel.loc[(df_voxel.voxel_k < tr_k_range[0]), 'voxel_k'] = tr_k_range[0] # k_min + # merge voxel (inner join) + df_cell = pd.merge(df_cell, df_voxel, on=['position_x', 'position_y', 'position_z']) - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, ext=None, figbgcolor=None, **kwargs): - """ - input: - focus: string; default is 'cell_type' - column name within cell dataframe. + # merge cell_density (left join) + df_cellcount = df_cell.loc[:,['voxel_i','voxel_j','voxel_k','ID']].groupby(['voxel_i','voxel_j','voxel_k']).count().reset_index() + ls_column = list(df_cellcount.columns) + ls_column[-1] = 'cell_count_voxel' + df_cellcount.columns = ls_column + s_density = f"cell_density_{self.data['metadata']['spatial_units']}3" + df_cellcount[s_density] = df_cellcount.loc[:,'cell_count_voxel'] / self.get_voxel_volume() + df_cell = pd.merge( + df_cell, + df_cellcount, + on = ['voxel_i', 'voxel_j', 'voxel_k'], + how = 'left', + ) - z_slice: floating point number; default is 0.0 - z-axis position to slice a 2D xy-plain out of the - 3D substrate concentration mesh. if z_slice position - is not an exact mesh center coordinate, then z_slice - will be adjusted to the nearest mesh center value, - the smaller one, if the coordinate lies on a saddle point. + # get column label set + es_column = set(df_cell.columns) - z_axis: for a categorical focus: set of labels; + # get vector length + for s_var_spatial in es_var_spatial: + es_vector = es_column.intersection({f'{s_var_spatial}_x',f'{s_var_spatial}_y',f'{s_var_spatial}_z'}) + if len(es_vector) > 0: + # linear algebra + #a_vector = df_cell.loc[:,ls_vector].values + #a_length = np.sqrt(np.diag(np.dot(a_vector, a_vector.T))) + # pythoagoras + a_length = None + for s_vector in es_vector: + a_vectorsq = df_cell.loc[:,s_vector].values**2 + if (a_length is None): + a_length = a_vectorsq + else: + a_length += a_vectorsq + a_length = a_length**(1/2) + # result + df_cell[f'{s_var_spatial}_vectorlength'] = a_length + + # physicell + if not (self.data['discrete_cells']['physiboss'] is None): + df_cell = pd.merge( + df_cell, + self.data['discrete_cells']['physiboss'], + left_index = True, + right_index = True, + how = 'left', + ) + + + # microenvironment + if self.microenv: + # merge substrate (left join) + df_sub = self.get_substrate_df() + for s_sub in df_sub.index: + for s_rate in df_sub.columns: + s_var = f'{s_sub}_{s_rate}' + df_cell[s_var] = df_sub.loc[s_sub,s_rate] + + # merge concentration (left join) + df_conc = self.get_conc_df(z_slice=None, values=1, drop=set(), keep=set()) + df_conc.drop({'time', 'runtime','xmlfile'}, axis=1, inplace=True) + df_cell = pd.merge( + df_cell, + df_conc, + on = ['voxel_i', 'voxel_j', 'voxel_k'], + how = 'left', + ) + + # variable typing + do_type = {} + [do_type.update({k:v}) for k,v in do_var_type.items() if k in es_column] + do_type.update(self.custom_data_type) + do_int = do_type.copy() + [do_int.update({k:int}) for k in do_int.keys()] + ls_int = sorted(do_int.keys()) + df_cell.loc[:,ls_int] = df_cell.loc[:,ls_int].round() + df_cell = df_cell.astype(do_int) + df_cell = df_cell.astype(do_type) + + # categorical translation + try: # bue 20240805: missing in MCDS version <= 0.5 (November 2021) + df_cell.loc[:,'current_death_model'] = df_cell.loc[:,'current_death_model'].replace(ds_death_model) # bue 20230614: this column looks like an artefact to me + except KeyError: + pass + df_cell.loc[:,'cycle_model'] = df_cell.loc[:,'cycle_model'].replace(ds_cycle_model) + df_cell.loc[:,'cycle_model'] = df_cell.loc[:,'cycle_model'].replace(ds_death_model) + df_cell.loc[:,'current_phase'] = df_cell.loc[:,'current_phase'].replace(ds_cycle_phase) + df_cell.loc[:,'current_phase'] = df_cell.loc[:,'current_phase'].replace(ds_death_phase) + df_cell.loc[:,'cell_type'] = df_cell.loc[:,'cell_type'].replace(self.data['metadata']['cell_type']) + df_cell.loc[:,'chemotaxis_index'] = df_cell.loc[:,'chemotaxis_index'].replace(self.data['metadata']['substrate']) + + # filter + es_attribute = set(df_cell.columns).difference(es_coor_cell) + if (len(keep) > 0): + es_delete = es_attribute.difference(keep) + else: + es_delete = es_attribute.intersection(drop) + + if (values > 1): # by minimal number of states + for s_column in set(df_cell.columns).difference(es_coor_cell): + if len(set(df_cell.loc[:,s_column])) < values: + es_delete.add(s_column) + if self.verbose and (len(es_delete) > 0): + print('es_delete:', es_delete) + df_cell.drop(es_delete, axis=1, inplace=True) + + # output + df_cell = df_cell.loc[:,sorted(df_cell.columns)] + df_cell.sort_values('ID', axis=0, inplace=True) + df_cell.set_index('ID', inplace=True) + df_cell = df_cell.copy() + return df_cell + + + def get_cell_df_at(self, x, y, z=0, values=1, drop=set(), keep=set()): + """ + input: + x: floating point number + position x-coordinate. + + y: floating point number + position y-coordinate. + + z: floating point number; default is 0 + position z-coordinate. + + values: integer; default is 1 + minimal number of values a variable has to have to be outputted. + variables that have only 1 state carry no information. + None is a state too. + + drop: set of strings; default is an empty set + set of column labels to be dropped for the dataframe. + don't worry: essential columns like ID, coordinates + and time will never be dropped. + Attention: when the keep parameter is given, then + the drop parameter has to be an empty set! + + keep: set of strings; default is an empty set + set of column labels to be kept in the dataframe. + set values=1 to be sure that all variables are kept. + don't worry: essential columns like ID, coordinates + and time will always be kept. + + output: + df_voxel: pandas dataframe + x, y, z voxel filtered cell dataframe. + + description: + function returns the cell dataframe for the voxel + specified with the x, y, z position coordinate. + """ + df_voxel = None + + # is coordinate inside the domain? + b_calc = self.is_in_mesh(x=x, y=y, z=z, halt=False) + if b_calc: + + # get mesh and mesh spacing + dm, dn, dp = self.get_voxel_spacing() + ar_m, ar_n, ar_p = self.get_mesh() + + # get voxel coordinate + i, j, k = self.get_voxel_ijk(x, y, z, is_in_mesh=False) + m = ar_m[j, i, k] + n = ar_n[j, i, k] + p = ar_p[j, i, k] + + # get voxel + df_cell = self.get_cell_df(values=values, drop=drop, keep=keep) + inside_voxel = ( + (df_cell['position_x'] <= m + dm / 2) & + (df_cell['position_x'] >= m - dm / 2) & + (df_cell['position_y'] <= n + dn / 2) & + (df_cell['position_y'] >= n - dn / 2) & + (df_cell['position_z'] <= p + dp / 2) & + (df_cell['position_z'] >= p - dp / 2) + ) + df_voxel = df_cell[inside_voxel] + + # output + return df_voxel + + + def get_cell_attribute_list(self): + """ + input: + + output: + ls_cellattr: list of strings + alphabetically ordered list of all tracked cell attributes. + + description: + function returns a list with all cell attribute labels, + alphabetically ordered. + """ + df_cell = self.get_cell_df() + ls_cellattr = sorted(set(df_cell.columns).difference(es_coor_cell)) + return ls_cellattr + + + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title=None, grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, ax=None, figsizepx=None, directory=None, ext=None, figbgcolor=None): + """ + input: + focus: string; default is 'cell_type' + column name within cell dataframe. + + z_slice: floating point number; default is 0.0 + z-axis position to slice a 2D xy-plain out of the + 3D substrate concentration mesh. if z_slice position + is not an exact mesh center coordinate, then z_slice + will be adjusted to the nearest mesh center value, + the smaller one, if the coordinate lies on a saddle point. + + z_axis: for a categorical focus: set of labels; for a numeric focus: tuple of two floats; default is None depending on the focus column variable dtype, default extracts labels or min and max values from data. - alpha: floating point number; default is 1.0 + alpha: floating point number; default is 1 alpha channel transparency value between 1 (not transparent at all) and 0 (totally transparent). @@ -1585,6 +1873,11 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is None output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -1593,10 +1886,6 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the pandas dataframe plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html - output: fig: matplotlib figure, depending on ext, either as object or as file. the figure contains the scatter plot and color bar (numerical data) @@ -1625,7 +1914,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma figsizepx = [i_width, i_height] except FileNotFoundError: if self.verbose: - print(f'Warning @ TimeStepts.plot_scatter : could not load {s_pathfile}.') + print(f'Warning @ pyMCDSts.plot_scatter : could not load {s_pathfile}.') figsizepx = [640, 480] # handle figure size @@ -1713,7 +2002,7 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma df_cell.loc[(df_cell.loc[:,focus] == s_category), s_focus_color] = s_color # generate category color dictionary else: - ds_color = pdplt.df_label_to_color( + ds_color = df_label_to_color( df_abc = df_cell, s_focus = focus, es_label = es_category, @@ -1746,12 +2035,11 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma s = 's', grid = grid, ax = ax, - **kwargs, ) # plot categorical data legen if not (es_category is None): - pdplt.ax_colorlegend( + ax_colorlegend( ax = ax, ds_color = ds_color, s_loc = legend_loc, @@ -1765,9 +2053,12 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma else: # handle output path and filename - s_path = self.path + f"/cell_{focus.replace(' ','_')}_z{round(z_slice,9)}/" + if (directory is None): + s_path = self.path + f'/cell_{focus}_z{round(z_slice,9)}/' + else: + s_path = f'{directory}/' os.makedirs(s_path, exist_ok=True) - s_file = self.xmlfile.replace('.xml', f"_{focus.replace(' ','_')}.{ext}") + s_file = self.xmlfile.replace('.xml', f'_{focus}.{ext}') s_pathfile = f'{s_path}{s_file}' # handle figure background color if figbgcolor is None: @@ -1783,12 +2074,15 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma return fig - def make_cell_vtk(self, attribute=['cell_type']): + def make_cell_vtk(self, attribute=['cell_type'], visualize=True): """ input: attribute: list of strings; default is ['cell_type'] column name within cell dataframe. + visualize: boolean; default is True + additionally, visualize cells using vtk renderer. + output: s_vtkpathfile: vtk 3D glyph polynomial data file that contains cells. @@ -1839,18 +2133,15 @@ def make_cell_vtk(self, attribute=['cell_type']): # fill this grid with given attributes for s_attribute in attribute: b_bool = False - if pd.api.types.is_bool_dtype(df_cell.loc[:, s_attribute].dtype): + if (df_cell.loc[:, s_attribute].dtype == bool): #in {bool, np.bool_, np.bool}): b_bool = True voa_data = vtk.vtkStringArray() - print(f'boole: {s_attribute}') - elif pd.api.types.is_string_dtype(df_cell.loc[:, s_attribute].dtype): + elif (df_cell.loc[:, s_attribute].dtype == str) or (df_cell.loc[:, s_attribute].dtype == np.object_): # in {str, np.str_, np.object_}): voa_data = vtk.vtkStringArray() - print(f'string: {s_attribute}') - elif pd.api.types.is_integer_dtype(df_cell.loc[:, s_attribute].dtype) or pd.api.types.is_float_dtype(df_cell.loc[:, s_attribute].dtype): + elif (df_cell.loc[:, s_attribute].dtype == int) or (df_cell.loc[:, s_attribute].dtype == float): # in {int, np.int_, np.int8, np.int16, np.int32, np.int64, float, np.float16, np.float32, np.float64, np.float128}): voa_data = vtk.vtkFloatArray() - print(f'numeric: {s_attribute}') else: - sys.exit(f'Error @ TimeStep.make_cell_vtk : {s_attribute} {df_cell.loc[:, s_attribute].dtype} unknown df_cell column data type.') + sys.exit(f'Error @ pyMCDS.make_cell_vtk : {s_attribute} {df_cell.loc[:, s_attribute].dtype} unknown df_cell column data type.') voa_data.SetName(s_attribute) for i in df_cell.index: @@ -1863,6 +2154,8 @@ def make_cell_vtk(self, attribute=['cell_type']): voa_data.InsertNextValue(df_cell.loc[i, s_attribute]) vug_data.GetPointData().AddArray(voa_data) + # free memory + #del voa_data # generate sphere source vss_data = vtk.vtkSphereSource() @@ -1882,6 +2175,49 @@ def make_cell_vtk(self, attribute=['cell_type']): vg_data.SetColorModeToColorByScalar() vg_data.Update() + # visualize + if (visualize): + # select first attribute + s_attribute = attribute[0] + + # build VTKLooktupTable (color scheme) + vlt_color = vtk.vtkLookupTable() + i_element = df_cell.loc[:, s_attribute].unique().shape[0] + if (i_element > 256): + i_element = 256 + vlt_color.SetNumberOfTableValues(i_element) + vlt_color.SetHueRange(9/12, 0/12) # rainbow heat map + vlt_color.Build() + + # set up the mapper + vpdm_data = vtk.vtkPolyDataMapper() + vpdm_data.SetInputConnection(vg_data.GetOutputPort()) + vpdm_data.ScalarVisibilityOn() + vpdm_data.SetLookupTable(vlt_color) + vpdm_data.ColorByArrayComponent(s_attribute, 1) + + # set up the actor + actor = vtk.vtkActor() + actor.SetMapper(vpdm_data) + + # do renderer setup + ren = vtk.vtkRenderer() + renWin = vtk.vtkRenderWindow() + renWin.AddRenderer(ren) + renWin.SetSize(800, 600) + iren = vtk.vtkRenderWindowInteractor() + iren.SetRenderWindow(renWin) + + # add the actor to the renderer + #ren.ResetCamera() + ren.SetBackground(1/3, 1/3, 1/3) # gray + ren.AddActor(actor) + + # render + iren.Initialize() + renWin.Render() + iren.Start() + # write VTK s_vtkpathfile = self.path + '/' + s_vtkfile vw_writer = vtk.vtkXMLPolyDataWriter() @@ -1892,246 +2228,6 @@ def make_cell_vtk(self, attribute=['cell_type']): return s_vtkpathfile - ## MICROENVIRONMENT AND CELL AGENT RELATED FUNCTIONS ## - - def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=True): - """ - input: - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be - specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape czyx is the output. - - output: - a_tczyx_img: numpy array or ome tiff file. - - description: - function to transform chosen mcds output into an 1[um] spaced - czyx (channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - an ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - """ - # handle channels - ls_substrate = self.get_substrate_list() - ls_celltype = self.get_celltype_list() - - if not (focus is None): - ls_substrate = [s_substrate for s_substrate in ls_substrate if s_substrate in set(focus)] - ls_celltype = [s_celltype for s_celltype in ls_celltype if s_celltype in set(focus)] - if (set(focus) != set(ls_substrate).union(set(ls_celltype))): - sys.exit(f'Error : {focus} not found in {ls_substrate} {ls_celltype}') - - # const - ls_coor_mnp = ['mesh_center_m', 'mesh_center_n', 'mesh_center_p'] # xyz - ls_coor_xyz = ['position_x', 'position_y', 'position_z'] # xyz - ls_coor = ['voxel_x', 'voxel_y', 'voxel_z'] - - # time step tensor - i_time = int(self.get_time()) - - # get xy coordinate dataframe - lr_axis_z = list(self.get_mesh_mnp_axis()[2] - self.get_voxel_spacing()[2] / 2) - lr_axis_z.append(self.get_mesh_mnp_axis()[2][-1] + self.get_voxel_spacing()[2] / 2) - lll_coor = [] - for i_x in range(int(round(self.get_voxel_ijk_range()[0][1] * self.get_voxel_spacing()[0]))): - for i_y in range(int(round(self.get_voxel_ijk_range()[1][1] * self.get_voxel_spacing()[1]))): - lll_coor.append([i_x, i_y]) - df_coor = pd.DataFrame(lll_coor, columns=ls_coor[:2]) - lr_axis_z[-1] += 1 - - # extract voxel radius - di_grow = {} - for s_substarte in ls_substrate: - di_grow.update({ - s_substarte : int(np.round(np.mean(self.get_voxel_spacing()[:2])) - 1) - }) - - # get and shift substrate xy data - df_conc = self.get_conc_df() - df_conc = df_conc.loc[:, ls_coor_mnp + ls_substrate] - df_conc.loc[:, 'mesh_center_m'] = (df_conc.loc[:, 'mesh_center_m'] - self.get_xyz_range()[0][0]).round() - df_conc.loc[:, 'mesh_center_n'] = (df_conc.loc[:, 'mesh_center_n'] - self.get_xyz_range()[1][0]).round() - df_conc.rename({'mesh_center_m':'voxel_x', 'mesh_center_n':'voxel_y', 'mesh_center_p':'voxel_z'}, axis=1, inplace=True) - df_conc = df_conc.astype({'voxel_x': int, 'voxel_y': int, 'voxel_z': float}) - # level the cake - for s_channel in conc_cutoff.keys(): - try: - df_conc.loc[:, s_channel] = df_conc.loc[:, s_channel] - conc_cutoff[s_channel] + 1 # positive values starting at > 0 - df_conc.loc[(df_conc.loc[:, s_channel] <= conc_cutoff[s_channel]), s_channel] = 0 - except KeyError: - pass - - - # get cell data - df_cell = self.get_cell_df().reset_index() - - # extract cell radius - for s_celltype in ls_celltype: - try: - i_cell_grow = int(round(df_cell.loc[(df_cell.cell_type == s_celltype), 'radius'].mean()) - 1) - except: - i_cell_grow = 0 - di_grow.update({s_celltype : i_cell_grow}) - - # filter and shift - df_cell = df_cell.loc[:, ls_coor_xyz + ['cell_type', cell_attribute]] - if (cell_attribute == 'cell_type'): - sys.exit(f'Error @ TimeStep.make_ome_tiff : cell_attribute cannot be cell_type.') - elif (df_cell.loc[:, cell_attribute].dtype == str) or (df_cell.loc[:, cell_attribute].dtype == np.object_): # in {str, np.str_, np.object_}): - sys.exit(f'Error @ TimeStep.make_ome_tiff : {cell_attribute} {df_cell.loc[:, cell_attribute].dtype} cell_attribute cannot be string or object. cell_attribute has to be boolean, integer, or float.') - elif (df_cell.loc[:, cell_attribute].dtype == bool): # in {bool, np.bool_, np.bool}): - df_cell = df_cell.astype({cell_attribute: int}) - df_cell.loc[:, 'position_x'] = (df_cell.loc[:, 'position_x'] - self.get_xyz_range()[0][0]).round() - df_cell.loc[:, 'position_y'] = (df_cell.loc[:, 'position_y'] - self.get_xyz_range()[1][0]).round() - df_cell.rename({'position_x':'voxel_x', 'position_y':'voxel_y', 'position_z':'voxel_z'}, axis=1, inplace=True) - df_cell = df_cell.astype({'voxel_x': int, 'voxel_y': int, 'voxel_z': float}) - # level the cake - df_cell.loc[:, cell_attribute] = df_cell.loc[:, cell_attribute] - df_cell.loc[:, cell_attribute].min() + 1 # positive values starting at > 0 - - # check for duplicates: two cell at exactelly the same xyz position. - #if self.verbose and df_cell.loc[:,['voxel_x', 'voxel_y', 'voxel_z']].duplicated().any(): - # df_duplicate = df_cell.loc[(df_cell.loc[:, ['voxel_x', 'voxel_y', 'voxel_z']].duplicated()), :] - # sys.exit(f"Error @ TimeStep.make_ome_tiff : {df_duplicate} cells at exactely the same xyz voxel position detected. cannot pivot!") - - # pivot cell_type - df_cell = df_cell.pivot_table(index=ls_coor, columns='cell_type', values=cell_attribute, aggfunc='sum').reset_index() # fill_value is na - for s_celltype in ls_celltype: - if not s_celltype in set(df_cell.columns): - df_cell[s_celltype] = 0 - - # each C channel - time step tensors - la_czyx_img = [] - ls_channel = ls_substrate + ls_celltype - for s_channel in ls_channel: - - # get channel dataframe - if s_channel in set(ls_substrate): - df_channel = df_conc.loc[:, ls_coor + [s_channel]] - elif s_channel in set(ls_celltype): - df_channel = df_cell.loc[:, ls_coor + [s_channel]] - else: - sys.exit(f'Error @ TimeStep.make_ome_tiff : {s_channel} unknown channel detected. not in substrate and cell type list {ls_substrate} {ls_celltype}!') - - # each z axis - la_zyx_img = [] - for i_zaxis in range(len(lr_axis_z)): - if (i_zaxis < (len(lr_axis_z) - 1)): - print(f'processing: {i_time} [min] {s_channel} [channel] {i_zaxis} [z_axis] ...') - # extract z layer - df_yxchannel = df_channel.loc[ - ((df_channel.loc[:, ls_coor[2]] >= lr_axis_z[i_zaxis]) & (df_channel.loc[:, ls_coor[2]] < lr_axis_z[i_zaxis + 1])), - ls_coor[:2] + [s_channel] - ] - - # drop row with na and duplicate entries - df_yxchannel = df_yxchannel.dropna(axis=0) - df_yxchannel = df_yxchannel.drop_duplicates() - - # merge with coooridnates and get image - # bue 20240811: df_coor left side merge will cut off reset cell that are out of the xyz domain range, which is what we want. - df_yxchannel = pd.merge(df_coor, df_yxchannel, on=ls_coor[:2], how='left').replace({np.nan: 0}) - try: - df_yxchannel = df_yxchannel.pivot(columns=ls_coor[0], index=ls_coor[1], values=s_channel) - except ValueError: # two cells from the same cell type very close to each other detetced. - if self.verbose: - df_duplicate = df_cell.loc[(df_yxchannel.loc[:, ['voxel_x', 'voxel_y']].duplicated()), :] - print(f'Warning: {s_channel} {df_duplicate} cells within 1[um] distance form each detected. cannot pivot. erase cell type from this timestep.') - df_yxchannel.loc[:,s_channel] = 0 # erase cells - df_yxchannel = df_yxchannel.drop_duplicates() - df_yxchannel = df_yxchannel.pivot(columns=ls_coor[0], index=ls_coor[1], values=s_channel) - a_yx_img = df_yxchannel.values - - # grow - a_yx_img = imagine.grow_seed(a_yx_img, i_step=di_grow[s_channel], b_verbose=False) - - # update output - la_zyx_img.append(a_yx_img) - a_zyx_img = np.array(la_zyx_img, np.float32) - la_czyx_img.append(np.array(a_zyx_img, np.float32)) - - # output - a_czyx_img = np.array(la_czyx_img, dtype=np.float32) - - # numpy array - if not file: - return a_czyx_img - - # write to file - else: - if self.verbose: - print('a_czyx_img shape:', a_czyx_img.shape) - # generate filename - s_channel = '' - for s_substrate in ls_substrate: - try: - r_value = conc_cutoff[s_substrate] - s_channel += f'_{s_substrate}{r_value}' - except KeyError: - s_channel += f'_{s_substrate}' - for s_celltype in ls_celltype: - s_channel += f'_{s_celltype}' - if len(ls_celltype) > 0: - s_channel += f'_{cell_attribute}' - s_tifffile = self.xmlfile.replace('.xml', f'{s_channel}.ome.tiff') - s_tifffile = s_tifffile.replace(' ','_') - if (len(s_tifffile) > 255): - print(f"Warning: filename {len(s_tifffile)} > 255 character.") - s_tifffile = self.xmlfile.replace('.xml', f'_channels.ome.tiff') - print(f"file name adjusted to {s_tifffile}.") - s_tiffpathfile = self.path + '/' + s_tifffile - - # save to file - OmeTiffWriter.save( - a_czyx_img, - s_tiffpathfile, - dim_order = 'CZYX', - #ome_xml=x_img, - channel_names = ls_channel, - image_names = [s_tifffile.replace('.ome.tiff','')], - physical_pixel_sizes = bioio_base.types.PhysicalPixelSizes(self.get_voxel_spacing()[2], 1.0, 1.0), # z,y,x [um] - #channel_colors=, - #fs_kwargs={}, - ) - return s_tiffpathfile - - - def render_neuroglancer(self, tiffpathfile, timestep=0, intensity_cmap='gray'): - """ - help(pcdl.render_neuroglancer) - try: mcds.render_neuroglancer(mcds.make_ome_tiff()) - """ - o_viewer = render_neuroglancer( - tiffpathfile = tiffpathfile, - timestep = timestep, - intensity_cmap = intensity_cmap, - ) - return o_viewer - - ## GRAPH RELATED FUNCTIONS ## def get_attached_graph_dict(self): @@ -2145,7 +2241,7 @@ def get_attached_graph_dict(self): description: function returns the attached cell graph as a dictionary object. """ - return self.data['cell']['dei_graph']['attached_cells'].copy() + return self.data['discrete_cells']['graph']['attached_cells'] def get_neighbor_graph_dict(self): @@ -2159,7 +2255,7 @@ def get_neighbor_graph_dict(self): description: function returns the cell neighbor graph as a dictionary object. """ - return self.data['cell']['dei_graph']['neighbor_cells'].copy() + return self.data['discrete_cells']['graph']['neighbor_cells'] def get_spring_graph_dict(self): @@ -2173,7 +2269,7 @@ def get_spring_graph_dict(self): description: function returns the attached spring cell graph as a dictionary object. """ - return self.data['cell']['dei_graph']['spring_attached_cells'].copy() + return self.data['discrete_cells']['graph']['spring_attached_cells'] def make_graph_gml(self, graph_type, edge_attribute=True, node_attribute=[]): @@ -2248,7 +2344,7 @@ def make_graph_gml(self, graph_type, edge_attribute=True, node_attribute=[]): elif (o_attribute.dtype == float): #in {float, np.float16, np.float32, np.float64, np.float128}): f.write(f' {s_attribute} {o_attribute}\n') else: - sys.exit(f'Error @ TimeStep.make_graph_gml : attribute {o_attribute}; type {o_attribute.dtype}; type seems not to be bool, int, float, or string.') + sys.exit(f'Error @ pyMCDS.make_graph_gml : attribute {o_attribute}; type {o_attribute.dtype}; type seems not to be bool, int, float, or string.') f.write(f' ]\n') # edge for i_dst in ei_dst: @@ -2273,70 +2369,12 @@ def make_graph_gml(self, graph_type, edge_attribute=True, node_attribute=[]): return s_gmlpathfile - ## ANNDATA RELATED FUNCTIONS ## - - def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs'): - """ - input: - values: integer; default is 1 - minimal number of values a variable has to have to be outputted. - variables that have only 1 state carry no information. - None is a state too. - - drop: set of strings; default is an empty set - set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. - Attention: when the keep parameter is given, then - the drop parameter has to be an empty set! - - keep: set of strings; default is an empty set - set of column labels to be kept in the dataframe. - set values=1 to be sure that all variables are kept. - don't worry: essential columns like ID, coordinates - and time will always be kept. - - scale: string; default 'maxabs' - specify how the data should be scaled. - possible values are None, maxabs, minmax, std. - for more input, check out: help(pcdl.scaler) - - output: - annmcds: anndata object - for this one time step. - - description: - function to transform a mcds time step into an anndata object - for downstream analysis. - """ - # processing - if self.verbose: - print(f'processing: 1/1 {round(self.get_time(),9)}[min] mcds into anndata obj.') - df_cell = self.get_cell_df(values=values, drop=drop, keep=keep) - df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( - df_cell = df_cell, - scale = scale, - graph_attached = self.get_attached_graph_dict(), - graph_neighbor = self.get_neighbor_graph_dict(), - graph_method = self.get_physicell_version(), - ) - annmcds = ad.AnnData( - X = df_count, - obs = df_obs, - obsm = d_obsm, - obsp = d_obsp, - uns = d_uns - ) - # output - return annmcds - - ## LOAD DATA ## def _read_xml(self, xmlfile, output_path='.'): """ input: - self: TimeStep class instance. + self: pyMCDS class instance. xmlfile: string name of the xml file with or without path @@ -2347,11 +2385,11 @@ def _read_xml(self, xmlfile, output_path='.'): the PhysiCell output files are stored. output: - self: TimeStep class instance with loaded data. + self: pyMCDS class instance with loaded data. description: internal function to load the data from the PhysiCell output files - into the TimeStep instance. + into the pyMCDS instance. """ ##################### # path and filename # @@ -2374,26 +2412,19 @@ def _read_xml(self, xmlfile, output_path='.'): b_celltype = False # generate output dictionary - d_mcds = { - 'metadata': {}, - 'mesh': {}, - 'substrate': { - 'ds_substrate': {}, - }, - 'metadata': {}, - 'cell': { - 'ds_celltype': {}, - }, - 'raw_substrate': {}, - 'raw_cell': { - 'units': {}, - }, - } + d_mcds = {} + d_mcds['metadata'] = {} + d_mcds['metadata']['substrate'] = {} + d_mcds['metadata']['cell_type'] = {} + d_mcds['mesh'] = {} + d_mcds['continuum_variables'] = {} + d_mcds['discrete_cells'] = {} + d_mcds['discrete_cells']['units'] = {} + ############################### # read PhysiCell_settings.xml # ############################### - ## get celltype dict # bue: used for cell_type label:id mapping for data generated with physicell versions < 3.15. if not ((self.settingxml is None) or (self.settingxml is False)): @@ -2408,9 +2439,8 @@ def _read_xml(self, xmlfile, output_path='.'): for x_celltype in self.x_settingxml.find('cell_definitions').findall('cell_definition'): # s_id = str(x_celltype.get('ID')) - # I don't like spaces in cell type names! - s_celltype = x_celltype.get('name') #.replace(' ','_') # ROH - d_mcds['cell']['ds_celltype'].update({s_id : s_celltype}) + s_celltype = x_celltype.get('name').replace(' ', '_') + d_mcds['metadata']['cell_type'].update({s_id : s_celltype}) b_celltype = True ####################################### @@ -2434,25 +2464,25 @@ def _read_xml(self, xmlfile, output_path='.'): ### find the metadata node ### x_metadata = x_root.find('metadata') - ## get multicellds xml version + # get multicellds xml version d_mcds['metadata']['multicellds_version'] = f"MultiCellDS_{x_root.get('version')}" - ## get physicell software version + # get physicell software version x_software = x_metadata.find('software') x_physicelln = x_software.find('name') x_physicellv = x_software.find('version') d_mcds['metadata']['physicell_version'] = f'{x_physicelln.text}_{x_physicellv.text}' - ## get timestamp + # get timestamp x_time = x_metadata.find('created') d_mcds['metadata']['created'] = x_time.text - ## get current simulated time + # get current simulated time x_time = x_metadata.find('current_time') d_mcds['metadata']['current_time'] = float(x_time.text) d_mcds['metadata']['time_units'] = x_time.get('units') - ## get current runtime + # get current runtime x_time = x_metadata.find('current_runtime') d_mcds['metadata']['current_runtime'] = float(x_time.text) d_mcds['metadata']['runtime_units'] = x_time.get('units') @@ -2468,7 +2498,7 @@ def _read_xml(self, xmlfile, output_path='.'): ### find the mesh node ### x_microenv = x_root.find('microenvironment').find('domain') # find the microenvironment node x_mesh = x_microenv.find('mesh') - d_mcds['metadata']['spatial_unit'] = x_mesh.get('units') + d_mcds['metadata']['spatial_units'] = x_mesh.get('units') # while we're at it, find the mesh s_x_coor = x_mesh.find('x_coordinates').text @@ -2483,38 +2513,38 @@ def _read_xml(self, xmlfile, output_path='.'): s_delim = x_mesh.find('z_coordinates').get('delimiter') ar_z_coor = np.array(s_z_coor.split(s_delim), dtype=np.float64) - ## get mesh grid + # reshape into a meshgrid d_mcds['mesh']['mnp_grid'] = np.array(np.meshgrid(ar_x_coor, ar_y_coor, ar_z_coor, indexing='xy')) - ## get mesh center axis + # get mesh center axis d_mcds['mesh']['mnp_axis'] = [ np.unique(ar_x_coor), np.unique(ar_y_coor), np.unique(ar_z_coor), ] - ## get mesh center range + # get mesh center range d_mcds['mesh']['mnp_range'] = [ (d_mcds['mesh']['mnp_axis'][0].min(), d_mcds['mesh']['mnp_axis'][0].max()), (d_mcds['mesh']['mnp_axis'][1].min(), d_mcds['mesh']['mnp_axis'][1].max()), (d_mcds['mesh']['mnp_axis'][2].min(), d_mcds['mesh']['mnp_axis'][2].max()), ] - ## get voxel range + # get voxel range d_mcds['mesh']['ijk_range'] = [ (0, len(d_mcds['mesh']['mnp_axis'][0]) - 1), (0, len(d_mcds['mesh']['mnp_axis'][1]) - 1), (0, len(d_mcds['mesh']['mnp_axis'][2]) - 1), ] - ## get voxel axis + # get voxel axis d_mcds['mesh']['ijk_axis'] = [ np.array(range(d_mcds['mesh']['ijk_range'][0][1] + 1)), np.array(range(d_mcds['mesh']['ijk_range'][1][1] + 1)), np.array(range(d_mcds['mesh']['ijk_range'][2][1] + 1)), ] - ## get mesh bounding box range [xmin, ymin, zmin, xmax, ymax, zmax] + # get mesh bounding box range [xmin, ymin, zmin, xmax, ymax, zmax] s_bboxcoor = x_mesh.find('bounding_box').text s_delim = x_mesh.find('bounding_box').get('delimiter') ar_bboxcoor = np.array(s_bboxcoor.split(s_delim), dtype=np.float64) @@ -2531,34 +2561,10 @@ def _read_xml(self, xmlfile, output_path='.'): if self.verbose: print(f'reading: {s_voxelpathfile}') - ## get voxle coordinates # center of voxel specified by first three rows [ x, y, z ] - d_mcds['mesh']['mnp_coordinate'] = ar_mesh_initial[:3, :] - - ## get voxel volume # volume specified by fourth row - ar_volume = ar_mesh_initial[3, :] - if (len(set(ar_volume)) != 1): - sys.exit(f'Error @ TimeStep._read_xml : mesh is not built out of a unique voxel volume {ar_volume}.') - d_mcds['mesh']['volume'] = ar_volume[0] - - ## get mesh voxel spacing - tr_m_range, tr_n_range, tr_p_range = d_mcds['mesh']['mnp_range'] - ar_m_axis, ar_n_axis, ar_p_axis = d_mcds['mesh']['mnp_axis'] - - if (len(set(tr_m_range)) == 1): # m axis - dm = np.float64(1.0) - else: - dm = (tr_m_range[1] - tr_m_range[0]) / (ar_m_axis.shape[0] - 1) - - if (len(set(tr_n_range)) == 1): # n axis - dn = np.float64(1.0) - else: - dn = (tr_n_range[1] - tr_n_range[0]) / (ar_n_axis.shape[0] - 1) - - dp = d_mcds['mesh']['volume'] / (dm * dn) # p axis - - d_mcds['mesh']['mnp_spacing'] = [dm, dn, dp] + d_mcds['mesh']['mnp_coordinate'] = ar_mesh_initial[:3, :] + d_mcds['mesh']['volumes'] = ar_mesh_initial[3, :] ################################ @@ -2578,37 +2584,37 @@ def _read_xml(self, xmlfile, output_path='.'): if self.verbose: print(f'reading: {s_microenvpathfile}') - # raw_substrate, unlike in the matlab version the individual chemical + # continuum_variables, unlike in the matlab version the individual chemical # species will be primarily accessed through their names e.g. - # d_mcds['raw_substrate']['oxygen']['units'] - # d_mcds['raw_substrate']['glucose']['data'] + # d_mcds['continuum_variables']['oxygen']['units'] + # d_mcds['continuum_variables']['glucose']['data'] # substrate loop for i_s, x_substrate in enumerate(x_microenv.find('variables').findall('variable')): # i don't like spaces in species names! - s_substrate = x_substrate.get('name') #.replace(' ','_') # ROH + s_substrate = x_substrate.get('name').replace(' ', '_') - d_mcds['raw_substrate'][s_substrate] = {} - d_mcds['raw_substrate'][s_substrate]['units'] = x_substrate.get('units') + d_mcds['continuum_variables'][s_substrate] = {} + d_mcds['continuum_variables'][s_substrate]['units'] = x_substrate.get('units') if self.verbose: print(f'parsing: {s_substrate} data') # update metadata substrate ID label dictionary - d_mcds['substrate']['ds_substrate'].update({str(i_s) : s_substrate}) + d_mcds['metadata']['substrate'].update({str(i_s) : s_substrate}) # initialize meshgrid shaped array for concentration data - d_mcds['raw_substrate'][s_substrate]['data'] = np.zeros(d_mcds['mesh']['mnp_grid'][0].shape) + d_mcds['continuum_variables'][s_substrate]['data'] = np.zeros(d_mcds['mesh']['mnp_grid'][0].shape) # diffusion data for each species - d_mcds['raw_substrate'][s_substrate]['diffusion_coefficient'] = {} - d_mcds['raw_substrate'][s_substrate]['diffusion_coefficient']['value'] = float(x_substrate.find('physical_parameter_set').find('diffusion_coefficient').text) - d_mcds['raw_substrate'][s_substrate]['diffusion_coefficient']['units'] = x_substrate.find('physical_parameter_set').find('diffusion_coefficient').get('units') + d_mcds['continuum_variables'][s_substrate]['diffusion_coefficient'] = {} + d_mcds['continuum_variables'][s_substrate]['diffusion_coefficient']['value'] = float(x_substrate.find('physical_parameter_set').find('diffusion_coefficient').text) + d_mcds['continuum_variables'][s_substrate]['diffusion_coefficient']['units'] = x_substrate.find('physical_parameter_set').find('diffusion_coefficient').get('units') # decay data for each species - d_mcds['raw_substrate'][s_substrate]['decay_rate'] = {} - d_mcds['raw_substrate'][s_substrate]['decay_rate']['value'] = float(x_substrate.find('physical_parameter_set').find('decay_rate').text) - d_mcds['raw_substrate'][s_substrate]['decay_rate']['units'] = x_substrate.find('physical_parameter_set').find('decay_rate').get('units') + d_mcds['continuum_variables'][s_substrate]['decay_rate'] = {} + d_mcds['continuum_variables'][s_substrate]['decay_rate']['value'] = float(x_substrate.find('physical_parameter_set').find('decay_rate').text) + d_mcds['continuum_variables'][s_substrate]['decay_rate']['units'] = x_substrate.find('physical_parameter_set').find('decay_rate').get('units') # store data from microenvironment file as numpy array # iterate over each voxel @@ -2622,70 +2628,13 @@ def _read_xml(self, xmlfile, output_path='.'): k = np.where(np.abs(ar_center[2] - d_mcds['mesh']['mnp_axis'][2]) < 1e-10)[0][0] # store value - d_mcds['raw_substrate'][s_substrate]['data'][j, i, k] = ar_microenv[4+i_s, i_voxel] - - ## get substrate listing - ds_substrate = d_mcds['substrate']['ds_substrate'] - ls_substrate = [ds_substrate[s_key] for s_key in sorted(ds_substrate, key=int)] - # store values - d_mcds['substrate']['ls_substarte'] = ls_substrate - - ## get substrate df - # extract data - ls_column = ['substrate','decay_rate','diffusion_coefficient'] - ll_sub = [] - for s_substrate in d_mcds['substrate']['ls_substarte']: - s_decay_value = d_mcds['raw_substrate'][s_substrate]['decay_rate']['value'] - s_diffusion_value = d_mcds['raw_substrate'][s_substrate]['diffusion_coefficient']['value'] - ll_sub.append([s_substrate, s_decay_value, s_diffusion_value]) - # generate dataframe - df_substrate = pd.DataFrame(ll_sub, columns=ls_column) - df_substrate.set_index('substrate', inplace=True) - df_substrate.columns.name = 'attribute' - # store values - d_mcds['substrate']['df_substarte'] = df_substrate - - ## get conc df - # flatten mesh coordnates - ar_m, ar_n, ar_p = d_mcds['mesh']['mnp_grid'] - ar_m = ar_m.flatten(order='C') - ar_n = ar_n.flatten(order='C') - ar_p = ar_p.flatten(order='C') - # get mesh spacing - dm, dn, dp = d_mcds['mesh']['mnp_spacing'] - # get voxel coordinates - ai_i = ((ar_m - ar_m.min()) / dm) - ai_j = ((ar_n - ar_n.min()) / dn) - ai_k = ((ar_p - ar_p.min()) / dp) - # handle coordinates - ls_column = [ - 'voxel_i','voxel_j','voxel_k', - 'mesh_center_m','mesh_center_n','mesh_center_p' - ] - la_data = [ai_i, ai_j, ai_k, ar_m, ar_n, ar_p] - # handle concentrations - for s_substrate in d_mcds['substrate']['ls_substarte']: - ls_column.append(s_substrate) - ar_conc = d_mcds['raw_substrate'][s_substrate]['data'].copy() - la_data.append(ar_conc.flatten(order='C')) - # generate dataframe - aa_data = np.array(la_data) - df_conc = pd.DataFrame(aa_data.T, columns=ls_column) - df_conc['time'] = d_mcds['metadata']['current_time'] - df_conc['runtime'] = d_mcds['metadata']['current_runtime'] / 60 # in min - df_conc['xmlfile'] = self.xmlfile - d_dtype = {'voxel_i': int, 'voxel_j': int, 'voxel_k': int} - df_conc = df_conc.astype(d_dtype) - # store values - df_conc.sort_values(['voxel_i', 'voxel_j', 'voxel_k', 'time'], axis=0, inplace=True) - df_conc.reset_index(drop=True, inplace=True) - df_conc.index.name = 'index' - d_mcds['substrate']['df_conc'] = df_conc + d_mcds['continuum_variables'][s_substrate]['data'][j, i, k] = ar_microenv[4+i_s, i_voxel] #################### # handle cell data # #################### + if self.verbose: print('working on discrete cell data ...') @@ -2703,9 +2652,8 @@ def _read_xml(self, xmlfile, output_path='.'): try: for x_celltype in x_celldata.find('cell_types').findall('type'): s_id = str(x_celltype.get('ID')) - # I don't like spaces in cell type names! - s_celltype = x_celltype.text #.replace(' ','_') # ROH - d_mcds['cell']['ds_celltype'].update({s_id : s_celltype}) + s_celltype = (x_celltype.text).replace(' ', '_') + d_mcds['metadata']['cell_type'].update({s_id : s_celltype}) b_celltype = True except AttributeError: pass @@ -2713,67 +2661,66 @@ def _read_xml(self, xmlfile, output_path='.'): # metadata cell_type label:id mapping detection ~ label information lost (silver quality) if not b_celltype: for x_label in x_celldata.find('labels').findall('label'): - # I don't like spaces in cell type names! - s_variable = x_label.tex #.replace(' ','_') # ROH + s_variable = x_label.text.replace(' ', '_') if s_variable in es_var_cell: for i_id in range(int(x_label.get('size'))): s_id = str(i_id) - d_mcds['cell']['ds_celltype'].update({s_id : s_id}) + d_mcds['metadata']['cell_type'].update({s_id : s_id}) b_celltype = True # iterate over labels which are children of labels these will be used to label data arrays ls_variable = [] for x_label in x_celldata.find('labels').findall('label'): # I don't like spaces in my dictionary keys! - s_variable = x_label.text #.replace(' ','_') # ROH + s_variable = x_label.text.replace(' ', '_') i_variable = int(x_label.get('size')) s_unit = x_label.get('units') # variable unique for each celltype substrate combination if s_variable in es_var_subs: - if (len(d_mcds['substrate']['ds_substrate']) > 0): + if (len(d_mcds['metadata']['substrate']) > 0): # continuum_variable id label sorting (becaus this is an id label mapping dict) - ls_substrate = [d_mcds['substrate']['ds_substrate'][o_key] for o_key in sorted(d_mcds['substrate']['ds_substrate'].keys(), key=int)] + ls_substrate = [d_mcds['metadata']['substrate'][o_key] for o_key in sorted(d_mcds['metadata']['substrate'].keys(), key=int)] for s_substrate in ls_substrate: s_variable_subs = s_substrate + '_' + s_variable ls_variable.append(s_variable_subs) - d_mcds['raw_cell']['units'].update({s_variable_subs : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_subs : s_unit}) else: ls_substrate = [str(i_substrate) for i_substrate in range(i_variable)] for s_substrate in ls_substrate: s_variable_subs = s_variable + '_' + s_substrate ls_variable.append(s_variable_subs) - d_mcds['raw_cell']['units'].update({s_variable_subs : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_subs : s_unit}) # variable unique for each celltype celltype combination elif s_variable in es_var_cell: - if (len(d_mcds['cell']['ds_celltype']) > 0): - # raw_cell id label sorting (becaus this is an id label mapping dict) - ls_celltype = [d_mcds['cell']['ds_celltype'][o_key] for o_key in sorted(d_mcds['cell']['ds_celltype'].keys(), key=int)] + if (len(d_mcds['metadata']['cell_type']) > 0): + # discrete_cells id label sorting (becaus this is an id label mapping dict) + ls_celltype = [d_mcds['metadata']['cell_type'][o_key] for o_key in sorted(d_mcds['metadata']['cell_type'].keys(), key=int)] for s_celltype in ls_celltype: s_variable_celltype = s_celltype + '_' + s_variable ls_variable.append(s_variable_celltype) - d_mcds['raw_cell']['units'].update({s_variable_celltype : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_celltype : s_unit}) else: ls_celltype = [str(i_celltype) for i_celltype in range(i_variable)] for s_celltype in ls_celltype: s_variable_celltype = s_variable + '_' + s_celltype ls_variable.append(s_variable_celltype) - d_mcds['raw_cell']['units'].update({s_variable_celltype : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_celltype : s_unit}) # variable unique for each dead model elif s_variable in es_var_death: for i_deathrate in range(i_variable): s_variable_deathrate = s_variable + '_' + str(i_deathrate) ls_variable.append(s_variable_deathrate) - d_mcds['raw_cell']['units'].update({s_variable_deathrate : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_deathrate : s_unit}) # spatial variable elif s_variable in es_var_spatial: for s_axis in ['_x','_y','_z']: s_variable_spatial = s_variable + s_axis ls_variable.append(s_variable_spatial) - d_mcds['raw_cell']['units'].update({s_variable_spatial: s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable_spatial: s_unit}) # simple variable and vectors else: @@ -2782,7 +2729,7 @@ def _read_xml(self, xmlfile, output_path='.'): ls_variable.append(f'{s_variable}_{str(i_n).zfill(3)}') else: ls_variable.append(s_variable) - d_mcds['raw_cell']['units'].update({s_variable : s_unit}) + d_mcds['discrete_cells']['units'].update({s_variable : s_unit}) # load the file s_cellpathfile = self.path + '/' + x_celldata.find('filename').text @@ -2792,99 +2739,76 @@ def _read_xml(self, xmlfile, output_path='.'): print(f'reading: {s_cellpathfile}') except ValueError: # hack: some old PhysiCell versions generates a corrupt cells.mat file, if there are zero cells. if self.verbose: - print(f'Warning @ TimeStep._read_xml : corrupt {s_cellpathfile} detected!\nassuming time step with zero cells because of a known bug in PhysiCell MultiCellDS version 0.5 output.') + print(f'Warning @ pyMCDS._read_xml : corrupt {s_cellpathfile} detected!\nassuming time step with zero cells because of a known bug in PhysiCell MultiCellDS version 0.5 output.') ar_cell = np.empty([len(ls_variable),0]) # check for column label mapping error (as good as it gets) if (ar_cell.shape[0] != len(ls_variable)): - sys.exit(f'Error @ TimeStep._read_xml : extracted column label list leng {len(ls_variable)} and data array shape {ar_cell.shape} are incompatible!') + sys.exit(f'Error @ pyMCDS._read_xml : extracted column label list leng {len(ls_variable)} and data array shape {ar_cell.shape} are incompatible!') # metadata cell_type label:id mapping detection ~ label information lost (bronze quality) if not b_celltype: for r_celltype in set(ar_cell[ls_variable.index('cell_type'),:]): s_celltype = str(int(r_celltype)) - d_mcds['cell']['ds_celltype'].update({s_celltype : s_celltype}) + d_mcds['metadata']['cell_type'].update({s_celltype : s_celltype}) b_celltype = True # store data - d_mcds['raw_cell']['data'] = {} + d_mcds['discrete_cells']['data'] = {} for i_col in range(len(ls_variable)): - d_mcds['raw_cell']['data'].update({ls_variable[i_col]: ar_cell[i_col,:]}) + d_mcds['discrete_cells']['data'].update({ls_variable[i_col]: ar_cell[i_col,:]}) - ## get celltype list - ds_celltype = d_mcds['cell']['ds_celltype'] - ls_celltype = [ds_celltype[s_key] for s_key in sorted(ds_celltype, key=int)] - # store values - d_mcds['cell']['ls_celltype'] = ls_celltype + ##################### + # handle graph data # + ##################### + d_mcds['discrete_cells']['graph'] = {} + d_mcds['discrete_cells']['graph'].update({'neighbor_cells': {}}) + d_mcds['discrete_cells']['graph'].update({'attached_cells': {}}) + d_mcds['discrete_cells']['graph'].update({'spring_attached_cells': {}}) - ## get cell df - # get cell position and more - df_cell = pd.DataFrame(d_mcds['raw_cell']['data']) - df_cell['time'] = d_mcds['metadata']['current_time'] - df_cell['runtime'] = d_mcds['metadata']['current_runtime'] / 60 # in min - df_cell['xmlfile'] = self.xmlfile - df_voxel = df_cell.loc[:,['position_x','position_y','position_z']].copy() + if self.graph: + if self.verbose: + print('working on graph data ...') - # get mesh spacing - dm, dn, dp = d_mcds['mesh']['mnp_spacing'] + # neighborhood cell graph + s_cellpathfile = self.path + '/' + x_cell.find('neighbor_graph').find('filename').text + dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) + if self.verbose: + print(f'reading: {s_cellpathfile}') - # get mesh and voxel min max values - tr_m_range, tr_n_range, tr_p_range = d_mcds['mesh']['mnp_range'] - tr_i_range, tr_j_range, tr_k_range = d_mcds['mesh']['ijk_range'] + # store data + d_mcds['discrete_cells']['graph'].update({'neighbor_cells': dei_graph}) - # get voxel for each cell - df_voxel.loc[:,'voxel_i'] = np.round((df_voxel.loc[:,'position_x'] - tr_m_range[0]) / dm).astype(int) - df_voxel.loc[:,'voxel_j'] = np.round((df_voxel.loc[:,'position_y'] - tr_n_range[0]) / dn).astype(int) - df_voxel.loc[:,'voxel_k'] = np.round((df_voxel.loc[:,'position_z'] - tr_p_range[0]) / dp).astype(int) - df_voxel.loc[(df_voxel.voxel_i > tr_i_range[1]), 'voxel_i'] = tr_i_range[1] # i_max - df_voxel.loc[(df_voxel.voxel_i < tr_i_range[0]), 'voxel_i'] = tr_i_range[0] # i_min - df_voxel.loc[(df_voxel.voxel_j > tr_j_range[1]), 'voxel_j'] = tr_j_range[1] # j_max - df_voxel.loc[(df_voxel.voxel_j < tr_j_range[0]), 'voxel_j'] = tr_j_range[0] # j_min - df_voxel.loc[(df_voxel.voxel_k > tr_k_range[1]), 'voxel_k'] = tr_k_range[1] # k_max - df_voxel.loc[(df_voxel.voxel_k < tr_k_range[0]), 'voxel_k'] = tr_k_range[0] # k_min + # attached cell graph + s_cellpathfile = self.path + '/' + x_cell.find('attached_cells_graph').find('filename').text + dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) + if self.verbose: + print(f'reading: {s_cellpathfile}') - # merge voxel (inner join) - df_cell = pd.merge(df_cell, df_voxel, on=['position_x', 'position_y', 'position_z']) + # store data + d_mcds['discrete_cells']['graph'].update({'attached_cells': dei_graph}) - # merge cell_density (left join) - df_cellcount = df_cell.loc[:,['voxel_i','voxel_j','voxel_k','ID']].groupby(['voxel_i','voxel_j','voxel_k']).count().reset_index() - ls_column = list(df_cellcount.columns) - ls_column[-1] = 'cell_count_voxel' - df_cellcount.columns = ls_column - s_density = f"cell_density_{d_mcds['metadata']['spatial_unit']}3" - df_cellcount[s_density] = df_cellcount.loc[:,'cell_count_voxel'] / d_mcds['mesh']['volume'] - df_cell = pd.merge( - df_cell, - df_cellcount, - on = ['voxel_i', 'voxel_j', 'voxel_k'], - how = 'left', - ) + # spring attached cell graph + try: + s_cellpathfile = self.path + '/' + x_cell.find('spring_attached_cells_graph').find('filename').text + dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) + if self.verbose: + print(f'reading: {s_cellpathfile}') - # get column label set - es_column = set(df_cell.columns) + # store data + d_mcds['discrete_cells']['graph'].update({'spring_attached_cells': dei_graph}) + except AttributeError: + pass - # get vector length - for s_var_spatial in es_var_spatial: - es_vector = es_column.intersection({f'{s_var_spatial}_x',f'{s_var_spatial}_y',f'{s_var_spatial}_z'}) - if len(es_vector) > 0: - # linear algebra - #a_vector = df_cell.loc[:,ls_vector].values - #a_length = np.sqrt(np.diag(np.dot(a_vector, a_vector.T))) - # pythoagoras - a_length = None - for s_vector in es_vector: - a_vectorsq = df_cell.loc[:,s_vector].values**2 - if (a_length is None): - a_length = a_vectorsq - else: - a_length += a_vectorsq - a_length = a_length**(1/2) - # result - df_cell[f'{s_var_spatial}_vectorlength'] = a_length - # physiboss + ######################### + # handle physiboss data # + ######################### + + d_mcds['discrete_cells']['physiboss'] = None + if self.physiboss: if self.verbose: print('working on physiboss data ...') @@ -2908,155 +2832,14 @@ def _read_xml(self, xmlfile, output_path='.'): for s_node in sorted(es_node): df_physiboss[f'node_{s_node}'] = df_physiboss.state.str.find(s_node) > -1 - # store data - df_cell = pd.merge( - df_cell, - df_physiboss, - left_index = True, - right_index = True, - how = 'left', - ) - elif self.verbose: - print(f'Warning @ TimeStep._read_xml : physiboss file missing {s_intracellpathfile}.') + print(f'Warning @ pyMCDS._read_xml : physiboss file missing {s_intracellpathfile}.') else: pass - - # microenvironment - if self.microenv: - # merge substrate (left join) - df_sub = d_mcds['substrate']['df_substarte'] - for s_sub in df_sub.index: - for s_rate in df_sub.columns: - s_var = f'{s_sub}_{s_rate}' - df_cell[s_var] = df_sub.loc[s_sub,s_rate] - - # merge concentration (left join) - df_conc = d_mcds['substrate']['df_conc'].copy() # voxel and mesh coordinates - df_conc.drop({'time', 'runtime','xmlfile'}, axis=1, inplace=True) - df_cell = pd.merge( - df_cell, - df_conc, - on = ['voxel_i', 'voxel_j', 'voxel_k'], - how = 'left', - ) - - # variable typing - do_type = {} - [do_type.update({k:v}) for k,v in do_var_type.items() if k in es_column] - do_type.update(self.custom_data_type) - do_int = do_type.copy() - [do_int.update({k:int}) for k in do_int.keys()] - ls_int = sorted(do_int.keys()) - df_cell.loc[:,ls_int] = df_cell.loc[:,ls_int].round() - df_cell = df_cell.astype(do_int) - df_cell = df_cell.astype(do_type) - - # categorical translation - try: # bue 20240805: missing in MCDS version <= 0.5 (November 2021) - df_cell.loc[:,'current_death_model'] = df_cell.loc[:,'current_death_model'].replace(ds_death_model) # bue 20230614: this column looks like an artefact to me - except KeyError: - pass - df_cell.loc[:,'cycle_model'] = df_cell.loc[:,'cycle_model'].replace(ds_cycle_model) - df_cell.loc[:,'cycle_model'] = df_cell.loc[:,'cycle_model'].replace(ds_death_model) - df_cell.loc[:,'current_phase'] = df_cell.loc[:,'current_phase'].replace(ds_cycle_phase) - df_cell.loc[:,'current_phase'] = df_cell.loc[:,'current_phase'].replace(ds_death_phase) - df_cell.loc[:,'cell_type'] = df_cell.loc[:,'cell_type'].replace(d_mcds['cell']['ds_celltype']) - df_cell.loc[:,'chemotaxis_index'] = df_cell.loc[:,'chemotaxis_index'].replace(d_mcds['substrate']['ds_substrate']) - - # store - df_cell = df_cell.loc[:,sorted(df_cell.columns)] - df_cell.sort_values('ID', axis=0, inplace=True) - df_cell.set_index('ID', inplace=True) - d_mcds['cell']['df_cell'] = df_cell.copy() - - ## get cell attribute list - d_mcds['cell']['ls_cellattr'] = sorted(set(d_mcds['cell']['df_cell'].columns).difference(es_coor_cell)) - - - #################### - # handle unit data # - #################### - - if self.verbose: - print('working on unit data ...') - - # extract data - ds_unit = {} - - # units for metadata parameters - ds_unit.update({'time': d_mcds['metadata']['time_units']}) - ds_unit.update({'runtime': d_mcds['metadata']['runtime_units']}) - ds_unit.update({'spatial_unit': d_mcds['metadata']['spatial_unit']}) - - # microenvironment - if self.microenv: - for s_substrate in d_mcds['substrate']['ls_substarte']: - # unit from substrate parameters - s_unit = d_mcds['raw_substrate'][s_substrate]['units'] - ds_unit.update({s_substrate: s_unit}) - - # units from microenvironment parameters - s_diffusion_key = f'{s_substrate}_diffusion_coefficient' - s_diffusion_unit = d_mcds['raw_substrate'][s_substrate]['diffusion_coefficient']['units'] - ds_unit.update({s_diffusion_key: s_diffusion_unit}) - - s_decay_key = f'{s_substrate}_decay_rate' - s_decay_unit = d_mcds['raw_substrate'][s_substrate]['decay_rate']['units'] - ds_unit.update({s_decay_key: s_decay_unit}) - - # units from cell parameters - ds_unit.update(d_mcds['raw_cell']['units']) - - # output - del ds_unit['ID'] - d_mcds['metadata']['ds_unit'] = ds_unit - - - ##################### - # handle graph data # - ##################### - - d_mcds['cell']['dei_graph'] = {} - d_mcds['cell']['dei_graph'].update({'neighbor_cells': {}}) - d_mcds['cell']['dei_graph'].update({'attached_cells': {}}) - d_mcds['cell']['dei_graph'].update({'spring_attached_cells': {}}) - - if self.graph: - if self.verbose: - print('working on graph data ...') - - # neighborhood cell graph - s_cellpathfile = self.path + '/' + x_cell.find('neighbor_graph').find('filename').text - dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) - if self.verbose: - print(f'reading: {s_cellpathfile}') - - # store data - d_mcds['cell']['dei_graph'].update({'neighbor_cells': dei_graph}) - - # attached cell graph - s_cellpathfile = self.path + '/' + x_cell.find('attached_cells_graph').find('filename').text - dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) - if self.verbose: - print(f'reading: {s_cellpathfile}') - # store data - d_mcds['cell']['dei_graph'].update({'attached_cells': dei_graph}) - - # spring attached cell graph - try: - s_cellpathfile = self.path + '/' + x_cell.find('spring_attached_cells_graph').find('filename').text - dei_graph = graphfile_parser(s_pathfile=s_cellpathfile) - if self.verbose: - print(f'reading: {s_cellpathfile}') - - # store data - d_mcds['cell']['dei_graph'].update({'spring_attached_cells': dei_graph}) - except AttributeError: - pass + d_mcds['discrete_cells']['physiboss'] = df_physiboss ########## diff --git a/pcdl/timeseries.py b/pcdl/pyMCDSts.py similarity index 75% rename from pcdl/timeseries.py rename to pcdl/pyMCDSts.py index b3d1ae3..95ab9c8 100644 --- a/pcdl/timeseries.py +++ b/pcdl/pyMCDSts.py @@ -1,5 +1,5 @@ ######### -# title: timeseries.py +# title: pyMCDSts.py # # language: python3 # date: 2022-08-22 @@ -7,25 +7,25 @@ # authors: Patrick Wall, Randy Heiland, Paul Macklin, Elmar Bucher # # description: -# timeseries.py defines an object class, able to load and access +# pyMCDSts.py defines an object class, able to load and access # within python a time series of mcds objects loaded from a single -# PhysiCell model output directory. timeseries.py was first forked from +# PhysiCell model output directory. pyMCDSts.py was first forked from # PhysiCell-Tools python-loader, where it was implemented as # pyMCDS_timeseries.py, then totally rewritten and further developed. -#### +# +# the make_image and make_movie functions are cloned from the PhysiCell +# Makefile. note on difference image magick convert and mogrify: +# + https://graphicsmagick-tools.narkive.com/9Sowc4HF/gm-tools-mogrify-vs-convert +######### # load libraries -import anndata as ad -import bioio_base -from bioio.writers import OmeTiffWriter import glob import matplotlib.pyplot as plt import numpy as np import os import pandas as pd -from pcdl import render_neuroglancer -from pcdl.timestep import TimeStep, es_coor_cell, es_coor_conc, _anndextract +from pcdl.pyMCDS import pyMCDS, es_coor_cell, es_coor_conc import platform import sys @@ -80,15 +80,14 @@ def make_gif(path, interface='jpeg'): if path.endswith('/'): path = path[:-1] if not os.path.isdir(path): sys.exit(f'Error @ make_gif : {path} path does not exist.') - s_ofile = path.split('/')[-1] - if s_ofile.startswith('.'): s_ofile = s_ofile[1:] - if (len(s_ofile) == 0): s_ofile = 'movie' - s_ofile += f'_{interface}.gif' - s_ofile = s_ofile.replace(' ','_') - s_opathfile = path + '/' + s_ofile + s_file = path.split('/')[-1] + if s_file.startswith('.'): s_file = s_file[1:] + if (len(s_file) == 0): s_file = 'movie' + s_file += f'_{interface}.gif' + s_opathfile = f'{path}/{s_file}' s_ipathfiles = f'{path}/*.{interface}' # genaerate gif - s_cmd = f'{s_magick}convert "{s_ipathfiles}" "{s_opathfile}"' + s_cmd = f'{s_magick}convert {s_ipathfiles} {s_opathfile}' if (os.system(s_cmd) != 0): sys.exit("Error @ make_gif : imagemagick could not generatet the gif.") @@ -134,7 +133,6 @@ def make_movie(path, interface='jpeg', framerate=12): if s_ofile.startswith('.'): s_ofile = s_ofile[1:] if (len(s_ofile) == 0): s_ofile = 'movie' s_ofile += f'_{interface}{framerate}.mp4' - s_ofile = s_ofile.replace(' ','_') s_opathfile = f'{s_path}/{s_ofile}' # generate input file list @@ -146,7 +144,7 @@ def make_movie(path, interface='jpeg', framerate=12): f.close() # genearete movie - s_cmd = f'ffmpeg -y -r {framerate} -f concat -i ffmpeginput.txt -vcodec libx264 -pix_fmt yuv420p -strict -2 -tune animation -crf 15 -acodec none "{s_ofile}"' # -safe 0 + s_cmd = f'ffmpeg -y -r {framerate} -f concat -i ffmpeginput.txt -vcodec libx264 -pix_fmt yuv420p -strict -2 -tune animation -crf 15 -acodec none {s_ofile}' # -safe 0 if (os.system(s_cmd) != 0): sys.exit("Error @ make_movie : ffmpeg could not generatet the movie.") os.remove('ffmpeginput.txt') @@ -160,7 +158,7 @@ def make_movie(path, interface='jpeg', framerate=12): # classes # ########### -class TimeSeries: +class pyMCDSts: def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True): """ input: @@ -180,10 +178,11 @@ def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=Tru microenv: boole; default True should the microenvironment data be loaded? - setting microenv to False will use less memory and speed up processing. + setting microenv to False will use less memory and speed up + processing, similar to the original pyMCDS_cells.py script. graph: boole; default True - should the graphs, like cell_neighbor_graph.txt, be loaded? + should the graphs be loaded? setting graph to False will use less memory and speed up processing. physiboss: boole; default True @@ -200,12 +199,12 @@ def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=Tru setting verbose to False for less text output, while processing. output: - mcdsts: TimeSeries class instance + mcdsts: pyMCDSts class instance this instance offers functions to process all stored time steps from a simulation. description: - TimeSeries.__init__ generates a class instance the instance offers + pyMCDSts.__init__ generates a class instance the instance offers functions to process all time steps in the output_path directory. """ output_path = output_path.replace('\\','/') @@ -214,7 +213,7 @@ def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=Tru if (output_path.endswith('/')) and (len(output_path) > 1): output_path = output_path[:-1] if not os.path.isdir(output_path): - print(f'Error @ TimeSeries.__init__ : this is not a path! could not load {output_path}.') + print(f'Error @ pyMCDSts.__init__ : this is not a path! could not load {output_path}.') self.path = output_path self.ls_xmlfile = [s_pathfile.replace('\\','/').split('/')[-1] for s_pathfile in sorted(glob.glob(self.path + f'/output*.xml'))] # bue 2022-10-22: is output*.xml always the correct pattern? self.custom_data_type = custom_data_type @@ -227,7 +226,6 @@ def __init__(self, output_path='.', custom_data_type={}, load=True, microenv=Tru self.read_mcds() else: self.l_mcds = None - self.l_annmcds = None def set_verbose_false(self): @@ -259,8 +257,6 @@ def set_verbose_true(self): def make_gif(self, path, interface='jpeg'): """ help(pcdl.make_gif) - try: mcdsts.make_gif(mcdsts.plot_scatter()) - try: mcdsts.make_gif(mcdsts.plot_contour('substrate')) """ s_opathfile = make_gif(path=path, interface=interface) return s_opathfile @@ -269,32 +265,17 @@ def make_gif(self, path, interface='jpeg'): def make_movie(self, path, interface='jpeg', framerate=12): """ help(pcdl.make_movie) - try: mcdsts.make_movie(mcdsts.plot_scatter()) - try: mcdsts.make_movie(mcdsts.plot_contour('substrate')) """ s_opathfile = make_movie(path=path, interface=interface, framerate=framerate) return s_opathfile - def render_neuroglancer(self, tiffpathfile, timestep=0, intensity_cmap='gray'): - """ - help(pcdl.render_neuroglancer) - try: mcdsts.render_neuroglancer(mcdsts.make_ome_tiff(), 0) - """ - o_viewer = render_neuroglancer( - tiffpathfile = tiffpathfile, - timestep = timestep, - intensity_cmap = intensity_cmap, - ) - return o_viewer - - ## LOAD DATA ## def get_xmlfile_list(self): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. output: xmlfile_list: list of strings @@ -312,7 +293,7 @@ def get_xmlfile_list(self): def read_mcds(self, xmlfile_list=None): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. xmlfile_list: list of strings; default None list of physicell output*.xml strings. @@ -322,7 +303,7 @@ def read_mcds(self, xmlfile_list=None): description: the function returns a list of mcds objects loaded by - TimeStep calls. + pyMCDS calls. """ # handle input if (xmlfile_list is None): @@ -333,7 +314,7 @@ def read_mcds(self, xmlfile_list=None): # load mcds objects into list l_mcds = [] for s_xmlpathfile in ls_xmlpathfile: - mcds = TimeStep( + mcds = pyMCDS( xmlfile = s_xmlpathfile, custom_data_type = self.custom_data_type, microenv = self.microenv, @@ -355,7 +336,7 @@ def read_mcds(self, xmlfile_list=None): def get_mcds_list(self): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. output: self.l_mcds: list of chronologically ordered mcds objects. @@ -373,7 +354,7 @@ def get_mcds_list(self): def get_conc_df(self, values=1, drop=set(), keep=set(), collapse=True): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have to be outputted. @@ -458,7 +439,7 @@ def get_conc_df(self, values=1, drop=set(), keep=set(), collapse=True): def get_conc_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have @@ -521,10 +502,10 @@ def get_conc_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dlr_variable_range - def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, ext='jpeg', figbgcolor=None, **kwargs): + def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cmap='viridis', title='', grid=True, xlim=None, ylim=None, xyequal=True, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: - self: TimeSeries class instance + self: pyMCDSts class instance focus: string column name within conc dataframe, for example. @@ -575,6 +556,11 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -583,11 +569,6 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the matplotlib contour and contourf function. - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html - + https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html - output: fig: matplotlib figures, depending on ext, either as files or as objects. the figures contains the contour plot and color bar. @@ -656,9 +637,9 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma xyequal = xyequal, ax = None, figsizepx = figsizepx, + directory = directory, ext = ext, figbgcolor = figbgcolor, - **kwargs, ) lo_output.append(o_output) @@ -666,9 +647,11 @@ def plot_contour(self, focus, z_slice=0.0, extrema=None, alpha=1, fill=True, cma return lo_output - def make_conc_vtk(self): + def make_conc_vtk(self, visualize=True): """ input: + visualize: boolean; default is False + additionally, visualize cells using vtk renderer. output: ls_vtkpathfile: one vtk file per mcds time step that contains @@ -686,7 +669,7 @@ def make_conc_vtk(self): # processing ls_vtkpathfile = [] for mcds in self.get_mcds_list(): - s_vtkpathfile = mcds.make_conc_vtk() + s_vtkpathfile = mcds.make_conc_vtk(visualize=visualize) ls_vtkpathfile.append(s_vtkpathfile) # output @@ -698,7 +681,7 @@ def make_conc_vtk(self): def get_cell_df(self, values=1, drop=set(), keep=set(), collapse=True): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have to be outputted. @@ -786,7 +769,7 @@ def get_cell_df(self, values=1, drop=set(), keep=set(), collapse=True): def get_cell_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): """ input: - self: TimeSeries class instance. + self: pyMCDSts class instance. values: integer; default is 1 minimal number of values a variable has to have @@ -851,10 +834,10 @@ def get_cell_attribute(self, values=1, drop=set(), keep=set(), allvalues=False): return dl_variable_range - def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, ext='jpeg', figbgcolor=None, **kwargs): + def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cmap='viridis', title='', grid=True, legend_loc='lower left', xlim=None, ylim=None, xyequal=True, s=1.0, figsizepx=None, directory=None, ext='jpeg', figbgcolor=None): """ input: - self: TimeSeries class instance + self: pyMCDSts class instance focus: string; default is 'cell_type' column name within cell dataframe. @@ -915,6 +898,11 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma None tries to take the values from the initial.svg file. fall back setting is [640, 480]. + directory: string; default None + if None, a meaningful output directory name will be generated, + based on focus and z_slice parameters, else the resulting plots + will be moved to the explicit name directory. + ext: string; default is jpeg output image format. possible formats are jpeg, png, and tiff. None will return the matplotlib fig object. @@ -923,10 +911,6 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma or white (jpeg, tiff). figure background color. - **kwargs: possible additional keyword arguments input, - handled by the pandas dataframe plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame - output: fig: matplotlib figures, depending on ext, either as files or as objects. the figures contains the scatter plot and @@ -964,9 +948,9 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma s = s, ax = None, figsizepx = figsizepx, + directory = directory, ext = ext, figbgcolor = figbgcolor, - **kwargs, ) lo_output.append(o_output) @@ -974,12 +958,15 @@ def plot_scatter(self, focus='cell_type', z_slice=0.0, z_axis=None, alpha=1, cma return lo_output - def make_cell_vtk(self, attribute=['cell_type']): + def make_cell_vtk(self, attribute=['cell_type'], visualize=False): """ input: attribute: list of strings; default is ['cell_type'] column name within cell dataframe. + visualize: boolean; default is False + additionally, visualize cells using vtk renderer. + output: ls_vtkpathfile: one 3D glyph vtk file per mcds time step that contains cells. @@ -997,6 +984,7 @@ def make_cell_vtk(self, attribute=['cell_type']): for mcds in self.get_mcds_list(): s_vtkpathfile = mcds.make_cell_vtk( attribute = attribute, + visualize = visualize, ) ls_vtkpathfile.append(s_vtkpathfile) @@ -1004,147 +992,12 @@ def make_cell_vtk(self, attribute=['cell_type']): return ls_vtkpathfile - ## OME TIFF RELATED FUNCTIONS ## - - def make_ome_tiff(self, cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=True): - """ - input: - cell_attribute: strings; default is 'ID', which will result in a - cell segmentation mask. - column name within the cell dataframe. - the column data type has to be numeric (bool, int, float) - and cannot be string. - the result will be stored as 32 bit float. - - conc_cutoff: dictionary string to real; default is an empty dictionary. - if a contour from a substrate not should be cut by greater - than zero (shifted to integer 1), another cutoff value can be specified here. - - focus: set of strings; default is a None - set of substrate and cell_type names to specify what will be - translated into ome tiff format. - if None, all substrates and cell types will be processed. - - file: boolean; default True - if True, an ome tiff file is the output. - if False, a numpy array with shape tczyx is the output. - - collapse: boole; default True - should all mcds time steps from the time series be collapsed - into one ome tiff file (numpy array), - or an ome tiff file (numpy array) for each time step? - - output: - a_tczyx_img: numpy array or ome tiff file. - - - description: - function to transform chosen mcdsts output into an 1[um] spaced - tczyx (time, channel, z-axis, y-axis, x-axis) ome tiff file or numpy array, - one substrate or cell_type per channel. - a ome tiff file is more or less: - a numpy array, containing the image information - and a xml, containing the microscopy metadata information, - like the channel labels. - the ome tiff file format can for example be read by the napari - or fiji (imagej) software. - - https://napari.org/stable/ - https://fiji.sc/ - """ - # for each T time step - l_tczyx_img = [] - for i, mcds in enumerate(self.get_mcds_list()): - # processing - b_file = True # 10 - if (not file and not collapse) or (not file and collapse) or (file and collapse): # 00, 01, 11 - b_file = False - o_tczyx_img = mcds.make_ome_tiff( - cell_attribute = cell_attribute, - conc_cutoff = conc_cutoff, - focus = focus, - file = b_file - ) - l_tczyx_img.append(o_tczyx_img) - - # handle channels - ls_substrate = mcds.get_substrate_list() - ls_celltype = mcds.get_celltype_list() - - if not (focus is None): - ls_substrate = [s_substrate for s_substrate in ls_substrate if s_substrate in set(focus)] - ls_celltype = [s_celltype for s_celltype in ls_celltype if s_celltype in set(focus)] - if (set(focus) != set(ls_substrate).union(set(ls_celltype))): - sys.exit(f'Error : {focus} not found in {ls_substrate} {ls_celltype}') - - # output 00 list of numpy arrays - if (not file and not collapse): # 00 - if self.verbose: - print(f'la_tczyx_img shape: {len(l_tczyx_img)} * {l_tczyx_img[0].shape}') - return l_tczyx_img - - # output 01 numpy array - elif (not file and collapse): # 01 - # numpy array - a_tczyx_img = np.array(l_tczyx_img) - if self.verbose: - print('a_tczyx_img shape:', a_tczyx_img.shape) - return a_tczyx_img - - # output 10 list of pathfile strings - elif (file and not collapse): # 10 - return l_tczyx_img - - # output 11 ometiff file - elif (file and collapse): # 11 - a_tczyx_img = np.array(l_tczyx_img) - if self.verbose: - print('a_tczyx_img shape:', a_tczyx_img.shape) - - # generate filename - s_channel = '' - for s_substrate in ls_substrate: - try: - r_value = conc_cutoff[s_substrate] - s_channel += f'_{s_substrate}{r_value}' - except KeyError: - s_channel += f'_{s_substrate}' - for s_celltype in ls_celltype: - s_channel += f'_{s_celltype}' - if len(ls_celltype) > 0: - s_channel += f'_{cell_attribute}' - s_tifffile = f"timeseries{s_channel.replace(' ','_')}.ome.tiff" - if (len(s_tifffile) > 255): - print(f"Warning: filename {len(s_tifffile)} > 255 character.") - s_tifffile = 'timeseries_channels.ome.tiff' - print(f"file name adjusted to {s_tifffile}.") - s_tiffpathfile = self.path + '/' + s_tifffile - - # save to file - OmeTiffWriter.save( - a_tczyx_img, - s_tiffpathfile, - dim_order = 'TCZYX', - #ome_xml=x_img, - channel_names = ls_substrate + ls_celltype, - image_names = [f'timeseries_{cell_attribute}'], - physical_pixel_sizes = bioio_base.types.PhysicalPixelSizes(mcds.get_voxel_spacing()[2], 1.0, 1.0), # z,y,x [um] - #channel_colors=, - #fs_kwargs={}, - ) - return s_tiffpathfile - - # error case - else: - sys.exit(f'Error @ make_ome_tiff : {file} {collapse} strange file collapse combination.') - - ## TIME SERIES RELATED FUNCTIONS ## - def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanmean, frame='cell', z_slice=None, logy=False, ylim=None, secondary_y=None, subplots=False, sharex=False, sharey=False, linestyle='-', linewidth=None, cmap=None, color=None, grid=True, legend=True, yunit=None, title=None, ax=None, figsizepx=[640, 480], ext=None, figbgcolor=None, **kwargs): + def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanmean, frame='cell', z_slice=None, logy=False, ylim=None, secondary_y=None, subplots=False, sharex=False, sharey=False, linestyle='-', linewidth=None, cmap=None, color=None, grid=True, legend=True, yunit=None, title=None, ax=None, figsizepx=[640, 480], ext=None, figbgcolor=None): """ input: - self: TimeSeries class instance + self: pyMCDSts class instance focus_cat: string; default is None categorical or boolean data column within dataframe specified under frame. @@ -1242,10 +1095,6 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme figure background color. only relevant if ext not is None. - **kwargs: possible additional keyword arguments input, - handled by the pandas series plot function. - + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.plot.html - output: if ext is None: a fig matplotlib figure, containing the ax axis object, is returned. else: an image file is generated under the returned path. @@ -1313,7 +1162,7 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme mcds.set_verbose_true() # error else: - sys.exit(f"Error @ TimeSeries.plot_timeseries : unknown frame {frame}. known are cell_df and conc_df.") + sys.exit(f"Error @ pyMCDSts.plot_timeseries : unknown frame {frame}. known are cell_df and conc_df.") # handle z_slize if not (z_slice is None): df_frame = df_frame.loc[(df_frame.mesh_center_p == z_slice),:] @@ -1328,7 +1177,7 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme df_aggregate = o_aggregate.loc[:,[focus_num]] df_aggregate.columns = [r_time] else: - sys.exit(f'Error @ TimeSeries.plot_timeseries : {aggregate_num} calculation returns unexpected variable type {type(o_aggregate)}.\nthe expected type is a pandas Series or DataFrame.') + sys.exit(f'Error @ pyMCDSts.plot_timeseries : {aggregate_num} calculation returns unexpected variable type {type(o_aggregate)}.\nthe expected type is a pandas Series or DataFrame.') # store result if (df_series is None): df_series = df_aggregate @@ -1349,9 +1198,9 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme elif (focus_num == 'count'): ylabel = f'focus_num [{yunit}]' elif (yunit is None): - ylabel = f"{aggregate_num.__name__.replace('nan','')} {focus_num}" + ylabel = f"{aggregate_num.__name__.replace('np.nan','')} {focus_num}" else: - ylabel = f"{aggregate_num.__name__.replace('nan','')} {focus_num} [{yunit}]" + ylabel = f"{aggregate_num.__name__.replace('np.nan','')} {focus_num} [{yunit}]" # generate series line plot if (ax is None): @@ -1385,8 +1234,7 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme ylabel = ylabel, xlabel = f"time [{mcds.get_unit_dict()['time']}]", title = title, - ax = ax, - **kwargs, + ax = ax ) else: # if color @@ -1406,8 +1254,7 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme ylabel = ylabel, xlabel = f"time [{mcds.get_unit_dict()['time']}]", title = title, - ax = ax, - **kwargs, + ax = ax ) # output @@ -1415,10 +1262,9 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme return fig else: if (focus_num == 'count'): - s_ofile = f'timeseries_{frame}_{focus_cat}_{focus_num}.{ext}'.replace(' ','_') + s_pathfile = self.path + f'/timeseries_{frame}_{focus_cat}_{focus_num}.{ext}' else: - s_ofile = f"timeseries_{frame}_{focus_cat}_{focus_num}_{aggregate_num.__name__.replace('np.nan','')}.{ext}".replace(' ','_') - s_pathfile = self.path + '/' + s_ofile + s_pathfile = self.path + f"/timeseries_{frame}_{focus_cat}_{focus_num}_{aggregate_num.__name__.replace('np.nan','')}.{ext}" if figbgcolor is None: figbgcolor = 'auto' plt.tight_layout() @@ -1432,7 +1278,7 @@ def plot_timeseries(self, focus_cat=None, focus_num=None, aggregate_num=np.nanme def make_graph_gml(self, graph_type, edge_attribute=True, node_attribute=[]): """ input: - self: TimeSeries class instance. + self: pyMCDS class instance. graph_type: string to specify which physicell output data should be processed. @@ -1479,170 +1325,3 @@ def make_graph_gml(self, graph_type, edge_attribute=True, node_attribute=[]): # outout return ls_pathfile - - - ## ANNDATA RELATED FUNCTIONS ## - - def get_anndata(self, values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True): - """ - input: - values: integer; default is 1 - minimal number of values a variable has to have to be outputted. - variables that have only 1 state carry no information. - None is a state too. - - drop: set of strings; default is an empty set - set of column labels to be dropped for the dataframe. - don't worry: essential columns like ID, coordinates - and time will never be dropped. - Attention: when the keep parameter is given, then - the drop parameter has to be an empty set! - - keep: set of strings; default is an empty set - set of column labels to be kept in the dataframe. - don't worry: essential columns like ID, coordinates - and time will always be kept. - - scale: string; default 'maxabs' - specify how the data should be scaled. - possible values are None, maxabs, minmax, std. - for more input, check out: help(pcdl.scaler) - - collapse: boole; default True - should all mcds time steps from the time series be collapsed - into one single anndata object, or a list of anndata objects - for each time step? - - keep_mcds: boole; default True - should the loaded original mcds be kept in memory - after transformation? - - output: - annmcds or self.l_annmcds: anndata object or list of anndata objects. - what is returned depends on the collapse setting. - - description: - function to transform mcds time steps into one or many - anndata objects for downstream analysis. - """ - # initialize vaiable - l_annmcds = [] - df_anncount = None - df_annobs = None - ar_annobsm = None - - # variable triage - if (values < 2): - ls_column = list(self.l_mcds[0].get_cell_df(drop=drop, keep=keep).columns) - else: - ls_column = sorted(es_coor_cell.difference({'ID'})) - ls_column.extend(sorted(self.get_cell_attribute(values=values, drop=drop, keep=keep, allvalues=False).keys())) - - # collapse warning - if collapse and self.verbose: - print('Warning @ mcdsts.get_anndata : only df_cell data, but not graph data, can be collapsed.') - - # processing - lann_mcds = [] - i_mcds = len(self.l_mcds) - for i in range(i_mcds): - # fetch mcds - if keep_mcds: - mcds = self.l_mcds[i] - else: - mcds = self.l_mcds.pop(0) - # extract physicell version - s_physicellv = mcds.get_physicell_version(), - # extract time and dataframes - r_time = round(mcds.get_time(),9) - if self.verbose: - print(f'processing: {i+1}/{i_mcds} {r_time}[min] mcds into anndata obj.') - df_cell = mcds.get_cell_df() - df_cell = df_cell.loc[:,ls_column] - - # pack collapsed - if collapse: - # extract - df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( - df_cell=df_cell, - scale = scale, - #graph_attached = {}, - #graph_neighbor = {}, - #graph_spring = {}, - #graph_method = s_physicellv, - ) - # count - df_count.reset_index(inplace=True) - df_count.index = df_count.ID + f'id_{r_time}min' - df_count.index.name = 'id_time' - df_count.drop('ID', axis=1, inplace=True) - if df_anncount is None: - df_anncount = df_count - else: - df_anncount = pd.concat([df_anncount, df_count], axis=0) - # obs - df_obs.reset_index(inplace=True) - df_obs.index = df_obs.ID + f'id_{r_time}min' - df_obs.index.name = 'id_time' - if df_annobs is None: - df_annobs = df_obs - else: - df_annobs = pd.concat([df_annobs, df_obs], axis=0) - # obsm (spatial) - if ar_annobsm is None: - ar_annobsm = d_obsm['spatial'] - else: - ar_annobsm = np.vstack([ar_annobsm, d_obsm['spatial']]) - # obsp: nop (graph) - # uns: nop (graph) - - # pack not collapsed - else: - # extract - df_count, df_obs, d_obsm, d_obsp, d_uns = _anndextract( - df_cell=df_cell, - scale = scale, - graph_attached = mcds.get_attached_graph_dict(), - graph_neighbor = mcds.get_neighbor_graph_dict(), - graph_spring = mcds.get_spring_graph_dict(), - graph_method = s_physicellv, - ) - # annmcds - ann_mcds = ad.AnnData( - X = df_count, - obs = df_obs, - obsm = d_obsm, - obsp = d_obsp, - uns = d_uns, - ) - lann_mcds.append(ann_mcds) - - # output - if collapse: - ann_mcdsts = ad.AnnData( - X = df_anncount, - obs = df_annobs, - obsm = {'spatial': ar_annobsm}, - #obsp = d_obsp, - #uns = d_uns - ) - return ann_mcdsts - else: - self.l_annmcds = lann_mcds - return self.l_annmcds - - - def get_annmcds_list(self): - """ - input: - self: TimeSeries class instance. - - output: - self.l_annmcds: list of chronologically ordered anndata mcds objects. - watch out, this is a pointer to the - self.l_annmcds list of anndata mcds objects, not a copy of self.l_annmcds! - - description: - function returns a binding to the self.l_annmcds list of anndata mcds objects. - """ - return self.l_annmcds diff --git a/pyproject.toml b/pyproject.toml index 23190fc..a057e59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dynamic = ["version"] description = "physicell data loader (pcdl) provides a platform independent, python3 based, pip installable interface to load output, generated with the PhysiCell agent based modeling framework, into python3." readme = "README.md" -requires-python = ">=3.10, <4" +requires-python = ">=3.8, <4" license = "BSD-3-Clause" #license-files = {paths = ["LICENSE"]} @@ -71,14 +71,10 @@ classifiers = [ # bue 2024-12-06: enforcing some versions dependencies = [ "anndata>=0.10.8", - "bioio>=1.2.1", # needs numpy < 2.0.0 - "bioio-ome-tiff", "matplotlib", - "neuroglancer", "numpy", "pandas>=2.2.2", "requests", - "scikit-image>=0.24.0", "scipy>=1.13.0", "vtk", ] @@ -87,30 +83,28 @@ dependencies = [ [project.scripts] # special thanks to Miguel Ponce-de-Leon who introduced me to entry point scripts! # metadata -pcdl_get_version = "pcdl.commandline:get_version" -pcdl_get_unit_dict = "pcdl.commandline:get_unit_dict" +pcdl_get_version = "pcdl.pyCLI:get_version" +pcdl_get_unit_dict = "pcdl.pyCLI:get_unit_dict" # substrate -pcdl_get_substrate_list = "pcdl.commandline:get_substrate_list" -pcdl_get_conc_attribute = "pcdl.commandline:get_conc_attribute" -pcdl_get_conc_df = "pcdl.commandline:get_conc_df" -pcdl_plot_contour = "pcdl.commandline:plot_contour" -pcdl_make_conc_vtk = "pcdl.commandline:make_conc_vtk" +pcdl_get_substrate_list = "pcdl.pyCLI:get_substrate_list" +pcdl_get_conc_attribute = "pcdl.pyCLI:get_conc_attribute" +pcdl_get_conc_df = "pcdl.pyCLI:get_conc_df" +pcdl_plot_contour = "pcdl.pyCLI:plot_contour" +pcdl_make_conc_vtk = "pcdl.pyCLI:make_conc_vtk" # cell agent -pcdl_get_celltype_list = "pcdl.commandline:get_celltype_list" -pcdl_get_cell_attribute_list = "pcdl.commandline:get_cell_attribute_list" -pcdl_get_cell_attribute = "pcdl.commandline:get_cell_attribute" -pcdl_get_cell_df = "pcdl.commandline:get_cell_df" -pcdl_get_anndata = "pcdl.commandline:get_anndata" -pcdl_make_graph_gml = "pcdl.commandline:make_graph_gml" -pcdl_plot_scatter = "pcdl.commandline:plot_scatter" -pcdl_make_cell_vtk = "pcdl.commandline:make_cell_vtk" +pcdl_get_celltype_list = "pcdl.pyCLI:get_celltype_list" +pcdl_get_cell_attribute_list = "pcdl.pyCLI:get_cell_attribute_list" +pcdl_get_cell_attribute = "pcdl.pyCLI:get_cell_attribute" +pcdl_get_cell_df = "pcdl.pyCLI:get_cell_df" +pcdl_get_anndata = "pcdl.pyCLI:get_anndata" +pcdl_make_graph_gml = "pcdl.pyCLI:make_graph_gml" +pcdl_plot_scatter = "pcdl.pyCLI:plot_scatter" +pcdl_make_cell_vtk = "pcdl.pyCLI:make_cell_vtk" # substrate and cell agent -pcdl_plot_timeseries = "pcdl.commandline:plot_timeseries" -pcdl_make_ome_tiff = "pcdl.commandline:make_ome_tiff" -pcdl_render_neuroglancer = "pcdl.commandline:render_neuroglancer" +pcdl_plot_timeseries = "pcdl.pyCLI:plot_timeseries" # making movies -pcdl_make_gif = "pcdl.commandline:make_gif" -pcdl_make_movie = "pcdl.commandline:make_movie" +pcdl_make_gif = "pcdl.pyCLI:make_gif" +pcdl_make_movie = "pcdl.pyCLI:make_movie" [project.urls] diff --git a/test/pcmodel/Makefile b/test/pcmodel/Makefile index b0d63af..7aa7d18 100644 --- a/test/pcmodel/Makefile +++ b/test/pcmodel/Makefile @@ -3,11 +3,11 @@ PROGRAM_NAME := project CC := g++ # CC := g++-mp-7 # typical macports compiler name -# CC := g++-7 # typical homebrew compiler name +# CC := g++-7 # typical homebrew compiler name -# Check for environment definitions of compiler +# Check for environment definitions of compiler # e.g., on CC = g++-7 on OSX -ifdef PHYSICELL_CPP +ifdef PHYSICELL_CPP CC := $(PHYSICELL_CPP) endif @@ -18,8 +18,8 @@ endif ARCH := native # best auto-tuning # ARCH := core2 # a reasonably safe default for most CPUs since 2007 # ARCH := corei7 -# ARCH := corei7-avx # earlier i7 -# ARCH := core-avx-i # i7 ivy bridge or newer +# ARCH := corei7-avx # earlier i7 +# ARCH := core-avx-i # i7 ivy bridge or newer # ARCH := core-avx2 # i7 with Haswell or newer # ARCH := nehalem # ARCH := westmere @@ -31,7 +31,7 @@ ARCH := native # best auto-tuning # ARCH := bonnell # ARCH := silvermont # ARCH := skylake-avx512 -# ARCH := nocona #64-bit pentium 4 or later +# ARCH := nocona #64-bit pentium 4 or later # CFLAGS := -march=$(ARCH) -Ofast -s -fomit-frame-pointer -mfpmath=both -fopenmp -m64 -std=c++11 CFLAGS := -march=$(ARCH) -O3 -fomit-frame-pointer -mfpmath=both -fopenmp -m64 -std=c++11 @@ -53,7 +53,7 @@ COMPILE_COMMAND := $(CC) $(CFLAGS) $(EXTRA_FLAGS) LINK_COMMAND := $(CC) $(CFLAGS_LINK) $(EXTRA_FLAGS) BioFVM_OBJECTS := BioFVM_vector.o BioFVM_mesh.o BioFVM_microenvironment.o BioFVM_solvers.o BioFVM_matlab.o \ -BioFVM_utilities.o BioFVM_basic_agent.o BioFVM_MultiCellDS.o BioFVM_agent_container.o +BioFVM_utilities.o BioFVM_basic_agent.o BioFVM_MultiCellDS.o BioFVM_agent_container.o PhysiCell_core_OBJECTS := PhysiCell_phenotype.o PhysiCell_cell_container.o PhysiCell_standard_models.o \ PhysiCell_cell.o PhysiCell_custom.o PhysiCell_utilities.o PhysiCell_constants.o PhysiCell_basic_signaling.o \ @@ -71,10 +71,10 @@ pugixml_OBJECTS := pugixml.o PhysiCell_OBJECTS := $(BioFVM_OBJECTS) $(pugixml_OBJECTS) $(PhysiCell_core_OBJECTS) $(PhysiCell_module_OBJECTS) ALL_OBJECTS := $(PhysiCell_OBJECTS) $(PhysiCell_custom_module_OBJECTS) -# compile the project +# compile the project all: main.cpp $(ALL_OBJECTS) - $(COMPILE_COMMAND) -o $(PROGRAM_NAME) $(ALL_OBJECTS) main.cpp + $(COMPILE_COMMAND) -o $(PROGRAM_NAME) $(ALL_OBJECTS) main.cpp make name static: main.cpp $(ALL_OBJECTS) $(MaBoSS) @@ -85,70 +85,70 @@ name: @echo "Executable name is" $(PROGRAM_NAME) @echo "" -# PhysiCell core components +# PhysiCell core components PhysiCell_phenotype.o: ./core/PhysiCell_phenotype.cpp $(COMPILE_COMMAND) -c ./core/PhysiCell_phenotype.cpp - + PhysiCell_digital_cell_line.o: ./core/PhysiCell_digital_cell_line.cpp $(COMPILE_COMMAND) -c ./core/PhysiCell_digital_cell_line.cpp PhysiCell_cell.o: ./core/PhysiCell_cell.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_cell.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_cell.cpp PhysiCell_cell_container.o: ./core/PhysiCell_cell_container.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_cell_container.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_cell_container.cpp + PhysiCell_standard_models.o: ./core/PhysiCell_standard_models.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_standard_models.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_standard_models.cpp + PhysiCell_utilities.o: ./core/PhysiCell_utilities.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_utilities.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_utilities.cpp + PhysiCell_custom.o: ./core/PhysiCell_custom.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_custom.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_custom.cpp + PhysiCell_constants.o: ./core/PhysiCell_constants.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_constants.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_constants.cpp + PhysiCell_signal_behavior.o: ./core/PhysiCell_signal_behavior.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_signal_behavior.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_signal_behavior.cpp + PhysiCell_rules.o: ./core/PhysiCell_rules.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_rules.cpp + $(COMPILE_COMMAND) -c ./core/PhysiCell_rules.cpp # BioFVM core components (needed by PhysiCell) - + BioFVM_vector.o: ./BioFVM/BioFVM_vector.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_vector.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_vector.cpp BioFVM_agent_container.o: ./BioFVM/BioFVM_agent_container.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_agent_container.cpp - + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_agent_container.cpp + BioFVM_mesh.o: ./BioFVM/BioFVM_mesh.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_mesh.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_mesh.cpp BioFVM_microenvironment.o: ./BioFVM/BioFVM_microenvironment.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_microenvironment.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_microenvironment.cpp BioFVM_solvers.o: ./BioFVM/BioFVM_solvers.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_solvers.cpp + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_solvers.cpp BioFVM_utilities.o: ./BioFVM/BioFVM_utilities.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_utilities.cpp - + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_utilities.cpp + BioFVM_basic_agent.o: ./BioFVM/BioFVM_basic_agent.cpp - $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_basic_agent.cpp - + $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_basic_agent.cpp + BioFVM_matlab.o: ./BioFVM/BioFVM_matlab.cpp $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_matlab.cpp BioFVM_MultiCellDS.o: ./BioFVM/BioFVM_MultiCellDS.cpp $(COMPILE_COMMAND) -c ./BioFVM/BioFVM_MultiCellDS.cpp - + pugixml.o: ./BioFVM/pugixml.cpp $(COMPILE_COMMAND) -c ./BioFVM/pugixml.cpp - + # standard PhysiCell modules PhysiCell_SVG.o: ./modules/PhysiCell_SVG.cpp @@ -165,158 +165,158 @@ PhysiCell_various_outputs.o: ./modules/PhysiCell_various_outputs.cpp PhysiCell_pugixml.o: ./modules/PhysiCell_pugixml.cpp $(COMPILE_COMMAND) -c ./modules/PhysiCell_pugixml.cpp - + PhysiCell_settings.o: ./modules/PhysiCell_settings.cpp $(COMPILE_COMMAND) -c ./modules/PhysiCell_settings.cpp - + PhysiCell_basic_signaling.o: ./core/PhysiCell_basic_signaling.cpp - $(COMPILE_COMMAND) -c ./core/PhysiCell_basic_signaling.cpp - + $(COMPILE_COMMAND) -c ./core/PhysiCell_basic_signaling.cpp + PhysiCell_geometry.o: ./modules/PhysiCell_geometry.cpp - $(COMPILE_COMMAND) -c ./modules/PhysiCell_geometry.cpp - + $(COMPILE_COMMAND) -c ./modules/PhysiCell_geometry.cpp + # user-defined PhysiCell modules -custom.o: ./custom_modules/custom.cpp +custom.o: ./custom_modules/custom.cpp $(COMPILE_COMMAND) -c ./custom_modules/custom.cpp # cleanup reset: - rm -f *.cpp - cp ./sample_projects/Makefile-default Makefile + rm -f *.cpp + cp ./sample_projects/Makefile-default Makefile rm -f ./custom_modules/* - touch ./custom_modules/empty.txt - touch ALL_CITATIONS.txt + touch ./custom_modules/empty.txt + touch ALL_CITATIONS.txt touch ./core/PhysiCell_cell.cpp - rm ALL_CITATIONS.txt - cp ./config/PhysiCell_settings-backup.xml ./config/PhysiCell_settings.xml + rm ALL_CITATIONS.txt + cp ./config/PhysiCell_settings-backup.xml ./config/PhysiCell_settings.xml touch ./config/empty.csv rm -f ./config/*.csv - + clean: rm -f *.o rm -f $(PROGRAM_NAME)* - + data-cleanup: rm -rf ./output mkdir ./output touch ./output/empty.txt - -# archival - -checkpoint: - zip -r $$(date +%b_%d_%Y_%H%M).zip Makefile *.cpp *.h config/*.xml custom_modules/* - + +# archival + +checkpoint: + zip -r $$(date +%b_%d_%Y_%H%M).zip Makefile *.cpp *.h config/*.xml custom_modules/* + zip: - zip -r latest.zip Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* + zip -r latest.zip Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* cp latest.zip $$(date +%b_%d_%Y_%H%M).zip - cp latest.zip VERSION_$(VERSION).zip + cp latest.zip VERSION_$(VERSION).zip mv *.zip archives/ - + tar: - tar --ignore-failed-read -czf latest.tar Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* + tar --ignore-failed-read -czf latest.tar Makefile* *.cpp *.h BioFVM/* config/* core/* custom_modules/* matlab/* modules/* sample_projects/* cp latest.tar $$(date +%b_%d_%Y_%H%M).tar cp latest.tar VERSION_$(VERSION).tar mv *.tar archives/ -unzip: - cp ./archives/latest.zip . - unzip latest.zip - -untar: +unzip: + cp ./archives/latest.zip . + unzip latest.zip + +untar: cp ./archives/latest.tar . tar -xzf latest.tar -# easier animation +# easier animation FRAMERATE := 24 OUTPUT := output -jpeg: - @magick identify -format "%h" $(OUTPUT)/initial.svg > __H.txt - @magick identify -format "%w" $(OUTPUT)/initial.svg > __W.txt - @expr 2 \* \( $$(grep . __H.txt) / 2 \) > __H1.txt - @expr 2 \* \( $$(grep . __W.txt) / 2 \) > __W1.txt - @echo "$$(grep . __W1.txt)!x$$(grep . __H1.txt)!" > __resize.txt +jpeg: + @magick identify -format "%h" $(OUTPUT)/initial.svg > __H.txt + @magick identify -format "%w" $(OUTPUT)/initial.svg > __W.txt + @expr 2 \* \( $$(grep . __H.txt) / 2 \) > __H1.txt + @expr 2 \* \( $$(grep . __W.txt) / 2 \) > __W1.txt + @echo "$$(grep . __W1.txt)!x$$(grep . __H1.txt)!" > __resize.txt @magick mogrify -format jpg -resize $$(grep . __resize.txt) $(OUTPUT)/s*.svg - rm -f __H*.txt __W*.txt __resize.txt - -gif: - magick convert $(OUTPUT)/s*.svg $(OUTPUT)/out.gif - + rm -f __H*.txt __W*.txt __resize.txt + +gif: + magick convert $(OUTPUT)/s*.svg $(OUTPUT)/out.gif + movie: ffmpeg -r $(FRAMERATE) -f image2 -i $(OUTPUT)/snapshot%08d.jpg -vcodec libx264 -pix_fmt yuv420p -strict -2 -tune animation -crf 15 -acodec none $(OUTPUT)/out.mp4 + +# upgrade rules -# upgrade rules - -SOURCE := PhysiCell_upgrade.zip -get-upgrade: - @echo $$(curl https://raw.githubusercontent.com/MathCancer/PhysiCell/master/VERSION.txt) > VER.txt - @echo https://github.com/MathCancer/PhysiCell/releases/download/$$(grep . VER.txt)/PhysiCell_V.$$(grep . VER.txt).zip > DL_FILE.txt +SOURCE := PhysiCell_upgrade.zip +get-upgrade: + @echo $$(curl https://raw.githubusercontent.com/MathCancer/PhysiCell/master/VERSION.txt) > VER.txt + @echo https://github.com/MathCancer/PhysiCell/releases/download/$$(grep . VER.txt)/PhysiCell_V.$$(grep . VER.txt).zip > DL_FILE.txt rm -f VER.txt $$(curl -L $$(grep . DL_FILE.txt) --output PhysiCell_upgrade.zip) - rm -f DL_FILE.txt + rm -f DL_FILE.txt -PhysiCell_upgrade.zip: - make get-upgrade +PhysiCell_upgrade.zip: + make get-upgrade upgrade: $(SOURCE) unzip $(SOURCE) PhysiCell/VERSION.txt - mv -f PhysiCell/VERSION.txt . - unzip $(SOURCE) PhysiCell/core/* - cp -r PhysiCell/core/* core - unzip $(SOURCE) PhysiCell/modules/* - cp -r PhysiCell/modules/* modules - unzip $(SOURCE) PhysiCell/sample_projects/* - cp -r PhysiCell/sample_projects/* sample_projects - unzip $(SOURCE) PhysiCell/BioFVM/* + mv -f PhysiCell/VERSION.txt . + unzip $(SOURCE) PhysiCell/core/* + cp -r PhysiCell/core/* core + unzip $(SOURCE) PhysiCell/modules/* + cp -r PhysiCell/modules/* modules + unzip $(SOURCE) PhysiCell/sample_projects/* + cp -r PhysiCell/sample_projects/* sample_projects + unzip $(SOURCE) PhysiCell/BioFVM/* cp -r PhysiCell/BioFVM/* BioFVM unzip $(SOURCE) PhysiCell/documentation/User_Guide.pdf mv -f PhysiCell/documentation/User_Guide.pdf documentation rm -f -r PhysiCell - rm -f $(SOURCE) + rm -f $(SOURCE) # use: make save PROJ=your_project_name PROJ := my_project -save: +save: echo "Saving project as $(PROJ) ... " mkdir -p ./user_projects mkdir -p ./user_projects/$(PROJ) mkdir -p ./user_projects/$(PROJ)/custom_modules - mkdir -p ./user_projects/$(PROJ)/config + mkdir -p ./user_projects/$(PROJ)/config cp main.cpp ./user_projects/$(PROJ) cp Makefile ./user_projects/$(PROJ) cp VERSION.txt ./user_projects/$(PROJ) cp ./config/* ./user_projects/$(PROJ)/config cp ./custom_modules/* ./user_projects/$(PROJ)/custom_modules -load: +load: echo "Loading project from $(PROJ) ... " cp ./user_projects/$(PROJ)/main.cpp . cp ./user_projects/$(PROJ)/Makefile . - cp ./user_projects/$(PROJ)/config/* ./config/ - cp ./user_projects/$(PROJ)/custom_modules/* ./custom_modules/ + cp ./user_projects/$(PROJ)/config/* ./config/ + cp ./user_projects/$(PROJ)/custom_modules/* ./custom_modules/ pack: @echo " " @echo "Preparing project $(PROJ) for sharing ... " - @echo " " + @echo " " cd ./user_projects && zip -r $(PROJ).zip $(PROJ) @echo " " @echo "Share ./user_projects/$(PROJ).zip ... " @echo "Other users can unzip $(PROJ).zip in their ./user_projects, compile, and run." - @echo " " + @echo " " unpack: @echo " " @echo "Preparing shared project $(PROJ).zip for use ... " - @echo " " - cd ./user_projects && unzip $(PROJ).zip + @echo " " + cd ./user_projects && unzip $(PROJ).zip @echo " " @echo "Load this project via make load PROJ=$(PROJ) ... " - @echo " " + @echo " " list-user-projects: @echo "user projects::" diff --git a/test/pcmodel/config/PhysiCell_settings-backup.xml b/test/pcmodel/config/PhysiCell_settings-backup.xml new file mode 100644 index 0000000..e69de29 diff --git a/test/pcmodel/custom_modules/custom.cpp b/test/pcmodel/custom_modules/custom.cpp index df17f9c..59b23bd 100644 --- a/test/pcmodel/custom_modules/custom.cpp +++ b/test/pcmodel/custom_modules/custom.cpp @@ -69,136 +69,136 @@ void create_cell_types( void ) { - // set the random seed - if (parameters.ints.find_index("random_seed") != -1) - { - SeedRandom(parameters.ints("random_seed")); - } - - /* - Put any modifications to default cell definition here if you - want to have "inherited" by other cell types. - - This is a good place to set default functions. - */ - - initialize_default_cell_definition(); - cell_defaults.phenotype.secretion.sync_to_microenvironment( µenvironment ); - - cell_defaults.functions.volume_update_function = standard_volume_update_function; - cell_defaults.functions.update_velocity = standard_update_cell_velocity; - - cell_defaults.functions.update_migration_bias = NULL; - cell_defaults.functions.update_phenotype = NULL; // update_cell_and_death_parameters_O2_based; - cell_defaults.functions.custom_cell_rule = NULL; - cell_defaults.functions.contact_function = NULL; - - cell_defaults.functions.add_cell_basement_membrane_interactions = NULL; - cell_defaults.functions.calculate_distance_to_membrane = NULL; - - /* - This parses the cell definitions in the XML config file. - */ - - initialize_cell_definitions_from_pugixml(); - - /* - This builds the map of cell definitions and summarizes the setup. - */ - - build_cell_definitions_maps(); - - /* - This intializes cell signal and response dictionaries - */ - - setup_signal_behavior_dictionaries(); - - /* - Cell rule definitions - */ - - setup_cell_rules(); - - /* - Put any modifications to individual cell definitions here. - - This is a good place to set custom functions. - */ - - cell_defaults.functions.update_phenotype = phenotype_function; - cell_defaults.functions.custom_cell_rule = custom_function; - cell_defaults.functions.contact_function = contact_function; - - /* - This builds the map of cell definitions and summarizes the setup. - */ - - display_cell_definitions( std::cout ); - - return; + // set the random seed + if (parameters.ints.find_index("random_seed") != -1) + { + SeedRandom(parameters.ints("random_seed")); + } + + /* + Put any modifications to default cell definition here if you + want to have "inherited" by other cell types. + + This is a good place to set default functions. + */ + + initialize_default_cell_definition(); + cell_defaults.phenotype.secretion.sync_to_microenvironment( µenvironment ); + + cell_defaults.functions.volume_update_function = standard_volume_update_function; + cell_defaults.functions.update_velocity = standard_update_cell_velocity; + + cell_defaults.functions.update_migration_bias = NULL; + cell_defaults.functions.update_phenotype = NULL; // update_cell_and_death_parameters_O2_based; + cell_defaults.functions.custom_cell_rule = NULL; + cell_defaults.functions.contact_function = NULL; + + cell_defaults.functions.add_cell_basement_membrane_interactions = NULL; + cell_defaults.functions.calculate_distance_to_membrane = NULL; + + /* + This parses the cell definitions in the XML config file. + */ + + initialize_cell_definitions_from_pugixml(); + + /* + This builds the map of cell definitions and summarizes the setup. + */ + + build_cell_definitions_maps(); + + /* + This intializes cell signal and response dictionaries + */ + + setup_signal_behavior_dictionaries(); + + /* + Cell rule definitions + */ + + setup_cell_rules(); + + /* + Put any modifications to individual cell definitions here. + + This is a good place to set custom functions. + */ + + cell_defaults.functions.update_phenotype = phenotype_function; + cell_defaults.functions.custom_cell_rule = custom_function; + cell_defaults.functions.contact_function = contact_function; + + /* + This builds the map of cell definitions and summarizes the setup. + */ + + display_cell_definitions( std::cout ); + + return; } void setup_microenvironment( void ) { - // set domain parameters - - // put any custom code to set non-homogeneous initial conditions or - // extra Dirichlet nodes here. - - // initialize BioFVM - - initialize_microenvironment(); - - return; + // set domain parameters + + // put any custom code to set non-homogeneous initial conditions or + // extra Dirichlet nodes here. + + // initialize BioFVM + + initialize_microenvironment(); + + return; } void setup_tissue( void ) { - double Xmin = microenvironment.mesh.bounding_box[0]; - double Ymin = microenvironment.mesh.bounding_box[1]; - double Zmin = microenvironment.mesh.bounding_box[2]; - - double Xmax = microenvironment.mesh.bounding_box[3]; - double Ymax = microenvironment.mesh.bounding_box[4]; - double Zmax = microenvironment.mesh.bounding_box[5]; - - if( default_microenvironment_options.simulate_2D == true ) - { - Zmin = 0.0; - Zmax = 0.0; - } - - double Xrange = Xmax - Xmin; - double Yrange = Ymax - Ymin; - double Zrange = Zmax - Zmin; - - // create some of each type of cell - - Cell* pC; - - for( int k=0; k < cell_definitions_by_index.size() ; k++ ) - { - Cell_Definition* pCD = cell_definitions_by_index[k]; - std::cout << "Placing cells of type " << pCD->name << " ... " << std::endl; - for( int n = 0 ; n < parameters.ints("number_of_cells") ; n++ ) - { - std::vector position = {0,0,0}; - position[0] = Xmin + UniformRandom()*Xrange; - position[1] = Ymin + UniformRandom()*Yrange; - position[2] = Zmin + UniformRandom()*Zrange; - - pC = create_cell( *pCD ); - pC->assign_position( position ); - } - } - std::cout << std::endl; - - // load cells from your CSV file (if enabled) - load_cells_from_pugixml(); - set_parameters_from_distributions(); - - return; + double Xmin = microenvironment.mesh.bounding_box[0]; + double Ymin = microenvironment.mesh.bounding_box[1]; + double Zmin = microenvironment.mesh.bounding_box[2]; + + double Xmax = microenvironment.mesh.bounding_box[3]; + double Ymax = microenvironment.mesh.bounding_box[4]; + double Zmax = microenvironment.mesh.bounding_box[5]; + + if( default_microenvironment_options.simulate_2D == true ) + { + Zmin = 0.0; + Zmax = 0.0; + } + + double Xrange = Xmax - Xmin; + double Yrange = Ymax - Ymin; + double Zrange = Zmax - Zmin; + + // create some of each type of cell + + Cell* pC; + + for( int k=0; k < cell_definitions_by_index.size() ; k++ ) + { + Cell_Definition* pCD = cell_definitions_by_index[k]; + std::cout << "Placing cells of type " << pCD->name << " ... " << std::endl; + for( int n = 0 ; n < parameters.ints("number_of_cells") ; n++ ) + { + std::vector position = {0,0,0}; + position[0] = Xmin + UniformRandom()*Xrange; + position[1] = Ymin + UniformRandom()*Yrange; + position[2] = Zmin + UniformRandom()*Zrange; + + pC = create_cell( *pCD ); + pC->assign_position( position ); + } + } + std::cout << std::endl; + + // load cells from your CSV file (if enabled) + load_cells_from_pugixml(); + set_parameters_from_distributions(); + + return; } std::vector my_coloring_function( Cell* pCell ) @@ -208,7 +208,7 @@ void phenotype_function( Cell* pCell, Phenotype& phenotype, double dt ) { return; } void custom_function( Cell* pCell, Phenotype& phenotype , double dt ) -{ return; } +{ return; } void contact_function( Cell* pMe, Phenotype& phenoMe , Cell* pOther, Phenotype& phenoOther , double dt ) -{ return; } +{ return; } \ No newline at end of file diff --git a/test/pcmodel/custom_modules/custom.h b/test/pcmodel/custom_modules/custom.h index a7405d1..0e6df8d 100644 --- a/test/pcmodel/custom_modules/custom.h +++ b/test/pcmodel/custom_modules/custom.h @@ -66,27 +66,27 @@ */ #include "../core/PhysiCell.h" -#include "../modules/PhysiCell_standard_modules.h" +#include "../modules/PhysiCell_standard_modules.h" -using namespace BioFVM; +using namespace BioFVM; using namespace PhysiCell; -// setup functions to help us along +// setup functions to help us along void create_cell_types( void ); -void setup_tissue( void ); +void setup_tissue( void ); -// set up the BioFVM microenvironment -void setup_microenvironment( void ); +// set up the BioFVM microenvironment +void setup_microenvironment( void ); -// custom pathology coloring function +// custom pathology coloring function std::vector my_coloring_function( Cell* ); -// custom functions can go here +// custom functions can go here void phenotype_function( Cell* pCell, Phenotype& phenotype, double dt ); void custom_function( Cell* pCell, Phenotype& phenotype , double dt ); -void contact_function( Cell* pMe, Phenotype& phenoMe , Cell* pOther, Phenotype& phenoOther , double dt ); +void contact_function( Cell* pMe, Phenotype& phenoMe , Cell* pOther, Phenotype& phenoOther , double dt ); diff --git a/test/pcmodel/custom_modules/empty.txt b/test/pcmodel/custom_modules/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/pcmodel/main.cpp b/test/pcmodel/main.cpp index f8285d6..2f7e98c 100644 --- a/test/pcmodel/main.cpp +++ b/test/pcmodel/main.cpp @@ -74,181 +74,181 @@ #include #include "./core/PhysiCell.h" -#include "./modules/PhysiCell_standard_modules.h" +#include "./modules/PhysiCell_standard_modules.h" -// put custom code modules here! - -#include "./custom_modules/custom.h" +// put custom code modules here! +#include "./custom_modules/custom.h" + using namespace BioFVM; using namespace PhysiCell; int main( int argc, char* argv[] ) { - // load and parse settings file(s) - - bool XML_status = false; - char copy_command [1024]; - if( argc > 1 ) - { - XML_status = load_PhysiCell_config_file( argv[1] ); - sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); - } - else - { - XML_status = load_PhysiCell_config_file( "./config/PhysiCell_settings.xml" ); - sprintf( copy_command , "cp ./config/PhysiCell_settings.xml %s" , PhysiCell_settings.folder.c_str() ); - } - if( !XML_status ) - { exit(-1); } - - // copy config file to output directry - system( copy_command ); - - // OpenMP setup - omp_set_num_threads(PhysiCell_settings.omp_num_threads); - - // time setup - std::string time_units = "min"; - - /* Microenvironment setup */ - - setup_microenvironment(); // modify this in the custom code - - /* PhysiCell setup */ - - // set mechanics voxel size, and match the data structure to BioFVM - double mechanics_voxel_size = 30; - Cell_Container* cell_container = create_cell_container_for_microenvironment( microenvironment, mechanics_voxel_size ); - - /* Users typically start modifying here. START USERMODS */ - - create_cell_types(); - - setup_tissue(); - - /* Users typically stop modifying here. END USERMODS */ - - // set MultiCellDS save options - - set_save_biofvm_mesh_as_matlab( true ); - set_save_biofvm_data_as_matlab( true ); - set_save_biofvm_cell_data( true ); - set_save_biofvm_cell_data_as_custom_matlab( true ); - - // save a simulation snapshot - - char filename[1024]; - sprintf( filename , "%s/initial" , PhysiCell_settings.folder.c_str() ); - save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); - - // save a quick SVG cross section through z = 0, after setting its - // length bar to 200 microns - - PhysiCell_SVG_options.length_bar = 200; - - // for simplicity, set a pathology coloring function - - std::vector (*cell_coloring_function)(Cell*) = my_coloring_function; - std::string (*substrate_coloring_function)(double, double, double) = paint_by_density_percentage; - - sprintf( filename , "%s/initial.svg" , PhysiCell_settings.folder.c_str() ); - SVG_plot( filename , microenvironment, 0.0 , PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function ); - - sprintf( filename , "%s/legend.svg" , PhysiCell_settings.folder.c_str() ); - create_plot_legend( filename , cell_coloring_function ); - - display_citations(); - - // set the performance timers - - BioFVM::RUNTIME_TIC(); - BioFVM::TIC(); - - std::ofstream report_file; - if( PhysiCell_settings.enable_legacy_saves == true ) - { - sprintf( filename , "%s/simulation_report.txt" , PhysiCell_settings.folder.c_str() ); - - report_file.open(filename); // create the data log file - report_file<<"simulated time\tnum cells\tnum division\tnum death\twall time"<update_all_cells( PhysiCell_globals.current_time ); - - /* - Custom add-ons could potentially go here. - */ - - PhysiCell_globals.current_time += diffusion_dt; - } - - if( PhysiCell_settings.enable_legacy_saves == true ) - { - log_output(PhysiCell_globals.current_time, PhysiCell_globals.full_output_index, microenvironment, report_file); - report_file.close(); - } - } - catch( const std::exception& e ) - { // reference to the base of a polymorphic object - std::cout << e.what(); // information from length_error printed - } - - // save a final simulation snapshot - - sprintf( filename , "%s/final" , PhysiCell_settings.folder.c_str() ); - save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); - - sprintf( filename , "%s/final.svg" , PhysiCell_settings.folder.c_str() ); - SVG_plot(filename, microenvironment, 0.0, PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function); - - // timer - - std::cout << std::endl << "Total simulation runtime: " << std::endl; - BioFVM::display_stopwatch_value( std::cout , BioFVM::runtime_stopwatch_value() ); - - return 0; + // load and parse settings file(s) + + bool XML_status = false; + char copy_command [1024]; + if( argc > 1 ) + { + XML_status = load_PhysiCell_config_file( argv[1] ); + sprintf( copy_command , "cp %s %s" , argv[1] , PhysiCell_settings.folder.c_str() ); + } + else + { + XML_status = load_PhysiCell_config_file( "./config/PhysiCell_settings.xml" ); + sprintf( copy_command , "cp ./config/PhysiCell_settings.xml %s" , PhysiCell_settings.folder.c_str() ); + } + if( !XML_status ) + { exit(-1); } + + // copy config file to output directry + system( copy_command ); + + // OpenMP setup + omp_set_num_threads(PhysiCell_settings.omp_num_threads); + + // time setup + std::string time_units = "min"; + + /* Microenvironment setup */ + + setup_microenvironment(); // modify this in the custom code + + /* PhysiCell setup */ + + // set mechanics voxel size, and match the data structure to BioFVM + double mechanics_voxel_size = 30; + Cell_Container* cell_container = create_cell_container_for_microenvironment( microenvironment, mechanics_voxel_size ); + + /* Users typically start modifying here. START USERMODS */ + + create_cell_types(); + + setup_tissue(); + + /* Users typically stop modifying here. END USERMODS */ + + // set MultiCellDS save options + + set_save_biofvm_mesh_as_matlab( true ); + set_save_biofvm_data_as_matlab( true ); + set_save_biofvm_cell_data( true ); + set_save_biofvm_cell_data_as_custom_matlab( true ); + + // save a simulation snapshot + + char filename[1024]; + sprintf( filename , "%s/initial" , PhysiCell_settings.folder.c_str() ); + save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); + + // save a quick SVG cross section through z = 0, after setting its + // length bar to 200 microns + + PhysiCell_SVG_options.length_bar = 200; + + // for simplicity, set a pathology coloring function + + std::vector (*cell_coloring_function)(Cell*) = my_coloring_function; + std::string (*substrate_coloring_function)(double, double, double) = paint_by_density_percentage; + + sprintf( filename , "%s/initial.svg" , PhysiCell_settings.folder.c_str() ); + SVG_plot( filename , microenvironment, 0.0 , PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function ); + + sprintf( filename , "%s/legend.svg" , PhysiCell_settings.folder.c_str() ); + create_plot_legend( filename , cell_coloring_function ); + + display_citations(); + + // set the performance timers + + BioFVM::RUNTIME_TIC(); + BioFVM::TIC(); + + std::ofstream report_file; + if( PhysiCell_settings.enable_legacy_saves == true ) + { + sprintf( filename , "%s/simulation_report.txt" , PhysiCell_settings.folder.c_str() ); + + report_file.open(filename); // create the data log file + report_file<<"simulated time\tnum cells\tnum division\tnum death\twall time"<update_all_cells( PhysiCell_globals.current_time ); + + /* + Custom add-ons could potentially go here. + */ + + PhysiCell_globals.current_time += diffusion_dt; + } + + if( PhysiCell_settings.enable_legacy_saves == true ) + { + log_output(PhysiCell_globals.current_time, PhysiCell_globals.full_output_index, microenvironment, report_file); + report_file.close(); + } + } + catch( const std::exception& e ) + { // reference to the base of a polymorphic object + std::cout << e.what(); // information from length_error printed + } + + // save a final simulation snapshot + + sprintf( filename , "%s/final" , PhysiCell_settings.folder.c_str() ); + save_PhysiCell_to_MultiCellDS_v2( filename , microenvironment , PhysiCell_globals.current_time ); + + sprintf( filename , "%s/final.svg" , PhysiCell_settings.folder.c_str() ); + SVG_plot(filename, microenvironment, 0.0, PhysiCell_globals.current_time, cell_coloring_function, substrate_coloring_function); + + // timer + + std::cout << std::endl << "Total simulation runtime: " << std::endl; + BioFVM::display_stopwatch_value( std::cout , BioFVM::runtime_stopwatch_value() ); + + return 0; } diff --git a/test/test_anndata_2d.py b/test/test_anndata_2d.py new file mode 100644 index 0000000..bbbcb44 --- /dev/null +++ b/test/test_anndata_2d.py @@ -0,0 +1,181 @@ +#### +# title: test_anndata_2d.py +# +# language: python3 +# author: Elmar Bucher +# date: 2023-06-24 +# license: BSD 3-Clause +# +# description: +# pytest unit test library for the pcdl library TimeStep and TimeSeries class. +# + https://docs.pytest.org/ +# +# note: +# assert actual == expected, message +# == value equality +# is reference equality +# pytest.approx for real values +##### + + +# load library +import numpy as np +import os +import pandas as pd +import pathlib +import pcdl + + +# const +s_path_2d = str(pathlib.Path(pcdl.__file__).parent.resolve()/'output_2d') +s_file_2d = 'output00000024.xml' +s_pathfile_2d = f'{s_path_2d}/{s_file_2d}' + + +# test data +if not os.path.exists(s_path_2d): + pcdl.install_data() + + +## helper function ## +class TestPyAnndataScaler(object): + ''' test for pcdl.scaler function ''' + a_x = np.array([[ 1.,-1., 2., 0.],[ 2., 0., 0.,0.],[ 0., 1.,-1.,0.]]) + df_x = pd.DataFrame(a_x, columns=['a','b','c','d']) + + def test_scaler_none(self, df_x=df_x): + df_scaled = pcdl.pyAnnData.scaler(df_x=df_x, scale=None) + assert(str(type(df_scaled)) == "") and \ + (all(df_scaled == df_x)) + + def test_scaler_minabs(self, df_x=df_x): + df_scaled = pcdl.pyAnnData.scaler(df_x=df_x, scale='maxabs') + assert(str(type(df_scaled)) == "") and \ + (df_scaled.values.sum().round(3) == 2.0) and \ + (df_scaled.values.min().round(3) == -1.0) and \ + (df_scaled.values.max().round(3) == 1.0) + + def test_scaler_minmax(self, df_x=df_x): + df_scaled = pcdl.pyAnnData.scaler(df_x=df_x, scale='minmax') + assert(str(type(df_scaled)) == "") and \ + (df_scaled.values.sum().round(3) == 4.333) and \ + (df_scaled.values.min().round(3) == 0.0) and \ + (df_scaled.values.max().round(3) == 1.0) + + def test_scaler_std(self, df_x=df_x): + df_scaled = pcdl.pyAnnData.scaler(df_x=df_x, scale='std') + assert(str(type(df_scaled)) == "") and \ + (df_scaled.values.sum().round(3) == 0.0) and \ + (df_scaled.values.min().round(3) == -1.0) and \ + (df_scaled.values.max().round(3) == 1.091) + + +## load physicell data time step ## +class TestPyAnndataTimeStep(object): + ''' test for pcdl.TimeStep class. ''' + + ## get_anndata command ## + def test_mcds_get_anndata(self): + mcds = pcdl.TimeStep(s_pathfile_2d, verbose=False) + ann = mcds.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs') + assert(str(type(mcds)) == "") and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 7) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 2) and \ + (len(ann.obsp) == 2) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 1) + + +## load physicell data time series ## +class TestPyAnndataTimeSeries(object): + ''' test for pcdl.TestSeries class. ''' + + # get_anndata + # get_annmcds_list {integrated} + # value {1, _2_} + # collaps {True, _False_} + # keep_mcds {True, _False_} + + ## get_anndata command ## + def test_mcdsts_get_anndata(self): + mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 8) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 2) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 0) + + def test_mcdsts_get_anndata_value(self): + mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) + ann = mcdsts.get_anndata(values=2, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 50) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 7) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 2) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (50, 0)) and \ + (len(ann.uns) == 0) + + def test_mcdsts_get_anndata_collapsefalse(self): + mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=False, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (str(type(mcdsts.l_annmcds)) == "") and \ + (len(mcdsts.l_annmcds) == 25) and \ + (all([str(type(ann)) == "" for ann in mcdsts.l_annmcds])) and \ + (mcdsts.l_annmcds[24].X.shape[0] > 9) and \ + (mcdsts.l_annmcds[24].X.shape[1] == 105) and \ + (mcdsts.l_annmcds[24].obs.shape[0] > 9) and \ + (mcdsts.l_annmcds[24].obs.shape[1] == 7) and \ + (mcdsts.l_annmcds[24].obsm['spatial'].shape[0] > 9) and \ + (mcdsts.l_annmcds[24].obsm['spatial'].shape[1] == 2) and \ + (len(mcdsts.l_annmcds[24].obsp) == 4) and \ + (mcdsts.l_annmcds[24].var.shape == (105, 0)) and \ + (len(mcdsts.l_annmcds[24].uns) == 2) + + def test_mcdsts_get_anndata_keepmcdsfalse(self): + mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=False) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 0) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 8) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 2) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 0) + diff --git a/test/test_anndata_3d.py b/test/test_anndata_3d.py new file mode 100644 index 0000000..27743e2 --- /dev/null +++ b/test/test_anndata_3d.py @@ -0,0 +1,157 @@ +#### +# title: test_anndata_3d.py +# +# language: python3 +# author: Elmar Bucher +# date: 2023-06-24 +# license: BSD 3-Clause +# +# description: +# pytest unit test library for the pcdl library TimeStep and TimeSeries class. +# + https://docs.pytest.org/ +# +# note: +# assert actual == expected, message +# == value equality +# is reference equality +# pytest.approx for real values +##### + + +# load library +import numpy as np +import os +import pandas as pd +import pathlib +import pcdl + + +# const +s_path_3d = str(pathlib.Path(pcdl.__file__).parent.resolve()/'output_3d') +s_file_3d = 'output00000024.xml' +s_pathfile_3d = f'{s_path_3d}/{s_file_3d}' + + +# test data +if not os.path.exists(s_path_3d): + pcdl.install_data() + + +########### +# 3D only # +########### + +## load physicell data time step ## +class TestPyAnndata3DTimeStep(object): + ''' test for pcdl.TimeStep class. ''' + + ## get_anndata command ## + def test_mcds_get_anndata(self): + mcds = pcdl.TimeStep(s_pathfile_3d, verbose=False) + ann = mcds.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs') + assert(str(type(mcds)) == "") and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 7) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 3) and \ + (len(ann.obsp) == 2) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 1) + + +################## +# test for speed # +################## +# BUE: test functions are mirrored test_anndata_2d.py + +## load physicell data time series ## +class TestPyAnndata3DTimeSeries(object): + ''' test for pcdl.TestSeries class. ''' + + # get_anndata + # get_annmcds_list {integrated} + # value {1, _2_} + # collaps {True, _False_} + # keep_mcds {True, _False_} + + ## get_anndata command ## + def test_mcdsts_get_anndata(self): + mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 8) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 3) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 0) + + def test_mcdsts_get_anndata_value(self): + mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) + ann = mcdsts.get_anndata(values=2, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 56) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 7) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 3) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (56, 0)) and \ + (len(ann.uns) == 0) + + def test_mcdsts_get_anndata_collapsefalse(self): + mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=False, keep_mcds=True) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 25) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (str(type(mcdsts.l_annmcds)) == "") and \ + (len(mcdsts.l_annmcds) == 25) and \ + (all([str(type(ann)) == "" for ann in mcdsts.l_annmcds])) and \ + (mcdsts.l_annmcds[24].X.shape[0] > 9) and \ + (mcdsts.l_annmcds[24].X.shape[1] == 105) and \ + (mcdsts.l_annmcds[24].obs.shape[0] > 9) and \ + (mcdsts.l_annmcds[24].obs.shape[1] == 7) and \ + (mcdsts.l_annmcds[24].obsm['spatial'].shape[0] > 9) and \ + (mcdsts.l_annmcds[24].obsm['spatial'].shape[1] == 3) and \ + (len(mcdsts.l_annmcds[24].obsp) == 4) and \ + (mcdsts.l_annmcds[24].var.shape == (105, 0)) and \ + (len(mcdsts.l_annmcds[24].uns) == 2) + + def test_mcdsts_get_anndata_keepmcdsfalse(self): + mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) + ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=False) + l_annmcds = mcdsts.get_annmcds_list() + assert(str(type(mcdsts)) == "") and \ + (len(mcdsts.l_mcds) == 0) and \ + (l_annmcds == mcdsts.l_annmcds) and \ + (mcdsts.l_annmcds is None) and \ + (str(type(ann)) == "") and \ + (ann.X.shape[0] > 9) and \ + (ann.X.shape[1] == 105) and \ + (ann.obs.shape[0] > 9) and \ + (ann.obs.shape[1] == 8) and \ + (ann.obsm['spatial'].shape[0] > 9) and \ + (ann.obsm['spatial'].shape[1] == 3) and \ + (len(ann.obsp) == 0) and \ + (ann.var.shape == (105, 0)) and \ + (len(ann.uns) == 0) + diff --git a/test/test_commandline_2d.py b/test/test_cli_2d.py similarity index 85% rename from test/test_commandline_2d.py rename to test/test_cli_2d.py index c0a26b1..e8df23a 100644 --- a/test/test_commandline_2d.py +++ b/test/test_cli_2d.py @@ -1,4 +1,4 @@ -##### +### # title: test_cli_2d.py # # language: python3 @@ -39,15 +39,15 @@ if not os.path.exists(s_path_2d): pcdl.install_data() -print(f"process: pcdl command line interface functions from the command line...") +print(f"process: pcdl pyCLI functions from the command line...") ############################## # metadata realted test code # ############################## -class TestCommandLineInterfaceVersion(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliVersion(object): + ''' tests for one pcdl.pyCli function. ''' # timestep: # + path (pathfile, path) ok @@ -70,8 +70,8 @@ def test_pcdl_get_version_timestep(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceUnitDict(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliUnitDict(object): + ''' tests for one pcdl.pyCli function. ''' # timestep: # + path (pathfile, path) ok @@ -129,8 +129,8 @@ def test_pcdl_get_unit_dict_timestep_settingxmlnone(self): # substarte relatd test code # ############################## -class TestCommandLineInterfaceSubstrateList(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliSubstrateList(object): + ''' tests for one pcdl.pyCli function. ''' # timestep: # + path (pathfile, path) ok @@ -153,8 +153,8 @@ def test_pcdl_get_substrate_list_timestep(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceConcDfAttribute(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliConcDfAttribute(object): + ''' tests for one pcdl.pyCli function. ''' # timeseries collapsed: # + path (str) nop @@ -210,8 +210,8 @@ def test_pcdl_get_conc_attribute_timeseries_allvalues(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceConcDf(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliConcDf(object): + ''' tests for one pcdl.pyCli function. ''' # timeseries # + collapse (true false) ok @@ -305,8 +305,8 @@ def test_pcdl_get_conc_df_timestep_keep(self): assert o_result.returncode == 0 -class TestCommandLineInterfacePlotContour(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliPlotContour(object): + ''' tests for one pcdl.pyCli function. ''' # time series and time steps. # + path nop @@ -362,8 +362,8 @@ def test_pcdl_plot_contour_set(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceConcVtk(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliConcVtk(object): + ''' tests for one pcdl.pyCli function. ''' # timestep and timeseries: # + path nop @@ -393,8 +393,8 @@ def test_pcdl_make_conc_vtk_timestep_default(self): # cell agent realted test code # ################################ -class TestCommandLineInterfaceCelltypeList(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliCelltypeList(object): + ''' tests for one pcdl.pyCli function. ''' # timestep: # + path (pathfile, path) ok @@ -417,59 +417,8 @@ def test_pcdl_get_celltype_list_timestep(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceCellAttributeList(object): - ''' tests for one pcdl command line interface function. ''' - - # timeseries collapsed: - # + path (str) nop - # + microenv (true, _false_) ok - # + physiboss (true, _false_) - # + settingxml (string, _none_, _false_) ok - # + verbose (true, _false_) nop - - def test_pcdl_get_cell_attribute_list_timeseries(self): - o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 - - def test_pcdl_get_cell_attribute_list_timeseries_microenv(self): - o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 - - def test_pcdl_get_cell_attribute_list_timeseries_physiboss(self): - o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 - - def test_pcdl_get_cell_attribute_list_timeseries_settingxmlfalse(self): - o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 - - def test_pcdl_get_cell_attribute_list_timeseries_settingxmlnone(self): - o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - assert o_result.returncode == 0 - - -class TestCommandLineInterfaceCellDfAttribute(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliCellDfAttribute(object): + ''' tests for one pcdl.pyCli function. ''' # timeseries collapsed: # + path (str) nop @@ -574,9 +523,60 @@ def test_pcdl_get_cell_attribute_timeseries_allvalues(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceCellDf(object): +class TestPyCliCellAttributeList(object): ''' tests for one pcdl command line interface function. ''' + # timeseries collapsed: + # + path (str) nop + # + microenv (true, _false_) ok + # + physiboss (true, _false_) + # + settingxml (string, _none_, _false_) ok + # + verbose (true, _false_) nop + + def test_pcdl_get_cell_attribute_list_timeseries(self): + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + + def test_pcdl_get_cell_attribute_list_timeseries_microenv(self): + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + + def test_pcdl_get_cell_attribute_list_timeseries_physiboss(self): + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + + def test_pcdl_get_cell_attribute_list_timeseries_settingxmlfalse(self): + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + + def test_pcdl_get_cell_attribute_list_timeseries_settingxmlnone(self): + o_result = subprocess.run(['pcdl_get_cell_attribute_list', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) + print(f'o_result: {o_result}\n') + print(f'o_result.returncode: {o_result.returncode}\n') + print(f'o_result.stdout: {o_result.stdout}\n') + print(f'o_result.stderr: {o_result.stderr}\n') + assert o_result.returncode == 0 + + +class TestPyCliCellDf(object): + ''' tests for one pcdl.pyCli function. ''' + # timeseries # + collapse (true false) ok @@ -746,8 +746,8 @@ def test_pcdl_get_cell_df_timestep_keep(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceAnndata(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliAnndata(object): + ''' tests for one pcdl.pyCli function. ''' # timeseries # + collapse (true false) ok @@ -921,7 +921,6 @@ def test_pcdl_get_anndata_timestep_settingxmlfalse(self): os.remove(f'{s_path_2d}/output00000024_cell_maxabs.h5ad') assert o_result.returncode == 0 - def test_pcdl_get_anndata_timestep_settingxmlnone(self): o_result = subprocess.run(['pcdl_get_anndata', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -968,8 +967,8 @@ def test_pcdl_get_anndata_timestep_scale(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceGraphGml(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliGraphGml(object): + ''' tests for one pcdl.pyCli function. ''' # timestep and timeseries: # + path nop @@ -992,6 +991,7 @@ def test_pcdl_make_graph_gml_timeseries_default(self): os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_neighbor.gml') assert o_result.returncode == 0 + def test_pcdl_make_graph_gml_timeseries_customtype_nodeattribute_one(self): o_result = subprocess.run(['pcdl_make_graph_gml', s_path_2d, 'neighbor', '--custom_data_type', 'sample:bool', '--node_attribute', 'sample'], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -1173,8 +1173,8 @@ def test_pcdl_make_graph_gml_timestep_node_attribute_many(self): assert o_result.returncode == 0 -class TestCommandLineInterfacePlotScatter(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliPlotScatter(object): + ''' tests for one pcdl.pyCli function. ''' # time series and time steps. # + path nop @@ -1240,8 +1240,8 @@ def test_pcdl_plot_scatter_set(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceCellVtk(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliCellVtk(object): + ''' tests for one pcdl.pyCli function. ''' # timestep and timeseries: # + path nop @@ -1390,8 +1390,8 @@ def test_pcdl_make_cell_vtk_timestep_attribute_many(self): # substrate and cell agenat test code # ####################################### -class TestCommandLineInterfacePlotTimeSeries(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliPlotTimeSeries(object): + ''' tests for one pcdl.pyCli function. ''' # time series. # + path nop @@ -1469,198 +1469,12 @@ def test_pcdl_plot_timeseries_set(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceOmeTiff(object): - ''' tests for one pcdl command line interface function. ''' - - # timestep and timeseries: - # + path nop - # + customtype nop (because bool int float might in the end be treated the same and str is recogniced) - # + microenv (true, false) ok - # + physiboss (true, _false_) ok - # + settingxml (string, _none_, _false_) ok - # + verbose (true, _false_) nop - # + cell_attribute (ID, _dead_, _cell_count_voxel_, _pressure_) ok - # + collapse (true, _false_) ok - - def test_pcdl_make_ome_tiff_timeseries_default(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_microenv(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--microenv', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_physiboss(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--physiboss', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_settingxmlfalse(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--settingxml', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_settingxmlnone(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--settingxml', 'none'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_dead(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'dead'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_dead.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_cellcountvoxel(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'cell_count_voxel'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_cell_count_voxel.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_cellattribute_pressure(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, 'pressure'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen_water_default_blood_cells_pressure.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_conccutoff_oxygenminusone(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--conc_cutoff', 'oxygen:-1'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen-1_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_focus_(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--focus', 'oxygen'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/timeseries_oxygen.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timeseries_collapse_false(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_path_2d, '--collapse', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - for i_step in range(25): - os.remove(f'{s_path_2d}/output000000{str(i_step).zfill(2)}_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_default(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_microenv(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--microenv', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_physiboss(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--physiboss', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_settingxmlfalse(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--settingxml', 'false'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_settingxmlnone(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, '--settingxml', 'none'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_cellattribute_dead(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'dead'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_dead.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_cellattribute_cellcountvoxel(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'cell_count_voxel'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_cell_count_voxel.ome.tiff') - assert o_result.returncode == 0 - - def test_pcdl_make_ome_tiff_timestep_cellattribute_pressure(self): - o_result = subprocess.run(['pcdl_make_ome_tiff', s_pathfile_2d, 'pressure'], check=False, capture_output=True) - print(f'o_result: {o_result}\n') - print(f'o_result.returncode: {o_result.returncode}\n') - print(f'o_result.stdout: {o_result.stdout}\n') - print(f'o_result.stderr: {o_result.stderr}\n') - os.remove(f'{s_path_2d}/output00000024_oxygen_water_default_blood_cells_pressure.ome.tiff') - assert o_result.returncode == 0 - - ########################### # making movies test code # ########################### -class TestCommandLineInterfaceMakeGif(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliMakeGif(object): + ''' tests for one pcdl.pyCli function. ''' # time series # + path nop @@ -1668,6 +1482,7 @@ class TestCommandLineInterfaceMakeGif(object): def test_pcdl_make_gif_timeseries_default(self): o_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) + print(f'o_path: {o_path}\n') s_path = f'{s_path_2d}/cell_cell_type_z0.0/' o_result = subprocess.run(['pcdl_make_gif', s_path], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -1679,6 +1494,7 @@ def test_pcdl_make_gif_timeseries_default(self): def test_pcdl_make_gif_timeseries_interface(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') s_path = f'{s_path_2d}/conc_oxygen_z0.0/' o_result = subprocess.run(['pcdl_make_gif', s_path, 'tiff'], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -1689,8 +1505,8 @@ def test_pcdl_make_gif_timeseries_interface(self): assert o_result.returncode == 0 -class TestCommandLineInterfaceMakeMove(object): - ''' tests for one pcdl command line interface function. ''' +class TestPyCliMakeMovie(object): + ''' tests for one pcdl.pyCli function. ''' # time series # + path nop @@ -1699,6 +1515,7 @@ class TestCommandLineInterfaceMakeMove(object): def test_pcdl_make_movie_timeseries_default(self): o_path = subprocess.run(['pcdl_plot_scatter', s_path_2d], check=False, capture_output=True) + print(f'o_path: {o_path}\n') s_path = f'{s_path_2d}/cell_cell_type_z0.0/' o_result = subprocess.run(['pcdl_make_movie', s_path], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -1710,6 +1527,7 @@ def test_pcdl_make_movie_timeseries_default(self): def test_pcdl_make_movie_timeseries_interface(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'tiff'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') s_path = f'{s_path_2d}/conc_oxygen_z0.0/' o_result = subprocess.run(['pcdl_make_movie', s_path, 'tiff'], check=False, capture_output=True) print(f'o_result: {o_result}\n') @@ -1721,6 +1539,7 @@ def test_pcdl_make_movie_timeseries_interface(self): def test_pcdl_make_movie_timeseries_farme(self): o_path = subprocess.run(['pcdl_plot_contour', s_path_2d, 'oxygen', '--ext', 'jpeg'], check=False, capture_output=True) + print(f'o_path: {o_path}\n') s_path = f'{s_path_2d}/conc_oxygen_z0.0/' o_result = subprocess.run(['pcdl_make_movie', s_path, '--framerate', '9'], check=False, capture_output=True) print(f'o_result: {o_result}\n') diff --git a/test/test_timeseries_2d.py b/test/test_timeseries_2d.py index 7120c81..1d355f4 100644 --- a/test/test_timeseries_2d.py +++ b/test/test_timeseries_2d.py @@ -7,7 +7,7 @@ # license: BSD 3-Clause # # description: -# pytest unit test library for the pcdl library TimeSeries class. +# pytest unit test library for the pcdl library pyMCDSts class. # + https://docs.pytest.org/ # # note: @@ -39,9 +39,9 @@ ## making movies related functions ## -class TestTimeSeriesMovies(object): - ''' tests for loading a pcdl.TimeSeries data set. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) +class TestPyMcdsTsMovies(object): + ''' tests for loading a pcdl.pyMCDS data set. ''' + mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=True) ## make_gif and magick ommand ## def test_mcdsts_make_gif_jpeg(self, mcdsts=mcdsts): @@ -51,7 +51,7 @@ def test_mcdsts_make_gif_jpeg(self, mcdsts=mcdsts): path = s_opath, #interface = 'jpeg', ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_jpeg.gif')) #os.remove(s_opathfile) @@ -64,7 +64,7 @@ def test_mcdsts_make_gif_tiff(self, mcdsts=mcdsts): path = s_opath, interface = 'tiff', ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_tiff.gif')) #os.remove(s_opathfile) @@ -79,7 +79,7 @@ def test_mcdsts_make_movie_jpeg12(self, mcdsts=mcdsts): #interface = 'jpeg', #framerate = 12, ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_jpeg12.mp4')) #os.remove(s_opathfile) @@ -93,7 +93,7 @@ def test_mcdsts_make_movie_tiff12(self, mcdsts=mcdsts): interface = 'tiff', #framerate = 12, ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_tiff12.mp4')) #os.remove(s_opathfile) @@ -107,7 +107,7 @@ def test_mcdsts_make_movie_jpeg6(self, mcdsts=mcdsts): #interface = 'jpeg', framerate = 6, ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (os.path.exists(s_opathfile)) and \ (s_opathfile.endswith('pcdl/output_2d/cell_cell_type_z0.0/cell_cell_type_z0.0_jpeg6.mp4')) #os.remove(s_opathfile) @@ -116,61 +116,61 @@ def test_mcdsts_make_movie_jpeg6(self, mcdsts=mcdsts): ## data loading related functions ## -class TestTimeSeriesInit(object): - ''' tests for loading a pcdl.TimeSeries data set. ''' +class TestPyMcdsTsInit(object): + ''' tests for loading a pcdl.pyMCDSts data set. ''' def test_mcdsts_set_verbose_true(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=False, verbose=False) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=False, verbose=False) mcdsts.set_verbose_true() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (mcdsts.verbose) def test_mcdsts_set_verbose_false(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=False, verbose=True) mcdsts.set_verbose_false() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (not mcdsts.verbose) ## get_xmlfile and read_mcds command and get_mcds_list ## def test_mcdsts_get_xmlfile_list(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=False, verbose=True) ls_xmlfile = mcdsts.get_xmlfile_list() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_xmlfile[0] == 'output00000000.xml') and \ (ls_xmlfile[-1] == 'output00000024.xml') and \ (len(ls_xmlfile) == 25) def test_mcdsts_get_mcds_list(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=True, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=True, verbose=True) l_mcds = mcdsts.get_mcds_list() - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 0) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(mcdsts.l_mcds) == 25) and \ (mcdsts.l_mcds == l_mcds) def test_mcdsts_read_mcds(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=False, verbose=True) l_mcds_loadfalse = mcdsts.get_mcds_list() mcdsts.read_mcds() - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 0) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(mcdsts.l_mcds) == 25) and \ (l_mcds_loadfalse is None) def test_mcdsts_read_mcds_xmlfilelist(self): - mcdsts = pcdl.TimeSeries(s_path_2d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_2d, load=False, verbose=True) ls_xmlfile = mcdsts.get_xmlfile_list() ls_xmlfile = ls_xmlfile[-3:] l_mcds = mcdsts.read_mcds(ls_xmlfile) - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 1320) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(ls_xmlfile) == 3) and \ @@ -180,13 +180,13 @@ def test_mcdsts_read_mcds_xmlfilelist(self): ## micro environment related functions ## -class TestTimeSeriesMicroenv(object): - ''' tests for pcdl.TimeSeriesmicro environment related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) +class TestPyMcdsTsMicroenv(object): + ''' tests for pcdl.pyMCDS micro environment related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=True) def test_mcdsts_get_conc_df(self, mcdsts=mcdsts): ldf_conc = mcdsts.get_conc_df(values=2, drop=set(), keep=set(), collapse=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(ldf_conc)) == "") and \ (str(type(ldf_conc[0])) == "") and \ (ldf_conc[0].shape == (121, 9)) and \ @@ -195,13 +195,13 @@ def test_mcdsts_get_conc_df(self, mcdsts=mcdsts): def test_mcdsts_get_conc_df_collapse(self, mcdsts=mcdsts): df_conc = mcdsts.get_conc_df(values=2, drop=set(), keep=set(), collapse=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (3025, 11)) def test_mcdsts_get_conc_attribute(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=1, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -210,7 +210,7 @@ def test_mcdsts_get_conc_attribute(self, mcdsts=mcdsts): def test_mcdsts_get_conc_attribute_values(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=2, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -219,7 +219,7 @@ def test_mcdsts_get_conc_attribute_values(self, mcdsts=mcdsts): def test_mcdsts_get_conc_attribute_allvalues(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=1, drop=set(), keep=set(), allvalues=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -244,7 +244,7 @@ def test_mcdsts_plot_contour_if(self, mcdsts=mcdsts): ext = 'jpeg', # test file case figbgcolor = None, # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].replace('\\','/').endswith('/pcdl/output_2d/conc_oxygen_z0.0/output00000000_oxygen.jpeg')) and \ (os.path.exists(ls_pathfile[0])) and \ (os.path.getsize(ls_pathfile[0]) > 2**10) and \ @@ -261,27 +261,27 @@ def test_mcdsts_plot_contour_else(self, mcdsts=mcdsts): focus = 'oxygen', z_slice = 0.0, # jump over if extrema = [0, 38], # jump over if - #alpha = 1, # TimeStep - #fill = True, # TimeStep - #cmap = 'viridis', # TimeStep + #alpha = 1, # pyMCDS + #fill = True, # pyMCDS + #cmap = 'viridis', # pyMCDS title = 'abc', # test non default - #grid = True, # TimeStep + #grid = True, # pyMCDS xlim = [-31, 301], # jump over if ylim = [-21, 201], # jump over if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS figsizepx = [641, 481], # test non even pixel ext = None, # test fig case figbgcolor = 'yellow', # not a file ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(l_fig[0])) == "") and \ (str(type(l_fig[-1])) == "") and \ (len(l_fig) == 25) plt.close() def test_mcdsts_make_conc_vtk(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_conc_vtk() - assert(str(type(mcdsts)) == "") and \ + ls_pathfile = mcdsts.make_conc_vtk(visualize=False) + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_2d/output00000000_conc.vtr')) and \ (ls_pathfile[-1].endswith('/pcdl/output_2d/output00000024_conc.vtr')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -295,13 +295,13 @@ def test_mcdsts_make_conc_vtk(self, mcdsts=mcdsts): ## cell related functions ## -class TestTimeSeriesCell(object): - ''' tests for pcdl.TimeSeries cell related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=False) +class TestPyMcdsCell(object): + ''' tests for pcdl.pyMCDS cell related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=False) def test_mcdsts_get_cell_df(self, mcdsts=mcdsts): ldf_cell = mcdsts.get_cell_df(values=2, drop=set(), keep=set(), collapse=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(ldf_cell)) == "") and \ (str(type(ldf_cell[0])) == "") and \ (ldf_cell[0].shape[0] > 9) and \ @@ -312,14 +312,14 @@ def test_mcdsts_get_cell_df(self, mcdsts=mcdsts): def test_mcdsts_get_cell_df_collapse(self, mcdsts=mcdsts): df_cell = mcdsts.get_cell_df(values=2, drop=set(), keep=set(), collapse=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 67) def test_mcdsts_get_cell_attribute(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=1, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (str(type(dl_cell['dead'])) == "") and \ (str(type(dl_cell['dead'][0])) == "") and \ @@ -337,13 +337,13 @@ def test_mcdsts_get_cell_attribute(self, mcdsts=mcdsts): def test_mcdsts_get_cell_attribute_values(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=2, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (len(dl_cell.keys()) == 54) def test_mcdsts_get_cell_attribute_allvalues(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=1, drop=set(), keep=set(), allvalues=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (str(type(dl_cell['dead'])) == "") and \ (str(type(dl_cell['dead'][0])) == "") and \ @@ -372,13 +372,13 @@ def test_mcdsts_plot_scatter_num(self, mcdsts=mcdsts): #legend_loc='lower left', # matplotlib xlim = None, # test if ylim = None, # test if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS s = None, # test if figsizepx = None, # case extract from initial.svg ext = 'jpeg', # generate file case figbgcolor = None, # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].replace('\\','/').endswith('/pcdl/output_2d/cell_pressure_z0.0/output00000000_pressure.jpeg')) and \ (os.path.exists(ls_pathfile[0])) and \ (os.path.getsize(ls_pathfile[0]) > 2**10) and \ @@ -394,28 +394,28 @@ def test_mcdsts_plot_scatter_cat(self, mcdsts=mcdsts): focus='cell_type', # case categorical z_slice = 0.0, # jump over if z_axis = None, # test iff categorical - #alpha = 1, # TimeStep - #cmap = 'viridis', # TimeStep + #alpha = 1, # pyMCDS + #cmap = 'viridis', # pyMCDS title = 'abc', # test non default - #grid = True, # TimeStep - #legend_loc='lower left', # TimeStep + #grid = True, # pyMCDS + #legend_loc='lower left', # pyMCDS xlim = None, # test if ylim = None, # test if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS s = None, # test if figsizepx = [641, 481], # test case non even pixel number ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(l_fig[0])) == "") and \ (str(type(l_fig[-1])) == "") and \ (len(l_fig) == 25) plt.close() def test_mcdsts_make_cell_vtk(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_cell_vtk() - assert(str(type(mcdsts)) == "") and \ + ls_pathfile = mcdsts.make_cell_vtk(visualize=False) + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_2d/output00000000_cell.vtp')) and \ (ls_pathfile[-1].endswith('/pcdl/output_2d/output00000024_cell.vtp')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -428,15 +428,14 @@ def test_mcdsts_make_cell_vtk(self, mcdsts=mcdsts): ## graph related functions ## - -class TestTimeSeriesGraph(object): - ''' tests for pcdl.TimeSeries graph related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=False) +class TestPyMcdsGraph(object): + ''' tests for pcdl.pyMCDS graph related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=False) ## graph related functions ## def test_mcdsts_get_graph_gml_attached_defaultattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='attached', edge_attribute=True, node_attribute=[]) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_2d/output00000000_attached.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_2d/output00000024_attached.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -447,7 +446,7 @@ def test_mcdsts_get_graph_gml_attached_defaultattr(self, mcdsts=mcdsts): def test_mcdsts_get_graph_gml_neighbor_noneattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='neighbor', edge_attribute=False, node_attribute=[]) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_2d/output00000000_neighbor.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -458,7 +457,7 @@ def test_mcdsts_get_graph_gml_neighbor_noneattr(self, mcdsts=mcdsts): def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='neighbor', edge_attribute=True, node_attribute=['dead','cell_count_voxel','cell_density_micron3','cell_type']) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_2d/output00000000_neighbor.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -468,98 +467,11 @@ def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): os.remove(s_pathfile) -## ome tiff related functions ## - -class TestTimeSeriesOmeTiff(object): - ''' tests for pcdl.TimeSeries ome tiff related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=False) - - ## ome tiff related functions ## - def test_mcdsts_make_ome_tiff_defaultattr_00(self, mcdsts=mcdsts): - la_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (type(la_ometiff) is list) and \ - (type(la_ometiff[0]) is np.ndarray) and \ - (type(la_ometiff[-1]) is np.ndarray) and \ - (la_ometiff[0].dtype == np.float32) and \ - (la_ometiff[-1].dtype == np.float32) and \ - (la_ometiff[0].shape == (4, 1, 200, 300)) and \ - (la_ometiff[-1].shape == (4, 1, 200, 300)) and \ - (len(la_ometiff) == 25) - - def test_mcdsts_make_ome_tiff_defaultattr_01(self, mcdsts=mcdsts): - a_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (type(a_ometiff) is np.ndarray) and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (25, 4, 1, 200, 300)) - - def test_mcdsts_make_ome_tiff_defaultattr_10(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (ls_pathfile[0].endswith('pcdl/output_2d/output00000000_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (ls_pathfile[-1].endswith('pcdl/output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(ls_pathfile[0])) and \ - (os.path.exists(ls_pathfile[-1])) and \ - (os.path.getsize(ls_pathfile[0]) > 2**10) and\ - (os.path.getsize(ls_pathfile[-1]) > 2**10) and\ - (len(ls_pathfile) == 25) - for s_pathfile in ls_pathfile: - os.remove(s_pathfile) - - def test_mcdsts_make_ome_tiff_defaultattr_11(self, mcdsts=mcdsts): - s_pathfile = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (s_pathfile.endswith('pcdl/output_2d/timeseries_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10 ) - os.remove(s_pathfile) - +## timeseries related functions ## -class TestTimeSeriesNeuroglancer(object): - ''' tests for loading a pcdl.TimeSeries data set. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) - - ## make_gif and magick ommand ## - def test_mcdsts_render_neuroglancer_default(self, mcdsts=mcdsts): - s_tiffpathfile = mcdsts.make_ome_tiff() - o_viewer = mcdsts.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - #timestep = 0, - #intensity_cmap='gray', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - def test_mcdsts_render_neuroglancer_timestep(self, mcdsts=mcdsts): - s_tiffpathfile = mcdsts.make_ome_tiff() - o_viewer = mcdsts.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - timestep = 12, - intensity_cmap='gray', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - def test_mcdsts_render_neuroglancer_cmap(self, mcdsts=mcdsts): - s_tiffpathfile = mcdsts.make_ome_tiff() - o_viewer = mcdsts.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - timestep = 0, - intensity_cmap='magma', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - -## time series related functions ## - -class TestTimeSeriesTimeseries(object): - ''' tests for pcdl.TimeSeries graph related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=False) +class TestPyMcdsTimeseries(object): + ''' tests for pcdl.pyMCDS graph related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_2d, verbose=False) ## plot_timeseries command ## def test_mcdsts_plot_timeseries_none_none_none_cell_ax_jpeg(self, mcdsts=mcdsts): @@ -589,7 +501,7 @@ def test_mcdsts_plot_timeseries_none_none_none_cell_ax_jpeg(self, mcdsts=mcdsts) ext = 'jpeg', # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (s_pathfile.endswith('/pcdl/output_2d/timeseries_cell_total_count.jpeg')) and \ (os.path.exists(s_pathfile)) os.remove(s_pathfile) @@ -620,7 +532,7 @@ def test_mcdsts_plot_timeseries_cat_none_yunit_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -650,7 +562,7 @@ def test_mcdsts_plot_timeseries_none_num_yunit_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -680,7 +592,7 @@ def test_mcdsts_plot_timeseries_cat_num_none_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -711,7 +623,7 @@ def test_mcdsts_plot_timeseries_none_none_none_conc_ax_jpeg(self, mcdsts=mcdsts) ext = 'jpeg', # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (s_pathfile.endswith('/pcdl/output_2d/timeseries_conc_total_count.jpeg')) and \ (os.path.exists(s_pathfile)) os.remove(s_pathfile) @@ -743,7 +655,7 @@ def test_mcdsts_plot_timeseries_cat_none_yunit_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -773,7 +685,7 @@ def test_mcdsts_plot_timeseries_none_num_yunit_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -803,97 +715,7 @@ def test_mcdsts_plot_timeseries_cat_num_none_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() - -## anndata time series related functions ## - -class TestTimeSeriesAnnData(object): - ''' test for pcdl.TestSeries class. ''' - - # get_anndata - # get_annmcds_list {integrated} - # value {1, _2_} - # collaps {True, _False_} - # keep_mcds {True, _False_} - - ## get_anndata command ## - def test_mcdsts_get_anndata(self): - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 2) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - - def test_mcdsts_get_anndata_value(self): - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) - ann = mcdsts.get_anndata(values=2, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 50) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 2) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (50, 0)) and \ - (len(ann.uns) == 0) - - def test_mcdsts_get_anndata_collapsefalse(self): - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=False, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (str(type(mcdsts.l_annmcds)) == "") and \ - (len(mcdsts.l_annmcds) == 25) and \ - (all([str(type(ann)) == "" for ann in mcdsts.l_annmcds])) and \ - (mcdsts.l_annmcds[24].X.shape[0] > 9) and \ - (mcdsts.l_annmcds[24].X.shape[1] == 105) and \ - (mcdsts.l_annmcds[24].obs.shape[0] > 9) and \ - (mcdsts.l_annmcds[24].obs.shape[1] == 7) and \ - (mcdsts.l_annmcds[24].obsm['spatial'].shape[0] > 9) and \ - (mcdsts.l_annmcds[24].obsm['spatial'].shape[1] == 2) and \ - (len(mcdsts.l_annmcds[24].obsp) == 4) and \ - (mcdsts.l_annmcds[24].var.shape == (105, 0)) and \ - (len(mcdsts.l_annmcds[24].uns) == 2) - - def test_mcdsts_get_anndata_keepmcdsfalse(self): - mcdsts = pcdl.TimeSeries(s_path_2d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=False) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 0) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 2) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - diff --git a/test/test_timeseries_3d.py b/test/test_timeseries_3d.py index bbf9a8e..a718dc5 100644 --- a/test/test_timeseries_3d.py +++ b/test/test_timeseries_3d.py @@ -7,7 +7,7 @@ # license: BSD 3-Clause # # description: -# pytest unit test library for the pcdl library TimeSeries class. +# pytest unit test library for the pcdl library pyMCDSts class. # + https://docs.pytest.org/ # # note: @@ -43,61 +43,61 @@ ## data loading related functions ## -class TestTimeSeries3dInit(object): - ''' tests for loading a pcdl.TimeSeries data set. ''' +class TestPyMcdsTs3DInit(object): + ''' tests for loading a pcdl.pyMCDSts data set. ''' def test_mcdsts_set_verbose_true(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=False, verbose=False) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=False, verbose=False) mcdsts.set_verbose_true() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (mcdsts.verbose) def test_mcdsts_set_verbose_false(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=False, verbose=True) mcdsts.set_verbose_false() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (not mcdsts.verbose) ## get_xmlfile and read_mcds command and get_mcds_list ## def test_mcdsts_get_xmlfile_list(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=False, verbose=True) ls_xmlfile = mcdsts.get_xmlfile_list() - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_xmlfile[0] == 'output00000000.xml') and \ (ls_xmlfile[-1] == 'output00000024.xml') and \ (len(ls_xmlfile) == 25) def test_mcdsts_get_mcds_list(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=True, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=True, verbose=True) l_mcds = mcdsts.get_mcds_list() - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 0) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(mcdsts.l_mcds) == 25) and \ (mcdsts.l_mcds == l_mcds) def test_mcdsts_read_mcds(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=False, verbose=True) l_mcds_loadfalse = mcdsts.get_mcds_list() mcdsts.read_mcds() - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 0) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(mcdsts.l_mcds) == 25) and \ (l_mcds_loadfalse is None) def test_mcdsts_read_mcds_xmlfilelist(self): - mcdsts = pcdl.TimeSeries(s_path_3d, load=False, verbose=True) + mcdsts = pcdl.pyMCDSts(s_path_3d, load=False, verbose=True) ls_xmlfile = mcdsts.get_xmlfile_list() ls_xmlfile = ls_xmlfile[-3:] l_mcds = mcdsts.read_mcds(ls_xmlfile) - assert(str(type(mcdsts)) == "") and \ - (str(type(mcdsts.l_mcds[0])) == "") and \ - (str(type(mcdsts.l_mcds[-1])) == "") and \ + assert(str(type(mcdsts)) == "") and \ + (str(type(mcdsts.l_mcds[0])) == "") and \ + (str(type(mcdsts.l_mcds[-1])) == "") and \ (mcdsts.l_mcds[0].get_time() == 1320) and \ (mcdsts.l_mcds[-1].get_time() == 1440) and \ (len(ls_xmlfile) == 3) and \ @@ -107,13 +107,13 @@ def test_mcdsts_read_mcds_xmlfilelist(self): ## micro environment related functions ## -class TestTimeSeries3dMicroenv(object): - ''' tests for pcdl.TimeStep micro environment related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) +class TestPyMcdsTs3DMicroenv(object): + ''' tests for pcdl.pyMCDS micro environment related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_3d, verbose=True) def test_mcdsts_get_conc_df(self, mcdsts=mcdsts): ldf_conc = mcdsts.get_conc_df(values=2, drop=set(), keep=set(), collapse=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(ldf_conc)) == "") and \ (str(type(ldf_conc[0])) == "") and \ (ldf_conc[0].shape == (1331, 9)) and \ @@ -122,13 +122,13 @@ def test_mcdsts_get_conc_df(self, mcdsts=mcdsts): def test_mcdsts_get_conc_df_collapse(self, mcdsts=mcdsts): df_conc = mcdsts.get_conc_df(values=2, drop=set(), keep=set(), collapse=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (33275, 11)) def test_mcdsts_get_conc_attribute(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=1, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -137,7 +137,7 @@ def test_mcdsts_get_conc_attribute(self, mcdsts=mcdsts): def test_mcdsts_get_conc_attribute_values(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=2, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -146,7 +146,7 @@ def test_mcdsts_get_conc_attribute_values(self, mcdsts=mcdsts): def test_mcdsts_get_conc_attribute_allvalues(self, mcdsts=mcdsts): dl_conc = mcdsts.get_conc_attribute(values=1, drop=set(), keep=set(), allvalues=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_conc)) == "") and \ (str(type(dl_conc['oxygen'])) == "") and \ (str(type(dl_conc['oxygen'][0])) == "") and \ @@ -159,19 +159,19 @@ def test_mcdsts_plot_contour_if(self, mcdsts=mcdsts): focus = 'oxygen', z_slice = -3.333, # test if extrema = None, # test if and for loop - #alpha = 1, # TimeStep - #fill = True, # TimeStep - #cmap = 'viridis', # TimeStep + #alpha = 1, # pyMCD + #fill = True, # pyMCD + #cmap = 'viridis', # pyMCD #title = '', # test default - #grid = True, # TimeStep + #grid = True, # pyMCD xlim = None, # test if ylim = None, # test if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCD figsizepx = None, # test if ext = 'jpeg', figbgcolor = None, # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].replace('\\','/').endswith('/pcdl/output_3d/conc_oxygen_z-5.0/output00000000_oxygen.jpeg')) and \ (os.path.exists(ls_pathfile[0])) and \ (os.path.getsize(ls_pathfile[0]) > 2**10) and \ @@ -187,19 +187,19 @@ def test_mcdsts_plot_contour_else(self, mcdsts=mcdsts): focus = 'oxygen', z_slice = 0.0, # jump over if extrema = [0, 38], # jump over if - #alpha = 1, # TimeStep - #fill = True, # TimeStep - #cmap = 'viridis', # TimeStep + #alpha = 1, # pyMCDS + #fill = True, # pyMCDS + #cmap = 'viridis', # pyMCDS title = 'abc', # test non default - #grid = True, # TimeStep + #grid = True, # pyMCDS xlim = [-31, 301], # jump over if ylim = [-21, 201], # jump over if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS figsizepx = [641, 481], # test non even pixel ext = None, figbgcolor = 'yellow', # jump over if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(l_fig[0])) == "") and \ (str(type(l_fig[-1])) == "") and \ (len(l_fig) == 25) @@ -207,8 +207,8 @@ def test_mcdsts_plot_contour_else(self, mcdsts=mcdsts): def test_mcdsts_make_conc_vtk(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_conc_vtk() - assert(str(type(mcdsts)) == "") and \ + ls_pathfile = mcdsts.make_conc_vtk(visualize=False) + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_3d/output00000000_conc.vtr')) and \ (ls_pathfile[-1].endswith('/pcdl/output_3d/output00000024_conc.vtr')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -222,13 +222,13 @@ def test_mcdsts_make_conc_vtk(self, mcdsts=mcdsts): ## cell related functions ## -class TestTimeSeries3dCell(object): - ''' tests for pcdl.TimeStep cell related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=False) +class TestPyMcds3DCell(object): + ''' tests for pcdl.pyMCDS cell related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_3d, verbose=False) def test_mcdsts_get_cell_df(self, mcdsts=mcdsts): ldf_cell = mcdsts.get_cell_df(values=2, drop=set(), keep=set(), collapse=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(ldf_cell)) == "") and \ (str(type(ldf_cell[0])) == "") and \ (ldf_cell[0].shape[0] > 9) and \ @@ -239,14 +239,14 @@ def test_mcdsts_get_cell_df(self, mcdsts=mcdsts): def test_mcdsts_get_cell_df_collapse(self, mcdsts=mcdsts): df_cell = mcdsts.get_cell_df(values=2, drop=set(), keep=set(), collapse=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 73) def test_mcdsts_get_cell_attribute(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=1, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (str(type(dl_cell['dead'])) == "") and \ (str(type(dl_cell['dead'][0])) == "") and \ @@ -264,13 +264,13 @@ def test_mcdsts_get_cell_attribute(self, mcdsts=mcdsts): def test_mcdsts_get_cell_attribute_values(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=2, drop=set(), keep=set(), allvalues=False) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (len(dl_cell.keys()) == 60) def test_mcdsts_get_cell_attribute_allvalues(self, mcdsts=mcdsts): dl_cell = mcdsts.get_cell_attribute(values=1, drop=set(), keep=set(), allvalues=True) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(dl_cell)) == "") and \ (str(type(dl_cell['dead'])) == "") and \ (str(type(dl_cell['dead'][0])) == "") and \ @@ -299,13 +299,13 @@ def test_mcdsts_plot_scatter_num(self, mcdsts=mcdsts): #legend_loc='lower left', # matplotlib xlim = None, # test if ylim = None, # test if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS s = None, # test if figsizepx = None, # case extract from initial.svg ext = 'jpeg', figbgcolor = None, # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].replace('\\','/').endswith('/pcdl/output_3d/cell_pressure_z-5.0/output00000000_pressure.jpeg')) and \ (os.path.exists(ls_pathfile[0])) and \ (os.path.getsize(ls_pathfile[0]) > 2**10) and \ @@ -321,28 +321,28 @@ def test_mcdsts_plot_scatter_cat(self, mcdsts=mcdsts): focus='cell_type', # case categorical z_slice = 0.0, # jump over if z_axis = None, # test iff categorical - #alpha = 1, # TimeStep - #cmap = 'viridis', # TimeStep + #alpha = 1, # pyMCDS + #cmap = 'viridis', # pyMCDS title = 'abc', # test non default - #grid = True, # TimeStep - #legend_loc='lower left', # TimeStep + #grid = True, # pyMCDS + #legend_loc='lower left', # pyMCDS xlim = None, # test if ylim = None, # test if - #xyequal = True, # TimeStep + #xyequal = True, # pyMCDS s = None, # test if figsizepx = [641, 481], # test case non even pixel number ext = None, figbgcolor = 'cyan', # jump over if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(l_fig[0])) == "") and \ (str(type(l_fig[-1])) == "") and \ (len(l_fig) == 25) plt.close() def test_mcdsts_make_cell_vtk(self, mcdsts=mcdsts): - ls_pathfile = mcdsts.make_cell_vtk() - assert(str(type(mcdsts)) == "") and \ + ls_pathfile = mcdsts.make_cell_vtk(visualize=False) + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_3d/output00000000_cell.vtp')) and \ (ls_pathfile[-1].endswith('/pcdl/output_3d/output00000024_cell.vtp')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -355,14 +355,14 @@ def test_mcdsts_make_cell_vtk(self, mcdsts=mcdsts): ## graph related functions ## -class TestTimeSeries3dGraph(object): - ''' tests for pcdl.TimeStep graph related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=False) +class TestPyMcds3DGraph(object): + ''' tests for pcdl.pyMCDS graph related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_3d, verbose=False) ## graph related functions ## def test_mcdsts_get_graph_gml_attached_defaultattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='attached', edge_attribute=True, node_attribute=[]) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_3d/output00000000_attached.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_3d/output00000024_attached.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -373,7 +373,7 @@ def test_mcdsts_get_graph_gml_attached_defaultattr(self, mcdsts=mcdsts): def test_mcdsts_get_graph_gml_neighbor_noneattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='neighbor', edge_attribute=False, node_attribute=[]) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_3d/output00000000_neighbor.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -384,7 +384,7 @@ def test_mcdsts_get_graph_gml_neighbor_noneattr(self, mcdsts=mcdsts): def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): ls_pathfile = mcdsts.make_graph_gml(graph_type='neighbor', edge_attribute=True, node_attribute=['dead','cell_count_voxel','cell_density_micron3','cell_type']) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (ls_pathfile[0].endswith('/pcdl/output_3d/output00000000_neighbor.gml')) and \ (ls_pathfile[-1].endswith('/pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(ls_pathfile[0])) and \ @@ -393,37 +393,12 @@ def test_mcdsts_get_graph_gml_neighbor_allattr(self, mcdsts=mcdsts): for s_pathfile in ls_pathfile: os.remove(s_pathfile) -## ome.tiff related functions ## -class TestTimeSeries3dOmeTiff(object): - ''' tests for pcdl.TimeStep graph related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=False) - ## graph related functions ## - def test_mcdsts_make_ome_tiff_defaultattr_00(self, mcdsts=mcdsts): - la_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=False) - assert(str(type(mcdsts)) == "") and \ - (type(la_ometiff) is list) and \ - (type(la_ometiff[0]) is np.ndarray) and \ - (type(la_ometiff[-1]) is np.ndarray) and \ - (la_ometiff[0].dtype == np.float32) and \ - (la_ometiff[-1].dtype == np.float32) and \ - (la_ometiff[0].shape == (4, 11, 200, 300)) and \ - (la_ometiff[-1].shape == (4, 11, 200, 300)) and \ - (len(la_ometiff) == 25) - - def test_mcdsts_make_ome_tiff_defaultattr_01(self, mcdsts=mcdsts): - a_ometiff = mcdsts.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False, collapse=True) - assert(str(type(mcdsts)) == "") and \ - (type(a_ometiff) is np.ndarray) and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (25, 4, 11, 200, 300)) - - -## time series related functions ## - -class TestTimeSeries3dTimeseries(object): - ''' tests for pcdl.TimeStep graph related functions. ''' - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=False) +## timeseries related functions ## + +class TestPyMcds3DTimeseries(object): + ''' tests for pcdl.pyMCDS graph related functions. ''' + mcdsts = pcdl.pyMCDSts(s_path_3d, verbose=False) ## plot_timeseries command ## def test_mcdsts_plot_timeseries_none_none_none_cell_ax_jpeg(self, mcdsts=mcdsts): @@ -453,7 +428,7 @@ def test_mcdsts_plot_timeseries_none_none_none_cell_ax_jpeg(self, mcdsts=mcdsts) ext = 'jpeg', # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (s_pathfile.endswith('/pcdl/output_3d/timeseries_cell_total_count.jpeg')) and \ (os.path.exists(s_pathfile)) os.remove(s_pathfile) @@ -484,7 +459,7 @@ def test_mcdsts_plot_timeseries_cat_none_yunit_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -514,7 +489,7 @@ def test_mcdsts_plot_timeseries_none_num_yunit_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -544,7 +519,7 @@ def test_mcdsts_plot_timeseries_cat_num_none_cell(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -575,7 +550,7 @@ def test_mcdsts_plot_timeseries_none_none_none_conc_ax_jpeg(self, mcdsts=mcdsts) ext = 'jpeg', # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (s_pathfile.endswith('/pcdl/output_3d/timeseries_conc_total_count.jpeg')) and \ (os.path.exists(s_pathfile)) os.remove(s_pathfile) @@ -607,7 +582,7 @@ def test_mcdsts_plot_timeseries_cat_none_yunit_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -637,7 +612,7 @@ def test_mcdsts_plot_timeseries_none_num_yunit_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() @@ -667,96 +642,7 @@ def test_mcdsts_plot_timeseries_cat_num_none_conc(self, mcdsts=mcdsts): ext = None, # test if else {'jpeg', None} figbgcolor = None # test if ) - assert(str(type(mcdsts)) == "") and \ + assert(str(type(mcdsts)) == "") and \ (str(type(fig)) == "") plt.close() - -## anndata time series related functions ## -class TestTimeSeries3dAnnData(object): - ''' test for pcdl.TestSeries class. ''' - - # get_anndata - # get_annmcds_list {integrated} - # value {1, _2_} - # collaps {True, _False_} - # keep_mcds {True, _False_} - - ## get_anndata command ## - def test_mcdsts_get_anndata(self): - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 3) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - - def test_mcdsts_get_anndata_value(self): - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) - ann = mcdsts.get_anndata(values=2, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 56) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 3) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (56, 0)) and \ - (len(ann.uns) == 0) - - def test_mcdsts_get_anndata_collapsefalse(self): - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=False, keep_mcds=True) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 25) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (str(type(mcdsts.l_annmcds)) == "") and \ - (len(mcdsts.l_annmcds) == 25) and \ - (all([str(type(ann)) == "" for ann in mcdsts.l_annmcds])) and \ - (mcdsts.l_annmcds[24].X.shape[0] > 9) and \ - (mcdsts.l_annmcds[24].X.shape[1] == 105) and \ - (mcdsts.l_annmcds[24].obs.shape[0] > 9) and \ - (mcdsts.l_annmcds[24].obs.shape[1] == 7) and \ - (mcdsts.l_annmcds[24].obsm['spatial'].shape[0] > 9) and \ - (mcdsts.l_annmcds[24].obsm['spatial'].shape[1] == 3) and \ - (len(mcdsts.l_annmcds[24].obsp) == 4) and \ - (mcdsts.l_annmcds[24].var.shape == (105, 0)) and \ - (len(mcdsts.l_annmcds[24].uns) == 2) - - def test_mcdsts_get_anndata_keepmcdsfalse(self): - mcdsts = pcdl.TimeSeries(s_path_3d, verbose=True) - ann = mcdsts.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs', collapse=True, keep_mcds=False) - l_annmcds = mcdsts.get_annmcds_list() - assert(str(type(mcdsts)) == "") and \ - (len(mcdsts.l_mcds) == 0) and \ - (l_annmcds == mcdsts.l_annmcds) and \ - (mcdsts.l_annmcds is None) and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 8) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 3) and \ - (len(ann.obsp) == 0) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 0) - diff --git a/test/test_timestep_2d.py b/test/test_timestep_2d.py index f09ca37..c114c58 100644 --- a/test/test_timestep_2d.py +++ b/test/test_timestep_2d.py @@ -7,7 +7,7 @@ # license: BSD 3-Clause # # description: -# pytest unit test library for the pcdl library TimeStep class. +# pytest unit test library for the pcdl library pyMCDS class. # + https://docs.pytest.org/ # # note: @@ -22,7 +22,6 @@ import matplotlib.pyplot as plt import numpy as np import os -import pandas as pd import pathlib import pcdl @@ -38,267 +37,256 @@ pcdl.install_data() -# const -s_path_2d = str(pathlib.Path(pcdl.__file__).parent.resolve()/'output_2d') -s_file_2d = 'output00000024.xml' -s_pathfile_2d = f'{s_path_2d}/{s_file_2d}' - - -# test data -if not os.path.exists(s_path_2d): - pcdl.install_data() - - ## data loading related functions ## -class TestTimeStepInit(object): - ''' tests for loading a pcdl.TimeStep data set. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInit(object): + ''' tests for loading a pcdl.pyMCDS data set. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitMicroenvFalse(object): - ''' tests for loading a pcdl.TimeStep data set with microenv false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=False, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitMicroenvFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with microenv false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=False, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 116) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 116) -class TestTimeStepInitGraphFalse(object): - ''' tests for loading a pcdl.TimeStep data set with graph false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=False, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitGraphFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with graph false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=False, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) == 0) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) == 0) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) == 0) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) == 0) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitPhysibossFalse(object): - ''' tests for loading a pcdl.TimeStep data set with physiboss false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=False, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitPhysibossFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with physiboss false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=False, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitSettingxmlFalse(object): - ''' tests for loading a pcdl.TimeStep data set with settingxml false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml=False, verbose=True) +class TestPyMcdsInitSettingxmlFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with settingxml false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml=False, verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitSettingxmlNone(object): - ''' tests for loading a pcdl.TimeStep data set with settingxml none. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml=None, verbose=True) +class TestPyMcdsInitSettingxmlNone(object): + ''' tests for loading a pcdl.pyMCDS data set with settingxml none. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml=None, verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitVerboseTrue(object): - ''' tests for loading a pcdl.TimeStep data set and set_verbose_false function. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitVerboseTrue(object): + ''' tests for loading a pcdl.pyMCDS data set and set_verbose_false function. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_verbose_true(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (mcds.verbose) def test_mcds_set_verbose_false(self, mcds=mcds): mcds.set_verbose_false() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (not mcds.verbose) -class TestTimeStepInitVerboseFalse(object): - ''' tests for loading a pcdl.TimeStep data set and set_verbose_true function. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=False) +class TestPyMcdsInitVerboseFalse(object): + ''' tests for loading a pcdl.pyMCDS data set and set_verbose_true function. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=False) def test_mcds_verbose_false(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (not mcds.verbose) def test_mcds_set_verbose_true(self, mcds=mcds): mcds.set_verbose_true() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (mcds.verbose) ## metadata related functions ## -class TestTimeStepMetadata(object): - ''' tests for pcdl.TimeStep metadata related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsMetadata(object): + ''' tests for pcdl.pyMCDS metadata related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_get_multicellds_version(self, mcds=mcds): s_mcdsversion = mcds.get_multicellds_version() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(s_mcdsversion)) == "") and \ (s_mcdsversion == 'MultiCellDS_2') def test_mcds_get_physicell_version(self, mcds=mcds): s_pcversion = mcds.get_physicell_version() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(s_pcversion)) == "") and \ (s_pcversion == 'PhysiCell_1.14.1') def test_mcds_get_timestamp(self, mcds=mcds): s_timestamp = mcds.get_timestamp() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(s_timestamp)) == "") and \ (s_timestamp == '2025-01-05T08:08:22Z') def test_mcds_get_time(self, mcds=mcds): r_time = mcds.get_time() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(r_time)) == "") and \ (r_time == 1440.0) def test_mcds_get_runtime(self, mcds=mcds): r_runtime = mcds.get_runtime() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(r_runtime)) == "") and \ (r_runtime == 1.952156) ## setting related functions ## -class TestTimeStepSetting(object): - ''' tests for pcdl.TimeStep setting related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsSetting(object): + ''' tests for pcdl.pyMCDS setting related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_get_unit_dict(self, mcds=mcds): ds_unit = mcds.get_unit_dict() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ds_unit)) == "") and \ (len(ds_unit) == 108) and \ (ds_unit['oxygen'] == 'dimensionless') @@ -306,13 +294,13 @@ def test_mcds_get_unit_dict(self, mcds=mcds): ## mesh related functions ## -class TestTimeStepMesh(object): - ''' tests for pcdl.TimeStep mesh related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsMesh(object): + ''' tests for pcdl.pyMCDS mesh related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_get_voxel_ijk_range(self, mcds=mcds): lti_range = mcds.get_voxel_ijk_range() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(lti_range)) == "") and \ (str(type(lti_range[0])) == "") and \ (str(type(lti_range[0][0])) == "") and \ @@ -320,7 +308,7 @@ def test_mcds_get_voxel_ijk_range(self, mcds=mcds): def test_mcds_get_mesh_mnp_range(self, mcds=mcds): ltr_range = mcds.get_mesh_mnp_range() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ltr_range)) == "") and \ (str(type(ltr_range[0])) == "") and \ (str(type(ltr_range[0][0])) == "") and \ @@ -328,7 +316,7 @@ def test_mcds_get_mesh_mnp_range(self, mcds=mcds): def test_mcds_get_xyz_range(self, mcds=mcds): ltr_range = mcds.get_xyz_range() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ltr_range)) == "") and \ (str(type(ltr_range[0])) == "") and \ (str(type(ltr_range[0][0])) == "") and \ @@ -336,7 +324,7 @@ def test_mcds_get_xyz_range(self, mcds=mcds): def test_mcds_get_voxel_ijk_axis(self, mcds=mcds): lai_axis = mcds.get_voxel_ijk_axis() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(lai_axis)) == "") and \ (str(type(lai_axis[0])) == "") and \ (str(type(lai_axis[0][0])).startswith("") and \ + assert(str(type(mcds)) == "") and \ (str(type(lar_axis)) == "") and \ (str(type(lar_axis[0])) == "") and \ (str(type(lar_axis[0][0])) == "") and \ @@ -358,14 +346,14 @@ def test_mcds_get_mesh_mnp_axis(self, mcds=mcds): def test_mcds_get_mesh_flat_false(self, mcds=mcds): aar_mesh = mcds.get_mesh(flat=False) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(aar_mesh)) == "") and \ (aar_mesh.dtype == np.float64) and \ (aar_mesh.shape == (3, 11, 11, 1)) def test_mcds_get_mesh_flat_true(self, mcds=mcds): aar_mesh = mcds.get_mesh(flat=True) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(aar_mesh)) == "") and \ (aar_mesh.dtype == np.float64) and \ (aar_mesh.shape == (2, 11, 11)) @@ -373,7 +361,7 @@ def test_mcds_get_mesh_flat_true(self, mcds=mcds): def test_mcds_get_mesh_2d(self, mcds=mcds): aar_mesh_flat = mcds.get_mesh(flat=True) aar_mesh_2d = mcds.get_mesh_2D() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(aar_mesh_2d)) == "") and \ (aar_mesh_2d.dtype == np.float64) and \ (aar_mesh_2d.shape == (2, 11, 11)) @@ -386,7 +374,7 @@ def test_mcds_get_mesh_coordinate(self, mcds=mcds): er_p_cube = set(ar_p_cube.flatten()) # linear coordinates aar_voxel = mcds.get_mesh_coordinate() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(aar_voxel)) == "") and \ (aar_voxel.dtype == np.float64) and \ (aar_voxel.shape == (3, 121)) and \ @@ -396,30 +384,30 @@ def test_mcds_get_mesh_coordinate(self, mcds=mcds): def test_mcds_get_voxel_volume(self, mcds=mcds): r_volume = mcds.get_voxel_volume() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(r_volume)) == "") and \ (r_volume == 6000.0) # bue: check else in 3D def test_mcds_get_mesh_spacing(self, mcds=mcds): lr_spacing = mcds.get_mesh_spacing() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(lr_spacing)) == "") and \ (str(type(lr_spacing[0])) == "") and \ (str(type(lr_spacing[1])) == "") and \ (str(type(lr_spacing[-1])) == "") and \ - (lr_spacing == [30.0, 20.0, 10.0]) + (lr_spacing == [30.0, 20.0, 1.0]) def test_mcds_get_voxel_spacing(self, mcds=mcds): lr_spacing = mcds.get_voxel_spacing() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(lr_spacing)) == "") and \ (str(type(lr_spacing[0])) == "") and \ (str(type(lr_spacing[-1])) == "") and \ (lr_spacing == [30.0, 20.0, 10.0]) def test_mcds_is_in_mesh(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (mcds.is_in_mesh(x=0, y=0, z=0, halt=False)) and \ (not mcds.is_in_mesh(x=301, y=0, z=0, halt=False)) and \ (not mcds.is_in_mesh(x=0, y=201, z=0, halt=False)) and \ @@ -430,7 +418,7 @@ def test_mcds_get_mesh_mnp(self, mcds=mcds): li_mesh_1 = mcds.get_mesh_mnp(x=15, y=10, z=0, is_in_mesh=True) # if b_calc li_mesh_2 = mcds.get_mesh_mnp(x=30, y=20, z=0, is_in_mesh=True) # if b_calc li_mesh_none = mcds.get_mesh_mnp(x=-31, y=-21, z=-6, is_in_mesh=True) # else b_calc - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(li_mesh_0)) == "") and \ (str(type(li_mesh_0[0])) == "") and \ (li_mesh_0 == [-15.0, -10.0, 0.0]) and \ @@ -443,7 +431,7 @@ def test_mcds_get_voxel_ijk(self, mcds=mcds): li_voxel_1 = mcds.get_voxel_ijk(x=15, y=10, z=0, is_in_mesh=True) # if b_calc li_voxel_2 = mcds.get_voxel_ijk(x=30, y=20, z=0, is_in_mesh=True) # if b_calc li_voxel_none = mcds.get_voxel_ijk(x=-31, y=-21, z=-6, is_in_mesh=True) # else b_calc - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(li_voxel_0)) == "") and \ (str(type(li_voxel_0[0])) == "") and \ (li_voxel_0 == [0, 0, 0]) and \ @@ -454,63 +442,96 @@ def test_mcds_get_voxel_ijk(self, mcds=mcds): ## micro environment related functions ## -class TestTimeStepMicroenv(object): - ''' tests for pcdl.TimeStep micro environment related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsMicroenv(object): + ''' tests for pcdl.pyMCDS micro environment related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_get_substrate_name(self, mcds=mcds): ls_substrate = mcds.get_substrate_list() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ls_substrate)) == "") and \ (str(type(ls_substrate[0])) == "") and \ (ls_substrate == ['oxygen','water']) def test_mcds_get_substrate_dict(self, mcds=mcds): ds_substrate = mcds.get_substrate_dict() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ds_substrate)) == "") and \ (str(type(ds_substrate['0'])) == "") and \ (len(ds_substrate) == 2) def test_mcds_get_substrate_df(self, mcds=mcds): df_substrate = mcds.get_substrate_df() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_substrate)) == "") and \ (df_substrate.shape == (2, 2)) + def test_mcds_get_concentration_zslice_none(self, mcds=mcds): + ar_conc = mcds.get_concentration(substrate='oxygen', z_slice=None) + assert(str(type(mcds)) == "") and \ + (str(type(ar_conc)) == "") and \ + (ar_conc.dtype == np.float64) and \ + (ar_conc.shape == (11, 11, 1)) + + def test_mcds_get_concentration_zslice_meshcenter(self, mcds=mcds): + ar_conc = mcds.get_concentration(substrate='oxygen', z_slice=0, halt=False) + assert(str(type(mcds)) == "") and \ + (str(type(ar_conc)) == "") and \ + (ar_conc.dtype == np.float64) and \ + (ar_conc.shape == (11, 11)) + + def test_mcds_get_concentration_zslice_notmeshcenter(self, mcds=mcds): + ar_conc = mcds.get_concentration(substrate='oxygen', z_slice=-3.333, halt=False) + assert(str(type(mcds)) == "") and \ + (str(type(ar_conc)) == "") and \ + (ar_conc.dtype == np.float64) and \ + (ar_conc.shape == (11, 11)) + + def test_mcds_get_concentration_at_inmeash(self, mcds=mcds): + ar_conc = mcds.get_concentration_at(x=0, y=0, z=0) + assert(str(type(mcds)) == "") and \ + (str(type(ar_conc)) == "") and \ + (ar_conc.dtype == np.float64) and \ + (ar_conc.shape == (2,)) + + def test_mcds_get_concentration_at_notinmeash(self, mcds=mcds): + ar_conc = mcds.get_concentration_at(x=-31, y=-21, z=-6) + assert(str(type(mcds)) == "") and \ + (ar_conc is None) + def test_mcds_get_conc_df(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_zslice_center(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=0, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_zslice_outofcenter(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=-6, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_values(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=2, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_drop(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop={'oxygen'}, keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 10)) def test_mcds_get_conc_df_keep(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop=set(), keep={'oxygen'}) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 10)) @@ -533,7 +554,7 @@ def test_mcds_plot_contour(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not at file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -557,15 +578,15 @@ def test_mcds_plot_contourf(self, mcds=mcds): ext = 'tiff', # test file case figbgcolor = 'yellow', # jump over if ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/conc_oxygen_z0/output00000024_oxygen.tiff')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) def test_mcds_make_conc_vtk(self, mcds=mcds): - s_pathfile = mcds.make_conc_vtk() - assert(str(type(mcds)) == "") and \ + s_pathfile = mcds.make_conc_vtk(visualize=False) + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/output00000024_conc.vtr')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -573,59 +594,71 @@ def test_mcds_make_conc_vtk(self, mcds=mcds): ## cell related functions ## -class TestTimeStepCell(object): - ''' tests for pcdl.TimeStep cell related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsCell(object): + ''' tests for pcdl.pyMCDS cell related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_get_cell_attribute_list(self, mcds=mcds): ls_cellattr = mcds.get_cell_attribute_list() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ls_cellattr)) == "") and \ (str(type(ls_cellattr[0])) == "") and \ (len(ls_cellattr) == 110) def test_mcds_get_celltype_list(self, mcds=mcds): ls_celltype = mcds.get_celltype_list() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ls_celltype)) == "") and \ (str(type(ls_celltype[0])) == "") and \ (len(ls_celltype) == 2) def test_mcds_get_celltype_dict(self, mcds=mcds): ds_celltype = mcds.get_celltype_dict() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ds_celltype)) == "") and \ (str(type(ds_celltype['0'])) == "") and \ (len(ds_celltype) == 2) def test_mcds_get_cell_df(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_get_cell_df_values(self, mcds=mcds): df_cell = mcds.get_cell_df(values=2, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 66) def test_mcds_get_cell_df_drop(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop={'oxygen'}, keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 121) def test_mcds_get_cell_df_keep(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop=set(), keep={'oxygen'}) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 13) + def test_mcds_get_cell_df_at_inmeash(self, mcds=mcds): + df_cell = mcds.get_cell_df_at(x=0, y=0, z=0, values=1, drop=set(), keep=set()) + assert(str(type(mcds)) == "") and \ + (str(type(df_cell)) == "") and \ + (df_cell.shape[0] > 0) and \ + (df_cell.shape[1] == 122) + + def test_mcds_get_cell_df_at_notinmeash(self, mcds=mcds): + df_cell = mcds.get_cell_df_at(x=-31, y=-21, z=-6, values=1, drop=set(), keep=set()) + assert(str(type(mcds)) == "") and \ + (df_cell is None) + # scatter categorical def test_mcds_plot_scatter_cat_if(self, mcds=mcds): fig = mcds.plot_scatter( @@ -646,7 +679,7 @@ def test_mcds_plot_scatter_cat_if(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -669,7 +702,7 @@ def test_mcds_plot_scatter_cat_else1(self, mcds=mcds): ext = 'tiff', # test file case figbgcolor = 'cyan', # jump over if ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/cell_cell_type_z0/output00000024_cell_type.tiff')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -695,7 +728,7 @@ def test_mcds_plot_scatter_cat_else2(self, mcds=mcds): #ext = None, # test fig case #figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -719,7 +752,7 @@ def test_mcds_plot_scatter_num_if(self, mcds=mcds): #ext = None, # test fig case #figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -742,23 +775,24 @@ def test_mcds_plot_scatter_num_else(self, mcds=mcds): #ext = None, # test fig case #figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() def test_mcds_make_cell_vtk_attribute_default(self, mcds=mcds): s_pathfile = mcds.make_cell_vtk( #attribute=['cell_type'], + visualize=False, ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) def test_mcds_make_cell_vtk_attribute_zero(self, mcds=mcds): - s_pathfile = mcds.make_cell_vtk(attribute=[]) - assert(str(type(mcds)) == "") and \ + s_pathfile = mcds.make_cell_vtk(attribute=[], visualize=False) + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -767,8 +801,9 @@ def test_mcds_make_cell_vtk_attribute_zero(self, mcds=mcds): def test_mcds_make_cell_vtk_attribute_many(self, mcds=mcds): s_pathfile = mcds.make_cell_vtk( attribute=['dead', 'cell_count_voxel', 'pressure', 'cell_type'], + visualize=False, ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_2d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -777,24 +812,24 @@ def test_mcds_make_cell_vtk_attribute_many(self, mcds=mcds): ## graph related functions ## -class TestTimeStepGraph(object): - ''' tests for pcdl.TimeStep graph related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsGraph(object): + ''' tests for pcdl.pyMCDS graph related functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) # graph dictionatry def test_mcds_get_attached_graph_dict(self, mcds=mcds): - dei_graph = mcds.data['cell']['dei_graph']['attached_cells'] + dei_graph = mcds.data['discrete_cells']['graph']['attached_cells'] #print('graph attached:', sorted(dei_graph.items())) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(dei_graph)) == "") and \ (str(type(dei_graph[10])) == "") and \ (len(dei_graph[10]) == 0) and \ (len(dei_graph) > 9) def test_mcds_get_neighbor_graph_dict(self, mcds=mcds): - dei_graph = mcds.data['cell']['dei_graph']['neighbor_cells'] + dei_graph = mcds.data['discrete_cells']['graph']['neighbor_cells'] #print('graph neighbor:', sorted(dei_graph.items())) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(dei_graph)) == "") and \ (str(type(dei_graph[10])) == "") and \ (len(dei_graph[10]) == 6) and \ @@ -807,7 +842,7 @@ def test_mcds_make_graph_gml_attached_defaultattr(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_attached.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -822,7 +857,7 @@ def test_mcds_make_graph_gml_attached_edgeattrfalse(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_attached.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -837,7 +872,7 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -857,7 +892,7 @@ def test_mcds_make_graph_gml_neighbor_defaultattr(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -872,7 +907,7 @@ def test_mcds_make_graph_gml_neighbor_edgeattrfalse(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -887,7 +922,7 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -900,174 +935,3 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): (s_file.find('edge [\n source') > -1) and \ (s_file.find('distance_microns') > -1) os.remove(s_pathfile) - - -## ome tiff related functions ## - -class TestTimeStepOmeTiff(object): - ''' tests for pcdl.TimeStep graph related functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) - - ## ome tiff related functions ## - def test_mcds_make_ome_tiff_default(self, mcds=mcds): - s_pathfile = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True) - assert(str(type(mcds)) == "") and \ - (s_pathfile.replace('\\','/').endswith('pcdl/output_2d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10) - os.remove(s_pathfile) - - def test_mcds_make_ome_tiff_bool(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='dead', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_int(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='cell_count_voxel', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_float(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='pressure', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and\ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_conccutoff(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={'oxygen': -1}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 1, 200, 300)) and \ - (a_ometiff[2].min() == 0.0) and \ - (a_ometiff[3].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) and \ - (a_ometiff[1].max() >= 1.0) and \ - (a_ometiff[2].max() >= 1.0) and \ - (a_ometiff[3].max() >= 1.0) - - def test_mcds_make_ome_tiff_focus(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus={'default'}, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (1, 1, 200, 300)) and \ - (a_ometiff[0].min() == 0.0) and \ - (a_ometiff[0].max() >= 1.0) - - -class TestTimeStepNeuroglancer(object): - ''' tests for loading a pcdl.TimeStep data set. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_2d, output_path=s_path_2d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) - - ## make_gif and magick ommand ## - def test_mcds_render_neuroglancer_default(self, mcds=mcds): - s_tiffpathfile = mcds.make_ome_tiff() - o_viewer = mcds.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - #timestep = 0, - #intensity_cmap='gray', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - def test_mcds_render_neuroglancer_timestep(self, mcds=mcds): - s_tiffpathfile = mcds.make_ome_tiff() - o_viewer = mcds.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - timestep = -1, - intensity_cmap='gray', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - def test_mcds_render_neuroglancer_cmap(self, mcds=mcds): - s_tiffpathfile = mcds.make_ome_tiff() - o_viewer = mcds.render_neuroglancer( - tiffpathfile = s_tiffpathfile, - timestep = 0, - intensity_cmap='magma', - ) - assert(str(type(o_viewer)) == "") and \ - (str(o_viewer).startswith('http://127.0.0.1:')) - os.remove(s_tiffpathfile) - - -## anndata helper function ## -class TestTimeStepScaler(object): - ''' test for pcdl.scaler function ''' - a_x = np.array([[ 1.,-1., 2., 0.],[ 2., 0., 0.,0.],[ 0., 1.,-1.,0.]]) - df_x = pd.DataFrame(a_x, columns=['a','b','c','d']) - - def test_scaler_none(self, df_x=df_x): - df_scaled = pcdl.timestep.scaler(df_x=df_x, scale=None) - assert(str(type(df_scaled)) == "") and \ - (all(df_scaled == df_x)) - - def test_scaler_minabs(self, df_x=df_x): - df_scaled = pcdl.timestep.scaler(df_x=df_x, scale='maxabs') - assert(str(type(df_scaled)) == "") and \ - (df_scaled.values.sum().round(3) == 2.0) and \ - (df_scaled.values.min().round(3) == -1.0) and \ - (df_scaled.values.max().round(3) == 1.0) - - def test_scaler_minmax(self, df_x=df_x): - df_scaled = pcdl.timestep.scaler(df_x=df_x, scale='minmax') - assert(str(type(df_scaled)) == "") and \ - (df_scaled.values.sum().round(3) == 4.333) and \ - (df_scaled.values.min().round(3) == 0.0) and \ - (df_scaled.values.max().round(3) == 1.0) - - def test_scaler_std(self, df_x=df_x): - df_scaled = pcdl.timestep.scaler(df_x=df_x, scale='std') - assert(str(type(df_scaled)) == "") and \ - (df_scaled.values.sum().round(3) == 0.0) and \ - (df_scaled.values.min().round(3) == -1.0) and \ - (df_scaled.values.max().round(3) == 1.091) - - -## anndata time step related functions ## -class TestTimeStepAnnData(object): - ''' test for pcdl.TimeStep class. ''' - - ## get_anndata command ## - def test_mcds_get_anndata(self): - mcds = pcdl.TimeStep(s_pathfile_2d, verbose=False) - ann = mcds.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs') - assert(str(type(mcds)) == "") and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 2) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - diff --git a/test/test_timestep_3d.py b/test/test_timestep_3d.py index 8b370dd..4af50c5 100644 --- a/test/test_timestep_3d.py +++ b/test/test_timestep_3d.py @@ -7,7 +7,7 @@ # license: BSD 3-Clause # # description: -# pytest unit test library for the pcdl library TimeStep class. +# pytest unit test library for the pcdl library pyMCDS class. # focus is only in 3d and speed. # + https://docs.pytest.org/ # @@ -22,7 +22,6 @@ # load library import numpy as np import os -import pandas as pd import pathlib import pcdl import matplotlib.pyplot as plt @@ -43,15 +42,15 @@ # 3D only # ########### -class TestTimeStep3dOnly(object): - ''' test for 3D only conditions in pcdl.TimeStep functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True +class TestPyMcds3dOnly(object): + ''' test for 3D only conditions in pcdl.pyMCDS functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True ## mesh related functions ## # bue: check if in 2D def test_mcds_get_mesh_spacing(self, mcds=mcds): lr_spacing = mcds.get_mesh_spacing() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(lr_spacing)) == "") and \ (str(type(lr_spacing[0])) == "") and \ (str(type(lr_spacing[1])) == "") and \ @@ -63,180 +62,180 @@ def test_mcds_get_mesh_spacing(self, mcds=mcds): # test workhorse for speed # ############################ -class TestTimeStepInit(object): - ''' tests for loading a pcdl.TimeStep data set. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInit(object): + ''' tests for loading a pcdl.pyMCDS data set. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitMicroenvFalse(object): - ''' tests for loading a pcdl.TimeStep data set with microenv false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=False, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitMicroenvFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with microenv false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=False, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 116) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 116) -class TestTimeStepInitGraphFalse(object): - ''' tests for loading a pcdl.TimeStep data set with graph false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=False, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitGraphFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with graph false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=False, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) == 0) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) == 0) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) == 0) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) == 0) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -class TestTimeStepInitPhysibossFalse(object): - ''' tests for loading a pcdl.TimeStep data set with physiboss false. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=False, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitPhysibossFalse(object): + ''' tests for loading a pcdl.pyMCDS data set with physiboss false. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=False, settingxml='PhysiCell_settings.xml', verbose=True) df_cell = mcds.get_cell_df() def test_mcds_init_microenv(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_init_graph(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['attached_cells'])) == "") and \ - (str(type(mcds.data['cell']['dei_graph']['neighbor_cells'])) == "") and \ - (len(mcds.data['cell']['dei_graph']['attached_cells']) > 9) and \ - (len(mcds.data['cell']['dei_graph']['neighbor_cells']) > 9) + assert(str(type(mcds)) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['attached_cells'])) == "") and \ + (str(type(mcds.data['discrete_cells']['graph']['neighbor_cells'])) == "") and \ + (len(mcds.data['discrete_cells']['graph']['attached_cells']) > 9) and \ + (len(mcds.data['discrete_cells']['graph']['neighbor_cells']) > 9) - #def test_mcds_init_physiboss(self, mcds=mcds): - # assert(str(type(mcds)) == "") and \ - # (mcds.data['cell']['physiboss'] == None) + def test_mcds_init_physiboss(self, mcds=mcds): + assert(str(type(mcds)) == "") and \ + (mcds.data['discrete_cells']['physiboss'] == None) def test_mcds_init_settingxml(self, mcds=mcds, df_cell=df_cell): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (set(df_cell.columns).issuperset({'default_fusion_rates'})) and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) -#class TestTimeStepInitSettingxmlFalse(object): -# ''' tests for loading a pcdl.TimeStep data set with settingxml false. ''' +#class TestPyMcdsInitSettingxmlFalse(object): +# ''' tests for loading a pcdl.pyMCDS data set with settingxml false. ''' # NOP PhysiCell >= v1.14.0 -#class TestTimeStepInitSettingxmlNone(object): -# ''' tests for loading a pcdl.TimeStep data set with settingxml none. ''' +#class TestPyMcdsInitSettingxmlNone(object): +# ''' tests for loading a pcdl.pyMCDS data set with settingxml none. ''' # NOP PhysiCell >= v1.14.0 -class TestTimeStepInitVerboseTrue(object): - ''' tests for loading a pcdl.TimeStep data set and set_verbose_false function. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) +class TestPyMcdsInitVerboseTrue(object): + ''' tests for loading a pcdl.pyMCDS data set and set_verbose_false function. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True) def test_mcds_verbose_true(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (mcds.verbose) def test_mcds_set_verbose_false(self, mcds=mcds): mcds.set_verbose_false() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (not mcds.verbose) -class TestTimeStepInitVerboseFalse(object): - ''' tests for loading a pcdl.TimeStep data set and set_verbose_true function. ''' - mcds = pcdl.TimeStep(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=False) +class TestPyMcdsInitVerboseFalse(object): + ''' tests for loading a pcdl.pyMCDS data set and set_verbose_true function. ''' + mcds = pcdl.pyMCDS(xmlfile=s_file_3d, output_path=s_path_3d, custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=False) def test_mcds_verbose_false(self, mcds=mcds): - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (not mcds.verbose) def test_mcds_set_verbose_true(self, mcds=mcds): mcds.set_verbose_true() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (mcds.verbose) -class TestTimeStep3dSettingWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.TimeStep unit related workhorse functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True +class TestPyMcds3dSettingWorkhorse(object): + ''' tests on 3D data set, for speed, for pcdl.pyMCDS unit related workhorse functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True def test_mcds_get_unit_dict(self, mcds=mcds): ds_unit = mcds.get_unit_dict() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(ds_unit)) == "") and \ (len(ds_unit) > 9) and \ (ds_unit['oxygen'] == 'dimensionless') -class TestTimeStep3dMicroenvWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.TimeStep microenvironment related workhorse functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True +class TestPyMcds3dMicroenvWorkhorse(object): + ''' tests on 3D data set, for speed, for pcdl.pyMCDS microenvironment related workhorse functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True ## micro environment related functions ## def test_mcds_get_conc_df(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (1331, 11)) #(df_conc.shape[0] > 9) and \ @@ -244,31 +243,31 @@ def test_mcds_get_conc_df(self, mcds=mcds): def test_mcds_get_conc_df_zslice_center(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=0, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_zslice_outofcenter(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=-6, halt=False, values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (121, 11)) def test_mcds_get_conc_df_values(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=2, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (1331, 11)) def test_mcds_get_conc_df_drop(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop={'oxygen'}, keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (1331, 10)) def test_mcds_get_conc_df_keep(self, mcds=mcds): df_conc = mcds.get_conc_df(z_slice=None, halt=False, values=1, drop=set(), keep={'oxygen'}) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_conc)) == "") and \ (df_conc.shape == (1331, 10)) @@ -292,7 +291,7 @@ def test_mcds_plot_contour(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not at file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -315,50 +314,50 @@ def test_mcds_plot_contourf(self, mcds=mcds): ext = 'tiff', # test file case figbgcolor = 'orange', # jump over if ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/conc_oxygen_z-5.0/output00000024_oxygen.tiff')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) def test_mcds_make_conc_vtk(self, mcds=mcds): - s_pathfile = mcds.make_conc_vtk() - assert(str(type(mcds)) == "") and \ + s_pathfile = mcds.make_conc_vtk(visualize=False) + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/output00000024_conc.vtr')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) -class TestTimeStep3dCellWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.TimeStep cell related workhorse functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True +class TestPyMcds3dCellWorkhorse(object): + ''' tests on 3D data set, for speed, for pcdl.pyMCDS cell related workhorse functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True ## cell related functions ## def test_mcds_get_cell_df(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 122) def test_mcds_get_cell_df_values(self, mcds=mcds): df_cell = mcds.get_cell_df(values=2, drop=set(), keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 72) def test_mcds_get_cell_df_drop(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop={'oxygen'}, keep=set()) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 121) def test_mcds_get_cell_df_keep(self, mcds=mcds): df_cell = mcds.get_cell_df(values=1, drop=set(), keep={'oxygen'}) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(df_cell)) == "") and \ (df_cell.shape[0] > 9) and \ (df_cell.shape[1] == 13) @@ -384,7 +383,7 @@ def test_mcds_plot_scatter_cat_if(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -407,7 +406,7 @@ def test_mcds_plot_scatter_cat_else1(self, mcds=mcds): ext = 'tiff', # test file case figbgcolor = 'lime', # jump over if ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/cell_cell_type_z-5.0/output00000024_cell_type.tiff')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -433,7 +432,7 @@ def test_mcds_plot_scatter_cat_else2(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -457,7 +456,7 @@ def test_mcds_plot_scatter_num_if(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() @@ -480,23 +479,24 @@ def test_mcds_plot_scatter_num_else(self, mcds=mcds): ext = None, # test fig case figbgcolor = None, # not a file ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (str(type(fig)) == "") plt.close() def test_mcds_make_cell_vtk_attribute_default(self, mcds=mcds): s_pathfile = mcds.make_cell_vtk( #attribute=['cell_type'], + visualize=False, ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) def test_mcds_make_cell_vtk_attribute_zero(self, mcds=mcds): - s_pathfile = mcds.make_cell_vtk(attribute=[]) - assert(str(type(mcds)) == "") and \ + s_pathfile = mcds.make_cell_vtk(attribute=[], visualize=False) + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) @@ -505,17 +505,18 @@ def test_mcds_make_cell_vtk_attribute_zero(self, mcds=mcds): def test_mcds_make_cell_vtk_attribute_many(self, mcds=mcds): s_pathfile = mcds.make_cell_vtk( attribute=['dead', 'cell_count_voxel', 'pressure', 'cell_type'], + visualize=False, ) - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('/pcdl/output_3d/output00000024_cell.vtp')) and \ (os.path.exists(s_pathfile)) and \ (os.path.getsize(s_pathfile) > 2**10) os.remove(s_pathfile) -class TestTimeStep3dGraphWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.TimeStep graph related workhorse functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True +class TestPyMcds3dGraphWorkhorse(object): + ''' tests on 3D data set, for speed, for pcdl.pyMCDS graph related workhorse functions. ''' + mcds = pcdl.pyMCDS(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True ## graph related functions ## # attached graph gml files @@ -524,7 +525,7 @@ def test_mcds_make_graph_gml_attached_defaultattr(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_attached.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -539,7 +540,7 @@ def test_mcds_make_graph_gml_attached_edgeattrfalse(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_attached.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -554,7 +555,7 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -574,7 +575,7 @@ def test_mcds_make_graph_gml_neighbor_defaultattr(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -589,7 +590,7 @@ def test_mcds_make_graph_gml_neighbor_edgeattrfalse(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -604,7 +605,7 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): f = open(s_pathfile) s_file = f.read() f.close() - assert(str(type(mcds)) == "") and \ + assert(str(type(mcds)) == "") and \ (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_neighbor.gml')) and \ (os.path.exists(s_pathfile)) and \ (s_file.find('Creator "pcdl_v') > -1) and \ @@ -617,46 +618,3 @@ def test_mcds_make_graph_gml_neighbor_nodeattrtrue(self, mcds=mcds): (s_file.find('edge [\n source') > -1) and \ (s_file.find('distance_microns') > -1) os.remove(s_pathfile) - - -class TestTimeStep3dOmeTiffWorkhorse(object): - ''' tests on 3D data set, for speed, for pcdl.TimeStep ome tiff related workhorse functions. ''' - mcds = pcdl.TimeStep(xmlfile=s_pathfile_3d) # custom_data_type={}, microenv=True, graph=True, physiboss=True, settingxml='PhysiCell_settings.xml', verbose=True - - ## ome tiff related functions ## - def test_mcds_make_ome_tiff_default(self, mcds=mcds): - s_pathfile = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=True) - assert(str(type(mcds)) == "") and \ - (s_pathfile.replace('\\','/').endswith('pcdl/output_3d/output00000024_oxygen_water_default_blood_cells_ID.ome.tiff')) and \ - (os.path.exists(s_pathfile)) and \ - (os.path.getsize(s_pathfile) > 2**10) - os.remove(s_pathfile) - - def test_mcds_make_ome_tiff_nofile(self, mcds=mcds): - a_ometiff = mcds.make_ome_tiff(cell_attribute='ID', conc_cutoff={}, focus=None, file=False) - assert(str(type(mcds)) == "") and \ - (str(type(a_ometiff)) == "") and \ - (a_ometiff.dtype == np.float32) and \ - (a_ometiff.shape == (4, 11, 200, 300)) - - -## anndata time step related functions ## -class TestTimeStep3dAnnData(object): - ''' test for pcdl.TimeStep class. ''' - - ## get_anndata command ## - def test_mcds_get_anndata(self): - mcds = pcdl.TimeStep(s_pathfile_3d, verbose=False) - ann = mcds.get_anndata(values=1, drop=set(), keep=set(), scale='maxabs') - assert(str(type(mcds)) == "") and \ - (str(type(ann)) == "") and \ - (ann.X.shape[0] > 9) and \ - (ann.X.shape[1] == 105) and \ - (ann.obs.shape[0] > 9) and \ - (ann.obs.shape[1] == 7) and \ - (ann.obsm['spatial'].shape[0] > 9) and \ - (ann.obsm['spatial'].shape[1] == 3) and \ - (len(ann.obsp) == 2) and \ - (ann.var.shape == (105, 0)) and \ - (len(ann.uns) == 1) - From 7d01a4fcff5671c188db422972daa56a717fe58b Mon Sep 17 00:00:00 2001 From: bue Date: Wed, 23 Jul 2025 17:58:11 -0400 Subject: [PATCH 41/41] @ pcdl v3 : update python compatibility to not yet end-of-life cycle versions. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a057e59..039b19b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dynamic = ["version"] description = "physicell data loader (pcdl) provides a platform independent, python3 based, pip installable interface to load output, generated with the PhysiCell agent based modeling framework, into python3." readme = "README.md" -requires-python = ">=3.8, <4" +requires-python = ">=3.9, <4" license = "BSD-3-Clause" #license-files = {paths = ["LICENSE"]}