qmenta-sdk-lib 2.0.1.dev3468__tar.gz → 2.0.1.dev3469__tar.gz
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_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/PKG-INFO +1 -1
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/pyproject.toml +1 -1
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/context.py +163 -104
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/context.py +125 -48
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/__init__.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/__init__.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/bids/__init__.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/bids/make_entrypoint.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/bids/wrapper.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/client.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/communication.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/directory_utils.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/executor.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/init.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/__init__.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/client.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/executor.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/parse_settings.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/log_capture.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/make_entrypoint.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/__init__.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/context.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/file_filter.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/inputs.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/integration_ui.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/integration_workflow_ui.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/launch_gui.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/make_files.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/modalities.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/outputs.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/run_test_docker.py +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/templates_tool_maker/Dockerfile_schema +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/templates_tool_maker/description_schema +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/templates_tool_maker/qmenta.png +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/templates_tool_maker/test_tool_schema +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/templates_tool_maker/tool_schema +0 -0
- {qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/tool_maker.py +0 -0
|
@@ -51,7 +51,8 @@ class AnalysisContext:
|
|
|
51
51
|
|
|
52
52
|
def __init__(self, analysis_id, comm):
|
|
53
53
|
"""
|
|
54
|
-
Context object that interfaces with the QMENTA platform. Should not be
|
|
54
|
+
Context object that interfaces with the QMENTA platform. Should not be
|
|
55
|
+
created directly.
|
|
55
56
|
"""
|
|
56
57
|
self.analysis_id = analysis_id
|
|
57
58
|
self.__comm = comm
|
|
@@ -73,15 +74,19 @@ class AnalysisContext:
|
|
|
73
74
|
Returns
|
|
74
75
|
-------
|
|
75
76
|
dict
|
|
76
|
-
Dictionary that contains important information about the
|
|
77
|
+
Dictionary that contains important information about the
|
|
78
|
+
current analysis, such as:
|
|
77
79
|
* state (str) : State of the analysis
|
|
78
80
|
* name (str) : Name of the analysis
|
|
79
81
|
* script_name (str) : Name of the script
|
|
80
|
-
* analysis_id (str) : A unique identifier for the
|
|
81
|
-
|
|
82
|
+
* analysis_id (str) : A unique identifier for the
|
|
83
|
+
current analysis
|
|
84
|
+
* user_id (str) : Username of the user that started
|
|
85
|
+
the analysis
|
|
82
86
|
* patient_secret_name (str) : Name of the subject
|
|
83
87
|
* ssid (str) : Timepoint identifier
|
|
84
|
-
* settings (dict) : Settings dictionary. See documentation in
|
|
88
|
+
* settings (dict) : Settings dictionary. See documentation in
|
|
89
|
+
method `get_settings`
|
|
85
90
|
* tags (list) : List of tags
|
|
86
91
|
* version (str) : Tool version
|
|
87
92
|
"""
|
|
@@ -105,14 +110,17 @@ class AnalysisContext:
|
|
|
105
110
|
Returns
|
|
106
111
|
-------
|
|
107
112
|
dict
|
|
108
|
-
Dictionary that contains important information about the current
|
|
113
|
+
Dictionary that contains important information about the current
|
|
114
|
+
analysis, such as:
|
|
109
115
|
* state (str) : State of the analysis
|
|
110
116
|
* name (str) : Name of the analysis
|
|
111
117
|
* script_name (str) : Name of the script
|
|
112
|
-
* user_id (str) : Username of the user that started the
|
|
118
|
+
* user_id (str) : Username of the user that started the
|
|
119
|
+
analysis
|
|
113
120
|
* patient_secret_name (str) : Name of the subject
|
|
114
121
|
* ssid (str) : Timepoint identifier
|
|
115
|
-
* settings (dict) : Settings dictionary. See documentation in
|
|
122
|
+
* settings (dict) : Settings dictionary. See documentation in
|
|
123
|
+
method `get_settings`
|
|
116
124
|
* tags (list) : List of tags
|
|
117
125
|
* version (str) : Tool version
|
|
118
126
|
"""
|
|
@@ -154,7 +162,8 @@ class AnalysisContext:
|
|
|
154
162
|
|
|
155
163
|
def get_communication_object(self):
|
|
156
164
|
"""
|
|
157
|
-
Return the communication object used for communication
|
|
165
|
+
Return the communication object used for communication
|
|
166
|
+
with the platform
|
|
158
167
|
|
|
159
168
|
Returns
|
|
160
169
|
-------
|
|
@@ -178,11 +187,13 @@ class AnalysisContext:
|
|
|
178
187
|
status : QCStatus
|
|
179
188
|
QCStatus.PASS or QCStatus.FAIL
|
|
180
189
|
comments : str, optional
|
|
181
|
-
Additional comments explaining why the QC status has been
|
|
190
|
+
Additional comments explaining why the QC status has been
|
|
191
|
+
set to pass or fail.
|
|
182
192
|
entity : QCEntity, optional
|
|
183
193
|
QCEntity.ANALYSIS or QCEntity.SESSION
|
|
184
194
|
input_id : str, optional
|
|
185
|
-
The id of the session in the settings definition when
|
|
195
|
+
The id of the session in the settings definition when
|
|
196
|
+
entity is QCEntity.SESSION
|
|
186
197
|
"""
|
|
187
198
|
if entity == self.QCEntity.ANALYSIS:
|
|
188
199
|
item_id = self.analysis_id
|
|
@@ -232,7 +243,8 @@ class AnalysisContext:
|
|
|
232
243
|
"patient_manager/get_patient_list",
|
|
233
244
|
req_headers={"X-Range": "items=0-9999"},
|
|
234
245
|
req_data={
|
|
235
|
-
"pars_patient_secret_name":
|
|
246
|
+
"pars_patient_secret_name": "string;"
|
|
247
|
+
f"{self.analysis_data['patient_secret_name']}"
|
|
236
248
|
},
|
|
237
249
|
)
|
|
238
250
|
self.logger.info(data_sessions[0])
|
|
@@ -349,7 +361,8 @@ class AnalysisContext:
|
|
|
349
361
|
Parameters
|
|
350
362
|
----------
|
|
351
363
|
value : int, optional
|
|
352
|
-
Number between 0 and 100 indicating the current status of the
|
|
364
|
+
Number between 0 and 100 indicating the current status of the
|
|
365
|
+
execution
|
|
353
366
|
message : str, optional
|
|
354
367
|
Progress message.
|
|
355
368
|
"""
|
|
@@ -366,12 +379,14 @@ class AnalysisContext:
|
|
|
366
379
|
value = int(value) # Cast to int to avoid float/string problems
|
|
367
380
|
if value > 100:
|
|
368
381
|
self.logger.warning(
|
|
369
|
-
"The progress value should be between 0 and 100
|
|
382
|
+
"The progress value should be between 0 and 100 "
|
|
383
|
+
"(using 100 instead)"
|
|
370
384
|
)
|
|
371
385
|
value = 100
|
|
372
386
|
elif value < 0:
|
|
373
387
|
self.logger.warning(
|
|
374
|
-
"The progress value should be between 0 and 100
|
|
388
|
+
"The progress value should be between 0 and 100 "
|
|
389
|
+
"(using 0 instead)"
|
|
375
390
|
)
|
|
376
391
|
value = 0
|
|
377
392
|
data_to_send["value"] = value
|
|
@@ -391,8 +406,10 @@ class AnalysisContext:
|
|
|
391
406
|
Returns
|
|
392
407
|
-------
|
|
393
408
|
dict
|
|
394
|
-
Settings for the current analysis. This includes all the
|
|
395
|
-
|
|
409
|
+
Settings for the current analysis. This includes all the
|
|
410
|
+
parameters defined in the tool specification
|
|
411
|
+
(checkboxes, input fields, etc.) and can be accessed using their
|
|
412
|
+
identifier.
|
|
396
413
|
"""
|
|
397
414
|
return self.analysis_data["settings"]
|
|
398
415
|
|
|
@@ -404,10 +421,13 @@ class AnalysisContext:
|
|
|
404
421
|
reg_expression=None,
|
|
405
422
|
file_filter_condition_name=None,
|
|
406
423
|
subject_container_id=None,
|
|
424
|
+
unpack=True,
|
|
407
425
|
):
|
|
408
426
|
"""
|
|
409
|
-
Returns the files in the input container specified by
|
|
410
|
-
|
|
427
|
+
Returns the files in the input container specified by
|
|
428
|
+
`input_id` that match the modality, tags
|
|
429
|
+
and regular expression, if any. (X)OR that are selected by
|
|
430
|
+
the file filter condition
|
|
411
431
|
defined in the tool settings.
|
|
412
432
|
|
|
413
433
|
Parameters
|
|
@@ -418,38 +438,47 @@ class AnalysisContext:
|
|
|
418
438
|
Optional modality, will allow any modalities if None given.
|
|
419
439
|
Default is None.
|
|
420
440
|
tags : set, optional
|
|
421
|
-
Optional set of tags, will allow any tags if None given,
|
|
441
|
+
Optional set of tags, will allow any tags if None given,
|
|
442
|
+
and will match any set of tags (including
|
|
422
443
|
the empty set) otherwise.
|
|
423
444
|
Default is None.
|
|
424
445
|
reg_expression : str, optional
|
|
425
|
-
Regular expression to match with the file names in the specified
|
|
446
|
+
Regular expression to match with the file names in the specified
|
|
447
|
+
container, will allow any file names
|
|
426
448
|
if None given.
|
|
427
449
|
Default is None.
|
|
428
450
|
file_filter_condition_name : str, optional
|
|
429
451
|
File filter specified in the settings of the tool.
|
|
430
452
|
Default is None.
|
|
431
453
|
subject_container_id : str, optional
|
|
454
|
+
unpack: bool, optional
|
|
432
455
|
|
|
433
456
|
Examples
|
|
434
457
|
--------
|
|
435
458
|
|
|
436
|
-
Get all files that passed c_TEST (including any selection
|
|
459
|
+
Get all files that passed c_TEST (including any selection
|
|
460
|
+
the user may have done using the GUI)
|
|
437
461
|
|
|
438
462
|
>>> context.get_files('input', file_filter_condition_name='c_TEST')
|
|
439
463
|
|
|
440
464
|
Get all files that passed c_TEST and have no tags
|
|
441
465
|
|
|
442
|
-
>>> context.get_files('input', file_fitler_condition_name='c_TEST',
|
|
466
|
+
>>> context.get_files('input', file_fitler_condition_name='c_TEST',
|
|
467
|
+
tags=set()
|
|
443
468
|
|
|
444
|
-
Get all files that passed c_TEST and have the DTI tag in their
|
|
469
|
+
Get all files that passed c_TEST and have the DTI tag in their
|
|
470
|
+
list of tags
|
|
445
471
|
|
|
446
|
-
>>> context.get_files('input', file_fitler_condition_name='c_TEST',
|
|
472
|
+
>>> context.get_files('input', file_fitler_condition_name='c_TEST',
|
|
473
|
+
tags={'DTI'})
|
|
447
474
|
|
|
448
475
|
Get all files that passed c_TEST whose name match the given regexp
|
|
449
476
|
|
|
450
|
-
>>> context.get_files('input', file_filter_condition_name='c_TEST',
|
|
477
|
+
>>> context.get_files('input', file_filter_condition_name='c_TEST',
|
|
478
|
+
reg_expression='^some_expression[0-9]')
|
|
451
479
|
|
|
452
|
-
Alternatively, you can use no file_filter_condition_name to browse
|
|
480
|
+
Alternatively, you can use no file_filter_condition_name to browse
|
|
481
|
+
through the whole input container
|
|
453
482
|
|
|
454
483
|
>>> context.get_files('input')
|
|
455
484
|
|
|
@@ -475,7 +504,10 @@ class AnalysisContext:
|
|
|
475
504
|
)
|
|
476
505
|
|
|
477
506
|
if is_subject_setting and subject_container_id is None:
|
|
478
|
-
msg =
|
|
507
|
+
msg = (
|
|
508
|
+
"must define subject_container_id for subjects with"
|
|
509
|
+
" multiple sessions"
|
|
510
|
+
)
|
|
479
511
|
self.logger.error(msg)
|
|
480
512
|
raise Exception(msg)
|
|
481
513
|
|
|
@@ -525,7 +557,8 @@ class AnalysisContext:
|
|
|
525
557
|
]
|
|
526
558
|
|
|
527
559
|
if file_filter_condition_name:
|
|
528
|
-
# Get the files that passed the file_filter and take those that
|
|
560
|
+
# Get the files that passed the file_filter and take those that
|
|
561
|
+
# have the required conditions
|
|
529
562
|
|
|
530
563
|
selected_files = []
|
|
531
564
|
|
|
@@ -535,9 +568,8 @@ class AnalysisContext:
|
|
|
535
568
|
file_filter_condition_name
|
|
536
569
|
]
|
|
537
570
|
except KeyError:
|
|
538
|
-
msg = "File filter condition {}
|
|
539
|
-
|
|
540
|
-
)
|
|
571
|
+
msg = f"File filter condition {file_filter_condition_name}"
|
|
572
|
+
f"not specified in the container {input_id} settings !"
|
|
541
573
|
self.logger.error(msg)
|
|
542
574
|
raise Exception(msg)
|
|
543
575
|
|
|
@@ -575,7 +607,8 @@ class AnalysisContext:
|
|
|
575
607
|
|
|
576
608
|
return selected_files
|
|
577
609
|
else:
|
|
578
|
-
# in case the file filter condition name is not specified then we
|
|
610
|
+
# in case the file filter condition name is not specified then we
|
|
611
|
+
# need to take the other
|
|
579
612
|
# concrete search conditions i.e. modality, tags, reg_expression
|
|
580
613
|
|
|
581
614
|
# filter the files according to the filter defined
|
|
@@ -607,7 +640,8 @@ class AnalysisContext:
|
|
|
607
640
|
selected_files = []
|
|
608
641
|
for file_data in container_data["meta"]:
|
|
609
642
|
|
|
610
|
-
# TODO: Here we are assuming that file metadata has a name and a
|
|
643
|
+
# TODO: Here we are assuming that file metadata has a name and a
|
|
644
|
+
# metadata attribute. Need of jsonschema
|
|
611
645
|
|
|
612
646
|
fname, fmetadata, ftags = (
|
|
613
647
|
file_data["name"],
|
|
@@ -651,13 +685,15 @@ class AnalysisContext:
|
|
|
651
685
|
patient_metadata_id=None,
|
|
652
686
|
):
|
|
653
687
|
"""
|
|
654
|
-
Add a new scan session. Only functional for analysis tools of type
|
|
688
|
+
Add a new scan session. Only functional for analysis tools of type
|
|
689
|
+
`data`.
|
|
655
690
|
|
|
656
691
|
Parameters
|
|
657
692
|
----------
|
|
658
693
|
ssid : int, optional
|
|
659
694
|
date_at_scan : str, optional
|
|
660
|
-
Possible formats: '%d.%m.%Y' / '%Y-%m-%d'.
|
|
695
|
+
Possible formats: '%d.%m.%Y' / '%Y-%m-%d'.
|
|
696
|
+
Examples: '01.01.2000' / '2000-01-01'
|
|
661
697
|
age_at_scan : int, optional
|
|
662
698
|
backup_age : int, optional
|
|
663
699
|
patient_metadata_id : int, optional
|
|
@@ -671,13 +707,15 @@ class AnalysisContext:
|
|
|
671
707
|
patient_secret_name = self.analysis_data["patient_secret_name"]
|
|
672
708
|
except KeyError:
|
|
673
709
|
raise RuntimeError(
|
|
674
|
-
"Patient secret name not available in analysis information.
|
|
710
|
+
"Patient secret name not available in analysis information. "
|
|
711
|
+
"Check platform failures."
|
|
675
712
|
)
|
|
676
713
|
try:
|
|
677
714
|
md5_original_file = self.get_settings()["md5_original_file"]
|
|
678
715
|
except KeyError:
|
|
679
716
|
raise RuntimeError(
|
|
680
|
-
"MD5 hash not available via analysis settings.
|
|
717
|
+
"MD5 hash not available via analysis settings. "
|
|
718
|
+
"Check platform failures."
|
|
681
719
|
)
|
|
682
720
|
|
|
683
721
|
data = {
|
|
@@ -732,9 +770,8 @@ class AnalysisContext:
|
|
|
732
770
|
return True
|
|
733
771
|
else:
|
|
734
772
|
raise RuntimeError(
|
|
735
|
-
"Intermediate chunk upload
|
|
736
|
-
|
|
737
|
-
)
|
|
773
|
+
"Intermediate chunk upload "
|
|
774
|
+
f"failed: {response.text} != {expected_response}"
|
|
738
775
|
)
|
|
739
776
|
except Exception as e:
|
|
740
777
|
logger.error(
|
|
@@ -813,7 +850,8 @@ class AnalysisContext:
|
|
|
813
850
|
file_info : dict, optional
|
|
814
851
|
File information metadata.
|
|
815
852
|
file_format : str, optional
|
|
816
|
-
Use "dicom" when .zip file with .dcm slices (uploading a "nifti"
|
|
853
|
+
Use "dicom" when .zip file with .dcm slices (uploading a "nifti"
|
|
854
|
+
file is automatically recognized as such).
|
|
817
855
|
protocol : str, optional
|
|
818
856
|
container_id : str, optional
|
|
819
857
|
replace : str, optional
|
|
@@ -862,7 +900,7 @@ class AnalysisContext:
|
|
|
862
900
|
) and chunk_retry_count <= self.__max_chunk_retries:
|
|
863
901
|
start_position = chunk_num * self.__comm.chunk_size
|
|
864
902
|
self.logger.debug(
|
|
865
|
-
"Chunk size is {
|
|
903
|
+
"Chunk size " f"is {self.__comm.chunk_size}"
|
|
866
904
|
)
|
|
867
905
|
end_position = start_position + self.__comm.chunk_size - 1
|
|
868
906
|
bytes_to_send = self.__comm.chunk_size
|
|
@@ -883,9 +921,8 @@ class AnalysisContext:
|
|
|
883
921
|
"Content-Range": bytes_range,
|
|
884
922
|
"Session-ID": str(session_id),
|
|
885
923
|
"Content-Length": str(bytes_to_send),
|
|
886
|
-
"Content-Disposition": "attachment;
|
|
887
|
-
|
|
888
|
-
),
|
|
924
|
+
"Content-Disposition": "attachment; "
|
|
925
|
+
f"filename={file_name}",
|
|
889
926
|
}
|
|
890
927
|
|
|
891
928
|
# If it is the last chunk, define more header fields
|
|
@@ -893,35 +930,30 @@ class AnalysisContext:
|
|
|
893
930
|
chunk_num * self.__comm.chunk_size + bytes_to_send
|
|
894
931
|
)
|
|
895
932
|
if accumulated_bytes == total_bytes:
|
|
933
|
+
last_data = {
|
|
934
|
+
"X-Mint-Analysis-Output": str(self.analysis_id),
|
|
935
|
+
"X-Mint-File-Destination-Path": destination_path,
|
|
936
|
+
"X-Mint-File-Tags": ",".join(tags),
|
|
937
|
+
}
|
|
896
938
|
self.logger.debug("Sending last data chunk")
|
|
897
|
-
req_headers.update(
|
|
898
|
-
{
|
|
899
|
-
"X-Mint-Analysis-Output": str(
|
|
900
|
-
self.analysis_id
|
|
901
|
-
),
|
|
902
|
-
"X-Mint-File-Destination-Path": destination_path,
|
|
903
|
-
"X-Mint-File-Tags": ",".join(tags),
|
|
904
|
-
}
|
|
905
|
-
)
|
|
939
|
+
req_headers.update(last_data)
|
|
906
940
|
|
|
907
941
|
# Conditional headers
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
)
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
)
|
|
924
|
-
req_headers["X-Mint-Replace-Flag"] = replace.value
|
|
942
|
+
for key, value, attr in [
|
|
943
|
+
("X-Mint-File-Info", file_info, None),
|
|
944
|
+
("X-Mint-File-Format", file_format, str),
|
|
945
|
+
("X-Mint-Protocol", protocol, str),
|
|
946
|
+
("X-Mint-Container", container_id, str),
|
|
947
|
+
(
|
|
948
|
+
"X-Mint-Replace-Flag",
|
|
949
|
+
replace,
|
|
950
|
+
lambda x: x.value,
|
|
951
|
+
),
|
|
952
|
+
]:
|
|
953
|
+
if value:
|
|
954
|
+
req_headers[key] = (
|
|
955
|
+
str(value) if attr is None else attr(value)
|
|
956
|
+
)
|
|
925
957
|
req_headers["X-Requested-With"] = "XMLHttpRequest"
|
|
926
958
|
|
|
927
959
|
# Send data chunk
|
|
@@ -943,9 +975,8 @@ class AnalysisContext:
|
|
|
943
975
|
wait_time = 2 * wait_time
|
|
944
976
|
time.sleep(wait_time)
|
|
945
977
|
|
|
946
|
-
if
|
|
947
|
-
|
|
948
|
-
): # Restarted when successful upload
|
|
978
|
+
if data or chunk_retry_count != 0:
|
|
979
|
+
# Restarted when successful upload
|
|
949
980
|
msg = (
|
|
950
981
|
"File {} could not be uploaded to the platform.\n"
|
|
951
982
|
"data - {}\n"
|
|
@@ -977,6 +1008,9 @@ class AnalysisContext:
|
|
|
977
1008
|
),
|
|
978
1009
|
"container_id": container_id,
|
|
979
1010
|
}
|
|
1011
|
+
|
|
1012
|
+
self.logger.info("REQUEST DATA" + str(request_data))
|
|
1013
|
+
|
|
980
1014
|
url_request_response = self.__comm.send_request(
|
|
981
1015
|
"file_manager/get_upload_url", req_data=request_data
|
|
982
1016
|
)
|
|
@@ -1038,13 +1072,16 @@ class AnalysisContext:
|
|
|
1038
1072
|
self.logger.error(msg)
|
|
1039
1073
|
raise RuntimeError(msg)
|
|
1040
1074
|
|
|
1075
|
+
req_data = {
|
|
1076
|
+
"container_id": container_id,
|
|
1077
|
+
"path": destination_path,
|
|
1078
|
+
"md5": md5_hash,
|
|
1079
|
+
}
|
|
1080
|
+
self.logger.info("VERIFY RESPONSE " + str(req_data))
|
|
1081
|
+
|
|
1041
1082
|
verification_response = self.__comm.send_request(
|
|
1042
1083
|
"file_manager/verify_upload_via_url",
|
|
1043
|
-
req_data=
|
|
1044
|
-
"container_id": container_id,
|
|
1045
|
-
"path": destination_path,
|
|
1046
|
-
"md5": md5_hash,
|
|
1047
|
-
},
|
|
1084
|
+
req_data=req_data,
|
|
1048
1085
|
)
|
|
1049
1086
|
if not verification_response.get("success", False):
|
|
1050
1087
|
msg = "The upload could not be verified: {}".format(
|
|
@@ -1057,7 +1094,8 @@ class AnalysisContext:
|
|
|
1057
1094
|
|
|
1058
1095
|
def download_resource(self, resource_path, destination_path):
|
|
1059
1096
|
"""
|
|
1060
|
-
Downloads a resource file from the bucket (user/group based resources,
|
|
1097
|
+
Downloads a resource file from the bucket (user/group based resources,
|
|
1098
|
+
or legacy).
|
|
1061
1099
|
|
|
1062
1100
|
Parameters
|
|
1063
1101
|
----------
|
|
@@ -1108,7 +1146,8 @@ class AnalysisContext:
|
|
|
1108
1146
|
|
|
1109
1147
|
def _get_manual_analysis_data(self):
|
|
1110
1148
|
"""
|
|
1111
|
-
Returns a dictionary with the manual analysis data generated during
|
|
1149
|
+
Returns a dictionary with the manual analysis data generated during
|
|
1150
|
+
the manual step (user interaction)
|
|
1112
1151
|
|
|
1113
1152
|
Returns
|
|
1114
1153
|
-------
|
|
@@ -1156,11 +1195,13 @@ class AnalysisContext:
|
|
|
1156
1195
|
title : str, optional
|
|
1157
1196
|
How the metadata field should be presented
|
|
1158
1197
|
readonly : bool, optional
|
|
1159
|
-
Whether the user should be able to edit the value on the platform
|
|
1198
|
+
Whether the user should be able to edit the value on the platform
|
|
1199
|
+
or not (only by analyses)
|
|
1160
1200
|
patient_secret_name : str, optional
|
|
1161
1201
|
Manually select the patient_secret_name
|
|
1162
1202
|
ssid : str, optional
|
|
1163
|
-
Manually select the ssid. This is useful to choose a specific
|
|
1203
|
+
Manually select the ssid. This is useful to choose a specific
|
|
1204
|
+
session in a longitudinal analysis.
|
|
1164
1205
|
"""
|
|
1165
1206
|
|
|
1166
1207
|
if not isinstance(key, str):
|
|
@@ -1172,7 +1213,8 @@ class AnalysisContext:
|
|
|
1172
1213
|
)
|
|
1173
1214
|
if not isinstance(value, (int, list, str, float)):
|
|
1174
1215
|
raise Exception(
|
|
1175
|
-
"Parameter 'value' must be of type 'int', 'list', 'str',
|
|
1216
|
+
"Parameter 'value' must be of type 'int', 'list', 'str', "
|
|
1217
|
+
"or 'float'."
|
|
1176
1218
|
)
|
|
1177
1219
|
|
|
1178
1220
|
if not self.get_metadata_value(key):
|
|
@@ -1241,11 +1283,13 @@ class AnalysisContext:
|
|
|
1241
1283
|
Parameters
|
|
1242
1284
|
----------
|
|
1243
1285
|
key : str, optional
|
|
1244
|
-
The ID of the metadata parameter. If not set, it will return all
|
|
1286
|
+
The ID of the metadata parameter. If not set, it will return all
|
|
1287
|
+
the metadata for the session.
|
|
1245
1288
|
patient_secret_name: str, optional
|
|
1246
1289
|
Manually select the patient_secret_name
|
|
1247
1290
|
ssid : str, optional
|
|
1248
|
-
Manually select the ssid. This is useful to choose a specific
|
|
1291
|
+
Manually select the ssid. This is useful to choose a specific
|
|
1292
|
+
session in a longitudinal analysis.
|
|
1249
1293
|
|
|
1250
1294
|
Returns
|
|
1251
1295
|
-------
|
|
@@ -1294,7 +1338,8 @@ class AnalysisContext:
|
|
|
1294
1338
|
Parameters
|
|
1295
1339
|
----------
|
|
1296
1340
|
params : list
|
|
1297
|
-
The definition of the parameter. It may contain "title", "type",
|
|
1341
|
+
The definition of the parameter. It may contain "title", "type",
|
|
1342
|
+
"visible", "readonly" or "order".
|
|
1298
1343
|
"""
|
|
1299
1344
|
|
|
1300
1345
|
if not isinstance(params, list):
|
|
@@ -1302,7 +1347,10 @@ class AnalysisContext:
|
|
|
1302
1347
|
|
|
1303
1348
|
for data_to_send in params:
|
|
1304
1349
|
if not isinstance(data_to_send, dict):
|
|
1305
|
-
msg =
|
|
1350
|
+
msg = (
|
|
1351
|
+
"Format error. Metadata param configuration should be "
|
|
1352
|
+
"stored in a dictionary"
|
|
1353
|
+
)
|
|
1306
1354
|
self.logger.error(msg)
|
|
1307
1355
|
raise TypeError(msg)
|
|
1308
1356
|
|
|
@@ -1380,9 +1428,8 @@ class AnalysisContext:
|
|
|
1380
1428
|
"""
|
|
1381
1429
|
if not isinstance(variables_dict, dict):
|
|
1382
1430
|
self.logger.error(
|
|
1383
|
-
"The output variables are not of type dictionary !
|
|
1384
|
-
|
|
1385
|
-
)
|
|
1431
|
+
"The output variables are not of type dictionary ! "
|
|
1432
|
+
"(actual type {})".format(str(type(variables_dict)))
|
|
1386
1433
|
)
|
|
1387
1434
|
raise RuntimeError(
|
|
1388
1435
|
"The output variables are not of type dictionary !"
|
|
@@ -1470,7 +1517,8 @@ class Container:
|
|
|
1470
1517
|
class File:
|
|
1471
1518
|
def __init__(self, container_id, name, metadata, tags, comm):
|
|
1472
1519
|
"""
|
|
1473
|
-
Object that represents a file and all its metadata in the platform.
|
|
1520
|
+
Object that represents a file and all its metadata in the platform.
|
|
1521
|
+
Should not be created directly.
|
|
1474
1522
|
"""
|
|
1475
1523
|
self.__comm = comm
|
|
1476
1524
|
self.__container_id = container_id
|
|
@@ -1504,13 +1552,16 @@ class File:
|
|
|
1504
1552
|
|
|
1505
1553
|
def get_file_info(self):
|
|
1506
1554
|
"""
|
|
1507
|
-
Get the file information. The type of information depends on the
|
|
1508
|
-
|
|
1555
|
+
Get the file information. The type of information depends on the
|
|
1556
|
+
type of file
|
|
1557
|
+
(e.g. nifti files include information such as 'Data strides',
|
|
1558
|
+
'Data type', 'Dimensions' or 'Voxel size').
|
|
1509
1559
|
|
|
1510
1560
|
Returns
|
|
1511
1561
|
-------
|
|
1512
1562
|
dict
|
|
1513
|
-
Dictionary of key-value pairs that depend on the format of the
|
|
1563
|
+
Dictionary of key-value pairs that depend on the format of the
|
|
1564
|
+
file.
|
|
1514
1565
|
"""
|
|
1515
1566
|
return self.__metadata.get("info", {})
|
|
1516
1567
|
|
|
@@ -1540,8 +1591,9 @@ class File:
|
|
|
1540
1591
|
If `get_file_path` is called before the file has been downloaded
|
|
1541
1592
|
"""
|
|
1542
1593
|
if self.__download_path is None:
|
|
1543
|
-
err_msg =
|
|
1544
|
-
|
|
1594
|
+
err_msg = (
|
|
1595
|
+
"File {!r} has no path because it has not been"
|
|
1596
|
+
" downloaded".format(self.name)
|
|
1545
1597
|
)
|
|
1546
1598
|
self.logger.error(err_msg)
|
|
1547
1599
|
raise RuntimeError(err_msg)
|
|
@@ -1599,7 +1651,8 @@ class File:
|
|
|
1599
1651
|
|
|
1600
1652
|
os.makedirs(os.path.dirname(destination_file_path), exist_ok=True)
|
|
1601
1653
|
with open(destination_file_path, "wb") as fp:
|
|
1602
|
-
# For Google Cloud Bucket, performance is at its best at 1 MB or
|
|
1654
|
+
# For Google Cloud Bucket, performance is at its best at 1 MB or
|
|
1655
|
+
# larger chunks.
|
|
1603
1656
|
for chunk in download_response.iter_content(
|
|
1604
1657
|
chunk_size=self.__comm.chunk_size
|
|
1605
1658
|
): # Use 10 MB chunk
|
|
@@ -1614,14 +1667,16 @@ class File:
|
|
|
1614
1667
|
|
|
1615
1668
|
def download(self, dest_path, unpack=True):
|
|
1616
1669
|
"""
|
|
1617
|
-
Downloads a file or the contents of a packed file to to the
|
|
1670
|
+
Downloads a file or the contents of a packed file to to the
|
|
1671
|
+
specified `path`.
|
|
1618
1672
|
|
|
1619
1673
|
Parameters
|
|
1620
1674
|
----------
|
|
1621
1675
|
dest_path:
|
|
1622
1676
|
Path where the file should be downloaded to.
|
|
1623
1677
|
unpack:
|
|
1624
|
-
Tells the function whether the file should be unpacked to the
|
|
1678
|
+
Tells the function whether the file should be unpacked to the
|
|
1679
|
+
given folder.
|
|
1625
1680
|
|
|
1626
1681
|
Returns
|
|
1627
1682
|
-------
|
|
@@ -1636,13 +1691,16 @@ class File:
|
|
|
1636
1691
|
self.logger.debug("Using path {!r}".format(dest_path))
|
|
1637
1692
|
|
|
1638
1693
|
if source_file.endswith(".zip") and unpack:
|
|
1639
|
-
# Download the zip to a temporary directory and unpack its
|
|
1694
|
+
# Download the zip to a temporary directory and unpack its
|
|
1695
|
+
# contents to the user dir path
|
|
1640
1696
|
with TemporaryDirectory() as temp_dir:
|
|
1641
1697
|
temp_path = os.path.join(temp_dir, source_file)
|
|
1642
1698
|
self.__download_file(temp_path)
|
|
1699
|
+
|
|
1643
1700
|
with zipfile.ZipFile(temp_path, "r") as zip_ref:
|
|
1644
1701
|
# file_list = zip_ref.namelist()
|
|
1645
|
-
# self.logger.info("Decompressing {!r} to
|
|
1702
|
+
# self.logger.info("Decompressing {!r} to
|
|
1703
|
+
# {!r}".format(file_list, dest_path))
|
|
1646
1704
|
os.makedirs(dest_path, exist_ok=True)
|
|
1647
1705
|
zip_ref.extractall(path=dest_path)
|
|
1648
1706
|
|
|
@@ -1669,7 +1727,8 @@ def _should_include_file(
|
|
|
1669
1727
|
search_tags,
|
|
1670
1728
|
search_reg_expression,
|
|
1671
1729
|
):
|
|
1672
|
-
# Checks if file should be selected or not according to the
|
|
1730
|
+
# Checks if file should be selected or not according to the
|
|
1731
|
+
# specified selectors.
|
|
1673
1732
|
|
|
1674
1733
|
assert fname
|
|
1675
1734
|
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/context.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
1
3
|
import logging
|
|
2
4
|
import os
|
|
3
5
|
import shutil
|
|
@@ -10,6 +12,7 @@ from qmenta.sdk.directory_utils import TemporaryDirectory
|
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class LocalAnalysisContext:
|
|
15
|
+
|
|
13
16
|
class QCEntity(Enum):
|
|
14
17
|
"""
|
|
15
18
|
Enum with the following options:
|
|
@@ -28,9 +31,16 @@ class LocalAnalysisContext:
|
|
|
28
31
|
PASS = "pass"
|
|
29
32
|
FAIL = "fail"
|
|
30
33
|
|
|
31
|
-
def __init__(
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
settings,
|
|
37
|
+
src_folder,
|
|
38
|
+
out_folder,
|
|
39
|
+
res_folder,
|
|
40
|
+
):
|
|
32
41
|
"""
|
|
33
|
-
Context object that interfaces with the QMENTA platform. Should not
|
|
42
|
+
Context object that interfaces with the QMENTA platform. Should not
|
|
43
|
+
be created directly.
|
|
34
44
|
"""
|
|
35
45
|
|
|
36
46
|
self.__settings = settings
|
|
@@ -45,17 +55,21 @@ class LocalAnalysisContext:
|
|
|
45
55
|
Returns
|
|
46
56
|
-------
|
|
47
57
|
dict
|
|
48
|
-
Dictionary that contains important information about the current
|
|
58
|
+
Dictionary that contains important information about the current
|
|
59
|
+
analysis, such as:
|
|
49
60
|
* state (str) : State of the analysis
|
|
50
61
|
* name (str) : Name of the analysis
|
|
51
62
|
* script_name (str) : Name of the script
|
|
52
|
-
* user_id (str) : Username of the user that started the
|
|
63
|
+
* user_id (str) : Username of the user that started the
|
|
64
|
+
analysis
|
|
53
65
|
* patient_secret_name (str) : Name of the subject
|
|
54
66
|
* ssid (str) : Timepoint identifier
|
|
55
|
-
* settings (dict) : Settings dictionary. See documentation in
|
|
67
|
+
* settings (dict) : Settings dictionary. See documentation in
|
|
68
|
+
method `get_settings`
|
|
56
69
|
* tags (list) : List of tags
|
|
57
70
|
* version (str) : Tool version
|
|
58
71
|
"""
|
|
72
|
+
|
|
59
73
|
return {
|
|
60
74
|
"state": "local_running",
|
|
61
75
|
"name": "local",
|
|
@@ -83,12 +97,15 @@ class LocalAnalysisContext:
|
|
|
83
97
|
status : QCStatus
|
|
84
98
|
QCStatus.PASS or QCStatus.FAIL
|
|
85
99
|
comments : str, optional
|
|
86
|
-
Additional comments explaining why the QC status has been set to
|
|
100
|
+
Additional comments explaining why the QC status has been set to
|
|
101
|
+
pass or fail.
|
|
87
102
|
entity : QCEntity, optional
|
|
88
103
|
QCEntity.ANALYSIS or QCEntity.SESSION
|
|
89
104
|
input_id : str, optional
|
|
90
|
-
The id of the session in the settings definition when entity is
|
|
105
|
+
The id of the session in the settings definition when entity is
|
|
106
|
+
QCEntity.SESSION
|
|
91
107
|
"""
|
|
108
|
+
|
|
92
109
|
logger = logging.getLogger(__name__)
|
|
93
110
|
|
|
94
111
|
logger.info("Setting QC status to {}: {}".format(status, comments))
|
|
@@ -98,6 +115,7 @@ class LocalAnalysisContext:
|
|
|
98
115
|
Changes the analysis or session QC status.
|
|
99
116
|
|
|
100
117
|
"""
|
|
118
|
+
|
|
101
119
|
logger = logging.getLogger(__name__)
|
|
102
120
|
|
|
103
121
|
logger.info("Getting QC status")
|
|
@@ -111,17 +129,17 @@ class LocalAnalysisContext:
|
|
|
111
129
|
path : str
|
|
112
130
|
The location where to store the requirements.r
|
|
113
131
|
"""
|
|
132
|
+
|
|
114
133
|
logger = logging.getLogger(__name__)
|
|
115
134
|
|
|
116
135
|
if os.path.exists(path):
|
|
117
136
|
raise RuntimeError(
|
|
118
|
-
"Cannot download session qa because {}
|
|
119
|
-
|
|
120
|
-
)
|
|
137
|
+
"Cannot download session qa because {}"
|
|
138
|
+
"already exists!".format(path)
|
|
121
139
|
)
|
|
122
140
|
|
|
123
141
|
logger.info(
|
|
124
|
-
"Session QA requirements not supported locally. Skipping..."
|
|
142
|
+
"Session QA requirements not supported locally. " "Skipping..."
|
|
125
143
|
)
|
|
126
144
|
|
|
127
145
|
def upload_session_qa_report(self, input_id, path):
|
|
@@ -135,9 +153,10 @@ class LocalAnalysisContext:
|
|
|
135
153
|
path : str
|
|
136
154
|
The json file with the QA status for all files and rules
|
|
137
155
|
"""
|
|
156
|
+
|
|
138
157
|
logger = logging.getLogger(__name__)
|
|
139
158
|
logger.info(
|
|
140
|
-
"Session QA requirements not supported locally. Skipping..."
|
|
159
|
+
"Session QA requirements not supported locally. " "Skipping..."
|
|
141
160
|
)
|
|
142
161
|
|
|
143
162
|
@staticmethod
|
|
@@ -148,15 +167,17 @@ class LocalAnalysisContext:
|
|
|
148
167
|
Parameters
|
|
149
168
|
----------
|
|
150
169
|
value : int, optional
|
|
151
|
-
Number between 0 and 100 indicating the current status of
|
|
170
|
+
Number between 0 and 100 indicating the current status of
|
|
171
|
+
the execution
|
|
152
172
|
message : str, optional
|
|
153
173
|
Progress message.
|
|
154
174
|
"""
|
|
175
|
+
|
|
155
176
|
logger = logging.getLogger(__name__)
|
|
156
177
|
|
|
157
178
|
if not value and not message:
|
|
158
179
|
logger.warning(
|
|
159
|
-
"No information is available to report the progress"
|
|
180
|
+
"No information is available to report the " "progress"
|
|
160
181
|
)
|
|
161
182
|
return
|
|
162
183
|
|
|
@@ -164,12 +185,14 @@ class LocalAnalysisContext:
|
|
|
164
185
|
value = int(value) # Cast to int to avoid float/string problems
|
|
165
186
|
if value > 100:
|
|
166
187
|
logger.warning(
|
|
167
|
-
"The progress value should be between 0 and 100 (using
|
|
188
|
+
"The progress value should be between 0 and 100 (using "
|
|
189
|
+
"100 instead)"
|
|
168
190
|
)
|
|
169
191
|
value = 100
|
|
170
192
|
elif value < 0:
|
|
171
193
|
logger.warning(
|
|
172
|
-
"The progress value should be between 0 and 100
|
|
194
|
+
"The progress value should be between 0 and 100 "
|
|
195
|
+
"(using 0 instead)"
|
|
173
196
|
)
|
|
174
197
|
value = 0
|
|
175
198
|
|
|
@@ -184,6 +207,7 @@ class LocalAnalysisContext:
|
|
|
184
207
|
dict
|
|
185
208
|
Settings for the current analysis.
|
|
186
209
|
"""
|
|
210
|
+
|
|
187
211
|
return self.__settings
|
|
188
212
|
|
|
189
213
|
def get_files(
|
|
@@ -194,9 +218,11 @@ class LocalAnalysisContext:
|
|
|
194
218
|
reg_expression=None,
|
|
195
219
|
file_filter_condition_name=None,
|
|
196
220
|
subject_container_id=None,
|
|
221
|
+
unpack=True,
|
|
197
222
|
):
|
|
198
223
|
"""
|
|
199
|
-
Returns the files in the input container specified by `input_id`
|
|
224
|
+
Returns the files in the input container specified by `input_id`
|
|
225
|
+
that match the modality, tags
|
|
200
226
|
and regular expression, if any.
|
|
201
227
|
|
|
202
228
|
Parameters
|
|
@@ -207,11 +233,13 @@ class LocalAnalysisContext:
|
|
|
207
233
|
Optional modality, will allow any modalities if None given.
|
|
208
234
|
Default is None.
|
|
209
235
|
tags : set, optional
|
|
210
|
-
Optional set of tags, will allow any tags if None given,
|
|
236
|
+
Optional set of tags, will allow any tags if None given,
|
|
237
|
+
and will match any set of tags (including
|
|
211
238
|
the empty set) otherwise.
|
|
212
239
|
Default is None.
|
|
213
240
|
reg_expression : str, optional
|
|
214
|
-
Regular expression to match with the file names in the
|
|
241
|
+
Regular expression to match with the file names in the
|
|
242
|
+
specified container, will allow any file names
|
|
215
243
|
if None given.
|
|
216
244
|
Default is None.
|
|
217
245
|
file_filter_condition_name : str, optional
|
|
@@ -223,6 +251,7 @@ class LocalAnalysisContext:
|
|
|
223
251
|
list of File
|
|
224
252
|
List of selected file handlers from the container.
|
|
225
253
|
"""
|
|
254
|
+
|
|
226
255
|
logger = logging.getLogger(__name__)
|
|
227
256
|
logger.info(
|
|
228
257
|
"Get files: {} [modality={} / tags={} / regex={}]".format(
|
|
@@ -233,15 +262,18 @@ class LocalAnalysisContext:
|
|
|
233
262
|
file_handlers = []
|
|
234
263
|
|
|
235
264
|
# Create File
|
|
265
|
+
|
|
236
266
|
for file_description in self.__settings[input_id]:
|
|
237
267
|
if "path" not in file_description:
|
|
238
|
-
msg =
|
|
239
|
-
|
|
268
|
+
msg = (
|
|
269
|
+
'Cannot find the key "path" for the input container'
|
|
270
|
+
" {}".format(input_id)
|
|
240
271
|
)
|
|
241
272
|
logger.error(msg)
|
|
242
273
|
raise RuntimeError(msg)
|
|
243
274
|
|
|
244
275
|
# Use file_filter_condition_name if available
|
|
276
|
+
|
|
245
277
|
if file_filter_condition_name:
|
|
246
278
|
if "file_filter_condition_name" not in file_description:
|
|
247
279
|
continue # Skip
|
|
@@ -308,6 +340,7 @@ class LocalAnalysisContext:
|
|
|
308
340
|
protocol : None
|
|
309
341
|
container_id : None
|
|
310
342
|
"""
|
|
343
|
+
|
|
311
344
|
logger = logging.getLogger(__name__)
|
|
312
345
|
if tags:
|
|
313
346
|
logger.warning("Tags ignored (local execution): {}".format(tags))
|
|
@@ -343,7 +376,8 @@ class LocalAnalysisContext:
|
|
|
343
376
|
|
|
344
377
|
def download_resource(self, resource_path, destination_path):
|
|
345
378
|
"""
|
|
346
|
-
Downloads a file from the user/group tool resources. The resource
|
|
379
|
+
Downloads a file from the user/group tool resources. The resource
|
|
380
|
+
path can include subdirectories and must
|
|
347
381
|
be relative:
|
|
348
382
|
Downloads a resource file to the container.
|
|
349
383
|
|
|
@@ -354,6 +388,7 @@ class LocalAnalysisContext:
|
|
|
354
388
|
destination_path : str
|
|
355
389
|
Path where the file will be downloaded to
|
|
356
390
|
"""
|
|
391
|
+
|
|
357
392
|
logger = logging.getLogger(__name__)
|
|
358
393
|
logger.info(self.__res_folder)
|
|
359
394
|
if not self.__res_folder: # Default keyword for external users
|
|
@@ -361,14 +396,12 @@ class LocalAnalysisContext:
|
|
|
361
396
|
return
|
|
362
397
|
else:
|
|
363
398
|
logger.info(
|
|
364
|
-
"Mocking resources from directory:
|
|
365
|
-
|
|
366
|
-
)
|
|
399
|
+
"Mocking resources from directory:"
|
|
400
|
+
" {}".format(self.__res_folder)
|
|
367
401
|
)
|
|
368
402
|
logger.info(
|
|
369
|
-
"The selected mode ({}) is ignored in local
|
|
370
|
-
|
|
371
|
-
)
|
|
403
|
+
"The selected mode ({}) is ignored in local "
|
|
404
|
+
"execution.".format(type)
|
|
372
405
|
)
|
|
373
406
|
|
|
374
407
|
logger.info(
|
|
@@ -389,17 +422,20 @@ class LocalAnalysisContext:
|
|
|
389
422
|
|
|
390
423
|
def _get_manual_analysis_data(self):
|
|
391
424
|
"""
|
|
392
|
-
Returns a dictionary with the manual analysis data generated during
|
|
425
|
+
Returns a dictionary with the manual analysis data generated during
|
|
426
|
+
the manual step (user interaction)
|
|
393
427
|
|
|
394
428
|
Returns
|
|
395
429
|
-------
|
|
396
430
|
dict
|
|
397
431
|
The values generated during the manual step.
|
|
398
432
|
"""
|
|
433
|
+
|
|
399
434
|
logger = logging.getLogger(__name__)
|
|
400
435
|
logger.info("Getting manual analysis data")
|
|
401
436
|
logger.warning(
|
|
402
|
-
"Getting manual analysis data is not supported locally.
|
|
437
|
+
"Getting manual analysis data is not supported locally. "
|
|
438
|
+
"Skipping..."
|
|
403
439
|
)
|
|
404
440
|
return {}
|
|
405
441
|
|
|
@@ -424,8 +460,10 @@ class LocalAnalysisContext:
|
|
|
424
460
|
title : str, optional
|
|
425
461
|
How the metadata field should be presented
|
|
426
462
|
readonly : bool, optional
|
|
427
|
-
Whether the user should be able to edit the value on the platform
|
|
463
|
+
Whether the user should be able to edit the value on the platform
|
|
464
|
+
or not (only by analyses)
|
|
428
465
|
"""
|
|
466
|
+
|
|
429
467
|
logger = logging.getLogger(__name__)
|
|
430
468
|
|
|
431
469
|
assert isinstance(key, str)
|
|
@@ -444,13 +482,20 @@ class LocalAnalysisContext:
|
|
|
444
482
|
"Invalid type for the metadata value: {!r}".format(type(value))
|
|
445
483
|
)
|
|
446
484
|
|
|
447
|
-
logger.info(
|
|
485
|
+
logger.info(
|
|
486
|
+
"Setting metadata parameter: {} =" "{!r}".format(key, value)
|
|
487
|
+
)
|
|
448
488
|
logger.warning(
|
|
449
|
-
"Setting metadata values is not supported locally. Skipping..."
|
|
489
|
+
"Setting metadata values is not supported locally. " "Skipping..."
|
|
450
490
|
)
|
|
451
491
|
return None
|
|
452
492
|
|
|
453
|
-
def get_metadata_value(
|
|
493
|
+
def get_metadata_value(
|
|
494
|
+
self,
|
|
495
|
+
key,
|
|
496
|
+
patient_secret_name=None,
|
|
497
|
+
ssid=None,
|
|
498
|
+
):
|
|
454
499
|
"""
|
|
455
500
|
Gets the value of a metadata parameter for the current session
|
|
456
501
|
|
|
@@ -467,26 +512,30 @@ class LocalAnalysisContext:
|
|
|
467
512
|
-------
|
|
468
513
|
The value of the parameter if it exists, None otherwise
|
|
469
514
|
"""
|
|
515
|
+
|
|
470
516
|
logger = logging.getLogger(__name__)
|
|
471
517
|
logger.info("Getting metadata value: {}".format(key))
|
|
472
518
|
logger.warning(
|
|
473
|
-
"Getting metadata values is not supported locally. Skipping..."
|
|
519
|
+
"Getting metadata values is not supported locally. " "Skipping..."
|
|
474
520
|
)
|
|
475
521
|
return None
|
|
476
522
|
|
|
477
523
|
def _assure_metadata_parameters(self, params):
|
|
478
524
|
"""
|
|
479
|
-
Check whether a metadata parameter exists, creating it if not.
|
|
525
|
+
Check whether a metadata parameter exists, creating it if not.
|
|
526
|
+
Not supported for local testing.
|
|
480
527
|
|
|
481
528
|
Parameters
|
|
482
529
|
----------
|
|
483
530
|
params : dict
|
|
484
|
-
The definition of the parameter. It may contain "title", "type",
|
|
531
|
+
The definition of the parameter. It may contain "title", "type",
|
|
532
|
+
"visible", "readonly" or "order".
|
|
485
533
|
"""
|
|
534
|
+
|
|
486
535
|
logger = logging.getLogger(__name__)
|
|
487
536
|
logger.info("Setting metadata param (ckeck): {}".format(params))
|
|
488
537
|
logger.warning(
|
|
489
|
-
"Assuring metadata params is not supported locally. Skipping..."
|
|
538
|
+
"Assuring metadata params is not supported locally. " "Skipping..."
|
|
490
539
|
)
|
|
491
540
|
|
|
492
541
|
def set_analysis_output_variables(
|
|
@@ -502,15 +551,26 @@ class LocalAnalysisContext:
|
|
|
502
551
|
clear_previous : bool, optional
|
|
503
552
|
Whether the previous output variables should be cleared or not
|
|
504
553
|
"""
|
|
554
|
+
|
|
505
555
|
logger = logging.getLogger(__name__)
|
|
506
556
|
logger.warning("Output variables not supported locally. Skipping...")
|
|
507
557
|
|
|
508
558
|
|
|
509
559
|
class LocalFile:
|
|
510
|
-
|
|
560
|
+
|
|
561
|
+
def __init__(
|
|
562
|
+
self,
|
|
563
|
+
name,
|
|
564
|
+
input_container_path,
|
|
565
|
+
modality,
|
|
566
|
+
tags,
|
|
567
|
+
file_info,
|
|
568
|
+
):
|
|
511
569
|
"""
|
|
512
|
-
Object that represents a file and all its metadata in the platform.
|
|
570
|
+
Object that represents a file and all its metadata in the platform.
|
|
571
|
+
Should not be created directly.
|
|
513
572
|
"""
|
|
573
|
+
|
|
514
574
|
self.name = name
|
|
515
575
|
self.__input_container_path = input_container_path
|
|
516
576
|
self.__download_path = None
|
|
@@ -527,6 +587,7 @@ class LocalFile:
|
|
|
527
587
|
str or None
|
|
528
588
|
Modality of the file or None if not known.
|
|
529
589
|
"""
|
|
590
|
+
|
|
530
591
|
return self.__modality
|
|
531
592
|
|
|
532
593
|
@staticmethod
|
|
@@ -539,18 +600,23 @@ class LocalFile:
|
|
|
539
600
|
str or None
|
|
540
601
|
File format or None if not known.
|
|
541
602
|
"""
|
|
603
|
+
|
|
542
604
|
return None
|
|
543
605
|
|
|
544
606
|
def get_file_info(self):
|
|
545
607
|
"""
|
|
546
|
-
Get the file information. The type of information depends on the type
|
|
547
|
-
|
|
608
|
+
Get the file information. The type of information depends on the type
|
|
609
|
+
of file
|
|
610
|
+
(e.g. nifti files include information such as 'Data strides',
|
|
611
|
+
'Data type', 'Dimensions' or 'Voxel size').
|
|
548
612
|
|
|
549
613
|
Returns
|
|
550
614
|
-------
|
|
551
615
|
dict
|
|
552
|
-
Dictionary of key-value pairs that depend on the format of the
|
|
616
|
+
Dictionary of key-value pairs that depend on the format of the
|
|
617
|
+
file.
|
|
553
618
|
"""
|
|
619
|
+
|
|
554
620
|
return self.__file_info
|
|
555
621
|
|
|
556
622
|
def get_file_tags(self):
|
|
@@ -562,6 +628,7 @@ class LocalFile:
|
|
|
562
628
|
set
|
|
563
629
|
Set of tags associated to this file
|
|
564
630
|
"""
|
|
631
|
+
|
|
565
632
|
return self.__tags
|
|
566
633
|
|
|
567
634
|
def get_file_path(self):
|
|
@@ -578,9 +645,11 @@ class LocalFile:
|
|
|
578
645
|
RuntimeError
|
|
579
646
|
If `get_file_path` is called before the file has been downloaded
|
|
580
647
|
"""
|
|
648
|
+
|
|
581
649
|
if self.__download_path is None:
|
|
582
|
-
err_msg =
|
|
583
|
-
|
|
650
|
+
err_msg = (
|
|
651
|
+
"File {!r} has no path because it has not been "
|
|
652
|
+
"downloaded".format(self.name)
|
|
584
653
|
)
|
|
585
654
|
logging.getLogger(__name__).error(err_msg)
|
|
586
655
|
raise RuntimeError(err_msg)
|
|
@@ -596,6 +665,7 @@ class LocalFile:
|
|
|
596
665
|
destination_file_path : str
|
|
597
666
|
Path where the file should be downloaded.
|
|
598
667
|
"""
|
|
668
|
+
|
|
599
669
|
logger = logging.getLogger(__name__)
|
|
600
670
|
logger.info(
|
|
601
671
|
"Downloading {!r} to {!r}".format(self.name, destination_file_path)
|
|
@@ -607,30 +677,37 @@ class LocalFile:
|
|
|
607
677
|
|
|
608
678
|
def download(self, dest_path, unpack=True):
|
|
609
679
|
"""
|
|
610
|
-
Downloads a file or the contents of a packed file to to the specified
|
|
680
|
+
Downloads a file or the contents of a packed file to to the specified
|
|
681
|
+
`path`.
|
|
611
682
|
|
|
612
683
|
Parameters
|
|
613
684
|
----------
|
|
614
685
|
dest_path:
|
|
615
686
|
Path where the file should be downloaded to.
|
|
616
687
|
unpack:
|
|
617
|
-
Tells the function whether the file should be unpacked to the
|
|
688
|
+
Tells the function whether the file should be unpacked to the
|
|
689
|
+
given folder.
|
|
618
690
|
|
|
619
691
|
Returns
|
|
620
692
|
-------
|
|
621
693
|
str:
|
|
622
694
|
The full path of the file or the folder with the unpacked files.
|
|
623
695
|
"""
|
|
696
|
+
|
|
624
697
|
logger = logging.getLogger(__name__)
|
|
625
698
|
source_file = self.name
|
|
626
699
|
|
|
627
700
|
# Normalize path
|
|
701
|
+
|
|
628
702
|
dest_path = os.path.abspath(dest_path)
|
|
629
703
|
|
|
630
704
|
logger.debug("Using path {!r}".format(dest_path))
|
|
631
705
|
|
|
632
706
|
if source_file.endswith(".zip") and unpack:
|
|
633
|
-
|
|
707
|
+
|
|
708
|
+
# Download the zip to a temporary directory and unpack its
|
|
709
|
+
# contents to the user dir path
|
|
710
|
+
|
|
634
711
|
with TemporaryDirectory() as temp_dir:
|
|
635
712
|
temp_path = os.path.join(temp_dir, source_file)
|
|
636
713
|
self.__download_file(temp_path)
|
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/bids/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/bids/wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/communication.py
RENAMED
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/directory_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/__init__.py
RENAMED
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/client.py
RENAMED
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/local/executor.py
RENAMED
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/log_capture.py
RENAMED
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/make_entrypoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qmenta_sdk_lib-2.0.1.dev3468 → qmenta_sdk_lib-2.0.1.dev3469}/python/qmenta/sdk/tool_maker/inputs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|