diff --git a/environment.yml b/environment.yml index 62986d87445aceef07f429afa33e9e4ce4806d6c..63297d753a74a42d7edc4c6e3def2c6368b0528a 100644 --- a/environment.yml +++ b/environment.yml @@ -2,8 +2,8 @@ name: knoten channels: - conda-forge - usgs-astrogeology + - plotly dependencies: - - bokeh - coveralls - csmapi - gdal @@ -13,6 +13,9 @@ dependencies: - matplotlib - numpy - plio + - plotly + - plotly-orca + - psutil - pvl - pyproj - pysis diff --git a/knoten/vis.py b/knoten/vis.py index 772b896faa1f55a85b3bfe52f983d11bee769108..e7cefff01eb040374e7f40013bd96358db836bb6 100644 --- a/knoten/vis.py +++ b/knoten/vis.py @@ -15,10 +15,10 @@ import pandas as pd from pysis import isis from pysis.exceptions import ProcessError -import holoviews as hv -from holoviews import opts, dim -from bokeh.models import HoverTool - +import plotly.graph_objects as go +import plotly.express as px +from plotly.subplots import make_subplots +import plotly.figure_factory as ff def reproject(record, semi_major, semi_minor, source_proj, dest_proj, **kwargs): """ @@ -121,7 +121,7 @@ def point_info(cube_path, x, y, point_type, allow_outside=False): return pvlres = pvl.load(campt_output.name) - + if len(x) > 1 and len(y) > 1: for r in pvlres: # convert all pixels to PLIO pixels from ISIS @@ -133,10 +133,170 @@ def point_info(cube_path, x, y, point_type, allow_outside=False): return pvlres -def reprojection_diff(isd, cube, nx=4, ny=8): +def plot_diff(data, title='diff plot', colx='x', coly='y', coldx='diffx', coldy='diffy', colmag='magnitude', width=500, height=500): + import matplotlib.cm as cm + from matplotlib.colors import Normalize + + fig = make_subplots(rows=2, cols=2, column_widths=[0.9, .1], row_width=[.9, .1], + shared_xaxes=True, shared_yaxes=True, horizontal_spacing = 0.01, vertical_spacing = 0.01) + + quiver_plot = ff.create_quiver(data[colx], + data[coly], + data[coldx], + data[coldy], + scale=1, + line_color='#e3838e', + name='offset direction', + arrow_scale=0.1) + + for i in range(len(quiver_plot.data)): + quiver_plot.data[i].xaxis='x1' + quiver_plot.data[i].yaxis='y1' + + quiver_plot.layout.xaxis1.update({'anchor': 'y1'}) + quiver_plot.layout.yaxis1.update({'anchor': 'x1', 'domain': [.55, 1]}) + + fig.add_trace(quiver_plot.data[0], row=2, col=1) + + text = [f'{coldx}: {r[coldx]}<br>{coldy}: {r[coldy]}<br>{colmag}: {r[colmag]}' for i,r in data.iterrows()] + fig.add_trace(go.Scatter(x=data[colx], y=data[coly], + customdata=data, + mode='markers', + name=f'{colx},{coly}', + hovertext=text, + marker=dict( + color=data[colmag], + colorbar=dict( + thickness = 5, + outlinewidth = 0, + ypad=0, + title=f'{colmag}' + ), + colorscale="viridis", + )), row=2, col=1) + + xavg = data.groupby(colx).apply(np.mean) + fig.add_trace(go.Scatter(x=xavg.index, y=xavg[colmag], + customdata=xavg, + name=f'{colx} mean error', + mode='lines+markers', + line_shape='spline', + line_color='seagreen', + marker=dict( + color=xavg[colmag], + colorscale="viridis", + )), row=1, col=1) + + yavg = data.groupby(coly).apply(np.mean) + fig.add_trace(go.Scatter(x=yavg[colmag],y=yavg.index, + customdata=yavg, + name=f'{coly} mean error', + mode='lines+markers', + line_shape='spline', + line_color='purple', + marker=dict( + color=yavg[colmag], + colorscale="viridis", + )), row=2, col=2) + + fig.update_layout(width=width, height=height, showlegend=True,legend_orientation="h", title_text=title) + fig.update_yaxes(autorange="reversed", title_text=coly, row=2, col=1) + fig.update_xaxes(title_text=colx, row=2, col=1) + + fig.update_xaxes(title_text=f'mean error', row=2, col=2) + + return fig + + +def plot_diff_3d(data, title='3D diff plot', colx='x', coly='y', colz='z', coldx='diffx', coldy='diffy', coldz='diffz', colmag='magnitude', width=500, height=500): + + text = [f'{coldx}: {r[coldx]}<br>{coldy}: {r[coldy]}<br>{coldz}: {r[coldz]}<br>{colmag}: {r[colmag]}' for i,r in data.iterrows()] + + plot_data = { + "type": "scatter3d", + "mode": "markers", + "name": "original", + "text": text, + "x": data[colx], + "y": data[coly], + "z": data[colz], + "marker": { "colorscale": 'viridis', + "opacity": .8, + "size": 5, + "color": data[colmag], + "colorbar":{ + "thickness": 5, + "outlinewidth": 0, + "ypad": 0, + "title":f'{colmag}' + } + } + } + + + layout = { + "title": title, + "width": width, + "height": height, + "scene": { + "aspectratio": {"x": 1, "y": 1, "z": 0.8}, + } + } + + fig = go.Figure(data=[plot_data], layout=layout) + + return fig + +def plot_diff_3d_cone(data, title='3D diff plot', colx='x', coly='y', colz='z', + colu='u', colv='v',colw='w', + coldx='diffx', coldy='diffy', coldz='diffz', + coldv = 'diffu', coldu='diffv', coldw='diffw', + colxyz_mag='xyz_magnitude', coluvw_mag='uvw_magnitude', + width=500, height=500): + text = [f'{coldx}: {r[coldx]}<br>\ + {coldy}: {r[coldy]}<br>\ + {coldz}: {r[coldz]}<br>\ + {coldu}: {r[coldu]}<br>\ + {coldv}: {r[coldv]}<br>\ + {coldw}: {r[coldw]}<br>\ + {colxyz_mag}: {r[colxyz_mag]}<br>\ + {coluvw_mag}: {r[coluvw_mag]}' for i,r in data.iterrows()] + + plot_data = { + "type": "cone", + "text":text, + "x": data[colx], + "y": data[coly], + "z": data[colz], + "u": data[colu], + "v": data[colv], + "w": data[colw], + "sizeref": 5, + "colorscale": 'viridis', + "colorbar":{ + "thickness": 5, + "outlinewidth": 0, + "ypad": 0, + "title": f'mean({colxyz_mag},{coluvw_mag})' + } + } + + layout = { + "title": title, + "width": width, + "height": height, + "scene": { + "aspectratio": {"x": 1, "y": 1, "z": 0.8}, + } + } + + fig = go.Figure(data=[plot_data], layout=layout) + + return fig + +def reprojection_diff(isd, cube, nx=10, ny=50, width=500, height=500): """ """ - hv.extension('bokeh') isdjson = json.load(open(isd)) @@ -149,59 +309,121 @@ def reprojection_diff(isd, cube, nx=4, ny=8): csmcam = csm.create_csm(isd) - # CS101 C++ programming class style dividers - ############################## + # get data for isis image to ground, csm ground to image isis_pts = point_info(cube, xs, ys, 'image') - isisgnds = np.asarray([g[1]['BodyFixedCoordinate'].value for g in isis_pts]) - csm_pts = np.asarray([[p.samp, p.line] for p in [csmcam.groundToImage(csmapi.EcefCoord(*(np.asarray(bf)*1000))) for bf in isisgnds]]) - isis2csm_diff = csm_pts - np.asarray([xs,ys]).T + isisgnds = np.asarray([np.asarray(g[1]['BodyFixedCoordinate'].value)*1000 for g in isis_pts]) + csm_pts = np.asarray([[p.samp, p.line] for p in [csmcam.groundToImage(csmapi.EcefCoord(*bf)) for bf in isisgnds]]) + + isis2csm_diff = np.asarray([xs,ys]).T - csm_pts isis2csm_diffmag = np.linalg.norm(isis2csm_diff, axis=1) isis2csm_angles = np.arctan2(*isis2csm_diff.T[::-1]) - data = np.asarray([csm_pts.T[0], csm_pts.T[1], xs, ys, isis2csm_diff.T[0], isis2csm_diff.T[1], isis2csm_diffmag, isis2csm_angles]).T - data = pd.DataFrame(data, columns=['x', 'y', 'isisx','isisy', 'diffx', 'diffy', 'magnitude', 'angles']) + isis2csm_data = np.asarray([csm_pts.T[0], csm_pts.T[1], xs, ys, isis2csm_diff.T[0], isis2csm_diff.T[1], isis2csm_diffmag, isis2csm_angles]).T + isis2csm_data = pd.DataFrame(isis2csm_data, columns=['csm sample', 'csm line', 'isis sample','isis line', 'diff sample', 'diff line', 'magnitude', 'angles']) - isis2ground2csm_plot = hv.VectorField((data['x'], data['y'], data['angles'], data['magnitude']), group='isis2ground2csmimage' ).opts(opts.VectorField(pivot='tail', colorbar=True, cmap='coolwarm', title='ISIS2Ground->CSM2Image Pixel Diff', arrow_heads=True, magnitude='Magnitude', color=dim('Magnitude'))) - isis2ground2csm_plot = isis2ground2csm_plot.redim(x='sample', y='line') - isis2ground2csm_plot = isis2ground2csm_plot.opts(plot=dict(width=500, height=1000)) - isis2ground2csm_plot = isis2ground2csm_plot*hv.Points(data, group='isis2ground2csmimage').opts(size=5, tools=['hover'], invert_yaxis=True) - isis2ground2csm_plot = isis2ground2csm_plot.redim.range(a=(data['magnitude'].min(), data['magnitude'].max())) - ############################## + isis2csm_plot = plot_diff(isis2csm_data, colx='isis sample', coly='isis line', + coldx='diff sample', coldy='diff line', + title="ISIS2Ground->CSM2Image", width=width, height=height) - ############################## + + # get data for csm image to ground, isis ground to image csmgnds = np.asarray([[p.x, p.y, p.z] for p in [csmcam.imageToGround(csmapi.ImageCoord(y,x), 0) for x,y in zip(xs,ys)]]) - csmlon, csmlat, _ = reproject(csmgnds.T, isdjson['radii']['semimajor'], isdjson['radii']['semiminor'], 'geocent', 'latlong') - isis_imgpts = point_info(cube, csmlon, csmlat, 'ground') + csmlon, csmlat, _ = reproject(csmgnds.T, isdjson['radii']['semimajor'], isdjson['radii']['semimajor'], 'geocent', 'latlong') + + isis_imgpts = point_info(cube, (csmlon+360)%360, csmlat, 'ground') isis_imgpts = np.asarray([(p[1]['Sample'], p[1]['Line']) for p in isis_imgpts]) - csm2isis_diff = isis_imgpts - np.asarray([xs,ys]).T + csm2isis_diff = np.asarray([xs,ys]).T - isis_imgpts csm2isis_diffmag = np.linalg.norm(csm2isis_diff, axis=1) - csm2isis_angles = np.arctan2(*csm2isis_diff.T[::-1]) - + csm2isis_angles = np.arctan2(*(csm2isis_diff/csm2isis_diffmag[:,np.newaxis]).T[::-1]) csm2isis_data = np.asarray([xs, ys, isis_imgpts.T[0], isis_imgpts.T[1], csm2isis_diff.T[0], csm2isis_diff.T[1], csm2isis_diffmag, csm2isis_angles]).T - csm2isis_data = pd.DataFrame(csm2isis_data, columns=['x', 'y', 'csmx','csmy', 'diffx', 'diffy', 'magnitude', 'angles']) + csm2isis_data = pd.DataFrame(csm2isis_data, columns=['csm sample', 'csm line', 'isis sample','isis line', 'diff sample', 'diff line', 'magnitude', 'angles']) - csm2ground2isis_plot = hv.VectorField((csm2isis_data['x'], csm2isis_data['y'], csm2isis_data['angles'], csm2isis_data['magnitude']), group='csmground2image2isis').opts(opts.VectorField(pivot='tail', colorbar=True, cmap='coolwarm', title='CSM2Ground->ISIS2Image Pixel Diff', arrow_heads=True, magnitude='Magnitude', color=dim('Magnitude'))) - csm2ground2isis_plot = csm2ground2isis_plot.redim(x='sample', y='line') - csm2ground2isis_plot = csm2ground2isis_plot.opts(plot=dict(width=500, height=1000)) - csm2ground2isis_plot = csm2ground2isis_plot*hv.Points(csm2isis_data, group='csmground2image2isis').opts(size=5, tools=['hover'], invert_yaxis=True) - ############################### + csm2isis_plot = plot_diff(csm2isis_data, colx='csm sample', coly='csm line', + coldx='diff sample', coldy='diff line', + title="CSM2Ground->ISIS2Image", width=width, height=height) - ############################### + # get data for footprint comparison isis_lonlat = np.asarray([[p[1]['PositiveEast360Longitude'].value, p[1]['PlanetocentricLatitude'].value] for p in isis_pts]) - csm_lonlat = np.asarray([csmlon+360, csmlat]).T + csm_lonlat = np.asarray([(csmlon+360)%360, csmlat]).T - isiscsm_difflatlon = isis_lonlat - csm_lonlat + isiscsm_difflatlon = csm_lonlat - isis_lonlat isiscsm_difflatlonmag = np.linalg.norm(isiscsm_difflatlon, axis=1) isiscsm_angleslatlon = np.arctan2(*isiscsm_difflatlon.T[::-1]) - isiscsm_latlondata = np.asarray([isis_lonlat.T[0], isis_lonlat.T[1], csm_lonlat.T[0], csm_lonlat.T[1], isiscsm_difflatlon.T[0], isiscsm_difflatlon.T[1], isiscsm_difflatlonmag, isiscsm_angleslatlon]).T - isiscsm_latlondata = pd.DataFrame(isiscsm_latlondata, columns=['isislon', 'isislat', 'csmlon','csmlat', 'difflon', 'difflat', 'magnitude', 'angles']) + isiscsm_latlondata = pd.DataFrame(isiscsm_latlondata, columns=['isis lon', 'isis lat', 'csm lon','csm lat', 'diff lon', 'diff lat', 'magnitude', 'angles']) + + isiscsm_latlonplot = plot_diff(isiscsm_latlondata, colx='isis lon', coly='isis lat', + coldx='diff lon', coldy='diff lat', + title="ISIS Lat/Lon vs CSM Lat/Lon", width=width, height=height) + - isiscsm_plotlatlon = hv.VectorField((isiscsm_latlondata['isislon'], isiscsm_latlondata['isislat'], isiscsm_latlondata['angles'], isiscsm_latlondata['magnitude']), group='isisvscsmlatlon').opts(opts.VectorField(pivot='tail', colorbar=True, cmap='coolwarm', title='Image2Ground latlon Diff', arrow_heads=True, magnitude='Magnitude', color=dim('Magnitude'))) - isiscsm_plotlatlon = isiscsm_plotlatlon.redim(x='longitude', y='latitude') - isiscsm_plotlatlon = isiscsm_plotlatlon.opts(plot=dict(width=500, height=1000)) - isiscsm_plotlatlon = isiscsm_plotlatlon*hv.Points(isiscsm_latlondata, ['isislon', 'isislat'], group='isisvscsmlatlon').opts(size=5, tools=['hover'], invert_yaxis=True) - ############################### + isiscsm_diffbf = isisgnds - csmgnds + isiscsm_diffbfmag = np.linalg.norm(isiscsm_diffbf, axis=1) + isiscsm_anglesbf = np.arctan2(*isiscsm_diffbf.T[::-1]) + + isiscsm_bfdata = np.asarray([isisgnds.T[0], isisgnds.T[1], isisgnds.T[2], csmgnds.T[0], csmgnds.T[1], csmgnds.T[2], isiscsm_diffbf.T[0], isiscsm_diffbf.T[1], isiscsm_diffbf.T[2], isiscsm_diffbfmag, isiscsm_anglesbf]).T + isiscsm_bfdata = pd.DataFrame(isiscsm_bfdata, columns=['isisx', 'isisy', 'isisz', 'csmx','csmy', 'csmz', 'diffx', 'diffy', 'diffz', 'magnitude', 'angles']) + isiscsm_bfplot = plot_diff_3d(isiscsm_bfdata, colx='isisx', coly='isisy', colz='isisz', + title='ISIS Body-Fixed vs CSM Body-Fixed', width=width, height=height) + + return isis2csm_plot, csm2isis_plot, isiscsm_latlonplot, isiscsm_bfplot, isis2csm_data, csm2isis_data, isiscsm_latlondata, isiscsm_bfdata + + +def external_orientation_diff(isd, cube, nx=4, ny=4, width=500, height=500): + csmcam = csm.create_csm(isd) + isdjson = json.load(open(isd)) + nlines, nsamples = isdjson['image_lines'], isdjson['image_samples'] + + xs, ys = np.mgrid[0:nsamples:nsamples/nx, 0:nlines:nlines/ny] + xs, ys = xs.flatten(), ys.flatten() - return isis2ground2csm_plot, csm2ground2isis_plot, isiscsm_plotlatlon, data, csm2isis_data, isiscsm_latlondata + isis_pts = point_info(cube, xs, ys, "image") + isis_lv_bf = np.asarray([p[1]['LookDirectionBodyFixed'] for p in isis_pts]) + isis_pos_bf = np.asarray([p[1]['SpacecraftPosition'].value for p in isis_pts])*1000 + isis_ephem_times = np.asarray([p[1]['EphemerisTime'].value for p in isis_pts]) + + csm_locus = [csmcam.imageToRemoteImagingLocus(csmapi.ImageCoord(y, x)) for x,y in zip(xs,ys)] + csm_lv_bf = np.asarray([[lv.direction.x, lv.direction.y, lv.direction.z] for lv in csm_locus]) + csm_pos_bf = np.asarray([[lv.point.x, lv.point.y, lv.point.z] for lv in csm_locus]) + csm_ephem_times = np.asarray([csmcam.getImageTime(csmapi.ImageCoord(y, x)) for x,y in zip(xs,ys)]) + csm_ephem_times += isdjson['center_ephemeris_time'] + + csmisis_diff_pos = csm_pos_bf - isis_pos_bf + csmisis_diff_lv = csm_lv_bf - isis_lv_bf + csmisis_diff_ephem = csm_ephem_times - isis_ephem_times + csmisis_diff_pos_mag = np.linalg.norm(csmisis_diff_pos, axis=1) + csmisis_diff_lv_mag = np.linalg.norm(csmisis_diff_lv, axis=1) + + csmisis_diff_pos_data = np.asarray([csm_pos_bf.T[0], csm_pos_bf.T[1], csm_pos_bf.T[2], + isis_pos_bf.T[0], isis_pos_bf.T[1], isis_pos_bf.T[2], + csmisis_diff_pos.T[0], csmisis_diff_pos.T[1], csmisis_diff_pos.T[2], + csmisis_diff_pos_mag]) + csmisis_diff_pos_data = pd.DataFrame(csmisis_diff_pos_data.T, columns=['csm pos x', 'csm pos y', 'csm pos z', + 'isis pos x', 'isis pos y', 'isis pos z', + 'diffx', 'diffy', 'diffz', + 'magnitude']) + + csmisis_diff_pos_plot = plot_diff_3d(csmisis_diff_pos_data, colx='isis pos x', coly='isis pos y', colz='isis pos z', + title='ISIS CSM Position Difference') + + csmisis_diff_lv_data = np.asarray([isis_pos_bf.T[0], isis_pos_bf.T[1], isis_pos_bf.T[2], + csm_lv_bf.T[0], csm_lv_bf.T[1], csm_lv_bf.T[2], + isis_lv_bf.T[0], isis_lv_bf.T[1], isis_lv_bf.T[2], + csmisis_diff_pos.T[0], csmisis_diff_pos.T[1], csmisis_diff_pos.T[2], + csmisis_diff_lv.T[0], csmisis_diff_lv.T[1], csmisis_diff_lv.T[2], + csmisis_diff_pos_mag, csmisis_diff_lv_mag, isis_ephem_times, csm_ephem_times, csmisis_diff_ephem]) + csmisis_diff_lv_data = pd.DataFrame(csmisis_diff_lv_data.T, columns=['isis pos x', 'isis pos y', 'isis pos z', + 'csm lv x', 'csm lv y', 'csm lv z', + 'isis lv x', 'isis lv y', 'isis lv z', + 'diffx', 'diffy', 'diffz', + 'diffu', 'diffv', 'diffw', + 'xyz_magnitude', 'uvw_magnitude', 'isis ephem time', 'csm ephem time', 'diff ephem']) + + csmisis_diff_lv_plot = plot_diff_3d_cone(csmisis_diff_lv_data, colx='isis pos x', coly='isis pos y', colz='isis pos z', + colu='isis lv x', colv='isis lv y', colw='isis lv z', + title='ISIS CSM Position and Look Vector Difference', width=width, height=height) + + csmisis_diff_ephem_plot = go.Figure(go.Scatter(x=np.linspace(0, nlines, ny), y=csmisis_diff_ephem, line_shape='spline')).update_layout(title='ISIS CSM Ephem Time Difference', width=width, height=height/2).update_xaxes(title_text='Line').update_yaxes(title_text='Time Delta Seconds') + + return csmisis_diff_lv_plot, csmisis_diff_ephem_plot, csmisis_diff_lv_data