v2.0.0
Loading...
Searching...
No Matches
bids_path.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "bids_path.h"
18#include "bids_const.h"
19
20#include <algorithm>
21
22//=============================================================================================================
23// QT INCLUDES
24//=============================================================================================================
25
26#include <QFileInfo>
27#include <QDirIterator>
28#include <QRegularExpression>
29
30//=============================================================================================================
31// USED NAMESPACES
32//=============================================================================================================
33
34using namespace BIDSLIB;
35
36//=============================================================================================================
37// DEFINE MEMBER METHODS
38//=============================================================================================================
39
43
44//=============================================================================================================
45
46BIDSPath::BIDSPath(const QString& sRoot,
47 const QString& sSubject,
48 const QString& sSession,
49 const QString& sTask,
50 const QString& sDatatype,
51 const QString& sSuffix,
52 const QString& sExtension)
53: m_sRoot(sRoot)
54, m_sSubject(sSubject)
55, m_sSession(sSession)
56, m_sTask(sTask)
57, m_sDatatype(sDatatype)
58, m_sSuffix(sSuffix)
59, m_sExtension(sExtension)
60{
61}
62
63//=============================================================================================================
64
66: m_sRoot(other.m_sRoot)
67, m_sSubject(other.m_sSubject)
68, m_sSession(other.m_sSession)
69, m_sTask(other.m_sTask)
70, m_sAcquisition(other.m_sAcquisition)
71, m_sRun(other.m_sRun)
72, m_sProcessing(other.m_sProcessing)
73, m_sSpace(other.m_sSpace)
74, m_sRecording(other.m_sRecording)
75, m_sSplit(other.m_sSplit)
76, m_sDescription(other.m_sDescription)
77, m_sDatatype(other.m_sDatatype)
78, m_sSuffix(other.m_sSuffix)
79, m_sExtension(other.m_sExtension)
80{
81}
82
83//=============================================================================================================
84
88
89//=============================================================================================================
90// Setters
91//=============================================================================================================
92
93void BIDSPath::setRoot(const QString& sRoot)
94{
95 m_sRoot = sRoot;
96}
97void BIDSPath::setSubject(const QString& sSubject)
98{
99 m_sSubject = sSubject;
100}
101void BIDSPath::setSession(const QString& sSession)
102{
103 m_sSession = sSession;
104}
105void BIDSPath::setTask(const QString& sTask)
106{
107 m_sTask = sTask;
108}
109void BIDSPath::setAcquisition(const QString& sAcq)
110{
111 m_sAcquisition = sAcq;
112}
113void BIDSPath::setRun(const QString& sRun)
114{
115 m_sRun = zeroPad(sRun);
116}
117void BIDSPath::setProcessing(const QString& sProc)
118{
119 m_sProcessing = sProc;
120}
121void BIDSPath::setSpace(const QString& sSpace)
122{
123 m_sSpace = sSpace;
124}
125void BIDSPath::setRecording(const QString& sRec)
126{
127 m_sRecording = sRec;
128}
129void BIDSPath::setSplit(const QString& sSplit)
130{
131 m_sSplit = zeroPad(sSplit);
132}
133void BIDSPath::setDescription(const QString& sDesc)
134{
135 m_sDescription = sDesc;
136}
137void BIDSPath::setDatatype(const QString& sDatatype)
138{
139 m_sDatatype = sDatatype;
140}
141void BIDSPath::setSuffix(const QString& sSuffix)
142{
143 m_sSuffix = sSuffix;
144}
145void BIDSPath::setExtension(const QString& sExtension)
146{
147 m_sExtension = sExtension;
148}
149
150//=============================================================================================================
151// Getters
152//=============================================================================================================
153
154QString BIDSPath::root() const
155{
156 return m_sRoot;
157}
158QString BIDSPath::subject() const
159{
160 return m_sSubject;
161}
162QString BIDSPath::session() const
163{
164 return m_sSession;
165}
166QString BIDSPath::task() const
167{
168 return m_sTask;
169}
171{
172 return m_sAcquisition;
173}
174QString BIDSPath::run() const
175{
176 return m_sRun;
177}
178QString BIDSPath::processing() const
179{
180 return m_sProcessing;
181}
182QString BIDSPath::space() const
183{
184 return m_sSpace;
185}
186QString BIDSPath::recording() const
187{
188 return m_sRecording;
189}
190QString BIDSPath::split() const
191{
192 return m_sSplit;
193}
195{
196 return m_sDescription;
197}
198QString BIDSPath::datatype() const
199{
200 return m_sDatatype;
201}
202QString BIDSPath::suffix() const
203{
204 return m_sSuffix;
205}
206QString BIDSPath::extension() const
207{
208 return m_sExtension;
209}
210
211//=============================================================================================================
212// Path construction
213//=============================================================================================================
214
215QString BIDSPath::basename() const
216{
217 QStringList parts;
218
219 // Build ordered entity key-value pairs
220 if (!m_sSubject.isEmpty())
221 parts << QStringLiteral("sub-") + m_sSubject;
222 if (!m_sSession.isEmpty())
223 parts << QStringLiteral("ses-") + m_sSession;
224 if (!m_sTask.isEmpty())
225 parts << QStringLiteral("task-") + m_sTask;
226 if (!m_sAcquisition.isEmpty())
227 parts << QStringLiteral("acq-") + m_sAcquisition;
228 if (!m_sRun.isEmpty())
229 parts << QStringLiteral("run-") + m_sRun;
230 if (!m_sProcessing.isEmpty())
231 parts << QStringLiteral("proc-") + m_sProcessing;
232 if (!m_sSpace.isEmpty())
233 parts << QStringLiteral("space-") + m_sSpace;
234 if (!m_sRecording.isEmpty())
235 parts << QStringLiteral("recording-") + m_sRecording;
236 if (!m_sSplit.isEmpty())
237 parts << QStringLiteral("split-") + m_sSplit;
238 if (!m_sDescription.isEmpty())
239 parts << QStringLiteral("desc-") + m_sDescription;
240
241 // Append suffix
242 if (!m_sSuffix.isEmpty())
243 parts << m_sSuffix;
244
245 QString name = parts.join(QStringLiteral("_"));
246
247 // Append extension
248 if (!m_sExtension.isEmpty())
249 name += m_sExtension;
250
251 return name;
252}
253
254//=============================================================================================================
255
256QString BIDSPath::directory() const
257{
258 QDir dir(m_sRoot);
259
260 if (!m_sSubject.isEmpty())
261 dir = QDir(dir.filePath(QStringLiteral("sub-") + m_sSubject));
262
263 if (!m_sSession.isEmpty())
264 dir = QDir(dir.filePath(QStringLiteral("ses-") + m_sSession));
265
266 if (!m_sDatatype.isEmpty())
267 dir = QDir(dir.filePath(m_sDatatype));
268
269 return dir.path() + QDir::separator();
270}
271
272//=============================================================================================================
273
274QString BIDSPath::filePath() const
275{
276 return QDir(directory()).filePath(basename());
277}
278
279//=============================================================================================================
280// Convenience methods
281//=============================================================================================================
282
283BIDSPath BIDSPath::withSuffix(const QString& sSuffix, const QString& sExtension) const
284{
285 BIDSPath result(*this);
286 result.m_sSuffix = sSuffix;
287 result.m_sExtension = sExtension;
288 return result;
289}
290
291//=============================================================================================================
292
294{
295 return withSuffix(QStringLiteral("channels"), QStringLiteral(".tsv"));
296}
297
298//=============================================================================================================
299
301{
302 // Electrodes file typically doesn't include task entity
303 BIDSPath result(*this);
304 result.m_sTask.clear();
305 result.m_sRun.clear();
306 result.m_sSuffix = QStringLiteral("electrodes");
307 result.m_sExtension = QStringLiteral(".tsv");
308 return result;
309}
310
311//=============================================================================================================
312
314{
315 // Coordsystem file typically doesn't include task entity
316 BIDSPath result(*this);
317 result.m_sTask.clear();
318 result.m_sRun.clear();
319 result.m_sSuffix = QStringLiteral("coordsystem");
320 result.m_sExtension = QStringLiteral(".json");
321 return result;
322}
323
324//=============================================================================================================
325
327{
328 return withSuffix(QStringLiteral("events"), QStringLiteral(".tsv"));
329}
330
331//=============================================================================================================
332
334{
335 return withSuffix(m_sSuffix.isEmpty() ? m_sDatatype : m_sSuffix,
336 QStringLiteral(".json"));
337}
338
339//=============================================================================================================
340
342{
343 return QFileInfo::exists(filePath());
344}
345
346//=============================================================================================================
347
349{
350 QDir dir(directory());
351 if (dir.exists())
352 return true;
353 return dir.mkpath(QStringLiteral("."));
354}
355
356//=============================================================================================================
357
358QList<BIDSPath> BIDSPath::match() const
359{
360 // Like mne_bids.BIDSPath.match: every set entity must equal the file's, unset ones match anything
361 QList<BIDSPath> results;
362 if (m_sRoot.isEmpty() || !QDir(m_sRoot).exists())
363 return results;
364
365 QDirIterator it(m_sRoot, QDir::Files, QDirIterator::Subdirectories);
366 while (it.hasNext()) {
367 const QFileInfo file(it.next());
368 const QString name = file.fileName();
369 const int dot = name.indexOf(QLatin1Char('.'));
370 if (dot <= 0)
371 continue;
372
373 BIDSPath p;
374 p.setRoot(m_sRoot);
375 p.setExtension(name.mid(dot));
376 const QString dirName = file.dir().dirName();
377 if (!dirName.startsWith(QStringLiteral("sub-")) && !dirName.startsWith(QStringLiteral("ses-")))
378 p.setDatatype(dirName);
379
380 bool valid = true;
381 const QStringList parts = name.left(dot).split(QLatin1Char('_'));
382 for (int i = 0; i < parts.size() && valid; ++i) {
383 const int dash = parts[i].indexOf(QLatin1Char('-'));
384 if (dash < 0) {
385 // Only the last part may be the suffix
386 valid = (i == parts.size() - 1);
387 p.setSuffix(parts[i]);
388 continue;
389 }
390 const QString key = parts[i].left(dash);
391 const QString val = parts[i].mid(dash + 1);
392 if (key == QStringLiteral("sub"))
393 p.setSubject(val);
394 else if (key == QStringLiteral("ses"))
395 p.setSession(val);
396 else if (key == QStringLiteral("task"))
397 p.setTask(val);
398 else if (key == QStringLiteral("acq"))
399 p.setAcquisition(val);
400 else if (key == QStringLiteral("run"))
401 p.setRun(val);
402 else if (key == QStringLiteral("proc"))
403 p.setProcessing(val);
404 else if (key == QStringLiteral("space"))
405 p.setSpace(val);
406 else if (key == QStringLiteral("recording"))
407 p.setRecording(val);
408 else if (key == QStringLiteral("split"))
409 p.setSplit(val);
410 else if (key == QStringLiteral("desc"))
411 p.setDescription(val);
412 }
413 if (!valid || p.subject().isEmpty())
414 continue;
415
416 const QList<std::pair<QString, QString>> wanted{
417 {m_sSubject, p.subject()}, {m_sSession, p.session()}, {m_sTask, p.task()}, {m_sAcquisition, p.acquisition()}, {m_sRun, p.run()}, {m_sProcessing, p.processing()}, {m_sSpace, p.space()}, {m_sRecording, p.recording()}, {m_sSplit, p.split()}, {m_sDescription, p.description()}, {m_sDatatype, p.datatype()}, {m_sSuffix, p.suffix()}, {m_sExtension, p.extension()}};
418 if (std::all_of(wanted.cbegin(), wanted.cend(), [](const auto& w) { return w.first.isEmpty() || w.first == w.second; }))
419 results.append(p);
420 }
421
422 std::sort(results.begin(), results.end(), [](const BIDSPath& a, const BIDSPath& b) { return a.filePath() < b.filePath(); });
423 return results;
424}
425
426//=============================================================================================================
427// Validation
428//=============================================================================================================
429
430bool BIDSPath::isValidEntityValue(const QString& sValue)
431{
432 if (sValue.isEmpty())
433 return true;
434 // Entity values must not contain -, _, or /
435 static const QRegularExpression forbidden(QStringLiteral("[\\-_/]"));
436 return !sValue.contains(forbidden);
437}
438
439//=============================================================================================================
440// Operators
441//=============================================================================================================
442
444{
445 if (this != &other) {
446 m_sRoot = other.m_sRoot;
447 m_sSubject = other.m_sSubject;
448 m_sSession = other.m_sSession;
449 m_sTask = other.m_sTask;
450 m_sAcquisition = other.m_sAcquisition;
451 m_sRun = other.m_sRun;
452 m_sProcessing = other.m_sProcessing;
453 m_sSpace = other.m_sSpace;
454 m_sRecording = other.m_sRecording;
455 m_sSplit = other.m_sSplit;
456 m_sDescription = other.m_sDescription;
457 m_sDatatype = other.m_sDatatype;
458 m_sSuffix = other.m_sSuffix;
459 m_sExtension = other.m_sExtension;
460 }
461 return *this;
462}
463
464//=============================================================================================================
465// Static helpers
466//=============================================================================================================
467
468QString BIDSPath::zeroPad(const QString& sValue)
469{
470 if (sValue.isEmpty())
471 return sValue;
472
473 bool ok = false;
474 int num = sValue.toInt(&ok);
475 if (ok) {
476 return QStringLiteral("%1").arg(num, 2, 10, QLatin1Char('0'));
477 }
478 return sValue;
479}
Centralised BIDS vocabulary: datatype / suffix / extension whitelists, FIFF↔BIDS channel-type and coo...
Programmatic construction and matching of BIDS-compliant directories, filenames and sidecar paths.
BIDS dataset reading, writing, path construction, and sidecar metadata handling for iEEG/EEG/MEG.
QString subject() const
void setSession(const QString &sSession)
void setSpace(const QString &sSpace)
QString filePath() const
QString session() const
void setRecording(const QString &sRecording)
void setSubject(const QString &sSubject)
Definition bids_path.cpp:97
QString split() const
void setDatatype(const QString &sDatatype)
QString description() const
BIDSPath electrodesTsvPath() const
bool exists() const
QString run() const
void setDescription(const QString &sDescription)
void setRun(const QString &sRun)
void setAcquisition(const QString &sAcquisition)
QString root() const
void setSplit(const QString &sSplit)
BIDSPath channelsTsvPath() const
static bool isValidEntityValue(const QString &sValue)
QString directory() const
QString acquisition() const
void setTask(const QString &sTask)
BIDSPath & operator=(const BIDSPath &other)
QString extension() const
void setProcessing(const QString &sProcessing)
void setRoot(const QString &sRoot)
Definition bids_path.cpp:93
QString space() const
QString suffix() const
QString basename() const
void setSuffix(const QString &sSuffix)
QString processing() const
QString recording() const
BIDSPath coordsystemJsonPath() const
QList< BIDSPath > match() const
QString task() const
BIDSPath withSuffix(const QString &sSuffix, const QString &sExtension) const
BIDSPath eventsTsvPath() const
void setExtension(const QString &sExtension)
bool mkdirs() const
QString datatype() const
BIDSPath sidecarJsonPath() const