qmenta-client 1.1.dev1446__py3-none-any.whl → 1.1.dev1468__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/__init__.py CHANGED
@@ -1 +1 @@
1
- __path__ = __import__('pkgutil').extend_path(__path__, __name__)
1
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
qmenta/client/Account.py CHANGED
@@ -9,7 +9,7 @@ from qmenta.core import errors
9
9
  from .Project import Project
10
10
  from .utils import load_json
11
11
 
12
- logger_name = 'qmenta.client'
12
+ logger_name = "qmenta.client"
13
13
 
14
14
 
15
15
  class Account:
@@ -35,9 +35,7 @@ class Account:
35
35
  The Auth object used for communication with the platform
36
36
  """
37
37
 
38
- def __init__(self, username, password,
39
- base_url="https://platform.qmenta.com",
40
- verify_certificates=True):
38
+ def __init__(self, username, password, base_url="https://platform.qmenta.com", verify_certificates=True):
41
39
 
42
40
  self._cookie = None
43
41
  self.username = username
@@ -64,17 +62,14 @@ class Account:
64
62
  """
65
63
  logger = logging.getLogger(logger_name)
66
64
  try:
67
- auth = platform.Auth.login(
68
- self.username, self.password, base_url=self.baseurl,
69
- ask_for_2fa_input=True
70
- )
65
+ auth = platform.Auth.login(self.username, self.password, base_url=self.baseurl, ask_for_2fa_input=True)
71
66
  except errors.PlatformError as e:
72
- logger.error('Failed to log in: {}'.format(e))
67
+ logger.error("Failed to log in: {}".format(e))
73
68
  self.auth = None
74
69
  raise
75
70
 
76
71
  self.auth = auth
77
- logger.info('Logged in as {}'.format(self.username))
72
+ logger.info("Logged in as {}".format(self.username))
78
73
 
79
74
  def logout(self):
80
75
  """
@@ -88,12 +83,12 @@ class Account:
88
83
 
89
84
  logger = logging.getLogger(logger_name)
90
85
  try:
91
- platform.parse_response(platform.post(self.auth, 'logout'))
86
+ platform.parse_response(platform.post(self.auth, "logout"))
92
87
  except errors.PlatformError as e:
93
- logger.error('Logout was unsuccessful: {}'.format(e))
88
+ logger.error("Logout was unsuccessful: {}".format(e))
94
89
  raise
95
90
 
96
- logger.info('Logged out successfully')
91
+ logger.info("Logged out successfully")
97
92
 
98
93
  def get_project(self, project_id):
99
94
  """
@@ -114,14 +109,9 @@ class Account:
114
109
  return Project(self, int(project_id))
115
110
  elif type(project_id) is str:
116
111
  projects = self.projects
117
- projects_match = [
118
- proj for proj in projects if proj['name'] == project_id
119
- ]
112
+ projects_match = [proj for proj in projects if proj["name"] == project_id]
120
113
  if not projects_match:
121
- raise Exception(
122
- f"Project {project_id} does not exist or is "
123
- f"not available for this user."
124
- )
114
+ raise Exception(f"Project {project_id} does not exist or is not available for this user.")
125
115
  return Project(self, int(projects_match[0]["id"]))
126
116
 
127
117
  @property
@@ -137,11 +127,9 @@ class Account:
137
127
  logger = logging.getLogger(logger_name)
138
128
 
139
129
  try:
140
- data = platform.parse_response(platform.post(
141
- self.auth, 'projectset_manager/get_projectset_list'
142
- ))
130
+ data = platform.parse_response(platform.post(self.auth, "projectset_manager/get_projectset_list"))
143
131
  except errors.PlatformError as e:
144
- logger.error('Failed to get project list: {}'.format(e))
132
+ logger.error("Failed to get project list: {}".format(e))
145
133
  raise
146
134
 
147
135
  titles = []
@@ -149,8 +137,7 @@ class Account:
149
137
  titles.append({"name": project["name"], "id": project["_id"]})
150
138
  return titles
151
139
 
152
- def add_project(self, project_abbreviation, project_name,
153
- description="", users=None, from_date="", to_date=""):
140
+ def add_project(self, project_abbreviation, project_name, description="", users=None, from_date="", to_date=""):
154
141
  """
155
142
  Add a new project to the user account.
156
143
 
@@ -183,17 +170,20 @@ class Account:
183
170
  return False
184
171
 
185
172
  try:
186
- platform.parse_response(platform.post(
187
- self.auth, 'projectset_manager/upsert_project',
188
- data={
189
- "name": project_name,
190
- "description": description,
191
- "from_date": from_date,
192
- "to_date": to_date,
193
- "abbr": project_abbreviation,
194
- "users": "|".join(users)
195
- }
196
- ))
173
+ platform.parse_response(
174
+ platform.post(
175
+ self.auth,
176
+ "projectset_manager/upsert_project",
177
+ data={
178
+ "name": project_name,
179
+ "description": description,
180
+ "from_date": from_date,
181
+ "to_date": to_date,
182
+ "abbr": project_abbreviation,
183
+ "users": "|".join(users),
184
+ },
185
+ )
186
+ )
197
187
  except errors.PlatformError as e:
198
188
  logger.error(e)
199
189
  return False
@@ -205,9 +195,15 @@ class Account:
205
195
  logger.error("Project could note be created.")
206
196
  return False
207
197
 
208
- def _send_request(self, path, req_parameters=None, req_headers=None,
209
- stream=False, return_raw_response=False,
210
- response_timeout=900.0):
198
+ def _send_request(
199
+ self,
200
+ path,
201
+ req_parameters=None,
202
+ req_headers=None,
203
+ stream=False,
204
+ return_raw_response=False,
205
+ response_timeout=900.0,
206
+ ):
211
207
  """
212
208
  TODO: not used and contains warnings, maybe we should delete it.
213
209
 
@@ -234,34 +230,33 @@ class Account:
234
230
  """
235
231
 
236
232
  req_headers = req_headers or {}
237
- req_url = '/'.join((self.baseurl, path))
233
+ req_url = "/".join((self.baseurl, path))
238
234
  if self._cookie is not None:
239
235
  req_headers["Cookie"] = self._cookie
240
236
  req_headers["Mint-Api-Call"] = "1"
241
237
 
242
238
  logger = logging.getLogger(logger_name)
243
239
  try:
244
- if path == 'upload':
240
+ if path == "upload":
245
241
  response = self.pool.request(
246
- 'POST',
242
+ "POST",
247
243
  req_url,
248
244
  body=req_parameters,
249
245
  headers=req_headers,
250
246
  timeout=response_timeout,
251
- preload_content=not stream
247
+ preload_content=not stream,
252
248
  )
253
249
  else:
254
250
  response = self.pool.request(
255
- 'POST',
251
+ "POST",
256
252
  req_url,
257
253
  req_parameters or {},
258
254
  headers=req_headers,
259
255
  timeout=response_timeout,
260
- preload_content=not stream
256
+ preload_content=not stream,
261
257
  )
262
258
  if response.status >= 400:
263
- raise HTTPError(
264
- 'STATUS {}: {}'.format(response.status, response.reason))
259
+ raise HTTPError("STATUS {}: {}".format(response.status, response.reason))
265
260
  except Exception as e:
266
261
  error = "Could not send request. ERROR: {0}".format(e)
267
262
  logger.error(error)
@@ -283,8 +278,7 @@ class Account:
283
278
  try:
284
279
  parsed_content = load_json(response.data)
285
280
  except Exception:
286
- error = "Could not parse the response as JSON data: {}".format(
287
- response.data)
281
+ error = "Could not parse the response as JSON data: {}".format(response.data)
288
282
  logger.error(error)
289
283
  raise
290
284
 
@@ -293,8 +287,8 @@ class Account:
293
287
  error = parsed_content["error"] or "Unknown error"
294
288
  logger.error(error)
295
289
  raise Exception(error)
296
- elif 'success' in parsed_content and parsed_content['success'] == 3:
297
- error = parsed_content['message']
290
+ elif "success" in parsed_content and parsed_content["success"] == 3:
291
+ error = parsed_content["message"]
298
292
  logger.error(error)
299
293
  raise Exception(error)
300
294
  return parsed_content
qmenta/client/File.py CHANGED
@@ -32,14 +32,12 @@ class File:
32
32
  `ff_container = File(project, "MRB", "1", "gre_field_mapping_3.zip")`
33
33
  """
34
34
 
35
- def __init__(self, project: qmenta.client.Project, subject_name: str,
36
- ssid: str, file_name: str):
35
+ def __init__(self, project: qmenta.client.Project, subject_name: str, ssid: str, file_name: str):
37
36
  self.file_name = file_name
38
37
  self.project = project
39
38
  self._container_id = None
40
39
  for cont in self.project.list_input_containers():
41
- if cont["patient_secret_name"] == subject_name and cont[
42
- "ssid"] == ssid:
40
+ if cont["patient_secret_name"] == subject_name and cont["ssid"] == ssid:
43
41
  self._container_id = cont["container_id"]
44
42
  break
45
43
  if self.file_name.endswith(".zip"):
@@ -52,8 +50,7 @@ class File:
52
50
  def __str__(self):
53
51
  return self.file_name
54
52
 
55
- def pull_scan_sequence_info(self, specify_dicom_tags=None,
56
- output_format: str = "csv"):
53
+ def pull_scan_sequence_info(self, specify_dicom_tags=None, output_format: str = "csv"):
57
54
  """
58
55
  Pulling scan sequence information (e.g. series number, series type,
59
56
  series description, number of files, file formats available)
@@ -77,13 +74,11 @@ class File:
77
74
  if specify_dicom_tags is None:
78
75
  specify_dicom_tags = list()
79
76
  valid_output_formats = ["csv", "tsv", "json", "dict"]
80
- assert output_format in valid_output_formats, \
81
- f"Output format not valid. Choose one of {valid_output_formats}"
77
+ assert output_format in valid_output_formats, f"Output format not valid. Choose one of {valid_output_formats}"
82
78
 
83
79
  with TemporaryDirectory() as temp_dir:
84
80
  local_file_name = mkstemp(dir=temp_dir) + ".zip"
85
- self.project.download_file(self._container_id, self.file_name,
86
- local_file_name, overwrite=False)
81
+ self.project.download_file(self._container_id, self.file_name, local_file_name, overwrite=False)
87
82
  if self._file_type == "DICOM":
88
83
  unzip_dicoms(local_file_name, temp_dir, exclude_members=None)
89
84
  dicoms = glob.glob(os.path.join(temp_dir, "*"))
@@ -97,20 +92,11 @@ class File:
97
92
  # only needed for one file, all have the same data.
98
93
  for attribute in specify_dicom_tags:
99
94
  # error handling of attributes send by the user
100
- assert len(
101
- attribute) == 2, (
102
- "Invalid length of DICOM Attribute"
103
- )
104
- assert isinstance(attribute[0],
105
- int), (
106
- "Invalid type of DICOM Attribute"
107
- )
108
- output_data["dicom tag"].append(
109
- open_file[attribute].tag)
110
- output_data["attribute"].append(
111
- open_file[attribute].name)
112
- output_data["value"].append(
113
- open_file[attribute].value)
95
+ assert len(attribute) == 2, "Invalid length of DICOM Attribute"
96
+ assert isinstance(attribute[0], int), "Invalid type of DICOM Attribute"
97
+ output_data["dicom tag"].append(open_file[attribute].tag)
98
+ output_data["attribute"].append(open_file[attribute].name)
99
+ output_data["value"].append(open_file[attribute].value)
114
100
  except Exception as e:
115
101
  print(str(e))
116
102
  pass
@@ -120,14 +106,10 @@ class File:
120
106
  output_data["value"].append(number_files)
121
107
 
122
108
  if output_format == "csv":
123
- pd.DataFrame(output_data).to_csv(
124
- f"scan_sequence_info.{output_format}", index=False)
109
+ pd.DataFrame(output_data).to_csv(f"scan_sequence_info.{output_format}", index=False)
125
110
 
126
111
  elif output_format == "tsv":
127
- pd.DataFrame(output_data).to_csv(
128
- f"scan_sequence_info.{output_format}", sep="\t",
129
- index=False
130
- )
112
+ pd.DataFrame(output_data).to_csv(f"scan_sequence_info.{output_format}", sep="\t", index=False)
131
113
 
132
114
  elif output_format == "json":
133
115
  with open(f"scan_sequence_info.{output_format}", "w") as fp:
qmenta/client/Project.py CHANGED
@@ -43,8 +43,7 @@ def convert_qc_value_to_qcstatus(value):
43
43
  elif value == "":
44
44
  return QCStatus.UNDERTERMINED
45
45
  else:
46
- logger.error(f"The input value '{value}' cannot be converted "
47
- f"to class QCStatus.")
46
+ logger.error(f"The input value '{value}' cannot be converted to class QCStatus.")
48
47
  return False
49
48
 
50
49
 
@@ -76,6 +75,7 @@ class Project:
76
75
  """
77
76
 
78
77
  """ Project Related Methods """
78
+
79
79
  def __init__(self, account: Account, project_id, max_upload_retries=5):
80
80
  # if project_id is a string (the name of the project), get the
81
81
  # project id (int)
@@ -134,6 +134,7 @@ class Project:
134
134
  return rep
135
135
 
136
136
  """ Upload / Download Data Related Methods """
