Skip to content

Commit

Permalink
Add command line options for saving and exporting (invesalius#85)
Browse files Browse the repository at this point in the history
* Create option in app.py to import and then save the project

* Add command to import and export in STL to all available presets

* Create option to open InVesalius without gui

* Add option --export

* No-gui doing less work when creating surface

* Print on debug

* Fix missing session assignment in non-gui mode

* Add option to import every group of DICOM images

* Checking surface index exists in actors_dict before removing it
  • Loading branch information
bryant1410 authored and tfmoraes committed Aug 14, 2017
1 parent 78173f1 commit 2fade61
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 141 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git*
.python-version
Dockerfile
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM ubuntu:16.04

RUN apt-get update
RUN apt-get install -y \
cython \
locales \
python-concurrent.futures \
python-gdcm \
python-matplotlib \
python-nibabel \
python-numpy \
python-pil \
python-psutil \
python-scipy \
python-serial \
python-skimage \
python-vtk6 \
python-vtkgdcm \
python-wxgtk3.0

RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR /usr/local/app

COPY . .

RUN python setup.py build_ext --inplace
150 changes: 138 additions & 12 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import os
import sys
import shutil
import traceback

import re

if sys.platform == 'win32':
import _winreg
Expand Down Expand Up @@ -218,7 +221,8 @@ def Startup(self):
self.control = Controller(self.main)

self.fc = wx.FutureCall(1, self.ShowMain)
wx.FutureCall(1, parse_comand_line)
options, args = parse_comand_line()
wx.FutureCall(1, use_cmd_optargs, options, args)

# Check for updates
from threading import Thread
Expand All @@ -244,6 +248,24 @@ def ShowMain(self):
if self.fc.IsRunning():
self.Raise()


def non_gui_startup(options, args):
lang = 'en'
_ = i18n.InstallLanguage(lang)

from invesalius.control import Controller
from invesalius.project import Project

session = ses.Session()
if not session.ReadSession():
session.CreateItens()
session.SetLanguage(lang)
session.WriteSessionFile()

control = Controller(None)

use_cmd_optargs(options, args)

# ------------------------------------------------------------------


Expand All @@ -262,39 +284,138 @@ def parse_comand_line():
action="store_true",
dest="debug")

parser.add_option('--no-gui',
action='store_true',
dest='no_gui')

# -i or --import: import DICOM directory
# chooses largest series
parser.add_option("-i", "--import",
action="store",
dest="dicom_dir")

parser.add_option("--import-all",
action="store")

parser.add_option("-s", "--save",
help="Save the project after an import.")

parser.add_option("-t", "--threshold",
help="Define the threshold for the export (e.g. 100-780).")

parser.add_option("-e", "--export",
help="Export to STL.")

parser.add_option("-a", "--export-to-all",
help="Export to STL for all mask presets.")

options, args = parser.parse_args()
return options, args


def use_cmd_optargs(options, args):
# If debug argument...
if options.debug:
Publisher.subscribe(print_events, Publisher.ALL_TOPICS)
session = ses.Session()
session.debug = 1

# If import DICOM argument...
if options.dicom_dir:
import_dir = options.dicom_dir
Publisher.sendMessage('Import directory', import_dir)
Publisher.sendMessage('Import directory', {'directory': import_dir, 'gui': not options.no_gui})

if options.save:
Publisher.sendMessage('Save project', os.path.abspath(options.save))
exit(0)

check_for_export(options)

return True
elif options.import_all:
import invesalius.reader.dicom_reader as dcm
for patient in dcm.GetDicomGroups(options.import_all):
for group in patient.GetGroups():
Publisher.sendMessage('Import group', {'group': group, 'gui': not options.no_gui})
check_for_export(options, suffix=group.title, remove_surfaces=False)
Publisher.sendMessage('Remove masks', [0])
return True

# Check if there is a file path somewhere in what the user wrote
# In case there is, try opening as it was a inv3
else:
i = len(args)
while i:
i -= 1
file = args[i].decode(sys.stdin.encoding)
if os.path.isfile(file):
path = os.path.abspath(file)
Publisher.sendMessage('Open project', path)
i = 0
for arg in reversed(args):
if os.path.isfile(arg):
path_ = os.path.abspath(arg.decode(sys.stdin.encoding))
Publisher.sendMessage('Open project', path_)

check_for_export(options)
return True
return False


