Getting Setup¶
This section goes through various steps to get setup for outlier detection. For more details see the ingesting ISIS control networks tutorial.
Imports¶
[1]:
# Required for autocnet imports
import os
os.environ['ISISROOT'] = '/usgs/cpkgs/anaconda3_linux/envs/isis4.2.0'
# Autocnet Imports
from autocnet.graph.network import NetworkCandidateGraph
from autocnet.graph.edge import NetworkEdge
from autocnet.io.db.model import Matches, Points
from autocnet.transformation.roi import Roi
# Helpful Python Modules
import matplotlib.pyplot as plt # plotting package
import numpy as np # numerical computing package
Setting up the NetworkCandidateGraph¶
Config¶
The config various settings that autocnet will use when connecting to other services. Primarily, the config is used to define:
The database your NetworkCandidateGraph will use
The redis queue and slurm settings for cluster based processing
The spatial reference system for geometries such as image footprints
[2]:
config_path = 'config.yml'
Loading the control network¶
This cell will check if the database your config file points to already has a control network ingested in it. If it doesn’t then it goes through the steps from the ingesting ISIS control networks tutorial.
[3]:
data_directory = "/work/projects/control_network_metrics/tutorials/isis_ingestion"
lis_path = os.path.join(data_directory, "apollo_lronac_cubes.lis")
cnet_path = os.path.join(data_directory, "AS15_landingsite_apollolro_jig1.net")
ncg = NetworkCandidateGraph()
ncg.config_from_file(config_path)
ncg.from_database()
if len(ncg) == 0:
print(f'Ingesting control network {cnet_path}.')
ncg = NetworkCandidateGraph.from_cnet(cnet_path, lis_path, config_path)
else:
print('Network already in database')
Network already in database
/work/users/jmapel/anaconda_local/envs/autocnet_local/lib/python3.7/site-packages/sqlalchemy/orm/relationships.py:1997: SAWarning: Setting backref / back_populates on relationship Overlay.points to refer to viewonly relationship Points.overlay should include sync_backref=False set on the Overlay.points relationship. (this warning may be suppressed after 10 occurrences)
(rel_b, rel_a, rel_b),
/work/users/jmapel/anaconda_local/envs/autocnet_local/lib/python3.7/site-packages/sqlalchemy/orm/relationships.py:1997: SAWarning: Setting backref / back_populates on relationship Points.overlay to refer to viewonly relationship Overlay.points should include sync_backref=False set on the Points.overlay relationship. (this warning may be suppressed after 10 occurrences)
(rel_b, rel_a, rel_b),
Check network¶
Look at the graph of the network to ensure it isn’t malformed. Each image in the network is represented by a node and overlapping images have an edge between their nodes. We will be doing pair-wise outlier detection, so we will check each edge in the network graph
[4]:
ncg.plot()
[4]:
<AxesSubplot:>
Getting the pairwise image matches¶
The first process we need to do is collect all of the pairwise matches between images. Our control network currently contains control points and all of the measurements of them. We need to convert these multi-image relationships into all of the common points between each pair of images.
To do this, we’re going to use Autocnet’s apply function to run parallel processing on each edge of the NetworkCandidateGraph
[5]:
?NetworkCandidateGraph.apply
SLURM parameters¶
These parameters will be used when creating SLURM jobs for cluster processing via the apply function. Depending on the complexity of the jobs you are running, you may want to change the walltime and arraychunk parameters.
[6]:
walltime="00:30:00"
log_dir = '/scratch/jmapel/autocnet_tut/logs'
arraychunk=75
chunksize=16723
Convert control measures and points to image matches¶
This cell uses apply to run the network_to_matches function on each edge. It is very important that this function only get run once per edge or it will add duplicate matches. So, this cell also contains a check that skips the function if the database already has matches in it.
[7]:
?NetworkEdge.network_to_matches
[8]:
with ncg.session_scope() as session:
num_matches = session.query(Matches).count()
if num_matches == 0:
print("Loading matches table")
njobs = ncg.apply('network_to_matches',
on='edges',
# SLURM kwargs
walltime=walltime,
log_dir=os.path.join(log_dir, 'matches'),
arraychunk=arraychunk,
chunksize=chunksize)
else:
print("Matches table already populated")
Loading matches table
Looking at the matches¶
The pairwise image matches are stored on the edges of the graph and can be accessed via the networkX graph or the matches table in the database
[9]:
for source, dest, edge in ncg.edges(data='data'):
print(f'Edge ({source}, {dest}) has {len(edge.matches)} matches')
Edge (10, 6) has 440 matches
Edge (10, 3) has 4 matches
Edge (10, 4) has 4 matches
Edge (10, 2) has 4 matches
Edge (10, 11) has 39 matches
Edge (10, 8) has 477 matches
Edge (10, 5) has 4 matches
Edge (10, 7) has 54 matches
Edge (1, 3) has 178 matches
Edge (1, 4) has 82 matches
Edge (1, 2) has 197 matches
Edge (1, 5) has 19 matches
Edge (6, 3) has 4 matches
Edge (6, 4) has 4 matches
Edge (6, 2) has 4 matches
Edge (6, 11) has 38 matches
Edge (6, 8) has 419 matches
Edge (6, 5) has 4 matches
Edge (6, 7) has 46 matches
Edge (3, 9) has 3 matches
Edge (3, 4) has 271 matches
Edge (3, 2) has 274 matches
Edge (3, 11) has 5 matches
Edge (3, 8) has 4 matches
Edge (3, 5) has 181 matches
Edge (3, 7) has 2 matches
Edge (9, 4) has 3 matches
Edge (9, 2) has 3 matches
Edge (9, 11) has 239 matches
Edge (9, 8) has 42 matches
Edge (9, 5) has 3 matches
Edge (9, 7) has 213 matches
Edge (4, 2) has 166 matches
Edge (4, 11) has 5 matches
Edge (4, 8) has 4 matches
Edge (4, 5) has 199 matches
Edge (4, 7) has 2 matches
Edge (2, 11) has 5 matches
Edge (2, 8) has 4 matches
Edge (2, 5) has 89 matches
Edge (2, 7) has 2 matches
Edge (11, 8) has 171 matches
Edge (11, 5) has 5 matches
Edge (11, 7) has 275 matches
Edge (8, 5) has 4 matches
Edge (8, 7) has 184 matches
Edge (5, 7) has 2 matches
Outlier Detection¶
Reprojective Error¶
These two functions check for outliers by attempting to reproject measures between images. Any pair of measures that do not repoject to each other wth in a given tolerance are flagged.
[ ]:
?NetworkEdge.compute_fundamental_matrix
[ ]:
?NetworkEdge.compute_homography
These cells also demonstrate how to pass kwargs through apply. Any kwargs that are not specific to the apply function are passed on to the function being applied.
[10]:
njobs = ncg.apply('compute_fundamental_matrix',
on='edges',
# homography kwargs
method='mle',
reproj_threshold=5,
# SLURM kwargs
walltime=walltime,
log_dir=os.path.join(log_dir, 'fundamental'),
arraychunk=arraychunk,
chunksize=chunksize)
[11]:
njobs = ncg.apply('compute_homography',
on='edges',
# fundamental matrix kwargs
method='lmeds',
reproj_threshold=5,
# SLURM kwargs
walltime=walltime,
log_dir=os.path.join(log_dir, 'homography'),
arraychunk=arraychunk,
chunksize=chunksize)
You can check the queue_length property on the NetworkCandidateGraph object to see how many jobs are either waiting to be processed or in process. You can also use the squeue command on a command line or in your notebook to check what jobs slurm has.
[14]:
ncg.queue_length
[14]:
0
[13]:
!squeue -u jmapel
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
17780111 longall jupyter- jmapel R 2:09:58 1 neb16
17796596_1 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_2 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_3 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_4 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_5 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_6 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_7 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_8 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_9 shortall AutoCNet jmapel R 0:03 1 neb14
17796596_10 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_11 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_12 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_13 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_14 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_15 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_16 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_17 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_18 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_19 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_20 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_21 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_22 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_23 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_24 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_25 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_26 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_27 shortall AutoCNet jmapel R 0:03 1 neb15
17796596_28 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_29 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_30 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_31 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_32 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_33 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_34 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_35 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_36 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_37 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_38 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_39 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_40 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_41 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_42 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_43 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_44 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_45 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_46 shortall AutoCNet jmapel R 0:03 1 neb16
17796596_47 shortall AutoCNet jmapel R 0:03 1 neb16
Looking at the results¶
The reprojective error checks add a property to each called masks. This is a Pandas dataframe that contains a column for each check that has been done on the edge. If the row for a match has a true in it, then that match passed the column’s check. Conversely, if the row for a match has a false in it, then that match failsed the column’s check. We can use some dataframe techniques to look at our results
[15]:
for source, dest, edge in ncg.edges(data="data"):
num_matches = len(edge.matches)
# MLE requires at least 8 points so skip anything with too few
if num_matches < 8:
continue
print(f'edge ({source}, {dest})')
print('num matches:', num_matches)
print('passed homography:', sum(edge.masks['homography']))
print('passed fundamental:', sum(edge.masks['fundamental']))
print('too few matches to compute fundamental matrix')
print('')
edge (10, 6)
num matches: 440
passed homography: 440
passed fundamental: 426
too few matches to compute fundamental matrix
edge (10, 11)
num matches: 39
passed homography: 30
passed fundamental: 38
too few matches to compute fundamental matrix
edge (10, 8)
num matches: 477
passed homography: 369
passed fundamental: 447
too few matches to compute fundamental matrix
edge (10, 7)
num matches: 54
passed homography: 45
passed fundamental: 54
too few matches to compute fundamental matrix
edge (1, 3)
num matches: 178
passed homography: 138
passed fundamental: 91
too few matches to compute fundamental matrix
edge (1, 4)
num matches: 82
passed homography: 61
passed fundamental: 52
too few matches to compute fundamental matrix
edge (1, 2)
num matches: 197
passed homography: 152
passed fundamental: 134
too few matches to compute fundamental matrix
edge (1, 5)
num matches: 19
passed homography: 17
passed fundamental: 16
too few matches to compute fundamental matrix
edge (6, 11)
num matches: 38
passed homography: 37
passed fundamental: 38
too few matches to compute fundamental matrix
edge (6, 8)
num matches: 419
passed homography: 419
passed fundamental: 393
too few matches to compute fundamental matrix
edge (6, 7)
num matches: 46
passed homography: 39
passed fundamental: 45
too few matches to compute fundamental matrix
edge (3, 4)
num matches: 271
passed homography: 255
passed fundamental: 177
too few matches to compute fundamental matrix
edge (3, 2)
num matches: 274
passed homography: 237
passed fundamental: 174
too few matches to compute fundamental matrix
edge (3, 5)
num matches: 181
passed homography: 162
passed fundamental: 93
too few matches to compute fundamental matrix
edge (9, 11)
num matches: 239
passed homography: 154
passed fundamental: 193
too few matches to compute fundamental matrix
edge (9, 8)
num matches: 42
passed homography: 38
passed fundamental: 39
too few matches to compute fundamental matrix
edge (9, 7)
num matches: 213
passed homography: 138
passed fundamental: 191
too few matches to compute fundamental matrix
edge (4, 2)
num matches: 166
passed homography: 157
passed fundamental: 98
too few matches to compute fundamental matrix
edge (4, 5)
num matches: 199
passed homography: 169
passed fundamental: 133
too few matches to compute fundamental matrix
edge (2, 5)
num matches: 89
passed homography: 81
passed fundamental: 62
too few matches to compute fundamental matrix
edge (11, 8)
num matches: 171
passed homography: 171
passed fundamental: 154
too few matches to compute fundamental matrix
edge (11, 7)
num matches: 275
passed homography: 190
passed fundamental: 272
too few matches to compute fundamental matrix
edge (8, 7)
num matches: 184
passed homography: 117
passed fundamental: 167
too few matches to compute fundamental matrix
Digging in on edge (9, 7)¶
Edge (9, 7) has a lot of matches but many failures; let’s take a closer look at it.
[16]:
edge_9_7 = ncg.edges[(9, 7)]['data']
image_9 = ncg.nodes[9]
image_7 = ncg.nodes[7]
print(edge_9_7)
print(image_9)
print(image_7)
Source Image Index: 7
Destination Image Index: 9
Available Masks: fundamental homography
match_id
1171 True False
1172 True False
1173 True False
1174 True False
1175 True False
... ... ...
1455 True True
1457 True True
1458 True True
1460 True True
1462 True False
[213 rows x 2 columns]
{'data':
NodeID: 9
Image Name: /work/projects/control_network_metrics/tutorials/isis_ingestion/M102135625RE.lev1_8b.cub
Image PATH: /work/projects/control_network_metrics/tutorials/isis_ingestion/M102135625RE.lev1_8b.cub
Number Keypoints: 0
Available Masks : Empty DataFrame
Columns: []
Index: []
Type: <class 'autocnet.graph.node.NetworkNode'>
}
{'data':
NodeID: 7
Image Name: /work/projects/control_network_metrics/tutorials/isis_ingestion/M102128467RE.lev1_8b.cub
Image PATH: /work/projects/control_network_metrics/tutorials/isis_ingestion/M102128467RE.lev1_8b.cub
Number Keypoints: 0
Available Masks : Empty DataFrame
Columns: []
Index: []
Type: <class 'autocnet.graph.node.NetworkNode'>
}
Looking at the matches that failed outlier detection¶
We can use the masks dataframe to index the matches dataframe on our edge and see the matches that failed each check
[17]:
failed_fundamental = edge_9_7.matches.loc[(~edge_9_7.masks[['fundamental']]).all(axis=1)]
failed_fundamental
[17]:
| geom | point_id | source_measure_id | destin_measure_id | source | source_idx | destination | destination_idx | lat | lon | ... | source_apriori_x | source_apriori_y | destination_x | destination_y | destination_apriori_x | destination_apriori_y | shift_x | shift_y | original_destination_x | original_destination_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1178 | None | 788 | 2324 | 2325 | 7 | 0 | 9 | 0 | None | None | ... | 2231.12 | 16213.7 | 180.316 | 16595.5 | 204.205 | 16599.5 | None | None | None | None |
| 1228 | None | 1269 | 3678 | 3677 | 7 | 0 | 9 | 0 | None | None | ... | 3082.24 | 6807.21 | 1001.12 | 7160.22 | 978.351 | 7163.4 | None | None | None | None |
| 1230 | None | 1271 | 3684 | 3683 | 7 | 0 | 9 | 0 | None | None | ... | 3908.22 | 6813.23 | 1773.38 | 7180.67 | 1797.61 | 7184.37 | None | None | None | None |
| 1214 | None | 1243 | 3610 | 3609 | 7 | 0 | 9 | 0 | None | None | ... | 3983.41 | 1699.58 | 1877.61 | 2095.96 | 1840.91 | 2099.48 | None | None | None | None |
| 1222 | None | 1261 | 3653 | 3652 | 7 | 0 | 9 | 0 | None | None | ... | 2674.81 | 5831.07 | 493.5 | 6213.5 | -0.5 | -0.5 | None | None | None | None |
| 1223 | None | 1262 | 3655 | 3654 | 7 | 0 | 9 | 0 | None | None | ... | 3078.79 | 5826.54 | 893.047 | 6201.25 | 918.537 | 6205.46 | None | None | None | None |
| 1238 | None | 1283 | 3716 | 3715 | 7 | 0 | 9 | 0 | None | None | ... | 2557.44 | 8894.67 | 398.233 | 9273.58 | 424.971 | 9277.79 | None | None | None | None |
| 1246 | None | 1296 | 3750 | 3749 | 7 | 0 | 9 | 0 | None | None | ... | 3361.97 | 10921 | 1318.89 | 11293.3 | 1356.33 | 11296.8 | None | None | None | None |
| 1247 | None | 1297 | 3753 | 3752 | 7 | 0 | 9 | 0 | None | None | ... | 4664.18 | 10913.2 | 2828.5 | 11267.7 | 2791.51 | 11270.5 | None | None | None | None |
| 1250 | None | 1304 | 3769 | 3768 | 7 | 0 | 9 | 0 | None | None | ... | 2488.51 | 12971.5 | 411.565 | 13351.7 | 429.924 | 13355.6 | None | None | None | None |
| 1251 | None | 1305 | 3772 | 3771 | 7 | 0 | 9 | 0 | None | None | ... | 2955.16 | 12943.5 | 1011.06 | 13290.9 | 991.051 | 13294 | None | None | None | None |
| 1252 | None | 1306 | 3774 | 3773 | 7 | 0 | 9 | 0 | None | None | ... | 3363.38 | 12945.5 | 1387.89 | 13317.5 | 1405.88 | 13321.7 | None | None | None | None |
| 1256 | None | 1318 | 3802 | 3801 | 7 | 0 | 9 | 0 | None | None | ... | 2520.43 | 15001.8 | 471.544 | 15381 | 495.315 | 15385 | None | None | None | None |
| 1264 | None | 1328 | 3829 | 3828 | 7 | 0 | 9 | 0 | None | None | ... | 4237.17 | 16023.8 | 2321.67 | 16425.3 | 2309.89 | 16428.2 | None | None | None | None |
| 1267 | None | 1333 | 3841 | 3843 | 7 | 0 | 9 | 0 | None | None | ... | 3323.35 | 17014 | 1364.5 | 17390.5 | -0.5 | -0.5 | None | None | None | None |
| 1269 | None | 1335 | 3847 | 3849 | 7 | 0 | 9 | 0 | None | None | ... | 4195 | 17097.3 | 2265.5 | 17466.5 | -0.5 | -0.5 | None | None | None | None |
| 1278 | None | 1341 | 3865 | 3864 | 7 | 0 | 9 | 0 | None | None | ... | 3774.68 | 18065.9 | 1926.82 | 18441.7 | 1900.79 | 18444.7 | None | None | None | None |
| 1280 | None | 1345 | 3875 | 3874 | 7 | 0 | 9 | 0 | None | None | ... | 2409.25 | 19093 | 414.645 | 19472.6 | 439.25 | 19476.8 | None | None | None | None |
| 1312 | None | 1380 | 3977 | 3976 | 7 | 0 | 9 | 0 | None | None | ... | 4117.91 | 24208.4 | 2376.63 | 24604 | 2353.01 | 24607.2 | None | None | None | None |
| 1324 | None | 1392 | 4011 | 4010 | 7 | 0 | 9 | 0 | None | None | ... | 3231.98 | 26258.6 | 1420.5 | 26633.5 | -0.5 | -0.5 | None | None | None | None |
| 1325 | None | 1393 | 4014 | 4013 | 7 | 0 | 9 | 0 | None | None | ... | 3602.89 | 26266.7 | 1781.33 | 26634.1 | 1807.03 | 26638.5 | None | None | None | None |
| 1334 | None | 1398 | 4028 | 4027 | 7 | 0 | 9 | 0 | None | None | ... | 3667.78 | 27259.9 | 1867.94 | 27626.6 | 1892.47 | 27630.9 | None | None | None | None |
22 rows × 22 columns
[18]:
failed_homography = edge_9_7.matches.loc[(~edge_9_7.masks[['homography']]).all(axis=1)]
failed_homography
[18]:
| geom | point_id | source_measure_id | destin_measure_id | source | source_idx | destination | destination_idx | lat | lon | ... | source_apriori_x | source_apriori_y | destination_x | destination_y | destination_apriori_x | destination_apriori_y | shift_x | shift_y | original_destination_x | original_destination_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1177 | None | 787 | 2321 | 2322 | 7 | 0 | 9 | 0 | None | None | ... | 2193.72 | 15099.1 | 124.431 | 15480.9 | 148.703 | 15485.1 | None | None | None | None |
| 1180 | None | 790 | 2331 | 2332 | 7 | 0 | 9 | 0 | None | None | ... | 2058.33 | 21232.2 | 76.913 | 21614.3 | 101.831 | 21618.5 | None | None | None | None |
| 1181 | None | 791 | 2335 | 2336 | 7 | 0 | 9 | 0 | None | None | ... | 2055.99 | 22259.5 | 90.9603 | 22641.3 | 115.847 | 22645.5 | None | None | None | None |
| 1182 | None | 792 | 2338 | 2339 | 7 | 0 | 9 | 0 | None | None | ... | 2077.71 | 23322.8 | 233.419 | 23718.5 | 208.588 | 23721.5 | None | None | None | None |
| 1358 | None | 1416 | 4073 | 4072 | 7 | 0 | 9 | 0 | None | None | ... | 4388.95 | 37514.9 | 3240.27 | 37842 | 3225.51 | 37843.1 | None | None | None | None |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1288 | None | 1354 | 3901 | 3900 | 7 | 0 | 9 | 0 | None | None | ... | 4633.26 | 20103.5 | 2761.5 | 20468.5 | -0.5 | -0.5 | None | None | None | None |
| 1289 | None | 1356 | 3906 | 3905 | 7 | 0 | 9 | 0 | None | None | ... | 2449.74 | 21087.8 | 490.169 | 21466.9 | 514.354 | 21471 | None | None | None | None |
| 1290 | None | 1357 | 3909 | 3908 | 7 | 0 | 9 | 0 | None | None | ... | 2893.79 | 21162.4 | 958.412 | 21537.6 | 983.276 | 21541.8 | None | None | None | None |
| 1291 | None | 1358 | 3912 | 3911 | 7 | 0 | 9 | 0 | None | None | ... | 3285.83 | 21131.4 | 1388.39 | 21530.8 | 1364.02 | 21533.8 | None | None | None | None |
| 1292 | None | 1359 | 3915 | 3914 | 7 | 0 | 9 | 0 | None | None | ... | 3685.25 | 21185.8 | 1808.5 | 21558.5 | -0.5 | -0.5 | None | None | None | None |
75 rows × 22 columns
We can even use some techniques to see the matches that failed both checks
[19]:
failed_both = edge_9_7.matches.loc[(~edge_9_7.masks[['fundamental', 'homography']]).all(axis=1)]
failed_both
[19]:
| geom | point_id | source_measure_id | destin_measure_id | source | source_idx | destination | destination_idx | lat | lon | ... | source_apriori_x | source_apriori_y | destination_x | destination_y | destination_apriori_x | destination_apriori_y | shift_x | shift_y | original_destination_x | original_destination_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1178 | None | 788 | 2324 | 2325 | 7 | 0 | 9 | 0 | None | None | ... | 2231.12 | 16213.7 | 180.316 | 16595.5 | 204.205 | 16599.5 | None | None | None | None |
| 1228 | None | 1269 | 3678 | 3677 | 7 | 0 | 9 | 0 | None | None | ... | 3082.24 | 6807.21 | 1001.12 | 7160.22 | 978.351 | 7163.4 | None | None | None | None |
| 1230 | None | 1271 | 3684 | 3683 | 7 | 0 | 9 | 0 | None | None | ... | 3908.22 | 6813.23 | 1773.38 | 7180.67 | 1797.61 | 7184.37 | None | None | None | None |
| 1214 | None | 1243 | 3610 | 3609 | 7 | 0 | 9 | 0 | None | None | ... | 3983.41 | 1699.58 | 1877.61 | 2095.96 | 1840.91 | 2099.48 | None | None | None | None |
| 1222 | None | 1261 | 3653 | 3652 | 7 | 0 | 9 | 0 | None | None | ... | 2674.81 | 5831.07 | 493.5 | 6213.5 | -0.5 | -0.5 | None | None | None | None |
| 1223 | None | 1262 | 3655 | 3654 | 7 | 0 | 9 | 0 | None | None | ... | 3078.79 | 5826.54 | 893.047 | 6201.25 | 918.537 | 6205.46 | None | None | None | None |
| 1264 | None | 1328 | 3829 | 3828 | 7 | 0 | 9 | 0 | None | None | ... | 4237.17 | 16023.8 | 2321.67 | 16425.3 | 2309.89 | 16428.2 | None | None | None | None |
| 1267 | None | 1333 | 3841 | 3843 | 7 | 0 | 9 | 0 | None | None | ... | 3323.35 | 17014 | 1364.5 | 17390.5 | -0.5 | -0.5 | None | None | None | None |
8 rows × 22 columns
Viewing individual matches¶
Let’s take a closer look at the matches that failed both checks. We’re going to use the Roi (Region of Interest) objects in Autocnet that allow you to look at a small portion of an image.
[20]:
roi_size = 25
for idx, match in failed_both.iterrows():
with ncg.session_scope() as session:
point_name = session.query(Points).filter(Points.id == match["point_id"]).first().identifier
source_image = ncg.nodes[match['source']]['data']
dest_image = ncg.nodes[match['destination']]['data']
source_roi = Roi(source_image.geodata, match['source_x'], match['source_y'], size_x=roi_size, size_y=roi_size)
dest_roi = Roi(dest_image.geodata, match['destination_x'], match['destination_y'], size_x=roi_size, size_y=roi_size)
fig, (ax1, ax2) = plt.subplots(1,2)
fig.suptitle(point_name)
ax1.imshow(source_roi.array, cmap='gray')
ax1.plot(source_roi.center[0] + source_roi.axr, source_roi.center[1] + source_roi.ayr, 'ro')
ax1.title.set_text(os.path.split(source_image['image_name'])[-1])
ax2.imshow(dest_roi.array, cmap='gray')
ax2.plot(dest_roi.center[0] + dest_roi.axr, dest_roi.center[1] + dest_roi.ayr, 'bo')
ax2.title.set_text(os.path.split(dest_image['image_name'])[-1])
fig.show()
[ ]: