Skip to content

Commit a45e560

Browse files
committed
Add transducer tracking result to session definition (OpenwaterHealth#182)
1 parent d78e5ed commit a45e560

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

src/openlifu/db/session.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import asdict, dataclass, field
66
from datetime import datetime
77
from pathlib import Path
8-
from typing import Dict, List, Tuple
8+
from typing import Dict, List, Tuple, Optional
99

1010
import numpy as np
1111

@@ -28,6 +28,26 @@ class ArrayTransform:
2828
(In order to apply the transform to transducer points,
2929
first represent the points in these units.)"""
3030

31+
@dataclass
32+
class TransducerTrackingResult:
33+
"""
34+
Class representing the results of running the transducer tracking
35+
algorithm.
36+
"""
37+
38+
photoscan_id: str
39+
"""ID of the photoscan object used for transducer tracking"""
40+
41+
transducer_to_photoscan_transform: ArrayTransform
42+
"""Transform output by transducer tracking algorithm to register the transducer surface to the photoscan model"""
43+
44+
photoscan_to_volume_transform: ArrayTransform
45+
"""Transform output by the transducer tracking algorithm to register the photoscan model the volume's skin segmentation"""
46+
47+
transducer_tracking_approved: Optional[bool] = False
48+
"""Approval state of transducer tracking result. `True` means the user has provided some kind of
49+
confirmation that the transducer transforms in this result agrees with reality."""
50+
3151
@dataclass
3252
class Session:
3353
"""
@@ -83,9 +103,8 @@ class Session:
83103
only. None of the other transforms in the list are considered to be approved.
84104
"""
85105

86-
transducer_tracking_approved: bool | None = False
87-
"""Approval state of transducer tracking. `True` means the user has provided some kind of
88-
confirmation that the transducer transform in this session agrees with reality."""
106+
transducer_tracking_results: Optional[List[TransducerTrackingResult]] = field(default_factory=list)
107+
"""List of any transducer tracking results"""
89108

90109
def __post_init__(self):
91110
if self.id is None and self.name is None:
@@ -130,6 +149,11 @@ def from_dict(d:Dict):
130149
raise ValueError("Sessions no longer recognize a volume attribute -- it is now volume_id.")
131150
if 'array_transform' in d:
132151
d['array_transform'] = ArrayTransform(np.array(d['array_transform']['matrix']), d['array_transform']['units'])
152+
if 'transducer_tracking_results' in d:
153+
d['transducer_tracking_results'] = [TransducerTrackingResult(t['photoscan_id'],
154+
ArrayTransform(np.array(t['transducer_to_photoscan_transform']['matrix']),t['transducer_to_photoscan_transform']['units']),
155+
ArrayTransform(np.array(t['photoscan_to_volume_transform']['matrix']), t['photoscan_to_volume_transform']['units']),
156+
t['transducer_tracking_approved']) for t in d['transducer_tracking_results']]
133157
if isinstance(d['targets'], list):
134158
if len(d['targets'])>0 and isinstance(d['targets'][0], dict):
135159
d['targets'] = [Point.from_dict(p) for p in d['targets']]
@@ -165,12 +189,13 @@ def to_dict(self):
165189
d['markers'] = [p.to_dict() for p in d['markers']]
166190

167191
d['array_transform'] = asdict(d['array_transform'])
168-
169192
for target_id,(approval,transforms) in d['virtual_fit_results'].items():
170193
d['virtual_fit_results'][target_id] = (
171194
approval,
172195
[asdict(t) for t in transforms],
173196
)
197+
198+
d['transducer_tracking_results'] = [asdict(t) for t in d['transducer_tracking_results']]
174199

175200
return d
176201

tests/resources/example_db/subjects/example_subject/sessions/example_session/example_session.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,28 @@
4343
]
4444
]
4545
},
46-
"transducer_tracking_approved": false
46+
"transducer_tracking_results": [
47+
{
48+
"photoscan_id": "example_photoscan",
49+
"transducer_to_photoscan_transform": {
50+
"matrix": [
51+
[1.0, 0.0, 0.0, 0.0],
52+
[0.0, 1.0, 0.0, 0.0],
53+
[0.0, 0.0, 1.0, 0.0],
54+
[0.0, 0.0, 0.0, 1.0]
55+
],
56+
"units": "mm"
57+
},
58+
"photoscan_to_volume_transform": {
59+
"matrix": [
60+
[1.0, 0.0, 0.0, 0.0],
61+
[0.0, 1.0, 0.0, 0.0],
62+
[0.0, 0.0, 1.0, 0.0],
63+
[0.0, 0.0, 0.0, 1.0]
64+
],
65+
"units": "mm"
66+
},
67+
"transducer_tracking_approved": false
68+
}
69+
]
4770
}

tests/test_database.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from openlifu.db.database import Database, OnConflictOpts
1919
from openlifu.db.session import ArrayTransform
2020
from openlifu.photoscan import Photoscan
21+
from openlifu.db.session import ArrayTransform, TransducerTrackingResult
2122
from openlifu.plan import Protocol, Run
2223
from openlifu.xdc import Transducer
2324

@@ -55,6 +56,11 @@ def test_new_database(tmp_path:Path):
5556
assert len(db1.get_subject_ids()) == 0
5657
assert len(db1.get_transducer_ids()) == 0
5758

59+
@pytest.fixture()
60+
def example_transducer_tracking_result() -> TransducerTrackingResult:
61+
return TransducerTrackingResult(photoscan_id="example_photoscan",
62+
transducer_to_photoscan_transform = ArrayTransform(np.eye(4),"mm"),
63+
photoscan_to_volume_transform = ArrayTransform(np.eye(4),"mm"))
5864

5965
def test_write_protocol(example_database: Database):
6066
protocol = Protocol(name="bleh", id="a_protocol_called_bleh")
@@ -304,6 +310,12 @@ def test_write_session_associated_object_structure_created(example_database: Dat
304310
assert example_database.get_solutions_filename(example_subject.id, session.id).is_file()
305311
assert example_database.get_runs_filename(example_subject.id, session.id).is_file()
306312

313+
def test_write_session_with_transducer_tracking_results(example_database: Database, example_subject: Subject, example_transducer_tracking_result):
314+
""" Test that when there is a transducer tracking result class associated with a session, the session
315+
is correctly written to file."""
316+
session = Session(name="bleh", id='a_session',subject_id=example_subject.id)
317+
session.transducer_tracking_results = [example_transducer_tracking_result]
318+
example_database.write_session(example_subject, session)
307319

308320
def test_write_run(example_database: Database, tmp_path:Path):
309321
subject_id = "example_subject"

0 commit comments

Comments
 (0)