def sanitize(text):
text = str(text).strip().replace(' ', '_')
return re.sub(r'(?u)[^-\w.]', '', text)


def check_for_export(options, suffix='', remove_surfaces=False):
suffix = sanitize(suffix)

if options.export:
if not options.threshold:
print("Need option --threshold when using --export.")
exit(1)
threshold_range = tuple([int(n) for n in options.threshold.split(',')])

if suffix:
if options.export.endswith('.stl'):
path_ = '{}-{}.stl'.format(options.export[:-4], suffix)
else:
path_ = '{}-{}.stl'.format(options.export, suffix)
else:
path_ = options.export

export(path_, threshold_range, remove_surface=remove_surfaces)
elif options.export_to_all:
# noinspection PyBroadException
try:
from invesalius.project import Project

for threshold_name, threshold_range in Project().presets.thresh_ct.iteritems():
if isinstance(threshold_range[0], int):
path_ = u'{}-{}-{}.stl'.format(options.export_to_all, suffix, threshold_name)
export(path_, threshold_range, remove_surface=True)
except:
traceback.print_exc()
finally:
exit(0)


def export(path_, threshold_range, remove_surface=False):
import invesalius.constants as const

Publisher.sendMessage('Set threshold values', threshold_range)

surface_options = {
'method': {
'algorithm': 'Default',
'options': {},
}, 'options': {
'index': 0,
'name': '',
'quality': _('Optimal *'),
'fill': False,
'keep_largest': False,
'overwrite': False,
}
}
Publisher.sendMessage('Create surface from index', surface_options)
Publisher.sendMessage('Export surface to file', (path_, const.FILETYPE_STL))
if remove_surface:
Publisher.sendMessage('Remove surfaces', [0])


