• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ghiggi / gpm_api / 7798551251

06 Feb 2024 10:55AM UTC coverage: 59.56%. Remained the same
7798551251

push

github

ghiggi
Fix documentation

3305 of 5549 relevant lines covered (59.56%)

0.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/gpm_api/visualization/plot_3d.py
1
#!/usr/bin/env python3
2
"""
×
3
Created on Fri Aug 18 15:34:19 2023
4

5
@author: ghiggi
6
"""
7
import numpy as np
×
8

9
# TODO:
10
# - Isosurface contour buggy at low reflectivity
11
#   --> Should I replace values as 0-1 at each round?
12
# - 3D terrain
13
# - surface on bin surface height
14

15

16
def create_pyvista_2d_surface(data_array, spacing=(1, 1, 1), origin=(0, 0, 0)):
×
17
    """Create pyvista ImageData object from 2D xr.DataArray."""
18
    import pyvista as pv
×
19

20
    dimensions = (data_array.shape[0], data_array.shape[1], 1)
×
21
    surf = pv.ImageData(
×
22
        dimensions=dimensions,
23
        spacing=spacing,
24
        origin=origin,
25
    )
26
    data = data_array.values
×
27
    data = data[:, ::-1]
×
28
    surf.point_data.set_array(data.flatten(order="F"), name=data_array.name)
×
29
    surf.set_active_scalars(data_array.name)
×
30
    return surf
×
31

32

33
def create_pyvista_3d_volume(data_array, spacing=(1, 1, 0.25), origin=(0, 0, 0)):
×
34
    """Create pyvista ImageData object from 3D xr.DataArray."""
35
    import pyvista as pv
×
36

37
    # Remove vertical areas without values
38
    data_array = data_array.gpm_api.slice_range_with_valid_data()
×
39

40
    # Create ImageData object
41
    # - TODO: scale (factor)
42
    dimensions = data_array.shape
×
43
    vol = pv.ImageData(
×
44
        dimensions=dimensions,
45
        spacing=spacing,
46
        origin=origin,
47
    )
48
    data = data_array.values
×
49
    data = data[:, ::-1, ::-1]
×
50
    vol.point_data.set_array(data.flatten(order="F"), name=data_array.name)
×
51
    vol.set_active_scalars(data_array.name)
×
52
    return vol
×
53

54

55
def get_slider_button_positions(i, num_buttons, spacing_factor=0.04):
×
56
    """Return the pointa and pointb parameters for pl.add_slider_widget."""
57
    if num_buttons < 1:
×
58
        raise ValueError("Number of buttons must be at least 1")
×
59

60
    # Define margin
61
    min_pointa = 0.025
×
62
    max_pointb = 0.98
×
63

64
    # Define allowable buttons width
65
    total_width = max_pointb - min_pointa - spacing_factor * (num_buttons - 1)
×
66

67
    # Define button width
68
    button_width = total_width / num_buttons
×
69

70
    # Define pointa and pointb
71
    start_x = min_pointa + i * (button_width)
×
72
    if i > 0:
×
73
        start_x = start_x + spacing_factor
×
74
    end_x = start_x + button_width
×
75
    pointa = (start_x, 0.1)
×
76
    pointb = (end_x, 0.1)
×
77
    return pointa, pointb
×
78

79

80
class OpacitySlider:
×
81
    """Opacity Slider for pyvista pl.add_slider_widget."""
82

83
    def __init__(self, pl_actor):
×
84
        self.pl_actor = pl_actor
×
85

86
    def __call__(self, value):
×
87
        self.pl_actor.GetProperty().SetOpacity(value)
×
88

89

90
def add_3d_isosurfaces(
×
91
    vol,
92
    pl,
93
    isovalues=[30, 40, 50],
94
    opacities=[0.3, 0.5, 1],
95
    method="contour",
96
    style="surface",
97
    add_sliders=False,
98
    **mesh_kwargs,
99
):
100
    """Add 3D isosurface to a pyvista plotter object.
101

102
    If add_sliders=True, isosurface opacity can be adjusted interactively.
103

104
    """
105
    # Checks
106
    if len(isovalues) != len(opacities):
×
107
        raise ValueError("Expected same number of isovalues and opacities values.")
×
108
    # TODO: check there are values larger than max isovalues
109

110
    # Define opacity dictionary
111
    dict_opacity = dict(zip(isovalues, opacities))
×
112

113
    # Precompute isosurface
114
    dict_isosurface = {isovalue: vol.contour([isovalue], method=method) for isovalue in isovalues}
×
115
    n_isosurfaces = len(dict_isosurface)
×
116
    # Add isosurfaces
117
    for i, (isovalue, isosurface) in enumerate(dict_isosurface.items()):
×
118
        pl_actor = pl.add_mesh(
×
119
            isosurface, opacity=dict_opacity[isovalue], style=style, **mesh_kwargs
120
        )
121
        if add_sliders:
×
122
            # Define opacity slider
123
            opacity_slider = OpacitySlider(pl_actor)
×
124
            # Define slicer button position
125
            pointa, pointb = get_slider_button_positions(i=i, num_buttons=n_isosurfaces)
×
126
            # Add slider widget
