Repository navigation
Expand file tree
/
Copy pathcopier.yml
More file actions
300 lines (273 loc) · 12 KB
/
Copy pathcopier.yml
File metadata and controls
300 lines (273 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
_subdirectory: template
_min_copier_version: "9.0.0"
_envops:
keep_trailing_newline: true
_templates_suffix: ""
# ---------------------------------------------------------------------------
# 1. What kind of thing is this?
# ---------------------------------------------------------------------------
project_type:
type: str
help: >-
"team" = a real project that belongs on shared storage, with a raw-data
landing zone under source_data/ and a working area under analysis_data/.
"personal" = a one-off / exploratory thing that only needs a spot under
your home/ directory. Nothing shared, nothing else is scaffolded.
choices:
Team project (source_data/ + analysis_data/): team
Personal / exploratory (home/<you>/): personal
default: team
# ---------------------------------------------------------------------------
# 2. Identity
# ---------------------------------------------------------------------------
project_name:
type: str
help: "Human-readable project name (e.g. 'Xenium remission cohort')"
validator: >-
{% if not project_name.strip() %}Project name cannot be empty{% endif %}
project_slug:
type: str
help: "Folder / repo name (lowercase, no spaces). Leave default to auto-derive."
default: >-
{{ project_name|lower|trim|replace(' ', '-')|replace('_', '-') }}
validator: >-
{% if not (project_slug | regex_search('^[a-z0-9][a-z0-9-]*[a-z0-9]$')) %}
project_slug must be lowercase, alphanumeric and hyphens only, and not
start or end with a hyphen (got: "{{ project_slug }}")
{% endif %}
owner:
type: str
help: >-
Your username as it appears under home/ (e.g. 'skrause'). Used as the
contact/creator in the project manifest, and as the target folder for
personal projects.
pi_or_group:
type: str
help: "Responsible PI / working group (for the manifest & README)"
when: "{{ project_type == 'team' }}"
description:
type: str
help: "One or two sentences describing the project"
default: "TBD"
data_sensitivity:
type: str
help: "Data sensitivity classification, for the manifest"
choices:
"Public / open data": public
"Internal (no patient data)": internal
"Patient-related / sensitive": sensitive
default: internal
when: "{{ project_type == 'team' }}"
uses_reference_data:
type: str
help: >-
Does this project read from reference_data/ (genomes, databases, models)?
Purely documentation — nothing is copied or linked, this just gets noted
in the manifest so others know what the project depends on.
default: ""
when: "{{ project_type == 'team' }}"
data_type:
type: str
multiselect: true
help: >-
What kind of data does this project deal with? (pick any that apply)
Each selection also becomes a modality folder: source_data/ gets a
raw/processed skeleton per selection, analysis_data/ gets a
01_preprocessing/02_quality_control skeleton per selection. Leave empty
for a minimal project (just README + PROJECT.yml, nothing else).
choices:
"scRNA-seq / snRNA-seq": scrna-seq
"Spatial transcriptomics (Visium / Xenium / MERFISH / CosMx)": spatial-transcriptomics
"scATAC-seq / bulk ATAC-seq": atac-seq
"Bulk RNA-seq": bulk-rna-seq
"WGS / WES (genomic sequencing)": wgs-wes
"Long-read sequencing (ONT / PacBio)": long-read-sequencing
"Proteomics (mass spectrometry)": proteomics
"Flow cytometry / CyTOF": flow-cytometry
"DNA methylation (bisulfite / EM-seq)": methylation
"Hi-C / chromatin conformation": hi-c
"Whole-slide histology (H&E / IHC)": whole-slide-histology
"Multiplex imaging (CODEX / PhenoCycler / Xenium / MERFISH imaging)": multiplex-imaging
"Confocal / light-sheet microscopy": confocal-microscopy
"MRI / CT / PET (DICOM)": radiology-dicom
"Cryo-EM": cryo-em
"Other / not listed": other
default: []
when: "{{ project_type == 'team' }}"
data_type_other:
type: str
help: "Briefly describe the 'other' data type(s)"
default: ""
when: "{{ project_type == 'team' and 'other' in data_type }}"
# ---------------------------------------------------------------------------
# 3. Version control & data versioning — both fully optional
# ---------------------------------------------------------------------------
init_git:
type: bool
help: "Initialize a git repository in the project's code/working folder?"
default: true
init_dvc:
type: bool
help: >-
Initialize DVC for data versioning? Works with or without git — if git
is off, DVC is initialized with 'dvc init --no-scm'.
default: false
dvc_remote_url:
type: str
help: >-
DVC remote storage URL (e.g. an S3 bucket, or a path under
reference_data/ or a dedicated dvc-store/ volume). Leave blank to
configure this later with 'dvc remote add'.
default: ""
when: "{{ init_dvc }}"
# ---------------------------------------------------------------------------
# 4. Backup (Amanda)
# ---------------------------------------------------------------------------
backup:
type: bool
help: >-
Should this directory be picked up by the group's Amanda backups?
Recorded in PROJECT.yml and used by the group's exclude-list sync script
to decide what Amanda skips. See the "Backup (Amanda)" section in the
template repo's README for how this ties into the actual Amanda config.
default: "{{ project_type == 'team' }}"
# ---------------------------------------------------------------------------
# Internal / computed — not asked, just derived
# ---------------------------------------------------------------------------
project_root:
type: str
when: false
default: >-
{% if project_type == 'team' %}analysis_data/{{ project_slug }}{% else %}home/{{ owner }}/{{ project_slug }}{% endif %}
_tasks:
# --- 0. Sanity check: are we even at the root of the shared storage? ---
# If NONE of the expected top-level folders exist here, this probably
# isn't /data — warn, but don't block. Create the standard skeleton once
# so the run (and any future ones) can proceed normally either way.
- command: |
found=0
{% if project_type == 'team' %}
for d in reference_data public_data home; do
{% else %}
for d in source_data analysis_data reference_data public_data; do
{% endif %}
[ -d "$d" ] && found=1
done
if [ "$found" -eq 0 ]; then
RED='\033[1;31m'
NC='\033[0m'
printf "${RED}WARNING: none of source_data/, analysis_data/, reference_data/,${NC}\n"
printf "${RED}public_data/, home/ exist in %s.${NC}\n" "$(pwd)"
printf "${RED}This does NOT look like the root of the shared data storage.${NC}\n"
printf "Continue anyway and create the project (and the standard skeleton) here? [y/N] "
read ans
case "$ans" in
y|Y|yes|YES|Yes)
mkdir -p source_data analysis_data public_data home
mkdir -p reference_data/genomes reference_data/databases
mkdir -p reference_data/models/classification \
reference_data/models/deconvolution \
reference_data/models/segmentation \
reference_data/models/llm
;;
*)
printf "${RED}Aborting - removing what was just created here.${NC}\n"
{% if project_type == 'team' %}
rm -rf "source_data/{{ project_slug }}" "analysis_data/{{ project_slug }}"
rmdir source_data analysis_data 2>/dev/null || true
{% else %}
rm -rf "home/{{ owner }}/{{ project_slug }}"
rmdir "home/{{ owner }}" home 2>/dev/null || true
{% endif %}
exit 1
;;
esac
fi
working_directory: "."
- command: |
{% set extra_mod = [(data_type_other.strip() | lower | replace(' ', '-') | replace('_', '-'))] if (project_type == 'team' and 'other' in data_type and data_type_other.strip()) else [] %}
{% set mods = ((data_type | reject('equalto', 'other') | list) + extra_mod) if project_type == 'team' else [] %}
{% if mods %}
set -e
SLUG="{{ project_slug }}"
echo "{{ mods | join(',') }}" | tr ',' '\n' | while IFS= read -r raw_m; do
m=$(echo "$raw_m" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
[ -z "$m" ] && continue
SD="source_data/$SLUG/$m"
mkdir -p "$SD/raw" "$SD/processed/scripts" "$SD/processed/logs" "$SD/processed/output"
touch "$SD/metadata_table"
cat > "$SD/README.md" <<EOF2
# $m — source data
Raw/incoming data for the "$m" modality of {{ project_name }}.
- raw/ untouched incoming files, read-only once landed
- processed/ scripts/, logs/, output/ from staging this modality's data
- metadata_table sample/metadata table for this modality
EOF2
AD="analysis_data/$SLUG/$m"
mkdir -p "$AD/01_preprocessing/scripts" "$AD/01_preprocessing/notebooks" "$AD/01_preprocessing/output" "$AD/01_preprocessing/environment"
touch "$AD/01_preprocessing/scripts/.gitkeep" "$AD/01_preprocessing/notebooks/.gitkeep" "$AD/01_preprocessing/output/.gitkeep" "$AD/01_preprocessing/environment/.gitkeep"
cat > "$AD/01_preprocessing/README.md" <<EOF3
# $m — 01_preprocessing
Raw -> usable data for the "$m" modality.
EOF3
mkdir -p "$AD/02_quality_control"
cat > "$AD/02_quality_control/README.md" <<EOF4
# $m — 02_quality_control
QC checks on the preprocessed "$m" data.
EOF4
cat > "$AD/README.md" <<EOF5
# $m
Analysis pipeline for the "$m" modality of {{ project_name }}.
- 01_preprocessing/ raw -> usable data
- 02_quality_control/ QC checks on the preprocessed data
EOF5
done
mkdir -p "analysis_data/$SLUG/environment"
echo "# add requirements.txt / environment.yml / pyproject.toml here" > "analysis_data/$SLUG/environment/README.md"
{% else %}
true
{% endif %}
working_directory: "."
- command: sed -i "s/__CREATION_DATE__/$(date +%Y-%m-%d)/" PROJECT.yml 2>/dev/null || true
working_directory: "{{ project_root }}"
- command: >-
{% if init_git %}git init -q{% else %}true{% endif %}
working_directory: "{{ project_root }}"
- command: >-
{% if init_git %}git add -A && git commit -q -m "Bootstrap project from lab template" || true{% else %}true{% endif %}
working_directory: "{{ project_root }}"
- command: >-
{% if init_dvc and init_git %}dvc init -q
{% elif init_dvc and not init_git %}dvc init -q --no-scm
{% else %}true{% endif %}
working_directory: "{{ project_root }}"
- command: >-
{% if init_dvc and dvc_remote_url %}dvc remote add -d storage "{{ dvc_remote_url }}"{% else %}true{% endif %}
working_directory: "{{ project_root }}"
- command: >-
{% if init_dvc and init_git %}git add -A && git commit -q -m "Initialize DVC" || true{% else %}true{% endif %}
working_directory: "{{ project_root }}"
- command: >-
{% if backup %}touch .backup{% else %}true{% endif %}
working_directory: "{{ project_root }}"
_message_after_copy: |
Project scaffolded:
{% if project_type == 'team' -%}
source_data/{{ project_slug }}/ <- raw/incoming data lands here (immutable, not versioned)
analysis_data/{{ project_slug }}/ <- your working project ({{ 'git' if init_git else 'no git' }}{{ ', dvc' if init_dvc else '' }})
{%- else -%}
home/{{ owner }}/{{ project_slug }}/ <- your personal working folder ({{ 'git' if init_git else 'no git' }}{{ ', dvc' if init_dvc else '' }})
{%- endif %}
Next steps:
- Fill in the README / PROJECT.yml description if you left it as TBD.
{%- if init_dvc and not dvc_remote_url %}
- Set a DVC remote: cd {{ project_root }} && dvc remote add -d storage <url>
{%- endif %}
{%- if project_type == 'team' %}
- Put raw/incoming files under source_data/{{ project_slug }}/ and treat them as read-only.
{%- endif %}
{%- if project_type == 'team' %}
- Backup: {{ 'included' if backup else 'excluded' }} (recorded in PROJECT.yml). Run
scripts/sync-amanda-excludes.sh at the /data root so Amanda's exclude
list picks up this setting.
{%- endif %}