Skip to content

Commit 47f3da2

Browse files
berkgevecijourdain
authored andcommitted
fix(crop): crop variable field by absolute lat/lon range
The variable-data crop filter (EAMExtract) trimmed by amounts measured from the global extent (range_to_trim) but applied them to the data's own bounds, while the base map (EAMTransformAndExtract) clipped at absolute lat/lon. The two frames only agree for global data, so a regional mesh (e.g. lat 55-85N) had its field mis-cropped while the map cropped correctly. Give EAMExtract absolute LongitudeRange/LatitudeRange and mask cell centers directly, in the same [-180,180] x [-90,90] frame as EAMTransformAndExtract. Drop range_to_trim; DataReader.crop now passes the raw [min, max] ranges. Both map and field now share one convention and crop consistently for any data extent.
1 parent fe46fcf commit 47f3da2

2 files changed

Lines changed: 39 additions & 49 deletions

File tree

src/e3sm_quickview/pipeline.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,6 @@
77
from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper
88

99

10-
def range_to_trim(range, max):
11-
"""
12-
Convert an range interval contained in [-180, 180] (such as [-170, 160])
13-
into an interval specifying how much to trim (for our example: [10, 20]).
14-
'max' is 180 for longitude or 90 for latitude
15-
"""
16-
(min_range, max_range) = range
17-
return [min_range + max, max - max_range]
18-
19-
2010
def load_plugins():
2111
try:
2212
plugin_dir = Path(__file__).with_name("plugins")
@@ -151,8 +141,8 @@ def __init__(self, projection="Mollweide"):
151141
)
152142
self._crop = simple.EAMExtract(
153143
Input=self.center_meridian,
154-
TrimLongitude=[0, 0],
155-
TrimLatitude=[0, 0],
144+
LongitudeRange=[-180, 180],
145+
LatitudeRange=[-90, 90],
156146
)
157147
self.proj = simple.EAMProject( # noqa: F821
158148
Input=self._crop,
@@ -250,8 +240,8 @@ def update(self, time=0.0):
250240
self.geometry.UpdatePipeline(time)
251241

252242
def crop(self, longitude_min_max, latitude_min_max):
253-
self._crop.TrimLongitude = range_to_trim(longitude_min_max, 180)
254-
self._crop.TrimLatitude = range_to_trim(latitude_min_max, 90)
243+
self._crop.LongitudeRange = longitude_min_max
244+
self._crop.LatitudeRange = latitude_min_max
255245

256246

257247
class EAMVisSource:

src/e3sm_quickview/plugins/eam_projection.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -596,15 +596,15 @@ def RequestData(self, request, inInfo, outInfo):
596596
@smdomain.datatype(dataTypes=["vtkPolyData"], composite_data_supported=False)
597597
@smproperty.xml(
598598
"""
599-
<DoubleVectorProperty name="Trim Longitude"
600-
command="SetTrimLongitude"
599+
<DoubleVectorProperty name="Longitude Range"
600+
command="SetLongitudeRange"
601601
number_of_elements="2"
602-
default_values="0 0">
602+
default_values="-180 180">
603603
</DoubleVectorProperty>
604-
<DoubleVectorProperty name="Trim Latitude"
605-
command="SetTrimLatitude"
604+
<DoubleVectorProperty name="Latitude Range"
605+
command="SetLatitudeRange"
606606
number_of_elements="2"
607-
default_values="0 0">
607+
default_values="-90 90">
608608
</DoubleVectorProperty>
609609
"""
610610
)
@@ -613,44 +613,44 @@ def __init__(self):
613613
super().__init__(
614614
nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid"
615615
)
616-
self.trim_lon = [0, 0]
617-
self.trim_lat = [0, 0]
616+
self.lon_range = [-180.0, 180.0]
617+
self.lat_range = [-90.0, 90.0]
618618
self.cached_cell_centers = None
619619
self._cached_output = None
620-
self._last_was_trimmed = False
620+
self._last_was_cropped = False
621621

622-
def SetTrimLongitude(self, left, right):
623-
if left < 0 or left > 360 or right < 0 or right > 360 or left > (360 - right):
622+
def SetLongitudeRange(self, min, max):
623+
if min < -180 or max > 180 or min > max:
624624
print_error(
625-
f"SetTrimLongitude called with invalid parameters: {left=}, {right=}"
625+
f"SetLongitudeRange called with invalid parameters: {min=}, {max=}"
626626
)
627627
return
628-
if self.trim_lon[0] != left or self.trim_lon[1] != right:
629-
self.trim_lon = [left, right]
628+
if self.lon_range[0] != min or self.lon_range[1] != max:
629+
self.lon_range = [min, max]
630630
self.Modified()
631631

632-
def SetTrimLatitude(self, left, right):
633-
if left < 0 or left > 180 or right < 0 or right > 180 or left > (180 - right):
632+
def SetLatitudeRange(self, min, max):
633+
if min < -90 or max > 90 or min > max:
634634
print_error(
635-
f"SetTrimLatitude called with invalid parameters: {left=}, {right=}"
635+
f"SetLatitudeRange called with invalid parameters: {min=}, {max=}"
636636
)
637637
return
638-
if self.trim_lat[0] != left or self.trim_lat[1] != right:
639-
self.trim_lat = [left, right]
638+
if self.lat_range[0] != min or self.lat_range[1] != max:
639+
self.lat_range = [min, max]
640640
self.Modified()
641641

642642
def RequestData(self, request, inInfo, outInfo):
643643
with _perf.timed("extract.RequestData"):
644644
inData = self.GetInputData(inInfo, 0, 0)
645645
outData = self.GetOutputData(outInfo, 0)
646-
if self.trim_lon == [0, 0] and self.trim_lat == [0, 0]:
646+
if self.lon_range == [-180.0, 180.0] and self.lat_range == [-90.0, 90.0]:
647647
outData.ShallowCopy(inData)
648648
# Only invalidate the shared points when transitioning *out* of a
649-
# trimmed state — the original code did it unconditionally, which
649+
# cropped state — the original code did it unconditionally, which
650650
# defeated EAMProject's cache on every pipeline update.
651-
if self._last_was_trimmed:
651+
if self._last_was_cropped:
652652
outData.GetPoints().Modified()
653-
self._last_was_trimmed = False
653+
self._last_was_cropped = False
654654
return 1
655655

656656
if self.cached_cell_centers and self.cached_cell_centers.GetMTime() >= max(
@@ -699,20 +699,20 @@ def RequestData(self, request, inInfo, outInfo):
699699
cell_types.DeepCopy(outData.GetCellTypesArray())
700700
outData.SetCells(cell_types, cells)
701701

702-
# compute the new bounds by trimming the inData bounds
703-
bounds = list(inData.GetBounds())
704-
bounds[0] = bounds[0] + self.trim_lon[0]
705-
bounds[1] = bounds[1] - self.trim_lon[1]
706-
bounds[2] = bounds[2] + self.trim_lat[0]
707-
bounds[3] = bounds[3] - self.trim_lat[1]
702+
# Crop against absolute lon/lat ranges, in the same
703+
# [-180, 180] x [-90, 90] frame as EAMTransformAndExtract,
704+
# so the result is independent of the data's own extent
705+
# (regional meshes no longer get mis-cropped).
706+
lon_min, lon_max = self.lon_range
707+
lat_min, lat_max = self.lat_range
708708

709-
# add HIDDENCELL based on bounds
709+
# add HIDDENCELL based on ranges
710710
with _perf.timed("extract.ghost_mask"):
711711
outside_mask = (
712-
(cc[:, 0] < bounds[0])
713-
| (cc[:, 0] > bounds[1])
714-
| (cc[:, 1] < bounds[2])
715-
| (cc[:, 1] > bounds[3])
712+
(cc[:, 0] < lon_min)
713+
| (cc[:, 0] > lon_max)
714+
| (cc[:, 1] < lat_min)
715+
| (cc[:, 1] > lat_max)
716716
)
717717
# Create ghost array (0 = visible, HIDDENCELL = invisible)
718718
ghost_np = np.where(
@@ -728,7 +728,7 @@ def RequestData(self, request, inInfo, outInfo):
728728

729729
self._cached_output = outData.NewInstance()
730730
self._cached_output.ShallowCopy(outData)
731-
self._last_was_trimmed = True
731+
self._last_was_cropped = True
732732
return 1
733733

734734

0 commit comments

Comments
 (0)