qmenta-client 2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qmenta_client-2.1/PKG-INFO +18 -0
- qmenta_client-2.1/pyproject.toml +39 -0
- qmenta_client-2.1/src/qmenta/__init__.py +1 -0
- qmenta_client-2.1/src/qmenta/client/Account.py +326 -0
- qmenta_client-2.1/src/qmenta/client/File.py +151 -0
- qmenta_client-2.1/src/qmenta/client/Project.py +3163 -0
- qmenta_client-2.1/src/qmenta/client/Subject.py +300 -0
- qmenta_client-2.1/src/qmenta/client/__init__.py +6 -0
- qmenta_client-2.1/src/qmenta/client/utils.py +80 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: qmenta-client
|
|
3
|
+
Version: 2.1
|
|
4
|
+
Summary: Python client lib to interact with the QMENTA platform.
|
|
5
|
+
Author: QMENTA
|
|
6
|
+
Author-email: dev@qmenta.com
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: future (>=0.18.2,<0.19.0)
|
|
16
|
+
Requires-Dist: pytest-cov (>=6.1.1,<7.0.0)
|
|
17
|
+
Requires-Dist: qmenta-core (==4.1)
|
|
18
|
+
Project-URL: Homepage, https://www.qmenta.com/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "qmenta-client"
|
|
3
|
+
version = "2.1"
|
|
4
|
+
description = "Python client lib to interact with the QMENTA platform."
|
|
5
|
+
authors = ["QMENTA <dev@qmenta.com>"]
|
|
6
|
+
homepage = "https://www.qmenta.com/"
|
|
7
|
+
classifiers=[
|
|
8
|
+
"Development Status :: 4 - Beta",
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
]
|
|
11
|
+
packages = [
|
|
12
|
+
{ include = "qmenta", from = "src" }
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.poetry.dependencies]
|
|
16
|
+
python = "^3.10"
|
|
17
|
+
future = "^0.18.2"
|
|
18
|
+
qmenta-core = "4.1"
|
|
19
|
+
pytest-cov = "^6.1.1"
|
|
20
|
+
|
|
21
|
+
[tool.poetry.group.dev.dependencies]
|
|
22
|
+
pytest = "^7.1.2"
|
|
23
|
+
sphinx = "^5.0"
|
|
24
|
+
sphinx-rtd-theme = "^1.0.0"
|
|
25
|
+
dom-toml = "^0.6.0"
|
|
26
|
+
scriv = {version = "^0.15.2", extras = ["toml"]}
|
|
27
|
+
coverage = "^7.2.5"
|
|
28
|
+
flake8 = "^7.1.2"
|
|
29
|
+
responses = "^0.25.7"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
34
|
+
build-backend = "poetry.core.masonry.api"
|
|
35
|
+
|
|
36
|
+
[tool.scriv]
|
|
37
|
+
format = "md"
|
|
38
|
+
md_header_level = "2"
|
|
39
|
+
insert_marker = "scriv-insert-here"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from qmenta.core import errors
|
|
5
|
+
from qmenta.core import platform
|
|
6
|
+
from urllib3.exceptions import HTTPError
|
|
7
|
+
|
|
8
|
+
from .Project import Project
|
|
9
|
+
from .utils import load_json
|
|
10
|
+
|
|
11
|
+
logger_name = "qmenta.client"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Account:
|
|
15
|
+
"""
|
|
16
|
+
It represents your QMENTA account and implements the HTTP connection
|
|
17
|
+
with the server. Once it is instantiated it will act as an identifier
|
|
18
|
+
used by the rest of objects.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
username : str
|
|
23
|
+
Username on the platform. To get one go to https://platform.qmenta.com
|
|
24
|
+
password : str
|
|
25
|
+
The password assigned to the username.
|
|
26
|
+
base_url : str
|
|
27
|
+
The base url of the platform.
|
|
28
|
+
verify_certificates : bool
|
|
29
|
+
verify SSL certificates?
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
auth : qmenta.core.platform.Auth
|
|
34
|
+
The Auth object used for communication with the platform
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
username,
|
|
40
|
+
password,
|
|
41
|
+
base_url="https://platform.qmenta.com",
|
|
42
|
+
verify_certificates=True,
|
|
43
|
+
):
|
|
44
|
+
|
|
45
|
+
self._cookie = None
|
|
46
|
+
self.username = username
|
|
47
|
+
self.password = password
|
|
48
|
+
self.baseurl = base_url
|
|
49
|
+
self.verify_certificates = verify_certificates
|
|
50
|
+
self.auth = None
|
|
51
|
+
self.login()
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
rep = "<Account session for {}>".format(self.username)
|
|
55
|
+
return rep
|
|
56
|
+
|
|
57
|
+
def login(self):
|
|
58
|
+
"""
|
|
59
|
+
Login to the platform.
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
qmenta.core.platform.ConnectionError
|
|
64
|
+
When the connection to the platform fails.
|
|
65
|
+
qmenta.core.platform.InvalidLoginError
|
|
66
|
+
When invalid credentials are provided.
|
|
67
|
+
"""
|
|
68
|
+
logger = logging.getLogger(logger_name)
|
|
69
|
+
try:
|
|
70
|
+
auth = platform.Auth.login(
|
|
71
|
+
self.username,
|
|
72
|
+
self.password,
|
|
73
|
+
base_url=self.baseurl,
|
|
74
|
+
ask_for_2fa_input=True,
|
|
75
|
+
)
|
|
76
|
+
except errors.PlatformError as e:
|
|
77
|
+
logger.error("Failed to log in: {}".format(e))
|
|
78
|
+
self.auth = None
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
self.auth = auth
|
|
82
|
+
logger.info("Logged in as {}".format(self.username))
|
|
83
|
+
|
|
84
|
+
def logout(self):
|
|
85
|
+
"""
|
|
86
|
+
Logout from the platform.
|
|
87
|
+
|
|
88
|
+
Raises
|
|
89
|
+
------
|
|
90
|
+
qmenta.core.errors.PlatformError
|
|
91
|
+
When the logout was not successful
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
logger = logging.getLogger(logger_name)
|
|
95
|
+
try:
|
|
96
|
+
platform.parse_response(platform.post(self.auth, "logout"))
|
|
97
|
+
except errors.PlatformError as e:
|
|
98
|
+
logger.error("Logout was unsuccessful: {}".format(e))
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
logger.info("Logged out successfully")
|
|
102
|
+
|
|
103
|
+
def get_project(self, project_id):
|
|
104
|
+
"""
|
|
105
|
+
Retrieve a project instance, given its id, which can be obtained
|
|
106
|
+
checking account.projects.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
project_id : int or str
|
|
111
|
+
ID of the project to retrieve, either the numeric ID or the name
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
Project
|
|
116
|
+
A project object representing the desired project
|
|
117
|
+
"""
|
|
118
|
+
if type(project_id) is int or type(project_id) is float:
|
|
119
|
+
return Project(self, int(project_id))
|
|
120
|
+
elif type(project_id) is str:
|
|
121
|
+
projects = self.projects
|
|
122
|
+
projects_match = [
|
|
123
|
+
proj for proj in projects if proj["name"] == project_id
|
|
124
|
+
]
|
|
125
|
+
if not projects_match:
|
|
126
|
+
raise Exception(
|
|
127
|
+
f"Project {project_id} does not exist or is not available "
|
|
128
|
+
f"for this user."
|
|
129
|
+
)
|
|
130
|
+
return Project(self, int(projects_match[0]["id"]))
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def projects(self):
|
|
135
|
+
"""
|
|
136
|
+
List all the projects available to the current user.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
list[dict]
|
|
141
|
+
List of project information (name, and id)
|
|
142
|
+
"""
|
|
143
|
+
logger = logging.getLogger(logger_name)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
data = platform.parse_response(
|
|
147
|
+
platform.post(
|
|
148
|
+
self.auth, "projectset_manager/get_projectset_list"
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
except errors.PlatformError as e:
|
|
152
|
+
logger.error("Failed to get project list: {}".format(e))
|
|
153
|
+
raise
|
|
154
|
+
|
|
155
|
+
titles = []
|
|
156
|
+
for project in data:
|
|
157
|
+
titles.append({"name": project["name"], "id": project["_id"]})
|
|
158
|
+
return titles
|
|
159
|
+
|
|
160
|
+
def add_project(
|
|
161
|
+
self,
|
|
162
|
+
project_abbreviation,
|
|
163
|
+
project_name,
|
|
164
|
+
description="",
|
|
165
|
+
users=None,
|
|
166
|
+
from_date="",
|
|
167
|
+
to_date="",
|
|
168
|
+
):
|
|
169
|
+
"""
|
|
170
|
+
Add a new project to the user account.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
project_abbreviation : str
|
|
175
|
+
Abbreviation of the project name.
|
|
176
|
+
project_name : str
|
|
177
|
+
Project name.
|
|
178
|
+
description : str
|
|
179
|
+
Description of the project.
|
|
180
|
+
users : list[str]
|
|
181
|
+
List of users to which this project is available.
|
|
182
|
+
from_date : str
|
|
183
|
+
Date of beginning of the project.
|
|
184
|
+
to_date : str
|
|
185
|
+
Date of ending of the project.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
bool
|
|
190
|
+
True if project was correctly added, False otherwise
|
|
191
|
+
"""
|
|
192
|
+
if users is None:
|
|
193
|
+
users = []
|
|
194
|
+
logger = logging.getLogger(logger_name)
|
|
195
|
+
for project in self.projects:
|
|
196
|
+
if project["name"] == project_name:
|
|
197
|
+
logger.error("Project name or abbreviation already exists.")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
platform.parse_response(
|
|
202
|
+
platform.post(
|
|
203
|
+
self.auth,
|
|
204
|
+
"projectset_manager/upsert_project",
|
|
205
|
+
data={
|
|
206
|
+
"name": project_name,
|
|
207
|
+
"description": description,
|
|
208
|
+
"from_date": from_date,
|
|
209
|
+
"to_date": to_date,
|
|
210
|
+
"abbr": project_abbreviation,
|
|
211
|
+
"users": "|".join(users),
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
except errors.PlatformError as e:
|
|
216
|
+
logger.error(e)
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
for project in self.projects:
|
|
220
|
+
if project["name"] == project_name:
|
|
221
|
+
logger.info("Project was successfully created.")
|
|
222
|
+
return Project(self, int(project["id"]))
|
|
223
|
+
logger.error("Project could note be created.")
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
def _send_request(
|
|
227
|
+
self,
|
|
228
|
+
path,
|
|
229
|
+
req_parameters=None,
|
|
230
|
+
req_headers=None,
|
|
231
|
+
stream=False,
|
|
232
|
+
return_raw_response=False,
|
|
233
|
+
response_timeout=900.0,
|
|
234
|
+
):
|
|
235
|
+
"""
|
|
236
|
+
TODO: not used and contains warnings, maybe we should delete it.
|
|
237
|
+
|
|
238
|
+
Send a request to the QMENTA Platform.
|
|
239
|
+
|
|
240
|
+
Interaction with the server is performed as POST requests.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
req_parameters : dict
|
|
245
|
+
Data to send in the POST request.
|
|
246
|
+
req_headers : dict
|
|
247
|
+
Extra headers to include in the request:
|
|
248
|
+
stream : bool
|
|
249
|
+
Defer downloading the response body until accessing the
|
|
250
|
+
response.content attribute.
|
|
251
|
+
return_raw_response : bool
|
|
252
|
+
When True, return the response from the
|
|
253
|
+
server as-is. When False (by default),
|
|
254
|
+
parse the answer as json to return a
|
|
255
|
+
dictionary.
|
|
256
|
+
response_timeout : float
|
|
257
|
+
The timeout time in seconds to wait for the response.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
req_headers = req_headers or {}
|
|
261
|
+
req_url = "/".join((self.baseurl, path))
|
|
262
|
+
if self._cookie is not None:
|
|
263
|
+
req_headers["Cookie"] = self._cookie
|
|
264
|
+
req_headers["Mint-Api-Call"] = "1"
|
|
265
|
+
|
|
266
|
+
logger = logging.getLogger(logger_name)
|
|
267
|
+
try:
|
|
268
|
+
if path == "upload":
|
|
269
|
+
response = self.pool.request(
|
|
270
|
+
"POST",
|
|
271
|
+
req_url,
|
|
272
|
+
body=req_parameters,
|
|
273
|
+
headers=req_headers,
|
|
274
|
+
timeout=response_timeout,
|
|
275
|
+
preload_content=not stream,
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
response = self.pool.request(
|
|
279
|
+
"POST",
|
|
280
|
+
req_url,
|
|
281
|
+
req_parameters or {},
|
|
282
|
+
headers=req_headers,
|
|
283
|
+
timeout=response_timeout,
|
|
284
|
+
preload_content=not stream,
|
|
285
|
+
)
|
|
286
|
+
if response.status >= 400:
|
|
287
|
+
raise HTTPError(
|
|
288
|
+
"STATUS {}: {}".format(response.status, response.reason)
|
|
289
|
+
)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
error = "Could not send request. ERROR: {0}".format(e)
|
|
292
|
+
logger.error(error)
|
|
293
|
+
raise
|
|
294
|
+
|
|
295
|
+
# Set the login cookie in our object
|
|
296
|
+
if "set-cookie" in response.headers:
|
|
297
|
+
self._cookie = response.headers["set-cookie"]
|
|
298
|
+
|
|
299
|
+
if return_raw_response:
|
|
300
|
+
return response
|
|
301
|
+
|
|
302
|
+
# raise exception if there is no response from server
|
|
303
|
+
if not response:
|
|
304
|
+
error = "No response from server."
|
|
305
|
+
logger.error(error)
|
|
306
|
+
raise Exception(error)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
parsed_content = load_json(response.data)
|
|
310
|
+
except Exception:
|
|
311
|
+
error = "Could not parse the response as JSON data: {}".format(
|
|
312
|
+
response.data
|
|
313
|
+
)
|
|
314
|
+
logger.error(error)
|
|
315
|
+
raise
|
|
316
|
+
|
|
317
|
+
# throw exceptions if anything strange happened
|
|
318
|
+
if "error" in parsed_content:
|
|
319
|
+
error = parsed_content["error"] or "Unknown error"
|
|
320
|
+
logger.error(error)
|
|
321
|
+
raise Exception(error)
|
|
322
|
+
elif "success" in parsed_content and parsed_content["success"] == 3:
|
|
323
|
+
error = parsed_content["message"]
|
|
324
|
+
logger.error(error)
|
|
325
|
+
raise Exception(error)
|
|
326
|
+
return parsed_content
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from tempfile import TemporaryDirectory, mkstemp
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from pydicom import dcmread
|
|
9
|
+
|
|
10
|
+
import qmenta.client
|
|
11
|
+
from qmenta.client.utils import unzip_dicoms
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class File:
|
|
15
|
+
"""
|
|
16
|
+
It represents files stored in the QMENTA Platform.
|
|
17
|
+
Once it is instantiated it will act as an identifier used by the rest of
|
|
18
|
+
objects.
|
|
19
|
+
|
|
20
|
+
:param project: A QMENTA Project instance
|
|
21
|
+
:type project: qmenta.client.Project
|
|
22
|
+
|
|
23
|
+
:param subject_name: The name of the subject you want to work with
|
|
24
|
+
:type subject_name: str
|
|
25
|
+
|
|
26
|
+
:param ssid: The ssid of the subject you want to work with
|
|
27
|
+
:type ssid: str
|
|
28
|
+
|
|
29
|
+
:param file_name: The file_name of the file you want to work with
|
|
30
|
+
:type file_name: str
|
|
31
|
+
|
|
32
|
+
`ff_container = File(project, "MRB", "1", "gre_field_mapping_3.zip")`
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
project: qmenta.client.Project,
|
|
38
|
+
subject_name: str,
|
|
39
|
+
ssid: str,
|
|
40
|
+
file_name: str,
|
|
41
|
+
):
|
|
42
|
+
self.file_name = file_name
|
|
43
|
+
self.project = project
|
|
44
|
+
self._container_id = None
|
|
45
|
+
for cont in self.project.list_input_containers():
|
|
46
|
+
if (
|
|
47
|
+
cont["patient_secret_name"] == subject_name
|
|
48
|
+
and cont["ssid"] == ssid
|
|
49
|
+
):
|
|
50
|
+
self._container_id = cont["container_id"]
|
|
51
|
+
break
|
|
52
|
+
if self.file_name.endswith(".zip"):
|
|
53
|
+
self._file_type = "DICOM"
|
|
54
|
+
elif self.file_name.endswith(".nii"):
|
|
55
|
+
self._file_type = "NIfTI"
|
|
56
|
+
elif self.file_name.endswith(".nii.gz"):
|
|
57
|
+
self._file_type = "NIfTI-GZ"
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return self.file_name
|
|
61
|
+
|
|
62
|
+
def pull_scan_sequence_info(
|
|
63
|
+
self, specify_dicom_tags=None, output_format: str = "csv"
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Pulling scan sequence information (e.g. series number, series type,
|
|
67
|
+
series description, number of files, file formats available)
|
|
68
|
+
via the API in CSV/TSV or JSON format.
|
|
69
|
+
|
|
70
|
+
Additionally, pulling specified DICOM headers themselves via tha API.
|
|
71
|
+
|
|
72
|
+
It downloads the file defined in the File object and extracts the
|
|
73
|
+
DICOM information. Nifti is not supported at the moment
|
|
74
|
+
|
|
75
|
+
:param specify_dicom_tags: tags to extract from DICOM header
|
|
76
|
+
:type output_format: list
|
|
77
|
+
|
|
78
|
+
:param output_format: A file output type. Supported formats: "csv",
|
|
79
|
+
"tsv", "json", dict
|
|
80
|
+
:type output_format: str
|
|
81
|
+
|
|
82
|
+
:return:
|
|
83
|
+
:rtype dict:
|
|
84
|
+
"""
|
|
85
|
+
if specify_dicom_tags is None:
|
|
86
|
+
specify_dicom_tags = list()
|
|
87
|
+
valid_output_formats = ["csv", "tsv", "json", "dict"]
|
|
88
|
+
assert (
|
|
89
|
+
output_format in valid_output_formats
|
|
90
|
+
), f"Output format not valid. Choose one of {valid_output_formats}"
|
|
91
|
+
|
|
92
|
+
with TemporaryDirectory() as temp_dir:
|
|
93
|
+
local_file_name = mkstemp(dir=temp_dir) + ".zip"
|
|
94
|
+
self.project.download_file(
|
|
95
|
+
self._container_id,
|
|
96
|
+
self.file_name,
|
|
97
|
+
local_file_name,
|
|
98
|
+
overwrite=False,
|
|
99
|
+
)
|
|
100
|
+
if self._file_type == "DICOM":
|
|
101
|
+
unzip_dicoms(local_file_name, temp_dir, exclude_members=None)
|
|
102
|
+
dicoms = glob.glob(os.path.join(temp_dir, "*"))
|
|
103
|
+
output_data = defaultdict(list)
|
|
104
|
+
number_files = 0
|
|
105
|
+
for dcm_file in dicoms:
|
|
106
|
+
try:
|
|
107
|
+
open_file = dcmread(dcm_file)
|
|
108
|
+
number_files += 1
|
|
109
|
+
if number_files == 1:
|
|
110
|
+
# only needed for one file, all have the same data.
|
|
111
|
+
for attribute in specify_dicom_tags:
|
|
112
|
+
# error handling of attributes send by the user
|
|
113
|
+
assert (
|
|
114
|
+
len(attribute) == 2
|
|
115
|
+
), "Invalid length of DICOM Attribute"
|
|
116
|
+
assert isinstance(
|
|
117
|
+
attribute[0], int
|
|
118
|
+
), "Invalid type of DICOM Attribute"
|
|
119
|
+
output_data["dicom tag"].append(
|
|
120
|
+
open_file[attribute].tag
|
|
121
|
+
)
|
|
122
|
+
output_data["attribute"].append(
|
|
123
|
+
open_file[attribute].name
|
|
124
|
+
)
|
|
125
|
+
output_data["value"].append(
|
|
126
|
+
open_file[attribute].value
|
|
127
|
+
)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(str(e))
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
output_data["dicom tag"].append("N/A")
|
|
133
|
+
output_data["attribute"].append("Number of files")
|
|
134
|
+
output_data["value"].append(number_files)
|
|
135
|
+
|
|
136
|
+
if output_format == "csv":
|
|
137
|
+
pd.DataFrame(output_data).to_csv(
|
|
138
|
+
f"scan_sequence_info.{output_format}", index=False
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
elif output_format == "tsv":
|
|
142
|
+
pd.DataFrame(output_data).to_csv(
|
|
143
|
+
f"scan_sequence_info.{output_format}", sep="\t", index=False
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
elif output_format == "json":
|
|
147
|
+
with open(f"scan_sequence_info.{output_format}", "w") as fp:
|
|
148
|
+
json.dump(output_data, fp)
|
|
149
|
+
|
|
150
|
+
elif output_format == "dict":
|
|
151
|
+
return output_data
|