qmenta-client 1.1.dev1504__py3-none-any.whl → 1.1.dev1507__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qmenta/client/Project.py CHANGED
@@ -231,7 +231,7 @@ class Project:
231
231
  name="",
232
232
  input_data_type="qmenta_medical_image_data:3.10",
233
233
  add_to_container_id=0,
234
- chunk_size=2 ** 9,
234
+ chunk_size=2**9,
235
235
  split_data=False,
236
236
  ):
237
237
  """
@@ -262,14 +262,14 @@ class Project:
262
262
  a power of 2: 2**x. Default value of x is 9 (chunk_size = 512 kB)
263
263
  split_data : bool
264
264
  If True, the platform will try to split the uploaded file into
265
- different sessions. It will be ignored when the ssid is given.
265
+ different sessions. It will be ignored when the ssid or a
266
+ add_to_container_id are given.
266
267
 
267
268
  Returns
268
269
  -------
269
270
  bool
270
271
  True if correctly uploaded, False otherwise.
271
272
  """
272
-
273
273
  filename = os.path.split(file_path)[1]
274
274
  input_data_type = "offline_analysis:1.0" if result else input_data_type
275
275
 
@@ -280,6 +280,8 @@ class Project:
280
280
 
281
281
  total_bytes = os.path.getsize(file_path)
282
282
 
283
+ split_data = self.__assert_split_data(split_data, ssid, add_to_container_id)
284
+
283
285
  # making chunks of the file and sending one by one
284
286
  logger = logging.getLogger(logger_name)
285
287
  with open(file_path, "rb") as file_object:
@@ -296,10 +298,6 @@ class Project:
296
298
  response = None
297
299
  last_chunk = False
298
300
 
299
- if ssid and split_data:
300
- logger.warning("split-data argument will be ignored because" + " ssid has been specified")
301
- split_data = False
302
-
303
301
  while True:
304
302
  data = file_object.read(chunk_size)
305
303
  if not data:
@@ -374,8 +372,7 @@ class Project:
374
372
  logger.error(error)
375
373
  return False
376
374
 
377
- message = "Your data was successfully uploaded."
378
- message += "The uploaded file will be soon processed !"
375
+ message = "Your data was successfully uploaded. The uploaded file will be soon processed !"
379
376
  logger.info(message)
380
377
  return True
381
378
 
@@ -477,7 +474,7 @@ class Project:
477
474
  self._account.auth, "file_manager/download_file", data=params, stream=True
478
475
  ) as response, open(local_filename, "wb") as f:
479
476
 
480
- for chunk in response.iter_content(chunk_size=2 ** 9 * 1024):
477
+ for chunk in response.iter_content(chunk_size=2**9 * 1024):
481
478
  f.write(chunk)
482
479
  f.flush()
483
480
 
@@ -523,7 +520,7 @@ class Project:
523
520
  self._account.auth, "file_manager/download_file", data=params, stream=True
524
521
  ) as response, open(zip_name, "wb") as f:
525
522
 
526
- for chunk in response.iter_content(chunk_size=2 ** 9 * 1024):
523
+ for chunk in response.iter_content(chunk_size=2**9 * 1024):
527
524
  f.write(chunk)
528
525
  f.flush()
529
526
 
