diff --git a/knoten/bundle.py b/knoten/bundle.py index 789b4e510bf430ba29169da2d0b20b775b10d51e..ed64bdc82a3c1a1df7aa880ccffd956fb3c12d42 100644 --- a/knoten/bundle.py +++ b/knoten/bundle.py @@ -3,7 +3,51 @@ import pandas as pd import pvl import os import csmapi + +from pysis import isis +from ale.drivers import loads from collections import OrderedDict +from knoten.csm import create_csm + +def generate_sensors(cubes, directory=None, clean=False): + """ + Generate a set of USGSCSM sensor models from a list of ISIS cube files + + Parameters + ---------- + cubes : str + Directory/filename of a file containing ISIS cube file paths + directory : str + Output directory to save resulting json files. Defaults to the + same directory as cube list file + clean : flag + Option to delete json file outputs + + Returns + ------- + sensors : dictionary + Dictionary mapping ISIS serial numbers to USGSCSM sensor models + """ + if directory is None: + directory = os.path.dirname(cubes) + + isd_files = [] + sensors = {} + for line in open(cubes): + basename = os.path.splitext(os.path.basename(line.strip()))[0] + isd = os.path.join(directory, basename+'.json') + isd_files.append(isd) + with open(isd, 'w+') as f: + f.write(loads(line.strip(), formatter='usgscsm')) + + sn = isis.getsn(from_=line.strip()).strip().decode('utf-8') + sensors[sn] = create_csm(isd) + + if clean: + for isd in isd_files: + os.remove(isd) + + return sensors def closest_approach(points, direction): """ @@ -203,4 +247,36 @@ def compute_jacobian(network, sensors, parameters): jacobian[2*i : 2*i+2, image_column : image_column+len(params)] = compute_sensor_partials(sensor, params, ground_pt) jacobian[2*i : 2*i+2, point_column : point_column+3] = compute_ground_partials(sensor, ground_pt) - return jacobian + return jacobian, coefficient_columns + +def compute_residuals(network, sensors): + """ + Compute the error in the observations by taking the difference between the + ground points groundToImage projections and measure values. + + Parameters + ---------- + network : DataFrame + The control network as a dataframe generated by plio + sensors : dict + A dictionary that maps ISIS serial numbers to CSM sensors + + Returns + ------- + V : np.array + The control network dataframe with updated ground points + """ + num_meas = len(network) + V = np.zeros((num_meas, 2)) + + for i in range(num_meas): + row = network.iloc[i] + serial = row["serialnumber"] + ground_pt = row[["adjustedX", "adjustedY", "adjustedZ"]].values + ground_pt = csmapi.EcefCoord(ground_pt[0], ground_pt[1], ground_pt[2]) + sensor = sensors[serial] + img_coord = sensor.groundToImage(ground_pt) + V[i,:] = [row['line'] - img_coord.line, row['sample'] - img_coord.samp] + + V = V.reshape(num_meas*2) + return V diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 657015c3bb2d1c2a0f54f47a8b47efa16c97e802..7f8c02cb9b2b83559b2fa9b86b6caee4203fb96f 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -119,7 +119,7 @@ def test_compute_jacobian(control_network, sensors): ground_partials = [-(i+1) * np.ones((2, 3)) for i in range(9)] with mock.patch('knoten.bundle.compute_sensor_partials', side_effect=sensor_partials) as sensor_par_mock, \ mock.patch('knoten.bundle.compute_ground_partials', side_effect=ground_partials) as ground_par_mock: - J = bundle.compute_jacobian(control_network, sensors, parameters) + J, coefficient_columns = bundle.compute_jacobian(control_network, sensors, parameters) expected_J = [ [1, 1, 0, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0, 0, 0, 0, 0], @@ -141,3 +141,17 @@ def test_compute_jacobian(control_network, sensors): [0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, -9, -9, -9], [0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, -9, -9, -9]] np.testing.assert_array_equal(J, expected_J) + +def test_compute_residuals(control_network, sensors): + # sensor.groundToImage.side_effect = [csmapi.ImageCoord(-0.1, 7.8), csmapi.ImageCoord(0.7, 6.6), csmapi.ImageCoord(1.5, 5.4), + # csmapi.ImageCoord(2.3, 4.2), csmapi.ImageCoord(3.1, 4.9), csmapi.ImageCoord(5.8, 3.7), + # csmapi.ImageCoord(6.6, 2.5), csmapi.ImageCoord(7.4, 1.3), csmapi.ImageCoord(8.2, 0.1)] + + sensors['a'].groundToImage.side_effect = [csmapi.ImageCoord(-0.1, 7.8), csmapi.ImageCoord(5.8, 3.7)] + sensors['b'].groundToImage.side_effect = [csmapi.ImageCoord(0.7, 6.6), csmapi.ImageCoord(3.1, 4.9), csmapi.ImageCoord(6.6, 2.5)] + sensors['c'].groundToImage.side_effect = [csmapi.ImageCoord(1.5, 5.4), csmapi.ImageCoord(7.4, 1.3)] + sensors['d'].groundToImage.side_effect = [csmapi.ImageCoord(2.3, 4.2), csmapi.ImageCoord(8.2, 0.1)] + + V = bundle.compute_residuals(control_network, sensors) + assert V.shape == (18,) + np.testing.assert_allclose(V, [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1])