Skip to content

Commit

Permalink
Added implant prediction for cranioplasty. (#843)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulojamorim authored Sep 13, 2024
1 parent 9ceaaa3 commit 7af2240
Show file tree
Hide file tree
Showing 20 changed files with 2,075 additions and 44 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/bundles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ jobs:
url: "https://raw.githubusercontent.com/invesalius/weights/main/mandible_ct/mandible_jit_ct.pt"
target: ./ai/mandible_jit_ct/

- name: Download cranioplasty binary ai weight
if: ${{ matrix.ai_ready == 1 }}
uses: suisei-cn/actions-download-file@818d6b7dc8fe73f2f924b6241f2b1134ca1377d9
with:
url: "https://github.com/invesalius/weights/blob/main/cranioplasty_jit_ct_binary/cranioplasty_jit_ct_binary.pt"
target: ./ai/cranioplasty_jit_ct_binary/

- name: Download cranioplasty gray ai weight
if: ${{ matrix.ai_ready == 1 }}
uses: suisei-cn/actions-download-file@818d6b7dc8fe73f2f924b6241f2b1134ca1377d9
with:
url: "https://github.com/invesalius/weights/blob/main/cranioplasty_jit_ct_gray/cranioplasty_jit_ct_gray.pt"
target: ./ai/cranioplasty_jit_ct_gray/

- name: Download trachea ai weight
if: ${{ matrix.ai_ready == 1 }}
uses: suisei-cn/actions-download-file@818d6b7dc8fe73f2f924b6241f2b1134ca1377d9
Expand Down
39 changes: 26 additions & 13 deletions bundle_tools/win/insert_version_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,53 @@

# -*- coding: UTF-8 -*-

import pathlib
import sys
import pathlib


def edit_file(path, date, commit_hash, nightly):
"""
This code inserts the date and hash of the last commit into dialogs.py so that
This code inserts the date and hash of the last commit into dialogs.py so that
the user can provide this information (about window) in any support request.
"""

file = open(path,"r")
file = open(path, "r")
content = file.read()
file.close()

#path = invesalius/gui/dialog.py
# path = invesalius/gui/dialog.py
to_replace = "info.Version = const.INVESALIUS_VERSION"

if nightly:
new_content = to_replace + " + '(Nightly)' + '\\n' + 'Release date: '+ '"\
+ date + "'+'\\n Commit hash: " + commit_hash[0:9] + "...'"
new_content = (
to_replace
+ " + '(Nightly)' + '\\n' + 'Release date: '+ '"
+ date
+ "'+'\\n Commit hash: "
+ commit_hash[0:9]
+ "...'"
)
else:
new_content = to_replace + "+ '\\n' + 'Release date: '+ '"\
+ date + "'+'\\n Commit hash: " + commit_hash[0:9] + "...'"


new_content = (
to_replace
+ "+ '\\n' + 'Release date: '+ '"
+ date
+ "'+'\\n Commit hash: "
+ commit_hash[0:9]
+ "...'"
)

content = content.replace(to_replace, new_content)

file = open(path,"w")
file = open(path, "w")
file.write(content)
file.close()

if __name__ == '__main__':

if __name__ == "__main__":
path = pathlib.Path(sys.argv[1])
date = sys.argv[2]
commit_hash = sys.argv[3]
nightly = sys.argv[4]

edit_file(path, date, commit_hash, bool(nightly))
edit_file(path, date, commit_hash, bool(nightly))
15 changes: 8 additions & 7 deletions docs/devel/example_closure.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Python closure example


def OuterCount(start):
counter = [start] # counter is 1-element array
print("Passed outside")
counter = [start] # counter is 1-element array
print("Passed outside")

def InnerCount():
counter[0] = counter[0] + 1
print("Passed inside")
return counter[0]
def InnerCount():
counter[0] = counter[0] + 1
print("Passed inside")
return counter[0]

return InnerCount
return InnerCount


print("Init counter at 5")
Expand Down
19 changes: 9 additions & 10 deletions docs/devel/example_singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
# http://en.wikipedia.org/wiki/Singleton_pattern
# http://www.vincehuston.org/dp/singleton.html


class Singleton(type):
# This is a Gary Robinson implementation:
# http://www.garyrobinson.net/2004/03/python_singleto.html
def __init__(cls,name,bases,dic):
super(Singleton,cls).__init__(name, bases, dic)
def __init__(cls, name, bases, dic):
super(Singleton, cls).__init__(name, bases, dic)
cls.instance = None

def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
Expand All @@ -21,21 +22,21 @@ class Bone(object):
# Only one project will be initialized per time. Therefore, we use
# Singleton design pattern for implementing it
__metaclass__ = Singleton

def __init__(self):
self.size = 100

def RemovePart(self, part_size):
self.size -= part_size # self.size = self.size - part_size


class Dog():
class Dog:
def __init__(self, name):
self.name = name
self.bone = Bone()

def EatBonePart(self, part_size):
self.bone.RemovePart(part_size)
self.bone.RemovePart(part_size)


print("Initial state:")
Expand All @@ -53,5 +54,3 @@ def EatBonePart(self, part_size):
d2.EatBonePart(20)
print(f"Bone size of {d1.name}: {d1.bone.size}")
print(f"Bone size of {d2.name}: {d2.bone.size}")


6 changes: 3 additions & 3 deletions docs/devel/example_singleton_pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self):
self.__bind_events()

