From 9b336859ac227369090d7f85e1a984af097053d4 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Mon, 9 May 2022 17:41:44 -0600 Subject: [PATCH 1/6] ENH: Change CME to a single surface with opacity This changes the CME to be a contoured surface based on DP, rather than displaying the entire volume. It highlights where the CME is in space now, rather than showing the data within the volume of the CME. --- pvw/server/enlil.py | 53 +++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index 3966487..7bd9dc6 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -93,12 +93,18 @@ def __init__(self, dirname): registrationName='enlil-data', FileName=[fname]) self.data.Dimensions = '(longitude, latitude, radius)' + # Force all cell data to point data right off the bat + self.data = pvs.CellDatatoPointData( + registrationName=f"3D-CellDatatoPointData", + Input=self.data) + self.data.ProcessAllArrays = 1 + self.data.PassCellData = 1 + self.time_string = pvs.Text(registrationName='Time') # Don't add in any text right now self.time_string.Text = "" # Need to keep track of whether the CME/Threshold should be visible # so that we can hide 0 value returns - self._CME_VISIBLE = True self._THRESHOLD_VISIBLE = False # Keep track of satellite labels self._sat_views = [] @@ -110,10 +116,12 @@ def __init__(self, dirname): self.threshold_cme.ThresholdRange = [1e-5, 1e5] # DP is the variable name in Enlil self.threshold_cme.Scalars = ['CELLS', 'DP'] - # This resamples the CME to a uniform grid to make Volume rendering - # work better and faster - self.cme = pvs.ResampleToImage(registrationName='resampled_cme', - Input=self.threshold_cme) + self.cme = pvs.Contour(registrationName='contoured_cme', + Input=self.data) + self.cme.ContourBy = ['POINTS', 'DP'] + self.cme.ComputeNormals = 0 + self.cme.Isosurfaces = [1e-06] + self.cme.PointMergeMethod = 'Uniform Binning' # Move to point arrays for contouring self._cme_points = pvs.CellDatatoPointData( @@ -216,16 +224,11 @@ def _setup_views(self): # CME Threshold disp = pvs.Show(self.cme, self.view, - 'UniformGridRepresentation') + 'GeometryRepresentation') self.displays[self.cme] = disp - disp.Representation = 'Volume' - disp.ColorArrayName = ['POINTS', 'Bz'] - disp.LookupTable = bzLUT - disp.OpacityArray = [None, ''] - disp.OpacityTransferFunction = 'PiecewiseFunction' - disp.ScalarOpacityFunction = bzPWF - disp.ScalarOpacityUnitDistance = 0.02 - disp.OpacityArrayName = [None, ''] + disp.Representation = 'Surface' + disp.ColorArrayName = [None, ''] + disp.Opacity = 0.25 # CME Contours disp = pvs.Show(self.cme_contours, self.view, @@ -534,9 +537,7 @@ def change_visibility(self, obj, visibility): else: return ["Visibility can only be 'on' or 'off'"] - # Keep track of whether the CME and Threshold variables are visible - if obj == "cme": - self._CME_VISIBLE = {"on": True, "off": False}[visibility] + # Keep track of whether the Threshold variables are visible if obj == "threshold": self._THRESHOLD_VISIBLE = {"on": True, "off": False}[visibility] self.update(None, None) @@ -556,7 +557,8 @@ def change_color_variable(self, name): # Update all displays to be colored by this variable for obj, disp in self.displays.items(): if obj in (self.lon_arrows, self.lon_streamlines, - self.lat_arrows, self.lat_streamlines): + self.lat_arrows, self.lat_streamlines, + self.cme): # We don't want to update the longitude arrow colors continue pvs.ColorBy(disp, variable) @@ -861,21 +863,6 @@ def update(self, caller, event): getattr(self, x).Center = self.evolutions[x].get_position( curr_time) - if self._CME_VISIBLE: - # We need to force an update of the filters to populate the data. - # The CellData[variable] will be None if there is no data - # calculated based on the thresholding. In that case, we want to - # hide the object from view. - # NOTE: We only want to update the pipeline if necessary - pvs.UpdatePipeline(time=pv_time, proxy=self.threshold_cme) - if self.cme.Input.CellData['DP'] is None: - pvs.Hide(self.cme, self.view) - else: - pvs.Show(self.cme, self.view) - else: - # CME isn't visible, so hide it - pvs.Hide(self.cme, self.view) - if self._THRESHOLD_VISIBLE: # NOTE: Only update pipeline if necessary, same as the CME pvs.UpdatePipeline(time=pv_time, proxy=self.threshold_data) From a6cb05916dadc35977b8e92bb5d1ebaad0be59f9 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Mon, 9 May 2022 17:42:46 -0600 Subject: [PATCH 2/6] ENH: Smooth the lon/lat surfaces This colors by point-data rather than cell-data which looks nicer from a visual perspective rather than showing the cell outlines. --- pvw/server/enlil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index 7bd9dc6..06f3fc2 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -255,14 +255,14 @@ def _setup_views(self): 'GeometryRepresentation') self.displays[self.lat_slice] = disp disp.Representation = 'Surface' - disp.ColorArrayName = ['CELLS', 'Bz'] + disp.ColorArrayName = ['POINTS', 'Bz'] disp.LookupTable = bzLUT # Longitude disp = pvs.Show(self.lon_slice, self.view, 'GeometryRepresentation') self.displays[self.lon_slice] = disp disp.Representation = 'Surface' - disp.ColorArrayName = ['CELLS', 'Bz'] + disp.ColorArrayName = ['POINTS', 'Bz'] disp.LookupTable = bzLUT # Streamlines From 47f56849173acfb75b402ddc2498475610edfb12 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 10 May 2022 14:54:42 -0600 Subject: [PATCH 3/6] MNT: Remove unused cell to point data filter --- pvw/server/enlil.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index 06f3fc2..e75ce2b 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -89,14 +89,14 @@ def __init__(self, dirname): self.evolutions = {x.name: x for x in load_evolution_files(dirname)} # create a new 'NetCDF Reader' from the full data path fname = os.path.join(dirname, "pv-data-3d.nc") - self.data = pvs.NetCDFReader( + self.celldata = pvs.NetCDFReader( registrationName='enlil-data', FileName=[fname]) - self.data.Dimensions = '(longitude, latitude, radius)' + self.celldata.Dimensions = '(longitude, latitude, radius)' - # Force all cell data to point data right off the bat + # Force all cell data to point data in the volume self.data = pvs.CellDatatoPointData( registrationName=f"3D-CellDatatoPointData", - Input=self.data) + Input=self.celldata) self.data.ProcessAllArrays = 1 self.data.PassCellData = 1 @@ -123,15 +123,9 @@ def __init__(self, dirname): self.cme.Isosurfaces = [1e-06] self.cme.PointMergeMethod = 'Uniform Binning' - # Move to point arrays for contouring - self._cme_points = pvs.CellDatatoPointData( - registrationName=f"CME-points", - Input=self.threshold_cme) - self._cme_points.ProcessAllArrays = 1 - self.cme_contours = pvs.Contour( registrationName='CME-contour', - Input=self._cme_points) + Input=self.threshold_cme) self.cme_contours.ContourBy = ['POINTS', 'Density'] self.cme_contours.Isosurfaces = [] self.cme_contours.PointMergeMethod = 'Uniform Binning' From 9e7b82a5d727b67f925f2bedcdfe81d74010fcce Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 10 May 2022 15:37:37 -0600 Subject: [PATCH 4/6] FIX: Push calculations onto the planes This removes the full domain CellDatatoPointData filter unless it is needed, before we were applying it for every timestep. --- pvw/server/enlil.py | 71 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index e75ce2b..ccdcc07 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -142,23 +142,33 @@ def __init__(self, dirname): Input=self.threshold_data) # Create a Longitude slice - self.lon_slice = pvs.Slice( - registrationName='Longitude', Input=self.data) - self.lon_slice.SliceType = 'Plane' - self.lon_slice.HyperTreeGridSlicer = 'Plane' - self.lon_slice.SliceOffsetValues = [0.0] - self.lon_slice.SliceType.Origin = [0, 0, 0] - self.lon_slice.SliceType.Normal = [0.0, 0.0, 1.0] + self.lon_slice_data = pvs.Slice( + registrationName='Longitude', Input=self.celldata) + self.lon_slice_data.SliceType = 'Plane' + self.lon_slice_data.HyperTreeGridSlicer = 'Plane' + self.lon_slice_data.SliceOffsetValues = [0.0] + self.lon_slice_data.SliceType.Origin = [0, 0, 0] + self.lon_slice_data.SliceType.Normal = [0.0, 0.0, 1.0] + # Now make point data on that slice + self.lon_slice = pvs.CellDatatoPointData( + registrationName=f"lon-slice-CellDatatoPointData", + Input=self.lon_slice_data) + self.lon_slice.ProcessAllArrays = 1 self._add_streamlines("lon") # Create a Latitude slice - self.lat_slice = pvs.Slice( - registrationName='Latitude', Input=self.data) - self.lat_slice.SliceType = 'Plane' - self.lat_slice.HyperTreeGridSlicer = 'Plane' - self.lat_slice.SliceOffsetValues = [0.0] - self.lat_slice.SliceType.Origin = [0, 0, 0] - self.lat_slice.SliceType.Normal = [0.0, 1.0, 0.0] + self.lat_slice_data = pvs.Slice( + registrationName='Latitude', Input=self.celldata) + self.lat_slice_data.SliceType = 'Plane' + self.lat_slice_data.HyperTreeGridSlicer = 'Plane' + self.lat_slice_data.SliceOffsetValues = [0.0] + self.lat_slice_data.SliceType.Origin = [0, 0, 0] + self.lat_slice_data.SliceType.Normal = [0.0, 1.0, 0.0] + # Now make point data on that slice + self.lat_slice = pvs.CellDatatoPointData( + registrationName=f"lat-slice-CellDatatoPointData", + Input=self.lat_slice_data) + self.lat_slice.ProcessAllArrays = 1 self._add_streamlines("lat") # Dictionary mapping of string names to the object @@ -387,38 +397,26 @@ def _add_streamlines(self, plane): raise ValueError("The plane must be one of ('lon', 'lat').") # Our stream tracer source needs to have the same plane # as our slice, and 0.2 for the radius - curr_slice = getattr(self, f"{plane}_slice") + curr_slice_data = getattr(self, f"{plane}_slice_data") stream_source = pvs.Ellipse(registrationName=f"{plane}-StreamSource") stream_source.Center = [0.0, 0.0, 0.0] - stream_source.Normal = curr_slice.SliceType.Normal + stream_source.Normal = curr_slice_data.SliceType.Normal radius = [0.2, 0, 0] if plane == "lon" else [0, 0, 0.2] stream_source.MajorRadiusVector = radius # Controls how many streamlines we have stream_source.Resolution = 50 # Create the magnetic field vectors through a PV Function + curr_slice = getattr(self, f"{plane}_slice") bvec = pvs.Calculator(registrationName=f"{plane}-Bvec", Input=curr_slice) - bvec.AttributeType = 'Cell Data' + bvec.AttributeType = 'Point Data' bvec.ResultArrayName = 'Bvec' bvec.Function = 'Bx*iHat + By*jHat + Bz*kHat' - # Make sure we are doing the CellDatatoPointData on the slice - # directly and not up above, so that it is perfectly on this plane, - # otherwise the streamlines will be out-of-plane potentially - point_data = pvs.CellDatatoPointData( - registrationName=f"{plane}-CellDatatoPointData", - Input=bvec) - # Limit the arrays to process to the vector components and - # direction (Br) for coloring the polarity - # NOTE: In the processing steps, we multiply - # Br * sign(BP), meaning the Br here contains the - # polarity implicitly. - point_data.ProcessAllArrays = 0 - point_data.CellDataArraytoprocess = ['Br', 'Bvec'] stream_input = pvs.StreamTracerWithCustomSource( registrationName=f"{plane}-StreamTracerWithCustomSource", - Input=point_data, + Input=bvec, SeedSource=stream_source) stream_input.Vectors = ['POINTS', 'Bvec'] stream_input.SurfaceStreamlines = 1 @@ -485,7 +483,7 @@ def get_variable_range(self, name): Name of variable to colormap all of the surfaces by """ variable = VARIABLE_MAP[name] - return self.data.CellData.GetArray(variable).GetRange() + return self.celldata.CellData.GetArray(variable).GetRange() @exportRpc("pv.enlil.directory") def update_dataset(self, dirname): @@ -726,9 +724,9 @@ def snap_solar_plane(self, clip): raise ValueError('The snapping clip plane must be either ' '"ecliptic" or "equator"') - self.lon_slice.SliceType.Normal = loc + self.lon_slice_data.SliceType.Normal = loc # Also update the stream source so they stay in-sync - self.lon_stream_source.Normal = self.lon_slice.SliceType.Normal + self.lon_stream_source.Normal = loc @exportRpc("pv.enlil.rotate_plane") def rotate_plane(self, plane, angle): @@ -747,8 +745,9 @@ def rotate_plane(self, plane, angle): # We want the normal to the plane, so we are really rotating the # normal vector here, which swaps the x/z and adds a negative # tilt is only in the xz plane - self.lon_slice.SliceType.Normal = [-y, 0, -x] - self.lon_stream_source.Normal = self.lon_slice.SliceType.Normal + loc = [-y, 0, -x] + self.lon_slice_data.SliceType.Normal = loc + self.lon_stream_source.Normal = loc elif plane == "lat": raise NotImplementedError("Updating the latitudinal plane angle " "is not implemented.") From 7d78b290011dcf292fa09b05beb3e52bb31d3456 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 10 May 2022 15:51:40 -0600 Subject: [PATCH 5/6] ENH: Add streamlines to the latitudinal plane --- pvw/server/enlil.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index ccdcc07..07b0d66 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -294,12 +294,45 @@ def _setup_views(self): disp.ColorArrayName = ["POINTS", 'Br'] disp.LookupTable = bpLUT + # latitudinal plane + disp = pvs.Show(self.lat_slice, self.view, 'GeometryRepresentation') + self.displays[self.lat_slice] = disp + disp.Representation = 'Surface' + disp.ColorArrayName = ['POINTS', 'Bz'] + disp.LookupTable = bzLUT + + # Streamlines + disp = pvs.Show(self.lat_streamlines, self.view, + 'GeometryRepresentation') + # Add in a magnetic polarity colormap (radial in or out) + # with two values blue/red + # separate=True makes sure it doesn't overwrite the Br of the + # frontend choices + bpLUT = pvs.GetColorTransferFunction('Br', disp, separate=True) + bpLUT.RGBPoints = [-1e5, 0.5, 0.5, 0.5, + 1e5, 0.9, 0.9, 0.9] + bpLUT.ScalarRangeInitialized = 1.0 + bpLUT.NumberOfTableValues = 2 + self.displays[self.lat_streamlines] = disp + disp.Representation = 'Surface' + disp.ColorArrayName = ["POINTS", 'Br'] + disp.LookupTable = bpLUT + + # B-field vectors + disp = pvs.Show(self.lat_arrows, self.view, + 'GeometryRepresentation') + self.displays[self.lat_arrows] = disp + disp.Representation = 'Surface' + disp.ColorArrayName = ["POINTS", 'Br'] + disp.LookupTable = bpLUT + # Set colormaps for name in VARIABLE_MAP: self.set_colormap(name) # hide this data from the default initial view for x in [self.lon_slice, self.lon_arrows, self.lon_streamlines, + self.lat_arrows, self.lat_streamlines, self.threshold, self.cme_contours]: pvs.Hide(x, self.view) @@ -521,11 +554,17 @@ def change_visibility(self, obj, visibility): if obj == "lon_streamlines": # We also want to turn on the vectors pvs.Show(self.objs["lon_arrows"], self.view) + elif obj == "lat_streamlines": + # We also want to turn on the vectors + pvs.Show(self.objs["lat_arrows"], self.view) elif visibility == "off": pvs.Hide(self.objs[obj], self.view) if obj == "lon_streamlines": # We also want to turn off the vectors pvs.Hide(self.objs["lon_arrows"], self.view) + elif obj == "lat_streamlines": + # We also want to turn off the vectors + pvs.Hide(self.objs["lat_arrows"], self.view) else: return ["Visibility can only be 'on' or 'off'"] @@ -574,6 +613,8 @@ def change_color_variable(self, name): # But we want to hide the streamlines colorbar disp = self.displays[self.lon_streamlines] disp.SetScalarBarVisibility(self.view, False) + disp = self.displays[self.lat_streamlines] + disp.SetScalarBarVisibility(self.view, False) # restore active source pvs.SetActiveSource(None) From 8e80dec761da5b0b506e4a0195d49828f87a3bd2 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Tue, 10 May 2022 16:01:31 -0600 Subject: [PATCH 6/6] ENH: Add ability to rotate the latitudinal plane --- pvw/server/enlil.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pvw/server/enlil.py b/pvw/server/enlil.py index 07b0d66..1210913 100644 --- a/pvw/server/enlil.py +++ b/pvw/server/enlil.py @@ -790,14 +790,9 @@ def rotate_plane(self, plane, angle): self.lon_slice_data.SliceType.Normal = loc self.lon_stream_source.Normal = loc elif plane == "lat": - raise NotImplementedError("Updating the latitudinal plane angle " - "is not implemented.") - # TODO: Implement the latitudinal adjustment. - # Currently this fails with a segfault, which I - # assume has to do with some slicing of grid cells changing - # sizes when creating a new slice angle, but it is odd that - # it only happens for the latitudinal plane... - # self.lat_slice.SliceType.Normal = [-y, x, 0] + loc = [-y, x, 0] + self.lat_slice_data.SliceType.Normal = loc + self.lat_stream_source.Normal = loc else: raise ValueError("You can only update the 'lon' or 'lat' plane.")