qmenta-client 1.1.dev1326__py3-none-any.whl → 1.1.dev1349__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
@@ -200,14 +200,45 @@ def wrap_search_criteria(search_criteria={}):
200
200
  return modality, tags, file_metadata
201
201
 
202
202
 
203
+ def convert_qc_value_to_qcstatus(value):
204
+ """
205
+ Convert input string to QCStatus class.
206
+
207
+ Parameters
208
+ ----------
209
+ value : str
210
+ Value to convert.
211
+
212
+ Returns
213
+ -------
214
+ QCStatus or Bool
215
+ QCStatus.PASS, QCStatus.FAIL or QCStatus.UNDETERMINED
216
+ False if the Value cannot be convered.
217
+ """
218
+ logger = logging.getLogger(logger_name)
219
+ if value == "pass":
220
+ return QCStatus.PASS
221
+ elif value == "fail":
222
+ return QCStatus.FAIL
223
+ elif value == "":
224
+ return QCStatus.UNDERTERMINED
225
+ else:
226
+ logger.error(f"The input value '{value}' cannot be converted "
227
+ f"to class QCStatus.")
228
+ return False
229
+
230
+
203
231
  class QCStatus(Enum):
204
232
  """
205
233
  Enum with the following options:
206
- FAIL, PASS
234
+ FAIL, PASS, UNDERTERMINED
235
+ that are the possible options for the QA/QC status of both
236
+ analysis and session.
207
237
  """
208
238
 
209
239
  PASS = "pass"
210
240
  FAIL = "fail"
241
+ UNDERTERMINED = ""
211
242
 
212
243
 
213
244
  class Project:
@@ -644,10 +675,27 @@ class Project:
644
675
  return data["fields"]
645
676
 
646
677
  def get_analysis(self, analysis_name_or_id):
678
+ """
679
+ Returns the analysis corresponding with the analysis id or analysis name
680
+
681
+ Parameters
682
+ ----------
683
+ analysis_name_or_id : int, str
684
+ analysis_id or analysis name to search for
685
+
686
+ Returns
687
+ -------
688
+ analysis: dict
689
+ A dictionary containing the analysis information
690
+ """
647
691
  if isinstance(analysis_name_or_id, int):
648
692
  search_tag = "id"
649
693
  elif isinstance(analysis_name_or_id, str):
650
- search_tag = "p_n"
694
+ if analysis_name_or_id.isdigit():
695
+ search_tag = "id"
696
+ analysis_name_or_id = int(analysis_name_or_id)
697
+ else:
698
+ search_tag = "p_n"
651
699
  else:
652
700
  raise Exception("The analysis identifier must be its name or an " "integer")
653
701
 
@@ -667,32 +715,37 @@ class Project:
667
715
 
668
716
  def list_analysis(self, search_condition={}, items=(0, 9999)):
669
717
  """
670
- List the analysis available to the user.
671
- search condition example:
672
- search_condition = {
673
- "secret_name":"014_S_6920",
674
- "from_d": "06.02.2025",
675
- "with_child_analysis": 1,
676
- "state": "completed"
718
+ List the analysis available to the user.\n
719
+
720
+ Examples
721
+ --------
722
+
723
+ >>> search_condition = {
724
+ "secret_name":"014_S_6920",
725
+ "from_d": "06.02.2025",
726
+ "with_child_analysis": 1,
727
+ "state": "completed"
677
728
  }
729
+ list_analysis(search_condition=search_condition)
678
730
 
679
731
  Parameters
680
732
  ----------
681
733
  search_condition : dict
682
- p_n: str # analysis_name
683
- type: str # analysis_type
684
- from_d: str # dd.mm.yyyy
685
- to_d: str # dd.mm.yyyy
686
- qa_status: str # pass/fail/nd
687
- secret_name: str # Subject_ID
688
- tags: str #
689
- with_child_analysis: 1
690
- # if 1, child analysis of workflows will appear
691
- id: str # container_id
692
- state: # running, completed, pending, exception
693
- username: str #
694
- limit : int
695
- Max number of results
734
+ - p_n: str Analysis name
735
+ - type: str Type
736
+ - from_d: str dd.mm.yyyy Date from
737
+ - to_d: str dd.mm.yyyy Date to
738
+ - qa_status: str pass/fail/nd QC status
739
+ - secret_name: str Subject ID
740
+ - tags: str
741
+ - with_child_analysis: 1 if 1, child analysis of workflows will appear
742
+ - id: str ID
743
+ - state: running, completed, pending, exception
744
+ - username: str
745
+
746
+ items : List[int]
747
+ list containing two elements [min, max] that correspond to the
748
+ mininum and maximum range of analysis listed
696
749
 
697
750
  Returns
698
751
  -------
@@ -811,23 +864,48 @@ class Project:
811
864
  ]
812
865
  return containers
813
866
 
814
- def list_result_containers(self, limit=1000):
867
+ def list_result_containers(self, search_condition={}, items=(0, 9999)):
815
868
  """
