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

Merge main to cleanup branch #26

Merged
merged 27 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
11ad4ed
do not oversample by default
njneeteson Jul 11, 2023
67efac6
add skeletonization
njneeteson Jul 11, 2023
4677400
Merge branch 'main' into mkuczyns/tests
mkuczyns Jul 17, 2023
380e4f8
Merge pull request #19 from SpectraCollab/mkuczyns/cleanup
mkuczyns Apr 10, 2024
50016cf
Merge pull request #20 from SpectraCollab/main
mkuczyns Apr 10, 2024
69c43dc
Changed readme title
mkuczyns Apr 10, 2024
7420df4
Initial commit
mkuczyns Apr 10, 2024
34470da
Initial commit
mkuczyns Apr 11, 2024
c9a552d
Fixed padded image positioning
mkuczyns Apr 11, 2024
eeef6e7
Updated ITKIOScanco version
mkuczyns Apr 11, 2024
61d90a3
Removed padding
mkuczyns Apr 11, 2024
59c8a6b
Changed datatype to int16
mkuczyns Apr 11, 2024
4a0990f
Initial commit
mkuczyns Apr 11, 2024
203778c
Initial commit
mkuczyns Apr 11, 2024
f7f7a8b
Removed
mkuczyns Apr 11, 2024
f7eed9d
Added contribution info
mkuczyns Apr 11, 2024
0457fee
Merge pull request #21 from SpectraCollab/mkuczyns/tests
mkuczyns Apr 11, 2024
7e0530e
Matched ORMIR style template
mkuczyns May 2, 2024
7c3529d
Fixed author names
mkuczyns May 2, 2024
1943ccf
Ran black
mkuczyns May 2, 2024
c1d522d
Merge pull request #22 from SpectraCollab/mkuczyns/tests
mkuczyns May 2, 2024
f164def
Fixes for final JOSS submission
mkuczyns May 13, 2024
6216798
Merge pull request #23 from SpectraCollab/mkuczyns/tests
mkuczyns May 13, 2024
f6977ae
Removed colons from headings
mkuczyns May 14, 2024
8f6c2a7
Merge pull request #24 from SpectraCollab/mkuczyns/tests
mkuczyns May 14, 2024
c2b327b
Added acknowledgements
mkuczyns May 29, 2024
b3b0cc0
Merge pull request #25 from SpectraCollab/mkuczyns/tests
mkuczyns May 29, 2024
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
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
Loading