qmenta-client 1.1.dev1230__py3-none-any.whl → 1.1.dev1289__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qmenta/client/Project.py +397 -112
- {qmenta_client-1.1.dev1230.dist-info → qmenta_client-1.1.dev1289.dist-info}/METADATA +4 -3
- {qmenta_client-1.1.dev1230.dist-info → qmenta_client-1.1.dev1289.dist-info}/RECORD +4 -4
- {qmenta_client-1.1.dev1230.dist-info → qmenta_client-1.1.dev1289.dist-info}/WHEEL +1 -1
qmenta/client/Project.py
CHANGED
|
@@ -11,7 +11,6 @@ from enum import Enum
|
|
|
11
11
|
from qmenta.client import Account
|
|
12
12
|
from qmenta.core import errors
|
|
13
13
|
from qmenta.core import platform
|
|
14
|
-
from .Subject import Subject
|
|
15
14
|
|
|
16
15
|
if sys.version_info[0] == 3:
|
|
17
16
|
# Note: this branch & variable is only needed for python 2/3 compatibility
|
|
@@ -161,45 +160,137 @@ class Project:
|
|
|
161
160
|
dict
|
|
162
161
|
A list of dictionary of {"metadata_name": "metadata_value"}
|
|
163
162
|
"""
|
|
164
|
-
return self.get_subjects_metadata(
|
|
163
|
+
return self.get_subjects_metadata()
|
|
165
164
|
|
|
166
|
-
def get_subjects_metadata(self,
|
|
165
|
+
def get_subjects_metadata(self, search_criteria={}, items=(0, 9999)):
|
|
167
166
|
"""
|
|
168
|
-
List all
|
|
167
|
+
List all subjects data from the selected project that meet the defined
|
|
168
|
+
search criteria.
|
|
169
|
+
|
|
169
170
|
Parameters
|
|
170
171
|
----------
|
|
171
|
-
cache: bool
|
|
172
|
-
Whether to use the cached metadata or not
|
|
173
|
-
|
|
174
172
|
search_criteria: dict
|
|
175
173
|
Each element is a string and is built using the formatting
|
|
176
|
-
|
|
174
|
+
"type;value", or "type;operation|value"
|
|
175
|
+
|
|
176
|
+
Complete search_criteria Dictionary Explanation:
|
|
177
|
+
|
|
178
|
+
search_criteria = {
|
|
179
|
+
"pars_patient_secret_name": "string;SUBJECTID",
|
|
180
|
+
"pars_ssid": "integer;OPERATOR|SSID",
|
|
181
|
+
"pars_modalities": "string;MODALITY",
|
|
182
|
+
"pars_tags": "tags;TAGS",
|
|
183
|
+
"pars_age_at_scan": "integer;OPERATOR|AGE_AT_SCAN",
|
|
184
|
+
"pars_[dicom]_KEY": "KEYTYPE;KEYVALUE",
|
|
185
|
+
"pars_PROJECTMETADATA": "METADATATYPE;METADATAVALUE",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Where:
|
|
189
|
+
"pars_patient_secret_name": Applies the search to the 'Subject ID'.
|
|
190
|
+
SUBJECTID is a comma separated list of strings.
|
|
191
|
+
"pars_ssid": Applies the search to the 'Session ID'.
|
|
192
|
+
SSID is an integer.
|
|
193
|
+
OPERATOR is the operator to apply. One of:
|
|
194
|
+
- Equal: eq
|
|
195
|
+
- Different Than: ne
|
|
196
|
+
- Greater Than: gt
|
|
197
|
+
- Greater/Equal To: gte
|
|
198
|
+
- Lower Than: lt
|
|
199
|
+
- Lower/Equal To: lte
|
|
200
|
+
|
|
201
|
+
"pars_modalities": Applies the search to the file 'Modalities'
|
|
202
|
+
available within each Subject ID.
|
|
203
|
+
MODALITY is a comma separated list of string.
|
|
204
|
+
"pars_tags": Applies the search to the file 'Tags' available within
|
|
205
|
+
each Subject ID and to the subject-level 'Tags'.
|
|
206
|
+
TAGS is a comma separated list of strings.
|
|
207
|
+
"pars_age_at_scan": Applies the search to the 'age_at_scan' metadata
|
|
208
|
+
field.
|
|
209
|
+
AGE_AT_SCAN is an integer.
|
|
210
|
+
"pars_[dicom]_KEY": Applies the search to the metadata fields
|
|
211
|
+
available within each file. KEY must be one of the
|
|
212
|
+
metadata keys of the files. The full list of KEYS is shown above via
|
|
213
|
+
'file_m["metadata"]["info"].keys()'.
|
|
214
|
+
KEYTYPE is the type of the KEY. One of:
|
|
215
|
+
- integer
|
|
216
|
+
- string
|
|
217
|
+
- list
|
|
218
|
+
|
|
219
|
+
if 'integer' you must also include an OPERATOR
|
|
220
|
+
(i.e., "integer;OPERATOR|KEYVALUE").
|
|
221
|
+
KEYVALUE is the expected value of the KEY.
|
|
222
|
+
"pars_[dicom]_PROJECTMETADATA": Applies to the metadata defined
|
|
223
|
+
within the 'Metadata Manager' of the project.
|
|
224
|
+
PROJECTMETADATA is the ID of the metadata field.
|
|
225
|
+
METADATATYPE is the type of the metadata field. One of:
|
|
226
|
+
- string
|
|
227
|
+
- integer
|
|
228
|
+
- list
|
|
229
|
+
- decimal
|
|
230
|
+
- single_option
|
|
231
|
+
- multiple_option
|
|
232
|
+
|
|
233
|
+
if 'integer' or 'decimal' you must also include an OPERATOR
|
|
234
|
+
(i.e., "integer;OPERATOR|METADATAVALUE").
|
|
235
|
+
KEYVALUE is the expected value of the metadata.
|
|
236
|
+
|
|
237
|
+
1) Example:
|
|
238
|
+
search_criteria = {
|
|
239
|
+
"pars_patient_secret_name": "string;abide",
|
|
240
|
+
"pars_ssid": "integer;eq|2"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
2) Example:
|
|
244
|
+
search_criteria = {
|
|
245
|
+
"pars_modalities": "string;T1",
|
|
246
|
+
"pars_tags": "tags;flair",
|
|
247
|
+
"pars_[dicom]_Manufacturer": "string;ge",
|
|
248
|
+
"pars_[dicom]_FlipAngle": "integer;gt|5",
|
|
249
|
+
}
|
|
177
250
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
251
|
+
Note the search criteria applies to all the files included within a
|
|
252
|
+
session. Hence, it does not imply that all the criteria conditions
|
|
253
|
+
are applied to the same files. In example 2) above, it means that
|
|
254
|
+
any Subject ID/Session ID that has a file classified with a 'T1'
|
|
255
|
+
modality, a file with a 'flair' tag, a file whose Manufacturer
|
|
256
|
+
contains 'ge', and a file whose FlipAngle is greater than '5º' will
|
|
257
|
+
be selected.
|
|
181
258
|
|
|
182
259
|
Returns
|
|
183
260
|
-------
|
|
184
261
|
dict
|
|
185
262
|
A list of dictionary of {"metadata_name": "metadata_value"}
|
|
263
|
+
|
|
186
264
|
"""
|
|
187
265
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
266
|
+
assert len(items) == 2, f"The number of elements in items " \
|
|
267
|
+
f"'{len(items)}' should be equal to two."
|
|
268
|
+
assert all([isinstance(item, int) for item in items]), \
|
|
269
|
+
f"All items elements '{items}' should be integers."
|
|
270
|
+
|
|
271
|
+
assert all([key[:5] == "pars_" for key in search_criteria.keys()]), \
|
|
272
|
+
f"All keys of the search_criteria dictionary " \
|
|
273
|
+
f"'{search_criteria.keys()}' must start with 'pars_'."
|
|
274
|
+
|
|
275
|
+
operator_list = ["eq", "ne", "gt", "gte", "lt", "lte"]
|
|
276
|
+
for key, value in search_criteria.items():
|
|
277
|
+
if value.split(";")[0] in ["integer", "decimal"]:
|
|
278
|
+
assert value.split(";")[1].split("|")[0] in operator_list, \
|
|
279
|
+
f"Search criteria of type '{value.split(';')[0]}' must " \
|
|
280
|
+
f"include an operator ({', '.join(operator_list)})."
|
|
281
|
+
|
|
282
|
+
content = platform.parse_response(platform.post(
|
|
283
|
+
self._account.auth, "patient_manager/get_patient_list",
|
|
284
|
+
data=search_criteria,
|
|
285
|
+
headers={"X-Range": f"items={items[0]}-{items[1]}"}
|
|
286
|
+
))
|
|
197
287
|
return content
|
|
198
288
|
|
|
199
289
|
@property
|
|
200
290
|
def subjects(self):
|
|
201
291
|
"""
|
|
202
|
-
Return the list of subject names from the selected
|
|
292
|
+
Return the list of subject names (Subject ID) from the selected
|
|
293
|
+
project.
|
|
203
294
|
|
|
204
295
|
:return: a list of subject names
|
|
205
296
|
:rtype: List(Strings)
|
|
@@ -211,12 +302,13 @@ class Project:
|
|
|
211
302
|
|
|
212
303
|
def check_subject_name(self, subject_name):
|
|
213
304
|
"""
|
|
214
|
-
Check if a given subject name exists in the selected
|
|
305
|
+
Check if a given subject name (Subject ID) exists in the selected
|
|
306
|
+
project.
|
|
215
307
|
|
|
216
308
|
Parameters
|
|
217
309
|
----------
|
|
218
310
|
subject_name : str
|
|
219
|
-
|
|
311
|
+
Subject ID of the subject to check
|
|
220
312
|
|
|
221
313
|
Returns
|
|
222
314
|
-------
|
|
@@ -329,12 +421,32 @@ class Project:
|
|
|
329
421
|
else:
|
|
330
422
|
return None
|
|
331
423
|
|
|
332
|
-
def list_analysis(self,
|
|
424
|
+
def list_analysis(self, search_condition={}, items=(0, 9999)):
|
|
333
425
|
"""
|
|
334
426
|
List the analysis available to the user.
|
|
427
|
+
search condition example:
|
|
428
|
+
search_condition = {
|
|
429
|
+
"secret_name":"014_S_6920",
|
|
430
|
+
"from_d": "06.02.2025",
|
|
431
|
+
"with_child_analysis": 1,
|
|
432
|
+
"state": "completed"
|
|
433
|
+
}
|
|
335
434
|
|
|
336
435
|
Parameters
|
|
337
436
|
----------
|
|
437
|
+
search_condition : dict
|
|
438
|
+
p_n: str # analysis_name
|
|
439
|
+
type: str # analysis_type
|
|
440
|
+
from_d: str # dd.mm.yyyy
|
|
441
|
+
to_d: str # dd.mm.yyyy
|
|
442
|
+
qa_status: str # pass/fail/nd
|
|
443
|
+
secret_name: str # Subject_ID
|
|
444
|
+
tags: str #
|
|
445
|
+
with_child_analysis: 1
|
|
446
|
+
# if 1, child analysis of workflows will appear
|
|
447
|
+
id: str # container_id
|
|
448
|
+
state: # running, completed, pending, exception
|
|
449
|
+
username: str #
|
|
338
450
|
limit : int
|
|
339
451
|
Max number of results
|
|
340
452
|
|
|
@@ -343,55 +455,121 @@ class Project:
|
|
|
343
455
|
dict
|
|
344
456
|
List of analysis, each a dictionary
|
|
345
457
|
"""
|
|
346
|
-
|
|
458
|
+
assert len(items) == 2, f"The number of elements in items " \
|
|
459
|
+
f"'{len(items)}' should be equal to two."
|
|
460
|
+
assert all([isinstance(item, int) for item in items]), \
|
|
461
|
+
f"All items elements '{items}' should be integers."
|
|
462
|
+
search_keys = {
|
|
463
|
+
"p_n": str,
|
|
464
|
+
"type": str,
|
|
465
|
+
"from_d": str,
|
|
466
|
+
"to_d": str,
|
|
467
|
+
"qa_status": str,
|
|
468
|
+
"secret_name": str,
|
|
469
|
+
"tags": str,
|
|
470
|
+
"with_child_analysis": int,
|
|
471
|
+
"id": int,
|
|
472
|
+
"state": str,
|
|
473
|
+
"username": str,
|
|
474
|
+
}
|
|
475
|
+
for key in search_condition.keys():
|
|
476
|
+
if key not in search_keys.keys():
|
|
477
|
+
raise Exception(
|
|
478
|
+
(
|
|
479
|
+
f"This key '{key}' is not accepted by this"
|
|
480
|
+
"search condition"
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
if not isinstance(search_condition[key], search_keys[key]):
|
|
484
|
+
raise Exception(
|
|
485
|
+
(
|
|
486
|
+
f"The key {key} in the search condition"
|
|
487
|
+
f"is not type {search_keys[key]}"
|
|
488
|
+
)
|
|
489
|
+
)
|
|
490
|
+
req_headers = {"X-Range": f"items={items[0]}-{items[1] - 1}"}
|
|
347
491
|
return platform.parse_response(platform.post(
|
|
348
492
|
auth=self._account.auth,
|
|
349
493
|
endpoint="analysis_manager/get_analysis_list",
|
|
350
|
-
headers=req_headers
|
|
494
|
+
headers=req_headers,
|
|
495
|
+
data=search_condition
|
|
351
496
|
))
|
|
352
497
|
|
|
353
|
-
def
|
|
354
|
-
|
|
498
|
+
def get_subject_container_id(self, subject_name, ssid):
|
|
499
|
+
"""
|
|
500
|
+
Given a Subject ID and Session ID, return its Container ID.
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
subject_name : str
|
|
505
|
+
Subject ID of the subject in the project.
|
|
506
|
+
ssid : str
|
|
507
|
+
Session ID of the subject in the project.
|
|
508
|
+
|
|
509
|
+
Returns
|
|
510
|
+
-------
|
|
511
|
+
int or bool
|
|
512
|
+
The Container ID of the subject in the project, or False if
|
|
513
|
+
the subject is not found.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
search_criteria = {
|
|
355
517
|
"s_n": subject_name,
|
|
518
|
+
"ssid": ssid
|
|
356
519
|
}
|
|
357
520
|
response = self.list_input_containers(
|
|
358
|
-
|
|
521
|
+
search_criteria=search_criteria
|
|
359
522
|
)
|
|
360
523
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
else:
|
|
367
|
-
return None
|
|
524
|
+
for subject in response:
|
|
525
|
+
if subject["patient_secret_name"] == subject_name and \
|
|
526
|
+
subject["ssid"] == ssid:
|
|
527
|
+
return subject["container_id"]
|
|
528
|
+
return False
|
|
368
529
|
|
|
369
|
-
def list_input_containers(self,
|
|
530
|
+
def list_input_containers(self, search_criteria={}, items=(0, 9999)):
|
|
370
531
|
"""
|
|
371
|
-
|
|
532
|
+
Retrieve the list of input containers available to the user under a
|
|
533
|
+
certain search criteria.
|
|
372
534
|
|
|
373
535
|
Parameters
|
|
374
536
|
----------
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
537
|
+
search_criteria : dict
|
|
538
|
+
Each element is a string and is built using the formatting
|
|
539
|
+
"type;value".
|
|
540
|
+
|
|
541
|
+
List of possible keys:
|
|
542
|
+
d_n: container_name # TODO: WHAT IS THIS???
|
|
543
|
+
s_n: subject_id
|
|
544
|
+
Subject ID of the subject in the platform.
|
|
545
|
+
ssid: session_id
|
|
546
|
+
Session ID of the subejct in the platform.
|
|
547
|
+
from_d: from date
|
|
548
|
+
Starting date in which perform the search. Format: DD.MM.YYYY
|
|
549
|
+
to_d: to date
|
|
550
|
+
End date in which perform the search. Format: DD.MM.YYYY
|
|
551
|
+
sets: data sets (modalities) # TODO: WHAT IS THIS???
|
|
552
|
+
|
|
553
|
+
items: Tuple(int, int)
|
|
554
|
+
Starting and ending element of the search.
|
|
383
555
|
|
|
384
556
|
Returns
|
|
385
557
|
-------
|
|
386
558
|
dict
|
|
387
|
-
|
|
388
|
-
|
|
559
|
+
List of containers, each a dictionary containing the following
|
|
560
|
+
information:
|
|
561
|
+
{"container_name", "container_id", "patient_secret_name", "ssid"}
|
|
389
562
|
"""
|
|
390
563
|
|
|
391
|
-
|
|
564
|
+
assert len(items) == 2, f"The number of elements in items " \
|
|
565
|
+
f"'{len(items)}' should be equal to two."
|
|
566
|
+
assert all([isinstance(item, int) for item in items]), \
|
|
567
|
+
f"All items elements '{items}' should be integers."
|
|
568
|
+
|
|
392
569
|
response = platform.parse_response(platform.post(
|
|
393
570
|
self._account.auth, "file_manager/get_container_list",
|
|
394
|
-
data=
|
|
571
|
+
data=search_criteria,
|
|
572
|
+
headers={"X-Range": f"items={items[0]}-{items[1]}"}
|
|
395
573
|
))
|
|
396
574
|
containers = [
|
|
397
575
|
{
|
|
@@ -492,12 +670,15 @@ class Project:
|
|
|
492
670
|
Returns
|
|
493
671
|
-------
|
|
494
672
|
dict
|
|
495
|
-
Dictionary with the metadata.
|
|
673
|
+
Dictionary with the metadata. False otherwise.
|
|
496
674
|
"""
|
|
497
675
|
all_metadata = self.list_container_files_metadata(container_id)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
676
|
+
if all_metadata:
|
|
677
|
+
for file_meta in all_metadata:
|
|
678
|
+
if file_meta["name"] == filename:
|
|
679
|
+
return file_meta
|
|
680
|
+
else:
|
|
681
|
+
return False
|
|
501
682
|
|
|
502
683
|
def change_file_metadata(self, container_id, filename, modality, tags):
|
|
503
684
|
"""
|
|
@@ -620,16 +801,17 @@ class Project:
|
|
|
620
801
|
container_id, zip_name))
|
|
621
802
|
return True
|
|
622
803
|
|
|
623
|
-
def get_subject_id(self, subject_name,
|
|
804
|
+
def get_subject_id(self, subject_name, ssid):
|
|
624
805
|
"""
|
|
625
|
-
Given a
|
|
806
|
+
Given a Subject ID and Session ID, return its Patient ID in the
|
|
807
|
+
project.
|
|
626
808
|
|
|
627
809
|
Parameters
|
|
628
810
|
----------
|
|
629
811
|
subject_name : str
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
812
|
+
Subject ID of the subject in the project.
|
|
813
|
+
ssid : str
|
|
814
|
+
Session ID of the subject in the project.
|
|
633
815
|
|
|
634
816
|
Returns
|
|
635
817
|
-------
|
|
@@ -638,37 +820,12 @@ class Project:
|
|
|
638
820
|
the subject is not found.
|
|
639
821
|
"""
|
|
640
822
|
|
|
641
|
-
for user in self.get_subjects_metadata(
|
|
642
|
-
if user["patient_secret_name"] == subject_name
|
|
823
|
+
for user in self.get_subjects_metadata():
|
|
824
|
+
if user["patient_secret_name"] == str(subject_name) and \
|
|
825
|
+
user["ssid"] == str(ssid):
|
|
643
826
|
return int(user["_id"])
|
|
644
827
|
return False
|
|
645
828
|
|
|
646
|
-
def get_subject(self, subject_name, cache=True):
|
|
647
|
-
"""
|
|
648
|
-
Return a subject object, representing a subject from the project.
|
|
649
|
-
|
|
650
|
-
Parameters
|
|
651
|
-
----------
|
|
652
|
-
subject_name : str
|
|
653
|
-
Name of the subject.
|
|
654
|
-
cache: bool
|
|
655
|
-
Whether to use the cached metadata or not
|
|
656
|
-
|
|
657
|
-
Returns
|
|
658
|
-
-------
|
|
659
|
-
Subject or bool
|
|
660
|
-
A Subject instance representing the desired subject, or
|
|
661
|
-
False if the subject was not found.
|
|
662
|
-
|
|
663
|
-
"""
|
|
664
|
-
subject_id = self.get_subject_id(subject_name, cache=cache)
|
|
665
|
-
if subject_id is False:
|
|
666
|
-
return False
|
|
667
|
-
subj = Subject(subject_name)
|
|
668
|
-
subj.subject_id = subject_id
|
|
669
|
-
subj.project = self
|
|
670
|
-
return subj
|
|
671
|
-
|
|
672
829
|
def add_subject(self, subject):
|
|
673
830
|
"""
|
|
674
831
|
Add a subject to the project.
|
|
@@ -704,18 +861,114 @@ class Project:
|
|
|
704
861
|
"Subject {0} was successfully created".format(subject.name))
|
|
705
862
|
return True
|
|
706
863
|
|
|
707
|
-
def
|
|
864
|
+
def change_subject_metadata(self, patient_id, subject_name, ssid, tags,
|
|
865
|
+
age_at_scan, metadata):
|
|
708
866
|
"""
|
|
709
|
-
|
|
867
|
+
Change the Subject ID, Session ID, Tags, Age at Scan and Metadata of
|
|
868
|
+
the session with Patient ID
|
|
869
|
+
|
|
870
|
+
Parameters
|
|
871
|
+
----------
|
|
872
|
+
patient_id : Integer
|
|
873
|
+
Patient ID representing the session to modify.
|
|
874
|
+
subject_name : String
|
|
875
|
+
Represents the new Subject ID.
|
|
876
|
+
ssid : String
|
|
877
|
+
Represents the new Session ID.
|
|
878
|
+
tags : list of strings in lowercase
|
|
879
|
+
Represents the new tags of the session.
|
|
880
|
+
age_at_scan : Integer
|
|
881
|
+
Represents the new Age at Scan of the Session.
|
|
882
|
+
metadata : Dictionary
|
|
883
|
+
Each pair key/value representing the new metadata values.
|
|
884
|
+
|
|
885
|
+
The keys must either all start with "md\\_" or none start
|
|
886
|
+
with "md\\_".
|
|
887
|
+
|
|
888
|
+
The key represents the ID of the metadata field.
|
|
889
|
+
|
|
890
|
+
Returns
|
|
891
|
+
-------
|
|
892
|
+
bool
|
|
893
|
+
True if correctly modified, False otherwise
|
|
894
|
+
"""
|
|
895
|
+
logger = logging.getLogger(logger_name)
|
|
896
|
+
|
|
897
|
+
try:
|
|
898
|
+
patient_id = str(int(patient_id))
|
|
899
|
+
except ValueError:
|
|
900
|
+
raise ValueError(f"'patient_id': '{patient_id}' not valid. "
|
|
901
|
+
f"Must be convertible to int.")
|
|
902
|
+
|
|
903
|
+
assert isinstance(tags, list) and \
|
|
904
|
+
all(isinstance(item, str) for item in tags), \
|
|
905
|
+
f"tags: '{tags}' should be a list of strings."
|
|
906
|
+
tags = [tag.lower() for tag in tags]
|
|
907
|
+
|
|
908
|
+
assert subject_name is not None and subject_name != "", \
|
|
909
|
+
"subject_name must be a non empty string."
|
|
910
|
+
assert ssid is not None and ssid != "", \
|
|
911
|
+
"ssid must be a non empty string."
|
|
912
|
+
|
|
913
|
+
try:
|
|
914
|
+
age_at_scan = str(int(age_at_scan)) if age_at_scan else None
|
|
915
|
+
except ValueError:
|
|
916
|
+
raise ValueError(f"age_at_scan: '{age_at_scan}' not valid. "
|
|
917
|
+
f"Must be an integer.")
|
|
918
|
+
|
|
919
|
+
assert isinstance(metadata, dict), \
|
|
920
|
+
f"metadata: '{metadata}' should be a dictionary."
|
|
921
|
+
|
|
922
|
+
assert all("md_" == key[:3] for key in metadata.keys()) or \
|
|
923
|
+
all("md_" != key[:3] for key in metadata.keys()), \
|
|
924
|
+
f"metadata: '{metadata}' must be a dictionary whose keys " \
|
|
925
|
+
f"are either all starting with 'md_' or none."
|
|
926
|
+
|
|
927
|
+
metadata_keys = self.metadata_parameters.keys()
|
|
928
|
+
assert \
|
|
929
|
+
all([key[3:] in metadata_keys
|
|
930
|
+
if "md_" == key[:3] else key in metadata_keys
|
|
931
|
+
for key in metadata.keys()]), \
|
|
932
|
+
f"Some metadata keys provided ({', '.join(metadata.keys())}) " \
|
|
933
|
+
f"are not available in the project. They can be added via the " \
|
|
934
|
+
f"Metadata Manager via the QMENTA Platform graphical user " \
|
|
935
|
+
f"interface (GUI)."
|
|
936
|
+
|
|
937
|
+
post_data = {
|
|
938
|
+
"patient_id": patient_id,
|
|
939
|
+
"secret_name": str(subject_name),
|
|
940
|
+
"ssid": str(ssid),
|
|
941
|
+
"tags": ",".join(tags),
|
|
942
|
+
"age_at_scan": age_at_scan,
|
|
943
|
+
}
|
|
944
|
+
for key, value in metadata.items():
|
|
945
|
+
id = key[3:] if "md_" == key[:3] else key
|
|
946
|
+
post_data[f"last_vals.{id}"] = value
|
|
947
|
+
|
|
948
|
+
try:
|
|
949
|
+
platform.parse_response(platform.post(
|
|
950
|
+
self._account.auth,
|
|
951
|
+
"patient_manager/upsert_patient",
|
|
952
|
+
data=post_data
|
|
953
|
+
))
|
|
954
|
+
except errors.PlatformError:
|
|
955
|
+
logger.error(f"Patient ID '{patient_id}' could not be modified.")
|
|
956
|
+
return False
|
|
957
|
+
|
|
958
|
+
logger.info(f"Patient ID '{patient_id}' successfully modified.")
|
|
959
|
+
return True
|
|
960
|
+
|
|
961
|
+
def delete_session(self, subject_name, session_id):
|
|
962
|
+
"""
|
|
963
|
+
Delete a session from a subject within a project providing the
|
|
964
|
+
Subject ID and Session ID.
|
|
710
965
|
|
|
711
966
|
Parameters
|
|
712
967
|
----------
|
|
713
968
|
subject_name : str
|
|
714
|
-
|
|
969
|
+
Subject ID of the subject
|
|
715
970
|
session_id : int
|
|
716
|
-
The
|
|
717
|
-
cache : bool
|
|
718
|
-
Whether to use the cached metadata or not
|
|
971
|
+
The Session ID of the session that will be deleted
|
|
719
972
|
|
|
720
973
|
Returns
|
|
721
974
|
-------
|
|
@@ -723,31 +976,31 @@ class Project:
|
|
|
723
976
|
True if correctly deleted, False otherwise.
|
|
724
977
|
"""
|
|
725
978
|
logger = logging.getLogger(logger_name)
|
|
726
|
-
all_sessions = self.get_subjects_metadata(
|
|
979
|
+
all_sessions = self.get_subjects_metadata()
|
|
727
980
|
|
|
728
|
-
|
|
981
|
+
session_to_del = [
|
|
729
982
|
s for s in all_sessions if
|
|
730
|
-
s["patient_secret_name"] == subject_name and
|
|
731
|
-
|
|
732
|
-
) == session_id
|
|
983
|
+
s["patient_secret_name"] == subject_name and
|
|
984
|
+
s["ssid"] == session_id
|
|
733
985
|
]
|
|
734
986
|
|
|
735
|
-
if not
|
|
987
|
+
if not session_to_del:
|
|
736
988
|
logger.error(
|
|
737
989
|
f"Session {subject_name}/{session_id} could not be found "
|
|
738
990
|
f"in this project."
|
|
739
991
|
)
|
|
740
992
|
return False
|
|
741
|
-
elif len(
|
|
993
|
+
elif len(session_to_del) > 1:
|
|
742
994
|
raise RuntimeError(
|
|
743
|
-
"Multiple sessions with same
|
|
995
|
+
"Multiple sessions with same Subject ID and Session ID."
|
|
996
|
+
" Contact support."
|
|
744
997
|
)
|
|
745
998
|
else:
|
|
746
999
|
logger.info("{}/{} found (id {})".format(
|
|
747
|
-
subject_name, session_id,
|
|
1000
|
+
subject_name, session_id, session_to_del[0]["_id"]
|
|
748
1001
|
))
|
|
749
1002
|
|
|
750
|
-
session =
|
|
1003
|
+
session = session_to_del[0]
|
|
751
1004
|
|
|
752
1005
|
try:
|
|
753
1006
|
platform.parse_response(platform.post(
|
|
@@ -767,14 +1020,46 @@ class Project:
|
|
|
767
1020
|
)
|
|
768
1021
|
return True
|
|
769
1022
|
|
|
1023
|
+
def delete_session_by_patientid(self, patient_id):
|
|
1024
|
+
"""
|
|
1025
|
+
Delete a session from a subject within a project providing the
|
|
1026
|
+
Patient ID.
|
|
1027
|
+
|
|
1028
|
+
Parameters
|
|
1029
|
+
----------
|
|
1030
|
+
patient_id : str
|
|
1031
|
+
Patient ID of the Session ID/Subject ID
|
|
1032
|
+
|
|
1033
|
+
Returns
|
|
1034
|
+
-------
|
|
1035
|
+
bool
|
|
1036
|
+
True if correctly deleted, False otherwise.
|
|
1037
|
+
"""
|
|
1038
|
+
logger = logging.getLogger(logger_name)
|
|
1039
|
+
|
|
1040
|
+
try:
|
|
1041
|
+
platform.parse_response(platform.post(
|
|
1042
|
+
self._account.auth, "patient_manager/delete_patient",
|
|
1043
|
+
data={
|
|
1044
|
+
"patient_id": str(int(patient_id)), "delete_files": 1
|
|
1045
|
+
}
|
|
1046
|
+
))
|
|
1047
|
+
except errors.PlatformError:
|
|
1048
|
+
logger.error(f"Patient ID {patient_id} could not be deleted.")
|
|
1049
|
+
return False
|
|
1050
|
+
|
|
1051
|
+
logger.info(f"Patient ID {patient_id} successfully deleted.")
|
|
1052
|
+
return True
|
|
1053
|
+
|
|
770
1054
|
def delete_subject(self, subject_name):
|
|
771
1055
|
"""
|
|
772
|
-
Delete a subject from the project.
|
|
1056
|
+
Delete a subject from the project. It deletes all its available
|
|
1057
|
+
sessions only providing the Subject ID.
|
|
773
1058
|
|
|
774
1059
|
Parameters
|
|
775
1060
|
----------
|
|
776
1061
|
subject_name : str
|
|
777
|
-
|
|
1062
|
+
Subject ID of the subject to be deleted.
|
|
778
1063
|
|
|
779
1064
|
Returns
|
|
780
1065
|
-------
|
|
@@ -784,7 +1069,7 @@ class Project:
|
|
|
784
1069
|
|
|
785
1070
|
logger = logging.getLogger(logger_name)
|
|
786
1071
|
# Always fetch the session IDs from the platform before deleting them
|
|
787
|
-
all_sessions = self.get_subjects_metadata(
|
|
1072
|
+
all_sessions = self.get_subjects_metadata()
|
|
788
1073
|
|
|
789
1074
|
sessions_to_del = [
|
|
790
1075
|
s for s in all_sessions if s["patient_secret_name"] == subject_name
|
|
@@ -799,7 +1084,7 @@ class Project:
|
|
|
799
1084
|
return False
|
|
800
1085
|
|
|
801
1086
|
for ssid in [s["ssid"] for s in sessions_to_del]:
|
|
802
|
-
if not self.delete_session(subject_name, ssid
|
|
1087
|
+
if not self.delete_session(subject_name, ssid):
|
|
803
1088
|
return False
|
|
804
1089
|
return True
|
|
805
1090
|
|
|
@@ -884,16 +1169,16 @@ class Project:
|
|
|
884
1169
|
add_to_container_id=0, chunk_size=2 ** 9,
|
|
885
1170
|
split_data=False):
|
|
886
1171
|
"""
|
|
887
|
-
Upload a file to the platform
|
|
1172
|
+
Upload a ZIP file to the platform.
|
|
888
1173
|
|
|
889
1174
|
Parameters
|
|
890
1175
|
----------
|
|
891
1176
|
file_path : str
|
|
892
|
-
Path to the file to upload.
|
|
1177
|
+
Path to the ZIP file to upload.
|
|
893
1178
|
subject_name : str
|
|
894
|
-
Subject to
|
|
1179
|
+
Subject ID of the data to upload in the project in QMENTA Platform.
|
|
895
1180
|
ssid : str
|
|
896
|
-
|
|
1181
|
+
Session ID of the Subject ID (i.e., ID of the timepoint).
|
|
897
1182
|
date_of_scan : str
|
|
898
1183
|
Date of scan/creation of the file
|
|
899
1184
|
description : str
|
|
@@ -903,7 +1188,7 @@ class Project:
|
|
|
903
1188
|
name : str
|
|
904
1189
|
Name of the file in the platform
|
|
905
1190
|
input_data_type : str
|
|
906
|
-
|
|
1191
|
+
qmenta_medical_image_data:3.11
|
|
907
1192
|
add_to_container_id : int
|
|
908
1193
|
ID of the container to which this file should be added (if id > 0)
|
|
909
1194
|
chunk_size : int
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: qmenta-client
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.dev1289
|
|
4
4
|
Summary: Python client lib to interact with the QMENTA platform.
|
|
5
|
-
Home-page: https://www.qmenta.com/
|
|
6
5
|
Author: QMENTA
|
|
7
6
|
Author-email: dev@qmenta.com
|
|
8
7
|
Requires-Python: >=3.10,<4.0
|
|
@@ -12,5 +11,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Dist: future (>=0.18.2,<0.19.0)
|
|
16
16
|
Requires-Dist: qmenta-core (>=4.0.1,<5.0.0)
|
|
17
|
+
Project-URL: Homepage, https://www.qmenta.com/
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
qmenta/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
|
|
2
2
|
qmenta/client/Account.py,sha256=MEljEy9cmg2uP2FG1d3TZAgfj66EE2_3PQAZ9rvpCXY,9647
|
|
3
3
|
qmenta/client/File.py,sha256=ZgvSqejIosUt4uoX7opUnPnp5XGEaJNMRwFC0mQVB8k,5344
|
|
4
|
-
qmenta/client/Project.py,sha256=
|
|
4
|
+
qmenta/client/Project.py,sha256=Y0G5vSVODThAL8kGwmtkcZrE49vB2LVlNf2eRrqr8dg,59767
|
|
5
5
|
qmenta/client/Subject.py,sha256=lhxxVdQ6d-GNoQC8mrJwa4L1f44nJc4PcJtDspmKN7I,8756
|
|
6
6
|
qmenta/client/__init__.py,sha256=AjTojBhZeW5nl0i605KS8S1Gl5tPNc1hdzD47BGNfoI,147
|
|
7
7
|
qmenta/client/utils.py,sha256=5DK2T_HQprrCwLS0Ycm2CjseaYmAUKaJkJvYoW-Rqzc,2479
|
|
8
|
-
qmenta_client-1.1.
|
|
9
|
-
qmenta_client-1.1.
|
|
10
|
-
qmenta_client-1.1.
|
|
8
|
+
qmenta_client-1.1.dev1289.dist-info/METADATA,sha256=khcRoMKuV0hhGvxc-9tjMy9RRIXugkzyE5sKZ_PuF4M,672
|
|
9
|
+
qmenta_client-1.1.dev1289.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
10
|
+
qmenta_client-1.1.dev1289.dist-info/RECORD,,
|