137
+
137
138
  def _upload_chunk(
138
139
  self,
139
140
  data,
@@ -350,14 +351,14 @@ class Project:
350
351
  retries_count += 1
351
352
  time.sleep(retries_count * 5)
352
353
  if retries_count > self.max_retries:
353
- error_message = "Error Code: 416; " "Requested Range Not Satisfiable (NGINX)"
354
+ error_message = "Error Code: 416; Requested Range Not Satisfiable (NGINX)"
354
355
  logger.error(error_message)
355
356
  break
356
357
  else:
357
358
  retries_count += 1
358
359
  time.sleep(retries_count * 5)
359
360
  if retries_count > max_retries:
360
- error_message = "Number of retries has been reached. " "Upload process stops here !"
361
+ error_message = "Number of retries has been reached. Upload process stops here !"
361
362
  logger.error(error_message)
362
363
  break
363
364
 
@@ -451,14 +452,12 @@ class Project:
451
452
  """
452
453
  logger = logging.getLogger(logger_name)
453
454
  if not isinstance(file_name, str):
454
- raise ValueError("The name of the file to download (file_name) "
455
- "should be of type string.")
455
+ raise ValueError("The name of the file to download (file_name) should be of type string.")
456
456
  if not isinstance(file_name, str):
457
- raise ValueError("The name of the output file (local_filename) "
458
- "should be of type string.")
457
+ raise ValueError("The name of the output file (local_filename) should be of type string.")
459
458
 
460
459
  if file_name not in self.list_container_files(container_id):
461
- msg = f'File "{file_name}" does not exist in container ' f"{container_id}"
460
+ msg = f'File "{file_name}" does not exist in container {container_id}'
462
461
  logger.error(msg)
463
462
  return False
464
463
 
@@ -479,7 +478,7 @@ class Project:
479
478
  f.write(chunk)
480
479
  f.flush()
481
480
 
482
- logger.info(f"File {file_name} from container {container_id} saved to" f" {local_filename}")
481
+ logger.info(f"File {file_name} from container {container_id} saved to {local_filename}")
483
482
  return True
484
483
 
485
484
  def download_files(self, container_id, filenames, zip_name="files.zip", overwrite=False):
@@ -500,18 +499,14 @@ class Project:
500
499
  logger = logging.getLogger(logger_name)
501
500
 
502
501
  if not all([isinstance(file_name, str) for file_name in filenames]):
503
- raise ValueError("The name of the files to download (filenames) "
504
- "should be of type string.")
502
+ raise ValueError("The name of the files to download (filenames) should be of type string.")
505
503
  if not isinstance(zip_name, str):
506
- raise ValueError("The name of the output ZIP file (zip_name) "
507
- "should be of type string.")
504
+ raise ValueError("The name of the output ZIP file (zip_name) should be of type string.")
508
505
 
509
506
  files_not_in_container = list(filter(lambda f: f not in self.list_container_files(container_id), filenames))
510
507
 
511
508
  if files_not_in_container:
512
- msg = (
513
- f"The following files are missing in container " f"{container_id}: {', '.join(files_not_in_container)}"
514
- )
509
+ msg = f"The following files are missing in container {container_id}: {', '.join(files_not_in_container)}"
515
510
  logger.error(msg)
516
511
  return False
517
512
 
@@ -575,6 +570,7 @@ class Project:
575
570
  return True
576
571
 
577
572
  """ Subject/Session Related Methods """
573
+
578
574
  @property
579
575
  def subjects(self):
580
576
  """
@@ -810,12 +806,12 @@ class Project:
810
806
 
811
807
  """
812
808
 
813
- assert len(items) == 2, f"The number of elements in items " f"'{len(items)}' should be equal to two."
809
+ assert len(items) == 2, f"The number of elements in items '{len(items)}' should be equal to two."
814
810
  assert all([isinstance(item, int) for item in items]), f"All items elements '{items}' should be integers."
815
811
 
816
- assert all([key[:5] == "pars_" for key in search_criteria.keys()]), (
817
- f"All keys of the search_criteria dictionary " f"'{search_criteria.keys()}' must start with 'pars_'."
818
- )
812
+ assert all(
813
+ [key[:5] == "pars_" for key in search_criteria.keys()]
814
+ ), f"All keys of the search_criteria dictionary '{search_criteria.keys()}' must start with 'pars_'."
819
815
 
820
816
  for key, value in search_criteria.items():
821
817
  if value.split(";")[0] in ["integer", "decimal"]:
@@ -869,7 +865,7 @@ class Project:
869
865
  try:
870
866
  patient_id = str(int(patient_id))
871
867
  except ValueError:
872
- raise ValueError(f"'patient_id': '{patient_id}' not valid. " f"Must be convertible to int.")
868
+ raise ValueError(f"'patient_id': '{patient_id}' not valid. Must be convertible to int.")
873
869
 
874
870
  assert isinstance(tags, list) and all(
875
871
  isinstance(item, str) for item in tags
@@ -882,13 +878,13 @@ class Project:
882
878
  try:
883
879
  age_at_scan = str(int(age_at_scan)) if age_at_scan else None
884
880
  except ValueError:
885
- raise ValueError(f"age_at_scan: '{age_at_scan}' not valid. " f"Must be an integer.")
881
+ raise ValueError(f"age_at_scan: '{age_at_scan}' not valid. Must be an integer.")
886
882
 
887
883
  assert isinstance(metadata, dict), f"metadata: '{metadata}' should be a dictionary."
888
884
 
889
- assert all("md_" == key[:3] for key in metadata.keys()) or all("md_" != key[:3] for key in metadata.keys()), (
890
- f"metadata: '{metadata}' must be a dictionary whose keys " f"are either all starting with 'md_' or none."
891
- )
885
+ assert all("md_" == key[:3] for key in metadata.keys()) or all(
886
+ "md_" != key[:3] for key in metadata.keys()
887
+ ), f"metadata: '{metadata}' must be a dictionary whose keys are either all starting with 'md_' or none."
892
888
 
893
889
  metadata_keys = self.metadata_parameters.keys()
894
890
  assert all(
@@ -1047,27 +1043,26 @@ class Project:
1047
1043
  # them from the results.
1048
1044
  subjects = list()
1049
1045
  for subject in content:
1050
- files = platform.parse_response(platform.post(
1051
- self._account.auth, "file_manager/get_container_files",
1052
- data={"container_id": str(int(subject["container_id"]))}
1053
- ))
1046
+ files = platform.parse_response(
1047
+ platform.post(
1048
+ self._account.auth,
1049
+ "file_manager/get_container_files",
1050
+ data={"container_id": str(int(subject["container_id"]))},
1051
+ )
1052
+ )
1054
1053
 
1055
1054
  for file in files["meta"]:
1056
- if modality and \
1057
- modality != (file.get("metadata") or {}).get("modality"):
1055
+ if modality and modality != (file.get("metadata") or {}).get("modality"):
1058
1056
  continue
1059
1057
  if tags and not all([tag in file.get("tags") for tag in tags]):
1060
1058
  continue
1061
1059
  if dicoms:
1062
1060
  result_values = list()
1063
1061
  for key, dict_value in dicoms.items():
1064
- f_value = ((file.get("metadata") or {})
1065
- .get("info") or {}).get(key)
1062
+ f_value = ((file.get("metadata") or {}).get("info") or {}).get(key)
1066
1063
  d_operator = dict_value["operation"]
1067
1064
  d_value = dict_value["value"]
1068
- result_values.append(
1069
- self.__operation(d_value, d_operator, f_value)
1070
- )
1065
+ result_values.append(self.__operation(d_value, d_operator, f_value))
1071
1066
 
1072
1067
  if not all(result_values):
1073
1068
  continue
@@ -1150,10 +1145,10 @@ class Project:
1150
1145
  ]
1151
1146
 
1152
1147
  if not session_to_del:
1153
- logger.error(f"Session {subject_name}/{session_id} could not be found " f"in this project.")
1148
+ logger.error(f"Session {subject_name}/{session_id} could not be found in this project.")
1154
1149
  return False
1155
1150
  elif len(session_to_del) > 1:
1156
- raise RuntimeError("Multiple sessions with same Subject ID and Session ID." " Contact support.")
1151
+ raise RuntimeError("Multiple sessions with same Subject ID and Session ID. Contact support.")
1157
1152
  else:
1158
1153
  logger.info("{}/{} found (id {})".format(subject_name, session_id, session_to_del[0]["_id"]))
1159
1154
 
@@ -1168,10 +1163,10 @@ class Project:
1168
1163
  )
1169
1164
  )
1170
1165
  except errors.PlatformError:
1171
- logger.error(f"Session \"{subject_name}/{session['ssid']}\" could" f" not be deleted.")
1166
+ logger.error(f"Session \"{subject_name}/{session['ssid']}\" could not be deleted.")
1172
1167
  return False
1173
1168
 
1174
- logger.info(f"Session \"{subject_name}/{session['ssid']}\" successfully " f"deleted.")
1169
+ logger.info(f"Session \"{subject_name}/{session['ssid']}\" successfully deleted.")
1175
1170
  return True
1176
1171
 
1177
1172
  def delete_session_by_patientid(self, patient_id):
@@ -1273,7 +1268,7 @@ class Project:
1273
1268
  {"container_name", "container_id", "patient_secret_name", "ssid"}
1274
1269
  """
1275
1270
 
1276
- assert len(items) == 2, f"The number of elements in items " f"'{len(items)}' should be equal to two."
1271
+ assert len(items) == 2, f"The number of elements in items '{len(items)}' should be equal to two."
1277
1272
  assert all([isinstance(item, int) for item in items]), f"All items elements '{items}' should be integers."
1278
1273
 
1279
1274
  response = platform.parse_response(
@@ -1316,7 +1311,8 @@ class Project:
1316
1311
  ----------
1317
1312
  search_condition : dict
1318
1313
  - p_n: str or None Analysis name
1319
- - type: str or None Type
1314
+ - type: str or None Type (analysis_code:analysis_version).
1315
+ - analysis_code: str or None Type
1320
1316
  - from_d: str or None dd.mm.yyyy Date from
1321
1317
  - to_d: str or None dd.mm.yyyy Date to
1322
1318
  - qa_status: str or None pass/fail/nd QC status
@@ -1372,13 +1368,7 @@ class Project:
1372
1368
  return False
1373
1369
  return content["files"]
1374
1370
 
1375
- def list_container_filter_files(
1376
- self,
1377
- container_id,
1378
- modality="",
1379
- metadata_info={},
1380
- tags=[]
1381
- ):
1371
+ def list_container_filter_files(self, container_id, modality="", metadata_info={}, tags=[]):
1382
1372
  """
1383
1373
  List the name of the files available inside a given container.
1384
1374
  search condition example:
@@ -1414,23 +1404,12 @@ class Project:
1414
1404
  if modality == "":
1415
1405
  modality_bool = True
1416
1406
  else:
1417
- modality_bool = modality == metadata_file["metadata"].get(
1418
- "modality"
1419
- )
1407
+ modality_bool = modality == metadata_file["metadata"].get("modality")
1420
1408
  for key in metadata_info.keys():
1421
- meta_key = (
1422
- (
1423
- metadata_file.get("metadata") or {}
1424
- ).get("info") or {}).get(
1425
- key
1426
- )
1409
+ meta_key = ((metadata_file.get("metadata") or {}).get("info") or {}).get(key)
1427
1410
  if meta_key is None:
1428
- logging.getLogger(logger_name).warning(
1429
- f"{key} is not in file_info from file {file}"
1430
- )
1431
- info_bool.append(
1432
- metadata_info[key] == meta_key
1433
- )
1411
+ logging.getLogger(logger_name).warning(f"{key} is not in file_info from file {file}")
1412
+ info_bool.append(metadata_info[key] == meta_key)
1434
1413
  if all(tags_bool) and all(info_bool) and modality_bool:
1435
1414
  selected_files.append(file)
1436
1415
  return selected_files
@@ -1486,13 +1465,12 @@ class Project:
1486
1465
  analysis_name_or_id = int(analysis_name_or_id)
1487
1466
  else:
1488
1467
  search_tag = "p_n"
1489
- excluded_characters = ["\\", "[", "]", "(", ")",
1490
- "{", "}", "+", "*"]
1468
+ excluded_characters = ["\\", "[", "]", "(", ")", "{", "}", "+", "*"]
1491
1469
  excluded_bool = [character in analysis_name_or_id for character in excluded_characters]
1492
1470
  if any(excluded_bool):
1493
1471
  raise Exception(f"p_n does not allow characters {excluded_characters}")
1494
1472
  else:
1495
- raise Exception("The analysis identifier must be its name or an " "integer")
1473
+ raise Exception("The analysis identifier must be its name or an integer")
1496
1474
 
1497
1475
  search_condition = {
1498
1476
  search_tag: analysis_name_or_id,
@@ -1502,7 +1480,7 @@ class Project:
1502
1480
  )
1503
1481
 
1504
1482
  if len(response) > 1:
1505
- raise Exception(f"multiple analyses with name " f"{analysis_name_or_id} found")
1483
+ raise Exception(f"multiple analyses with name {analysis_name_or_id} found")
1506
1484
  elif len(response) == 1:
1507
1485
  return response[0]
1508
1486
  else:
@@ -1530,7 +1508,8 @@ class Project:
1530
1508
  ----------
1531
1509
  search_condition : dict
1532
1510
  - p_n: str or None Analysis name
1533
- - type: str or None Type
1511
+ - type: str or None Type (analysis_code:analysis_version).
1512
+ - analysis_code: str or None Type
1534
1513
  - from_d: str or None dd.mm.yyyy Date from
1535
1514
  - to_d: str or None dd.mm.yyyy Date to
1536
1515
  - qa_status: str or None pass/fail/nd QC status
@@ -1550,11 +1529,12 @@ class Project:
1550
1529
  dict
1551
1530
  List of analysis, each a dictionary
1552
1531
  """
1553
- assert len(items) == 2, f"The number of elements in items " f"'{len(items)}' should be equal to two."
1532
+ assert len(items) == 2, f"The number of elements in items '{len(items)}' should be equal to two."
1554
1533
  assert all([isinstance(item, int) for item in items]), f"All items elements '{items}' should be integers."
1555
1534
  search_keys = {
1556
1535
  "p_n": str,
1557
1536
  "type": str,
1537
+ "analysis_code": str,
1558
1538
  "from_d": str,
1559
1539
  "to_d": str,
1560
1540
  "qa_status": str,
@@ -1567,12 +1547,11 @@ class Project:
1567
1547
  }
1568
1548
  for key in search_condition.keys():
1569
1549
  if key not in search_keys.keys():
1570
- raise Exception((f"This key '{key}' is not accepted by this" "search condition"))
1550
+ raise Exception((f"This key '{key}' is not accepted by this search condition"))
1571
1551
  if not isinstance(search_condition[key], search_keys[key]) and search_condition[key] is not None:
1572
- raise Exception((f"The key {key} in the search condition" f"is not type {search_keys[key]}"))
1552
+ raise Exception((f"The key {key} in the search condition is not type {search_keys[key]}"))
1573
1553
  if "p_n" == key:
1574
- excluded_characters = ["\\", "[", "]", "(", ")",
1575
- "{", "}", "+", "*"]
1554
+ excluded_characters = ["\\", "[", "]", "(", ")", "{", "}", "+", "*"]
1576
1555
  excluded_bool = [character in search_condition["p_n"] for character in excluded_characters]
1577
1556
  if any(excluded_bool):
1578
1557
  raise Exception(f"p_n does not allow characters {excluded_characters}")
@@ -1598,6 +1577,7 @@ class Project:
1598
1577
  settings=None,
1599
1578
  tags=None,
1600
1579
  preferred_destination=None,
1580
+ ignore_file_selection=True,
1601
1581
  ):
1602
1582
  """
1603
1583
  Starts an analysis on a subject.
@@ -1627,6 +1607,10 @@ class Project:
1627
1607
  The tags of the analysis.
1628
1608
  preferred_destination : str
1629
1609
  The machine on which to run the analysis
1610
+ ignore_file_selection : Bool
1611
+ When the file filter of the analysis is satified by multiple files.
1612
+ False if you want to select manually the file to input to the
1613
+ analysis. True otherwise and the analysis will cancelled.
1630
1614
 
1631
1615
  Returns
1632
1616
  -------
@@ -1668,7 +1652,9 @@ class Project:
1668
1652
  post_data["preferred_destination"] = preferred_destination
1669
1653
 
1670
1654
  logger.debug(f"post_data = {post_data}")
1671
- return self.__handle_start_analysis(post_data, ignore_warnings=ignore_warnings)
1655
+ return self.__handle_start_analysis(
1656
+ post_data, ignore_warnings=ignore_warnings, ignore_file_selection=ignore_file_selection
1657
+ )
1672
1658
 
1673
1659
  def delete_analysis(self, analysis_id):
1674
1660
  """
@@ -1759,17 +1745,16 @@ class Project:
1759
1745
  try:
1760
1746
  analysis_id = str(int(analysis_id))
1761
1747
  except ValueError:
1762
- raise ValueError(f"'analysis_id' has to be an integer"
1763
- f" not '{analysis_id}'.")
1748
+ raise ValueError(f"'analysis_id' has to be an integer not '{analysis_id}'.")
1764
1749
 
1765
1750
  file_name = file_name if file_name else f"logs_{analysis_id}.txt"
1766
1751
  try:
1767
1752
  res = platform.post(
1768
- auth=self._account.auth,
1769
- endpoint="analysis_manager/download_execution_file",
1770
- data={"project_id": analysis_id, "file": f"logs_{analysis_id}"},
1771
- timeout=1000
1772
- )
1753
+ auth=self._account.auth,
1754
+ endpoint="analysis_manager/download_execution_file",
1755
+ data={"project_id": analysis_id, "file": f"logs_{analysis_id}"},
1756
+ timeout=1000,
1757
+ )
1773
1758
  except Exception:
