Skip to content

Commit e20da2b

Browse files
authored
Merge pull request #138 from DCC-Lab/jlb/fluence
Add fluence rate option.
2 parents 78aa2be + c579cb2 commit e20da2b

File tree

29 files changed

+239
-73
lines changed

29 files changed

+239
-73
lines changed

pytissueoptics/examples/benchmarks/cube60.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "MCX Homogeneous cube"

pytissueoptics/examples/benchmarks/cubesph60b.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "MCX Sphere"

pytissueoptics/examples/benchmarks/skinvessel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "Skin vessel"

pytissueoptics/examples/benchmarks/sphshells.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "MCX Spherical shells"

pytissueoptics/examples/rayscattering/ex01.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "Divergent source propagation through a multi-layered tissue"
@@ -24,6 +25,7 @@ def exampleCode():
2425
viewer.reportStats()
2526

2627
viewer.show2D(View2DProjectionX())
28+
viewer.show2D(View2DProjectionX(energyType=EnergyType.FLUENCE_RATE))
2729
viewer.show2D(View2DProjectionX(solidLabel="middleLayer"))
2830
viewer.show2D(View2DSurfaceZ(solidLabel="middleLayer", surfaceLabel="interface1", surfaceEnergyLeaving=False))
2931
viewer.show1D(Direction.Z_POS)

pytissueoptics/examples/rayscattering/ex02.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "Propagation in an Infinite Medium"

pytissueoptics/examples/rayscattering/ex03.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = (

pytissueoptics/examples/rayscattering/ex04.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "Custom layer stack"

pytissueoptics/examples/rayscattering/ex05.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import env # noqa: F401
2+
23
from pytissueoptics import * # noqa: F403
34

45
TITLE = "Sphere inside a cube"

pytissueoptics/rayscattering/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
View2DSurfaceY,
1414
View2DSurfaceZ,
1515
)
16-
from .energyLogging import EnergyLogger
16+
from .energyLogging import EnergyLogger, EnergyType
1717
from .materials import ScatteringMaterial
1818
from .opencl import CONFIG, disableOpenCL, hardwareAccelerationIsAvailable
1919
from .photon import Photon
@@ -29,6 +29,7 @@
2929
"DirectionalSource",
3030
"DivergentSource",
3131
"EnergyLogger",
32+
"EnergyType",
3233
"ScatteringScene",
3334
"Viewer",
3435
"PointCloudStyle",

pytissueoptics/rayscattering/display/profiles/profile1D.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,29 @@
44
from matplotlib import pyplot as plt
55

66
from pytissueoptics.rayscattering.display.utils import Direction
7+
from pytissueoptics.rayscattering.energyLogging import EnergyType
78

89

910
class Profile1D:
1011
"""
1112
Since 1D profiles are easily generated from existing 2D views or 3D data, this class is only used as a small
1213
dataclass. Only used internally Profile1DFactory when Viewer.show1D() is called. The user should only use the
13-
endpoint Viewer.show1D() which doesn't require to create a Profile1D object.
14+
endpoint Viewer.show1D() which doesn't require creating a Profile1D object.
1415
"""
1516

16-
def __init__(self, data: np.ndarray, horizontalDirection: Direction, limits: Tuple[float, float], name: str = None):
17+
def __init__(
18+
self,
19+
data: np.ndarray,
20+
horizontalDirection: Direction,
21+
limits: Tuple[float, float],
22+
name: str = None,
23+
energyType=EnergyType.DEPOSITION,
24+
):
1725
self.data = data
1826
self.limits = limits
1927
self.horizontalDirection = horizontalDirection
2028
self.name = name
29+
self.energyType = energyType
2130

2231
def show(self, logScale: bool = True):
2332
limits = sorted(self.limits)
@@ -33,5 +42,5 @@ def show(self, logScale: bool = True):
3342
plt.title(self.name)
3443
plt.xlim(*limits)
3544
plt.xlabel("xyz"[self.horizontalDirection.axis])
36-
plt.ylabel("Energy")
45+
plt.ylabel("Deposited energy" if self.energyType == EnergyType.DEPOSITION else "Fluence rate")
3746
plt.show()

