From 7c5440b6bcc105c03443882b50494681705bb731 Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Fri, 30 Apr 2021 14:31:22 -0600 Subject: [PATCH 1/6] add residual entrypoint, raise errors for missing measurements --- geomagio/api/ws/algorithms.py | 36 +++++++++++++++++++++++++++++++- geomagio/residual/Calculation.py | 9 +++++--- geomagio/residual/Measurement.py | 3 ++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index 572045ba8..af957ee43 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -1,8 +1,17 @@ -from fastapi import APIRouter, Depends +from typing import List + +from fastapi import APIRouter, Depends, HTTPException from starlette.responses import Response from ... import TimeseriesFactory from ...algorithm import DbDtAlgorithm +from ...residual import ( + calculate, + Reading, + MARK_TYPES, + INCLINATION_TYPES, + DECLINATION_TYPES, +) from .DataApiQuery import DataApiQuery from .data import format_timeseries, get_data_factory, get_data_query, get_timeseries @@ -25,3 +34,28 @@ def get_dbdt( return format_timeseries( timeseries=timeseries, format=query.format, elements=elements ) + + +@router.post("/algorithms/residual", response_model=Reading) +def calculate_residual(reading: Reading, adjust_reference: bool = True): + missing_types = get_missing_measurement_types(reading=reading) + if len(missing_types) != 0: + error_message = ", ".join(t.value for t in missing_types) + raise HTTPException( + status_code=400, + detail=f"Missing {error_message} measurements in input reading", + ) + return calculate(reading=reading, adjust_reference=adjust_reference) + + +def get_missing_measurement_types(reading: Reading) -> List[str]: + measurement_types = [m.measurement_type for m in reading.measurements] + missing_types = [] + missing_types.extend( + [type for type in DECLINATION_TYPES if type not in measurement_types] + ) + missing_types.extend( + [type for type in INCLINATION_TYPES if type not in measurement_types] + ) + missing_types.extend([type for type in MARK_TYPES if type not in measurement_types]) + return missing_types diff --git a/geomagio/residual/Calculation.py b/geomagio/residual/Calculation.py index ddc108a4d..c18fe228e 100644 --- a/geomagio/residual/Calculation.py +++ b/geomagio/residual/Calculation.py @@ -28,7 +28,10 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: NOTE: rest of reading object is shallow copy. """ # reference measurement, used to adjust absolutes - reference = reading[mt.WEST_DOWN][0] + try: + reference = adjust_reference and reading[mt.WEST_DOWN][0] or None + except: + raise ValueError(f"Missing {mt.WEST_DOWN.value} measurement") # calculate inclination inclination, f, i_mean = calculate_I( hemisphere=reading.hemisphere, measurements=reading.measurements @@ -39,13 +42,13 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: corrected_f=corrected_f, inclination=inclination, mean=i_mean, - reference=adjust_reference and reference or None, + reference=reference, ) absoluteD, meridian = calculate_D_absolute( azimuth=reading.azimuth, h_baseline=absoluteH.baseline, measurements=reading.measurements, - reference=adjust_reference and reference or None, + reference=reference, ) # populate diagnostics object with averaged measurements diagnostics = Diagnostics( diff --git a/geomagio/residual/Measurement.py b/geomagio/residual/Measurement.py index f8be19437..bbf2f420a 100644 --- a/geomagio/residual/Measurement.py +++ b/geomagio/residual/Measurement.py @@ -54,7 +54,8 @@ def average_measurement( measurements = [m for m in measurements if m.measurement_type in types] if len(measurements) == 0: # no measurements to average - return None + error_message = ", ".join(t.value for t in types) + raise ValueError(f"Missing {error_message} measurements") starttime = safe_min([m.time.timestamp for m in measurements if m.time]) endtime = safe_max([m.time.timestamp for m in measurements if m.time]) measurement = AverageMeasurement( -- GitLab From 81ee0797d69815f921754fc2f61cc02d7a7e1662 Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Fri, 30 Apr 2021 14:39:15 -0600 Subject: [PATCH 2/6] Order type imports, rename error message --- geomagio/api/ws/algorithms.py | 8 ++++---- geomagio/residual/Measurement.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index af957ee43..f904dd9a0 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -8,9 +8,9 @@ from ...algorithm import DbDtAlgorithm from ...residual import ( calculate, Reading, - MARK_TYPES, - INCLINATION_TYPES, DECLINATION_TYPES, + INCLINATION_TYPES, + MARK_TYPES, ) from .DataApiQuery import DataApiQuery from .data import format_timeseries, get_data_factory, get_data_query, get_timeseries @@ -40,10 +40,10 @@ def get_dbdt( def calculate_residual(reading: Reading, adjust_reference: bool = True): missing_types = get_missing_measurement_types(reading=reading) if len(missing_types) != 0: - error_message = ", ".join(t.value for t in missing_types) + missing_types = ", ".join(t.value for t in missing_types) raise HTTPException( status_code=400, - detail=f"Missing {error_message} measurements in input reading", + detail=f"Missing {missing_types} measurements in input reading", ) return calculate(reading=reading, adjust_reference=adjust_reference) diff --git a/geomagio/residual/Measurement.py b/geomagio/residual/Measurement.py index bbf2f420a..8b0fe6b3c 100644 --- a/geomagio/residual/Measurement.py +++ b/geomagio/residual/Measurement.py @@ -54,8 +54,8 @@ def average_measurement( measurements = [m for m in measurements if m.measurement_type in types] if len(measurements) == 0: # no measurements to average - error_message = ", ".join(t.value for t in types) - raise ValueError(f"Missing {error_message} measurements") + missing_types = ", ".join(t.value for t in types) + raise ValueError(f"Missing {missing_types} measurements") starttime = safe_min([m.time.timestamp for m in measurements if m.time]) endtime = safe_max([m.time.timestamp for m in measurements if m.time]) measurement = AverageMeasurement( -- GitLab From 0266b40715cb98f42f1e767b59905b22de8b1202 Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Fri, 30 Apr 2021 15:00:14 -0600 Subject: [PATCH 3/6] Use missing measurements method in calculation --- geomagio/api/ws/algorithms.py | 17 +---------------- geomagio/residual/Calculation.py | 22 ++++++++++++++++++---- geomagio/residual/Measurement.py | 4 ---- geomagio/residual/__init__.py | 2 ++ 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index f904dd9a0..c67c463fe 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -8,9 +8,7 @@ from ...algorithm import DbDtAlgorithm from ...residual import ( calculate, Reading, - DECLINATION_TYPES, - INCLINATION_TYPES, - MARK_TYPES, + get_missing_measurement_types, ) from .DataApiQuery import DataApiQuery from .data import format_timeseries, get_data_factory, get_data_query, get_timeseries @@ -46,16 +44,3 @@ def calculate_residual(reading: Reading, adjust_reference: bool = True): detail=f"Missing {missing_types} measurements in input reading", ) return calculate(reading=reading, adjust_reference=adjust_reference) - - -def get_missing_measurement_types(reading: Reading) -> List[str]: - measurement_types = [m.measurement_type for m in reading.measurements] - missing_types = [] - missing_types.extend( - [type for type in DECLINATION_TYPES if type not in measurement_types] - ) - missing_types.extend( - [type for type in INCLINATION_TYPES if type not in measurement_types] - ) - missing_types.extend([type for type in MARK_TYPES if type not in measurement_types]) - return missing_types diff --git a/geomagio/residual/Calculation.py b/geomagio/residual/Calculation.py index c18fe228e..6c44d4af3 100644 --- a/geomagio/residual/Calculation.py +++ b/geomagio/residual/Calculation.py @@ -28,10 +28,11 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: NOTE: rest of reading object is shallow copy. """ # reference measurement, used to adjust absolutes - try: - reference = adjust_reference and reading[mt.WEST_DOWN][0] or None - except: - raise ValueError(f"Missing {mt.WEST_DOWN.value} measurement") + missing_types = get_missing_measurement_types(reading=reading) + if len(missing_types) != 0: + missing_types = ", ".join(t.value for t in missing_types) + raise ValueError(f"Missing {missing_types} measurements in input reading") + reference = adjust_reference and reading[mt.WEST_DOWN][0] or None # calculate inclination inclination, f, i_mean = calculate_I( hemisphere=reading.hemisphere, measurements=reading.measurements @@ -281,3 +282,16 @@ def calculate_scale_value( residual_change = m2.residual - m1.residual scale_value = corrected_f * field_change / np.abs(residual_change) return scale_value + + +def get_missing_measurement_types(reading: Reading) -> List[str]: + measurement_types = [m.measurement_type for m in reading.measurements] + missing_types = [] + missing_types.extend( + [type for type in DECLINATION_TYPES if type not in measurement_types] + ) + missing_types.extend( + [type for type in INCLINATION_TYPES if type not in measurement_types] + ) + missing_types.extend([type for type in MARK_TYPES if type not in measurement_types]) + return missing_types diff --git a/geomagio/residual/Measurement.py b/geomagio/residual/Measurement.py index 8b0fe6b3c..e052f53bc 100644 --- a/geomagio/residual/Measurement.py +++ b/geomagio/residual/Measurement.py @@ -52,10 +52,6 @@ def average_measurement( """ if types: measurements = [m for m in measurements if m.measurement_type in types] - if len(measurements) == 0: - # no measurements to average - missing_types = ", ".join(t.value for t in types) - raise ValueError(f"Missing {missing_types} measurements") starttime = safe_min([m.time.timestamp for m in measurements if m.time]) endtime = safe_max([m.time.timestamp for m in measurements if m.time]) measurement = AverageMeasurement( diff --git a/geomagio/residual/__init__.py b/geomagio/residual/__init__.py index ec2de5e8a..352c66b32 100644 --- a/geomagio/residual/__init__.py +++ b/geomagio/residual/__init__.py @@ -9,6 +9,7 @@ from .Calculation import ( calculate_HZ_absolutes, calculate_I, calculate_scale_value, + get_missing_measurement_types, ) from .CalFileFactory import CalFileFactory from .Measurement import Measurement, AverageMeasurement, average_measurement @@ -35,6 +36,7 @@ __all__ = [ "calculate_I", "calculate_scale_value", "DECLINATION_TYPES", + "get_missing_measurement_types", "INCLINATION_TYPES", "MARK_TYPES", "Measurement", -- GitLab From 39606901d475826d1e31161add2f7cbc622574f3 Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Fri, 30 Apr 2021 16:21:25 -0600 Subject: [PATCH 4/6] Move missing type method to residual --- geomagio/api/ws/algorithms.py | 5 +---- geomagio/residual/Calculation.py | 15 +-------------- geomagio/residual/Reading.py | 7 +++++++ geomagio/residual/__init__.py | 2 -- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index c67c463fe..f2eeee314 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import APIRouter, Depends, HTTPException from starlette.responses import Response @@ -8,7 +6,6 @@ from ...algorithm import DbDtAlgorithm from ...residual import ( calculate, Reading, - get_missing_measurement_types, ) from .DataApiQuery import DataApiQuery from .data import format_timeseries, get_data_factory, get_data_query, get_timeseries @@ -36,7 +33,7 @@ def get_dbdt( @router.post("/algorithms/residual", response_model=Reading) def calculate_residual(reading: Reading, adjust_reference: bool = True): - missing_types = get_missing_measurement_types(reading=reading) + missing_types = reading.get_missing_measurement_types() if len(missing_types) != 0: missing_types = ", ".join(t.value for t in missing_types) raise HTTPException( diff --git a/geomagio/residual/Calculation.py b/geomagio/residual/Calculation.py index 6c44d4af3..fe1ab1c1e 100644 --- a/geomagio/residual/Calculation.py +++ b/geomagio/residual/Calculation.py @@ -28,7 +28,7 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: NOTE: rest of reading object is shallow copy. """ # reference measurement, used to adjust absolutes - missing_types = get_missing_measurement_types(reading=reading) + missing_types = reading.get_missing_measurement_types() if len(missing_types) != 0: missing_types = ", ".join(t.value for t in missing_types) raise ValueError(f"Missing {missing_types} measurements in input reading") @@ -282,16 +282,3 @@ def calculate_scale_value( residual_change = m2.residual - m1.residual scale_value = corrected_f * field_change / np.abs(residual_change) return scale_value - - -def get_missing_measurement_types(reading: Reading) -> List[str]: - measurement_types = [m.measurement_type for m in reading.measurements] - missing_types = [] - missing_types.extend( - [type for type in DECLINATION_TYPES if type not in measurement_types] - ) - missing_types.extend( - [type for type in INCLINATION_TYPES if type not in measurement_types] - ) - missing_types.extend([type for type in MARK_TYPES if type not in measurement_types]) - return missing_types diff --git a/geomagio/residual/Reading.py b/geomagio/residual/Reading.py index 06c8efb97..519245b69 100644 --- a/geomagio/residual/Reading.py +++ b/geomagio/residual/Reading.py @@ -8,6 +8,7 @@ from pydantic import BaseModel from .. import TimeseriesUtility from ..TimeseriesFactory import TimeseriesFactory from .Absolute import Absolute +from .Calculation import DECLINATION_TYPES, INCLINATION_TYPES, MARK_TYPES from .Measurement import Measurement, average_measurement from .Diagnostics import Diagnostics from .MeasurementType import MeasurementType @@ -52,6 +53,12 @@ class Reading(BaseModel): return absolute return None + def get_missing_measurement_types(self) -> List[str]: + measurement_types = [m.measurement_type for m in self.measurements] + all_types = DECLINATION_TYPES + INCLINATION_TYPES + MARK_TYPES + missing = [t for t in all_types if t not in measurement_types] + return missing + def load_ordinates( self, observatory: str, diff --git a/geomagio/residual/__init__.py b/geomagio/residual/__init__.py index 352c66b32..ec2de5e8a 100644 --- a/geomagio/residual/__init__.py +++ b/geomagio/residual/__init__.py @@ -9,7 +9,6 @@ from .Calculation import ( calculate_HZ_absolutes, calculate_I, calculate_scale_value, - get_missing_measurement_types, ) from .CalFileFactory import CalFileFactory from .Measurement import Measurement, AverageMeasurement, average_measurement @@ -36,7 +35,6 @@ __all__ = [ "calculate_I", "calculate_scale_value", "DECLINATION_TYPES", - "get_missing_measurement_types", "INCLINATION_TYPES", "MARK_TYPES", "Measurement", -- GitLab From 3a9d06c984aa5fa785956e91af5db32bf99d611a Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Fri, 30 Apr 2021 16:26:13 -0600 Subject: [PATCH 5/6] return None if no types are found to average --- geomagio/residual/Measurement.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/geomagio/residual/Measurement.py b/geomagio/residual/Measurement.py index e052f53bc..f8be19437 100644 --- a/geomagio/residual/Measurement.py +++ b/geomagio/residual/Measurement.py @@ -52,6 +52,9 @@ def average_measurement( """ if types: measurements = [m for m in measurements if m.measurement_type in types] + if len(measurements) == 0: + # no measurements to average + return None starttime = safe_min([m.time.timestamp for m in measurements if m.time]) endtime = safe_max([m.time.timestamp for m in measurements if m.time]) measurement = AverageMeasurement( -- GitLab From 1f85191863c7a31da0173a22f0d9cdf618b70586 Mon Sep 17 00:00:00 2001 From: pcain-usgs <pcain@usgs.gov> Date: Tue, 4 May 2021 09:30:22 -0600 Subject: [PATCH 6/6] Raise value error with HTTPException --- geomagio/api/ws/algorithms.py | 12 ++++-------- geomagio/residual/Reading.py | 8 ++++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index f2eeee314..bcc42a359 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -33,11 +33,7 @@ def get_dbdt( @router.post("/algorithms/residual", response_model=Reading) def calculate_residual(reading: Reading, adjust_reference: bool = True): - missing_types = reading.get_missing_measurement_types() - if len(missing_types) != 0: - missing_types = ", ".join(t.value for t in missing_types) - raise HTTPException( - status_code=400, - detail=f"Missing {missing_types} measurements in input reading", - ) - return calculate(reading=reading, adjust_reference=adjust_reference) + try: + return calculate(reading=reading, adjust_reference=adjust_reference) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/geomagio/residual/Reading.py b/geomagio/residual/Reading.py index 519245b69..437da4992 100644 --- a/geomagio/residual/Reading.py +++ b/geomagio/residual/Reading.py @@ -8,10 +8,14 @@ from pydantic import BaseModel from .. import TimeseriesUtility from ..TimeseriesFactory import TimeseriesFactory from .Absolute import Absolute -from .Calculation import DECLINATION_TYPES, INCLINATION_TYPES, MARK_TYPES from .Measurement import Measurement, average_measurement from .Diagnostics import Diagnostics -from .MeasurementType import MeasurementType +from .MeasurementType import ( + MeasurementType, + DECLINATION_TYPES, + INCLINATION_TYPES, + MARK_TYPES, +) class Reading(BaseModel): -- GitLab