1774
1759
  logger.error(f"Could not export the analysis log of '{analysis_id}'")
1775
1760
  return False
@@ -1784,8 +1769,7 @@ class Project:
1784
1769
 
1785
1770
  """ QC Status Related Methods """
1786
1771
 
1787
- def set_qc_status_analysis(self, analysis_id,
1788
- status=QCStatus.UNDERTERMINED, comments=""):
1772
+ def set_qc_status_analysis(self, analysis_id, status=QCStatus.UNDERTERMINED, comments=""):
1789
1773
  """
1790
1774
  Changes the analysis QC status.
1791
1775
 
@@ -1814,25 +1798,27 @@ class Project:
1814
1798
  try:
1815
1799
  analysis_id = str(int(analysis_id))
1816
1800
  except ValueError:
1817
- raise ValueError(f"analysis_id: '{analysis_id}' not valid. " f"Must be convertible to int.")
1801
+ raise ValueError(f"analysis_id: '{analysis_id}' not valid. Must be convertible to int.")
1818
1802
 
1819
1803
  try:
1820
1804
  platform.parse_response(
1821
1805
  platform.post(
1822
1806
  auth=self._account.auth,
1823
1807
  endpoint="projectset_manager/set_qa_status",
1824
- data={"item_ids": analysis_id, "status": status.value,
1825
- "comments": str(comments), "entity": "analysis"},
1808
+ data={
1809
+ "item_ids": analysis_id,
1810
+ "status": status.value,
1811
+ "comments": str(comments),
1812
+ "entity": "analysis",
1813
+ },
1826
1814
  )
1827
1815
  )
1828
1816
  except Exception:
1829
- logger.error(f"It was not possible to change the QC status of"
1830
- f"Analysis ID: {analysis_id}")
1817
+ logger.error(f"It was not possible to change the QC status of Analysis ID: {analysis_id}")
1831
1818
  return False
1832
1819
  return True
1833
1820
 
1834
- def set_qc_status_subject(self, patient_id,
1835
- status=QCStatus.UNDERTERMINED, comments=""):
1821
+ def set_qc_status_subject(self, patient_id, status=QCStatus.UNDERTERMINED, comments=""):
1836
1822
  """
