26 |
27 | Expand source code
28 |
29 | from typing import List
30 | from os.path import isfile
31 | import numpy as np
32 | import pickle
33 |
34 | from py_factor_graph.variables import PoseVariable, LandmarkVariable
35 | from py_factor_graph.measurements import (
36 | PoseMeasurement2D,
37 | AmbiguousPoseMeasurement2D,
38 | FGRangeMeasurement,
39 | AmbiguousFGRangeMeasurement,
40 | )
41 | from py_factor_graph.priors import PosePrior, LandmarkPrior
42 | from py_factor_graph.factor_graph import (
43 | FactorGraphData,
44 | )
45 | from py_factor_graph.utils.name_utils import (
46 | get_robot_idx_from_frame_name,
47 | get_time_idx_from_frame_name,
48 | )
49 | from py_factor_graph.utils.data_utils import get_covariance_matrix_from_list
50 |
51 |
52 | def parse_efg_file(filepath: str) -> FactorGraphData:
53 | """
54 | Parse a factor graph file to extract the factors and variables. Requires
55 | that the file ends with .fg (e.g. "my_file.fg").
56 |
57 | Args:
58 | filepath: The path to the factor graph file.
59 |
60 | Returns:
61 | FactorGraphData: The factor graph data.
62 | """
63 | assert isfile(filepath), f"{filepath} is not a file"
64 | assert filepath.endswith(".fg"), f"{filepath} is not an efg file"
65 |
66 | pose_var_header = "Variable Pose SE2"
67 | landmark_var_header = "Variable Landmark R2"
68 | pose_measure_header = "Factor SE2RelativeGaussianLikelihoodFactor"
69 | amb_measure_header = "Factor AmbiguousDataAssociationFactor"
70 | range_measure_header = "Factor SE2R2RangeGaussianLikelihoodFactor"
71 | pose_prior_header = "Factor UnarySE2ApproximateGaussianPriorFactor"
72 | landmark_prior_header = "Landmark" # don't have any of these yet
73 |
74 | new_fg_data = FactorGraphData(dimension=2)
75 |
76 | with open(filepath, "r") as f:
77 | for line in f:
78 | if line.startswith(pose_var_header):
79 | line_items = line.split()
80 | pose_name = line_items[3]
81 | x = float(line_items[4])
82 | y = float(line_items[5])
83 | theta = float(line_items[6])
84 | pose_var = PoseVariable(pose_name, (x, y), theta)
85 | new_fg_data.add_pose_variable(pose_var)
86 | elif line.startswith(landmark_var_header):
87 | line_items = line.split()
88 | landmark_name = line_items[3]
89 | x = float(line_items[4])
90 | y = float(line_items[5])
91 | landmark_var = LandmarkVariable(landmark_name, (x, y))
92 | new_fg_data.add_landmark_variable(landmark_var)
93 | elif line.startswith(pose_measure_header):
94 | line_items = line.split()
95 | base_pose = line_items[2]
96 | local_pose = line_items[3]
97 | delta_x = float(line_items[4])
98 | delta_y = float(line_items[5])
99 | delta_theta = float(line_items[6])
100 | covar_list = [float(x) for x in line_items[8:]]
101 | covar = get_covariance_matrix_from_list(covar_list)
102 | # assert covar[0, 0] == covar[1, 1]
103 | trans_weight = 1 / (covar[0, 0])
104 | rot_weight = 1 / (covar[2, 2])
105 | measure = PoseMeasurement2D(
106 | base_pose,
107 | local_pose,
108 | delta_x,
109 | delta_y,
110 | delta_theta,
111 | trans_weight,
112 | rot_weight,
113 | )
114 |
115 | base_pose_idx = get_robot_idx_from_frame_name(base_pose)
116 | local_pose_idx = get_robot_idx_from_frame_name(local_pose)
117 | base_time_idx = get_time_idx_from_frame_name(base_pose)
118 | local_time_idx = get_time_idx_from_frame_name(local_pose)
119 |
120 | # if either the robot indices are different or the time indices
121 | # are not sequential then it is a loop closure
122 | if (
123 | base_pose_idx != local_pose_idx
124 | or local_time_idx != base_time_idx + 1
125 | ):
126 | new_fg_data.add_loop_closure(measure)
127 |
128 | # otherwise it is an odometry measurement
129 | else:
130 | new_fg_data.add_odom_measurement(base_pose_idx, measure)
131 |
132 | elif line.startswith(range_measure_header):
133 | line_items = line.split()
134 | var1 = line_items[2]
135 | var2 = line_items[3]
136 | dist = float(line_items[4])
137 | stddev = float(line_items[5])
138 | range_measure = FGRangeMeasurement((var1, var2), dist, stddev)
139 | new_fg_data.add_range_measurement(range_measure)
140 |
141 | elif line.startswith(pose_prior_header):
142 | line_items = line.split()
143 | pose_name = line_items[2]
144 | x = float(line_items[3])
145 | y = float(line_items[4])
146 | theta = float(line_items[5])
147 | covar_list = [float(x) for x in line_items[7:]]
148 | covar = get_covariance_matrix_from_list(covar_list)
149 | pose_prior = PosePrior(pose_name, (x, y), theta, covar)
150 | new_fg_data.add_pose_prior(pose_prior)
151 |
152 | elif line.startswith(landmark_prior_header):
153 | raise NotImplementedError("Landmark priors not implemented yet")
154 | elif line.startswith(amb_measure_header):
155 | line_items = line.split()
156 |
157 | # if it is a range measurement then add to ambiguous range
158 | # measurements list
159 | if "SE2R2RangeGaussianLikelihoodFactor" in line:
160 | raise NotImplementedError(
161 | "Need to parse for ambiguous range measurements measurement"
162 | )
163 |
164 | # if it is a pose measurement then add to ambiguous pose
165 | # measurements list
166 | elif "SE2RelativeGaussianLikelihoodFactor" in line:
167 | raise NotImplementedError(
168 | "Need to parse for ambiguous pose measurement"
169 | )
170 |
171 | # this is a case that we haven't planned for yet
172 | else:
173 | raise NotImplementedError(
174 | f"Unknown measurement type in ambiguous measurement: {line}"
175 | )
176 |
177 | return new_fg_data
178 |
179 |
180 | def parse_pickle_file(filepath: str) -> FactorGraphData:
181 | """
182 | Retrieve a pickled FactorGraphData object. Requires that the
183 | file ends with .pickle (e.g. "my_file.pickle").
184 |
185 | Args:
186 | filepath: The path to the factor graph file.
187 |
188 | Returns:
189 | FactorGraphData: The factor graph data.
190 | """
191 | assert isfile(filepath), f"{filepath} is not a file"
192 | assert filepath.endswith(".pickle"), f"{filepath} is not a pickle file"
193 |
194 | with open(filepath, "rb") as f:
195 | data = pickle.load(f)
196 | return data
197 |
198 |