CSM Sandbox¶
Install Prerequisites¶
conda create -n csm-sandbox -c conda-forge knoten=0.4 notebook matplotlib ipywidgets 'ipympl>=0.9.6'
conda activate csm-sandbox
ARM Compatibility Issues
ARM machines may have dependency issues, for now we recommend running in an x86 environment.
On ARM Macs, you can tell conda to create an x86 environment by prefixing your command with CONDA_SUBDIR=osx-64
:
CONDA_SUBDIR=osx-64 conda create -n csm-sandbox -c conda-forge knoten=0.4 notebook matplotlib ipywidgets 'ipympl>=0.9.6'
Running the notebook on your computer¶
This notebook has an interactive widget.
To try it, download it on your computer and unzip it. In a terminal, cd
into its folder.
After installing the prerequisites as above, run jupyter notebook
in that folder to open the notebook in your browser.
Imports¶
Methods for reading/printing stats¶
def print_stats(dict, search_keys):
for search_key in search_keys:
print(f"{search_key+": ":<25}" + str(dict[search_key]))
def plot_footprint(lons, lats, aspect):
plt.close()
plt.rcParams['figure.figsize'] = aspect
plt.axes().set_aspect('equal','datalim')
plt.plot(lons, lats)
plt.xlabel('Longitude (deg)')
plt.ylabel('Latitude (deg)')
plt.title('CSM footprint')
plt.show()
def plot_footprint_comparison(fp1, fp2, aspect, dic_o, dic_n):
plt.clf() # clear previous figure
fp1_plot, = plt.plot(fp1[0], fp1[1], 'b') # Plot footprint 1 in blue
fp2_plot, = plt.plot(fp2[0], fp2[1], 'r') # Plot footprint 2 in red
plt.title('Original vs. Modified Footprint') # Title and axis labels
plt.xlabel('Longitude (deg)')
plt.ylabel('Latitude (deg)')
fp1_plot.set_label(fp1[2]) # Labels/Legend
fp2_plot.set_label(fp2[2])
plt.legend()
plt.axis('equal') # Set equal scale on both axes so the original shape won't be distorted
plt.grid(color='#DDDDDD', linewidth=0.5) # Grid makes scale/transformation more visible
# Display Values
o_foc_len = dic_o['focal_length_model']['focal_length']
o_det_cen = (dic_o['detector_center']['line'], dic_o['detector_center']['sample'])
o_disto = dic_o['optical_distortion']['radial']['coefficients']
o_eph_time = dic_o['center_ephemeris_time']
n_foc_len = dic_n['focal_length_model']['focal_length']
n_det_cen = (dic_n['detector_center']['line'], dic_n['detector_center']['sample'])
n_disto = dic_n['optical_distortion']['radial']['coefficients']
n_eph_time = dic_n['center_ephemeris_time']
text_labels = ('Focal Length: \n' +
'Detector Center (L, S): \n' +
'Radial Distortion: \n' +
'Ephemeris Center Time: ')
old_values = (f'{o_foc_len:.0f} \n' +
f'({o_det_cen[0]:.0f}, {o_det_cen[1]:.0f}) \n' +
f'({o_disto[0]:.2f}, {o_disto[1]:.4f}, {o_disto[2]:.7f}) \n' +
f'{o_eph_time:.1f}')
new_values = (f'→ {n_foc_len:.0f} \n' +
f'→ ({n_det_cen[0]:.0f}, {n_det_cen[1]:.0f}) \n' +
f'→ ({n_disto[0]:.2f}, {n_disto[1]:.4f}, {n_disto[2]:.7f}) \n' +
f'→ {n_eph_time:.1f}')
plt.subplots_adjust(top=0.75) # Set Plot to bottom 3/4
plt.gcf().text(0.02, 0.85, text_labels, fontsize=10, color='black') # Put text on top
plt.gcf().text(0.3, 0.85, old_values, fontsize=10, color='blue') # Put text on top
plt.gcf().text(0.6, 0.85, new_values, fontsize=10, color='red') # Put text on top
plt.show() # Show plot
Stats/Footprint of original ISD¶
# Load Dict from JSON-style ISD File
isd_file = 'csm-sandbox-isd-file.json'
isd_file_mod = 'csm-sandbox-isd-file-mod.json'
# The included ISD file in this example came from
# the P01_001404_1722_XI_07S090W.IMG MRO CTX image.
with open(isd_file) as json_file:
isd_dict = json.load(json_file)
# Print selected values from ISD
print_stats(isd_dict, ('focal_length_model', 'detector_center', 'optical_distortion', 'center_ephemeris_time'))
# Create Camera Model
camera = csm.create_csm(isd_file)
# Get the footprint using the model
boundary = csm.generate_boundary((isd_dict['image_lines'], isd_dict['image_samples']))
lons, lats, alts = csm.generate_latlon_boundary(camera, boundary)
# # This line can plot the footprint of the original ISD
# plot_footprint(lons, lats, [5,1])
Modify ISD/write to file¶
# clear the plot from any previous footprints/plots
plt.close()
# Copy the ISD Dictionary, we will modify it and compare to the original.
isd_dict_mod = copy.deepcopy(isd_dict)
print('Adjust Sliders to add or subtract from the values at the following ISD Keys.')
print('Note: \033[34mThe original blue footprint is staying in the same place.\033[0m')
print('\033[31mThe modifications to the ISD change the geometry of the new red footprint.\033[0m')
print()
# Slider Widgets
wide_lay = Layout(width='600px')
wide_desc = {'description_width': '150px'}
@widgets.interact(
fl_add=widgets.FloatSlider(min=-250, max=250, step=1, description='Focal Length', layout=wide_lay, style=wide_desc, readout_format='.0f'),
dcl_add=widgets.FloatSlider(min=-4000, max=4000, step=50, description='Detector Center Line', layout=wide_lay, style=wide_desc, readout_format='.0f'),
dcs_add=widgets.FloatSlider(min=-4000, max=4000, step=50, description='Detector Center Sample', layout=wide_lay, style=wide_desc, readout_format='.0f'),
opt_x=widgets.FloatSlider(min=-1, max=1, step=0.01, description='Optical Distortion X', layout=wide_lay, style=wide_desc, readout_format='.2f'),
opt_y=widgets.FloatSlider(min=-0.003, max=0.003, step=0.0001, description='Optical Distortion Y', layout=wide_lay, style=wide_desc, readout_format='.4f'),
opt_z=widgets.FloatSlider(min=-1e-5, max=1e-5, step=1e-7, description='Optical Distortion Z', layout=wide_lay, style=wide_desc, readout_format='.7f'),
ect_add=widgets.FloatSlider(min=-20, max=20, step=0.1, description='Exposure (Center) Time', layout=wide_lay, style=wide_desc, readout_format='.1f')
)
# This function executed whenever one of the slider widgets is adjusted
def exec_widget_function(fl_add, dcl_add, dcs_add, opt_x, opt_y, opt_z, ect_add):
# Note: print() statements within this function may cause flickering.
# Try writing something on the plot instead if you need output.
# If you're curious where the ISD values came from,
# Detector Center was from NAIF Boresight Line/Sample
# Optical Distortion was from NAIF OD_K
# ISIS uses the NAIF Keywords, but Knoten CSM uses other derived ISD values.
old_fl = isd_dict['focal_length_model']['focal_length']
old_dcl = isd_dict['detector_center']['line']
old_dcs = isd_dict['detector_center']['sample']
old_odx = isd_dict['optical_distortion']['radial']['coefficients'][0]
old_ody = isd_dict['optical_distortion']['radial']['coefficients'][1]
old_odz = isd_dict['optical_distortion']['radial']['coefficients'][2]
old_ect = isd_dict['center_ephemeris_time']
new_values = {
'focal_length_model': {'focal_length': old_fl + fl_add},
'detector_center': {'line': old_dcl + dcl_add, 'sample': old_dcs + dcs_add},
'optical_distortion': {'radial': {'coefficients': [old_odx + opt_x, old_ody + opt_y, old_odz + opt_z]}},
'center_ephemeris_time': old_ect + ect_add
}
# Modify Values in Dictionary
for key,value in new_values.items():
isd_dict_mod[key] = new_values[key]
# Write ISD to file
with open(isd_file_mod, 'w') as json_file:
json.dump(isd_dict_mod, json_file, indent=4)
# Create Camera Model
camera = csm.create_csm(isd_file_mod)
# Get the footprint using the model
boundary_mod = csm.generate_boundary((isd_dict_mod["image_lines"], isd_dict_mod["image_samples"]))
lons_mod, lats_mod, alts_mod = csm.generate_latlon_boundary(camera, boundary_mod)
# Plot it
plot_footprint_comparison((lons, lats, "Original"),(lons_mod, lats_mod, "Modified"), [7,3], isd_dict, isd_dict_mod)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
(This empty space below helps the above widget display with less jumping/flickering.)