Skip to content
Snippets Groups Projects
Commit 821e8afb authored by Bauck, Kirsten (Contractor) Hailey's avatar Bauck, Kirsten (Contractor) Hailey
Browse files

Merge branch 'refactor_overlaps' into 'main'

refactor overlaps and add sensor classes

See merge request astrogeology/autocnet!686
parents 76526f6f 8945f4a0
No related branches found
No related tags found
No related merge requests found
...@@ -42,6 +42,9 @@ release. ...@@ -42,6 +42,9 @@ release.
- Functionality to add measure to a point - Functionality to add measure to a point
- Functionality to convert from sample, line (x,y) pixel coordinates to Body-Centered, Body-Fixed (BCBF) coordinates in meters. - Functionality to convert from sample, line (x,y) pixel coordinates to Body-Centered, Body-Fixed (BCBF) coordinates in meters.
- Functionality to test for valid input images. - Functionality to test for valid input images.
- Refactored place_points_in_overlap to make it easier to understand and work with
- Created `sensor.py` that creates a class for either a 'csm' camera sensor or an 'isis' camera sensor. This removes confusing and wordy code. Now just need to create a sensor object based on the input 'csm' or 'isis'. Code inside classes will figure out the rest.
- Fuctionality to convert between oc2xyz and xyz2oc in `spatial.py`
### Fixed ### Fixed
- string injection via format with sqlalchemy text() object. - string injection via format with sqlalchemy text() object.
......
...@@ -584,7 +584,7 @@ class Points(Base, BaseMixin): ...@@ -584,7 +584,7 @@ class Points(Base, BaseMixin):
choosername=choosername)) choosername=choosername))
return point return point
def add_measures_to_point(self, candidates, choosername='autocnet'): def add_measures_to_point(self, candidates, sensor, choosername='autocnet', **kwargs):
""" """
Attempt to add 1+ measures to a point from a list of candidate nodes. The Attempt to add 1+ measures to a point from a list of candidate nodes. The
function steps over each node and attempts to use the node's sensor model function steps over each node and attempts to use the node's sensor model
...@@ -603,10 +603,9 @@ class Points(Base, BaseMixin): ...@@ -603,10 +603,9 @@ class Points(Base, BaseMixin):
if not os.path.exists(node['image_path']): if not os.path.exists(node['image_path']):
log.info(f'Unable to find input image {node["image_path"]}') log.info(f'Unable to find input image {node["image_path"]}')
continue continue
try: try:
# ToDo: We want ot abstract away this to be a generic sensor model. No more 'isis' vs. 'csm' in the code # TODO: Take this projection out of the CSM model and work it into the point
sample, line = isis.ground_to_image(node["image_path"], self.geom.x, self.geom.y) sample, line = sensor.calculate_sample_line(node, self.geom.x, self.geom.y, **kwargs)
except: except:
log.info(f"{node['image_path']} failed ground_to_image. Likely due to being processed incorrectly or is just a bad image that failed campt.") log.info(f"{node['image_path']} failed ground_to_image. Likely due to being processed incorrectly or is just a bad image that failed campt.")
......
...@@ -3,6 +3,7 @@ import pytest ...@@ -3,6 +3,7 @@ import pytest
import shapely import shapely
from autocnet.io.db.model import Points from autocnet.io.db.model import Points
from autocnet.spatial import sensor
def test_points_exists(tables): def test_points_exists(tables):
assert Points.__tablename__ in tables assert Points.__tablename__ in tables
...@@ -72,6 +73,7 @@ def test_create_point_with_reference_measure(session): ...@@ -72,6 +73,7 @@ def test_create_point_with_reference_measure(session):
def test_add_measures_to_point(session): def test_add_measures_to_point(session):
point = Points() point = Points()
point.adjusted = shapely.Point(0,0,0) point.adjusted = shapely.Point(0,0,0)
test_sensor = sensor.create_sensor('isis')
node = MagicMock() node = MagicMock()
node.isis_serial = 'serial' node.isis_serial = 'serial'
...@@ -81,7 +83,7 @@ def test_add_measures_to_point(session): ...@@ -81,7 +83,7 @@ def test_add_measures_to_point(session):
with patch('autocnet.spatial.isis.ground_to_image') as mocked_call: with patch('autocnet.spatial.isis.ground_to_image') as mocked_call:
mocked_call.return_value = (0.5, 0.5) mocked_call.return_value = (0.5, 0.5)
point.add_measures_to_point(reference_nodes) point.add_measures_to_point(reference_nodes, test_sensor)
assert len(point.measures) == 4 assert len(point.measures) == 4
assert point.measures[0].line == 0.5 assert point.measures[0].line == 0.5
......
import numpy as np
def is_valid_lroc_polar_image(roi_array,
include_var=True,
include_mean=False,
include_std=False):
"""
Checks if a numpy array representing an ROI from an lorc polar image is valid.
Can check using variance, mean, and standard deviation, baed on user input.
It is highly encouraged that at the very least the variance check is used.
Parameters
__________
roi_array : np.array
A numpy array representing a ROI from an image, meaning the values are pixels
include_var : bool
Choose whether to filter images based on variance. Default True.
include_mean : bool
Choose whether to filter images based on mean. Default True.
Goal is to get rid of overally dark images.
include_std : bool
Choose whether to filter images based on standard deviation. Default True.
Goal is to get rid of overally saturated images.
Returns
_______
is_valid : bool
Returns True is passes the checks, returns false otherwise.
"""
functions = []
if include_var:
# Get rid of super bad images
var_func = lambda x : False if np.var(roi_array) == 0 else True
functions.append(var_func)
if include_mean:
# Get rid of overally dark images
mean_func = lambda x : False if np.mean(roi_array) < 0.0005 else True
functions.append(mean_func)
if include_std:
# Get rid over overally saturated images
std_func = lambda x : False if np.std(roi_array) > 0.001 else True
functions.append(std_func)
return all(func(roi_array) for func in functions)
def is_valid_lroc_image(roi_array, include_var=True):
"""
Checks if a numpy array representing an ROI from an lroc image is valid.
Can check using variance, based on user input.
It is highly encouraged that the variance check is used.
Parameters
__________
roi_array : np.array
A numpy array representing a ROI from an image, meaning the values are pixels
include_var : bool
Choose whether to filter images based on variance. Default True.
Returns
_______
is_valid : bool
Returns True is passes the checks, returns false otherwise.
"""
functions = []
if include_var:
# Get rid of super bad images
var_func = lambda x : False if np.var(roi_array) == 0 else True
functions.append(var_func)
return all(func(roi_array) for func in functions)
\ No newline at end of file
import json import json
import logging import logging
import math import math
import os
import numpy as np import numpy as np
import shapely import shapely
from sqlalchemy import text
from autocnet.cg.cg import create_points_along_line from autocnet.cg.cg import create_points_along_line
from autocnet.io.db.model import Images, Measures, Overlay, Points, JsonEncoder from autocnet.io.db.model import Images, Points, JsonEncoder
from autocnet.graph.node import NetworkNode from autocnet.graph.node import NetworkNode
from autocnet.spatial import isis from autocnet.spatial import isis
from autocnet.transformation import roi from autocnet.transformation import roi
from autocnet.matcher.cpu_extractor import extract_most_interesting from autocnet.matcher.cpu_extractor import extract_most_interesting
from autocnet.matcher.validation import is_valid_lroc_polar_image
import time import time
# Set up logging file # Set up logging file
...@@ -143,50 +142,6 @@ def find_points_in_centroids(radius, ...@@ -143,50 +142,6 @@ def find_points_in_centroids(radius,
points.extend(line_points) points.extend(line_points)
return points return points
def is_valid_lroc_polar_image(roi_array,
include_var=True,
include_mean=False,
include_std=False):
"""
Checks if a numpy array representing an ROI from an image is valid.
Can check using variance, mean, and standard deviation, baed on unser input.
It is highly encouraged that at the very least the variance check is used.
Parameters
__________
roi_array : np.array
A numpy array representing a ROI from an image, meaning the values are pixels
include_var : bool
Choose whether to filter images based on variance. Default True.
include_mean : bool
Choose whether to filter images based on mean. Default True.
Goal is to get rid of overally dark images.
include_std : bool
Choose whether to filter images based on standard deviation. Default True.
Goal is to get rid of overally saturated images.
Returns
_______
is_valid : bool
Returns True is passes the checks, returns false otherwise.
"""
functions = []
if include_var:
# Get rid of super bad images
var_func = lambda x : False if np.var(roi_array) == 0 else True
functions.append(var_func)
if include_mean:
# Get rid of overally dark images
mean_func = lambda x : False if np.mean(roi_array) < 0.0005 else True
functions.append(mean_func)
if include_std:
# Get rid over overally saturated images
std_func = lambda x : False if np.std(roi_array) > 0.001 else True
functions.append(std_func)
return all(func(roi_array) for func in functions)
def find_intresting_point(nodes, lon, lat, size=71): def find_intresting_point(nodes, lon, lat, size=71):
""" """
Find an intresting point close the given lon, lat given a list data structure that contains Find an intresting point close the given lon, lat given a list data structure that contains
......
This diff is collapsed.
from autocnet.transformation.spatial import reproject, og2oc, oc2xyz
from autocnet.spatial import isis
from autocnet.spatial import overlap
import csmapi
import logging
# set up the logger file
log = logging.getLogger(__name__)
class ISISSensor:
def __init__(self):
self.sensor_type = "ISISSensor"
def calculate_sample_line(self, node, lon, lat, **kwargs):
"""
Calculate the sample and line for an isis camera sensor
Parameters
----------
node: obj
autocnet object containing image information
lon: int
longitude of point
lat: int
latitude of point
Returns
-------
sample: int
sample of point
line: int
lint of point
"""
sample, line = isis.ground_to_image(node["image_path"], lon, lat)
return sample, line
def linesamp2xyz(node, sample, line, **kwargs):
"""
Convert a line and sample into and x,y,z point for isis camera model
Parameters
----------
node: obj
autocnet object containing image information
sample: int
sample of point
line: int
lint of point
Returns
-------
x,y,z : int(s)
x,y,z coordinates of the point
"""
cube_path = node['image_path']
try:
p = isis.point_info(cube_path, sample, line, point_type='image')
except:
log.debug(f"Image coordinates {sample}, {line} do not project fot image {cube_path}.")
try:
x, y, z = p["BodyFixedCoordinate"].value
except:
x, y, z = p["BodyFixedCoordinate"]
if getattr(p["BodyFixedCoordinate"], "units", "None").lower() == "km":
x = x * 1000
y = y * 1000
z = z * 1000
return x,y,z
class CSMSensor:
def __init__(self):
self.sensor_type = "CSMSensor"
def calculate_sample_line(self, node, lon, lat, **kwargs):
"""
Calculate the sample and line for an csm camera sensor
Parameters
----------
node: obj
autocnet object containing image information
lon: int
longitude of point
lat: int
latitude of point
kwargs: dict
Contain information to be used if the csm sensor model is passed
csm_kwargs = {
'semi_major': int,
'semi_minor': int,
'height': int,
'ncg': obj,
'needs_projection': bool
}
Returns
-------
sample: int
sample of point
line: int
lint of point
"""
semi_major = kwargs.get('semi_major', None)
semi_minor = kwargs.get('semi_minor', None)
needs_projection = kwargs.get('needs_projection', None)
# TODO: Take this projection out of the CSM model and work it into the point
if needs_projection:
height = kwargs.get('height', None)
x,y,z = oc2xyz(lon, lat, semi_major, semi_minor, height)
# The CSM conversion makes the LLA/ECEF conversion explicit
gnd = csmapi.EcefCoord(x, y, z)
else:
ncg = kwargs.get('ncg', None)
height = ncg.dem.get_height(lat, lon)
# Get the BCEF coordinate from the lon, lat
x, y, z = reproject([lon, lat, height],
semi_major, semi_minor, 'latlon', 'geocent')
gnd = csmapi.EcefCoord(x, y, z)
image_coord = node.camera.groundToImage(gnd)
sample, line = image_coord.samp, image_coord.line
return sample,line
def linesamp2xyz(node, sample, line, **kwargs):
"""
Convert a line and sample into and x,y,z point for csm camera model
Parameters
----------
node: obj
autocnet object containing image information
sample: int
sample of point
line: int
lint of point
kwargs: dict
Contain information to be used if the csm sensor model is passed
csm_kwargs = {
'semi_major': int,
'semi_minor': int,
'ncg': obj,
}
Returns
-------
x,y,z : int(s)
x,y,z coordinates of the point
"""
semi_major = kwargs.get('semi_major', None)
semi_minor = kwargs.get('semi_minor', None)
ncg = kwargs.get('ncg', None)
image_coord = csmapi.ImageCoord(sample, line)
pcoord = node.camera.imageToGround(image_coord)
# Get the BCEF coordinate from the lon, lat
# TODO: Take this projection out of the CSM model and work it into the point
updated_lon_og, updated_lat_og, _ = reproject([pcoord.x, pcoord.y, pcoord.z],
semi_major, semi_minor, 'geocent', 'latlon')
updated_lon, updated_lat = og2oc(updated_lon_og, updated_lat_og, semi_major, semi_minor)
updated_height = ncg.dem.get_height(updated_lat, updated_lon)
# Get the BCEF coordinate from the lon, lat
x, y, z = reproject([updated_lon_og, updated_lat_og, updated_height],
semi_major, semi_minor, 'latlon', 'geocent')
return x,y,z
def create_sensor(sensor_type):
sensor_type = sensor_type.lower()
sensor_classes = {
"isis": ISISSensor,
"csm": CSMSensor
}
if sensor_type in sensor_classes:
return sensor_classes[sensor_type]()
else:
raise Exception(f"Unsupported sensor type: {sensor_type}, accept 'isis' or 'csm'")
\ No newline at end of file
...@@ -113,3 +113,68 @@ def reproject(record, semi_major, semi_minor, source_proj, dest_proj, **kwargs): ...@@ -113,3 +113,68 @@ def reproject(record, semi_major, semi_minor, source_proj, dest_proj, **kwargs):
y, x, z = pyproj.transform(source_pyproj, dest_pyproj, record[0], record[1], record[2], **kwargs) y, x, z = pyproj.transform(source_pyproj, dest_pyproj, record[0], record[1], record[2], **kwargs)
return y, x, z return y, x, z
# TODO: Take this projection out of the CSM model and work it into the point
def oc2xyz(lon, lat, semi_major, semi_minor, height):
"""
Project from ocentric to graphic
Parameters
----------
lon: int
longitude of point
lat: int
latitude of point
semi_minor: int
Get from the plantery body in use
semi_major: int
Get from the plantary body in use
Returns
-------
x,y,z: int(s)
The x, y, z coordinated to the converted point
"""
lon_og, lat_og = oc2og(lon, lat, semi_major, semi_minor)
x, y, z = reproject([lon_og, lat_og, height],
semi_major, semi_minor,
'latlon', 'geocent')
return x,y,z
# TODO: Take this projection out of the CSM model and work it into the point
def xyz2oc(x, y, z, semi_major, semi_minor):
"""
Project from graphic to ocentric
Parameters
----------
x: int
x coordinate of the point
y: int
y coordinate of the point
z: int
z coordinate (height) of the point
semi_minor: int
Get from the plantery body in use
semi_major: int
Get from the plantary body in use
Returns
-------
updated_lon: int
longitude of the point after conversion
updated_lat: int
latitude of the point after conversion
"""
lon_og, lat_og, _ = reproject([x, y, z], semi_major, semi_minor,
'geocent', 'latlon')
updated_lon, updated_lat = og2oc(lon_og, lat_og, semi_major, semi_minor)
return updated_lon, updated_lat
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment