Skip to content

Commit

Permalink
Initial commit for bug fix #393 - Sift mixes up projections and area …
Browse files Browse the repository at this point in the history
…definitions; unit testing still pending
  • Loading branch information
AnaVukasinovic committed Feb 3, 2025
1 parent 6a4aa6d commit f7ffc9d
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# PyCharm project settings
.idea

# VSCode project settings
.vscode

# OSX DS files
.DS_Store

Expand Down Expand Up @@ -115,3 +118,6 @@ CMakeCache.txt
cmake_install.cmake
CMakeFiles/
Makefile

# Windows soft links
*.lnk
Binary file added Test_Data.lnk
Binary file not shown.
6 changes: 6 additions & 0 deletions uwsift/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
from uwsift.workspace import CachingWorkspace, SimpleWorkspace
from uwsift.workspace.collector import ResourceSearchPathCollector


LOG = logging.getLogger(__name__)
configure_loggers()

Expand Down Expand Up @@ -1099,6 +1100,11 @@ def _open_wizard(self, *args, **kwargs):

if self._wizard_dialog.exec_():
LOG.info("Loading products from open wizard...")
# The selected projection in the main window is the same as the one chosen in the open file wizard
self.ui.projectionComboBox.setCurrentIndex(self._wizard_dialog.ui.projectionComboBox.currentIndex())
#setting resampling info in layer details pane for displaying the resolution of the Area
self.ui.layerDetailsPane.set_resampling_info(self._wizard_dialog.resampling_info)
#
scenes = self._wizard_dialog.scenes
reader = self._wizard_dialog.get_reader()

Expand Down
65 changes: 49 additions & 16 deletions uwsift/etc/SIFT/config/area_definitions.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,54 @@
area_definitions:

MTG FCI FDSS 1km: mtg_fci_fdss_1km
MTG FCI FDSS 2km: mtg_fci_fdss_2km
MTG FCI FDSS 500m: mtg_fci_fdss_500m
MSG SEVIRI FES 3km: msg_seviri_fes_3km
MSG SEVIRI FES 1km: msg_seviri_fes_1km
MSG SEVIRI RSS 3km: msg_seviri_rss_3km
MSG SEVIRI RSS 1km: msg_seviri_rss_1km
MSG SEVIRI IODC 3km: msg_seviri_iodc_3km
MSG SEVIRI IODC 1km: msg_seviri_iodc_1km
GOES-East ABI F 500m: goes_east_abi_f_500m
GOES-East ABI F 1km: goes_east_abi_f_1km
GOES-East ABI F 2km: goes_east_abi_f_2km
GOES-West ABI F 500m: goes_west_abi_f_500m
GOES-West ABI F 1km: goes_west_abi_f_1km
GOES-West ABI F 2km: goes_west_abi_f_2km
EUROL: eurol
MTG FCI FDSS:
{
1km: mtg_fci_fdss_1km,
2km: mtg_fci_fdss_2km,
500m: mtg_fci_fdss_500m,
32km: mtg_fci_fdss_32km
}

MSG SEVERI FES:
{
3km: msg_seviri_fes_3km,
1km: msg_seviri_fes_1km,
9km: msg_seviri_fes_9km
}
MSG SEVIRI RSS:
{
3km: msg_seviri_rss_3km,
1km: msg_seviri_rss_1km
}
MSG SEVIRI IODC:
{
3km: msg_seviri_iodc_3km,
1km: msg_seviri_iodc_1km
}
GOES-East ABI F:
{
500m: goes_east_abi_f_500m,
1km: goes_east_abi_f_1km,
2km: goes_east_abi_f_2km
}

GOES-West ABI F:
{
500m: goes_west_abi_f_500m,
1km: goes_west_abi_f_1km,
2km: goes_west_abi_f_2km
}

EUROL:
{
eurol: eurol
}








#---- SIFT doesn't implement proj=eqc correctly/completely ----
# Plate Carree 3km: worldeqc3km
3 changes: 2 additions & 1 deletion uwsift/etc/SIFT/config/logging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ logging:
# for all loggers.
loggers:
all:
level: DEBUG
#level: DEBUG
level: INFO
vispy:
level: INFO
sqlalchemy:
Expand Down
1 change: 1 addition & 0 deletions uwsift/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@
"""

from uwsift.model.document import Document # noqa

105 changes: 81 additions & 24 deletions uwsift/model/area_definitions_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
# plane chart) should be available always. It will be only added though,
# if there is no other area definition found in configuration which uses the
# 'latlong' projection (or its PROJ.4 aliases)
"Plate Carree": "plate_carree"
"Plate Carree":
{
"plate_carree" : "plate_carree"
}
}

DEFAULT_AREA_DEFINITIONS_YAML = """
plate_carree:
description:
Expand All @@ -54,59 +57,113 @@ class AreaDefinitionsManager:
"""

_available_area_defs_by_id: None | dict[str, AreaDefinition] = None
_available_area_defs_id_by_name: None | dict[str, str] = None
_available_area_defs_group_by_group_name: None | dict[str, dict[str, str]] = None #changed | group_name : { name : id name : id}



@classmethod
def init_available_area_defs(cls) -> None:
cls._available_area_defs_by_id = {}
cls._available_area_defs_id_by_name = {}
cls._available_area_defs_group_by_group_name = {}

desired_area_defs = config.get("area_definitions", {})

for area_def_name, area_id in desired_area_defs.items():
try:
area_def = get_area_def(area_id)
except AreaNotFound as e:
LOG.warning(
for area_def_group_name, area_def_group in desired_area_defs.items():

for area_resolution, area_id in area_def_group.items():
try:
area_def = get_area_def(area_id)
except AreaNotFound as e:
LOG.warning(
f"Area definition configured for display name" f" '{area_def_name}' unknown: {e}. Skipping..."
)
continue
)
continue
#adding ar
LOG.info(f"Adding area definition: {area_def_group_name} , {area_resolution} -> {area_id}")
cls._available_area_defs_by_id[area_id] = area_def

cls._available_area_defs_group_by_group_name[area_def_group_name] = area_def_group


LOG.info(f"Adding area definition: {area_def_name} -> {area_id}")
cls._available_area_defs_by_id[area_id] = area_def
cls._available_area_defs_id_by_name[area_def_name] = area_id
# cls._available_area_defs_group_by_group_name and cls._available_area_defs_by_id are initialised

# Check for existence of at least one 'latlong' projection
# (https://proj.org/operations/conversions/latlon.html)
for area_def in cls._available_area_defs_by_id.values():
if area_def.crs.is_geographic:
return

# Add default area definition(s)?
for area_def_name, area_id in DEFAULT_AREAS.items():
area_def = load_area_from_string(DEFAULT_AREA_DEFINITIONS_YAML, area_id)
for area_def_group_name, area_def_group in DEFAULT_AREAS.items():
for area_resolution, area_id in area_def_group.items():
area_def = load_area_from_string(DEFAULT_AREA_DEFINITIONS_YAML, area_id)

LOG.info(f"Adding area definition: {area_def_group_name} , {area_resolution} -> {area_id}")
cls._available_area_defs_by_id[area_id] = area_def

LOG.info(f"Adding default area definition:" f" {area_def_name} -> {area_id}")
cls._available_area_defs_by_id[area_id] = area_def
cls._available_area_defs_id_by_name[area_def_name] = area_id
cls._available_area_defs_group_by_group_name[area_def_group_name] = area_def_group
#

#returns all area projections
@classmethod
def available_area_def_names(cls):
return cls._available_area_defs_id_by_name.keys()

return cls._available_area_defs_group_by_group_name.keys()
@classmethod
def area_def_by_id(cls, id):
return cls._available_area_defs_by_id.get(id)

#returns the first area definition from the group
@classmethod
def area_def_by_name(cls, name):
return cls.area_def_by_id(cls._available_area_defs_id_by_name.get(name))
area_group = cls._available_area_defs_group_by_group_name.get(name)
first_key = next(iter(area_group))
area_id = area_group[first_key]
return cls.area_def_by_id(area_id)

@classmethod
def default_area_def_name(cls): # TODO: take from configuration, make robust
# Since nothing has been configured, take the first key (i.e. the
# display_name) of the known area definitions
return next(iter(cls._available_area_defs_id_by_name))
return next(iter(cls._available_area_defs_group_by_group_name))


#returns all possible resolutions for one area projection
@classmethod
def available_area_def_group_resolutions(cls, group_name):
return cls._available_area_defs_group_by_group_name.get(group_name).keys()

#returns area group by its name --- for example if a group_name = MSG SEVERI FES, a return value will be {3 km: msg_seviri_fes_3km, 1 km: msg_seviri_fes_1km}
@classmethod
def area_group_by_group_name(cls, group_name):
return cls._available_area_defs_group_by_group_name.get(group_name)

#returns area definition by its name and resolution --- for example if a group_name = MSG SEVERI FES and resolution = 3km , a return value will be area definition for id = msg_seviri_fes_3km
@classmethod
def area_def_by_group_name_and_resolution(cls, group_name, resolution):
return cls.area_def_by_id(cls._available_area_defs_group_by_group_name.get(group_name).get(resolution))

#prepares area def for resampling
@classmethod
def prepare_area_def_for_resampling(cls, area_def, width, height):
# when the shape is changed, it also affects other parameters in the area definition
area_def.width = width
area_def.height = height
area_def.pixel_size_x = (area_def.area_extent[2] - area_def.area_extent[0]) / float(area_def.width)
area_def.pixel_size_y = (area_def.area_extent[3] - area_def.area_extent[1]) / float(area_def.height)
area_def.pixel_upper_left = (float(area_def.area_extent[0]) + float(area_def.pixel_size_x) / 2, float(area_def.area_extent[3]) - float(area_def.pixel_size_y) / 2)
area_def.pixel_offset_x = -area_def.area_extent[0] / area_def.pixel_size_x
area_def.pixel_offset_y = area_def.area_extent[3] / area_def.pixel_size_y


# calculates pixel_size_x and pixel_size_y for an area definition with custom resolution values
@classmethod
def area_def_custom_resolution_values(cls, area_def, width, height):
if width and height:
pixel_size_x = (area_def.area_extent[2] - area_def.area_extent[0]) / float(width)
pixel_size_y = (area_def.area_extent[3] - area_def.area_extent[1]) / float(height)
return pixel_size_x, pixel_size_y



# TODO: Why does this need to be a class being updated instead of an instance of a class?
Expand Down
1 change: 1 addition & 0 deletions uwsift/model/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def activate_product_uuid_as_new_dataset(self, uuid: UUID, insert_before=0, **im
info[Info.FAMILY] = self._family_for_product_or_info(info)
presentation = self._insert_dataset_with_info(info, insert_before=insert_before)


# signal updates from the document
self.didAddDataset.emit(info, presentation)

Expand Down
18 changes: 13 additions & 5 deletions uwsift/ui/layer_details_widget_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,28 @@ def setupUi(self, LayerDetailsPane):
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.layerWavelengthValue)
self.layerColormapLabel = QtWidgets.QLabel(LayerDetailsPane)
self.layerColormapLabel.setObjectName("layerColormapLabel")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.layerColormapLabel)
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.layerColormapLabel)
self.layerColormapValue = QtWidgets.QLabel(LayerDetailsPane)
self.layerColormapValue.setObjectName("layerColormapValue")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.layerColormapValue)
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.layerColormapValue)
self.layerColorLimitsLabel = QtWidgets.QLabel(LayerDetailsPane)
self.layerColorLimitsLabel.setObjectName("layerColorLimitsLabel")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.layerColorLimitsLabel)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.layerColorLimitsLabel)
self.layerColorLimitsValue = QtWidgets.QLabel(LayerDetailsPane)
self.layerColorLimitsValue.setObjectName("layerColorLimitsValue")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.layerColorLimitsValue)
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.layerColorLimitsValue)
self.layerResolutionLabel = QtWidgets.QLabel(LayerDetailsPane)
self.layerResolutionLabel.setObjectName("layerResolutionLabel")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.layerResolutionLabel)
self.layerResolutionValue = QtWidgets.QLabel(LayerDetailsPane)
self.layerResolutionValue.setObjectName("layerResolutionValue")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.layerResolutionValue)
self.layerAreaResolutionLabel = QtWidgets.QLabel(LayerDetailsPane)
self.layerAreaResolutionLabel.setObjectName("layerAreaResolutionLabel")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.layerAreaResolutionLabel)
self.layerAreaResolutionValue = QtWidgets.QLabel(LayerDetailsPane)
self.layerAreaResolutionValue.setObjectName("layerAreaResolutionValue")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.layerAreaResolutionValue)
self.verticalLayout.addLayout(self.formLayout)
self.kindDetailsStackedWidget = QtWidgets.QStackedWidget(LayerDetailsPane)
self.kindDetailsStackedWidget.setFrameShape(QtWidgets.QFrame.Box)
Expand Down Expand Up @@ -195,8 +201,10 @@ def retranslateUi(self, LayerDetailsPane):
self.layerColormapValue.setText(_translate("LayerDetailsPane", "Rainbow (IR Default)"))
self.layerColorLimitsLabel.setText(_translate("LayerDetailsPane", "Color Limits:"))
self.layerColorLimitsValue.setText(_translate("LayerDetailsPane", "-109.00 ~ 55.00°C"))
self.layerResolutionLabel.setText(_translate("LayerDetailsPane", "Resolution:"))
self.layerResolutionLabel.setText(_translate("LayerDetailsPane", "Native Resolution:"))
self.layerResolutionValue.setText(_translate("LayerDetailsPane", "1 km"))
self.layerAreaResolutionLabel.setText(_translate("LayerDetailsPane", "Area Resolution:"))
self.layerAreaResolutionValue.setText(_translate("LayerDetailsPane", "N/A"))
self.vmin_slider.setToolTip(_translate("LayerDetailsPane", "minimum color limit"))
self.vmax_slider.setToolTip(_translate("LayerDetailsPane", "maximum color limit"))
self.gammaLabel.setText(_translate("LayerDetailsPane", "Gamma: "))
Expand Down
16 changes: 16 additions & 0 deletions uwsift/ui/open_file_wizard_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ def setupUi(self, openFileWizard):
self.projectionComboBox.setSizeIncrement(QtCore.QSize(1, 0))
self.projectionComboBox.setObjectName("projectionComboBox")
self.productSelectionButtonLayout.addWidget(self.projectionComboBox)
self.resolutionLabel = QtWidgets.QLabel(self.productSelectionPage)
self.resolutionLabel.setObjectName("resolutionLabel")
self.productSelectionButtonLayout.addWidget(self.resolutionLabel)
self.resolutionComboBox = QtWidgets.QComboBox(self.productSelectionPage)
sizePolicy1 = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy1.setHorizontalStretch(1)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.resolutionComboBox.sizePolicy().hasHeightForWidth())
self.resolutionComboBox.setSizePolicy(sizePolicy1)
self.resolutionComboBox.setSizeIncrement(QtCore.QSize(1, 0))
self.resolutionComboBox.setObjectName("resolutionComboBox")
self.productSelectionButtonLayout.addWidget(self.resolutionComboBox)

#

self.resamplingShapeLabel = QtWidgets.QLabel(self.productSelectionPage)
self.resamplingShapeLabel.setObjectName("resamplingShapeLabel")
self.productSelectionButtonLayout.addWidget(self.resamplingShapeLabel)
Expand Down Expand Up @@ -207,6 +222,7 @@ def retranslateUi(self, openFileWizard):
self.radiusOfInfluenceLabel.setText(_translate("openFileWizard", "Radius of Influence:"))
self.radiusOfInfluenceSpinBox.setSuffix(_translate("openFileWizard", " m"))
self.projectionLabel.setText(_translate("openFileWizard", "Projection:"))
self.resolutionLabel.setText(_translate("openFileWizard", "Resolution:"))
self.resamplingShapeLabel.setText(_translate("openFileWizard", "Shape:"))
self.selectIDTable.setSortingEnabled(True)

Expand Down
Loading

0 comments on commit f7ffc9d

Please sign in to comment.