def __bind_events(self):
Publisher.subscribe(self.RemovePiece, 'Eat piece of pizza')
Publisher.subscribe(self.RemovePiece, "Eat piece of pizza")

def RemovePiece(self, pubsub_evt):
person = pubsub_evt
Expand All @@ -38,13 +38,13 @@ def RemovePiece(self, pubsub_evt):
print(f"{person.name} is hungry!")


class Person():
class Person:
def __init__(self, name):
self.name = name
self.pizza = Pizza()

def EatPieceOfPizza(self):
Publisher.sendMessage('Eat piece of pizza', pubsub_evt=self)
Publisher.sendMessage("Eat piece of pizza", pubsub_evt=self)


print("Initial state:")
Expand Down
6 changes: 6 additions & 0 deletions invesalius/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@
ID_SEGMENTATION_BRAIN = wx.NewIdRef()
ID_SEGMENTATION_TRACHEA = wx.NewIdRef()
ID_SEGMENTATION_MANDIBLE_CT = wx.NewIdRef()
ID_PLANNING_CRANIOPLASTY = wx.NewIdRef()
ID_CROP_MASK = wx.NewIdRef()
ID_DENSITY_MEASURE = wx.NewIdRef()
ID_MASK_DENSITY_MEASURE = wx.NewIdRef()
Expand Down Expand Up @@ -734,6 +735,11 @@
SURFACE_INTERPOLATION = 1
LANGUAGE = 2
SLICE_INTERPOLATION = 3
# Logging
LOGGING = 4
LOGGING_LEVEL = 5
APPEND_LOG_FILE = 6
LOGFILE = 7


# ------------ Logging options key------------
Expand Down
21 changes: 21 additions & 0 deletions invesalius/data/imagedata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import numpy as np
from scipy.ndimage import shift, zoom
from skimage.color import rgb2gray
from skimage.measure import label
from vtkmodules.util import numpy_support
from vtkmodules.vtkFiltersCore import vtkImageAppend
from vtkmodules.vtkImagingCore import vtkExtractVOI, vtkImageClip, vtkImageResample
Expand Down Expand Up @@ -574,6 +575,19 @@ def get_LUT_value(data: np.ndarray, window: int, level: int) -> np.ndarray:
return data


def get_LUT_value_normalized(img, a_min, a_max, b_min=0.0, b_max=1.0, clip=True):
# based on https://docs.monai.io/en/latest/_modules/monai/transforms/intensity/array.html#ScaleIntensity

print(a_min, a_max, b_min, b_max, clip)
img = (img - a_min) / (a_max - a_min)
img = img * (b_max - b_min) + b_min

if clip:
img = np.clip(img, b_min, b_max)

return img