@@ -2045,7 +2042,7 @@ class Project:
2045
2042
  """
2046
2043
  Parse QC (Quality Control) text output into a structured dictionary format.
2047
2044
 
2048
- This function takes raw QC text output (typically from medical imaging quality checks)
2045
+ This function takes raw QC text output (from the Protocol Adherence analysis)
2049
2046
  and parses it into a structured format that separates passed and failed rules,
2050
2047
  along with their associated files and conditions.
2051
2048
 
@@ -2104,18 +2101,15 @@ class Project:
2104
2101
 
2105
2102
  _, text = self.get_qc_status_subject(patient_id=patient_id, subject_name=subject_name, ssid=ssid)
2106
2103
 
2107
- result = {
2108
- "passed": [],
2109
- "failed": []
2110
- }
2104
+ result = {"passed": [], "failed": []}
2111
2105
 
2112
2106
  # Split into failed and passed sections
2113
- sections = re.split(r'={10,}\n\n', text)
2107
+ sections = re.split(r"={10,}\n\n", text)
2114
2108
  if len(sections) == 3:
2115
- failed_section = sections[1].split('=' * 10)[0].strip()
2109
+ failed_section = sections[1].split("=" * 10)[0].strip()
2116
2110
  passed_section = sections[2].strip()
2117
2111
  else:
2118
- section = sections[1].split('=' * 10)[0].strip()
2112
+ section = sections[1].split("=" * 10)[0].strip()
2119
2113
  if "PASSED QC MESSAGES" in section:
2120
2114
  passed_section = section
2121
2115
  failed_section = ""
@@ -2123,85 +2117,13 @@ class Project:
2123
2117
  failed_section = section
2124
2118
  passed_section = ""
2125
2119
 
2126
- # Parse failed rules
2127
- failed_rules = re.split(r'\n ❌ ', failed_section)
2128
- for rule_text in failed_rules[1:]: # Skip first empty part
2129
- rule_name = rule_text.split(' ❌')[0].strip()
2130
- rule_data = {
2131
- "rule": rule_name,
2132
- "files": [],
2133
- "failed_conditions": {}
2134
- }
2135
-
2136
- # Extract all file comparisons for this rule
2137
- file_comparisons = re.split(r'\t- Comparison with file:', rule_text)
2138
- for comp in file_comparisons[1:]: # Skip first part
2139
- file_name = comp.split('\n')[0].strip()
2140
- conditions_match = re.search(
2141
- r'Conditions:(.*?)(?=\n\t- Comparison|\n\n|$)',
2142
- comp,
2143
- re.DOTALL
2144
- )
2145
- if not conditions_match:
2146
- continue
2147
-
2148
- conditions_text = conditions_match.group(1).strip()
2149
- # Parse conditions
2150
- conditions = []
2151
- for line in conditions_text.split('\n'):
2152
- line = line.strip()
2153
- if line.startswith('·'):
2154
- status = '✔' if '✔' in line else '🚫'
2155
- condition = re.sub(r'^· [✔🚫]\s*', '', line)
2156
- conditions.append({
2157
- "status": "passed" if status == '✔' else "failed",
2158
- "condition": condition
2159
- })
2160
-
2161
- # Add to failed conditions summary
2162
- for cond in conditions:
2163
- if cond['status'] == 'failed':
2164
- cond_text = cond['condition']
2165
- if cond_text not in rule_data['failed_conditions']:
2166
- rule_data['failed_conditions'][cond_text] = 0
2167
- rule_data['failed_conditions'][cond_text] += 1
2168
-
2169
- rule_data['files'].append({
2170
- "file": file_name,
2171
- "conditions": conditions
2172
- })
2173
-
2174
- result['failed'].append(rule_data)
2120
+ # Parse failed rules
2121
+ failed_rules = re.split(r"\n ❌ ", failed_section)
2122
+ result = self.__parse_fail_rules(failed_rules, result)
2175
2123
 
2176
2124
  # Parse passed rules
2177
- passed_rules = re.split(r'\n ✅ ', passed_section)
2178
- for rule_text in passed_rules[1:]: # Skip first empty part
2179
- rule_name = rule_text.split(' ✅')[0].strip()
2180
- rule_data = {
2181
- "rule": rule_name,
2182
- "sub_rule": None,
2183
- "files": []
2184
- }
2185
-
2186
- # Get sub-rule
2187
- sub_rule_match = re.search(r'Sub-rule: (.*?)\n', rule_text)
2188
- if sub_rule_match:
2189
- rule_data['sub_rule'] = sub_rule_match.group(1).strip()
2190
-
2191
- # Get files passed
2192
- files_passed = re.search(r'List of files passed:(.*?)(?=\n\n|\Z)', rule_text, re.DOTALL)
2193
- if files_passed:
2194
- for line in files_passed.group(1).split('\n'):
2195
- line = line.strip()
2196
- if line.startswith('·'):
2197
- file_match = re.match(r'· (.*?) \((\d+)/(\d+)\)', line)
2198
- if file_match:
2199
- rule_data['files'].append({
2200
- "file": file_match.group(1).strip(),
2201
- "passed_conditions": int(file_match.group(2)),
2202
- })
2203
-
2204
- result['passed'].append(rule_data)
2125
+ passed_rules = re.split(r"\n ✅ ", passed_section)
2126
+ result = self.__parse_pass_rules(passed_rules, result)
2205
2127
 
2206
2128
  return result
2207
2129
 
@@ -2272,19 +2194,14 @@ class Project:
2272
2194
 
2273
2195
  # Initialize statistics
2274
2196
  stats = {
2275
- 'passed_rules': 0,
2276
- 'failed_rules': 0,
2197
+ "passed_rules": 0,
2198
+ "failed_rules": 0,
2277
2199
  "subjects_passed": 0,
2278
2200
  "subjects_with_failed": 0,
2279
- 'num_passed_files_distribution': defaultdict(int), # How many rules have N passed files
2280
- 'file_stats': {
2281
- 'total': 0,
2282
- 'passed': 0,
2283
- 'failed': 0,
2284
- 'pass_percentage': 0.0
2285
- },
2286
- 'condition_failure_rates': defaultdict(lambda: {'count': 0, 'percentage': 0.0}),
2287
- 'rule_success_rates': defaultdict(lambda: {'passed': 0, 'failed': 0, 'success_rate': 0.0}),
2201
+ "num_passed_files_distribution": defaultdict(int), # How many rules have N passed files
2202
+ "file_stats": {"total": 0, "passed": 0, "failed": 0, "pass_percentage": 0.0},
2203
+ "condition_failure_rates": defaultdict(lambda: {"count": 0, "percentage": 0.0}),
2204
+ "rule_success_rates": defaultdict(lambda: {"passed": 0, "failed": 0, "success_rate": 0.0}),
2288
2205
  }
2289
2206
 
2290
2207
  total_failures = 0
@@ -2294,56 +2211,56 @@ class Project:
2294
2211
  # sum subjects with some failed qc message
2295
2212
  stats["subjects_with_failed"] = sum([1 for rules in qc_results_list if rules["failed"]])
2296
2213
  # sum rules that have passed
2297
- stats["passed_rules"] = sum([len(rules['passed']) for rules in qc_results_list if rules["failed"]])
2214
+ stats["passed_rules"] = sum([len(rules["passed"]) for rules in qc_results_list if rules["failed"]])
2298
2215
  # sum rules that have failed
2299
- stats["failed_rules"] = sum([len(rules['failed']) for rules in qc_results_list if rules["failed"]])
2216
+ stats["failed_rules"] = sum([len(rules["failed"]) for rules in qc_results_list if rules["failed"]])
2300
2217
 
2301
2218
  for qc_results in qc_results_list:
2302
2219
 
2303
2220
  # Count passed files distribution
2304
- for rule in qc_results['passed']:
2305
- num_files = len(rule['files'])
2306
- stats['num_passed_files_distribution'][num_files] += 1
2307
- stats['file_stats']['passed'] += len(rule['files'])
2308
- stats['file_stats']['total'] += len(rule['files'])
2309
- rule_name = rule['rule']
2310
- stats['rule_success_rates'][rule_name]['passed'] += 1
2311
-
2312
- for rule in qc_results['failed']:
2313
- stats['file_stats']['total'] += len(rule['files'])
2314
- stats['file_stats']['failed'] += len(rule['files'])
2315
- for condition, count in rule['failed_conditions'].items():
2221
+ for rule in qc_results["passed"]:
2222
+ num_files = len(rule["files"])
2223
+ stats["num_passed_files_distribution"][num_files] += 1
2224
+ stats["file_stats"]["passed"] += len(rule["files"])
2225
+ stats["file_stats"]["total"] += len(rule["files"])
2226
+ rule_name = rule["rule"]
2227
+ stats["rule_success_rates"][rule_name]["passed"] += 1
2228
+
2229
+ for rule in qc_results["failed"]:
2230
+ stats["file_stats"]["total"] += len(rule["files"])
2231
+ stats["file_stats"]["failed"] += len(rule["files"])
2232
+ for condition, count in rule["failed_conditions"].items():
2316
2233
  # Extract just the condition text without actual value
2317
- clean_condition = re.sub(r'\.\s*Actual value:.*$', '', condition)
2318
- stats['condition_failure_rates'][clean_condition]['count'] += count
2234
+ clean_condition = re.sub(r"\.\s*Actual value:.*$", "", condition)
2235
+ stats["condition_failure_rates"][clean_condition]["count"] += count
2319
2236
  total_failures += count
2320
- rule_name = rule['rule']
2321
- stats['rule_success_rates'][rule_name]['failed'] += 1
2237
+ rule_name = rule["rule"]
2238
+ stats["rule_success_rates"][rule_name]["failed"] += 1
2322
2239
 
2323
- if stats['file_stats']['total'] > 0:
2324
- stats['file_stats']['pass_percentage'] = round(
2325
- (stats['file_stats']['passed'] / stats['file_stats']['total']) * 100, 2
2240
+ if stats["file_stats"]["total"] > 0:
2241
+ stats["file_stats"]["pass_percentage"] = round(
2242
+ (stats["file_stats"]["passed"] / stats["file_stats"]["total"]) * 100, 2
2326
2243
  )
2327
2244
 
2328
2245
  # Calculate condition failure percentages
2329
- for condition in stats['condition_failure_rates']:
2246
+ for condition in stats["condition_failure_rates"]:
2330
2247
  if total_failures > 0:
2331
- stats['condition_failure_rates'][condition]['percentage'] = round(
2332
- (stats['condition_failure_rates'][condition]['count'] / total_failures) * 100, 2
2248
+ stats["condition_failure_rates"][condition]["percentage"] = round(
2249
+ (stats["condition_failure_rates"][condition]["count"] / total_failures) * 100, 2
2333
2250
  )
2334
2251
 
2335
2252
  # Calculate rule success rates
2336
- for rule in stats['rule_success_rates']:
2337
- total = stats['rule_success_rates'][rule]['passed'] + stats['rule_success_rates'][rule]['failed']
2253
+ for rule in stats["rule_success_rates"]:
2254
+ total = stats["rule_success_rates"][rule]["passed"] + stats["rule_success_rates"][rule]["failed"]
2338
2255
  if total > 0:
2339
- stats['rule_success_rates'][rule]['success_rate'] = round(
2340
- (stats['rule_success_rates'][rule]['passed'] / total) * 100, 2
2256
+ stats["rule_success_rates"][rule]["success_rate"] = round(
2257
+ (stats["rule_success_rates"][rule]["passed"] / total) * 100, 2
2341
2258
  )
2342
2259
 
2343
2260
  # Convert defaultdict to regular dict for cleaner JSON output
2344
- stats['num_passed_files_distribution'] = dict(stats['num_passed_files_distribution'])
2345
- stats['condition_failure_rates'] = dict(stats['condition_failure_rates'])
2346
- stats['rule_success_rates'] = dict(stats['rule_success_rates'])
2261
+ stats["num_passed_files_distribution"] = dict(stats["num_passed_files_distribution"])
2262
+ stats["condition_failure_rates"] = dict(stats["condition_failure_rates"])
2263
+ stats["rule_success_rates"] = dict(stats["rule_success_rates"])
2347
2264
 
2348
2265
  return stats
2349
2266
 
@@ -2673,3 +2590,107 @@ class Project:
2673
2590
  value.replace(d_type + ";", "")
2674
2591
  file_metadata[d_tag] = {"operation": "in-list", "value": value.replace(d_type + ";", "").split(";")}
2675
2592
  return modality, tags, file_metadata
2593
+
2594
+ def __assert_split_data(self, split_data, ssid, add_to_container_id):
2595
+ """
2596
+ Assert if the split_data parameter is possible to use in regards
2597
+ to the ssid and add_to_container_id parameters during upload.
2598
+ Changes its status to False if needed.
2599
+
2600
+ Parameters
2601
+ ----------
2602
+ split_data : Bool
2603
+ split_data parameter from method 'upload_file'.
2604
+ ssid : str
2605
+ Session ID.
2606
+ add_to_container_id : int or bool
2607
+ Container ID or False
2608
+
2609
+ Returns
2610
+ -------
2611
+ split_data : Bool
2612
+
2613
+ """
2614
+
2615
+ logger = logging.getLogger(logger_name)
2616
+ if ssid and split_data:
2617
+ logger.warning("split-data argument will be ignored because ssid has been specified")
2618
+ split_data = False
2619
+
2620
+ if add_to_container_id and split_data:
2621
+ logger.warning("split-data argument will be ignored because add_to_container_id has been specified")
2622
+ split_data = False
2623
+
2624
+ return split_data
2625
+
2626
+ def __parse_fail_rules(self, failed_rules, result):
2627
+ """
2628
+ Parse fail rules.
2629
+ """
2630
+
2631
+ for rule_text in failed_rules[1:]: # Skip first empty part
2632
+ rule_name = rule_text.split(" ❌")[0].strip()
2633
+ rule_data = {"rule": rule_name, "files": [], "failed_conditions": {}}
2634
+
2635
+ # Extract all file comparisons for this rule
2636
+ file_comparisons = re.split(r"\t- Comparison with file:", rule_text)
2637
+ for comp in file_comparisons[1:]: # Skip first part
2638
+ file_name = comp.split("\n")[0].strip()
2639
+ conditions_match = re.search(r"Conditions:(.*?)(?=\n\t- Comparison|\n\n|$)", comp, re.DOTALL)
2640
+ if not conditions_match:
2641
+ continue
2642
+
2643
+ conditions_text = conditions_match.group(1).strip()
2644
+ # Parse conditions
2645
+ conditions = []
2646
+ for line in conditions_text.split("\n"):
2647
+ line = line.strip()
2648
+ if line.startswith("·"):
2649
+ status = "✔" if "✔" in line else "🚫"
2650
+ condition = re.sub(r"^· [✔🚫]\s*", "", line)
2651
+ conditions.append({"status": "passed" if status == "✔" else "failed", "condition": condition})
2652
+
2653
+ # Add to failed conditions summary
2654
+ for cond in conditions:
2655
+ if cond["status"] == "failed":
2656
+ cond_text = cond["condition"]
2657
+ if cond_text not in rule_data["failed_conditions"]:
2658
+ rule_data["failed_conditions"][cond_text] = 0
2659
+ rule_data["failed_conditions"][cond_text] += 1
2660
+
2661
+ rule_data["files"].append({"file": file_name, "conditions": conditions})
2662
+
2663
+ result["failed"].append(rule_data)
2664
+ return result
2665
+
2666
+ def __parse_pass_rules(self, passed_rules, result):
2667
+ """
2668
+ Parse pass rules.
2669
+ """
2670
+
2671
+ for rule_text in passed_rules[1:]: # Skip first empty part
2672
+ rule_name = rule_text.split(" ✅")[0].strip()
2673
+ rule_data = {"rule": rule_name, "sub_rule": None, "files": []}
2674
+
2675
+ # Get sub-rule
2676
+ sub_rule_match = re.search(r"Sub-rule: (.*?)\n", rule_text)
2677
+ if sub_rule_match:
2678
+ rule_data["sub_rule"] = sub_rule_match.group(1).strip()
2679
+
2680
+ # Get files passed
2681
+ files_passed = re.search(r"List of files passed:(.*?)(?=\n\n|\Z)", rule_text, re.DOTALL)
2682
+ if files_passed:
2683
+ for line in files_passed.group(1).split("\n"):
2684
+ line = line.strip()
2685
+ if line.startswith("·"):
2686
+ file_match = re.match(r"· (.*?) \((\d+)/(\d+)\)", line)
2687
+ if file_match:
2688
+ rule_data["files"].append(
2689
+ {
2690
+ "file": file_match.group(1).strip(),
2691
+ "passed_conditions": int(file_match.group(2)),
2692
+ }
2693
+ )
2694
+
2695
+ result["passed"].append(rule_data)
2696
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qmenta-client
3
- Version: 1.1.dev1504
3
+ Version: 1.1.dev1507
4
4
  Summary: Python client lib to interact with the QMENTA platform.
5
5
  Author: QMENTA
6
6
  Author-email: dev@qmenta.com
@@ -1,10 +1,10 @@
1
1
  qmenta/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
2
2
  qmenta/client/Account.py,sha256=7BOWHtRbHdfpBYQqv9v2m2Fag13pExZSxFsjDA7UsW0,9500
3
3
  qmenta/client/File.py,sha256=iCrzrd7rIfjjW2AgMgUoK-ZF2wf-95wCcPKxKw6PGyg,4816
4
- qmenta/client/Project.py,sha256=vx1GabCiVG7gYqnkNmqhqRyzy7ml7eT4jjnUciBq_Gw,99565
4
+ qmenta/client/Project.py,sha256=pV9mW90BzPIMOFauPiAONBIsYJfGsbi_Xabbe9DW32U,100493
5
5
  qmenta/client/Subject.py,sha256=b5sg9UFtn11bmPM-xFXP8aehOm_HGxnhgT7IPKbrZnE,8688
6
6
  qmenta/client/__init__.py,sha256=Mtqe4zf8n3wuwMXSALENQgp5atQY5VcsyXWs2hjBs28,133
7
7
  qmenta/client/utils.py,sha256=vWUAW0r9yDetdlwNo86sdzKn03FNGvwa7D9UtOA3TEc,2419
8
- qmenta_client-1.1.dev1504.dist-info/METADATA,sha256=iZHBMCXur-LBBJuvAnmSpRVX4Q0vkq7U32VcH4TGy_g,672
9
- qmenta_client-1.1.dev1504.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
10
- qmenta_client-1.1.dev1504.dist-info/RECORD,,
8
+ qmenta_client-1.1.dev1507.dist-info/METADATA,sha256=hGQLiIggQT0NX8tOF8VIklAu9VdsOSldflacTNBIo9g,672
9
+ qmenta_client-1.1.dev1507.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
10
+ qmenta_client-1.1.dev1507.dist-info/RECORD,,