textual-file-viewer 0.10.0a2__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.
- textual_file_viewer-0.10.0a2/.gitignore +7 -0
- textual_file_viewer-0.10.0a2/.gitlab-ci.yml +147 -0
- textual_file_viewer-0.10.0a2/MANIFEST.in +2 -0
- textual_file_viewer-0.10.0a2/PKG-INFO +50 -0
- textual_file_viewer-0.10.0a2/README.md +15 -0
- textual_file_viewer-0.10.0a2/main.py +8 -0
- textual_file_viewer-0.10.0a2/pyproject.toml +108 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/FiraCode-Medium.ttf +0 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/__init__.py +0 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/dicom_sr.py +129 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/dicom_tree.py +265 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/dicom_viewer.py +194 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/file_viewer.py +103 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/file_viewer.tcss +51 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/image_viewer.py +248 -0
- textual_file_viewer-0.10.0a2/src/textual_file_viewer/mhd_nifti_viewer.py +68 -0
- textual_file_viewer-0.10.0a2/tests/_data/test.csv +2 -0
- textual_file_viewer-0.10.0a2/tests/test_csv.py +13 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
variables:
|
|
2
|
+
PYTHON_VERSION: "3.10"
|
|
3
|
+
|
|
4
|
+
stages:
|
|
5
|
+
- static_analysis
|
|
6
|
+
- test
|
|
7
|
+
- build
|
|
8
|
+
- publish
|
|
9
|
+
|
|
10
|
+
before_script:
|
|
11
|
+
- pip install --upgrade pip uv --root-user-action=ignore
|
|
12
|
+
|
|
13
|
+
.setup_python_venv: &setup_python_venv
|
|
14
|
+
- uv venv
|
|
15
|
+
- uv sync --group dev
|
|
16
|
+
- source .venv/bin/activate
|
|
17
|
+
|
|
18
|
+
pylint_mypy:
|
|
19
|
+
stage: static_analysis
|
|
20
|
+
image: python:${PYTHON_VERSION}
|
|
21
|
+
needs: []
|
|
22
|
+
interruptible: true
|
|
23
|
+
allow_failure: true
|
|
24
|
+
script:
|
|
25
|
+
- *setup_python_venv
|
|
26
|
+
- uv add pylint-gitlab
|
|
27
|
+
- pylint --output-format=pylint_gitlab.GitlabCodeClimateReporter --fail-under=5 --jobs=0 $(git ls-files '*.py') > codeclimate.json
|
|
28
|
+
- pylint --output-format=pylint_gitlab.GitlabPagesHtmlReporter --fail-under=5 --jobs=0 $(git ls-files '*.py') > pylint_results.html
|
|
29
|
+
- mypy src --namespace-packages --junit-xml mypy_junit.xml
|
|
30
|
+
artifacts:
|
|
31
|
+
expire_in: never
|
|
32
|
+
when: always
|
|
33
|
+
paths:
|
|
34
|
+
- pylint_results.html
|
|
35
|
+
- mypy_junit.xml
|
|
36
|
+
- codeclimate.json
|
|
37
|
+
reports:
|
|
38
|
+
junit: mypy_junit.xml
|
|
39
|
+
codequality: codeclimate.json
|
|
40
|
+
|
|
41
|
+
.parallel:
|
|
42
|
+
parallel:
|
|
43
|
+
matrix:
|
|
44
|
+
- PYTHON_VERSION: [ "3.10", "3.11", "3.12", "3.13", "3.14" ]
|
|
45
|
+
|
|
46
|
+
pytest:
|
|
47
|
+
# noinspection YAMLSchemaValidation
|
|
48
|
+
parallel: !reference [ .parallel,parallel ]
|
|
49
|
+
stage: test
|
|
50
|
+
needs: [ ] # Run when pipeline starts, don't wait for pylint to finish
|
|
51
|
+
image: python:${PYTHON_VERSION}
|
|
52
|
+
interruptible: true
|
|
53
|
+
script:
|
|
54
|
+
- *setup_python_venv
|
|
55
|
+
- echo -e "[run]\nomit = .venv/*" > .coveragerc
|
|
56
|
+
- pytest --junitxml=results.xml --cov=. --cov-report xml:coverage.xml --cov-report term tests/
|
|
57
|
+
coverage: '/TOTAL.*\s+(\d+%)$/'
|
|
58
|
+
artifacts:
|
|
59
|
+
expire_in: never
|
|
60
|
+
paths:
|
|
61
|
+
- results.xml
|
|
62
|
+
- coverage.xml
|
|
63
|
+
reports:
|
|
64
|
+
junit: results.xml
|
|
65
|
+
coverage_report:
|
|
66
|
+
coverage_format: cobertura
|
|
67
|
+
path: coverage.xml
|
|
68
|
+
|
|
69
|
+
build:
|
|
70
|
+
stage: build
|
|
71
|
+
image: python:${PYTHON_VERSION}
|
|
72
|
+
dependencies: [] # Don't load dependencies from previous stage
|
|
73
|
+
script:
|
|
74
|
+
# Set the version in pyproject.toml to the current git tag
|
|
75
|
+
- VERSION=$(git describe --exact-match --tags)
|
|
76
|
+
- uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version $VERSION
|
|
77
|
+
# Build the package
|
|
78
|
+
- uv build
|
|
79
|
+
artifacts:
|
|
80
|
+
paths:
|
|
81
|
+
- dist
|
|
82
|
+
only: # Only run this stage when a tag is created/pushed
|
|
83
|
+
- tags
|
|
84
|
+
|
|
85
|
+
# This stage publishes to our Gitlab package registry
|
|
86
|
+
gitlab_registry:
|
|
87
|
+
stage: publish
|
|
88
|
+
image: python:${PYTHON_VERSION}
|
|
89
|
+
dependencies: [build] # Load the artifacts from the build stage
|
|
90
|
+
variables:
|
|
91
|
+
# With the following variables, we tell uv how to publish our package.
|
|
92
|
+
# Gitlab has built in usernames and passwords for publish packages. This means you don't need to change them!
|
|
93
|
+
UV_PUBLISH_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"
|
|
94
|
+
UV_PUBLISH_USERNAME: "gitlab-ci-token"
|
|
95
|
+
UV_PUBLISH_PASSWORD: "${CI_JOB_TOKEN}"
|
|
96
|
+
script:
|
|
97
|
+
- uv publish dist/*.whl
|
|
98
|
+
only: # Only run this stage when a tag is created/pushed
|
|
99
|
+
- tags
|
|
100
|
+
|
|
101
|
+
# This stage publishes to testpypi
|
|
102
|
+
testpypi_registry:
|
|
103
|
+
stage: publish
|
|
104
|
+
image: python:${PYTHON_VERSION}
|
|
105
|
+
dependencies: [build] # Load the artifacts from the build stage
|
|
106
|
+
id_tokens:
|
|
107
|
+
PYPI_ID_TOKEN:
|
|
108
|
+
aud: testpypi
|
|
109
|
+
script:
|
|
110
|
+
# https://docs.pypi.org/trusted-publishers/using-a-publisher/#gitlab-cicd
|
|
111
|
+
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/#gitlab-cicd
|
|
112
|
+
- apt update && apt install -y jq
|
|
113
|
+
- uv venv
|
|
114
|
+
- uv pip install -U id==1.5.0
|
|
115
|
+
|
|
116
|
+
# Retrieve the OIDC token from GitLab CI/CD, and exchange it for a PyPI API token
|
|
117
|
+
- oidc_token=$(uv run -m id PYPI)
|
|
118
|
+
- resp=$(curl -X POST https://test.pypi.org/_/oidc/mint-token -d "{\"token\":\"${oidc_token}\"}")
|
|
119
|
+
- api_token=$(jq --raw-output '.token' <<< "${resp}")
|
|
120
|
+
|
|
121
|
+
# Upload to PyPI authenticating via the newly-minted token
|
|
122
|
+
- uv publish -u __token__ -p "${api_token}" dist/* --publish-url "https://test.pypi.org/legacy/"
|
|
123
|
+
only: # Only run this stage when a tag is created/pushed
|
|
124
|
+
- tags
|
|
125
|
+
|
|
126
|
+
# This stage publishes to pypi
|
|
127
|
+
pypi_registry:
|
|
128
|
+
stage: publish
|
|
129
|
+
image: python:${PYTHON_VERSION}
|
|
130
|
+
dependencies: [build] # Load the artifacts from the build stage
|
|
131
|
+
id_tokens:
|
|
132
|
+
PYPI_ID_TOKEN:
|
|
133
|
+
aud: pypi
|
|
134
|
+
script:
|
|
135
|
+
- apt update && apt install -y jq
|
|
136
|
+
- uv venv
|
|
137
|
+
- uv pip install -U id==1.5.0
|
|
138
|
+
|
|
139
|
+
# Retrieve the OIDC token from GitLab CI/CD, and exchange it for a PyPI API token
|
|
140
|
+
- oidc_token=$(uv run -m id PYPI)
|
|
141
|
+
- resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\":\"${oidc_token}\"}")
|
|
142
|
+
- api_token=$(jq --raw-output '.token' <<< "${resp}")
|
|
143
|
+
|
|
144
|
+
# Upload to PyPI authenticating via the newly-minted token
|
|
145
|
+
- uv publish -u __token__ -p "${api_token}" dist/*
|
|
146
|
+
only: # Only run this stage when a tag is created/pushed
|
|
147
|
+
- tags
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: textual-file-viewer
|
|
3
|
+
Version: 0.10.0a2
|
|
4
|
+
Summary: View the contents of several common file types
|
|
5
|
+
Project-URL: Repository, https://gitlab.com/pjhdekoning/textual-file-viewer
|
|
6
|
+
Project-URL: Issues, https://gitlab.com/pjhdekoning/textual-file-viewer/issues
|
|
7
|
+
Author-email: Patrick de Koning <pjhdekoning@lumc.nl>
|
|
8
|
+
Keywords: TUI
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: openpyxl
|
|
23
|
+
Requires-Dist: pandas>=2.0
|
|
24
|
+
Requires-Dist: pillow
|
|
25
|
+
Requires-Dist: pydicom
|
|
26
|
+
Requires-Dist: pylibjpeg[libjpeg,openjpeg]
|
|
27
|
+
Requires-Dist: rich-pixels>=3.0.1
|
|
28
|
+
Requires-Dist: simpleitk>=2.4.1
|
|
29
|
+
Requires-Dist: textual-image>=0.7.0
|
|
30
|
+
Requires-Dist: textual-slider>=0.2.0
|
|
31
|
+
Requires-Dist: textual-sortable-datatable
|
|
32
|
+
Requires-Dist: textual>=1.0
|
|
33
|
+
Requires-Dist: typer
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# textual-file-viewer
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
Commandline tool to view .xslx, .csv, DICOM, MetaImage (.mha/.mhd) and Niftii files.
|
|
48
|
+
```shell
|
|
49
|
+
file-viewer FILENAME
|
|
50
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# textual-file-viewer
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
Commandline tool to view .xslx, .csv, DICOM, MetaImage (.mha/.mhd) and Niftii files.
|
|
13
|
+
```shell
|
|
14
|
+
file-viewer FILENAME
|
|
15
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "textual-file-viewer"
|
|
7
|
+
description = "View the contents of several common file types"
|
|
8
|
+
keywords = ["TUI"]
|
|
9
|
+
version = "0.10.0-a2"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Healthcare Industry",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Natural Language :: English",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"numpy",
|
|
28
|
+
"openpyxl",
|
|
29
|
+
"pandas>=2.0",
|
|
30
|
+
"pillow",
|
|
31
|
+
"pydicom",
|
|
32
|
+
"pylibjpeg[libjpeg,openjpeg]",
|
|
33
|
+
"rich-pixels>=3.0.1",
|
|
34
|
+
"simpleitk>=2.4.1",
|
|
35
|
+
"textual-image>=0.7.0",
|
|
36
|
+
"textual-slider>=0.2.0",
|
|
37
|
+
"textual-sortable-datatable",
|
|
38
|
+
"textual>=1.0",
|
|
39
|
+
"typer",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
authors = [
|
|
43
|
+
{ name = "Patrick de Koning", email = "pjhdekoning@lumc.nl" },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"mypy>=1.14.1",
|
|
49
|
+
"pylint-gitlab",
|
|
50
|
+
"pylint>=3.3.7",
|
|
51
|
+
"pytest-cov",
|
|
52
|
+
"pytest-textual-snapshot>=1.1.0",
|
|
53
|
+
"ruff>=0.13.0",
|
|
54
|
+
"textual-dev>=1.7.0",
|
|
55
|
+
"ty>=0.0.1a20",
|
|
56
|
+
"types-python-dateutil>=2.9.0.20241206",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Repository = "https://gitlab.com/pjhdekoning/textual-file-viewer"
|
|
61
|
+
Issues = "https://gitlab.com/pjhdekoning/textual-file-viewer/issues"
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
file-viewer = "textual_file_viewer.file_viewer:app"
|
|
65
|
+
dicom-viewer = "textual_file_viewer.dicom_viewer:app"
|
|
66
|
+
mhd-viewer = "textual_file_viewer.mhd_nifti_viewer:app"
|
|
67
|
+
|
|
68
|
+
# PYLINT
|
|
69
|
+
[tool.pylint.main]
|
|
70
|
+
init-hook = 'import sys; sys.path.append("."); sys.path.append("src")'
|
|
71
|
+
|
|
72
|
+
[tool.pylint."messages control"]
|
|
73
|
+
disable = [
|
|
74
|
+
"missing-function-docstring",
|
|
75
|
+
"missing-module-docstring",
|
|
76
|
+
"missing-class-docstring",
|
|
77
|
+
"logging-format-interpolation",
|
|
78
|
+
"logging-fstring-interpolation",
|
|
79
|
+
"no-name-in-module",
|
|
80
|
+
"duplicate-code"
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.pylint.format]
|
|
84
|
+
max-line-length = 125
|
|
85
|
+
max-module-lines = 1000
|
|
86
|
+
|
|
87
|
+
[tool.pylint.design]
|
|
88
|
+
min-public-methods = 1
|
|
89
|
+
max-args = 10
|
|
90
|
+
max-locals = 25
|
|
91
|
+
|
|
92
|
+
[tool.pylint.basic]
|
|
93
|
+
good-names = ["e", "f", "i", "j", "k", "m", "x", "y", "z", "_", "id", "df", "md"]
|
|
94
|
+
|
|
95
|
+
[tool.pylint.exceptions]
|
|
96
|
+
overgeneral-exceptions = ["builtins.Exception"]
|
|
97
|
+
|
|
98
|
+
# MYPY
|
|
99
|
+
[tool.mypy]
|
|
100
|
+
warn_redundant_casts = true
|
|
101
|
+
ignore_missing_imports = true
|
|
102
|
+
disallow_untyped_calls = true
|
|
103
|
+
disallow_untyped_defs = true
|
|
104
|
+
check_untyped_defs = true
|
|
105
|
+
warn_return_any = true
|
|
106
|
+
no_implicit_optional = true
|
|
107
|
+
strict_optional = true
|
|
108
|
+
mypy_path = "src"
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import pydicom
|
|
2
|
+
from pydicom import DataElement, Dataset
|
|
3
|
+
from rich.markup import escape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dsrdump(ds: pydicom.Dataset, depth: int = 0, result: list[str] | None = None) -> list[str]:
|
|
7
|
+
"""
|
|
8
|
+
Recursively dumps a DICOM SR dataset mimicking DCMTK dsrdump output format,
|
|
9
|
+
enhanced with rich colored terminal output.
|
|
10
|
+
"""
|
|
11
|
+
# Top-level SR Document Root
|
|
12
|
+
if result is None:
|
|
13
|
+
result = [top_level_document_root(ds)]
|
|
14
|
+
|
|
15
|
+
# Traverse Content Sequence items if present
|
|
16
|
+
content_seq = ds.get("ContentSequence", [])
|
|
17
|
+
for item in content_seq:
|
|
18
|
+
value_type = escape(item.get("ValueType", "CONTAINER"))
|
|
19
|
+
rel_type = escape(item.get("RelationshipType", ""))
|
|
20
|
+
|
|
21
|
+
# Build Code Meaning string (Concept Name)
|
|
22
|
+
concept_str = code_meaning(item)
|
|
23
|
+
|
|
24
|
+
# Build Value / Content representation based on ValueType
|
|
25
|
+
val_str = value_content_representation(item, value_type)
|
|
26
|
+
|
|
27
|
+
indent = " " * depth
|
|
28
|
+
# Construct and print the formatted line
|
|
29
|
+
line_parts = [p for p in [
|
|
30
|
+
f"[cyan]<{rel_type}>[/cyan]",
|
|
31
|
+
f"[magenta]{value_type}:[/magenta]",
|
|
32
|
+
f"[green]{concept_str}[/green]",
|
|
33
|
+
f"[yellow]{val_str}[/yellow]"
|
|
34
|
+
] if p]
|
|
35
|
+
result.append(indent + " ".join(line_parts))
|
|
36
|
+
|
|
37
|
+
# Recurse for nested child sequences
|
|
38
|
+
if "ContentSequence" in item:
|
|
39
|
+
dsrdump(item, depth=depth + 1, result=result)
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def top_level_document_root(ds: Dataset) -> str:
|
|
44
|
+
if "ConceptNameCodeSequence" in ds and len(ds.ConceptNameCodeSequence) > 0:
|
|
45
|
+
item = ds.ConceptNameCodeSequence[0]
|
|
46
|
+
val = escape(item.get("CodeValue", ""))
|
|
47
|
+
scheme = escape(item.get("CodingSchemeDesignator", ""))
|
|
48
|
+
meaning = escape(item.get("CodeMeaning", ""))
|
|
49
|
+
doc_title = f'({val},{scheme},"{meaning}")'
|
|
50
|
+
return f"[green]{doc_title}[/green] "
|
|
51
|
+
|
|
52
|
+
return ''
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def code_meaning(item: Dataset) -> str:
|
|
56
|
+
concept_str = ""
|
|
57
|
+
if "ConceptNameCodeSequence" in item and len(item.ConceptNameCodeSequence) > 0:
|
|
58
|
+
c = item.ConceptNameCodeSequence[0]
|
|
59
|
+
c_val = escape(c.get("CodeValue", ""))
|
|
60
|
+
c_scheme = escape(c.get("CodingSchemeDesignator", ""))
|
|
61
|
+
c_meaning = escape(c.get("CodeMeaning", ""))
|
|
62
|
+
concept_str = f'({c_val},{c_scheme},"{c_meaning}")'
|
|
63
|
+
return concept_str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def value_content_representation(item: Dataset, value_type: str) -> str:
|
|
67
|
+
val_str = ""
|
|
68
|
+
if value_type == "TEXT":
|
|
69
|
+
val_str = f'= "{escape(item.get("TextValue", ""))}"'
|
|
70
|
+
elif value_type == "CODE":
|
|
71
|
+
val_str = concept_value_representation(item, val_str)
|
|
72
|
+
elif value_type in ("NUM", "NUMERIC"):
|
|
73
|
+
val_str = numeric_value_representation(item, val_str)
|
|
74
|
+
elif value_type == "PNAME":
|
|
75
|
+
val_str = f'= "{escape(str(item.get("PersonName", "")))}"'
|
|
76
|
+
elif value_type == "UIDREF":
|
|
77
|
+
val_str = f"= {escape(str(item.get('UID', '')))}"
|
|
78
|
+
elif value_type == "DATE":
|
|
79
|
+
val_str = f"= {escape(str(item.get('Date', '')))}"
|
|
80
|
+
elif value_type == "TIME":
|
|
81
|
+
val_str = f"= {escape(str(item.get('Time', '')))}"
|
|
82
|
+
elif value_type == "DATETIME":
|
|
83
|
+
val_str = f"= {escape(str(item.get('DateTime', '')))}"
|
|
84
|
+
elif value_type == "IMAGE":
|
|
85
|
+
val_str = image_content_representation(item, val_str)
|
|
86
|
+
elif value_type == "CONTAINER":
|
|
87
|
+
continuity = escape(item.get("ContinuityOfContent", ""))
|
|
88
|
+
if continuity:
|
|
89
|
+
val_str = f"\\[{continuity}]"
|
|
90
|
+
return val_str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def image_content_representation(item: Dataset, val_str: str) -> str:
|
|
94
|
+
if "ReferencedSOPSequence" in item and len(item.ReferencedSOPSequence) > 0:
|
|
95
|
+
ref = item.ReferencedSOPSequence[0]
|
|
96
|
+
ref_class = escape(str(ref.get("ReferencedSOPClassUID", "")))
|
|
97
|
+
ref_inst = escape(str(ref.get("ReferencedSOPInstanceUID", "")))
|
|
98
|
+
val_str = f"= ({ref_class},{ref_inst})"
|
|
99
|
+
return val_str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def concept_value_representation(item: Dataset, val_str: str) -> str:
|
|
103
|
+
if "ConceptCodeSequence" in item and len(item.ConceptCodeSequence) > 0:
|
|
104
|
+
cc = item.ConceptCodeSequence[0]
|
|
105
|
+
cc_val = escape(cc.get("CodeValue", ""))
|
|
106
|
+
cc_scheme = escape(cc.get("CodingSchemeDesignator", ""))
|
|
107
|
+
cc_meaning = escape(cc.get("CodeMeaning", ""))
|
|
108
|
+
val_str = f'= ({cc_val},{cc_scheme},"{cc_meaning}")'
|
|
109
|
+
return val_str
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def numeric_value_representation(item: Dataset, val_str: str) -> str:
|
|
113
|
+
num_val = ""
|
|
114
|
+
if "MeasuredValueSequence" in item and len(item.MeasuredValueSequence) > 0:
|
|
115
|
+
mv = item.MeasuredValueSequence[0]
|
|
116
|
+
num = escape(str(mv.get("NumericValue", "")))
|
|
117
|
+
unit_str = ""
|
|
118
|
+
if (
|
|
119
|
+
"MeasurementUnitsCodeSequence" in mv
|
|
120
|
+
and len(mv.MeasurementUnitsCodeSequence) > 0
|
|
121
|
+
):
|
|
122
|
+
u = mv.MeasurementUnitsCodeSequence[0]
|
|
123
|
+
u_val = escape(u.get("CodeValue", ""))
|
|
124
|
+
u_scheme = escape(u.get("CodingSchemeDesignator", ""))
|
|
125
|
+
u_meaning = escape(u.get("CodeMeaning", ""))
|
|
126
|
+
unit_str = f'({u_val},{u_scheme},"{u_meaning}")'
|
|
127
|
+
num_val = f"= {num} {unit_str}".strip()
|
|
128
|
+
val_str = num_val
|
|
129
|
+
return val_str
|