def image_normalize(image, min_=0.0, max_=1.0, output_dtype=np.int16):
output = np.empty(shape=image.shape, dtype=output_dtype)
imin, imax = image.min(), image.max()
Expand Down Expand Up @@ -706,3 +720,10 @@ def random_sample_sphere(radius=3, size=100):
scale = radius * np.divide(r, norm)
xyz = scale * uvw
return xyz


def get_largest_connected_component(image):
labels = label(image)
assert labels.max() != 0
largest_component = labels == np.argmax(np.bincount(labels.flat)[1:]) + 1
return largest_component
125 changes: 125 additions & 0 deletions invesalius/gui/deep_learning_seg_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,128 @@ def OnSegment(self, evt):
def OnStop(self, evt):
super().OnStop(evt)
self.chk_apply_resize_by_spacing.Enable()


class ImplantSegmenterDialog(DeepLearningSegmenterDialog):
def __init__(self, parent):
super().__init__(
parent=parent,
title=_("Implant prediction (CT)"),
has_torch=True,
has_plaidml=False,
has_theano=False,
segmenter=segment.ImplantCTSegmentProcess,
)

def _init_gui(self):
super()._init_gui()

self.patch_txt = wx.StaticText(self, label="Patch size:")

patch_size = [
"48",
"96",
"160",
"192",
"240",
"288",
"320",
"336",
"384",
"432",
"480",
"528",
]

self.patch_cmb = wx.ComboBox(self, choices=patch_size, value="480")

self.path_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.path_sizer.Add(self.patch_txt, 0, wx.EXPAND | wx.ALL, 5)
self.path_sizer.Add(self.patch_cmb, 2, wx.EXPAND | wx.ALL, 5)

self.method = wx.RadioBox(
self,
-1,
"Method:",
wx.DefaultPosition,
wx.DefaultSize,
["Binary", "Gray"],
2,
wx.HORIZONTAL | wx.ALIGN_LEFT | wx.NO_BORDER,
)

self.method_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.method_sizer.Add(self.method, 2, wx.ALL, 5)

self.Layout()
self.Centre()

def _do_layout(self):
super()._do_layout()
self.main_sizer.Insert(8, self.path_sizer, 0, wx.EXPAND | wx.ALL, 5)
self.main_sizer.Insert(9, self.method_sizer, 0, wx.EXPAND | wx.ALL, 5)

def OnSegment(self, evt):
self.ShowProgress()
self.t0 = time.time()
self.elapsed_time_timer.Start(1000)
image = slc.Slice().matrix
backend = self.cb_backends.GetValue()
if backend.lower() == "pytorch":
try:
device_id = self.torch_devices[self.cb_devices.GetValue()]
except (KeyError, AttributeError):
device_id = "cpu"
else:
try:
device_id = self.plaidml_devices[self.cb_devices.GetValue()]
except (KeyError, AttributeError):
device_id = "llvm_cpu.0"
apply_wwwl = self.chk_apply_wwwl.GetValue()
create_new_mask = self.chk_new_mask.GetValue()
use_gpu = self.chk_use_gpu.GetValue()
prob_threshold = self.sld_threshold.GetValue() / 100.0
method = self.method.GetSelection()

self.btn_close.Disable()
self.btn_stop.Enable()
self.btn_segment.Disable()
self.chk_new_mask.Disable()

window_width = slc.Slice().window_width
window_level = slc.Slice().window_level

overlap = self.overlap_options[self.overlap.GetSelection()]

patch_size = int(self.patch_cmb.GetValue())

try:
self.ps = self.segmenter(
image,
create_new_mask,
backend,
device_id,
use_gpu,
overlap,
apply_wwwl,
window_width,
window_level,
method=method,
patch_size=patch_size,
resize_by_spacing=True,
image_spacing=slc.Slice().spacing,
)
self.ps.start()
except (multiprocessing.ProcessError, OSError, ValueError) as err:
self.OnStop(None)
self.HideProgress()
dlg = dialogs.ErrorMessageBox(
None,
"It was not possible to start brain segmentation because:" + "\n" + str(err),
"Brain segmentation error",
# wx.ICON_ERROR | wx.OK,
)
dlg.ShowModal()

def OnStop(self, evt):
super().OnStop(evt)
Loading

0 comments on commit 7af2240

Please sign in to comment.