qmenta-client 1.1.dev1507__py3-none-any.whl → 2.0__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 or a
266
- add_to_container_id are given.
265
+ different sessions. It will be ignored when the ssid is given.
267
266
 
268
267
  Returns
269
268
  -------
270
269
  bool
271
270
  True if correctly uploaded, False otherwise.
272
271
  """
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,8 +280,6 @@ 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
-
285
283
  # making chunks of the file and sending one by one
286
284
  logger = logging.getLogger(logger_name)
287
285
  with open(file_path, "rb") as file_object:
@@ -298,6 +296,10 @@ class Project:
298
296
  response = None
299
297
  last_chunk = False
300
298
 
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
+
301
303
  while True:
302
304
  data = file_object.read(chunk_size)
303
305
  if not data:
@@ -372,7 +374,8 @@ class Project:
372
374
  logger.error(error)
373
375
  return False
374
376
 
375
- message = "Your data was successfully uploaded. The uploaded file will be soon processed !"
377
+ message = "Your data was successfully uploaded."
378
+ message += "The uploaded file will be soon processed !"
376
379
  logger.info(message)
377
380
  return True
378
381
 
@@ -474,7 +477,7 @@ class Project:
474
477
  self._account.auth, "file_manager/download_file", data=params, stream=True
475
478
  ) as response, open(local_filename, "wb") as f:
476
479
 
477
- for chunk in response.iter_content(chunk_size=2**9 * 1024):
480
+ for chunk in response.iter_content(chunk_size=2 ** 9 * 1024):
478
481
  f.write(chunk)
479
482
  f.flush()
480
483
 
@@ -520,7 +523,7 @@ class Project:
520
523
  self._account.auth, "file_manager/download_file", data=params, stream=True
521
524
  ) as response, open(zip_name, "wb") as f:
522
525
 
523
- for chunk in response.iter_content(chunk_size=2**9 * 1024):
526
+ for chunk in response.iter_content(chunk_size=2 ** 9 * 1024):
524
527
  f.write(chunk)
525
528
  f.flush()
526
529
 
@@ -2042,7 +2045,7 @@ class Project:
2042
2045
  """
2043
2046
  Parse QC (Quality Control) text output into a structured dictionary format.
2044
2047
 
2045
- This function takes raw QC text output (from the Protocol Adherence analysis)
2048
+ This function takes raw QC text output (typically from medical imaging quality checks)
2046
2049
  and parses it into a structured format that separates passed and failed rules,
2047
2050
  along with their associated files and conditions.
2048
2051
 
@@ -2101,15 +2104,18 @@ class Project:
2101
2104
 
2102
2105
  _, text = self.get_qc_status_subject(patient_id=patient_id, subject_name=subject_name, ssid=ssid)
2103
2106
 
2104
- result = {"passed": [], "failed": []}
2107
+ result = {
2108
+ "passed": [],
2109
+ "failed": []
2110
+ }
2105
2111
 
2106
2112
  # Split into failed and passed sections
2107
- sections = re.split(r"={10,}\n\n", text)
2113
+ sections = re.split(r'={10,}\n\n', text)
2108
2114
  if len(sections) == 3:
2109
- failed_section = sections[1].split("=" * 10)[0].strip()
2115
+ failed_section = sections[1].split('=' * 10)[0].strip()
2110
2116
  passed_section = sections[2].strip()
2111
2117
  else:
2112
- section = sections[1].split("=" * 10)[0].strip()
2118
+ section = sections[1].split('=' * 10)[0].strip()
2113
2119
  if "PASSED QC MESSAGES" in section:
2114
2120
  passed_section = section
2115
2121
  failed_section = ""
@@ -2117,13 +2123,85 @@ class Project:
2117
2123
  failed_section = section
2118
2124
  passed_section = ""
2119
2125
 
2120
- # Parse failed rules
2121
- failed_rules = re.split(r"\n ❌ ", failed_section)
2122
- result = self.__parse_fail_rules(failed_rules, result)
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)
2123
2175
 
2124
2176
  # Parse passed rules
2125
- passed_rules = re.split(r"\n ✅ ", passed_section)
2126
- result = self.__parse_pass_rules(passed_rules, result)
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)
2127
2205
 
2128
2206
  return result
2129
2207
 
@@ -2194,14 +2272,19 @@ class Project:
2194
2272
 
2195
2273
  # Initialize statistics