816
869
  List the result containers available to the user.
870
+ Examples
871
+ --------
872
+
873
+ >>> search_condition = {
874
+ "secret_name":"014_S_6920",
875
+ "from_d": "06.02.2025",
876
+ "with_child_analysis": 1,
877
+ "state": "completed"
878
+ }
879
+ list_result_containers(search_condition=search_condition)
817
880
 
818
881
  Parameters
819
882
  ----------
820
- limit : int
821
- Max number of results
883
+ search_condition : dict
884
+ - p_n: str Analysis name
885
+ - type: str Type
886
+ - from_d: str dd.mm.yyyy Date from
887
+ - to_d: str dd.mm.yyyy Date to
888
+ - qa_status: str pass/fail/nd QC status
889
+ - secret_name: str Subject ID
890
+ - tags: str
891
+ - with_child_analysis: 1 if 1, child analysis of workflows will appear
892
+ - id: str ID
893
+ - state: running, completed, pending, exception
894
+ - username: str
895
+ items : List[int]
896
+ list containing two elements [min, max] that correspond to the
897
+ mininum and maximum range of analysis listed
822
898
 
823
899
  Returns
824
900
  -------
825
901
  dict
826
902
  List of containers, each a dictionary
827
903
  {"name": "container-name", "id": "container_id"}
904
+ if "id": None, that analysis did not had an output container,
905
+ probably it is a workflow
828
906
  """
829
- analysis = self.list_analysis(limit)
830
- return [{"name": a["name"], "id": a["out_container_id"]} for a in analysis]
907
+ analyses = self.list_analysis(search_condition, items)
908
+ return [{"name": analysis["name"], "id": (analysis.get("out_container_id") or None)} for analysis in analyses]
831
909
 
832
910
  def list_container_files(
833
911
  self,
@@ -1841,7 +1919,8 @@ class Project:
1841
1919
  modalities.append(modality)
1842
1920
  return modalities
1843
1921
 
1844
- def set_qc_status(self, analysis_id, status=QCStatus.PASS, comments=""):
1922
+ def set_qc_status_analysis(self, analysis_id,
1923
+ status=QCStatus.UNDERTERMINED, comments=""):
1845
1924
  """
1846
1925
  Changes the analysis QC status.
1847
1926
 
@@ -1850,85 +1929,257 @@ class Project:
1850
1929
  analysis_id : int
1851
1930
  Analysis ID number
1852
1931
  status : QCStatus
1853
- QCStatus.PASS or QCStatus.FAIL
1932
+ QCStatus.PASS, QCStatus.FAIL or QCStatus.UNDETERMINED
1854
1933
  comments : str, optional
