Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions scripts/cli_tract_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def _build_arg_parser():

optional flags: Always start with "--" and are considered as boolean flags

The --force flag overwrites an existing output file without asking.

The --use_file_names_as_index col_name1:path_dir_index col_name2:path_dir_index2 .. col_nameN:path_dir_indexN
Example directory layout:
=========================
Expand Down Expand Up @@ -86,6 +88,9 @@ def main():

parser = _build_arg_parser()
args = parser.parse_args()
# The files are only opened to check that they exist
for f in args.tractographies:
f.close()

# Load the global modules after the parsing of parameters
from tract_querier.tractography import tractography_from_files
Expand Down
30 changes: 16 additions & 14 deletions scripts/cli_tract_querier.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ def main():
):
parser.error("incorrect number of arguments")

input_split = os.path.splitext(options.tractography_file_name)
output_split = os.path.splitext(options.output_file_name)
if len(output_split[-1]) > 0:
options.output_file_name = output_split[0]
tractography_extension = output_split[1]
else:
tractography_extension = input_split[1]

if tractography_extension not in ('.trk', '.vtk', '.vtp'):
parser.error(
"output format %r not supported, it must be .trk, .vtk or .vtp; "
"add it to the output prefix, e.g. -o %s.trk"
% (tractography_extension, options.output_file_name +
tractography_extension)
)

options.threshold = float(options.threshold)
options.length_threshold = float(options.length_threshold)

Expand Down Expand Up @@ -208,20 +224,6 @@ def main():
options.tractography_file_name
)

tractography_extension = os.path.splitext(options.tractography_file_name)[-1]
if tractography_extension == '.trk':
tractography_extra_kwargs = {
'affine': tr.affine,
'image_dimensions': tr.image_dims
}
input_split = os.path.splitext(options.tractography_file_name)
output_split = os.path.splitext(options.output_file_name)
if len(output_split[-1]) > 0:
options.output_file_name = output_split[0]
tractography_extension = output_split[1]
else:
tractography_extension = input_split[1]

