Skip to content

Commit 72555de

Browse files
authored
Fix long filename issue in SlicesDir (#1551)
1 parent 3eefea6 commit 72555de

File tree

1 file changed

+64
-3
lines changed

1 file changed

+64
-3
lines changed

xcp_d/interfaces/plotting.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,9 @@ class _SlicesDirInputSpec(FSLCommandInputSpec):
530530
desc='output every second axial slice rather than just 9 ortho slices',
531531
)
532532

533+
# Copy in_files from the original location to the runtime.cwd
533534
in_files = InputMultiPath(
534-
File(exists=True),
535+
File(exists=True, copyfile=False),
535536
argstr='%s',
536537
mandatory=True,
537538
position=-1,
@@ -568,6 +569,58 @@ class SlicesDir(FSLCommand):
568569
_cmd = 'slicesdir'
569570
input_spec = _SlicesDirInputSpec
570571
output_spec = _SlicesDirOutputSpec
572+
_short_basenames = None
573+
_short_outline = None
574+
575+
def _run_interface(self, runtime):
576+
"""Create symlinks with short names before running slicesdir.
577+
578+
``slicesdir`` names its output files by replacing path separators in the input
579+
file paths with underscores. When input paths are long (e.g., in deep working
580+
directories), the derived output filenames can exceed the OS's 255-character
581+
filename limit (see https://github.com/PennLINC/xcp_d/issues/1545).
582+
583+
We work around this by creating symlinks with short names in the working
584+
directory, and passing those short names to ``slicesdir`` instead of the
585+
original long paths.
586+
"""
587+
self._short_basenames = []
588+
for i, f in enumerate(self.inputs.in_files):
589+
ext = '.nii.gz' if f.endswith('.nii.gz') else os.path.splitext(f)[1]
590+
basename = f'img{i}{ext}'
591+
symlink_path = os.path.join(runtime.cwd, basename)
592+
if os.path.lexists(symlink_path):
593+
os.unlink(symlink_path)
594+
595+
os.symlink(os.path.abspath(f), symlink_path)
596+
self._short_basenames.append(basename)
597+
598+
if isdefined(self.inputs.outline_image):
599+
f = self.inputs.outline_image
600+
ext = '.nii.gz' if f.endswith('.nii.gz') else os.path.splitext(f)[1]
601+
self._short_outline = f'outline{ext}'
602+
symlink_path = os.path.join(runtime.cwd, self._short_outline)
603+
if os.path.lexists(symlink_path):
604+
os.unlink(symlink_path)
605+
606+
os.symlink(os.path.abspath(f), symlink_path)
607+
608+
runtime = super()._run_interface(runtime)
609+
return runtime
610+
611+
def _format_arg(self, name, spec, value):
612+
"""Use short symlink basenames for in_files and outline_image.
613+
614+
This ensures the ``slicesdir`` command line uses short paths, producing
615+
short output filenames that won't exceed the OS filename length limit.
616+
"""
617+
if name == 'in_files' and self._short_basenames is not None:
618+
return ' '.join(self._short_basenames)
619+
620+
if name == 'outline_image' and self._short_outline is not None:
621+
return spec.argstr % self._short_outline
622+
623+
return super()._format_arg(name, spec, value)
571624

572625
def _list_outputs(self):
573626
"""Create a Bunch which contains all possible files generated by running the interface.
@@ -586,13 +639,21 @@ def _list_outputs(self):
586639

587640
out_dir = os.path.abspath(os.path.join(os.getcwd(), 'slicesdir'))
588641
outputs['out_dir'] = out_dir
642+
643+
# Use short basenames if available (set by _run_interface),
644+
# otherwise fall back to the original path-based naming.
645+
if self._short_basenames is not None:
646+
in_basenames = self._short_basenames
647+
else:
648+
in_basenames = [f.replace(os.sep, '_') for f in self.inputs.in_files]
649+
589650
outputs['out_files'] = [
590651
self._gen_fname(
591-
basename=f.replace(os.sep, '_'),
652+
basename=name,
592653
cwd=out_dir,
593654
ext=self.inputs.out_extension,
594655
)
595-
for f in self.inputs.in_files
656+
for name in in_basenames
596657
]
597658
temp_files = [
598659
'grota.png',

0 commit comments

Comments
 (0)