1855
- Additional comments explaining why the QC status has been
1856
- set to pass or fail.
1934
+ Additional comments explaining why the reasoning of setting the
1935
+ QC status to such state.
1936
+
1937
+ Returns
1938
+ -------
1939
+ bool
1940
+ True if the QC status was correctly changed, False otherwise.
1857
1941
  """
1942
+
1858
1943
  logger = logging.getLogger(__name__)
1859
1944
  logger.info(f"Setting QC status to {status}: {comments}")
1860
1945
 
1861
- platform.parse_response(
1862
- platform.post(
1863
- auth=self._account.auth,
1864
- endpoint="projectset_manager/set_qa_status",
1865
- data={"item_ids": analysis_id, "status": status.value, "comments": comments, "entity": "analysis"},
1946
+ if not isinstance(status, QCStatus):
1947
+ raise ValueError("Status should be type 'QCStatus'.")
1948
+
1949
+ try:
1950
+ analysis_id = str(int(analysis_id))
1951
+ except ValueError:
1952
+ raise ValueError(f"analysis_id: '{analysis_id}' not valid. " f"Must be convertible to int.")
1953
+
1954
+ try:
1955
+ platform.parse_response(
1956
+ platform.post(
1957
+ auth=self._account.auth,
1958
+ endpoint="projectset_manager/set_qa_status",
1959
+ data={"item_ids": analysis_id, "status": status.value,
1960
+ "comments": str(comments), "entity": "analysis"},
1961
+ )
1866
1962
  )
1867
- )
1963
+ except Exception:
1964
+ logger.error(f"It was not possible to change the QC status of"
1965
+ f"Analysis ID: {analysis_id}")
1966
+ return False
1967
+ return True
1968
+
1969
+ def set_qc_status_subject(self, patient_id,
1970
+ status=QCStatus.UNDERTERMINED, comments=""):
1971
+ """
1972
+ Changes the QC status of a Patient ID (equivalent to a
1973
+ Subject ID/Session ID).
1974
+
1975
+ Parameters
1976
+ ----------
1977
+ patient_id : int
1978
+ Patient ID number of the session.
1979
+ status : QCStatus
1980
+ QCStatus.PASS, QCStatus.FAIL or QCStatus.UNDETERMINED
1981
+ comments : str, optional
1982
+ Additional comments explaining why the reasoning of setting the
1983
+ QC status to such state.
1984
+
1985
+ Returns
1986
+ -------
1987
+ bool
1988
+ True if the QC status was correctly changed, False otherwise.
1989
+ """
1990
+ logger = logging.getLogger(__name__)
1991
+ logger.info(f"Setting QC status to {status}: {comments}")
1868
1992
 
1869
- def get_qc_status(self, patient_secret_name=None, ssid=None, analysis_id=None):
1993
+ if not isinstance(status, QCStatus):
1994
+ raise ValueError("Status should be type 'QCStatus'.")
1995
+
1996
+ try:
1997
+ patient_id = str(int(patient_id))
1998
+ except ValueError:
1999
+ raise ValueError(f"'patient_id': '{patient_id}' not valid. " f"Must be convertible to int.")
2000
+
2001
+ try:
2002
+ platform.parse_response(
2003
+ platform.post(
2004
+ auth=self._account.auth,
2005
+ endpoint="projectset_manager/set_qa_status",
2006
+ data={"item_ids": patient_id, "status": status.value,
2007
+ "comments": str(comments), "entity": "patients"},
2008
+ )
2009
+ )
2010
+ except Exception:
2011
+ logger.error(f"It was not possible to change the QC status of"
2012
+ f"Patient ID: {patient_id}")
2013
+ return False
2014
+ return True
2015
+
2016
+ def get_qc_status_analysis(self, analysis_id):
1870
2017
  """
1871
- Gets the session QC status of a session. If the analysis_id is
1872
- specified, it returns the QC of the
1873
- analysis instead.
2018
+ Gets the QC status of an anlaysis provided its Analysis ID.
1874
2019
 