127
            pl.add_slider_widget(
×
128
                callback=opacity_slider,
129
                rng=[0, 1],
130
                value=dict_opacity[isovalue],
131
                title=f"Isovalue={isovalue}",
132
                pointa=pointa,
133
                pointb=pointb,
134
                style="modern",
135
            )
136

137

138
class IsosurfaceSlider:
×
139
    def __init__(self, vol, method="contour", isovalue=None):
×
140
        """Define pyvista slider for 3D isosurfaces."""
141

142
        self.vol = vol
×
143
        vmin, vmax = vol.get_data_range()
×
144
        self.vmin = vmin
×
145
        self.vmax = vmax
×
146
        self.method = method
×
147
        if isovalue is None:
×
148
            isovalue = vmin + (vmax - vmin) / 2
×
149
        self.isovalue = isovalue
×
150
        self.isosurface = vol.contour([isovalue], method=method)
×
151

152
    def __call__(self, value):
×
153
        self.isovalue = value
×
154
        self.update()
×
155

156
    def update(self):
×
157
        result = self.vol.contour([self.isovalue], method=self.method)
×
158
        self.isosurface.copy_from(result)
×
159

160

161
def add_3d_isosurface_slider(vol, pl, method="contour", isovalue=None, **mesh_kwargs):
×
162
    """Add a 3D isosurface slider enabling to slide through the 3D volume."""
163
    isosurface_slider = IsosurfaceSlider(vol, method=method, isovalue=isovalue)
×
164
    isosurface = isosurface_slider.isosurface
×
165
    pl.add_mesh(isosurface, **mesh_kwargs)
×
166
    pl.add_slider_widget(
×
167
        callback=isosurface_slider,
168
        rng=vol.get_data_range(),
169
        value=isosurface_slider.isovalue,
170
        title="Isosurface",
171
        pointa=(0.4, 0.9),
172
        pointb=(0.9, 0.9),
173
        style="modern",
174
    )
175

176

177
class OrthogonalSlicesSlider:
×
178
    def __init__(self, vol, x=1, y=1, z=1):
×
179
        """Define pyvista sliders for 3D orthogonal slices."""
180

181
        self.vol = vol
×
182
        self.slices = vol.slice_orthogonal(x=x, y=y, z=z)
×
183
        # Set default parameters
184
        self.kwargs = {
×
185
            "x": x,
186
            "y": y,
187
            "z": z,
188
        }
189

190
    def __call__(self, param, value):
×
191
        self.kwargs[param] = value
×
192
        self.update()
×
193

194
    def update(self):
×
195
        # This is where you call your simulation
196
        result = self.vol.slice_orthogonal(**self.kwargs)
×
197
        self.slices[0].copy_from(result[0])
×
198
        self.slices[1].copy_from(result[1])
×
199
        self.slices[2].copy_from(result[2])
×
200
        return
×
201

202

203
def add_3d_orthogonal_slices(vol, pl, x=None, y=None, z=None, add_sliders=False, **mesh_kwargs):
×
204
    """Add 3D orthogonal slices with interactive sliders."""
205
    # Define bounds
206
    x_rng = vol.bounds[0:2]
×
207
    y_rng = vol.bounds[2:4]
×
208
    z_rng = vol.bounds[4:6]
×
209

210
    # Define default values if not provided
211
    # - If value is 0, means no slice plotted !
212
    if x is None:
×
213
        x = int(np.diff(x_rng) / 2)
×
214
    if y is None:
×
215
        y = int(np.diff(y_rng) / 2)
×
216
    if z is None:
×
217
        z = int(z_rng[0] + 0.01)
×
218

219
    # Define orthogonal slices (and sliders)
220
    if add_sliders:
×
221
        orthogonal_slices_slider = OrthogonalSlicesSlider(vol)
×
222
        orthogonal_slices = orthogonal_slices_slider.slices
×
223
    else:
224
        orthogonal_slices = vol.slice_orthogonal(x=x, y=y, z=z, progress_bar=False)
×
225

226
    # Display orthogonal slices
227
    pl.add_mesh(orthogonal_slices, **mesh_kwargs)
×
228

229
    # Add slider widgets
230
    if add_sliders:
×
231
        pl.add_slider_widget(
×
232
            callback=lambda value: orthogonal_slices_slider("x", int(value)),
233
            rng=x_rng,
234
            value=x,
235
            title="Along-Track",
236
            pointa=(0.025, 0.1),
237
            pointb=(0.31, 0.1),
238
            style="modern",
239
        )
240
        pl.add_slider_widget(
×
241
            callback=lambda value: orthogonal_slices_slider("y", int(value)),
242
            rng=y_rng,
243
            value=y,
244
            title="Cross-Track",
245
            pointa=(0.35, 0.1),
246
            pointb=(0.64, 0.1),
247
            style="modern",
248
        )
249
        pl.add_slider_widget(
×
250
            callback=lambda value: orthogonal_slices_slider("z", value),
251
            rng=z_rng,
252
            value=z,
253
            title="Elevation",
254
            pointa=(0.67, 0.1),
255
            pointb=(0.98, 0.1),
256
            style="modern",
257
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc