Skip to content

Commit f8d144e

Browse files
authored
Merge pull request #381 from DESm1th/bug_fixes
[FIX] Add small bug fixes we've accumulated
2 parents 3f78b7f + 41f02ce commit f8d144e

9 files changed

Lines changed: 139 additions & 41 deletions

File tree

‎bin/dm_link.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import glob
7575
import logging
7676
import os
77+
import re
7778
import sys
7879

7980
from docopt import docopt
@@ -245,6 +246,8 @@ def get_scanid_from_lookup_table(archive_path):
245246
"""
246247
global lookup
247248
basename = os.path.basename(os.path.normpath(archive_path))
249+
# Strip whitespace, because the scans.csv entries can't contain them
250+
basename = re.sub(r'\s+', '', basename)
248251
source_name = basename[:-len(datman.utils.get_extension(basename))]
249252
lookupinfo = lookup[lookup["source_name"] == source_name]
250253

‎bin/dm_qc_report.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
--log-to-server If set, all log messages will also be sent to the
2121
configured logging server. This is useful when the
2222
script is run on the queue, since it swallows logging.
23+
--max-mem STR The maximum memory required if submitted to the
24+
queue. Should include the units (e.g. GB) [default: 5GB]
2325
-q --quiet Only report errors
2426
-v --verbose Be chatty
2527
-d --debug Be extra chatty
@@ -64,6 +66,7 @@ def main():
6466
REMAKE = arguments["--remake"]
6567
REFRESH = arguments["--refresh"]
6668
use_server = arguments["--log-to-server"]
69+
max_mem = arguments["--max-mem"]
6770
verbose = arguments["--verbose"]
6871
debug = arguments["--debug"]
6972
quiet = arguments["--quiet"]
@@ -81,7 +84,7 @@ def main():
8184
logger.setLevel(logging.DEBUG)
8285

8386
if not session:
84-
return submit_subjects(config)
87+
return submit_subjects(config, max_mem=max_mem)
8588

8689
if not datman.dashboard.dash_found:
8790
logger.error("Dashboard database not found, can't run.")
@@ -131,11 +134,13 @@ def add_server_handler(config):
131134
logger.addHandler(server_handler)
132135

133136

134-
def submit_subjects(config):
137+
def submit_subjects(config, max_mem="5GB"):
135138
"""Submit a job for each subject in the study that still needs metrics.
136139
137140
Args:
138141
config (:obj:`datman.config.config`): A config object for the study.
142+
max_mem (int, optional): The maximum amount of memory needed for jobs.
143+
Default = 5GB.
139144
"""
140145
missing_cmds = check_prerequisites()
141146
if missing_cmds:
@@ -157,7 +162,7 @@ def submit_subjects(config):
157162
logger.info(f"Submitting QC job for {subject}.")
158163
datman.utils.submit_job(
159164
command, job_name, "/tmp", system=config.system,
160-
argslist="--mem=5G"
165+
argslist=f"--mem={max_mem}"
161166
)
162167

163168

‎bin/dm_sftp.py‎

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,25 @@
88
<study>: Short name of the study to process
99
1010
Options:
11+
--before <date> Only download scans added to the server before
12+
the given date. Date format is expected to be
13+
YYYY-MM-DD. Can be combined with --after.
14+
--after <date> Only download scans added to the server after
15+
the given date. Date format is expected to be
16+
YYYY-MM-DD. Can be combined with --before.
1117
-h --help Show this screen.
1218
-q --quiet Suppress output.
1319
-v --verbose Show more output.
1420
-d --debug Show lots of output.
1521
--dry-run
1622
1723
"""
24+
import fnmatch
1825
import logging
1926
import os
2027
import shutil
21-
import fnmatch
28+
import time
29+
from datetime import datetime
2230

2331
import pysftp
2432

@@ -39,6 +47,10 @@ def main():
3947
quiet = arguments["--quiet"]
4048
study = arguments["<study>"]
4149

50+
start_date, end_date = get_date_range(
51+
arguments["--after"], arguments["--before"]
52+
)
53+
4254
# setup logging
4355
log_level = logging.WARN
4456

@@ -108,7 +120,40 @@ def main():
108120
# process each folder in turn
109121
logger.debug("Copying from:{} to:{}"
110122
.format(valid_dir, zips_path))
111-
process_dir(sftp, valid_dir, zips_path)
123+
process_dir(sftp, valid_dir, zips_path,
124+
start=start_date, end=end_date)
125+
126+
127+
def get_date_range(after_str=None, before_str=None):
128+
if before_str:
129+
end_date = parse_date(before_str)
130+
else:
131+
end_date = None
132+
133+
if after_str:
134+
start_date = parse_date(after_str)
135+
else:
136+
start_date = None
137+
138+
if start_date and end_date and start_date > end_date:
139+
raise ValueError(
140+
"Start of date range is after the cut off date. When both "
141+
"--after and --before are in use, --after date must precede "
142+
"--before date"
143+
)
144+
145+
return start_date, end_date
146+
147+
148+
def parse_date(date_str):
149+
try:
150+
dt = datetime.strptime(date_str, "%Y-%m-%d")
151+
except ValueError as e:
152+
raise ValueError(
153+
"Invalid date format given, expected format YYYY-MM-DD"
154+
) from e
155+
parsed = time.mktime(dt.timetuple())
156+
return parsed
112157

113158

114159
def get_server_config(cfg):
@@ -204,7 +249,7 @@ def get_valid_remote_dirs(connection, mrfolders):
204249
return valid_dirs
205250

206251

207-
def process_dir(connection, directory, zips_path):
252+
def process_dir(connection, directory, zips_path, start=None, end=None):
208253
"""Process a directory on the ftp server,
209254
copy new files to zips_path
210255
"""
@@ -218,14 +263,18 @@ def process_dir(connection, directory, zips_path):
218263
return
219264
for file_name in files:
220265
if connection.isfile(file_name):
221-
get_file(connection, file_name, zips_path)
266+
get_file(connection, file_name, zips_path,
267+
start=start, end=end)
222268
else:
223-
get_folder(connection, file_name, zips_path)
269+
get_folder(connection, file_name, zips_path,
270+
start=start, end=end)
224271

225272

226-
def get_folder(connection, folder_name, dst_path):
273+
def get_folder(connection, folder_name, dst_path, start=None, end=None):
227274
expected_file = os.path.join(dst_path, folder_name + ".zip")
228-
if not download_needed(connection, folder_name, expected_file):
275+
if not download_needed(connection, folder_name, expected_file,
276+
start=start, end=end
277+
):
229278
logger.debug("File: {} already exists, skipping".format(folder_name))
230279
return
231280

@@ -239,26 +288,34 @@ def get_folder(connection, folder_name, dst_path):
239288
new_zip))
240289

241290

242-
def get_file(connection, file_name, zips_path):
291+
def get_file(connection, file_name, zips_path, start=None, end=None):
243292
target = os.path.join(zips_path, file_name)
244-
if download_needed(connection, file_name, target):
293+
if download_needed(connection, file_name, target, start=start, end=end):
245294
logger.info("Copying new remote file: {}".format(file_name))
246295
connection.get(file_name, target, preserve_mtime=True)
247296
else:
248297
logger.debug("File: {} already exists, skipping".format(file_name))
249298

250299

251-
def download_needed(sftp, filename, target):
300+
def download_needed(sftp, filename, target, start=None, end=None):
252301
"""Check if a local copy of the file exists,
253302
If no local copy exists return True
254303
If local copy exists and is older than remote return True
255304
otherwise return false"""
305+
306+
remote_mtime = sftp.stat(filename).st_mtime
307+
308+
if start and remote_mtime < start:
309+
return False
310+
311+
if end and remote_mtime > end:
312+
return False
313+
256314
if not os.path.isfile(target):
257315
return True
258316

259317
# check the file modification times
260318
local_mtime = os.path.getmtime(target)
261-
remote_mtime = sftp.stat(filename).st_mtime
262319
if local_mtime < remote_mtime:
263320
return True
264321

‎bin/dm_xnat_extract.py‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,12 @@ def collect_zips(config, args):
386386
return []
387387

388388
if args.experiment:
389-
ident = get_identifier(config, args.experiment)
389+
try:
390+
ident = get_identifier(config, args.experiment)
391+
except datman.exceptions.ParseException as e:
392+
logger.error(f"Ignoring invalid ID {args.experiment} - {e}")
393+
return []
394+
390395
if not ident:
391396
logger.error(f"Invalid session ID {args.experiment}.")
392397
return []
@@ -400,12 +405,22 @@ def collect_zips(config, args):
400405

401406
zip_files = []
402407
for zip_path in glob.glob(os.path.join(zip_folder, "*.zip")):
408+
if not os.path.exists(zip_path):
409+
logger.debug(f"Broken symlink found. Ignoring {zip_path}")
410+
continue
411+
403412
sess_name = os.path.basename(zip_path).replace(".zip", "")
404-
ident = get_identifier(config, sess_name)
413+
try:
414+
ident = get_identifier(config, sess_name)
415+
except datman.exceptions.ParseException as e:
416+
logger.error(f"Ignoring invalid ID {sess_name} - {e}")
417+
continue
418+
405419
if not ident:
406420
logger.error(
407421
f"Ignoring invalid zip file name in dicom dir: {sess_name}")
408422
continue
423+
409424
zip_files.append(
410425
(None, datman.importers.ZipImporter(ident, zip_path))
411426
)

‎bin/dm_xnat_upload.py‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ def process_archive(file_name, dicom_dir):
156156
exper_id))
157157
return
158158

