Skip to content

Commit

Permalink
Merge pull request #26 from SpectraCollab/main
Browse files Browse the repository at this point in the history
Merge main to cleanup branch
  • Loading branch information
mkuczyns authored Nov 12, 2024
2 parents 3560c18 + b3b0cc0 commit f1d2479
Show file tree
Hide file tree
Showing 42 changed files with 926 additions and 239 deletions.
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ORMIR_2022
ORMIR_XCT is a Python package for processing high resolution peripheral computed tomography (HR-pQCT) scans. Development of this project began during the “Building the Jupyter Community in Musculoskeletal Imaging Research” workshop hosted by the Open and Reproducible Musculoskeletal Imaging Research (ORMIR) group.
# ORMIR_XCT

**By:** [Michael T. Kuczynski](https://www.linkedin.com/in/mkuczyns/), [Nathan J. Neeteson](https://www.linkedin.com/in/nathan-neeteson/), [Kathryn S. Stok](https://www.linkedin.com/in/kstok/), [Andrew J. Burghardt](https://www.linkedin.com/in/aburghardt/), [Michelle A. Espinosa Hernandez](https://www.linkedin.com/in/michelleaespinosah/), [Jared Vicory](https://www.kitware.com/jared-vicory/), [Justin J. Tse](https://www.linkedin.com/in/justin-j-tse/), [Pholpat Durongbhan](https://www.linkedin.com/in/pholpatd/), [Serena Bonaretti](https://sbonaretti.github.io/), [Andy Kin On Wong](https://www.linkedin.com/in/andy-kin-on-wong-76408859/), [Steven K. Boyd](https://mccaig.ucalgary.ca/boyd), [Sarah L. Manske](https://www.linkedin.com/in/sarah-manske-b5402b41/), 2023
**Version:** 1.0

- ORMIR_XCT is a Python package for processing high resolution peripheral computed tomography (HR-pQCT) scans.
- Development of this project began during the “Building the Jupyter Community in Musculoskeletal Imaging Research” workshop hosted by the Open and Reproducible Musculoskeletal Imaging Research (ORMIR) group.

---

## Installation
### Step 1: Install the ORMIR_XCT Anaconda Environment:
Expand All @@ -15,4 +22,27 @@ If using an Apple M1, M2, or M3 processor, run the following command instead:
`pip install -e .`

### Step 4: Run Scripts:
The modules in the `ormir_xct` directory can now be run. Examples for each module are provided in the `examples` directory.
The modules in the `ormir_xct` directory can now be run. Examples for each module are provided in the `examples` directory.

---

## Example Jupyter Notebooks
- Example Jupyter Notebooks demonstrating the major functionality of the ORMIR_XCT package are provided in the *[examples](https://github.com/SpectraCollab/ORMIR_XCT/tree/main/examples)* directory.

---

## Ways to Contribute
### Reporting Bugs
- Bugs can be reported by creating a new GitHub issue in this repository. For each bug, please provide details on how to reproduce the bug and the specific error message (if possible).

---

### Contributing New Features
- To add a new feature, expand existing functionality, add documentation, or other contributions, please submit a new GitHub issue outlining your contribution in detail.
- When submitting a new pull request, ensure you outline what you have changed and why it is necessary to make this change.

---

## Citation
When using the ORMIR_XCT package, please use the following citation:
- *Kuczynski, M.T., et al. "ORMIR_XCT: A Python package for high resolution peripheral quantitative computed tomography image processing." arXiv preprint arXiv:2309.04602 (2023).*
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies:
- itk-core==5.3.0
- itk-filtering==5.3.0
- itk-io==5.3.0
- itk-ioscanco==0.9.2
- itk-ioscanco==0.10.0
- itk-numerics==5.3.0
- itk-registration==5.3.0
- itk-segmentation==5.3.0
Expand Down
182 changes: 134 additions & 48 deletions examples/Automatic_Contour_Example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,46 @@
"source": [
"# Automatic Contour Example\n",
"\n",
"This Jupyter Notebook provides an example of running the automatic periosteal contouring script from the ORMIR_XCT package. An example periosteal contour generated using the standard IPL workflow is used as comparison. The DICE coefficient, Jaccard index, and Hausdorff distance are used to compare between the two contour methods.\n",
"- **By:** [Michael T. Kuczynski](https://www.linkedin.com/in/mkuczyns/), 2024 \n",
"- **License:** CC-BY \n",
"- **How to cite:** Cite the ORMIR_XCT publication: *Kuczynski, M.T., et al. \"ORMIR_XCT: A Python package for high resolution peripheral quantitative computed tomography image processing.\" arXiv preprint arXiv:2309.04602 (2023).*"
]
},
{
"cell_type": "markdown",
"id": "3a3b8d0f",
"metadata": {},
"source": [
"---\n",
"# Aims\n",
"\n",
"The ORMIR_XCT automatic contouring script performs image segmentation on HR-pQCT images of joints to separate bone from other tissues. The script outputs the proximal, distal, and full joint mask for each image."
"- This Jupyter Notebook provides an example of running the automatic periosteal contouring script from the ORMIR_XCT package. \n",
"- An example periosteal contour generated using the standard IPL workflow is used as comparison. \n",
"- The DICE coefficient, Jaccard index, and Hausdorff distance are used to compare between the two contour methods.\n",
"- The ORMIR_XCT automatic contouring script performs image segmentation on HR-pQCT images of joints to separate bone from other tissues. The script outputs the proximal, distal, and full joint mask for each image.\n",
"\n",
" **Table of contents** \n",
" [Step 1: Imports](#imports) \n",
" [Step 2: Automatic Contour](#contour) \n",
" [Step 3: Display Results](#results) \n",
" [Step 4: Compare IPL to ORMIR_XCT](#compare)\n"
]
},
{
"cell_type": "markdown",
"id": "7ae0270b",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "bcdc9bff",
"metadata": {},
"source": [
"# Step 1: Imports\n",
"<a name=\"imports\"></a>\n",
"## *Step 1: Imports:*\n",
"\n",
"Import modules/packages and set the input image path. "
]
Expand All @@ -36,7 +65,10 @@
"from matplotlib import pyplot as plt\n",
"\n",
"from ormir_xct.autocontour.autocontour import autocontour\n",
"from ormir_xct.util.segmentation_evaluation import calculate_dice_and_jaccard, hausdorff_sitk"
"from ormir_xct.util.segmentation_evaluation import (\n",
" calculate_dice_and_jaccard,\n",
" hausdorff_sitk,\n",
")"
]
},
{
Expand All @@ -54,12 +86,21 @@
"ipl_mask = sitk.ReadImage(joint_seg_ipl_path, sitk.sitkUInt8)"
]
},
{
"cell_type": "markdown",
"id": "21195166",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "fbdc7d4f",
"metadata": {},
"source": [
"# Step 2: Run the ORMIR_XCT Automatic Contour\n",
"<a name=\"contour\"></a>\n",
"## *Step 2: Run the ORMIR_XCT Automatic Contour:*\n",
"\n",
"Run the ORMIR_XCT automatic periosteal contour script on the input grayscale joint image. This script will return the distal, proximal, and full joint mask. The full joint mask will be used for comparison with the IPL periosteal contour workflow results.\n",
"\n",
Expand All @@ -80,18 +121,29 @@
"outputs": [],
"source": [
"mu_water = 0.24090\n",
"rescale_slope = 1603.51904 \n",
"rescale_slope = 1603.51904\n",
"rescale_intercept = -391.209015\n",
"\n",
"dst_mask, prx_mask, ormir_mask = autocontour(gray_img, mu_water, rescale_slope, rescale_intercept)"
"dst_mask, prx_mask, ormir_mask = autocontour(\n",
" gray_img, mu_water, rescale_slope, rescale_intercept\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5fba53c0",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "b81b793e",
"metadata": {},
"source": [
"# Step 3: Display Results\n",
"<a name=\"results\"></a>\n",
"## *Step 3: Display Results:*\n",
"\n",
"Now display the ORMIR_XCT and IPL segmentations together."
]
Expand All @@ -117,13 +169,13 @@
"outputs": [],
"source": [
"# Get the slices we want to view\n",
"view_slice1 = (slice(None), 45 - ormir_np.shape[1]//2, slice(None))\n",
"view_slice2 = (slice(None), ormir_np.shape[1]//2, slice(None))\n",
"view_slice3 = (slice(None), 45 + ormir_np.shape[1]//2, slice(None))\n",
"view_slice1 = (slice(None), 45 - ormir_np.shape[1] // 2, slice(None))\n",
"view_slice2 = (slice(None), ormir_np.shape[1] // 2, slice(None))\n",
"view_slice3 = (slice(None), 45 + ormir_np.shape[1] // 2, slice(None))\n",
"\n",
"slice1 = abs(int(45 - ormir_np.shape[1]//2))\n",
"slice2 = int(ormir_np.shape[1]//2)\n",
"slice3 = int(45 + ormir_np.shape[1]//2)"
"slice1 = abs(int(45 - ormir_np.shape[1] // 2))\n",
"slice2 = int(ormir_np.shape[1] // 2)\n",
"slice3 = int(45 + ormir_np.shape[1] // 2)"
]
},
{
Expand All @@ -147,56 +199,69 @@
],
"source": [
"# Plot the segmentations overlaid onto the grayscale image for 3 slices\n",
"fig, axs = plt.subplots(3, 3, figsize=(50,30))\n",
"fig, axs = plt.subplots(3, 3, figsize=(50, 30))\n",
"\n",
"# Slice 1\n",
"axs[0][0].set_title('ORMIR_XCT Automatic Contour\\nSlice: {}'.format(slice1), fontsize = 40)\n",
"axs[0][0].imshow(gray_np[view_slice1], cmap='gray')\n",
"axs[0][0].imshow(ormir_np[view_slice1], cmap='rainbow', alpha=0.3)\n",
"axs[0][0].set_title(\n",
" \"ORMIR_XCT Automatic Contour\\nSlice: {}\".format(slice1), fontsize=40\n",
")\n",
"axs[0][0].imshow(gray_np[view_slice1], cmap=\"gray\")\n",
"axs[0][0].imshow(ormir_np[view_slice1], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[0][1].set_title('IPL Automatic Contour\\nSlice: {}'.format(slice1), fontsize = 40)\n",
"axs[0][1].imshow(gray_np[view_slice1], cmap='gray')\n",
"axs[0][1].imshow(ipl_np[view_slice1], cmap='rainbow', alpha=0.3)\n",
"axs[0][1].set_title(\"IPL Automatic Contour\\nSlice: {}\".format(slice1), fontsize=40)\n",
"axs[0][1].imshow(gray_np[view_slice1], cmap=\"gray\")\n",
"axs[0][1].imshow(ipl_np[view_slice1], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[0][2].set_title('ORMIR_XCT and IPL Autocontour Overlaid\\nSlice: {}'.format(slice1), fontsize = 40)\n",
"axs[0][2].imshow(ormir_np[view_slice1], cmap='gray')\n",
"axs[0][2].imshow(ipl_np[view_slice1], cmap='rainbow', vmin=0, vmax=1, alpha=0.3)\n",
"axs[0][2].set_title(\n",
" \"ORMIR_XCT and IPL Autocontour Overlaid\\nSlice: {}\".format(slice1), fontsize=40\n",
")\n",
"axs[0][2].imshow(ormir_np[view_slice1], cmap=\"gray\")\n",
"axs[0][2].imshow(ipl_np[view_slice1], cmap=\"rainbow\", vmin=0, vmax=1, alpha=0.3)\n",
"\n",
"# Slice 2\n",
"axs[1][0].set_title('Slice: {}'.format(slice2), fontsize = 40)\n",
"axs[1][0].imshow(gray_np[view_slice2], cmap='gray')\n",
"axs[1][0].imshow(ormir_np[view_slice2], cmap='rainbow', alpha=0.3)\n",
"axs[1][0].set_title(\"Slice: {}\".format(slice2), fontsize=40)\n",
"axs[1][0].imshow(gray_np[view_slice2], cmap=\"gray\")\n",
"axs[1][0].imshow(ormir_np[view_slice2], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[1][1].set_title('Slice: {}'.format(slice2), fontsize = 40)\n",
"axs[1][1].imshow(gray_np[view_slice2], cmap='gray')\n",
"axs[1][1].imshow(ipl_np[view_slice2], cmap='rainbow', alpha=0.3)\n",
"axs[1][1].set_title(\"Slice: {}\".format(slice2), fontsize=40)\n",
"axs[1][1].imshow(gray_np[view_slice2], cmap=\"gray\")\n",
"axs[1][1].imshow(ipl_np[view_slice2], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[1][2].set_title('Slice: {}'.format(slice2), fontsize = 40)\n",
"axs[1][2].imshow(ormir_np[view_slice2], cmap='gray')\n",
"axs[1][2].imshow(ipl_np[view_slice2], cmap='rainbow', vmin=0, vmax=1, alpha=0.3)\n",
"axs[1][2].set_title(\"Slice: {}\".format(slice2), fontsize=40)\n",
"axs[1][2].imshow(ormir_np[view_slice2], cmap=\"gray\")\n",
"axs[1][2].imshow(ipl_np[view_slice2], cmap=\"rainbow\", vmin=0, vmax=1, alpha=0.3)\n",
"\n",
"# Slice 3\n",
"axs[2][0].set_title('Slice: {}'.format(slice3), fontsize = 40)\n",
"axs[2][0].imshow(gray_np[view_slice3], cmap='gray')\n",
"axs[2][0].imshow(ormir_np[view_slice3], cmap='rainbow', alpha=0.3)\n",
"axs[2][0].set_title(\"Slice: {}\".format(slice3), fontsize=40)\n",
"axs[2][0].imshow(gray_np[view_slice3], cmap=\"gray\")\n",
"axs[2][0].imshow(ormir_np[view_slice3], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[2][1].set_title('Slice: {}'.format(slice3), fontsize = 40)\n",
"axs[2][1].imshow(gray_np[view_slice3], cmap='gray')\n",
"axs[2][1].imshow(ipl_np[view_slice3], cmap='rainbow', alpha=0.3)\n",
"axs[2][1].set_title(\"Slice: {}\".format(slice3), fontsize=40)\n",
"axs[2][1].imshow(gray_np[view_slice3], cmap=\"gray\")\n",
"axs[2][1].imshow(ipl_np[view_slice3], cmap=\"rainbow\", alpha=0.3)\n",
"\n",
"axs[2][2].set_title('Slice: {}'.format(slice3), fontsize = 40)\n",
"axs[2][2].imshow(ormir_np[view_slice3], cmap='gray')\n",
"axs[2][2].imshow(ipl_np[view_slice3], cmap='rainbow', vmin=0, vmax=1, alpha=0.3)\n",
"axs[2][2].set_title(\"Slice: {}\".format(slice3), fontsize=40)\n",
"axs[2][2].imshow(ormir_np[view_slice3], cmap=\"gray\")\n",
"axs[2][2].imshow(ipl_np[view_slice3], cmap=\"rainbow\", vmin=0, vmax=1, alpha=0.3)\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "df74fe73",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "747bf6db",
"metadata": {},
"source": [
"# Step 4: Compare Between IPL and ORMIR_XCT\n",
"<a name=\"compare\"></a>\n",
"## *Step 4: Compare Between IPL and ORMIR_XCT:*\n",
"\n",
"Now compare segmentations using metrics.\n",
"\n",
Expand All @@ -210,7 +275,9 @@
"metadata": {},
"outputs": [],
"source": [
"resampled_ormir = sitk.Resample(ormir_mask, ipl_mask, interpolator=sitk.sitkNearestNeighbor)\n",
"resampled_ormir = sitk.Resample(\n",
" ormir_mask, ipl_mask, interpolator=sitk.sitkNearestNeighbor\n",
")\n",
"\n",
"ormir_np = sitk.GetArrayFromImage(resampled_ormir)"
]
Expand All @@ -236,11 +303,30 @@
"dice, jaccard = calculate_dice_and_jaccard(ipl_np, ormir_np)\n",
"hausdorff = hausdorff_sitk(ipl_mask, resampled_ormir)\n",
"\n",
"print('DICE: ', dice)\n",
"print('Jaccard: ', jaccard)\n",
"print('Mean Hausdorff Distance: ', hausdorff[0])\n",
"print('Maximum Hausdorff Distance: ', hausdorff[1])"
"print(\"DICE: \", dice)\n",
"print(\"Jaccard: \", jaccard)\n",
"print(\"Mean Hausdorff Distance: \", hausdorff[0])\n",
"print(\"Maximum Hausdorff Distance: \", hausdorff[1])"
]
},
{
"cell_type": "markdown",
"id": "e2eb10cb",
"metadata": {},
"source": [
"---\n",
"<a name=\"attribution\"></a>\n",
"\n",
"Notebook created using the [template](https://github.com/ORMIRcommunity/templates/blob/main/ORMIR_nb_template.ipynb) of the [ORMIR community](https://ormircommunity.github.io/) (version 1.0, 2023)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa654d72",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand Down
Loading

0 comments on commit f1d2479

Please sign in to comment.