2020
+ Parameters
2021
+ ----------
2022
+ analysis_id : int
2023
+ Analysis ID number
2024
+
2025
+ Returns
2026
+ -------
2027
+ status : QCStatus or False
2028
+ QCStatus.PASS, QCStatus.FAIL or QCStatus.UNDETERMINED
2029
+ False if the status cannot be extracted.
2030
+ comments : str or False
2031
+ Comments defined in the QC Status.
2032
+ False if the comments cannot be extracted.
1875
2033
  """
1876
- if patient_secret_name and ssid:
2034
+ try:
2035
+ search_criteria = {"id": analysis_id}
2036
+ to_return = self.list_analysis(search_criteria)
2037
+ return convert_qc_value_to_qcstatus(to_return[0]["qa_status"]), to_return[0]["qa_comments"]
2038
+ except IndexError:
2039
+ # Handle the case where no matching analysis is found
2040
+ logging.error(f"No analysis was found with such Analysis ID: '{analysis_id}'.")
2041
+ return False, False
2042
+ except Exception:
2043
+ # Handle other potential exceptions
2044
+ logging.error(f"It was not possible to extract the QC status"
2045
+ f"from Analysis ID: {analysis_id}")
2046
+ return False, False
2047
+
2048
+ def get_qc_status_subject(self, patient_id=None, subject_name=None,
2049
+ ssid=None):
2050
+ """
2051
+ Gets the session QC status via the patient ID or the Subject ID
2052
+ and the Session ID.
2053
+
2054
+ Parameters
2055
+ ----------
2056
+ patient_id : int
2057
+ Patient ID number of the session
2058
+ subject_name : string
2059
+ Subject ID of the session
2060
+ ssid : string
2061
+ Session ID
2062
+
2063
+ Returns
2064
+ -------
2065
+ status : QCStatus or False
2066
+ QCStatus.PASS, QCStatus.FAIL or QCStatus.UNDETERMINED
2067
+ False if the status cannot be extracted.
2068
+ comments : str or False
2069
+ Comments defined in the QC Status.
2070
+ False if the comments cannot be extracted.
2071
+ """
2072
+
2073
+ if patient_id:
2074
+ try:
2075
+ patient_id = int(patient_id)
2076
+ except ValueError:
2077
+ raise ValueError(f"patient_id '{patient_id}' should be an "
2078
+ f"integer.")
2079
+ sessions = self.get_subjects_metadata(search_criteria={})
2080
+ session = [session for session in sessions if int(session["_id"]) == patient_id]
2081
+ if len(session) < 1:
2082
+ logging.error(f"No session was found with Patient ID: "
2083
+ f"'{patient_id}'.")
2084
+ return False, False
2085
+ return convert_qc_value_to_qcstatus(session[0]["qa_status"]), session[0]["qa_comments"]
2086
+ elif subject_name and ssid:
1877
2087
  session = self.get_subjects_metadata(
1878
2088
  search_criteria={
1879
- "pars_patient_secret_name": f"string;" f"{patient_secret_name}",
2089
+ "pars_patient_secret_name": f"string;{subject_name}",
1880
2090
  "pars_ssid": f"integer;eq|{ssid}",
1881
2091
  }
1882
2092
  )
1883
- to_return = session["qa_status"], session["qa_comments"]
1884
- elif analysis_id:
1885
- try:
1886
- to_return = [
1887
- analysis["qa_data"] for analysis in self.list_analysis() if analysis["_id"] == analysis_id
1888
- ][0]
1889
- to_return = to_return["qa_status"], to_return["qa_comments"]
1890
- except IndexError:
1891
- # Handle the case where no matching analysis is found
1892
- to_return = None
1893
- except Exception as e:
1894
- # Handle other potential exceptions
1895
- print(f"An error occurred: {e}")
1896
- to_return = None
2093
+ if len(session) < 1:
2094
+ logging.error(f"No session was found with Subject ID: "
2095
+ f"'{subject_name}' and Session ID: '{ssid}'.")
2096
+ return False, False
2097
+ return convert_qc_value_to_qcstatus(session[0]["qa_status"]), session[0]["qa_comments"]
1897
2098
  else:
1898
- raise Exception(f"Must specify {patient_secret_name} and {ssid} or {analysis_id}.")
1899
- return to_return
2099
+ raise ValueError("Either 'patient_id' or 'subject_name' and 'ssid'"
2100
+ " must not be empty.")
1900
2101
 
1901
- def set_project_qa_rules(self, rules_file_path, guidance_text=""):
2102
+ def set_project_pa_rules(self, rules_file_path, guidance_text=""):
1902
2103
  """
1903
- Logs in to the Qmenta platform, retrieves the project ID based on the project name,
1904
- and updates the project's QA rules using the provided rules file.
2104
+ Updates the active project's protocol adherence rules using the
2105
+ provided rules file.
1905
2106
 
1906
- Args:
1907
- rules_file_path (str): The file path to the JSON file containing the QA rules.
1908
- guidance_text (str): Description of the rules. Only visible for Platform admins.
2107
+ Parameters
2108
+ ----------
2109
+ rules_file_path : str
2110
+ The file path to the JSON file containing the protocol
2111
+ adherence rules.
2112
+ guidance_text : str
2113
+ Description of the protocol adherence rules.
1909
2114
 
