Skip to content

Commit bb7c286

Browse files
committed
Merge remote-tracking branch 'upstream/master' into enh/docker_alpine
2 parents 0807931 + 2eb5291 commit bb7c286

File tree

9 files changed

+260
-41
lines changed

9 files changed

+260
-41
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ jobs:
2121
- '3.9'
2222
- '3.10'
2323
- '3.11'
24+
- '3.12'
25+
# Seems needs work in traits: https://github.com/nipy/heudiconv/pull/799#issuecomment-2447298795
26+
# - '3.13'
2427
steps:
2528
- name: Check out repository
2629
uses: actions/checkout@v4

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# v1.3.1 (Fri Oct 25 2024)
2+
3+
#### 🐛 Bug Fix
4+
5+
- Fix assignment of sensitive git-annex metadata data via glob patterns (regression introduced by #739) [#793](https://github.com/nipy/heudiconv/pull/793) ([@bpinsard](https://github.com/bpinsard))
6+
7+
#### Authors: 1
8+
9+
- Basile ([@bpinsard](https://github.com/bpinsard))
10+
11+
---
12+
113
# v1.3.0 (Wed Oct 02 2024)
214

315
#### 🚀 Enhancement

Dockerfile

Lines changed: 157 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,159 @@
1-
FROM python:3.10-alpine AS builder
2-
3-
RUN apk add bash \
4-
gcc \
5-
g++ \
6-
libc-dev \
7-
make \
8-
cmake \
9-
util-linux-dev \
10-
curl \
11-
git
12-
RUN pip install --no-cache-dir pylibjpeg-libjpeg traits==6.3.2
13-
14-
ARG DCM2NIIX_VERSION=v1.0.20240202
15-
RUN git clone https://github.com/rordenlab/dcm2niix /tmp/dcm2niix \
16-
&& cd /tmp/dcm2niix \
17-
&& git fetch --tags \
18-
&& git checkout $DCM2NIIX_VERSION \
19-
&& mkdir /tmp/dcm2niix/build \
20-
&& cd /tmp/dcm2niix/build \
21-
&& cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON -DCMAKE_INSTALL_PREFIX:PATH=/usr/ .. \
22-
&& make -j1 \
23-
&& make install \
24-
&& rm -rf /tmp/dcm2niix
25-
26-
FROM python:3.10-alpine
27-
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
28-
COPY --from=builder /usr/bin/dcm2niix /usr/bin/dcm2niix
29-
30-
RUN apk update && apk add --no-cache git git-annex pigz gcompat
31-
32-
RUN pip install --no-cache-dir heudiconv
1+
# Generated by Neurodocker and Reproenv.
332

3+
FROM neurodebian:bookworm
4+
ENV PATH="/opt/dcm2niix-v1.0.20240202/bin:$PATH"
5+
RUN apt-get update -qq \
6+
&& apt-get install -y -q --no-install-recommends \
7+
ca-certificates \
8+
cmake \
9+
g++ \
10+
gcc \
11+
git \
12+
make \
13+
pigz \
14+
zlib1g-dev \
15+
&& rm -rf /var/lib/apt/lists/* \
16+
&& git clone https://github.com/rordenlab/dcm2niix /tmp/dcm2niix \
17+
&& cd /tmp/dcm2niix \
18+
&& git fetch --tags \
19+
&& git checkout v1.0.20240202 \
20+
&& mkdir /tmp/dcm2niix/build \
21+
&& cd /tmp/dcm2niix/build \
22+
&& cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON -DCMAKE_INSTALL_PREFIX:PATH=/opt/dcm2niix-v1.0.20240202 .. \
23+
&& make -j1 \
24+
&& make install \
25+
&& rm -rf /tmp/dcm2niix
26+
RUN apt-get update -qq \
27+
&& apt-get install -y -q --no-install-recommends \
28+
gcc \
29+
git \
30+
git-annex-standalone \
31+
libc-dev \
32+
liblzma-dev \
33+
netbase \
34+
pigz \
35+
&& rm -rf /var/lib/apt/lists/*
36+
COPY [".", \
37+
"/src/heudiconv"]
38+
ENV CONDA_DIR="/opt/miniconda-py39_4.12.0" \
39+
PATH="/opt/miniconda-py39_4.12.0/bin:$PATH"
40+
RUN apt-get update -qq \
41+
&& apt-get install -y -q --no-install-recommends \
42+
bzip2 \
43+
ca-certificates \
44+
curl \
45+
&& rm -rf /var/lib/apt/lists/* \
46+
# Install dependencies.
47+
&& export PATH="/opt/miniconda-py39_4.12.0/bin:$PATH" \
48+
&& echo "Downloading Miniconda installer ..." \
49+
&& conda_installer="/tmp/miniconda.sh" \
50+
&& curl -fsSL -o "$conda_installer" https://repo.continuum.io/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh \
51+
&& bash "$conda_installer" -b -p /opt/miniconda-py39_4.12.0 \
52+
&& rm -f "$conda_installer" \
53+
# Prefer packages in conda-forge
54+
&& conda config --system --prepend channels conda-forge \
55+
# Packages in lower-priority channels not considered if a package with the same
56+
# name exists in a higher priority channel. Can dramatically speed up installations.
57+
# Conda recommends this as a default
58+
# https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-channels.html
59+
&& conda config --set channel_priority strict \
60+
&& conda config --system --set auto_update_conda false \
61+
&& conda config --system --set show_channel_urls true \
62+
# Enable `conda activate`
63+
&& conda init bash \
64+
&& conda install -y --name base \
65+
"python=3.9" \
66+
"traits>=4.6.0" \
67+
"scipy" \
68+
"numpy" \
69+
"nomkl" \
70+
"pandas" \
71+
"gdcm" \
72+
&& bash -c "source activate base \
73+
&& python -m pip install --no-cache-dir --editable \
74+
"/src/heudiconv[all]"" \
75+
# Clean up
76+
&& sync && conda clean --all --yes && sync \
77+
&& rm -rf ~/.cache/pip/*
3478
ENTRYPOINT ["heudiconv"]
79+
80+
# Save specification to JSON.
81+
RUN printf '{ \
82+
"pkg_manager": "apt", \
83+
"existing_users": [ \
84+
"root" \
85+
], \
86+
"instructions": [ \
87+
{ \
88+
"name": "from_", \
89+
"kwds": { \
90+
"base_image": "neurodebian:bookworm" \
91+
} \
92+
}, \
93+
{ \
94+
"name": "env", \
95+
"kwds": { \
96+
"PATH": "/opt/dcm2niix-v1.0.20240202/bin:$PATH" \
97+
} \
98+
}, \
99+
{ \
100+
"name": "run", \
101+
"kwds": { \
102+
"command": "apt-get update -qq\\napt-get install -y -q --no-install-recommends \\\\\\n ca-certificates \\\\\\n cmake \\\\\\n g++ \\\\\\n gcc \\\\\\n git \\\\\\n make \\\\\\n pigz \\\\\\n zlib1g-dev\\nrm -rf /var/lib/apt/lists/*\\ngit clone https://github.com/rordenlab/dcm2niix /tmp/dcm2niix\\ncd /tmp/dcm2niix\\ngit fetch --tags\\ngit checkout v1.0.20240202\\nmkdir /tmp/dcm2niix/build\\ncd /tmp/dcm2niix/build\\ncmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON -DCMAKE_INSTALL_PREFIX:PATH=/opt/dcm2niix-v1.0.20240202 ..\\nmake -j1\\nmake install\\nrm -rf /tmp/dcm2niix" \
103+
} \
104+
}, \
105+
{ \
106+
"name": "install", \
107+
"kwds": { \
108+
"pkgs": [ \
109+
"git", \
110+
"gcc", \
111+
"pigz", \
112+
"liblzma-dev", \
113+
"libc-dev", \
114+
"git-annex-standalone", \
115+
"netbase" \
116+
], \
117+
"opts": null \
118+
} \
119+
}, \
120+
{ \
121+
"name": "run", \
122+
"kwds": { \
123+
"command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n gcc \\\\\\n git \\\\\\n git-annex-standalone \\\\\\n libc-dev \\\\\\n liblzma-dev \\\\\\n netbase \\\\\\n pigz \\\\\\n && rm -rf /var/lib/apt/lists/*" \
124+
} \
125+
}, \
126+
{ \
127+
"name": "copy", \
128+
"kwds": { \
129+
"source": [ \
130+
".", \
131+
"/src/heudiconv" \
132+
], \
133+
"destination": "/src/heudiconv" \
134+
} \
135+
}, \
136+
{ \
137+
"name": "env", \
138+
"kwds": { \
139+
"CONDA_DIR": "/opt/miniconda-py39_4.12.0", \
140+
"PATH": "/opt/miniconda-py39_4.12.0/bin:$PATH" \
141+
} \
142+
}, \
143+
{ \
144+
"name": "run", \
145+
"kwds": { \
146+
"command": "apt-get update -qq\\napt-get install -y -q --no-install-recommends \\\\\\n bzip2 \\\\\\n ca-certificates \\\\\\n curl\\nrm -rf /var/lib/apt/lists/*\\n# Install dependencies.\\nexport PATH=\\"/opt/miniconda-py39_4.12.0/bin:$PATH\\"\\necho \\"Downloading Miniconda installer ...\\"\\nconda_installer=\\"/tmp/miniconda.sh\\"\\ncurl -fsSL -o \\"$conda_installer\\" https://repo.continuum.io/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh\\nbash \\"$conda_installer\\" -b -p /opt/miniconda-py39_4.12.0\\nrm -f \\"$conda_installer\\"\\n# Prefer packages in conda-forge\\nconda config --system --prepend channels conda-forge\\n# Packages in lower-priority channels not considered if a package with the same\\n# name exists in a higher priority channel. Can dramatically speed up installations.\\n# Conda recommends this as a default\\n# https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-channels.html\\nconda config --set channel_priority strict\\nconda config --system --set auto_update_conda false\\nconda config --system --set show_channel_urls true\\n# Enable `conda activate`\\nconda init bash\\nconda install -y --name base \\\\\\n \\"python=3.9\\" \\\\\\n \\"traits>=4.6.0\\" \\\\\\n \\"scipy\\" \\\\\\n \\"numpy\\" \\\\\\n \\"nomkl\\" \\\\\\n \\"pandas\\" \\\\\\n \\"gdcm\\"\\nbash -c \\"source activate base\\n python -m pip install --no-cache-dir --editable \\\\\\n \\"/src/heudiconv[all]\\"\\"\\n# Clean up\\nsync && conda clean --all --yes && sync\\nrm -rf ~/.cache/pip/*" \
147+
} \
148+
}, \
149+
{ \
150+
"name": "entrypoint", \
151+
"kwds": { \
152+
"args": [ \
153+
"heudiconv" \
154+
] \
155+
} \
156+
} \
157+
] \
158+
}' > /.reproenv.json
159+
# End saving to specification to JSON.

Dockerfile.alpine

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
FROM python:3.10-alpine AS builder
2+
3+
RUN apk add bash \
4+
gcc \
5+
g++ \
6+
libc-dev \
7+
make \
8+
cmake \
9+
util-linux-dev \
10+
curl \
11+
git
12+
RUN pip install --no-cache-dir pylibjpeg-libjpeg traits==6.3.2
13+
14+
ARG DCM2NIIX_VERSION=v1.0.20240202
15+
RUN git clone https://github.com/rordenlab/dcm2niix /tmp/dcm2niix \
16+
&& cd /tmp/dcm2niix \
17+
&& git fetch --tags \
18+
&& git checkout $DCM2NIIX_VERSION \
19+
&& mkdir /tmp/dcm2niix/build \
20+
&& cd /tmp/dcm2niix/build \
21+
&& cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON -DCMAKE_INSTALL_PREFIX:PATH=/usr/ .. \
22+
&& make -j1 \
23+
&& make install \
24+
&& rm -rf /tmp/dcm2niix
25+
26+
FROM python:3.10-alpine
27+
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
28+
COPY --from=builder /usr/bin/dcm2niix /usr/bin/dcm2niix
29+
30+
RUN apk update && apk add --no-cache git git-annex pigz gcompat
31+
32+
RUN pip install --no-cache-dir heudiconv
33+
34+
ENTRYPOINT ["heudiconv"]

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
:target: https://repology.org/project/python:heudiconv/versions
4141
:alt: PyPI
4242

43+
.. image:: https://img.shields.io/badge/RRID-SCR__017427-blue
44+
:target: https://identifiers.org/RRID:SCR_017427
45+
:alt: RRID
46+
4347
About
4448
-----
4549

heudiconv/external/dlad.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ def add_to_datalad(
156156

157157
# Provide metadata for sensitive information
158158
sensitive_patterns = [
159-
"sourcedata",
159+
"sourcedata/**",
160160
"*_scans.tsv", # top level
161161
"*/*_scans.tsv", # within subj
162162
"*/*/*_scans.tsv", # within sess/subj
163-
"*/anat", # within subj
164-
"*/*/anat", # within ses/subj
163+
"*/anat/*", # within subj
164+
"*/*/anat/*", # within ses/subj
165165
]
166166
for sp in sensitive_patterns:
167167
mark_sensitive(ds, sp, annexed_files)

heudiconv/parser.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os.path as op
1010
import re
1111
import shutil
12+
import sys
1213
from types import ModuleType
1314
from typing import Optional
1415

@@ -22,7 +23,18 @@
2223

2324
_VCS_REGEX = r"%s\.(?:git|gitattributes|svn|bzr|hg)(?:%s|$)" % (op.sep, op.sep)
2425

25-
_UNPACK_FORMATS = tuple(sum((x[1] for x in shutil.get_unpack_formats()), []))
26+
27+
def _get_unpack_formats() -> dict[str, bool]:
28+
"""For each extension return if it is a tar"""
29+
out = {}
30+
for _, exts, d in shutil.get_unpack_formats():
31+
for e in exts:
32+
out[e] = bool(re.search(r"\btar\b", d.lower()))
33+
return out
34+
35+
36+
_UNPACK_FORMATS = _get_unpack_formats()
37+
_TAR_UNPACK_FORMATS = tuple(k for k, is_tar in _UNPACK_FORMATS.items() if is_tar)
2638

2739

2840
@docstring_parameter(_VCS_REGEX)
@@ -114,7 +126,7 @@ def get_extracted_dicoms(fl: Iterable[str]) -> ItemsView[Optional[str], list[str
114126

115127
# needs sorting to keep the generated "session" label deterministic
116128
for _, t in enumerate(sorted(fl)):
117-
if not t.endswith(_UNPACK_FORMATS):
129+
if not t.endswith(tuple(_UNPACK_FORMATS)):
118130
sessions[None].append(t)
119131
continue
120132

@@ -127,7 +139,14 @@ def get_extracted_dicoms(fl: Iterable[str]) -> ItemsView[Optional[str], list[str
127139

128140
# check content and sanitize permission bits before extraction
129141
os.chmod(tmpdir, mode=0o700)
130-
shutil.unpack_archive(t, extract_dir=tmpdir)
142+
# For tar (only!) starting with 3.12 we should provide filter
143+
# (enforced in 3.14) on how to filter/safe-guard filenames.
144+
kws: dict[str, str] = {}
145+
if sys.version_info >= (3, 12) and t.endswith(_TAR_UNPACK_FORMATS):
146+
# Allow for a user-workaround if would be desired
147+
# see e.g. https://docs.python.org/3.12/library/tarfile.html#extraction-filters
148+
kws["filter"] = os.environ.get("HEUDICONV_TAR_FILTER", "tar")
149+
shutil.unpack_archive(t, extract_dir=tmpdir, **kws) # type: ignore[arg-type]
131150

132151
archive_content = list(find_files(regex=".*", topdir=tmpdir))
133152

heudiconv/tests/test_regression.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def test_conversion(
5555
heuristic,
5656
anon_cmd,
5757
template="sourcedata/sub-{subject}/*/*/*.tgz",
58+
xargs=["--datalad"],
5859
)
5960
runner(args) # run conversion
6061

@@ -96,6 +97,18 @@ def test_conversion(
9697
for key in keys:
9798
assert orig[key] == conv[key]
9899

100+
# validate sensitive marking
101+
from datalad.api import Dataset
102+
103+
ds = Dataset(outdir)
104+
all_meta = dict(ds.repo.get_metadata("."))
105+
target_rec = {"distribution-restrictions": ["sensitive"]}
106+
for pth, meta in all_meta.items():
107+
if "anat" in pth or "scans.tsv" in pth:
108+
assert meta == target_rec
109+
else:
110+
assert meta == {}
111+
99112

100113
@pytest.mark.skipif(not have_datalad, reason="no datalad")
101114
def test_multiecho(

utils/gen-docker-image.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ VER=$(grep -Po '(?<=^__version__ = ).*' $thisd/../heudiconv/info.py | sed 's/"//
77

88
image="kaczmarj/neurodocker:0.9.1"
99

10-
docker run --rm $image generate docker \
11-
--base-image neurodebian:bullseye \
10+
if hash podman; then
11+
OCI_BINARY=podman
12+
elif hash docker; then
13+
OCI_BINARY=docker
14+
else
15+
echo "ERROR: no podman or docker found" >&2
16+
exit 1
17+
fi
18+
19+
${OCI_BINARY:-docker} run --rm $image generate docker \
20+
--base-image neurodebian:bookworm \
1221
--pkg-manager apt \
1322
--dcm2niix \
14-
version=v1.0.20220720 \
23+
version=v1.0.20240202 \
1524
method=source \
1625
cmake_opts="-DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON" \
1726
--install \

0 commit comments

Comments
 (0)