From d85e44d3b0a2084aa47e19d22daf21b611304a90 Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 19:32:47 -0400 Subject: [PATCH 1/7] format imports --- pytissueoptics/__init__.py | 3 +- pytissueoptics/__main__.py | 2 +- pytissueoptics/examples/__init__.py | 4 +- pytissueoptics/examples/benchmarks/env.py | 3 +- pytissueoptics/examples/rayscattering/env.py | 3 +- pytissueoptics/examples/rayscattering/ex01.py | 2 +- pytissueoptics/examples/scene/env.py | 3 +- pytissueoptics/examples/scene/example0.py | 2 +- pytissueoptics/examples/scene/example1.py | 2 +- pytissueoptics/examples/scene/example2.py | 2 +- pytissueoptics/examples/scene/example3.py | 2 +- pytissueoptics/examples/scene/example4.py | 10 ++++- pytissueoptics/rayscattering/__init__.py | 26 ++++++++---- .../display/profiles/profileFactory.py | 5 +-- .../rayscattering/display/utils/__init__.py | 2 +- .../display/utils/volumeSlicer.py | 15 +++---- .../rayscattering/display/viewer.py | 11 +++-- .../rayscattering/display/views/__init__.py | 18 ++++++-- .../display/views/defaultViews.py | 4 +- .../rayscattering/display/views/view2D.py | 2 +- .../display/views/viewFactory.py | 16 +++++--- .../rayscattering/energyLogging/__init__.py | 2 +- .../energyLogging/energyLogger.py | 8 ++-- .../energyLogging/pointCloudFactory.py | 2 +- .../rayscattering/opencl/CLPhotons.py | 10 ++--- .../rayscattering/opencl/CLScene.py | 13 +++--- .../rayscattering/opencl/__init__.py | 2 +- .../rayscattering/opencl/buffers/__init__.py | 4 +- .../rayscattering/opencl/buffers/solidCL.py | 3 +- .../rayscattering/opencl/buffers/surfaceCL.py | 1 - .../rayscattering/opencl/buffers/vertexCL.py | 2 +- .../rayscattering/opencl/utils/CLKeyLog.py | 3 +- .../opencl/utils/CLParameters.py | 5 +-- .../rayscattering/opencl/utils/__init__.py | 2 +- pytissueoptics/rayscattering/photon.py | 4 +- .../rayscattering/samples/__init__.py | 2 +- pytissueoptics/rayscattering/source.py | 14 +++---- .../rayscattering/statistics/statistics.py | 3 +- .../tests/display/testProfileFactory.py | 6 +-- .../tests/display/testViewFactory.py | 6 +-- .../rayscattering/tests/display/testViewer.py | 15 ++++--- .../tests/energyLogging/testEnergyLogger.py | 10 ++--- .../energyLogging/testPointCloudFactory.py | 2 +- .../tests/opencl/config/testIPPTable.py | 2 +- .../tests/opencl/src/testCLFresnel.py | 6 +-- .../tests/opencl/src/testCLPhoton.py | 8 ++-- .../tests/opencl/src/testCLRandom.py | 4 +- .../opencl/src/testCLScatteringMaterial.py | 2 +- .../tests/opencl/src/testCLSmoothing.py | 2 +- .../tests/opencl/src/testCLVectorOperators.py | 6 +-- .../tests/opencl/testCLKeyLog.py | 8 ++-- .../tests/opencl/testCLPhotons.py | 4 +- .../tests/statistics/testStats.py | 2 +- .../rayscattering/tests/testPhoton.py | 11 +++-- .../tests/testScatteringScene.py | 2 +- .../rayscattering/tests/testSource.py | 8 ++-- .../tests/testSourceAccelerated.py | 7 ++-- .../rayscattering/tests/testUtils.py | 3 +- pytissueoptics/rayscattering/utils.py | 2 +- pytissueoptics/scene/__init__.py | 19 +++++++-- pytissueoptics/scene/geometry/__init__.py | 10 ++--- pytissueoptics/scene/geometry/bbox.py | 1 + pytissueoptics/scene/geometry/polygon.py | 3 +- pytissueoptics/scene/geometry/quad.py | 2 +- .../scene/geometry/surfaceCollection.py | 5 +-- pytissueoptics/scene/geometry/triangle.py | 2 +- pytissueoptics/scene/intersection/__init__.py | 2 +- .../scene/intersection/intersectionFinder.py | 10 ++--- .../intersection/mollerTrumboreIntersect.py | 4 +- .../scene/intersection/raySource.py | 2 + pytissueoptics/scene/loader/loadSolid.py | 2 +- pytissueoptics/scene/loader/loader.py | 2 +- .../scene/loader/parsers/__init__.py | 2 +- pytissueoptics/scene/loader/parsers/parser.py | 2 +- pytissueoptics/scene/logger/__init__.py | 2 +- pytissueoptics/scene/logger/logger.py | 4 +- pytissueoptics/scene/scene/scene.py | 6 +-- pytissueoptics/scene/solids/__init__.py | 12 +++--- pytissueoptics/scene/solids/cone.py | 2 +- pytissueoptics/scene/solids/cube.py | 3 +- pytissueoptics/scene/solids/cuboid.py | 12 +++++- pytissueoptics/scene/solids/cylinder.py | 2 +- pytissueoptics/scene/solids/ellipsoid.py | 2 +- pytissueoptics/scene/solids/lens.py | 6 +-- pytissueoptics/scene/solids/solid.py | 16 ++++++-- pytissueoptics/scene/solids/solidFactory.py | 2 +- .../scene/solids/stack/cuboidStacker.py | 4 +- .../scene/solids/stack/stackResult.py | 5 ++- pytissueoptics/scene/tests/__init__.py | 1 - .../scene/tests/geometry/testBoundingBox.py | 2 +- .../tests/geometry/testSurfaceCollection.py | 3 +- .../scene/tests/geometry/testUtils.py | 4 +- .../benchmarkIntersectionFinder.py | 41 ++++++++++++------- .../intersection/testBoundingBoxIntersect.py | 2 +- .../intersection/testIntersectionFinder.py | 8 ++-- .../intersection/testPolygonIntersect.py | 2 +- .../scene/tests/logger/testLogger.py | 4 +- .../scene/tests/scene/benchmarkScenes.py | 4 +- pytissueoptics/scene/tests/scene/testScene.py | 2 +- .../scene/tests/solids/testCuboid.py | 4 +- .../scene/tests/solids/testCylinder.py | 2 +- .../scene/tests/solids/testEllipsoid.py | 2 +- .../scene/tests/solids/testSolid.py | 13 ++++-- .../scene/tests/solids/testSphere.py | 2 +- .../scene/tests/solids/testThickLens.py | 4 +- .../scene/tests/tree/testSpacePartition.py | 6 +-- .../binary/testSplitConstructor.py | 4 +- .../scene/tests/viewer/testMayaviSolid.py | 3 +- .../scene/tests/viewer/testMayaviViewer.py | 2 +- pytissueoptics/scene/tree/__init__.py | 2 +- pytissueoptics/scene/tree/node.py | 2 +- pytissueoptics/scene/tree/spacePartition.py | 5 +-- .../treeConstructor/binary/SAHSearchResult.py | 2 +- .../tree/treeConstructor/binary/__init__.py | 2 +- .../binary/noSplitOneAxisConstructor.py | 5 +-- .../binary/splitTreeAxesConstructor.py | 4 +- pytissueoptics/scene/viewer/__init__.py | 2 +- pytissueoptics/scene/viewer/displayable.py | 1 + .../scene/viewer/mayavi/MayaviSolid.py | 2 +- .../scene/viewer/mayavi/MayaviViewer.py | 5 +-- .../scene/viewer/mayavi/__init__.py | 4 +- 121 files changed, 348 insertions(+), 282 deletions(-) diff --git a/pytissueoptics/__init__.py b/pytissueoptics/__init__.py index 47a54a43..b8a6ad31 100644 --- a/pytissueoptics/__init__.py +++ b/pytissueoptics/__init__.py @@ -1,5 +1,4 @@ -from .scene import * from .rayscattering import * - +from .scene import * __version__ = "2.0.0b1" diff --git a/pytissueoptics/__main__.py b/pytissueoptics/__main__.py index 7847c648..1589da01 100644 --- a/pytissueoptics/__main__.py +++ b/pytissueoptics/__main__.py @@ -1,6 +1,6 @@ +import argparse import os import sys -import argparse from pytissueoptics import __version__ from pytissueoptics.examples import loadExamples diff --git a/pytissueoptics/examples/__init__.py b/pytissueoptics/examples/__init__.py index 03bc4f48..cd579b0c 100644 --- a/pytissueoptics/examples/__init__.py +++ b/pytissueoptics/examples/__init__.py @@ -1,13 +1,13 @@ +import importlib import os import re import sys -import importlib from dataclasses import dataclass from typing import List from pygments import highlight -from pygments.lexers import PythonLexer from pygments.formatters import TerminalFormatter +from pygments.lexers import PythonLexer EXAMPLE_MODULE = "rayscattering" EXAMPLE_FILE_PATTERN = r"^(ex\d+)\.py$" diff --git a/pytissueoptics/examples/benchmarks/env.py b/pytissueoptics/examples/benchmarks/env.py index ab711b66..1dd49688 100644 --- a/pytissueoptics/examples/benchmarks/env.py +++ b/pytissueoptics/examples/benchmarks/env.py @@ -1,8 +1,9 @@ # We set up the environment so we do not have to install PyTissueOptics to run the examples. # By adjusting the path, we use the current version in development. -import sys import os +import sys + sys.path.insert(0, os.path.dirname( os.path.dirname( diff --git a/pytissueoptics/examples/rayscattering/env.py b/pytissueoptics/examples/rayscattering/env.py index ab711b66..1dd49688 100644 --- a/pytissueoptics/examples/rayscattering/env.py +++ b/pytissueoptics/examples/rayscattering/env.py @@ -1,8 +1,9 @@ # We set up the environment so we do not have to install PyTissueOptics to run the examples. # By adjusting the path, we use the current version in development. -import sys import os +import sys + sys.path.insert(0, os.path.dirname( os.path.dirname( diff --git a/pytissueoptics/examples/rayscattering/ex01.py b/pytissueoptics/examples/rayscattering/ex01.py index f07efb59..483553ff 100644 --- a/pytissueoptics/examples/rayscattering/ex01.py +++ b/pytissueoptics/examples/rayscattering/ex01.py @@ -1,5 +1,5 @@ import env -from pytissueoptics import * +from pytissueoptics import * TITLE = "Divergent source propagation through a multi-layered tissue" diff --git a/pytissueoptics/examples/scene/env.py b/pytissueoptics/examples/scene/env.py index ab711b66..1dd49688 100644 --- a/pytissueoptics/examples/scene/env.py +++ b/pytissueoptics/examples/scene/env.py @@ -1,8 +1,9 @@ # We set up the environment so we do not have to install PyTissueOptics to run the examples. # By adjusting the path, we use the current version in development. -import sys import os +import sys + sys.path.insert(0, os.path.dirname( os.path.dirname( diff --git a/pytissueoptics/examples/scene/example0.py b/pytissueoptics/examples/scene/example0.py index cc3b390b..1f8664e8 100644 --- a/pytissueoptics/examples/scene/example0.py +++ b/pytissueoptics/examples/scene/example0.py @@ -10,7 +10,7 @@ def exampleCode(): - from pytissueoptics.scene import Vector, Cuboid, Sphere, Ellipsoid, MayaviViewer + from pytissueoptics.scene import Cuboid, Ellipsoid, MayaviViewer, Sphere, Vector cuboid = Cuboid(a=1, b=3, c=1, position=Vector(1, 0, 0)) sphere = Sphere(radius=0.5, position=Vector(0, 0, 0)) diff --git a/pytissueoptics/examples/scene/example1.py b/pytissueoptics/examples/scene/example1.py index a01b4d67..1caf9115 100644 --- a/pytissueoptics/examples/scene/example1.py +++ b/pytissueoptics/examples/scene/example1.py @@ -8,7 +8,7 @@ def exampleCode(): - from pytissueoptics.scene import Vector, Cuboid, MayaviViewer + from pytissueoptics.scene import Cuboid, MayaviViewer, Vector centerCube = Cuboid(a=1, b=1, c=1, position=Vector(0, 0, 0)) topCube = Cuboid(a=1, b=1, c=1, position=Vector(0, 2, 0)) diff --git a/pytissueoptics/examples/scene/example2.py b/pytissueoptics/examples/scene/example2.py index 95403094..cf54680b 100644 --- a/pytissueoptics/examples/scene/example2.py +++ b/pytissueoptics/examples/scene/example2.py @@ -7,7 +7,7 @@ def exampleCode(): - from pytissueoptics.scene import Cuboid, Vector, MayaviViewer + from pytissueoptics.scene import Cuboid, MayaviViewer, Vector cuboid1 = Cuboid(1, 1, 1, position=Vector(2, 0, 0)) cuboid2 = Cuboid(2, 1, 1, position=Vector(0, 2, 0)) diff --git a/pytissueoptics/examples/scene/example3.py b/pytissueoptics/examples/scene/example3.py index fe0b4272..9914cfa9 100644 --- a/pytissueoptics/examples/scene/example3.py +++ b/pytissueoptics/examples/scene/example3.py @@ -6,7 +6,7 @@ def exampleCode(): - from pytissueoptics.scene import loadSolid, MayaviViewer + from pytissueoptics.scene import MayaviViewer, loadSolid solid = loadSolid("pytissueoptics/examples/scene/droid.obj") diff --git a/pytissueoptics/examples/scene/example4.py b/pytissueoptics/examples/scene/example4.py index 2368a774..500dae40 100644 --- a/pytissueoptics/examples/scene/example4.py +++ b/pytissueoptics/examples/scene/example4.py @@ -6,7 +6,15 @@ def exampleCode(): - from pytissueoptics.scene import Vector, MayaviViewer, RefractiveMaterial, ThickLens, SymmetricLens, PlanoConvexLens, PlanoConcaveLens + from pytissueoptics.scene import ( + MayaviViewer, + PlanoConcaveLens, + PlanoConvexLens, + RefractiveMaterial, + SymmetricLens, + ThickLens, + Vector, + ) material = RefractiveMaterial(refractiveIndex=1.44) lens1 = ThickLens(30, 60, diameter=25.4, thickness=4, material=material, position=Vector(0, 0, 0)) diff --git a/pytissueoptics/rayscattering/__init__.py b/pytissueoptics/rayscattering/__init__.py index 39e5692b..9e723d5a 100644 --- a/pytissueoptics/rayscattering/__init__.py +++ b/pytissueoptics/rayscattering/__init__.py @@ -1,13 +1,25 @@ -from .photon import Photon -from .materials import ScatteringMaterial +from .display.viewer import Direction, PointCloudStyle, Viewer, ViewGroup, Visibility +from .display.views import ( + View2DProjection, + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSlice, + View2DSliceX, + View2DSliceY, + View2DSliceZ, + View2DSurface, + View2DSurfaceX, + View2DSurfaceY, + View2DSurfaceZ, +) from .energyLogging import EnergyLogger -from .source import PencilPointSource, IsotropicPointSource, DirectionalSource, DivergentSource +from .materials import ScatteringMaterial +from .opencl import CONFIG, hardwareAccelerationIsAvailable +from .photon import Photon from .scatteringScene import ScatteringScene +from .source import DirectionalSource, DivergentSource, IsotropicPointSource, PencilPointSource from .statistics import Stats -from .display.viewer import Viewer, PointCloudStyle, Visibility, ViewGroup, Direction -from .display.views import View2DProjection, View2DProjectionX, View2DProjectionY, View2DProjectionZ, \ - View2DSurface, View2DSurfaceX, View2DSurfaceY, View2DSurfaceZ, View2DSlice, View2DSliceX, View2DSliceY, View2DSliceZ -from .opencl import hardwareAccelerationIsAvailable, CONFIG __all__ = ["Photon", "ScatteringMaterial", "PencilPointSource", "IsotropicPointSource", "DirectionalSource", "DivergentSource", "EnergyLogger", "ScatteringScene", "Viewer", "PointCloudStyle", "Visibility", "ViewGroup", diff --git a/pytissueoptics/rayscattering/display/profiles/profileFactory.py b/pytissueoptics/rayscattering/display/profiles/profileFactory.py index eb6dc60d..ea93bd4c 100644 --- a/pytissueoptics/rayscattering/display/profiles/profileFactory.py +++ b/pytissueoptics/rayscattering/display/profiles/profileFactory.py @@ -3,11 +3,10 @@ import numpy as np from pytissueoptics.rayscattering import utils -from pytissueoptics.rayscattering.energyLogging import EnergyLogger -from pytissueoptics.rayscattering.energyLogging import PointCloudFactory -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.display.profiles import Profile1D from pytissueoptics.rayscattering.display.utils import Direction +from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloudFactory +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene class ProfileFactory: diff --git a/pytissueoptics/rayscattering/display/utils/__init__.py b/pytissueoptics/rayscattering/display/utils/__init__.py index e9cb2533..b14362c0 100644 --- a/pytissueoptics/rayscattering/display/utils/__init__.py +++ b/pytissueoptics/rayscattering/display/utils/__init__.py @@ -1,2 +1,2 @@ -from .direction import Direction, DEFAULT_X_VIEW_DIRECTIONS, DEFAULT_Y_VIEW_DIRECTIONS, DEFAULT_Z_VIEW_DIRECTIONS +from .direction import DEFAULT_X_VIEW_DIRECTIONS, DEFAULT_Y_VIEW_DIRECTIONS, DEFAULT_Z_VIEW_DIRECTIONS, Direction from .volumeSlicer import VolumeSlicer diff --git a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py index 087e37e1..71c19d8b 100644 --- a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py +++ b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py @@ -3,18 +3,13 @@ # License: BSD Style. import numpy as np - -from traits.api import HasTraits, Instance, Array, \ - on_trait_change -from traitsui.api import View, Item, HGroup, Group - -from tvtk.api import tvtk -from tvtk.pyface.scene import Scene - from mayavi import mlab from mayavi.core.api import PipelineBase, Source -from mayavi.core.ui.api import SceneEditor, MayaviScene, \ - MlabSceneModel +from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor +from traits.api import Array, HasTraits, Instance, on_trait_change +from traitsui.api import Group, HGroup, Item, View +from tvtk.api import tvtk +from tvtk.pyface.scene import Scene try: #--------------------------------------------------------------------------- diff --git a/pytissueoptics/rayscattering/display/viewer.py b/pytissueoptics/rayscattering/display/viewer.py index 358ef5fe..91bc30cd 100644 --- a/pytissueoptics/rayscattering/display/viewer.py +++ b/pytissueoptics/rayscattering/display/viewer.py @@ -4,15 +4,14 @@ import numpy as np from pytissueoptics.rayscattering import utils -from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.display.profiles import ProfileFactory +from pytissueoptics.rayscattering.display.utils import Direction +from pytissueoptics.rayscattering.display.views import View2D, ViewGroup +from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloudFactory from pytissueoptics.rayscattering.energyLogging.pointCloud import PointCloud -from pytissueoptics.rayscattering.energyLogging import PointCloudFactory -from pytissueoptics.rayscattering.source import Source from pytissueoptics.rayscattering.scatteringScene import ScatteringScene +from pytissueoptics.rayscattering.source import Source from pytissueoptics.rayscattering.statistics import Stats -from pytissueoptics.rayscattering.display.utils import Direction -from pytissueoptics.rayscattering.display.views import ViewGroup, View2D -from pytissueoptics.rayscattering.display.profiles import ProfileFactory from pytissueoptics.scene import MAYAVI_AVAILABLE, MayaviViewer, ViewPointStyle diff --git a/pytissueoptics/rayscattering/display/views/__init__.py b/pytissueoptics/rayscattering/display/views/__init__.py index 1d4d16a2..43b05af3 100644 --- a/pytissueoptics/rayscattering/display/views/__init__.py +++ b/pytissueoptics/rayscattering/display/views/__init__.py @@ -1,4 +1,16 @@ -from .view2D import ViewGroup, View2D +from .defaultViews import ( + View2DProjection, + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSlice, + View2DSliceX, + View2DSliceY, + View2DSliceZ, + View2DSurface, + View2DSurfaceX, + View2DSurfaceY, + View2DSurfaceZ, +) +from .view2D import View2D, ViewGroup from .viewFactory import ViewFactory -from .defaultViews import View2DProjection, View2DProjectionX, View2DProjectionY, View2DProjectionZ, \ - View2DSurface, View2DSurfaceX, View2DSurfaceY, View2DSurfaceZ, View2DSlice, View2DSliceX, View2DSliceY, View2DSliceZ diff --git a/pytissueoptics/rayscattering/display/views/defaultViews.py b/pytissueoptics/rayscattering/display/views/defaultViews.py index 815c2ff0..136e4f33 100644 --- a/pytissueoptics/rayscattering/display/views/defaultViews.py +++ b/pytissueoptics/rayscattering/display/views/defaultViews.py @@ -1,9 +1,9 @@ -from typing import Tuple, Union, List +from typing import List, Tuple, Union import numpy as np -from pytissueoptics.rayscattering.display.views.view2D import View2D, ViewGroup from pytissueoptics.rayscattering.display.utils.direction import * +from pytissueoptics.rayscattering.display.views.view2D import View2D, ViewGroup class View2DProjection(View2D): diff --git a/pytissueoptics/rayscattering/display/views/view2D.py b/pytissueoptics/rayscattering/display/views/view2D.py index d1e118d8..3763919e 100644 --- a/pytissueoptics/rayscattering/display/views/view2D.py +++ b/pytissueoptics/rayscattering/display/views/view2D.py @@ -1,6 +1,6 @@ import copy from enum import Flag -from typing import Tuple, Union, Optional, List +from typing import List, Optional, Tuple, Union import matplotlib import numpy as np diff --git a/pytissueoptics/rayscattering/display/views/viewFactory.py b/pytissueoptics/rayscattering/display/views/viewFactory.py index 1f906084..3fec5da2 100644 --- a/pytissueoptics/rayscattering/display/views/viewFactory.py +++ b/pytissueoptics/rayscattering/display/views/viewFactory.py @@ -1,12 +1,18 @@ -from typing import Union, List, Tuple +from typing import List, Tuple, Union import numpy as np -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.rayscattering.display.views.view2D import ViewGroup, View2D -from pytissueoptics.rayscattering.display.views.defaultViews import View2DProjectionX, View2DProjectionY, View2DProjectionZ, \ - View2DSurfaceX, View2DSurfaceY, View2DSurfaceZ from pytissueoptics.rayscattering import utils +from pytissueoptics.rayscattering.display.views.defaultViews import ( + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSurfaceX, + View2DSurfaceY, + View2DSurfaceZ, +) +from pytissueoptics.rayscattering.display.views.view2D import View2D, ViewGroup +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene class ViewFactory: diff --git a/pytissueoptics/rayscattering/energyLogging/__init__.py b/pytissueoptics/rayscattering/energyLogging/__init__.py index 247d61e6..d7131696 100644 --- a/pytissueoptics/rayscattering/energyLogging/__init__.py +++ b/pytissueoptics/rayscattering/energyLogging/__init__.py @@ -1,3 +1,3 @@ +from .energyLogger import EnergyLogger from .pointCloud import PointCloud from .pointCloudFactory import PointCloudFactory -from .energyLogger import EnergyLogger diff --git a/pytissueoptics/rayscattering/energyLogging/energyLogger.py b/pytissueoptics/rayscattering/energyLogging/energyLogger.py index e2112c06..21adb9a1 100644 --- a/pytissueoptics/rayscattering/energyLogging/energyLogger.py +++ b/pytissueoptics/rayscattering/energyLogging/energyLogger.py @@ -1,15 +1,15 @@ import os import pickle -from typing import Union, List +from typing import List, Union import numpy as np from pytissueoptics.rayscattering import utils -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.rayscattering.display.views.view2D import ViewGroup, View2D +from pytissueoptics.rayscattering.display.views.view2D import View2D, ViewGroup from pytissueoptics.rayscattering.display.views.viewFactory import ViewFactory -from pytissueoptics.scene.logger.logger import Logger, InteractionKey +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.scene.geometry import Vector +from pytissueoptics.scene.logger.logger import InteractionKey, Logger class EnergyLogger(Logger): diff --git a/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py b/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py index ed0a93ee..55f8af2d 100644 --- a/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py +++ b/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py @@ -1,7 +1,7 @@ import numpy as np -from pytissueoptics.scene.logger import Logger, InteractionKey from pytissueoptics.rayscattering.energyLogging import PointCloud +from pytissueoptics.scene.logger import InteractionKey, Logger class PointCloudFactory: diff --git a/pytissueoptics/rayscattering/opencl/CLPhotons.py b/pytissueoptics/rayscattering/opencl/CLPhotons.py index 04658844..95bef074 100644 --- a/pytissueoptics/rayscattering/opencl/CLPhotons.py +++ b/pytissueoptics/rayscattering/opencl/CLPhotons.py @@ -4,15 +4,15 @@ import numpy as np from pytissueoptics.rayscattering.opencl import WEIGHT_THRESHOLD -from pytissueoptics.rayscattering.opencl.utils import CLKeyLog, CLParameters, BatchTiming -from pytissueoptics.rayscattering.opencl.CLScene import CLScene -from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram -from pytissueoptics.rayscattering.opencl.buffers.seedCL import SeedCL from pytissueoptics.rayscattering.opencl.buffers.dataPointCL import DataPointCL from pytissueoptics.rayscattering.opencl.buffers.photonCL import PhotonCL +from pytissueoptics.rayscattering.opencl.buffers.seedCL import SeedCL +from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram +from pytissueoptics.rayscattering.opencl.CLScene import CLScene +from pytissueoptics.rayscattering.opencl.utils import BatchTiming, CLKeyLog, CLParameters from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.scene.logger.logger import Logger from pytissueoptics.scene.geometry import Environment +from pytissueoptics.scene.logger.logger import Logger PROPAGATION_SOURCE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src', 'propagation.c') diff --git a/pytissueoptics/rayscattering/opencl/CLScene.py b/pytissueoptics/rayscattering/opencl/CLScene.py index 31ce44ee..5c69d575 100644 --- a/pytissueoptics/rayscattering/opencl/CLScene.py +++ b/pytissueoptics/rayscattering/opencl/CLScene.py @@ -2,15 +2,14 @@ import numpy as np -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.rayscattering.opencl.buffers import SolidCLInfo, \ - SurfaceCLInfo, TriangleCLInfo +from pytissueoptics.rayscattering.opencl.buffers import SolidCLInfo, SurfaceCLInfo, TriangleCLInfo +from pytissueoptics.rayscattering.opencl.buffers.materialCL import MaterialCL from pytissueoptics.rayscattering.opencl.buffers.solidCandidateCL import SolidCandidateCL -from pytissueoptics.rayscattering.opencl.buffers.vertexCL import VertexCL -from pytissueoptics.rayscattering.opencl.buffers.triangleCL import TriangleCL -from pytissueoptics.rayscattering.opencl.buffers.surfaceCL import SurfaceCL from pytissueoptics.rayscattering.opencl.buffers.solidCL import SolidCL -from pytissueoptics.rayscattering.opencl.buffers.materialCL import MaterialCL +from pytissueoptics.rayscattering.opencl.buffers.surfaceCL import SurfaceCL +from pytissueoptics.rayscattering.opencl.buffers.triangleCL import TriangleCL +from pytissueoptics.rayscattering.opencl.buffers.vertexCL import VertexCL +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene NO_LOG_ID = 0 NO_SOLID_ID = -1 diff --git a/pytissueoptics/rayscattering/opencl/__init__.py b/pytissueoptics/rayscattering/opencl/__init__.py index 92c65472..646ba690 100644 --- a/pytissueoptics/rayscattering/opencl/__init__.py +++ b/pytissueoptics/rayscattering/opencl/__init__.py @@ -1,4 +1,4 @@ -from pytissueoptics.rayscattering.opencl.config.CLConfig import warnings, CLConfig, OPENCL_AVAILABLE, WEIGHT_THRESHOLD +from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_AVAILABLE, WEIGHT_THRESHOLD, CLConfig, warnings from pytissueoptics.rayscattering.opencl.config.IPPTable import IPPTable OPENCL_OK = True diff --git a/pytissueoptics/rayscattering/opencl/buffers/__init__.py b/pytissueoptics/rayscattering/opencl/buffers/__init__.py index a4544ef6..6abbafa7 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/__init__.py +++ b/pytissueoptics/rayscattering/opencl/buffers/__init__.py @@ -1,10 +1,8 @@ -from .CLObject import CLObject, EmptyBuffer, RandomBuffer, BufferOf - +from .CLObject import BufferOf, CLObject, EmptyBuffer, RandomBuffer from .dataPointCL import DataPointCL from .materialCL import MaterialCL from .photonCL import PhotonCL from .seedCL import SeedCL - from .solidCandidateCL import SolidCandidateCL from .solidCL import SolidCL, SolidCLInfo from .surfaceCL import SurfaceCL, SurfaceCLInfo diff --git a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py index b682e601..ad79808c 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py @@ -1,8 +1,7 @@ from typing import List, NamedTuple -from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.rayscattering.opencl.buffers.CLObject import * - +from pytissueoptics.scene.geometry import BoundingBox SolidCLInfo = NamedTuple("SolidInfo", [("bbox", BoundingBox), ("firstSurfaceID", int), ("lastSurfaceID", int)]) diff --git a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py index 52e43074..40185fd2 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py @@ -2,7 +2,6 @@ from pytissueoptics.rayscattering.opencl.buffers.CLObject import * - SurfaceCLInfo = NamedTuple("SurfaceInfo", [("firstPolygonID", int), ("lastPolygonID", int), ("insideMaterialID", int), ("outsideMaterialID", int), ("insideSolidID", int), ("outsideSolidID", int), diff --git a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py index 9c2b58be..bc7fb5f0 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py @@ -1,7 +1,7 @@ from typing import List -from pytissueoptics.scene.geometry import Vertex from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +from pytissueoptics.scene.geometry import Vertex class VertexCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py b/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py index 23aa7803..6e39ba2e 100644 --- a/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py +++ b/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py @@ -3,10 +3,9 @@ import numpy as np -from pytissueoptics.rayscattering.opencl.CLScene import CLScene, NO_LOG_ID, NO_SOLID_LABEL +from pytissueoptics.rayscattering.opencl.CLScene import NO_LOG_ID, NO_SOLID_LABEL, CLScene from pytissueoptics.scene.logger import InteractionKey, Logger - SOLID_ID_COL = 4 SURFACE_ID_COL = 5 diff --git a/pytissueoptics/rayscattering/opencl/utils/CLParameters.py b/pytissueoptics/rayscattering/opencl/utils/CLParameters.py index b0d783c4..04a59628 100644 --- a/pytissueoptics/rayscattering/opencl/utils/CLParameters.py +++ b/pytissueoptics/rayscattering/opencl/utils/CLParameters.py @@ -1,9 +1,8 @@ -import psutil import numpy as np +import psutil -from pytissueoptics.rayscattering.opencl.buffers import DataPointCL from pytissueoptics.rayscattering.opencl import CONFIG, warnings - +from pytissueoptics.rayscattering.opencl.buffers import DataPointCL DATAPOINT_SIZE = DataPointCL.getItemSize() diff --git a/pytissueoptics/rayscattering/opencl/utils/__init__.py b/pytissueoptics/rayscattering/opencl/utils/__init__.py index a269c4db..d06aebb0 100644 --- a/pytissueoptics/rayscattering/opencl/utils/__init__.py +++ b/pytissueoptics/rayscattering/opencl/utils/__init__.py @@ -1,3 +1,3 @@ +from .batchTiming import BatchTiming from .CLKeyLog import CLKeyLog from .CLParameters import CLParameters -from .batchTiming import BatchTiming diff --git a/pytissueoptics/rayscattering/photon.py b/pytissueoptics/rayscattering/photon.py index 7945762f..ce0c0387 100644 --- a/pytissueoptics/rayscattering/photon.py +++ b/pytissueoptics/rayscattering/photon.py @@ -8,8 +8,8 @@ from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.scene.geometry import Environment, Vector from pytissueoptics.scene.intersection import Ray -from pytissueoptics.scene.intersection.intersectionFinder import IntersectionFinder, Intersection -from pytissueoptics.scene.logger import Logger, InteractionKey +from pytissueoptics.scene.intersection.intersectionFinder import Intersection, IntersectionFinder +from pytissueoptics.scene.logger import InteractionKey, Logger WEIGHT_THRESHOLD = 1e-4 MIN_ANGLE = 0.0001 diff --git a/pytissueoptics/rayscattering/samples/__init__.py b/pytissueoptics/rayscattering/samples/__init__.py index af4c6c6c..1bb84225 100644 --- a/pytissueoptics/rayscattering/samples/__init__.py +++ b/pytissueoptics/rayscattering/samples/__init__.py @@ -1,2 +1,2 @@ -from .phantomTissue import PhantomTissue from .infiniteTissue import InfiniteTissue +from .phantomTissue import PhantomTissue diff --git a/pytissueoptics/rayscattering/source.py b/pytissueoptics/rayscattering/source.py index 69318f08..8e131a11 100644 --- a/pytissueoptics/rayscattering/source.py +++ b/pytissueoptics/rayscattering/source.py @@ -1,24 +1,24 @@ import hashlib import random import time -from typing import List, Union, Optional, Tuple +from typing import List, Optional, Tuple, Union + import numpy as np from pytissueoptics.rayscattering import utils from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.opencl import CONFIG, IPPTable, validateOpenCL, warnings from pytissueoptics.rayscattering.opencl.CLPhotons import CLPhotons -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.photon import Photon -from pytissueoptics.rayscattering.opencl import IPPTable, CONFIG, validateOpenCL, warnings -from pytissueoptics.scene.solids import Sphere -from pytissueoptics.scene.geometry import Vector, Environment +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene +from pytissueoptics.scene.geometry import Environment, Vector from pytissueoptics.scene.intersection import FastIntersectionFinder from pytissueoptics.scene.logger import Logger +from pytissueoptics.scene.solids import Sphere from pytissueoptics.scene.solids.cone import Cone from pytissueoptics.scene.solids.cylinder import Cylinder from pytissueoptics.scene.utils import progressBar -from pytissueoptics.scene.viewer import MayaviViewer -from pytissueoptics.scene.viewer import Displayable +from pytissueoptics.scene.viewer import Displayable, MayaviViewer class Source(Displayable): diff --git a/pytissueoptics/rayscattering/statistics/statistics.py b/pytissueoptics/rayscattering/statistics/statistics.py index d2a39bfb..85b9eaf9 100644 --- a/pytissueoptics/rayscattering/statistics/statistics.py +++ b/pytissueoptics/rayscattering/statistics/statistics.py @@ -6,9 +6,8 @@ from pytissueoptics.rayscattering import utils from pytissueoptics.rayscattering.display.views.defaultViews import View2DProjection -from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloud, PointCloudFactory from pytissueoptics.rayscattering.opencl.CLScene import NO_SOLID_LABEL -from pytissueoptics.rayscattering.energyLogging import PointCloud, PointCloudFactory @dataclass diff --git a/pytissueoptics/rayscattering/tests/display/testProfileFactory.py b/pytissueoptics/rayscattering/tests/display/testProfileFactory.py index 1a4d5513..9e1a35b1 100644 --- a/pytissueoptics/rayscattering/tests/display/testProfileFactory.py +++ b/pytissueoptics/rayscattering/tests/display/testProfileFactory.py @@ -4,13 +4,13 @@ from mockito import mock, when from pytissueoptics import View2DProjectionX -from pytissueoptics.scene.solids import Cube, Sphere -from pytissueoptics.rayscattering.materials import ScatteringMaterial -from pytissueoptics.rayscattering.display.profiles import ProfileFactory, Profile1D +from pytissueoptics.rayscattering.display.profiles import Profile1D, ProfileFactory from pytissueoptics.rayscattering.display.utils import Direction from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.scene.logger import InteractionKey +from pytissueoptics.scene.solids import Cube, Sphere class TestProfileFactory(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/tests/display/testViewFactory.py b/pytissueoptics/rayscattering/tests/display/testViewFactory.py index e3b475fa..e0ec81ca 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewFactory.py +++ b/pytissueoptics/rayscattering/tests/display/testViewFactory.py @@ -1,11 +1,11 @@ import unittest -from mockito import mock, when, verify +from mockito import mock, verify, when -from pytissueoptics.scene.solids import Sphere, Cube +from pytissueoptics.rayscattering.display.views import * from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.rayscattering.display.views import * +from pytissueoptics.scene.solids import Cube, Sphere class TestViewFactory(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/tests/display/testViewer.py b/pytissueoptics/rayscattering/tests/display/testViewer.py index 16ef68da..37a74d67 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewer.py +++ b/pytissueoptics/rayscattering/tests/display/testViewer.py @@ -3,18 +3,17 @@ from unittest.mock import patch import numpy as np -from mockito import mock, when, verify, ANY +from mockito import ANY, mock, verify, when from pytissueoptics import Direction, View2DProjectionX, ViewGroup -from pytissueoptics.scene.logger import Logger -from pytissueoptics.scene.geometry import BoundingBox -from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.display.profiles import Profile1D, ProfileFactory +from pytissueoptics.rayscattering.display.viewer import PointCloudStyle, Viewer, Visibility +from pytissueoptics.rayscattering.display.views import View2D +from pytissueoptics.rayscattering.energyLogging import EnergyLogger, PointCloud, PointCloudFactory from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.source import Source -from pytissueoptics.rayscattering.display.viewer import Viewer, Visibility, PointCloudStyle -from pytissueoptics.rayscattering.display.profiles import ProfileFactory, Profile1D -from pytissueoptics.rayscattering.display.views import View2D -from pytissueoptics.rayscattering.energyLogging import PointCloudFactory, PointCloud +from pytissueoptics.scene.geometry import BoundingBox +from pytissueoptics.scene.logger import Logger def patchMayaviRender(func): diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py index 9816b655..f1cef977 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py @@ -2,18 +2,18 @@ import os import tempfile import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import numpy as np -from pytissueoptics.rayscattering.materials import ScatteringMaterial -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene -from pytissueoptics.rayscattering.energyLogging import EnergyLogger from pytissueoptics.rayscattering.display.utils import Direction from pytissueoptics.rayscattering.display.views import * -from pytissueoptics.scene.solids import Cube +from pytissueoptics.rayscattering.energyLogging import EnergyLogger +from pytissueoptics.rayscattering.materials import ScatteringMaterial +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.scene.geometry import Vector from pytissueoptics.scene.logger import InteractionKey +from pytissueoptics.scene.solids import Cube class TestEnergyLogger(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py b/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py index ff7d3c81..fc17b522 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py @@ -3,7 +3,7 @@ import numpy as np from pytissueoptics.rayscattering.energyLogging import PointCloudFactory -from pytissueoptics.scene.logger import Logger, InteractionKey +from pytissueoptics.scene.logger import InteractionKey, Logger class TestPointCloudFactory(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py b/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py index 74721454..1b7d46bc 100644 --- a/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py +++ b/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py @@ -3,7 +3,7 @@ import tempfile import unittest -from pytissueoptics.rayscattering.opencl.config.IPPTable import IPPTable, DEFAULT_IPP +from pytissueoptics.rayscattering.opencl.config.IPPTable import DEFAULT_IPP, IPPTable def tempTablePath(func): diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py index 9372779d..3362d4e1 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py @@ -5,10 +5,10 @@ import numpy as np -from pytissueoptics import Vector, ScatteringMaterial +from pytissueoptics import ScatteringMaterial, Vector from pytissueoptics.rayscattering.opencl import OPENCL_AVAILABLE, OPENCL_OK +from pytissueoptics.rayscattering.opencl.buffers import MaterialCL, SeedCL, SurfaceCL, SurfaceCLInfo from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -from pytissueoptics.rayscattering.opencl.buffers import MaterialCL, SurfaceCL, SurfaceCLInfo, SeedCL from pytissueoptics.rayscattering.tests.opencl.src.CLObjects import IntersectionCL if OPENCL_AVAILABLE: @@ -16,8 +16,8 @@ else: cl = None -from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram from pytissueoptics.rayscattering.opencl.buffers import CLObject +from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram @dataclass diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py index 85aae9f8..41b0ab46 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py @@ -5,12 +5,12 @@ import numpy as np -from pytissueoptics import Vector, ScatteringMaterial, ScatteringScene +from pytissueoptics import ScatteringMaterial, ScatteringScene, Vector from pytissueoptics.rayscattering.opencl import OPENCL_AVAILABLE, OPENCL_OK -from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram -from pytissueoptics.rayscattering.opencl.CLScene import NO_SURFACE_ID, NO_LOG_ID, NO_SOLID_ID, CLScene from pytissueoptics.rayscattering.opencl.buffers import * +from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram +from pytissueoptics.rayscattering.opencl.CLScene import NO_LOG_ID, NO_SOLID_ID, NO_SURFACE_ID, CLScene +from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR from pytissueoptics.scene.geometry import Vertex if OPENCL_AVAILABLE: diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py index 1f3f79fc..ec3b0c5b 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py @@ -4,9 +4,9 @@ import numpy as np from pytissueoptics.rayscattering.opencl import OPENCL_OK -from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -from pytissueoptics.rayscattering.opencl.buffers import SeedCL, EmptyBuffer +from pytissueoptics.rayscattering.opencl.buffers import EmptyBuffer, SeedCL from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram +from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR @unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py index 4443942a..b5c35ce7 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py @@ -5,9 +5,9 @@ import numpy as np from pytissueoptics.rayscattering.opencl import OPENCL_OK -from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR from pytissueoptics.rayscattering.opencl.buffers import BufferOf, EmptyBuffer, RandomBuffer from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram +from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR @unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py index b9fe36e7..e1efcda6 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py @@ -7,11 +7,11 @@ from pytissueoptics import * from pytissueoptics.rayscattering.opencl import OPENCL_OK from pytissueoptics.rayscattering.opencl.buffers import * +from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR from pytissueoptics.rayscattering.tests.opencl.src.CLObjects import IntersectionCL, RayCL from pytissueoptics.scene.geometry.triangle import Triangle from pytissueoptics.scene.geometry.vertex import Vertex -from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram @unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py index a2c6543c..1b59ee5d 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py @@ -5,11 +5,11 @@ import numpy as np from numpy.lib import recfunctions as rfn -from pytissueoptics.scene.geometry import Vector from pytissueoptics.rayscattering.opencl import OPENCL_AVAILABLE, OPENCL_OK -from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram from pytissueoptics.rayscattering.opencl.buffers import BufferOf +from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram +from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR +from pytissueoptics.scene.geometry import Vector if OPENCL_AVAILABLE: import pyopencl as cl diff --git a/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py b/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py index c3680941..33a6d565 100644 --- a/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py +++ b/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py @@ -1,12 +1,12 @@ import unittest import numpy as np -from mockito import mock, when, verify, arg_that +from mockito import arg_that, mock, verify, when -from pytissueoptics import Cube, ScatteringMaterial, Sphere, ScatteringScene, EnergyLogger -from pytissueoptics.rayscattering.opencl.utils import CLKeyLog +from pytissueoptics import Cube, EnergyLogger, ScatteringMaterial, ScatteringScene, Sphere from pytissueoptics.rayscattering.opencl import OPENCL_OK -from pytissueoptics.rayscattering.opencl.CLScene import CLScene, NO_LOG_ID, NO_SOLID_ID, NO_SURFACE_ID, NO_SOLID_LABEL +from pytissueoptics.rayscattering.opencl.CLScene import NO_LOG_ID, NO_SOLID_ID, NO_SOLID_LABEL, NO_SURFACE_ID, CLScene +from pytissueoptics.rayscattering.opencl.utils import CLKeyLog from pytissueoptics.scene.logger import InteractionKey diff --git a/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py b/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py index 06ba1166..19568c24 100644 --- a/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py +++ b/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py @@ -2,10 +2,10 @@ import numpy as np -from pytissueoptics import ScatteringScene, ScatteringMaterial, EnergyLogger, Cube +from pytissueoptics import Cube, EnergyLogger, ScatteringMaterial, ScatteringScene +from pytissueoptics.rayscattering.opencl import OPENCL_OK, WEIGHT_THRESHOLD from pytissueoptics.rayscattering.opencl.CLPhotons import CLPhotons from pytissueoptics.scene.geometry import Environment -from pytissueoptics.rayscattering.opencl import WEIGHT_THRESHOLD, OPENCL_OK from pytissueoptics.scene.logger import InteractionKey diff --git a/pytissueoptics/rayscattering/tests/statistics/testStats.py b/pytissueoptics/rayscattering/tests/statistics/testStats.py index 39861d77..1f07b937 100644 --- a/pytissueoptics/rayscattering/tests/statistics/testStats.py +++ b/pytissueoptics/rayscattering/tests/statistics/testStats.py @@ -6,8 +6,8 @@ from unittest.mock import patch from pytissueoptics.rayscattering.energyLogging import EnergyLogger -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.materials import ScatteringMaterial +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.statistics import Stats from pytissueoptics.scene.geometry import Vector from pytissueoptics.scene.logger import InteractionKey diff --git a/pytissueoptics/rayscattering/tests/testPhoton.py b/pytissueoptics/rayscattering/tests/testPhoton.py index 982dbed5..957c2968 100644 --- a/pytissueoptics/rayscattering/tests/testPhoton.py +++ b/pytissueoptics/rayscattering/tests/testPhoton.py @@ -3,21 +3,20 @@ import unittest from unittest.mock import patch -from mockito import mock, when, verify +from mockito import mock, verify, when from pytissueoptics.rayscattering import Photon -from pytissueoptics.rayscattering.photon import WEIGHT_THRESHOLD -from pytissueoptics.rayscattering.fresnel import FresnelIntersection, FresnelIntersect +from pytissueoptics.rayscattering.fresnel import FresnelIntersect, FresnelIntersection from pytissueoptics.rayscattering.materials import ScatteringMaterial -from pytissueoptics.scene import Vector, Logger -from pytissueoptics.scene.geometry import Environment, Triangle, Polygon +from pytissueoptics.rayscattering.photon import WEIGHT_THRESHOLD +from pytissueoptics.scene import Logger, Vector +from pytissueoptics.scene.geometry import Environment, Polygon, Triangle from pytissueoptics.scene.geometry.polygon import WORLD_LABEL from pytissueoptics.scene.intersection.intersectionFinder import Intersection, IntersectionFinder from pytissueoptics.scene.intersection.mollerTrumboreIntersect import MollerTrumboreIntersect from pytissueoptics.scene.logger import InteractionKey from pytissueoptics.scene.solids import Solid - EPS = MollerTrumboreIntersect.EPS_CATCH diff --git a/pytissueoptics/rayscattering/tests/testScatteringScene.py b/pytissueoptics/rayscattering/tests/testScatteringScene.py index dcdd9158..683d0004 100644 --- a/pytissueoptics/rayscattering/tests/testScatteringScene.py +++ b/pytissueoptics/rayscattering/tests/testScatteringScene.py @@ -4,8 +4,8 @@ from mockito import mock, verify, when -from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.rayscattering.materials import ScatteringMaterial +from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.scene.solids import Cuboid from pytissueoptics.scene.viewer import MayaviViewer diff --git a/pytissueoptics/rayscattering/tests/testSource.py b/pytissueoptics/rayscattering/tests/testSource.py index da1b57f8..ad0b08d0 100644 --- a/pytissueoptics/rayscattering/tests/testSource.py +++ b/pytissueoptics/rayscattering/tests/testSource.py @@ -3,15 +3,15 @@ import unittest import numpy as np -from mockito import mock, when, verify +from mockito import mock, verify, when -from pytissueoptics.rayscattering import PencilPointSource, Photon, EnergyLogger +from pytissueoptics.rayscattering import EnergyLogger, PencilPointSource, Photon from pytissueoptics.rayscattering.materials import ScatteringMaterial -from pytissueoptics.rayscattering.source import Source, IsotropicPointSource, DirectionalSource, DivergentSource from pytissueoptics.rayscattering.scatteringScene import ScatteringScene +from pytissueoptics.rayscattering.source import DirectionalSource, DivergentSource, IsotropicPointSource, Source from pytissueoptics.scene.geometry import Environment, Vector -from pytissueoptics.scene.solids import Solid from pytissueoptics.scene.logger import Logger +from pytissueoptics.scene.solids import Solid class TestSource(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/tests/testSourceAccelerated.py b/pytissueoptics/rayscattering/tests/testSourceAccelerated.py index 116c611e..73b4b819 100644 --- a/pytissueoptics/rayscattering/tests/testSourceAccelerated.py +++ b/pytissueoptics/rayscattering/tests/testSourceAccelerated.py @@ -6,12 +6,11 @@ import numpy as np from mockito import mock, verify, when -from pytissueoptics import Vector, ScatteringScene, ScatteringMaterial, EnergyLogger, Logger -from pytissueoptics.rayscattering.opencl import CONFIG, OPENCL_OK -from pytissueoptics.rayscattering.source import Source +from pytissueoptics import EnergyLogger, Logger, ScatteringMaterial, ScatteringScene, Vector +from pytissueoptics.rayscattering.opencl import CONFIG, OPENCL_OK, IPPTable from pytissueoptics.rayscattering.opencl.CLPhotons import CLPhotons +from pytissueoptics.rayscattering.source import Source from pytissueoptics.scene.geometry import Environment -from pytissueoptics.rayscattering.opencl import IPPTable def tempTablePath(func): diff --git a/pytissueoptics/rayscattering/tests/testUtils.py b/pytissueoptics/rayscattering/tests/testUtils.py index 748647c4..a452fdf8 100644 --- a/pytissueoptics/rayscattering/tests/testUtils.py +++ b/pytissueoptics/rayscattering/tests/testUtils.py @@ -2,8 +2,7 @@ import numpy as np -from pytissueoptics.rayscattering.utils import logNorm, labelsEqual, \ - labelContained +from pytissueoptics.rayscattering.utils import labelContained, labelsEqual, logNorm class TestLogNorm(unittest.TestCase): diff --git a/pytissueoptics/rayscattering/utils.py b/pytissueoptics/rayscattering/utils.py index 310994fb..69801acd 100644 --- a/pytissueoptics/rayscattering/utils.py +++ b/pytissueoptics/rayscattering/utils.py @@ -1,8 +1,8 @@ +import warnings from typing import List import numpy as np -import warnings warnings.formatwarning = lambda msg, *args, **kwargs: f'{msg}\n' warn = warnings.warn diff --git a/pytissueoptics/scene/__init__.py b/pytissueoptics/scene/__init__.py index 75e538f4..6e3c82ae 100644 --- a/pytissueoptics/scene/__init__.py +++ b/pytissueoptics/scene/__init__.py @@ -1,10 +1,21 @@ -from .solids import Cuboid, Cube, Sphere, Ellipsoid, Cylinder, Cone, ThickLens, SymmetricLens, PlanoConvexLens, PlanoConcaveLens from .geometry import Vector -from .scene import Scene from .loader import Loader, loadSolid -from .viewer import MayaviViewer, MAYAVI_AVAILABLE, ViewPointStyle -from .logger import Logger, InteractionKey +from .logger import InteractionKey, Logger from .material import RefractiveMaterial +from .scene import Scene +from .solids import ( + Cone, + Cube, + Cuboid, + Cylinder, + Ellipsoid, + PlanoConcaveLens, + PlanoConvexLens, + Sphere, + SymmetricLens, + ThickLens, +) +from .viewer import MAYAVI_AVAILABLE, MayaviViewer, ViewPointStyle __all__ = ["Cuboid", "Cube", "Sphere", "Ellipsoid", "Cylinder", "Cone", "Vector", "Scene", "Loader", "loadSolid", "MayaviViewer", "MAYAVI_AVAILABLE", "ViewPointStyle", "Logger", "InteractionKey", diff --git a/pytissueoptics/scene/geometry/__init__.py b/pytissueoptics/scene/geometry/__init__.py index 09f7e4d5..828a4083 100644 --- a/pytissueoptics/scene/geometry/__init__.py +++ b/pytissueoptics/scene/geometry/__init__.py @@ -1,8 +1,8 @@ -from .vector import Vector -from .vertex import Vertex from .bbox import BoundingBox -from .polygon import Polygon, Environment -from .triangle import Triangle +from .polygon import Environment, Polygon from .quad import Quad -from .surfaceCollection import SurfaceCollection, INTERFACE_KEY from .rotation import Rotation +from .surfaceCollection import INTERFACE_KEY, SurfaceCollection +from .triangle import Triangle +from .vector import Vector +from .vertex import Vertex diff --git a/pytissueoptics/scene/geometry/bbox.py b/pytissueoptics/scene/geometry/bbox.py index 66e9aec8..fdfd32db 100644 --- a/pytissueoptics/scene/geometry/bbox.py +++ b/pytissueoptics/scene/geometry/bbox.py @@ -1,4 +1,5 @@ from typing import List + from pytissueoptics.scene.geometry import Vector, Vertex diff --git a/pytissueoptics/scene/geometry/polygon.py b/pytissueoptics/scene/geometry/polygon.py index 34640e30..2fcddc60 100644 --- a/pytissueoptics/scene/geometry/polygon.py +++ b/pytissueoptics/scene/geometry/polygon.py @@ -1,8 +1,7 @@ from dataclasses import dataclass from typing import List -from pytissueoptics.scene.geometry import Vector, Vertex -from pytissueoptics.scene.geometry import BoundingBox +from pytissueoptics.scene.geometry import BoundingBox, Vector, Vertex WORLD_LABEL = "world" diff --git a/pytissueoptics/scene/geometry/quad.py b/pytissueoptics/scene/geometry/quad.py index ff47dc96..812be215 100644 --- a/pytissueoptics/scene/geometry/quad.py +++ b/pytissueoptics/scene/geometry/quad.py @@ -1,4 +1,4 @@ -from pytissueoptics.scene.geometry import Polygon, Vector, Environment, Vertex +from pytissueoptics.scene.geometry import Environment, Polygon, Vector, Vertex class Quad(Polygon): diff --git a/pytissueoptics/scene/geometry/surfaceCollection.py b/pytissueoptics/scene/geometry/surfaceCollection.py index 7d53c324..a81598f3 100644 --- a/pytissueoptics/scene/geometry/surfaceCollection.py +++ b/pytissueoptics/scene/geometry/surfaceCollection.py @@ -1,8 +1,7 @@ -from typing import List, Dict from logging import getLogger +from typing import Dict, List -from pytissueoptics.scene.geometry import Environment -from pytissueoptics.scene.geometry import Polygon +from pytissueoptics.scene.geometry import Environment, Polygon logger = getLogger(__name__) diff --git a/pytissueoptics/scene/geometry/triangle.py b/pytissueoptics/scene/geometry/triangle.py index dec53871..07aee388 100644 --- a/pytissueoptics/scene/geometry/triangle.py +++ b/pytissueoptics/scene/geometry/triangle.py @@ -1,4 +1,4 @@ -from pytissueoptics.scene.geometry import Polygon, Vector, Environment, Vertex +from pytissueoptics.scene.geometry import Environment, Polygon, Vector, Vertex class Triangle(Polygon): diff --git a/pytissueoptics/scene/intersection/__init__.py b/pytissueoptics/scene/intersection/__init__.py index 5df524c5..326c6cb1 100644 --- a/pytissueoptics/scene/intersection/__init__.py +++ b/pytissueoptics/scene/intersection/__init__.py @@ -1,3 +1,3 @@ +from .intersectionFinder import FastIntersectionFinder, Intersection, SimpleIntersectionFinder from .ray import Ray from .raySource import RaySource, UniformRaySource -from .intersectionFinder import SimpleIntersectionFinder, FastIntersectionFinder, Intersection diff --git a/pytissueoptics/scene/intersection/intersectionFinder.py b/pytissueoptics/scene/intersection/intersectionFinder.py index 4ce12058..5faff1be 100644 --- a/pytissueoptics/scene/intersection/intersectionFinder.py +++ b/pytissueoptics/scene/intersection/intersectionFinder.py @@ -1,17 +1,17 @@ import sys from dataclasses import dataclass -from typing import List, Tuple, Optional +from typing import List, Optional, Tuple from pytissueoptics.scene import shader -from pytissueoptics.scene.geometry import Vector, Polygon, Environment +from pytissueoptics.scene.geometry import Environment, Polygon, Vector from pytissueoptics.scene.geometry.polygon import WORLD_LABEL from pytissueoptics.scene.intersection import Ray -from pytissueoptics.scene.tree import SpacePartition, Node -from pytissueoptics.scene.tree.treeConstructor.binary import NoSplitThreeAxesConstructor -from pytissueoptics.scene.scene import Scene from pytissueoptics.scene.intersection.bboxIntersect import GemsBoxIntersect from pytissueoptics.scene.intersection.mollerTrumboreIntersect import MollerTrumboreIntersect +from pytissueoptics.scene.scene import Scene from pytissueoptics.scene.solids import Solid +from pytissueoptics.scene.tree import Node, SpacePartition +from pytissueoptics.scene.tree.treeConstructor.binary import NoSplitThreeAxesConstructor @dataclass diff --git a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py index 0be0b650..72d58aaf 100644 --- a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py +++ b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py @@ -1,6 +1,6 @@ -from typing import Union, Optional +from typing import Optional, Union -from pytissueoptics.scene.geometry import Vector, Triangle, Quad, Polygon +from pytissueoptics.scene.geometry import Polygon, Quad, Triangle, Vector from pytissueoptics.scene.intersection import Ray diff --git a/pytissueoptics/scene/intersection/raySource.py b/pytissueoptics/scene/intersection/raySource.py index 47449364..b0529234 100644 --- a/pytissueoptics/scene/intersection/raySource.py +++ b/pytissueoptics/scene/intersection/raySource.py @@ -1,6 +1,8 @@ import math from typing import List + import numpy as np + from pytissueoptics.scene import Vector from pytissueoptics.scene.intersection import Ray diff --git a/pytissueoptics/scene/loader/loadSolid.py b/pytissueoptics/scene/loader/loadSolid.py index 0d242201..3ae19ede 100644 --- a/pytissueoptics/scene/loader/loadSolid.py +++ b/pytissueoptics/scene/loader/loadSolid.py @@ -1,6 +1,6 @@ from pytissueoptics.scene.geometry import Vector -from pytissueoptics.scene.solids import Solid, SolidFactory from pytissueoptics.scene.loader import Loader +from pytissueoptics.scene.solids import Solid, SolidFactory def loadSolid(filepath: str, position: Vector = Vector(0, 0, 0), diff --git a/pytissueoptics/scene/loader/loader.py b/pytissueoptics/scene/loader/loader.py index 5b5de71a..d1abca7d 100644 --- a/pytissueoptics/scene/loader/loader.py +++ b/pytissueoptics/scene/loader/loader.py @@ -1,10 +1,10 @@ import pathlib from typing import List +from pytissueoptics.scene.geometry import SurfaceCollection, Triangle, Vector, Vertex, primitives from pytissueoptics.scene.loader.parsers import OBJParser from pytissueoptics.scene.loader.parsers.parsedSurface import ParsedSurface from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.geometry import Vector, Triangle, SurfaceCollection, Vertex, primitives from pytissueoptics.scene.utils.progressBar import progressBar diff --git a/pytissueoptics/scene/loader/parsers/__init__.py b/pytissueoptics/scene/loader/parsers/__init__.py index 47e2d8c1..8257725d 100644 --- a/pytissueoptics/scene/loader/parsers/__init__.py +++ b/pytissueoptics/scene/loader/parsers/__init__.py @@ -1,2 +1,2 @@ -from .parser import Parser from .obj import OBJParser +from .parser import Parser diff --git a/pytissueoptics/scene/loader/parsers/parser.py b/pytissueoptics/scene/loader/parsers/parser.py index b4b0eb3e..091969ca 100644 --- a/pytissueoptics/scene/loader/parsers/parser.py +++ b/pytissueoptics/scene/loader/parsers/parser.py @@ -1,4 +1,4 @@ -from typing import List, Dict +from typing import Dict, List from pytissueoptics.scene.loader.parsers.parsedObject import ParsedObject diff --git a/pytissueoptics/scene/logger/__init__.py b/pytissueoptics/scene/logger/__init__.py index 1014cea4..a30ee4fe 100644 --- a/pytissueoptics/scene/logger/__init__.py +++ b/pytissueoptics/scene/logger/__init__.py @@ -1 +1 @@ -from .logger import Logger, InteractionKey +from .logger import InteractionKey, Logger diff --git a/pytissueoptics/scene/logger/logger.py b/pytissueoptics/scene/logger/logger.py index f2c54457..2683ed77 100644 --- a/pytissueoptics/scene/logger/logger.py +++ b/pytissueoptics/scene/logger/logger.py @@ -2,13 +2,13 @@ import pickle import warnings from dataclasses import dataclass -from typing import List, Dict, Optional, Union from enum import Enum +from typing import Dict, List, Optional, Union import numpy as np -from pytissueoptics.scene.logger.listArrayContainer import ListArrayContainer from pytissueoptics.scene.geometry import Vector +from pytissueoptics.scene.logger.listArrayContainer import ListArrayContainer @dataclass(frozen=True) diff --git a/pytissueoptics/scene/scene/scene.py b/pytissueoptics/scene/scene/scene.py index 7ed4f25a..ecc14faa 100644 --- a/pytissueoptics/scene/scene/scene.py +++ b/pytissueoptics/scene/scene/scene.py @@ -1,11 +1,9 @@ import sys import warnings -from typing import List, Optional, Dict +from typing import Dict, List, Optional -from pytissueoptics.scene.geometry import Environment -from pytissueoptics.scene.geometry import Vector +from pytissueoptics.scene.geometry import INTERFACE_KEY, BoundingBox, Environment, Polygon, Vector from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.geometry import Polygon, BoundingBox, INTERFACE_KEY from pytissueoptics.scene.viewer.displayable import Displayable from pytissueoptics.scene.viewer.mayavi import MayaviViewer diff --git a/pytissueoptics/scene/solids/__init__.py b/pytissueoptics/scene/solids/__init__.py index 57d81acc..f479e632 100644 --- a/pytissueoptics/scene/solids/__init__.py +++ b/pytissueoptics/scene/solids/__init__.py @@ -1,9 +1,9 @@ -from .solid import Solid -from .solidFactory import SolidFactory -from .cuboid import Cuboid +from .cone import Cone from .cube import Cube +from .cuboid import Cuboid +from .cylinder import Cylinder from .ellipsoid import Ellipsoid +from .lens import PlanoConcaveLens, PlanoConvexLens, SymmetricLens, ThickLens +from .solid import Solid +from .solidFactory import SolidFactory from .sphere import Sphere -from .cylinder import Cylinder -from .cone import Cone -from .lens import ThickLens, SymmetricLens, PlanoConvexLens, PlanoConcaveLens diff --git a/pytissueoptics/scene/solids/cone.py b/pytissueoptics/scene/solids/cone.py index d387b299..593874aa 100644 --- a/pytissueoptics/scene/solids/cone.py +++ b/pytissueoptics/scene/solids/cone.py @@ -1,4 +1,4 @@ -from pytissueoptics.scene.geometry import primitives, Vector +from pytissueoptics.scene.geometry import Vector, primitives from pytissueoptics.scene.solids import Cylinder diff --git a/pytissueoptics/scene/solids/cube.py b/pytissueoptics/scene/solids/cube.py index 42cc8ce3..80f13bef 100644 --- a/pytissueoptics/scene/solids/cube.py +++ b/pytissueoptics/scene/solids/cube.py @@ -1,5 +1,4 @@ -from pytissueoptics.scene.geometry import Vector -from pytissueoptics.scene.geometry import primitives +from pytissueoptics.scene.geometry import Vector, primitives from pytissueoptics.scene.solids import Cuboid diff --git a/pytissueoptics/scene/solids/cuboid.py b/pytissueoptics/scene/solids/cuboid.py index 9e8ea7e9..f6d2e84d 100644 --- a/pytissueoptics/scene/solids/cuboid.py +++ b/pytissueoptics/scene/solids/cuboid.py @@ -2,9 +2,17 @@ import numpy as np -from pytissueoptics.scene.geometry import Vector, Quad, Triangle, Vertex, BoundingBox, Polygon, primitives +from pytissueoptics.scene.geometry import ( + BoundingBox, + Polygon, + Quad, + SurfaceCollection, + Triangle, + Vector, + Vertex, + primitives, +) from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.geometry import SurfaceCollection from pytissueoptics.scene.solids.stack.cuboidStacker import CuboidStacker from pytissueoptics.scene.solids.stack.stackResult import StackResult diff --git a/pytissueoptics/scene/solids/cylinder.py b/pytissueoptics/scene/solids/cylinder.py index ef8b977a..e03cbf22 100644 --- a/pytissueoptics/scene/solids/cylinder.py +++ b/pytissueoptics/scene/solids/cylinder.py @@ -2,7 +2,7 @@ import warnings from typing import List -from pytissueoptics.scene.geometry import Vector, Triangle, primitives, Vertex +from pytissueoptics.scene.geometry import Triangle, Vector, Vertex, primitives from pytissueoptics.scene.solids import Solid diff --git a/pytissueoptics/scene/solids/ellipsoid.py b/pytissueoptics/scene/solids/ellipsoid.py index 9fc0e3d6..6c5d95bc 100644 --- a/pytissueoptics/scene/solids/ellipsoid.py +++ b/pytissueoptics/scene/solids/ellipsoid.py @@ -4,7 +4,7 @@ import numpy as np -from pytissueoptics.scene.geometry import Vector, Triangle, primitives, Vertex +from pytissueoptics.scene.geometry import Triangle, Vector, Vertex, primitives from pytissueoptics.scene.solids import Solid diff --git a/pytissueoptics/scene/solids/lens.py b/pytissueoptics/scene/solids/lens.py index fe1ca3d0..20a8983c 100644 --- a/pytissueoptics/scene/solids/lens.py +++ b/pytissueoptics/scene/solids/lens.py @@ -1,12 +1,12 @@ +import itertools import math from typing import List -import itertools import numpy as np -from pytissueoptics.scene.geometry import Vector, primitives, Vertex -from pytissueoptics.scene.solids import Cylinder +from pytissueoptics.scene.geometry import Vector, Vertex, primitives from pytissueoptics.scene.material import RefractiveMaterial +from pytissueoptics.scene.solids import Cylinder class ThickLens(Cylinder): diff --git a/pytissueoptics/scene/solids/solid.py b/pytissueoptics/scene/solids/solid.py index fe813194..a8f3b9f1 100644 --- a/pytissueoptics/scene/solids/solid.py +++ b/pytissueoptics/scene/solids/solid.py @@ -1,10 +1,20 @@ import warnings -from typing import Callable, List, Dict +from typing import Callable, Dict, List import numpy as np -from pytissueoptics.scene.geometry import Vector, utils, Polygon, Rotation, BoundingBox, Vertex -from pytissueoptics.scene.geometry import primitives, Environment, SurfaceCollection, INTERFACE_KEY +from pytissueoptics.scene.geometry import ( + INTERFACE_KEY, + BoundingBox, + Environment, + Polygon, + Rotation, + SurfaceCollection, + Vector, + Vertex, + primitives, + utils, +) INITIAL_SOLID_ORIENTATION = Vector(0, 0, 1) diff --git a/pytissueoptics/scene/solids/solidFactory.py b/pytissueoptics/scene/solids/solidFactory.py index 35ee81bc..271d4568 100644 --- a/pytissueoptics/scene/solids/solidFactory.py +++ b/pytissueoptics/scene/solids/solidFactory.py @@ -1,6 +1,6 @@ from typing import List -from pytissueoptics.scene.geometry import Vector, Vertex, SurfaceCollection, primitives +from pytissueoptics.scene.geometry import SurfaceCollection, Vector, Vertex, primitives from pytissueoptics.scene.solids import Solid diff --git a/pytissueoptics/scene/solids/stack/cuboidStacker.py b/pytissueoptics/scene/solids/stack/cuboidStacker.py index 36ce6d2d..e7b3a049 100644 --- a/pytissueoptics/scene/solids/stack/cuboidStacker.py +++ b/pytissueoptics/scene/solids/stack/cuboidStacker.py @@ -1,6 +1,6 @@ -from typing import List, Dict +from typing import Dict, List -from pytissueoptics.scene.geometry import Vector, SurfaceCollection, INTERFACE_KEY +from pytissueoptics.scene.geometry import INTERFACE_KEY, SurfaceCollection, Vector from pytissueoptics.scene.solids.stack.stackResult import StackResult diff --git a/pytissueoptics/scene/solids/stack/stackResult.py b/pytissueoptics/scene/solids/stack/stackResult.py index a0d85d07..682d32f3 100644 --- a/pytissueoptics/scene/solids/stack/stackResult.py +++ b/pytissueoptics/scene/solids/stack/stackResult.py @@ -1,6 +1,7 @@ -from typing import List, Dict from dataclasses import dataclass -from pytissueoptics.scene.geometry import Vector, SurfaceCollection, Vertex +from typing import Dict, List + +from pytissueoptics.scene.geometry import SurfaceCollection, Vector, Vertex @dataclass diff --git a/pytissueoptics/scene/tests/__init__.py b/pytissueoptics/scene/tests/__init__.py index b9cb67f9..5ad129a7 100644 --- a/pytissueoptics/scene/tests/__init__.py +++ b/pytissueoptics/scene/tests/__init__.py @@ -1,6 +1,5 @@ from matplotlib import pyplot as plt - SHOW_VISUAL_TESTS = False diff --git a/pytissueoptics/scene/tests/geometry/testBoundingBox.py b/pytissueoptics/scene/tests/geometry/testBoundingBox.py index e31b108c..b21e5154 100644 --- a/pytissueoptics/scene/tests/geometry/testBoundingBox.py +++ b/pytissueoptics/scene/tests/geometry/testBoundingBox.py @@ -1,6 +1,6 @@ import unittest -from pytissueoptics.scene.geometry import BoundingBox, Vector, Polygon, Vertex +from pytissueoptics.scene.geometry import BoundingBox, Polygon, Vector, Vertex class TestBoundingBox(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/geometry/testSurfaceCollection.py b/pytissueoptics/scene/tests/geometry/testSurfaceCollection.py index 7bf2db4d..c876afd7 100644 --- a/pytissueoptics/scene/tests/geometry/testSurfaceCollection.py +++ b/pytissueoptics/scene/tests/geometry/testSurfaceCollection.py @@ -2,8 +2,7 @@ from mockito import mock, verify, when -from pytissueoptics.scene.geometry import Polygon, Environment -from pytissueoptics.scene.geometry import SurfaceCollection, INTERFACE_KEY +from pytissueoptics.scene.geometry import INTERFACE_KEY, Environment, Polygon, SurfaceCollection class TestSurfaceCollection(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/geometry/testUtils.py b/pytissueoptics/scene/tests/geometry/testUtils.py index dbc0c9dc..053870dd 100644 --- a/pytissueoptics/scene/tests/geometry/testUtils.py +++ b/pytissueoptics/scene/tests/geometry/testUtils.py @@ -1,10 +1,10 @@ -from dataclasses import dataclass import unittest +from dataclasses import dataclass import numpy as np from pytissueoptics.scene.geometry import Rotation -from pytissueoptics.scene.geometry.utils import eulerRotationMatrix, rotateVerticesArray, getAxisAngleBetween +from pytissueoptics.scene.geometry.utils import eulerRotationMatrix, getAxisAngleBetween, rotateVerticesArray from pytissueoptics.scene.geometry.vector import Vector diff --git a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py index 70cc6c57..7985836a 100644 --- a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py +++ b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py @@ -1,27 +1,40 @@ +import time from typing import List import numpy as np +import pandas -from pytissueoptics.scene.geometry import Vector, BoundingBox -from pytissueoptics.scene.solids import Cuboid -from pytissueoptics.scene.scene import Scene -from pytissueoptics.scene.logger import Logger +from pytissueoptics.scene.geometry import BoundingBox, Vector +from pytissueoptics.scene.intersection import ( + FastIntersectionFinder, + Ray, + RaySource, + SimpleIntersectionFinder, + UniformRaySource, +) from pytissueoptics.scene.intersection.intersectionFinder import Intersection +from pytissueoptics.scene.logger import Logger +from pytissueoptics.scene.scene import Scene +from pytissueoptics.scene.solids import Cuboid +from pytissueoptics.scene.tests.scene.benchmarkScenes import ( + AAxisAlignedPolygonScene, + ACubeScene, + APolygonScene, + ASphereScene, + DiagonallyAlignedSpheres, + PhantomScene, + RandomShapesScene, + TwoCubesScene, + TwoSpheresScene, + XAlignedSpheres, + ZAlignedSpheres, +) from pytissueoptics.scene.tree import TreeConstructor -from pytissueoptics.scene.tree.treeConstructor.binary.splitTreeAxesConstructor import SplitThreeAxesConstructor from pytissueoptics.scene.tree.treeConstructor.binary.noSplitOneAxisConstructor import NoSplitOneAxisConstructor from pytissueoptics.scene.tree.treeConstructor.binary.noSplitThreeAxesConstructor import NoSplitThreeAxesConstructor -from pytissueoptics.scene.intersection import FastIntersectionFinder, SimpleIntersectionFinder, \ - UniformRaySource, RaySource, Ray -from pytissueoptics.scene.tests.scene.benchmarkScenes import AAxisAlignedPolygonScene, APolygonScene, ACubeScene, \ - ASphereScene, TwoCubesScene, TwoSpheresScene, RandomShapesScene, XAlignedSpheres, ZAlignedSpheres, \ - DiagonallyAlignedSpheres, PhantomScene +from pytissueoptics.scene.tree.treeConstructor.binary.splitTreeAxesConstructor import SplitThreeAxesConstructor from pytissueoptics.scene.viewer import MayaviViewer -import pandas -import time - - pandas.set_option('display.max_columns', 20) pandas.set_option('display.width', 1200) diff --git a/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py b/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py index 06e13201..b4d1c4c6 100644 --- a/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py +++ b/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py @@ -1,6 +1,6 @@ import unittest -from pytissueoptics.scene.geometry import Vector, BoundingBox +from pytissueoptics.scene.geometry import BoundingBox, Vector from pytissueoptics.scene.intersection import Ray from pytissueoptics.scene.intersection.bboxIntersect import BoxIntersectStrategy, GemsBoxIntersect, ZacharBoxIntersect diff --git a/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py b/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py index 9a5d60e4..c9343122 100644 --- a/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py +++ b/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py @@ -3,13 +3,15 @@ from pytissueoptics.scene.geometry import Vector, primitives from pytissueoptics.scene.geometry.polygon import WORLD_LABEL -from pytissueoptics.scene.intersection import SimpleIntersectionFinder, FastIntersectionFinder, Ray, UniformRaySource +from pytissueoptics.scene.intersection import FastIntersectionFinder, Ray, SimpleIntersectionFinder, UniformRaySource from pytissueoptics.scene.intersection.intersectionFinder import IntersectionFinder from pytissueoptics.scene.scene import Scene -from pytissueoptics.scene.solids import Sphere, Cube +from pytissueoptics.scene.solids import Cube, Sphere from pytissueoptics.scene.tests.scene.benchmarkScenes import PhantomScene from pytissueoptics.scene.tree.treeConstructor.binary import ( - NoSplitOneAxisConstructor, NoSplitThreeAxesConstructor, SplitThreeAxesConstructor + NoSplitOneAxisConstructor, + NoSplitThreeAxesConstructor, + SplitThreeAxesConstructor, ) diff --git a/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py b/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py index df5cf4cb..3d1c925b 100644 --- a/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py +++ b/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py @@ -1,6 +1,6 @@ import unittest -from pytissueoptics.scene.geometry import Triangle, Vector, Quad, Polygon, Vertex +from pytissueoptics.scene.geometry import Polygon, Quad, Triangle, Vector, Vertex from pytissueoptics.scene.intersection import Ray from pytissueoptics.scene.intersection.mollerTrumboreIntersect import MollerTrumboreIntersect diff --git a/pytissueoptics/scene/tests/logger/testLogger.py b/pytissueoptics/scene/tests/logger/testLogger.py index 6aff95cb..d4d127c4 100644 --- a/pytissueoptics/scene/tests/logger/testLogger.py +++ b/pytissueoptics/scene/tests/logger/testLogger.py @@ -1,11 +1,11 @@ import os -import unittest import tempfile +import unittest import numpy as np from pytissueoptics.scene.geometry import Vector -from pytissueoptics.scene.logger import Logger, InteractionKey +from pytissueoptics.scene.logger import InteractionKey, Logger class TestLogger(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/scene/benchmarkScenes.py b/pytissueoptics/scene/tests/scene/benchmarkScenes.py index 0d55f744..57830b37 100644 --- a/pytissueoptics/scene/tests/scene/benchmarkScenes.py +++ b/pytissueoptics/scene/tests/scene/benchmarkScenes.py @@ -1,6 +1,6 @@ +from pytissueoptics.scene.geometry import BoundingBox, Polygon, SurfaceCollection, Vector, Vertex from pytissueoptics.scene.scene import Scene -from pytissueoptics.scene.geometry import Vector, Polygon, SurfaceCollection, BoundingBox, Vertex -from pytissueoptics.scene.solids import Cuboid, Sphere, Cube, Ellipsoid, Solid +from pytissueoptics.scene.solids import Cube, Cuboid, Ellipsoid, Solid, Sphere class AAxisAlignedPolygonScene(Scene): diff --git a/pytissueoptics/scene/tests/scene/testScene.py b/pytissueoptics/scene/tests/scene/testScene.py index abddd95d..7e9164f6 100644 --- a/pytissueoptics/scene/tests/scene/testScene.py +++ b/pytissueoptics/scene/tests/scene/testScene.py @@ -3,8 +3,8 @@ from mockito import mock, verify, when from pytissueoptics.scene import Cuboid +from pytissueoptics.scene.geometry import INTERFACE_KEY, BoundingBox, Environment, Vector from pytissueoptics.scene.scene import Scene -from pytissueoptics.scene.geometry import Vector, BoundingBox, Environment, INTERFACE_KEY from pytissueoptics.scene.solids import Solid diff --git a/pytissueoptics/scene/tests/solids/testCuboid.py b/pytissueoptics/scene/tests/solids/testCuboid.py index e739fa8b..d5d2d4ea 100644 --- a/pytissueoptics/scene/tests/solids/testCuboid.py +++ b/pytissueoptics/scene/tests/solids/testCuboid.py @@ -2,8 +2,8 @@ import numpy as np -from pytissueoptics.scene.geometry import Vector, primitives, Vertex, INTERFACE_KEY -from pytissueoptics.scene.solids import Cuboid, Cube +from pytissueoptics.scene.geometry import INTERFACE_KEY, Vector, Vertex, primitives +from pytissueoptics.scene.solids import Cube, Cuboid class TestCuboid(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/solids/testCylinder.py b/pytissueoptics/scene/tests/solids/testCylinder.py index 1d7cfa67..177265dd 100644 --- a/pytissueoptics/scene/tests/solids/testCylinder.py +++ b/pytissueoptics/scene/tests/solids/testCylinder.py @@ -1,5 +1,5 @@ -import unittest import math +import unittest from pytissueoptics.scene.geometry import Vector, Vertex, primitives from pytissueoptics.scene.solids import Cylinder diff --git a/pytissueoptics/scene/tests/solids/testEllipsoid.py b/pytissueoptics/scene/tests/solids/testEllipsoid.py index 716e9ec7..7ce19f55 100644 --- a/pytissueoptics/scene/tests/solids/testEllipsoid.py +++ b/pytissueoptics/scene/tests/solids/testEllipsoid.py @@ -1,5 +1,5 @@ -import unittest import math +import unittest from pytissueoptics.scene.geometry import Vector, Vertex, primitives from pytissueoptics.scene.solids import Ellipsoid diff --git a/pytissueoptics/scene/tests/solids/testSolid.py b/pytissueoptics/scene/tests/solids/testSolid.py index d292564c..54bfc1ba 100644 --- a/pytissueoptics/scene/tests/solids/testSolid.py +++ b/pytissueoptics/scene/tests/solids/testSolid.py @@ -3,10 +3,17 @@ from mockito import mock, verify, when -from pytissueoptics.scene.geometry import Vector, Quad, Polygon, Vertex, Environment -from pytissueoptics.scene.geometry import primitives +from pytissueoptics.scene.geometry import ( + INTERFACE_KEY, + Environment, + Polygon, + Quad, + SurfaceCollection, + Vector, + Vertex, + primitives, +) from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.geometry import SurfaceCollection, INTERFACE_KEY class TestSolid(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/solids/testSphere.py b/pytissueoptics/scene/tests/solids/testSphere.py index 2eeb5923..5a0ab533 100644 --- a/pytissueoptics/scene/tests/solids/testSphere.py +++ b/pytissueoptics/scene/tests/solids/testSphere.py @@ -1,5 +1,5 @@ -import unittest import math +import unittest from pytissueoptics.scene.geometry import Vector, Vertex, primitives from pytissueoptics.scene.solids import Sphere diff --git a/pytissueoptics/scene/tests/solids/testThickLens.py b/pytissueoptics/scene/tests/solids/testThickLens.py index ef3682a7..58f84ee0 100644 --- a/pytissueoptics/scene/tests/solids/testThickLens.py +++ b/pytissueoptics/scene/tests/solids/testThickLens.py @@ -1,9 +1,9 @@ -import unittest import math +import unittest -from pytissueoptics.scene.solids import ThickLens, SymmetricLens, PlanoConvexLens, PlanoConcaveLens from pytissueoptics.scene.geometry import Vector from pytissueoptics.scene.material import RefractiveMaterial +from pytissueoptics.scene.solids import PlanoConcaveLens, PlanoConvexLens, SymmetricLens, ThickLens class TestThickLens(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/tree/testSpacePartition.py b/pytissueoptics/scene/tests/tree/testSpacePartition.py index 1a0594ad..0ddba8d7 100644 --- a/pytissueoptics/scene/tests/tree/testSpacePartition.py +++ b/pytissueoptics/scene/tests/tree/testSpacePartition.py @@ -1,10 +1,10 @@ import unittest -from mockito import verifyNoUnwantedInteractions, expect +from mockito import expect, verifyNoUnwantedInteractions -from pytissueoptics.scene.geometry import Polygon, BoundingBox, Vertex, Vector +from pytissueoptics.scene.geometry import BoundingBox, Polygon, Vector, Vertex from pytissueoptics.scene.tree import Node, SpacePartition -from pytissueoptics.scene.tree.treeConstructor import TreeConstructor, SplitNodeResult +from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult, TreeConstructor class TestSpacePartition(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py b/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py index b94bc0cd..e341a911 100644 --- a/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py +++ b/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py @@ -1,10 +1,10 @@ import unittest from math import sqrt -from pytissueoptics.scene.geometry import Triangle, Vector, BoundingBox, Environment, Vertex +from pytissueoptics.scene.geometry import BoundingBox, Environment, Triangle, Vector, Vertex from pytissueoptics.scene.intersection import Ray from pytissueoptics.scene.tree import Node -from pytissueoptics.scene.tree.treeConstructor.binary import SplitThreeAxesConstructor, SAHSearchResult +from pytissueoptics.scene.tree.treeConstructor.binary import SAHSearchResult, SplitThreeAxesConstructor class TestSplitConstructor(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/viewer/testMayaviSolid.py b/pytissueoptics/scene/tests/viewer/testMayaviSolid.py index 094e02aa..768d9ce9 100644 --- a/pytissueoptics/scene/tests/viewer/testMayaviSolid.py +++ b/pytissueoptics/scene/tests/viewer/testMayaviSolid.py @@ -1,9 +1,8 @@ import unittest from pytissueoptics.scene import Vector -from pytissueoptics.scene.geometry import Triangle, primitives, Quad, Polygon +from pytissueoptics.scene.geometry import Polygon, Quad, SurfaceCollection, Triangle, primitives from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.geometry import SurfaceCollection from pytissueoptics.scene.viewer.mayavi import MayaviSolid diff --git a/pytissueoptics/scene/tests/viewer/testMayaviViewer.py b/pytissueoptics/scene/tests/viewer/testMayaviViewer.py index 927dcde7..c84aa158 100644 --- a/pytissueoptics/scene/tests/viewer/testMayaviViewer.py +++ b/pytissueoptics/scene/tests/viewer/testMayaviViewer.py @@ -8,7 +8,7 @@ from pytissueoptics import Logger from pytissueoptics.scene.geometry import Vector from pytissueoptics.scene.scene import Scene -from pytissueoptics.scene.solids import Cuboid, Sphere, Ellipsoid +from pytissueoptics.scene.solids import Cuboid, Ellipsoid, Sphere from pytissueoptics.scene.tests import SHOW_VISUAL_TESTS, compareVisuals from pytissueoptics.scene.viewer.mayavi import MayaviViewer, ViewPointStyle diff --git a/pytissueoptics/scene/tree/__init__.py b/pytissueoptics/scene/tree/__init__.py index fc776e4d..c4f45797 100644 --- a/pytissueoptics/scene/tree/__init__.py +++ b/pytissueoptics/scene/tree/__init__.py @@ -1,3 +1,3 @@ from .node import Node -from .treeConstructor.treeConstructor import TreeConstructor from .spacePartition import SpacePartition +from .treeConstructor.treeConstructor import TreeConstructor diff --git a/pytissueoptics/scene/tree/node.py b/pytissueoptics/scene/tree/node.py index 671533c5..4738a68b 100644 --- a/pytissueoptics/scene/tree/node.py +++ b/pytissueoptics/scene/tree/node.py @@ -1,6 +1,6 @@ from typing import List -from pytissueoptics.scene.geometry import Polygon, BoundingBox +from pytissueoptics.scene.geometry import BoundingBox, Polygon class Node: diff --git a/pytissueoptics/scene/tree/spacePartition.py b/pytissueoptics/scene/tree/spacePartition.py index 367c1b2e..a9fcc24c 100644 --- a/pytissueoptics/scene/tree/spacePartition.py +++ b/pytissueoptics/scene/tree/spacePartition.py @@ -1,8 +1,7 @@ from typing import List, Optional -from pytissueoptics.scene.geometry import BoundingBox, Vector, Polygon -from pytissueoptics.scene.tree import TreeConstructor -from pytissueoptics.scene.tree import Node +from pytissueoptics.scene.geometry import BoundingBox, Polygon, Vector +from pytissueoptics.scene.tree import Node, TreeConstructor class SpacePartition: diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py b/pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py index 23c8252e..9bef2bae 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import List -from pytissueoptics.scene.geometry import Polygon, BoundingBox +from pytissueoptics.scene.geometry import BoundingBox, Polygon @dataclass diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py index 65ce8c73..d4fb5417 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py @@ -1,4 +1,4 @@ -from .SAHSearchResult import SAHSearchResult from .noSplitOneAxisConstructor import NoSplitOneAxisConstructor from .noSplitThreeAxesConstructor import NoSplitThreeAxesConstructor +from .SAHSearchResult import SAHSearchResult from .splitTreeAxesConstructor import SplitThreeAxesConstructor diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py index e64b2e3b..8ca71ee0 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py @@ -1,10 +1,9 @@ -from typing import List, Tuple import sys +from typing import List, Tuple from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.scene.tree import Node -from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult -from pytissueoptics.scene.tree.treeConstructor import TreeConstructor +from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult, TreeConstructor from pytissueoptics.scene.tree.treeConstructor.binary import SAHSearchResult diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py index a28e2fef..129f665c 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py @@ -1,9 +1,9 @@ -from typing import List, Tuple, Optional import sys +from typing import List, Optional, Tuple from pytissueoptics.scene import Vector -from pytissueoptics.scene.intersection import Ray from pytissueoptics.scene.geometry import Polygon, Triangle +from pytissueoptics.scene.intersection import Ray from pytissueoptics.scene.tree import Node from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult from pytissueoptics.scene.tree.treeConstructor.binary import NoSplitOneAxisConstructor diff --git a/pytissueoptics/scene/viewer/__init__.py b/pytissueoptics/scene/viewer/__init__.py index a202a44f..5cf52e2a 100644 --- a/pytissueoptics/scene/viewer/__init__.py +++ b/pytissueoptics/scene/viewer/__init__.py @@ -1,2 +1,2 @@ -from .mayavi import MayaviViewer, MAYAVI_AVAILABLE, ViewPointStyle from .displayable import Displayable +from .mayavi import MAYAVI_AVAILABLE, MayaviViewer, ViewPointStyle diff --git a/pytissueoptics/scene/viewer/displayable.py b/pytissueoptics/scene/viewer/displayable.py index 93acfa95..8af7ee9e 100644 --- a/pytissueoptics/scene/viewer/displayable.py +++ b/pytissueoptics/scene/viewer/displayable.py @@ -1,4 +1,5 @@ from abc import abstractmethod + from pytissueoptics.scene.viewer import MayaviViewer diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py b/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py index b2cb7d1f..eaa3b344 100644 --- a/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py +++ b/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py @@ -1,6 +1,6 @@ from typing import List, Tuple -from pytissueoptics.scene.geometry import primitives, Vertex, Polygon +from pytissueoptics.scene.geometry import Polygon, Vertex, primitives from pytissueoptics.scene.solids import Solid from pytissueoptics.scene.viewer.mayavi import MayaviTriangleMesh from pytissueoptics.scene.viewer.mayavi.MayaviNormals import MayaviNormals diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py b/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py index 919fc0fb..486dba4a 100644 --- a/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py +++ b/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py @@ -2,8 +2,7 @@ from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.scene.logger import Logger -from pytissueoptics.scene.viewer.mayavi.viewPoint import ViewPointStyle, ViewPointFactory - +from pytissueoptics.scene.viewer.mayavi.viewPoint import ViewPointFactory, ViewPointStyle try: from mayavi import mlab @@ -11,8 +10,8 @@ except ImportError: MAYAVI_AVAILABLE = False -from pytissueoptics.scene.viewer.mayavi import MayaviSolid from pytissueoptics.scene.solids import Solid +from pytissueoptics.scene.viewer.mayavi import MayaviSolid class MayaviViewer: diff --git a/pytissueoptics/scene/viewer/mayavi/__init__.py b/pytissueoptics/scene/viewer/mayavi/__init__.py index 3c12a797..0c604ffe 100644 --- a/pytissueoptics/scene/viewer/mayavi/__init__.py +++ b/pytissueoptics/scene/viewer/mayavi/__init__.py @@ -1,4 +1,4 @@ +from .MayaviSolid import MayaviObject, MayaviSolid from .MayaviTriangleMesh import MayaviTriangleMesh -from .MayaviSolid import MayaviSolid, MayaviObject -from .MayaviViewer import MayaviViewer, MAYAVI_AVAILABLE +from .MayaviViewer import MAYAVI_AVAILABLE, MayaviViewer from .viewPoint import ViewPointStyle From e88aebf6502e7a373ff98e457b3e16acdf886599 Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 20:07:17 -0400 Subject: [PATCH 2/7] fix relative imports --- pytissueoptics/rayscattering/fresnel.py | 2 +- pytissueoptics/rayscattering/scatteringScene.py | 3 +-- pytissueoptics/scene/geometry/bbox.py | 3 ++- pytissueoptics/scene/geometry/polygon.py | 4 +++- pytissueoptics/scene/geometry/quad.py | 4 +++- pytissueoptics/scene/geometry/triangle.py | 4 +++- pytissueoptics/scene/geometry/vertex.py | 2 +- pytissueoptics/scene/intersection/bboxIntersect.py | 3 ++- pytissueoptics/scene/intersection/intersectionFinder.py | 7 ++++--- .../scene/intersection/mollerTrumboreIntersect.py | 3 ++- pytissueoptics/scene/loader/parsers/obj/objParser.py | 2 +- pytissueoptics/scene/solids/cone.py | 4 ++-- pytissueoptics/scene/solids/cube.py | 4 ++-- pytissueoptics/scene/solids/cuboid.py | 2 +- pytissueoptics/scene/solids/cylinder.py | 4 ++-- pytissueoptics/scene/solids/ellipsoid.py | 2 +- pytissueoptics/scene/tree/spacePartition.py | 4 +++- .../scene/tree/treeConstructor/binary/__init__.py | 2 +- .../treeConstructor/binary/noSplitOneAxisConstructor.py | 2 +- .../binary/{SAHSearchResult.py => sahSearchResult.py} | 0 .../treeConstructor/binary/splitTreeAxesConstructor.py | 2 +- pytissueoptics/scene/viewer/displayable.py | 2 +- pytissueoptics/scene/viewer/mayavi/__init__.py | 6 +++--- .../viewer/mayavi/{MayaviNormals.py => mayaviNormals.py} | 0 .../scene/viewer/mayavi/{MayaviSolid.py => mayaviSolid.py} | 5 +++-- .../{MayaviTriangleMesh.py => mayaviTriangleMesh.py} | 0 .../viewer/mayavi/{MayaviViewer.py => mayaviViewer.py} | 3 ++- 27 files changed, 46 insertions(+), 33 deletions(-) rename pytissueoptics/scene/tree/treeConstructor/binary/{SAHSearchResult.py => sahSearchResult.py} (100%) rename pytissueoptics/scene/viewer/mayavi/{MayaviNormals.py => mayaviNormals.py} (100%) rename pytissueoptics/scene/viewer/mayavi/{MayaviSolid.py => mayaviSolid.py} (94%) rename pytissueoptics/scene/viewer/mayavi/{MayaviTriangleMesh.py => mayaviTriangleMesh.py} (100%) rename pytissueoptics/scene/viewer/mayavi/{MayaviViewer.py => mayaviViewer.py} (99%) diff --git a/pytissueoptics/rayscattering/fresnel.py b/pytissueoptics/rayscattering/fresnel.py index 572048a0..a1c5a84a 100644 --- a/pytissueoptics/rayscattering/fresnel.py +++ b/pytissueoptics/rayscattering/fresnel.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from pytissueoptics.scene.geometry import Environment, Vector -from pytissueoptics.scene.intersection.intersectionFinder import Intersection +from pytissueoptics.scene.intersection import Intersection @dataclass diff --git a/pytissueoptics/rayscattering/scatteringScene.py b/pytissueoptics/rayscattering/scatteringScene.py index 717f86c1..e99de0d0 100644 --- a/pytissueoptics/rayscattering/scatteringScene.py +++ b/pytissueoptics/rayscattering/scatteringScene.py @@ -2,9 +2,8 @@ import numpy as np -from pytissueoptics import Vector from pytissueoptics.rayscattering.materials import ScatteringMaterial -from pytissueoptics.scene import MayaviViewer, Scene +from pytissueoptics.scene import MayaviViewer, Scene, Vector from pytissueoptics.scene.solids import Solid from pytissueoptics.scene.viewer.displayable import Displayable diff --git a/pytissueoptics/scene/geometry/bbox.py b/pytissueoptics/scene/geometry/bbox.py index fdfd32db..93791252 100644 --- a/pytissueoptics/scene/geometry/bbox.py +++ b/pytissueoptics/scene/geometry/bbox.py @@ -1,6 +1,7 @@ from typing import List -from pytissueoptics.scene.geometry import Vector, Vertex +from .vector import Vector +from .vertex import Vertex class BoundingBox: diff --git a/pytissueoptics/scene/geometry/polygon.py b/pytissueoptics/scene/geometry/polygon.py index 2fcddc60..0cae76d3 100644 --- a/pytissueoptics/scene/geometry/polygon.py +++ b/pytissueoptics/scene/geometry/polygon.py @@ -1,7 +1,9 @@ from dataclasses import dataclass from typing import List -from pytissueoptics.scene.geometry import BoundingBox, Vector, Vertex +from .bbox import BoundingBox +from .vector import Vector +from .vertex import Vertex WORLD_LABEL = "world" diff --git a/pytissueoptics/scene/geometry/quad.py b/pytissueoptics/scene/geometry/quad.py index 812be215..79aa74e5 100644 --- a/pytissueoptics/scene/geometry/quad.py +++ b/pytissueoptics/scene/geometry/quad.py @@ -1,4 +1,6 @@ -from pytissueoptics.scene.geometry import Environment, Polygon, Vector, Vertex +from .polygon import Environment, Polygon +from .vector import Vector +from .vertex import Vertex class Quad(Polygon): diff --git a/pytissueoptics/scene/geometry/triangle.py b/pytissueoptics/scene/geometry/triangle.py index 07aee388..cc4edf47 100644 --- a/pytissueoptics/scene/geometry/triangle.py +++ b/pytissueoptics/scene/geometry/triangle.py @@ -1,4 +1,6 @@ -from pytissueoptics.scene.geometry import Environment, Polygon, Vector, Vertex +from .polygon import Environment, Polygon +from .vector import Vector +from .vertex import Vertex class Triangle(Polygon): diff --git a/pytissueoptics/scene/geometry/vertex.py b/pytissueoptics/scene/geometry/vertex.py index 3eeddb0c..f62611f4 100644 --- a/pytissueoptics/scene/geometry/vertex.py +++ b/pytissueoptics/scene/geometry/vertex.py @@ -1,4 +1,4 @@ -from pytissueoptics.scene.geometry import Vector +from .vector import Vector class Vertex(Vector): diff --git a/pytissueoptics/scene/intersection/bboxIntersect.py b/pytissueoptics/scene/intersection/bboxIntersect.py index a89f07dc..91738f37 100644 --- a/pytissueoptics/scene/intersection/bboxIntersect.py +++ b/pytissueoptics/scene/intersection/bboxIntersect.py @@ -1,7 +1,8 @@ from typing import Union from pytissueoptics.scene.geometry import BoundingBox, Vector -from pytissueoptics.scene.intersection import Ray + +from .ray import Ray class BoxIntersectStrategy: diff --git a/pytissueoptics/scene/intersection/intersectionFinder.py b/pytissueoptics/scene/intersection/intersectionFinder.py index 5faff1be..76b781e1 100644 --- a/pytissueoptics/scene/intersection/intersectionFinder.py +++ b/pytissueoptics/scene/intersection/intersectionFinder.py @@ -5,14 +5,15 @@ from pytissueoptics.scene import shader from pytissueoptics.scene.geometry import Environment, Polygon, Vector from pytissueoptics.scene.geometry.polygon import WORLD_LABEL -from pytissueoptics.scene.intersection import Ray -from pytissueoptics.scene.intersection.bboxIntersect import GemsBoxIntersect -from pytissueoptics.scene.intersection.mollerTrumboreIntersect import MollerTrumboreIntersect from pytissueoptics.scene.scene import Scene from pytissueoptics.scene.solids import Solid from pytissueoptics.scene.tree import Node, SpacePartition from pytissueoptics.scene.tree.treeConstructor.binary import NoSplitThreeAxesConstructor +from .bboxIntersect import GemsBoxIntersect +from .mollerTrumboreIntersect import MollerTrumboreIntersect +from .ray import Ray + @dataclass class Intersection: diff --git a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py index 72d58aaf..9b18f8e3 100644 --- a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py +++ b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py @@ -1,7 +1,8 @@ from typing import Optional, Union from pytissueoptics.scene.geometry import Polygon, Quad, Triangle, Vector -from pytissueoptics.scene.intersection import Ray + +from .ray import Ray class MollerTrumboreIntersect: diff --git a/pytissueoptics/scene/loader/parsers/obj/objParser.py b/pytissueoptics/scene/loader/parsers/obj/objParser.py index 70a9f89f..998c6827 100644 --- a/pytissueoptics/scene/loader/parsers/obj/objParser.py +++ b/pytissueoptics/scene/loader/parsers/obj/objParser.py @@ -1,8 +1,8 @@ from typing import List -from pytissueoptics.scene.loader.parsers import Parser from pytissueoptics.scene.loader.parsers.parsedObject import ParsedObject from pytissueoptics.scene.loader.parsers.parsedSurface import ParsedSurface +from pytissueoptics.scene.loader.parsers.parser import Parser from pytissueoptics.scene.utils.progressBar import progressBar diff --git a/pytissueoptics/scene/solids/cone.py b/pytissueoptics/scene/solids/cone.py index 593874aa..bc65d2e0 100644 --- a/pytissueoptics/scene/solids/cone.py +++ b/pytissueoptics/scene/solids/cone.py @@ -1,5 +1,5 @@ -from pytissueoptics.scene.geometry import Vector, primitives -from pytissueoptics.scene.solids import Cylinder +from ..geometry import Vector, primitives +from .cylinder import Cylinder class Cone(Cylinder): diff --git a/pytissueoptics/scene/solids/cube.py b/pytissueoptics/scene/solids/cube.py index 80f13bef..13760112 100644 --- a/pytissueoptics/scene/solids/cube.py +++ b/pytissueoptics/scene/solids/cube.py @@ -1,5 +1,5 @@ -from pytissueoptics.scene.geometry import Vector, primitives -from pytissueoptics.scene.solids import Cuboid +from ..geometry import Vector, primitives +from .cuboid import Cuboid class Cube(Cuboid): diff --git a/pytissueoptics/scene/solids/cuboid.py b/pytissueoptics/scene/solids/cuboid.py index f6d2e84d..2ccfa715 100644 --- a/pytissueoptics/scene/solids/cuboid.py +++ b/pytissueoptics/scene/solids/cuboid.py @@ -12,7 +12,7 @@ Vertex, primitives, ) -from pytissueoptics.scene.solids import Solid +from pytissueoptics.scene.solids.solid import Solid from pytissueoptics.scene.solids.stack.cuboidStacker import CuboidStacker from pytissueoptics.scene.solids.stack.stackResult import StackResult diff --git a/pytissueoptics/scene/solids/cylinder.py b/pytissueoptics/scene/solids/cylinder.py index e03cbf22..c9d4b438 100644 --- a/pytissueoptics/scene/solids/cylinder.py +++ b/pytissueoptics/scene/solids/cylinder.py @@ -2,8 +2,8 @@ import warnings from typing import List -from pytissueoptics.scene.geometry import Triangle, Vector, Vertex, primitives -from pytissueoptics.scene.solids import Solid +from ..geometry import Triangle, Vector, Vertex, primitives +from .solid import Solid class Cylinder(Solid): diff --git a/pytissueoptics/scene/solids/ellipsoid.py b/pytissueoptics/scene/solids/ellipsoid.py index 6c5d95bc..f7953d83 100644 --- a/pytissueoptics/scene/solids/ellipsoid.py +++ b/pytissueoptics/scene/solids/ellipsoid.py @@ -5,7 +5,7 @@ import numpy as np from pytissueoptics.scene.geometry import Triangle, Vector, Vertex, primitives -from pytissueoptics.scene.solids import Solid +from pytissueoptics.scene.solids.solid import Solid def hash2(obj): diff --git a/pytissueoptics/scene/tree/spacePartition.py b/pytissueoptics/scene/tree/spacePartition.py index a9fcc24c..7cef5b7d 100644 --- a/pytissueoptics/scene/tree/spacePartition.py +++ b/pytissueoptics/scene/tree/spacePartition.py @@ -1,7 +1,9 @@ from typing import List, Optional from pytissueoptics.scene.geometry import BoundingBox, Polygon, Vector -from pytissueoptics.scene.tree import Node, TreeConstructor + +from .node import Node +from .treeConstructor import TreeConstructor class SpacePartition: diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py index d4fb5417..478dd9f6 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py @@ -1,4 +1,4 @@ from .noSplitOneAxisConstructor import NoSplitOneAxisConstructor from .noSplitThreeAxesConstructor import NoSplitThreeAxesConstructor -from .SAHSearchResult import SAHSearchResult +from .sahSearchResult import SAHSearchResult from .splitTreeAxesConstructor import SplitThreeAxesConstructor diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py index 8ca71ee0..2d135faf 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py @@ -4,7 +4,7 @@ from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.scene.tree import Node from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult, TreeConstructor -from pytissueoptics.scene.tree.treeConstructor.binary import SAHSearchResult +from .sahSearchResult import SAHSearchResult class NoSplitOneAxisConstructor(TreeConstructor): diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py b/pytissueoptics/scene/tree/treeConstructor/binary/sahSearchResult.py similarity index 100% rename from pytissueoptics/scene/tree/treeConstructor/binary/SAHSearchResult.py rename to pytissueoptics/scene/tree/treeConstructor/binary/sahSearchResult.py diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py index 129f665c..c997c8ac 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py @@ -3,7 +3,7 @@ from pytissueoptics.scene import Vector from pytissueoptics.scene.geometry import Polygon, Triangle -from pytissueoptics.scene.intersection import Ray +from pytissueoptics.scene.intersection.ray import Ray from pytissueoptics.scene.tree import Node from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult from pytissueoptics.scene.tree.treeConstructor.binary import NoSplitOneAxisConstructor diff --git a/pytissueoptics/scene/viewer/displayable.py b/pytissueoptics/scene/viewer/displayable.py index 8af7ee9e..6d2544e2 100644 --- a/pytissueoptics/scene/viewer/displayable.py +++ b/pytissueoptics/scene/viewer/displayable.py @@ -1,6 +1,6 @@ from abc import abstractmethod -from pytissueoptics.scene.viewer import MayaviViewer +from pytissueoptics.scene.viewer.mayavi import MayaviViewer class Displayable: diff --git a/pytissueoptics/scene/viewer/mayavi/__init__.py b/pytissueoptics/scene/viewer/mayavi/__init__.py index 0c604ffe..a6d54180 100644 --- a/pytissueoptics/scene/viewer/mayavi/__init__.py +++ b/pytissueoptics/scene/viewer/mayavi/__init__.py @@ -1,4 +1,4 @@ -from .MayaviSolid import MayaviObject, MayaviSolid -from .MayaviTriangleMesh import MayaviTriangleMesh -from .MayaviViewer import MAYAVI_AVAILABLE, MayaviViewer +from .mayaviSolid import MayaviObject, MayaviSolid +from .mayaviTriangleMesh import MayaviTriangleMesh +from .mayaviViewer import MAYAVI_AVAILABLE, MayaviViewer from .viewPoint import ViewPointStyle diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviNormals.py b/pytissueoptics/scene/viewer/mayavi/mayaviNormals.py similarity index 100% rename from pytissueoptics/scene/viewer/mayavi/MayaviNormals.py rename to pytissueoptics/scene/viewer/mayavi/mayaviNormals.py diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py b/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py similarity index 94% rename from pytissueoptics/scene/viewer/mayavi/MayaviSolid.py rename to pytissueoptics/scene/viewer/mayavi/mayaviSolid.py index eaa3b344..08bf2f65 100644 --- a/pytissueoptics/scene/viewer/mayavi/MayaviSolid.py +++ b/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py @@ -2,8 +2,9 @@ from pytissueoptics.scene.geometry import Polygon, Vertex, primitives from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.viewer.mayavi import MayaviTriangleMesh -from pytissueoptics.scene.viewer.mayavi.MayaviNormals import MayaviNormals + +from .mayaviNormals import MayaviNormals +from .mayaviTriangleMesh import MayaviTriangleMesh class MayaviObject: diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviTriangleMesh.py b/pytissueoptics/scene/viewer/mayavi/mayaviTriangleMesh.py similarity index 100% rename from pytissueoptics/scene/viewer/mayavi/MayaviTriangleMesh.py rename to pytissueoptics/scene/viewer/mayavi/mayaviTriangleMesh.py diff --git a/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py b/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py similarity index 99% rename from pytissueoptics/scene/viewer/mayavi/MayaviViewer.py rename to pytissueoptics/scene/viewer/mayavi/mayaviViewer.py index 486dba4a..d99a4b46 100644 --- a/pytissueoptics/scene/viewer/mayavi/MayaviViewer.py +++ b/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py @@ -11,7 +11,8 @@ MAYAVI_AVAILABLE = False from pytissueoptics.scene.solids import Solid -from pytissueoptics.scene.viewer.mayavi import MayaviSolid + +from .mayaviSolid import MayaviSolid class MayaviViewer: From f4840be90383cbc268bbc2431eb12b187f6c96ef Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 21:06:51 -0400 Subject: [PATCH 3/7] fix ruff lint check --- pyproject.toml | 5 ++++- pytissueoptics/__init__.py | 4 ++-- pytissueoptics/examples/benchmarks/cube60.py | 4 ++-- .../examples/benchmarks/cubesph60b.py | 4 ++-- .../examples/benchmarks/skinvessel.py | 4 ++-- .../examples/benchmarks/sphshells.py | 4 ++-- pytissueoptics/examples/rayscattering/ex01.py | 4 ++-- pytissueoptics/examples/rayscattering/ex02.py | 4 ++-- pytissueoptics/examples/rayscattering/ex03.py | 4 ++-- pytissueoptics/examples/rayscattering/ex04.py | 4 ++-- pytissueoptics/examples/rayscattering/ex05.py | 4 ++-- pytissueoptics/examples/scene/example0.py | 2 +- pytissueoptics/examples/scene/example1.py | 2 +- pytissueoptics/examples/scene/example2.py | 2 +- pytissueoptics/examples/scene/example3.py | 2 +- pytissueoptics/examples/scene/example4.py | 2 +- .../display/profiles/__init__.py | 2 ++ .../display/profiles/profileFactory.py | 4 ++-- .../rayscattering/display/utils/__init__.py | 8 ++++++++ .../display/utils/volumeSlicer.py | 12 ------------ .../rayscattering/display/views/__init__.py | 18 ++++++++++++++++++ .../display/views/defaultViews.py | 7 ++++++- .../rayscattering/display/views/view2D.py | 18 ++++++++++++------ .../rayscattering/energyLogging/__init__.py | 6 ++++++ .../energyLogging/energyLogger.py | 2 +- .../rayscattering/materials/__init__.py | 2 +- .../rayscattering/opencl/__init__.py | 6 ++++++ .../rayscattering/opencl/buffers/CLObject.py | 1 - .../rayscattering/opencl/buffers/__init__.py | 19 +++++++++++++++++++ .../opencl/buffers/dataPointCL.py | 4 +++- .../opencl/buffers/materialCL.py | 5 ++++- .../rayscattering/opencl/buffers/photonCL.py | 3 ++- .../rayscattering/opencl/buffers/seedCL.py | 4 +++- .../rayscattering/opencl/buffers/solidCL.py | 5 ++++- .../opencl/buffers/solidCandidateCL.py | 4 +++- .../rayscattering/opencl/buffers/surfaceCL.py | 4 +++- .../opencl/buffers/triangleCL.py | 5 ++++- .../rayscattering/opencl/buffers/vertexCL.py | 5 ++++- .../rayscattering/opencl/utils/__init__.py | 2 ++ .../opencl/utils/optimalWorkUnits.py | 2 +- .../rayscattering/samples/__init__.py | 2 ++ .../rayscattering/statistics/__init__.py | 2 +- .../tests/display/testProfile1D.py | 1 - .../rayscattering/tests/display/testView2D.py | 15 +++++++++++++-- .../tests/display/testViewFactory.py | 12 +++++++++++- .../rayscattering/tests/display/testViewer.py | 1 - .../tests/energyLogging/testEnergyLogger.py | 8 +++++++- .../tests/opencl/src/testCLIntersection.py | 4 ++-- .../tests/opencl/src/testCLPhoton.py | 14 +++++++++++++- .../tests/opencl/src/testCLSmoothing.py | 19 ++++++++++++++----- .../tests/statistics/testStats.py | 1 - .../rayscattering/tests/testPhoton.py | 2 +- pytissueoptics/rayscattering/utils.py | 4 ++-- pytissueoptics/scene/geometry/__init__.py | 13 +++++++++++++ pytissueoptics/scene/geometry/bbox.py | 5 ++++- pytissueoptics/scene/geometry/polygon.py | 5 ++++- pytissueoptics/scene/intersection/__init__.py | 9 +++++++++ .../scene/intersection/bboxIntersect.py | 2 +- pytissueoptics/scene/loader/__init__.py | 2 ++ .../scene/loader/parsers/__init__.py | 4 ++-- .../scene/loader/parsers/obj/__init__.py | 2 +- .../scene/loader/parsers/obj/objParser.py | 2 +- pytissueoptics/scene/logger/__init__.py | 2 ++ pytissueoptics/scene/scene/__init__.py | 2 +- pytissueoptics/scene/shader/__init__.py | 2 +- pytissueoptics/scene/solids/__init__.py | 15 +++++++++++++++ pytissueoptics/scene/solids/lens.py | 2 +- pytissueoptics/scene/solids/solid.py | 5 +++-- .../scene/solids/stack/cuboidStacker.py | 7 +++++-- .../scene/tests/geometry/testRotation.py | 1 - .../benchmarkIntersectionFinder.py | 16 ++++++++-------- .../scene/tests/utils/testNoProgressBar.py | 2 +- pytissueoptics/scene/tree/__init__.py | 2 ++ .../scene/tree/treeConstructor/__init__.py | 2 ++ .../tree/treeConstructor/binary/__init__.py | 2 ++ .../binary/noSplitOneAxisConstructor.py | 1 + .../tree/treeConstructor/treeConstructor.py | 1 - pytissueoptics/scene/utils/__init__.py | 4 +++- pytissueoptics/scene/viewer/__init__.py | 2 ++ .../scene/viewer/mayavi/__init__.py | 9 +++++++++ pytissueoptics/testExamples.py | 7 +++++-- 81 files changed, 309 insertions(+), 107 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bb0a990c..06f218ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,10 @@ line-length = 120 target-version = "py39" [tool.ruff.lint] -select = ["ALL"] +ignore = [ + "PT009", # unittest-style asserts + "F405", # Name defined from star imports +] [tool.ruff.format] quote-style = "double" diff --git a/pytissueoptics/__init__.py b/pytissueoptics/__init__.py index b8a6ad31..dfe42a0b 100644 --- a/pytissueoptics/__init__.py +++ b/pytissueoptics/__init__.py @@ -1,4 +1,4 @@ -from .rayscattering import * -from .scene import * +from .rayscattering import * # noqa: F403 +from .scene import * # noqa: F403 __version__ = "2.0.0b1" diff --git a/pytissueoptics/examples/benchmarks/cube60.py b/pytissueoptics/examples/benchmarks/cube60.py index 378d0c06..9ad0ef7e 100644 --- a/pytissueoptics/examples/benchmarks/cube60.py +++ b/pytissueoptics/examples/benchmarks/cube60.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "MCX Homogeneous cube" diff --git a/pytissueoptics/examples/benchmarks/cubesph60b.py b/pytissueoptics/examples/benchmarks/cubesph60b.py index faf927f3..7f30f8c0 100644 --- a/pytissueoptics/examples/benchmarks/cubesph60b.py +++ b/pytissueoptics/examples/benchmarks/cubesph60b.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "MCX Sphere" diff --git a/pytissueoptics/examples/benchmarks/skinvessel.py b/pytissueoptics/examples/benchmarks/skinvessel.py index f64e99b2..b03c52a7 100644 --- a/pytissueoptics/examples/benchmarks/skinvessel.py +++ b/pytissueoptics/examples/benchmarks/skinvessel.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Skin vessel" diff --git a/pytissueoptics/examples/benchmarks/sphshells.py b/pytissueoptics/examples/benchmarks/sphshells.py index bf4ba2ec..9e7ae6b9 100644 --- a/pytissueoptics/examples/benchmarks/sphshells.py +++ b/pytissueoptics/examples/benchmarks/sphshells.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "MCX Spherical shells" diff --git a/pytissueoptics/examples/rayscattering/ex01.py b/pytissueoptics/examples/rayscattering/ex01.py index 483553ff..2d05a783 100644 --- a/pytissueoptics/examples/rayscattering/ex01.py +++ b/pytissueoptics/examples/rayscattering/ex01.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Divergent source propagation through a multi-layered tissue" diff --git a/pytissueoptics/examples/rayscattering/ex02.py b/pytissueoptics/examples/rayscattering/ex02.py index ad0922f9..02a9d7f9 100644 --- a/pytissueoptics/examples/rayscattering/ex02.py +++ b/pytissueoptics/examples/rayscattering/ex02.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Propagation in an Infinite Medium" diff --git a/pytissueoptics/examples/rayscattering/ex03.py b/pytissueoptics/examples/rayscattering/ex03.py index 7a1b9044..f1938d2f 100644 --- a/pytissueoptics/examples/rayscattering/ex03.py +++ b/pytissueoptics/examples/rayscattering/ex03.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Propagate in a in a non-scattering custom scene with an optical lens." \ "Learn to save and load your data so you don't have to simulate again." diff --git a/pytissueoptics/examples/rayscattering/ex04.py b/pytissueoptics/examples/rayscattering/ex04.py index 8999407b..532e5efe 100644 --- a/pytissueoptics/examples/rayscattering/ex04.py +++ b/pytissueoptics/examples/rayscattering/ex04.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Custom layer stack" diff --git a/pytissueoptics/examples/rayscattering/ex05.py b/pytissueoptics/examples/rayscattering/ex05.py index bd4a90db..343b0611 100644 --- a/pytissueoptics/examples/rayscattering/ex05.py +++ b/pytissueoptics/examples/rayscattering/ex05.py @@ -1,5 +1,5 @@ -import env -from pytissueoptics import * +import env # noqa: F401 +from pytissueoptics import * # noqa: F403 TITLE = "Sphere inside a cube" diff --git a/pytissueoptics/examples/scene/example0.py b/pytissueoptics/examples/scene/example0.py index 1f8664e8..7bf1cc1d 100644 --- a/pytissueoptics/examples/scene/example0.py +++ b/pytissueoptics/examples/scene/example0.py @@ -1,4 +1,4 @@ -import env +import env # noqa: F401 TITLE = "Explore different Shapes" diff --git a/pytissueoptics/examples/scene/example1.py b/pytissueoptics/examples/scene/example1.py index 1caf9115..bf5b2f42 100644 --- a/pytissueoptics/examples/scene/example1.py +++ b/pytissueoptics/examples/scene/example1.py @@ -1,4 +1,4 @@ -import env +import env # noqa: F401 TITLE = "Transforms on a Solid" diff --git a/pytissueoptics/examples/scene/example2.py b/pytissueoptics/examples/scene/example2.py index cf54680b..d1b8fa65 100644 --- a/pytissueoptics/examples/scene/example2.py +++ b/pytissueoptics/examples/scene/example2.py @@ -1,4 +1,4 @@ -import env +import env # noqa: F401 TITLE = "Stacking Cuboids" diff --git a/pytissueoptics/examples/scene/example3.py b/pytissueoptics/examples/scene/example3.py index 9914cfa9..b61e0182 100644 --- a/pytissueoptics/examples/scene/example3.py +++ b/pytissueoptics/examples/scene/example3.py @@ -1,4 +1,4 @@ -import env +import env # noqa: F401 TITLE = "Load a .obj wavefront file" diff --git a/pytissueoptics/examples/scene/example4.py b/pytissueoptics/examples/scene/example4.py index 500dae40..47fd42e7 100644 --- a/pytissueoptics/examples/scene/example4.py +++ b/pytissueoptics/examples/scene/example4.py @@ -1,4 +1,4 @@ -import env +import env # noqa: F401 TITLE = "Lenses" diff --git a/pytissueoptics/rayscattering/display/profiles/__init__.py b/pytissueoptics/rayscattering/display/profiles/__init__.py index 17bf865d..db129cca 100644 --- a/pytissueoptics/rayscattering/display/profiles/__init__.py +++ b/pytissueoptics/rayscattering/display/profiles/__init__.py @@ -1,2 +1,4 @@ from .profile1D import Profile1D from .profileFactory import ProfileFactory + +__all__ = ['Profile1D', 'ProfileFactory'] diff --git a/pytissueoptics/rayscattering/display/profiles/profileFactory.py b/pytissueoptics/rayscattering/display/profiles/profileFactory.py index ea93bd4c..bcf9835e 100644 --- a/pytissueoptics/rayscattering/display/profiles/profileFactory.py +++ b/pytissueoptics/rayscattering/display/profiles/profileFactory.py @@ -117,7 +117,7 @@ def _correctCapitalization(self, solidLabel, surfaceLabel): if solidLabel is None: return None, None originalSolidLabels = self._logger.getSeenSolidLabels() - lowerCaseSolidLabels = [l.lower() for l in originalSolidLabels] + lowerCaseSolidLabels = [label.lower() for label in originalSolidLabels] if solidLabel.lower() in lowerCaseSolidLabels: labelIndex = lowerCaseSolidLabels.index(solidLabel.lower()) solidLabel = originalSolidLabels[labelIndex] @@ -126,7 +126,7 @@ def _correctCapitalization(self, solidLabel, surfaceLabel): return solidLabel, None originalSurfaceLabels = self._logger.getSeenSurfaceLabels(solidLabel) - lowerCaseSurfaceLabels = [l.lower() for l in originalSurfaceLabels] + lowerCaseSurfaceLabels = [label.lower() for label in originalSurfaceLabels] altLabel = f'{solidLabel}_{surfaceLabel}' if altLabel.lower() in lowerCaseSurfaceLabels: diff --git a/pytissueoptics/rayscattering/display/utils/__init__.py b/pytissueoptics/rayscattering/display/utils/__init__.py index b14362c0..066ba238 100644 --- a/pytissueoptics/rayscattering/display/utils/__init__.py +++ b/pytissueoptics/rayscattering/display/utils/__init__.py @@ -1,2 +1,10 @@ from .direction import DEFAULT_X_VIEW_DIRECTIONS, DEFAULT_Y_VIEW_DIRECTIONS, DEFAULT_Z_VIEW_DIRECTIONS, Direction from .volumeSlicer import VolumeSlicer + +__all__ = [ + "DEFAULT_X_VIEW_DIRECTIONS", + "DEFAULT_Y_VIEW_DIRECTIONS", + "DEFAULT_Z_VIEW_DIRECTIONS", + "Direction", + "VolumeSlicer" +] diff --git a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py index 71c19d8b..6e7436e6 100644 --- a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py +++ b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py @@ -113,12 +113,7 @@ def _ipw_3d_z_default(self): #--------------------------------------------------------------------------- @on_trait_change('scene3d.activated') def display_scene3d(self): - outline = mlab.pipeline.outline(self.data_src3d, - figure=self.scene3d.mayavi_scene, - colormap=self._colormap) - # self.scene3d.mlab.view(40, 50) self.scene3d.mlab.view(*self._cameraView.values()) - # self.scene3d.mlab.pitch(self._cameraPitch) # Interaction properties can only be changed after the scene # has been created, and thus the interactor exists @@ -175,13 +170,6 @@ def move_view(obj, evt): ipw.ipw.slice_position = 0.5*self.data.shape[ self._axis_names[axis_name]] - # Position the view for the scene - views = dict(x=( 0, 90), - y=(90, 90), - z=( 0, 0), - ) - # scene.mlab.view(*views[axis_name]) - scene.mlab.view(*self._cameraView.values()) scene.mlab.pitch(self._cameraPitch) diff --git a/pytissueoptics/rayscattering/display/views/__init__.py b/pytissueoptics/rayscattering/display/views/__init__.py index 43b05af3..e67d5c5f 100644 --- a/pytissueoptics/rayscattering/display/views/__init__.py +++ b/pytissueoptics/rayscattering/display/views/__init__.py @@ -14,3 +14,21 @@ ) from .view2D import View2D, ViewGroup from .viewFactory import ViewFactory + +__all__ = [ + "View2D", + "ViewGroup", + "ViewFactory", + "View2DProjection", + "View2DProjectionX", + "View2DProjectionY", + "View2DProjectionZ", + "View2DSlice", + "View2DSliceX", + "View2DSliceY", + "View2DSliceZ", + "View2DSurface", + "View2DSurfaceX", + "View2DSurfaceY", + "View2DSurfaceZ", +] diff --git a/pytissueoptics/rayscattering/display/views/defaultViews.py b/pytissueoptics/rayscattering/display/views/defaultViews.py index 136e4f33..6bdab213 100644 --- a/pytissueoptics/rayscattering/display/views/defaultViews.py +++ b/pytissueoptics/rayscattering/display/views/defaultViews.py @@ -2,7 +2,12 @@ import numpy as np -from pytissueoptics.rayscattering.display.utils.direction import * +from pytissueoptics.rayscattering.display.utils import ( + DEFAULT_X_VIEW_DIRECTIONS, + DEFAULT_Y_VIEW_DIRECTIONS, + DEFAULT_Z_VIEW_DIRECTIONS, + Direction, +) from pytissueoptics.rayscattering.display.views.view2D import View2D, ViewGroup diff --git a/pytissueoptics/rayscattering/display/views/view2D.py b/pytissueoptics/rayscattering/display/views/view2D.py index 3763919e..2bb88b74 100644 --- a/pytissueoptics/rayscattering/display/views/view2D.py +++ b/pytissueoptics/rayscattering/display/views/view2D.py @@ -7,7 +7,12 @@ from matplotlib import pyplot as plt from pytissueoptics.rayscattering import utils -from pytissueoptics.rayscattering.display.utils.direction import * +from pytissueoptics.rayscattering.display.utils import ( + DEFAULT_X_VIEW_DIRECTIONS, + DEFAULT_Y_VIEW_DIRECTIONS, + DEFAULT_Z_VIEW_DIRECTIONS, + Direction, +) class ViewGroup(Flag): @@ -62,7 +67,7 @@ def __init__(self, projectionDirection: Direction, horizontalDirection: Directio self._position = position self._thickness = thickness - limits = [sorted(l) for l in limits] if limits else [None, None] + limits = [sorted(limit) for limit in limits] if limits else [None, None] self._limitsU, self._limitsV = limits self._binSize = (binSize, binSize) if isinstance(binSize, (int, float)) else binSize self._binsU, self._binsV = None, None @@ -85,7 +90,7 @@ def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float self._binSize = (binSize3D[self.axisU], binSize3D[self.axisV]) limits = [self._limitsU, self._limitsV] - self._binsU, self._binsV = [int((l[1] - l[0]) / b) for l, b in zip(limits, self._binSize)] + self._binsU, self._binsV = [int((limit[1] - limit[0]) / bin_) for limit, bin_ in zip(limits, self._binSize)] if self._verticalIsNegative: self._limitsV = self._limitsV[::-1] @@ -96,8 +101,9 @@ def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float try: self._dataUV = np.zeros((self._binsU, self._binsV), dtype=np.float32) except MemoryError: - raise MemoryError(f"Cannot allocate memory for 2D view. " - f"Consider increasing `defaultBinSize` of EnergyLogger.") + raise MemoryError( + "Cannot allocate memory for 2D view. Consider increasing `defaultBinSize` of EnergyLogger." + ) def extractData(self, dataPoints: np.ndarray): """ @@ -113,7 +119,7 @@ def extractData(self, dataPoints: np.ndarray): u, v, w = dataPoints[:, 1 + self.axisU], dataPoints[:, 1 + self.axisV], dataPoints[:, 0] sumUVProjection = np.histogram2d(u, v, weights=w, bins=(self._binsU, self._binsV), - range=(sorted(self._limitsU), sorted(self._limitsV)))[0] + range=(sorted(self._limitsU), sorted(self._limitsV)))[0] self._dataUV += np.flip(sumUVProjection, axis=1) self._hasData = True diff --git a/pytissueoptics/rayscattering/energyLogging/__init__.py b/pytissueoptics/rayscattering/energyLogging/__init__.py index d7131696..a36c755d 100644 --- a/pytissueoptics/rayscattering/energyLogging/__init__.py +++ b/pytissueoptics/rayscattering/energyLogging/__init__.py @@ -1,3 +1,9 @@ from .energyLogger import EnergyLogger from .pointCloud import PointCloud from .pointCloudFactory import PointCloudFactory + +__all__ = [ + "EnergyLogger", + "PointCloud", + "PointCloudFactory", +] diff --git a/pytissueoptics/rayscattering/energyLogging/energyLogger.py b/pytissueoptics/rayscattering/energyLogging/energyLogger.py index 21adb9a1..f67529ec 100644 --- a/pytissueoptics/rayscattering/energyLogging/energyLogger.py +++ b/pytissueoptics/rayscattering/energyLogging/energyLogger.py @@ -136,7 +136,7 @@ def load(self, filepath: str): utils.warn("WARNING: The logger at '{}' use to discard 3D data, but it was reloaded with keep3D=True. " "This may corrupt the statistics and the 3D visualization. Proceed at your own risk.".format(filepath)) if self._defaultViews != oldDefaultViews: - utils.warn("WARNING: Cannot provide new default views to a loaded logger. " + utils.warn("WARNING: Cannot provide new default views to a loaded logger from '{}'." "Using only the views from the file.".format(filepath)) @property diff --git a/pytissueoptics/rayscattering/materials/__init__.py b/pytissueoptics/rayscattering/materials/__init__.py index 6123a3ec..7df6593c 100644 --- a/pytissueoptics/rayscattering/materials/__init__.py +++ b/pytissueoptics/rayscattering/materials/__init__.py @@ -1 +1 @@ -from .scatteringMaterial import ScatteringMaterial +from .scatteringMaterial import ScatteringMaterial as ScatteringMaterial diff --git a/pytissueoptics/rayscattering/opencl/__init__.py b/pytissueoptics/rayscattering/opencl/__init__.py index 646ba690..115d8eb2 100644 --- a/pytissueoptics/rayscattering/opencl/__init__.py +++ b/pytissueoptics/rayscattering/opencl/__init__.py @@ -29,3 +29,9 @@ def validateOpenCL() -> bool: def hardwareAccelerationIsAvailable() -> bool: return OPENCL_AVAILABLE and OPENCL_OK + + +__all__ = [ + "IPPTable", + "WEIGHT_THRESHOLD" +] diff --git a/pytissueoptics/rayscattering/opencl/buffers/CLObject.py b/pytissueoptics/rayscattering/opencl/buffers/CLObject.py index fc75419e..04c19a9f 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/CLObject.py +++ b/pytissueoptics/rayscattering/opencl/buffers/CLObject.py @@ -2,7 +2,6 @@ try: import pyopencl as cl - import pyopencl.tools except ImportError: class DummyType: def __getattr__(self, item): diff --git a/pytissueoptics/rayscattering/opencl/buffers/__init__.py b/pytissueoptics/rayscattering/opencl/buffers/__init__.py index 6abbafa7..7bbdf802 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/__init__.py +++ b/pytissueoptics/rayscattering/opencl/buffers/__init__.py @@ -8,3 +8,22 @@ from .surfaceCL import SurfaceCL, SurfaceCLInfo from .triangleCL import TriangleCL, TriangleCLInfo from .vertexCL import VertexCL + +__all__ = [ + "BufferOf", + "CLObject", + "EmptyBuffer", + "RandomBuffer", + "DataPointCL", + "MaterialCL", + "PhotonCL", + "SeedCL", + "SolidCandidateCL", + "SolidCL", + "SolidCLInfo", + "SurfaceCL", + "SurfaceCLInfo", + "TriangleCL", + "TriangleCLInfo", + "VertexCL", +] diff --git a/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py b/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py index e2300ce1..1f0ec816 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py @@ -1,4 +1,6 @@ -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + +from .CLObject import CLObject, cl class DataPointCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/buffers/materialCL.py b/pytissueoptics/rayscattering/opencl/buffers/materialCL.py index 5cd3317c..ad277bb1 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/materialCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/materialCL.py @@ -1,7 +1,10 @@ from typing import List +import numpy as np + from pytissueoptics.rayscattering.materials.scatteringMaterial import ScatteringMaterial -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * + +from .CLObject import CLObject, cl class MaterialCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/buffers/photonCL.py b/pytissueoptics/rayscattering/opencl/buffers/photonCL.py index 4e95e6fe..dec35b3c 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/photonCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/photonCL.py @@ -1,6 +1,7 @@ +import numpy as np from numpy.lib import recfunctions as rfn -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +from .CLObject import CLObject, cl class PhotonCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/buffers/seedCL.py b/pytissueoptics/rayscattering/opencl/buffers/seedCL.py index 00f2416f..3071bdae 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/seedCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/seedCL.py @@ -1,4 +1,6 @@ -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + +from .CLObject import CLObject, cl class SeedCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py index ad79808c..7db07eb2 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py @@ -1,8 +1,11 @@ from typing import List, NamedTuple -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + from pytissueoptics.scene.geometry import BoundingBox +from .CLObject import CLObject, cl + SolidCLInfo = NamedTuple("SolidInfo", [("bbox", BoundingBox), ("firstSurfaceID", int), ("lastSurfaceID", int)]) diff --git a/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py b/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py index 5b9e1fbb..595f42b6 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py @@ -1,4 +1,6 @@ -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + +from .CLObject import CLObject, cl class SolidCandidateCL(CLObject): diff --git a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py index 40185fd2..1f5b60f1 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py @@ -1,6 +1,8 @@ from typing import List, NamedTuple -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + +from .CLObject import CLObject, cl SurfaceCLInfo = NamedTuple("SurfaceInfo", [("firstPolygonID", int), ("lastPolygonID", int), ("insideMaterialID", int), ("outsideMaterialID", int), diff --git a/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py b/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py index a901afd8..9ed0a12b 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py @@ -1,8 +1,11 @@ from typing import List, NamedTuple -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + from pytissueoptics.scene.geometry import Vector +from .CLObject import CLObject, cl + TriangleCLInfo = NamedTuple("TriangleInfo", [("vertexIDs", list), ("normal", Vector)]) diff --git a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py index bc7fb5f0..39dbe697 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py @@ -1,8 +1,11 @@ from typing import List -from pytissueoptics.rayscattering.opencl.buffers.CLObject import * +import numpy as np + from pytissueoptics.scene.geometry import Vertex +from .CLObject import CLObject, cl + class VertexCL(CLObject): STRUCT_NAME = "Vertex" diff --git a/pytissueoptics/rayscattering/opencl/utils/__init__.py b/pytissueoptics/rayscattering/opencl/utils/__init__.py index d06aebb0..2d7ac9f7 100644 --- a/pytissueoptics/rayscattering/opencl/utils/__init__.py +++ b/pytissueoptics/rayscattering/opencl/utils/__init__.py @@ -1,3 +1,5 @@ from .batchTiming import BatchTiming from .CLKeyLog import CLKeyLog from .CLParameters import CLParameters + +__all__ = ["BatchTiming", "CLKeyLog", "CLParameters"] diff --git a/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py b/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py index 4b128a16..cc9977cd 100644 --- a/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py +++ b/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py @@ -4,7 +4,7 @@ import matplotlib.pyplot as plt import numpy as np -from pytissueoptics import * +from pytissueoptics import Cuboid, DirectionalSource, EnergyLogger, ScatteringMaterial, ScatteringScene, Sphere, Vector from pytissueoptics.rayscattering.opencl import CONFIG MAX_SECONDS_PER_TEST = 5 diff --git a/pytissueoptics/rayscattering/samples/__init__.py b/pytissueoptics/rayscattering/samples/__init__.py index 1bb84225..d40ac3c4 100644 --- a/pytissueoptics/rayscattering/samples/__init__.py +++ b/pytissueoptics/rayscattering/samples/__init__.py @@ -1,2 +1,4 @@ from .infiniteTissue import InfiniteTissue from .phantomTissue import PhantomTissue + +__all__ = ["InfiniteTissue", "PhantomTissue"] diff --git a/pytissueoptics/rayscattering/statistics/__init__.py b/pytissueoptics/rayscattering/statistics/__init__.py index 04d4b290..cf971419 100644 --- a/pytissueoptics/rayscattering/statistics/__init__.py +++ b/pytissueoptics/rayscattering/statistics/__init__.py @@ -1 +1 @@ -from .statistics import Stats +from .statistics import Stats as Stats diff --git a/pytissueoptics/rayscattering/tests/display/testProfile1D.py b/pytissueoptics/rayscattering/tests/display/testProfile1D.py index 57f09e63..24be5f8a 100644 --- a/pytissueoptics/rayscattering/tests/display/testProfile1D.py +++ b/pytissueoptics/rayscattering/tests/display/testProfile1D.py @@ -1,4 +1,3 @@ -import filecmp import os import tempfile import unittest diff --git a/pytissueoptics/rayscattering/tests/display/testView2D.py b/pytissueoptics/rayscattering/tests/display/testView2D.py index f690ac46..e11936dd 100644 --- a/pytissueoptics/rayscattering/tests/display/testView2D.py +++ b/pytissueoptics/rayscattering/tests/display/testView2D.py @@ -1,4 +1,3 @@ -import filecmp import os import tempfile import unittest @@ -7,8 +6,20 @@ import numpy as np from matplotlib import pyplot as plt +from pytissueoptics import View2DSurface from pytissueoptics.rayscattering.display.utils import Direction -from pytissueoptics.rayscattering.display.views import * +from pytissueoptics.rayscattering.display.views import ( + View2D, + View2DProjection, + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSliceX, + View2DSliceY, + View2DSliceZ, + View2DSurfaceX, + ViewGroup, +) from pytissueoptics.rayscattering.tests import SHOW_VISUAL_TESTS from pytissueoptics.scene.tests import compareVisuals diff --git a/pytissueoptics/rayscattering/tests/display/testViewFactory.py b/pytissueoptics/rayscattering/tests/display/testViewFactory.py index e0ec81ca..0d20ad25 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewFactory.py +++ b/pytissueoptics/rayscattering/tests/display/testViewFactory.py @@ -2,7 +2,17 @@ from mockito import mock, verify, when -from pytissueoptics.rayscattering.display.views import * +from pytissueoptics.rayscattering.display.views import ( + View2D, + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSurfaceX, + View2DSurfaceY, + View2DSurfaceZ, + ViewFactory, + ViewGroup, +) from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.scatteringScene import ScatteringScene from pytissueoptics.scene.solids import Cube, Sphere diff --git a/pytissueoptics/rayscattering/tests/display/testViewer.py b/pytissueoptics/rayscattering/tests/display/testViewer.py index 37a74d67..58eae098 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewer.py +++ b/pytissueoptics/rayscattering/tests/display/testViewer.py @@ -1,4 +1,3 @@ -import sys import unittest from unittest.mock import patch diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py index f1cef977..3261895a 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py @@ -7,7 +7,13 @@ import numpy as np from pytissueoptics.rayscattering.display.utils import Direction -from pytissueoptics.rayscattering.display.views import * +from pytissueoptics.rayscattering.display.views import ( + View2DProjection, + View2DProjectionX, + View2DSliceX, + View2DSurfaceY, + ViewGroup, +) from pytissueoptics.rayscattering.energyLogging import EnergyLogger from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.scatteringScene import ScatteringScene diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py index 06a16e36..9354cc63 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py @@ -4,7 +4,7 @@ import numpy as np -from pytissueoptics import * +from pytissueoptics import Cuboid, ScatteringMaterial, ScatteringScene, Vector from pytissueoptics.rayscattering.opencl import OPENCL_OK from pytissueoptics.rayscattering.opencl.CLPhotons import CLScene from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram @@ -36,7 +36,7 @@ def testRayIntersection(self): clScene.solids, clScene.surfaces, clScene.triangles, clScene.vertices, clScene.solidCandidates, intersections]) - except Exception as e: + except Exception: traceback.print_exc(0) self.program.getData(clScene.solidCandidates) diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py index 41b0ab46..2d19faa8 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py @@ -7,7 +7,19 @@ from pytissueoptics import ScatteringMaterial, ScatteringScene, Vector from pytissueoptics.rayscattering.opencl import OPENCL_AVAILABLE, OPENCL_OK -from pytissueoptics.rayscattering.opencl.buffers import * +from pytissueoptics.rayscattering.opencl.buffers import ( + DataPointCL, + MaterialCL, + PhotonCL, + SeedCL, + SolidCandidateCL, + SolidCL, + SurfaceCL, + SurfaceCLInfo, + TriangleCL, + TriangleCLInfo, + VertexCL, +) from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram from pytissueoptics.rayscattering.opencl.CLScene import NO_LOG_ID, NO_SOLID_ID, NO_SURFACE_ID, CLScene from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py index e1efcda6..acf4c7c2 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py @@ -4,14 +4,23 @@ import numpy as np -from pytissueoptics import * +from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.opencl import OPENCL_OK -from pytissueoptics.rayscattering.opencl.buffers import * +from pytissueoptics.rayscattering.opencl.buffers import ( + DataPointCL, + MaterialCL, + SeedCL, + SolidCandidateCL, + SolidCL, + SurfaceCL, + TriangleCL, + TriangleCLInfo, + VertexCL, +) from pytissueoptics.rayscattering.opencl.CLProgram import CLProgram from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR from pytissueoptics.rayscattering.tests.opencl.src.CLObjects import IntersectionCL, RayCL -from pytissueoptics.scene.geometry.triangle import Triangle -from pytissueoptics.scene.geometry.vertex import Vertex +from pytissueoptics.scene.geometry import Triangle, Vector, Vertex @unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') @@ -74,7 +83,7 @@ def _getSmoothNormal(self, atPosition: Vector, rayDirection: Vector = Vector(0, try: self.program.launchKernel("setSmoothNormals", N=N, arguments=[intersectionCL, triangleCL, verticesCL, rayCL]) - except Exception as e: + except Exception: traceback.print_exc(0) self.program.getData(intersectionCL) diff --git a/pytissueoptics/rayscattering/tests/statistics/testStats.py b/pytissueoptics/rayscattering/tests/statistics/testStats.py index 1f07b937..d2e8ee52 100644 --- a/pytissueoptics/rayscattering/tests/statistics/testStats.py +++ b/pytissueoptics/rayscattering/tests/statistics/testStats.py @@ -1,6 +1,5 @@ import io import os -import sys import tempfile import unittest from unittest.mock import patch diff --git a/pytissueoptics/rayscattering/tests/testPhoton.py b/pytissueoptics/rayscattering/tests/testPhoton.py index 957c2968..66671b70 100644 --- a/pytissueoptics/rayscattering/tests/testPhoton.py +++ b/pytissueoptics/rayscattering/tests/testPhoton.py @@ -10,7 +10,7 @@ from pytissueoptics.rayscattering.materials import ScatteringMaterial from pytissueoptics.rayscattering.photon import WEIGHT_THRESHOLD from pytissueoptics.scene import Logger, Vector -from pytissueoptics.scene.geometry import Environment, Polygon, Triangle +from pytissueoptics.scene.geometry import Environment, Polygon from pytissueoptics.scene.geometry.polygon import WORLD_LABEL from pytissueoptics.scene.intersection.intersectionFinder import Intersection, IntersectionFinder from pytissueoptics.scene.intersection.mollerTrumboreIntersect import MollerTrumboreIntersect diff --git a/pytissueoptics/rayscattering/utils.py b/pytissueoptics/rayscattering/utils.py index 69801acd..c86e1b1a 100644 --- a/pytissueoptics/rayscattering/utils.py +++ b/pytissueoptics/rayscattering/utils.py @@ -23,7 +23,7 @@ def labelsEqual(label1: str, label2: str) -> bool: return label1.lower() == label2.lower() -def labelContained(label: str, labels: List[str]) -> bool: +def labelContained(label: str, inLabels: List[str]) -> bool: if label is None: return False - return any(labelsEqual(label, l) for l in labels) + return any(labelsEqual(label, inLabel) for inLabel in inLabels) diff --git a/pytissueoptics/scene/geometry/__init__.py b/pytissueoptics/scene/geometry/__init__.py index 828a4083..568c5af1 100644 --- a/pytissueoptics/scene/geometry/__init__.py +++ b/pytissueoptics/scene/geometry/__init__.py @@ -6,3 +6,16 @@ from .triangle import Triangle from .vector import Vector from .vertex import Vertex + +__all__ = [ + "BoundingBox", + "Polygon", + "Quad", + "Rotation", + "SurfaceCollection", + "Triangle", + "Vector", + "Vertex", + "Environment", + INTERFACE_KEY, +] \ No newline at end of file diff --git a/pytissueoptics/scene/geometry/bbox.py b/pytissueoptics/scene/geometry/bbox.py index 93791252..a557871f 100644 --- a/pytissueoptics/scene/geometry/bbox.py +++ b/pytissueoptics/scene/geometry/bbox.py @@ -1,8 +1,11 @@ -from typing import List +from typing import TYPE_CHECKING, List from .vector import Vector from .vertex import Vertex +if TYPE_CHECKING: + from pytissueoptics.scene.geometry.polygon import Polygon + class BoundingBox: _AXIS_KEYS = ['x', 'y', 'z'] diff --git a/pytissueoptics/scene/geometry/polygon.py b/pytissueoptics/scene/geometry/polygon.py index 0cae76d3..3768c8f9 100644 --- a/pytissueoptics/scene/geometry/polygon.py +++ b/pytissueoptics/scene/geometry/polygon.py @@ -1,10 +1,13 @@ from dataclasses import dataclass -from typing import List +from typing import TYPE_CHECKING, List from .bbox import BoundingBox from .vector import Vector from .vertex import Vertex +if TYPE_CHECKING: + from pytissueoptics.scene.solids.solid import Solid + WORLD_LABEL = "world" diff --git a/pytissueoptics/scene/intersection/__init__.py b/pytissueoptics/scene/intersection/__init__.py index 326c6cb1..e89c99d1 100644 --- a/pytissueoptics/scene/intersection/__init__.py +++ b/pytissueoptics/scene/intersection/__init__.py @@ -1,3 +1,12 @@ from .intersectionFinder import FastIntersectionFinder, Intersection, SimpleIntersectionFinder from .ray import Ray from .raySource import RaySource, UniformRaySource + +__all__ = [ + "Intersection", + "FastIntersectionFinder", + "SimpleIntersectionFinder", + "Ray", + "RaySource", + "UniformRaySource", +] diff --git a/pytissueoptics/scene/intersection/bboxIntersect.py b/pytissueoptics/scene/intersection/bboxIntersect.py index 91738f37..84b879c3 100644 --- a/pytissueoptics/scene/intersection/bboxIntersect.py +++ b/pytissueoptics/scene/intersection/bboxIntersect.py @@ -8,7 +8,7 @@ class BoxIntersectStrategy: # fixme(?) LSP violation. Current implementations behave differently when the ray lies on a box plane. def getIntersection(self, ray: Ray, bbox: BoundingBox) -> Union[Vector, None]: - raise NotImplemented + raise NotImplementedError class GemsBoxIntersect(BoxIntersectStrategy): diff --git a/pytissueoptics/scene/loader/__init__.py b/pytissueoptics/scene/loader/__init__.py index cbca9a79..d0ddaecb 100644 --- a/pytissueoptics/scene/loader/__init__.py +++ b/pytissueoptics/scene/loader/__init__.py @@ -1,2 +1,4 @@ from .loader import Loader from .loadSolid import loadSolid + +__all__ = ["Loader", "loadSolid"] diff --git a/pytissueoptics/scene/loader/parsers/__init__.py b/pytissueoptics/scene/loader/parsers/__init__.py index 8257725d..2a06832a 100644 --- a/pytissueoptics/scene/loader/parsers/__init__.py +++ b/pytissueoptics/scene/loader/parsers/__init__.py @@ -1,2 +1,2 @@ -from .obj import OBJParser -from .parser import Parser +from .obj import OBJParser as OBJParser +from .parser import Parser as Parser diff --git a/pytissueoptics/scene/loader/parsers/obj/__init__.py b/pytissueoptics/scene/loader/parsers/obj/__init__.py index 0f179b11..8079a89f 100644 --- a/pytissueoptics/scene/loader/parsers/obj/__init__.py +++ b/pytissueoptics/scene/loader/parsers/obj/__init__.py @@ -1 +1 @@ -from .objParser import OBJParser +from .objParser import OBJParser as OBJParser diff --git a/pytissueoptics/scene/loader/parsers/obj/objParser.py b/pytissueoptics/scene/loader/parsers/obj/objParser.py index 998c6827..2d3ee386 100644 --- a/pytissueoptics/scene/loader/parsers/obj/objParser.py +++ b/pytissueoptics/scene/loader/parsers/obj/objParser.py @@ -3,7 +3,7 @@ from pytissueoptics.scene.loader.parsers.parsedObject import ParsedObject from pytissueoptics.scene.loader.parsers.parsedSurface import ParsedSurface from pytissueoptics.scene.loader.parsers.parser import Parser -from pytissueoptics.scene.utils.progressBar import progressBar +from pytissueoptics.scene.utils import progressBar class OBJParser(Parser): diff --git a/pytissueoptics/scene/logger/__init__.py b/pytissueoptics/scene/logger/__init__.py index a30ee4fe..bcb892dc 100644 --- a/pytissueoptics/scene/logger/__init__.py +++ b/pytissueoptics/scene/logger/__init__.py @@ -1 +1,3 @@ from .logger import InteractionKey, Logger + +__all__ = ['InteractionKey', 'Logger'] diff --git a/pytissueoptics/scene/scene/__init__.py b/pytissueoptics/scene/scene/__init__.py index 421848f9..6882bfca 100644 --- a/pytissueoptics/scene/scene/__init__.py +++ b/pytissueoptics/scene/scene/__init__.py @@ -1 +1 @@ -from .scene import Scene +from .scene import Scene as Scene diff --git a/pytissueoptics/scene/shader/__init__.py b/pytissueoptics/scene/shader/__init__.py index 01fa268b..722b2757 100644 --- a/pytissueoptics/scene/shader/__init__.py +++ b/pytissueoptics/scene/shader/__init__.py @@ -1 +1 @@ -from .utils import getSmoothNormal +from .utils import getSmoothNormal as getSmoothNormal diff --git a/pytissueoptics/scene/solids/__init__.py b/pytissueoptics/scene/solids/__init__.py index f479e632..cb3fad96 100644 --- a/pytissueoptics/scene/solids/__init__.py +++ b/pytissueoptics/scene/solids/__init__.py @@ -7,3 +7,18 @@ from .solid import Solid from .solidFactory import SolidFactory from .sphere import Sphere + +__all__ = [ + "Solid", + "SolidFactory", + "Cuboid", + "Sphere", + "Cube", + "Cylinder", + "Cone", + "Ellipsoid", + "PlanoConvexLens", + "PlanoConcaveLens", + "ThickLens", + "SymmetricLens" +] diff --git a/pytissueoptics/scene/solids/lens.py b/pytissueoptics/scene/solids/lens.py index 20a8983c..74418453 100644 --- a/pytissueoptics/scene/solids/lens.py +++ b/pytissueoptics/scene/solids/lens.py @@ -58,7 +58,7 @@ def _computeEdgeThickness(self, centerThickness) -> float: dt2 *= -np.sign(self._backRadius) edgeThickness = centerThickness - dt1 - dt2 if edgeThickness < 0: - raise ValueError(f"Desired center thickness is too small for the given radii and diameter.") + raise ValueError("Desired center thickness is too small for the given radii and diameter.") return edgeThickness @property diff --git a/pytissueoptics/scene/solids/solid.py b/pytissueoptics/scene/solids/solid.py index a8f3b9f1..0f05dc4d 100644 --- a/pytissueoptics/scene/solids/solid.py +++ b/pytissueoptics/scene/solids/solid.py @@ -1,4 +1,5 @@ import warnings +from functools import partial from typing import Callable, Dict, List import numpy as np @@ -123,7 +124,7 @@ def rotate(self, xTheta=0, yTheta=0, zTheta=0, rotationCenter: Vector = None): solid surface to compute its new normal. """ rotation = Rotation(xTheta, yTheta, zTheta) - rotationFunction = lambda vertices: self._rotateWithEuler(vertices, rotation) + rotationFunction = partial(self._rotateWithEuler, rotation=rotation) self._rotateWith(rotationFunction, rotationCenter) self._rotation.add(rotation) @@ -133,7 +134,7 @@ def orient(self, towards: Vector): Note that the original solid orientation is set to (0, 0, 1). """ initialOrientation = self._orientation axis, angle = utils.getAxisAngleBetween(initialOrientation, towards) - rotationFunction = lambda vertices: self._rotateWithAxisAngle(vertices, axis, angle) + rotationFunction = partial(self._rotateWithAxisAngle, axis=axis, angle=angle) self._rotateWith(rotationFunction, None) self._orientation = towards diff --git a/pytissueoptics/scene/solids/stack/cuboidStacker.py b/pytissueoptics/scene/solids/stack/cuboidStacker.py index e7b3a049..f5a3855d 100644 --- a/pytissueoptics/scene/solids/stack/cuboidStacker.py +++ b/pytissueoptics/scene/solids/stack/cuboidStacker.py @@ -1,8 +1,11 @@ -from typing import Dict, List +from typing import TYPE_CHECKING, Dict, List from pytissueoptics.scene.geometry import INTERFACE_KEY, SurfaceCollection, Vector from pytissueoptics.scene.solids.stack.stackResult import StackResult +if TYPE_CHECKING: + from pytissueoptics.scene.solids.cuboid import Cuboid + class CuboidStacker: """ Internal helper class to prepare and assemble a cuboid stack from 2 cuboids. """ @@ -65,7 +68,7 @@ def _validateShapeMatch(self): fixedAxes.remove(self._stackAxis) for fixedAxis in fixedAxes: assert self._onCuboid.shape[fixedAxis] == self._otherCuboid.shape[fixedAxis], \ - f"Stacking of mismatched surfaces is not supported." + "Stacking of mismatched surfaces is not supported." def _translateOtherCuboid(self): relativePosition = [0, 0, 0] diff --git a/pytissueoptics/scene/tests/geometry/testRotation.py b/pytissueoptics/scene/tests/geometry/testRotation.py index 5f6ae7b4..9c2f2be6 100644 --- a/pytissueoptics/scene/tests/geometry/testRotation.py +++ b/pytissueoptics/scene/tests/geometry/testRotation.py @@ -1,7 +1,6 @@ import unittest from pytissueoptics.scene.geometry import Rotation -from pytissueoptics.scene.geometry.vector import Vector class TestRotation(unittest.TestCase): diff --git a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py index 7985836a..8b9c7920 100644 --- a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py +++ b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py @@ -167,7 +167,7 @@ def _runValidationForConstructor(self, constructor: TreeConstructor, referenceMi viewer.show() def runBenchmark(self): - print(f"=================== BENCHMARK ====================") + print("=================== BENCHMARK ====================") for scene in self.scenes: self.runBenchmarkForScene(scene) @@ -222,16 +222,16 @@ def runBenchmarkForSceneWithConstructorAndSource(self, scene: Scene, constructor def _saveSimpleStats(self, scene: Scene, intersectionFinder: SimpleIntersectionFinder, traversalTime: float): self.stats.loc[self.stats.shape[0]] = [f"{scene.__class__.__name__}", f"{len(scene.getPolygons()):^12}", - f"-", + "-", f"{intersectionFinder.__class__.__name__:^12.15s}", - f"-", + "-", f"{traversalTime:^12.2f}", f"{traversalTime:^12.2f}", - f"-", - f"-", - f"-", - f"-", - f"-"] + "-", + "-", + "-", + "-", + "-"] def _saveFastStats(self, scene: Scene, intersectionFinder: FastIntersectionFinder, traversalTime: float, buildTime: float): diff --git a/pytissueoptics/scene/tests/utils/testNoProgressBar.py b/pytissueoptics/scene/tests/utils/testNoProgressBar.py index 4b850ae6..b9243c3e 100644 --- a/pytissueoptics/scene/tests/utils/testNoProgressBar.py +++ b/pytissueoptics/scene/tests/utils/testNoProgressBar.py @@ -1,6 +1,6 @@ import unittest -from pytissueoptics.scene.utils.progressBar import noProgressBar +from pytissueoptics.scene.utils import noProgressBar class TestNoProgressBar(unittest.TestCase): diff --git a/pytissueoptics/scene/tree/__init__.py b/pytissueoptics/scene/tree/__init__.py index c4f45797..17ee1a2f 100644 --- a/pytissueoptics/scene/tree/__init__.py +++ b/pytissueoptics/scene/tree/__init__.py @@ -1,3 +1,5 @@ from .node import Node from .spacePartition import SpacePartition from .treeConstructor.treeConstructor import TreeConstructor + +__all__ = ["Node", "SpacePartition", "TreeConstructor"] diff --git a/pytissueoptics/scene/tree/treeConstructor/__init__.py b/pytissueoptics/scene/tree/treeConstructor/__init__.py index b424e9c3..bcd049b6 100644 --- a/pytissueoptics/scene/tree/treeConstructor/__init__.py +++ b/pytissueoptics/scene/tree/treeConstructor/__init__.py @@ -1,2 +1,4 @@ from .splitNodeResult import SplitNodeResult from .treeConstructor import TreeConstructor + +__all__ = ["SplitNodeResult", "TreeConstructor"] diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py index 478dd9f6..f96ddcf2 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/__init__.py @@ -2,3 +2,5 @@ from .noSplitThreeAxesConstructor import NoSplitThreeAxesConstructor from .sahSearchResult import SAHSearchResult from .splitTreeAxesConstructor import SplitThreeAxesConstructor + +__all__ = ["NoSplitOneAxisConstructor", "NoSplitThreeAxesConstructor", "SplitThreeAxesConstructor", "SAHSearchResult"] diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py index 2d135faf..65ec73f0 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py @@ -4,6 +4,7 @@ from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.scene.tree import Node from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult, TreeConstructor + from .sahSearchResult import SAHSearchResult diff --git a/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py b/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py index 4b3fbd61..3e9764bf 100644 --- a/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py @@ -1,4 +1,3 @@ -from pytissueoptics.scene.geometry import BoundingBox from pytissueoptics.scene.tree import Node from pytissueoptics.scene.tree.treeConstructor import SplitNodeResult diff --git a/pytissueoptics/scene/utils/__init__.py b/pytissueoptics/scene/utils/__init__.py index 26a3f9a4..db441ab8 100644 --- a/pytissueoptics/scene/utils/__init__.py +++ b/pytissueoptics/scene/utils/__init__.py @@ -1 +1,3 @@ -from .progressBar import progressBar +from .progressBar import noProgressBar, progressBar + +__all__ = ["noProgressBar", "progressBar"] diff --git a/pytissueoptics/scene/viewer/__init__.py b/pytissueoptics/scene/viewer/__init__.py index 5cf52e2a..a3de150a 100644 --- a/pytissueoptics/scene/viewer/__init__.py +++ b/pytissueoptics/scene/viewer/__init__.py @@ -1,2 +1,4 @@ from .displayable import Displayable from .mayavi import MAYAVI_AVAILABLE, MayaviViewer, ViewPointStyle + +__all__ = ["Displayable", "MAYAVI_AVAILABLE", "MayaviViewer", "ViewPointStyle"] diff --git a/pytissueoptics/scene/viewer/mayavi/__init__.py b/pytissueoptics/scene/viewer/mayavi/__init__.py index a6d54180..217e9cc6 100644 --- a/pytissueoptics/scene/viewer/mayavi/__init__.py +++ b/pytissueoptics/scene/viewer/mayavi/__init__.py @@ -2,3 +2,12 @@ from .mayaviTriangleMesh import MayaviTriangleMesh from .mayaviViewer import MAYAVI_AVAILABLE, MayaviViewer from .viewPoint import ViewPointStyle + +__all__ = [ + "MayaviObject", + "MayaviSolid", + "MayaviTriangleMesh", + "MayaviViewer", + "MAYAVI_AVAILABLE", + "ViewPointStyle", +] diff --git a/pytissueoptics/testExamples.py b/pytissueoptics/testExamples.py index 56d195a4..05a45f08 100644 --- a/pytissueoptics/testExamples.py +++ b/pytissueoptics/testExamples.py @@ -1,6 +1,9 @@ +import importlib +import os +import re import unittest -from pytissueoptics.examples import * +from pytissueoptics.examples import EXAMPLE_DIR, EXAMPLE_FILE_PATTERN, EXAMPLE_FILES, EXAMPLE_MODULE, loadExamples class TestExamples(unittest.TestCase): @@ -15,7 +18,7 @@ def testExampleFormat(self): self.assertTrue(hasattr(module, "TITLE")) self.assertTrue(hasattr(module, "DESCRIPTION")) self.assertTrue(hasattr(module, "exampleCode")) - self.assertTrue(srcCode.startswith("import env\nfrom pytissueoptics import *")) + self.assertTrue(srcCode.startswith("import env")) self.assertTrue(srcCode.endswith("if __name__ == \"__main__\":\n" + " exampleCode()\n")) From c9ee48bff1caaf311e91ad239c2f2d520a3c9a8f Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 21:11:07 -0400 Subject: [PATCH 4/7] format with ruff --- pytissueoptics/__main__.py | 5 +- pytissueoptics/examples/__init__.py | 2 +- pytissueoptics/examples/benchmarks/env.py | 7 +- .../examples/benchmarks/skinvessel.py | 16 +- pytissueoptics/examples/rayscattering/env.py | 7 +- pytissueoptics/examples/rayscattering/ex01.py | 5 +- pytissueoptics/examples/rayscattering/ex02.py | 6 +- pytissueoptics/examples/rayscattering/ex03.py | 6 +- pytissueoptics/examples/rayscattering/ex05.py | 7 +- pytissueoptics/examples/scene/env.py | 7 +- pytissueoptics/examples/scene/example2.py | 2 +- pytissueoptics/rayscattering/__init__.py | 60 +++-- .../display/profiles/__init__.py | 2 +- .../display/profiles/profile1D.py | 12 +- .../display/profiles/profileFactory.py | 61 +++-- .../rayscattering/display/utils/__init__.py | 2 +- .../rayscattering/display/utils/direction.py | 2 +- .../display/utils/volumeSlicer.py | 119 ++++----- .../rayscattering/display/viewer.py | 113 ++++++--- .../display/views/defaultViews.py | 220 ++++++++++++---- .../rayscattering/display/views/view2D.py | 55 ++-- .../display/views/viewFactory.py | 39 ++- .../energyLogging/energyLogger.py | 86 +++++-- .../rayscattering/energyLogging/pointCloud.py | 5 +- .../energyLogging/pointCloudFactory.py | 7 +- pytissueoptics/rayscattering/fresnel.py | 20 +- .../rayscattering/opencl/CLPhotons.py | 71 ++++-- .../rayscattering/opencl/CLProgram.py | 13 +- .../rayscattering/opencl/CLScene.py | 26 +- .../rayscattering/opencl/__init__.py | 5 +- .../rayscattering/opencl/buffers/CLObject.py | 11 +- .../opencl/buffers/dataPointCL.py | 15 +- .../opencl/buffers/materialCL.py | 15 +- .../rayscattering/opencl/buffers/photonCL.py | 18 +- .../rayscattering/opencl/buffers/seedCL.py | 2 +- .../rayscattering/opencl/buffers/solidCL.py | 14 +- .../opencl/buffers/solidCandidateCL.py | 4 +- .../rayscattering/opencl/buffers/surfaceCL.py | 33 ++- .../opencl/buffers/triangleCL.py | 5 +- .../rayscattering/opencl/buffers/vertexCL.py | 4 +- .../rayscattering/opencl/config/CLConfig.py | 80 +++--- .../rayscattering/opencl/config/IPPTable.py | 45 +--- .../rayscattering/opencl/utils/CLKeyLog.py | 17 +- .../opencl/utils/CLParameters.py | 10 +- .../rayscattering/opencl/utils/batchTiming.py | 29 ++- .../opencl/utils/optimalWorkUnits.py | 26 +- pytissueoptics/rayscattering/photon.py | 29 ++- .../rayscattering/samples/infiniteTissue.py | 2 +- .../rayscattering/samples/phantomTissue.py | 5 +- .../rayscattering/scatteringScene.py | 6 +- pytissueoptics/rayscattering/source.py | 115 ++++++--- .../rayscattering/statistics/statistics.py | 39 +-- .../tests/display/testProfile1D.py | 16 +- .../tests/display/testProfileFactory.py | 30 ++- .../rayscattering/tests/display/testView2D.py | 94 +++---- .../tests/display/testViewFactory.py | 44 ++-- .../rayscattering/tests/display/testViewer.py | 88 ++++--- .../tests/energyLogging/testEnergyLogger.py | 4 +- .../energyLogging/testPointCloudFactory.py | 8 +- .../tests/energyLogging/testPointcloud.py | 1 - .../tests/materials/testScatteringMaterial.py | 6 +- .../tests/opencl/config/testCLConfig.py | 24 +- .../tests/opencl/config/testIPPTable.py | 1 + .../tests/opencl/src/CLObjects.py | 40 +-- .../tests/opencl/src/testCLFresnel.py | 54 ++-- .../tests/opencl/src/testCLIntersection.py | 26 +- .../tests/opencl/src/testCLPhoton.py | 234 ++++++++++++++---- .../tests/opencl/src/testCLRandom.py | 2 +- .../opencl/src/testCLScatteringMaterial.py | 27 +- .../tests/opencl/src/testCLSmoothing.py | 28 ++- .../tests/opencl/src/testCLVectorOperators.py | 17 +- .../tests/opencl/testCLKeyLog.py | 40 +-- .../tests/opencl/testCLPhotons.py | 2 +- .../tests/statistics/testStats.py | 30 ++- .../rayscattering/tests/testFresnel.py | 7 +- .../rayscattering/tests/testPhoton.py | 114 ++++++--- .../tests/testScatteringScene.py | 4 +- .../rayscattering/tests/testSource.py | 94 ++++--- .../tests/testSourceAccelerated.py | 15 +- .../rayscattering/tests/testUtils.py | 18 +- pytissueoptics/rayscattering/utils.py | 2 +- pytissueoptics/scene/__init__.py | 45 +++- pytissueoptics/scene/geometry/__init__.py | 2 +- pytissueoptics/scene/geometry/bbox.py | 29 ++- pytissueoptics/scene/geometry/polygon.py | 15 +- pytissueoptics/scene/geometry/quad.py | 20 +- pytissueoptics/scene/geometry/rotation.py | 3 +- .../scene/geometry/surfaceCollection.py | 7 +- pytissueoptics/scene/geometry/triangle.py | 19 +- pytissueoptics/scene/geometry/utils.py | 14 +- pytissueoptics/scene/geometry/vector.py | 52 ++-- .../scene/intersection/bboxIntersect.py | 8 +- .../scene/intersection/intersectionFinder.py | 9 +- .../intersection/mollerTrumboreIntersect.py | 12 +- .../scene/intersection/raySource.py | 8 +- pytissueoptics/scene/loader/loadSolid.py | 10 +- pytissueoptics/scene/loader/loader.py | 21 +- .../scene/loader/parsers/obj/objParser.py | 50 ++-- pytissueoptics/scene/loader/parsers/parser.py | 1 + pytissueoptics/scene/logger/__init__.py | 2 +- .../scene/logger/listArrayContainer.py | 2 +- pytissueoptics/scene/logger/logger.py | 41 +-- pytissueoptics/scene/material.py | 1 - pytissueoptics/scene/scene/scene.py | 31 +-- pytissueoptics/scene/shader/utils.py | 13 +- pytissueoptics/scene/solids/__init__.py | 2 +- pytissueoptics/scene/solids/cone.py | 15 +- pytissueoptics/scene/solids/cube.py | 14 +- pytissueoptics/scene/solids/cuboid.py | 86 ++++--- pytissueoptics/scene/solids/cylinder.py | 25 +- pytissueoptics/scene/solids/ellipsoid.py | 82 ++++-- pytissueoptics/scene/solids/lens.py | 109 ++++++-- pytissueoptics/scene/solids/solid.py | 43 ++-- pytissueoptics/scene/solids/solidFactory.py | 25 +- pytissueoptics/scene/solids/sphere.py | 43 ++-- .../scene/solids/stack/cuboidStacker.py | 48 ++-- .../scene/solids/stack/stackResult.py | 3 +- pytissueoptics/scene/tests/__init__.py | 8 +- .../scene/tests/geometry/testBoundingBox.py | 6 +- .../scene/tests/geometry/testPolygon.py | 7 +- .../scene/tests/geometry/testTriangle.py | 3 +- .../scene/tests/geometry/testUtils.py | 4 +- .../scene/tests/geometry/testVector.py | 12 +- .../benchmarkIntersectionFinder.py | 163 +++++++----- .../intersection/testBoundingBoxIntersect.py | 2 +- .../intersection/testIntersectionFinder.py | 10 +- .../intersection/testPolygonIntersect.py | 14 +- .../scene/tests/intersection/testRay.py | 2 +- .../tests/loader/parsers/testOBJParser.py | 12 +- .../scene/tests/loader/testLoadSolid.py | 6 +- .../scene/tests/loader/testLoader.py | 5 +- .../tests/logger/testListArrayContainer.py | 5 +- pytissueoptics/scene/tests/scene/testScene.py | 42 ++-- .../scene/tests/shader/testSmoothing.py | 6 +- pytissueoptics/scene/tests/solids/testCone.py | 28 ++- .../scene/tests/solids/testCuboid.py | 38 +-- .../scene/tests/solids/testCylinder.py | 30 ++- .../scene/tests/solids/testEllipsoid.py | 4 +- .../scene/tests/solids/testSolid.py | 93 ++++--- .../scene/tests/solids/testSphere.py | 4 +- .../scene/tests/solids/testThickLens.py | 7 +- .../scene/tests/tree/testSpacePartition.py | 4 +- .../binary/testSplitConstructor.py | 36 ++- .../scene/tests/viewer/testMayaviSolid.py | 11 +- .../scene/tests/viewer/testMayaviViewer.py | 14 +- pytissueoptics/scene/tree/node.py | 5 +- pytissueoptics/scene/tree/spacePartition.py | 5 +- .../binary/noSplitOneAxisConstructor.py | 18 +- .../binary/noSplitThreeAxesConstructor.py | 7 +- .../binary/splitTreeAxesConstructor.py | 119 ++++++--- .../tree/treeConstructor/treeConstructor.py | 5 +- .../scene/viewer/mayavi/mayaviSolid.py | 9 +- .../scene/viewer/mayavi/mayaviViewer.py | 106 ++++++-- pytissueoptics/testExamples.py | 5 +- 154 files changed, 2787 insertions(+), 1560 deletions(-) diff --git a/pytissueoptics/__main__.py b/pytissueoptics/__main__.py index 1589da01..89446300 100644 --- a/pytissueoptics/__main__.py +++ b/pytissueoptics/__main__.py @@ -7,8 +7,9 @@ ap = argparse.ArgumentParser(prog="python -m pytissueoptics", description="Run PyTissueOptics examples. ") ap.add_argument("-v", "--version", action="version", version=f"PyTissueOptics {__version__}") -ap.add_argument("-e", "--examples", required=False, default="all", - help="Run specific examples by number, e.g. -e 1,2,3. ") +ap.add_argument( + "-e", "--examples", required=False, default="all", help="Run specific examples by number, e.g. -e 1,2,3. " +) ap.add_argument("-l", "--list", required=False, action="store_true", help="List available examples. ") ap.add_argument("-t", "--tests", required=False, action="store_true", help="Run unit tests. ") diff --git a/pytissueoptics/examples/__init__.py b/pytissueoptics/examples/__init__.py index cd579b0c..0d2a7ba5 100644 --- a/pytissueoptics/examples/__init__.py +++ b/pytissueoptics/examples/__init__.py @@ -32,7 +32,7 @@ def loadExamples() -> List[Example]: for file in EXAMPLE_FILES: name = re.match(EXAMPLE_FILE_PATTERN, file).group(1) module = importlib.import_module(f"pytissueoptics.examples.{EXAMPLE_MODULE}.{name}") - with open(os.path.join(EXAMPLE_DIR, file), 'r') as f: + with open(os.path.join(EXAMPLE_DIR, file), "r") as f: srcCode = f.read() pattern = r"def exampleCode\(\):\s*(.*?)\s*if __name__ == \"__main__\":" srcCode = re.search(pattern, srcCode, re.DOTALL).group(1) diff --git a/pytissueoptics/examples/benchmarks/env.py b/pytissueoptics/examples/benchmarks/env.py index 1dd49688..37f642b4 100644 --- a/pytissueoptics/examples/benchmarks/env.py +++ b/pytissueoptics/examples/benchmarks/env.py @@ -4,9 +4,4 @@ import os import sys -sys.path.insert(0, - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__)))))) +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) diff --git a/pytissueoptics/examples/benchmarks/skinvessel.py b/pytissueoptics/examples/benchmarks/skinvessel.py index b03c52a7..efc57603 100644 --- a/pytissueoptics/examples/benchmarks/skinvessel.py +++ b/pytissueoptics/examples/benchmarks/skinvessel.py @@ -13,15 +13,23 @@ def exampleCode(): # Units in mm and mm-1 waterLayer = Cuboid(1, 0.1, 1, material=ScatteringMaterial(mu_a=0.00004, mu_s=1, g=1, n=1.33), label="water") - epidermisLayer = Cuboid(1, 0.06, 1, material=ScatteringMaterial(mu_a=1.65724, mu_s=37.59398, g=0.9, n=1.44), label="epidermis") - dermisLayer = Cuboid(1, 0.84, 1, material=ScatteringMaterial(mu_a=0.04585, mu_s=35.65406, g=0.9, n=1.38), label="dermis") + epidermisLayer = Cuboid( + 1, 0.06, 1, material=ScatteringMaterial(mu_a=1.65724, mu_s=37.59398, g=0.9, n=1.44), label="epidermis" + ) + dermisLayer = Cuboid( + 1, 0.84, 1, material=ScatteringMaterial(mu_a=0.04585, mu_s=35.65406, g=0.9, n=1.38), label="dermis" + ) zStack = waterLayer.stack(epidermisLayer).stack(dermisLayer) zStack.translateTo(Vector(0, 0, 0)) - bloodVessel = Cylinder(0.1, 0.99, material=ScatteringMaterial(mu_a=23.05427, mu_s=9.3985, g=0.9, n=1.361), label="blood") + bloodVessel = Cylinder( + 0.1, 0.99, material=ScatteringMaterial(mu_a=23.05427, mu_s=9.3985, g=0.9, n=1.361), label="blood" + ) scene = ScatteringScene([zStack, bloodVessel]) - source = DirectionalSource(position=Vector(0, -0.399, 0), direction=Vector(0, 1, 0), N=N, diameter=0.6, displaySize=0.06) + source = DirectionalSource( + position=Vector(0, -0.399, 0), direction=Vector(0, 1, 0), N=N, diameter=0.6, displaySize=0.06 + ) logger = EnergyLogger(scene, defaultBinSize=0.001) source.propagate(scene, logger=logger) diff --git a/pytissueoptics/examples/rayscattering/env.py b/pytissueoptics/examples/rayscattering/env.py index 1dd49688..37f642b4 100644 --- a/pytissueoptics/examples/rayscattering/env.py +++ b/pytissueoptics/examples/rayscattering/env.py @@ -4,9 +4,4 @@ import os import sys -sys.path.insert(0, - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__)))))) +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) diff --git a/pytissueoptics/examples/rayscattering/ex01.py b/pytissueoptics/examples/rayscattering/ex01.py index 2d05a783..2b142023 100644 --- a/pytissueoptics/examples/rayscattering/ex01.py +++ b/pytissueoptics/examples/rayscattering/ex01.py @@ -12,8 +12,9 @@ def exampleCode(): tissue = samples.PhantomTissue() logger = EnergyLogger(tissue) - source = DivergentSource(position=Vector(0, 0, -0.2), direction=Vector(0, 0, 1), N=N, - diameter=0.1, divergence=0.4, displaySize=0.2) + source = DivergentSource( + position=Vector(0, 0, -0.2), direction=Vector(0, 0, 1), N=N, diameter=0.1, divergence=0.4, displaySize=0.2 + ) tissue.show(source=source) diff --git a/pytissueoptics/examples/rayscattering/ex02.py b/pytissueoptics/examples/rayscattering/ex02.py index 02a9d7f9..d849a8a6 100644 --- a/pytissueoptics/examples/rayscattering/ex02.py +++ b/pytissueoptics/examples/rayscattering/ex02.py @@ -10,14 +10,16 @@ def exampleCode(): import math + N = 10000 if hardwareAccelerationIsAvailable() else 25 myMaterial = ScatteringMaterial(mu_s=30.0, mu_a=0.1, g=0.9) tissue = samples.InfiniteTissue(myMaterial) logger = EnergyLogger(tissue) - source = DivergentSource(position=Vector(0, 0, 0), direction=Vector(0, 0, 1), N=N, - diameter=0.2, divergence=math.pi/4) + source = DivergentSource( + position=Vector(0, 0, 0), direction=Vector(0, 0, 1), N=N, diameter=0.2, divergence=math.pi / 4 + ) source.propagate(tissue, logger=logger) diff --git a/pytissueoptics/examples/rayscattering/ex03.py b/pytissueoptics/examples/rayscattering/ex03.py index f1938d2f..18a79aee 100644 --- a/pytissueoptics/examples/rayscattering/ex03.py +++ b/pytissueoptics/examples/rayscattering/ex03.py @@ -1,8 +1,10 @@ import env # noqa: F401 from pytissueoptics import * # noqa: F403 -TITLE = "Propagate in a in a non-scattering custom scene with an optical lens." \ - "Learn to save and load your data so you don't have to simulate again." +TITLE = ( + "Propagate in a in a non-scattering custom scene with an optical lens." + "Learn to save and load your data so you don't have to simulate again." +) DESCRIPTION = """ Thin Cuboid solids are used as screens for visualization, and a SymmetricLens() as a lens. They all go into a diff --git a/pytissueoptics/examples/rayscattering/ex05.py b/pytissueoptics/examples/rayscattering/ex05.py index 343b0611..450a6110 100644 --- a/pytissueoptics/examples/rayscattering/ex05.py +++ b/pytissueoptics/examples/rayscattering/ex05.py @@ -14,12 +14,13 @@ def exampleCode(): material2 = ScatteringMaterial(mu_s=30, mu_a=0.2, g=0.9, n=1.7) cube = Cuboid(a=3, b=3, c=3, position=Vector(0, 0, 0), material=material1, label="cube") - sphere = Sphere(radius=1, order=3, position=Vector(0, 0, 0), material=material2, label="sphere", - smooth=True) + sphere = Sphere(radius=1, order=3, position=Vector(0, 0, 0), material=material2, label="sphere", smooth=True) scene = ScatteringScene([cube, sphere]) logger = EnergyLogger(scene) - source = DirectionalSource(position=Vector(0, 0, -2), direction=Vector(0, 0, 1), N=N, diameter=0.5, displaySize=0.25) + source = DirectionalSource( + position=Vector(0, 0, -2), direction=Vector(0, 0, 1), N=N, diameter=0.5, displaySize=0.25 + ) source.propagate(scene, logger) diff --git a/pytissueoptics/examples/scene/env.py b/pytissueoptics/examples/scene/env.py index 1dd49688..37f642b4 100644 --- a/pytissueoptics/examples/scene/env.py +++ b/pytissueoptics/examples/scene/env.py @@ -4,9 +4,4 @@ import os import sys -sys.path.insert(0, - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__)))))) +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) diff --git a/pytissueoptics/examples/scene/example2.py b/pytissueoptics/examples/scene/example2.py index d1b8fa65..28b866a4 100644 --- a/pytissueoptics/examples/scene/example2.py +++ b/pytissueoptics/examples/scene/example2.py @@ -26,4 +26,4 @@ def exampleCode(): if __name__ == "__main__": - exampleCode() + exampleCode() diff --git a/pytissueoptics/rayscattering/__init__.py b/pytissueoptics/rayscattering/__init__.py index 9e723d5a..fc012536 100644 --- a/pytissueoptics/rayscattering/__init__.py +++ b/pytissueoptics/rayscattering/__init__.py @@ -1,17 +1,17 @@ from .display.viewer import Direction, PointCloudStyle, Viewer, ViewGroup, Visibility from .display.views import ( - View2DProjection, - View2DProjectionX, - View2DProjectionY, - View2DProjectionZ, - View2DSlice, - View2DSliceX, - View2DSliceY, - View2DSliceZ, - View2DSurface, - View2DSurfaceX, - View2DSurfaceY, - View2DSurfaceZ, + View2DProjection, + View2DProjectionX, + View2DProjectionY, + View2DProjectionZ, + View2DSlice, + View2DSliceX, + View2DSliceY, + View2DSliceZ, + View2DSurface, + View2DSurfaceX, + View2DSurfaceY, + View2DSurfaceZ, ) from .energyLogging import EnergyLogger from .materials import ScatteringMaterial @@ -21,8 +21,34 @@ from .source import DirectionalSource, DivergentSource, IsotropicPointSource, PencilPointSource from .statistics import Stats -__all__ = ["Photon", "ScatteringMaterial", "PencilPointSource", "IsotropicPointSource", "DirectionalSource", - "DivergentSource", "EnergyLogger", "ScatteringScene", "Viewer", "PointCloudStyle", "Visibility", "ViewGroup", - "Direction", "View2DProjection", "View2DProjectionX", "View2DProjectionY", "View2DProjectionZ", - "View2DSurface", "View2DSurfaceX", "View2DSurfaceY", "View2DSurfaceZ", "View2DSlice", "View2DSliceX", - "View2DSliceY", "View2DSliceZ", "samples", "Stats", "hardwareAccelerationIsAvailable", "CONFIG"] +__all__ = [ + "Photon", + "ScatteringMaterial", + "PencilPointSource", + "IsotropicPointSource", + "DirectionalSource", + "DivergentSource", + "EnergyLogger", + "ScatteringScene", + "Viewer", + "PointCloudStyle", + "Visibility", + "ViewGroup", + "Direction", + "View2DProjection", + "View2DProjectionX", + "View2DProjectionY", + "View2DProjectionZ", + "View2DSurface", + "View2DSurfaceX", + "View2DSurfaceY", + "View2DSurfaceZ", + "View2DSlice", + "View2DSliceX", + "View2DSliceY", + "View2DSliceZ", + "samples", + "Stats", + "hardwareAccelerationIsAvailable", + "CONFIG", +] diff --git a/pytissueoptics/rayscattering/display/profiles/__init__.py b/pytissueoptics/rayscattering/display/profiles/__init__.py index db129cca..3054357b 100644 --- a/pytissueoptics/rayscattering/display/profiles/__init__.py +++ b/pytissueoptics/rayscattering/display/profiles/__init__.py @@ -1,4 +1,4 @@ from .profile1D import Profile1D from .profileFactory import ProfileFactory -__all__ = ['Profile1D', 'ProfileFactory'] +__all__ = ["Profile1D", "ProfileFactory"] diff --git a/pytissueoptics/rayscattering/display/profiles/profile1D.py b/pytissueoptics/rayscattering/display/profiles/profile1D.py index 30cfd98d..67a4ca9d 100644 --- a/pytissueoptics/rayscattering/display/profiles/profile1D.py +++ b/pytissueoptics/rayscattering/display/profiles/profile1D.py @@ -12,8 +12,8 @@ class Profile1D: dataclass. Only used internally Profile1DFactory when Viewer.show1D() is called. The user should only use the endpoint Viewer.show1D() which doesn't require to create a Profile1D object. """ - def __init__(self, data: np.ndarray, - horizontalDirection: Direction, limits: Tuple[float, float], name: str = None): + + def __init__(self, data: np.ndarray, horizontalDirection: Direction, limits: Tuple[float, float], name: str = None): self.data = data self.limits = limits self.horizontalDirection = horizontalDirection @@ -26,12 +26,12 @@ def show(self, logScale: bool = True): limits = (limits[1], limits[0]) bins = np.linspace(limits[0], limits[1], self.data.size + 1)[:-1] - plt.bar(bins, self.data, width=np.diff(bins)[0], align='edge') + plt.bar(bins, self.data, width=np.diff(bins)[0], align="edge") if logScale: - plt.yscale('log') + plt.yscale("log") plt.title(self.name) plt.xlim(*limits) - plt.xlabel('xyz'[self.horizontalDirection.axis]) - plt.ylabel('Energy') + plt.xlabel("xyz"[self.horizontalDirection.axis]) + plt.ylabel("Energy") plt.show() diff --git a/pytissueoptics/rayscattering/display/profiles/profileFactory.py b/pytissueoptics/rayscattering/display/profiles/profileFactory.py index bcf9835e..765276a3 100644 --- a/pytissueoptics/rayscattering/display/profiles/profileFactory.py +++ b/pytissueoptics/rayscattering/display/profiles/profileFactory.py @@ -20,9 +20,15 @@ def __init__(self, scene: ScatteringScene, logger: EnergyLogger): self._defaultBinSize3D = [logger.defaultBinSize] * 3 self._infiniteLimits = logger.infiniteLimits - def create(self, horizontalDirection: Direction, solidLabel: str = None, surfaceLabel: str = None, - surfaceEnergyLeaving: bool = True, limits: Tuple[float, float] = None, - binSize: float = None) -> Profile1D: + def create( + self, + horizontalDirection: Direction, + solidLabel: str = None, + surfaceLabel: str = None, + surfaceEnergyLeaving: bool = True, + limits: Tuple[float, float] = None, + binSize: float = None, + ) -> Profile1D: solidLabel, surfaceLabel = self._correctCapitalization(solidLabel, surfaceLabel) if binSize is None: binSize = self._defaultBinSize3D[horizontalDirection.axis] @@ -32,11 +38,13 @@ def create(self, horizontalDirection: Direction, solidLabel: str = None, surface bins = int((limits[1] - limits[0]) / binSize) if self._logger.has3D: - histogram = self._extractHistogramFrom3D(horizontalDirection, solidLabel, surfaceLabel, - surfaceEnergyLeaving, limits, bins) + histogram = self._extractHistogramFrom3D( + horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins + ) else: - histogram = self._extractHistogramFromViews(horizontalDirection, solidLabel, surfaceLabel, - surfaceEnergyLeaving, limits, bins) + histogram = self._extractHistogramFromViews( + horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, bins + ) name = self._createName(horizontalDirection, solidLabel, surfaceLabel, surfaceEnergyLeaving) return Profile1D(histogram, horizontalDirection, limits, name) @@ -54,8 +62,15 @@ def _getDefaultLimits(self, horizontalDirection: Direction, solidLabel: str = No limits3D = [(d[0], d[1]) for d in limits3D] return limits3D[horizontalDirection.axis] - def _extractHistogramFrom3D(self, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, - surfaceEnergyLeaving: bool, limits: Tuple[float, float], bins: int): + def _extractHistogramFrom3D( + self, + horizontalDirection: Direction, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool, + limits: Tuple[float, float], + bins: int, + ): pointCloud = self._pointCloudFactory.getPointCloud(solidLabel, surfaceLabel) if surfaceLabel: @@ -72,8 +87,15 @@ def _extractHistogramFrom3D(self, horizontalDirection: Direction, solidLabel: st histogram, _ = np.histogram(x, bins=bins, range=limits, weights=w) return histogram - def _extractHistogramFromViews(self, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, - surfaceEnergyLeaving: bool, limits: Tuple[float, float], bins: int): + def _extractHistogramFromViews( + self, + horizontalDirection: Direction, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool, + limits: Tuple[float, float], + bins: int, + ): for view in self._logger.views: if view.axis == horizontalDirection.axis: continue @@ -128,7 +150,7 @@ def _correctCapitalization(self, solidLabel, surfaceLabel): originalSurfaceLabels = self._logger.getSeenSurfaceLabels(solidLabel) lowerCaseSurfaceLabels = [label.lower() for label in originalSurfaceLabels] - altLabel = f'{solidLabel}_{surfaceLabel}' + altLabel = f"{solidLabel}_{surfaceLabel}" if altLabel.lower() in lowerCaseSurfaceLabels: surfaceLabel = altLabel @@ -137,15 +159,16 @@ def _correctCapitalization(self, solidLabel, surfaceLabel): surfaceLabel = originalSurfaceLabels[labelIndex] return solidLabel, surfaceLabel - def _createName(self, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, - surfaceEnergyLeaving: bool) -> str: - name = 'Energy profile along ' + 'xyz'[horizontalDirection.axis] + def _createName( + self, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, surfaceEnergyLeaving: bool + ) -> str: + name = "Energy profile along " + "xyz"[horizontalDirection.axis] if solidLabel: - name += ' of ' + solidLabel + name += " of " + solidLabel if surfaceLabel: - name += ' surface ' + surfaceLabel + name += " surface " + surfaceLabel if surfaceEnergyLeaving: - name += ' (leaving)' + name += " (leaving)" else: - name += ' (entering)' + name += " (entering)" return name diff --git a/pytissueoptics/rayscattering/display/utils/__init__.py b/pytissueoptics/rayscattering/display/utils/__init__.py index 066ba238..ae6087cb 100644 --- a/pytissueoptics/rayscattering/display/utils/__init__.py +++ b/pytissueoptics/rayscattering/display/utils/__init__.py @@ -6,5 +6,5 @@ "DEFAULT_Y_VIEW_DIRECTIONS", "DEFAULT_Z_VIEW_DIRECTIONS", "Direction", - "VolumeSlicer" + "VolumeSlicer", ] diff --git a/pytissueoptics/rayscattering/display/utils/direction.py b/pytissueoptics/rayscattering/display/utils/direction.py index 6b4006fd..b890bd73 100644 --- a/pytissueoptics/rayscattering/display/utils/direction.py +++ b/pytissueoptics/rayscattering/display/utils/direction.py @@ -14,7 +14,7 @@ def isSameAxisAs(self, other) -> bool: @property def axis(self) -> int: - """ Returns an integer between 0 and 2 representing the x, y, or z axis, ignoring direction sign. """ + """Returns an integer between 0 and 2 representing the x, y, or z axis, ignoring direction sign.""" return self.value % 3 @property diff --git a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py index 6e7436e6..aa2f30ec 100644 --- a/pytissueoptics/rayscattering/display/utils/volumeSlicer.py +++ b/pytissueoptics/rayscattering/display/utils/volumeSlicer.py @@ -12,31 +12,24 @@ from tvtk.pyface.scene import Scene try: - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # The layout of the dialog created - #--------------------------------------------------------------------------- - VIEW = View(HGroup( - Group( - Item('scene_y', - editor=SceneEditor(scene_class=Scene), - height=250, width=300), - Item('scene_z', - editor=SceneEditor(scene_class=Scene), - height=250, width=300), - show_labels=False, + # --------------------------------------------------------------------------- + VIEW = View( + HGroup( + Group( + Item("scene_y", editor=SceneEditor(scene_class=Scene), height=250, width=300), + Item("scene_z", editor=SceneEditor(scene_class=Scene), height=250, width=300), + show_labels=False, + ), + Group( + Item("scene_x", editor=SceneEditor(scene_class=Scene), height=250, width=300), + Item("scene3d", editor=SceneEditor(scene_class=MayaviScene), height=250, width=300), + show_labels=False, + ), ), - Group( - Item('scene_x', - editor=SceneEditor(scene_class=Scene), - height=250, width=300), - Item('scene3d', - editor=SceneEditor(scene_class=MayaviScene), - height=250, width=300), - show_labels=False, - ), - ), resizable=True, - title='Volume Slicer', + title="Volume Slicer", ) except Exception as e: VIEW = e @@ -64,8 +57,8 @@ class VolumeSlicer(HasTraits): view = VIEW - #--------------------------------------------------------------------------- - def __init__(self, hist3D: np.ndarray, colormap: str = 'viridis', interpolate=False, **traits): + # --------------------------------------------------------------------------- + def __init__(self, hist3D: np.ndarray, colormap: str = "viridis", interpolate=False, **traits): self._colormap = colormap self._cameraView = {"azimuth": -30, "zenith": 215, "distance": None, "pointingTowards": None, "roll": -0} self._cameraPitch = -3 @@ -85,33 +78,34 @@ def __init__(self, hist3D: np.ndarray, colormap: str = 'viridis', interpolate=Fa def show(self): self.configure_traits() - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Default values - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- def _data_src3d_default(self): - return mlab.pipeline.scalar_field(self.data, - figure=self.scene3d.mayavi_scene) + return mlab.pipeline.scalar_field(self.data, figure=self.scene3d.mayavi_scene) def make_ipw_3d(self, axis_name): - ipw = mlab.pipeline.image_plane_widget(self.data_src3d, - figure=self.scene3d.mayavi_scene, - plane_orientation='%s_axes' % axis_name, colormap=self._colormap) + ipw = mlab.pipeline.image_plane_widget( + self.data_src3d, + figure=self.scene3d.mayavi_scene, + plane_orientation="%s_axes" % axis_name, + colormap=self._colormap, + ) return ipw def _ipw_3d_x_default(self): - return self.make_ipw_3d('x') + return self.make_ipw_3d("x") def _ipw_3d_y_default(self): - return self.make_ipw_3d('y') + return self.make_ipw_3d("y") def _ipw_3d_z_default(self): - return self.make_ipw_3d('z') - + return self.make_ipw_3d("z") - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Scene activation callbaks - #--------------------------------------------------------------------------- - @on_trait_change('scene3d.activated') + # --------------------------------------------------------------------------- + @on_trait_change("scene3d.activated") def display_scene3d(self): self.scene3d.mlab.view(*self._cameraView.values()) @@ -123,36 +117,32 @@ def display_scene3d(self): self.scene3d.scene.background = (0.11, 0.11, 0.11) # Keep the view always pointing up - self.scene3d.scene.interactor.interactor_style = \ - tvtk.InteractorStyleTerrain() - + self.scene3d.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain() def make_side_view(self, axis_name): - scene = getattr(self, 'scene_%s' % axis_name) + scene = getattr(self, "scene_%s" % axis_name) # To avoid copying the data, we take a reference to the # raw VTK dataset, and pass it on to mlab. Mlab will create # a Mayavi source from the VTK without copying it. # We have to specify the figure so that the data gets # added on the figure we are interested in. - outline = mlab.pipeline.outline( - self.data_src3d.mlab_source.dataset, - figure=scene.mayavi_scene) + outline = mlab.pipeline.outline(self.data_src3d.mlab_source.dataset, figure=scene.mayavi_scene) ipw = mlab.pipeline.image_plane_widget( - outline, - plane_orientation='%s_axes' % axis_name, colormap=self._colormap) + outline, plane_orientation="%s_axes" % axis_name, colormap=self._colormap + ) if not self._interpolate: ipw.ipw.texture_interpolate = "off" ipw.ipw.set_input_data(ipw.ipw._get_input()) - setattr(self, 'ipw_%s' % axis_name, ipw) + setattr(self, "ipw_%s" % axis_name, ipw) # Synchronize positions between the corresponding image plane # widgets on different views. - ipw.ipw.sync_trait('slice_position', - getattr(self, 'ipw_3d_%s'% axis_name).ipw) + ipw.ipw.sync_trait("slice_position", getattr(self, "ipw_3d_%s" % axis_name).ipw) # Make left-clicking create a crosshair ipw.ipw.left_button_action = 0 + # Add a callback on the image plane widget interaction to # move the others def move_view(obj, evt): @@ -160,42 +150,39 @@ def move_view(obj, evt): for other_axis, axis_number in self._axis_names.items(): if other_axis == axis_name: continue - ipw3d = getattr(self, 'ipw_3d_%s' % other_axis) + ipw3d = getattr(self, "ipw_3d_%s" % other_axis) ipw3d.ipw.slice_position = position[axis_number] - ipw.ipw.add_observer('InteractionEvent', move_view) - ipw.ipw.add_observer('StartInteractionEvent', move_view) + ipw.ipw.add_observer("InteractionEvent", move_view) + ipw.ipw.add_observer("StartInteractionEvent", move_view) # Center the image plane widget - ipw.ipw.slice_position = 0.5*self.data.shape[ - self._axis_names[axis_name]] + ipw.ipw.slice_position = 0.5 * self.data.shape[self._axis_names[axis_name]] scene.mlab.view(*self._cameraView.values()) scene.mlab.pitch(self._cameraPitch) # 2D interaction: only pan and zoom - scene.scene.interactor.interactor_style = \ - tvtk.InteractorStyleImage() + scene.scene.interactor.interactor_style = tvtk.InteractorStyleImage() scene.scene.background = (0.11, 0.11, 0.11) - - @on_trait_change('scene_x.activated') + @on_trait_change("scene_x.activated") def display_scene_x(self): - return self.make_side_view('x') + return self.make_side_view("x") - @on_trait_change('scene_y.activated') + @on_trait_change("scene_y.activated") def display_scene_y(self): - return self.make_side_view('y') + return self.make_side_view("y") - @on_trait_change('scene_z.activated') + @on_trait_change("scene_z.activated") def display_scene_z(self): - return self.make_side_view('z') + return self.make_side_view("z") -if __name__ == '__main__': +if __name__ == "__main__": # Volume Slicer example with some data x, y, z = np.ogrid[-5:5:64j, -5:5:64j, -5:5:64j] - data = np.sin(3 * x) / x + 0.05 * z ** 2 + np.cos(3 * y) + data = np.sin(3 * x) / x + 0.05 * z**2 + np.cos(3 * y) m = VolumeSlicer(data) m.show() diff --git a/pytissueoptics/rayscattering/display/viewer.py b/pytissueoptics/rayscattering/display/viewer.py index 91bc30cd..1f21280c 100644 --- a/pytissueoptics/rayscattering/display/viewer.py +++ b/pytissueoptics/rayscattering/display/viewer.py @@ -20,6 +20,7 @@ class Visibility(Flag): A Visibility is a bit Flag representing what to show inside a 3D visualization. They can be combined with the `|` operator (bitwise OR). `AUTO` will automatically switch to DEFAULT_3D if 3D data is present, else DEFAULT_2D. """ + SCENE = 1 SOURCE = 2 POINT_CLOUD = 4 @@ -52,11 +53,23 @@ class PointCloudStyle: surfaceReverseColormap (bool): Same as `reverseColormap` but for the surface points. """ - def __init__(self, solidLabel: str = None, surfaceLabel: str = None, showSolidPoints: bool = True, - showSurfacePointsLeaving: bool = True, showSurfacePointsEntering: bool = False, - showPointsAsSpheres: bool = False, pointSize: float = 0.15, scaleWithValue: bool = True, - colormap: str = "rainbow", reverseColormap: bool = False, surfacePointSize: float = 0.01, - surfaceScaleWithValue: bool = False, surfaceColormap: str = None, surfaceReverseColormap: bool = None): + def __init__( + self, + solidLabel: str = None, + surfaceLabel: str = None, + showSolidPoints: bool = True, + showSurfacePointsLeaving: bool = True, + showSurfacePointsEntering: bool = False, + showPointsAsSpheres: bool = False, + pointSize: float = 0.15, + scaleWithValue: bool = True, + colormap: str = "rainbow", + reverseColormap: bool = False, + surfacePointSize: float = 0.01, + surfaceScaleWithValue: bool = False, + surfaceColormap: str = None, + surfaceReverseColormap: bool = None, + ): self.solidLabel = solidLabel self.surfaceLabel = surfaceLabel self.showSolidPoints = showSolidPoints @@ -89,9 +102,16 @@ def __init__(self, scene: ScatteringScene, source: Source, logger: EnergyLogger) def listViews(self): return self._logger.listViews() - def show3D(self, visibility=Visibility.AUTO, viewsVisibility: Union[ViewGroup, List[int]] = ViewGroup.SCENE, - pointCloudStyle=PointCloudStyle(), viewsSolidLabels: List[str] = None, viewsSurfaceLabels: List[str] = None, - viewsLogScale: bool = True, viewsColormap: str = "viridis"): + def show3D( + self, + visibility=Visibility.AUTO, + viewsVisibility: Union[ViewGroup, List[int]] = ViewGroup.SCENE, + pointCloudStyle=PointCloudStyle(), + viewsSolidLabels: List[str] = None, + viewsSurfaceLabels: List[str] = None, + viewsLogScale: bool = True, + viewsColormap: str = "viridis", + ): if not MAYAVI_AVAILABLE: utils.warn("Package 'mayavi' is not available. Please install it to use 3D visualizations.") return @@ -119,8 +139,13 @@ def show3D(self, visibility=Visibility.AUTO, viewsVisibility: Union[ViewGroup, L self._viewer3D.show() - def show3DVolumeSlicer(self, binSize: float = None, logScale: bool = True, interpolate: bool = False, - limits: Tuple[tuple, tuple, tuple]=None): + def show3DVolumeSlicer( + self, + binSize: float = None, + logScale: bool = True, + interpolate: bool = False, + limits: Tuple[tuple, tuple, tuple] = None, + ): if not MAYAVI_AVAILABLE: utils.warn("ERROR: Package 'mayavi' is not available. Please install it to use 3D visualizations.") return @@ -138,15 +163,19 @@ def show3DVolumeSlicer(self, binSize: float = None, logScale: bool = True, inter # np.histogramdd only works in float64 and requires around 3 times the final memory. requiredMemoryInGB = 3 * 8 * bins[0] * bins[1] * bins[2] / 1024**3 if requiredMemoryInGB > 4: - utils.warn(f"WARNING: The volume slicer will require a lot of memory ({round(requiredMemoryInGB, 2)} GB). " - f"Consider using a larger binSize or tighter limits.") + utils.warn( + f"WARNING: The volume slicer will require a lot of memory ({round(requiredMemoryInGB, 2)} GB). " + f"Consider using a larger binSize or tighter limits." + ) points = self._pointCloudFactory.getPointCloudOfSolids().solidPoints try: hist, _ = np.histogramdd(points[:, 1:], bins=bins, weights=points[:, 0], range=limits) except MemoryError: - utils.warn("ERROR: Not enough memory to create the volume slicer. " - "Consider using a larger binSize or tighter limits.") + utils.warn( + "ERROR: Not enough memory to create the volume slicer. " + "Consider using a larger binSize or tighter limits." + ) return hist = hist.astype(np.float32) @@ -154,6 +183,7 @@ def show3DVolumeSlicer(self, binSize: float = None, logScale: bool = True, inter hist = utils.logNorm(hist) from pytissueoptics.rayscattering.display.utils.volumeSlicer import VolumeSlicer + slicer = VolumeSlicer(hist, interpolate=interpolate) slicer.show() @@ -166,9 +196,16 @@ def show2DAllViews(self, viewGroup=ViewGroup.ALL): continue self.show2D(viewIndex=i) - def show1D(self, along: Direction, logScale: bool = True, - solidLabel: str = None, surfaceLabel: str = None, surfaceEnergyLeaving: bool = True, - limits: Tuple[float, float] = None, binSize: float = None): + def show1D( + self, + along: Direction, + logScale: bool = True, + solidLabel: str = None, + surfaceLabel: str = None, + surfaceEnergyLeaving: bool = True, + limits: Tuple[float, float] = None, + binSize: float = None, + ): profile = self._profileFactory.create(along, solidLabel, surfaceLabel, surfaceEnergyLeaving, limits, binSize) profile.show(logScale=logScale) @@ -188,9 +225,14 @@ def _drawPointCloudOfSolids(self, pointCloud: PointCloud, style: PointCloudStyle if not style.showSolidPoints: return - self._viewer3D.addDataPoints(pointCloud.solidPoints, scale=style.pointSize, - scaleWithValue=style.scaleWithValue, colormap=style.colormap, - reverseColormap=style.reverseColormap, asSpheres=style.showPointsAsSpheres) + self._viewer3D.addDataPoints( + pointCloud.solidPoints, + scale=style.pointSize, + scaleWithValue=style.scaleWithValue, + colormap=style.colormap, + reverseColormap=style.reverseColormap, + asSpheres=style.showPointsAsSpheres, + ) def _drawPointCloudOfSurfaces(self, pointCloud: PointCloud, style: PointCloudStyle): if pointCloud.surfacePoints is None: @@ -205,13 +247,23 @@ def _drawPointCloudOfSurfaces(self, pointCloud: PointCloud, style: PointCloudSty if len(surfacePoints) == 0: return - self._viewer3D.addDataPoints(surfacePoints, scale=style.surfacePointSize, - scaleWithValue=style.surfaceScaleWithValue, colormap=style.surfaceColormap, - reverseColormap=style.surfaceReverseColormap, - asSpheres=style.showPointsAsSpheres) - - def _addViews(self, viewsVisibility: Union[ViewGroup, List[int]], solidLabels: List[str] = None, - surfaceLabels: List[str] = None, logScale: bool = True, colormap: str = "viridis"): + self._viewer3D.addDataPoints( + surfacePoints, + scale=style.surfacePointSize, + scaleWithValue=style.surfaceScaleWithValue, + colormap=style.surfaceColormap, + reverseColormap=style.surfaceReverseColormap, + asSpheres=style.showPointsAsSpheres, + ) + + def _addViews( + self, + viewsVisibility: Union[ViewGroup, List[int]], + solidLabels: List[str] = None, + surfaceLabels: List[str] = None, + logScale: bool = True, + colormap: str = "viridis", + ): if isinstance(viewsVisibility, list): for viewIndex in viewsVisibility: self._addView(self._logger.getView(viewIndex), logScale, colormap) @@ -220,8 +272,11 @@ def _addViews(self, viewsVisibility: Union[ViewGroup, List[int]], solidLabels: L for view in self._logger.views: correctGroup = view.group in viewsVisibility correctSolidLabel = solidLabels is None or utils.labelContained(view.solidLabel, solidLabels) - correctSurfaceLabel = surfaceLabels is None or view.surfaceLabel is None or \ - utils.labelContained(view.surfaceLabel, surfaceLabels) + correctSurfaceLabel = ( + surfaceLabels is None + or view.surfaceLabel is None + or utils.labelContained(view.surfaceLabel, surfaceLabels) + ) if correctGroup and correctSolidLabel and correctSurfaceLabel: self._addView(view, logScale, colormap) diff --git a/pytissueoptics/rayscattering/display/views/defaultViews.py b/pytissueoptics/rayscattering/display/views/defaultViews.py index 6bdab213..1f316811 100644 --- a/pytissueoptics/rayscattering/display/views/defaultViews.py +++ b/pytissueoptics/rayscattering/display/views/defaultViews.py @@ -12,40 +12,72 @@ class View2DProjection(View2D): - def __init__(self, projectionDirection: Direction, horizontalDirection: Direction, solidLabel: str = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(projectionDirection, horizontalDirection, - solidLabel=solidLabel, limits=limits, binSize=binSize) + def __init__( + self, + projectionDirection: Direction, + horizontalDirection: Direction, + solidLabel: str = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + projectionDirection, horizontalDirection, solidLabel=solidLabel, limits=limits, binSize=binSize + ) def _filter(self, dataPoints: np.ndarray) -> np.ndarray: return dataPoints class View2DProjectionX(View2DProjection): - def __init__(self, solidLabel: str = None, limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): + def __init__( + self, + solidLabel: str = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): super().__init__(*DEFAULT_X_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize) class View2DProjectionY(View2DProjection): - def __init__(self, solidLabel: str = None, limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): + def __init__( + self, + solidLabel: str = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): super().__init__(*DEFAULT_Y_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize) class View2DProjectionZ(View2DProjection): - def __init__(self, solidLabel: str = None, limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): + def __init__( + self, + solidLabel: str = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): super().__init__(*DEFAULT_Z_VIEW_DIRECTIONS, solidLabel=solidLabel, limits=limits, binSize=binSize) class View2DSurface(View2D): - def __init__(self, projectionDirection: Direction, horizontalDirection: Direction, solidLabel: str, surfaceLabel: str, - surfaceEnergyLeaving: bool = True, limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(projectionDirection, horizontalDirection, solidLabel=solidLabel, surfaceLabel=surfaceLabel, - surfaceEnergyLeaving=surfaceEnergyLeaving, limits=limits, binSize=binSize) + def __init__( + self, + projectionDirection: Direction, + horizontalDirection: Direction, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool = True, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + projectionDirection, + horizontalDirection, + solidLabel=solidLabel, + surfaceLabel=surfaceLabel, + surfaceEnergyLeaving=surfaceEnergyLeaving, + limits=limits, + binSize=binSize, + ) def _filter(self, dataPoints: np.ndarray) -> np.ndarray: if self._surfaceEnergyLeaving: @@ -61,36 +93,82 @@ def group(self) -> ViewGroup: class View2DSurfaceX(View2DSurface): - def __init__(self, solidLabel: str, surfaceLabel: str, surfaceEnergyLeaving: bool = True, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_X_VIEW_DIRECTIONS, solidLabel=solidLabel, surfaceLabel=surfaceLabel, - surfaceEnergyLeaving=surfaceEnergyLeaving, limits=limits, binSize=binSize) + def __init__( + self, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool = True, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_X_VIEW_DIRECTIONS, + solidLabel=solidLabel, + surfaceLabel=surfaceLabel, + surfaceEnergyLeaving=surfaceEnergyLeaving, + limits=limits, + binSize=binSize, + ) class View2DSurfaceY(View2DSurface): - def __init__(self, solidLabel: str, surfaceLabel: str, surfaceEnergyLeaving: bool = True, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_Y_VIEW_DIRECTIONS, solidLabel=solidLabel, surfaceLabel=surfaceLabel, - surfaceEnergyLeaving=surfaceEnergyLeaving, limits=limits, binSize=binSize) + def __init__( + self, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool = True, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_Y_VIEW_DIRECTIONS, + solidLabel=solidLabel, + surfaceLabel=surfaceLabel, + surfaceEnergyLeaving=surfaceEnergyLeaving, + limits=limits, + binSize=binSize, + ) class View2DSurfaceZ(View2DSurface): - def __init__(self, solidLabel: str, surfaceLabel: str, surfaceEnergyLeaving: bool = True, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_Z_VIEW_DIRECTIONS, solidLabel=solidLabel, surfaceLabel=surfaceLabel, - surfaceEnergyLeaving=surfaceEnergyLeaving, limits=limits, binSize=binSize) + def __init__( + self, + solidLabel: str, + surfaceLabel: str, + surfaceEnergyLeaving: bool = True, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_Z_VIEW_DIRECTIONS, + solidLabel=solidLabel, + surfaceLabel=surfaceLabel, + surfaceEnergyLeaving=surfaceEnergyLeaving, + limits=limits, + binSize=binSize, + ) class View2DSlice(View2D): - def __init__(self, projectionDirection: Direction, horizontalDirection: Direction, position: float, - solidLabel: str = None, thickness: float = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(projectionDirection, horizontalDirection, solidLabel=solidLabel, position=position, - thickness=thickness, limits=limits, binSize=binSize) + def __init__( + self, + projectionDirection: Direction, + horizontalDirection: Direction, + position: float, + solidLabel: str = None, + thickness: float = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + projectionDirection, + horizontalDirection, + solidLabel=solidLabel, + position=position, + thickness=thickness, + limits=limits, + binSize=binSize, + ) def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float, float, float]): if self._thickness is None: @@ -99,30 +177,64 @@ def setContext(self, limits3D: List[Tuple[float, float]], binSize3D: Tuple[float def _filter(self, dataPoints: np.ndarray) -> np.ndarray: dataPositions = dataPoints[:, 1 + self.axis] - insideSlice = np.logical_and(dataPositions > self._position - self._thickness / 2, - dataPositions < self._position + self._thickness / 2) + insideSlice = np.logical_and( + dataPositions > self._position - self._thickness / 2, dataPositions < self._position + self._thickness / 2 + ) return dataPoints[insideSlice] class View2DSliceX(View2DSlice): - def __init__(self, position: float, solidLabel: str = None, thickness: float = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_X_VIEW_DIRECTIONS, position=position, solidLabel=solidLabel, thickness=thickness, - limits=limits, binSize=binSize) + def __init__( + self, + position: float, + solidLabel: str = None, + thickness: float = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_X_VIEW_DIRECTIONS, + position=position, + solidLabel=solidLabel, + thickness=thickness, + limits=limits, + binSize=binSize, + ) class View2DSliceY(View2DSlice): - def __init__(self, position: float, solidLabel: str = None, thickness: float = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_Y_VIEW_DIRECTIONS, position=position, solidLabel=solidLabel, thickness=thickness, - limits=limits, binSize=binSize) + def __init__( + self, + position: float, + solidLabel: str = None, + thickness: float = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_Y_VIEW_DIRECTIONS, + position=position, + solidLabel=solidLabel, + thickness=thickness, + limits=limits, + binSize=binSize, + ) class View2DSliceZ(View2DSlice): - def __init__(self, position: float, solidLabel: str = None, thickness: float = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): - super().__init__(*DEFAULT_Z_VIEW_DIRECTIONS, position=position, solidLabel=solidLabel, thickness=thickness, - limits=limits, binSize=binSize) + def __init__( + self, + position: float, + solidLabel: str = None, + thickness: float = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): + super().__init__( + *DEFAULT_Z_VIEW_DIRECTIONS, + position=position, + solidLabel=solidLabel, + thickness=thickness, + limits=limits, + binSize=binSize, + ) diff --git a/pytissueoptics/rayscattering/display/views/view2D.py b/pytissueoptics/rayscattering/display/views/view2D.py index 2bb88b74..021edb13 100644 --- a/pytissueoptics/rayscattering/display/views/view2D.py +++ b/pytissueoptics/rayscattering/display/views/view2D.py @@ -27,6 +27,7 @@ class ViewGroup(Flag): Except for surface groups, where the default views created include a single 2D projection in the direction of the surface normal. `SURFACES_ENTERING` specifies the energy that entered the surface (energy direction opposite to the surface normal). """ + SCENE = 1 SOLIDS = 2 SURFACES_ENTERING = 4 @@ -36,11 +37,18 @@ class ViewGroup(Flag): class View2D: - def __init__(self, projectionDirection: Direction, horizontalDirection: Direction, - solidLabel: str = None, surfaceLabel: str = None, surfaceEnergyLeaving: bool = True, - position: float = None, thickness: float = None, - limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, - binSize: Union[float, Tuple[int, int]] = None): + def __init__( + self, + projectionDirection: Direction, + horizontalDirection: Direction, + solidLabel: str = None, + surfaceLabel: str = None, + surfaceEnergyLeaving: bool = True, + position: float = None, + thickness: float = None, + limits: Tuple[Tuple[float, float], Tuple[float, float]] = None, + binSize: Union[float, Tuple[int, int]] = None, + ): """ The 2D view plane is obtained by looking towards the 'projectionDirection'. The 'horizontalDirection' represents which axis to use as the horizontal axis in the resulting 2D view. If the 'horizontalDirection' is @@ -58,8 +66,9 @@ def __init__(self, projectionDirection: Direction, horizontalDirection: Directio """ self._projectionDirection = projectionDirection self._horizontalDirection = horizontalDirection - assert not self._projectionDirection.isSameAxisAs(self._horizontalDirection), "Projection and horizontal " \ - "directions must be orthogonal." + assert not self._projectionDirection.isSameAxisAs(self._horizontalDirection), ( + "Projection and horizontal directions must be orthogonal." + ) self._solidLabel = solidLabel self._surfaceLabel = surfaceLabel @@ -118,8 +127,9 @@ def extractData(self, dataPoints: np.ndarray): return u, v, w = dataPoints[:, 1 + self.axisU], dataPoints[:, 1 + self.axisV], dataPoints[:, 0] - sumUVProjection = np.histogram2d(u, v, weights=w, bins=(self._binsU, self._binsV), - range=(sorted(self._limitsU), sorted(self._limitsV)))[0] + sumUVProjection = np.histogram2d( + u, v, weights=w, bins=(self._binsU, self._binsV), range=(sorted(self._limitsU), sorted(self._limitsV)) + )[0] self._dataUV += np.flip(sumUVProjection, axis=1) self._hasData = True @@ -131,7 +141,7 @@ def _filter(self, dataPoints: np.ndarray) -> np.ndarray: raise NotImplementedError() def flip(self): - """ Flips the view as if it was seen from behind. """ + """Flips the view as if it was seen from behind.""" self._projectionDirection = Direction((self._projectionDirection.value + 3) % 6) self._horizontalDirection = Direction((self._horizontalDirection.value + 3) % 6) @@ -189,7 +199,7 @@ def getImageDataWithDefaultAlignment(self, logScale: bool = True) -> np.ndarray: return image - def show(self, logScale: bool = True, colormap: str = 'viridis'): + def show(self, logScale: bool = True, colormap: str = "viridis"): cmap = copy.copy(matplotlib.cm.get_cmap(colormap)) cmap.set_bad(cmap.colors[0]) @@ -198,12 +208,12 @@ def show(self, logScale: bool = True, colormap: str = 'viridis'): # N.B.: imshow() expects the data to be (y, x), so we need to transpose the array. plt.imshow(image.T, cmap=cmap, extent=self._limitsU + self._limitsV) plt.title(self.name) - plt.xlabel('xyz'[self.axisU]) - plt.ylabel('xyz'[self.axisV]) + plt.xlabel("xyz"[self.axisU]) + plt.ylabel("xyz"[self.axisV]) plt.show() - def initDataFrom(self, source: 'View2D'): - """ Extract data from one view to another when there is only a difference in orientation. """ + def initDataFrom(self, source: "View2D"): + """Extract data from one view to another when there is only a difference in orientation.""" assert self.isContainedBy(source), "Cannot extract data from views that are not equivalent." dataUV = source._dataUV.copy() @@ -213,7 +223,7 @@ def initDataFrom(self, source: 'View2D'): self._dataUV = np.flip(dataUV.T, axis=(0, 1)) self._hasData = source._hasData - def isEqualTo(self, other: 'View2D') -> bool: + def isEqualTo(self, other: "View2D") -> bool: if not self.isContainedBy(other): return False if self._projectionDirection != other._projectionDirection: @@ -228,7 +238,7 @@ def isEqualTo(self, other: 'View2D') -> bool: return False return True - def isContainedBy(self, other: 'View2D') -> bool: + def isContainedBy(self, other: "View2D") -> bool: if self._projectionDirection.axis != other._projectionDirection.axis: return False if not utils.labelsEqual(self._solidLabel, other._solidLabel): @@ -302,22 +312,22 @@ def size(self) -> Tuple[float, float]: @property def axis(self) -> int: - """ The axis that represents the plane of the 2D view. """ + """The axis that represents the plane of the 2D view.""" return self._projectionDirection.axis @property def axisU(self) -> int: - """ The horizontal axis of the 2D view. Could also be referred to as the 'x' axis. """ + """The horizontal axis of the 2D view. Could also be referred to as the 'x' axis.""" return self._horizontalDirection.axis @property def axisV(self) -> int: - """ The vertical axis of the 2D view. Could also be referred to as the 'y' axis. """ + """The vertical axis of the 2D view. Could also be referred to as the 'y' axis.""" return 3 - self.axis - self.axisU @property def _verticalIsNegative(self) -> bool: - """ Algorithm for cartesian axes to know if the resulting vertical axis is negative (the axis unit vector + """Algorithm for cartesian axes to know if the resulting vertical axis is negative (the axis unit vector goes down from the viewer's point of view). """ horizontalAxisForNegativeVertical = (self._projectionDirection.axis + self._projectionDirection.sign) % 3 @@ -341,8 +351,7 @@ def name(self) -> str: @property def description(self) -> str: - return f"{self.name} towards {self._projectionDirection.name} " \ - f"with {self._horizontalDirection.name} horizontal." + return f"{self.name} towards {self._projectionDirection.name} with {self._horizontalDirection.name} horizontal." @property def group(self) -> ViewGroup: diff --git a/pytissueoptics/rayscattering/display/views/viewFactory.py b/pytissueoptics/rayscattering/display/views/viewFactory.py index 3fec5da2..01113c97 100644 --- a/pytissueoptics/rayscattering/display/views/viewFactory.py +++ b/pytissueoptics/rayscattering/display/views/viewFactory.py @@ -16,8 +16,9 @@ class ViewFactory: - def __init__(self, scene: ScatteringScene, defaultBinSize: Union[float, Tuple[float, float, float]], - infiniteLimits: tuple): + def __init__( + self, scene: ScatteringScene, defaultBinSize: Union[float, Tuple[float, float, float]], infiniteLimits: tuple + ): self._scene = scene self._defaultBinSize3D = defaultBinSize @@ -55,8 +56,14 @@ def _createFromGroup(self, viewGroup: ViewGroup): return views - def _getDefaultSurfaceViews(self, solidLabel: str, surfaceLabel: str, - includeLeaving: bool, includeEntering: bool, takenFromSolid: str = None) -> List[View2D]: + def _getDefaultSurfaceViews( + self, + solidLabel: str, + surfaceLabel: str, + includeLeaving: bool, + includeEntering: bool, + takenFromSolid: str = None, + ) -> List[View2D]: if takenFromSolid is None: takenFromSolid = solidLabel surfaceNormal = self._getSurfaceNormal(takenFromSolid, surfaceLabel) @@ -85,12 +92,15 @@ def _getSurfaceNormal(self, solidLabel: str, surfaceLabel: str): return surfaceNormal - def _getDefaultContainedSurfacesViews(self, solidLabel: str, includeLeaving: bool, includeEntering: bool) -> List[View2D]: + def _getDefaultContainedSurfacesViews( + self, solidLabel: str, includeLeaving: bool, includeEntering: bool + ) -> List[View2D]: views = [] for containedSolidLabel in self._scene.getContainedSolidLabels(solidLabel): for surfaceLabel in self._scene.getSurfaceLabels(containedSolidLabel): - views += self._getDefaultSurfaceViews(solidLabel, surfaceLabel, includeLeaving, includeEntering, - takenFromSolid=containedSolidLabel) + views += self._getDefaultSurfaceViews( + solidLabel, surfaceLabel, includeLeaving, includeEntering, takenFromSolid=containedSolidLabel + ) return views def _setContext(self, view: View2D): @@ -98,8 +108,11 @@ def _setContext(self, view: View2D): solid = self._scene.getSolid(view.solidLabel) limits3D = solid.getBoundingBox().xyzLimits if not self._viewHasValidSurfaceLabel(view): - utils.warn("Surface label '{}' not found in solid '{}'. Available surface labels: {}".format( - view.surfaceLabel, view.solidLabel, solid.surfaceLabels)) + utils.warn( + "Surface label '{}' not found in solid '{}'. Available surface labels: {}".format( + view.surfaceLabel, view.solidLabel, solid.surfaceLabels + ) + ) else: sceneBoundingBox = self._scene.getBoundingBox() if sceneBoundingBox is None: @@ -126,6 +139,8 @@ def _viewHasValidSurfaceLabel(self, view) -> bool: @staticmethod def _getDefaultViewsXYZ(solidLabel: str = None) -> List[View2D]: - return [View2DProjectionX(solidLabel=solidLabel), - View2DProjectionY(solidLabel=solidLabel), - View2DProjectionZ(solidLabel=solidLabel)] + return [ + View2DProjectionX(solidLabel=solidLabel), + View2DProjectionY(solidLabel=solidLabel), + View2DProjectionZ(solidLabel=solidLabel), + ] diff --git a/pytissueoptics/rayscattering/energyLogging/energyLogger.py b/pytissueoptics/rayscattering/energyLogging/energyLogger.py index f67529ec..ed63576b 100644 --- a/pytissueoptics/rayscattering/energyLogging/energyLogger.py +++ b/pytissueoptics/rayscattering/energyLogging/energyLogger.py @@ -13,9 +13,15 @@ class EnergyLogger(Logger): - def __init__(self, scene: ScatteringScene, filepath: str = None, keep3D: bool = True, - views: Union[ViewGroup, List[View2D]] = ViewGroup.ALL, defaultBinSize: Union[float, tuple] = 0.01, - infiniteLimits=((-5, 5), (-5, 5), (-5, 5))): + def __init__( + self, + scene: ScatteringScene, + filepath: str = None, + keep3D: bool = True, + views: Union[ViewGroup, List[View2D]] = ViewGroup.ALL, + defaultBinSize: Union[float, tuple] = 0.01, + infiniteLimits=((-5, 5), (-5, 5), (-5, 5)), + ): """ Log the energy deposited by scattering photons as well as the energy that crossed surfaces. Every interaction is linked to a specific solid and surface of the scene when applicable. This `EnergyLogger` has to be given to @@ -74,16 +80,17 @@ def addView(self, view: View2D) -> bool: self._views.append(view) return True - utils.warn(f"ERROR: Cannot create view {view.name}. The 3D data was discarded and the required data was not " - f"found in existing views.") + utils.warn( + f"ERROR: Cannot create view {view.name}. The 3D data was discarded and the required data was not " + f"found in existing views." + ) return False def updateView(self, view: View2D): if view in self._outdatedViews: self._compileViews([view]) - def showView(self, view: View2D = None, viewIndex: int = None, - logScale: bool = True, colormap: str = 'viridis'): + def showView(self, view: View2D = None, viewIndex: int = None, logScale: bool = True, colormap: str = "viridis"): assert viewIndex is not None or view is not None, "Either `viewIndex` or `view` must be specified." if viewIndex is None: @@ -111,33 +118,64 @@ def save(self, filepath: str = None): filepath = self._filepath with open(filepath, "wb") as file: - pickle.dump((self._data, self.info, self._labels, self._views, self._defaultViews, self._outdatedViews, - self._nDataPointsRemoved, self._sceneHash, self.has3D), file) + pickle.dump( + ( + self._data, + self.info, + self._labels, + self._views, + self._defaultViews, + self._outdatedViews, + self._nDataPointsRemoved, + self._sceneHash, + self.has3D, + ), + file, + ) def load(self, filepath: str): self._filepath = filepath if not os.path.exists(filepath): - utils.warn("No logger file found at '{}'. No data loaded, but it will create a new file " - "at this location if the logger is saved later on.".format(filepath)) + utils.warn( + "No logger file found at '{}'. No data loaded, but it will create a new file " + "at this location if the logger is saved later on.".format(filepath) + ) return with open(filepath, "rb") as file: - self._data, self.info, self._labels, self._views, oldDefaultViews, self._outdatedViews, \ - self._nDataPointsRemoved, oldSceneHash, oldHas3D = pickle.load(file) + ( + self._data, + self.info, + self._labels, + self._views, + oldDefaultViews, + self._outdatedViews, + self._nDataPointsRemoved, + oldSceneHash, + oldHas3D, + ) = pickle.load(file) if oldSceneHash != self._sceneHash: - utils.warn("WARNING: The scene used to create the logger at '{}' is different from the current " - "scene. This may corrupt statistics and visualization. Proceed at your own risk.".format(filepath)) + utils.warn( + "WARNING: The scene used to create the logger at '{}' is different from the current " + "scene. This may corrupt statistics and visualization. Proceed at your own risk.".format(filepath) + ) if oldHas3D and not self._keep3D: - utils.warn("WARNING: The logger at '{}' use to store 3D data, but it was reloaded with keep3D=False. " - "The 3D data will be compiled to 2D views and discarded.".format(filepath)) + utils.warn( + "WARNING: The logger at '{}' use to store 3D data, but it was reloaded with keep3D=False. " + "The 3D data will be compiled to 2D views and discarded.".format(filepath) + ) if not oldHas3D and self._keep3D: - utils.warn("WARNING: The logger at '{}' use to discard 3D data, but it was reloaded with keep3D=True. " - "This may corrupt the statistics and the 3D visualization. Proceed at your own risk.".format(filepath)) + utils.warn( + "WARNING: The logger at '{}' use to discard 3D data, but it was reloaded with keep3D=True. " + "This may corrupt the statistics and the 3D visualization. Proceed at your own risk.".format(filepath) + ) if self._defaultViews != oldDefaultViews: - utils.warn("WARNING: Cannot provide new default views to a loaded logger from '{}'." - "Using only the views from the file.".format(filepath)) + utils.warn( + "WARNING: Cannot provide new default views to a loaded logger from '{}'." + "Using only the views from the file.".format(filepath) + ) @property def views(self) -> List[View2D]: @@ -145,8 +183,10 @@ def views(self) -> List[View2D]: def getView(self, index: int) -> View2D: if index < 0 or index >= len(self._views): - raise IndexError(f"View index {index} is out of range [0, {len(self._views)}]. Use `.listViews()` to see " - f"available views.") + raise IndexError( + f"View index {index} is out of range [0, {len(self._views)}]. Use `.listViews()` to see " + f"available views." + ) return self._views[index] def _getViewIndex(self, view: View2D) -> int: diff --git a/pytissueoptics/rayscattering/energyLogging/pointCloud.py b/pytissueoptics/rayscattering/energyLogging/pointCloud.py index 6e5e8b19..8354d650 100644 --- a/pytissueoptics/rayscattering/energyLogging/pointCloud.py +++ b/pytissueoptics/rayscattering/energyLogging/pointCloud.py @@ -4,10 +4,9 @@ class PointCloud: - """ Each point is an array of the form (weight, x, y, z) and the resulting point cloud array is of shape (n, 4). """ + """Each point is an array of the form (weight, x, y, z) and the resulting point cloud array is of shape (n, 4).""" - def __init__(self, solidPoints: Optional[np.ndarray] = None, - surfacePoints: Optional[np.ndarray] = None): + def __init__(self, solidPoints: Optional[np.ndarray] = None, surfacePoints: Optional[np.ndarray] = None): self.solidPoints = solidPoints self.surfacePoints = surfacePoints diff --git a/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py b/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py index 55f8af2d..05486585 100644 --- a/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py +++ b/pytissueoptics/rayscattering/energyLogging/pointCloudFactory.py @@ -10,8 +10,7 @@ def __init__(self, logger: Logger): def getPointCloud(self, solidLabel: str = None, surfaceLabel: str = None) -> PointCloud: if not solidLabel and not surfaceLabel: - return PointCloud(self.getPointCloudOfSolids().solidPoints, - self.getPointCloudOfSurfaces().surfacePoints) + return PointCloud(self.getPointCloudOfSolids().solidPoints, self.getPointCloudOfSurfaces().surfacePoints) points = self._logger.getDataPoints(InteractionKey(solidLabel, surfaceLabel)) if surfaceLabel: return PointCloud(None, points) @@ -29,7 +28,9 @@ def getPointCloudOfSolids(self) -> PointCloud: def getPointCloudOfSurfaces(self, solidLabel: str = None) -> PointCloud: points = [] - solidLabels = [solidLabel] if solidLabel else [_solidLabel for _solidLabel in self._logger.getStoredSolidLabels()] + solidLabels = ( + [solidLabel] if solidLabel else [_solidLabel for _solidLabel in self._logger.getStoredSolidLabels()] + ) for _solidLabel in solidLabels: for surfaceLabel in self._logger.getStoredSurfaceLabels(_solidLabel): points.append(self.getPointCloud(_solidLabel, surfaceLabel).surfacePoints) diff --git a/pytissueoptics/rayscattering/fresnel.py b/pytissueoptics/rayscattering/fresnel.py index a1c5a84a..826a6bf6 100644 --- a/pytissueoptics/rayscattering/fresnel.py +++ b/pytissueoptics/rayscattering/fresnel.py @@ -61,7 +61,7 @@ def _getIsReflected(self) -> bool: return False def _getReflectionCoefficient(self) -> float: - """ Fresnel reflection coefficient, directly from MCML code in + """Fresnel reflection coefficient, directly from MCML code in Wang, L-H, S.L. Jacques, L-Q Zheng: MCML - Monte Carlo modeling of photon transport in multi-layered tissues. Computer Methods and Programs in Biomedicine 47:131-146, 1995. @@ -74,8 +74,8 @@ def _getReflectionCoefficient(self) -> float: return 0 if self._thetaIn == 0: - R = (n2-n1)/(n2+n1) - return R*R + R = (n2 - n1) / (n2 + n1) + return R * R sa1 = math.sin(self._thetaIn) @@ -83,14 +83,14 @@ def _getReflectionCoefficient(self) -> float: if sa2 >= 1: return 1 - ca1 = math.sqrt(1-sa1*sa1) - ca2 = math.sqrt(1-sa2*sa2) + ca1 = math.sqrt(1 - sa1 * sa1) + ca2 = math.sqrt(1 - sa2 * sa2) - cap = ca1*ca2 - sa1*sa2 # c+ = cc - ss. - cam = ca1*ca2 + sa1*sa2 # c- = cc + ss. - sap = sa1*ca2 + ca1*sa2 # s+ = sc + cs. - sam = sa1*ca2 - ca1*sa2 # s- = sc - cs. - r = 0.5*sam*sam*(cam*cam+cap*cap)/(sap*sap*cam*cam) + cap = ca1 * ca2 - sa1 * sa2 # c+ = cc - ss. + cam = ca1 * ca2 + sa1 * sa2 # c- = cc + ss. + sap = sa1 * ca2 + ca1 * sa2 # s+ = sc + cs. + sam = sa1 * ca2 - ca1 * sa2 # s- = sc - cs. + r = 0.5 * sam * sam * (cam * cam + cap * cap) / (sap * sap * cam * cam) return r def _getReflectionDeflection(self) -> float: diff --git a/pytissueoptics/rayscattering/opencl/CLPhotons.py b/pytissueoptics/rayscattering/opencl/CLPhotons.py index 95bef074..7e793901 100644 --- a/pytissueoptics/rayscattering/opencl/CLPhotons.py +++ b/pytissueoptics/rayscattering/opencl/CLPhotons.py @@ -14,7 +14,7 @@ from pytissueoptics.scene.geometry import Environment from pytissueoptics.scene.logger.logger import Logger -PROPAGATION_SOURCE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src', 'propagation.c') +PROPAGATION_SOURCE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "src", "propagation.c") class CLPhotons: @@ -43,28 +43,50 @@ def propagate(self, IPP: float, verbose: bool = False): scene = CLScene(self._scene, params.workItemAmount) - kernelPhotons = PhotonCL(self._positions[0:params.maxPhotonsPerBatch], self._directions[0:params.maxPhotonsPerBatch], - materialID=scene.getMaterialID(self._initialMaterial), solidID=scene.getSolidID(self._initialSolid)) - photonPool = PhotonCL(self._positions[params.maxPhotonsPerBatch:], self._directions[params.maxPhotonsPerBatch:], - materialID=scene.getMaterialID(self._initialMaterial), solidID=scene.getSolidID(self._initialSolid)) + kernelPhotons = PhotonCL( + self._positions[0 : params.maxPhotonsPerBatch], + self._directions[0 : params.maxPhotonsPerBatch], + materialID=scene.getMaterialID(self._initialMaterial), + solidID=scene.getSolidID(self._initialSolid), + ) + photonPool = PhotonCL( + self._positions[params.maxPhotonsPerBatch :], + self._directions[params.maxPhotonsPerBatch :], + materialID=scene.getMaterialID(self._initialMaterial), + solidID=scene.getSolidID(self._initialSolid), + ) photonPool.make(program.device) seeds = SeedCL(params.maxPhotonsPerBatch) logger = DataPointCL(size=params.maxLoggableInteractions) photonCount = 0 batchCount = 0 - + if verbose: timing = BatchTiming(self._N) while photonCount < self._N: t1 = time.time_ns() - program.launchKernel(kernelName="propagate", N=np.int32(params.workItemAmount), - arguments=[np.int32(params.photonsPerWorkItem), - np.int32(params.maxLoggableInteractionsPerWorkItem), - self._weightThreshold, np.int32(params.workItemAmount), kernelPhotons, - scene.materials, scene.nSolids, scene.solids, scene.surfaces, scene.triangles, - scene.vertices, scene.solidCandidates, seeds, logger]) + program.launchKernel( + kernelName="propagate", + N=np.int32(params.workItemAmount), + arguments=[ + np.int32(params.photonsPerWorkItem), + np.int32(params.maxLoggableInteractionsPerWorkItem), + self._weightThreshold, + np.int32(params.workItemAmount), + kernelPhotons, + scene.materials, + scene.nSolids, + scene.solids, + scene.surfaces, + scene.triangles, + scene.vertices, + scene.solidCandidates, + seeds, + logger, + ], + ) t2 = time.time_ns() log = program.getData(logger) t3 = time.time_ns() @@ -73,17 +95,24 @@ def propagate(self, IPP: float, verbose: bool = False): logger.reset() program.getData(kernelPhotons, returnData=False) - batchPhotonCount, photonCount = self._replaceFullyPropagatedPhotons(kernelPhotons, photonPool, - photonCount, params.maxPhotonsPerBatch) + batchPhotonCount, photonCount = self._replaceFullyPropagatedPhotons( + kernelPhotons, photonPool, photonCount, params.maxPhotonsPerBatch + ) if verbose: - timing.recordBatch(batchPhotonCount, propagationTime=(t2 - t1), dataTransferTime=(t3 - t2), - dataConversionTime=(t4 - t3), totalTime=(time.time_ns() - t1)) + timing.recordBatch( + batchPhotonCount, + propagationTime=(t2 - t1), + dataTransferTime=(t3 - t2), + dataConversionTime=(t4 - t3), + totalTime=(time.time_ns() - t1), + ) params.maxPhotonsPerBatch = kernelPhotons.length batchCount += 1 - def _replaceFullyPropagatedPhotons(self, kernelPhotons: PhotonCL, photonPool: PhotonCL, photonCount: int, - currentKernelLength: int) -> (int, int): + def _replaceFullyPropagatedPhotons( + self, kernelPhotons: PhotonCL, photonPool: PhotonCL, photonCount: int, currentKernelLength: int + ) -> (int, int): photonsToReplace = np.where(kernelPhotons.hostBuffer["weight"] == 0)[0] batchPhotonCount = len(photonsToReplace) @@ -92,9 +121,9 @@ def _replaceFullyPropagatedPhotons(self, kernelPhotons: PhotonCL, photonPool: Ph newPhotonsRemaining = photonCount + currentKernelLength < self._N if newPhotonsRemaining: - replacement_photons = photonPool.hostBuffer[photonCount:photonCount + batchPhotonCount] - photonsToRemove = photonsToReplace[len(replacement_photons):] - photonsToReplace = photonsToReplace[:len(replacement_photons)] + replacement_photons = photonPool.hostBuffer[photonCount : photonCount + batchPhotonCount] + photonsToRemove = photonsToReplace[len(replacement_photons) :] + photonsToReplace = photonsToReplace[: len(replacement_photons)] kernelPhotons.hostBuffer[photonsToReplace] = replacement_photons else: photonsToRemove = photonsToReplace diff --git a/pytissueoptics/rayscattering/opencl/CLProgram.py b/pytissueoptics/rayscattering/opencl/CLProgram.py index 026e22d9..a16bc351 100644 --- a/pytissueoptics/rayscattering/opencl/CLProgram.py +++ b/pytissueoptics/rayscattering/opencl/CLProgram.py @@ -23,7 +23,7 @@ def __init__(self, sourcePath: str): self._mainQueue = cl.CommandQueue(self._context) self._program: Optional[cl.Program] = None - self._include = '' + self._include = "" self._mocks = [] def release(self): @@ -50,8 +50,7 @@ def launchKernel(self, kernelName: str, N: int, arguments: list, verbose: bool = try: kernel(self._mainQueue, (N,), None, *buffers) except cl.MemoryError: - raise MemoryError(f"Cannot allocate {sizeOnDevice//1024**2} MB on the device;" - f"the buffers are too large.") + raise MemoryError(f"Cannot allocate {sizeOnDevice // 1024**2} MB on the device;the buffers are too large.") self._mainQueue.finish() t2 = time.time() @@ -62,7 +61,7 @@ def _build(self, objects: List[CLObject]): for _object in objects: _object.build(self._device, self._context) - typeDeclarations = ''.join([_object.declaration for _object in objects]) + typeDeclarations = "".join([_object.declaration for _object in objects]) sourceCode = self._include + typeDeclarations + self._makeSource(self._sourcePath) for code, mock in self._mocks: @@ -87,12 +86,12 @@ def include(self, code: str): @staticmethod def _makeSource(sourcePath) -> str: includeDir = os.path.dirname(sourcePath) - sourceCode = '' - with open(sourcePath, 'r') as f: + sourceCode = "" + with open(sourcePath, "r") as f: line = f.readline() while line.startswith("#include"): libFileName = line.split('"')[1] - with open(os.path.join(includeDir, libFileName), 'r') as libFile: + with open(os.path.join(includeDir, libFileName), "r") as libFile: sourceCode += libFile.read() line = f.readline() sourceCode += line diff --git a/pytissueoptics/rayscattering/opencl/CLScene.py b/pytissueoptics/rayscattering/opencl/CLScene.py index 5c69d575..22793a3d 100644 --- a/pytissueoptics/rayscattering/opencl/CLScene.py +++ b/pytissueoptics/rayscattering/opencl/CLScene.py @@ -101,9 +101,17 @@ def _compileSurface(self, polygonRef, firstPolygonID, lastPolygonID): outsideSolidID = self.getSolidID(outsideEnvironment.solid) toSmooth = polygonRef.toSmooth - self._surfacesInfo.append(SurfaceCLInfo(firstPolygonID, lastPolygonID, - insideMaterialID, outsideMaterialID, - insideSolidID, outsideSolidID, toSmooth)) + self._surfacesInfo.append( + SurfaceCLInfo( + firstPolygonID, + lastPolygonID, + insideMaterialID, + outsideMaterialID, + insideSolidID, + outsideSolidID, + toSmooth, + ) + ) def _processSolid(self, solid): solidVertices = solid.getVertices() @@ -126,8 +134,11 @@ def _processSurface(self, surfaceLabel, polygons, vertexToID): # todo: consider skipping this step if the solid is not a stack. currentSolid = triangle.insideEnvironment.solid if lastSolid and lastSolid != currentSolid: - self._compileSurface(polygonRef=polygons[i - 1], - firstPolygonID=firstPolygonID, lastPolygonID=len(self._trianglesInfo) - 1) + self._compileSurface( + polygonRef=polygons[i - 1], + firstPolygonID=firstPolygonID, + lastPolygonID=len(self._trianglesInfo) - 1, + ) firstPolygonID = len(self._trianglesInfo) vertexIDs = [vertexToID[id(v)] for v in triangle.vertices] @@ -136,5 +147,6 @@ def _processSurface(self, surfaceLabel, polygons, vertexToID): self._processPolygon(triangle, surfaceLabel, surfaceID=newSurfaceID) lastSolid = currentSolid - self._compileSurface(polygonRef=polygons[-1], - firstPolygonID=firstPolygonID, lastPolygonID=len(self._trianglesInfo) - 1) + self._compileSurface( + polygonRef=polygons[-1], firstPolygonID=firstPolygonID, lastPolygonID=len(self._trianglesInfo) - 1 + ) diff --git a/pytissueoptics/rayscattering/opencl/__init__.py b/pytissueoptics/rayscattering/opencl/__init__.py index 115d8eb2..703e0ee8 100644 --- a/pytissueoptics/rayscattering/opencl/__init__.py +++ b/pytissueoptics/rayscattering/opencl/__init__.py @@ -31,7 +31,4 @@ def hardwareAccelerationIsAvailable() -> bool: return OPENCL_AVAILABLE and OPENCL_OK -__all__ = [ - "IPPTable", - "WEIGHT_THRESHOLD" -] +__all__ = ["IPPTable", "WEIGHT_THRESHOLD"] diff --git a/pytissueoptics/rayscattering/opencl/buffers/CLObject.py b/pytissueoptics/rayscattering/opencl/buffers/CLObject.py index 04c19a9f..3972ea72 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/CLObject.py +++ b/pytissueoptics/rayscattering/opencl/buffers/CLObject.py @@ -3,9 +3,11 @@ try: import pyopencl as cl except ImportError: + class DummyType: def __getattr__(self, item): return None + cl = DummyType() cl.cltypes = DummyType() @@ -28,8 +30,9 @@ def build(self, device: cl.Device, context): if self._buildOnce: return self.make(device) - self._DEVICE_buffer = cl.Buffer(context, cl.mem_flags.READ_WRITE | cl.mem_flags.USE_HOST_PTR, - hostbuf=self.hostBuffer) + self._DEVICE_buffer = cl.Buffer( + context, cl.mem_flags.READ_WRITE | cl.mem_flags.USE_HOST_PTR, hostbuf=self.hostBuffer + ) def make(self, device): if self.STRUCT_DTYPE: @@ -49,7 +52,7 @@ def name(self) -> str: @property def declaration(self) -> str: if not self._declaration or self._skipDeclaration: - return '' + return "" return self._declaration @property @@ -86,7 +89,7 @@ def nBytes(self) -> int: @classmethod def getItemSize(cls) -> int: - """ Returns the size of a single item in bytes aligned with the next power of 2. """ + """Returns the size of a single item in bytes aligned with the next power of 2.""" if cls.STRUCT_DTYPE is None: raise NotImplementedError() diff --git a/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py b/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py index 1f0ec816..5a3cfeb4 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/dataPointCL.py @@ -7,12 +7,15 @@ class DataPointCL(CLObject): STRUCT_NAME = "DataPoint" STRUCT_DTYPE = np.dtype( - [("delta_weight", cl.cltypes.float), - ("x", cl.cltypes.float), - ("y", cl.cltypes.float), - ("z", cl.cltypes.float), - ("solidID", cl.cltypes.int), - ("surfaceID", cl.cltypes.int)]) + [ + ("delta_weight", cl.cltypes.float), + ("x", cl.cltypes.float), + ("y", cl.cltypes.float), + ("z", cl.cltypes.float), + ("solidID", cl.cltypes.int), + ("surfaceID", cl.cltypes.int), + ] + ) def __init__(self, size: int): self._size = size diff --git a/pytissueoptics/rayscattering/opencl/buffers/materialCL.py b/pytissueoptics/rayscattering/opencl/buffers/materialCL.py index ad277bb1..a1300f32 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/materialCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/materialCL.py @@ -10,12 +10,15 @@ class MaterialCL(CLObject): STRUCT_NAME = "Material" STRUCT_DTYPE = np.dtype( - [("mu_s", cl.cltypes.float), - ("mu_a", cl.cltypes.float), - ("mu_t", cl.cltypes.float), - ("g", cl.cltypes.float), - ("n", cl.cltypes.float), - ("albedo", cl.cltypes.float)]) + [ + ("mu_s", cl.cltypes.float), + ("mu_a", cl.cltypes.float), + ("mu_t", cl.cltypes.float), + ("g", cl.cltypes.float), + ("n", cl.cltypes.float), + ("albedo", cl.cltypes.float), + ] + ) def __init__(self, materials: List[ScatteringMaterial]): self._materials = materials diff --git a/pytissueoptics/rayscattering/opencl/buffers/photonCL.py b/pytissueoptics/rayscattering/opencl/buffers/photonCL.py index dec35b3c..dc6775c7 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/photonCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/photonCL.py @@ -7,15 +7,17 @@ class PhotonCL(CLObject): STRUCT_NAME = "Photon" STRUCT_DTYPE = np.dtype( - [("position", cl.cltypes.float3), - ("direction", cl.cltypes.float3), - ("er", cl.cltypes.float3), - ("weight", cl.cltypes.float), - ("materialID", cl.cltypes.uint), - ("solidID", cl.cltypes.int)]) + [ + ("position", cl.cltypes.float3), + ("direction", cl.cltypes.float3), + ("er", cl.cltypes.float3), + ("weight", cl.cltypes.float), + ("materialID", cl.cltypes.uint), + ("solidID", cl.cltypes.int), + ] + ) - def __init__(self, positions: np.ndarray, directions: np.ndarray, - materialID: int, solidID: int, weight=1.0): + def __init__(self, positions: np.ndarray, directions: np.ndarray, materialID: int, solidID: int, weight=1.0): self._positions = positions self._directions = directions self._N = positions.shape[0] diff --git a/pytissueoptics/rayscattering/opencl/buffers/seedCL.py b/pytissueoptics/rayscattering/opencl/buffers/seedCL.py index 3071bdae..99e7f6a0 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/seedCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/seedCL.py @@ -9,4 +9,4 @@ def __init__(self, size: int): super().__init__(buildOnce=True) def _getInitialHostBuffer(self) -> np.ndarray: - return np.random.randint(low=0, high=2 ** 32 - 1, size=self._size, dtype=cl.cltypes.uint) + return np.random.randint(low=0, high=2**32 - 1, size=self._size, dtype=cl.cltypes.uint) diff --git a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py index 7db07eb2..cf4936e3 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/solidCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/solidCL.py @@ -6,17 +6,19 @@ from .CLObject import CLObject, cl -SolidCLInfo = NamedTuple("SolidInfo", [("bbox", BoundingBox), - ("firstSurfaceID", int), ("lastSurfaceID", int)]) +SolidCLInfo = NamedTuple("SolidInfo", [("bbox", BoundingBox), ("firstSurfaceID", int), ("lastSurfaceID", int)]) class SolidCL(CLObject): STRUCT_NAME = "Solid" STRUCT_DTYPE = np.dtype( - [("bbox_min", cl.cltypes.float3), - ("bbox_max", cl.cltypes.float3), - ("firstSurfaceID", cl.cltypes.uint), - ("lastSurfaceID", cl.cltypes.uint)]) + [ + ("bbox_min", cl.cltypes.float3), + ("bbox_max", cl.cltypes.float3), + ("firstSurfaceID", cl.cltypes.uint), + ("lastSurfaceID", cl.cltypes.uint), + ] + ) def __init__(self, solidsInfo: List[SolidCLInfo]): self._solidsInfo = solidsInfo diff --git a/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py b/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py index 595f42b6..4eed3f56 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/solidCandidateCL.py @@ -5,9 +5,7 @@ class SolidCandidateCL(CLObject): STRUCT_NAME = "SolidCandidate" - STRUCT_DTYPE = np.dtype( - [("distance", cl.cltypes.float), - ("solidID", cl.cltypes.uint)]) + STRUCT_DTYPE = np.dtype([("distance", cl.cltypes.float), ("solidID", cl.cltypes.uint)]) def __init__(self, nWorkUnits: int, nSolids: int): self._size = nWorkUnits * nSolids diff --git a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py index 1f5b60f1..265aef05 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/surfaceCL.py @@ -4,22 +4,33 @@ from .CLObject import CLObject, cl -SurfaceCLInfo = NamedTuple("SurfaceInfo", [("firstPolygonID", int), ("lastPolygonID", int), - ("insideMaterialID", int), ("outsideMaterialID", int), - ("insideSolidID", int), ("outsideSolidID", int), - ("toSmooth", bool)]) +SurfaceCLInfo = NamedTuple( + "SurfaceInfo", + [ + ("firstPolygonID", int), + ("lastPolygonID", int), + ("insideMaterialID", int), + ("outsideMaterialID", int), + ("insideSolidID", int), + ("outsideSolidID", int), + ("toSmooth", bool), + ], +) class SurfaceCL(CLObject): STRUCT_NAME = "Surface" STRUCT_DTYPE = np.dtype( - [("firstPolygonID", cl.cltypes.uint), - ("lastPolygonID", cl.cltypes.uint), - ("insideMaterialID", cl.cltypes.uint), - ("outsideMaterialID", cl.cltypes.uint), - ("insideSolidID", cl.cltypes.int), - ("outsideSolidID", cl.cltypes.int), - ("toSmooth", cl.cltypes.uint)]) + [ + ("firstPolygonID", cl.cltypes.uint), + ("lastPolygonID", cl.cltypes.uint), + ("insideMaterialID", cl.cltypes.uint), + ("outsideMaterialID", cl.cltypes.uint), + ("insideSolidID", cl.cltypes.int), + ("outsideSolidID", cl.cltypes.int), + ("toSmooth", cl.cltypes.uint), + ] + ) def __init__(self, surfacesInfo: List[SurfaceCLInfo]): self._surfacesInfo = surfacesInfo diff --git a/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py b/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py index 9ed0a12b..10b5c346 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/triangleCL.py @@ -11,10 +11,7 @@ class TriangleCL(CLObject): STRUCT_NAME = "Triangle" - STRUCT_DTYPE = np.dtype( - [("vertexIDs", cl.cltypes.uint, 3), - ("normal", cl.cltypes.float3)] - ) + STRUCT_DTYPE = np.dtype([("vertexIDs", cl.cltypes.uint, 3), ("normal", cl.cltypes.float3)]) def __init__(self, trianglesInfo: List[TriangleCLInfo]): self._trianglesInfo = trianglesInfo diff --git a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py index 39dbe697..0f855ea3 100644 --- a/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py +++ b/pytissueoptics/rayscattering/opencl/buffers/vertexCL.py @@ -9,9 +9,7 @@ class VertexCL(CLObject): STRUCT_NAME = "Vertex" - STRUCT_DTYPE = np.dtype( - [("position", cl.cltypes.float3), - ("normal", cl.cltypes.float3)]) + STRUCT_DTYPE = np.dtype([("position", cl.cltypes.float3), ("normal", cl.cltypes.float3)]) def __init__(self, vertices: List[Vertex]): self._vertices = vertices diff --git a/pytissueoptics/rayscattering/opencl/config/CLConfig.py b/pytissueoptics/rayscattering/opencl/config/CLConfig.py index 4e3e50a0..1b3e30cd 100644 --- a/pytissueoptics/rayscattering/opencl/config/CLConfig.py +++ b/pytissueoptics/rayscattering/opencl/config/CLConfig.py @@ -9,15 +9,15 @@ OPENCL_AVAILABLE = True except ImportError: + class DummyCL: def __getattr__(self, item): return None - cl = DummyCL() OPENCL_AVAILABLE = False -warnings.formatwarning = lambda msg, *args, **kwargs: f'{msg}\n' +warnings.formatwarning = lambda msg, *args, **kwargs: f"{msg}\n" OPENCL_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MODULE_PATH = os.path.dirname(os.path.dirname(OPENCL_PATH)) @@ -31,7 +31,7 @@ def __getattr__(self, item): "N_WORK_UNITS": None, "MAX_MEMORY_MB": None, "IPP_TEST_N_PHOTONS": 1000, - "BATCH_LOAD_FACTOR": 0.20 + "BATCH_LOAD_FACTOR": 0.20, } DEFAULT_MAX_MEMORY_MB = 1024 @@ -59,8 +59,9 @@ def validate(self): if key not in self._config: self._config[key] = DEFAULT_CONFIG[key] self.save() - raise ValueError(errorMessage + "The parameter '{}' is missing. Resetting to default " - "value...".format(key)) + raise ValueError( + errorMessage + "The parameter '{}' is missing. Resetting to default value...".format(key) + ) self._validateDeviceIndex() self._validateMaxMemory() @@ -73,34 +74,42 @@ def validate(self): if not self._config[key] > 0: self._config[key] = DEFAULT_CONFIG[key] self.save() - raise ValueError(errorMessage + "The parameter '{}' must be greater than 0. Resetting to default " - "value...".format(key)) + raise ValueError( + errorMessage + + "The parameter '{}' must be greater than 0. Resetting to default value...".format(key) + ) def _validateDeviceIndex(self): numberOfDevices = len(self._devices) if self.DEVICE_INDEX is not None: if self.DEVICE_INDEX not in range(numberOfDevices): - warnings.warn( - f"Invalid device index {self.DEVICE_INDEX}. Resetting to 'null' for automatic selection.") + warnings.warn(f"Invalid device index {self.DEVICE_INDEX}. Resetting to 'null' for automatic selection.") self._config["DEVICE_INDEX"] = None return self._validateDeviceIndex() elif numberOfDevices == 0: - raise ValueError("No OpenCL devices found. Please install the OpenCL drivers for your hardware or " - "disable hardware acceleration by creating a light source with the argument " - "`useHardwareAcceleration=False`.") + raise ValueError( + "No OpenCL devices found. Please install the OpenCL drivers for your hardware or " + "disable hardware acceleration by creating a light source with the argument " + "`useHardwareAcceleration=False`." + ) elif numberOfDevices == 1: self.showAvailableDevices() warnings.warn( f"Using the only available OpenCL device 0 ({self._devices[0].name}). \n\tIf your desired device " f"doesn't show, it may be because its OpenCL drivers are not installed. \n\tTo reset device selection, " - f"reset global CONFIG.DEVICE_INDEX parameter to 'None'.") + f"reset global CONFIG.DEVICE_INDEX parameter to 'None'." + ) self._config["DEVICE_INDEX"] = 0 else: self.showAvailableDevices() - deviceIndex = int(input(f"Please select your device by entering the corresponding index between " - f"[0-{numberOfDevices - 1}]: ")) - assert deviceIndex in range(numberOfDevices), f"Invalid device index '{deviceIndex}'. Not in " \ - f"range [0-{numberOfDevices - 1}]. " + deviceIndex = int( + input( + f"Please select your device by entering the corresponding index between [0-{numberOfDevices - 1}]: " + ) + ) + assert deviceIndex in range(numberOfDevices), ( + f"Invalid device index '{deviceIndex}'. Not in range [0-{numberOfDevices - 1}]. " + ) self._config["DEVICE_INDEX"] = deviceIndex self.save() @@ -111,27 +120,33 @@ def _validateMaxMemory(self): as OpenCL device (which has a max memory equal to the RAM). """ if self._config["MAX_MEMORY_MB"] is None: - maxDeviceMemoryMB = self.device.global_mem_size // 1024 ** 2 + maxDeviceMemoryMB = self.device.global_mem_size // 1024**2 if maxDeviceMemoryMB * 0.75 < DEFAULT_MAX_MEMORY_MB: self._config["MAX_MEMORY_MB"] = maxDeviceMemoryMB * 0.75 - warnings.warn(f"Setting MAX_MEMORY_MB to {self._config['MAX_MEMORY_MB']} MB, which corresponds to 75% " - f"of your device's memory.") + warnings.warn( + f"Setting MAX_MEMORY_MB to {self._config['MAX_MEMORY_MB']} MB, which corresponds to 75% " + f"of your device's memory." + ) else: self._config["MAX_MEMORY_MB"] = DEFAULT_MAX_MEMORY_MB - warnings.warn(f"Setting MAX_MEMORY_MB to {self._config['MAX_MEMORY_MB']} MB to limit out-of-memory " - f"errors even though your device has a capacity of {maxDeviceMemoryMB} MB. \n\tYou can " - f"change this global parameter under `CONFIG.MAX_MEMORY_MB`.") + warnings.warn( + f"Setting MAX_MEMORY_MB to {self._config['MAX_MEMORY_MB']} MB to limit out-of-memory " + f"errors even though your device has a capacity of {maxDeviceMemoryMB} MB. \n\tYou can " + f"change this global parameter under `CONFIG.MAX_MEMORY_MB`." + ) self.save() def _autoSetNWorkUnits(self): if not self._needToRunTest(): return - warnings.warn("... Running a test to find optimal N_WORK_UNITS between 128 and 32768. This may take a few " - "minutes. ") + warnings.warn( + "... Running a test to find optimal N_WORK_UNITS between 128 and 32768. This may take a few minutes. " + ) self.AUTO_SAVE = False try: from pytissueoptics.rayscattering.opencl.utils.optimalWorkUnits import computeOptimalNWorkUnits + optimalNWorkUnits = computeOptimalNWorkUnits() self.AUTO_SAVE = True except Exception as e: @@ -141,15 +156,18 @@ def _autoSetNWorkUnits(self): raise ValueError( f"The automatic test for optimal N_WORK_UNITS failed. Please retry after adressing the error " f"or manually set N_WORK_UNITS in the config file at " - f"'{OPENCL_CONFIG_RELPATH}'. \n... Error message: {e}") + f"'{OPENCL_CONFIG_RELPATH}'. \n... Error message: {e}" + ) self._processOptimalNWorkUnits(optimalNWorkUnits) self.save() def _needToRunTest(self) -> bool: warnings.warn("The parameter N_WORK_UNITS is not set.") time.sleep(0.1) # Used to make sure previous warnings are printed before the following input prompt - answer = input("Press enter to run the test to find the optimal value for N_WORK_UNITS, or enter it manually " - "if you already know it: ") + answer = input( + "Press enter to run the test to find the optimal value for N_WORK_UNITS, or enter it manually " + "if you already know it: " + ) if answer == "": return True elif answer.isnumeric(): @@ -264,5 +282,7 @@ def clContext(self): def showAvailableDevices(self): print("Available devices:") for i, device in enumerate(self._devices): - print(f"... Device [{i}]: {device.name} ({device.global_mem_size // 1024 ** 2} MB " - f"| {device.max_clock_frequency} MHz)") + print( + f"... Device [{i}]: {device.name} ({device.global_mem_size // 1024**2} MB " + f"| {device.max_clock_frequency} MHz)" + ) diff --git a/pytissueoptics/rayscattering/opencl/config/IPPTable.py b/pytissueoptics/rayscattering/opencl/config/IPPTable.py index 18a8b274..0fc833c9 100644 --- a/pytissueoptics/rayscattering/opencl/config/IPPTable.py +++ b/pytissueoptics/rayscattering/opencl/config/IPPTable.py @@ -41,40 +41,13 @@ def _assertExists(self): DEFAULT_IPP = { - "-6887487125664597075": [ - 40000, - 144.571 - ], - "2325526903167451785": [ - 11601000, - 17.43 - ], - "-6919696058704085231": [ - 3901000, - 17.915 - ], - "-2058547611842612924": [ - 11000, - 2843.68 - ], - "3321215562015198964": [ - 223000, - 17.973 - ], - "-7640516670101814241": [ - 21000, - 2846.064 - ], - "6019286260145673908": [ - 246000, - 23.237 - ], - "7995554833896372811": [ - 256000, - 86.142 - ], - "-181984282493345035": [ - 11812662, - 41.95 - ] + "-6887487125664597075": [40000, 144.571], + "2325526903167451785": [11601000, 17.43], + "-6919696058704085231": [3901000, 17.915], + "-2058547611842612924": [11000, 2843.68], + "3321215562015198964": [223000, 17.973], + "-7640516670101814241": [21000, 2846.064], + "6019286260145673908": [246000, 23.237], + "7995554833896372811": [256000, 86.142], + "-181984282493345035": [11812662, 41.95], } diff --git a/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py b/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py index 6e39ba2e..fcf22250 100644 --- a/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py +++ b/pytissueoptics/rayscattering/opencl/utils/CLKeyLog.py @@ -11,10 +11,11 @@ class CLKeyLog: - """ Parses a DataPointCL array of shape (N, 6) where each point is of the form + """Parses a DataPointCL array of shape (N, 6) where each point is of the form (weight, x, y, z, solidID, surfaceID) to extract a dictionary of InteractionKey and their corresponding datapoint array of the form (weight, x, y, z). The - translation from IDs to their corresponding labels is done using the given CLScene. """ + translation from IDs to their corresponding labels is done using the given CLScene.""" + def __init__(self, log: np.ndarray, sceneCL: CLScene): self._log = log self._sceneCL = sceneCL @@ -27,7 +28,7 @@ def __init__(self, log: np.ndarray, sceneCL: CLScene): self._extractKeyLog() def toSceneLogger(self, sceneLogger: Logger): - """ Writes the extracted key-based log to the given scene logger.""" + """Writes the extracted key-based log to the given scene logger.""" for key, points in self._keyLog.items(): sceneLogger.logDataPointArray(points, key) @@ -44,8 +45,8 @@ def _extractNoKeyLog(self): self._keyLog[InteractionKey(NO_SOLID_LABEL, None)] = self._log def _sortLocal(self): - """ Sorts the log locally by solidID and surfaceID, - and for each local batch, it also extracts start and end indices for each key. """ + """Sorts the log locally by solidID and surfaceID, + and for each local batch, it also extracts start and end indices for each key.""" pool = ThreadPool(8) try: @@ -84,7 +85,7 @@ def _sortBatch(self, startIdx: int): @staticmethod def _getValueChangeIndices(log: np.ndarray, column: int): - """ Returns the indices where there is a change in the value of the given column. """ + """Returns the indices where there is a change in the value of the given column.""" indices = np.where(log[:-1, column] != log[1:, column])[0] + 1 return np.concatenate(([0], indices, [log.shape[0]])) @@ -93,7 +94,7 @@ def _sort(log: np.ndarray, columns: List[int]): return log[np.lexsort([log[:, i] for i in columns])] def _merge(self): - """ Merges the local batches into a single key log with unique interaction keys. """ + """Merges the local batches into a single key log with unique interaction keys.""" self._log = self._log[:, :4] for i, batchKeyIndices in enumerate(self._keyIndices): @@ -102,7 +103,7 @@ def _merge(self): if len(indices) == 0: continue a, b = batchStartIndex + indices[0], batchStartIndex + indices[1] - points = self._log[a: b] + points = self._log[a:b] key = self._getInteractionKey(*keyIDs) if key not in self._keyLog: self._keyLog[key] = [points] diff --git a/pytissueoptics/rayscattering/opencl/utils/CLParameters.py b/pytissueoptics/rayscattering/opencl/utils/CLParameters.py index 04a59628..00883007 100644 --- a/pytissueoptics/rayscattering/opencl/utils/CLParameters.py +++ b/pytissueoptics/rayscattering/opencl/utils/CLParameters.py @@ -9,7 +9,7 @@ class CLParameters: def __init__(self, N, AVG_IT_PER_PHOTON): - nBatch = 1/CONFIG.BATCH_LOAD_FACTOR + nBatch = 1 / CONFIG.BATCH_LOAD_FACTOR avgPhotonsPerBatch = int(np.ceil(N / min(nBatch, CONFIG.N_WORK_UNITS))) self._maxLoggerMemory = self._calculateAverageBatchMemorySize(avgPhotonsPerBatch, AVG_IT_PER_PHOTON) self._maxPhotonsPerBatch = min(2 * avgPhotonsPerBatch, N) @@ -63,6 +63,8 @@ def requiredRAMBytes(self) -> float: def _assertEnoughRAM(self): freeSystemRAM = psutil.virtual_memory().available if self.requiredRAMBytes > 0.8 * freeSystemRAM: - warnings.warn(f"WARNING: Available system RAM might not be enough for the simulation. " - f"Estimated requirement: {self.requiredRAMBytes // 1024**2} MB, " - f"Available: {freeSystemRAM // 1024**2} MB.") + warnings.warn( + f"WARNING: Available system RAM might not be enough for the simulation. " + f"Estimated requirement: {self.requiredRAMBytes // 1024**2} MB, " + f"Available: {freeSystemRAM // 1024**2} MB." + ) diff --git a/pytissueoptics/rayscattering/opencl/utils/batchTiming.py b/pytissueoptics/rayscattering/opencl/utils/batchTiming.py index f41737fe..d114506d 100644 --- a/pytissueoptics/rayscattering/opencl/utils/batchTiming.py +++ b/pytissueoptics/rayscattering/opencl/utils/batchTiming.py @@ -2,6 +2,7 @@ class BatchTiming: """ Used to record and display the progress of a batched photon propagation. """ + def __init__(self, totalPhotons: int): self._photonCount = 0 self._totalPhotons = totalPhotons @@ -22,12 +23,19 @@ def __init__(self, totalPhotons: int): self._printHeader() - def recordBatch(self, photonCount: int, propagationTime: float, dataTransferTime: float, dataConversionTime: float, totalTime: float): + def recordBatch( + self, + photonCount: int, + propagationTime: float, + dataTransferTime: float, + dataConversionTime: float, + totalTime: float, + ): """ Photon count is the number of photons that were propagated in the batch. The other times are in nanoseconds. Propagation time is the time it took to run the propagation kernel. Data transfer time is the time it took to - transfer the raw 3D data from the GPU. Data conversion time is the time it took to sort and convert the - interactions IDs into proper InteractionKey points. + transfer the raw 3D data from the GPU. Data conversion time is the time it took to sort and convert the + interactions IDs into proper InteractionKey points. """ self._photonCount += photonCount self._propagationTime += propagationTime @@ -57,14 +65,21 @@ def _printProgress(self): timeElapsedString = f"{timeElapsed:.2f} s" timeLeftString = f"{timeLeft:.2f} s" - formatted_values = [f" {value} ".center(width) for value, width in - zip([self._batchCount, progressString, speedString, timeElapsedString, timeLeftString], self._columnWidths)] + formatted_values = [ + f" {value} ".center(width) + for value, width in zip( + [self._batchCount, progressString, speedString, timeElapsedString, timeLeftString], self._columnWidths + ) + ] print(":: ".join(formatted_values)) def _printFooter(self): print("\nComputation splits:") - splits = {"Propagation": self._propagationTime, "Data transfer": self._dataTransferTime, - "Data conversion": self._dataConversionTime} + splits = { + "Propagation": self._propagationTime, + "Data transfer": self._dataTransferTime, + "Data conversion": self._dataConversionTime, + } for key, value in splits.items(): print(f"\t{key}: {value / self._totalTime * 100:.1f}%") print("".join(["=" * self._width]) + "\n") diff --git a/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py b/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py index cc9977cd..67c97ef1 100644 --- a/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py +++ b/pytissueoptics/rayscattering/opencl/utils/optimalWorkUnits.py @@ -22,8 +22,7 @@ def computeOptimalNWorkUnits() -> int: material1 = ScatteringMaterial(mu_s=5, mu_a=0.8, g=0.9, n=1.4) material2 = ScatteringMaterial(mu_s=10, mu_a=0.8, g=0.9, n=1.7) cube = Cuboid(a=3, b=3, c=3, position=Vector(0, 0, 0), material=material1, label="Cube") - sphere = Sphere(radius=1, order=2, position=Vector(0, 0, 0), material=material2, label="Sphere", - smooth=True) + sphere = Sphere(radius=1, order=2, position=Vector(0, 0, 0), material=material2, label="Sphere", smooth=True) scene = ScatteringScene([cube, sphere]) arr_workUnits, arr_speed = [], [] @@ -38,8 +37,9 @@ def computeOptimalNWorkUnits() -> int: totalTime = 0 timedOut = False for _ in range(AVERAGING): - source = DirectionalSource(position=Vector(0, 0, -2), direction=Vector(0, 0, 1), N=N, - useHardwareAcceleration=True, diameter=0.5) + source = DirectionalSource( + position=Vector(0, 0, -2), direction=Vector(0, 0, 1), N=N, useHardwareAcceleration=True, diameter=0.5 + ) logger = EnergyLogger(scene) t0 = time.time() @@ -47,8 +47,10 @@ def computeOptimalNWorkUnits() -> int: elapsedTime = time.time() - t0 if elapsedTime > MAX_SECONDS_PER_TEST: - print(f"... [{i + 1 - MIN_bits}/{MAX_bits + 1 - MIN_bits}] {CONFIG.N_WORK_UNITS} \t units : " - f"Test is getting too slow on this hardware. Aborting.") + print( + f"... [{i + 1 - MIN_bits}/{MAX_bits + 1 - MIN_bits}] {CONFIG.N_WORK_UNITS} \t units : " + f"Test is getting too slow on this hardware. Aborting." + ) timedOut = True break @@ -59,20 +61,24 @@ def computeOptimalNWorkUnits() -> int: break timePerPhoton /= AVERAGING totalTime /= AVERAGING - print(f"... [{i+1-MIN_bits}/{MAX_bits+1-MIN_bits}] {CONFIG.N_WORK_UNITS} \t units : {timePerPhoton:.6f} s/p [{AVERAGING}x {totalTime:.2f}s]") + print( + f"... [{i + 1 - MIN_bits}/{MAX_bits + 1 - MIN_bits}] {CONFIG.N_WORK_UNITS} \t units : {timePerPhoton:.6f} s/p [{AVERAGING}x {totalTime:.2f}s]" + ) arr_workUnits.append(CONFIG.N_WORK_UNITS) arr_speed.append(timePerPhoton * 10**6) CONFIG.N_WORK_UNITS = None - plt.plot(arr_workUnits, arr_speed, 'o') + plt.plot(arr_workUnits, arr_speed, "o") plt.xlabel("N_WORK_UNITS") plt.ylabel("Time per photon (us)") plt.semilogy() - print(f"Found an optimal N_WORK_UNITS of {arr_workUnits[np.argmin(arr_speed)]}. \nPlease analyze and close the " - f"plot to continue.") + print( + f"Found an optimal N_WORK_UNITS of {arr_workUnits[np.argmin(arr_speed)]}. \nPlease analyze and close the " + f"plot to continue." + ) plt.show() return arr_workUnits[np.argmin(arr_speed)] diff --git a/pytissueoptics/rayscattering/photon.py b/pytissueoptics/rayscattering/photon.py index ce0c0387..523d5497 100644 --- a/pytissueoptics/rayscattering/photon.py +++ b/pytissueoptics/rayscattering/photon.py @@ -53,8 +53,13 @@ def material(self) -> ScatteringMaterial: def solidLabel(self): return self._environment.solidLabel - def setContext(self, environment: Environment, intersectionFinder: IntersectionFinder = None, logger: Logger = None, - fresnelIntersect=FresnelIntersect()): + def setContext( + self, + environment: Environment, + intersectionFinder: IntersectionFinder = None, + logger: Logger = None, + fresnelIntersect=FresnelIntersect(), + ): self._environment: Environment = environment self._intersectionFinder = intersectionFinder self._logger = logger @@ -124,7 +129,9 @@ def reflectOrRefract(self, intersection: Intersection): smoothAngle = math.acos(intersection.normal.dot(intersection.polygon.normal)) minDeflectionAngle = smoothAngle + abs(fresnelIntersection.angleDeflection) / 2 + MIN_ANGLE if abs(fresnelIntersection.angleDeflection) < minDeflectionAngle: - fresnelIntersection.angleDeflection = minDeflectionAngle * np.sign(fresnelIntersection.angleDeflection) + fresnelIntersection.angleDeflection = minDeflectionAngle * np.sign( + fresnelIntersection.angleDeflection + ) self.reflect(fresnelIntersection) else: @@ -132,9 +139,13 @@ def reflectOrRefract(self, intersection: Intersection): if intersection.isSmooth: # Prevent refraction from not crossing the raw surface. - maxDeflectionAngle = abs(np.pi / 2 - math.acos(intersection.polygon.normal.dot(self._direction))) - MIN_ANGLE + maxDeflectionAngle = ( + abs(np.pi / 2 - math.acos(intersection.polygon.normal.dot(self._direction))) - MIN_ANGLE + ) if abs(fresnelIntersection.angleDeflection) > maxDeflectionAngle: - fresnelIntersection.angleDeflection = maxDeflectionAngle * np.sign(fresnelIntersection.angleDeflection) + fresnelIntersection.angleDeflection = maxDeflectionAngle * np.sign( + fresnelIntersection.angleDeflection + ) self.refract(fresnelIntersection) @@ -143,7 +154,7 @@ def reflectOrRefract(self, intersection: Intersection): if mut1 == 0: intersection.distanceLeft = 0 elif mut2 != 0: - intersection.distanceLeft *= mut1/mut2 + intersection.distanceLeft *= mut1 / mut2 else: intersection.distanceLeft = math.inf @@ -161,12 +172,10 @@ def moveTo(self, position: Vector): self._position = position def reflect(self, fresnelIntersection: FresnelIntersection): - self._direction.rotateAround(fresnelIntersection.incidencePlane, - fresnelIntersection.angleDeflection) + self._direction.rotateAround(fresnelIntersection.incidencePlane, fresnelIntersection.angleDeflection) def refract(self, fresnelIntersection: FresnelIntersection): - self._direction.rotateAround(fresnelIntersection.incidencePlane, - fresnelIntersection.angleDeflection) + self._direction.rotateAround(fresnelIntersection.incidencePlane, fresnelIntersection.angleDeflection) def scatter(self): theta, phi = self.material.getScatteringAngles() diff --git a/pytissueoptics/rayscattering/samples/infiniteTissue.py b/pytissueoptics/rayscattering/samples/infiniteTissue.py index 480d7534..644ef64c 100644 --- a/pytissueoptics/rayscattering/samples/infiniteTissue.py +++ b/pytissueoptics/rayscattering/samples/infiniteTissue.py @@ -3,7 +3,7 @@ class InfiniteTissue(ScatteringScene): - """ An infinite tissue with a single material.""" + """An infinite tissue with a single material.""" def __init__(self, material: ScatteringMaterial = ScatteringMaterial(mu_s=2, mu_a=0.1, g=0.8, n=1.4)): super().__init__([], worldMaterial=material) diff --git a/pytissueoptics/rayscattering/samples/phantomTissue.py b/pytissueoptics/rayscattering/samples/phantomTissue.py index 19512a79..fdf0a48d 100644 --- a/pytissueoptics/rayscattering/samples/phantomTissue.py +++ b/pytissueoptics/rayscattering/samples/phantomTissue.py @@ -4,7 +4,8 @@ class PhantomTissue(ScatteringScene): - """ Phantom tissue consisting of 3 layers with various optical properties. """ + """Phantom tissue consisting of 3 layers with various optical properties.""" + TISSUE = [] def __init__(self, worldMaterial=ScatteringMaterial()): @@ -23,7 +24,7 @@ def _create(self): frontLayer = Cuboid(w, w, t[0], material=ScatteringMaterial(mu_s[0], mu_a, g, n[0]), label="frontLayer") middleLayer = Cuboid(w, w, t[1], material=ScatteringMaterial(mu_s[1], mu_a, g, n[1]), label="middleLayer") backLayer = Cuboid(w, w, t[2], material=ScatteringMaterial(mu_s[2], mu_a, g, n[2]), label="backLayer") - layerStack = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + layerStack = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") layerStack.translateTo(Vector(0, 0, sum(t) / 2)) self.TISSUE = [layerStack] diff --git a/pytissueoptics/rayscattering/scatteringScene.py b/pytissueoptics/rayscattering/scatteringScene.py index e99de0d0..7d223610 100644 --- a/pytissueoptics/rayscattering/scatteringScene.py +++ b/pytissueoptics/rayscattering/scatteringScene.py @@ -15,8 +15,10 @@ def __init__(self, solids: List[Solid], worldMaterial=ScatteringMaterial(), igno def add(self, solid: Solid, position: Vector = None): polygonSample = solid.getPolygons()[0] if not isinstance(polygonSample.insideEnvironment.material, ScatteringMaterial): - raise Exception(f"Solid '{solid.getLabel()}' has no ScatteringMaterial defined. " - f"This is required for any RayScatteringScene. ") + raise Exception( + f"Solid '{solid.getLabel()}' has no ScatteringMaterial defined. " + f"This is required for any RayScatteringScene. " + ) super().add(solid, position) def show(self, source: Displayable = None, opacity=0.8, colormap="cool", **kwargs): diff --git a/pytissueoptics/rayscattering/source.py b/pytissueoptics/rayscattering/source.py index 8e131a11..042f299f 100644 --- a/pytissueoptics/rayscattering/source.py +++ b/pytissueoptics/rayscattering/source.py @@ -22,7 +22,14 @@ class Source(Displayable): - def __init__(self, position: Vector, N: int, useHardwareAcceleration: bool = True, displaySize: float = 0.1, seed: Optional[int] = None): + def __init__( + self, + position: Vector, + N: int, + useHardwareAcceleration: bool = True, + displaySize: float = 0.1, + seed: Optional[int] = None, + ): self._position = position self._N = N self._seed = seed @@ -85,8 +92,10 @@ def _getExperimentHash(self, scene: ScatteringScene) -> int: return hash((scene, self)) def _measureIPP(self, scene: ScatteringScene): - warnings.warn("This experiment was not seen before. The program will need to estimate the average interactions " - "per photon (IPP). \n... Estimating IPP]") + warnings.warn( + "This experiment was not seen before. The program will need to estimate the average interactions " + "per photon (IPP). \n... Estimating IPP]" + ) t0 = time.time() tempN = self._N @@ -109,16 +118,15 @@ def _updateIPP(self, scene: ScatteringScene, logger: Logger = None): table = IPPTable() table.updateIPP(self._getExperimentHash(scene), self._N, measuredIPP) - def _propagateOpenCL(self, IPP: float, scene: ScatteringScene, logger: Logger = None, - showProgress: bool = True): + def _propagateOpenCL(self, IPP: float, scene: ScatteringScene, logger: Logger = None, showProgress: bool = True): if showProgress: print(f"Propagating {self._N} photons with hardware acceleration on device {CONFIG.device.name}...") self._photons.setContext(scene, self._environment, logger=logger) self._photons.propagate(IPP=IPP, verbose=showProgress) def getInitialPositionsAndDirections(self) -> Tuple[np.ndarray, np.ndarray]: - """ To be implemented by subclasses. Needs to return a tuple containing the - initial positions and normalized directions of the photons as (N, 3) numpy arrays. """ + """To be implemented by subclasses. Needs to return a tuple containing the + initial positions and normalized directions of the photons as (N, 3) numpy arrays.""" raise NotImplementedError def _loadPhotons(self): @@ -140,8 +148,10 @@ def _prepareLogger(self, logger: Optional[Logger]): if logger is None: return if not isinstance(logger, EnergyLogger): - utils.warn("WARNING: Logging to the base class `Logger` will not allow for energy visualization. " - "Please use `EnergyLogger` instead to unlock all features.") + utils.warn( + "WARNING: Logging to the base class `Logger` will not allow for energy visualization. " + "Please use `EnergyLogger` instead to unlock all features." + ) if "photonCount" not in logger.info: logger.info["photonCount"] = 0 @@ -156,8 +166,10 @@ def _prepareLogger(self, logger: Optional[Logger]): logger.info["sourceHash"] = hash(self) else: if logger.info["sourceHash"] != hash(self): - utils.warn("WARNING: The logger was previously used with a different source. This may corrupt " - "statistics and visualization. Proceed at your own risk.") + utils.warn( + "WARNING: The logger was previously used with a different source. This may corrupt " + "statistics and visualization. Proceed at your own risk." + ) def _saveLogger(self, logger: Logger): if logger is None: @@ -172,13 +184,13 @@ def photons(self): def getPhotonCount(self) -> int: return self._N - def addToViewer(self, viewer: MayaviViewer, representation='surface', colormap='Wistia', opacity=1.0, **kwargs): - sphere = Sphere(radius=self.displaySize/2, position=self._position) + def addToViewer(self, viewer: MayaviViewer, representation="surface", colormap="Wistia", opacity=1.0, **kwargs): + sphere = Sphere(radius=self.displaySize / 2, position=self._position) viewer.add(sphere, representation=representation, colormap=colormap, opacity=opacity, **kwargs) @property def _nameHash(self) -> int: - return int(hashlib.sha256(type(self).__name__.encode('utf-8')).hexdigest(), 16) + return int(hashlib.sha256(type(self).__name__.encode("utf-8")).hexdigest(), 16) @property def _hashComponents(self) -> tuple: @@ -189,8 +201,16 @@ def __hash__(self): class DirectionalSource(Source): - def __init__(self, position: Vector, direction: Vector, diameter: float, N: int, - useHardwareAcceleration: bool = True, displaySize: float = 0.1, seed: Optional[int] = None): + def __init__( + self, + position: Vector, + direction: Vector, + diameter: float, + N: int, + useHardwareAcceleration: bool = True, + displaySize: float = 0.1, + seed: Optional[int] = None, + ): self._diameter = diameter self._direction = direction self._direction.normalize() @@ -198,20 +218,22 @@ def __init__(self, position: Vector, direction: Vector, diameter: float, N: int, self._xAxis.normalize() self._yAxis = self._direction.cross(self._xAxis) self._yAxis.normalize() - super().__init__(position=position, N=N, useHardwareAcceleration=useHardwareAcceleration, displaySize=displaySize, seed=seed) + super().__init__( + position=position, N=N, useHardwareAcceleration=useHardwareAcceleration, displaySize=displaySize, seed=seed + ) def getInitialPositionsAndDirections(self) -> Tuple[np.ndarray, np.ndarray]: positions = self._getInitialPositions() directions = self._getInitialDirections() return positions, directions - def addToViewer(self, viewer: MayaviViewer, representation='surface', colormap='Wistia', opacity=1, **kwargs): + def addToViewer(self, viewer: MayaviViewer, representation="surface", colormap="Wistia", opacity=1, **kwargs): baseHeight = 0.5 * self.displaySize - baseCenter = self._position + self._direction * baseHeight/2 - base = Cylinder(radius=self.displaySize/8, length=baseHeight, position=baseCenter) + baseCenter = self._position + self._direction * baseHeight / 2 + base = Cylinder(radius=self.displaySize / 8, length=baseHeight, position=baseCenter) coneHeight = self.displaySize - baseHeight - coneCenter = self._position + self._direction * (baseHeight + coneHeight/2) - arrow = Cone(position=coneCenter, radius=self.displaySize/3, length=coneHeight) + coneCenter = self._position + self._direction * (baseHeight + coneHeight / 2) + arrow = Cone(position=coneCenter, radius=self.displaySize / 3, length=coneHeight) base.orient(self._direction) arrow.orient(self._direction) @@ -247,9 +269,24 @@ def _hashComponents(self) -> tuple: class PencilPointSource(DirectionalSource): - def __init__(self, position: Vector, direction: Vector, N: int, useHardwareAcceleration: bool = True, displaySize: float = 0.1, seed: Optional[int] = None): - super().__init__(position=position, direction=direction, diameter=0, N=N, - useHardwareAcceleration=useHardwareAcceleration, displaySize=displaySize, seed=seed) + def __init__( + self, + position: Vector, + direction: Vector, + N: int, + useHardwareAcceleration: bool = True, + displaySize: float = 0.1, + seed: Optional[int] = None, + ): + super().__init__( + position=position, + direction=direction, + diameter=0, + N=N, + useHardwareAcceleration=useHardwareAcceleration, + displaySize=displaySize, + seed=seed, + ) class IsotropicPointSource(Source): @@ -261,19 +298,35 @@ def getInitialPositionsAndDirections(self) -> Tuple[np.ndarray, np.ndarray]: @property def _hashComponents(self) -> tuple: - return self._position, + return (self._position,) class DivergentSource(DirectionalSource): - def __init__(self, position: Vector, direction: Vector, diameter: float, divergence: float, N: int, - useHardwareAcceleration: bool = True, displaySize: float = 0.1, seed: Optional[int] = None): + def __init__( + self, + position: Vector, + direction: Vector, + diameter: float, + divergence: float, + N: int, + useHardwareAcceleration: bool = True, + displaySize: float = 0.1, + seed: Optional[int] = None, + ): self._divergence = divergence - super().__init__(position=position, direction=direction, diameter=diameter, N=N, - useHardwareAcceleration=useHardwareAcceleration, displaySize=displaySize, seed=seed) + super().__init__( + position=position, + direction=direction, + diameter=diameter, + N=N, + useHardwareAcceleration=useHardwareAcceleration, + displaySize=displaySize, + seed=seed, + ) def _getInitialDirections(self): - thetaDiameter = np.tan(self._divergence/2) * 2 + thetaDiameter = np.tan(self._divergence / 2) * 2 directions = self._getUniformlySampledDisc(thetaDiameter) directions += self._direction.array directions /= np.linalg.norm(directions, axis=1, keepdims=True) diff --git a/pytissueoptics/rayscattering/statistics/statistics.py b/pytissueoptics/rayscattering/statistics/statistics.py index 85b9eaf9..5416535b 100644 --- a/pytissueoptics/rayscattering/statistics/statistics.py +++ b/pytissueoptics/rayscattering/statistics/statistics.py @@ -60,10 +60,12 @@ def _computeStats(self, solidLabel: str = None): except ZeroDivisionError: utils.warn("WARNING: No energy input for solid '{}'".format(solidLabel)) absorbance = None - self._solidStatsMap[solidLabel] = SolidStats(absorbance, - self.getAbsorbance(solidLabel, useTotalEnergy=True), - self.getTransmittance(solidLabel), - self._getSurfaceStats(solidLabel)) + self._solidStatsMap[solidLabel] = SolidStats( + absorbance, + self.getAbsorbance(solidLabel, useTotalEnergy=True), + self.getTransmittance(solidLabel), + self._getSurfaceStats(solidLabel), + ) def _makeReport(self, solidLabel: str = None, reportString: str = ""): if solidLabel: @@ -87,13 +89,16 @@ def _reportSolid(self, solidLabel: str): reportString = "Report of solid '{}'\n".format(solidLabel) if solidStats.absorbance is None: - reportString += (" Absorbance: N/A ({:.2f}% of total power)\n".format(solidStats.totalAbsorbance)) + reportString += " Absorbance: N/A ({:.2f}% of total power)\n".format(solidStats.totalAbsorbance) reportString += " Absorbance + Transmittance: N/A\n" return reportString - reportString += ( - " Absorbance: {:.2f}% ({:.2f}% of total power)\n".format(solidStats.absorbance, solidStats.totalAbsorbance)) - reportString += (" Absorbance + Transmittance: {:.1f}%\n".format(solidStats.absorbance + solidStats.transmittance)) + reportString += " Absorbance: {:.2f}% ({:.2f}% of total power)\n".format( + solidStats.absorbance, solidStats.totalAbsorbance + ) + reportString += " Absorbance + Transmittance: {:.1f}%\n".format( + solidStats.absorbance + solidStats.transmittance + ) for surfaceLabel, surfaceStats in solidStats.surfaces.items(): reportString += " Transmittance at '{}': {:.1f}%\n".format(surfaceLabel, surfaceStats.transmittance) @@ -122,8 +127,10 @@ def _getAbsorbedEnergyFromViews(self, solidLabel: str) -> float: continue return view.getSum() - raise Exception(f"Could not extract absorbance for solid '{solidLabel}'. The 3D data was discarded and " - f"no stored 2D view corresponds to this solid.") + raise Exception( + f"Could not extract absorbance for solid '{solidLabel}'. The 3D data was discarded and " + f"no stored 2D view corresponds to this solid." + ) def _viewContainsSolid(self, view, solidLabel: str) -> bool: solidLimits = self._logger.getSolidLimits(solidLabel) @@ -176,9 +183,11 @@ def _getSurfaceEnergyFromViews(self, solidLabel: str, surfaceLabel: str, leaving if not self._viewContainsSolid(view, solidLabel): continue return view.getSum() - raise Exception(f"Could not extract energy {['entering', 'leaving'][leaving]} surface '{surfaceLabel}' " - f"of solid '{solidLabel}'. The 3D data was discarded and no stored 2D view corresponds " - f"to this surface.") + raise Exception( + f"Could not extract energy {['entering', 'leaving'][leaving]} surface '{surfaceLabel}' " + f"of solid '{solidLabel}'. The 3D data was discarded and no stored 2D view corresponds " + f"to this surface." + ) def _getSurfaceStats(self, solidLabel: str) -> Dict[str, SurfaceStats]: stats = {} @@ -187,8 +196,8 @@ def _getSurfaceStats(self, solidLabel: str) -> Dict[str, SurfaceStats]: return stats def getTransmittance(self, solidLabel: str, surfaceLabel: str = None, useTotalEnergy=False): - """ Uses local energy input for the desired solid by default. Specify 'useTotalEnergy' = True - to compare instead with total input energy of the scene. """ + """Uses local energy input for the desired solid by default. Specify 'useTotalEnergy' = True + to compare instead with total input energy of the scene.""" if self._extractFromViews: return self._getTransmittanceFromViews(solidLabel, surfaceLabel, useTotalEnergy) diff --git a/pytissueoptics/rayscattering/tests/display/testProfile1D.py b/pytissueoptics/rayscattering/tests/display/testProfile1D.py index 24be5f8a..68f1b552 100644 --- a/pytissueoptics/rayscattering/tests/display/testProfile1D.py +++ b/pytissueoptics/rayscattering/tests/display/testProfile1D.py @@ -11,35 +11,35 @@ from pytissueoptics.rayscattering.tests import SHOW_VISUAL_TESTS from pytissueoptics.scene.tests import compareVisuals -TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), 'testImages') +TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), "testImages") OVERWRITE_REFERENCE_IMAGES = False class TestProfile1D(unittest.TestCase): def testWhenShow_shouldPlotTheProfileWithCorrectDataAndAxis(self): - profile = Profile1D(np.array([1, 2, 8, 4, 1]), Direction.X_NEG, - limits=(0, 1), name='Test Profile along X_NEG') + profile = Profile1D(np.array([1, 2, 8, 4, 1]), Direction.X_NEG, limits=(0, 1), name="Test Profile along X_NEG") with patch("matplotlib.pyplot.show") as mockShow: profile.show() mockShow.assert_called_once() - referenceImage = os.path.join(TEST_IMAGES_DIR, 'profile1D.png') + referenceImage = os.path.join(TEST_IMAGES_DIR, "profile1D.png") if OVERWRITE_REFERENCE_IMAGES: plt.savefig(referenceImage) self.skipTest("Overwriting reference image") if not SHOW_VISUAL_TESTS: - self.skipTest("Visual tests are disabled. Set rayscattering.tests.SHOW_VISUAL_TESTS to True to enable them.") + self.skipTest( + "Visual tests are disabled. Set rayscattering.tests.SHOW_VISUAL_TESTS to True to enable them." + ) with tempfile.TemporaryDirectory() as tempdir: - currentImage = os.path.join(tempdir, 'test.png') + currentImage = os.path.join(tempdir, "test.png") plt.savefig(currentImage) plt.close() - isOK = compareVisuals(referenceImage, currentImage, - title="TestView2D: View2DProjectionX") + isOK = compareVisuals(referenceImage, currentImage, title="TestView2D: View2DProjectionX") if not isOK: self.fail("Visual test failed.") diff --git a/pytissueoptics/rayscattering/tests/display/testProfileFactory.py b/pytissueoptics/rayscattering/tests/display/testProfileFactory.py index 9e1a35b1..16410809 100644 --- a/pytissueoptics/rayscattering/tests/display/testProfileFactory.py +++ b/pytissueoptics/rayscattering/tests/display/testProfileFactory.py @@ -97,8 +97,9 @@ def testWhenCreateProfileOfSurfaceEnergyLeaving_shouldOnlyExtractSurfaceEnergyLe surfaceData = np.array([[1, 1.5, 1.5, 1.5], [-1, -1.5, -1.5, -1.5]]) self.TEST_LOGGER.logDataPointArray(surfaceData, InteractionKey("cube", "top")) - profile = self.profileFactory.create(Direction.Z_POS, solidLabel="cube", surfaceLabel="top", - surfaceEnergyLeaving=True, binSize=1) + profile = self.profileFactory.create( + Direction.Z_POS, solidLabel="cube", surfaceLabel="top", surfaceEnergyLeaving=True, binSize=1 + ) expectedData = np.array([0, 0, 0, 1]) self.assertTrue(np.array_equal(expectedData, profile.data)) @@ -107,22 +108,25 @@ def testWhenCreateProfileOfSurfaceEnergyEntering_shouldOnlyExtractSurfaceEnergyE surfaceData = np.array([[1, 1.5, 1.5, 1.5], [-1, -1.5, -1.5, -1.5]]) self.TEST_LOGGER.logDataPointArray(surfaceData, InteractionKey("cube", "top")) - profile = self.profileFactory.create(Direction.Z_POS, solidLabel="cube", surfaceLabel="top", - surfaceEnergyLeaving=False, binSize=1) + profile = self.profileFactory.create( + Direction.Z_POS, solidLabel="cube", surfaceLabel="top", surfaceEnergyLeaving=False, binSize=1 + ) expectedData = np.array([1, 0, 0, 0]) self.assertTrue(np.array_equal(expectedData, profile.data)) def testWhenCreateProfileWithBadLabelCapitalization_shouldCorrectLabels(self): self.TEST_LOGGER.logDataPointArray(np.array([[1, 0, 0, 0]]), InteractionKey("cube", "top")) - profile = self.profileFactory.create(Direction.Z_POS, solidLabel="Cube", surfaceLabel="Top", - surfaceEnergyLeaving=True) + profile = self.profileFactory.create( + Direction.Z_POS, solidLabel="Cube", surfaceLabel="Top", surfaceEnergyLeaving=True + ) self.assertEqual(1, profile.data.sum()) def testWhenCreateProfile_shouldSetProperName(self): self.TEST_LOGGER.logDataPointArray(np.array([[1, 0, 0, 0]]), InteractionKey("cube", "top")) - profile = self.profileFactory.create(Direction.Z_POS, solidLabel="cube", surfaceLabel="top", - surfaceEnergyLeaving=True) + profile = self.profileFactory.create( + Direction.Z_POS, solidLabel="cube", surfaceLabel="top", surfaceEnergyLeaving=True + ) self.assertEqual("Energy profile along z of cube surface top (leaving)", profile.name) def testGivenEmptyLogger_whenCreateProfile_shouldReturnEmptyProfile(self): @@ -167,11 +171,13 @@ def testGiven2DLogger_whenCreateSurfaceProfile_shouldExtractSolidProfileFromLogg expectedSurfaceDataEntering = np.array([1, 0, 0, 0]) profileDirection = Direction.Z_POS # All directions are allowed except for surface axis (Y) - profile = self.profileFactory.create(profileDirection, binSize=1, solidLabel="cube", surfaceLabel="top", - surfaceEnergyLeaving=True) + profile = self.profileFactory.create( + profileDirection, binSize=1, solidLabel="cube", surfaceLabel="top", surfaceEnergyLeaving=True + ) self.assertTrue(np.array_equal(expectedSurfaceDataLeaving, profile.data)) - profile = self.profileFactory.create(profileDirection, binSize=1, solidLabel="cube", surfaceLabel="top", - surfaceEnergyLeaving=False) + profile = self.profileFactory.create( + profileDirection, binSize=1, solidLabel="cube", surfaceLabel="top", surfaceEnergyLeaving=False + ) self.assertTrue(np.array_equal(expectedSurfaceDataEntering, profile.data)) def testGiven2DLoggerWithASingleView_whenCreateProfileAlongThisViewWithSameProperties_shouldExtractProfile(self): diff --git a/pytissueoptics/rayscattering/tests/display/testView2D.py b/pytissueoptics/rayscattering/tests/display/testView2D.py index e11936dd..3d3ace85 100644 --- a/pytissueoptics/rayscattering/tests/display/testView2D.py +++ b/pytissueoptics/rayscattering/tests/display/testView2D.py @@ -119,9 +119,7 @@ def testGivenProjectionView_whenExtractData_shouldProject3DPointsToThis2DView(se view = View2DProjectionX() view.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) value = 0.5 - dataPoints = np.array([[value, 0, 2.05, 2.05], - [value, 5, 2.05, 2.05], - [value, 0, 2.5, 2.95]]) + dataPoints = np.array([[value, 0, 2.05, 2.05], [value, 5, 2.05, 2.05], [value, 0, 2.5, 2.95]]) view.extractData(dataPoints) @@ -138,9 +136,7 @@ def testWhenGetImageDataWithoutLogScale_shouldReturnImageOfRawData(self): view = View2DProjectionX() view.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) value = 0.5 - dataPoints = np.array([[value, 0, 2.05, 2.05], - [value, 5, 2.05, 2.05], - [value, 0, 2.5, 2.95]]) + dataPoints = np.array([[value, 0, 2.05, 2.05], [value, 5, 2.05, 2.05], [value, 0, 2.5, 2.95]]) view.extractData(dataPoints) image = view.getImageData(logScale=False) @@ -154,9 +150,7 @@ def testWhenGetImageDataWithLogScale_shouldReturnImageOfLogData(self): view = View2DProjectionX() view.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) value = 0.5 - dataPoints = np.array([[value, 0, 2.05, 2.05], - [value, 5, 2.05, 2.05], - [value, 0, 2.5, 2.95]]) + dataPoints = np.array([[value, 0, 2.05, 2.05], [value, 5, 2.05, 2.05], [value, 0, 2.5, 2.95]]) view.extractData(dataPoints) image = view.getImageData(logScale=True) @@ -168,9 +162,7 @@ def testWhenGetImageDataWithoutAutoFlip_shouldPreserveRawUVDirections(self): view = View2DProjection(projectionDirection=Direction.Z_POS, horizontalDirection=Direction.Y_NEG) view.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) value = 0.5 - dataPoints = np.array([[value, 2.05, 2.05, 0], - [value, 2.05, 2.05, 5], - [value, 2.5, 2.95, 0]]) + dataPoints = np.array([[value, 2.05, 2.05, 0], [value, 2.05, 2.05, 5], [value, 2.5, 2.95, 0]]) view.extractData(dataPoints) image = view.getImageData(logScale=False, autoFlip=False) @@ -182,9 +174,7 @@ def testWhenGetImageData_shouldAutoFlipAxesToBePositive(self): view = View2DProjection(projectionDirection=Direction.Z_POS, horizontalDirection=Direction.Y_NEG) view.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) value = 0.5 - dataPoints = np.array([[value, 2.05, 2.05, 0], - [value, 2.05, 2.05, 5], - [value, 2.5, 2.95, 0]]) + dataPoints = np.array([[value, 2.05, 2.05, 0], [value, 2.05, 2.05, 5], [value, 2.5, 2.95, 0]]) view.extractData(dataPoints) image = view.getImageData(logScale=False) @@ -203,7 +193,9 @@ def testGivenXViewWithCustomHorizontal_shouldHaveImageDataWithDefaultAlignmentEq with self.subTest(horizontalDirection=horizontalDirection.name): self._shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(customView) - def testGivenXViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(self): + def testGivenXViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView( + self, + ): for horizontalDirection in [Direction.Y_POS, Direction.Y_NEG, Direction.Z_POS, Direction.Z_NEG]: customView = View2DProjection(Direction.X_NEG, horizontalDirection=horizontalDirection) with self.subTest(horizontalDirection=horizontalDirection.name): @@ -215,7 +207,9 @@ def testGivenYViewWithCustomHorizontal_shouldHaveImageDataWithDefaultAlignmentEq with self.subTest(horizontalDirection=horizontalDirection.name): self._shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(customView) - def testGivenYViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(self): + def testGivenYViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView( + self, + ): for horizontalDirection in [Direction.X_POS, Direction.X_NEG, Direction.Z_POS, Direction.Z_NEG]: customView = View2DProjection(Direction.Y_POS, horizontalDirection=horizontalDirection) with self.subTest(horizontalDirection=horizontalDirection.name): @@ -227,7 +221,9 @@ def testGivenZViewWithCustomHorizontal_shouldHaveImageDataWithDefaultAlignmentEq with self.subTest(horizontalDirection=horizontalDirection.name): self._shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(customView) - def testGivenZViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(self): + def testGivenZViewWithReverseProjectionAndAnyHorizontal_shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView( + self, + ): for horizontalDirection in [Direction.X_POS, Direction.X_NEG, Direction.Y_POS, Direction.Y_NEG]: customView = View2DProjection(Direction.Z_NEG, horizontalDirection=horizontalDirection) with self.subTest(horizontalDirection=horizontalDirection.name): @@ -240,9 +236,7 @@ def _shouldHaveImageDataWithDefaultAlignmentEqualToNaturalView(self, customView: naturalView = View2DProjectionY() else: naturalView = View2DProjectionZ() - dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [0.5, 2.95, 2.05, 2.5]]) naturalView.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) customView.setContext([(2, 3), (2, 3), (2, 3)], (0.1, 0.1, 0.1)) naturalView.extractData(dataPoints) @@ -425,17 +419,17 @@ def testGivenAViewWithDifferentLimits_shouldNotBeEqual(self): def testWhenFlip_shouldFlipViewToBeSeenFromBehind(self): defaultViews = [View2DProjectionX(), View2DProjectionY(), View2DProjectionZ()] - viewsFromBehind = [View2DProjection(Direction.X_NEG, Direction.Z_NEG), - View2DProjection(Direction.Y_POS, Direction.Z_NEG), - View2DProjection(Direction.Z_NEG, Direction.X_POS)] + viewsFromBehind = [ + View2DProjection(Direction.X_NEG, Direction.Z_NEG), + View2DProjection(Direction.Y_POS, Direction.Z_NEG), + View2DProjection(Direction.Z_NEG, Direction.X_POS), + ] for view, viewFromBehind in zip(defaultViews, viewsFromBehind): with self.subTest(axis=view.axis): self._whenFlip_shouldEqualOtherView(view, viewFromBehind) def _whenFlip_shouldEqualOtherView(self, view: View2D, otherView: View2D): - dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [0.5, 2.95, 2.05, 2.5]]) view.setContext([(2, 4), (2, 4), (2, 4)], (0.1, 0.1, 0.1)) otherView.setContext([(2, 4), (2, 4), (2, 4)], (0.1, 0.1, 0.1)) view.extractData(dataPoints) @@ -484,15 +478,14 @@ def testWhenInitDataFromAnotherViewThatContainsThisViewOpposite_shouldExtractThe with self.subTest(projection=Direction.X_NEG.name, horizontal=horizontalDirection.name): self._whenInitDataFromAnotherView_shouldExtractTheCorrectData(viewGiver, viewReceiver) - def _whenInitDataFromAnotherView_shouldExtractTheCorrectData(self, viewGiver: View2DProjection, - viewReceiver: View2DProjection): + def _whenInitDataFromAnotherView_shouldExtractTheCorrectData( + self, viewGiver: View2DProjection, viewReceiver: View2DProjection + ): viewExpected = View2DProjection(viewReceiver.projectionDirection, viewReceiver._horizontalDirection) viewGiver.setContext([(2, 3), (2, 3), (2, 3)], (0.2, 0.2, 0.2)) viewReceiver.setContext([(2, 3), (2, 3), (2, 3)], (0.2, 0.2, 0.2)) viewExpected.setContext([(2, 3), (2, 3), (2, 3)], (0.2, 0.2, 0.2)) - dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [0.5, 2.95, 2.05, 2.5]]) viewGiver.extractData(dataPoints) viewExpected.extractData(dataPoints) @@ -513,9 +506,7 @@ def testShouldHaveProperDescription(self): def testGivenASurfaceViewOfEnergyLeaving_whenExtractSurfaceData_shouldOnlyStorePositiveEnergy(self): view = View2DSurfaceX(solidLabel="A", surfaceLabel="B", surfaceEnergyLeaving=True) view.setContext([(2, 4), (2, 4), (2, 4)], (0.1, 0.1, 0.1)) - dataPoints = np.array([[-0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [-0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[-0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [-0.5, 2.95, 2.05, 2.5]]) view.extractData(dataPoints) self.assertEqual(0.5, view.getSum()) @@ -523,23 +514,21 @@ def testGivenASurfaceViewOfEnergyLeaving_whenExtractSurfaceData_shouldOnlyStoreP def testGivenASurfaceViewOfEnergyEntering_whenExtractSurfaceData_shouldOnlyStoreNegativeEnergyAsPositive(self): view = View2DSurfaceX(solidLabel="A", surfaceLabel="B", surfaceEnergyLeaving=False) view.setContext([(2, 4), (2, 4), (2, 4)], (0.1, 0.1, 0.1)) - dataPoints = np.array([[-0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [-0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[-0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [-0.5, 2.95, 2.05, 2.5]]) view.extractData(dataPoints) self.assertEqual(1, view.getSum()) def testGivenASliceView_whenExtractData_shouldOnlyProjectDataThatLiesInsideTheSlice(self): - sliceViews = [View2DSliceX(position=2, thickness=0.1), - View2DSliceY(position=3, thickness=0.1), - View2DSliceZ(position=4, thickness=0.1)] + sliceViews = [ + View2DSliceX(position=2, thickness=0.1), + View2DSliceY(position=3, thickness=0.1), + View2DSliceZ(position=4, thickness=0.1), + ] for view in sliceViews: with self.subTest(axis=view.axis): view.setContext([(2, 5), (2, 5), (2, 5)], (0.1, 0.1, 0.1)) - dataPoints = np.array([[0.5, 2.00, 3.00, 4.00], - [1.0, 2.04, 3.04, 4.04], - [2.0, 2.10, 3.10, 4.10]]) + dataPoints = np.array([[0.5, 2.00, 3.00, 4.00], [1.0, 2.04, 3.04, 4.04], [2.0, 2.10, 3.10, 4.10]]) view.extractData(dataPoints) self.assertEqual(1.5, view.getSum()) @@ -551,17 +540,15 @@ def testShouldHaveSizeInPhysicalUnits(self): def testWhenShow_shouldPlotTheViewWithCorrectDataAndAxes(self): view = View2DProjection(Direction.X_POS, Direction.Y_POS) view.setContext([(2, 4), (2, 4), (2, 4)], (0.1, 0.1, 0.1)) - dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], - [0.5, 2.05, 2.05, 2.05], - [0.5, 2.95, 2.05, 2.5]]) + dataPoints = np.array([[0.5, 2.05, 2.05, 2.05], [0.5, 2.05, 2.05, 2.05], [0.5, 2.95, 2.05, 2.5]]) view.extractData(dataPoints) with patch("matplotlib.pyplot.show") as mockShow: view.show() mockShow.assert_called_once() - TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), 'testImages') - referenceImage = os.path.join(TEST_IMAGES_DIR, 'viewXPOS_uYPOS_vZNEG.png') + TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), "testImages") + referenceImage = os.path.join(TEST_IMAGES_DIR, "viewXPOS_uYPOS_vZNEG.png") OVERWRITE_REFERENCE_IMAGES = False if OVERWRITE_REFERENCE_IMAGES: @@ -569,14 +556,15 @@ def testWhenShow_shouldPlotTheViewWithCorrectDataAndAxes(self): self.skipTest("Overwriting reference image") if not SHOW_VISUAL_TESTS: - self.skipTest("Visual tests are disabled. Set rayscattering.tests.SHOW_VISUAL_TESTS to True to enable them.") + self.skipTest( + "Visual tests are disabled. Set rayscattering.tests.SHOW_VISUAL_TESTS to True to enable them." + ) with tempfile.TemporaryDirectory() as tempdir: - currentImage = os.path.join(tempdir, 'test.png') + currentImage = os.path.join(tempdir, "test.png") plt.savefig(currentImage) plt.close() - isOK = compareVisuals(referenceImage, currentImage, - title="TestView2D: View2DProjectionX") + isOK = compareVisuals(referenceImage, currentImage, title="TestView2D: View2DProjectionX") if not isOK: self.fail("Visual test failed.") diff --git a/pytissueoptics/rayscattering/tests/display/testViewFactory.py b/pytissueoptics/rayscattering/tests/display/testViewFactory.py index 0d20ad25..09c17813 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewFactory.py +++ b/pytissueoptics/rayscattering/tests/display/testViewFactory.py @@ -89,19 +89,23 @@ def testWhenBuildASolidsViewGroup_shouldCreateAndReturnTheDefaultProjectionViews self.assertEqual(solidLabels[i // 3], view.solidLabel) self.assertIsInstance(view, viewTypes[i % 3]) - def testWhenBuildASurfacesLeavingViewGroup_shouldCreateAndReturnTheDefaultLeavingSurfaceViewForEachSolidSurface(self): + def testWhenBuildASurfacesLeavingViewGroup_shouldCreateAndReturnTheDefaultLeavingSurfaceViewForEachSolidSurface( + self, + ): # These include a single view for the surface of the sphere taken along Z, # and another view of the energy leaving from the cube and into the sphere surfaceLeavingViews = self.viewFactory.build(ViewGroup.SURFACES_LEAVING) - expectedViews = [View2DSurfaceX("cube", "cube_left"), - View2DSurfaceX("cube", "cube_right"), - View2DSurfaceY("cube", "cube_bottom"), - View2DSurfaceY("cube", "cube_top"), - View2DSurfaceZ("cube", "cube_front"), - View2DSurfaceZ("cube", "cube_back"), - View2DSurfaceZ("cube", "sphere_ellipsoid"), - View2DSurfaceZ("sphere", "sphere_ellipsoid")] + expectedViews = [ + View2DSurfaceX("cube", "cube_left"), + View2DSurfaceX("cube", "cube_right"), + View2DSurfaceY("cube", "cube_bottom"), + View2DSurfaceY("cube", "cube_top"), + View2DSurfaceZ("cube", "cube_front"), + View2DSurfaceZ("cube", "cube_back"), + View2DSurfaceZ("cube", "sphere_ellipsoid"), + View2DSurfaceZ("sphere", "sphere_ellipsoid"), + ] expectedViews[1].flip() expectedViews[2].flip() expectedViews[5].flip() @@ -113,17 +117,21 @@ def testWhenBuildASurfacesLeavingViewGroup_shouldCreateAndReturnTheDefaultLeavin for i in range(len(expectedViews)): self.assertTrue(expectedViews[i].isEqualTo(surfaceLeavingViews[i])) - def testWhenBuildASurfacesEnteringViewGroup_shouldCreateAndReturnTheDefaultEnteringSurfaceViewForEachSolidSurface(self): + def testWhenBuildASurfacesEnteringViewGroup_shouldCreateAndReturnTheDefaultEnteringSurfaceViewForEachSolidSurface( + self, + ): surfaceEnteringViews = self.viewFactory.build(ViewGroup.SURFACES_ENTERING) - expectedViews = [View2DSurfaceX("cube", "cube_left", surfaceEnergyLeaving=False), - View2DSurfaceX("cube", "cube_right", surfaceEnergyLeaving=False), - View2DSurfaceY("cube", "cube_bottom", surfaceEnergyLeaving=False), - View2DSurfaceY("cube", "cube_top", surfaceEnergyLeaving=False), - View2DSurfaceZ("cube", "cube_front", surfaceEnergyLeaving=False), - View2DSurfaceZ("cube", "cube_back", surfaceEnergyLeaving=False), - View2DSurfaceZ("cube", "sphere_ellipsoid", surfaceEnergyLeaving=False), - View2DSurfaceZ("sphere", "sphere_ellipsoid", surfaceEnergyLeaving=False)] + expectedViews = [ + View2DSurfaceX("cube", "cube_left", surfaceEnergyLeaving=False), + View2DSurfaceX("cube", "cube_right", surfaceEnergyLeaving=False), + View2DSurfaceY("cube", "cube_bottom", surfaceEnergyLeaving=False), + View2DSurfaceY("cube", "cube_top", surfaceEnergyLeaving=False), + View2DSurfaceZ("cube", "cube_front", surfaceEnergyLeaving=False), + View2DSurfaceZ("cube", "cube_back", surfaceEnergyLeaving=False), + View2DSurfaceZ("cube", "sphere_ellipsoid", surfaceEnergyLeaving=False), + View2DSurfaceZ("sphere", "sphere_ellipsoid", surfaceEnergyLeaving=False), + ] expectedViews[1].flip() expectedViews[2].flip() expectedViews[5].flip() diff --git a/pytissueoptics/rayscattering/tests/display/testViewer.py b/pytissueoptics/rayscattering/tests/display/testViewer.py index 58eae098..6087c52c 100644 --- a/pytissueoptics/rayscattering/tests/display/testViewer.py +++ b/pytissueoptics/rayscattering/tests/display/testViewer.py @@ -16,8 +16,8 @@ def patchMayaviRender(func): - for module in ['show', 'gcf', 'figure', 'clf', 'triangular_mesh', 'points3d']: - func = patch('mayavi.mlab.' + module)(func) + for module in ["show", "gcf", "figure", "clf", "triangular_mesh", "points3d"]: + func = patch("mayavi.mlab." + module)(func) return func @@ -43,7 +43,7 @@ def testWhenListViews_shouldListTheLoggerViews(self): self.viewer.listViews() verify(self.logger, times=1).listViews() - @patch('pytissueoptics.rayscattering.statistics.statistics.Stats.report') + @patch("pytissueoptics.rayscattering.statistics.statistics.Stats.report") def testWhenReportStats_shouldReportStats(self, mockReport): self.viewer.reportStats() mockReport.assert_called_once() @@ -66,16 +66,14 @@ def testWhenShow2D_shouldShow2DViewFromLogger(self): self.viewer.show2D(view) - verify(self.logger, times=1).showView(view=view, viewIndex=None, - logScale=True, colormap=ANY(str)) + verify(self.logger, times=1).showView(view=view, viewIndex=None, logScale=True, colormap=ANY(str)) def testWhenShow2DWithIndex_shouldShow2DViewFromLogger(self): when(self.logger).showView(...).thenReturn() self.viewer.show2D(viewIndex=1) - verify(self.logger, times=1).showView(view=None, viewIndex=1, - logScale=True, colormap=ANY(str)) + verify(self.logger, times=1).showView(view=None, viewIndex=1, logScale=True, colormap=ANY(str)) def testWhenShow2DAllViews_shouldShowAllViews(self): when(self.logger).showView(...).thenReturn() @@ -88,8 +86,7 @@ def testWhenShow2DAllViews_shouldShowAllViews(self): self.viewer.show2DAllViews() for i in range(2): - verify(self.logger, times=1).showView(view=None, viewIndex=i, - logScale=True, colormap=ANY(str)) + verify(self.logger, times=1).showView(view=None, viewIndex=i, logScale=True, colormap=ANY(str)) def testWhenShow2DAllViewsWithViewGroup_shouldOnlyShowViewsOfThisGroup(self): when(self.logger).showView(...).thenReturn() @@ -101,13 +98,12 @@ def testWhenShow2DAllViewsWithViewGroup_shouldOnlyShowViewsOfThisGroup(self): self.viewer.show2DAllViews(ViewGroup.SOLIDS) - verify(self.logger, times=1).showView(view=None, viewIndex=1, - logScale=True, colormap=ANY(str)) - verify(self.logger, times=0).showView(view=None, viewIndex=0, - logScale=True, colormap=ANY(str)) + verify(self.logger, times=1).showView(view=None, viewIndex=1, logScale=True, colormap=ANY(str)) + verify(self.logger, times=0).showView(view=None, viewIndex=0, logScale=True, colormap=ANY(str)) def testWhenShow3DWithoutMayaviInstalled_shouldWarnAndIgnore(self): from pytissueoptics.rayscattering.display import viewer + viewer.MAYAVI_AVAILABLE = False with self.assertWarns(UserWarning): self.viewer.show3D() @@ -127,11 +123,14 @@ def testWhenShow3DWithSource_shouldDisplaySource(self, mockShow, *args): mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addDataPoints') - def testWhenShow3DWithDefaultPointCloud_shouldDisplayPointCloudOfSolidsAndSurfaceLeaving(self, mockAddDataPoints, mockShow, *args): + @patch("pytissueoptics.scene.viewer.MayaviViewer.addDataPoints") + def testWhenShow3DWithDefaultPointCloud_shouldDisplayPointCloudOfSolidsAndSurfaceLeaving( + self, mockAddDataPoints, mockShow, *args + ): mockPointCloudFactory = mock(PointCloudFactory) - aPointCloud = PointCloud(solidPoints=np.array([[0.5, 0, 0, 0]]), - surfacePoints=np.array([[1, 0, 0, 0], [-1, 0, 0, 0]])) + aPointCloud = PointCloud( + solidPoints=np.array([[0.5, 0, 0, 0]]), surfacePoints=np.array([[1, 0, 0, 0], [-1, 0, 0, 0]]) + ) when(mockPointCloudFactory).getPointCloud(...).thenReturn(aPointCloud) self.viewer._pointCloudFactory = mockPointCloudFactory @@ -146,7 +145,7 @@ def testWhenShow3DWithDefaultPointCloud_shouldDisplayPointCloudOfSolidsAndSurfac mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addDataPoints') + @patch("pytissueoptics.scene.viewer.MayaviViewer.addDataPoints") def testGivenNoData_whenShow3DWithPointCloud_shouldNotDisplayPointCloud(self, mockAddDataPoints, mockShow, *args): mockPointCloudFactory = mock(PointCloudFactory) aPointCloud = PointCloud() @@ -159,41 +158,46 @@ def testGivenNoData_whenShow3DWithPointCloud_shouldNotDisplayPointCloud(self, mo mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addDataPoints') + @patch("pytissueoptics.scene.viewer.MayaviViewer.addDataPoints") def testWhenShow3DWithSurfacePointCloud_shouldOnlyDisplaySurfacePoints(self, mockAddDataPoints, mockShow, *args): mockPointCloudFactory = mock(PointCloudFactory) - aPointCloud = PointCloud(solidPoints=np.array([[0.5, 0, 0, 0]]), - surfacePoints=np.array([[1, 0, 0, 0], [-1, 0, 0, 0]])) + aPointCloud = PointCloud( + solidPoints=np.array([[0.5, 0, 0, 0]]), surfacePoints=np.array([[1, 0, 0, 0], [-1, 0, 0, 0]]) + ) when(mockPointCloudFactory).getPointCloud(...).thenReturn(aPointCloud) self.viewer._pointCloudFactory = mockPointCloudFactory - self.viewer.show3D(visibility=Visibility.POINT_CLOUD, - pointCloudStyle=PointCloudStyle(showSolidPoints=False)) + self.viewer.show3D(visibility=Visibility.POINT_CLOUD, pointCloudStyle=PointCloudStyle(showSolidPoints=False)) mockAddDataPoints.assert_called_once() self.assertTrue(np.array_equal(mockAddDataPoints.call_args[0][0], aPointCloud.leavingSurfacePoints)) mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addDataPoints') - def testWhenShow3DWithEnteringSurfacePointCloud_shouldOnlyDisplayEnteringSurfacePoints(self, mockAddDataPoints, mockShow, *args): + @patch("pytissueoptics.scene.viewer.MayaviViewer.addDataPoints") + def testWhenShow3DWithEnteringSurfacePointCloud_shouldOnlyDisplayEnteringSurfacePoints( + self, mockAddDataPoints, mockShow, *args + ): mockPointCloudFactory = mock(PointCloudFactory) - aPointCloud = PointCloud(solidPoints=np.array([[0.5, 0, 0, 0]]), - surfacePoints=np.array([[1, 0, 0, 0], [-1, 1, 1, 1]])) + aPointCloud = PointCloud( + solidPoints=np.array([[0.5, 0, 0, 0]]), surfacePoints=np.array([[1, 0, 0, 0], [-1, 1, 1, 1]]) + ) when(mockPointCloudFactory).getPointCloud(...).thenReturn(aPointCloud) self.viewer._pointCloudFactory = mockPointCloudFactory - self.viewer.show3D(visibility=Visibility.POINT_CLOUD, - pointCloudStyle=PointCloudStyle(showSolidPoints=False, - showSurfacePointsLeaving=False, - showSurfacePointsEntering=True)) + self.viewer.show3D( + visibility=Visibility.POINT_CLOUD, + pointCloudStyle=PointCloudStyle( + showSolidPoints=False, showSurfacePointsLeaving=False, showSurfacePointsEntering=True + ), + ) mockAddDataPoints.assert_called_once() self.assertTrue(np.array_equal(mockAddDataPoints.call_args[0][0], aPointCloud.enteringSurfacePointsPositive)) mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addImage') + @patch("pytissueoptics.scene.viewer.MayaviViewer.addImage") def testWhenShow3DWithViews_shouldAdd2DImageOfTheseViewsInThe3DDisplay(self, mockAddImage, mockShow, *args): self._givenLoggerWithXSceneView() sceneView = self.logger.views[0] @@ -208,8 +212,10 @@ def testWhenShow3DWithViews_shouldAdd2DImageOfTheseViewsInThe3DDisplay(self, moc mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addImage') - def testWhenShow3DWithViewsIndexList_shouldAdd2DImageOfTheseViewsInThe3DDisplay(self, mockAddImage, mockShow, *args): + @patch("pytissueoptics.scene.viewer.MayaviViewer.addImage") + def testWhenShow3DWithViewsIndexList_shouldAdd2DImageOfTheseViewsInThe3DDisplay( + self, mockAddImage, mockShow, *args + ): self._givenLoggerWithXSceneView() sceneView = self.logger.views[0] theViewIndex = 9 @@ -225,7 +231,7 @@ def testWhenShow3DWithViewsIndexList_shouldAdd2DImageOfTheseViewsInThe3DDisplay( mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addImage') + @patch("pytissueoptics.scene.viewer.MayaviViewer.addImage") def testGiven3DLogger_whenShow3DDefault_shouldDisplayEverythingExceptViews(self, mockAddImage, mockShow, *args): mockPointCloudFactory = mock(PointCloudFactory) aPointCloud = PointCloud() @@ -241,8 +247,10 @@ def testGiven3DLogger_whenShow3DDefault_shouldDisplayEverythingExceptViews(self, mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addImage') - def testGiven2DLogger_whenShow3DDefault_shouldDisplayEverythingExceptPointCloud(self, mockAddImage, mockShow, *args): + @patch("pytissueoptics.scene.viewer.MayaviViewer.addImage") + def testGiven2DLogger_whenShow3DDefault_shouldDisplayEverythingExceptPointCloud( + self, mockAddImage, mockShow, *args + ): self._givenLoggerWithXSceneView() self.logger.has3D = False @@ -259,8 +267,10 @@ def testGiven2DLogger_whenShow3DDefault_shouldDisplayEverythingExceptPointCloud( mockShow.assert_called_once() @patchMayaviRender - @patch('pytissueoptics.scene.viewer.MayaviViewer.addImage') - def testGiven2DLogger_whenShow3DWithDefault3DVisibility_shouldWarnAndDisplayDefault2D(self, mockAddImage, mockShow, *args): + @patch("pytissueoptics.scene.viewer.MayaviViewer.addImage") + def testGiven2DLogger_whenShow3DWithDefault3DVisibility_shouldWarnAndDisplayDefault2D( + self, mockAddImage, mockShow, *args + ): self._givenLoggerWithXSceneView() self.logger.has3D = False diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py index 3261895a..b2edaeb4 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testEnergyLogger.py @@ -24,7 +24,7 @@ class TestEnergyLogger(unittest.TestCase): CUBE_CENTER = Vector(0.5, 0.5, 0.5) - CUBE = Cube(1, position=CUBE_CENTER, material=ScatteringMaterial(), label='cube') + CUBE = Cube(1, position=CUBE_CENTER, material=ScatteringMaterial(), label="cube") TEST_SCENE = ScatteringScene([CUBE]) INTERACTION_KEY = InteractionKey(CUBE.getLabel()) @@ -50,7 +50,7 @@ def testWhenGetViewWithInvalidIndex_shouldRaiseException(self): with self.assertRaises(IndexError): self.logger.getView(25) - @patch('sys.stdout', new_callable=io.StringIO) + @patch("sys.stdout", new_callable=io.StringIO) def testWhenListViews_shouldPrintListOfViews(self, mock_stdout): self.logger.listViews() self.assertEqual(len(self.logger.views) + 1, len(mock_stdout.getvalue().splitlines())) diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py b/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py index fc17b522..37088ec9 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testPointCloudFactory.py @@ -7,10 +7,10 @@ class TestPointCloudFactory(unittest.TestCase): - SOLID_LABEL_A = 'solidA' - SOLID_LABEL_B = 'solidB' - SOLID_A_SURFACE = 'surfaceA' - SOLID_B_SURFACE = 'surfaceB' + SOLID_LABEL_A = "solidA" + SOLID_LABEL_B = "solidB" + SOLID_A_SURFACE = "surfaceA" + SOLID_B_SURFACE = "surfaceB" N_POINTS_PER_SOLID = 3 N_POINTS_PER_SURFACE = 2 diff --git a/pytissueoptics/rayscattering/tests/energyLogging/testPointcloud.py b/pytissueoptics/rayscattering/tests/energyLogging/testPointcloud.py index 66c0a401..1ff92e4c 100644 --- a/pytissueoptics/rayscattering/tests/energyLogging/testPointcloud.py +++ b/pytissueoptics/rayscattering/tests/energyLogging/testPointcloud.py @@ -50,4 +50,3 @@ def _createTestPointCloud(): solidPoints = np.array([[0.5, 1, 0, 0], [0.5, 1, 0, 0.1], [0.5, 1, 0, -0.1]]) surfacePoints = np.array([[1, 1, 0, 0.1], [-1, 1, 0, -0.1]]) return PointCloud(solidPoints, surfacePoints) - diff --git a/pytissueoptics/rayscattering/tests/materials/testScatteringMaterial.py b/pytissueoptics/rayscattering/tests/materials/testScatteringMaterial.py index 9623f786..0541c3b1 100644 --- a/pytissueoptics/rayscattering/tests/materials/testScatteringMaterial.py +++ b/pytissueoptics/rayscattering/tests/materials/testScatteringMaterial.py @@ -11,7 +11,7 @@ def testShouldHaveAlbedo(self): expectedAlbedo = 2 / (2 + 8) self.assertEqual(expectedAlbedo, material.getAlbedo()) - @patch('random.random') + @patch("random.random") def testShouldHaveScatteringDistance(self, mockRandom): randomDistanceRatio = 0.5 mockRandom.return_value = randomDistanceRatio @@ -30,7 +30,7 @@ def testGivenFullAnisotropyFactor_shouldHaveZeroThetaScatteringAngle(self): theta, phi = material.getScatteringAngles() self.assertEqual(0, theta) - @patch('random.random') + @patch("random.random") def testShouldHaveThetaScatteringAngleBetween0AndPi(self, mockRandom): mockRandom.return_value = 0 material = ScatteringMaterial(mu_s=8, mu_a=2, g=0, n=1.4) @@ -45,7 +45,7 @@ def testShouldHaveThetaScatteringAngleBetween0AndPi(self, mockRandom): theta, _ = material.getScatteringAngles() self.assertEqual(0, theta) - @patch('random.random') + @patch("random.random") def testShouldHavePhiScatteringAngleBetween0And2Pi(self, mockRandom): mockRandom.return_value = 0 material = ScatteringMaterial(mu_s=8, mu_a=2, g=0, n=1.4) diff --git a/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py b/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py index e2999338..5162d3a5 100644 --- a/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py +++ b/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py @@ -14,10 +14,11 @@ def wrapper(*args, **kwargs): clc.OPENCL_CONFIG_PATH = os.path.join(tempDir, "config.json") func(*args, **kwargs) clc.OPENCL_CONFIG_PATH = previousPath + return wrapper -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLConfig(unittest.TestCase): @tempConfigPath def testGivenNoConfigFile_shouldWarnAndCreateANewOne(self): @@ -43,16 +44,20 @@ def testGivenNewConfigFile_shouldHaveDefaultsFromEnvironment(self): @tempConfigPath def testGivenCompleteConfigFile_shouldBeValid(self): with open(clc.OPENCL_CONFIG_PATH, "w") as f: - f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 1000, ' - '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}') + f.write( + '{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 1000, ' + '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}' + ) config = clc.CLConfig() config.validate() @tempConfigPath def testGivenMaxMemoryNotSet_whenValidate_shouldWarnAndSetMaxMemory(self): with open(clc.OPENCL_CONFIG_PATH, "w") as f: - f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": null, ' - '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}') + f.write( + '{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": null, ' + '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}' + ) with patch("os.getenv", return_value=None): config = clc.CLConfig() with self.assertWarns(UserWarning): @@ -63,8 +68,7 @@ def testGivenMaxMemoryNotSet_whenValidate_shouldWarnAndSetMaxMemory(self): @tempConfigPath def testGivenFileIsMissingParameter_whenValidate_shouldResetDefaultValueAndRaise(self): with open(clc.OPENCL_CONFIG_PATH, "w") as f: - f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 1000, ' - '"IPP_TEST_N_PHOTONS": 1000}') + f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 1000, "IPP_TEST_N_PHOTONS": 1000}') config = clc.CLConfig() with self.assertRaises(ValueError): config.validate() @@ -74,8 +78,10 @@ def testGivenFileIsMissingParameter_whenValidate_shouldResetDefaultValueAndRaise @tempConfigPath def testGivenFileWithAParameterBelowOrEqualToZero_whenValidate_shouldResetDefaultValueAndRaise(self): with open(clc.OPENCL_CONFIG_PATH, "w") as f: - f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 0, ' - '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}') + f.write( + '{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 0, ' + '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}' + ) with patch("os.getenv", return_value=None): config = clc.CLConfig() diff --git a/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py b/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py index 1b7d46bc..b84b3981 100644 --- a/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py +++ b/pytissueoptics/rayscattering/tests/opencl/config/testIPPTable.py @@ -11,6 +11,7 @@ def wrapper(*args, **kwargs): with tempfile.TemporaryDirectory() as tempDir: IPPTable.TABLE_PATH = os.path.join(tempDir, "ipp.json") func(*args, **kwargs) + return wrapper diff --git a/pytissueoptics/rayscattering/tests/opencl/src/CLObjects.py b/pytissueoptics/rayscattering/tests/opencl/src/CLObjects.py index 2e3e887b..d9dad91b 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/CLObjects.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/CLObjects.py @@ -14,18 +14,30 @@ class IntersectionCL(CLObject): STRUCT_NAME = "Intersection" - STRUCT_DTYPE = np.dtype([("exists", cl.cltypes.uint), - ("distance", cl.cltypes.float), - ("position", cl.cltypes.float3), - ("normal", cl.cltypes.float3), - ("surfaceID", cl.cltypes.uint), - ("polygonID", cl.cltypes.uint), - ("distanceLeft", cl.cltypes.float), - ("isSmooth", cl.cltypes.uint), - ("rawNormal", cl.cltypes.float3)]) + STRUCT_DTYPE = np.dtype( + [ + ("exists", cl.cltypes.uint), + ("distance", cl.cltypes.float), + ("position", cl.cltypes.float3), + ("normal", cl.cltypes.float3), + ("surfaceID", cl.cltypes.uint), + ("polygonID", cl.cltypes.uint), + ("distanceLeft", cl.cltypes.float), + ("isSmooth", cl.cltypes.uint), + ("rawNormal", cl.cltypes.float3), + ] + ) - def __init__(self, distance: float = 10, position=Vector(0, 0, 0), normal=Vector(0, 0, 1), - surfaceID=0, polygonID=0, distanceLeft: float = 0, **kwargs): + def __init__( + self, + distance: float = 10, + position=Vector(0, 0, 0), + normal=Vector(0, 0, 1), + surfaceID=0, + polygonID=0, + distanceLeft: float = 0, + **kwargs, + ): self._distance = distance self._position = position self._normal = normal @@ -51,9 +63,9 @@ def _getInitialHostBuffer(self) -> np.ndarray: class RayCL(CLObject): STRUCT_NAME = "Ray" - STRUCT_DTYPE = np.dtype([("origin", cl.cltypes.float4), - ("direction", cl.cltypes.float4), - ("length", cl.cltypes.float)]) + STRUCT_DTYPE = np.dtype( + [("origin", cl.cltypes.float4), ("direction", cl.cltypes.float4), ("length", cl.cltypes.float)] + ) def __init__(self, origins: np.ndarray, directions: np.ndarray, lengths: np.ndarray): self._origins = origins diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py index 3362d4e1..0c34f98b 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLFresnel.py @@ -29,7 +29,7 @@ class FresnelResult: nextSolidID: int -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLFresnel(unittest.TestCase): OUTSIDE_SOLID_ID = 0 INSIDE_SOLID_ID = 1 @@ -45,8 +45,19 @@ def _setUpWith(self, n1=1.0, n2=1.5, normal=Vector(0, 0, 1)): outsideMaterial = ScatteringMaterial(0.8, 0.2, 0.9, n1) insideMaterial = ScatteringMaterial(0.8, 0.2, 0.9, n2) self.materials = MaterialCL([outsideMaterial, insideMaterial]) - self.surfaces = SurfaceCL([SurfaceCLInfo(0, 1, self.INSIDE_MATERIAL_ID, self.OUTSIDE_MATERIAL_ID, - self.INSIDE_SOLID_ID, self.OUTSIDE_SOLID_ID, False)]) + self.surfaces = SurfaceCL( + [ + SurfaceCLInfo( + 0, + 1, + self.INSIDE_MATERIAL_ID, + self.OUTSIDE_MATERIAL_ID, + self.INSIDE_SOLID_ID, + self.OUTSIDE_SOLID_ID, + False, + ) + ] + ) self.normal = normal self.n1 = n1 self.n2 = n2 @@ -123,9 +134,18 @@ def _computeFresnelIntersection(self, rayDirection: Vector) -> FresnelResult: singleRayDirectionBuffer = cl.cltypes.make_float3(*rayDirection.array) seeds = SeedCL(N) fresnelBuffer = FresnelIntersectionCL(N) - self.program.launchKernel("computeFresnelIntersectionKernel", N=N, - arguments=[singleRayDirectionBuffer, self.intersection, - self.materials, self.surfaces, seeds, fresnelBuffer]) + self.program.launchKernel( + "computeFresnelIntersectionKernel", + N=N, + arguments=[ + singleRayDirectionBuffer, + self.intersection, + self.materials, + self.surfaces, + seeds, + fresnelBuffer, + ], + ) return self._getFresnelResult(fresnelBuffer) @@ -142,9 +162,11 @@ def _getReflectionCoefficient(self, rayDirection: Vector) -> float: self.program._build([self.materials, self.surfaces, self.intersection]) self.program.include(self.materials.declaration + self.surfaces.declaration + self.intersection.declaration) - self.program.launchKernel("getReflectionCoefficientKernel", N=N, - arguments=[np.float32(self.n1), np.float32(self.n2), - np.float32(thetaIn), coefficientBuffer]) + self.program.launchKernel( + "getReflectionCoefficientKernel", + N=N, + arguments=[np.float32(self.n1), np.float32(self.n2), np.float32(thetaIn), coefficientBuffer], + ) return float(self.program.getData(coefficientBuffer)[0]) def _getFresnelResult(self, fresnelBuffer) -> FresnelResult: @@ -177,11 +199,15 @@ def _mockIsReflected(self, isReflected: bool): class FresnelIntersectionCL(CLObject): STRUCT_NAME = "FresnelIntersection" - STRUCT_DTYPE = np.dtype([("incidencePlane", cl.cltypes.float3), - ("isReflected", cl.cltypes.uint), - ("angleDeflection", cl.cltypes.float), - ("nextMaterialID", cl.cltypes.uint), - ("nextSolidID", cl.cltypes.int)]) + STRUCT_DTYPE = np.dtype( + [ + ("incidencePlane", cl.cltypes.float3), + ("isReflected", cl.cltypes.uint), + ("angleDeflection", cl.cltypes.float), + ("nextMaterialID", cl.cltypes.uint), + ("nextSolidID", cl.cltypes.int), + ] + ) def __init__(self, N: int): self._N = N diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py index 9354cc63..44c92f57 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLIntersection.py @@ -13,7 +13,7 @@ from pytissueoptics.rayscattering.tests.opencl.src.testCLFresnel import IntersectionCL -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLIntersection(unittest.TestCase): def setUp(self): sourcePath = os.path.join(OPENCL_SOURCE_DIR, "intersection.c") @@ -26,16 +26,26 @@ def testRayIntersection(self): rayLength = 10 rayOrigin = [0, 0, -7] - rays = RayCL(origins=np.full((N, 3), rayOrigin), - directions=np.full((N, 3), [0, 0, 1]), - lengths=np.full(N, rayLength)) + rays = RayCL( + origins=np.full((N, 3), rayOrigin), directions=np.full((N, 3), [0, 0, 1]), lengths=np.full(N, rayLength) + ) intersections = IntersectionCL(skipDeclaration=True) try: - self.program.launchKernel("findIntersections", N=N, arguments=[rays, clScene.nSolids, - clScene.solids, clScene.surfaces, - clScene.triangles, clScene.vertices, - clScene.solidCandidates, intersections]) + self.program.launchKernel( + "findIntersections", + N=N, + arguments=[ + rays, + clScene.nSolids, + clScene.solids, + clScene.surfaces, + clScene.triangles, + clScene.vertices, + clScene.solidCandidates, + intersections, + ], + ) except Exception: traceback.print_exc(0) diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py index 2d19faa8..64c65846 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLPhoton.py @@ -49,7 +49,7 @@ class DataPointResult: surfaceID: int -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLPhoton(unittest.TestCase): def setUp(self): self.INITIAL_POSITION = Vector(2, 2, 0) @@ -64,12 +64,12 @@ def testWhenMoveBy_shouldMovePhotonByTheGivenDistanceTowardsItsDirection(self): self.assertEqual(self.INITIAL_POSITION + self.INITIAL_DIRECTION * 10, photonResult.position) def testWhenScatterByTheta0_shouldNotChangePhotonDirection(self): - phi, theta = np.pi/4, 0 + phi, theta = np.pi / 4, 0 photonResult = self._photonFunc("scatterBy", phi, theta) self._assertVectorAlmostEqual(self.INITIAL_DIRECTION, photonResult.direction, places=6) def testWhenScatterByThetaPi_shouldRotatePhotonDirectionToOpposite(self): - phi, theta = np.pi/4, np.pi + phi, theta = np.pi / 4, np.pi photonResult = self._photonFunc("scatterBy", phi, theta) @@ -81,7 +81,7 @@ def testWhenScatterByPhi0_shouldStillResetPhotonEr(self): photonResult = self._photonFunc("scatterBy", 0, 0) initialEr = photonResult.er - phi, theta = 0, np.pi/4 + phi, theta = 0, np.pi / 4 photonResult = self._photonFunc("scatterBy", phi, theta) self._assertVectorNotAlmostEqual(initialEr, photonResult.er) @@ -89,7 +89,7 @@ def testWhenScatterByPhi2Pi_shouldStillResetPhotonEr(self): photonResult = self._photonFunc("scatterBy", 0, 0) initialEr = photonResult.er - phi, theta = 2*np.pi, np.pi/4 + phi, theta = 2 * np.pi, np.pi / 4 photonResult = self._photonFunc("scatterBy", phi, theta) self._assertVectorNotAlmostEqual(initialEr, photonResult.er) @@ -97,7 +97,7 @@ def testWhenScatterBy_shouldChangePhotonErAndDirection(self): photonResult = self._photonFunc("scatterBy", 0, 0) initialEr = photonResult.er - phi, theta = np.pi/4, np.pi/4 + phi, theta = np.pi / 4, np.pi / 4 photonResult = self._photonFunc("scatterBy", phi, theta) self._assertVectorNotAlmostEqual(initialEr, photonResult.er) self._assertVectorNotAlmostEqual(self.INITIAL_DIRECTION, photonResult.direction) @@ -110,7 +110,7 @@ def testWhenReflect_shouldReflectPhoton(self): self.INITIAL_DIRECTION = Vector(1, -1, 0) self.INITIAL_DIRECTION.normalize() incidencePlane = Vector(0, 0, 1) - angleDeflection = np.pi/2 + angleDeflection = np.pi / 2 photonResult = self._photonFunc("reflect", incidencePlane, angleDeflection) @@ -122,7 +122,7 @@ def testWhenRefract_shouldRefractPhoton(self): self.INITIAL_DIRECTION = Vector(1, -1, 0) self.INITIAL_DIRECTION.normalize() incidencePlane = Vector(0, 0, 1) - angleDeflection = -np.pi/4 + angleDeflection = -np.pi / 4 photonResult = self._photonFunc("refract", incidencePlane, angleDeflection) @@ -165,7 +165,9 @@ def testWhenInteract_shouldDecreasePhotonWeight(self): expectedWeightLoss = self.INITIAL_WEIGHT * material.getAlbedo() self.assertAlmostEqual(self.INITIAL_WEIGHT - expectedWeightLoss, photonResult.weight) - def testWhenLogIntersectionWithPhotonLeavingIntoWorld_shouldLogOnlyOneIntersectionOnPreviousSolidWithPositiveWeightCrossing(self): + def testWhenLogIntersectionWithPhotonLeavingIntoWorld_shouldLogOnlyOneIntersectionOnPreviousSolidWithPositiveWeightCrossing( + self, + ): self.INITIAL_DIRECTION = Vector(0, 1, 1) self.INITIAL_DIRECTION.normalize() intersectionNormal = Vector(0, 0, 1) @@ -189,7 +191,9 @@ def testWhenLogIntersectionWithPhotonLeavingIntoWorld_shouldLogOnlyOneIntersecti self.assertAlmostEqual(0, dataPoint2.deltaWeight) self.assertAlmostEqual(NO_LOG_ID, dataPoint2.solidID) - def testWhenLogIntersectionWithPhotonEnteringFromWorld_shouldLogOnlyOneIntersectionOnSolidInsideWithNegativeWeightCrossing(self): + def testWhenLogIntersectionWithPhotonEnteringFromWorld_shouldLogOnlyOneIntersectionOnSolidInsideWithNegativeWeightCrossing( + self, + ): self.INITIAL_DIRECTION = Vector(0, 1, -1) self.INITIAL_DIRECTION.normalize() self.INITIAL_SOLID_ID = NO_SOLID_ID @@ -254,13 +258,21 @@ def testWhenReflectOrRefractWithReflectingIntersection_shouldReflectPhoton(self) self.INITIAL_SOLID_ID = NO_SOLID_ID self.INITIAL_DIRECTION = Vector(1, -1, 0) self.INITIAL_DIRECTION.normalize() - self._mockFresnelIntersection(isReflected=True, incidencePlane=Vector(0, 0, 1), - angleDeflection=np.pi / 2) + self._mockFresnelIntersection(isReflected=True, incidencePlane=Vector(0, 0, 1), angleDeflection=np.pi / 2) logger = DataPointCL(2) surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID=9, outsideSolidID=NO_SOLID_ID, toSmooth=False)]) - photonResult = self._photonFunc("reflectOrRefract", intersectionNormal, 0, 10, - MaterialCL([ScatteringMaterial()]), surfaces, logger, 0, SeedCL(1)) + photonResult = self._photonFunc( + "reflectOrRefract", + intersectionNormal, + 0, + 10, + MaterialCL([ScatteringMaterial()]), + surfaces, + logger, + 0, + SeedCL(1), + ) expectedDirection = Vector(1, 1, 0) expectedDirection.normalize() @@ -280,14 +292,27 @@ def testWhenReflectOrRefractWithRefractingIntersection_shouldRefractPhoton(self) self.INITIAL_DIRECTION.normalize() insideSolidID = 9 insideMaterialID = 0 - self._mockFresnelIntersection(isReflected=False, incidencePlane=Vector(0, 0, 1), - angleDeflection=-np.pi / 4, nextMaterialID=insideMaterialID, - nextSolidID=insideSolidID) + self._mockFresnelIntersection( + isReflected=False, + incidencePlane=Vector(0, 0, 1), + angleDeflection=-np.pi / 4, + nextMaterialID=insideMaterialID, + nextSolidID=insideSolidID, + ) logger = DataPointCL(2) surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID, outsideSolidID=NO_SOLID_ID, toSmooth=False)]) - photonResult = self._photonFunc("reflectOrRefract", intersectionNormal, 0, 10, - MaterialCL([ScatteringMaterial()]), surfaces, logger, 0, SeedCL(1)) + photonResult = self._photonFunc( + "reflectOrRefract", + intersectionNormal, + 0, + 10, + MaterialCL([ScatteringMaterial()]), + surfaces, + logger, + 0, + SeedCL(1), + ) expectedDirection = Vector(0, -1, 0) expectedDirection.normalize() @@ -310,8 +335,17 @@ def testWhenStepToInfinityWithNoIntersection_shouldKillPhoton(self): logger = DataPointCL(2) surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID=9, outsideSolidID=10, toSmooth=False)]) - photonResult = self._photonFunc("propagateStep", stepDistance, - MaterialCL([ScatteringMaterial()]), surfaces, TriangleCL([]), VertexCL([]), SeedCL(1), logger, 0) + photonResult = self._photonFunc( + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial()]), + surfaces, + TriangleCL([]), + VertexCL([]), + SeedCL(1), + logger, + 0, + ) self.assertEqual(0, photonResult.weight) @@ -321,8 +355,17 @@ def testWhenStepWithNoIntersection_shouldMovePhotonAndScatter(self): logger = DataPointCL(2) surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID=9, outsideSolidID=10, toSmooth=False)]) - photonResult = self._photonFunc("propagateStep", stepDistance, - MaterialCL([ScatteringMaterial()]), surfaces, TriangleCL([]), VertexCL([]), SeedCL(1), logger, 0) + photonResult = self._photonFunc( + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial()]), + surfaces, + TriangleCL([]), + VertexCL([]), + SeedCL(1), + logger, + 0, + ) expectedPosition = self.INITIAL_POSITION + self.INITIAL_DIRECTION * stepDistance self._assertVectorAlmostEqual(expectedPosition, photonResult.position) @@ -338,13 +381,24 @@ def testWhenStepWithIntersectionTooCloseToVertex_shouldMovePhotonAwayABitAndScat intersectionPosition = self.INITIAL_POSITION + self.INITIAL_DIRECTION * stepDistance logger = DataPointCL(2) - surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID=nextSolidID, outsideSolidID=self.INITIAL_SOLID_ID, toSmooth=False)]) + surfaces = SurfaceCL( + [SurfaceCLInfo(0, 0, 0, 0, insideSolidID=nextSolidID, outsideSolidID=self.INITIAL_SOLID_ID, toSmooth=False)] + ) triangles = TriangleCL([TriangleCLInfo([0, 1, 2], normal)]) # Put vertex slightly after the intersection point vertex = Vertex(intersectionPosition.x, intersectionPosition.y, intersectionPosition.z - 2e-7) vertex.normal = normal - photonResult = self._photonFunc("propagateStep", stepDistance, - MaterialCL([ScatteringMaterial()]), surfaces, triangles, VertexCL([vertex]), SeedCL(1), logger, 0) + photonResult = self._photonFunc( + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial()]), + surfaces, + triangles, + VertexCL([vertex]), + SeedCL(1), + logger, + 0, + ) # Due to the correction being under floating point rounding error, we cannot measure it. self._assertVectorAlmostEqual(intersectionPosition, photonResult.position) @@ -357,8 +411,17 @@ def testWhenStepWithNoDistance_shouldStepWithANewScatteringDistance(self): logger = DataPointCL(2) surfaces = SurfaceCL([SurfaceCLInfo(0, 0, 0, 0, insideSolidID=9, outsideSolidID=10, toSmooth=False)]) - photonResult = self._photonFunc("propagateStep", stepDistance, - MaterialCL([ScatteringMaterial(5, 2)]), surfaces, TriangleCL([]), VertexCL([]), SeedCL(1), logger, 0) + photonResult = self._photonFunc( + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial(5, 2)]), + surfaces, + TriangleCL([]), + VertexCL([]), + SeedCL(1), + logger, + 0, + ) self._assertVectorNotAlmostEqual(self.INITIAL_POSITION, photonResult.position) @@ -373,8 +436,15 @@ def testWhenStepWithIntersectionReflecting_shouldMovePhotonToIntersection(self): triangles = TriangleCL([TriangleCLInfo([0, 1, 2], Vector(0, 0, 1))]) vertices = VertexCL([Vertex(0, 0, 0)] * 3) photonResult = self._photonFunc( - "propagateStep", stepDistance, MaterialCL([ScatteringMaterial()]), - surfaces, triangles, vertices, SeedCL(1), logger, 0 + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial()]), + surfaces, + triangles, + vertices, + SeedCL(1), + logger, + 0, ) expectedPosition = self.INITIAL_POSITION + self.INITIAL_DIRECTION * intersectionDistance @@ -391,8 +461,15 @@ def testWhenStepWithIntersectionRefracting_shouldMovePhotonToIntersection(self): triangles = TriangleCL([TriangleCLInfo([0, 1, 2], Vector(0, 0, 1))]) vertices = VertexCL([Vertex(0, 0, 0)] * 3) photonResult = self._photonFunc( - "propagateStep", stepDistance, MaterialCL([ScatteringMaterial()]), - surfaces, triangles, vertices, SeedCL(1), logger, 0 + "propagateStep", + stepDistance, + MaterialCL([ScatteringMaterial()]), + surfaces, + triangles, + vertices, + SeedCL(1), + logger, + 0, ) expectedPosition = self.INITIAL_POSITION + self.INITIAL_DIRECTION * intersectionDistance @@ -421,13 +498,16 @@ def testWhenPropagateReachesMaxInteractions_shouldReturnCurrentPhotonState(self) def _photonFunc(self, funcName: str, *args) -> PhotonResult: self._addMissingDeclarations(args) - photonBuffer = PhotonCL(positions=np.array([self.INITIAL_POSITION.array]), - directions=np.array([self.INITIAL_DIRECTION.array]), - materialID=0, solidID=self.INITIAL_SOLID_ID, weight=self.INITIAL_WEIGHT) + photonBuffer = PhotonCL( + positions=np.array([self.INITIAL_POSITION.array]), + directions=np.array([self.INITIAL_DIRECTION.array]), + materialID=0, + solidID=self.INITIAL_SOLID_ID, + weight=self.INITIAL_WEIGHT, + ) npArgs = [np.float32(arg) if isinstance(arg, (float, int)) else arg for arg in args] npArgs = [cl.cltypes.make_float3(*arg.array) if isinstance(arg, Vector) else arg for arg in npArgs] - self.program.launchKernel(kernelName=funcName + "Kernel", N=1, - arguments=npArgs + [photonBuffer, np.int32(0)]) + self.program.launchKernel(kernelName=funcName + "Kernel", N=1, arguments=npArgs + [photonBuffer, np.int32(0)]) photonResult = self._getPhotonResult(photonBuffer) return photonResult @@ -440,13 +520,33 @@ def _photonPropagateInInfiniteMedium(self, factorOfMaxInteractions=1.0) -> Photo s = self._getCLSceneOfInfiniteMedium(material) logger = DataPointCL(maxInteractions) - photonBuffer = PhotonCL(positions=np.array([self.INITIAL_POSITION.array]), - directions=np.array([self.INITIAL_DIRECTION.array]), - materialID=0, solidID=self.INITIAL_SOLID_ID, weight=self.INITIAL_WEIGHT) - self.program.launchKernel(kernelName="propagate", N=1, - arguments=[np.int32(1), np.int32(maxInteractions), np.float32(WEIGHT_THRESHOLD), np.int32(1), - photonBuffer, s.materials, s.nSolids, s.solids, s.surfaces, s.triangles, - s.vertices, s.solidCandidates, SeedCL(1), logger]) + photonBuffer = PhotonCL( + positions=np.array([self.INITIAL_POSITION.array]), + directions=np.array([self.INITIAL_DIRECTION.array]), + materialID=0, + solidID=self.INITIAL_SOLID_ID, + weight=self.INITIAL_WEIGHT, + ) + self.program.launchKernel( + kernelName="propagate", + N=1, + arguments=[ + np.int32(1), + np.int32(maxInteractions), + np.float32(WEIGHT_THRESHOLD), + np.int32(1), + photonBuffer, + s.materials, + s.nSolids, + s.solids, + s.surfaces, + s.triangles, + s.vertices, + s.solidCandidates, + SeedCL(1), + logger, + ], + ) return self._getPhotonResult(photonBuffer) @staticmethod @@ -456,9 +556,17 @@ def _getCLSceneOfInfiniteMedium(material): return sceneCL def _addMissingDeclarations(self, kernelArguments): - self.program._include = '' - requiredObjects = [MaterialCL([ScatteringMaterial()]), SurfaceCL([]), SeedCL(1), VertexCL([]), - DataPointCL(1), SolidCandidateCL(1, 1), TriangleCL([]), SolidCL([])] + self.program._include = "" + requiredObjects = [ + MaterialCL([ScatteringMaterial()]), + SurfaceCL([]), + SeedCL(1), + VertexCL([]), + DataPointCL(1), + SolidCandidateCL(1, 1), + TriangleCL([]), + SolidCL([]), + ] missingObjects = [] for obj in requiredObjects: if any(isinstance(arg, type(obj)) for arg in kernelArguments): @@ -471,8 +579,14 @@ def _addMissingDeclarations(self, kernelArguments): def _getPhotonResult(self, photonBuffer: PhotonCL): data = self.program.getData(photonBuffer)[0] - return PhotonResult(position=Vector(*data[:3]), direction=Vector(*data[4:7]), er=Vector(*data[8:11]), - weight=data[12], materialID=data[13], solidID=data[14]) + return PhotonResult( + position=Vector(*data[:3]), + direction=Vector(*data[4:7]), + er=Vector(*data[8:11]), + weight=data[12], + materialID=data[13], + solidID=data[14], + ) def _getDataPointResult(self, dataPointBuffer: DataPointCL, i=0): data = self.program.getData(dataPointBuffer)[i] @@ -500,14 +614,22 @@ def _mockRandomValue(self, value): } return result; }""" - mockFunction = """float getRandomFloatValue(__global unsigned int *seeds, unsigned int id){ + mockFunction = ( + """float getRandomFloatValue(__global unsigned int *seeds, unsigned int id){ return %f; - }""" % value + }""" + % value + ) self.program.mock(getRandomFloatValueFunction, mockFunction) - def _mockFresnelIntersection(self, isReflected: bool, - incidencePlane: Vector = Vector(0, 1, 0), angleDeflection: float = np.pi / 2, - nextMaterialID=0, nextSolidID=0): + def _mockFresnelIntersection( + self, + isReflected: bool, + incidencePlane: Vector = Vector(0, 1, 0), + angleDeflection: float = np.pi / 2, + nextMaterialID=0, + nextSolidID=0, + ): fresnelCall = """FresnelIntersection fresnelIntersection = computeFresnelIntersection(photons[photonID].direction, intersection, materials, surfaces, seeds, gid);""" x, y, z = incidencePlane.array @@ -522,7 +644,9 @@ def _mockFresnelIntersection(self, isReflected: bool, def _mockFindIntersection(self, exists=True, distance=8.0, normal=Vector(0, 0, 1), surfaceID=0, distanceLeft=2): expectedPosition = self.INITIAL_POSITION + self.INITIAL_DIRECTION * distance - intersectionCall = """Intersection intersection = findIntersection(stepRay, scene, gid, photons[photonID].solidID);""" + intersectionCall = ( + """Intersection intersection = findIntersection(stepRay, scene, gid, photons[photonID].solidID);""" + ) px, py, pz = expectedPosition.array nx, ny, nz = normal.array mockCall = """Intersection intersection; diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py index ec3b0c5b..ce614227 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLRandom.py @@ -9,7 +9,7 @@ from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLRandom(unittest.TestCase): def setUp(self): sourcePath = os.path.join(OPENCL_SOURCE_DIR, "random.c") diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py index b5c35ce7..d9e99710 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLScatteringMaterial.py @@ -10,7 +10,7 @@ from pytissueoptics.rayscattering.opencl.config.CLConfig import OPENCL_SOURCE_DIR -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLScatteringMaterial(unittest.TestCase): def setUp(self): sourcePath = os.path.join(OPENCL_SOURCE_DIR, "scatteringMaterial.c") @@ -24,8 +24,9 @@ def testWhenGetScatteringDistance_shouldReturnCorrectScatteringDistance(self): distanceBuffer = EmptyBuffer(nWorkUnits) mu_t = np.float32(4) - self.program.launchKernel("getScatteringDistanceKernel", N=nWorkUnits, - arguments=[distanceBuffer, randomNumberBuffer, mu_t]) + self.program.launchKernel( + "getScatteringDistanceKernel", N=nWorkUnits, arguments=[distanceBuffer, randomNumberBuffer, mu_t] + ) distance = self.program.getData(distanceBuffer)[0] self.assertAlmostEqual(-math.log(randomNumber) / mu_t, distance, places=5) @@ -37,8 +38,9 @@ def testWhenGetScatteringDistanceInVacuum_shouldReturnInfinity(self): distanceBuffer = EmptyBuffer(nWorkUnits) mu_t = np.float32(0) - self.program.launchKernel("getScatteringDistanceKernel", N=nWorkUnits, - arguments=[distanceBuffer, randomNumberBuffer, mu_t]) + self.program.launchKernel( + "getScatteringDistanceKernel", N=nWorkUnits, arguments=[distanceBuffer, randomNumberBuffer, mu_t] + ) distance = self.program.getData(distanceBuffer)[0] self.assertEqual(math.inf, distance) @@ -49,8 +51,9 @@ def testWhenGetScatteringAnglePhi_shouldReturnAngleBetween0And2Pi(self): randomNumberBuffer = BufferOf(np.array(randomNumbers, dtype=np.float32)) anglePhiBuffer = EmptyBuffer(nWorkUnits) - self.program.launchKernel("getScatteringAnglePhiKernel", N=nWorkUnits, - arguments=[anglePhiBuffer, randomNumberBuffer]) + self.program.launchKernel( + "getScatteringAnglePhiKernel", N=nWorkUnits, arguments=[anglePhiBuffer, randomNumberBuffer] + ) anglesPhi = self.program.getData(anglePhiBuffer) expectedAngles = [0, math.pi, 2 * math.pi] @@ -62,8 +65,9 @@ def testWhenGetScatteringAngleTheta_shouldReturnAngleBetween0AndPi(self): randomNumberBuffer = BufferOf(np.array(randomNumbers, dtype=np.float32)) angleThetaBuffer = EmptyBuffer(nWorkUnits) g = np.float32(0) - self.program.launchKernel("getScatteringAngleThetaKernel", N=nWorkUnits, - arguments=[angleThetaBuffer, randomNumberBuffer, g]) + self.program.launchKernel( + "getScatteringAngleThetaKernel", N=nWorkUnits, arguments=[angleThetaBuffer, randomNumberBuffer, g] + ) anglesTheta = self.program.getData(angleThetaBuffer) expectedAngles = [math.pi, math.pi / 2, 0] @@ -74,8 +78,9 @@ def testGivenFullAnisotropyFactor_shouldReturnZeroThetaAngle(self): randomNumberBuffer = RandomBuffer(nWorkUnits) angleThetaBuffer = EmptyBuffer(nWorkUnits) g = np.float32(1) - self.program.launchKernel("getScatteringAngleThetaKernel", N=nWorkUnits, - arguments=[angleThetaBuffer, randomNumberBuffer, g]) + self.program.launchKernel( + "getScatteringAngleThetaKernel", N=nWorkUnits, arguments=[angleThetaBuffer, randomNumberBuffer, g] + ) anglesTheta = self.program.getData(angleThetaBuffer) expectedAngles = np.zeros(nWorkUnits) diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py index acf4c7c2..5fd09867 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLSmoothing.py @@ -23,7 +23,7 @@ from pytissueoptics.scene.geometry import Triangle, Vector, Vertex -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLNormalSmoothing(unittest.TestCase): def setUp(self): sourcePath = os.path.join(OPENCL_SOURCE_DIR, "intersection.c") @@ -76,13 +76,17 @@ def _getSmoothNormal(self, atPosition: Vector, rayDirection: Vector = Vector(0, triangleInfo = TriangleCLInfo([0, 1, 2], self.TRIANGLE.normal) triangleCL = TriangleCL([triangleInfo]) N = 1 - intersectionCL = IntersectionCL(polygonID=0, position=atPosition, normal=self.TRIANGLE.normal, skipDeclaration=True) - rayCL = RayCL(origins=np.full((N, 3), [0, 0, 0]), - directions=np.full((N, 3), rayDirection.array), - lengths=np.full(N, 10)) + intersectionCL = IntersectionCL( + polygonID=0, position=atPosition, normal=self.TRIANGLE.normal, skipDeclaration=True + ) + rayCL = RayCL( + origins=np.full((N, 3), [0, 0, 0]), directions=np.full((N, 3), rayDirection.array), lengths=np.full(N, 10) + ) try: - self.program.launchKernel("setSmoothNormals", N=N, arguments=[intersectionCL, triangleCL, verticesCL, rayCL]) + self.program.launchKernel( + "setSmoothNormals", N=N, arguments=[intersectionCL, triangleCL, verticesCL, rayCL] + ) except Exception: traceback.print_exc(0) @@ -91,9 +95,15 @@ def _getSmoothNormal(self, atPosition: Vector, rayDirection: Vector = Vector(0, return Vector(smoothNormal["x"], smoothNormal["y"], smoothNormal["z"]) def _addMissingDeclarations(self): - self.program._include = '' - missingObjects = [MaterialCL([ScatteringMaterial()]), SurfaceCL([]), SeedCL(1), - DataPointCL(1), SolidCandidateCL(1, 1), SolidCL([])] + self.program._include = "" + missingObjects = [ + MaterialCL([ScatteringMaterial()]), + SurfaceCL([]), + SeedCL(1), + DataPointCL(1), + SolidCandidateCL(1, 1), + SolidCL([]), + ] for clObject in missingObjects: clObject.make(self.program.device) diff --git a/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py b/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py index 1b59ee5d..4e118a28 100644 --- a/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py +++ b/pytissueoptics/rayscattering/tests/opencl/src/testCLVectorOperators.py @@ -17,7 +17,7 @@ cl = None -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLVectorOperators(unittest.TestCase): def setUp(self): sourcePath = os.path.join(OPENCL_SOURCE_DIR, "vectorOperators.c") @@ -45,7 +45,9 @@ def testWhenRotateAroundAxisWithAngle_shouldRotateVectorAroundThisAxisByGivenAng axisBuffer = self._vectorBuffer([axis]) angleBuffer = BufferOf(np.array([angle], dtype=np.float32)) - self.program.launchKernel("rotateAroundAxisGlobalKernel", N=N, arguments=[vectorBuffer, axisBuffer, angleBuffer]) + self.program.launchKernel( + "rotateAroundAxisGlobalKernel", N=N, arguments=[vectorBuffer, axisBuffer, angleBuffer] + ) rotatedVector = self._vectorsFromBuffer(vectorBuffer)[0] expectedVector = Vector(0, 1, 1) @@ -63,9 +65,14 @@ def testWhenGetAnyOrthogonal_shouldReturnANewOrthogonalVector(self): vectors.append(vector) expectedOrthogonalVectors.append(vector.getAnyOrthogonal()) - edgeCaseVectors = [Vector(-1, 0, 0), Vector(1, 0, 0), - Vector(0, 1, 0), Vector(0, -1, 0), - Vector(0, 0, 1), Vector(0, 0, -1)] + edgeCaseVectors = [ + Vector(-1, 0, 0), + Vector(1, 0, 0), + Vector(0, 1, 0), + Vector(0, -1, 0), + Vector(0, 0, 1), + Vector(0, 0, -1), + ] for vector in edgeCaseVectors: vectors.append(vector) expectedOrthogonalVectors.append(vector.getAnyOrthogonal()) diff --git a/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py b/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py index 33a6d565..9e27708c 100644 --- a/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py +++ b/pytissueoptics/rayscattering/tests/opencl/testCLKeyLog.py @@ -10,7 +10,7 @@ from pytissueoptics.scene.logger import InteractionKey -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLKeyLog(unittest.TestCase): def setUp(self): material1 = ScatteringMaterial(2, 0.8, 0.8, 1.4) @@ -25,13 +25,17 @@ def _createTestLog(self, sceneCL): sphereID = sceneCL.getSolidID(self.sphere) sphereSurfaceIDs = sceneCL.getSurfaceIDs(sphereID) - log = np.array([[0, 0, 0, 0, cubeID, NO_SURFACE_ID], - [2, 0, 0, 0, sphereID, NO_SURFACE_ID], - [3, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID], - [4, 9, 9, 9, NO_LOG_ID, 9], - [5, 0, 0, 0, cubeID, cubeSurfaceIDs[1]], - [6, 0, 0, 0, sphereID, sphereSurfaceIDs[1]], - [1, 0, 0, 0, cubeID, NO_SURFACE_ID]]) + log = np.array( + [ + [0, 0, 0, 0, cubeID, NO_SURFACE_ID], + [2, 0, 0, 0, sphereID, NO_SURFACE_ID], + [3, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID], + [4, 9, 9, 9, NO_LOG_ID, 9], + [5, 0, 0, 0, cubeID, cubeSurfaceIDs[1]], + [6, 0, 0, 0, sphereID, sphereSurfaceIDs[1]], + [1, 0, 0, 0, cubeID, NO_SURFACE_ID], + ] + ) return log def testGivenCLKeyLog_whenTransferToSceneLogger_shouldLogDataWithInteractionKeys(self): @@ -48,10 +52,12 @@ def testGivenCLKeyLog_whenTransferToSceneLogger_shouldLogDataWithInteractionKeys expectedCubeData = arg_that(lambda arg: np.array_equal(arg, np.array([[0, 0, 0, 0], [1, 0, 0, 0]]))) verify(sceneLogger).logDataPointArray(expectedCubeData, InteractionKey(self.cube.getLabel())) - expectedValuesWithKeys = [(2, InteractionKey(self.sphere.getLabel())), - (3, InteractionKey(NO_SOLID_LABEL)), - (5, InteractionKey(self.cube.getLabel(), self.cube.surfaceLabels[0])), - (6, InteractionKey(self.sphere.getLabel(), self.sphere.surfaceLabels[0]))] + expectedValuesWithKeys = [ + (2, InteractionKey(self.sphere.getLabel())), + (3, InteractionKey(NO_SOLID_LABEL)), + (5, InteractionKey(self.cube.getLabel(), self.cube.surfaceLabels[0])), + (6, InteractionKey(self.sphere.getLabel(), self.sphere.surfaceLabels[0])), + ] for value, expectedKey in expectedValuesWithKeys: expectedData = arg_that(lambda arg: np.array_equal(arg, np.array([[value, 0, 0, 0]]))) verify(sceneLogger).logDataPointArray(expectedData, expectedKey) @@ -59,9 +65,13 @@ def testGivenCLKeyLog_whenTransferToSceneLogger_shouldLogDataWithInteractionKeys def testGivenCLKeyLogForInfiniteScene_whenTransferToSceneLogger_shouldLogDataWithInteractionKeys(self): self.scene = ScatteringScene([], worldMaterial=ScatteringMaterial(1, 0.8, 0.8, 1.4)) sceneCL = CLScene(self.scene, nWorkUnits=10) - log = np.array([[1, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID], - [2, 0, 0, 0, NO_LOG_ID, 99], - [3, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID]]) + log = np.array( + [ + [1, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID], + [2, 0, 0, 0, NO_LOG_ID, 99], + [3, 0, 0, 0, NO_SOLID_ID, NO_SURFACE_ID], + ] + ) clKeyLog = CLKeyLog(log, sceneCL) sceneLogger = mock(EnergyLogger) when(sceneLogger).logDataPointArray(...).thenReturn() diff --git a/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py b/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py index 19568c24..de0d8154 100644 --- a/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py +++ b/pytissueoptics/rayscattering/tests/opencl/testCLPhotons.py @@ -9,7 +9,7 @@ from pytissueoptics.scene.logger import InteractionKey -@unittest.skipIf(not OPENCL_OK, 'OpenCL device not available.') +@unittest.skipIf(not OPENCL_OK, "OpenCL device not available.") class TestCLPhotons(unittest.TestCase): def testWhenPropagateWithoutContext_shouldNotPropagate(self): positions = np.array([[0, 0, 0], [0, 0, 0]]) diff --git a/pytissueoptics/rayscattering/tests/statistics/testStats.py b/pytissueoptics/rayscattering/tests/statistics/testStats.py index d2e8ee52..925446e5 100644 --- a/pytissueoptics/rayscattering/tests/statistics/testStats.py +++ b/pytissueoptics/rayscattering/tests/statistics/testStats.py @@ -14,15 +14,19 @@ class TestStats(unittest.TestCase): - EXPECTED_SOLID_REPORT_LINES = ["Report of solid 'cube'", - " Absorbance: 80.00% (80.00% of total power)", - " Absorbance + Transmittance: 100.0%", - " Transmittance at 'cube_front': 0.0%", - " Transmittance at 'cube_back': 20.0%", - ''] - EXPECTED_REPORT_LINES = EXPECTED_SOLID_REPORT_LINES[:-1] + ["Report of 'world'", - " Absorbed 20.00% of total power", - ''] + EXPECTED_SOLID_REPORT_LINES = [ + "Report of solid 'cube'", + " Absorbance: 80.00% (80.00% of total power)", + " Absorbance + Transmittance: 100.0%", + " Transmittance at 'cube_front': 0.0%", + " Transmittance at 'cube_back': 20.0%", + "", + ] + EXPECTED_REPORT_LINES = EXPECTED_SOLID_REPORT_LINES[:-1] + [ + "Report of 'world'", + " Absorbed 20.00% of total power", + "", + ] def _setUp(self, keep3D=True, sourceSolidLabel=None, noViews=False): logger = self.makeTestCubeLogger(keep3D=keep3D, sourceSolidLabel=sourceSolidLabel, noViews=noViews) @@ -88,7 +92,7 @@ def testWhenReportSolid_shouldPrintAFullReportOfThisSolidAndItsSurfaces(self): with self.subTest(["using2DLogger", "using3DLogger"][keep3D]): self._setUp(keep3D=keep3D) - with patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout: self.stats.report(solidLabel="cube") reportLines = mock_stdout.getvalue().splitlines() @@ -99,7 +103,7 @@ def testWhenReport_shouldPrintAFullReportOfAllSolidsAndTheirSurfaces(self): with self.subTest(["using2DLogger", "using3DLogger"][keep3D]): self._setUp(keep3D=keep3D) - with patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout: self.stats.report() reportLines = mock_stdout.getvalue().splitlines() @@ -124,7 +128,7 @@ def testWhenReportNonExistingSolid_shouldWarnAndIgnore(self): @staticmethod def makeTestCubeLogger(keep3D=True, sourceSolidLabel=None, noViews=False) -> EnergyLogger: - """ We log a few points taken from a unit cube centered at the origin where a single photon + """We log a few points taken from a unit cube centered at the origin where a single photon was propagated. We log one point entering front surface at z=0 with weight=1, then 8 points of weight 0.1 centered from z=0.1 to z=0.8, and one point exiting back surface at z=1 with a remaining weight of 0.2, so it correctly adds up to 1. @@ -138,7 +142,7 @@ def makeTestCubeLogger(keep3D=True, sourceSolidLabel=None, noViews=False) -> Ene frontInteraction = InteractionKey("cube", "cube_front") backInteraction = InteractionKey("cube", "cube_back") for i in range(1, 9): - logger.logDataPoint(0.1, Vector(0, 0, 0.1*i), solidInteraction) + logger.logDataPoint(0.1, Vector(0, 0, 0.1 * i), solidInteraction) logger.logDataPoint(-1, Vector(0, 0, 0), frontInteraction) logger.logDataPoint(0.2, Vector(0, 0, 1), backInteraction) logger.info["photonCount"] = 1 diff --git a/pytissueoptics/rayscattering/tests/testFresnel.py b/pytissueoptics/rayscattering/tests/testFresnel.py index c044f94a..78120f75 100644 --- a/pytissueoptics/rayscattering/tests/testFresnel.py +++ b/pytissueoptics/rayscattering/tests/testFresnel.py @@ -66,8 +66,8 @@ def testGivenPerpendicularIncidence_shouldHaveReflectionCoefficient(self): self.fresnelIntersect.compute(rayPerpendicular, intersection) - R = (n2-n1)/(n2+n1) - self.assertEqual(R ** 2, self.fresnelIntersect._getReflectionCoefficient()) + R = (n2 - n1) / (n2 + n1) + self.assertEqual(R**2, self.fresnelIntersect._getReflectionCoefficient()) def testIfGoingInside_shouldSetNextMaterialAsMaterialInsideSurface(self): n1, n2 = 1.0, 1.5 @@ -90,5 +90,4 @@ def testIfGoingOutside_shouldSetNextMaterialAsMaterialOutsideSurface(self): def _createIntersection(n1=1.0, n2=1.5, normal=Vector(0, 0, 1)): insideEnvironment = Environment(ScatteringMaterial(n=n2)) outsideEnvironment = Environment(ScatteringMaterial(n=n1)) - return Intersection(10, Vector(0, 0, 0), None, normal, - insideEnvironment, outsideEnvironment, distanceLeft=2) \ No newline at end of file + return Intersection(10, Vector(0, 0, 0), None, normal, insideEnvironment, outsideEnvironment, distanceLeft=2) diff --git a/pytissueoptics/rayscattering/tests/testPhoton.py b/pytissueoptics/rayscattering/tests/testPhoton.py index 66671b70..592701ac 100644 --- a/pytissueoptics/rayscattering/tests/testPhoton.py +++ b/pytissueoptics/rayscattering/tests/testPhoton.py @@ -62,8 +62,9 @@ def testWhenRefract_shouldOrientPhotonTowardsFresnelRefractionAngle(self): incidencePlane = self.photon.direction.cross(surfaceNormal) incidencePlane.normalize() refractionDeflection = math.pi / 20 - fresnelIntersection = FresnelIntersection(Environment(ScatteringMaterial()), incidencePlane, isReflected=False, - angleDeflection=refractionDeflection) + fresnelIntersection = FresnelIntersection( + Environment(ScatteringMaterial()), incidencePlane, isReflected=False, angleDeflection=refractionDeflection + ) self.photon.refract(fresnelIntersection) @@ -81,7 +82,7 @@ def testWhenScatter_shouldDecreasePhotonWeight(self): def testWhenScatter_shouldScatterPhotonDirection(self): material = ScatteringMaterial(mu_s=2, mu_a=1) - theta, phi = math.pi/5, math.pi + theta, phi = math.pi / 5, math.pi material.getScatteringAngles = lambda: (theta, phi) self.photon.setContext(Environment(material)) self.photon._er = Vector(-1, 0, 0) @@ -98,8 +99,9 @@ def testWhenReflect_shouldOrientPhotonTowardsFresnelReflectionAngle(self): incidencePlane = self.photon.direction.cross(surfaceNormal) incidencePlane.normalize() reflectionDeflection = 2 * incidenceAngle - math.pi - fresnelIntersection = FresnelIntersection(Environment(ScatteringMaterial()), incidencePlane, isReflected=True, - angleDeflection=reflectionDeflection) + fresnelIntersection = FresnelIntersection( + Environment(ScatteringMaterial()), incidencePlane, isReflected=True, angleDeflection=reflectionDeflection + ) self.photon.reflect(fresnelIntersection) reflectionAngle = incidenceAngle - reflectionDeflection @@ -140,8 +142,9 @@ def testWhenStepTooCloseToIntersection_shouldMovePhotonToIntersection(self): distance = 8 rayLength = distance - 0.5 * EPS intersectionFinder = self._createIntersectionFinder(distance, rayLength=rayLength) - self.photon.setContext(Environment(ScatteringMaterial(), self.solidOutside), - intersectionFinder=intersectionFinder) + self.photon.setContext( + Environment(ScatteringMaterial(), self.solidOutside), intersectionFinder=intersectionFinder + ) self.photon.step(rayLength) @@ -151,7 +154,9 @@ def testWhenStepTooCloseToIntersection_shouldMovePhotonToIntersection(self): def testWhenStepWithNoIntersection_shouldMovePhotonAcrossStepDistanceAndScatter(self): noIntersectionFinder = mock(IntersectionFinder) when(noIntersectionFinder).findIntersection(...).thenReturn(None) - self.photon.setContext(Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)), intersectionFinder=noIntersectionFinder) + self.photon.setContext( + Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)), intersectionFinder=noIntersectionFinder + ) stepDistance = 10 self.photon.step(distance=stepDistance) @@ -163,8 +168,9 @@ def testWhenStepWithNoIntersection_shouldMovePhotonAcrossStepDistanceAndScatter( def testWhenStepWithNoIntersection_shouldReturnADistanceLeftOfZero(self): noIntersectionFinder = mock(IntersectionFinder) when(noIntersectionFinder).findIntersection(...).thenReturn(None) - self.photon.setContext(Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)), - intersectionFinder=noIntersectionFinder) + self.photon.setContext( + Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)), intersectionFinder=noIntersectionFinder + ) distanceLeft = self.photon.step() @@ -188,8 +194,11 @@ def testWhenStepWithReflectingIntersection_shouldMovePhotonToIntersection(self): intersectionFinder = self._createIntersectionFinder(intersectionDistance, rayLength=totalDistance) environment = self._createEnvironment(scatteringDistance=totalDistance) environment.solid = self.solidOutside - self.photon.setContext(environment, intersectionFinder=intersectionFinder, - fresnelIntersect=self._createFresnelIntersectionFactory(isReflected=True)) + self.photon.setContext( + environment, + intersectionFinder=intersectionFinder, + fresnelIntersect=self._createFresnelIntersectionFactory(isReflected=True), + ) self.photon.step(totalDistance) @@ -202,8 +211,11 @@ def testWhenStepWithReflectingIntersection_shouldReturnDistanceLeft(self): intersectionFinder = self._createIntersectionFinder(intersectionDistance, rayLength=totalDistance) environment = self._createEnvironment(scatteringDistance=totalDistance) environment.solid = self.solidOutside - self.photon.setContext(environment, intersectionFinder=intersectionFinder, - fresnelIntersect=self._createFresnelIntersectionFactory(isReflected=True)) + self.photon.setContext( + environment, + intersectionFinder=intersectionFinder, + fresnelIntersect=self._createFresnelIntersectionFactory(isReflected=True), + ) distanceLeft = self.photon.step(totalDistance) @@ -212,8 +224,11 @@ def testWhenStepWithReflectingIntersection_shouldReturnDistanceLeft(self): def testWhenStepWithRefractingIntersection_shouldUpdatePhotonMaterialToNextMaterial(self): nextMaterial = ScatteringMaterial(mu_s=2, mu_a=1, g=0.8) - self.photon.setContext(Environment(ScatteringMaterial()), intersectionFinder=self._createIntersectionFinder(), - fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False)) + self.photon.setContext( + Environment(ScatteringMaterial()), + intersectionFinder=self._createIntersectionFinder(), + fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False), + ) self.photon.step() @@ -224,9 +239,11 @@ def testWhenStepWithRefractingIntersection_shouldMovePhotonToIntersection(self): intersectionDistance = 8 material = ScatteringMaterial(mu_s=2, mu_a=1, g=0.8) nextMaterial = ScatteringMaterial(mu_s=3, mu_a=1, g=0.8) - self.photon.setContext(Environment(material, self.solidOutside), - intersectionFinder=self._createIntersectionFinder(intersectionDistance, scatteringDistance), - fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False)) + self.photon.setContext( + Environment(material, self.solidOutside), + intersectionFinder=self._createIntersectionFinder(intersectionDistance, scatteringDistance), + fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False), + ) self.photon.step(scatteringDistance) @@ -238,8 +255,11 @@ def testWhenStepWithRefractingIntersection_shouldReturnDistanceLeftInNextMateria intersectionDistance = 8 material = ScatteringMaterial(mu_s=2, mu_a=1, g=0.8) nextMaterial = ScatteringMaterial(mu_s=3, mu_a=1, g=0.8) - self.photon.setContext(Environment(material), intersectionFinder=self._createIntersectionFinder(intersectionDistance, scatteringDistance), - fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False)) + self.photon.setContext( + Environment(material), + intersectionFinder=self._createIntersectionFinder(intersectionDistance, scatteringDistance), + fresnelIntersect=self._createFresnelIntersectionFactory(Environment(nextMaterial), isReflected=False), + ) distanceLeft = self.photon.step(scatteringDistance) @@ -251,8 +271,11 @@ def testWhenStepWithRefractingIntersectionToVacuum_shouldReturnInfiniteDistanceL intersectionDistance = 8 environment = Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)) nextEnvironment = Environment(ScatteringMaterial(mu_s=0, mu_a=0, g=0)) - self.photon.setContext(environment, intersectionFinder=self._createIntersectionFinder(intersectionDistance, initialScatteringDistance), - fresnelIntersect=self._createFresnelIntersectionFactory(nextEnvironment, isReflected=False)) + self.photon.setContext( + environment, + intersectionFinder=self._createIntersectionFinder(intersectionDistance, initialScatteringDistance), + fresnelIntersect=self._createFresnelIntersectionFactory(nextEnvironment, isReflected=False), + ) distanceLeft = self.photon.step(initialScatteringDistance) @@ -263,8 +286,11 @@ def testWhenStepWithRefractingIntersectionFromVacuum_shouldReturnZeroDistanceLef intersectionDistance = 8 environment = Environment(ScatteringMaterial(mu_s=0, mu_a=0, g=0)) nextEnvironment = Environment(ScatteringMaterial(mu_s=2, mu_a=1, g=0.8)) - self.photon.setContext(environment, intersectionFinder=self._createIntersectionFinder(intersectionDistance, initialScatteringDistance), - fresnelIntersect=self._createFresnelIntersectionFactory(nextEnvironment, isReflected=False)) + self.photon.setContext( + environment, + intersectionFinder=self._createIntersectionFinder(intersectionDistance, initialScatteringDistance), + fresnelIntersect=self._createFresnelIntersectionFactory(nextEnvironment, isReflected=False), + ) distanceLeft = self.photon.step(initialScatteringDistance) @@ -299,8 +325,9 @@ def givenALogger_whenSteppingInsideASolidAt(self, distance): enteringSurfaceNormal = self.INITIAL_DIRECTION.copy() enteringSurfaceNormal.multiply(-1) intersectionFinder = self._createIntersectionFinder(distance, normal=enteringSurfaceNormal) - self.photon.setContext(Environment(ScatteringMaterial(), self.solidOutside), - intersectionFinder=intersectionFinder, logger=logger) + self.photon.setContext( + Environment(ScatteringMaterial(), self.solidOutside), intersectionFinder=intersectionFinder, logger=logger + ) self.photon.step(distance + 2) return logger @@ -348,8 +375,9 @@ def givenALoggerAndNoSolidOutside_whenSteppingInsideASolidAt(self, distance): enteringSurfaceNormal = self.INITIAL_DIRECTION.copy() enteringSurfaceNormal.multiply(-1) intersectionFinder = self._createIntersectionFinder(distance, normal=enteringSurfaceNormal) - self.photon.setContext(Environment(ScatteringMaterial(), self.solidOutside), - intersectionFinder=intersectionFinder, logger=logger) + self.photon.setContext( + Environment(ScatteringMaterial(), self.solidOutside), intersectionFinder=intersectionFinder, logger=logger + ) self.photon.step(distance + 2) return logger @@ -369,14 +397,14 @@ def testWhenRouletteWithWeightAboveThreshold_shouldIgnoreRoulette(self): self.assertTrue(self.photon._weight == 1.1 * WEIGHT_THRESHOLD) - @patch('random.random', return_value=0.11) + @patch("random.random", return_value=0.11) def testWhenRouletteWithWeightBelowThresholdAndNotLucky_shouldKillPhoton(self, _): self.photon._weight = 0.9 * WEIGHT_THRESHOLD self.photon.roulette() self.assertFalse(self.photon.isAlive) - @patch('random.random', return_value=0.09) + @patch("random.random", return_value=0.09) def testWhenRouletteWithWeightBelowThresholdAndLucky_shouldRescaleWeightToPreserveStatistics(self, _): rouletteChance = 0.1 # defined in Photon.roulette() self.photon._weight = 0.9 * WEIGHT_THRESHOLD @@ -409,10 +437,16 @@ def _createIntersectionFinder(self, intersectionDistance=10, rayLength=None, nor position = self.INITIAL_POSITION + self.INITIAL_DIRECTION * intersectionDistance polygon = mock(Polygon) polygon.vertices = [] - intersection = Intersection(intersectionDistance, position=position, polygon=polygon, normal=normal, - insideEnvironment=Environment(ScatteringMaterial(), self.solidInside), - outsideEnvironment=Environment(ScatteringMaterial(), self.solidOutside), - surfaceLabel=self.SURFACE_LABEL, distanceLeft=rayLength-intersectionDistance) + intersection = Intersection( + intersectionDistance, + position=position, + polygon=polygon, + normal=normal, + insideEnvironment=Environment(ScatteringMaterial(), self.solidInside), + outsideEnvironment=Environment(ScatteringMaterial(), self.solidOutside), + surfaceLabel=self.SURFACE_LABEL, + distanceLeft=rayLength - intersectionDistance, + ) intersectionFinder = mock(IntersectionFinder) when(intersectionFinder).findIntersection(...).thenReturn(intersection) return intersectionFinder @@ -426,10 +460,12 @@ def _createEnvironment(scatteringDistance=1, phi=0.1, theta=0.2, albedo=0.1): return Environment(material) @staticmethod - def _createFresnelIntersectionFactory(nextEnvironment=Environment(ScatteringMaterial()), - isReflected=True, angleDeflection=0.1): - fresnelIntersection = FresnelIntersection(nextEnvironment, Vector(), isReflected=isReflected, - angleDeflection=angleDeflection) + def _createFresnelIntersectionFactory( + nextEnvironment=Environment(ScatteringMaterial()), isReflected=True, angleDeflection=0.1 + ): + fresnelIntersection = FresnelIntersection( + nextEnvironment, Vector(), isReflected=isReflected, angleDeflection=angleDeflection + ) fresnelIntersect = mock(FresnelIntersect) when(fresnelIntersect).compute(...).thenReturn(fresnelIntersection) return fresnelIntersect diff --git a/pytissueoptics/rayscattering/tests/testScatteringScene.py b/pytissueoptics/rayscattering/tests/testScatteringScene.py index 683d0004..6c758fa5 100644 --- a/pytissueoptics/rayscattering/tests/testScatteringScene.py +++ b/pytissueoptics/rayscattering/tests/testScatteringScene.py @@ -11,8 +11,8 @@ def patchMayaviShow(func): - for module in ['show', 'gcf', 'figure', 'clf', 'triangular_mesh']: - func = patch('mayavi.mlab.' + module)(func) + for module in ["show", "gcf", "figure", "clf", "triangular_mesh"]: + func = patch("mayavi.mlab." + module)(func) return func diff --git a/pytissueoptics/rayscattering/tests/testSource.py b/pytissueoptics/rayscattering/tests/testSource.py index ad0b08d0..1a9f03e7 100644 --- a/pytissueoptics/rayscattering/tests/testSource.py +++ b/pytissueoptics/rayscattering/tests/testSource.py @@ -33,14 +33,14 @@ def testWhenPropagate_shouldPropagateAllPhotons(self): def testWhenPropagate_shouldUpdatePhotonCountInLogger(self): logger = EnergyLogger(mock(ScatteringScene), views=[]) self.source.propagate(self._createTissue(), logger=logger, showProgress=False) - self.assertEqual(logger.info['photonCount'], 1) + self.assertEqual(logger.info["photonCount"], 1) - logger.info['photonCount'] = 10 + logger.info["photonCount"] = 10 self.source.propagate(self._createTissue(), logger=logger, showProgress=False) - self.assertEqual(logger.info['photonCount'], 10+1) + self.assertEqual(logger.info["photonCount"], 10 + 1) def testWhenPropagate_shouldSetSourceSolidLabelInLogger(self): - sourceSolidLabel = 'the source solid' + sourceSolidLabel = "the source solid" solid = mock(Solid) when(solid).getLabel().thenReturn(sourceSolidLabel) self.SOURCE_ENV = Environment(ScatteringMaterial(), solid) @@ -48,21 +48,21 @@ def testWhenPropagate_shouldSetSourceSolidLabelInLogger(self): self.source.propagate(self._createTissue(), logger=logger, showProgress=False) - self.assertEqual(logger.info['sourceSolidLabel'], sourceSolidLabel) + self.assertEqual(logger.info["sourceSolidLabel"], sourceSolidLabel) def testGivenSourceNotInASolid_whenPropagate_shouldSetSourceSolidLabelInLoggerAsNone(self): logger = EnergyLogger(mock(ScatteringScene), views=[]) self.source.propagate(self._createTissue(), logger=logger, showProgress=False) - self.assertEqual(logger.info['sourceSolidLabel'], None) + self.assertEqual(logger.info["sourceSolidLabel"], None) def testWhenPropagate_shouldSetSourceHashInLogger(self): logger = EnergyLogger(mock(ScatteringScene), views=[]) self.source.propagate(self._createTissue(), logger=logger, showProgress=False) - self.assertEqual(logger.info['sourceHash'], hash(self.source)) + self.assertEqual(logger.info["sourceHash"], hash(self.source)) def testGivenLoggerWithFilePath_whenPropagate_shouldSaveLogger(self): with tempfile.TemporaryDirectory() as tempdir: - filepath = os.path.join(tempdir, 'test.log') + filepath = os.path.join(tempdir, "test.log") with self.assertWarns(UserWarning): logger = EnergyLogger(mock(ScatteringScene), views=[], filepath=filepath) @@ -72,7 +72,7 @@ def testGivenLoggerWithFilePath_whenPropagate_shouldSaveLogger(self): def testGivenLoggerUsedOnADifferentSource_whenPropagate_shouldWarn(self): logger = EnergyLogger(mock(ScatteringScene), views=[]) - logger.info['sourceHash'] = 1234 + logger.info["sourceHash"] = 1234 with self.assertWarns(UserWarning): self.source.propagate(self._createTissue(), logger=logger, showProgress=False) @@ -108,21 +108,23 @@ def getInitialPositionsAndDirections(self): @property def _hashComponents(self) -> tuple: - return self._position, + return (self._position,) class TestPencilSource(unittest.TestCase): def testShouldHavePhotonsAllPointingInTheSourceDirection(self): sourceDirection = Vector(1, 0, 0) - pencilSource = PencilPointSource(position=Vector(), direction=sourceDirection, N=10, - useHardwareAcceleration=False) + pencilSource = PencilPointSource( + position=Vector(), direction=sourceDirection, N=10, useHardwareAcceleration=False + ) for photon in pencilSource.photons: self.assertEqual(sourceDirection, photon.direction) def testShouldHavePhotonsAllPositionedAtTheSourcePosition(self): sourcePosition = Vector(3, 3, 0) - pencilSource = PencilPointSource(position=sourcePosition, direction=Vector(0, 0, 1), N=10, - useHardwareAcceleration=False) + pencilSource = PencilPointSource( + position=sourcePosition, direction=Vector(0, 0, 1), N=10, useHardwareAcceleration=False + ) for photon in pencilSource.photons: self.assertEqual(sourcePosition, photon.position) @@ -148,8 +150,9 @@ def testGivenTwoIsotropicSourcesThatDifferInPosition_shouldNotHaveSameHash(self) class TestDirectionalSource(unittest.TestCase): def testShouldHavePhotonsAllPointingInTheSourceDirection(self): sourceDirection = Vector(1, 0, 0) - directionalSource = DirectionalSource(position=Vector(), direction=sourceDirection, diameter=1, N=10, - useHardwareAcceleration=False) + directionalSource = DirectionalSource( + position=Vector(), direction=sourceDirection, diameter=1, N=10, useHardwareAcceleration=False + ) for photon in directionalSource.photons: self.assertEqual(sourceDirection, photon.direction) @@ -157,8 +160,13 @@ def testShouldHavePhotonsUniformlyPositionedInsideTheSourceDiameter(self): np.random.seed(0) sourcePosition = Vector(3, 3, 0) sourceDiameter = 2 - directionalSourceTowardsY = DirectionalSource(position=sourcePosition, direction=Vector(0, 1, 0), - diameter=sourceDiameter, N=10, useHardwareAcceleration=False) + directionalSourceTowardsY = DirectionalSource( + position=sourcePosition, + direction=Vector(0, 1, 0), + diameter=sourceDiameter, + N=10, + useHardwareAcceleration=False, + ) for photon in directionalSourceTowardsY.photons: self.assertTrue(np.isclose(photon.position.y, sourcePosition.y)) self.assertTrue(photon.position.x <= sourcePosition.x + sourceDiameter / 2) @@ -185,8 +193,9 @@ def testShouldHavePhotonsUniformlyPositionedInsideTheSourceDiameter(self): np.random.seed(0) sourcePosition = Vector(3, 3, 0) sourceDiameter = 2 - divergentSourceTowardsY = DivergentSource(sourcePosition, Vector(0, 1, 0), sourceDiameter, - divergence=0.2, N=10, useHardwareAcceleration=False) + divergentSourceTowardsY = DivergentSource( + sourcePosition, Vector(0, 1, 0), sourceDiameter, divergence=0.2, N=10, useHardwareAcceleration=False + ) for photon in divergentSourceTowardsY.photons: self.assertTrue(np.isclose(photon.position.y, sourcePosition.y)) self.assertTrue(photon.position.x <= sourcePosition.x + sourceDiameter / 2) @@ -199,36 +208,47 @@ def testShouldHavePhotonsUniformlyPositionedInsideTheSourceDiameter(self): def testGivenNoDivergence_shouldHavePhotonsAllPointingInTheSourceDirection(self): sourceDirection = Vector(1, 0, 0) - divergentSource = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, divergence=0, N=10, - useHardwareAcceleration=False) + divergentSource = DivergentSource( + position=Vector(), direction=sourceDirection, diameter=1, divergence=0, N=10, useHardwareAcceleration=False + ) for photon in divergentSource.photons: self.assertEqual(sourceDirection, photon.direction) def testGivenDivergence_shouldHavePhotonsPointingInDifferentDirectionsAroundTheSourceDirection(self): sourceDirection = Vector(1, 0, 0) - divergence = np.pi/4 - divergentSource = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, - divergence=divergence, N=10, useHardwareAcceleration=False) - minDot = np.cos(divergence/2) + divergence = np.pi / 4 + divergentSource = DivergentSource( + position=Vector(), + direction=sourceDirection, + diameter=1, + divergence=divergence, + N=10, + useHardwareAcceleration=False, + ) + minDot = np.cos(divergence / 2) for photon in divergentSource.photons: dot = photon.direction.dot(sourceDirection) self.assertTrue(dot >= minDot) def testGivenTwoDivergentSourcesWithSamePropertiesExceptPhotonCount_shouldHaveSameHash(self): sourceDirection = Vector(1, 0, 0) - divergence = np.pi/4 - divergentSource1 = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, - divergence=divergence, N=1) - divergentSource2 = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, - divergence=divergence, N=2) + divergence = np.pi / 4 + divergentSource1 = DivergentSource( + position=Vector(), direction=sourceDirection, diameter=1, divergence=divergence, N=1 + ) + divergentSource2 = DivergentSource( + position=Vector(), direction=sourceDirection, diameter=1, divergence=divergence, N=2 + ) self.assertEqual(hash(divergentSource1), hash(divergentSource2)) def testGivenTwoDivergentSourcesThatDifferInDivergence_shouldNotHaveSameHash(self): sourceDirection = Vector(1, 0, 0) - divergence1 = np.pi/4 - divergence2 = np.pi/2 - divergentSource1 = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, - divergence=divergence1, N=1) - divergentSource2 = DivergentSource(position=Vector(), direction=sourceDirection, diameter=1, - divergence=divergence2, N=1) + divergence1 = np.pi / 4 + divergence2 = np.pi / 2 + divergentSource1 = DivergentSource( + position=Vector(), direction=sourceDirection, diameter=1, divergence=divergence1, N=1 + ) + divergentSource2 = DivergentSource( + position=Vector(), direction=sourceDirection, diameter=1, divergence=divergence2, N=1 + ) self.assertNotEqual(hash(divergentSource1), hash(divergentSource2)) diff --git a/pytissueoptics/rayscattering/tests/testSourceAccelerated.py b/pytissueoptics/rayscattering/tests/testSourceAccelerated.py index 73b4b819..dc0656f4 100644 --- a/pytissueoptics/rayscattering/tests/testSourceAccelerated.py +++ b/pytissueoptics/rayscattering/tests/testSourceAccelerated.py @@ -18,6 +18,7 @@ def wrapper(*args, **kwargs): with tempfile.TemporaryDirectory() as tempDir: IPPTable.TABLE_PATH = os.path.join(tempDir, "ipp.json") func(*args, **kwargs) + return wrapper @@ -31,14 +32,14 @@ def setUp(self): when(self.photons).setContext(...).thenReturn() when(self.photons).propagate(...).thenReturn() - @patch('pytissueoptics.rayscattering.source.CLPhotons') + @patch("pytissueoptics.rayscattering.source.CLPhotons") def testShouldLoadPhotons(self, _CLPhotonsClassMock): _CLPhotonsClassMock.return_value = self.photons source = SinglePhotonSourceAccelerated() self.assertIsNotNone(source.photons) @tempTablePath - @patch('pytissueoptics.rayscattering.source.CLPhotons') + @patch("pytissueoptics.rayscattering.source.CLPhotons") def testWhenPropagateNewExperiment_shouldWarnThatIPPWillBeEstimated(self, _CLPhotonsClassMock): _CLPhotonsClassMock.return_value = self.photons scene = self._createMockScene() @@ -49,7 +50,7 @@ def testWhenPropagateNewExperiment_shouldWarnThatIPPWillBeEstimated(self, _CLPho source.propagate(scene, logger, showProgress=False) @tempTablePath - @patch('pytissueoptics.rayscattering.source.CLPhotons') + @patch("pytissueoptics.rayscattering.source.CLPhotons") def testWhenPropagate_shouldSetCorrectPhotonContext(self, _CLPhotonsClassMock): _CLPhotonsClassMock.return_value = self.photons scene = self._createMockScene() @@ -62,7 +63,7 @@ def testWhenPropagate_shouldSetCorrectPhotonContext(self, _CLPhotonsClassMock): verify(self.photons).setContext(scene, self.SOURCE_ENV, logger=logger) @tempTablePath - @patch('pytissueoptics.rayscattering.source.CLPhotons') + @patch("pytissueoptics.rayscattering.source.CLPhotons") def testGivenExperimentInIPPTable_whenPropagate_shouldUseIPPFromTable(self, _CLPhotonsClassMock): _CLPhotonsClassMock.return_value = self.photons scene = self._createMockScene() @@ -78,8 +79,8 @@ def testGivenExperimentInIPPTable_whenPropagate_shouldUseIPPFromTable(self, _CLP verify(self.photons).propagate(IPP=IPP, verbose=False) @tempTablePath - @patch('pytissueoptics.rayscattering.source.CLPhotons') - @patch('pytissueoptics.rayscattering.source.Logger') + @patch("pytissueoptics.rayscattering.source.CLPhotons") + @patch("pytissueoptics.rayscattering.source.Logger") def testGivenExperimentNotInIPPTable_whenPropagate_shouldEstimateIPP(self, _LoggerClassMock, _CLPhotonsClassMock): _CLPhotonsClassMock.return_value = self.photons source = SinglePhotonSourceAccelerated() @@ -127,4 +128,4 @@ def getInitialPositionsAndDirections(self): @property def _hashComponents(self) -> tuple: - return self._position, + return (self._position,) diff --git a/pytissueoptics/rayscattering/tests/testUtils.py b/pytissueoptics/rayscattering/tests/testUtils.py index a452fdf8..75c9239e 100644 --- a/pytissueoptics/rayscattering/tests/testUtils.py +++ b/pytissueoptics/rayscattering/tests/testUtils.py @@ -21,17 +21,17 @@ def testGivenArray_shouldReturnLogNormalizedArray(self): class TestLabelsEqual(unittest.TestCase): def testGivenEqualLabels_shouldReturnTrue(self): - self.assertTrue(labelsEqual('a', 'a')) + self.assertTrue(labelsEqual("a", "a")) def testGivenDifferentLabels_shouldReturnFalse(self): - self.assertFalse(labelsEqual('a', 'b')) + self.assertFalse(labelsEqual("a", "b")) def testGivenDifferentCaseLabels_shouldReturnTrue(self): - self.assertTrue(labelsEqual('a', 'A')) + self.assertTrue(labelsEqual("a", "A")) def testGivenOneLabelNone_shouldReturnFalse(self): - self.assertFalse(labelsEqual('a', None)) - self.assertFalse(labelsEqual(None, 'a')) + self.assertFalse(labelsEqual("a", None)) + self.assertFalse(labelsEqual(None, "a")) def testGivenBothLabelsNone_shouldReturnTrue(self): self.assertTrue(labelsEqual(None, None)) @@ -39,13 +39,13 @@ def testGivenBothLabelsNone_shouldReturnTrue(self): class TestLabelContained(unittest.TestCase): def testGivenLabelContained_shouldReturnTrue(self): - self.assertTrue(labelContained('a', ['a', 'b', 'c'])) + self.assertTrue(labelContained("a", ["a", "b", "c"])) def testGivenLabelContainedDifferentCase_shouldReturnTrue(self): - self.assertTrue(labelContained('a', ['A', 'b', 'c'])) + self.assertTrue(labelContained("a", ["A", "b", "c"])) def testGivenLabelNotContained_shouldReturnFalse(self): - self.assertFalse(labelContained('a', ['b', 'c'])) + self.assertFalse(labelContained("a", ["b", "c"])) def testGivenLabelNone_shouldReturnFalse(self): - self.assertFalse(labelContained(None, ['a', 'b', 'c'])) + self.assertFalse(labelContained(None, ["a", "b", "c"])) diff --git a/pytissueoptics/rayscattering/utils.py b/pytissueoptics/rayscattering/utils.py index c86e1b1a..1037ac58 100644 --- a/pytissueoptics/rayscattering/utils.py +++ b/pytissueoptics/rayscattering/utils.py @@ -3,7 +3,7 @@ import numpy as np -warnings.formatwarning = lambda msg, *args, **kwargs: f'{msg}\n' +warnings.formatwarning = lambda msg, *args, **kwargs: f"{msg}\n" warn = warnings.warn diff --git a/pytissueoptics/scene/__init__.py b/pytissueoptics/scene/__init__.py index 6e3c82ae..4962affa 100644 --- a/pytissueoptics/scene/__init__.py +++ b/pytissueoptics/scene/__init__.py @@ -4,19 +4,38 @@ from .material import RefractiveMaterial from .scene import Scene from .solids import ( - Cone, - Cube, - Cuboid, - Cylinder, - Ellipsoid, - PlanoConcaveLens, - PlanoConvexLens, - Sphere, - SymmetricLens, - ThickLens, + Cone, + Cube, + Cuboid, + Cylinder, + Ellipsoid, + PlanoConcaveLens, + PlanoConvexLens, + Sphere, + SymmetricLens, + ThickLens, ) from .viewer import MAYAVI_AVAILABLE, MayaviViewer, ViewPointStyle -__all__ = ["Cuboid", "Cube", "Sphere", "Ellipsoid", "Cylinder", "Cone", "Vector", "Scene", "Loader", - "loadSolid", "MayaviViewer", "MAYAVI_AVAILABLE", "ViewPointStyle", "Logger", "InteractionKey", - "RefractiveMaterial", "ThickLens", "SymmetricLens", "PlanoConvexLens", "PlanoConcaveLens"] +__all__ = [ + "Cuboid", + "Cube", + "Sphere", + "Ellipsoid", + "Cylinder", + "Cone", + "Vector", + "Scene", + "Loader", + "loadSolid", + "MayaviViewer", + "MAYAVI_AVAILABLE", + "ViewPointStyle", + "Logger", + "InteractionKey", + "RefractiveMaterial", + "ThickLens", + "SymmetricLens", + "PlanoConvexLens", + "PlanoConcaveLens", +] diff --git a/pytissueoptics/scene/geometry/__init__.py b/pytissueoptics/scene/geometry/__init__.py index 568c5af1..2c886bb1 100644 --- a/pytissueoptics/scene/geometry/__init__.py +++ b/pytissueoptics/scene/geometry/__init__.py @@ -18,4 +18,4 @@ "Vertex", "Environment", INTERFACE_KEY, -] \ No newline at end of file +] diff --git a/pytissueoptics/scene/geometry/bbox.py b/pytissueoptics/scene/geometry/bbox.py index a557871f..d6bcffcc 100644 --- a/pytissueoptics/scene/geometry/bbox.py +++ b/pytissueoptics/scene/geometry/bbox.py @@ -8,8 +8,8 @@ class BoundingBox: - _AXIS_KEYS = ['x', 'y', 'z'] - _LIMIT_KEYS = ['min', 'max'] + _AXIS_KEYS = ["x", "y", "z"] + _LIMIT_KEYS = ["min", "max"] def __init__(self, xLim: List[float], yLim: List[float], zLim: List[float], validate=True): self._xLim = xLim @@ -22,21 +22,23 @@ def __init__(self, xLim: List[float], yLim: List[float], zLim: List[float], vali def __repr__(self) -> str: return f":(xLim={self._xLim}, yLim={self._yLim}, zLim={self._zLim})" - def __eq__(self, other: 'BoundingBox') -> bool: + def __eq__(self, other: "BoundingBox") -> bool: if self._xLim == other._xLim and self._yLim == other._yLim and self._zLim == other._zLim: return True else: return False def copy(self): - return BoundingBox([self._xLim[0], self._xLim[1]], [self._yLim[0], self._yLim[1]], [self._zLim[0], self._zLim[1]]) + return BoundingBox( + [self._xLim[0], self._xLim[1]], [self._yLim[0], self._yLim[1]], [self._zLim[0], self._zLim[1]] + ) def _checkIfCoherent(self): if not (self.xMax >= self.xMin and self.yMax >= self.yMin and self.zMax >= self.zMin): raise ValueError("Maximum limit value cannot be lower than minimum limit value.") @classmethod - def fromVertices(cls, vertices: List[Vertex]) -> 'BoundingBox': + def fromVertices(cls, vertices: List[Vertex]) -> "BoundingBox": vertexIter = range(len(vertices)) x = sorted([vertices[i].x for i in vertexIter]) y = sorted([vertices[i].y for i in vertexIter]) @@ -47,7 +49,7 @@ def fromVertices(cls, vertices: List[Vertex]) -> 'BoundingBox': return BoundingBox(xLim, yLim, zLim, validate=False) @classmethod - def fromPolygons(cls, polygons: List['Polygon']) -> 'BoundingBox': + def fromPolygons(cls, polygons: List["Polygon"]) -> "BoundingBox": bbox = None for polygon in polygons: if bbox is not None: @@ -82,8 +84,11 @@ def zMax(self) -> float: @property def center(self) -> Vector: - return Vector(self.xMin + (self.xMax - self.xMin) / 2, self.yMin + (self.yMax - self.yMin) / 2, - self.zMin + (self.zMax - self.zMin) / 2) + return Vector( + self.xMin + (self.xMax - self.xMin) / 2, + self.yMin + (self.yMax - self.yMin) / 2, + self.zMin + (self.zMax - self.zMin) / 2, + ) @property def xLim(self) -> List[float]: @@ -146,7 +151,7 @@ def contains(self, point: Vector): else: return False - def extendTo(self, other: 'BoundingBox'): + def extendTo(self, other: "BoundingBox"): if other.xMin < self.xMin: self._xLim[0] = other.xMin if other.xMax > self.xMax: @@ -160,7 +165,7 @@ def extendTo(self, other: 'BoundingBox'): if other.zMax > self.zMax: self._zLim[1] = other.zMax - def shrinkTo(self, other: 'BoundingBox'): + def shrinkTo(self, other: "BoundingBox"): if other.xMin > self.xMin: self._xLim[0] = other.xMin if other.xMax < self.xMax: @@ -174,7 +179,7 @@ def shrinkTo(self, other: 'BoundingBox'): if other.zMax < self.zMax: self._zLim[1] = other.zMax - def exclude(self, other: 'BoundingBox'): + def exclude(self, other: "BoundingBox"): if not self.intersects(other): return @@ -201,7 +206,7 @@ def exclude(self, other: 'BoundingBox'): def __getitem__(self, index: int) -> List[float]: return self._xyzLimits[index] - def intersects(self, other: 'BoundingBox') -> bool: + def intersects(self, other: "BoundingBox") -> bool: for axis in range(3): if other[axis][0] > self[axis][1] or other[axis][1] < self[axis][0]: return False diff --git a/pytissueoptics/scene/geometry/polygon.py b/pytissueoptics/scene/geometry/polygon.py index 3768c8f9..8466c931 100644 --- a/pytissueoptics/scene/geometry/polygon.py +++ b/pytissueoptics/scene/geometry/polygon.py @@ -14,7 +14,7 @@ @dataclass class Environment: material: ... - solid: 'Solid' = None + solid: "Solid" = None @property def solidLabel(self) -> str: @@ -31,9 +31,14 @@ class Polygon: for the normal to point towards the viewer. """ - def __init__(self, vertices: List[Vertex], normal: Vector = None, - insideEnvironment: Environment = None, outsideEnvironment: Environment = None, - surfaceLabel: str = None): + def __init__( + self, + vertices: List[Vertex], + normal: Vector = None, + insideEnvironment: Environment = None, + outsideEnvironment: Environment = None, + surfaceLabel: str = None, + ): self._vertices = vertices self._normal = normal self._insideEnvironment = insideEnvironment @@ -48,7 +53,7 @@ def __init__(self, vertices: List[Vertex], normal: Vector = None, self.resetBoundingBox() self.toSmooth = False - def __eq__(self, other: 'Polygon'): + def __eq__(self, other: "Polygon"): for vertex in self._vertices: if vertex not in other.vertices: return False diff --git a/pytissueoptics/scene/geometry/quad.py b/pytissueoptics/scene/geometry/quad.py index 79aa74e5..e20d1087 100644 --- a/pytissueoptics/scene/geometry/quad.py +++ b/pytissueoptics/scene/geometry/quad.py @@ -4,7 +4,19 @@ class Quad(Polygon): - def __init__(self, v1: Vertex, v2: Vertex, v3: Vertex, v4: Vertex, - insideEnvironment: Environment = None, outsideEnvironment: Environment = None, normal: Vector = None): - super().__init__(vertices=[v1, v2, v3, v4], - insideEnvironment=insideEnvironment, outsideEnvironment=outsideEnvironment, normal=normal) + def __init__( + self, + v1: Vertex, + v2: Vertex, + v3: Vertex, + v4: Vertex, + insideEnvironment: Environment = None, + outsideEnvironment: Environment = None, + normal: Vector = None, + ): + super().__init__( + vertices=[v1, v2, v3, v4], + insideEnvironment=insideEnvironment, + outsideEnvironment=outsideEnvironment, + normal=normal, + ) diff --git a/pytissueoptics/scene/geometry/rotation.py b/pytissueoptics/scene/geometry/rotation.py index 3efc396f..af7eeceb 100644 --- a/pytissueoptics/scene/geometry/rotation.py +++ b/pytissueoptics/scene/geometry/rotation.py @@ -1,4 +1,3 @@ - class Rotation: def __init__(self, xTheta: float = 0, yTheta: float = 0, zTheta: float = 0): self._xTheta = xTheta @@ -17,7 +16,7 @@ def yTheta(self): def zTheta(self): return self._zTheta - def add(self, other: 'Rotation'): + def add(self, other: "Rotation"): self._xTheta += other.xTheta self._yTheta += other.yTheta self._zTheta += other.zTheta diff --git a/pytissueoptics/scene/geometry/surfaceCollection.py b/pytissueoptics/scene/geometry/surfaceCollection.py index a81598f3..f9a0422c 100644 --- a/pytissueoptics/scene/geometry/surfaceCollection.py +++ b/pytissueoptics/scene/geometry/surfaceCollection.py @@ -76,13 +76,14 @@ def resetCentroids(self): for polygon in self.getPolygons(): polygon.resetCentroid() - def extend(self, other: 'SurfaceCollection'): + def extend(self, other: "SurfaceCollection"): assert not any(self._contains(surface) for surface in other.surfaceLabels) self._surfaces.update(other._surfaces) def _assertContains(self, surfaceLabel: str): - assert self._contains(surfaceLabel), f"Surface labeled {surfaceLabel} not found in available surfaces: " \ - f"{self.surfaceLabels}. " + assert self._contains(surfaceLabel), ( + f"Surface labeled {surfaceLabel} not found in available surfaces: {self.surfaceLabels}. " + ) def _contains(self, surfaceLabel: str) -> bool: return surfaceLabel in self.surfaceLabels diff --git a/pytissueoptics/scene/geometry/triangle.py b/pytissueoptics/scene/geometry/triangle.py index cc4edf47..29774aeb 100644 --- a/pytissueoptics/scene/geometry/triangle.py +++ b/pytissueoptics/scene/geometry/triangle.py @@ -4,7 +4,18 @@ class Triangle(Polygon): - def __init__(self, v1: Vertex, v2: Vertex, v3: Vertex, - insideEnvironment: Environment = None, outsideEnvironment: Environment = None, normal: Vector = None): - super().__init__(vertices=[v1, v2, v3], - insideEnvironment=insideEnvironment, outsideEnvironment=outsideEnvironment, normal=normal) + def __init__( + self, + v1: Vertex, + v2: Vertex, + v3: Vertex, + insideEnvironment: Environment = None, + outsideEnvironment: Environment = None, + normal: Vector = None, + ): + super().__init__( + vertices=[v1, v2, v3], + insideEnvironment=insideEnvironment, + outsideEnvironment=outsideEnvironment, + normal=normal, + ) diff --git a/pytissueoptics/scene/geometry/utils.py b/pytissueoptics/scene/geometry/utils.py index 8933fd6a..0d61d4c7 100644 --- a/pytissueoptics/scene/geometry/utils.py +++ b/pytissueoptics/scene/geometry/utils.py @@ -8,7 +8,7 @@ def rotateVerticesArray(verticesArray: np.ndarray, r: Rotation, inverse=False) -> np.ndarray: rotationMatrix = eulerRotationMatrix(r.xTheta, r.yTheta, r.zTheta, inverse=inverse) - return np.einsum('ij, kj->ki', rotationMatrix, verticesArray) + return np.einsum("ij, kj->ki", rotationMatrix, verticesArray) def eulerRotationMatrix(xTheta=0, yTheta=0, zTheta=0, inverse=False) -> np.ndarray: @@ -34,25 +34,19 @@ def eulerRotationMatrix(xTheta=0, yTheta=0, zTheta=0, inverse=False) -> np.ndarr def _zRotationMatrix(theta) -> np.ndarray: cosTheta = np.cos(theta * np.pi / 180) sinTheta = np.sin(theta * np.pi / 180) - return np.asarray([[cosTheta, -sinTheta, 0], - [sinTheta, cosTheta, 0], - [0, 0, 1]]) + return np.asarray([[cosTheta, -sinTheta, 0], [sinTheta, cosTheta, 0], [0, 0, 1]]) def _yRotationMatrix(theta) -> np.ndarray: cosTheta = np.cos(theta * np.pi / 180) sinTheta = np.sin(theta * np.pi / 180) - return np.asarray([[cosTheta, 0, sinTheta], - [0, 1, 0], - [-sinTheta, 0, cosTheta]]) + return np.asarray([[cosTheta, 0, sinTheta], [0, 1, 0], [-sinTheta, 0, cosTheta]]) def _xRotationMatrix(theta) -> np.ndarray: cosTheta = np.cos(theta * np.pi / 180) sinTheta = np.sin(theta * np.pi / 180) - return np.asarray([[1, 0, 0], - [0, cosTheta, -sinTheta], - [0, sinTheta, cosTheta]]) + return np.asarray([[1, 0, 0], [0, cosTheta, -sinTheta], [0, sinTheta, cosTheta]]) def getAxisAngleBetween(fromDirection: Vector, toDirection: Vector) -> Tuple[Vector, float]: diff --git a/pytissueoptics/scene/geometry/vector.py b/pytissueoptics/scene/geometry/vector.py index d79469c7..66ea21b9 100644 --- a/pytissueoptics/scene/geometry/vector.py +++ b/pytissueoptics/scene/geometry/vector.py @@ -30,31 +30,35 @@ def __repr__(self): def __iter__(self): return iter((self._x, self._y, self._z)) - def __eq__(self, other: 'Vector'): + def __eq__(self, other: "Vector"): tol = 1e-5 - if math.isclose(other._x, self._x, abs_tol=tol) and math.isclose(other._y, self._y, abs_tol=tol) and math.isclose(other._z, self._z, abs_tol=tol): + if ( + math.isclose(other._x, self._x, abs_tol=tol) + and math.isclose(other._y, self._y, abs_tol=tol) + and math.isclose(other._z, self._z, abs_tol=tol) + ): return True else: return False - def __sub__(self, other: 'Vector') -> 'Vector': + def __sub__(self, other: "Vector") -> "Vector": return Vector(self._x - other._x, self._y - other._y, self._z - other._z) - def __add__(self, other: 'Vector') -> 'Vector': + def __add__(self, other: "Vector") -> "Vector": return Vector(self._x + other._x, self._y + other._y, self._z + other._z) - def __mul__(self, scalar: float) -> 'Vector': + def __mul__(self, scalar: float) -> "Vector": return Vector(self._x * scalar, self._y * scalar, self._z * scalar) - def __truediv__(self, scalar: float) -> 'Vector': + def __truediv__(self, scalar: float) -> "Vector": return Vector(self._x / scalar, self._y / scalar, self._z / scalar) - def add(self, other: 'Vector'): + def add(self, other: "Vector"): self._x += other._x self._y += other._y self._z += other._z - def subtract(self, other: 'Vector'): + def subtract(self, other: "Vector"): self._x -= other._x self._y -= other._y self._z -= other._z @@ -70,7 +74,7 @@ def divide(self, scalar: float): self._z /= scalar def getNorm(self) -> float: - return (self._x ** 2 + self._y ** 2 + self._z ** 2) ** (1 / 2) + return (self._x**2 + self._y**2 + self._z**2) ** (1 / 2) def normalize(self): norm = self.getNorm() @@ -79,13 +83,13 @@ def normalize(self): self._y = self._y / norm self._z = self._z / norm - def cross(self, other: 'Vector') -> 'Vector': + def cross(self, other: "Vector") -> "Vector": ux, uy, uz = self._x, self._y, self._z vx, vy, vz = other._x, other._y, other._z return Vector(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx) - def dot(self, other: 'Vector') -> float: - return self._x*other._x + self._y*other._y + self._z*other._z + def dot(self, other: "Vector") -> float: + return self._x * other._x + self._y * other._y + self._z * other._z @property def array(self) -> list: @@ -96,10 +100,10 @@ def update(self, x: float, y: float, z: float): self._y = y self._z = z - def copy(self) -> 'Vector': + def copy(self) -> "Vector": return Vector(self._x, self._y, self._z) - def rotateAround(self, unitAxis: 'Vector', theta: float): + def rotateAround(self, unitAxis: "Vector", theta: float): """ Rotate the vector around `unitAxis` by `theta` radians. Assumes the axis to be a unit vector. Uses Rodrigues' rotation formula. @@ -128,19 +132,25 @@ def rotateAround(self, unitAxis: 'Vector', theta: float): Y = self._y Z = self._z - x = (cost + ux * ux * one_cost) * X \ - + (ux * uy * one_cost - uz * sint) * Y \ + x = ( + (cost + ux * ux * one_cost) * X + + (ux * uy * one_cost - uz * sint) * Y + (ux * uz * one_cost + uy * sint) * Z - y = (uy * ux * one_cost + uz * sint) * X \ - + (cost + uy * uy * one_cost) * Y \ + ) + y = ( + (uy * ux * one_cost + uz * sint) * X + + (cost + uy * uy * one_cost) * Y + (uy * uz * one_cost - ux * sint) * Z - z = (uz * ux * one_cost - uy * sint) * X \ - + (uz * uy * one_cost + ux * sint) * Y \ + ) + z = ( + (uz * ux * one_cost - uy * sint) * X + + (uz * uy * one_cost + ux * sint) * Y + (cost + uz * uz * one_cost) * Z + ) self.update(x, y, z) - def getAnyOrthogonal(self) -> 'Vector': + def getAnyOrthogonal(self) -> "Vector": if abs(self._z) < abs(self._x): return Vector(self._y, -self._x, 0) diff --git a/pytissueoptics/scene/intersection/bboxIntersect.py b/pytissueoptics/scene/intersection/bboxIntersect.py index 84b879c3..a895e135 100644 --- a/pytissueoptics/scene/intersection/bboxIntersect.py +++ b/pytissueoptics/scene/intersection/bboxIntersect.py @@ -12,12 +12,13 @@ def getIntersection(self, ray: Ray, bbox: BoundingBox) -> Union[Vector, None]: class GemsBoxIntersect(BoxIntersectStrategy): - """ Graphics Gems Fast Ray-Box Intersection. + """Graphics Gems Fast Ray-Box Intersection. https://github.com/erich666/GraphicsGems/blob/master/gems/RayBox.c If a ray lies on a box plane, it will consider this an intersection. If ray origin is inside box, it cannot compute intersection and will return ray.origin. """ + LEFT = 0 RIGHT = 1 MIDDLE = 2 @@ -76,10 +77,11 @@ def getIntersection(self, ray: Ray, bbox: BoundingBox) -> Union[Vector, None]: class ZacharBoxIntersect(BoxIntersectStrategy): - """ https://gamedev.stackexchange.com/a/18459 + """https://gamedev.stackexchange.com/a/18459 If a ray lies on a box plane, it will NOT consider this an intersection. If ray origin is inside box, it cannot compute intersection and will return ray.origin. """ + def getIntersection(self, ray: Ray, bbox: BoundingBox) -> Union[Vector, None]: inverseDirection = self._safeInverse(ray.direction) minCorner = Vector(bbox.xMin, bbox.yMin, bbox.zMin) @@ -120,4 +122,4 @@ def _safeInverse(direction: Vector) -> Vector: y += epsilon if z == 0.0: z += epsilon - return Vector(1.0/x, 1.0/y, 1.0/z) + return Vector(1.0 / x, 1.0 / y, 1.0 / z) diff --git a/pytissueoptics/scene/intersection/intersectionFinder.py b/pytissueoptics/scene/intersection/intersectionFinder.py index 76b781e1..2be19ec5 100644 --- a/pytissueoptics/scene/intersection/intersectionFinder.py +++ b/pytissueoptics/scene/intersection/intersectionFinder.py @@ -135,9 +135,9 @@ def findIntersection(self, ray: Ray, currentSolidLabel: str) -> Optional[Interse return self._composeIntersection(ray, closestIntersection) def _findBBoxIntersectingSolids(self, ray: Ray, currentSolidLabel: str) -> Optional[List[Tuple[float, Solid]]]: - """ We need to handle the special case where ray starts inside bbox. The Box Intersect will not compute + """We need to handle the special case where ray starts inside bbox. The Box Intersect will not compute the intersection for this case and will instead return ray.origin. When that happens, distance will be 0, - and we continue to check for possibly other contained solids. """ + and we continue to check for possibly other contained solids.""" solidCandidates = [] for solid in self._scene.solids: if solid.getLabel() == currentSolidLabel: @@ -154,8 +154,9 @@ def _findBBoxIntersectingSolids(self, ray: Ray, currentSolidLabel: str) -> Optio class FastIntersectionFinder(IntersectionFinder): def __init__(self, scene: Scene, constructor=NoSplitThreeAxesConstructor(), maxDepth=20, minLeafSize=6): super(FastIntersectionFinder, self).__init__(scene) - self._partition = SpacePartition(self._scene.getBoundingBox(), self._scene.getPolygons(), constructor, - maxDepth, minLeafSize) + self._partition = SpacePartition( + self._scene.getBoundingBox(), self._scene.getPolygons(), constructor, maxDepth, minLeafSize + ) def findIntersection(self, ray: Ray, currentSolidLabel: str) -> Optional[Intersection]: self._currentSolidLabel = currentSolidLabel diff --git a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py index 9b18f8e3..4d486fd8 100644 --- a/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py +++ b/pytissueoptics/scene/intersection/mollerTrumboreIntersect.py @@ -21,7 +21,7 @@ def getIntersection(self, ray: Ray, polygon: Union[Triangle, Quad, Polygon]) -> return self._getPolygonIntersection(ray, polygon) def _getTriangleIntersection(self, ray: Ray, triangle: Triangle) -> Optional[Vector]: - """ Möller–Trumbore ray-triangle 3D intersection algorithm. + """Möller–Trumbore ray-triangle 3D intersection algorithm. Added epsilon zones to avoid numerical errors in the OpenCL implementation. Modified to support rays with finite length: A. If the intersection is too far away, do not intersect. @@ -40,17 +40,17 @@ def _getTriangleIntersection(self, ray: Ray, triangle: Triangle) -> Optional[Vec if rayIsParallel: return None - inverseDeterminant = 1. / determinant + inverseDeterminant = 1.0 / determinant tVector = ray.origin - v1 u = tVector.dot(pVector) * inverseDeterminant - if u < -self.EPS_SIDE or u > 1.: + if u < -self.EPS_SIDE or u > 1.0: # EPS_SIDE is used to make the triangle a bit larger than it is # to be sure a ray could not sneak between two triangles. return None qVector = tVector.cross(edgeA) v = ray.direction.dot(qVector) * inverseDeterminant - if v < -self.EPS_SIDE or u + v > 1. + self.EPS_SIDE: + if v < -self.EPS_SIDE or u + v > 1.0 + self.EPS_SIDE: return None # Distance to intersection point @@ -63,8 +63,8 @@ def _getTriangleIntersection(self, ray: Ray, triangle: Triangle) -> Optional[Vec error -= u if v < -self.EPS: error -= v - if u + v > 1. + self.EPS: - error += u + v - 1. + if u + v > 1.0 + self.EPS: + error += u + v - 1.0 if error > 0: # Move the hit point towards the triangle center by this error factor. correctionDirection = v1 + v2 + v3 - hitPoint * 3 diff --git a/pytissueoptics/scene/intersection/raySource.py b/pytissueoptics/scene/intersection/raySource.py index b0529234..4b50efb3 100644 --- a/pytissueoptics/scene/intersection/raySource.py +++ b/pytissueoptics/scene/intersection/raySource.py @@ -21,7 +21,9 @@ def _createRays(self): class UniformRaySource(RaySource): - def __init__(self, position: Vector, direction: Vector, xTheta: float, yTheta: float, xResolution: int, yResolution: int): + def __init__( + self, position: Vector, direction: Vector, xTheta: float, yTheta: float, xResolution: int, yResolution: int + ): self._position = position self._direction = direction self._direction.normalize() @@ -53,5 +55,7 @@ def _getRayDirectionAt(self, xTheta, yTheta) -> Vector: """ xTheta += math.atan(self._direction.x / self._direction.z) yTheta += math.asin(self._direction.y) - rayDirection = Vector(-math.sin(xTheta)*math.cos(yTheta), math.sin(yTheta), -math.cos(xTheta)*math.cos(yTheta)) + rayDirection = Vector( + -math.sin(xTheta) * math.cos(yTheta), math.sin(yTheta), -math.cos(xTheta) * math.cos(yTheta) + ) return rayDirection diff --git a/pytissueoptics/scene/loader/loadSolid.py b/pytissueoptics/scene/loader/loadSolid.py index 3ae19ede..341bef27 100644 --- a/pytissueoptics/scene/loader/loadSolid.py +++ b/pytissueoptics/scene/loader/loadSolid.py @@ -3,7 +3,13 @@ from pytissueoptics.scene.solids import Solid, SolidFactory -def loadSolid(filepath: str, position: Vector = Vector(0, 0, 0), - material=None, label: str = "solidFromFile", smooth=False, showProgress: bool = True) -> Solid: +def loadSolid( + filepath: str, + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "solidFromFile", + smooth=False, + showProgress: bool = True, +) -> Solid: solids = Loader().load(filepath, showProgress=showProgress) return SolidFactory().fromSolids(solids, position, material, label, smooth) diff --git a/pytissueoptics/scene/loader/loader.py b/pytissueoptics/scene/loader/loader.py index d1abca7d..c16d817b 100644 --- a/pytissueoptics/scene/loader/loader.py +++ b/pytissueoptics/scene/loader/loader.py @@ -43,8 +43,12 @@ def _convert(self, showProgress: bool = True) -> List[Solid]: totalProgressBarLength = 0 for objectName, _object in self._parser.objects.items(): totalProgressBarLength += len(_object.surfaces.items()) - pbar = progressBar(total=totalProgressBarLength, desc="Converting File '{}'".format(self._filepath.split('/')[-1]), - unit="surfaces", disable=not showProgress) + pbar = progressBar( + total=totalProgressBarLength, + desc="Converting File '{}'".format(self._filepath.split("/")[-1]), + unit="surfaces", + disable=not showProgress, + ) solids = [] for objectName, _object in self._parser.objects.items(): @@ -52,15 +56,22 @@ def _convert(self, showProgress: bool = True) -> List[Solid]: for surfaceLabel, surface in _object.surfaces.items(): surfaces.add(surfaceLabel, self._convertSurfaceToTriangles(surface, vertices)) pbar.update(1) - solids.append(Solid(position=Vector(0, 0, 0), vertices=vertices, surfaces=surfaces, - primitive=primitives.POLYGON, label=objectName)) + solids.append( + Solid( + position=Vector(0, 0, 0), + vertices=vertices, + surfaces=surfaces, + primitive=primitives.POLYGON, + label=objectName, + ) + ) pbar.close() return solids @staticmethod def _convertSurfaceToTriangles(surface: ParsedSurface, vertices: List[Vertex]) -> List[Triangle]: - """ Converting to triangles only since loaded polygons are often not planar. """ + """Converting to triangles only since loaded polygons are often not planar.""" triangles = [] for polygonIndices in surface.polygons: polygonVertices = [vertices[i] for i in polygonIndices] diff --git a/pytissueoptics/scene/loader/parsers/obj/objParser.py b/pytissueoptics/scene/loader/parsers/obj/objParser.py index 2d3ee386..c6f0f2ba 100644 --- a/pytissueoptics/scene/loader/parsers/obj/objParser.py +++ b/pytissueoptics/scene/loader/parsers/obj/objParser.py @@ -11,7 +11,7 @@ def __init__(self, filepath: str, showProgress: bool = True): super().__init__(filepath, showProgress) def _checkFileExtension(self): - if self._filepath.endswith('.obj'): + if self._filepath.endswith(".obj"): return else: raise TypeError @@ -25,24 +25,30 @@ def _parse(self, showProgress: bool = True): - Groups start with 'g' - New objects will start with 'o' """ - self._PARSE_MAP = {'v': self._parseVertices, - 'vt': self._parseTexCoords, - 'vn': self._parseNormals, - 'usemtl': self._parseMaterial, - 'usemat': self._parseMaterial, - 'f': self._parseFace, - 'g': self._parseGroup, - 'o': self._parseObject} + self._PARSE_MAP = { + "v": self._parseVertices, + "vt": self._parseTexCoords, + "vn": self._parseNormals, + "usemtl": self._parseMaterial, + "usemat": self._parseMaterial, + "f": self._parseFace, + "g": self._parseGroup, + "o": self._parseObject, + } with open(self._filepath, "r") as file: - lines = [line.strip('\n') for line in file.readlines() if line != "\n"] - - for i in progressBar(range(len(lines)), desc="Parsing File '{}'".format(self._filepath.split('/')[-1]), - unit=" lines", disable=not showProgress): + lines = [line.strip("\n") for line in file.readlines() if line != "\n"] + + for i in progressBar( + range(len(lines)), + desc="Parsing File '{}'".format(self._filepath.split("/")[-1]), + unit=" lines", + disable=not showProgress, + ): self._parseLine(lines[i]) def _parseLine(self, line: str): - if line.startswith('#'): + if line.startswith("#"): return values = line.split() @@ -76,7 +82,7 @@ def _parseFace(self, values: List[str]): normalIndices = [] for verticesIndices in values[1:]: - vertexIndices = verticesIndices.split('/') + vertexIndices = verticesIndices.split("/") faceIndices.append(int(vertexIndices[0]) - 1) if len(vertexIndices) >= 2 and len(vertexIndices[1]) > 0: @@ -110,16 +116,16 @@ def _parseGroup(self, values: List[str]): self._currentSurfaceLabel = self.NO_SURFACE self._checkForNoObject() self._validateSurfaceLabel() - self._objects[self._currentObjectName].surfaces[self._currentSurfaceLabel] = ParsedSurface(polygons=[], - normals=[], - texCoords=[]) + self._objects[self._currentObjectName].surfaces[self._currentSurfaceLabel] = ParsedSurface( + polygons=[], normals=[], texCoords=[] + ) def _checkForNoObject(self): if len(self._objects) == 0 and self._currentObjectName == self.NO_OBJECT: - self._objects = { - self.NO_OBJECT: ParsedObject(material="", surfaces={})} + self._objects = {self.NO_OBJECT: ParsedObject(material="", surfaces={})} def _checkForNoSurface(self): if len(self._objects[self._currentObjectName].surfaces) == 0 and self._currentSurfaceLabel == self.NO_SURFACE: - self._objects[self._currentObjectName].surfaces[self.NO_SURFACE] = ParsedSurface(polygons=[], normals=[], - texCoords=[]) + self._objects[self._currentObjectName].surfaces[self.NO_SURFACE] = ParsedSurface( + polygons=[], normals=[], texCoords=[] + ) diff --git a/pytissueoptics/scene/loader/parsers/parser.py b/pytissueoptics/scene/loader/parsers/parser.py index 091969ca..a1b33e60 100644 --- a/pytissueoptics/scene/loader/parsers/parser.py +++ b/pytissueoptics/scene/loader/parsers/parser.py @@ -21,6 +21,7 @@ class Parser: The reason is that it is a global entity that has no complexity and is always needed for the conversion later down the line. """ + NO_OBJECT = "noObject" NO_SURFACE = "noSurface" diff --git a/pytissueoptics/scene/logger/__init__.py b/pytissueoptics/scene/logger/__init__.py index bcb892dc..f33816c7 100644 --- a/pytissueoptics/scene/logger/__init__.py +++ b/pytissueoptics/scene/logger/__init__.py @@ -1,3 +1,3 @@ from .logger import InteractionKey, Logger -__all__ = ['InteractionKey', 'Logger'] +__all__ = ["InteractionKey", "Logger"] diff --git a/pytissueoptics/scene/logger/listArrayContainer.py b/pytissueoptics/scene/logger/listArrayContainer.py index c567642b..d74c29e8 100644 --- a/pytissueoptics/scene/logger/listArrayContainer.py +++ b/pytissueoptics/scene/logger/listArrayContainer.py @@ -47,7 +47,7 @@ def append(self, item): else: self._array = np.concatenate((self._array, item), axis=0) - def extend(self, other: 'ListArrayContainer'): + def extend(self, other: "ListArrayContainer"): if self._list is None: self._list = copy.deepcopy(other._list) elif other._list is not None: diff --git a/pytissueoptics/scene/logger/logger.py b/pytissueoptics/scene/logger/logger.py index 2683ed77..15431d3d 100644 --- a/pytissueoptics/scene/logger/logger.py +++ b/pytissueoptics/scene/logger/logger.py @@ -43,23 +43,26 @@ def __init__(self, fromFilepath: str = None): self.load(fromFilepath) def getSeenSolidLabels(self) -> List[str]: - """ Returns a list of all solid labels that have been logged in the past - even if the data was discarded. """ + """Returns a list of all solid labels that have been logged in the past + even if the data was discarded.""" return list(self._labels.keys()) def getSeenSurfaceLabels(self, solidLabel: str) -> List[str]: - """ Returns a list of all surface labels that have been logged in the past - for the given solid even if the data was discarded. """ + """Returns a list of all surface labels that have been logged in the past + for the given solid even if the data was discarded.""" return self._labels[solidLabel] def getStoredSolidLabels(self) -> List[str]: - """ Returns a list of all solid labels that are currently stored in the logger. """ + """Returns a list of all solid labels that are currently stored in the logger.""" return list(set(key.solidLabel for key in self._data.keys())) def getStoredSurfaceLabels(self, solidLabel: str) -> List[str]: - """ Returns a list of all surface labels that are currently stored in the logger. """ - return [key.surfaceLabel for key in self._data.keys() if key.solidLabel == solidLabel - and key.surfaceLabel is not None] + """Returns a list of all surface labels that are currently stored in the logger.""" + return [ + key.surfaceLabel + for key in self._data.keys() + if key.solidLabel == solidLabel and key.surfaceLabel is not None + ] def logPoint(self, point: Vector, key: InteractionKey = None): self._appendData([point.x, point.y, point.z], DataType.POINT, key) @@ -71,17 +74,17 @@ def logSegment(self, start: Vector, end: Vector, key: InteractionKey = None): self._appendData([start.x, start.y, start.z, end.x, end.y, end.z], DataType.SEGMENT, key) def logPointArray(self, array: np.ndarray, key: InteractionKey = None): - """ 'array' must be of shape (n, 3) where second axis is (x, y, z) """ + """'array' must be of shape (n, 3) where second axis is (x, y, z)""" assert array.shape[1] == 3 and array.ndim == 2, "Point array must be of shape (n, 3)" self._appendData(array, DataType.POINT, key) def logDataPointArray(self, array: np.ndarray, key: InteractionKey): - """ 'array' must be of shape (n, 4) where second axis is (value, x, y, z) """ + """'array' must be of shape (n, 4) where second axis is (value, x, y, z)""" assert array.shape[1] == 4 and array.ndim == 2, "Data point array must be of shape (n, 4)" self._appendData(array, DataType.DATA_POINT, key) def logSegmentArray(self, array: np.ndarray, key: InteractionKey = None): - """ 'array' must be of shape (n, 6) where second axis is (x1, y1, z1, x2, y2, z2) """ + """'array' must be of shape (n, 6) where second axis is (x1, y1, z1, x2, y2, z2)""" assert array.shape[1] == 6 and array.ndim == 2, "Segment array must be of shape (n, 6)" self._appendData(array, DataType.SEGMENT, key) @@ -137,10 +140,14 @@ def _getData(self, dataType: DataType, key: InteractionKey = None) -> Optional[n def _keyExists(self, key: InteractionKey) -> bool: if key.solidLabel not in self.getStoredSolidLabels(): - warnings.warn(f"No data stored for solid labeled '{key.solidLabel}'. Available: {self.getStoredSolidLabels()}. ") + warnings.warn( + f"No data stored for solid labeled '{key.solidLabel}'. Available: {self.getStoredSolidLabels()}. " + ) elif key.surfaceLabel and key.surfaceLabel not in self.getStoredSurfaceLabels(key.solidLabel): - warnings.warn(f"No data stored for surface labeled '{key.surfaceLabel}' for solid '{key.solidLabel}'. " - f"Available: {self.getStoredSurfaceLabels(key.solidLabel)}. ") + warnings.warn( + f"No data stored for surface labeled '{key.surfaceLabel}' for solid '{key.solidLabel}'. " + f"Available: {self.getStoredSurfaceLabels(key.solidLabel)}. " + ) if key in self._data: return True return False @@ -159,8 +166,10 @@ def load(self, filepath: str): self._filepath = filepath if not os.path.exists(filepath): - warnings.warn("No logger file found at '{}'. No data loaded, but it will create a new file " - "at this location if the logger is saved later on.".format(filepath)) + warnings.warn( + "No logger file found at '{}'. No data loaded, but it will create a new file " + "at this location if the logger is saved later on.".format(filepath) + ) return with open(filepath, "rb") as file: diff --git a/pytissueoptics/scene/material.py b/pytissueoptics/scene/material.py index 332ac194..21afba09 100644 --- a/pytissueoptics/scene/material.py +++ b/pytissueoptics/scene/material.py @@ -1,4 +1,3 @@ - class RefractiveMaterial: def __init__(self, refractiveIndex): self.n = refractiveIndex diff --git a/pytissueoptics/scene/scene/scene.py b/pytissueoptics/scene/scene/scene.py index ecc14faa..3811e41e 100644 --- a/pytissueoptics/scene/scene/scene.py +++ b/pytissueoptics/scene/scene/scene.py @@ -9,13 +9,12 @@ class Scene(Displayable): - def __init__(self, solids: List[Solid] = None, ignoreIntersections=False, - worldMaterial=None): + def __init__(self, solids: List[Solid] = None, ignoreIntersections=False, worldMaterial=None): self._solids: List[Solid] = [] self._ignoreIntersections = ignoreIntersections self._solidsContainedIn: Dict[str, List[str]] = {} self._worldMaterial = worldMaterial - + if solids: for solid in solids: self.add(solid) @@ -34,15 +33,15 @@ def add(self, solid: Solid, position: Vector = None): def solids(self): return self._solids - def addToViewer(self, viewer: MayaviViewer, representation='surface', colormap='bone', opacity=0.1, **kwargs): + def addToViewer(self, viewer: MayaviViewer, representation="surface", colormap="bone", opacity=0.1, **kwargs): viewer.add(*self.solids, representation=representation, colormap=colormap, opacity=opacity, **kwargs) def getWorldEnvironment(self) -> Environment: return Environment(self._worldMaterial) def _validatePosition(self, newSolid: Solid): - """ Assert newSolid position is valid and make proper adjustments so that the - material at each solid interface is well-defined. """ + """Assert newSolid position is valid and make proper adjustments so that the + material at each solid interface is well-defined.""" if len(self._solids) == 0: return @@ -59,11 +58,13 @@ def _validatePosition(self, newSolid: Solid): elif otherSolid.contains(*newSolid.getVertices()): self._processContainedSolid(newSolid, container=otherSolid) else: - raise NotImplementedError("Cannot place a solid that partially intersects with an existing solid. " - "Since this might be underestimating containment, you can also create a " - "scene with 'ignoreIntersections=True' to ignore this error and manually " - "handle environments of contained solids with " - "containedSolid.setOutsideEnvironment(containerSolid.getEnvironment()).") + raise NotImplementedError( + "Cannot place a solid that partially intersects with an existing solid. " + "Since this might be underestimating containment, you can also create a " + "scene with 'ignoreIntersections=True' to ignore this error and manually " + "handle environments of contained solids with " + "containedSolid.setOutsideEnvironment(containerSolid.getEnvironment())." + ) def _processContainedSolid(self, solid: Solid, container: Solid): if container.isStack(): @@ -85,8 +86,10 @@ def _validateLabel(self, solid): idx = 2 while f"{solid.getLabel()}_{idx}" in labelSet: idx += 1 - warnings.warn(f"A solid with label '{solid.getLabel()}' already exists in the scene. " - f"Renaming to '{solid.getLabel()}_{idx}'.") + warnings.warn( + f"A solid with label '{solid.getLabel()}' already exists in the scene. " + f"Renaming to '{solid.getLabel()}_{idx}'." + ) solid.setLabel(f"{solid.getLabel()}_{idx}") def _findIntersectingSuspectsFor(self, solid) -> List[Solid]: @@ -197,7 +200,7 @@ def _getEnvironmentOfContainerAt(self, position: Vector, containerLabel: str) -> @staticmethod def _getEnvironmentOfStackAt(position: Vector, stack: Solid) -> Environment: - """ Returns the environment of the stack at the given position. + """Returns the environment of the stack at the given position. To do that we first find the interface in the stack that is closest to the given position. At the same time we find on which side of the interface we are and return the environment diff --git a/pytissueoptics/scene/shader/utils.py b/pytissueoptics/scene/shader/utils.py index e22f7c54..042a1f0e 100644 --- a/pytissueoptics/scene/shader/utils.py +++ b/pytissueoptics/scene/shader/utils.py @@ -4,13 +4,13 @@ def getSmoothNormal(polygon: Polygon, position: Vector) -> Vector: - """ If the intersecting polygon was prepared for smoothing (i.e. it has vertex + """If the intersecting polygon was prepared for smoothing (i.e. it has vertex normals), we interpolate the normal at the intersection point using the normal of all its vertices. The interpolation is done using the general barycentric - coordinates algorithm from http://www.geometry.caltech.edu/pubs/MHBD02.pdfv. """ + coordinates algorithm from http://www.geometry.caltech.edu/pubs/MHBD02.pdfv.""" if not polygon.toSmooth: return polygon.normal - + # Check edge case where the intersection is directly on a vertex, in which case we just return the vertex normal. for vertex in polygon.vertices: if (position - vertex).getNorm() < 1e-6: @@ -32,14 +32,15 @@ def _getBarycentricWeights(vertices: List[Vertex], position: Vector) -> List[flo for i, vertex in enumerate(vertices): prevVertex = vertices[(i - 1) % n] nextVertex = vertices[(i + 1) % n] - w = (_cotangent(position, vertex, prevVertex) + - _cotangent(position, vertex, nextVertex)) / (position - vertex).getNorm() ** 2 + w = (_cotangent(position, vertex, prevVertex) + _cotangent(position, vertex, nextVertex)) / ( + position - vertex + ).getNorm() ** 2 weights.append(w) return [w / sum(weights) for w in weights] def _cotangent(a: Vector, b: Vector, c: Vector) -> float: - """ Cotangent of triangle abc at vertex b. """ + """Cotangent of triangle abc at vertex b.""" ba = a - b bc = c - b norm = ba.cross(bc).getNorm() diff --git a/pytissueoptics/scene/solids/__init__.py b/pytissueoptics/scene/solids/__init__.py index cb3fad96..9e63b939 100644 --- a/pytissueoptics/scene/solids/__init__.py +++ b/pytissueoptics/scene/solids/__init__.py @@ -20,5 +20,5 @@ "PlanoConvexLens", "PlanoConcaveLens", "ThickLens", - "SymmetricLens" + "SymmetricLens", ] diff --git a/pytissueoptics/scene/solids/cone.py b/pytissueoptics/scene/solids/cone.py index bc65d2e0..49632635 100644 --- a/pytissueoptics/scene/solids/cone.py +++ b/pytissueoptics/scene/solids/cone.py @@ -3,9 +3,18 @@ class Cone(Cylinder): - def __init__(self, radius: float = 1, length: float = 1, u: int = 32, v: int = 3, s: int = 1, - position: Vector = Vector(0, 0, 0), material=None, primitive: str = primitives.DEFAULT, - label: str = "Cone"): + def __init__( + self, + radius: float = 1, + length: float = 1, + u: int = 32, + v: int = 3, + s: int = 1, + position: Vector = Vector(0, 0, 0), + material=None, + primitive: str = primitives.DEFAULT, + label: str = "Cone", + ): super().__init__(radius, length, u, v, s, position, material, primitive, label) def _getShrinkFactor(self, heightAlong: float) -> float: diff --git a/pytissueoptics/scene/solids/cube.py b/pytissueoptics/scene/solids/cube.py index 13760112..f5c49917 100644 --- a/pytissueoptics/scene/solids/cube.py +++ b/pytissueoptics/scene/solids/cube.py @@ -3,8 +3,12 @@ class Cube(Cuboid): - def __init__(self, edge: float, - position: Vector = Vector(0, 0, 0), material=None, - label: str = "cube", primitive: str = primitives.DEFAULT): - super().__init__(a=edge, b=edge, c=edge, position=position, - material=material, label=label, primitive=primitive) + def __init__( + self, + edge: float, + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "cube", + primitive: str = primitives.DEFAULT, + ): + super().__init__(a=edge, b=edge, c=edge, position=position, material=material, label=label, primitive=primitive) diff --git a/pytissueoptics/scene/solids/cuboid.py b/pytissueoptics/scene/solids/cuboid.py index 2ccfa715..91ed904b 100644 --- a/pytissueoptics/scene/solids/cuboid.py +++ b/pytissueoptics/scene/solids/cuboid.py @@ -19,47 +19,62 @@ class Cuboid(Solid): """ - Also known as the Right Rectangular Prism, the Cuboid is defined by its - width (a, b, c) in each axis (x, y, z) respectively. + Also known as the Right Rectangular Prism, the Cuboid is defined by its + width (a, b, c) in each axis (x, y, z) respectively. - The position refers to the vector from global origin to its centroid. - The generated mesh will be divided into the following subgroups: - Left (-x), Right (+x), Bottom (-y), Top (+y), Front (-z), Back (+z). + The position refers to the vector from global origin to its centroid. + The generated mesh will be divided into the following subgroups: + Left (-x), Right (+x), Bottom (-y), Top (+y), Front (-z), Back (+z). """ - def __init__(self, a: float, b: float, c: float, - vertices: List[Vertex] = None, position: Vector = Vector(0, 0, 0), surfaces: SurfaceCollection = None, - material=None, label: str = "cuboid", primitive: str = primitives.DEFAULT, labelOverride=True): - + def __init__( + self, + a: float, + b: float, + c: float, + vertices: List[Vertex] = None, + position: Vector = Vector(0, 0, 0), + surfaces: SurfaceCollection = None, + material=None, + label: str = "cuboid", + primitive: str = primitives.DEFAULT, + labelOverride=True, + ): self.shape = [a, b, c] if not vertices: - vertices = [Vertex(-a / 2, -b / 2, c / 2), Vertex(a / 2, -b / 2, c / 2), Vertex(a / 2, b / 2, c / 2), - Vertex(-a / 2, b / 2, c / 2), - Vertex(-a / 2, -b / 2, -c / 2), Vertex(a / 2, -b / 2, -c / 2), Vertex(a / 2, b / 2, -c / 2), - Vertex(-a / 2, b / 2, -c / 2)] + vertices = [ + Vertex(-a / 2, -b / 2, c / 2), + Vertex(a / 2, -b / 2, c / 2), + Vertex(a / 2, b / 2, c / 2), + Vertex(-a / 2, b / 2, c / 2), + Vertex(-a / 2, -b / 2, -c / 2), + Vertex(a / 2, -b / 2, -c / 2), + Vertex(a / 2, b / 2, -c / 2), + Vertex(-a / 2, b / 2, -c / 2), + ] super().__init__(vertices, position, surfaces, material, label, primitive, labelOverride=labelOverride) def _computeTriangleMesh(self): V = self._vertices - self._surfaces.add('left', [Triangle(V[4], V[0], V[3]), Triangle(V[3], V[7], V[4])]) - self._surfaces.add('right', [Triangle(V[1], V[5], V[6]), Triangle(V[6], V[2], V[1])]) - self._surfaces.add('bottom', [Triangle(V[4], V[5], V[1]), Triangle(V[1], V[0], V[4])]) - self._surfaces.add('top', [Triangle(V[3], V[2], V[6]), Triangle(V[6], V[7], V[3])]) - self._surfaces.add('front', [Triangle(V[5], V[4], V[7]), Triangle(V[7], V[6], V[5])]) - self._surfaces.add('back', [Triangle(V[0], V[1], V[2]), Triangle(V[2], V[3], V[0])]) + self._surfaces.add("left", [Triangle(V[4], V[0], V[3]), Triangle(V[3], V[7], V[4])]) + self._surfaces.add("right", [Triangle(V[1], V[5], V[6]), Triangle(V[6], V[2], V[1])]) + self._surfaces.add("bottom", [Triangle(V[4], V[5], V[1]), Triangle(V[1], V[0], V[4])]) + self._surfaces.add("top", [Triangle(V[3], V[2], V[6]), Triangle(V[6], V[7], V[3])]) + self._surfaces.add("front", [Triangle(V[5], V[4], V[7]), Triangle(V[7], V[6], V[5])]) + self._surfaces.add("back", [Triangle(V[0], V[1], V[2]), Triangle(V[2], V[3], V[0])]) def _computeQuadMesh(self): V = self._vertices - self._surfaces.add('left', [Quad(V[4], V[0], V[3], V[7])]) - self._surfaces.add('right', [Quad(V[1], V[5], V[6], V[2])]) - self._surfaces.add('bottom', [Quad(V[4], V[5], V[1], V[0])]) - self._surfaces.add('top', [Quad(V[3], V[2], V[6], V[7])]) - self._surfaces.add('front', [Quad(V[5], V[4], V[7], V[6])]) - self._surfaces.add('back', [Quad(V[0], V[1], V[2], V[3])]) - - def stack(self, other: 'Cuboid', onSurface: str = 'top', stackLabel="CuboidStack") -> 'Cuboid': + self._surfaces.add("left", [Quad(V[4], V[0], V[3], V[7])]) + self._surfaces.add("right", [Quad(V[1], V[5], V[6], V[2])]) + self._surfaces.add("bottom", [Quad(V[4], V[5], V[1], V[0])]) + self._surfaces.add("top", [Quad(V[3], V[2], V[6], V[7])]) + self._surfaces.add("front", [Quad(V[5], V[4], V[7], V[6])]) + self._surfaces.add("back", [Quad(V[0], V[1], V[2], V[3])]) + + def stack(self, other: "Cuboid", onSurface: str = "top", stackLabel="CuboidStack") -> "Cuboid": """ Basic implementation for stacking cuboids along an axis. @@ -79,13 +94,20 @@ def stack(self, other: 'Cuboid', onSurface: str = 'top', stackLabel="CuboidStack return Cuboid._fromStackResult(stackResult, label=stackLabel) @classmethod - def _fromStackResult(cls, stackResult: StackResult, label: str) -> 'Cuboid': + def _fromStackResult(cls, stackResult: StackResult, label: str) -> "Cuboid": # subtracting stackCentroid from all vertices because solid creation will translate back to position. for vertex in stackResult.vertices: vertex.subtract(stackResult.position) - cuboid = Cuboid(*stackResult.shape, position=stackResult.position, vertices=stackResult.vertices, - surfaces=stackResult.surfaces, label=label, primitive=stackResult.primitive, labelOverride=False) + cuboid = Cuboid( + *stackResult.shape, + position=stackResult.position, + vertices=stackResult.vertices, + surfaces=stackResult.surfaces, + label=label, + primitive=stackResult.primitive, + labelOverride=False, + ) cuboid._layerLabels = stackResult.layerLabels return cuboid @@ -94,7 +116,7 @@ def contains(self, *vertices: Vector) -> bool: relativeVertices = self._applyInverseRotation(relativeVertices) relativeVertices = np.asarray([vertex.array for vertex in relativeVertices]) - bounds = [s/2 for s in self.shape] + bounds = [s / 2 for s in self.shape] if np.any(np.abs(relativeVertices) >= bounds): return False @@ -111,7 +133,7 @@ def _aSingleLayerContains(self, relativeVertices: np.ndarray) -> bool: for layerLabel in self._layerLabels: bbox = self._getLayerBBox(layerLabel) bboxShape = [bbox.xWidth, bbox.yWidth, bbox.zWidth] - bounds = [s/2 for s in bboxShape] + bounds = [s / 2 for s in bboxShape] layerOffset = bbox.center - self.position layerRelativeVertices = relativeVertices - np.asarray(layerOffset.array) if np.all(np.abs(layerRelativeVertices) < bounds): diff --git a/pytissueoptics/scene/solids/cylinder.py b/pytissueoptics/scene/solids/cylinder.py index c9d4b438..f2aa6607 100644 --- a/pytissueoptics/scene/solids/cylinder.py +++ b/pytissueoptics/scene/solids/cylinder.py @@ -7,9 +7,19 @@ class Cylinder(Solid): - def __init__(self, radius: float = 1, length: float = 1, u: int = 32, v: int = 3, s: int = 2, - position: Vector = Vector(0, 0, 0), material=None, - primitive: str = primitives.DEFAULT, label: str = "cylinder", smooth=True): + def __init__( + self, + radius: float = 1, + length: float = 1, + u: int = 32, + v: int = 3, + s: int = 2, + position: Vector = Vector(0, 0, 0), + material=None, + primitive: str = primitives.DEFAULT, + label: str = "cylinder", + smooth=True, + ): """ Default cylinder orientation will be along the z axis. The front face towards the negative z axis and the back face towards the positive z axis. Position refers to its centroid. Available surfaces are "front", "lateral" and @@ -32,8 +42,9 @@ def __init__(self, radius: float = 1, length: float = 1, u: int = 32, v: int = 3 self._lateralStep = self._length / self._v self._radialStep = self._radius / self._s - super().__init__(position=position, material=material, primitive=primitive, - vertices=[], smooth=smooth, label=label) + super().__init__( + position=position, material=material, primitive=primitive, vertices=[], smooth=smooth, label=label + ) self.translateBy(Vector(0, 0, -length / 2)) self._position += Vector(0, 0, length / 2) @@ -60,7 +71,7 @@ def _computeVerticesOfLayers(self) -> tuple: v = self._v if self._length != 0 else 0 frontLayers = self._computeSectionVertices(lateralSteps=[0], radialSteps=list(range(1, self._s))) lateralLayers = self._computeSectionVertices(lateralSteps=list(range(v + 1)), radialSteps=[self._s]) - backLayers = self._computeSectionVertices(lateralSteps=[v], radialSteps=list(range(self._s-1, 0, -1))) + backLayers = self._computeSectionVertices(lateralSteps=[v], radialSteps=list(range(self._s - 1, 0, -1))) return frontLayers, lateralLayers, backLayers def _computeSectionVertices(self, lateralSteps: List[int], radialSteps: List[int]): @@ -83,7 +94,7 @@ def _createVertex(self, i: int, j: int, k: int) -> Vertex: # For lenses, we usually want a uniform mesh after the curve transform, but this transform tends to push # vertices outwards (particularly at low radius). To prevent a low mesh resolution in the center, we need # to increase the sampling around the center beforehand by forcing smaller radiusFactor values. - radiusFactor = radiusFactor ** 2 + radiusFactor = radiusFactor**2 r = self._radius * radiusFactor * shrinkFactor x = r * math.cos(i * self._angularStep) y = r * math.sin(i * self._angularStep) diff --git a/pytissueoptics/scene/solids/ellipsoid.py b/pytissueoptics/scene/solids/ellipsoid.py index f7953d83..ec2c51d3 100644 --- a/pytissueoptics/scene/solids/ellipsoid.py +++ b/pytissueoptics/scene/solids/ellipsoid.py @@ -14,23 +14,32 @@ def hash2(obj): class Ellipsoid(Solid): """ - We take the unit sphere, then calculate the theta, phi position of each vertex (with ISO mathematical - convention). Then we apply the ellipsoid formula in the spherical coordinate to isolate the component R. - We then calculate the difference the ellipsoid would with the unit sphere for this theta,phi and - then .add() or .subtract() the corresponding vector. + We take the unit sphere, then calculate the theta, phi position of each vertex (with ISO mathematical + convention). Then we apply the ellipsoid formula in the spherical coordinate to isolate the component R. + We then calculate the difference the ellipsoid would with the unit sphere for this theta,phi and + then .add() or .subtract() the corresponding vector. """ - def __init__(self, a: float = 1, b: float = 1, c: float = 1, order: int = 3, - position: Vector = Vector(0, 0, 0), material=None, - label: str = "ellipsoid", primitive: str = primitives.DEFAULT, smooth: bool = True): - + def __init__( + self, + a: float = 1, + b: float = 1, + c: float = 1, + order: int = 3, + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "ellipsoid", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + ): self._a = a self._b = b self._c = c self._order = order - super().__init__(position=position, material=material, label=label, primitive=primitive, - vertices=[], smooth=smooth) + super().__init__( + position=position, material=material, label=label, primitive=primitive, vertices=[], smooth=smooth + ) def _computeTriangleMesh(self): """ @@ -63,16 +72,31 @@ def _computeFirstOrderTriangleMesh(self): self._vertices = [*xyPlaneVertices, *yzPlaneVertices, *xzPlaneVertices] V = self._vertices - self._surfaces.add("ellipsoid", [Triangle(V[0], V[11], V[5]), Triangle(V[0], V[5], V[1]), - Triangle(V[0], V[1], V[7]), Triangle(V[0], V[7], V[10]), - Triangle(V[0], V[10], V[11]), Triangle(V[1], V[5], V[9]), - Triangle(V[5], V[11], V[4]), Triangle(V[11], V[10], V[2]), - Triangle(V[10], V[7], V[6]), Triangle(V[7], V[1], V[8]), - Triangle(V[3], V[9], V[4]), Triangle(V[3], V[4], V[2]), - Triangle(V[3], V[2], V[6]), Triangle(V[3], V[6], V[8]), - Triangle(V[3], V[8], V[9]), Triangle(V[4], V[9], V[5]), - Triangle(V[2], V[4], V[11]), Triangle(V[6], V[2], V[10]), - Triangle(V[8], V[6], V[7]), Triangle(V[9], V[8], V[1])]) + self._surfaces.add( + "ellipsoid", + [ + Triangle(V[0], V[11], V[5]), + Triangle(V[0], V[5], V[1]), + Triangle(V[0], V[1], V[7]), + Triangle(V[0], V[7], V[10]), + Triangle(V[0], V[10], V[11]), + Triangle(V[1], V[5], V[9]), + Triangle(V[5], V[11], V[4]), + Triangle(V[11], V[10], V[2]), + Triangle(V[10], V[7], V[6]), + Triangle(V[7], V[1], V[8]), + Triangle(V[3], V[9], V[4]), + Triangle(V[3], V[4], V[2]), + Triangle(V[3], V[2], V[6]), + Triangle(V[3], V[6], V[8]), + Triangle(V[3], V[8], V[9]), + Triangle(V[4], V[9], V[5]), + Triangle(V[2], V[4], V[11]), + Triangle(V[6], V[2], V[10]), + Triangle(V[8], V[6], V[7]), + Triangle(V[9], V[8], V[1]), + ], + ) def _computeNextOrderTriangleMesh(self): newPolygons = [] @@ -115,13 +139,13 @@ def _setVerticesPositionsFromCenter(self): for vertex in self._vertices: vertex.normalize() r = self._radiusTowards(vertex) - distanceFromUnitSphere = (r - 1.0) + distanceFromUnitSphere = r - 1.0 vertex.add(vertex * distanceFromUnitSphere) self.surfaces.resetNormals() @staticmethod def _findThetaPhi(vertex: Vertex): - phi = math.acos(vertex.z / (vertex.x ** 2 + vertex.y ** 2 + vertex.z ** 2)) + phi = math.acos(vertex.z / (vertex.x**2 + vertex.y**2 + vertex.z**2)) theta = 0 if vertex.x == 0.0: if vertex.y > 0.0: @@ -144,15 +168,21 @@ def _findThetaPhi(vertex: Vertex): def _radiusTowards(self, vertex): theta, phi = self._findThetaPhi(vertex) - return math.sqrt(1 / ((math.cos(theta) ** 2 * math.sin(phi) ** 2) / self._a ** 2 + ( - math.sin(theta) ** 2 * math.sin(phi) ** 2) / self._b ** 2 + math.cos(phi) ** 2 / self._c ** 2)) + return math.sqrt( + 1 + / ( + (math.cos(theta) ** 2 * math.sin(phi) ** 2) / self._a**2 + + (math.sin(theta) ** 2 * math.sin(phi) ** 2) / self._b**2 + + math.cos(phi) ** 2 / self._c**2 + ) + ) def _computeQuadMesh(self): raise NotImplementedError def contains(self, *vertices: Vector) -> bool: - """ Only returns true if all vertices are inside the minimum radius of the ellipsoid - towards each vertex direction (more restrictive with low order ellipsoids). """ + """Only returns true if all vertices are inside the minimum radius of the ellipsoid + towards each vertex direction (more restrictive with low order ellipsoids).""" relativeVertices = [vertex - self.position for vertex in vertices] relativeVertices = self._applyInverseRotation(relativeVertices) relativeVerticesArray = np.asarray([vertex.array for vertex in relativeVertices]) diff --git a/pytissueoptics/scene/solids/lens.py b/pytissueoptics/scene/solids/lens.py index 74418453..e34665e6 100644 --- a/pytissueoptics/scene/solids/lens.py +++ b/pytissueoptics/scene/solids/lens.py @@ -18,25 +18,52 @@ class ThickLens(Cylinder): The generated mesh will be divided into the following subgroups: Front, Side and Back. By default, front will point towards the negative z-axis. """ - def __init__(self, frontRadius: float, backRadius: float, diameter: float, thickness: float, - position: Vector = Vector(0, 0, 0), material=None, label: str = "thick lens", - primitive: str = primitives.DEFAULT, smooth: bool = True, u: int = 24, v: int = 2, s: int = 24): + + def __init__( + self, + frontRadius: float, + backRadius: float, + diameter: float, + thickness: float, + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "thick lens", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + u: int = 24, + v: int = 2, + s: int = 24, + ): frontRadius = frontRadius if frontRadius != 0 else math.inf backRadius = backRadius if backRadius != 0 else math.inf if abs(frontRadius) <= diameter / 2: - raise ValueError(f"Front radius must be greater than the lens radius. Front radius: {frontRadius}, " - f"lens radius: {diameter / 2}") + raise ValueError( + f"Front radius must be greater than the lens radius. Front radius: {frontRadius}, " + f"lens radius: {diameter / 2}" + ) if abs(backRadius) <= diameter / 2: - raise ValueError(f"Back radius must be greater than the lens radius. Back radius: {backRadius}, " - f"lens radius: {diameter / 2}") + raise ValueError( + f"Back radius must be greater than the lens radius. Back radius: {backRadius}, " + f"lens radius: {diameter / 2}" + ) self._diameter = diameter self._frontRadius = frontRadius self._backRadius = backRadius length = self._computeEdgeThickness(thickness) - super().__init__(radius=diameter / 2, length=length, u=u, v=v, s=s, position=position, - material=material, label=label, primitive=primitive, smooth=smooth) + super().__init__( + radius=diameter / 2, + length=length, + u=u, + v=v, + s=s, + position=position, + material=material, + label=label, + primitive=primitive, + smooth=smooth, + ) @property def _hasFrontCurvature(self) -> bool: @@ -47,14 +74,14 @@ def _hasBackCurvature(self) -> bool: return self._backRadius != 0 and self._backRadius != math.inf def _computeEdgeThickness(self, centerThickness) -> float: - """ Returns the thickness of the lens on its side. This is the length required to build the base cylinder - before applying surface curvature. """ + """Returns the thickness of the lens on its side. This is the length required to build the base cylinder + before applying surface curvature.""" dt1, dt2 = 0, 0 if self._hasFrontCurvature: - dt1 = abs(self._frontRadius) - math.sqrt(self._frontRadius**2 - self._diameter**2/4) + dt1 = abs(self._frontRadius) - math.sqrt(self._frontRadius**2 - self._diameter**2 / 4) dt1 *= np.sign(self._frontRadius) if self._hasBackCurvature: - dt2 = abs(self._backRadius) - math.sqrt(self._backRadius**2 - self._diameter**2/4) + dt2 = abs(self._backRadius) - math.sqrt(self._backRadius**2 - self._diameter**2 / 4) dt2 *= -np.sign(self._backRadius) edgeThickness = centerThickness - dt1 - dt2 if edgeThickness < 0: @@ -79,7 +106,7 @@ def backRadius(self) -> float: @property def focalLength(self) -> float: - """ Returns the focal length of the lens in air. Requires a refractive material to be defined.""" + """Returns the focal length of the lens in air. Requires a refractive material to be defined.""" if self._material is None or not issubclass(type(self._material), RefractiveMaterial): raise ValueError("Cannot compute focal length without refractive material defined.") # For thick lenses, the focal length is given by the lensmaker's equation: @@ -131,10 +158,22 @@ def smooth(self, surfaceLabel: str = None, reset: bool = True): class SymmetricLens(ThickLens): - """ A symmetrical thick lens of focal length `f` in air. """ - def __init__(self, f: float, diameter: float, thickness: float, material: RefractiveMaterial, - position: Vector = Vector(0, 0, 0), label: str = "lens", primitive: str = primitives.DEFAULT, - smooth: bool = True, u: int = 24, v: int = 2, s: int = 24): + """A symmetrical thick lens of focal length `f` in air.""" + + def __init__( + self, + f: float, + diameter: float, + thickness: float, + material: RefractiveMaterial, + position: Vector = Vector(0, 0, 0), + label: str = "lens", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + u: int = 24, + v: int = 2, + s: int = 24, + ): # For thick lenses, the focal length is given by the lensmaker's equation: # 1/f = (n - 1) * (1/R1 - 1/R2 + (n - 1) * d / (n * R1 * R2)) # with R2 = -R1, we get the following quadratic equation to solve: @@ -146,9 +185,20 @@ def __init__(self, f: float, diameter: float, thickness: float, material: Refrac class PlanoConvexLens(ThickLens): - def __init__(self, f: float, diameter: float, thickness: float, material: RefractiveMaterial, - position: Vector = Vector(0, 0, 0), label: str = "lens", primitive: str = primitives.DEFAULT, - smooth: bool = True, u: int = 24, v: int = 2, s: int = 24): + def __init__( + self, + f: float, + diameter: float, + thickness: float, + material: RefractiveMaterial, + position: Vector = Vector(0, 0, 0), + label: str = "lens", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + u: int = 24, + v: int = 2, + s: int = 24, + ): R1 = f * (material.n - 1) R2 = math.inf if f < 0: @@ -157,9 +207,20 @@ def __init__(self, f: float, diameter: float, thickness: float, material: Refrac class PlanoConcaveLens(ThickLens): - def __init__(self, f: float, diameter: float, thickness: float, material: RefractiveMaterial, - position: Vector = Vector(0, 0, 0), label: str = "lens", primitive: str = primitives.DEFAULT, - smooth: bool = True, u: int = 24, v: int = 2, s: int = 24): + def __init__( + self, + f: float, + diameter: float, + thickness: float, + material: RefractiveMaterial, + position: Vector = Vector(0, 0, 0), + label: str = "lens", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + u: int = 24, + v: int = 2, + s: int = 24, + ): R1 = math.inf R2 = f * (material.n - 1) if f < 0: diff --git a/pytissueoptics/scene/solids/solid.py b/pytissueoptics/scene/solids/solid.py index 0f05dc4d..e194315e 100644 --- a/pytissueoptics/scene/solids/solid.py +++ b/pytissueoptics/scene/solids/solid.py @@ -21,9 +21,17 @@ class Solid: - def __init__(self, vertices: List[Vertex], position: Vector = Vector(0, 0, 0), - surfaces: SurfaceCollection = None, material=None, - label: str = "solid", primitive: str = primitives.DEFAULT, smooth: bool = False, labelOverride=True): + def __init__( + self, + vertices: List[Vertex], + position: Vector = Vector(0, 0, 0), + surfaces: SurfaceCollection = None, + material=None, + label: str = "solid", + primitive: str = primitives.DEFAULT, + smooth: bool = False, + labelOverride=True, + ): self._vertices = vertices self._surfaces = surfaces self._material = material @@ -130,8 +138,8 @@ def rotate(self, xTheta=0, yTheta=0, zTheta=0, rotationCenter: Vector = None): self._rotation.add(rotation) def orient(self, towards: Vector): - """ Rotate the solid so that its direction is aligned with the given vector "towards". - Note that the original solid orientation is set to (0, 0, 1). """ + """Rotate the solid so that its direction is aligned with the given vector "towards". + Note that the original solid orientation is set to (0, 0, 1).""" initialOrientation = self._orientation axis, angle = utils.getAxisAngleBetween(initialOrientation, towards) rotationFunction = partial(self._rotateWithAxisAngle, axis=axis, angle=angle) @@ -149,7 +157,7 @@ def _rotateWith(self, rotationFunction: Callable[[List[Vector]], List[Vector]], rotatedVertices = [vertex + rotationCenter for vertex in rotatedVerticesAtOrigin] - for (vertex, rotatedVertex) in zip(self._vertices, rotatedVertices): + for vertex, rotatedVertex in zip(self._vertices, rotatedVertices): vertex.update(*rotatedVertex.array) self._position = rotatedVertices[-1] @@ -232,8 +240,8 @@ def _computeQuadMesh(self): def contains(self, *vertices: Vector) -> bool: """ Provides a simple implementation, which should be overwritten by subclasses to provide more accuracy. - This implementation will only check the outer bounding box of the solid to check if the vertices are outside. - Similarly, it will create a max internal bounding box and check if the vertices are inside. + This implementation will only check the outer bounding box of the solid to check if the vertices are outside. + Similarly, it will create a max internal bounding box and check if the vertices are inside. """ for vertex in vertices: if not self._bbox.contains(vertex): @@ -241,9 +249,12 @@ def contains(self, *vertices: Vector) -> bool: internalBBox = self._getInternalBBox() for vertex in vertices: if not internalBBox.contains(vertex): - warnings.warn(f"Method contains(Vertex) is not implemented for Solids of type {type(self).__name__}. " - "Returning False since Vertex does not lie in the internal bounding box " - "(underestimating containment). ", RuntimeWarning) + warnings.warn( + f"Method contains(Vertex) is not implemented for Solids of type {type(self).__name__}. " + "Returning False since Vertex does not lie in the internal bounding box " + "(underestimating containment). ", + RuntimeWarning, + ) return False return True @@ -255,9 +266,11 @@ def _getInternalBBox(self): def _applyInverseRotation(self, vertices: List[Vector]) -> List[Vector]: if self._rotation and self._orientation != INITIAL_SOLID_ORIENTATION: - raise Exception("Rotation correction (often used for solid containment checks) " - "is not implemented for solids that underwent rotations " - "with both the Euler rotate() and the axis-angle orient() methods.") + raise Exception( + "Rotation correction (often used for solid containment checks) " + "is not implemented for solids that underwent rotations " + "with both the Euler rotate() and the axis-angle orient() methods." + ) if self._rotation: return self._rotateWithEuler(vertices, self._rotation, inverse=True) if self._orientation != INITIAL_SOLID_ORIENTATION: @@ -284,7 +297,7 @@ def completeSurfaceLabel(self, surfaceLabel: str) -> str: return self._surfaces.processLabel(surfaceLabel) def smooth(self, surfaceLabel: str = None, reset: bool = True): - """ Prepare smoothing by calculating vertex normals. This is not done + """Prepare smoothing by calculating vertex normals. This is not done by default. The vertex normals are used during ray-polygon intersection to return an interpolated (smooth) normal. A vertex normal is defined by taking the average normal of all adjacent polygons. diff --git a/pytissueoptics/scene/solids/solidFactory.py b/pytissueoptics/scene/solids/solidFactory.py index 271d4568..b7859a79 100644 --- a/pytissueoptics/scene/solids/solidFactory.py +++ b/pytissueoptics/scene/solids/solidFactory.py @@ -8,15 +8,28 @@ class SolidFactory: _vertices: List[Vertex] _surfaces: SurfaceCollection - def fromSolids(self, solids: List[Solid], position: Vector = Vector(0, 0, 0), material=None, - label: str = "solidGroup", smooth=False) -> Solid: + def fromSolids( + self, + solids: List[Solid], + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "solidGroup", + smooth=False, + ) -> Solid: self._vertices = [] self._surfaces = SurfaceCollection() self._validateLabels(solids) self._fillSurfacesAndVertices(solids) - solid = Solid(vertices=self._vertices, surfaces=self._surfaces, material=material, label=label, - primitive=primitives.POLYGON, smooth=smooth, labelOverride=False) + solid = Solid( + vertices=self._vertices, + surfaces=self._surfaces, + material=material, + label=label, + primitive=primitives.POLYGON, + smooth=smooth, + labelOverride=False, + ) solid._position = self._getCentroid(solids) solid.translateTo(position) return solid @@ -55,7 +68,9 @@ def _validateSolidLabel(self, solidLabel: str) -> str: if solidLabel not in surfaceLabelsSolidNames: return solidLabel idx = 2 - solidLabelsWithNumbers = ["_".join(surfaceLabel.split("_")[0:2]) for surfaceLabel in self._surfaces.surfaceLabels] + solidLabelsWithNumbers = [ + "_".join(surfaceLabel.split("_")[0:2]) for surfaceLabel in self._surfaces.surfaceLabels + ] while f"{solidLabel}_{idx}" in solidLabelsWithNumbers: idx += 1 return f"{solidLabel}_{idx}" diff --git a/pytissueoptics/scene/solids/sphere.py b/pytissueoptics/scene/solids/sphere.py index ef2510c2..82851689 100644 --- a/pytissueoptics/scene/solids/sphere.py +++ b/pytissueoptics/scene/solids/sphere.py @@ -4,23 +4,38 @@ class Sphere(Ellipsoid): """ - The Sphere is the 3D analog to the circle. Meshing a sphere requires an infinite number of vertices. - The position refers to the vector from global origin to its centroid. - The radius of the sphere will determine the outermost distance from its centroid. + The Sphere is the 3D analog to the circle. Meshing a sphere requires an infinite number of vertices. + The position refers to the vector from global origin to its centroid. + The radius of the sphere will determine the outermost distance from its centroid. - This class offers two possible methods to generate the sphere mesh. - - With Quads: Specify the number of separation lines on the vertical axis and the horizontal axis of the sphere. - - With Triangle: Specify the order of splitting. This will generate what is known as an IcoSphere. + This class offers two possible methods to generate the sphere mesh. + - With Quads: Specify the number of separation lines on the vertical axis and the horizontal axis of the sphere. + - With Triangle: Specify the order of splitting. This will generate what is known as an IcoSphere. """ - def __init__(self, radius: float = 1.0, order: int = 3, - position: Vector = Vector(0, 0, 0), material=None, - label: str = "sphere", primitive: str = primitives.DEFAULT, smooth: bool = True): - + def __init__( + self, + radius: float = 1.0, + order: int = 3, + position: Vector = Vector(0, 0, 0), + material=None, + label: str = "sphere", + primitive: str = primitives.DEFAULT, + smooth: bool = True, + ): self._radius = radius - super().__init__(a=radius, b=radius, c=radius, order=order, position=position, material=material, - label=label, primitive=primitive, smooth=smooth) + super().__init__( + a=radius, + b=radius, + c=radius, + order=order, + position=position, + material=material, + label=label, + primitive=primitive, + smooth=smooth, + ) @property def radius(self): @@ -30,8 +45,8 @@ def _computeQuadMesh(self): raise NotImplementedError def contains(self, *vertices: Vector) -> bool: - """ Only returns true if all vertices are inside the minimum radius of the sphere - (more restrictive with low order spheres). """ + """Only returns true if all vertices are inside the minimum radius of the sphere + (more restrictive with low order spheres).""" minRadius = self._getMinimumRadius() for vertex in vertices: relativeVertex = vertex - self.position diff --git a/pytissueoptics/scene/solids/stack/cuboidStacker.py b/pytissueoptics/scene/solids/stack/cuboidStacker.py index f5a3855d..fbe8a6a3 100644 --- a/pytissueoptics/scene/solids/stack/cuboidStacker.py +++ b/pytissueoptics/scene/solids/stack/cuboidStacker.py @@ -8,9 +8,10 @@ class CuboidStacker: - """ Internal helper class to prepare and assemble a cuboid stack from 2 cuboids. """ - SURFACE_KEYS = ['left', 'right', 'bottom', 'top', 'front', 'back'] - SURFACE_PAIRS = [('left', 'right'), ('bottom', 'top'), ('front', 'back')] + """Internal helper class to prepare and assemble a cuboid stack from 2 cuboids.""" + + SURFACE_KEYS = ["left", "right", "bottom", "top", "front", "back"] + SURFACE_PAIRS = [("left", "right"), ("bottom", "top"), ("front", "back")] def __init__(self): self._onCuboid = None @@ -20,13 +21,13 @@ def __init__(self): self._newInterfaceIndex = None self._stackAxis = None - def stack(self, onCuboid: 'Cuboid', otherCuboid: 'Cuboid', onSurface: str = 'top') -> StackResult: + def stack(self, onCuboid: "Cuboid", otherCuboid: "Cuboid", onSurface: str = "top") -> StackResult: self._initStacking(onCuboid, otherCuboid, onSurface) self._translateOtherCuboid() self._configureInterfaceMaterial() return self._assemble() - def _initStacking(self, onCuboid: 'Cuboid', otherCuboid: 'Cuboid', onSurfaceLabel: str): + def _initStacking(self, onCuboid: "Cuboid", otherCuboid: "Cuboid", onSurfaceLabel: str): assert onSurfaceLabel in self.SURFACE_KEYS, f"Available surfaces to stack on are: {self.SURFACE_KEYS}" self._onCuboid = onCuboid self._otherCuboid = otherCuboid @@ -49,8 +50,9 @@ def _assertNoDuplicateLabel(self): otherCuboidLayerLabels = [self._otherCuboid.getLabel()] for label in onCuboidLayerLabels: - assert label not in otherCuboidLayerLabels, f"Found duplicate layer label in stack: {label}. " \ - f"Please rename one of the layers." + assert label not in otherCuboidLayerLabels, ( + f"Found duplicate layer label in stack: {label}. Please rename one of the layers." + ) def _getSurfaceAxis(self, surfaceLabel: str) -> int: return max(axis if surfaceLabel in surfacePair else -1 for axis, surfacePair in enumerate(self.SURFACE_PAIRS)) @@ -67,13 +69,15 @@ def _validateShapeMatch(self): fixedAxes = [0, 1, 2] fixedAxes.remove(self._stackAxis) for fixedAxis in fixedAxes: - assert self._onCuboid.shape[fixedAxis] == self._otherCuboid.shape[fixedAxis], \ + assert self._onCuboid.shape[fixedAxis] == self._otherCuboid.shape[fixedAxis], ( "Stacking of mismatched surfaces is not supported." + ) def _translateOtherCuboid(self): relativePosition = [0, 0, 0] - relativePosition[self._stackAxis] = self._onCuboid.shape[self._stackAxis] / 2 + \ - self._otherCuboid.shape[self._stackAxis] / 2 + relativePosition[self._stackAxis] = ( + self._onCuboid.shape[self._stackAxis] / 2 + self._otherCuboid.shape[self._stackAxis] / 2 + ) relativePosition = Vector(*relativePosition) if not self._stackingTowardsPositive: @@ -82,7 +86,7 @@ def _translateOtherCuboid(self): self._otherCuboid.translateTo(self._onCuboid.position + relativePosition) def _configureInterfaceMaterial(self): - """ Set new interface material and remove duplicate surfaces. """ + """Set new interface material and remove duplicate surfaces.""" oppositeSurfaceLabels = self._getAllSpecificLabels(self._otherCuboid, self._otherSurfaceLabel) if len(oppositeSurfaceLabels) > 1: raise Exception("Ill-defined interface material: Can only stack another stack along its stacked axis.") @@ -93,13 +97,19 @@ def _configureInterfaceMaterial(self): self._onCuboid.setOutsideEnvironment(oppositeEnvironment, surfaceLabel) interfacePolygons.extend(self._onCuboid.getPolygons(surfaceLabel)) - self._otherCuboid.setPolygons(surfaceLabel=self._getSpecificLabel(self._otherCuboid, self._otherSurfaceLabel), - polygons=interfacePolygons) + self._otherCuboid.setPolygons( + surfaceLabel=self._getSpecificLabel(self._otherCuboid, self._otherSurfaceLabel), polygons=interfacePolygons + ) def _assemble(self) -> StackResult: - return StackResult(shape=self._getStackShape(), position=self._getStackPosition(), - vertices=self._getStackVertices(), surfaces=self._getStackSurfaces(), - primitive=self._onCuboid.primitive, layerLabels=self._getLayerLabels()) + return StackResult( + shape=self._getStackShape(), + position=self._getStackPosition(), + vertices=self._getStackVertices(), + surfaces=self._getStackSurfaces(), + primitive=self._onCuboid.primitive, + layerLabels=self._getLayerLabels(), + ) def _getStackShape(self) -> List[float]: stackShape = self._onCuboid.shape.copy() @@ -166,7 +176,7 @@ def _correctInterfaceIndexesOfOtherCuboid(self): oldInterfaceIndex = int(surfaceLabel.split(INTERFACE_KEY)[1]) surfaceLabels[i] = f"{INTERFACE_KEY}{oldInterfaceIndex + self._newInterfaceIndex + 1}" - def _getUpdatedLayerLabelsOf(self, cuboid: 'Cuboid') -> Dict[str, List[str]]: + def _getUpdatedLayerLabelsOf(self, cuboid: "Cuboid") -> Dict[str, List[str]]: newInterfaceLabel = f"{INTERFACE_KEY}{self._newInterfaceIndex}" onCuboidLayerLabels = cuboid.getLayerLabelMap() @@ -176,7 +186,7 @@ def _getUpdatedLayerLabelsOf(self, cuboid: 'Cuboid') -> Dict[str, List[str]]: stackedSurfaceLabel = self._onSurfaceLabel if cuboid is self._onCuboid else self._otherSurfaceLabel for layerLabel, surfaceLabels in onCuboidLayerLabels.items(): for surfaceLabel in surfaceLabels: - labelComponents = surfaceLabel.split('_') + labelComponents = surfaceLabel.split("_") if stackedSurfaceLabel in labelComponents[-1]: surfaceLabels.remove(surfaceLabel) surfaceLabels.append(newInterfaceLabel) @@ -189,7 +199,7 @@ def _getSpecificLabel(self, cuboid, generalSurfaceLabel): def _getAllSpecificLabels(self, cuboid, generalSurfaceLabel): labels = [] for surfaceLabel in cuboid.surfaceLabels: - labelComponents = surfaceLabel.split('_') + labelComponents = surfaceLabel.split("_") if generalSurfaceLabel in labelComponents[-1]: labels.append(surfaceLabel) return labels diff --git a/pytissueoptics/scene/solids/stack/stackResult.py b/pytissueoptics/scene/solids/stack/stackResult.py index 682d32f3..315293bd 100644 --- a/pytissueoptics/scene/solids/stack/stackResult.py +++ b/pytissueoptics/scene/solids/stack/stackResult.py @@ -6,7 +6,8 @@ @dataclass class StackResult: - """ Domain DTO to help creation of cuboid stacks. """ + """Domain DTO to help creation of cuboid stacks.""" + shape: List[float] position: Vector vertices: List[Vertex] diff --git a/pytissueoptics/scene/tests/__init__.py b/pytissueoptics/scene/tests/__init__.py index 5ad129a7..7d45aa88 100644 --- a/pytissueoptics/scene/tests/__init__.py +++ b/pytissueoptics/scene/tests/__init__.py @@ -14,12 +14,12 @@ def _visualOK(event, OK: bool = True): fig, ax = plt.subplots(1, 2) ax[0].imshow(plt.imread(expectedImagePath)) ax[1].imshow(plt.imread(currentImagePath)) - ax[0].set_title('Expected view') - ax[1].set_title('Current view') + ax[0].set_title("Expected view") + ax[1].set_title("Current view") axOK = plt.axes([0.7, 0.05, 0.1, 0.075]) axFAIL = plt.axes([0.81, 0.05, 0.1, 0.075]) - btnOK = plt.Button(axOK, 'OK') - btnFAIL = plt.Button(axFAIL, 'FAIL') + btnOK = plt.Button(axOK, "OK") + btnFAIL = plt.Button(axFAIL, "FAIL") btnOK.on_clicked(_visualOK) btnFAIL.on_clicked(lambda event: _visualOK(event, False)) plt.suptitle(title) diff --git a/pytissueoptics/scene/tests/geometry/testBoundingBox.py b/pytissueoptics/scene/tests/geometry/testBoundingBox.py index b21e5154..9d10f50a 100644 --- a/pytissueoptics/scene/tests/geometry/testBoundingBox.py +++ b/pytissueoptics/scene/tests/geometry/testBoundingBox.py @@ -39,8 +39,10 @@ def testGivenNewBBoxFromVertices_shouldDefineBoundingBoxAroundVertices(self): self.assertEqual(bbox.zLim, [0, 3.001]) def testGivenNewBBoxFromPolygons_shouldDefineBoundingBoxAroundPolygons(self): - polygons = [Polygon(vertices=[Vertex(0, 0, 0), Vertex(1, 2, 1), Vertex(1, -1, 1)]), - Polygon(vertices=[Vertex(0, 0, 0), Vertex(-1, -1, -1), Vertex(-2, -2, -3)])] + polygons = [ + Polygon(vertices=[Vertex(0, 0, 0), Vertex(1, 2, 1), Vertex(1, -1, 1)]), + Polygon(vertices=[Vertex(0, 0, 0), Vertex(-1, -1, -1), Vertex(-2, -2, -3)]), + ] bbox = BoundingBox.fromPolygons(polygons) self.assertEqual(bbox.xLim, [-2, 1]) diff --git a/pytissueoptics/scene/tests/geometry/testPolygon.py b/pytissueoptics/scene/tests/geometry/testPolygon.py index 5f96e7c1..96c4324d 100644 --- a/pytissueoptics/scene/tests/geometry/testPolygon.py +++ b/pytissueoptics/scene/tests/geometry/testPolygon.py @@ -10,13 +10,14 @@ def testGivenANewPolygon_shouldDefineItsNormal(self): def testGivenANewPolygonWithNormal_shouldUseProvidedNormalWithoutNormalizing(self): forcedNormal = Vector(7, 5, 3) - polygon = Polygon(vertices=[Vertex(0, 0, 0), Vertex(2, 0, 0), Vertex(2, 2, 0), Vertex(1, 1, 0)], - normal=forcedNormal) + polygon = Polygon( + vertices=[Vertex(0, 0, 0), Vertex(2, 0, 0), Vertex(2, 2, 0), Vertex(1, 1, 0)], normal=forcedNormal + ) self.assertEqual(forcedNormal, polygon.normal) def testGivenANewPolygon_shouldDefineItsCentroid(self): polygon = Polygon(vertices=[Vertex(0, 0, 1), Vertex(2, 0, 0), Vertex(2, 2, 0), Vertex(1, 1, 0)]) - self.assertEqual(Vector(5/4, 3/4, 1/4), polygon.centroid) + self.assertEqual(Vector(5 / 4, 3 / 4, 1 / 4), polygon.centroid) def testGivenANewPolygon_whenModifyingVertexAndResetBoundingBox_shouldChangeBbox(self): triangle = Polygon(vertices=[Vertex(0, 0, 0), Vertex(2, 0, 0), Vertex(2, 2, 0), Vertex(1, 4, 2)]) diff --git a/pytissueoptics/scene/tests/geometry/testTriangle.py b/pytissueoptics/scene/tests/geometry/testTriangle.py index 65f900cf..a5a6bae7 100644 --- a/pytissueoptics/scene/tests/geometry/testTriangle.py +++ b/pytissueoptics/scene/tests/geometry/testTriangle.py @@ -10,7 +10,7 @@ def testGivenANewTriangle_shouldDefineItsNormal(self): def testGivenANewTriangle_shouldDefineItsCentroid(self): triangle = Triangle(v1=Vertex(0, 0, 1), v2=Vertex(2, 0, 0), v3=Vertex(2, 2, 0)) - self.assertEqual(Vector(4/3, 2/3, 1/3), triangle.centroid) + self.assertEqual(Vector(4 / 3, 2 / 3, 1 / 3), triangle.centroid) def testGivenANewTriangle_whenModifyingVertex_resetBoundingBoxShouldChangeBbox(self): triangle = Triangle(v1=Vertex(0, 0, 0), v2=Vertex(2, 0, 0), v3=Vertex(2, 2, 0)) @@ -21,4 +21,3 @@ def testGivenANewTriangle_whenModifyingVertex_resetBoundingBoxShouldChangeBbox(s newBbox = triangle.bbox self.assertNotEqual(oldBbox, newBbox) - diff --git a/pytissueoptics/scene/tests/geometry/testUtils.py b/pytissueoptics/scene/tests/geometry/testUtils.py index 053870dd..ffa2c41b 100644 --- a/pytissueoptics/scene/tests/geometry/testUtils.py +++ b/pytissueoptics/scene/tests/geometry/testUtils.py @@ -105,7 +105,9 @@ def testGivenTwoVectorsOnABaseAxisPlaneWithLessThan180DegreesBetween_shouldRetur def _generateAxisAlignedTestCases(self): testCases = [] for axis in [Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)]: - for (rotationAxis, rotationAxisKey) in zip([Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)], ['xTheta', 'yTheta', 'zTheta']): + for rotationAxis, rotationAxisKey in zip( + [Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)], ["xTheta", "yTheta", "zTheta"] + ): if axis == rotationAxis: continue for angle in [-180, -150, -70, -10, 10, 70, 150, 180]: diff --git a/pytissueoptics/scene/tests/geometry/testVector.py b/pytissueoptics/scene/tests/geometry/testVector.py index f4b5aece..0ac478fe 100644 --- a/pytissueoptics/scene/tests/geometry/testVector.py +++ b/pytissueoptics/scene/tests/geometry/testVector.py @@ -6,7 +6,6 @@ class TestVector(unittest.TestCase): - def setUp(self): self.vector = Vector(x=1, y=2.4, z=0.5) @@ -127,9 +126,14 @@ def testWhenGetAnyOrthogonal_shouldReturnANewOrthogonalVector(self): self.assertEqual(0, vector.dot(orthogonalVector)) self.assertNotEqual(0, orthogonalVector.getNorm()) - edgeCaseVectors = [Vector(-1, 0, 0), Vector(1, 0, 0), - Vector(0, 1, 0), Vector(0, -1, 0), - Vector(0, 0, 1), Vector(0, 0, -1)] + edgeCaseVectors = [ + Vector(-1, 0, 0), + Vector(1, 0, 0), + Vector(0, 1, 0), + Vector(0, -1, 0), + Vector(0, 0, 1), + Vector(0, 0, -1), + ] for vector in edgeCaseVectors: orthogonalVector = vector.getAnyOrthogonal() self.assertEqual(0, vector.dot(orthogonalVector)) diff --git a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py index 8b9c7920..f85428c6 100644 --- a/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py +++ b/pytissueoptics/scene/tests/intersection/benchmarkIntersectionFinder.py @@ -35,16 +35,19 @@ from pytissueoptics.scene.tree.treeConstructor.binary.splitTreeAxesConstructor import SplitThreeAxesConstructor from pytissueoptics.scene.viewer import MayaviViewer -pandas.set_option('display.max_columns', 20) -pandas.set_option('display.width', 1200) +pandas.set_option("display.max_columns", 20) +pandas.set_option("display.width", 1200) class IntersectionFinderBenchmark: def __init__(self, rayAmount=10000, maxDepth=100, minLeafSize=0, factor=10, constructors=None, displayViewer=False): self.scenes = self._getScenes() if constructors is None: - self.constructors = [NoSplitOneAxisConstructor(), NoSplitThreeAxesConstructor(), - SplitThreeAxesConstructor()] + self.constructors = [ + NoSplitOneAxisConstructor(), + NoSplitThreeAxesConstructor(), + SplitThreeAxesConstructor(), + ] else: self.constructors = constructors self.rayAmount = rayAmount @@ -56,8 +59,21 @@ def __init__(self, rayAmount=10000, maxDepth=100, minLeafSize=0, factor=10, cons self.simpleTraversalTime = [] self.count = 0 self.stats = pandas.DataFrame( - columns=["scene", "polygon count", "finder polycount", "algo name", "build time", "fast time", "total time", - "node count", "leaf count", "avg leaf depth", "avg leaf size", "improvement"]) + columns=[ + "scene", + "polygon count", + "finder polycount", + "algo name", + "build time", + "fast time", + "total time", + "node count", + "leaf count", + "avg leaf depth", + "avg leaf size", + "improvement", + ] + ) @staticmethod def _getScenes(): @@ -74,8 +90,9 @@ def _getScenes(): scene4 = DiagonallyAlignedSpheres() return [scene00, scene01, scene02, scene03, scene04, scene05, scene06, scene1, scene2, scene3, scene4] - def runValidation(self, resolution: int = 100, constructors: List[TreeConstructor] = None, - displayFailed: bool = True): + def runValidation( + self, resolution: int = 100, constructors: List[TreeConstructor] = None, displayFailed: bool = True + ): print(f"{('=' * 125):^125}") print( f"{str('name'):^20}" @@ -86,18 +103,29 @@ def runValidation(self, resolution: int = 100, constructors: List[TreeConstructo f" - {str('leaves'):^10}" f" - {str('depth'):^10}" f" - {str('missed'):^10}" - f" - {str('validated'):^10}") + f" - {str('validated'):^10}" + ) print(f"{('=' * 125):^125}") if constructors is not None: self.constructors = constructors - source = UniformRaySource(position=Vector(0, 4, 0), direction=Vector(0, 0, -1), xTheta=360, yTheta=90, - xResolution=int(5.12 * resolution), yResolution=int(2.56 * resolution)) + source = UniformRaySource( + position=Vector(0, 4, 0), + direction=Vector(0, 0, -1), + xTheta=360, + yTheta=90, + xResolution=int(5.12 * resolution), + yResolution=int(2.56 * resolution), + ) referenceMissedRays = self._runValidationReference(source, display=displayFailed) for constructor in self.constructors: self._runValidationForConstructor(constructor, referenceMissedRays, source, display=displayFailed) - def _runValidationReference(self, source: RaySource, display: bool = True, ): + def _runValidationReference( + self, + source: RaySource, + display: bool = True, + ): logger = Logger() scene = self.scenes[7] intersectionFinder = SimpleIntersectionFinder(scene) @@ -122,20 +150,23 @@ def _runValidationReference(self, source: RaySource, display: bool = True, ): f" - {str(' '):^10}" f" - {str(' '):^10}" f" - {missedRays:^10}" - f" - {str('REFERENCE'):^10}") + f" - {str('REFERENCE'):^10}" + ) if display: viewer = MayaviViewer() viewer.addLogger(logger) viewer.show() return missedRays - def _runValidationForConstructor(self, constructor: TreeConstructor, referenceMissed: int, source: RaySource, - display: bool = True): + def _runValidationForConstructor( + self, constructor: TreeConstructor, referenceMissed: int, source: RaySource, display: bool = True + ): logger = Logger() scene = self.scenes[7] t0 = time.time() - intersectionFinder = FastIntersectionFinder(scene, constructor=constructor, maxDepth=self.maxDepth, - minLeafSize=self.minLeafSize) + intersectionFinder = FastIntersectionFinder( + scene, constructor=constructor, maxDepth=self.maxDepth, minLeafSize=self.minLeafSize + ) t1 = time.time() constructionTime = t1 - t0 @@ -158,9 +189,8 @@ def _runValidationForConstructor(self, constructor: TreeConstructor, referenceMi f" - {len(partition.getLeafPolygons()):^10}" f" - {partition.getNodeCount():^10}" f" - {partition.getLeafCount():^10}", - f" - {partition.getAverageDepth():^10.2f}" - f" - {missedRays:^10}" - f" - {missedRays == referenceMissed:^10}") + f" - {partition.getAverageDepth():^10.2f} - {missedRays:^10} - {missedRays == referenceMissed:^10}", + ) if display and missedRays != referenceMissed: viewer = MayaviViewer() viewer.addLogger(logger) @@ -182,8 +212,9 @@ def runBenchmarkForScene(self, scene: Scene): def runReferenceBenchmarkForScene(self, scene: Scene): self.count += 1 - source = RandomPositionAndOrientationRaySource(int(self.rayAmount / self.factor), - scene.getBoundingBox().xyzLimits, position=Vector(0, 0, 0)) + source = RandomPositionAndOrientationRaySource( + int(self.rayAmount / self.factor), scene.getBoundingBox().xyzLimits, position=Vector(0, 0, 0) + ) intersectionFinder = SimpleIntersectionFinder(scene) startTime = time.time() for ray in source.rays: @@ -194,16 +225,19 @@ def runReferenceBenchmarkForScene(self, scene: Scene): print( f"{(self.count * 100) / (len(self.scenes) * (len(self.constructors) + 1)):.2f}% - {intersectionFinder.__class__.__name__:^12.15s}" f" - {traversalTime * self.factor:.2f}s" - f" - Improvement 1.00x") + f" - Improvement 1.00x" + ) self._saveSimpleStats(scene, intersectionFinder, traversalTime * self.factor) def runBenchmarkForSceneWithConstructor(self, scene: Scene, constructor: TreeConstructor): - source = RandomPositionAndOrientationRaySource(self.rayAmount, scene.getBoundingBox().xyzLimits, - position=Vector(0,0,0)) + source = RandomPositionAndOrientationRaySource( + self.rayAmount, scene.getBoundingBox().xyzLimits, position=Vector(0, 0, 0) + ) self.runBenchmarkForSceneWithConstructorAndSource(scene, constructor, source) - def runBenchmarkForSceneWithConstructorAndSource(self, scene: Scene, constructor: TreeConstructor, - source: RaySource): + def runBenchmarkForSceneWithConstructorAndSource( + self, scene: Scene, constructor: TreeConstructor, source: RaySource + ): self.count += 1 startTime = time.time() intersectionFinder = FastIntersectionFinder(scene, constructor, self.maxDepth, self.minLeafSize) @@ -217,43 +251,52 @@ def runBenchmarkForSceneWithConstructorAndSource(self, scene: Scene, constructor print( f"{(self.count * 100) / (len(self.scenes) * (len(self.constructors) + 1)):.2f}% - {constructor.__class__.__name__:^12.15s}" f" - {traversalTime:.2f}s" - f" - Improvement {((self.simpleTraversalTime[-1]) / traversalTime):.2f}x") + f" - Improvement {((self.simpleTraversalTime[-1]) / traversalTime):.2f}x" + ) self._saveFastStats(scene, intersectionFinder, traversalTime, buildTime) def _saveSimpleStats(self, scene: Scene, intersectionFinder: SimpleIntersectionFinder, traversalTime: float): - self.stats.loc[self.stats.shape[0]] = [f"{scene.__class__.__name__}", f"{len(scene.getPolygons()):^12}", - "-", - f"{intersectionFinder.__class__.__name__:^12.15s}", - "-", - f"{traversalTime:^12.2f}", - f"{traversalTime:^12.2f}", - "-", - "-", - "-", - "-", - "-"] + self.stats.loc[self.stats.shape[0]] = [ + f"{scene.__class__.__name__}", + f"{len(scene.getPolygons()):^12}", + "-", + f"{intersectionFinder.__class__.__name__:^12.15s}", + "-", + f"{traversalTime:^12.2f}", + f"{traversalTime:^12.2f}", + "-", + "-", + "-", + "-", + "-", + ] - def _saveFastStats(self, scene: Scene, intersectionFinder: FastIntersectionFinder, traversalTime: float, - buildTime: float): + def _saveFastStats( + self, scene: Scene, intersectionFinder: FastIntersectionFinder, traversalTime: float, buildTime: float + ): partition = intersectionFinder._partition self.partitions[-1].append(partition) - self.stats.loc[self.stats.shape[0]] = [f"{scene.__class__.__name__}", f"{len(scene.getPolygons()):^12}", - f"{len(partition.getLeafPolygons()):^12}", - f"{partition._constructor.__class__.__name__:^12.15s}", - f"{buildTime:^12.2f}", - f"{traversalTime:^12.2f}", - f"{buildTime + traversalTime:^12.2f}", - f"{partition.getNodeCount():^12}", - f"{partition.getLeafCount():^12}", - f"{partition.getAverageDepth():^12.2f}", - f"{partition.getAverageLeafSize():^12.2f}", - f"{((self.simpleTraversalTime[-1]) / traversalTime):.1f}"] + self.stats.loc[self.stats.shape[0]] = [ + f"{scene.__class__.__name__}", + f"{len(scene.getPolygons()):^12}", + f"{len(partition.getLeafPolygons()):^12}", + f"{partition._constructor.__class__.__name__:^12.15s}", + f"{buildTime:^12.2f}", + f"{traversalTime:^12.2f}", + f"{buildTime + traversalTime:^12.2f}", + f"{partition.getNodeCount():^12}", + f"{partition.getLeafCount():^12}", + f"{partition.getAverageDepth():^12.2f}", + f"{partition.getAverageLeafSize():^12.2f}", + f"{((self.simpleTraversalTime[-1]) / traversalTime):.1f}", + ] def displayStats(self): print(self.stats) - def displayBenchmarkTreeResults(self, objectsDisplay: bool = True, scenes: List[Scene] = None, - objectsOpacity: float = 0.5): + def displayBenchmarkTreeResults( + self, objectsDisplay: bool = True, scenes: List[Scene] = None, objectsOpacity: float = 0.5 + ): viewer = MayaviViewer() if scenes is None: scenes = self.scenes @@ -261,8 +304,7 @@ def displayBenchmarkTreeResults(self, objectsDisplay: bool = True, scenes: List[ for partition in self.partitions[j]: bBoxes = self._getCuboidsFromBBoxes(partition.getLeafBoundingBoxes()) if objectsDisplay: - viewer.add(*scene.getSolids(), representation="surface", lineWidth=0.05, - opacity=objectsOpacity) + viewer.add(*scene.getSolids(), representation="surface", lineWidth=0.05, opacity=objectsOpacity) viewer.add(*bBoxes, representation="wireframe", lineWidth=3, color=(1, 0, 0), opacity=0.7) viewer.show() viewer.clear() @@ -305,10 +347,15 @@ def _createRays(self): direction_ys = np.random.uniform(-1, 1, self._amount) direction_zs = np.random.uniform(-1, 1, self._amount) for i in range(self._amount): - self._rays.append(Ray(Vector(origin_xs[i], origin_ys[i], origin_zs[i]), Vector(direction_xs[i], direction_ys[i], direction_zs[i]))) + self._rays.append( + Ray( + Vector(origin_xs[i], origin_ys[i], origin_zs[i]), + Vector(direction_xs[i], direction_ys[i], direction_zs[i]), + ) + ) -if __name__ == '__main__': +if __name__ == "__main__": benchmark = IntersectionFinderBenchmark(rayAmount=10, maxDepth=25, minLeafSize=6, factor=10) benchmark.constructors = [NoSplitThreeAxesConstructor()] # benchmark.runBenchmark() diff --git a/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py b/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py index b4d1c4c6..ff7fa459 100644 --- a/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py +++ b/pytissueoptics/scene/tests/intersection/testBoundingBoxIntersect.py @@ -54,7 +54,7 @@ def testGivenRayInsideBox_shouldReturnRayOrigin(self): ray = Ray(rayOrigin, rayDirection) intersection = self.intersectStrategy.getIntersection(ray, box) - + self.assertEqual(ray.origin, intersection) def testGivenRayLengthShorterThanBoxIntersection_shouldReturnNone(self): diff --git a/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py b/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py index c9343122..50e86bfe 100644 --- a/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py +++ b/pytissueoptics/scene/tests/intersection/testIntersectionFinder.py @@ -90,7 +90,9 @@ def testGivenRayIsIntersectingMultipleSolids_shouldReturnClosestIntersection(sel self.assertEqual(0.5, intersection.position.y) self.assertAlmostEqual(4, intersection.position.z) - def testGivenRayThatFirstOnlyIntersectsWithAnotherSolidBoundingBoxBeforeIntersectingASolid_shouldFindIntersection(self): + def testGivenRayThatFirstOnlyIntersectsWithAnotherSolidBoundingBoxBeforeIntersectingASolid_shouldFindIntersection( + self, + ): direction = Vector(0, 0.9, 1) ray = Ray(origin=Vector(0, 0, 0), direction=direction) solidMissed = Sphere(radius=1, order=1, position=Vector(0, 0, 1.9)) @@ -212,7 +214,9 @@ def testGivenSmoothSolid_shouldSmoothTheIntersectionNormal(self): self.assertIsNotNone(intersection) self.assertEqual(expectedNormal, intersection.normal) - def testGivenSmoothSolid_shouldNotSmoothTheIntersectionNormalIfItChangesTheSignOfTheDotProductWithTheRayDirection(self): + def testGivenSmoothSolid_shouldNotSmoothTheIntersectionNormalIfItChangesTheSignOfTheDotProductWithTheRayDirection( + self, + ): ray = Ray(origin=Vector(0, 0.955, -2), direction=Vector(0, 0.02, 1)) solid = Sphere(radius=1, order=1) @@ -248,7 +252,7 @@ def setUp(self) -> None: SimpleIntersectionFinder(scene), FastIntersectionFinder(scene, constructor=NoSplitOneAxisConstructor(), maxDepth=3), FastIntersectionFinder(scene, constructor=NoSplitThreeAxesConstructor(), maxDepth=3), - FastIntersectionFinder(scene, constructor=SplitThreeAxesConstructor(), maxDepth=3) + FastIntersectionFinder(scene, constructor=SplitThreeAxesConstructor(), maxDepth=3), ] def _getSubTestTag(self, intersectionFinder: IntersectionFinder): diff --git a/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py b/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py index 3d1c925b..f802b57d 100644 --- a/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py +++ b/pytissueoptics/scene/tests/intersection/testPolygonIntersect.py @@ -20,7 +20,7 @@ def testGivenIntersectingRayAndPolygon_shouldReturnIntersectionPosition(self): rayDirection.normalize() ray = Ray(rayOrigin, rayDirection) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertEqual(0.45, intersection.x) self.assertEqual(0.25, intersection.y) @@ -32,7 +32,7 @@ def testGivenNonIntersectingRayAndPolygon_shouldReturnNone(self): rayDirection.normalize() ray = Ray(rayOrigin, rayDirection) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNone(intersection) @@ -42,7 +42,7 @@ def testGivenLineIntersectingRayAndPolygon_shouldReturnNone(self): rayDirection.normalize() ray = Ray(rayOrigin, rayDirection) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNone(intersection) @@ -52,7 +52,7 @@ def testGivenRayShorterThanPolygonIntersectionDistance_shouldReturnNone(self): rayDirection.normalize() ray = Ray(rayOrigin, rayDirection, length=1.8) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNone(intersection) @@ -62,7 +62,7 @@ def testGivenRayLongerThanPolygonIntersectionDistance_shouldReturnIntersectionPo rayDirection.normalize() ray = Ray(rayOrigin, rayDirection, length=2.2) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNotNone(intersection) @@ -76,7 +76,7 @@ def testGivenRayLandsInEpsilonRegionBeforePolygon_shouldStillReturnIntersectionP rayDirection.normalize() ray = Ray(rayOrigin, rayDirection, length=2 - MollerTrumboreIntersect.EPS_CATCH / 2) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNotNone(intersection) @@ -90,6 +90,6 @@ def testGivenRayLandsBeforeEpsilonRegionOfPolygon_shouldReturnNone(self): rayDirection.normalize() ray = Ray(rayOrigin, rayDirection, length=2 - MollerTrumboreIntersect.EPS_CATCH * 1.1) - for poly in ([self.triangle, self.quad, self.polygon]): + for poly in [self.triangle, self.quad, self.polygon]: intersection = self.intersectStrategy.getIntersection(ray, poly) self.assertIsNone(intersection) diff --git a/pytissueoptics/scene/tests/intersection/testRay.py b/pytissueoptics/scene/tests/intersection/testRay.py index f5c4f368..a630a102 100644 --- a/pytissueoptics/scene/tests/intersection/testRay.py +++ b/pytissueoptics/scene/tests/intersection/testRay.py @@ -8,4 +8,4 @@ class TestRay(unittest.TestCase): def testGivenNewRay_shouldNormalizeItsDirection(self): ray = Ray(origin=Vector(0, 0, 0), direction=Vector(2, 2, 0), length=10) - self.assertEqual(Vector(math.sqrt(2)/2, math.sqrt(2)/2, 0), ray.direction) + self.assertEqual(Vector(math.sqrt(2) / 2, math.sqrt(2) / 2, 0), ray.direction) diff --git a/pytissueoptics/scene/tests/loader/parsers/testOBJParser.py b/pytissueoptics/scene/tests/loader/parsers/testOBJParser.py index 0f0200cb..87228c4c 100644 --- a/pytissueoptics/scene/tests/loader/parsers/testOBJParser.py +++ b/pytissueoptics/scene/tests/loader/parsers/testOBJParser.py @@ -27,8 +27,16 @@ def testWithTestCube_shouldGiveCorrectAmountOfVertices(self): def testWithTestCube_shouldGiveCorrectVertices(self): parser = OBJParser(self._filepath("testCubeQuads.obj"), showProgress=False) - vertices = [[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], - [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] + vertices = [ + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, 0.5, -0.5], + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, 0.5], + ] self.assertCountEqual(vertices, parser.vertices) def testWithTestCube_shouldGiveCorrectAmountOfNormals(self): diff --git a/pytissueoptics/scene/tests/loader/testLoadSolid.py b/pytissueoptics/scene/tests/loader/testLoadSolid.py index f924d5d6..6ce1c3c2 100644 --- a/pytissueoptics/scene/tests/loader/testLoadSolid.py +++ b/pytissueoptics/scene/tests/loader/testLoadSolid.py @@ -1,4 +1,3 @@ - import os import unittest @@ -13,8 +12,9 @@ def testWhenLoadingFromOBJ_shouldReturnASolidOfThisFile(self): self.assertIsNotNone(solid) self.assertEqual(13, len(solid.getPolygons())) - self.assertEqual(["cube_front", "cube_back", "cube_left", "cube_right", "cube_top", "cube_bottom"], - solid.surfaceLabels) + self.assertEqual( + ["cube_front", "cube_back", "cube_left", "cube_right", "cube_top", "cube_bottom"], solid.surfaceLabels + ) def _filepath(self, fileName) -> str: return os.path.join(self.TEST_DIRECTORY, "parsers", "objFiles", fileName) diff --git a/pytissueoptics/scene/tests/loader/testLoader.py b/pytissueoptics/scene/tests/loader/testLoader.py index aab4beb6..7b203118 100644 --- a/pytissueoptics/scene/tests/loader/testLoader.py +++ b/pytissueoptics/scene/tests/loader/testLoader.py @@ -33,8 +33,9 @@ def testWhenLoadingMultiPolygonObject_shouldSplitInTriangles(self): def testWhenLoadingMultiGroupObject_shouldSplitCorrectGroups(self): loader = Loader() solids = loader.load(self._filepath("testCubeTrianglesMulti.obj"), showProgress=False) - self.assertCountEqual(["cube_front", "cube_back", "cube_bottom", - "cube_top", "cube_right", "cube_left"], solids[0].surfaceLabels) + self.assertCountEqual( + ["cube_front", "cube_back", "cube_bottom", "cube_top", "cube_right", "cube_left"], solids[0].surfaceLabels + ) def testWhenLoadingMultiGroupObject_shouldHaveCorrectAmountOfElementsPerGroup(self): loader = Loader() diff --git a/pytissueoptics/scene/tests/logger/testListArrayContainer.py b/pytissueoptics/scene/tests/logger/testListArrayContainer.py index 15048abf..e54d4806 100644 --- a/pytissueoptics/scene/tests/logger/testListArrayContainer.py +++ b/pytissueoptics/scene/tests/logger/testListArrayContainer.py @@ -50,8 +50,9 @@ def testWhenExtending_shouldOnlyExtendThisContainer(self): self.listArrayContainer.extend(self.otherListArrayContainer) - self.assertTrue(np.array_equal(np.array([[1, 2, 3], [7, 8, 9], [4, 5, 6], [10, 11, 12]]), - self.listArrayContainer.getData())) + self.assertTrue( + np.array_equal(np.array([[1, 2, 3], [7, 8, 9], [4, 5, 6], [10, 11, 12]]), self.listArrayContainer.getData()) + ) self.assertTrue(np.array_equal(np.array([[7, 8, 9], [10, 11, 12]]), self.otherListArrayContainer.getData())) def testWhenExtendingAnEmptyContainerWithAnother_shouldNotExtendListDataWithAReferenceOfTheOthersListData(self): diff --git a/pytissueoptics/scene/tests/scene/testScene.py b/pytissueoptics/scene/tests/scene/testScene.py index 7e9164f6..b9569e32 100644 --- a/pytissueoptics/scene/tests/scene/testScene.py +++ b/pytissueoptics/scene/tests/scene/testScene.py @@ -65,8 +65,9 @@ def testWhenAddingASolidInsideMultipleOtherSolids_shouldUpdateOutsideMaterialOfT OUTSIDE_SOLID = self.makeSolidWith(BoundingBox([1, 4], [1, 4], [1, 4]), name="Outside-solid") self.scene.add(OUTSIDE_SOLID) - TOPMOST_OUTSIDE_SOLID = self.makeSolidWith(BoundingBox([0, 5], [0, 5], [0, 5]), - contains=True, name="Topmost-outside-solid") + TOPMOST_OUTSIDE_SOLID = self.makeSolidWith( + BoundingBox([0, 5], [0, 5], [0, 5]), contains=True, name="Topmost-outside-solid" + ) self.scene.add(TOPMOST_OUTSIDE_SOLID) SOLID = self.makeSolidWith(BoundingBox([2, 3], [2, 3], [2, 3])) @@ -77,8 +78,9 @@ def testWhenAddingASolidInsideMultipleOtherSolids_shouldUpdateOutsideMaterialOfT verify(SOLID).setOutsideEnvironment(OUTSIDE_SOLID.getEnvironment()) def testWhenAddingASolidOverMultipleOtherSolids_shouldUpdateOutsideMaterialOfTheTopMostSolid(self): - TOPMOST_INSIDE_SOLID = self.makeSolidWith(BoundingBox([0, 5], [0, 5], [0, 5]), contains=True, - name="Topmost-inside-solid") + TOPMOST_INSIDE_SOLID = self.makeSolidWith( + BoundingBox([0, 5], [0, 5], [0, 5]), contains=True, name="Topmost-inside-solid" + ) self.scene.add(TOPMOST_INSIDE_SOLID) INSIDE_SOLID = self.makeSolidWith(BoundingBox([1, 4], [1, 4], [1, 4]), name="Inside-solid") @@ -89,7 +91,9 @@ def testWhenAddingASolidOverMultipleOtherSolids_shouldUpdateOutsideMaterialOfThe verify(TOPMOST_INSIDE_SOLID).setOutsideEnvironment(SOLID.getEnvironment()) - def testWhenAddingASolidThatFitsInsideOneButAlsoContainsOne_shouldUpdateOutsideMaterialOfThisSolidAndTheOneInside(self): + def testWhenAddingASolidThatFitsInsideOneButAlsoContainsOne_shouldUpdateOutsideMaterialOfThisSolidAndTheOneInside( + self, + ): INSIDE_SOLID = self.makeSolidWith(BoundingBox([2, 3], [2, 3], [2, 3]), name="Inside-solid") self.scene.add(INSIDE_SOLID) @@ -108,7 +112,7 @@ def testWhenAddingASolidInsideASolidStack_shouldUpdateOutsideMaterialOfThisSolid frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - CUBOID_STACK = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + CUBOID_STACK = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") self.scene.add(CUBOID_STACK, position=Vector(0, 0, 0)) SOLID_INSIDE_MIDDLE_LAYER = Cuboid(0.9, 0.9, 0.9, material="insideMaterial", label="insideSolid") @@ -118,13 +122,15 @@ def testWhenAddingASolidInsideASolidStack_shouldUpdateOutsideMaterialOfThisSolid anyPolygonOfSolidInside = SOLID_INSIDE_MIDDLE_LAYER.getPolygons()[0] self.assertEqual(anyPolygonOfSolidInside.outsideEnvironment, middleLayer.getEnvironment()) - def testWhenAddingASolidInsideASolidStackThatWasMovedAndRotated_shouldStillUpdateOutsideMaterialOfThisSolidToTheProperStackLayer(self): + def testWhenAddingASolidInsideASolidStackThatWasMovedAndRotated_shouldStillUpdateOutsideMaterialOfThisSolidToTheProperStackLayer( + self, + ): positionOffset = Vector(0, 10, 0) - rotations = {'yTheta': 35} + rotations = {"yTheta": 35} frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - CUBOID_STACK = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + CUBOID_STACK = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") CUBOID_STACK.translateTo(positionOffset) CUBOID_STACK.rotate(**rotations) self.scene.add(CUBOID_STACK) @@ -145,7 +151,7 @@ def testWhenAddingAStackOverAnotherSolid_shouldUpdateOutsideMaterialOfTheOtherSo frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - CUBOID_STACK = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + CUBOID_STACK = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") self.scene.add(CUBOID_STACK, position=Vector(0, 0, 0)) anyPolygonOfSolidInside = SOLID_INSIDE_FRONT_LAYER.getPolygons()[0] @@ -155,7 +161,7 @@ def testWhenAddingASolidThatPartiallyOverlapsWithInternalStackLayers_shouldNotAd frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - CUBOID_STACK = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + CUBOID_STACK = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") self.scene.add(CUBOID_STACK, position=Vector(0, 0, 0)) SOLID = Cuboid(0.9, 1.1, 0.9, material="outsideMaterial", label="outsideSolid") @@ -242,7 +248,7 @@ def testWhenGetEnvironmentWithPositionContainedInAStack_shouldReturnEnvironmentO frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - stack = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + stack = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") self.scene.add(stack, position=Vector(0, 0, 0)) frontEnv = self.scene.getEnvironmentAt(Vector(0, 0, -1)) @@ -294,7 +300,7 @@ def testWhenGetSolidFromInternalStackLayerLabel_shouldReturnTheWholeStack(self): frontLayer = Cuboid(1, 1, 1, material="frontMaterial", label="frontLayer") middleLayer = Cuboid(1, 1, 1, material="middleMaterial", label="middleLayer") backLayer = Cuboid(1, 1, 1, material="backMaterial", label="backLayer") - stack = backLayer.stack(middleLayer, 'front').stack(frontLayer, 'front') + stack = backLayer.stack(middleLayer, "front").stack(frontLayer, "front") self.scene.add(stack) returnedSolid = self.scene.getSolid("frontLayer") @@ -317,7 +323,7 @@ def testGivenASceneWithAStack_whenGetSolidLabels_shouldOnlyIncludeInternalStackL internalLayers = ["Layer1", "Layer2"] layer1 = Cuboid(1, 1, 1, label=internalLayers[0]) layer2 = Cuboid(1, 1, 1, label=internalLayers[1]) - stack = layer1.stack(layer2, 'front') + stack = layer1.stack(layer2, "front") self.scene.add(stack) labels = self.scene.getSolidLabels() @@ -351,7 +357,7 @@ def testWhenGetSurfaceLabelsOfAStack_shouldReturnTheSurfaceLabelsForAllItsLayers internalLayers = ["Layer1", "Layer2"] layer1 = Cuboid(1, 1, 1, label=internalLayers[0]) layer2 = Cuboid(1, 1, 1, label=internalLayers[1]) - stack = layer1.stack(layer2, 'front', stackLabel=STACK_LABEL) + stack = layer1.stack(layer2, "front", stackLabel=STACK_LABEL) self.scene.add(stack) labels = self.scene.getSurfaceLabels(STACK_LABEL) @@ -362,13 +368,13 @@ def testWhenGetSurfaceLabelsOfAStackLayer_shouldReturnTheSurfaceLabelsOfThisLaye internalLayers = ["Layer1", "Layer2"] layer1 = Cuboid(1, 1, 1, label=internalLayers[0]) layer2 = Cuboid(1, 1, 1, label=internalLayers[1]) - stack = layer1.stack(layer2, 'front') + stack = layer1.stack(layer2, "front") self.scene.add(stack) labels = self.scene.getSurfaceLabels(internalLayers[0]) self.assertEqual(6, len(labels)) - self.assertTrue(INTERFACE_KEY+"0" in labels) + self.assertTrue(INTERFACE_KEY + "0" in labels) def testGivenNoContainedSolids_shouldHaveNoContainedLabels(self): labels = self.scene.getContainedSolidLabels("Solid") @@ -393,7 +399,7 @@ def testWhenGetMaterials_shouldReturnAllMaterialsPresentInTheScene(self): layerMaterials = ["Material1", "Material2"] frontLayer = Cuboid(1, 1, 1, material=layerMaterials[0], label="Front Layer") middleLayer = Cuboid(1, 1, 1, material=layerMaterials[1], label="Middle Layer") - stack = middleLayer.stack(frontLayer, 'front') + stack = middleLayer.stack(frontLayer, "front") self.scene.add(stack) materials = self.scene.getMaterials() diff --git a/pytissueoptics/scene/tests/shader/testSmoothing.py b/pytissueoptics/scene/tests/shader/testSmoothing.py index 8f37c678..ed9d9e6f 100644 --- a/pytissueoptics/scene/tests/shader/testSmoothing.py +++ b/pytissueoptics/scene/tests/shader/testSmoothing.py @@ -8,10 +8,8 @@ class TestSmoothing(unittest.TestCase): def setUp(self): # Create a XY square polygon with normals pointing outwards along the Z axis. - vertices = [Vertex(0, 0, 0), Vertex(1, 0, 0), - Vertex(1, 1, 0), Vertex(0, 1, 0)] - normals = [Vector(-1, -1, 1), Vector(1, -1, 1), - Vector(1, 1, 1), Vector(-1, 1, 1)] + vertices = [Vertex(0, 0, 0), Vertex(1, 0, 0), Vertex(1, 1, 0), Vertex(0, 1, 0)] + normals = [Vector(-1, -1, 1), Vector(1, -1, 1), Vector(1, 1, 1), Vector(-1, 1, 1)] for i in range(4): normals[i].normalize() vertices[i].normal = normals[i] diff --git a/pytissueoptics/scene/tests/solids/testCone.py b/pytissueoptics/scene/tests/solids/testCone.py index 1868a460..a926963a 100644 --- a/pytissueoptics/scene/tests/solids/testCone.py +++ b/pytissueoptics/scene/tests/solids/testCone.py @@ -11,10 +11,16 @@ def testWhenContainsWithVerticesThatAreAllInsideTheCone_shouldReturnTrue(self): midRadius = r * 0.5 f = 0.9 cylinder = Cone(radius=r, length=h, u=32, v=2, position=Vector(0, 0, 0)) - - vertices = [Vertex(f*midRadius, 0, 0), Vertex(0, f*midRadius, 0), Vertex(-f*midRadius, 0, 0), Vertex(0, -f*midRadius, 0), - Vertex(0, 0, f*h*0.5), Vertex(0, 0, -f*h*0.5)] - + + vertices = [ + Vertex(f * midRadius, 0, 0), + Vertex(0, f * midRadius, 0), + Vertex(-f * midRadius, 0, 0), + Vertex(0, -f * midRadius, 0), + Vertex(0, 0, f * h * 0.5), + Vertex(0, 0, -f * h * 0.5), + ] + self.assertTrue(cylinder.contains(*vertices)) def testWhenContainsWithVerticesThatAreNotInsideTheCone_shouldReturnFalse(self): @@ -23,10 +29,16 @@ def testWhenContainsWithVerticesThatAreNotInsideTheCone_shouldReturnFalse(self): midRadius = r * 0.5 f = 1.1 cylinder = Cone(radius=r, length=h, u=32, v=2, position=Vector(0, 0, 0)) - - vertices = [Vertex(f*midRadius, 0, 0), Vertex(0, f*midRadius, 0), Vertex(-f*midRadius, 0, 0), Vertex(0, -f*midRadius, 0), - Vertex(0, 0, f*h*0.5), Vertex(0, 0, -f*h*0.5)] - + + vertices = [ + Vertex(f * midRadius, 0, 0), + Vertex(0, f * midRadius, 0), + Vertex(-f * midRadius, 0, 0), + Vertex(0, -f * midRadius, 0), + Vertex(0, 0, f * h * 0.5), + Vertex(0, 0, -f * h * 0.5), + ] + for vertex in vertices: self.assertFalse(cylinder.contains(vertex)) diff --git a/pytissueoptics/scene/tests/solids/testCuboid.py b/pytissueoptics/scene/tests/solids/testCuboid.py index d5d2d4ea..c51e09d1 100644 --- a/pytissueoptics/scene/tests/solids/testCuboid.py +++ b/pytissueoptics/scene/tests/solids/testCuboid.py @@ -47,21 +47,21 @@ def testWhenStackCuboidsWithTheSameLabel_shouldNotStack(self): otherCuboid = Cuboid(4, 5, 1) with self.assertRaises(AssertionError): - baseCuboid.stack(otherCuboid, 'front') + baseCuboid.stack(otherCuboid, "front") def testWhenStackOnNonExistentSurface_shouldNotStack(self): baseCuboid = Cuboid(4, 5, 3, label="BaseCuboid") otherCuboid = Cuboid(4, 5, 1, label="OtherCuboid") with self.assertRaises(Exception): - baseCuboid.stack(otherCuboid, onSurface='BadSurfaceKey') + baseCuboid.stack(otherCuboid, onSurface="BadSurfaceKey") def testWhenStackUnmatchedSurfaces_shouldNotStack(self): baseCuboid = Cuboid(5, 3, 4, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, label="OtherCuboid") with self.assertRaises(Exception): - baseCuboid.stack(otherCuboid, onSurface='right') + baseCuboid.stack(otherCuboid, onSurface="right") def testWhenStackOnASurface_shouldMoveTheOtherCuboidToBeAdjacentToThisSurface(self): basePosition = Vector(2, 2, 1) @@ -69,7 +69,7 @@ def testWhenStackOnASurface_shouldMoveTheOtherCuboidToBeAdjacentToThisSurface(se baseCuboid = Cuboid(5, 3, 4, position=basePosition, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, position=otherPosition, label="OtherCuboid") - baseCuboid.stack(otherCuboid, onSurface='bottom') + baseCuboid.stack(otherCuboid, onSurface="bottom") self.assertEqual(baseCuboid.position + Vector(0, -2, 0), otherCuboid.position) @@ -77,18 +77,18 @@ def testWhenStack_shouldShareSurfacesWithTheOtherCuboid(self): baseCuboid = Cuboid(5, 3, 4, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, label="OtherCuboid") - baseCuboid.stack(otherCuboid, onSurface='top') + baseCuboid.stack(otherCuboid, onSurface="top") - self.assertEqual(baseCuboid.getPolygons('top'), otherCuboid.getPolygons('bottom')) + self.assertEqual(baseCuboid.getPolygons("top"), otherCuboid.getPolygons("bottom")) def testWhenStack_shouldSetOtherCuboidEnvironmentAtInterface(self): baseCuboid = Cuboid(5, 3, 4, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, label="OtherCuboid") topEnvironment = otherCuboid.getEnvironment() - baseCuboid.stack(otherCuboid, onSurface='top') + baseCuboid.stack(otherCuboid, onSurface="top") - for polygon in baseCuboid.getPolygons('top'): + for polygon in baseCuboid.getPolygons("top"): self.assertEqual(topEnvironment, polygon.outsideEnvironment) def testWhenStack_shouldReturnANewCuboidMadeOfTheseTwoCuboids(self): @@ -96,7 +96,7 @@ def testWhenStack_shouldReturnANewCuboidMadeOfTheseTwoCuboids(self): baseCuboid = Cuboid(5, 3, 4, position=basePosition, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, label="OtherCuboid") - cuboidStack = baseCuboid.stack(otherCuboid, onSurface='bottom') + cuboidStack = baseCuboid.stack(otherCuboid, onSurface="bottom") self.assertEqual([5, 4, 4], cuboidStack.shape) self.assertEqual(basePosition - Vector(0, 0.5, 0), cuboidStack.position) @@ -105,7 +105,7 @@ def testWhenStack_shouldReturnANewCuboidWithAFirstInterface(self): baseCuboid = Cuboid(5, 3, 4, label="BaseCuboid") otherCuboid = Cuboid(5, 1, 4, label="OtherCuboid") - cuboidStack = baseCuboid.stack(otherCuboid, onSurface='top') + cuboidStack = baseCuboid.stack(otherCuboid, onSurface="top") self.assertTrue(f"{INTERFACE_KEY}0" in cuboidStack.surfaceLabels) @@ -115,7 +115,7 @@ def testWhenStack_shouldPreserveEnvironmentAtEachLayer(self): baseCuboid = Cuboid(5, 3, 4, material=baseMaterial, label="base") otherCuboid = Cuboid(5, 1, 4, material=otherMaterial, label="other") - cuboidStack = baseCuboid.stack(otherCuboid, onSurface='top') + cuboidStack = baseCuboid.stack(otherCuboid, onSurface="top") interfacePolygon = cuboidStack.getPolygons(f"{INTERFACE_KEY}0")[0] @@ -127,13 +127,13 @@ def testWhenStack_shouldPreserveEnvironmentAtEachLayer(self): def testWhenStackAnotherStack_shouldReturnANewCuboidWithAllStackInterfaces(self): baseCuboid1 = Cuboid(5, 3, 4, label="BaseCuboid1") otherCuboid1 = Cuboid(5, 1, 4, label="OtherCuboid1") - cuboidStack1 = baseCuboid1.stack(otherCuboid1, onSurface='top') + cuboidStack1 = baseCuboid1.stack(otherCuboid1, onSurface="top") baseCuboid2 = Cuboid(2, 4, 4, label="BaseCuboid2") otherCuboid2 = Cuboid(3, 4, 4, label="OtherCuboid2") - cuboidStack2 = baseCuboid2.stack(otherCuboid2, onSurface='right') + cuboidStack2 = baseCuboid2.stack(otherCuboid2, onSurface="right") - cuboidStack = cuboidStack1.stack(cuboidStack2, onSurface='right') + cuboidStack = cuboidStack1.stack(cuboidStack2, onSurface="right") for i in range(3): self.assertTrue(f"{INTERFACE_KEY}{i}" in cuboidStack.surfaceLabels) @@ -141,14 +141,14 @@ def testWhenStackAnotherStack_shouldReturnANewCuboidWithAllStackInterfaces(self) def testWhenStackAnotherStackNotAlongTheAlreadyStackedAxis_shouldNotStack(self): baseCuboid1 = Cuboid(5, 3, 4, label="base1") otherCuboid1 = Cuboid(5, 1, 4, label="other1") - cuboidStack1 = baseCuboid1.stack(otherCuboid1, onSurface='top') + cuboidStack1 = baseCuboid1.stack(otherCuboid1, onSurface="top") baseCuboid2 = Cuboid(2, 4, 4, label="base2") otherCuboid2 = Cuboid(3, 4, 4, label="other2") - cuboidStack2 = baseCuboid2.stack(otherCuboid2, onSurface='right') + cuboidStack2 = baseCuboid2.stack(otherCuboid2, onSurface="right") with self.assertRaises(Exception): - cuboidStack1.stack(cuboidStack2, onSurface='top') + cuboidStack1.stack(cuboidStack2, onSurface="top") def testWhenContainsWithVerticesThatAreAllInsideTheCuboid_shouldReturnTrue(self): cuboid = Cuboid(1, 1, 8, position=Vector(2, 2, 0)) @@ -170,10 +170,10 @@ def testGivenTwoCubesContainedAfterDifferentRotationCenters_whenContains_shouldR self.assertTrue(outerCube.contains(*innerCube.vertices)) distanceFromRotationCenter = 10 - rotationCenterXY = -np.sin(np.pi/4) * distanceFromRotationCenter + rotationCenterXY = -np.sin(np.pi / 4) * distanceFromRotationCenter rotationCenter = Vector(rotationCenterXY, rotationCenterXY, 0) - innerCube.translateTo(Vector(rotationCenterXY+distanceFromRotationCenter, rotationCenterXY, 0)) + innerCube.translateTo(Vector(rotationCenterXY + distanceFromRotationCenter, rotationCenterXY, 0)) outerCube.rotate(0, 0, 45) innerCube.rotate(0, 0, 45, rotationCenter) diff --git a/pytissueoptics/scene/tests/solids/testCylinder.py b/pytissueoptics/scene/tests/solids/testCylinder.py index 177265dd..de7404ac 100644 --- a/pytissueoptics/scene/tests/solids/testCylinder.py +++ b/pytissueoptics/scene/tests/solids/testCylinder.py @@ -56,7 +56,7 @@ def _getTotalTrianglesArea(surfaces): def testWhenContainsWithVerticesThatAreAllInsideTheCylinder_shouldReturnTrue(self): cylinder = Cylinder(radius=1, length=3, u=32, v=2, position=Vector(2, 2, 0)) cylinder.rotate(0, 45, 0) - vertices = [Vertex(2+1, 2, 1), Vertex(2, 2, 0.5)] + vertices = [Vertex(2 + 1, 2, 1), Vertex(2, 2, 0.5)] self.assertTrue(cylinder.contains(*vertices)) @@ -64,7 +64,7 @@ def testWhenContainsWithVerticesThatAreNotAllInsideTheCylinder_shouldReturnFalse cylinder = Cylinder(radius=1, length=3, u=32, v=2, position=Vector(2, 2, 0)) cylinder.rotate(0, 30, 0) vertices = [Vertex(3.51, 2, 2.6), Vertex(2, 2, 0.5)] - + self.assertFalse(cylinder.contains(*vertices)) def testWhenContainsWithVerticesOutsideMinRadius_shouldReturnFalse(self): @@ -73,10 +73,16 @@ def testWhenContainsWithVerticesOutsideMinRadius_shouldReturnFalse(self): minRadiusWith6Divisions = 0.866 f = minRadiusWith6Divisions * 1.01 cylinder = Cylinder(radius=r, length=h, u=6, position=Vector(0, 0, 0)) - - vertices = [Vertex(f * r, 0, 0), Vertex(0, f * r, 0), Vertex(0, 0, h * 0.51), - Vertex(-f * r, 0, 0), Vertex(0, -f * r, 0), Vertex(0, 0, -h * 0.51)] - + + vertices = [ + Vertex(f * r, 0, 0), + Vertex(0, f * r, 0), + Vertex(0, 0, h * 0.51), + Vertex(-f * r, 0, 0), + Vertex(0, -f * r, 0), + Vertex(0, 0, -h * 0.51), + ] + for vertex in vertices: self.assertFalse(cylinder.contains(vertex)) @@ -86,9 +92,15 @@ def testWhenContainsWithVerticesInsideMinRadius_shouldReturnTrue(self): minRadiusWith6Divisions = 0.866 f = minRadiusWith6Divisions * 0.99 cylinder = Cylinder(radius=r, length=h, u=6, position=Vector(0, 0, 0)) - - vertices = [Vertex(f * r, 0, 0), Vertex(0, f * r, 0), Vertex(0, 0, h * 0.49), - Vertex(-f * r, 0, 0), Vertex(0, -f * r, 0), Vertex(0, 0, -h * 0.49)] + + vertices = [ + Vertex(f * r, 0, 0), + Vertex(0, f * r, 0), + Vertex(0, 0, h * 0.49), + Vertex(-f * r, 0, 0), + Vertex(0, -f * r, 0), + Vertex(0, 0, -h * 0.49), + ] self.assertTrue(cylinder.contains(*vertices)) diff --git a/pytissueoptics/scene/tests/solids/testEllipsoid.py b/pytissueoptics/scene/tests/solids/testEllipsoid.py index 7ce19f55..64417f56 100644 --- a/pytissueoptics/scene/tests/solids/testEllipsoid.py +++ b/pytissueoptics/scene/tests/solids/testEllipsoid.py @@ -59,7 +59,7 @@ def _getPerfectEllipsoidArea(ellipsoid: Ellipsoid) -> float: a = ellipsoid._a b = ellipsoid._b c = ellipsoid._c - return 4 * math.pi * ((a ** p * b ** p + a ** p * c ** p + b ** p * c ** p) / 3) ** (1 / p) + return 4 * math.pi * ((a**p * b**p + a**p * c**p + b**p * c**p) / 3) ** (1 / p) def testWhenContainsWithVerticesThatAreAllInsideTheEllipsoid_shouldReturnTrue(self): ellipsoid = Ellipsoid(3, 1, 1, position=Vector(2, 2, 0)) @@ -75,7 +75,7 @@ def testWhenContainsWithVerticesThatAreNotAllInsideTheEllipsoid_shouldReturnFals self.assertFalse(ellipsoid.contains(*vertices)) def testWhenContainsWithVertexCloseToCenter_shouldReturnTrue(self): - """ Testing a special case that used to fail because relative vertex radius is smaller than 1.""" + """Testing a special case that used to fail because relative vertex radius is smaller than 1.""" ellipsoid = Ellipsoid(1, 1, 1) self.assertTrue(ellipsoid.contains(Vertex(0, 0, 0.2))) diff --git a/pytissueoptics/scene/tests/solids/testSolid.py b/pytissueoptics/scene/tests/solids/testSolid.py index 54bfc1ba..3dac4eb8 100644 --- a/pytissueoptics/scene/tests/solids/testSolid.py +++ b/pytissueoptics/scene/tests/solids/testSolid.py @@ -18,23 +18,34 @@ class TestSolid(unittest.TestCase): def setUp(self): - self.CUBOID_VERTICES = [Vertex(-1, -1, -1), Vertex(1, -1, -1), - Vertex(1, 1, -1), Vertex(-1, 1, -1), - Vertex(-1, -1, 1), Vertex(1, -1, 1), - Vertex(1, 1, 1), Vertex(-1, 1, 1)] + self.CUBOID_VERTICES = [ + Vertex(-1, -1, -1), + Vertex(1, -1, -1), + Vertex(1, 1, -1), + Vertex(-1, 1, -1), + Vertex(-1, -1, 1), + Vertex(1, -1, 1), + Vertex(1, 1, 1), + Vertex(-1, 1, 1), + ] V = self.CUBOID_VERTICES self.CUBOID_SURFACES = SurfaceCollection() - self.CUBOID_SURFACES.add('front', [Quad(V[0], V[1], V[2], V[3])]) - self.CUBOID_SURFACES.add('back', [Quad(V[5], V[4], V[7], V[6])]) - self.CUBOID_SURFACES.add('left', [Quad(V[4], V[0], V[3], V[7])]) - self.CUBOID_SURFACES.add('right', [Quad(V[1], V[5], V[6], V[2])]) - self.CUBOID_SURFACES.add('top', [Quad(V[3], V[2], V[6], V[7])]) - self.CUBOID_SURFACES.add('bottom', [Quad(V[4], V[5], V[1], V[0])]) + self.CUBOID_SURFACES.add("front", [Quad(V[0], V[1], V[2], V[3])]) + self.CUBOID_SURFACES.add("back", [Quad(V[5], V[4], V[7], V[6])]) + self.CUBOID_SURFACES.add("left", [Quad(V[4], V[0], V[3], V[7])]) + self.CUBOID_SURFACES.add("right", [Quad(V[1], V[5], V[6], V[2])]) + self.CUBOID_SURFACES.add("top", [Quad(V[3], V[2], V[6], V[7])]) + self.CUBOID_SURFACES.add("bottom", [Quad(V[4], V[5], V[1], V[0])]) self.material = "A Material" self.position = Vector(2, 2, 0) - self.solid = Solid(position=self.position, material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=self.CUBOID_SURFACES, primitive=primitives.TRIANGLE) + self.solid = Solid( + position=self.position, + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=self.CUBOID_SURFACES, + primitive=primitives.TRIANGLE, + ) def testShouldBeAtDesiredPosition(self): self.assertEqual(self.position, self.solid.position) @@ -92,9 +103,14 @@ def testWhenOrient_shouldOrientItsVertices(self): def testWhenRotateOrOrient_shouldRotateItsPolygons(self): polygon = self.createPolygonMock() - self.CUBOID_SURFACES.setPolygons('front', [polygon]) - solid = Solid(position=self.position, material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=self.CUBOID_SURFACES, primitive=primitives.TRIANGLE) + self.CUBOID_SURFACES.setPolygons("front", [polygon]) + solid = Solid( + position=self.position, + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=self.CUBOID_SURFACES, + primitive=primitives.TRIANGLE, + ) solid.rotate(xTheta=90, yTheta=90, zTheta=90) verify(polygon, times=1).resetNormal() @@ -104,9 +120,14 @@ def testWhenRotateOrOrient_shouldRotateItsPolygons(self): def testWhenRotateOrOrient_shouldRotateBBoxOfSolidAndPolygons(self): polygon = self.createPolygonMock() - self.CUBOID_SURFACES.setPolygons('front', [polygon]) - solid = Solid(position=self.position, material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=self.CUBOID_SURFACES, primitive=primitives.TRIANGLE) + self.CUBOID_SURFACES.setPolygons("front", [polygon]) + solid = Solid( + position=self.position, + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=self.CUBOID_SURFACES, + primitive=primitives.TRIANGLE, + ) oldBbox = solid.bbox solid.rotate(xTheta=90, yTheta=90, zTheta=90) @@ -153,9 +174,14 @@ def testWhenRotateAroundAnotherCenterOfRotation_shouldRotatePositionOfSolid(self def testWhenTranslate_shouldTranslateBBoxOfSolidAndPolygons(self): polygon = self.createPolygonMock() - self.CUBOID_SURFACES.setPolygons('front', [polygon]) - solid = Solid(position=self.position, material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=self.CUBOID_SURFACES, primitive=primitives.TRIANGLE) + self.CUBOID_SURFACES.setPolygons("front", [polygon]) + solid = Solid( + position=self.position, + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=self.CUBOID_SURFACES, + primitive=primitives.TRIANGLE, + ) oldBbox = solid.bbox solid.translateTo(Vector(1, -1, -1)) @@ -175,9 +201,9 @@ def testWhenSmooth_shouldSetVertexNormalAsAverageOfAdjacentPolygonNormals(self): self.solid.smooth() frontVertex = self.solid.vertices[0] - self.assertAlmostEqual(1/math.sqrt(3), frontVertex.normal.x) - self.assertAlmostEqual(1/math.sqrt(3), frontVertex.normal.y) - self.assertAlmostEqual(1/math.sqrt(3), frontVertex.normal.z) + self.assertAlmostEqual(1 / math.sqrt(3), frontVertex.normal.x) + self.assertAlmostEqual(1 / math.sqrt(3), frontVertex.normal.y) + self.assertAlmostEqual(1 / math.sqrt(3), frontVertex.normal.z) def testWhenSmoothWithSurfaceLabel_shouldOnlySmoothPolygonsFromThisSurface(self): self.solid.smooth("front") @@ -194,8 +220,13 @@ def testWhenSetLabel_shouldChangeLabel(self): def _testGivenNoSurfaces_whenCreateSolidWithAnyPrimitive_shouldRaiseException(self, anyPrimitive): with self.assertRaises(NotImplementedError): - Solid(position=self.position, material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=None, primitive=anyPrimitive) + Solid( + position=self.position, + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=None, + primitive=anyPrimitive, + ) def testGivenNoSurfaces_whenCreateSolidWithTrianglePrimitive_shouldRaiseException(self): self._testGivenNoSurfaces_whenCreateSolidWithAnyPrimitive_shouldRaiseException(primitives.TRIANGLE) @@ -215,9 +246,13 @@ def testWhenCheckIfContainsAVertexInsideInternalBBox_shouldReturnTrue(self): def testWhenCheckIfContainsAVertexPartiallyInside_shouldWarnAndReturnFalse(self): otherVertices = [Vertex(1, 1, -0.5), Vertex(3, 1, -0.5), Vertex(3, 3, -0.5), Vertex(1, 3, -0.5)] self.CUBOID_VERTICES.extend(otherVertices) - self.CUBOID_SURFACES.add('other', [Quad(*otherVertices)]) - self.solid = Solid(material=self.material, vertices=self.CUBOID_VERTICES, - surfaces=self.CUBOID_SURFACES, primitive=primitives.TRIANGLE) + self.CUBOID_SURFACES.add("other", [Quad(*otherVertices)]) + self.solid = Solid( + material=self.material, + vertices=self.CUBOID_VERTICES, + surfaces=self.CUBOID_SURFACES, + primitive=primitives.TRIANGLE, + ) with self.assertWarns(RuntimeWarning): self.assertFalse(self.solid.contains(Vertex(2, 2, -0.75))) diff --git a/pytissueoptics/scene/tests/solids/testSphere.py b/pytissueoptics/scene/tests/solids/testSphere.py index 5a0ab533..9fc41079 100644 --- a/pytissueoptics/scene/tests/solids/testSphere.py +++ b/pytissueoptics/scene/tests/solids/testSphere.py @@ -30,7 +30,7 @@ def testGivenASphere_shouldHaveCorrectVerticesLength(self): def testGivenALowOrderSphere_shouldNotApproachCorrectSphereArea(self): sphere = Sphere() icosphereArea = 0 - perfectSphereArea = 4 * math.pi * sphere.radius ** 2 + perfectSphereArea = 4 * math.pi * sphere.radius**2 for polygon in sphere.getPolygons(): icosphereArea += 0.5 * polygon.vertices[0].cross(polygon.vertices[1]).getNorm() @@ -40,7 +40,7 @@ def testGivenALowOrderSphere_shouldNotApproachCorrectSphereArea(self): def testGivenAHighOrderSphere_shouldApproachCorrectSphereArea(self): sphere = Sphere(radius=1, order=4) icosphereArea = 0 - perfectSphereArea = 4 * math.pi * sphere.radius ** 2 + perfectSphereArea = 4 * math.pi * sphere.radius**2 tolerance = 0.002 for polygon in sphere.getPolygons(): diff --git a/pytissueoptics/scene/tests/solids/testThickLens.py b/pytissueoptics/scene/tests/solids/testThickLens.py index 58f84ee0..33f8a84d 100644 --- a/pytissueoptics/scene/tests/solids/testThickLens.py +++ b/pytissueoptics/scene/tests/solids/testThickLens.py @@ -8,8 +8,7 @@ class TestThickLens(unittest.TestCase): def testGivenAFlatLens_shouldHaveInfiniteFocalLength(self): - lens = ThickLens(frontRadius=0, backRadius=0, diameter=1, thickness=1, - material=RefractiveMaterial(1.5)) + lens = ThickLens(frontRadius=0, backRadius=0, diameter=1, thickness=1, material=RefractiveMaterial(1.5)) self.assertEqual(math.inf, lens.focalLength) def testGivenAFlatLens_shouldHaveCenterThicknessEqualToEdgeThickness(self): @@ -68,7 +67,7 @@ def testShouldHaveDifferentEdgeThickness(self): def testGivenALensWithSurfaceRadiusEqualToLensRadius_shouldRaiseError(self): R = 3 with self.assertRaises(ValueError): - ThickLens(frontRadius=R, backRadius=-R, diameter=R*2, thickness=R*2) + ThickLens(frontRadius=R, backRadius=-R, diameter=R * 2, thickness=R * 2) def testShouldOnlySmoothFrontAndBackSurfaces(self): lens = ThickLens(frontRadius=3, backRadius=-3, diameter=1, thickness=1) @@ -105,7 +104,7 @@ def testShouldHaveBackSurfaceVerticesLyingOnASphereOfBackRadiusWithNormalsPointi def testShouldHaveLateralSurfaceVerticesLyingOnACylinderOfRadiusEqualToLensRadiusWithNormalsPointingOutward(self): r = 1 t = 1 - lens = ThickLens(frontRadius=3, backRadius=-3, diameter=r*2, thickness=t) + lens = ThickLens(frontRadius=3, backRadius=-3, diameter=r * 2, thickness=t) lateralPolygons = lens.getPolygons("lateral") for polygon in lateralPolygons: self.assertTrue(polygon.normal.dot(lens.direction) == 0) diff --git a/pytissueoptics/scene/tests/tree/testSpacePartition.py b/pytissueoptics/scene/tests/tree/testSpacePartition.py index 0ddba8d7..3e33bf47 100644 --- a/pytissueoptics/scene/tests/tree/testSpacePartition.py +++ b/pytissueoptics/scene/tests/tree/testSpacePartition.py @@ -20,7 +20,9 @@ def setUp(self): self.root = Node(polygons=self.polyList, bbox=bbox1) self.treeConstructor = TreeConstructor() - expect(self.treeConstructor, times=3)._splitNode(...).thenReturn(result1).thenReturn(result2).thenReturn(result3) + expect(self.treeConstructor, times=3)._splitNode(...).thenReturn(result1).thenReturn(result2).thenReturn( + result3 + ) self.tree = SpacePartition(bbox1, self.polyList, self.treeConstructor, minLeafSize=1) def testShouldHaveNodeCount(self): diff --git a/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py b/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py index e341a911..66396b89 100644 --- a/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py +++ b/pytissueoptics/scene/tests/tree/treeConstructor/binary/testSplitConstructor.py @@ -14,9 +14,11 @@ def setUp(self) -> None: def testGivenATriangleAndAPlane_whenPolygonAsRays_shouldReturnCorrectRays(self): triangle = Triangle(Vertex(0, 0, 0), Vertex(1, 0, 0), Vertex(1, 1, 0)) triangleRays = self._fbtc._getPolygonAsRays(triangle) - expectedTriangleRays = [Ray(Vector(0, 0, 0), Vector(1, 0, 0), 1.0), - Ray(Vector(1, 0, 0), Vector(0, 1, 0), 1.0), - Ray(Vector(1, 1, 0), Vector(-sqrt(2) / 2, -sqrt(2) / 2, 0), sqrt(2))] + expectedTriangleRays = [ + Ray(Vector(0, 0, 0), Vector(1, 0, 0), 1.0), + Ray(Vector(1, 0, 0), Vector(0, 1, 0), 1.0), + Ray(Vector(1, 1, 0), Vector(-sqrt(2) / 2, -sqrt(2) / 2, 0), sqrt(2)), + ] for i, tri in enumerate(triangleRays): self.assertEqual(tri.direction, expectedTriangleRays[i].direction, 2) @@ -52,7 +54,9 @@ def testGivenAPolygonAndAPlane_whenSplittingPolygon_splitPolygonShouldHaveGoodEn left, right = self._fbtc._splitTriangles(normal, dot) - expectedLeft = [Triangle(Vertex(0, 0, 0), Vertex(0, 0.5, 0), Vertex(0, 0.5, 0.5), insideEnvironment=myEnvironment)] + expectedLeft = [ + Triangle(Vertex(0, 0, 0), Vertex(0, 0.5, 0), Vertex(0, 0.5, 0.5), insideEnvironment=myEnvironment) + ] self.assertEqual(left[0], expectedLeft[0]) self.assertEqual(myEnvironment, left[0].insideEnvironment) @@ -106,7 +110,7 @@ def testGivenAPolygonAndAPlane_whenBarelySplittingBetweenTwoVertices_shouldRetur self.assertEqual(2, len(right)) def testGivenAPolygonAndAPlane_whenBarelySplittingOnTwoVertices_shouldNotSplit(self): - tolerance = 0.000001/2 + tolerance = 0.000001 / 2 toBeSplit = [Triangle(Vertex(0, tolerance, 0), Vertex(1, 1, 0), Vertex(1, -tolerance, 0))] splitValue = 0 splitAxis = "y" @@ -179,9 +183,11 @@ def testGivenAPolygonAndAPlane_whenSplittingOnPolygonPlane_shouldNotSplitAndRetu self.assertEqual(right[0], toBeSplit[0]) def testGivenUltraThinPolygon_whenSplitting_shouldStillReturn2Polygons(self): - vertices = [Vertex(8.860660171779822, 5.000000000000001, -4.9455), - Vertex(8.856599089933916, 4.995938918154095, -4.9455), - Vertex(8.856599089933916, 4.99986899735981, -4.9455)] + vertices = [ + Vertex(8.860660171779822, 5.000000000000001, -4.9455), + Vertex(8.856599089933916, 4.995938918154095, -4.9455), + Vertex(8.856599089933916, 4.99986899735981, -4.9455), + ] toBeSplit = [Triangle(*vertices)] splitAxis = "y" splitValue = 4.999868997359811 @@ -195,11 +201,15 @@ def testGivenUltraThinPolygon_whenSplitting_shouldStillReturn2Polygons(self): def testGivenANodeWith2Polygon_whenSplitting_shouldSplitBetweenPolygons(self): """This type of test is extremely sensitive on initial parameters.""" - expectedLeft = [Triangle(Vertex(0, 0, 0), Vertex(1, 1, 1), Vertex(1, -1, 1)), - Triangle(Vertex(0, 0, 0), Vertex(-1, -1, -1), Vertex(-2, -2, -3))] - expectedRight = [Triangle(Vertex(2, 4, 4), Vertex(2, 2, 2), Vertex(2, 2, 3)), - Triangle(Vertex(2, 5, 5), Vertex(2, 2, 2), Vertex(2, 2, 3)), - Triangle(Vertex(2, 6, 6), Vertex(3, 3, 3), Vertex(2, 2, 3))] + expectedLeft = [ + Triangle(Vertex(0, 0, 0), Vertex(1, 1, 1), Vertex(1, -1, 1)), + Triangle(Vertex(0, 0, 0), Vertex(-1, -1, -1), Vertex(-2, -2, -3)), + ] + expectedRight = [ + Triangle(Vertex(2, 4, 4), Vertex(2, 2, 2), Vertex(2, 2, 3)), + Triangle(Vertex(2, 5, 5), Vertex(2, 2, 2), Vertex(2, 2, 3)), + Triangle(Vertex(2, 6, 6), Vertex(3, 3, 3), Vertex(2, 2, 3)), + ] polygons = [] polygons.extend(expectedLeft) polygons.extend(expectedRight) diff --git a/pytissueoptics/scene/tests/viewer/testMayaviSolid.py b/pytissueoptics/scene/tests/viewer/testMayaviSolid.py index 768d9ce9..f1dc3716 100644 --- a/pytissueoptics/scene/tests/viewer/testMayaviSolid.py +++ b/pytissueoptics/scene/tests/viewer/testMayaviSolid.py @@ -8,19 +8,16 @@ class TestMayaviSolid(unittest.TestCase): def createSimpleSolid(self, primitive=primitives.TRIANGLE) -> Solid: - V = [Vector(0, 0, 0), Vector(0, 1, 0), Vector(1, 1, 0), Vector(1, 0, 0), - Vector(0.5, -1, 0)] + V = [Vector(0, 0, 0), Vector(0, 1, 0), Vector(1, 1, 0), Vector(1, 0, 0), Vector(0.5, -1, 0)] self.surfaces = SurfaceCollection() if primitive == primitives.TRIANGLE: self.surfaces.add("Face", [Triangle(V[0], V[1], V[2]), Triangle(V[0], V[2], V[3])]) if primitive == primitives.QUAD: self.surfaces.add("Face", [Quad(V[0], V[1], V[2], V[3])]) if primitive == "Polygon": - self.surfaces.add("Face", [Polygon([V[0], V[1], V[2], V[3], V[4]]), - Triangle(V[0], V[1], V[2])]) + self.surfaces.add("Face", [Polygon([V[0], V[1], V[2], V[3], V[4]]), Triangle(V[0], V[1], V[2])]) self.vertices = V - return Solid(position=Vector(0, 0, 0), vertices=self.vertices, - surfaces=self.surfaces, primitive=primitive) + return Solid(position=Vector(0, 0, 0), vertices=self.vertices, surfaces=self.surfaces, primitive=primitive) def testGivenNewMayaviSolidWithTrianglePrimitive_shouldExtractMayaviTriangleMeshFromSolid(self): solid = self.createSimpleSolid() @@ -37,7 +34,7 @@ def testGivenNewMayaviSolidWithQuadPrimitive_shouldExtractMayaviTriangleMeshFrom x, y, z, polygonIndices = mayaviSolid.triangleMesh.components self.assertTrue(len(x) == len(y) == len(z)) - self.assertEqual(2*len(solid.getPolygons()), len(polygonIndices)) + self.assertEqual(2 * len(solid.getPolygons()), len(polygonIndices)) self.assertEqual((0, 2, 3), polygonIndices[1]) def testGivenNewMayaviSolidWithArbitraryPolygonPrimitives_shouldExtractMayaviTriangleMeshFromSolid(self): diff --git a/pytissueoptics/scene/tests/viewer/testMayaviViewer.py b/pytissueoptics/scene/tests/viewer/testMayaviViewer.py index c84aa158..1d750b29 100644 --- a/pytissueoptics/scene/tests/viewer/testMayaviViewer.py +++ b/pytissueoptics/scene/tests/viewer/testMayaviViewer.py @@ -12,20 +12,19 @@ from pytissueoptics.scene.tests import SHOW_VISUAL_TESTS, compareVisuals from pytissueoptics.scene.viewer.mayavi import MayaviViewer, ViewPointStyle -TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), 'testImages') +TEST_IMAGES_DIR = os.path.join(os.path.dirname(__file__), "testImages") OVERWRITE_TEST_IMAGES = False def patchMayaviShow(func): - for module in ['show', 'gcf', 'figure', 'clf', 'triangular_mesh']: - func = patch('mayavi.mlab.' + module)(func) + for module in ["show", "gcf", "figure", "clf", "triangular_mesh"]: + func = patch("mayavi.mlab." + module)(func) return func @unittest.skipIf( - not SHOW_VISUAL_TESTS, - "Visual tests are disabled. Set scene.tests.SHOW_VISUAL_TESTS to True to enable them." + not SHOW_VISUAL_TESTS, "Visual tests are disabled. Set scene.tests.SHOW_VISUAL_TESTS to True to enable them." ) class TestMayaviViewer(unittest.TestCase): def setUp(self): @@ -82,12 +81,11 @@ def _assertViewerDisplays(self, displayName: str): self.skipTest("Cannot test when saving test images.") with tempfile.TemporaryDirectory() as tmpdir: - currentImageFile = os.path.join(tmpdir, 'currentViewer.png') + currentImageFile = os.path.join(tmpdir, "currentViewer.png") self.viewer.save(currentImageFile) self.viewer.close() - isOK = compareVisuals(expectedImageFile, currentImageFile, - title=f"TestMayaviViewer: {displayName}") + isOK = compareVisuals(expectedImageFile, currentImageFile, title=f"TestMayaviViewer: {displayName}") if not isOK: self.fail("Visual test failed.") diff --git a/pytissueoptics/scene/tree/node.py b/pytissueoptics/scene/tree/node.py index 4738a68b..a0e50f5f 100644 --- a/pytissueoptics/scene/tree/node.py +++ b/pytissueoptics/scene/tree/node.py @@ -4,8 +4,7 @@ class Node: - def __init__(self, parent: 'Node' = None, polygons: List[Polygon] = None, bbox: BoundingBox = None, depth: int = 0): - + def __init__(self, parent: "Node" = None, polygons: List[Polygon] = None, bbox: BoundingBox = None, depth: int = 0): self._parent = parent self._children = [] self._polygons = polygons @@ -13,7 +12,7 @@ def __init__(self, parent: 'Node' = None, polygons: List[Polygon] = None, bbox: self._depth = depth @property - def children(self) -> List['Node']: + def children(self) -> List["Node"]: return self._children @property diff --git a/pytissueoptics/scene/tree/spacePartition.py b/pytissueoptics/scene/tree/spacePartition.py index 7cef5b7d..221d5226 100644 --- a/pytissueoptics/scene/tree/spacePartition.py +++ b/pytissueoptics/scene/tree/spacePartition.py @@ -23,8 +23,9 @@ class SpacePartition: internally for benchmarking purposes. """ - def __init__(self, bbox: BoundingBox, polygons: List[Polygon], constructor: TreeConstructor, maxDepth=6, - minLeafSize=2): + def __init__( + self, bbox: BoundingBox, polygons: List[Polygon], constructor: TreeConstructor, maxDepth=6, minLeafSize=2 + ): self._maxDepth = maxDepth self._minLeafSize = minLeafSize self._polygons = polygons diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py index 65ec73f0..0b3ed3b0 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitOneAxisConstructor.py @@ -9,9 +9,14 @@ class NoSplitOneAxisConstructor(TreeConstructor): - - def __init__(self, nbOfSplitPlanes: int = 20, intersectionCost: float = 0.5, traversalCost: float = 1, - noSharedBonus: float = 2, emptySpaceBonus: float = 2): + def __init__( + self, + nbOfSplitPlanes: int = 20, + intersectionCost: float = 0.5, + traversalCost: float = 1, + noSharedBonus: float = 2, + emptySpaceBonus: float = 2, + ): super().__init__() self._nbOfSplitPlanes = nbOfSplitPlanes self._noSharedBonus = noSharedBonus @@ -30,8 +35,11 @@ def _splitNode(self, node: Node) -> SplitNodeResult: self.result.rightPolygons.extend(self.result.splitPolygons) self._trimChildrenBbox() stopCondition = self._checkStopCondition() - newNodeResult = SplitNodeResult(stopCondition, [self.result.leftBbox, self.result.rightBbox], - [self.result.leftPolygons, self.result.rightPolygons]) + newNodeResult = SplitNodeResult( + stopCondition, + [self.result.leftBbox, self.result.rightBbox], + [self.result.leftPolygons, self.result.rightPolygons], + ) return newNodeResult def _checkStopCondition(self): diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitThreeAxesConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitThreeAxesConstructor.py index 8a0ce997..14ea3ea8 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/noSplitThreeAxesConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/noSplitThreeAxesConstructor.py @@ -18,6 +18,9 @@ def _splitNode(self, node: Node) -> SplitNodeResult: self.result.rightPolygons.extend(self.result.splitPolygons) self._trimChildrenBbox() stopCondition = self._checkStopCondition() - newNodeResult = SplitNodeResult(stopCondition, [self.result.leftBbox, self.result.rightBbox], - [self.result.leftPolygons, self.result.rightPolygons]) + newNodeResult = SplitNodeResult( + stopCondition, + [self.result.leftBbox, self.result.rightBbox], + [self.result.leftPolygons, self.result.rightPolygons], + ) return newNodeResult diff --git a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py index c997c8ac..cd30395b 100644 --- a/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/binary/splitTreeAxesConstructor.py @@ -11,29 +11,35 @@ class SplitThreeAxesConstructor(NoSplitOneAxisConstructor): """ - This is an implementation of the proposed algorithms found in + This is an implementation of the proposed algorithms found in - Wald, Ingo, and Vlastimil Havran. 2006. “On Building Fast Kd-Trees for Ray Tracing, and on Doing That in O(N Log N). - ” In 2006 IEEE Symposium on Interactive Ray Tracing, 61–69. + Wald, Ingo, and Vlastimil Havran. 2006. “On Building Fast Kd-Trees for Ray Tracing, and on Doing That in O(N Log N). + ” In 2006 IEEE Symposium on Interactive Ray Tracing, 61–69. - Soupikov, Alexei, Maxim Shevtsov, and Alexander Kapustin. 2008. “Improving Kd-Tree Quality at a Reasonable - Construction Cost.” In Interactive Ray Tracing, 2008. RT 2008. IEEE Symposium on, 67–72. unknown. - - - This algorithm uses a - 1. Surface Area Heuristic (SAH) search on 3 axis - 2. Perfect Splits - 3. On-the-fly pruning - When the minimum SAH axis and value are found, a split cost is calculated. - If the children's cost is lower than the parent, splitting is allowed, else, no splitting occurs. + Soupikov, Alexei, Maxim Shevtsov, and Alexander Kapustin. 2008. “Improving Kd-Tree Quality at a Reasonable + Construction Cost.” In Interactive Ray Tracing, 2008. RT 2008. IEEE Symposium on, 67–72. unknown. + - + This algorithm uses a + 1. Surface Area Heuristic (SAH) search on 3 axis + 2. Perfect Splits + 3. On-the-fly pruning + When the minimum SAH axis and value are found, a split cost is calculated. + If the children's cost is lower than the parent, splitting is allowed, else, no splitting occurs. - Contrary to belief, the current implementation is not faster than non-split SAH-based tree construction. A split - results in 3 triangles, whereas a non-split results in a shared triangle, the equivalent of 2 distinct triangles. - It seems no matter the parameters of the tree, the intersection cost brought by the extra triangles is too high. + Contrary to belief, the current implementation is not faster than non-split SAH-based tree construction. A split + results in 3 triangles, whereas a non-split results in a shared triangle, the equivalent of 2 distinct triangles. + It seems no matter the parameters of the tree, the intersection cost brought by the extra triangles is too high. - """ + """ - def __init__(self, nbOfSplitPlanes: int = 20, intersectionCost: float = 3, traversalCost: float = 6, - noSharedBonus: float = 2, emptySpaceBonus: float = 2): + def __init__( + self, + nbOfSplitPlanes: int = 20, + intersectionCost: float = 3, + traversalCost: float = 6, + noSharedBonus: float = 2, + emptySpaceBonus: float = 2, + ): super().__init__(nbOfSplitPlanes, intersectionCost, traversalCost, noSharedBonus, emptySpaceBonus) def _splitNode(self, node: Node) -> SplitNodeResult: @@ -50,22 +56,29 @@ def _splitNode(self, node: Node) -> SplitNodeResult: self.result.rightPolygons.extend(right) self._trimChildrenBbox() stopCondition = self._checkStopCondition() - newNodeResult = SplitNodeResult(stopCondition, [self.result.leftBbox, self.result.rightBbox], - [self.result.leftPolygons, self.result.rightPolygons]) + newNodeResult = SplitNodeResult( + stopCondition, + [self.result.leftBbox, self.result.rightBbox], + [self.result.leftPolygons, self.result.rightPolygons], + ) return newNodeResult def _splitTriangles(self, planeNormal: Vector, planePoint: Vector) -> Tuple[List[Polygon], List[Polygon]]: goingLeft, goingRight = [], [] if self.result.splitPolygons: for polygon in self.result.splitPolygons: - left, right, = self._splitTriangle(polygon, planeNormal, planePoint) + ( + left, + right, + ) = self._splitTriangle(polygon, planeNormal, planePoint) goingLeft.extend(left) goingRight.extend(right) return goingLeft, goingRight - def _splitTriangle(self, polygon: Polygon, planeNormal: Vector, planePoint: Vector) -> Tuple[ - List[Polygon], List[Polygon]]: + def _splitTriangle( + self, polygon: Polygon, planeNormal: Vector, planePoint: Vector + ) -> Tuple[List[Polygon], List[Polygon]]: goingLeft, goingRight = [], [] leftVertices, rightVertices, contained = self._checkVerticesPlaneSide(polygon.vertices, planeNormal, planePoint) nLeft = len(leftVertices) @@ -82,8 +95,9 @@ def _splitTriangle(self, polygon: Polygon, planeNormal: Vector, planePoint: Vect goingRight.append(polygon) elif nContained != 0: - left, right = self._splitFromMiddleContained(leftVertices, rightVertices, contained, polygon, planeNormal, - planePoint) + left, right = self._splitFromMiddleContained( + leftVertices, rightVertices, contained, polygon, planeNormal, planePoint + ) goingLeft.extend(left) goingRight.extend(right) @@ -94,9 +108,18 @@ def _splitTriangle(self, polygon: Polygon, planeNormal: Vector, planePoint: Vect return goingLeft, goingRight - def _splitFromNotContained(self, leftVertices: List[Vector], rightVertices: List[Vector], polygon: Polygon, - planeNormal: Vector, planePoint: Vector) -> Tuple[List[Polygon], List[Polygon]]: - left, right, = [], [] + def _splitFromNotContained( + self, + leftVertices: List[Vector], + rightVertices: List[Vector], + polygon: Polygon, + planeNormal: Vector, + planePoint: Vector, + ) -> Tuple[List[Polygon], List[Polygon]]: + ( + left, + right, + ) = [], [] if len(leftVertices) == 1: direction = rightVertices[0] - leftVertices[0] ray = Ray(leftVertices[0], direction, direction.getNorm()) @@ -105,11 +128,14 @@ def _splitFromNotContained(self, leftVertices: List[Vector], rightVertices: List ray = Ray(leftVertices[0], direction, direction.getNorm()) intersectionPoint2 = self._intersectPlaneWithRay(planeNormal, planePoint, ray) left.append( - self._makeTriangleFromVertices(polygon, [intersectionPoint1, intersectionPoint2, leftVertices[0]])) + self._makeTriangleFromVertices(polygon, [intersectionPoint1, intersectionPoint2, leftVertices[0]]) + ) right.append( - self._makeTriangleFromVertices(polygon, [intersectionPoint1, rightVertices[0], intersectionPoint2])) + self._makeTriangleFromVertices(polygon, [intersectionPoint1, rightVertices[0], intersectionPoint2]) + ) right.append( - self._makeTriangleFromVertices(polygon, [intersectionPoint2, rightVertices[0], rightVertices[1]])) + self._makeTriangleFromVertices(polygon, [intersectionPoint2, rightVertices[0], rightVertices[1]]) + ) else: direction = leftVertices[0] - rightVertices[0] ray = Ray(rightVertices[0], direction, direction.getNorm()) @@ -118,16 +144,24 @@ def _splitFromNotContained(self, leftVertices: List[Vector], rightVertices: List ray = Ray(rightVertices[0], direction, direction.getNorm()) intersectionPoint2 = self._intersectPlaneWithRay(planeNormal, planePoint, ray) right.append( - self._makeTriangleFromVertices(polygon, [intersectionPoint1, intersectionPoint2, rightVertices[0]])) + self._makeTriangleFromVertices(polygon, [intersectionPoint1, intersectionPoint2, rightVertices[0]]) + ) left.append( - self._makeTriangleFromVertices(polygon, [intersectionPoint1, leftVertices[0], intersectionPoint2])) + self._makeTriangleFromVertices(polygon, [intersectionPoint1, leftVertices[0], intersectionPoint2]) + ) left.append(self._makeTriangleFromVertices(polygon, [intersectionPoint2, leftVertices[0], leftVertices[1]])) return left, right - def _splitFromMiddleContained(self, leftVertices: List[Vector], rightVertices: List[Vector], - contained: List[Vector], polygon: Polygon, planeNormal: Vector, planePoint: Vector) -> \ - Tuple[List[Polygon], List[Polygon]]: + def _splitFromMiddleContained( + self, + leftVertices: List[Vector], + rightVertices: List[Vector], + contained: List[Vector], + polygon: Polygon, + planeNormal: Vector, + planePoint: Vector, + ) -> Tuple[List[Polygon], List[Polygon]]: right, left = [], [] direction = leftVertices[0] - rightVertices[0] ray = Ray(leftVertices[0], direction, direction.getNorm()) @@ -147,12 +181,17 @@ def _splitFromMiddleContained(self, leftVertices: List[Vector], rightVertices: L @staticmethod def _makeTriangleFromVertices(parent: Polygon, vertices: List[Vector]) -> Polygon: if len(vertices) == 3: - return Triangle(*vertices, normal=parent.normal, insideEnvironment=parent.insideEnvironment, - outsideEnvironment=parent.outsideEnvironment) + return Triangle( + *vertices, + normal=parent.normal, + insideEnvironment=parent.insideEnvironment, + outsideEnvironment=parent.outsideEnvironment, + ) @staticmethod - def _checkVerticesPlaneSide(vertices: List[Vector], planeNormal: Vector, planePoint: Vector, tol=1e-6) -> Tuple[ - List[Vector], List[Vector], List[Vector]]: + def _checkVerticesPlaneSide( + vertices: List[Vector], planeNormal: Vector, planePoint: Vector, tol=1e-6 + ) -> Tuple[List[Vector], List[Vector], List[Vector]]: """Based on the fact that the plane normal will always point towards the positive axis, and that we search our min(SAH) in that order as well, we can conclude that if diff is negative, the point is on the right side of the plane, and if diff is positive, the point is on the left side of the plane.""" diff --git a/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py b/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py index 3e9764bf..87195fc0 100644 --- a/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py +++ b/pytissueoptics/scene/tree/treeConstructor/treeConstructor.py @@ -19,7 +19,8 @@ def constructTree(self, node: Node, maxDepth: int, minLeafSize: int): for i, polygonGroup in enumerate(splitNodeResult.polygonGroups): if len(polygonGroup) <= 0: continue - childNode = Node(parent=node, polygons=polygonGroup, bbox=splitNodeResult.groupsBbox[i], - depth=node.depth + 1) + childNode = Node( + parent=node, polygons=polygonGroup, bbox=splitNodeResult.groupsBbox[i], depth=node.depth + 1 + ) node.children.append(childNode) self.constructTree(childNode, maxDepth, minLeafSize) diff --git a/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py b/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py index 08bf2f65..f0ec89bd 100644 --- a/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py +++ b/pytissueoptics/scene/viewer/mayavi/mayaviSolid.py @@ -8,8 +8,9 @@ class MayaviObject: - def __init__(self, vertices: List[Vertex], polygons: List[Polygon], - loadNormals=True, primitive=primitives.TRIANGLE): + def __init__( + self, vertices: List[Vertex], polygons: List[Polygon], loadNormals=True, primitive=primitives.TRIANGLE + ): self._vertices = vertices self._polygons = polygons self._primitive = primitive @@ -63,9 +64,7 @@ def _getTriangleIndices(self): trianglesIndices = [] for polygonIndices in self._polygonsIndices: for i in range(len(polygonIndices) - 2): - trianglesIndices.append((polygonIndices[0], - polygonIndices[i + 1], - polygonIndices[i + 2])) + trianglesIndices.append((polygonIndices[0], polygonIndices[i + 1], polygonIndices[i + 2])) return trianglesIndices @property diff --git a/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py b/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py index d99a4b46..78ca9013 100644 --- a/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py +++ b/pytissueoptics/scene/viewer/mayavi/mayaviViewer.py @@ -6,6 +6,7 @@ try: from mayavi import mlab + MAYAVI_AVAILABLE = True except ImportError: MAYAVI_AVAILABLE = False @@ -18,35 +19,68 @@ class MayaviViewer: def __init__(self, viewPointStyle=ViewPointStyle.NATURAL): self._scenes = { - "DefaultScene": {"figureParameters": {"bgColor": (0.11, 0.11, 0.11), "fgColor": (0.9, 0.9, 0.9)}, - "Solids": [], }} + "DefaultScene": { + "figureParameters": {"bgColor": (0.11, 0.11, 0.11), "fgColor": (0.9, 0.9, 0.9)}, + "Solids": [], + } + } self._viewPoint = ViewPointFactory().create(viewPointStyle) self.clear() - def add(self, *solids: 'Solid', representation="wireframe", lineWidth=0.25, showNormals=False, normalLength=0.3, - colormap="viridis", reverseColormap=False, colorWithPosition=False, opacity=1, **kwargs): + def add( + self, + *solids: "Solid", + representation="wireframe", + lineWidth=0.25, + showNormals=False, + normalLength=0.3, + colormap="viridis", + reverseColormap=False, + colorWithPosition=False, + opacity=1, + **kwargs, + ): for solid in solids: mayaviSolid = MayaviSolid(solid, loadNormals=showNormals) self._scenes["DefaultScene"]["Solids"].append(mayaviSolid) - s = mlab.triangular_mesh(*mayaviSolid.triangleMesh.components, representation=representation, - line_width=lineWidth, colormap=colormap, opacity=opacity, **kwargs) + s = mlab.triangular_mesh( + *mayaviSolid.triangleMesh.components, + representation=representation, + line_width=lineWidth, + colormap=colormap, + opacity=opacity, + **kwargs, + ) s.module_manager.scalar_lut_manager.reverse_lut = reverseColormap if colorWithPosition: s.module_manager.lut_data_mode = "cell data" if showNormals: - mlab.quiver3d(*mayaviSolid.normals.components, line_width=lineWidth, scale_factor=normalLength, - color=(1, 1, 1)) - - def addLogger(self, logger: Logger, colormap="rainbow", reverseColormap=False, - pointScale=0.01, dataPointScale=0.15, scaleWithValue=True): + mlab.quiver3d( + *mayaviSolid.normals.components, line_width=lineWidth, scale_factor=normalLength, color=(1, 1, 1) + ) + + def addLogger( + self, + logger: Logger, + colormap="rainbow", + reverseColormap=False, + pointScale=0.01, + dataPointScale=0.15, + scaleWithValue=True, + ): self.addPoints(logger.getPoints(), colormap=colormap, reverseColormap=reverseColormap, scale=pointScale) - self.addDataPoints(logger.getDataPoints(), colormap=colormap, reverseColormap=reverseColormap, - scale=dataPointScale, scaleWithValue=scaleWithValue) + self.addDataPoints( + logger.getDataPoints(), + colormap=colormap, + reverseColormap=reverseColormap, + scale=dataPointScale, + scaleWithValue=scaleWithValue, + ) self.addSegments(logger.getSegments(), colormap=colormap, reverseColormap=reverseColormap) @staticmethod def addPoints(points: np.ndarray, colormap="rainbow", reverseColormap=False, scale=0.01, asSpheres=True): - """ 'points' has to be of shape (n, 3) where the second axis is (x, y, z). """ + """'points' has to be of shape (n, 3) where the second axis is (x, y, z).""" if points is None: return x, y, z = [points[:, i] for i in range(3)] @@ -55,9 +89,15 @@ def addPoints(points: np.ndarray, colormap="rainbow", reverseColormap=False, sca s.module_manager.scalar_lut_manager.reverse_lut = reverseColormap @staticmethod - def addDataPoints(dataPoints: np.ndarray, colormap="rainbow", reverseColormap=False, scale=0.15, - scaleWithValue=True, asSpheres=True): - """ 'dataPoints' has to be of shape (n, 4) where the second axis is (value, x, y, z). """ + def addDataPoints( + dataPoints: np.ndarray, + colormap="rainbow", + reverseColormap=False, + scale=0.15, + scaleWithValue=True, + asSpheres=True, + ): + """'dataPoints' has to be of shape (n, 4) where the second axis is (value, x, y, z).""" if dataPoints is None: return v, x, y, z = [dataPoints[:, i] for i in range(4)] @@ -68,7 +108,7 @@ def addDataPoints(dataPoints: np.ndarray, colormap="rainbow", reverseColormap=Fa @staticmethod def addSegments(segments: np.ndarray, colormap="rainbow", reverseColormap=False): - """ 'segments' has to be of shape (n, 6) where the second axis is (x1, y1, z1, x2, y2, z2). """ + """'segments' has to be of shape (n, 6) where the second axis is (x1, y1, z1, x2, y2, z2).""" if segments is None: return for segment in segments: @@ -79,8 +119,14 @@ def addSegments(segments: np.ndarray, colormap="rainbow", reverseColormap=False) s.module_manager.scalar_lut_manager.reverse_lut = reverseColormap @staticmethod - def addImage(image: np.ndarray, size: tuple = None, minCorner: tuple = (0, 0), - axis: int = 2, position: float = 0, colormap: str = 'viridis'): + def addImage( + image: np.ndarray, + size: tuple = None, + minCorner: tuple = (0, 0), + axis: int = 2, + position: float = 0, + colormap: str = "viridis", + ): if size is None: size = image.shape # Limitation: the current call to Mayavi.mlab.imshow will display half of the first pixel. @@ -102,8 +148,12 @@ def addImage(image: np.ndarray, size: tuple = None, minCorner: tuple = (0, 0), # The (X, Y) size has to be flipped to match rotations below for image axis 0 and 1. displaySize = size if axis == 2 else size[::-1] - p = mlab.imshow(image, colormap=colormap, interpolate=False, - extent=[0, displaySize[0], 0, displaySize[1], position, position]) + p = mlab.imshow( + image, + colormap=colormap, + interpolate=False, + extent=[0, displaySize[0], 0, displaySize[1], position, position], + ) p.actor.force_opaque = True tempPosition = [minCorner[0] + size[0] / 2, minCorner[1] + size[1] / 2] @@ -143,7 +193,13 @@ def close(self): mlab.close() def addBBox(self, bbox: BoundingBox, lineWidth=0.25, color=(1, 1, 1), opacity=1.0, **kwargs): - """ Adds a bounding box to the scene. """ - s = mlab.plot3d([bbox.xMin, bbox.xMax], [bbox.yMin, bbox.yMax], [bbox.zMin, bbox.zMax], - tube_radius=None, line_width=0, opacity=0) + """Adds a bounding box to the scene.""" + s = mlab.plot3d( + [bbox.xMin, bbox.xMax], + [bbox.yMin, bbox.yMax], + [bbox.zMin, bbox.zMax], + tube_radius=None, + line_width=0, + opacity=0, + ) mlab.outline(s, line_width=lineWidth, color=color, opacity=opacity, **kwargs) diff --git a/pytissueoptics/testExamples.py b/pytissueoptics/testExamples.py index 05a45f08..17ce496f 100644 --- a/pytissueoptics/testExamples.py +++ b/pytissueoptics/testExamples.py @@ -12,15 +12,14 @@ def testExampleFormat(self): for file in EXAMPLE_FILES: name = re.match(EXAMPLE_FILE_PATTERN, file).group(1) module = importlib.import_module(f"pytissueoptics.examples.{EXAMPLE_MODULE}.{name}") - with open(os.path.join(EXAMPLE_DIR, file), 'r') as f: + with open(os.path.join(EXAMPLE_DIR, file), "r") as f: srcCode = f.read() with self.subTest(name): self.assertTrue(hasattr(module, "TITLE")) self.assertTrue(hasattr(module, "DESCRIPTION")) self.assertTrue(hasattr(module, "exampleCode")) self.assertTrue(srcCode.startswith("import env")) - self.assertTrue(srcCode.endswith("if __name__ == \"__main__\":\n" + - " exampleCode()\n")) + self.assertTrue(srcCode.endswith('if __name__ == "__main__":\n' + " exampleCode()\n")) def testLoadExamples(self): allExamples = loadExamples() From 1548ced4d8f0040b7e26158ced9a14d70f56e446 Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 21:30:28 -0400 Subject: [PATCH 5/7] Create lint.yaml --- .github/workflows/lint.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..4c9c7165 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,24 @@ +name: Lint + +on: [ pull_request, workflow_dispatch ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install Ruff + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Lint check + run: ruff check pytissueoptics From 3ac9a75616e59668838116adfd48c67b55cc9222 Mon Sep 17 00:00:00 2001 From: JLBegin Date: Sat, 19 Apr 2025 21:44:03 -0400 Subject: [PATCH 6/7] Update tests.yaml --- .github/workflows/tests.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b5dba491..2316f228 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,8 +42,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install setuptools>=40.8.0 wheel pytest - python -m pip install . + python -m pip install -e .[dev] python -m pip freeze --all - name: Validate OpenCL From 38ce9964e6fb548a51b85d1f82eae63f56528d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Vigneault?= Date: Sun, 20 Apr 2025 11:10:39 -0400 Subject: [PATCH 7/7] manually add #noqa --- pytissueoptics/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytissueoptics/__init__.py b/pytissueoptics/__init__.py index 680fc61b..b90fc8af 100644 --- a/pytissueoptics/__init__.py +++ b/pytissueoptics/__init__.py @@ -1,5 +1,5 @@ from importlib.metadata import version -from .scene import * -from .rayscattering import * +from .scene import * # noqa: F403 +from .rayscattering import * # noqa: F403 __version__ = version("pytissueoptics")