Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating MkDocs #38

Merged
merged 10 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,26 @@ jobs:
with:
python-version: '3.10'

- name: Install Poetry
uses: Gr1N/setup-poetry@v8
with:
poetry-version: '1.8.3'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .[tests,docs]
run: poetry install --extras "tests docs"

- name: Update Sphinx htmls
- name: Build MkDocs documentation
run: |
make html
working-directory: docs
poetry run mkdocs build
working-directory: docs-mk
MohamedNasser8 marked this conversation as resolved.
Show resolved Hide resolved

- name: Publish to gh-pages
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with:
publish_branch: gh-pages # default: gh-pages
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/build/html/
publish_dir: docs-mk/site
force_orphan: true

- run: echo "Status of job = ${{ job.status }}."
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ junit
docs/build
docs/source/generated
docs/source/sg_execution_times.rst
docs-mk/site
docs-mk/docs/generated
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ We welcome contributions to OSIPI! To contribute, follow these steps:
- Make your changes and commit them with descriptive messages.
- Push your changes to your fork.
- Submit a pull request to the main OSIPI repository.

For more details on how to contribute, visit the [Developer Guide](https://osipi.github.io/pypi/developers_guide/index.html).
#### As mentioned before, this project is still in the early stages of development. If you'd like to contribute by adding functionality, we recommend opening an issue first to discuss your proposed functionality and the best ways to implement it.

## Details
Expand Down
12 changes: 12 additions & 0 deletions docs-mk/docs/about/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# About

osipi is developed by the **Open Source Initiative for Perfusion Imaging (OSIPI)**, an initiative of the perfusion study group of the **International Society for Magnetic Resonance in Medicine (ISMRM)**.
MohamedNasser8 marked this conversation as resolved.
Show resolved Hide resolved

The osipi package structure and logic follow the lexicon defined by OSIPI, and wrap around selected implementations collected in the code contributions of OSIPI.

## Scope

osipi currently only includes methods for the dynamic contrast (DC) approach to perfusion MRI (DC-MRI, a unifying term for the separate fields DCE-MRI and DSC-MRI). In particular, arterial spin labelling (ASL) solutions are not currently included, but this may change in the future.

!!! note "Future Updates"
The inclusion of arterial spin labelling (ASL) solutions may change in the future.
120 changes: 120 additions & 0 deletions docs-mk/docs/contribution/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Developer Guide

There are multiple ways to contribute to `osipi` and we welcome them all. `osipi` is a tool developed by the research community for the research community, and we are all responsible for ensuring it is as good as it can be. So, if you feel some part of it is not, fix it!

The way to do this is by making a pull request on GitHub. If you are not familiar with GitHub pull requests, it is not as scary as it sounds. The simplest way is to find the file that you want to edit on GitHub in your browser, edit it manually and follow the prompts to create a fork and pull request.

## Making a Pull Request (PR) to the OSIPI Package
### Step 1: Fork the Repository
First, fork the OSIPI repository to your GitHub account and make a new branch with the your new feature or bug fix.

### Step 2: Set Up Your Development Environment
After creating a fork, you have two options:
#### Option 1: Use Poetry

1. **Install Poetry**: If you don't have Poetry installed, follow the instructions [here](https://python-poetry.org/docs/#installation).
2. **Install Dependencies**: Run the following command in your terminal:
```sh
poetry install
```
#### Option 2: Use Requirements File

1. **Install Dependencies**: Run the following command in your terminal:
```sh
pip install -r requirements.txt
```

### Step 3: Make Your Contribution
Make your changes or add your contribution to the codebase.

### Step 4: Run Pre-commit Checks
Before pushing your changes, run pre-commit checks to ensure your code meets the project's standards.

1. Run the following command in your terminal:
```sh
pre-commit run --all-files
```

### Step 5: Push Your Changes and Create a PR
1. Push your changes to your forked repository.
2. Create a pull request (PR) to the main OSIPI repository.


## How to Contribute Examples

One way to contribute is by providing examples of how you used `osipi` for a specific task. These are usually real-world examples with a relevant aim, perhaps to derive some results that you have published. To package these up as an example, follow these steps:

1. Code up your example in a single Python file in a narrative style, similar to a notebook. Have a look at the current examples to see how these need to be formatted, especially their docstrings, to make sure they show up properly on the website.
2. When you save your file, make sure the filename starts with *plot_*.
3. Then drop your file in the examples folder `osipi-docs-examples` in the appropriate subfolder.

When the documentation is generated, your example will automatically appear in the examples gallery and also in the documentation of any function you are using in the example.

## How to Contribute Documentation

`osipi` is a user interface and for that reason good, clear and well-structured documentation is equally important as the quality of the functionality itself. We especially welcome suggestions for improving the documentation from end-users who are not necessarily contributing new code. You know best what works, and what doesn't.

If you are a user of `osipi`, and some part of the documentation is not as clear as it can be, then submit your suggestions for improvement and make sure that the next person will not have to face the same issues.

If you want to edit the documentation of a specific function, then you need to find the function in the osipi source code `osipi-src`. Find the function and edit the documentation string immediately below its definition. If you want to edit any other part of the documentation, find the appropriate file in the documentation source code `osipi-docs-source` and edit it there.

## How to Contribute Tests

Beyond documentation and functionality, solid testing is equally critical for ensuring long-term stability of a package. `osipi` uses a continuous integration model where all tests are run before each push to the central repository. This is important because often changes at one part of the code, even if tested well locally, can have unintended consequences at other parts. The testing prevents that these errors propagate and destabilize parts of the package.

If you find a bug in any part of the code, this obviously points to a flaw in the code, but it also reveals a gap in the testing. It is critical when this happens that both the code AND the tests are reviewed to ensure that in future a scenario of this type is picked up during testing.

The tests are defined in the folder `osipi-tests`.

## How to Contribute Functionality

OSIPI is always happy to receive new functionality for inclusion in the `osipi` package. This can be code that addresses a gap in the current functionality, or it can be code that improves the performance of a current implementation. Improvements can consist of extending the functionality (e.g. with new optional arguments), user friendliness or consistency, improvement of the accuracy or precision in the results, computation time, or platform independence, or improved documentation or code structure.

Contribution of functionality generally proceeds in two steps. In the first step you submit your code to the primary *contributions* repository as explained in its [wiki](https://github.com/OSIPI/DCE-DSC-MRI_CodeCollection/wiki/How-to-contribute-code). The task force will catalogue your code in the contributions repository and test it as explained in the [guidance](https://github.com/OSIPI/DCE-DSC-MRI_CodeCollection/wiki/The-testing-approach). Afterwards, if it is found to address a gap in `osipi` and/or improve existing functionality, you will be invited to submit a pull request to `osipi` containing your contribution formatted as required by the package.

While this is the general process, we accept there may be situations where a new submission to the contributions repository is overkill, for instance if your improvement concerns documentation only, or improvements in code structure or style. In that case a direct pull request to `osipi` may be acceptable - when in doubt please contact the OSIPI repository lead in the first instance to avoid unnecessary work.

See the section on design principles below for general requirements from `osipi` code snippets.

## How to Contribute Issues

If you have a constructive suggestion for how `osipi` can be improved, but you are not able to address it yourself for some reason, it is still extremely helpful if you write this up as an issue so it can be picked up by others at a later stage. To write up an issue, go to the `osipi` repository on GitHub, select `issues` and write a new one. Make sure to provide sufficient detail so that others can understand and address the issue.

## Design Principles

### Style Guide

`osipi` follows the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html). This means especially also that we expect proper attention to error handling. Consider for instance what happens if a user calls your snippet using arguments of incorrect type or length. Will they get an appropriate error message that will help them identify and fix the error?

### Package Structure

The `osipi` documentation follows the structure of the OSIPI Lexicon exactly - see [here](https://osipi.github.io/OSIPI_CAPLEX/) for a detailed description of the Lexicon.

From a user perspective, the package structure is a flat list of functions that can all be accessed as `osipi.some_function`. They are listed in the __init__ file of the package, directly under the folder `src\osipi`. For clarity, the code itself is organized into modules, but these may evolve over time and should not be accessed directly. Module names all start with an underscore `_module.py` to emphasize their private and transient nature. Equally, subfolders may be added in the future as the package grows.



### Code Snippets

`osipi` is a collection of *simple* code snippets following a *simple* functional programming paradigm (did you see how we said *simple* twice there?). Each code snippet is a Python function that takes OSIPI variables as arguments and returns other OSIPI variables as results. At this stage, we are *not* planning to include an object-oriented interface or internal logic as this reduces the modularity of the code snippets, reduces compatibility with other packages, and increases the overhead of learning how to use `osipi`. Therefore, all code contributions will essentially consist of a new function or an improvement of an existing function.

Beyond the general requirements of the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html), `osipi` requires that each new function is accompanied by an appropriate test in the tests folder, and that each function fully conforms to the Lexicon. In particular:

1. Each function must be defined in the lexicon, and the docstring must include a reference section containing the following four items:
- Lexicon URL: webpage in the Lexicon where the function is defined.
- Lexicon code: machine-readable code identifying the entry in the Lexicon.
- OSIPI name: human-readable name for the function as defined in the Lexicon.
- Adapted from contribution: module.py in the original snippet in the code contribution repository
2. Each argument to the function as well as each return value *must* be defined in the Lexicon. The docstring of the function must provide the following data on each argument and return value:
- Python data type (include type hint in the function definition)
- Lexicon code: machine-readable code identifying the corresponding quantity in the Lexicon.
- OSIPI name: human-readable name for the quantity as defined in the Lexicon.
3. All arguments and return values must be provided in OSIPI units as defined in the Lexicon.
4. Arguments should be provided using OSIPI notation as defined in the Lexicon.
5. The docstring of the function must contain a self-contained code example that runs the function and illustrates the output.

!!!note
If your function addresses entirely novel functionality or uses new variables that are not yet described in the Lexicon, then you should first contact the Lexicon maintainers and request that it is added as an entry to the Lexicon. Only afterwards can it be considered as a contribution to the `osipi` package.

!!!note
The original [library for code contributions](https://github.com/OSIPI/DCE-DSC-MRI_CodeCollection/wiki/How-to-contribute-code) is less stringent as to code structure and documentation or testing requirements. However, it is nevertheless advisable to adhere to the same guidelines when submitting code to the original contributions repository as this will make it easier for testers to understand and run your code, and it will reduce the overhead on your part in rewriting the code
5 changes: 5 additions & 0 deletions docs-mk/docs/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

# Examples


### Illustrating common use cases of osipi.
3 changes: 3 additions & 0 deletions docs-mk/docs/examples/aif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
************************
Arterial Input Functions
************************
52 changes: 52 additions & 0 deletions docs-mk/docs/examples/aif/plot_aif_parker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
======================================
The Parker AIF - a play with variables
======================================

Simulating a Parker AIF with different settings.

"""

import matplotlib.pyplot as plt

# %%
# Import necessary packages
import numpy as np
import osipi

# %%
# Generate synthetic AIF with default settings and plot the result.

# Define time points in units of seconds - in this case we use a time
# resolution of 0.5 sec and a total duration of 6 minutes.
t = np.arange(0, 6 * 60, 0.5)

# Create an AIF with default settings
ca = osipi.aif_parker(t)

# Plot the AIF over the full range
plt.plot(t, ca, "r-")
plt.plot(t, 0 * t, "k-")
plt.xlabel("Time (sec)")
plt.ylabel("Plasma concentration (mM)")
plt.show()

# %%
# The bolus arrival time (BAT) defaults to 0s. What happens if we
# change it? Let's try, by changing it in steps of 30s:

ca = osipi.aif_parker(t, BAT=0)
plt.plot(t, ca, "b-", label="BAT = 0s")
ca = osipi.aif_parker(t, BAT=30)
plt.plot(t, ca, "r-", label="BAT = 30s")
ca = osipi.aif_parker(t, BAT=60)
plt.plot(t, ca, "g-", label="BAT = 60s")
ca = osipi.aif_parker(t, BAT=90)
plt.plot(t, ca, "m-", label="BAT = 90s")
plt.xlabel("Time (sec)")
plt.ylabel("Plasma concentration (mM)")
plt.legend()
plt.show()

# Choose the last image as a thumbnail for the gallery
# sphinx_gallery_thumbnail_number = -1
51 changes: 51 additions & 0 deletions docs-mk/docs/examples/aif/plot_dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
==============
A dummy script
==============

Dummy script to illustrate structure of examples folder
"""

import matplotlib.pyplot as plt

# %%
# Import necessary packages
import numpy as np
import osipi

# %%
# Generate synthetic AIF with default settings and plot the result.

# Define time points in units of seconds - in this case we use a time
# resolution of 0.5 sec and a total duration of 6 minutes.
t = np.arange(0, 6 * 60, 0.5)

# Create an AIF with default settings
ca = osipi.aif_parker(t)

# Plot the AIF over the full range
plt.plot(t, ca, "r-")
plt.plot(t, 0 * t, "k-")
plt.xlabel("Time (sec)")
plt.ylabel("Plasma concentration (mM)")
plt.show()

# %%
# The bolus arrival time (BAT) defaults to 30s. What happens if we
# change it? Let's try, by changing it in steps of 30s:

ca = osipi.aif_parker(t, BAT=0)
plt.plot(t, ca, "b-", label="BAT = 0s")
ca = osipi.aif_parker(t, BAT=30)
plt.plot(t, ca, "r-", label="BAT = 30s")
ca = osipi.aif_parker(t, BAT=60)
plt.plot(t, ca, "g-", label="BAT = 60s")
ca = osipi.aif_parker(t, BAT=90)
plt.plot(t, ca, "m-", label="BAT = 90s")
plt.xlabel("Time (sec)")
plt.ylabel("Plasma concentration (mM)")
plt.legend()
plt.show()

# Choose the last image as a thumbnail for the gallery
# sphinx_gallery_thumbnail_number = -1
3 changes: 3 additions & 0 deletions docs-mk/docs/examples/tissue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*********************
Tissue concentrations
*********************
57 changes: 57 additions & 0 deletions docs-mk/docs/examples/tissue/plot_extended_tofts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
====================
The Extended Tofts model
====================

Simulating tissue concentrations from extended Tofts model with different settings.
"""

import matplotlib.pyplot as plt

# %%
# Import necessary packages
import numpy as np
import osipi

# %%
# Generate Parker AIF with default settings.

# Define time points in units of seconds - in this case we use a time
# resolution of 1 sec and a total duration of 6 minutes.
t = np.arange(0, 6 * 60, 1)

# Create an AIF with default settings
ca = osipi.aif_parker(t)

# %%
# Plot the tissue concentrations for an extracellular volume fraction
# of 0.2 and 3 different plasma volumes of 0.05, 0.2 and 0.6
Ktrans = 0.2 # in units of 1/min
ve = 0.2 # volume fraction between 0 and 1
vp = [0.05, 0.2, 0.6] # volume fraction between 0 and 1
ct = osipi.extended_tofts(t, ca, Ktrans, ve, vp[0])
plt.plot(t, ct, "b-", label=f"vp = {vp[0]}")
ct = osipi.extended_tofts(t, ca, Ktrans, ve, vp[1])
plt.plot(t, ct, "g-", label=f"vp = {vp[1]}")
ct = osipi.extended_tofts(t, ca, Ktrans, ve, vp[2])
plt.plot(t, ct, "m-", label=f"vp = {vp[2]}")
plt.xlabel("Time (sec)")
plt.ylabel("Tissue concentration (mM)")
plt.legend()
plt.show()

# %%
# Comparing different discretization methods for an extracellular
# volume fraction of 0.2, Ktrans of 0.2 /min and vp of 0.05
ct = osipi.extended_tofts(t, ca, Ktrans, ve, vp[0]) # Defaults to Convolution
plt.plot(t, ct, "b-", label="Convolution")
ct = osipi.extended_tofts(t, ca, Ktrans, ve, vp[0], discretization_method="exp")
plt.plot(t, ct, "g-", label="Exponential Convolution")
plt.title(f"Ktrans = {Ktrans} /min")
plt.xlabel("Time (sec)")
plt.ylabel("Tissue concentration (mM)")
plt.legend()
plt.show()

# Choose the last image as a thumbnail for the gallery
# sphinx_gallery_thumbnail_number = -1
Loading
Loading