1837
1823
  Changes the QC status of a Patient ID (equivalent to a
1838
1824
  Subject ID/Session ID).
@@ -1861,20 +1847,23 @@ class Project:
1861
1847
  try:
1862
1848
  patient_id = str(int(patient_id))
1863
1849
  except ValueError:
1864
- raise ValueError(f"'patient_id': '{patient_id}' not valid. " f"Must be convertible to int.")
1850
+ raise ValueError(f"'patient_id': '{patient_id}' not valid. Must be convertible to int.")
1865
1851
 
1866
1852
  try:
1867
1853
  platform.parse_response(
1868
1854
  platform.post(
1869
1855
  auth=self._account.auth,
1870
1856
  endpoint="projectset_manager/set_qa_status",
1871
- data={"item_ids": patient_id, "status": status.value,
1872
- "comments": str(comments), "entity": "patients"},
1857
+ data={
1858
+ "item_ids": patient_id,
1859
+ "status": status.value,
1860
+ "comments": str(comments),
1861
+ "entity": "patients",
1862
+ },
1873
1863
  )
1874
1864
  )
1875
1865
  except Exception:
1876
- logger.error(f"It was not possible to change the QC status of"
1877
- f"Patient ID: {patient_id}")
1866
+ logger.error(f"It was not possible to change the QC status of Patient ID: {patient_id}")
1878
1867
  return False
1879
1868
  return True
1880
1869
 
@@ -1906,12 +1895,10 @@ class Project:
1906
1895
  return False, False
1907
1896
  except Exception:
1908
1897
  # Handle other potential exceptions
1909
- logging.error(f"It was not possible to extract the QC status"
1910
- f"from Analysis ID: {analysis_id}")
1898
+ logging.error(f"It was not possible to extract the QC status from Analysis ID: {analysis_id}")
1911
1899
  return False, False