2196
2274
  stats = {
2197
- "passed_rules": 0,
2198
- "failed_rules": 0,
2275
+ 'passed_rules': 0,
2276
+ 'failed_rules': 0,
2199
2277
  "subjects_passed": 0,
2200
2278
  "subjects_with_failed": 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}),
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}),
2205
2288
  }
2206
2289
 
2207
2290
  total_failures = 0
@@ -2211,56 +2294,56 @@ class Project:
2211
2294
  # sum subjects with some failed qc message
2212
2295
  stats["subjects_with_failed"] = sum([1 for rules in qc_results_list if rules["failed"]])
2213
2296
  # sum rules that have passed
2214
- stats["passed_rules"] = sum([len(rules["passed"]) for rules in qc_results_list if rules["failed"]])
2297
+ stats["passed_rules"] = sum([len(rules['passed']) for rules in qc_results_list if rules["failed"]])
2215
2298
  # sum rules that have failed
2216
- stats["failed_rules"] = sum([len(rules["failed"]) for rules in qc_results_list if rules["failed"]])
2299
+ stats["failed_rules"] = sum([len(rules['failed']) for rules in qc_results_list if rules["failed"]])
2217
2300
 
2218
2301
  for qc_results in qc_results_list:
2219
2302
 
2220
2303
  # Count passed files distribution
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():
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():
2233
2316
  # Extract just the condition text without actual value
2234
- clean_condition = re.sub(r"\.\s*Actual value:.*$", "", condition)
2235
- stats["condition_failure_rates"][clean_condition]["count"] += count
2317
+ clean_condition = re.sub(r'\.\s*Actual value:.*$', '', condition)
2318
+ stats['condition_failure_rates'][clean_condition]['count'] += count
2236
2319
  total_failures += count
2237
- rule_name = rule["rule"]
2238
- stats["rule_success_rates"][rule_name]["failed"] += 1
2320
+ rule_name = rule['rule']
2321
+ stats['rule_success_rates'][rule_name]['failed'] += 1
2239
2322
 
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
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
2243
2326
  )
2244
2327
 
2245
2328
  # Calculate condition failure percentages
2246
- for condition in stats["condition_failure_rates"]:
2329
+ for condition in stats['condition_failure_rates']:
2247
2330
  if total_failures > 0:
2248
- stats["condition_failure_rates"][condition]["percentage"] = round(
2249
- (stats["condition_failure_rates"][condition]["count"] / total_failures) * 100, 2
2331
+ stats['condition_failure_rates'][condition]['percentage'] = round(
2332
+ (stats['condition_failure_rates'][condition]['count'] / total_failures) * 100, 2
2250
2333
  )
2251
2334
 
2252
2335
  # Calculate rule success rates
2253
- for rule in stats["rule_success_rates"]:
2254
- total = stats["rule_success_rates"][rule]["passed"] + stats["rule_success_rates"][rule]["failed"]
2336
+ for rule in stats['rule_success_rates']:
2337
+ total = stats['rule_success_rates'][rule]['passed'] + stats['rule_success_rates'][rule]['failed']
2255
2338
  if total > 0:
2256
- stats["rule_success_rates"][rule]["success_rate"] = round(
2257
- (stats["rule_success_rates"][rule]["passed"] / total) * 100, 2
2339
+ stats['rule_success_rates'][rule]['success_rate'] = round(
2340
+ (stats['rule_success_rates'][rule]['passed'] / total) * 100, 2
2258
2341
  )
2259
2342
 
2260
2343
  # Convert defaultdict to regular dict for cleaner JSON output
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"])
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'])
2264
2347
 
2265
2348
  return stats
2266
2349
 
@@ -2590,107 +2673,3 @@ class Project:
2590
2673
  value.replace(d_type + ";", "")
2591
2674
  file_metadata[d_tag] = {"operation": "in-list", "value": value.replace(d_type + ";", "").split(";")}
2592
2675
  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.dev1507
3
+ Version: 2.0
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=pV9mW90BzPIMOFauPiAONBIsYJfGsbi_Xabbe9DW32U,100493
4
+ qmenta/client/Project.py,sha256=vx1GabCiVG7gYqnkNmqhqRyzy7ml7eT4jjnUciBq_Gw,99565
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.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,,
8
+ qmenta_client-2.0.dist-info/METADATA,sha256=niv_y8-oKl7puOvmGkjnci_11YuMgUN7Qv2iO8umNOI,664
9
+ qmenta_client-2.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
10
+ qmenta_client-2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any