v2.0.0
Loading...
Searching...
No Matches
bids_path.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "bids_path.h"
40#include "bids_const.h"
41
42//=============================================================================================================
43// QT INCLUDES
44//=============================================================================================================
45
46#include <QFileInfo>
47#include <QDirIterator>
48#include <QRegularExpression>
49
50//=============================================================================================================
51// USED NAMESPACES
52//=============================================================================================================
53
54using namespace BIDSLIB;
55
56//=============================================================================================================
57// DEFINE MEMBER METHODS
58//=============================================================================================================
59
63
64//=============================================================================================================
65
66BIDSPath::BIDSPath(const QString& sRoot,
67 const QString& sSubject,
68 const QString& sSession,
69 const QString& sTask,
70 const QString& sDatatype,
71 const QString& sSuffix,
72 const QString& sExtension)
73 : m_sRoot(sRoot)
74 , m_sSubject(sSubject)
75 , m_sSession(sSession)
76 , m_sTask(sTask)
77 , m_sDatatype(sDatatype)
78 , m_sSuffix(sSuffix)
79 , m_sExtension(sExtension)
80{
81}
82
83//=============================================================================================================
84
86 : m_sRoot(other.m_sRoot)
87 , m_sSubject(other.m_sSubject)
88 , m_sSession(other.m_sSession)
89 , m_sTask(other.m_sTask)
90 , m_sAcquisition(other.m_sAcquisition)
91 , m_sRun(other.m_sRun)
92 , m_sProcessing(other.m_sProcessing)
93 , m_sSpace(other.m_sSpace)
94 , m_sRecording(other.m_sRecording)
95 , m_sSplit(other.m_sSplit)
96 , m_sDescription(other.m_sDescription)
97 , m_sDatatype(other.m_sDatatype)
98 , m_sSuffix(other.m_sSuffix)
99 , m_sExtension(other.m_sExtension)
100{
101}
102
103//=============================================================================================================
104
108
109//=============================================================================================================
110// Setters
111//=============================================================================================================
112
113void BIDSPath::setRoot(const QString& sRoot) { m_sRoot = sRoot; }
114void BIDSPath::setSubject(const QString& sSubject) { m_sSubject = sSubject; }
115void BIDSPath::setSession(const QString& sSession) { m_sSession = sSession; }
116void BIDSPath::setTask(const QString& sTask) { m_sTask = sTask; }
117void BIDSPath::setAcquisition(const QString& sAcq) { m_sAcquisition = sAcq; }
118void BIDSPath::setRun(const QString& sRun) { m_sRun = zeroPad(sRun); }
119void BIDSPath::setProcessing(const QString& sProc) { m_sProcessing = sProc; }
120void BIDSPath::setSpace(const QString& sSpace) { m_sSpace = sSpace; }
121void BIDSPath::setRecording(const QString& sRec) { m_sRecording = sRec; }
122void BIDSPath::setSplit(const QString& sSplit) { m_sSplit = zeroPad(sSplit); }
123void BIDSPath::setDescription(const QString& sDesc) { m_sDescription = sDesc; }
124void BIDSPath::setDatatype(const QString& sDatatype) { m_sDatatype = sDatatype; }
125void BIDSPath::setSuffix(const QString& sSuffix) { m_sSuffix = sSuffix; }
126void BIDSPath::setExtension(const QString& sExtension) { m_sExtension = sExtension; }
127
128//=============================================================================================================
129// Getters
130//=============================================================================================================
131
132QString BIDSPath::root() const { return m_sRoot; }
133QString BIDSPath::subject() const { return m_sSubject; }
134QString BIDSPath::session() const { return m_sSession; }
135QString BIDSPath::task() const { return m_sTask; }
136QString BIDSPath::acquisition() const { return m_sAcquisition; }
137QString BIDSPath::run() const { return m_sRun; }
138QString BIDSPath::processing() const { return m_sProcessing; }
139QString BIDSPath::space() const { return m_sSpace; }
140QString BIDSPath::recording() const { return m_sRecording; }
141QString BIDSPath::split() const { return m_sSplit; }
142QString BIDSPath::description() const { return m_sDescription; }
143QString BIDSPath::datatype() const { return m_sDatatype; }
144QString BIDSPath::suffix() const { return m_sSuffix; }
145QString BIDSPath::extension() const { return m_sExtension; }
146
147//=============================================================================================================
148// Path construction
149//=============================================================================================================
150
151QString BIDSPath::basename() const
152{
153 QStringList parts;
154
155 // Build ordered entity key-value pairs
156 if(!m_sSubject.isEmpty())
157 parts << QStringLiteral("sub-") + m_sSubject;
158 if(!m_sSession.isEmpty())
159 parts << QStringLiteral("ses-") + m_sSession;
160 if(!m_sTask.isEmpty())
161 parts << QStringLiteral("task-") + m_sTask;
162 if(!m_sAcquisition.isEmpty())
163 parts << QStringLiteral("acq-") + m_sAcquisition;
164 if(!m_sRun.isEmpty())
165 parts << QStringLiteral("run-") + m_sRun;
166 if(!m_sProcessing.isEmpty())
167 parts << QStringLiteral("proc-") + m_sProcessing;
168 if(!m_sSpace.isEmpty())
169 parts << QStringLiteral("space-") + m_sSpace;
170 if(!m_sRecording.isEmpty())
171 parts << QStringLiteral("rec-") + m_sRecording;
172 if(!m_sSplit.isEmpty())
173 parts << QStringLiteral("split-") + m_sSplit;
174 if(!m_sDescription.isEmpty())
175 parts << QStringLiteral("desc-") + m_sDescription;
176
177 // Append suffix
178 if(!m_sSuffix.isEmpty())
179 parts << m_sSuffix;
180
181 QString name = parts.join(QStringLiteral("_"));
182
183 // Append extension
184 if(!m_sExtension.isEmpty())
185 name += m_sExtension;
186
187 return name;
188}
189
190//=============================================================================================================
191
192QString BIDSPath::directory() const
193{
194 QDir dir(m_sRoot);
195
196 if(!m_sSubject.isEmpty())
197 dir = QDir(dir.filePath(QStringLiteral("sub-") + m_sSubject));
198
199 if(!m_sSession.isEmpty())
200 dir = QDir(dir.filePath(QStringLiteral("ses-") + m_sSession));
201
202 if(!m_sDatatype.isEmpty())
203 dir = QDir(dir.filePath(m_sDatatype));
204
205 return dir.path() + QDir::separator();
206}
207
208//=============================================================================================================
209
210QString BIDSPath::filePath() const
211{
212 return QDir(directory()).filePath(basename());
213}
214
215//=============================================================================================================
216// Convenience methods
217//=============================================================================================================
218
219BIDSPath BIDSPath::withSuffix(const QString& sSuffix, const QString& sExtension) const
220{
221 BIDSPath result(*this);
222 result.m_sSuffix = sSuffix;
223 result.m_sExtension = sExtension;
224 return result;
225}
226
227//=============================================================================================================
228
230{
231 return withSuffix(QStringLiteral("channels"), QStringLiteral(".tsv"));
232}
233
234//=============================================================================================================
235
237{
238 // Electrodes file typically doesn't include task entity
239 BIDSPath result(*this);
240 result.m_sTask.clear();
241 result.m_sRun.clear();
242 result.m_sSuffix = QStringLiteral("electrodes");
243 result.m_sExtension = QStringLiteral(".tsv");
244 return result;
245}
246
247//=============================================================================================================
248
250{
251 // Coordsystem file typically doesn't include task entity
252 BIDSPath result(*this);
253 result.m_sTask.clear();
254 result.m_sRun.clear();
255 result.m_sSuffix = QStringLiteral("coordsystem");
256 result.m_sExtension = QStringLiteral(".json");
257 return result;
258}
259
260//=============================================================================================================
261
263{
264 return withSuffix(QStringLiteral("events"), QStringLiteral(".tsv"));
265}
266
267//=============================================================================================================
268
270{
271 return withSuffix(m_sSuffix.isEmpty() ? m_sDatatype : m_sSuffix,
272 QStringLiteral(".json"));
273}
274
275//=============================================================================================================
276
278{
279 return QFileInfo::exists(filePath());
280}
281
282//=============================================================================================================
283
285{
286 QDir dir(directory());
287 if(dir.exists())
288 return true;
289 return dir.mkpath(QStringLiteral("."));
290}
291
292//=============================================================================================================
293
294QList<BIDSPath> BIDSPath::match() const
295{
296 QList<BIDSPath> results;
297
298 QDir dir(directory());
299 if(!dir.exists())
300 return results;
301
302 // Build a glob pattern from the set entities
303 QString pattern = QStringLiteral("sub-") + (m_sSubject.isEmpty() ? QStringLiteral("*") : m_sSubject);
304
305 if(!m_sSession.isEmpty())
306 pattern += QStringLiteral("_ses-") + m_sSession;
307 else
308 pattern += QStringLiteral("*");
309
310 pattern += QStringLiteral("*"); // match remaining entities
311
312 if(!m_sSuffix.isEmpty())
313 pattern += QStringLiteral("_") + m_sSuffix;
314
315 if(!m_sExtension.isEmpty())
316 pattern += m_sExtension;
317 else
318 pattern += QStringLiteral(".*");
319
320 QStringList entries = dir.entryList({pattern}, QDir::Files);
321
322 // Parse matching filenames back into BIDSPath objects
323 static const QRegularExpression entityRx(QStringLiteral("(\\w+)-(\\w+)"));
324 for(const QString& entry : entries) {
325 BIDSPath p;
326 p.setRoot(m_sRoot);
327 p.setDatatype(m_sDatatype);
328
329 // Extract entities from filename
330 auto it = entityRx.globalMatch(entry);
331 while(it.hasNext()) {
332 auto match = it.next();
333 const QString key = match.captured(1);
334 const QString val = match.captured(2);
335
336 if(key == QStringLiteral("sub")) p.setSubject(val);
337 else if(key == QStringLiteral("ses")) p.setSession(val);
338 else if(key == QStringLiteral("task")) p.setTask(val);
339 else if(key == QStringLiteral("acq")) p.setAcquisition(val);
340 else if(key == QStringLiteral("run")) p.setRun(val);
341 else if(key == QStringLiteral("proc")) p.setProcessing(val);
342 else if(key == QStringLiteral("space")) p.setSpace(val);
343 else if(key == QStringLiteral("rec")) p.setRecording(val);
344 else if(key == QStringLiteral("split")) p.setSplit(val);
345 else if(key == QStringLiteral("desc")) p.setDescription(val);
346 }
347
348 // Extract suffix and extension
349 // The suffix is the last _<word> before the extension
350 int dotIdx = entry.lastIndexOf(QLatin1Char('.'));
351 if(dotIdx > 0) {
352 p.setExtension(entry.mid(dotIdx));
353 QString nameWithoutExt = entry.left(dotIdx);
354 int lastUnder = nameWithoutExt.lastIndexOf(QLatin1Char('_'));
355 if(lastUnder >= 0) {
356 p.setSuffix(nameWithoutExt.mid(lastUnder + 1));
357 }
358 }
359
360 results.append(p);
361 }
362
363 return results;
364}
365
366//=============================================================================================================
367// Validation
368//=============================================================================================================
369
370bool BIDSPath::isValidEntityValue(const QString& sValue)
371{
372 if(sValue.isEmpty())
373 return true;
374 // Entity values must not contain -, _, or /
375 static const QRegularExpression forbidden(QStringLiteral("[\\-_/]"));
376 return !sValue.contains(forbidden);
377}
378
379//=============================================================================================================
380// Operators
381//=============================================================================================================
382
384{
385 if(this != &other) {
386 m_sRoot = other.m_sRoot;
387 m_sSubject = other.m_sSubject;
388 m_sSession = other.m_sSession;
389 m_sTask = other.m_sTask;
390 m_sAcquisition = other.m_sAcquisition;
391 m_sRun = other.m_sRun;
392 m_sProcessing = other.m_sProcessing;
393 m_sSpace = other.m_sSpace;
394 m_sRecording = other.m_sRecording;
395 m_sSplit = other.m_sSplit;
396 m_sDescription = other.m_sDescription;
397 m_sDatatype = other.m_sDatatype;
398 m_sSuffix = other.m_sSuffix;
399 m_sExtension = other.m_sExtension;
400 }
401 return *this;
402}
403
404//=============================================================================================================
405
406bool operator==(const BIDSPath& a, const BIDSPath& b)
407{
408 return a.root() == b.root() &&
409 a.subject() == b.subject() &&
410 a.session() == b.session() &&
411 a.task() == b.task() &&
412 a.acquisition() == b.acquisition() &&
413 a.run() == b.run() &&
414 a.processing() == b.processing() &&
415 a.space() == b.space() &&
416 a.recording() == b.recording() &&
417 a.split() == b.split() &&
418 a.description() == b.description() &&
419 a.datatype() == b.datatype() &&
420 a.suffix() == b.suffix() &&
421 a.extension() == b.extension();
422}
423
424//=============================================================================================================
425// Static helpers
426//=============================================================================================================
427
428QString BIDSPath::zeroPad(const QString& sValue)
429{
430 if(sValue.isEmpty())
431 return sValue;
432
433 bool ok = false;
434 int num = sValue.toInt(&ok);
435 if(ok) {
436 return QStringLiteral("%1").arg(num, 2, 10, QLatin1Char('0'));
437 }
438 return sValue;
439}
BIDS constants, channel type mappings, and allowed values.
bool operator==(const BIDSPath &a, const BIDSPath &b)
BIDSPath class declaration — BIDS-compliant path construction and entity management.
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)
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)
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