1912
1900
 
1913
- def get_qc_status_subject(self, patient_id=None, subject_name=None,
1914
- ssid=None):
1901
+ def get_qc_status_subject(self, patient_id=None, subject_name=None, ssid=None):
1915
1902
  """
1916
1903
  Gets the session QC status via the patient ID or the Subject ID
1917
1904
  and the Session ID.
@@ -1939,13 +1926,11 @@ class Project:
1939
1926
  try:
1940
1927
  patient_id = int(patient_id)
1941
1928
  except ValueError:
1942
- raise ValueError(f"patient_id '{patient_id}' should be an "
1943
- f"integer.")
1929
+ raise ValueError(f"patient_id '{patient_id}' should be an integer.")
1944
1930
  sessions = self.get_subjects_metadata(search_criteria={})
1945
1931
  session = [session for session in sessions if int(session["_id"]) == patient_id]
1946
1932
  if len(session) < 1:
1947
- logging.error(f"No session was found with Patient ID: "
1948
- f"'{patient_id}'.")
1933
+ logging.error(f"No session was found with Patient ID: '{patient_id}'.")
1949
1934
  return False, False
1950
1935
  return convert_qc_value_to_qcstatus(session[0]["qa_status"]), session[0]["qa_comments"]
1951
1936
  elif subject_name and ssid:
@@ -1956,15 +1941,14 @@ class Project:
1956
1941
  }
1957
1942
  )
1958
1943
  if len(session) < 1:
1959
- logging.error(f"No session was found with Subject ID: "
1960
- f"'{subject_name}' and Session ID: '{ssid}'.")
1944
+ logging.error(f"No session was found with Subject ID: '{subject_name}' and Session ID: '{ssid}'.")
1961
1945
  return False, False
1962
1946
  return convert_qc_value_to_qcstatus(session[0]["qa_status"]), session[0]["qa_comments"]
1963
1947
  else:
1964
- raise ValueError("Either 'patient_id' or 'subject_name' and 'ssid'"
1965
- " must not be empty.")
1948
+ raise ValueError("Either 'patient_id' or 'subject_name' and 'ssid' must not be empty.")
1966
1949
 
1967
1950
  """ Protocol Adherence Related Methods """
1951
+
1968
1952
  def set_project_pa_rules(self, rules_file_path, guidance_text=""):
1969
1953
  """
1970
1954
  Updates the active project's protocol adherence rules using the
@@ -1989,20 +1973,20 @@ class Project:
1989
1973
  with open(rules_file_path, "r") as fr:
1990
1974
  rules = json.load(fr)
1991
1975
  except FileNotFoundError:
1992
- logger.error(f"Pprotocol adherence rule file '{rules_file_path}' "
1993
- f"not found.")
1976
+ logger.error(f"Pprotocol adherence rule file '{rules_file_path}' not found.")
1994
1977
  return False
1995
1978
 
1996
1979
  # Update the project's QA rules
1997
- res = platform.parse_response(platform.post(
1998
- auth=self._account.auth,
1999
- endpoint="projectset_manager/set_session_qa_requirements",
2000
- data={"project_id": self._project_id, "rules": json.dumps(rules), "guidance_text": guidance_text},
2001
- ))
1980
+ res = platform.parse_response(
1981
+ platform.post(
1982
+ auth=self._account.auth,
1983
+ endpoint="projectset_manager/set_session_qa_requirements",
1984
+ data={"project_id": self._project_id, "rules": json.dumps(rules), "guidance_text": guidance_text},
1985
+ )
1986
+ )
2002
1987
 
2003
1988
  if not res.get("success") == 1:
2004
- logger.error("There was an error setting up the protocol "
2005
- "adherence rules.")
1989
+ logger.error("There was an error setting up the protocol adherence rules.")
2006
1990
  logger.error(platform.parse_response(res))
2007
1991
  return False
2008
1992
 
@@ -2028,15 +2012,16 @@ class Project:
2028
2012
  logger = logging.getLogger(logger_name)
2029
2013
 
2030
2014
  # Update the project's QA rules
2031
- res = platform.parse_response(platform.post(
2032
- auth=self._account.auth,
2033
- endpoint="projectset_manager/get_session_qa_requirements",
2034
- data={"project_id": self._project_id},
2035
- ))
2015
+ res = platform.parse_response(
2016
+ platform.post(
2017
+ auth=self._account.auth,
2018
+ endpoint="projectset_manager/get_session_qa_requirements",
2019
+ data={"project_id": self._project_id},
2020
+ )
2021
+ )
2036
2022
 
2037
2023
  if "rules" not in res:
2038
- logger.error(f"There was an error extracting the protocol "
2039
- f"adherence rules from {self._project_name}.")
2024
+ logger.error(f"There was an error extracting the protocol adherence rules from {self._project_name}.")
2040
2025
  logger.error(platform.parse_response(res))
2041
2026
  return False
2042
2027
 
@@ -2048,14 +2033,14 @@ class Project:
2048
2033
  with open(rules_file_path, "w") as fr:
2049
2034
  json.dump(res["rules"], fr, indent=4)
2050
2035
  except FileNotFoundError:
2051
- logger.error(f"Protocol adherence rules could not be exported"
2052
- f"to file: '{rules_file_path}'.")
2036
+ logger.error(f"Protocol adherence rules could not be exported to file: '{rules_file_path}'.")
2053
2037
  return False
2054
2038
 
2055
2039
  return res["guidance_text"]
2056
2040
 
2057
2041
  """ Helper Methods """