159+
160+
# Hold off on resource uploads until scan data exists.
161+
# We upload using xnat's direct-to-archive options, but if any data
162+
# exists for a session (even just resources) during upload then
163+
# everything gets punted to the prearchive and ends up requiring a manual
164+
# merge.
165+
if data_exists and not resource_exists:
166+
logger.debug("Uploading resource from: {}".format(archive_file))
167+
try:
168+
upload_non_dicom_data(archive_file, xnat_subject.project, scanid,
169+
xnat)
170+
except Exception as e:
171+
logger.debug("An exception occurred: {}".format(e))
172+
pass
173+
elif not data_exists and not resource_exists:
174+
logger.info(
175+
f"Skipping resource upload of {archive_file} until scans exist. "
176+
"Resource files will upload on a later run once scan upload "
177+
"complete."
178+
)
179+
180+
159181
if not data_exists:
160182
logger.info("Uploading dicoms from {}".format(archive_file))
161183
try:
@@ -166,14 +188,6 @@ def process_archive(file_name, dicom_dir):
166188
.format(archive_file, xnat_subject.project,
167189
xnat_subject.name, e))
168190

169-
if not resource_exists:
170-
logger.debug("Uploading resource from: {}".format(archive_file))
171-
try:
172-
upload_non_dicom_data(archive_file, xnat_subject.project, scanid,
173-
xnat)
174-
except Exception as e:
175-
logger.debug("An exception occurred: {}".format(e))
176-
pass
177191

178192

179193
def get_xnat_subject(ident, xnat):

‎datman/exporters/bids.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ def export(self, raw_data_dir: str, **kwargs):
137137
self.make_output_dir()
138138

139139
try:
140-
self.run_dcm2bids(raw_data_dir, force_dcm2niix=force_dcm2niix)
140+
self.run_dcm2bids(
141+
raw_data_dir,
142+
force_dcm2niix=force_dcm2niix,
143+
refresh=self.opts.refresh
144+
)
141145
except Exception as e:
142146
logger.error(f"Failed to extract to BIDs - {e}")
143147

@@ -178,7 +182,7 @@ def _get_scan_dir(self, download_dir: str, refresh: bool = False) -> str:
178182
if refresh:
179183
# Use existing tmp_dir instead of raw dcms
180184
return self.bids_tmp
181-
return download_dir
185+
return os.path.join(download_dir, self.dcm_dir)
182186

183187
def make_command(
184188
self, raw_data_dir: str, force_dcm2niix: bool = False

‎datman/exporters/bids_legacy.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,12 @@ def check_contents(self, expected, actual):
189189
misnamed[actual_name] = expected_name
190190
continue
191191

192-
expected_name = expected[scan][0]
193192
actual_name = actual[scan][0]
193+
if "blacklisted" in actual_name:
194+
# Do not 'fix' blacklisted scans by moving them back.
195+
continue
196+
197+
expected_name = expected[scan][0]
194198
if expected_name == actual_name:
195199
continue
196200
misnamed[actual_name] = expected_name

‎datman/importers.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ def __init__(self, ident, zip_path):
10721072
self.contents = self.parse_contents()
10731073
self.scans = self.get_scans()
10741074
self.resource_files = self.contents['resources']
1075-
self.dcm_subdir = os.path.split(self.scans[0].series_dir)[0]
1075+
self.dcm_subdir = self.get_dcm_subdir()
10761076
self.date = self.scans[0].date
10771077

10781078
@property
@@ -1138,6 +1138,15 @@ def dcm_subdir(self) -> str:
11381138
def dcm_subdir(self, value: str):
11391139
self._dcm_subdir = value
11401140

1141+
def get_dcm_subdir(self) -> str:
1142+
"""Find the common parent folder for all scan dicoms.
1143+
"""
1144+
series_dirs = [scan.series_dir for scan in self.scans]
1145+
prefix = os.path.commonpath(series_dirs)
1146+
if prefix == '':
1147+
return os.path.split(self.scans[0].series_dir)[0]
1148+
return prefix
1149+
11411150
def is_shared(self) -> bool:
11421151
# Can't track shared sessions with zip files.
11431152
return False

‎datman/metrics.py‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -229,23 +229,14 @@ class DTIMetrics(MetricDTI):
229229
"_b0.png": QCOutput(2, "b0 Montage")
230230
},
231231
"qc-dti": {
232-
"_qascripts_dti.csv": None,
233232
"_stats.csv": None,
234233
"_directions.png": QCOutput(3, "bvec Directions")
235-
},
236-
"qc-spikecount": {
237-
"_spikecount.csv": None
238234
}
239235
}
240236

241237
def generate(self, img_gap=2, width=1600):
242238
self.run(f"qc-dti {self.input} {self.bvec} {self.bval} "
243239
f"{self.output_root}", "qc-dti")
244-
245-
self.run(f"qc-spikecount {self.input} "
246-
f"{self.output_root + '_spikecount.csv'} {self.bval}",
247-
"qc-spikecount")
248-
249240
self.make_montage(self.output_root + "_montage.png")
250241
self.make_image(self.output_root + "_b0.png", img_gap, width)
251242

@@ -267,10 +258,6 @@ class FMRIMetrics(Metric):
267258
"_scanlengths.csv": None
268259
},
269260
"qc-fmri": {
270-
"_fd.csv": None,
271-
"_qascripts_bold.csv": None,
272-
"_spectra.csv": None,
273-
"_stats.csv": None,
274261
"_sfnr.nii.gz": None,
275262
"_corr.nii.gz": None
276263
},

0 commit comments

Comments
 (0)