def print_events(data):
"""
Print pubsub messages
Expand All @@ -305,8 +426,13 @@ def main():
"""
Initialize InVesalius GUI
"""
application = InVesalius(0)
application.MainLoop()
options, args = parse_comand_line()

if options.no_gui:
non_gui_startup(options, args)
else:
application = InVesalius(0)
application.MainLoop()

if __name__ == '__main__':
#Is needed because of pyinstaller
Expand Down
14 changes: 7 additions & 7 deletions invesalius/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,19 +694,19 @@
IR2: {1: 'REI'},
IR3: {2: 'NAI'}}

TIPS_IMG = [wx.ToolTip(_("Select left ear in image")),
wx.ToolTip(_("Select right ear in image")),
wx.ToolTip(_("Select nasion in image"))]
TIPS_IMG = [_("Select left ear in image"),
_("Select right ear in image"),
_("Select nasion in image")]

BTNS_TRK = {TR1: {3: _('LET')},
TR2: {4: _('RET')},
TR3: {5: _('NAT')},
SET: {6: _('SET')}}

TIPS_TRK = [wx.ToolTip(_("Select left ear with spatial tracker")),
wx.ToolTip(_("Select right ear with spatial tracker")),
wx.ToolTip(_("Select nasion with spatial tracker")),
wx.ToolTip(_("Show set coordinates in image"))]
TIPS_TRK = [_("Select left ear with spatial tracker"),
_("Select right ear with spatial tracker"),
_("Select nasion with spatial tracker"),
_("Show set coordinates in image")]

CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles'))
MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers'))
30 changes: 25 additions & 5 deletions invesalius/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(self, frame):

def __bind_events(self):
Publisher.subscribe(self.OnImportMedicalImages, 'Import directory')
Publisher.subscribe(self.OnImportGroup, 'Import group')
Publisher.subscribe(self.OnShowDialogImportDirectory,
'Show import directory dialog')
Publisher.subscribe(self.OnShowDialogImportOtherFiles,
Expand Down Expand Up @@ -105,6 +106,8 @@ def __bind_events(self):

Publisher.subscribe(self.SetBitmapSpacing, 'Set bitmap spacing')

Publisher.subscribe(self.OnSaveProject, 'Save project')

def SetBitmapSpacing(self, pubsub_evt):
proj = prj.Project()
proj.spacing = pubsub_evt.data
Expand Down Expand Up @@ -329,6 +332,10 @@ def OpenProject(self, filepath):
session.OpenProject(filepath)
Publisher.sendMessage("Enable state project", True)

def OnSaveProject(self, pubsub_evt):
path = pubsub_evt.data
self.SaveProject(path)

def SaveProject(self, path=None):
Publisher.sendMessage('Begin busy cursor')
session = ses.Session()
Expand Down Expand Up @@ -442,18 +449,19 @@ def LoadImportPanel(self, patient_series):
#----------- to import by command line ---------------------------------------------------

def OnImportMedicalImages(self, pubsub_evt):
directory = pubsub_evt.data
self.ImportMedicalImages(directory)
directory = pubsub_evt.data['directory']
gui = pubsub_evt.data['gui']
self.ImportMedicalImages(directory, gui)

def ImportMedicalImages(self, directory):
def ImportMedicalImages(self, directory, gui=True):
patients_groups = dcm.GetDicomGroups(directory)
name = directory.rpartition('\\')[-1].split('.')
print "patients: ", patients_groups

if len(patients_groups):
# OPTION 1: DICOM
group = dcm.SelectLargerDicomGroup(patients_groups)
matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=True)
matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=gui)
self.CreateDicomProject(dicom, matrix, matrix_filename)
else:
# OPTION 2: NIfTI, Analyze or PAR/REC
Expand All @@ -476,6 +484,18 @@ def ImportMedicalImages(self, directory):
self.LoadProject()
Publisher.sendMessage("Enable state project", True)

def OnImportGroup(self, pubsub_evt):
group = pubsub_evt.data['group']
gui = pubsub_evt.data['gui']
self.ImportGroup(group, gui)

def ImportGroup(self, group, gui=True):
matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=gui)
self.CreateDicomProject(dicom, matrix, matrix_filename)

self.LoadProject()
Publisher.sendMessage("Enable state project", True)

#-------------------------------------------------------------------------------------

def LoadProject(self):
Expand Down Expand Up @@ -785,7 +805,7 @@ def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True):
n_slices = len(filelist)
resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8)

if resolution_percentage < 1.0:
if resolution_percentage < 1.0 and gui:
re_dialog = dialog.ResizeImageDialog()
re_dialog.SetValue(int(resolution_percentage*100))
re_dialog_value = re_dialog.ShowModal()
Expand Down
24 changes: 14 additions & 10 deletions invesalius/data/slice_.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,19 +359,22 @@ def __set_current_mask_threshold(self, evt_pubsub):

# TODO: merge this code with apply_slice_buffer_to_mask
b_mask = self.buffer_slices["AXIAL"].mask
n = self.buffer_slices["AXIAL"].index + 1
self.current_mask.matrix[n, 1:, 1:] = b_mask
self.current_mask.matrix[n, 0, 0] = 1
if b_mask is not None:
n = self.buffer_slices["AXIAL"].index + 1
self.current_mask.matrix[n, 1:, 1:] = b_mask
self.current_mask.matrix[n, 0, 0] = 1

b_mask = self.buffer_slices["CORONAL"].mask
n = self.buffer_slices["CORONAL"].index + 1
self.current_mask.matrix[1:, n, 1:] = b_mask
self.current_mask.matrix[0, n, 0] = 1
if b_mask is not None:
n = self.buffer_slices["CORONAL"].index + 1
self.current_mask.matrix[1:, n, 1:] = b_mask
self.current_mask.matrix[0, n, 0] = 1

b_mask = self.buffer_slices["SAGITAL"].mask
n = self.buffer_slices["SAGITAL"].index + 1
self.current_mask.matrix[1:, 1:, n] = b_mask
self.current_mask.matrix[0, 0, n] = 1
if b_mask is not None:
n = self.buffer_slices["SAGITAL"].index + 1
self.current_mask.matrix[1:, 1:, n] = b_mask
self.current_mask.matrix[0, 0, n] = 1

if to_reload:
Publisher.sendMessage('Reload actual slice')
Expand Down Expand Up @@ -888,7 +891,8 @@ def SetMaskThreshold(self, index, threshold_range, slice_number=None,
self.current_mask.matrix[n+1, 1:, 1:] = m
else:
slice_ = self.buffer_slices[orientation].image
self.buffer_slices[orientation].mask = (255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max))).astype('uint8')
if slice_ is not None:
self.buffer_slices[orientation].mask = (255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max))).astype('uint8')

# Update viewer
#Publisher.sendMessage('Update slice viewer')
Expand Down
Loading

0 comments on commit 2fade61

Please sign in to comment.