2058
- def __handle_start_analysis(self, post_data, ignore_warnings=False, n_calls=0):
2042
+
2043
+ def __handle_start_analysis(self, post_data, ignore_warnings=False, ignore_file_selection=True, n_calls=0):
2059
2044
  """
2060
2045
  Handle the possible responses from the server after start_analysis.
2061
2046
  Sometimes we have to send a request again, and then check again the
@@ -2081,8 +2066,21 @@ class Project:
2081
2066
  platform.post(self._account.auth, "analysis_manager/analysis_registration", data=post_data)
2082
2067
  )
2083
2068
  logger.info(response["message"])
2084
- return int(response["analysis_id"])
2069
+ return int(response["analysis_id"]) if "analysis_id" in response else None
2070
+
2085
2071
  except platform.ChooseDataError as choose_data:
2072
+ if ignore_file_selection:
2073
+ logger.error(
2074
+ "Existence of multiple files satisfying the input data "
2075
+ "requirements and parameter ignore_file_selection==True."
2076
+ )
2077
+ logger.error(
2078
+ f"Unable to start the analysis: "
2079
+ f"{post_data['script_name']}:"
2080
+ f"{post_data['version']} with input "
2081
+ f"{post_data['as_input']}."
2082
+ )
2083
+ return None
2086
2084
  has_warning = False
2087
2085
 
2088
2086
  # logging any warning that we have
@@ -2097,44 +2095,104 @@ class Project:
2097
2095
  }
2098
2096
 
2099
2097
  if choose_data.data_to_choose:
2100
- # in case we have data to choose
2101
- chosen_files = {}
2102
- for settings_key in choose_data.data_to_choose:
2103
- chosen_files[settings_key] = {}
2104
- filters = choose_data.data_to_choose[settings_key]["filters"]
2105
- for filter_key in filters:
2106
- filter_data = filters[filter_key]
2107
-
2108
- # skip the filters that did not pass
2109
- if not filter_data["passed"]:
2110
- continue
2111
-
2112
- number_of_files_to_select = 1
2113
- if filter_data["range"][0] != 0:
2114
- number_of_files_to_select = filter_data["range"][0]
2115
- elif filter_data["range"][1] != 0:
2116
- number_of_files_to_select = min(filter_data["range"][1], len(filter_data["files"]))
2117
- else:
2118
- number_of_files_to_select = len(filter_data["files"])
2119
-
2120
- files_selection = [ff["_id"] for ff in filter_data["files"][:number_of_files_to_select]]
2121
- chosen_files[settings_key][filter_key] = files_selection
2122
-
2123
- new_post["user_preference"] = json.dumps(chosen_files)
2098
+ self.__handle_manual_choose_data(new_post, choose_data)
2124
2099
  else:
2125
2100
  if has_warning and not ignore_warnings:
2126
- logger.info("cancelling analysis due to warnings, " + 'set "ignore_warnings" to True to override')
2101
+ logger.error("Cancelling analysis due to warnings, set 'ignore_warnings' to True to override.")
2127
2102
  new_post["cancel"] = "1"
2128
2103
  else:
2129
2104
  logger.info("suppressing warnings")
2130
2105
  new_post["user_preference"] = "{}"
2131
2106
  new_post["_mint_only_warning"] = "1"
2132
2107
 
2133
- return self.__handle_start_analysis(new_post, ignore_warnings=ignore_warnings, n_calls=n_calls)
2108
+ return self.__handle_start_analysis(new_post, ignore_warnings, ignore_file_selection, n_calls)
2134
2109
  except platform.ActionFailedError as e:
2135
- logger.error(f"Unable to start the analysis: {e}")
2110
+ logger.error(f"Unable to start the analysis: {e}.")
2136
2111
  return None
2137
2112
 
2113
+ def __handle_manual_choose_data(self, post_data, choose_data):
2114
+ """
2115
+ Handle the responses of the user when there is need to select a file
2116
+ to start the analysis.
2117
+
2118
+ At the moment only supports a manual selection via the command-line
2119
+ interface.
2120
+
2121
+ Parameters
2122
+ ----------
2123
+ post_data : dict
2124
+ Current post_data dictionary. To be mofidied in-place.
2125
+ choose_data : platform.ChooseDataError
2126
+ Error raised when trying to start an analysis, but data has to be chosen.
2127
+ """
2128
+
2129
+ logger = logging.getLogger(logger_name)
2130
+ logger.warning("Multiple inputs available. You have to select the desired file/s to continue.")
2131
+ # in case we have data to choose
2132
+ chosen_files = {}
2133
+ for settings_key in choose_data.data_to_choose:
2134
+ logger.warning(f"Type next the file/s for the input with ID: '{settings_key}'.")
2135
+ chosen_files[settings_key] = {}
2136
+ filters = choose_data.data_to_choose[settings_key]["filters"]
2137
+ for filter_key in filters:
2138
+ filter_data = filters[filter_key]
2139
+
2140
+ # skip the filters that did not pass
2141
+ if not filter_data["passed"]:
2142
+ continue
2143
+ elif post_data.get("cancel"):
2144
+ continue
2145
+
2146
+ number_of_files_to_select = 1
2147
+ if filter_data["range"][0] != 0:
2148
+ number_of_files_to_select = filter_data["range"][0]
2149
+ elif filter_data["range"][1] != 0:
2150
+ number_of_files_to_select = min(filter_data["range"][1], len(filter_data["files"]))
2151
+ else:
2152
+ number_of_files_to_select = len(filter_data["files"])
2153
+
2154
+ # If the files need to be selected automatically based
2155
+ # on their file information.
2156
+ # Need to apply some filtering criteria at this stage.
2157
+ # Based on the information provided by next methods.
2158
+ # files_info = list_container_files_metadata() or
2159
+ # list_container_filter_files()
2160
+
2161
+ if number_of_files_to_select != len(filter_data["files"]):
2162
+ logger.warning(
2163
+ f" · File filter name: '{filter_key}'. Type "
2164
+ f"{number_of_files_to_select} file"
2165
+ f"{'s (i.e., file1.zip, file2.zip, file3.zip)' if number_of_files_to_select >1 else ''}."
2166
+ )
2167
+ save_file_ids, select_file_filter = {}, ""
2168
+ for file_ in filter_data["files"]:
2169
+ select_file_filter += f" · File name: {file_['name']}\n"
2170
+ save_file_ids[file_["name"]] = file_["_id"]
2171
+ names = [el.strip() for el in input(select_file_filter).strip().split(",")]
2172
+
2173
+ if len(names) != number_of_files_to_select:
2174
+ logger.error("The number of files selected does not correspond to the number of needed files.")
2175
+ logger.error(
2176
+ f"Selected: {len(names)} vs. "
2177
+ f"Number of files to select: "
2178
+ f"{number_of_files_to_select}."
2179
+ )
2180
+ logger.error("Cancelling analysis.")
2181
+ post_data["cancel"] = "1"
2182
+
2183
+ elif any([name not in save_file_ids for name in names]):
2184
+ logger.error(f"Some selected file/s '{', '.join(names)}' do not exist. Cancelling analysis...")
2185
+ post_data["cancel"] = "1"
2186
+ else:
2187
+ chosen_files[settings_key][filter_key] = [save_file_ids[name] for name in names]
2188
+
2189
+ else:
2190
+ logger.warning("Setting all available files to be input to the analysis.")
2191
+ files_selection = [ff["_id"] for ff in filter_data["files"][:number_of_files_to_select]]
2192
+ chosen_files[settings_key][filter_key] = files_selection
2193
+
2194
+ post_data["user_preference"] = json.dumps(chosen_files)
2195
+
2138
2196
  @staticmethod
2139
2197
  def __get_modalities(files):
2140
2198
  modalities = []
@@ -2284,9 +2342,9 @@ class Project:
2284
2342
  if key == "pars_modalities":
2285
2343
  modalities = value.split(";")[1].split(",")
2286
2344
  if len(modalities) != 1:
2287
- raise ValueError(f"A file can only have one modality. "
2288
- f"Provided Modalities: "
2289
- f"{', '.join(modalities)}.")
2345
+ raise ValueError(
2346
+ f"A file can only have one modality. Provided Modalities: {', '.join(modalities)}."
2347
+ )
2290
2348
  modality = modalities[0]
2291
2349
  elif key == "pars_tags":
2292
2350
  tags = value.split(";")[1].split(",")
@@ -2294,27 +2352,16 @@ class Project:
2294
2352
  d_tag = key.split("pars_[dicom]_")[1]
2295
2353
  d_type = value.split(";")[0]
2296
2354
  if d_type == "string":
2297
- file_metadata[d_tag] = {
2298
- "operation": "in",
2299
- "value": value.replace(d_type + ";", "")
2300
- }
2355
+ file_metadata[d_tag] = {"operation": "in", "value": value.replace(d_type + ";", "")}
2301
2356
  elif d_type == "integer":
2302
2357
  d_operator = value.split(";")[1].split("|")[0]
2303
2358
  d_value = value.split(";")[1].split("|")[1]
2304
- file_metadata[d_tag] = {
2305
- "operation": d_operator,
2306
- "value": int(d_value)}
2359
+ file_metadata[d_tag] = {"operation": d_operator, "value": int(d_value)}
2307
2360
  elif d_type == "decimal":
2308
2361
  d_operator = value.split(";")[1].split("|")[0]
2309
2362
  d_value = value.split(";")[1].split("|")[1]
2310
- file_metadata[d_tag] = {
2311
- "operation": d_operator,
2312
- "value": float(d_value)
2313
- }
2363
+ file_metadata[d_tag] = {"operation": d_operator, "value": float(d_value)}
2314
2364
  elif d_type == "list":
2315
2365
  value.replace(d_type + ";", "")
2316
- file_metadata[d_tag] = {
2317
- "operation": "in-list",
2318
- "value": value.replace(d_type + ";", "").split(";")
2319
- }
2366
+ file_metadata[d_tag] = {"operation": "in-list", "value": value.replace(d_type + ";", "").split(";")}
2320
2367
  return modality, tags, file_metadata
qmenta/client/Subject.py CHANGED
@@ -30,8 +30,7 @@ class Subject:
30
30
  self._all_data = None
31
31
 
32
32
  def __repr__(self):
33
- rep = "<Subject {} ({})>".format(self.name,
34
- self.project or "No project")
33
+ rep = "<Subject {} ({})>".format(self.name, self.project or "No project")
35
34
  return rep
36
35
 
37
36
  @property
@@ -110,14 +109,13 @@ class Subject:
110
109
 
111
110
  def get_all_data(self, cache=True):
112
111
  if not cache or not self._all_data:
113
- self._all_data = platform.parse_response(platform.post(
114
- auth=self.project._account.auth,
115
- endpoint='patient_manager/get_patient_profile_data',
116
- data={
117
- "patient_id": self.subject_id,
118
- "patient_secret_name": self.name
119
- }
120
- ))
112
+ self._all_data = platform.parse_response(
113
+ platform.post(
114
+ auth=self.project._account.auth,
115
+ endpoint="patient_manager/get_patient_profile_data",
116
+ data={"patient_id": self.subject_id, "patient_secret_name": self.name},
117
+ )
118
+ )
121
119
  return self._all_data
122
120
 
123
121
  @property
@@ -242,8 +240,7 @@ class Subject:
242
240
  List of dictionaries with the data of each analysis performed.
243
241
  """