1910
- Returns:
1911
- bool: True if the rules were set successfully, False otherwise.
2115
+ Returns
2116
+ -------
2117
+ bool: True if the protocol adherence rules were set successfully,
2118
+ False otherwise.
1912
2119
  """
2120
+ logger = logging.getLogger(logger_name)
1913
2121
  # Read the rules from the JSON file
1914
2122
  try:
1915
2123
  with open(rules_file_path, "r") as fr:
1916
2124
  rules = json.load(fr)
1917
2125
  except FileNotFoundError:
1918
- print(f"ERROR: Rules file '{rules_file_path}' not found.")
2126
+ logger.error(f"Pprotocol adherence rule file '{rules_file_path}' "
2127
+ f"not found.")
1919
2128
  return False
1920
2129
 
1921
2130
  # Update the project's QA rules
1922
- res = platform.post(
2131
+ res = platform.parse_response(platform.post(
1923
2132
  auth=self._account.auth,
1924
2133
  endpoint="projectset_manager/set_session_qa_requirements",
1925
2134
  data={"project_id": self._project_id, "rules": json.dumps(rules), "guidance_text": guidance_text},
1926
- )
2135
+ ))
1927
2136
 
1928
- if res.json().get("success") == 1:
1929
- print("Rules set up successfully!")
1930
- return True
1931
- else:
1932
- print("ERROR setting the rules")
1933
- print(res.json())
2137
+ if not res.get("success") == 1:
2138
+ logger.error("There was an error setting up the protocol "
2139
+ "adherence rules.")
2140
+ logger.error(platform.parse_response(res))
2141
+ return False
2142
+
2143
+ return True
2144
+
2145
+ def get_project_pa_rules(self, rules_file_path):
2146
+ """
2147
+ Retrive the active project's protocol adherence rules
2148
+
2149
+ Parameters
2150
+ ----------
2151
+ rules_file_path : str
2152
+ The file path to the JSON file to store the protocol adherence
2153
+ rules.
2154
+
2155
+ Returns
2156
+ -------
2157
+ guidance_text : str
2158
+ Description of the protocol adherence rules,
2159
+ False if extraction of the protocol adhernece rules is not
2160
+ possible.
2161
+ """
2162
+ logger = logging.getLogger(logger_name)
2163
+
2164
+ # Update the project's QA rules
2165
+ res = platform.parse_response(platform.post(
2166
+ auth=self._account.auth,
2167
+ endpoint="projectset_manager/get_session_qa_requirements",
2168
+ data={"project_id": self._project_id},
2169
+ ))
2170
+
2171
+ if "rules" not in res:
2172
+ logger.error(f"There was an error extracting the protocol "
2173
+ f"adherence rules from {self._project_name}.")
2174
+ logger.error(platform.parse_response(res))
1934
2175
  return False
2176
+
2177
+ try:
2178
+ with open(rules_file_path, "w") as fr:
2179
+ json.dump(res["rules"], fr, indent=4)
2180
+ except FileNotFoundError:
2181
+ logger.error(f"Protocol adherence rules could not be exported"
2182
+ f"to file: '{rules_file_path}'.")
2183
+ return False
2184
+
2185
+ return res["guidance_text"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qmenta-client
3
- Version: 1.1.dev1326
3
+ Version: 1.1.dev1349
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=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
2
2
  qmenta/client/Account.py,sha256=MEljEy9cmg2uP2FG1d3TZAgfj66EE2_3PQAZ9rvpCXY,9647
3
3
  qmenta/client/File.py,sha256=ZgvSqejIosUt4uoX7opUnPnp5XGEaJNMRwFC0mQVB8k,5344
4
- qmenta/client/Project.py,sha256=T_IoH_CpyFg0IaaXeQW_x1xH76n8FsX_2v0qWJo7Yuk,68063
4
+ qmenta/client/Project.py,sha256=xk-C4kf2i7RLsIp3fiwHQzlEOZpb1sL-KqxGqDex8Mc,76953
5
5
  qmenta/client/Subject.py,sha256=lhxxVdQ6d-GNoQC8mrJwa4L1f44nJc4PcJtDspmKN7I,8756
6
6
  qmenta/client/__init__.py,sha256=AjTojBhZeW5nl0i605KS8S1Gl5tPNc1hdzD47BGNfoI,147
7
7
  qmenta/client/utils.py,sha256=5DK2T_HQprrCwLS0Ycm2CjseaYmAUKaJkJvYoW-Rqzc,2479
8
- qmenta_client-1.1.dev1326.dist-info/METADATA,sha256=h6qd-k47PLto2IDPKURSwiuRhRyfFA3bZ8a0-IRbGrE,672
9
- qmenta_client-1.1.dev1326.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
10
- qmenta_client-1.1.dev1326.dist-info/RECORD,,
8
+ qmenta_client-1.1.dev1349.dist-info/METADATA,sha256=RBQVZKHts7FiBDWvv9C5Nf6lCj8I5YH6N_shy1JRBsc,672
9
+ qmenta_client-1.1.dev1349.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
10
+ qmenta_client-1.1.dev1349.dist-info/RECORD,,