Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies = [
"Pygments",
"siphash24; python_version < '3.13'",
"pyopencl",
"vtk>=9.4",
"vtk>=9.4,<9.5",
"mayavi-dev",
"pyqt5",
]
Expand Down Expand Up @@ -67,3 +67,14 @@ ignore = [
[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.coverage.run]
source = ["pytissueoptics"]
omit = [
"*/tests/*",
"*/test*.py",
"*/examples/*",
]

[tool.coverage.report]
show_missing = true
54 changes: 54 additions & 0 deletions pytissueoptics/examples/rayscattering/ex06.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import env # noqa: F401

from pytissueoptics import * # noqa: F403

TITLE = "Detectors"

DESCRIPTION = """ Sampling volume simulation with a directional source and two circle detectors. """


def exampleCode():
import numpy as np

N = 1000000 if hardwareAccelerationIsAvailable() else 1000

material = ScatteringMaterial(mu_s=10, mu_a=1, g=0.98, n=1.0)

cube = Cuboid(a=3, b=3, c=3, position=Vector(0, 0, 0), material=material, label="cube")

detectorA = Circle(
radius=0.25,
orientation=Vector(0, 0, -1),
position=Vector(0, 1, 1.501),
label="detectorA",
).asDetector(halfAngle=np.pi / 4)

detectorB = Circle(
radius=0.25,
orientation=Vector(0, 0, -1),
position=Vector(0, -1, 1.501),
label="detectorB",
).asDetector(halfAngle=np.pi / 4)

scene = ScatteringScene([cube, detectorA, detectorB])

logger = EnergyLogger(scene)
source = DirectionalSource(position=Vector(0, 0, -2), direction=Vector(0, 0, 1), N=N, diameter=0.5)

source.propagate(scene, logger)

viewer = Viewer(scene, source, logger)
viewer.reportStats()

# Either filter the whole logger
logger.filter(detectedBy=["detectorA", "detectorB"])
viewer.show2D(View2DProjectionX())
viewer.show3D()

# ... or keep the logger intact and filter individual views
# viewer.show2D(View2DProjectionX(detectedBy="detectorA"))
# viewer.show3D(pointCloudStyle=PointCloudStyle(detectedBy=["detectorA", "detectorB"]))


if __name__ == "__main__":
exampleCode()
6 changes: 3 additions & 3 deletions pytissueoptics/examples/scene/example2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
def exampleCode():
from pytissueoptics.scene import Cuboid, Vector, get3DViewer

cuboid1 = Cuboid(1, 1, 1, position=Vector(2, 0, 0))
cuboid2 = Cuboid(2, 1, 1, position=Vector(0, 2, 0))
cuboid3 = Cuboid(3, 1, 1, position=Vector(0, 0, 2))
cuboid1 = Cuboid(1, 1, 1, position=Vector(2, 0, 0), label="1")
cuboid2 = Cuboid(2, 1, 1, position=Vector(0, 2, 0), label="2")
cuboid3 = Cuboid(3, 1, 1, position=Vector(0, 0, 2), label="3")

viewer = get3DViewer()
viewer.add(cuboid1, cuboid2, cuboid3, representation="wireframe", lineWidth=5)
Expand Down
15 changes: 10 additions & 5 deletions pytissueoptics/rayscattering/display/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(
showSurfacePointsLeaving: bool = True,
showSurfacePointsEntering: bool = False,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
showPointsAsSpheres: bool = False,
pointSize: float = 0.15,
scaleWithValue: bool = True,
Expand All @@ -82,6 +83,7 @@ def __init__(
self.showSurfacePointsLeaving = showSurfacePointsLeaving
self.showSurfacePointsEntering = showSurfacePointsEntering
self.energyType = energyType
self.detectedBy = detectedBy
self.showPointsAsSpheres = showPointsAsSpheres

self.pointSize = pointSize
Expand All @@ -103,7 +105,6 @@ def __init__(self, scene: ScatteringScene, source: Source, logger: EnergyLogger)
self._logger = logger

self._viewer3D = None
self._pointCloudFactory = PointCloudFactory(logger)
self._profileFactory = ProfileFactory(scene, logger)

def listViews(self):
Expand Down Expand Up @@ -169,7 +170,7 @@ def show3DVolumeSlicer(
f"Consider using a larger binSize or tighter limits."
)

points = self._pointCloudFactory.getPointCloudOfSolids(energyType=energyType).solidPoints
points = PointCloudFactory(self._logger).getPointCloudOfSolids(energyType=energyType).solidPoints
try:
hist, _ = np.histogramdd(points[:, 1:], bins=bins, weights=points[:, 0], range=limits)
except MemoryError:
Expand Down Expand Up @@ -215,8 +216,12 @@ def reportStats(self, solidLabel: str = None, saveToFile: str = None, verbose=Tr
stats.report(solidLabel=solidLabel, saveToFile=saveToFile, verbose=verbose)

def _addPointCloud(self, style: PointCloudStyle):
pointCloud = self._pointCloudFactory.getPointCloud(
solidLabel=style.solidLabel, surfaceLabel=style.surfaceLabel, energyType=style.energyType
logger = self._logger if style.detectedBy is None else self._logger.getFiltered(style.detectedBy)

pointCloud = PointCloudFactory(logger).getPointCloud(
solidLabel=style.solidLabel,
surfaceLabel=style.surfaceLabel,
energyType=style.energyType,
)

self._drawPointCloudOfSolids(pointCloud, style)
Expand All @@ -241,7 +246,7 @@ def _drawPointCloudOfSurfaces(self, pointCloud: PointCloud, style: PointCloudSty
if pointCloud.surfacePoints is None:
return

surfacePoints = np.empty((0, 4))
surfacePoints = np.empty((0, 5))

if style.showSurfacePointsLeaving:
surfacePoints = np.vstack((surfacePoints, pointCloud.leavingSurfacePoints))
Expand Down
42 changes: 39 additions & 3 deletions pytissueoptics/rayscattering/display/views/defaultViews.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
projectionDirection,
Expand All @@ -29,6 +30,7 @@ def __init__(
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)

def _filter(self, dataPoints: np.ndarray) -> np.ndarray:
Expand All @@ -42,9 +44,15 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_X_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize, energyType=energyType
*DEFAULT_X_VIEW_DIRECTIONS,
solidLabel=solidLabel,
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)


Expand All @@ -55,9 +63,15 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Y_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize, energyType=energyType
*DEFAULT_Y_VIEW_DIRECTIONS,
solidLabel=solidLabel,
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)


Expand All @@ -68,9 +82,15 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Z_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize, energyType=energyType
*DEFAULT_Z_VIEW_DIRECTIONS,
solidLabel=solidLabel,
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)


Expand All @@ -84,6 +104,7 @@ def __init__(
surfaceEnergyLeaving: bool = True,
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
projectionDirection,
Expand All @@ -93,6 +114,7 @@ def __init__(
surfaceEnergyLeaving=surfaceEnergyLeaving,
limits=limits,
binSize=binSize,
detectedBy=detectedBy,
)

def _filter(self, dataPoints: np.ndarray) -> np.ndarray:
Expand All @@ -116,6 +138,7 @@ def __init__(
surfaceEnergyLeaving: bool = True,
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_X_VIEW_DIRECTIONS,
Expand All @@ -124,6 +147,7 @@ def __init__(
surfaceEnergyLeaving=surfaceEnergyLeaving,
limits=limits,
binSize=binSize,
detectedBy=detectedBy,
)


Expand All @@ -135,6 +159,7 @@ def __init__(
surfaceEnergyLeaving: bool = True,
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Y_VIEW_DIRECTIONS,
Expand All @@ -143,6 +168,7 @@ def __init__(
surfaceEnergyLeaving=surfaceEnergyLeaving,
limits=limits,
binSize=binSize,
detectedBy=detectedBy,
)


Expand All @@ -154,6 +180,7 @@ def __init__(
surfaceEnergyLeaving: bool = True,
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Z_VIEW_DIRECTIONS,
Expand All @@ -162,6 +189,7 @@ def __init__(
surfaceEnergyLeaving=surfaceEnergyLeaving,
limits=limits,
binSize=binSize,
detectedBy=detectedBy,
)


Expand All @@ -176,6 +204,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
projectionDirection,
Expand All @@ -186,6 +215,7 @@ def __init__(
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)

def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float, float, float]):
Expand All @@ -210,6 +240,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_X_VIEW_DIRECTIONS,
Expand All @@ -219,6 +250,7 @@ def __init__(
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)


Expand All @@ -231,6 +263,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Y_VIEW_DIRECTIONS,
Expand All @@ -240,6 +273,7 @@ def __init__(
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)


Expand All @@ -252,6 +286,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
super().__init__(
*DEFAULT_Z_VIEW_DIRECTIONS,
Expand All @@ -261,4 +296,5 @@ def __init__(
limits=limits,
binSize=binSize,
energyType=energyType,
detectedBy=detectedBy,
)
11 changes: 11 additions & 0 deletions pytissueoptics/rayscattering/display/views/view2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(
limits: Tuple[Tuple[float, float], Tuple[float, float]] = None,
binSize: Union[float, Tuple[int, int]] = None,
energyType=EnergyType.DEPOSITION,
detectedBy: Union[str, List[str]] = None,
):
"""
The 2D view plane is obtained by looking towards the 'projectionDirection'. The 'horizontalDirection'
Expand Down Expand Up @@ -78,6 +79,7 @@ def __init__(
self._position = position
self._thickness = thickness
self._energyType = energyType
self._detectedBy = detectedBy

limits = [sorted(limit) for limit in limits] if limits else [None, None]
self._limitsU, self._limitsV = limits
Expand All @@ -103,6 +105,7 @@ def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float

limits = [self._limitsU, self._limitsV]
self._binsU, self._binsV = [int((limit[1] - limit[0]) / bin_) for limit, bin_ in zip(limits, self._binSize)]
self._binsU, self._binsV = max(1, self._binsU), max(1, self._binsV)

if self._verticalIsNegative:
self._limitsV = self._limitsV[::-1]
Expand All @@ -121,6 +124,10 @@ def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float
def energyType(self) -> EnergyType:
return self._energyType

@property
def detectedBy(self) -> Union[str, List[str], None]:
return self._detectedBy

def extractData(self, dataPoints: np.ndarray):
"""
Used internally by Logger2D to store 3D datapoints into this 2D view.
Expand Down Expand Up @@ -245,6 +252,8 @@ def isEqualTo(self, other: "View2D") -> bool:
return False
if self._energyType != other._energyType:
return False
if self._detectedBy != other._detectedBy:
return False
return True

def isContainedBy(self, other: "View2D") -> bool:
Expand All @@ -260,6 +269,8 @@ def isContainedBy(self, other: "View2D") -> bool:
return False
if self._thickness != other._thickness:
return False
if self._detectedBy != other._detectedBy:
return False

# TODO: change/remove the following once the algorithm can extract a view contained inside a bigger view.
isTransposed = self.axisU != other.axisU
Expand Down
Loading