244
242
  all_analysis = self.project.list_analysis(limit=10000000)
245
- return [a for a in all_analysis if
246
- a["patient_secret_name"] == self._name]
243
+ return [a for a in all_analysis if a["patient_secret_name"] == self._name]
247
244
 
248
245
  def upload_mri(self, path):
249
246
  """
qmenta/client/__init__.py CHANGED
@@ -3,8 +3,4 @@ from .Project import Project
3
3
  from .Subject import Subject
4
4
 
5
5
 
6
- __all__ = [
7
- 'Account',
8
- 'Project',
9
- 'Subject'
10
- ]
6
+ __all__ = ["Account", "Project", "Subject"]
qmenta/client/utils.py CHANGED
@@ -67,14 +67,10 @@ def unzip_dicoms(zip_path, output_folder, exclude_members=None):
67
67
  if exclude_members is not None:
68
68
  # Filter members according to the passed exclusion rules
69
69
  for exclude_members_rule in exclude_members:
70
- members = [
71
- x for x in members if not re.match(exclude_members_rule, x)
72
- ]
70
+ members = [x for x in members if not re.match(exclude_members_rule, x)]
73
71
 
74
72
  # Extract all files and add them to the list of extracted files
75
73
  zfile.extractall(path=output_folder, members=members)
76
- current_extracted_files = [
77
- os.path.join(output_folder, fpath) for fpath in members
78
- ]
74
+ current_extracted_files = [os.path.join(output_folder, fpath) for fpath in members]
79
75
  extracted_files.extend(current_extracted_files)
80
76
  os.unlink(zip_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qmenta-client
3
- Version: 1.1.dev1446
3
+ Version: 1.1.dev1468
4
4
  Summary: Python client lib to interact with the QMENTA platform.
5
5
  Author: QMENTA
6
6
  Author-email: dev@qmenta.com
@@ -0,0 +1,10 @@
1
+ qmenta/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
2
+ qmenta/client/Account.py,sha256=7BOWHtRbHdfpBYQqv9v2m2Fag13pExZSxFsjDA7UsW0,9500
3
+ qmenta/client/File.py,sha256=iCrzrd7rIfjjW2AgMgUoK-ZF2wf-95wCcPKxKw6PGyg,4816
4
+ qmenta/client/Project.py,sha256=eIEmFcfh5dYjuHqGgfJfjvJNUYM8EEPFDwRidjjGOK8,86415
5
+ qmenta/client/Subject.py,sha256=b5sg9UFtn11bmPM-xFXP8aehOm_HGxnhgT7IPKbrZnE,8688
6
+ qmenta/client/__init__.py,sha256=Mtqe4zf8n3wuwMXSALENQgp5atQY5VcsyXWs2hjBs28,133
7
+ qmenta/client/utils.py,sha256=vWUAW0r9yDetdlwNo86sdzKn03FNGvwa7D9UtOA3TEc,2419
8
+ qmenta_client-1.1.dev1468.dist-info/METADATA,sha256=iVD5lU8ZbFqP5B6I8SdaN784WqN6nkx2MI3IG3lICOU,672
9
+ qmenta_client-1.1.dev1468.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
10
+ qmenta_client-1.1.dev1468.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- qmenta/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
2
- qmenta/client/Account.py,sha256=S9D0-lmcBln2o3DwJfmLfu5G2wjE7gEJCIeJu89v0Is,9647
3
- qmenta/client/File.py,sha256=ZgvSqejIosUt4uoX7opUnPnp5XGEaJNMRwFC0mQVB8k,5344
4
- qmenta/client/Project.py,sha256=7MPUskUznuCSN7SOdNyIZckDcnBnJvlMnIoIbF3KvzE,83483
5
- qmenta/client/Subject.py,sha256=lhxxVdQ6d-GNoQC8mrJwa4L1f44nJc4PcJtDspmKN7I,8756
6
- qmenta/client/__init__.py,sha256=AjTojBhZeW5nl0i605KS8S1Gl5tPNc1hdzD47BGNfoI,147
7
- qmenta/client/utils.py,sha256=5DK2T_HQprrCwLS0Ycm2CjseaYmAUKaJkJvYoW-Rqzc,2479
8
- qmenta_client-1.1.dev1446.dist-info/METADATA,sha256=Itzicjy42tkFa1VpEvKNWSu4X7YpB6AFfeA63CFwZv8,672
9
- qmenta_client-1.1.dev1446.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
10
- qmenta_client-1.1.dev1446.dist-info/RECORD,,