pytissueoptics/rayscattering/display/profiles/profileFactory.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pytissueoptics.rayscattering import utils
66
from pytissueoptics.rayscattering.display.profiles import Profile1D
77
from pytissueoptics.rayscattering.display.utils import Direction
8-
from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloudFactory
8+
from pytissueoptics.rayscattering.energyLogging import EnergyLogger, EnergyType, PointCloudFactory
99
from pytissueoptics.rayscattering.scatteringScene import ScatteringScene
1010

1111

@@ -28,6 +28,7 @@ def create(
2828
surfaceEnergyLeaving: bool = True,
2929
limits: Tuple[float, float] = None,
3030
binSize: float = None,
31+
energyType=EnergyType.DEPOSITION,
3132
) -> Profile1D:
3233
solidLabel, surfaceLabel = self._correctCapitalization(solidLabel, surfaceLabel)
3334
if binSize is None:
@@ -39,15 +40,15 @@ def create(
3940

4041
if self._logger.has3D:
4142
histogram = self._extractHistogramFrom3D(
42-
horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins
43+
horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins, energyType
4344
)
4445
else:
4546
histogram = self._extractHistogramFromViews(
46-
horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins
47+
horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins, energyType
4748
)
4849

49-
name = self._createName(horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving)
50-
return Profile1D(histogram, horizontalDirection, limits, name)
50+
name = self._createName(horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, energyType)
51+
return Profile1D(histogram, horizontalDirection, limits, name, energyType)
5152

5253
def _getDefaultLimits(self, horizontalDirection: Direction, solidLabel: str = None):
5354
if solidLabel:
@@ -70,8 +71,9 @@ def _extractHistogramFrom3D(
7071
surfaceEnergyLeaving: bool,
7172
limits: Tuple[float, float],
7273
bins: int,
74+
energyType: EnergyType,
7375
):
74-
pointCloud = self._pointCloudFactory.getPointCloud(solidLabel, surfaceLabel)
76+
pointCloud = self._pointCloudFactory.getPointCloud(solidLabel, surfaceLabel, energyType=energyType)
7577

7678
if surfaceLabel:
7779
if surfaceEnergyLeaving:
@@ -95,7 +97,10 @@ def _extractHistogramFromViews(
9597
surfaceEnergyLeaving: bool,
9698
limits: Tuple[float, float],
9799
bins: int,
100+
energyType: EnergyType,
98101
):
102+
energyMismatch = False
103+
99104
for view in self._logger.views:
100105
if view.axis == horizontalDirection.axis:
101106
continue
@@ -118,10 +123,16 @@ def _extractHistogramFromViews(
118123
continue
119124
if viewBins != bins:
120125
continue
126+
if not surfaceLabel and energyType != view.energyType:
127+
energyMismatch = True
128+
continue
121129

122130
return self._extractHistogramFromView(view, horizontalDirection)
123131

124-
raise RuntimeError("Cannot create 1D profile. The 3D data was discarded and no matching 2D view was found.")
132+
error_message = "Cannot create 1D profile. The 3D data was discarded and no matching 2D view was found."
133+
if energyMismatch:
134+
error_message += " Note that a view candidate was found to only differ in energy type."
135+
raise RuntimeError(error_message)
125136

126137
def _extractHistogramFromView(self, view, horizontalDirection: Direction):
127138
if view.axisU == horizontalDirection.axis:
@@ -160,9 +171,14 @@ def _correctCapitalization(self, solidLabel, surfaceLabel):
160171
return solidLabel, surfaceLabel
161172

162173
def _createName(
163-
self, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, surfaceEnergyLeaving: bool
174+
self,
175+
horizontalDirection: Direction,
176+
solidLabel: str,
177+
surfaceLabel: str,
178+
surfaceEnergyLeaving: bool,
179+
energyType: EnergyType,
164180
) -> str:
165-
name = "Energy profile along " + "xyz"[horizontalDirection.axis]
181+
name = f"{energyType.name} profile along " + "xyz"[horizontalDirection.axis]
166182
if solidLabel:
167183
name += " of " + solidLabel
168184
if surfaceLabel:

pytissueoptics/rayscattering/display/viewer.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
from pytissueoptics.rayscattering.display.profiles import ProfileFactory
88
from pytissueoptics.rayscattering.display.utils import Direction
99
from pytissueoptics.rayscattering.display.views import View2D, ViewGroup
10-
from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloudFactory
10+
from pytissueoptics.rayscattering.energyLogging import (
11+
EnergyLogger,
12+
EnergyType,
13+
PointCloudFactory,
14+
)
1115
from pytissueoptics.rayscattering.energyLogging.pointCloud import PointCloud
1216
from pytissueoptics.rayscattering.scatteringScene import ScatteringScene
1317
from pytissueoptics.rayscattering.source import Source
@@ -40,6 +44,7 @@ class PointCloudStyle:
4044
showSolidPoints (bool): Show the point clouds of the solids.
4145
showSurfacePointsLeaving (bool): Show energy that left the surface (direction with surface normal).
4246
showSurfacePointsEntering (bool): Show energy that entered the surface (direction opposite to surface normal).
47+
energyType (EnergyType): Type of energy to show for volumetric datapoints (deposition or fluence).
4348
4449
Other attributes:
4550
showPointsAsSpheres (bool): Show the points as spheres or as dots. Dots require less memory.
@@ -60,6 +65,7 @@ def __init__(
6065
showSolidPoints: bool = True,
6166
showSurfacePointsLeaving: bool = True,
6267
showSurfacePointsEntering: bool = False,
68+
energyType=EnergyType.DEPOSITION,
6369
showPointsAsSpheres: bool = False,
6470
pointSize: float = 0.15,
6571
scaleWithValue: bool = True,
@@ -75,6 +81,7 @@ def __init__(
7581
self.showSolidPoints = showSolidPoints
7682
self.showSurfacePointsLeaving = showSurfacePointsLeaving
7783
self.showSurfacePointsEntering = showSurfacePointsEntering
84+
self.energyType = energyType
7885
self.showPointsAsSpheres = showPointsAsSpheres
7986

8087
self.pointSize = pointSize
@@ -142,6 +149,7 @@ def show3DVolumeSlicer(
142149
logScale: bool = True,
143150
interpolate: bool = False,
144151
limits: Tuple[tuple, tuple, tuple] = None,
152+
energyType: EnergyType = EnergyType.DEPOSITION,
145153
):
146154
if not self._logger.has3D:
147155
utils.warn("ERROR: Cannot show 3D volume slicer without 3D data.")
@@ -161,7 +169,7 @@ def show3DVolumeSlicer(
161169
f"Consider using a larger binSize or tighter limits."
162170
)
163171

164-
points = self._pointCloudFactory.getPointCloudOfSolids().solidPoints
172+
points = self._pointCloudFactory.getPointCloudOfSolids(energyType=energyType).solidPoints
165173
try:
166174
hist, _ = np.histogramdd(points[:, 1:], bins=bins, weights=points[:, 0], range=limits)
167175
except MemoryError:
@@ -195,16 +203,21 @@ def show1D(
195203
surfaceEnergyLeaving: bool = True,
196204
limits: Tuple[float, float] = None,
197205
binSize: float = None,
206+
energyType: EnergyType = EnergyType.DEPOSITION,
198207
):
199-
profile = self._profileFactory.create(along, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, binSize)
208+
profile = self._profileFactory.create(
209+
along, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, binSize, energyType
210+
)
200211
profile.show(logScale=logScale)
201212

202213
def reportStats(self, solidLabel: str = None, saveToFile: str = None, verbose=True):
203214
stats = Stats(self._logger)
204215
stats.report(solidLabel=solidLabel, saveToFile=saveToFile, verbose=verbose)
205216

206217
def _addPointCloud(self, style: PointCloudStyle):
207-
pointCloud = self._pointCloudFactory.getPointCloud(solidLabel=style.solidLabel, surfaceLabel=style.surfaceLabel)
218+
pointCloud = self._pointCloudFactory.getPointCloud(
219+
solidLabel=style.solidLabel, surfaceLabel=style.surfaceLabel, energyType=style.energyType
220+
)
208221

209222
self._drawPointCloudOfSolids(pointCloud, style)
210223
self._drawPointCloudOfSurfaces(pointCloud, style)

0 commit comments

Comments
 (0)