if tractography_extension == '.trk':
if input_split[-1] == '.trk':
tractography_extra_kwargs = {
Expand Down
4 changes: 2 additions & 2 deletions tract_querier/query_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ def __init__(self, value):
self.value = value

def __str__(self):
return repr(self.value)
return str(self.value)


class TractQuerierLabelNotFound(ValueError):
Expand All @@ -655,7 +655,7 @@ def __init__(self, value):
self.value = value

def __str__(self):
return repr(self.value)
return str(self.value)


class RewriteChangeNotInPrescedence(ast.NodeTransformer):
Expand Down
1 change: 0 additions & 1 deletion tract_querier/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ def emptyline(self):

@safe_method
def default(self, line):
print(line)
if len(line) == 0:
return False

Expand Down
28 changes: 28 additions & 0 deletions tract_querier/tests/test_tract_math.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv
import io
import re

from tract_querier.tests import datasets
Expand All @@ -17,3 +19,29 @@ def test_tract_math_count(script_runner):
ret = script_runner.run(["tract_math", tract_file, "count"])
assert re.search('[^0-9]6783[^0-9]', ret.stdout) is not None
assert ret.success


def test_tract_math_existing_output(script_runner, tmp_path):
test_data = datasets.TestDataSet()
tract_file = test_data.files["tract_file"]
output = tmp_path / "count.csv"
output.write_text("old\n")

# Without --force and without an interactive stdin, do not prompt: fail
ret = script_runner.run(
["tract_math", tract_file, "count", str(output)],
stdin=io.StringIO("")
)
assert not ret.success
assert "--force" in ret.stderr
assert output.read_text() == "old\n"

ret = script_runner.run(
["tract_math", tract_file, "count", str(output), "--force"],
stdin=io.StringIO("")
)
assert ret.success
with open(output, newline='') as f:
rows = list(csv.reader(f))
assert len(rows) == 2
assert "6783" in rows[1]
21 changes: 21 additions & 0 deletions tract_querier/tests/test_tract_querier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ def test_help_option(script_runner):
assert ret.success


def test_tract_querier_unsupported_output_format(script_runner, tmp_path):
# ".01" is taken as the output format: it must be rejected before the
# files are loaded and the queries computed.
test_data = datasets.TestDataSet()
ret = script_runner.run([
"tract_querier",
"-a", test_data.files["atlas_file"],
"-t", test_data.files["tract_file"],
"-q", test_data.files["query_cst_file"],
"-o", str(tmp_path / "subj.01"),
])
assert not ret.success
assert "'.01' not supported" in ret.stderr
assert "Loading files" not in ret.stdout


def test_syntax_error_message_is_not_quoted():
from tract_querier import TractQuerierSyntaxError
assert str(TractQuerierSyntaxError("line 1\nline 2")) == "line 1\nline 2"


def test_tract_querier_query(script_runner):
test_data = datasets.TestDataSet()
atlas_file = test_data.files["atlas_file"]
Expand Down
62 changes: 35 additions & 27 deletions tract_querier/tract_math/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io
import csv
from os import path
import sys

import nibabel

Expand Down Expand Up @@ -98,6 +99,7 @@ def wrapper(*input_args):
# find optional flags and put them as a dictionary
# at the beginning of args
args, options_dict = find_optional_args(input_args)
force = options_dict.pop('--force', None) is not None

total_args = len(args)
argspec = inspect.getfullargspec(func)
Expand Down Expand Up @@ -136,7 +138,7 @@ def wrapper(*input_args):
option_and_args = (options_dict,) + args
out = func(*option_and_args)

process_output(out, file_output=file_output)
process_output(out, file_output=file_output, force=force)
elif (
total_args >= func_total_args or
len(args) == func_args
Expand All @@ -160,11 +162,16 @@ def wrapper(*input_args):
return internal_decorator


def process_output(output, file_output=None):
def process_output(output, file_output=None, force=False):
if output is None:
return

if file_output is not None and path.exists(file_output):
if file_output is not None and path.exists(file_output) and not force:
if not sys.stdin.isatty():
raise TractMathWrongArgumentsError(
'Output file %s exists, use --force to overwrite it'
% file_output
)
in_key = input("Overwrite file %s (y/N)? " % file_output)
if in_key.lower().strip() != 'y':
return
Expand All @@ -187,33 +194,34 @@ def process_output(output, file_output=None):
dialect = 'excel-tab'
else:
dialect = 'excel'
f = open(file_output, 'w')
writer = csv.DictWriter(f, output.keys(), dialect=dialect)
f = open(file_output, 'w', newline='')
with f:
writer = csv.DictWriter(f, output.keys(), dialect=dialect)

if hasattr(writer, 'writeheader'):
writer.writeheader()
else:
header = dict(zip(writer.fieldnames, writer.fieldnames))
writer.writerow(header)

first_value = next(iter(output.values()))
if (
not isinstance(first_value, str) and
isinstance(first_value, Iterable)
):
rows = (
dict(zip(*row))
for row in zip(
repeat(output.keys(), len(first_value)),
zip(*output.values())
if hasattr(writer, 'writeheader'):
writer.writeheader()
else:
header = dict(zip(writer.fieldnames, writer.fieldnames))
writer.writerow(header)

first_value = next(iter(output.values()))
if (
not isinstance(first_value, str) and
isinstance(first_value, Iterable)
):
rows = (
dict(zip(*row))
for row in zip(
repeat(output.keys(), len(first_value)),
zip(*output.values())
)
)
)
writer.writerows(rows)
else:
writer.writerow(output)
writer.writerows(rows)
else:
writer.writerow(output)

if file_output is None:
print(f.getvalue())
if file_output is None:
print(f.getvalue())


class TractMathWrongArgumentsError(TypeError):
Expand Down
9 changes: 5 additions & 4 deletions tract_querier/tract_math/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def tract_dump_endpoints(optional_flags, tractography):
return res


@tract_math_operation(': Minimum and maximum distance between two consecutive points')
@tract_math_operation(': minimum and maximum tract length')
def tract_point_distance_min_max(optional_flags, tractography):
dist_min = numpy.empty(len(tractography.tracts()))
dist_max = numpy.empty(len(tractography.tracts()))
Expand All @@ -241,7 +241,7 @@ def tract_subsample(optional_flags, tractography, points_per_tract, file_output)
)


@tract_math_operation('<mm per tract> <tractography_file_output>: subsamples tracts to a maximum number of points')
@tract_math_operation('<min length in mm> <tractography_file_output>: keeps only the tracts longer than the given length')
def tract_remove_short_tracts(optional_flags, tractography, min_tract_length, file_output):

min_tract_length = float(min_tract_length)
Expand Down Expand Up @@ -351,9 +351,10 @@ def tract_deform(optional_flags, tractography, image, file_output=None):


@tract_math_operation(
'<transform> [invert] <tractography_file_output>: apply a '
'<transform> <ref_image> [invert] <tractography_file_output>: apply a '
'affine transform to a tractography. '
'transform is assumed to be in RAS format like Nifti.'
'transform is assumed to be in RAS format like Nifti. '
'ref_image gives the affine and dimensions of the output.'
)
def tract_affine_transform(optional_flags,
tractography, transform_file, ref_image,
Expand Down
Loading